allow headers with zero or multiple spaces between key and value

This commit is contained in:
aler9
2020-06-14 17:29:40 +02:00
parent 37bd9a1b98
commit c06d302979
9 changed files with 99 additions and 73 deletions

View File

@@ -15,6 +15,7 @@ https://godoc.org/github.com/aler9/gortsplib
## Links ## Links
IETF Standard IETF Standards
* (1.0) https://tools.ietf.org/html/rfc2326 * RTSP 1.0 https://tools.ietf.org/html/rfc2326
* (2.0) https://tools.ietf.org/html/rfc7826 * RTSP 2.0 https://tools.ietf.org/html/rfc7826
* HTTP 1.1 https://tools.ietf.org/html/rfc2616

View File

@@ -137,7 +137,7 @@ func (c *ConnClient) WriteRequest(req *Request) (*Response, error) {
// ReadInterleavedFrame reads an InterleavedFrame. // ReadInterleavedFrame reads an InterleavedFrame.
func (c *ConnClient) ReadInterleavedFrame() (*InterleavedFrame, error) { func (c *ConnClient) ReadInterleavedFrame() (*InterleavedFrame, error) {
c.conf.NConn.SetReadDeadline(time.Now().Add(c.conf.ReadTimeout)) c.conf.NConn.SetReadDeadline(time.Now().Add(c.conf.ReadTimeout))
return readInterleavedFrame(c.br) return interleavedFrameRead(c.br)
} }
// WriteInterleavedFrame writes an InterleavedFrame. // WriteInterleavedFrame writes an InterleavedFrame.

View File

@@ -77,7 +77,7 @@ func (s *ConnServer) WriteResponse(res *Response) error {
// ReadInterleavedFrame reads an InterleavedFrame. // ReadInterleavedFrame reads an InterleavedFrame.
func (s *ConnServer) ReadInterleavedFrame() (*InterleavedFrame, error) { func (s *ConnServer) ReadInterleavedFrame() (*InterleavedFrame, error) {
s.conf.NConn.SetReadDeadline(time.Now().Add(s.conf.ReadTimeout)) s.conf.NConn.SetReadDeadline(time.Now().Add(s.conf.ReadTimeout))
return readInterleavedFrame(s.br) return interleavedFrameRead(s.br)
} }
// WriteInterleavedFrame writes an InterleavedFrame. // WriteInterleavedFrame writes an InterleavedFrame.

View File

@@ -14,7 +14,7 @@ const (
_MAX_HEADER_VALUE_LENGTH = 1024 _MAX_HEADER_VALUE_LENGTH = 1024
) )
func normalizeHeaderKey(in string) string { func headerKeyNormalize(in string) string {
switch strings.ToLower(in) { switch strings.ToLower(in) {
case "rtp-info": case "rtp-info":
return "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. // Header is a RTSP reader, present in both Requests and Responses.
type Header map[string][]string type Header map[string][]string
func readHeader(rb *bufio.Reader) (Header, error) { func headerRead(rb *bufio.Reader) (Header, error) {
h := make(Header) h := make(Header)
for { for {
@@ -59,12 +59,21 @@ func readHeader(rb *bufio.Reader) (Header, error) {
return nil, err return nil, err
} }
key += string(byts[:len(byts)-1]) key += string(byts[:len(byts)-1])
key = normalizeHeaderKey(key) key = headerKeyNormalize(key)
err = readByteEqual(rb, ' ') // https://tools.ietf.org/html/rfc2616
if err != nil { // The field value MAY be preceded by any amount of spaces
return nil, err 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) byts, err = readBytesLimited(rb, '\r', _MAX_HEADER_VALUE_LENGTH)
if err != nil { if err != nil {

View File

@@ -10,11 +10,15 @@ import (
var casesHeader = []struct { var casesHeader = []struct {
name string name string
byts []byte dec []byte
enc []byte
header Header header Header
}{ }{
{ {
"single", "single",
[]byte("Proxy-Require: gzipped-messages\r\n" +
"Require: implicit-play\r\n" +
"\r\n"),
[]byte("Proxy-Require: gzipped-messages\r\n" + []byte("Proxy-Require: gzipped-messages\r\n" +
"Require: implicit-play\r\n" + "Require: implicit-play\r\n" +
"\r\n"), "\r\n"),
@@ -25,6 +29,9 @@ var casesHeader = []struct {
}, },
{ {
"multiple", "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" + []byte("WWW-Authenticate: Digest realm=\"4419b63f5e51\", nonce=\"8b84a3b789283a8bea8da7fa7d41f08b\", stale=\"FALSE\"\r\n" +
"WWW-Authenticate: Basic realm=\"4419b63f5e51\"\r\n" + "WWW-Authenticate: Basic realm=\"4419b63f5e51\"\r\n" +
"\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) { func TestHeaderRead(t *testing.T) {
for _, c := range casesHeader { for _, c := range casesHeader {
t.Run(c.name, func(t *testing.T) { 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.NoError(t, err)
require.Equal(t, c.header, req) require.Equal(t, c.header, req)
}) })
@@ -55,44 +108,7 @@ func TestHeaderWrite(t *testing.T) {
err := c.header.write(bw) err := c.header.write(bw)
require.NoError(t, err) require.NoError(t, err)
bw.Flush() bw.Flush()
require.Equal(t, c.byts, buf.Bytes()) require.Equal(t, c.enc, 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)
}) })
} }
} }

View File

@@ -20,7 +20,7 @@ type InterleavedFrame struct {
Content []byte Content []byte
} }
func readInterleavedFrame(r io.Reader) (*InterleavedFrame, error) { func interleavedFrameRead(r io.Reader) (*InterleavedFrame, error) {
var header [4]byte var header [4]byte
_, err := io.ReadFull(r, header[:]) _, err := io.ReadFull(r, header[:])
if err != nil { if err != nil {

View File

@@ -45,10 +45,10 @@ type Request struct {
Content []byte Content []byte
} }
func readRequest(br *bufio.Reader) (*Request, error) { func readRequest(rb *bufio.Reader) (*Request, error) {
req := &Request{} req := &Request{}
byts, err := readBytesLimited(br, ' ', _MAX_METHOD_LENGTH) byts, err := readBytesLimited(rb, ' ', _MAX_METHOD_LENGTH)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -58,7 +58,7 @@ func readRequest(br *bufio.Reader) (*Request, error) {
return nil, fmt.Errorf("empty method") return nil, fmt.Errorf("empty method")
} }
byts, err = readBytesLimited(br, ' ', _MAX_PATH_LENGTH) byts, err = readBytesLimited(rb, ' ', _MAX_PATH_LENGTH)
if err != nil { if err != nil {
return nil, err 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) 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 { if err != nil {
return nil, err 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) return nil, fmt.Errorf("expected '%s', got '%s'", _RTSP_PROTO, proto)
} }
err = readByteEqual(br, '\n') err = readByteEqual(rb, '\n')
if err != nil { if err != nil {
return nil, err return nil, err
} }
req.Header, err = readHeader(br) req.Header, err = headerRead(rb)
if err != nil { if err != nil {
return nil, err return nil, err
} }
req.Content, err = readContent(br, req.Header) req.Content, err = readContent(rb, req.Header)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -131,10 +131,10 @@ type Response struct {
Content []byte Content []byte
} }
func readResponse(br *bufio.Reader) (*Response, error) { func readResponse(rb *bufio.Reader) (*Response, error) {
res := &Response{} res := &Response{}
byts, err := readBytesLimited(br, ' ', 255) byts, err := readBytesLimited(rb, ' ', 255)
if err != nil { if err != nil {
return nil, err 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) return nil, fmt.Errorf("expected '%s', got '%s'", _RTSP_PROTO, proto)
} }
byts, err = readBytesLimited(br, ' ', 4) byts, err = readBytesLimited(rb, ' ', 4)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -156,7 +156,7 @@ func readResponse(br *bufio.Reader) (*Response, error) {
} }
res.StatusCode = StatusCode(statusCode64) res.StatusCode = StatusCode(statusCode64)
byts, err = readBytesLimited(br, '\r', 255) byts, err = readBytesLimited(rb, '\r', 255)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -166,17 +166,17 @@ func readResponse(br *bufio.Reader) (*Response, error) {
return nil, fmt.Errorf("empty status") return nil, fmt.Errorf("empty status")
} }
err = readByteEqual(br, '\n') err = readByteEqual(rb, '\n')
if err != nil { if err != nil {
return nil, err return nil, err
} }
res.Header, err = readHeader(br) res.Header, err = headerRead(rb)
if err != nil { if err != nil {
return nil, err return nil, err
} }
res.Content, err = readContent(br, res.Header) res.Content, err = readContent(rb, res.Header)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -12,23 +12,23 @@ const (
_MAX_CONTENT_LENGTH = 4096 _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++ { for i := 1; i <= n; i++ {
byts, err := br.Peek(i) byts, err := rb.Peek(i)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if byts[len(byts)-1] == delim { if byts[len(byts)-1] == delim {
br.Discard(len(byts)) rb.Discard(len(byts))
return byts, nil return byts, nil
} }
} }
return nil, fmt.Errorf("buffer length exceeds %d", n) return nil, fmt.Errorf("buffer length exceeds %d", n)
} }
func readByteEqual(br *bufio.Reader, cmp byte) error { func readByteEqual(rb *bufio.Reader, cmp byte) error {
byt, err := br.ReadByte() byt, err := rb.ReadByte()
if err != nil { if err != nil {
return err return err
} }
@@ -40,7 +40,7 @@ func readByteEqual(br *bufio.Reader, cmp byte) error {
return nil 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"] cls, ok := header["Content-Length"]
if !ok || len(cls) != 1 { if !ok || len(cls) != 1 {
return nil, nil return nil, nil
@@ -56,7 +56,7 @@ func readContent(br *bufio.Reader, header Header) ([]byte, error) {
} }
ret := make([]byte, cl) ret := make([]byte, cl)
n, err := io.ReadFull(br, ret) n, err := io.ReadFull(rb, ret)
if err != nil && n != len(ret) { if err != nil && n != len(ret) {
return nil, err return nil, err
} }