mirror of
https://github.com/aler9/gortsplib
synced 2025-10-06 15:46:51 +08:00
allow headers with zero or multiple spaces between key and value
This commit is contained in:
@@ -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
|
||||||
|
@@ -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.
|
||||||
|
@@ -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.
|
||||||
|
17
header.go
17
header.go
@@ -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,13 +59,22 @@ 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
|
||||||
|
// The field value MAY be preceded by any amount of spaces
|
||||||
|
for {
|
||||||
|
byt, err := rb.ReadByte()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@@ -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)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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 {
|
||||||
|
14
request.go
14
request.go
@@ -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
|
||||||
}
|
}
|
||||||
|
14
response.go
14
response.go
@@ -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
|
||||||
}
|
}
|
||||||
|
14
utils.go
14
utils.go
@@ -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
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user