diff --git a/README.md b/README.md index f247e784..229432f7 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ https://godoc.org/github.com/aler9/gortsplib ## Links -IETF Standard -* (1.0) https://tools.ietf.org/html/rfc2326 -* (2.0) https://tools.ietf.org/html/rfc7826 +IETF Standards +* RTSP 1.0 https://tools.ietf.org/html/rfc2326 +* RTSP 2.0 https://tools.ietf.org/html/rfc7826 +* HTTP 1.1 https://tools.ietf.org/html/rfc2616 diff --git a/conn-client.go b/conn-client.go index 4c7ff157..bd907a3e 100644 --- a/conn-client.go +++ b/conn-client.go @@ -137,7 +137,7 @@ func (c *ConnClient) WriteRequest(req *Request) (*Response, error) { // ReadInterleavedFrame reads an InterleavedFrame. func (c *ConnClient) ReadInterleavedFrame() (*InterleavedFrame, error) { c.conf.NConn.SetReadDeadline(time.Now().Add(c.conf.ReadTimeout)) - return readInterleavedFrame(c.br) + return interleavedFrameRead(c.br) } // WriteInterleavedFrame writes an InterleavedFrame. diff --git a/conn-server.go b/conn-server.go index b4a6fb9d..7a095561 100644 --- a/conn-server.go +++ b/conn-server.go @@ -77,7 +77,7 @@ func (s *ConnServer) WriteResponse(res *Response) error { // ReadInterleavedFrame reads an InterleavedFrame. func (s *ConnServer) ReadInterleavedFrame() (*InterleavedFrame, error) { s.conf.NConn.SetReadDeadline(time.Now().Add(s.conf.ReadTimeout)) - return readInterleavedFrame(s.br) + return interleavedFrameRead(s.br) } // WriteInterleavedFrame writes an InterleavedFrame. diff --git a/header.go b/header.go index f4474e72..bd9bc04f 100644 --- a/header.go +++ b/header.go @@ -14,7 +14,7 @@ const ( _MAX_HEADER_VALUE_LENGTH = 1024 ) -func normalizeHeaderKey(in string) string { +func headerKeyNormalize(in string) string { switch strings.ToLower(in) { case "rtp-info": return "RTP-INFO" @@ -31,7 +31,7 @@ func normalizeHeaderKey(in string) string { // Header is a RTSP reader, present in both Requests and Responses. type Header map[string][]string -func readHeader(rb *bufio.Reader) (Header, error) { +func headerRead(rb *bufio.Reader) (Header, error) { h := make(Header) for { @@ -59,12 +59,21 @@ func readHeader(rb *bufio.Reader) (Header, error) { return nil, err } key += string(byts[:len(byts)-1]) - key = normalizeHeaderKey(key) + key = headerKeyNormalize(key) - err = readByteEqual(rb, ' ') - if err != nil { - return nil, err + // https://tools.ietf.org/html/rfc2616 + // The field value MAY be preceded by any amount of spaces + for { + byt, err := rb.ReadByte() + if err != nil { + return nil, err + } + + if byt != ' ' { + break + } } + rb.UnreadByte() byts, err = readBytesLimited(rb, '\r', _MAX_HEADER_VALUE_LENGTH) if err != nil { diff --git a/header_test.go b/header_test.go index 9a2637c8..77a6afd2 100644 --- a/header_test.go +++ b/header_test.go @@ -10,11 +10,15 @@ import ( var casesHeader = []struct { name string - byts []byte + dec []byte + enc []byte header Header }{ { "single", + []byte("Proxy-Require: gzipped-messages\r\n" + + "Require: implicit-play\r\n" + + "\r\n"), []byte("Proxy-Require: gzipped-messages\r\n" + "Require: implicit-play\r\n" + "\r\n"), @@ -25,6 +29,9 @@ var casesHeader = []struct { }, { "multiple", + []byte("WWW-Authenticate: Digest realm=\"4419b63f5e51\", nonce=\"8b84a3b789283a8bea8da7fa7d41f08b\", stale=\"FALSE\"\r\n" + + "WWW-Authenticate: Basic realm=\"4419b63f5e51\"\r\n" + + "\r\n"), []byte("WWW-Authenticate: Digest realm=\"4419b63f5e51\", nonce=\"8b84a3b789283a8bea8da7fa7d41f08b\", stale=\"FALSE\"\r\n" + "WWW-Authenticate: Basic realm=\"4419b63f5e51\"\r\n" + "\r\n"), @@ -35,12 +42,58 @@ var casesHeader = []struct { }, }, }, + { + "without space", + []byte("CSeq:2\r\n" + + "\r\n"), + []byte("CSeq: 2\r\n" + + "\r\n"), + Header{ + "CSeq": []string{"2"}, + }, + }, + { + "with multiple spaces", + []byte("CSeq: 2\r\n" + + "\r\n"), + []byte("CSeq: 2\r\n" + + "\r\n"), + Header{ + "CSeq": []string{"2"}, + }, + }, + { + "normalized keys, standard", + []byte("Content-type: testing\r\n" + + "Content-length: value\r\n" + + "\r\n"), + []byte("Content-Length: value\r\n" + + "Content-Type: testing\r\n" + + "\r\n"), + Header{ + "Content-Length": []string{"value"}, + "Content-Type": []string{"testing"}, + }, + }, + { + "normalized keys, non-standard", + []byte("Www-Authenticate: value\r\n" + + "Cseq: value\r\n" + + "\r\n"), + []byte("CSeq: value\r\n" + + "WWW-Authenticate: value\r\n" + + "\r\n"), + Header{ + "CSeq": []string{"value"}, + "WWW-Authenticate": []string{"value"}, + }, + }, } func TestHeaderRead(t *testing.T) { for _, c := range casesHeader { t.Run(c.name, func(t *testing.T) { - req, err := readHeader(bufio.NewReader(bytes.NewBuffer(c.byts))) + req, err := headerRead(bufio.NewReader(bytes.NewBuffer(c.dec))) require.NoError(t, err) require.Equal(t, c.header, req) }) @@ -55,44 +108,7 @@ func TestHeaderWrite(t *testing.T) { err := c.header.write(bw) require.NoError(t, err) bw.Flush() - require.Equal(t, c.byts, buf.Bytes()) - }) - } -} - -var casesHeaderNormalization = []struct { - name string - byts []byte - header Header -}{ - { - "standard", - []byte("Content-type: testing\r\n" + - "Content-length: value\r\n" + - "\r\n"), - Header{ - "Content-Type": []string{"testing"}, - "Content-Length": []string{"value"}, - }, - }, - { - "non-standard", - []byte("Www-Authenticate: value\r\n" + - "Cseq: value\r\n" + - "\r\n"), - Header{ - "WWW-Authenticate": []string{"value"}, - "CSeq": []string{"value"}, - }, - }, -} - -func TestHeaderNormalization(t *testing.T) { - for _, c := range casesHeaderNormalization { - t.Run(c.name, func(t *testing.T) { - req, err := readHeader(bufio.NewReader(bytes.NewBuffer(c.byts))) - require.NoError(t, err) - require.Equal(t, c.header, req) + require.Equal(t, c.enc, buf.Bytes()) }) } } diff --git a/interleaved-frame.go b/interleaved-frame.go index 1da0bf97..cfcbb109 100644 --- a/interleaved-frame.go +++ b/interleaved-frame.go @@ -20,7 +20,7 @@ type InterleavedFrame struct { Content []byte } -func readInterleavedFrame(r io.Reader) (*InterleavedFrame, error) { +func interleavedFrameRead(r io.Reader) (*InterleavedFrame, error) { var header [4]byte _, err := io.ReadFull(r, header[:]) if err != nil { diff --git a/request.go b/request.go index 192d6af6..93b6892e 100644 --- a/request.go +++ b/request.go @@ -45,10 +45,10 @@ type Request struct { Content []byte } -func readRequest(br *bufio.Reader) (*Request, error) { +func readRequest(rb *bufio.Reader) (*Request, error) { req := &Request{} - byts, err := readBytesLimited(br, ' ', _MAX_METHOD_LENGTH) + byts, err := readBytesLimited(rb, ' ', _MAX_METHOD_LENGTH) if err != nil { return nil, err } @@ -58,7 +58,7 @@ func readRequest(br *bufio.Reader) (*Request, error) { return nil, fmt.Errorf("empty method") } - byts, err = readBytesLimited(br, ' ', _MAX_PATH_LENGTH) + byts, err = readBytesLimited(rb, ' ', _MAX_PATH_LENGTH) if err != nil { return nil, err } @@ -78,7 +78,7 @@ func readRequest(br *bufio.Reader) (*Request, error) { return nil, fmt.Errorf("invalid url scheme '%s'", req.Url.Scheme) } - byts, err = readBytesLimited(br, '\r', _MAX_PROTOCOL_LENGTH) + byts, err = readBytesLimited(rb, '\r', _MAX_PROTOCOL_LENGTH) if err != nil { return nil, err } @@ -88,17 +88,17 @@ func readRequest(br *bufio.Reader) (*Request, error) { return nil, fmt.Errorf("expected '%s', got '%s'", _RTSP_PROTO, proto) } - err = readByteEqual(br, '\n') + err = readByteEqual(rb, '\n') if err != nil { return nil, err } - req.Header, err = readHeader(br) + req.Header, err = headerRead(rb) if err != nil { return nil, err } - req.Content, err = readContent(br, req.Header) + req.Content, err = readContent(rb, req.Header) if err != nil { return nil, err } diff --git a/response.go b/response.go index 14eaf0bb..ac06cce7 100644 --- a/response.go +++ b/response.go @@ -131,10 +131,10 @@ type Response struct { Content []byte } -func readResponse(br *bufio.Reader) (*Response, error) { +func readResponse(rb *bufio.Reader) (*Response, error) { res := &Response{} - byts, err := readBytesLimited(br, ' ', 255) + byts, err := readBytesLimited(rb, ' ', 255) if err != nil { return nil, err } @@ -144,7 +144,7 @@ func readResponse(br *bufio.Reader) (*Response, error) { return nil, fmt.Errorf("expected '%s', got '%s'", _RTSP_PROTO, proto) } - byts, err = readBytesLimited(br, ' ', 4) + byts, err = readBytesLimited(rb, ' ', 4) if err != nil { return nil, err } @@ -156,7 +156,7 @@ func readResponse(br *bufio.Reader) (*Response, error) { } res.StatusCode = StatusCode(statusCode64) - byts, err = readBytesLimited(br, '\r', 255) + byts, err = readBytesLimited(rb, '\r', 255) if err != nil { return nil, err } @@ -166,17 +166,17 @@ func readResponse(br *bufio.Reader) (*Response, error) { return nil, fmt.Errorf("empty status") } - err = readByteEqual(br, '\n') + err = readByteEqual(rb, '\n') if err != nil { return nil, err } - res.Header, err = readHeader(br) + res.Header, err = headerRead(rb) if err != nil { return nil, err } - res.Content, err = readContent(br, res.Header) + res.Content, err = readContent(rb, res.Header) if err != nil { return nil, err } diff --git a/utils.go b/utils.go index fb196482..a862a795 100644 --- a/utils.go +++ b/utils.go @@ -12,23 +12,23 @@ const ( _MAX_CONTENT_LENGTH = 4096 ) -func readBytesLimited(br *bufio.Reader, delim byte, n int) ([]byte, error) { +func readBytesLimited(rb *bufio.Reader, delim byte, n int) ([]byte, error) { for i := 1; i <= n; i++ { - byts, err := br.Peek(i) + byts, err := rb.Peek(i) if err != nil { return nil, err } if byts[len(byts)-1] == delim { - br.Discard(len(byts)) + rb.Discard(len(byts)) return byts, nil } } return nil, fmt.Errorf("buffer length exceeds %d", n) } -func readByteEqual(br *bufio.Reader, cmp byte) error { - byt, err := br.ReadByte() +func readByteEqual(rb *bufio.Reader, cmp byte) error { + byt, err := rb.ReadByte() if err != nil { return err } @@ -40,7 +40,7 @@ func readByteEqual(br *bufio.Reader, cmp byte) error { return nil } -func readContent(br *bufio.Reader, header Header) ([]byte, error) { +func readContent(rb *bufio.Reader, header Header) ([]byte, error) { cls, ok := header["Content-Length"] if !ok || len(cls) != 1 { return nil, nil @@ -56,7 +56,7 @@ func readContent(br *bufio.Reader, header Header) ([]byte, error) { } ret := make([]byte, cl) - n, err := io.ReadFull(br, ret) + n, err := io.ReadFull(rb, ret) if err != nil && n != len(ret) { return nil, err }