diff --git a/args.go b/args.go index a6db2238fc..a8e43941a8 100644 --- a/args.go +++ b/args.go @@ -361,6 +361,13 @@ func visitArgs(args []argsKV, f func(k, v []byte)) { } } +func visitArgsKey(args []argsKV, f func(k []byte)) { + for i, n := 0, len(args); i < n; i++ { + kv := &args[i] + f(kv.key) + } +} + func copyArgs(dst, src []argsKV) []argsKV { if cap(dst) < len(src) { tmp := make([]argsKV, len(src)) diff --git a/header.go b/header.go index c14532ca9a..105f0c31c9 100644 --- a/header.go +++ b/header.go @@ -42,8 +42,9 @@ type ResponseHeader struct { contentType []byte server []byte - h []argsKV - bufKV argsKV + h []argsKV + trailer []argsKV + bufKV argsKV cookies []argsKV } @@ -77,8 +78,9 @@ type RequestHeader struct { contentType []byte userAgent []byte - h []argsKV - bufKV argsKV + h []argsKV + trailer []argsKV + bufKV argsKV cookies []argsKV @@ -382,6 +384,117 @@ func (h *RequestHeader) SetMultipartFormBoundaryBytes(boundary []byte) { h.SetContentTypeBytes(h.bufKV.value) } +// SetTrailer sets header Trailer value for chunked response +// to indicate which headers will be sent after the body. +// +// Use Set to set the trailer header later. +// +// Trailers are only supported with chunked transfer. +// Trailers allow the sender to include additional headers at the end of chunked messages. +// +// The following trailers are forbidden: +// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length), +// 2. routing (e.g., Host), +// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]), +// 4. authentication (e.g., see [RFC7235] and [RFC6265]), +// 5. response control data (e.g., see Section 7.1 of [RFC7231]), +// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer) +// +// Return ErrBadTrailer if contain any forbidden trailers. +func (h *ResponseHeader) SetTrailer(trailer string) error { + return h.SetTrailerBytes(s2b(trailer)) +} + +// SetTrailerBytes sets Trailer header value for chunked response +// to indicate which headers will be sent after the body. +// +// Use Set to set the trailer header later. +// +// Trailers are only supported with chunked transfer. +// Trailers allow the sender to include additional headers at the end of chunked messages. +// +// The following trailers are forbidden: +// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length), +// 2. routing (e.g., Host), +// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]), +// 4. authentication (e.g., see [RFC7235] and [RFC6265]), +// 5. response control data (e.g., see Section 7.1 of [RFC7231]), +// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer) +// +// Return ErrBadTrailer if contain any forbidden trailers. +func (h *ResponseHeader) SetTrailerBytes(trailer []byte) error { + h.trailer = h.trailer[:0] + return h.AddTrailerBytes(trailer) +} + +// AddTrailer add Trailer header value for chunked response +// to indicate which headers will be sent after the body. +// +// Use Set to set the trailer header later. +// +// Trailers are only supported with chunked transfer. +// Trailers allow the sender to include additional headers at the end of chunked messages. +// +// The following trailers are forbidden: +// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length), +// 2. routing (e.g., Host), +// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]), +// 4. authentication (e.g., see [RFC7235] and [RFC6265]), +// 5. response control data (e.g., see Section 7.1 of [RFC7231]), +// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer) +// +// Return ErrBadTrailer if contain any forbidden trailers. +func (h *ResponseHeader) AddTrailer(trailer string) error { + return h.AddTrailerBytes(s2b(trailer)) +} + +var ErrBadTrailer = errors.New("contain forbidden trailer") + +// AddTrailerBytes add Trailer header value for chunked response +// to indicate which headers will be sent after the body. +// +// Use Set to set the trailer header later. +// +// Trailers are only supported with chunked transfer. +// Trailers allow the sender to include additional headers at the end of chunked messages. +// +// The following trailers are forbidden: +// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length), +// 2. routing (e.g., Host), +// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]), +// 4. authentication (e.g., see [RFC7235] and [RFC6265]), +// 5. response control data (e.g., see Section 7.1 of [RFC7231]), +// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer) +// +// Return ErrBadTrailer if contain any forbidden trailers. +func (h *ResponseHeader) AddTrailerBytes(trailer []byte) error { + var err error + for i := -1; i+1 < len(trailer); { + trailer = trailer[i+1:] + i = bytes.IndexByte(trailer, ',') + if i < 0 { + i = len(trailer) + } + key := trailer[:i] + for len(key) > 0 && key[0] == ' ' { + key = key[1:] + } + for len(key) > 0 && key[len(key)-1] == ' ' { + key = key[:len(key)-1] + } + // Forbidden by RFC 7230, section 4.1.2 + if isBadTrailer(key) { + err = ErrBadTrailer + continue + } + h.bufKV.key = append(h.bufKV.key[:0], key...) + normalizeHeaderKey(h.bufKV.key, h.disableNormalizing) + h.trailer = appendArgBytes(h.trailer, h.bufKV.key, nil, argsNoValue) + } + + return err +} + // MultipartFormBoundary returns boundary part // from 'multipart/form-data; boundary=...' Content-Type. func (h *RequestHeader) MultipartFormBoundary() []byte { @@ -530,6 +643,115 @@ func (h *RequestHeader) SetRequestURIBytes(requestURI []byte) { h.requestURI = append(h.requestURI[:0], requestURI...) } +// SetTrailer sets Trailer header value for chunked request +// to indicate which headers will be sent after the body. +// +// Use Set to set the trailer header later. +// +// Trailers are only supported with chunked transfer. +// Trailers allow the sender to include additional headers at the end of chunked messages. +// +// The following trailers are forbidden: +// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length), +// 2. routing (e.g., Host), +// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]), +// 4. authentication (e.g., see [RFC7235] and [RFC6265]), +// 5. response control data (e.g., see Section 7.1 of [RFC7231]), +// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer) +// +// Return ErrBadTrailer if contain any forbidden trailers. +func (h *RequestHeader) SetTrailer(trailer string) error { + return h.SetTrailerBytes(s2b(trailer)) +} + +// SetTrailerBytes sets Trailer header value for chunked request +// to indicate which headers will be sent after the body. +// +// Use Set to set the trailer header later. +// +// Trailers are only supported with chunked transfer. +// Trailers allow the sender to include additional headers at the end of chunked messages. +// +// The following trailers are forbidden: +// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length), +// 2. routing (e.g., Host), +// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]), +// 4. authentication (e.g., see [RFC7235] and [RFC6265]), +// 5. response control data (e.g., see Section 7.1 of [RFC7231]), +// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer) +// +// Return ErrBadTrailer if contain any forbidden trailers. +func (h *RequestHeader) SetTrailerBytes(trailer []byte) error { + h.trailer = h.trailer[:0] + return h.AddTrailerBytes(trailer) +} + +// AddTrailer add Trailer header value for chunked request +// to indicate which headers will be sent after the body. +// +// Use Set to set the trailer header later. +// +// Trailers are only supported with chunked transfer. +// Trailers allow the sender to include additional headers at the end of chunked messages. +// +// The following trailers are forbidden: +// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length), +// 2. routing (e.g., Host), +// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]), +// 4. authentication (e.g., see [RFC7235] and [RFC6265]), +// 5. response control data (e.g., see Section 7.1 of [RFC7231]), +// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer) +// +// Return ErrBadTrailer if contain any forbidden trailers. +func (h *RequestHeader) AddTrailer(trailer string) error { + return h.AddTrailerBytes(s2b(trailer)) +} + +// AddTrailerBytes add Trailer header value for chunked request +// to indicate which headers will be sent after the body. +// +// Use Set to set the trailer header later. +// +// Trailers are only supported with chunked transfer. +// Trailers allow the sender to include additional headers at the end of chunked messages. +// +// The following trailers are forbidden: +// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length), +// 2. routing (e.g., Host), +// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]), +// 4. authentication (e.g., see [RFC7235] and [RFC6265]), +// 5. response control data (e.g., see Section 7.1 of [RFC7231]), +// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer) +// +// Return ErrBadTrailer if contain any forbidden trailers. +func (h *RequestHeader) AddTrailerBytes(trailer []byte) error { + var err error + for i := -1; i+1 < len(trailer); { + trailer = trailer[i+1:] + i = bytes.IndexByte(trailer, ',') + if i < 0 { + i = len(trailer) + } + key := trailer[:i] + for len(key) > 0 && key[0] == ' ' { + key = key[1:] + } + for len(key) > 0 && key[len(key)-1] == ' ' { + key = key[:len(key)-1] + } + // Forbidden by RFC 7230, section 4.1.2 + if isBadTrailer(key) { + err = ErrBadTrailer + continue + } + h.bufKV.key = append(h.bufKV.key[:0], key...) + normalizeHeaderKey(h.bufKV.key, h.disableNormalizing) + h.trailer = appendArgBytes(h.trailer, h.bufKV.key, nil, argsNoValue) + } + + return err +} + // IsGet returns true if request method is GET. func (h *RequestHeader) IsGet() bool { return string(h.Method()) == MethodGet @@ -718,6 +940,7 @@ func (h *ResponseHeader) resetSkipNormalize() { h.h = h.h[:0] h.cookies = h.cookies[:0] + h.trailer = h.trailer[:0] } // Reset clears request header. @@ -739,6 +962,7 @@ func (h *RequestHeader) resetSkipNormalize() { h.host = h.host[:0] h.contentType = h.contentType[:0] h.userAgent = h.userAgent[:0] + h.trailer = h.trailer[:0] h.h = h.h[:0] h.cookies = h.cookies[:0] @@ -766,6 +990,7 @@ func (h *ResponseHeader) CopyTo(dst *ResponseHeader) { dst.server = append(dst.server, h.server...) dst.h = copyArgs(dst.h, h.h) dst.cookies = copyArgs(dst.cookies, h.cookies) + dst.trailer = copyArgs(dst.trailer, h.trailer) } // CopyTo copies all the headers to dst. @@ -784,6 +1009,7 @@ func (h *RequestHeader) CopyTo(dst *RequestHeader) { dst.host = append(dst.host, h.host...) dst.contentType = append(dst.contentType, h.contentType...) dst.userAgent = append(dst.userAgent, h.userAgent...) + dst.trailer = append(dst.trailer, h.trailer...) dst.h = copyArgs(dst.h, h.h) dst.cookies = copyArgs(dst.cookies, h.cookies) dst.cookiesCollected = h.cookiesCollected @@ -811,12 +1037,29 @@ func (h *ResponseHeader) VisitAll(f func(key, value []byte)) { f(strSetCookie, v) }) } + if len(h.trailer) > 0 { + f(strTrailer, appendArgsKeyBytes(nil, h.trailer, strCommaSpace)) + } visitArgs(h.h, f) if h.ConnectionClose() { f(strConnection, strClose) } } +// VisitAllTrailer calls f for each response Trailer. +// +// f must not retain references to value after returning. +func (h *ResponseHeader) VisitAllTrailer(f func(value []byte)) { + visitArgsKey(h.trailer, f) +} + +// VisitAllTrailer calls f for each request Trailer. +// +// f must not retain references to value after returning. +func (h *RequestHeader) VisitAllTrailer(f func(value []byte)) { + visitArgsKey(h.trailer, f) +} + // VisitAllCookie calls f for each response cookie. // // Cookie name is passed in key and the whole Set-Cookie header value @@ -858,6 +1101,9 @@ func (h *RequestHeader) VisitAll(f func(key, value []byte)) { if len(userAgent) > 0 { f(strUserAgent, userAgent) } + if len(h.trailer) > 0 { + f(strTrailer, appendArgsKeyBytes(nil, h.trailer, strCommaSpace)) + } h.collectCookies() if len(h.cookies) > 0 { @@ -914,6 +1160,8 @@ func (h *ResponseHeader) del(key []byte) { h.contentLengthBytes = h.contentLengthBytes[:0] case HeaderConnection: h.connectionClose = false + case HeaderTrailer: + h.trailer = h.trailer[:0] } h.h = delAllArgsBytes(h.h, key) } @@ -946,6 +1194,8 @@ func (h *RequestHeader) del(key []byte) { h.contentLengthBytes = h.contentLengthBytes[:0] case HeaderConnection: h.connectionClose = false + case HeaderTrailer: + h.trailer = h.trailer[:0] } h.h = delAllArgsBytes(h.h, key) } @@ -991,6 +1241,9 @@ func (h *ResponseHeader) setSpecialHeader(key, value []byte) bool { if caseInsensitiveCompare(strTransferEncoding, key) { // Transfer-Encoding is managed automatically. return true + } else if caseInsensitiveCompare(strTrailer, key) { + _ = h.SetTrailerBytes(value) + return true } case 'd': if caseInsensitiveCompare(strDate, key) { @@ -1036,6 +1289,9 @@ func (h *RequestHeader) setSpecialHeader(key, value []byte) bool { if caseInsensitiveCompare(strTransferEncoding, key) { // Transfer-Encoding is managed automatically. return true + } else if caseInsensitiveCompare(strTrailer, key) { + _ = h.SetTrailerBytes(value) + return true } case 'h': if caseInsensitiveCompare(strHost, key) { @@ -1060,6 +1316,9 @@ func (h *RequestHeader) setSpecialHeader(key, value []byte) bool { // the Content-Type, Content-Length, Connection, Server, Set-Cookie, // Transfer-Encoding and Date headers can only be set once and will // overwrite the previous value. +// +// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details), +// it will be sent after the chunked response body. func (h *ResponseHeader) Add(key, value string) { h.AddBytesKV(s2b(key), s2b(value)) } @@ -1072,6 +1331,9 @@ func (h *ResponseHeader) Add(key, value string) { // the Content-Type, Content-Length, Connection, Server, Set-Cookie, // Transfer-Encoding and Date headers can only be set once and will // overwrite the previous value. +// +// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details), +// it will be sent after the chunked response body. func (h *ResponseHeader) AddBytesK(key []byte, value string) { h.AddBytesKV(key, s2b(value)) } @@ -1084,6 +1346,9 @@ func (h *ResponseHeader) AddBytesK(key []byte, value string) { // the Content-Type, Content-Length, Connection, Server, Set-Cookie, // Transfer-Encoding and Date headers can only be set once and will // overwrite the previous value. +// +// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details), +// it will be sent after the chunked response body. func (h *ResponseHeader) AddBytesV(key string, value []byte) { h.AddBytesKV(s2b(key), value) } @@ -1096,6 +1361,9 @@ func (h *ResponseHeader) AddBytesV(key string, value []byte) { // the Content-Type, Content-Length, Connection, Server, Set-Cookie, // Transfer-Encoding and Date headers can only be set once and will // overwrite the previous value. +// +// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details), +// it will be sent after the chunked response body. func (h *ResponseHeader) AddBytesKV(key, value []byte) { if h.setSpecialHeader(key, value) { return @@ -1107,6 +1375,9 @@ func (h *ResponseHeader) AddBytesKV(key, value []byte) { // Set sets the given 'key: value' header. // +// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details), +// it will be sent after the chunked response body. +// // Use Add for setting multiple header values under the same key. func (h *ResponseHeader) Set(key, value string) { initHeaderKV(&h.bufKV, key, value, h.disableNormalizing) @@ -1115,6 +1386,9 @@ func (h *ResponseHeader) Set(key, value string) { // SetBytesK sets the given 'key: value' header. // +// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details), +// it will be sent after the chunked response body. +// // Use AddBytesK for setting multiple header values under the same key. func (h *ResponseHeader) SetBytesK(key []byte, value string) { h.bufKV.value = append(h.bufKV.value[:0], value...) @@ -1123,6 +1397,9 @@ func (h *ResponseHeader) SetBytesK(key []byte, value string) { // SetBytesV sets the given 'key: value' header. // +// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details), +// it will be sent after the chunked response body. +// // Use AddBytesV for setting multiple header values under the same key. func (h *ResponseHeader) SetBytesV(key string, value []byte) { k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing) @@ -1131,6 +1408,9 @@ func (h *ResponseHeader) SetBytesV(key string, value []byte) { // SetBytesKV sets the given 'key: value' header. // +// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details), +// it will be sent after the chunked response body. +// // Use AddBytesKV for setting multiple header values under the same key. func (h *ResponseHeader) SetBytesKV(key, value []byte) { h.bufKV.key = append(h.bufKV.key[:0], key...) @@ -1140,6 +1420,9 @@ func (h *ResponseHeader) SetBytesKV(key, value []byte) { // SetCanonical sets the given 'key: value' header assuming that // key is in canonical form. +// +// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details), +// it will be sent after the chunked response body. func (h *ResponseHeader) SetCanonical(key, value []byte) { if h.setSpecialHeader(key, value) { return @@ -1253,6 +1536,9 @@ func (h *RequestHeader) DelAllCookies() { // // Multiple headers with the same key may be added with this function. // Use Set for setting a single header for the given key. +// +// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details), +// it will be sent after the chunked request body. func (h *RequestHeader) Add(key, value string) { h.AddBytesKV(s2b(key), s2b(value)) } @@ -1261,6 +1547,9 @@ func (h *RequestHeader) Add(key, value string) { // // Multiple headers with the same key may be added with this function. // Use SetBytesK for setting a single header for the given key. +// +// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details), +// it will be sent after the chunked request body. func (h *RequestHeader) AddBytesK(key []byte, value string) { h.AddBytesKV(key, s2b(value)) } @@ -1269,6 +1558,9 @@ func (h *RequestHeader) AddBytesK(key []byte, value string) { // // Multiple headers with the same key may be added with this function. // Use SetBytesV for setting a single header for the given key. +// +// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details), +// it will be sent after the chunked request body. func (h *RequestHeader) AddBytesV(key string, value []byte) { h.AddBytesKV(s2b(key), value) } @@ -1281,6 +1573,9 @@ func (h *RequestHeader) AddBytesV(key string, value []byte) { // the Content-Type, Content-Length, Connection, Cookie, // Transfer-Encoding, Host and User-Agent headers can only be set once // and will overwrite the previous value. +// +// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details), +// it will be sent after the chunked request body. func (h *RequestHeader) AddBytesKV(key, value []byte) { if h.setSpecialHeader(key, value) { return @@ -1292,6 +1587,9 @@ func (h *RequestHeader) AddBytesKV(key, value []byte) { // Set sets the given 'key: value' header. // +// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details), +// it will be sent after the chunked request body. +// // Use Add for setting multiple header values under the same key. func (h *RequestHeader) Set(key, value string) { initHeaderKV(&h.bufKV, key, value, h.disableNormalizing) @@ -1300,6 +1598,9 @@ func (h *RequestHeader) Set(key, value string) { // SetBytesK sets the given 'key: value' header. // +// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details), +// it will be sent after the chunked request body. +// // Use AddBytesK for setting multiple header values under the same key. func (h *RequestHeader) SetBytesK(key []byte, value string) { h.bufKV.value = append(h.bufKV.value[:0], value...) @@ -1308,6 +1609,9 @@ func (h *RequestHeader) SetBytesK(key []byte, value string) { // SetBytesV sets the given 'key: value' header. // +// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details), +// it will be sent after the chunked request body. +// // Use AddBytesV for setting multiple header values under the same key. func (h *RequestHeader) SetBytesV(key string, value []byte) { k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing) @@ -1316,6 +1620,9 @@ func (h *RequestHeader) SetBytesV(key string, value []byte) { // SetBytesKV sets the given 'key: value' header. // +// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details), +// it will be sent after the chunked request body. +// // Use AddBytesKV for setting multiple header values under the same key. func (h *RequestHeader) SetBytesKV(key, value []byte) { h.bufKV.key = append(h.bufKV.key[:0], key...) @@ -1325,6 +1632,9 @@ func (h *RequestHeader) SetBytesKV(key, value []byte) { // SetCanonical sets the given 'key: value' header assuming that // key is in canonical form. +// +// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details), +// it will be sent after the chunked request body. func (h *RequestHeader) SetCanonical(key, value []byte) { if h.setSpecialHeader(key, value) { return @@ -1390,6 +1700,8 @@ func (h *ResponseHeader) peek(key []byte) []byte { return h.contentLengthBytes case HeaderSetCookie: return appendResponseCookieBytes(nil, h.cookies) + case HeaderTrailer: + return appendArgsKeyBytes(nil, h.trailer, strCommaSpace) default: return peekArgBytes(h.h, key) } @@ -1415,6 +1727,8 @@ func (h *RequestHeader) peek(key []byte) []byte { return appendRequestCookieBytes(nil, h.cookies) } return peekArgBytes(h.h, key) + case HeaderTrailer: + return appendArgsKeyBytes(nil, h.trailer, strCommaSpace) default: return peekArgBytes(h.h, key) } @@ -1498,6 +1812,61 @@ func (h *ResponseHeader) tryRead(r *bufio.Reader, n int) error { return nil } +// ReadTrailer reads response trailer header from r. +// +// io.EOF is returned if r is closed before reading the first byte. +func (h *ResponseHeader) ReadTrailer(r *bufio.Reader) error { + n := 1 + for { + err := h.tryReadTrailer(r, n) + if err == nil { + return nil + } + if err != errNeedMore { + return err + } + n = r.Buffered() + 1 + } +} + +func (h *ResponseHeader) tryReadTrailer(r *bufio.Reader, n int) error { + b, err := r.Peek(n) + if len(b) == 0 { + // Return ErrTimeout on any timeout. + if x, ok := err.(interface{ Timeout() bool }); ok && x.Timeout() { + return ErrTimeout + } + + if n == 1 || err == io.EOF { + return io.EOF + } + + // This is for go 1.6 bug. See https://github.com/golang/go/issues/14121 . + if err == bufio.ErrBufferFull { + if h.secureErrorLogMessage { + return &ErrSmallBuffer{ + error: fmt.Errorf("error when reading response trailer"), + } + } + return &ErrSmallBuffer{ + error: fmt.Errorf("error when reading response trailer: %s", errSmallBuffer), + } + } + + return fmt.Errorf("error when reading response trailer: %s", err) + } + b = mustPeekBuffered(r) + headersLen, errParse := h.parseTrailer(b) + if errParse != nil { + if err == io.EOF { + return err + } + return headerError("response", err, errParse, b, h.secureErrorLogMessage) + } + mustDiscard(r, headersLen) + return nil +} + func headerError(typ string, err, errParse error, b []byte, secureErrorLogMessage bool) error { if errParse != errNeedMore { return headerErrorMsg(typ, errParse, b, secureErrorLogMessage) @@ -1552,6 +1921,61 @@ func (h *RequestHeader) readLoop(r *bufio.Reader, waitForMore bool) error { } } +// ReadTrailer reads request trailer header from r. +// +// io.EOF is returned if r is closed before reading the first byte. +func (h *RequestHeader) ReadTrailer(r *bufio.Reader) error { + n := 1 + for { + err := h.tryReadTrailer(r, n) + if err == nil { + return nil + } + if err != errNeedMore { + return err + } + n = r.Buffered() + 1 + } +} + +func (h *RequestHeader) tryReadTrailer(r *bufio.Reader, n int) error { + b, err := r.Peek(n) + if len(b) == 0 { + // Return ErrTimeout on any timeout. + if x, ok := err.(interface{ Timeout() bool }); ok && x.Timeout() { + return ErrTimeout + } + + if n == 1 || err == io.EOF { + return io.EOF + } + + // This is for go 1.6 bug. See https://github.com/golang/go/issues/14121 . + if err == bufio.ErrBufferFull { + if h.secureErrorLogMessage { + return &ErrSmallBuffer{ + error: fmt.Errorf("error when reading request trailer"), + } + } + return &ErrSmallBuffer{ + error: fmt.Errorf("error when reading request trailer: %s", errSmallBuffer), + } + } + + return fmt.Errorf("error when reading request trailer: %s", err) + } + b = mustPeekBuffered(r) + headersLen, errParse := h.parseTrailer(b) + if errParse != nil { + if err == io.EOF { + return err + } + return headerError("request", err, errParse, b, h.secureErrorLogMessage) + } + mustDiscard(r, headersLen) + return nil +} + func (h *RequestHeader) tryRead(r *bufio.Reader, n int) error { h.resetSkipNormalize() b, err := r.Peek(n) @@ -1648,6 +2072,8 @@ func (h *ResponseHeader) WriteTo(w io.Writer) (int64, error) { // Header returns response header representation. // +// Headers that set as Trailer will not represent. Use TrailerHeader for trailers. +// // The returned value is valid until the request is released, // either though ReleaseRequest or your request handler returning. // Do not store references to returned value. Make copies instead. @@ -1656,6 +2082,29 @@ func (h *ResponseHeader) Header() []byte { return h.bufKV.value } +// writeTrailer writes response trailer to w. +func (h *ResponseHeader) writeTrailer(w *bufio.Writer) error { + _, err := w.Write(h.TrailerHeader()) + return err +} + +// TrailerHeader returns response trailer header representation. +// +// Trailers will only be received with chunked transfer. +// +// The returned value is valid until the request is released, +// either though ReleaseRequest or your request handler returning. +// Do not store references to returned value. Make copies instead. +func (h *ResponseHeader) TrailerHeader() []byte { + h.bufKV.value = h.bufKV.value[:0] + for _, t := range h.trailer { + value := h.peek(t.key) + h.bufKV.value = appendHeaderLine(h.bufKV.value, t.key, value) + } + h.bufKV.value = append(h.bufKV.value, strCRLF...) + return h.bufKV.value +} + // String returns response header representation. func (h *ResponseHeader) String() string { return string(h.Header()) @@ -1702,11 +2151,24 @@ func (h *ResponseHeader) AppendBytes(dst []byte) []byte { for i, n := 0, len(h.h); i < n; i++ { kv := &h.h[i] - if h.noDefaultDate || !bytes.Equal(kv.key, strDate) { + + // Exclude trailer from header + exclude := false + for _, t := range h.trailer { + if bytes.Equal(kv.key, t.key) { + exclude = true + break + } + } + if !exclude && (h.noDefaultDate || !bytes.Equal(kv.key, strDate)) { dst = appendHeaderLine(dst, kv.key, kv.value) } } + if len(h.trailer) > 0 { + dst = appendHeaderLine(dst, strTrailer, appendArgsKeyBytes(nil, h.trailer, strCommaSpace)) + } + n := len(h.cookies) if n > 0 { for i := 0; i < n; i++ { @@ -1738,6 +2200,8 @@ func (h *RequestHeader) WriteTo(w io.Writer) (int64, error) { // Header returns request header representation. // +// Headers that set as Trailer will not represent. Use TrailerHeader for trailers. +// // The returned value is valid until the request is released, // either though ReleaseRequest or your request handler returning. // Do not store references to returned value. Make copies instead. @@ -1746,6 +2210,29 @@ func (h *RequestHeader) Header() []byte { return h.bufKV.value } +// writeTrailer writes request trailer to w. +func (h *RequestHeader) writeTrailer(w *bufio.Writer) error { + _, err := w.Write(h.TrailerHeader()) + return err +} + +// TrailerHeader returns request trailer header representation. +// +// Trailers will only be received with chunked transfer. +// +// The returned value is valid until the request is released, +// either though ReleaseRequest or your request handler returning. +// Do not store references to returned value. Make copies instead. +func (h *RequestHeader) TrailerHeader() []byte { + h.bufKV.value = h.bufKV.value[:0] + for _, t := range h.trailer { + value := h.peek(t.key) + h.bufKV.value = appendHeaderLine(h.bufKV.value, t.key, value) + } + h.bufKV.value = append(h.bufKV.value, strCRLF...) + return h.bufKV.value +} + // RawHeaders returns raw header key/value bytes. // // Depending on server configuration, header keys may be normalized to @@ -1798,7 +2285,21 @@ func (h *RequestHeader) AppendBytes(dst []byte) []byte { for i, n := 0, len(h.h); i < n; i++ { kv := &h.h[i] - dst = appendHeaderLine(dst, kv.key, kv.value) + // Exclude trailer from header + exclude := false + for _, t := range h.trailer { + if bytes.Equal(kv.key, t.key) { + exclude = true + break + } + } + if !exclude { + dst = appendHeaderLine(dst, kv.key, kv.value) + } + } + + if len(h.trailer) > 0 { + dst = appendHeaderLine(dst, strTrailer, appendArgsKeyBytes(nil, h.trailer, strCommaSpace)) } // there is no need in h.collectCookies() here, since if cookies aren't collected yet, @@ -1837,6 +2338,37 @@ func (h *ResponseHeader) parse(buf []byte) (int, error) { return m + n, nil } +func (h *ResponseHeader) parseTrailer(buf []byte) (int, error) { + if buf[0] == '0' { + buf = buf[len(strCRLF)+1:] + } + var s headerScanner + s.b = buf + s.disableNormalizing = h.disableNormalizing + var err error + for s.next() { + if len(s.key) > 0 { + if bytes.IndexByte(s.key, ' ') != -1 || bytes.IndexByte(s.key, '\t') != -1 { + err = fmt.Errorf("invalid trailer key %q", s.key) + continue + } + // Forbidden by RFC 7230, section 4.1.2 + if isBadTrailer(s.key) { + err = fmt.Errorf("forbidden trailer key %q", s.key) + continue + } + } + h.h = appendArgBytes(h.h, s.key, s.value, argsHasValue) + } + if s.err != nil { + return 0, s.err + } + if err != nil { + return 0, err + } + return s.hLen, nil +} + func (h *RequestHeader) ignoreBody() bool { return h.IsGet() || h.IsHead() } @@ -1859,6 +2391,77 @@ func (h *RequestHeader) parse(buf []byte) (int, error) { return m + n, nil } +func (h *RequestHeader) parseTrailer(buf []byte) (int, error) { + if buf[0] == '0' { + buf = buf[len(strCRLF)+1:] + } + var s headerScanner + s.b = buf + s.disableNormalizing = h.disableNormalizing + var err error + for s.next() { + if len(s.key) > 0 { + if bytes.IndexByte(s.key, ' ') != -1 || bytes.IndexByte(s.key, '\t') != -1 { + err = fmt.Errorf("invalid trailer key %q", s.key) + continue + } + // Forbidden by RFC 7230, section 4.1.2 + if isBadTrailer(s.key) { + err = fmt.Errorf("forbidden trailer key %q", s.key) + continue + } + h.h = appendArgBytes(h.h, s.key, s.value, argsHasValue) + } + } + if s.err != nil { + return 0, s.err + } + if err != nil { + return 0, err + } + return s.hLen, nil +} + +func isBadTrailer(key []byte) bool { + switch key[0] | 0x20 { + case 'a': + return caseInsensitiveCompare(key, strAuthorization) + case 'c': + if len(key) > len(HeaderContentType) && caseInsensitiveCompare(key[:8], strContentType[:8]) { + // skip compare prefix 'Content-' + return caseInsensitiveCompare(key[8:], strContentEncoding[8:]) || + caseInsensitiveCompare(key[8:], strContentLength[8:]) || + caseInsensitiveCompare(key[8:], strContentType[8:]) || + caseInsensitiveCompare(key[8:], strContentRange[8:]) + } + return caseInsensitiveCompare(key, strConnection) + case 'e': + return caseInsensitiveCompare(key, strExpect) + case 'h': + return caseInsensitiveCompare(key, strHost) + case 'k': + return caseInsensitiveCompare(key, strKeepAlive) + case 'm': + return caseInsensitiveCompare(key, strMaxForwards) + case 'p': + if len(key) > len(HeaderProxyConnection) && caseInsensitiveCompare(key[:6], strProxyConnection[:6]) { + // skip compare prefix 'Proxy-' + return caseInsensitiveCompare(key[6:], strProxyConnection[6:]) || + caseInsensitiveCompare(key[6:], strProxyAuthenticate[6:]) || + caseInsensitiveCompare(key[6:], strProxyAuthorization[6:]) + } + case 'r': + return caseInsensitiveCompare(key, strRange) + case 't': + return caseInsensitiveCompare(key, strTE) || + caseInsensitiveCompare(key, strTrailer) || + caseInsensitiveCompare(key, strTransferEncoding) + case 'w': + return caseInsensitiveCompare(key, strWWWAuthenticate) + } + return false +} + func (h *ResponseHeader) parseFirstLine(buf []byte) (int, error) { bNext := buf var b []byte @@ -2028,6 +2631,10 @@ func (h *ResponseHeader) parseHeaders(buf []byte) (int, error) { } continue } + if caseInsensitiveCompare(s.key, strTrailer) { + err = h.SetTrailerBytes(s.value) + continue + } } h.h = appendArgBytes(h.h, s.key, s.value, argsHasValue) } @@ -2050,7 +2657,7 @@ func (h *ResponseHeader) parseHeaders(buf []byte) (int, error) { h.connectionClose = !hasHeaderValue(v, strKeepAlive) } - return len(buf) - len(s.b), nil + return len(buf) - len(s.b), err } func (h *RequestHeader) parseHeaders(buf []byte) (int, error) { @@ -2116,6 +2723,10 @@ func (h *RequestHeader) parseHeaders(buf []byte) (int, error) { } continue } + if caseInsensitiveCompare(s.key, strTrailer) { + err = h.SetTrailerBytes(s.value) + continue + } } } h.h = appendArgBytes(h.h, s.key, s.value, argsHasValue) @@ -2159,13 +2770,15 @@ func (h *RequestHeader) collectCookies() { h.cookiesCollected = true } +var errNonNumericChars = errors.New("non-numeric chars found") + func parseContentLength(b []byte) (int, error) { v, n, err := parseUintBuf(b) if err != nil { - return -1, err + return -1, fmt.Errorf("cannot parse Content-Length: %w", err) } if n != len(b) { - return -1, fmt.Errorf("non-numeric chars at the end of Content-Length") + return -1, fmt.Errorf("cannot parse Content-Length: %w", errNonNumericChars) } return v, nil } @@ -2499,6 +3112,17 @@ func AppendNormalizedHeaderKeyBytes(dst, key []byte) []byte { return AppendNormalizedHeaderKey(dst, b2s(key)) } +func appendArgsKeyBytes(dst []byte, args []argsKV, sep []byte) []byte { + for i, n := 0, len(args); i < n; i++ { + kv := &args[i] + dst = append(dst, kv.key...) + if i+1 < n { + dst = append(dst, sep...) + } + } + return dst +} + var ( errNeedMore = errors.New("need more data: cannot find trailing lf") errInvalidName = errors.New("invalid header name") diff --git a/header_test.go b/header_test.go index 27a292c859..f0415778a7 100644 --- a/header_test.go +++ b/header_test.go @@ -4,9 +4,9 @@ import ( "bufio" "bytes" "encoding/base64" + "errors" "fmt" "io" - "io/ioutil" "net/http" "reflect" "strings" @@ -535,6 +535,7 @@ func TestRequestHeaderDel(t *testing.T) { h.Set("User-Agent", "asdfas") h.Set("Content-Length", "1123") h.Set("Cookie", "foobar=baz") + h.Set(HeaderTrailer, "foo, bar") h.Del("foo-bar") h.Del("connection") @@ -543,6 +544,7 @@ func TestRequestHeaderDel(t *testing.T) { h.Del("user-agent") h.Del("content-length") h.Del("cookie") + h.Del("trailer") hv := h.Peek("aaa") if string(hv) != "bbb" { @@ -576,6 +578,10 @@ func TestRequestHeaderDel(t *testing.T) { if len(hv) > 0 { t.Fatalf("non-zero value: %q", hv) } + hv = h.Peek(HeaderTrailer) + if len(hv) > 0 { + t.Fatalf("non-zero value: %q", hv) + } cv := h.Cookie("foobar") if len(cv) > 0 { @@ -596,6 +602,7 @@ func TestResponseHeaderDel(t *testing.T) { h.Set(HeaderContentType, "aaa") h.Set(HeaderServer, "aaabbb") h.Set(HeaderContentLength, "1123") + h.Set(HeaderTrailer, "foo, bar") var c Cookie c.SetKey("foo") @@ -608,6 +615,7 @@ func TestResponseHeaderDel(t *testing.T) { h.Del(HeaderServer) h.Del("content-length") h.Del("set-cookie") + h.Del("trailer") hv := h.Peek("aaa") if string(hv) != "bbb" { @@ -633,6 +641,10 @@ func TestResponseHeaderDel(t *testing.T) { if len(hv) > 0 { t.Fatalf("non-zero value: %q", hv) } + hv = h.Peek(HeaderTrailer) + if len(hv) > 0 { + t.Fatalf("non-zero value: %q", hv) + } if h.Cookie(&c) { t.Fatalf("unexpected cookie obtianed: %q", &c) @@ -642,6 +654,51 @@ func TestResponseHeaderDel(t *testing.T) { } } +func TestResponseHeaderSetTrailerGetBytes(t *testing.T) { + t.Parallel() + + h := &ResponseHeader{} + h.noDefaultDate = true + h.Set("Foo", "bar") + h.Set(HeaderTrailer, "Baz") + h.Set("Baz", "test") + + headerBytes := h.Header() + n, err := h.parseFirstLine(headerBytes) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if string(headerBytes[n:]) != "Foo: bar\r\nTrailer: Baz\r\n\r\n" { + t.Fatalf("Unexpected header: %q. Expected %s", headerBytes[n:], "Foo: bar\nTrailer: Baz\n\n") + } + if string(h.TrailerHeader()) != "Baz: test\r\n\r\n" { + t.Fatalf("Unexpected trailer header: %q. Expected %s", h.TrailerHeader(), "Baz: test\r\n\r\n") + } +} + +func TestRequestHeaderSetTrailerGetBytes(t *testing.T) { + t.Parallel() + + h := &RequestHeader{} + h.Set("Foo", "bar") + h.Set(HeaderTrailer, "Baz") + h.Set("Baz", "test") + + headerBytes := h.Header() + n, err := h.parseFirstLine(headerBytes) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if string(headerBytes[n:]) != "Foo: bar\r\nTrailer: Baz\r\n\r\n" { + t.Fatalf("Unexpected header: %q. Expected %s", headerBytes[n:], "Foo: bar\nTrailer: Baz\n\n") + } + if string(h.TrailerHeader()) != "Baz: test\r\n\r\n" { + t.Fatalf("Unexpected trailer header: %q. Expected %s", h.TrailerHeader(), "Baz: test\r\n\r\n") + } +} + func TestAppendNormalizedHeaderKeyBytes(t *testing.T) { t.Parallel() @@ -1218,6 +1275,7 @@ func TestResponseHeaderCopyTo(t *testing.T) { h.Set(HeaderSetCookie, "foo=bar") h.Set(HeaderContentType, "foobar") h.Set("AAA-BBB", "aaaa") + h.Set(HeaderTrailer, "foo, bar") var h1 ResponseHeader h.CopyTo(&h1) @@ -1230,6 +1288,9 @@ func TestResponseHeaderCopyTo(t *testing.T) { if !bytes.Equal(h1.Peek("aaa-bbb"), h.Peek("AAA-BBB")) { t.Fatalf("unexpected aaa-bbb %q. Expected %q", h1.Peek("aaa-bbb"), h.Peek("aaa-bbb")) } + if !bytes.Equal(h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer)) { + t.Fatalf("unexpected trailer %q. Expected %q", h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer)) + } // flush buf h.bufKV = argsKV{} @@ -1249,6 +1310,7 @@ func TestRequestHeaderCopyTo(t *testing.T) { h.Set(HeaderContentType, "foobar") h.Set(HeaderHost, "aaaa") h.Set("aaaxxx", "123") + h.Set(HeaderTrailer, "foo, bar") var h1 RequestHeader h.CopyTo(&h1) @@ -1264,6 +1326,9 @@ func TestRequestHeaderCopyTo(t *testing.T) { if !bytes.Equal(h1.Peek("aaaxxx"), h.Peek("aaaxxx")) { t.Fatalf("unexpected aaaxxx %q. Expected %q", h1.Peek("aaaxxx"), h.Peek("aaaxxx")) } + if !bytes.Equal(h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer)) { + t.Fatalf("unexpected trailer %q. Expected %q", h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer)) + } // flush buf h.bufKV = argsKV{} @@ -1421,14 +1486,14 @@ func TestResponseHeaderVisitAll(t *testing.T) { var h ResponseHeader - r := bytes.NewBufferString("HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 123\r\nSet-Cookie: aa=bb; path=/foo/bar\r\nSet-Cookie: ccc\r\n\r\n") + r := bytes.NewBufferString("HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 123\r\nSet-Cookie: aa=bb; path=/foo/bar\r\nSet-Cookie: ccc\r\nTrailer: Foo, Bar\r\n\r\n") br := bufio.NewReader(r) if err := h.Read(br); err != nil { t.Fatalf("Unexpected error: %s", err) } - if h.Len() != 4 { - t.Fatalf("Unexpected number of headers: %d. Expected 4", h.Len()) + if h.Len() != 5 { + t.Fatalf("Unexpected number of headers: %d. Expected 5", h.Len()) } contentLengthCount := 0 contentTypeCount := 0 @@ -1455,6 +1520,10 @@ func TestResponseHeaderVisitAll(t *testing.T) { t.Fatalf("unexpected cookie header: %q. Expected %q", v, "ccc") } cookieCount++ + case HeaderTrailer: + if v != "Foo, Bar" { + t.Fatalf("Unexpected trailer header %q. Expected %q", v, "Foo, Bar") + } default: t.Fatalf("unexpected header %q=%q", k, v) } @@ -1475,14 +1544,14 @@ func TestRequestHeaderVisitAll(t *testing.T) { var h RequestHeader - r := bytes.NewBufferString("GET / HTTP/1.1\r\nHost: aa.com\r\nXX: YYY\r\nXX: ZZ\r\nCookie: a=b; c=d\r\n\r\n") + r := bytes.NewBufferString("GET / HTTP/1.1\r\nHost: aa.com\r\nXX: YYY\r\nXX: ZZ\r\nCookie: a=b; c=d\r\nTrailer: Foo, Bar\r\n\r\n") br := bufio.NewReader(r) if err := h.Read(br); err != nil { t.Fatalf("Unexpected error: %s", err) } - if h.Len() != 4 { - t.Fatalf("Unexpected number of header: %d. Expected 4", h.Len()) + if h.Len() != 5 { + t.Fatalf("Unexpected number of header: %d. Expected 5", h.Len()) } hostCount := 0 xxCount := 0 @@ -1509,6 +1578,10 @@ func TestRequestHeaderVisitAll(t *testing.T) { t.Fatalf("Unexpected cookie %q. Expected %q", v, "a=b; c=d") } cookieCount++ + case HeaderTrailer: + if v != "Foo, Bar" { + t.Fatalf("Unexpected trailer header %q. Expected %q", v, "Foo, Bar") + } default: t.Fatalf("Unexpected header %q=%q", k, v) } @@ -1524,7 +1597,7 @@ func TestRequestHeaderVisitAll(t *testing.T) { } } -func TestResponseHeaderVisitAllInOrder(t *testing.T) { +func TestRequestHeaderVisitAllInOrder(t *testing.T) { t.Parallel() var h RequestHeader @@ -1567,6 +1640,38 @@ func TestResponseHeaderVisitAllInOrder(t *testing.T) { }) } +func TestResponseHeaderAddTrailerError(t *testing.T) { + t.Parallel() + + var h ResponseHeader + err := h.AddTrailer("Foo, Content-Length , Bar,Transfer-Encoding,") + expectedTrailer := "Foo, Bar" + + if !errors.Is(err, ErrBadTrailer) { + t.Fatalf("unexpected err %q. Expected %q", err, ErrBadTrailer) + } + if trailer := string(h.Peek(HeaderTrailer)); trailer != expectedTrailer { + t.Fatalf("unexpected trailer %q. Expected %q", trailer, expectedTrailer) + } + +} + +func TestRequestHeaderAddTrailerError(t *testing.T) { + t.Parallel() + + var h RequestHeader + err := h.AddTrailer("Foo, Content-Length , Bar,Transfer-Encoding,") + expectedTrailer := "Foo, Bar" + + if !errors.Is(err, ErrBadTrailer) { + t.Fatalf("unexpected err %q. Expected %q", err, ErrBadTrailer) + } + if trailer := string(h.Peek(HeaderTrailer)); trailer != expectedTrailer { + t.Fatalf("unexpected trailer %q. Expected %q", trailer, expectedTrailer) + } + +} + func TestResponseHeaderCookie(t *testing.T) { t.Parallel() @@ -2106,7 +2211,6 @@ func TestRequestHeaderBufioPeek(t *testing.T) { t.Fatalf("Unexpected error when reading request: %s", err) } verifyRequestHeader(t, h, -2, "/", "foobar.com", "", "") - verifyTrailer(t, br, "aaaa") } func TestResponseHeaderBufioPeek(t *testing.T) { @@ -2121,7 +2225,6 @@ func TestResponseHeaderBufioPeek(t *testing.T) { t.Fatalf("Unexpected error when reading response: %s", err) } verifyResponseHeader(t, h, 200, 10, "aaa") - verifyTrailer(t, br, "0123456789") } func getHeaders(n int) string { @@ -2139,143 +2242,127 @@ func TestResponseHeaderReadSuccess(t *testing.T) { // straight order of content-length and content-type testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\n", - 200, 123, "text/html", "") + 200, 123, "text/html") if h.ConnectionClose() { t.Fatalf("unexpected connection: close") } // reverse order of content-length and content-type testResponseHeaderReadSuccess(t, h, "HTTP/1.1 202 OK\r\nContent-Type: text/plain; encoding=utf-8\r\nContent-Length: 543\r\nConnection: close\r\n\r\n", - 202, 543, "text/plain; encoding=utf-8", "") + 202, 543, "text/plain; encoding=utf-8") if !h.ConnectionClose() { t.Fatalf("expecting connection: close") } // tranfer-encoding: chunked testResponseHeaderReadSuccess(t, h, "HTTP/1.1 505 Internal error\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n", - 505, -1, "text/html", "") + 505, -1, "text/html") if h.ConnectionClose() { t.Fatalf("unexpected connection: close") } // reverse order of content-type and tranfer-encoding testResponseHeaderReadSuccess(t, h, "HTTP/1.1 343 foobar\r\nTransfer-Encoding: chunked\r\nContent-Type: text/json\r\n\r\n", - 343, -1, "text/json", "") + 343, -1, "text/json") // additional headers testResponseHeaderReadSuccess(t, h, "HTTP/1.1 100 Continue\r\nFoobar: baz\r\nContent-Type: aaa/bbb\r\nUser-Agent: x\r\nContent-Length: 123\r\nZZZ: werer\r\n\r\n", - 100, 123, "aaa/bbb", "") - - // trailer (aka body) - testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 32245\r\n\r\nqwert aaa", - 200, 32245, "text/plain", "qwert aaa") + 100, 123, "aaa/bbb") // ancient http protocol testResponseHeaderReadSuccess(t, h, "HTTP/0.9 300 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\nqqqq", - 300, 123, "text/html", "qqqq") + 300, 123, "text/html") // lf instead of crlf testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\nContent-Length: 123\nContent-Type: text/html\n\n", - 200, 123, "text/html", "") + 200, 123, "text/html") // Zero-length headers with mixed crlf and lf testResponseHeaderReadSuccess(t, h, "HTTP/1.1 400 OK\nContent-Length: 345\nZero-Value: \r\nContent-Type: aaa\n: zero-key\r\n\r\nooa", - 400, 345, "aaa", "ooa") + 400, 345, "aaa") // No space after colon testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\nContent-Length:34\nContent-Type: sss\n\naaaa", - 200, 34, "sss", "aaaa") + 200, 34, "sss") // invalid case testResponseHeaderReadSuccess(t, h, "HTTP/1.1 400 OK\nconTEnt-leNGTH: 123\nConTENT-TYPE: ass\n\n", - 400, 123, "ass", "") + 400, 123, "ass") // duplicate content-length testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Length: 456\r\nContent-Type: foo/bar\r\nContent-Length: 321\r\n\r\n", - 200, 321, "foo/bar", "") + 200, 321, "foo/bar") // duplicate content-type testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Length: 234\r\nContent-Type: foo/bar\r\nContent-Type: baz/bar\r\n\r\n", - 200, 234, "baz/bar", "") - - // both transfer-encoding: chunked and content-length - testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Type: foo/bar\r\nContent-Length: 123\r\nTransfer-Encoding: chunked\r\n\r\n", - 200, -1, "foo/bar", "") + 200, 234, "baz/bar") testResponseHeaderReadSuccess(t, h, "HTTP/1.1 300 OK\r\nContent-Type: foo/barr\r\nTransfer-Encoding: chunked\r\nContent-Length: 354\r\n\r\n", - 300, -1, "foo/barr", "") + 300, -1, "foo/barr") // duplicate transfer-encoding: chunked testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\nTransfer-Encoding: chunked\r\n\r\n", - 200, -1, "text/html", "") + 200, -1, "text/html") // no reason string in the first line testResponseHeaderReadSuccess(t, h, "HTTP/1.1 456\r\nContent-Type: xxx/yyy\r\nContent-Length: 134\r\n\r\naaaxxx", - 456, 134, "xxx/yyy", "aaaxxx") + 456, 134, "xxx/yyy") // blank lines before the first line testResponseHeaderReadSuccess(t, h, "\r\nHTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 0\r\n\r\nsss", - 200, 0, "aa", "sss") + 200, 0, "aa") if h.ConnectionClose() { t.Fatalf("unexpected connection: close") } // no content-length (informational responses) testResponseHeaderReadSuccess(t, h, "HTTP/1.1 101 OK\r\n\r\n", - 101, -2, "text/plain; charset=utf-8", "") + 101, -2, "text/plain; charset=utf-8") if h.ConnectionClose() { t.Fatalf("expecting connection: keep-alive for informational response") } // no content-length (no-content responses) testResponseHeaderReadSuccess(t, h, "HTTP/1.1 204 OK\r\n\r\n", - 204, -2, "text/plain; charset=utf-8", "") + 204, -2, "text/plain; charset=utf-8") if h.ConnectionClose() { t.Fatalf("expecting connection: keep-alive for no-content response") } // no content-length (not-modified responses) testResponseHeaderReadSuccess(t, h, "HTTP/1.1 304 OK\r\n\r\n", - 304, -2, "text/plain; charset=utf-8", "") + 304, -2, "text/plain; charset=utf-8") if h.ConnectionClose() { t.Fatalf("expecting connection: keep-alive for not-modified response") } // no content-length (identity transfer-encoding) testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Type: foo/bar\r\n\r\nabcdefg", - 200, -2, "foo/bar", "abcdefg") + 200, -2, "foo/bar") if !h.ConnectionClose() { t.Fatalf("expecting connection: close for identity response") } - // non-numeric content-length - testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Length: faaa\r\nContent-Type: text/html\r\n\r\nfoobar", - 200, -2, "text/html", "foobar") - testResponseHeaderReadSuccess(t, h, "HTTP/1.1 201 OK\r\nContent-Length: 123aa\r\nContent-Type: text/ht\r\n\r\naaa", - 201, -2, "text/ht", "aaa") - testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Length: aa124\r\nContent-Type: html\r\n\r\nxx", - 200, -2, "html", "xx") - // no content-type testResponseHeaderReadSuccess(t, h, "HTTP/1.1 400 OK\r\nContent-Length: 123\r\n\r\nfoiaaa", - 400, 123, string(defaultContentType), "foiaaa") + 400, 123, string(defaultContentType)) // no content-type and no default h.SetNoDefaultContentType(true) testResponseHeaderReadSuccess(t, h, "HTTP/1.1 400 OK\r\nContent-Length: 123\r\n\r\nfoiaaa", - 400, 123, "", "foiaaa") + 400, 123, "") h.SetNoDefaultContentType(false) // no headers testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\n\r\naaaabbb", - 200, -2, string(defaultContentType), "aaaabbb") + 200, -2, string(defaultContentType)) if !h.IsHTTP11() { t.Fatalf("expecting http/1.1 protocol") } // ancient http protocol testResponseHeaderReadSuccess(t, h, "HTTP/1.0 203 OK\r\nContent-Length: 123\r\nContent-Type: foobar\r\n\r\naaa", - 203, 123, "foobar", "aaa") + 203, 123, "foobar") if h.IsHTTP11() { t.Fatalf("ancient protocol must be non-http/1.1") } @@ -2285,7 +2372,7 @@ func TestResponseHeaderReadSuccess(t *testing.T) { // ancient http protocol with 'Connection: keep-alive' header. testResponseHeaderReadSuccess(t, h, "HTTP/1.0 403 aa\r\nContent-Length: 0\r\nContent-Type: 2\r\nConnection: Keep-Alive\r\n\r\nww", - 403, 0, "2", "ww") + 403, 0, "2") if h.IsHTTP11() { t.Fatalf("ancient protocol must be non-http/1.1") } @@ -2301,21 +2388,21 @@ func TestRequestHeaderReadSuccess(t *testing.T) { // simple headers testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\nHost: google.com\r\n\r\n", - -2, "/foo/bar", "google.com", "", "", "") + -2, "/foo/bar", "google.com", "", "", nil) if h.ConnectionClose() { t.Fatalf("unexpected connection: close header") } // simple headers with body testRequestHeaderReadSuccess(t, h, "GET /a/bar HTTP/1.1\r\nHost: gole.com\r\nconneCTION: close\r\n\r\nfoobar", - -2, "/a/bar", "gole.com", "", "", "foobar") + -2, "/a/bar", "gole.com", "", "", nil) if !h.ConnectionClose() { t.Fatalf("connection: close unset") } // ancient http protocol testRequestHeaderReadSuccess(t, h, "GET /bar HTTP/1.0\r\nHost: gole\r\n\r\npppp", - -2, "/bar", "gole", "", "", "pppp") + -2, "/bar", "gole", "", "", nil) if h.IsHTTP11() { t.Fatalf("ancient http protocol cannot be http/1.1") } @@ -2325,7 +2412,7 @@ func TestRequestHeaderReadSuccess(t *testing.T) { // ancient http protocol with 'Connection: keep-alive' header testRequestHeaderReadSuccess(t, h, "GET /aa HTTP/1.0\r\nHost: bb\r\nConnection: keep-alive\r\n\r\nxxx", - -2, "/aa", "bb", "", "", "xxx") + -2, "/aa", "bb", "", "", nil) if h.IsHTTP11() { t.Fatalf("ancient http protocol cannot be http/1.1") } @@ -2335,7 +2422,7 @@ func TestRequestHeaderReadSuccess(t *testing.T) { // complex headers with body testRequestHeaderReadSuccess(t, h, "GET /aabar HTTP/1.1\r\nAAA: bbb\r\nHost: ole.com\r\nAA: bb\r\n\r\nzzz", - -2, "/aabar", "ole.com", "", "", "zzz") + -2, "/aabar", "ole.com", "", "", nil) if !h.IsHTTP11() { t.Fatalf("expecting http/1.1 protocol") } @@ -2345,103 +2432,103 @@ func TestRequestHeaderReadSuccess(t *testing.T) { // lf instead of crlf testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\nHost: google.com\n\n", - -2, "/foo/bar", "google.com", "", "", "") + -2, "/foo/bar", "google.com", "", "", nil) // post method testRequestHeaderReadSuccess(t, h, "POST /aaa?bbb HTTP/1.1\r\nHost: foobar.com\r\nContent-Length: 1235\r\nContent-Type: aaa\r\n\r\nabcdef", - 1235, "/aaa?bbb", "foobar.com", "", "aaa", "abcdef") + 1235, "/aaa?bbb", "foobar.com", "", "aaa", nil) // zero-length headers with mixed crlf and lf testRequestHeaderReadSuccess(t, h, "GET /a HTTP/1.1\nHost: aaa\r\nZero: \n: Zero-Value\n\r\nxccv", - -2, "/a", "aaa", "", "", "xccv") + -2, "/a", "aaa", "", "", nil) // no space after colon testRequestHeaderReadSuccess(t, h, "GET /a HTTP/1.1\nHost:aaaxd\n\nsdfds", - -2, "/a", "aaaxd", "", "", "sdfds") + -2, "/a", "aaaxd", "", "", nil) // get with zero content-length testRequestHeaderReadSuccess(t, h, "GET /xxx HTTP/1.1\nHost: aaa.com\nContent-Length: 0\n\n", - 0, "/xxx", "aaa.com", "", "", "") + 0, "/xxx", "aaa.com", "", "", nil) // get with non-zero content-length testRequestHeaderReadSuccess(t, h, "GET /xxx HTTP/1.1\nHost: aaa.com\nContent-Length: 123\n\n", - 123, "/xxx", "aaa.com", "", "", "") + 123, "/xxx", "aaa.com", "", "", nil) // invalid case testRequestHeaderReadSuccess(t, h, "GET /aaa HTTP/1.1\nhoST: bbb.com\n\naas", - -2, "/aaa", "bbb.com", "", "", "aas") + -2, "/aaa", "bbb.com", "", "", nil) // referer testRequestHeaderReadSuccess(t, h, "GET /asdf HTTP/1.1\nHost: aaa.com\nReferer: bb.com\n\naaa", - -2, "/asdf", "aaa.com", "bb.com", "", "aaa") + -2, "/asdf", "aaa.com", "bb.com", "", nil) // duplicate host testRequestHeaderReadSuccess(t, h, "GET /aa HTTP/1.1\r\nHost: aaaaaa.com\r\nHost: bb.com\r\n\r\n", - -2, "/aa", "bb.com", "", "", "") + -2, "/aa", "bb.com", "", "", nil) // post with duplicate content-type testRequestHeaderReadSuccess(t, h, "POST /a HTTP/1.1\r\nHost: aa\r\nContent-Type: ab\r\nContent-Length: 123\r\nContent-Type: xx\r\n\r\n", - 123, "/a", "aa", "", "xx", "") + 123, "/a", "aa", "", "xx", nil) // post with duplicate content-length testRequestHeaderReadSuccess(t, h, "POST /xx HTTP/1.1\r\nHost: aa\r\nContent-Type: s\r\nContent-Length: 13\r\nContent-Length: 1\r\n\r\n", - 1, "/xx", "aa", "", "s", "") + 1, "/xx", "aa", "", "s", nil) // non-post with content-type testRequestHeaderReadSuccess(t, h, "GET /aaa HTTP/1.1\r\nHost: bbb.com\r\nContent-Type: aaab\r\n\r\n", - -2, "/aaa", "bbb.com", "", "aaab", "") + -2, "/aaa", "bbb.com", "", "aaab", nil) // non-post with content-length testRequestHeaderReadSuccess(t, h, "HEAD / HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 123\r\n\r\n", - 123, "/", "aaa.com", "", "", "") + 123, "/", "aaa.com", "", "", nil) // non-post with content-type and content-length testRequestHeaderReadSuccess(t, h, "GET /aa HTTP/1.1\r\nHost: aa.com\r\nContent-Type: abd/test\r\nContent-Length: 123\r\n\r\n", - 123, "/aa", "aa.com", "", "abd/test", "") + 123, "/aa", "aa.com", "", "abd/test", nil) // request uri with hostname testRequestHeaderReadSuccess(t, h, "GET http://gooGle.com/foO/%20bar?xxx#aaa HTTP/1.1\r\nHost: aa.cOM\r\n\r\ntrail", - -2, "http://gooGle.com/foO/%20bar?xxx#aaa", "aa.cOM", "", "", "trail") + -2, "http://gooGle.com/foO/%20bar?xxx#aaa", "aa.cOM", "", "", nil) // no protocol in the first line testRequestHeaderReadSuccess(t, h, "GET /foo/bar\r\nHost: google.com\r\n\r\nisdD", - -2, "/foo/bar", "google.com", "", "", "isdD") + -2, "/foo/bar", "google.com", "", "", nil) // blank lines before the first line testRequestHeaderReadSuccess(t, h, "\r\n\n\r\nGET /aaa HTTP/1.1\r\nHost: aaa.com\r\n\r\nsss", - -2, "/aaa", "aaa.com", "", "", "sss") + -2, "/aaa", "aaa.com", "", "", nil) // request uri with spaces testRequestHeaderReadSuccess(t, h, "GET /foo/ bar baz HTTP/1.1\r\nHost: aa.com\r\n\r\nxxx", - -2, "/foo/ bar baz", "aa.com", "", "", "xxx") + -2, "/foo/ bar baz", "aa.com", "", "", nil) // no host testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\nFOObar: assdfd\r\n\r\naaa", - -2, "/foo/bar", "", "", "", "aaa") + -2, "/foo/bar", "", "", "", nil) // no host, no headers testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\n\r\nfoobar", - -2, "/foo/bar", "", "", "", "foobar") + -2, "/foo/bar", "", "", "", nil) // post without content-length and content-type testRequestHeaderReadSuccess(t, h, "POST /aaa HTTP/1.1\r\nHost: aaa.com\r\n\r\nzxc", - -2, "/aaa", "aaa.com", "", "", "zxc") + -2, "/aaa", "aaa.com", "", "", nil) // post without content-type testRequestHeaderReadSuccess(t, h, "POST /abc HTTP/1.1\r\nHost: aa.com\r\nContent-Length: 123\r\n\r\npoiuy", - 123, "/abc", "aa.com", "", "", "poiuy") + 123, "/abc", "aa.com", "", "", nil) // post without content-length testRequestHeaderReadSuccess(t, h, "POST /abc HTTP/1.1\r\nHost: aa.com\r\nContent-Type: adv\r\n\r\n123456", - -2, "/abc", "aa.com", "", "adv", "123456") + -2, "/abc", "aa.com", "", "adv", nil) // invalid method testRequestHeaderReadSuccess(t, h, "POST /foo/bar HTTP/1.1\r\nHost: google.com\r\n\r\nmnbv", - -2, "/foo/bar", "google.com", "", "", "mnbv") + -2, "/foo/bar", "google.com", "", "", nil) // put request testRequestHeaderReadSuccess(t, h, "PUT /faa HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 123\r\nContent-Type: aaa\r\n\r\nxwwere", - 123, "/faa", "aaa.com", "", "aaa", "xwwere") + 123, "/faa", "aaa.com", "", "aaa", nil) } func TestResponseHeaderReadError(t *testing.T) { @@ -2462,11 +2549,19 @@ func TestResponseHeaderReadError(t *testing.T) { testResponseHeaderReadError(t, h, "HTTP/1.1 123foobar OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\n") testResponseHeaderReadError(t, h, "HTTP/1.1 foobar344 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\n") + // non-numeric content-length + testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\nContent-Length: faaa\r\nContent-Type: text/html\r\n\r\nfoobar") + testResponseHeaderReadError(t, h, "HTTP/1.1 201 OK\r\nContent-Length: 123aa\r\nContent-Type: text/ht\r\n\r\naaa") + testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\nContent-Length: aa124\r\nContent-Type: html\r\n\r\nxx") + // no headers testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\n") // no trailing crlf testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n") + + // forbidden trailer + testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\nContent-Length: -1\r\nTrailer: Foo, Content-Length\r\n\r\n") } func TestResponseHeaderReadErrorSecureLog(t *testing.T) { @@ -2511,6 +2606,9 @@ func TestRequestHeaderReadError(t *testing.T) { // post with invalid content-length testRequestHeaderReadError(t, h, "POST /a HTTP/1.1\r\nHost: bb\r\nContent-Type: aa\r\nContent-Length: dff\r\n\r\nqwerty") + + // forbidden trailer + testRequestHeaderReadError(t, h, "POST /a HTTP/1.1\r\nContent-Length: -1\r\nTrailer: Foo, Content-Length\r\n\r\n") } func TestRequestHeaderReadSecuredError(t *testing.T) { @@ -2541,7 +2639,7 @@ func testResponseHeaderReadError(t *testing.T, h *ResponseHeader, headers string } // make sure response header works after error testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Type: foo/bar\r\nContent-Length: 12345\r\n\r\nsss", - 200, 12345, "foo/bar", "sss") + 200, 12345, "foo/bar") } func testResponseHeaderReadSecuredError(t *testing.T, h *ResponseHeader, headers string) { @@ -2556,7 +2654,7 @@ func testResponseHeaderReadSecuredError(t *testing.T, h *ResponseHeader, headers } // make sure response header works after error testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Type: foo/bar\r\nContent-Length: 12345\r\n\r\nsss", - 200, 12345, "foo/bar", "sss") + 200, 12345, "foo/bar") } func testRequestHeaderReadError(t *testing.T, h *RequestHeader, headers string) { @@ -2569,7 +2667,7 @@ func testRequestHeaderReadError(t *testing.T, h *RequestHeader, headers string) // make sure request header works after error testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\nHost: aaaa\r\n\r\nxxx", - -2, "/foo/bar", "aaaa", "", "", "xxx") + -2, "/foo/bar", "aaaa", "", "", nil) } func testRequestHeaderReadSecuredError(t *testing.T, h *RequestHeader, headers string) { @@ -2584,11 +2682,11 @@ func testRequestHeaderReadSecuredError(t *testing.T, h *RequestHeader, headers s } // make sure request header works after error testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\nHost: aaaa\r\n\r\nxxx", - -2, "/foo/bar", "aaaa", "", "", "xxx") + -2, "/foo/bar", "aaaa", "", "", nil) } func testResponseHeaderReadSuccess(t *testing.T, h *ResponseHeader, headers string, expectedStatusCode, expectedContentLength int, - expectedContentType, expectedTrailer string) { + expectedContentType string) { r := bytes.NewBufferString(headers) br := bufio.NewReader(r) err := h.Read(br) @@ -2596,11 +2694,10 @@ func testResponseHeaderReadSuccess(t *testing.T, h *ResponseHeader, headers stri t.Fatalf("Unexpected error when parsing response headers: %s. headers=%q", err, headers) } verifyResponseHeader(t, h, expectedStatusCode, expectedContentLength, expectedContentType) - verifyTrailer(t, br, expectedTrailer) } func testRequestHeaderReadSuccess(t *testing.T, h *RequestHeader, headers string, expectedContentLength int, - expectedRequestURI, expectedHost, expectedReferer, expectedContentType, expectedTrailer string) { + expectedRequestURI, expectedHost, expectedReferer, expectedContentType string, expectedTrailer map[string]string) { r := bytes.NewBufferString(headers) br := bufio.NewReader(r) err := h.Read(br) @@ -2608,7 +2705,6 @@ func testRequestHeaderReadSuccess(t *testing.T, h *RequestHeader, headers string t.Fatalf("Unexpected error when parsing request headers: %s. headers=%q", err, headers) } verifyRequestHeader(t, h, expectedContentLength, expectedRequestURI, expectedHost, expectedReferer, expectedContentType) - verifyTrailer(t, br, expectedTrailer) } func verifyResponseHeader(t *testing.T, h *ResponseHeader, expectedStatusCode, expectedContentLength int, expectedContentType string) { @@ -2648,12 +2744,45 @@ func verifyRequestHeader(t *testing.T, h *RequestHeader, expectedContentLength i } } -func verifyTrailer(t *testing.T, r *bufio.Reader, expectedTrailer string) { - trailer, err := ioutil.ReadAll(r) +func verifyResponseTrailer(t *testing.T, h *ResponseHeader, expectedTrailers map[string]string) { + for k, v := range expectedTrailers { + got := h.Peek(k) + if !bytes.Equal(got, []byte(v)) { + t.Fatalf("Unexpected trailer %s. Expected %s. Got %q", k, v, got) + } + } +} + +func verifyRequestTrailer(t *testing.T, h *RequestHeader, expectedTrailers map[string]string) { + for k, v := range expectedTrailers { + got := h.Peek(k) + if !bytes.Equal(got, []byte(v)) { + t.Fatalf("Unexpected trailer %s. Expected %s. Got %q", k, v, got) + } + } +} + +func verifyTrailer(t *testing.T, r *bufio.Reader, expectedTrailers map[string]string, isReq bool) { + if isReq { + req := Request{} + err := req.Header.ReadTrailer(r) + if err == io.EOF && expectedTrailers == nil { + return + } + if err != nil { + t.Fatalf("Cannot read trailer: %s", err) + } + verifyRequestTrailer(t, &req.Header, expectedTrailers) + return + } + + resp := Response{} + err := resp.Header.ReadTrailer(r) + if err == io.EOF && expectedTrailers == nil { + return + } if err != nil { t.Fatalf("Cannot read trailer: %s", err) } - if !bytes.Equal(trailer, []byte(expectedTrailer)) { - t.Fatalf("Unexpected trailer %q. Expected %q", trailer, expectedTrailer) - } + verifyResponseTrailer(t, &resp.Header, expectedTrailers) } diff --git a/headers.go b/headers.go index 378dfec8fa..676a0da185 100644 --- a/headers.go +++ b/headers.go @@ -36,8 +36,9 @@ const ( HeaderVary = "Vary" // Connection management - HeaderConnection = "Connection" - HeaderKeepAlive = "Keep-Alive" + HeaderConnection = "Connection" + HeaderKeepAlive = "Keep-Alive" + HeaderProxyConnection = "Proxy-Connection" // Content negotiation HeaderAccept = "Accept" diff --git a/http.go b/http.go index c8c2eb439e..4b7a2197cf 100644 --- a/http.go +++ b/http.go @@ -1136,7 +1136,17 @@ func (req *Request) ContinueReadBody(r *bufio.Reader, maxBodySize int, preParseM return nil } - return req.ReadBody(r, contentLength, maxBodySize) + if err = req.ReadBody(r, contentLength, maxBodySize); err != nil { + return err + } + + if req.Header.ContentLength() == -1 { + err = req.Header.ReadTrailer(r) + if err != nil && err != io.EOF { + return err + } + } + return nil } // ReadBody reads request body from the given r, limiting the body size. @@ -1146,12 +1156,22 @@ func (req *Request) ContinueReadBody(r *bufio.Reader, maxBodySize int, preParseM func (req *Request) ReadBody(r *bufio.Reader, contentLength int, maxBodySize int) (err error) { bodyBuf := req.bodyBuffer() bodyBuf.Reset() - bodyBuf.B, err = readBody(r, contentLength, maxBodySize, bodyBuf.B) + + if contentLength >= 0 { + bodyBuf.B, err = readBody(r, contentLength, maxBodySize, bodyBuf.B) + + } else if contentLength == -1 { + bodyBuf.B, err = readBodyChunked(r, maxBodySize, bodyBuf.B) + + } else { + bodyBuf.B, err = readBodyIdentity(r, maxBodySize, bodyBuf.B) + req.Header.SetContentLength(len(bodyBuf.B)) + } + if err != nil { req.Reset() return err } - req.Header.SetContentLength(len(bodyBuf.B)) return nil } @@ -1197,12 +1217,12 @@ func (req *Request) ContinueReadBodyStream(r *bufio.Reader, maxBodySize int, pre if err == ErrBodyTooLarge { req.Header.SetContentLength(contentLength) req.body = bodyBuf - req.bodyStream = acquireRequestStream(bodyBuf, r, contentLength) + req.bodyStream = acquireRequestStream(bodyBuf, r, &req.Header) return nil } if err == errChunkedStream { req.body = bodyBuf - req.bodyStream = acquireRequestStream(bodyBuf, r, -1) + req.bodyStream = acquireRequestStream(bodyBuf, r, &req.Header) return nil } req.Reset() @@ -1210,7 +1230,7 @@ func (req *Request) ContinueReadBodyStream(r *bufio.Reader, maxBodySize int, pre } req.body = bodyBuf - req.bodyStream = acquireRequestStream(bodyBuf, r, contentLength) + req.bodyStream = acquireRequestStream(bodyBuf, r, &req.Header) req.Header.SetContentLength(contentLength) return nil } @@ -1245,7 +1265,17 @@ func (resp *Response) ReadLimitBody(r *bufio.Reader, maxBodySize int) error { } if !resp.mustSkipBody() { - return resp.ReadBody(r, maxBodySize) + err = resp.ReadBody(r, maxBodySize) + if err != nil { + return err + } + } + + if resp.Header.ContentLength() == -1 { + err = resp.Header.ReadTrailer(r) + if err != nil && err != io.EOF { + return err + } } return nil } @@ -1257,12 +1287,19 @@ func (resp *Response) ReadLimitBody(r *bufio.Reader, maxBodySize int) error { func (resp *Response) ReadBody(r *bufio.Reader, maxBodySize int) (err error) { bodyBuf := resp.bodyBuffer() bodyBuf.Reset() - bodyBuf.B, err = readBody(r, resp.Header.ContentLength(), maxBodySize, bodyBuf.B) - if err != nil { - return err + + contentLength := resp.Header.ContentLength() + if contentLength >= 0 { + bodyBuf.B, err = readBody(r, contentLength, maxBodySize, bodyBuf.B) + + } else if contentLength == -1 { + bodyBuf.B, err = readBodyChunked(r, maxBodySize, bodyBuf.B) + + } else { + bodyBuf.B, err = readBodyIdentity(r, maxBodySize, bodyBuf.B) + resp.Header.SetContentLength(len(bodyBuf.B)) } - resp.Header.SetContentLength(len(bodyBuf.B)) - return nil + return err } func (resp *Response) mustSkipBody() bool { @@ -1723,9 +1760,13 @@ func (req *Request) writeBodyStream(w *bufio.Writer) error { } } else { req.Header.SetContentLength(-1) - if err = req.Header.Write(w); err == nil { + err = req.Header.Write(w) + if err == nil { err = writeBodyChunked(w, req.bodyStream) } + if err == nil { + err = req.Header.writeTrailer(w) + } } err1 := req.closeBodyStream() if err == nil { @@ -1779,6 +1820,9 @@ func (resp *Response) writeBodyStream(w *bufio.Writer, sendBody bool) (err error if err == nil && sendBody { err = writeBodyChunked(w, resp.bodyStream) } + if err == nil { + err = resp.Header.writeTrailer(w) + } } } err1 := resp.closeBodyStream() @@ -1927,12 +1971,13 @@ func writeChunk(w *bufio.Writer, b []byte) error { if _, err := w.Write(b); err != nil { return err } - _, err := w.Write(strCRLF) - err1 := w.Flush() - if err == nil { - err = err1 + // If is end chunk, write CRLF after writing trailer + if n > 0 { + if _, err := w.Write(strCRLF); err != nil { + return err + } } - return err + return w.Flush() } // ErrBodyTooLarge is returned if either request or response body exceeds @@ -1940,17 +1985,10 @@ func writeChunk(w *bufio.Writer, b []byte) error { var ErrBodyTooLarge = errors.New("body size exceeds the given limit") func readBody(r *bufio.Reader, contentLength int, maxBodySize int, dst []byte) ([]byte, error) { - dst = dst[:0] - if contentLength >= 0 { - if maxBodySize > 0 && contentLength > maxBodySize { - return dst, ErrBodyTooLarge - } - return appendBodyFixedSize(r, dst, contentLength) - } - if contentLength == -1 { - return readBodyChunked(r, maxBodySize, dst) + if maxBodySize > 0 && contentLength > maxBodySize { + return dst, ErrBodyTooLarge } - return readBodyIdentity(r, maxBodySize, dst) + return appendBodyFixedSize(r, dst, contentLength) } var errChunkedStream = errors.New("chunked stream") @@ -2067,6 +2105,9 @@ func readBodyChunked(r *bufio.Reader, maxBodySize int, dst []byte) ([]byte, erro if err != nil { return dst, err } + if chunkSize == 0 { + return dst, err + } if maxBodySize > 0 && len(dst)+chunkSize > maxBodySize { return dst, ErrBodyTooLarge } @@ -2080,9 +2121,6 @@ func readBodyChunked(r *bufio.Reader, maxBodySize int, dst []byte) ([]byte, erro } } dst = dst[:len(dst)-strCRLFLen] - if chunkSize == 0 { - return dst, nil - } } } @@ -2098,8 +2136,9 @@ func parseChunkSize(r *bufio.Reader) (int, error) { error: fmt.Errorf("cannot read '\r' char at the end of chunk size: %s", err), } } - // Skip any trailing whitespace after chunk size. - if c == ' ' { + // Skip chunk extension after chunk size. + // Add support later if anyone needs it. + if c != '\r' { continue } if err := r.UnreadByte(); err != nil { diff --git a/http_test.go b/http_test.go index 8bd050b98e..978703a65f 100644 --- a/http_test.go +++ b/http_test.go @@ -155,6 +155,122 @@ func testResponseCopyTo(t *testing.T, src *Response) { } } +func TestRequestBodyStreamWithTrailer(t *testing.T) { + t.Parallel() + + testRequestBodyStreamWithTrailer(t, nil, false) + + body := createFixedBody(1e5) + testRequestBodyStreamWithTrailer(t, body, false) + testRequestBodyStreamWithTrailer(t, body, true) +} + +func testRequestBodyStreamWithTrailer(t *testing.T, body []byte, disableNormalizing bool) { + expectedTrailer := map[string]string{ + "foo": "testfoo", + "bar": "testbar", + } + + var req1 Request + req1.Header.disableNormalizing = disableNormalizing + req1.SetHost("google.com") + req1.SetBodyStream(bytes.NewBuffer(body), -1) + for k, v := range expectedTrailer { + err := req1.Header.AddTrailer(k) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + req1.Header.Set(k, v) + } + + w := &bytes.Buffer{} + bw := bufio.NewWriter(w) + if err := req1.Write(bw); err != nil { + t.Fatalf("unexpected error: %s", err) + } + if err := bw.Flush(); err != nil { + t.Fatalf("unexpected error: %s", err) + } + + var req2 Request + req2.Header.disableNormalizing = disableNormalizing + br := bufio.NewReader(w) + if err := req2.Read(br); err != nil { + t.Fatalf("unexpected error: %s", err) + } + + reqBody := req2.Body() + if !bytes.Equal(reqBody, body) { + t.Fatalf("unexpected body: %q. Expecting %q", reqBody, body) + } + + for k, v := range expectedTrailer { + kBytes := []byte(k) + normalizeHeaderKey(kBytes, disableNormalizing) + r := req2.Header.Peek(k) + if string(r) != v { + t.Fatalf("unexpected trailer header %q: %q. Expecting %s", kBytes, r, v) + } + } +} + +func TestResponseBodyStreamWithTrailer(t *testing.T) { + t.Parallel() + + testResponseBodyStreamWithTrailer(t, nil, false) + + body := createFixedBody(1e5) + testResponseBodyStreamWithTrailer(t, body, false) + testResponseBodyStreamWithTrailer(t, body, true) +} + +func testResponseBodyStreamWithTrailer(t *testing.T, body []byte, disableNormalizing bool) { + expectedTrailer := map[string]string{ + "foo": "testfoo", + "bar": "testbar", + } + var resp1 Response + resp1.Header.disableNormalizing = disableNormalizing + resp1.SetBodyStream(bytes.NewReader(body), -1) + for k, v := range expectedTrailer { + err := resp1.Header.AddTrailer(k) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + resp1.Header.Set(k, v) + } + + w := &bytes.Buffer{} + bw := bufio.NewWriter(w) + if err := resp1.Write(bw); err != nil { + t.Fatalf("unexpected error: %s", err) + } + if err := bw.Flush(); err != nil { + t.Fatalf("unexpected error: %s", err) + } + + var resp2 Response + resp2.Header.disableNormalizing = disableNormalizing + br := bufio.NewReader(w) + if err := resp2.Read(br); err != nil { + t.Fatalf("unexpected error: %s", err) + } + + respBody := resp2.Body() + if !bytes.Equal(respBody, body) { + t.Fatalf("unexpected body: %q. Expecting %q", respBody, body) + } + + for k, v := range expectedTrailer { + kBytes := []byte(k) + normalizeHeaderKey(kBytes, disableNormalizing) + r := resp2.Header.Peek(k) + if string(r) != v { + t.Fatalf("unexpected trailer header %q: %q. Expecting %s", kBytes, r, v) + } + } +} + func TestResponseBodyStreamDeflate(t *testing.T) { t.Parallel() @@ -1344,17 +1460,19 @@ func TestResponseReadLimitBody(t *testing.T) { // response with content-length testResponseReadLimitBodySuccess(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 10\r\n\r\n9876543210", 10) testResponseReadLimitBodySuccess(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 10\r\n\r\n9876543210", 100) - testResponseReadLimitBodyError(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 10\r\n\r\n9876543210", 9) + testResponseReadLimitBodyError(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 10\r\n\r\n9876543210", 9, ErrBodyTooLarge) // chunked response testResponseReadLimitBodySuccess(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 9) + testResponseReadLimitBodySuccess(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\nFoo: bar\r\n\r\n", 9) testResponseReadLimitBodySuccess(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 100) - testResponseReadLimitBodyError(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 2) + testResponseReadLimitBodySuccess(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\nfoobar\r\n\r\n", 100) + testResponseReadLimitBodyError(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 2, ErrBodyTooLarge) // identity response testResponseReadLimitBodySuccess(t, "HTTP/1.1 400 OK\r\nContent-Type: aa\r\n\r\n123456", 6) testResponseReadLimitBodySuccess(t, "HTTP/1.1 400 OK\r\nContent-Type: aa\r\n\r\n123456", 106) - testResponseReadLimitBodyError(t, "HTTP/1.1 400 OK\r\nContent-Type: aa\r\n\r\n123456", 5) + testResponseReadLimitBodyError(t, "HTTP/1.1 400 OK\r\nContent-Type: aa\r\n\r\n123456", 5, ErrBodyTooLarge) } func TestRequestReadLimitBody(t *testing.T) { @@ -1363,15 +1481,17 @@ func TestRequestReadLimitBody(t *testing.T) { // request with content-length testRequestReadLimitBodySuccess(t, "POST /foo HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 9\r\nContent-Type: aaa\r\n\r\n123456789", 9) testRequestReadLimitBodySuccess(t, "POST /foo HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 9\r\nContent-Type: aaa\r\n\r\n123456789", 92) - testRequestReadLimitBodyError(t, "POST /foo HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 9\r\nContent-Type: aaa\r\n\r\n123456789", 5) + testRequestReadLimitBodyError(t, "POST /foo HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 9\r\nContent-Type: aaa\r\n\r\n123456789", 5, ErrBodyTooLarge) // chunked request testRequestReadLimitBodySuccess(t, "POST /a HTTP/1.1\r\nHost: a.com\r\nTransfer-Encoding: chunked\r\nContent-Type: aa\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 9) + testRequestReadLimitBodySuccess(t, "POST /a HTTP/1.1\nHost: a.com\nTransfer-Encoding: chunked\nContent-Type: aa\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\nFoo: bar\r\n\r\n", 9) testRequestReadLimitBodySuccess(t, "POST /a HTTP/1.1\r\nHost: a.com\r\nTransfer-Encoding: chunked\r\nContent-Type: aa\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 999) - testRequestReadLimitBodyError(t, "POST /a HTTP/1.1\r\nHost: a.com\r\nTransfer-Encoding: chunked\r\nContent-Type: aa\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 8) + testRequestReadLimitBodySuccess(t, "POST /a HTTP/1.1\r\nHost: a.com\r\nTransfer-Encoding: chunked\r\nContent-Type: aa\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\nfoobar\r\n\r\n", 999) + testRequestReadLimitBodyError(t, "POST /a HTTP/1.1\r\nHost: a.com\r\nTransfer-Encoding: chunked\r\nContent-Type: aa\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 8, ErrBodyTooLarge) } -func testResponseReadLimitBodyError(t *testing.T, s string, maxBodySize int) { +func testResponseReadLimitBodyError(t *testing.T, s string, maxBodySize int, expectedErr error) { var req Response r := bytes.NewBufferString(s) br := bufio.NewReader(r) @@ -1379,8 +1499,8 @@ func testResponseReadLimitBodyError(t *testing.T, s string, maxBodySize int) { if err == nil { t.Fatalf("expecting error. s=%q, maxBodySize=%d", s, maxBodySize) } - if err != ErrBodyTooLarge { - t.Fatalf("unexpected error: %s. Expecting %s. s=%q, maxBodySize=%d", err, ErrBodyTooLarge, s, maxBodySize) + if err != expectedErr { + t.Fatalf("unexpected error: %s. Expecting %s. s=%q, maxBodySize=%d", err, expectedErr, s, maxBodySize) } } @@ -1393,7 +1513,7 @@ func testResponseReadLimitBodySuccess(t *testing.T, s string, maxBodySize int) { } } -func testRequestReadLimitBodyError(t *testing.T, s string, maxBodySize int) { +func testRequestReadLimitBodyError(t *testing.T, s string, maxBodySize int, expectedErr error) { var req Request r := bytes.NewBufferString(s) br := bufio.NewReader(r) @@ -1401,8 +1521,8 @@ func testRequestReadLimitBodyError(t *testing.T, s string, maxBodySize int) { if err == nil { t.Fatalf("expecting error. s=%q, maxBodySize=%d", s, maxBodySize) } - if err != ErrBodyTooLarge { - t.Fatalf("unexpected error: %s. Expecting %s. s=%q, maxBodySize=%d", err, ErrBodyTooLarge, s, maxBodySize) + if err != expectedErr { + t.Fatalf("unexpected error: %s. Expecting %s. s=%q, maxBodySize=%d", err, expectedErr, s, maxBodySize) } } @@ -1490,52 +1610,49 @@ func TestRequestWriteRequestURINoHost(t *testing.T) { func TestSetRequestBodyStreamFixedSize(t *testing.T) { t.Parallel() - testSetRequestBodyStream(t, "a", false) - testSetRequestBodyStream(t, string(createFixedBody(4097)), false) - testSetRequestBodyStream(t, string(createFixedBody(100500)), false) + testSetRequestBodyStream(t, "a") + testSetRequestBodyStream(t, string(createFixedBody(4097))) + testSetRequestBodyStream(t, string(createFixedBody(100500))) } func TestSetResponseBodyStreamFixedSize(t *testing.T) { t.Parallel() - testSetResponseBodyStream(t, "a", false) - testSetResponseBodyStream(t, string(createFixedBody(4097)), false) - testSetResponseBodyStream(t, string(createFixedBody(100500)), false) + testSetResponseBodyStream(t, "a") + testSetResponseBodyStream(t, string(createFixedBody(4097))) + testSetResponseBodyStream(t, string(createFixedBody(100500))) } func TestSetRequestBodyStreamChunked(t *testing.T) { t.Parallel() - testSetRequestBodyStream(t, "", true) + testSetRequestBodyStreamChunked(t, "", map[string]string{"Foo": "bar"}) body := "foobar baz aaa bbb ccc" - testSetRequestBodyStream(t, body, true) + testSetRequestBodyStreamChunked(t, body, nil) body = string(createFixedBody(10001)) - testSetRequestBodyStream(t, body, true) + testSetRequestBodyStreamChunked(t, body, map[string]string{"Foo": "test", "Bar": "test"}) } func TestSetResponseBodyStreamChunked(t *testing.T) { t.Parallel() - testSetResponseBodyStream(t, "", true) + testSetResponseBodyStreamChunked(t, "", map[string]string{"Foo": "bar"}) body := "foobar baz aaa bbb ccc" - testSetResponseBodyStream(t, body, true) + testSetResponseBodyStreamChunked(t, body, nil) body = string(createFixedBody(10001)) - testSetResponseBodyStream(t, body, true) + testSetResponseBodyStreamChunked(t, body, map[string]string{"Foo": "test", "Bar": "test"}) } -func testSetRequestBodyStream(t *testing.T, body string, chunked bool) { +func testSetRequestBodyStream(t *testing.T, body string) { var req Request req.Header.SetHost("foobar.com") req.Header.SetMethod(MethodPost) bodySize := len(body) - if chunked { - bodySize = -1 - } if req.IsBodyStream() { t.Fatalf("IsBodyStream must return false") } @@ -1563,12 +1680,56 @@ func testSetRequestBodyStream(t *testing.T, body string, chunked bool) { } } -func testSetResponseBodyStream(t *testing.T, body string, chunked bool) { +func testSetRequestBodyStreamChunked(t *testing.T, body string, trailer map[string]string) { + var req Request + req.Header.SetHost("foobar.com") + req.Header.SetMethod(MethodPost) + + if req.IsBodyStream() { + t.Fatalf("IsBodyStream must return false") + } + req.SetBodyStream(bytes.NewBufferString(body), -1) + if !req.IsBodyStream() { + t.Fatalf("IsBodyStream must return true") + } + + var w bytes.Buffer + bw := bufio.NewWriter(&w) + for k := range trailer { + err := req.Header.AddTrailer(k) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + } + if err := req.Write(bw); err != nil { + t.Fatalf("unexpected error when writing request: %s. body=%q", err, body) + } + for k, v := range trailer { + req.Header.Set(k, v) + } + if err := bw.Flush(); err != nil { + t.Fatalf("unexpected error when flushing request: %s. body=%q", err, body) + } + + var req1 Request + br := bufio.NewReader(&w) + if err := req1.Read(br); err != nil { + t.Fatalf("unexpected error when reading request: %s. body=%q", err, body) + } + if string(req1.Body()) != body { + t.Fatalf("unexpected body %q. Expecting %q", req1.Body(), body) + } + for k, v := range trailer { + r := req.Header.Peek(k) + if string(r) != v { + t.Fatalf("unexpected trailer %s. Expecting %s. Got %q", k, v, r) + } + } +} + +func testSetResponseBodyStream(t *testing.T, body string) { var resp Response bodySize := len(body) - if chunked { - bodySize = -1 - } if resp.IsBodyStream() { t.Fatalf("IsBodyStream must return false") } @@ -1596,6 +1757,50 @@ func testSetResponseBodyStream(t *testing.T, body string, chunked bool) { } } +func testSetResponseBodyStreamChunked(t *testing.T, body string, trailer map[string]string) { + var resp Response + if resp.IsBodyStream() { + t.Fatalf("IsBodyStream must return false") + } + resp.SetBodyStream(bytes.NewBufferString(body), -1) + if !resp.IsBodyStream() { + t.Fatalf("IsBodyStream must return true") + } + + var w bytes.Buffer + bw := bufio.NewWriter(&w) + for k := range trailer { + err := resp.Header.AddTrailer(k) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + } + if err := resp.Write(bw); err != nil { + t.Fatalf("unexpected error when writing response: %s. body=%q", err, body) + } + if err := bw.Flush(); err != nil { + t.Fatalf("unexpected error when flushing response: %s. body=%q", err, body) + } + for k, v := range trailer { + resp.Header.Set(k, v) + } + + var resp1 Response + br := bufio.NewReader(&w) + if err := resp1.Read(br); err != nil { + t.Fatalf("unexpected error when reading response: %s. body=%q", err, body) + } + if string(resp1.Body()) != body { + t.Fatalf("unexpected body %q. Expecting %q", resp1.Body(), body) + } + for k, v := range trailer { + r := resp.Header.Peek(k) + if string(r) != v { + t.Fatalf("unexpected trailer %s. Expecting %s. Got %q", k, v, r) + } + } +} + func TestRound2(t *testing.T) { t.Parallel() @@ -1622,7 +1827,7 @@ func TestRequestReadChunked(t *testing.T) { var req Request - s := "POST /foo HTTP/1.1\r\nHost: google.com\r\nTransfer-Encoding: chunked\r\nContent-Type: aa/bb\r\n\r\n3\r\nabc\r\n5\r\n12345\r\n0\r\n\r\ntrail" + s := "POST /foo HTTP/1.1\r\nHost: google.com\r\nTransfer-Encoding: chunked\r\nContent-Type: aa/bb\r\n\r\n3\r\nabc\r\n5\r\n12345\r\n0\r\n\r\nTrail: test\r\n\r\n" r := bytes.NewBufferString(s) rb := bufio.NewReader(r) err := req.Read(rb) @@ -1633,8 +1838,8 @@ func TestRequestReadChunked(t *testing.T) { if string(req.Body()) != expectedBody { t.Fatalf("Unexpected body %q. Expected %q", req.Body(), expectedBody) } - verifyRequestHeader(t, &req.Header, 8, "/foo", "google.com", "", "aa/bb") - verifyTrailer(t, rb, "trail") + verifyRequestHeader(t, &req.Header, -1, "/foo", "google.com", "", "aa/bb") + verifyTrailer(t, rb, map[string]string{"Trail": "test"}, true) } // See: https://github.com/erikdubbelboer/fasthttp/issues/34 @@ -1661,25 +1866,25 @@ func TestResponseReadWithoutBody(t *testing.T) { var resp Response - testResponseReadWithoutBody(t, &resp, "HTTP/1.1 304 Not Modified\r\nContent-Type: aa\r\nContent-Length: 1235\r\n\r\nfoobar", false, - 304, 1235, "aa", "foobar") + testResponseReadWithoutBody(t, &resp, "HTTP/1.1 304 Not Modified\r\nContent-Type: aa\r\nContent-Length: 1235\r\n\r\n", false, + 304, 1235, "aa", nil) - testResponseReadWithoutBody(t, &resp, "HTTP/1.1 204 Foo Bar\r\nContent-Type: aab\r\nTransfer-Encoding: chunked\r\n\r\n123\r\nss", false, - 204, -1, "aab", "123\r\nss") + testResponseReadWithoutBody(t, &resp, "HTTP/1.1 204 Foo Bar\r\nContent-Type: aab\r\nTransfer-Encoding: chunked\r\n\r\n0\r\nFoo: bar\r\n\r\n", false, + 204, -1, "aab", map[string]string{"Foo": "bar"}) - testResponseReadWithoutBody(t, &resp, "HTTP/1.1 123 AAA\r\nContent-Type: xxx\r\nContent-Length: 3434\r\n\r\naaaa", false, - 123, 3434, "xxx", "aaaa") + testResponseReadWithoutBody(t, &resp, "HTTP/1.1 123 AAA\r\nContent-Type: xxx\r\nContent-Length: 3434\r\n\r\n", false, + 123, 3434, "xxx", nil) - testResponseReadWithoutBody(t, &resp, "HTTP 200 OK\r\nContent-Type: text/xml\r\nContent-Length: 123\r\n\r\nxxxx", true, - 200, 123, "text/xml", "xxxx") + testResponseReadWithoutBody(t, &resp, "HTTP 200 OK\r\nContent-Type: text/xml\r\nContent-Length: 123\r\n\r\nfoobar\r\n", true, + 200, 123, "text/xml", nil) // '100 Continue' must be skipped. - testResponseReadWithoutBody(t, &resp, "HTTP/1.1 100 Continue\r\nFoo-bar: baz\r\n\r\nHTTP/1.1 329 aaa\r\nContent-Type: qwe\r\nContent-Length: 894\r\n\r\nfoobar", true, - 329, 894, "qwe", "foobar") + testResponseReadWithoutBody(t, &resp, "HTTP/1.1 100 Continue\r\nFoo-bar: baz\r\n\r\nHTTP/1.1 329 aaa\r\nContent-Type: qwe\r\nContent-Length: 894\r\n\r\n", true, + 329, 894, "qwe", nil) } func testResponseReadWithoutBody(t *testing.T, resp *Response, s string, skipBody bool, - expectedStatusCode, expectedContentLength int, expectedContentType, expectedTrailer string) { + expectedStatusCode, expectedContentLength int, expectedContentType string, expectedTrailer map[string]string) { r := bytes.NewBufferString(s) rb := bufio.NewReader(r) resp.SkipBody = skipBody @@ -1691,12 +1896,12 @@ func testResponseReadWithoutBody(t *testing.T, resp *Response, s string, skipBod t.Fatalf("Unexpected response body %q. Expected %q. response=%q", resp.Body(), "", s) } verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType) - verifyTrailer(t, rb, expectedTrailer) + verifyResponseTrailer(t, &resp.Header, expectedTrailer) // verify that ordinal response is read after null-body response resp.SkipBody = false testResponseReadSuccess(t, resp, "HTTP/1.1 300 OK\r\nContent-Length: 5\r\nContent-Type: bar\r\n\r\n56789aaa", - 300, 5, "bar", "56789", "aaa") + 300, 5, "bar", "56789", nil) } func TestRequestSuccess(t *testing.T) { @@ -1872,40 +2077,54 @@ func TestResponseReadSuccess(t *testing.T) { // usual response testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Length: 10\r\nContent-Type: foo/bar\r\n\r\n0123456789", - 200, 10, "foo/bar", "0123456789", "") + 200, 10, "foo/bar", "0123456789", nil) // zero response testResponseReadSuccess(t, resp, "HTTP/1.1 500 OK\r\nContent-Length: 0\r\nContent-Type: foo/bar\r\n\r\n", - 500, 0, "foo/bar", "", "") + 500, 0, "foo/bar", "", nil) // response with trailer - testResponseReadSuccess(t, resp, "HTTP/1.1 300 OK\r\nContent-Length: 5\r\nContent-Type: bar\r\n\r\n56789aaa", - 300, 5, "bar", "56789", "aaa") + testResponseReadSuccess(t, resp, "HTTP/1.1 300 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: bar\r\n\r\n5\r\n56789\r\n0\r\nfoo: bar\r\n\r\n", + 300, -1, "bar", "56789", map[string]string{"Foo": "bar"}) - // no conent-length ('identity' transfer-encoding) - testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: foobar\r\n\r\nzxxc", - 200, 4, "foobar", "zxxc", "") + // response with trailer disableNormalizing + resp.Header.DisableNormalizing() + testResponseReadSuccess(t, resp, "HTTP/1.1 300 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: bar\r\n\r\n5\r\n56789\r\n0\r\nfoo: bar\r\n\r\n", + 300, -1, "bar", "56789", map[string]string{"foo": "bar"}) + + // no content-length ('identity' transfer-encoding) + testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: foobar\r\n\r\nzxxxx", + 200, 5, "foobar", "zxxxx", nil) // explicitly stated 'Transfer-Encoding: identity' testResponseReadSuccess(t, resp, "HTTP/1.1 234 ss\r\nContent-Type: xxx\r\n\r\nxag", - 234, 3, "xxx", "xag", "") + 234, 3, "xxx", "xag", nil) // big 'identity' response body := string(createFixedBody(100500)) testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\n\r\n"+body, - 200, 100500, "aa", body, "") + 200, 100500, "aa", body, nil) // chunked response - testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nqwer\r\n2\r\nty\r\n0\r\n\r\nzzzzz", - 200, 6, "text/html", "qwerty", "zzzzz") + testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nqwer\r\n2\r\nty\r\n0\r\nFoo2: bar2\r\n\r\n", + 200, -1, "text/html", "qwerty", map[string]string{"Foo2": "bar2"}) // chunked response with non-chunked Transfer-Encoding. - testResponseReadSuccess(t, resp, "HTTP/1.1 230 OK\r\nContent-Type: text\r\nTransfer-Encoding: aaabbb\r\n\r\n2\r\ner\r\n2\r\nty\r\n0\r\n\r\nwe", - 230, 4, "text", "erty", "we") + testResponseReadSuccess(t, resp, "HTTP/1.1 230 OK\r\nContent-Type: text\r\nTransfer-Encoding: aaabbb\r\n\r\n2\r\ner\r\n2\r\nty\r\n0\r\nFoo3: bar3\r\n\r\n", + 230, -1, "text", "erty", map[string]string{"Foo3": "bar3"}) + + // chunked response with content-length + testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: foo/bar\r\nContent-Length: 123\r\nTransfer-Encoding: chunked\r\n\r\n4\r\ntest\r\n0\r\nFoo4:bar4\r\n\r\n", + 200, -1, "foo/bar", "test", map[string]string{"Foo4": "bar4"}) + + // chunked response with empty body + testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n0\r\nFoo5: bar5\r\n\r\n", + 200, -1, "text/html", "", map[string]string{"Foo5": "bar5"}) + + // chunked response with chunk extension + testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n3;ext\r\naaa\r\n0\r\nFoo6: bar6\r\n\r\n", + 200, -1, "text/html", "aaa", map[string]string{"Foo6": "bar6"}) - // zero chunked response - testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\nzzz", - 200, 0, "text/html", "", "zzz") } func TestResponseReadError(t *testing.T) { @@ -1922,8 +2141,13 @@ func TestResponseReadError(t *testing.T) { // empty body testResponseReadError(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: aaa\r\nContent-Length: 1234\r\n\r\n") - // short body + // invalid chunked body testResponseReadError(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: aaa\r\nContent-Length: 1234\r\n\r\nshort") + + // chunked body without end chunk + testResponseReadError(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: aaa\r\nTransfer-Encoding: chunked\r\n\r\nfoo") + + testResponseReadError(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: aaa\r\nTransfer-Encoding: chunked\r\n\r\n3\r\nfoo") } func testResponseReadError(t *testing.T, resp *Response, response string) { @@ -1934,12 +2158,12 @@ func testResponseReadError(t *testing.T, resp *Response, response string) { t.Fatalf("Expecting error for response=%q", response) } - testResponseReadSuccess(t, resp, "HTTP/1.1 303 Redisred sedfs sdf\r\nContent-Type: aaa\r\nContent-Length: 5\r\n\r\nHELLOaaa", - 303, 5, "aaa", "HELLO", "aaa") + testResponseReadSuccess(t, resp, "HTTP/1.1 303 Redisred sedfs sdf\r\nContent-Type: aaa\r\nContent-Length: 5\r\n\r\nHELLO", + 303, 5, "aaa", "HELLO", nil) } func testResponseReadSuccess(t *testing.T, resp *Response, response string, expectedStatusCode, expectedContentLength int, - expectedContenType, expectedBody, expectedTrailer string) { + expectedContentType, expectedBody string, expectedTrailer map[string]string) { r := bytes.NewBufferString(response) rb := bufio.NewReader(r) @@ -1948,11 +2172,11 @@ func testResponseReadSuccess(t *testing.T, resp *Response, response string, expe t.Fatalf("Unexpected error: %s", err) } - verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContenType) + verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType) if !bytes.Equal(resp.Body(), []byte(expectedBody)) { t.Fatalf("Unexpected body %q. Expected %q", resp.Body(), []byte(expectedBody)) } - verifyTrailer(t, rb, expectedTrailer) + verifyResponseTrailer(t, &resp.Header, expectedTrailer) } func TestReadBodyFixedSize(t *testing.T) { @@ -2109,28 +2333,24 @@ func testRequestPostArgsSuccess(t *testing.T, req *Request, s string, expectedAr func testReadBodyChunked(t *testing.T, bodySize int) { body := createFixedBody(bodySize) - chunkedBody := createChunkedBody(body) - expectedTrailer := []byte("chunked shit") - chunkedBody = append(chunkedBody, expectedTrailer...) + expectedTrailer := map[string]string{"Foo": "bar"} + chunkedBody := createChunkedBody(body, expectedTrailer, true) r := bytes.NewBuffer(chunkedBody) br := bufio.NewReader(r) - b, err := readBody(br, -1, 0, nil) + b, err := readBodyChunked(br, 0, nil) if err != nil { t.Fatalf("Unexpected error for bodySize=%d: %s. body=%q, chunkedBody=%q", bodySize, err, body, chunkedBody) } if !bytes.Equal(b, body) { t.Fatalf("Unexpected response read for bodySize=%d: %q. Expected %q. chunkedBody=%q", bodySize, b, body, chunkedBody) } - verifyTrailer(t, br, string(expectedTrailer)) + verifyTrailer(t, br, expectedTrailer, false) } func testReadBodyFixedSize(t *testing.T, bodySize int) { body := createFixedBody(bodySize) - expectedTrailer := []byte("traler aaaa") - bodyWithTrailer := append(body, expectedTrailer...) - - r := bytes.NewBuffer(bodyWithTrailer) + r := bytes.NewBuffer(body) br := bufio.NewReader(r) b, err := readBody(br, bodySize, 0, nil) if err != nil { @@ -2139,7 +2359,7 @@ func testReadBodyFixedSize(t *testing.T, bodySize int) { if !bytes.Equal(b, body) { t.Fatalf("Unexpected response read for bodySize=%d: %q. Expected %q", bodySize, b, body) } - verifyTrailer(t, br, string(expectedTrailer)) + verifyTrailer(t, br, nil, false) } func createFixedBody(bodySize int) []byte { @@ -2150,7 +2370,7 @@ func createFixedBody(bodySize int) []byte { return b } -func createChunkedBody(body []byte) []byte { +func createChunkedBody(body []byte, trailer map[string]string, withEnd bool) []byte { var b []byte chunkSize := 1 for len(body) > 0 { @@ -2163,7 +2383,17 @@ func createChunkedBody(body []byte) []byte { body = body[chunkSize:] chunkSize++ } - return append(b, []byte("0\r\n\r\n")...) + if withEnd { + b = append(b, "0\r\n"...) + for k, v := range trailer { + b = append(b, k...) + b = append(b, ": "...) + b = append(b, v...) + b = append(b, "\r\n"...) + } + b = append(b, "\r\n"...) + } + return b } func TestWriteMultipartForm(t *testing.T) { diff --git a/server_test.go b/server_test.go index b42197c985..2f7e6aef72 100644 --- a/server_test.go +++ b/server_test.go @@ -3609,7 +3609,7 @@ func TestStreamRequestBodyExceedMaxSize(t *testing.T) { } } -func TestStreamBodyReqestContentLength(t *testing.T) { +func TestStreamBodyRequestContentLength(t *testing.T) { t.Parallel() content := strings.Repeat("1", 1<<15) // 32K contentLength := len(content) @@ -3784,6 +3784,67 @@ func TestIncompleteBodyReturnsUnexpectedEOF(t *testing.T) { } } +func TestServerChunkedResponse(t *testing.T) { + t.Parallel() + + trailer := map[string]string{ + "AtEnd1": "1111", + "AtEnd2": "2222", + "AtEnd3": "3333", + } + + h := func(ctx *RequestCtx) { + ctx.Response.Header.DisableNormalizing() + ctx.Response.Header.Set("Transfer-Encoding", "chunked") + for k := range trailer { + err := ctx.Response.Header.AddTrailer(k) + if err != nil { + t.Errorf("unexpected error: %s", err) + } + } + ctx.Response.SetBodyStreamWriter(func(w *bufio.Writer) { + for i := 0; i < 3; i++ { + fmt.Fprintf(w, "message %d", i) + if err := w.Flush(); err != nil { + t.Errorf("unexpected error: %s", err) + } + time.Sleep(time.Second) + } + }) + for k, v := range trailer { + ctx.Response.Header.Set(k, v) + } + } + s := &Server{ + Handler: h, + } + + rw := &readWriter{} + rw.r.WriteString("GET / HTTP/1.1\r\nHost: test.com\r\n\r\n") + + if err := s.ServeConn(rw); err != nil { + t.Fatalf("Unexpected error from serveConn: %s", err) + } + + br := bufio.NewReader(&rw.w) + var resp Response + if err := resp.Read(br); err != nil { + t.Fatalf("Unexpected error when reading response: %s", err) + } + if resp.Header.ContentLength() != -1 { + t.Fatalf("Unexpected Content-Length %d. Expected %d", resp.Header.ContentLength(), -1) + } + if !bytes.Equal(resp.Body(), []byte("message 0"+"message 1"+"message 2")) { + t.Fatalf("Unexpected body %q. Expected %q", resp.Body(), "foobar") + } + for k, v := range trailer { + h := resp.Header.Peek(k) + if !bytes.Equal(resp.Header.Peek(k), []byte(v)) { + t.Fatalf("Unexpected trailer %s. Expected %s. Got %q", k, v, h) + } + } +} + func verifyResponse(t *testing.T, r *bufio.Reader, expectedStatusCode int, expectedContentType, expectedBody string) *Response { var resp Response if err := resp.Read(r); err != nil { diff --git a/streaming.go b/streaming.go index 1a3d748c3e..11750a9d25 100644 --- a/streaming.go +++ b/streaming.go @@ -10,10 +10,10 @@ import ( ) type requestStream struct { + header *RequestHeader prefetchedBytes *bytes.Reader reader *bufio.Reader totalBytesRead int - contentLength int chunkLeft int } @@ -22,18 +22,18 @@ func (rs *requestStream) Read(p []byte) (int, error) { n int err error ) - if rs.contentLength == -1 { + if rs.header.contentLength == -1 { if rs.chunkLeft == 0 { chunkSize, err := parseChunkSize(rs.reader) if err != nil { return 0, err } if chunkSize == 0 { - err = readCrLf(rs.reader) - if err == nil { - err = io.EOF + err = rs.header.ReadTrailer(rs.reader) + if err != nil && err != io.EOF { + return 0, err } - return 0, err + return 0, io.EOF } rs.chunkLeft = chunkSize } @@ -52,7 +52,7 @@ func (rs *requestStream) Read(p []byte) (int, error) { } return n, err } - if rs.totalBytesRead == rs.contentLength { + if rs.totalBytesRead == rs.header.contentLength { return 0, io.EOF } prefetchedSize := int(rs.prefetchedBytes.Size()) @@ -63,12 +63,12 @@ func (rs *requestStream) Read(p []byte) (int, error) { } n, err := rs.prefetchedBytes.Read(p) rs.totalBytesRead += n - if n == rs.contentLength { + if n == rs.header.contentLength { return n, io.EOF } return n, err } else { - left := rs.contentLength - rs.totalBytesRead + left := rs.header.contentLength - rs.totalBytesRead if len(p) > left { p = p[:left] } @@ -79,18 +79,17 @@ func (rs *requestStream) Read(p []byte) (int, error) { } } - if rs.totalBytesRead == rs.contentLength { + if rs.totalBytesRead == rs.header.contentLength { err = io.EOF } return n, err } -func acquireRequestStream(b *bytebufferpool.ByteBuffer, r *bufio.Reader, contentLength int) *requestStream { +func acquireRequestStream(b *bytebufferpool.ByteBuffer, r *bufio.Reader, h *RequestHeader) *requestStream { rs := requestStreamPool.Get().(*requestStream) rs.prefetchedBytes = bytes.NewReader(b.B) rs.reader = r - rs.contentLength = contentLength - + rs.header = h return rs } diff --git a/streaming_test.go b/streaming_test.go index 6066c39834..32f00035b5 100644 --- a/streaming_test.go +++ b/streaming_test.go @@ -3,6 +3,7 @@ package fasthttp import ( "bufio" "bytes" + "fmt" "io/ioutil" "sync" "testing" @@ -102,7 +103,7 @@ aaaaaaaaaa` func getChunkedTestEnv(t testing.TB) (*fasthttputil.InmemoryListener, []byte) { body := createFixedBody(128 * 1024) - chunkedBody := createChunkedBody(body) + chunkedBody := createChunkedBody(body, nil, true) testHandler := func(ctx *RequestCtx) { bodyBytes, err := ioutil.ReadAll(ctx.RequestBodyStream()) @@ -142,6 +143,70 @@ func getChunkedTestEnv(t testing.TB) (*fasthttputil.InmemoryListener, []byte) { return ln, formattedRequest } +func TestRequestStreamChunkedWithTrailer(t *testing.T) { + t.Parallel() + + body := createFixedBody(10) + expectedTrailer := map[string]string{ + "Foo": "footest", + "Bar": "bartest", + } + chunkedBody := createChunkedBody(body, expectedTrailer, true) + req := fmt.Sprintf(`POST / HTTP/1.1 +Host: example.com +Transfer-Encoding: chunked +Trailer: Foo, Bar + +%s +`, chunkedBody) + + ln := fasthttputil.NewInmemoryListener() + s := &Server{ + StreamRequestBody: true, + Handler: func(ctx *RequestCtx) { + all, err := ioutil.ReadAll(ctx.RequestBodyStream()) + if err != nil { + t.Errorf("unexpected error: %s", err) + } + if !bytes.Equal(all, body) { + t.Errorf("unexpected body %q. Expecting %q", all, body) + } + + for k, v := range expectedTrailer { + r := ctx.Request.Header.Peek(k) + if string(r) != v { + t.Errorf("unexpected trailer %s. Expecting %s. Got %q", k, v, r) + } + } + }, + } + + ch := make(chan struct{}) + go func() { + if err := s.Serve(ln); err != nil { + t.Errorf("unexpected error: %s", err) + } + close(ch) + }() + + conn, err := ln.Dial() + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if _, err = conn.Write([]byte(req)); err != nil { + t.Fatalf("unexpected error: %s", err) + } + if err := ln.Close(); err != nil { + t.Fatalf("error when closing listener: %s", err) + } + + select { + case <-ch: + case <-time.After(time.Second): + t.Fatal("timeout when waiting for the server to stop") + } +} + func TestRequestStream(t *testing.T) { t.Parallel() diff --git a/strings.go b/strings.go index e28eaac262..e4fb08bdac 100644 --- a/strings.go +++ b/strings.go @@ -20,31 +20,39 @@ var ( strColon = []byte(":") strColonSlashSlash = []byte("://") strColonSpace = []byte(": ") + strCommaSpace = []byte(", ") strGMT = []byte("GMT") strResponseContinue = []byte("HTTP/1.1 100 Continue\r\n\r\n") - strExpect = []byte(HeaderExpect) - strConnection = []byte(HeaderConnection) - strContentLength = []byte(HeaderContentLength) - strContentType = []byte(HeaderContentType) - strDate = []byte(HeaderDate) - strHost = []byte(HeaderHost) - strReferer = []byte(HeaderReferer) - strServer = []byte(HeaderServer) - strTransferEncoding = []byte(HeaderTransferEncoding) - strContentEncoding = []byte(HeaderContentEncoding) - strAcceptEncoding = []byte(HeaderAcceptEncoding) - strUserAgent = []byte(HeaderUserAgent) - strCookie = []byte(HeaderCookie) - strSetCookie = []byte(HeaderSetCookie) - strLocation = []byte(HeaderLocation) - strIfModifiedSince = []byte(HeaderIfModifiedSince) - strLastModified = []byte(HeaderLastModified) - strAcceptRanges = []byte(HeaderAcceptRanges) - strRange = []byte(HeaderRange) - strContentRange = []byte(HeaderContentRange) - strAuthorization = []byte(HeaderAuthorization) + strExpect = []byte(HeaderExpect) + strConnection = []byte(HeaderConnection) + strContentLength = []byte(HeaderContentLength) + strContentType = []byte(HeaderContentType) + strDate = []byte(HeaderDate) + strHost = []byte(HeaderHost) + strReferer = []byte(HeaderReferer) + strServer = []byte(HeaderServer) + strTransferEncoding = []byte(HeaderTransferEncoding) + strContentEncoding = []byte(HeaderContentEncoding) + strAcceptEncoding = []byte(HeaderAcceptEncoding) + strUserAgent = []byte(HeaderUserAgent) + strCookie = []byte(HeaderCookie) + strSetCookie = []byte(HeaderSetCookie) + strLocation = []byte(HeaderLocation) + strIfModifiedSince = []byte(HeaderIfModifiedSince) + strLastModified = []byte(HeaderLastModified) + strAcceptRanges = []byte(HeaderAcceptRanges) + strRange = []byte(HeaderRange) + strContentRange = []byte(HeaderContentRange) + strAuthorization = []byte(HeaderAuthorization) + strTE = []byte(HeaderTE) + strTrailer = []byte(HeaderTrailer) + strMaxForwards = []byte(HeaderMaxForwards) + strProxyConnection = []byte(HeaderProxyConnection) + strProxyAuthenticate = []byte(HeaderProxyAuthenticate) + strProxyAuthorization = []byte(HeaderProxyAuthorization) + strWWWAuthenticate = []byte(HeaderWWWAuthenticate) strCookieExpires = []byte("expires") strCookieDomain = []byte("domain")