package http
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"net/http/httptrace"
"net/http/internal"
"net/http/internal/ascii"
"net/textproto"
"reflect"
"sort"
"strconv"
"strings"
"sync"
"time"
"golang.org/x/net/http/httpguts"
)
var ErrLineTooLong = internal .ErrLineTooLong
type errorReader struct {
err error
}
func (r errorReader ) Read (p []byte ) (n int , err error ) {
return 0 , r .err
}
type byteReader struct {
b byte
done bool
}
func (br *byteReader ) Read (p []byte ) (n int , err error ) {
if br .done {
return 0 , io .EOF
}
if len (p ) == 0 {
return 0 , nil
}
br .done = true
p [0 ] = br .b
return 1 , io .EOF
}
type transferWriter struct {
Method string
Body io .Reader
BodyCloser io .Closer
ResponseToHEAD bool
ContentLength int64
Close bool
TransferEncoding []string
Header Header
Trailer Header
IsResponse bool
bodyReadError error
FlushHeaders bool
ByteReadCh chan readResult
}
func newTransferWriter (r any ) (t *transferWriter , err error ) {
t = &transferWriter {}
atLeastHTTP11 := false
switch rr := r .(type ) {
case *Request :
if rr .ContentLength != 0 && rr .Body == nil {
return nil , fmt .Errorf ("http: Request.ContentLength=%d with nil Body" , rr .ContentLength )
}
t .Method = valueOrDefault (rr .Method , "GET" )
t .Close = rr .Close
t .TransferEncoding = rr .TransferEncoding
t .Header = rr .Header
t .Trailer = rr .Trailer
t .Body = rr .Body
t .BodyCloser = rr .Body
t .ContentLength = rr .outgoingLength ()
if t .ContentLength < 0 && len (t .TransferEncoding ) == 0 && t .shouldSendChunkedRequestBody () {
t .TransferEncoding = []string {"chunked" }
}
if t .ContentLength != 0 && !isKnownInMemoryReader (t .Body ) {
t .FlushHeaders = true
}
atLeastHTTP11 = true
case *Response :
t .IsResponse = true
if rr .Request != nil {
t .Method = rr .Request .Method
}
t .Body = rr .Body
t .BodyCloser = rr .Body
t .ContentLength = rr .ContentLength
t .Close = rr .Close
t .TransferEncoding = rr .TransferEncoding
t .Header = rr .Header
t .Trailer = rr .Trailer
atLeastHTTP11 = rr .ProtoAtLeast (1 , 1 )
t .ResponseToHEAD = noResponseBodyExpected (t .Method )
}
if t .ResponseToHEAD {
t .Body = nil
if chunked (t .TransferEncoding ) {
t .ContentLength = -1
}
} else {
if !atLeastHTTP11 || t .Body == nil {
t .TransferEncoding = nil
}
if chunked (t .TransferEncoding ) {
t .ContentLength = -1
} else if t .Body == nil {
t .ContentLength = 0
}
}
if !chunked (t .TransferEncoding ) {
t .Trailer = nil
}
return t , nil
}
func (t *transferWriter ) shouldSendChunkedRequestBody () bool {
if t .ContentLength >= 0 || t .Body == nil {
return false
}
if t .Method == "CONNECT" {
return false
}
if requestMethodUsuallyLacksBody (t .Method ) {
t .probeRequestBody ()
return t .Body != nil
}
return true
}
func (t *transferWriter ) probeRequestBody () {
t .ByteReadCh = make (chan readResult , 1 )
go func (body io .Reader ) {
var buf [1 ]byte
var rres readResult
rres .n , rres .err = body .Read (buf [:])
if rres .n == 1 {
rres .b = buf [0 ]
}
t .ByteReadCh <- rres
close (t .ByteReadCh )
}(t .Body )
timer := time .NewTimer (200 * time .Millisecond )
select {
case rres := <- t .ByteReadCh :
timer .Stop ()
if rres .n == 0 && rres .err == io .EOF {
t .Body = nil
t .ContentLength = 0
} else if rres .n == 1 {
if rres .err != nil {
t .Body = io .MultiReader (&byteReader {b : rres .b }, errorReader {rres .err })
} else {
t .Body = io .MultiReader (&byteReader {b : rres .b }, t .Body )
}
} else if rres .err != nil {
t .Body = errorReader {rres .err }
}
case <- timer .C :
t .Body = io .MultiReader (finishAsyncByteRead {t }, t .Body )
t .FlushHeaders = true
}
}
func noResponseBodyExpected (requestMethod string ) bool {
return requestMethod == "HEAD"
}
func (t *transferWriter ) shouldSendContentLength () bool {
if chunked (t .TransferEncoding ) {
return false
}
if t .ContentLength > 0 {
return true
}
if t .ContentLength < 0 {
return false
}
if t .Method == "POST" || t .Method == "PUT" || t .Method == "PATCH" {
return true
}
if t .ContentLength == 0 && isIdentity (t .TransferEncoding ) {
if t .Method == "GET" || t .Method == "HEAD" {
return false
}
return true
}
return false
}
func (t *transferWriter ) writeHeader (w io .Writer , trace *httptrace .ClientTrace ) error {
if t .Close && !hasToken (t .Header .get ("Connection" ), "close" ) {
if _ , err := io .WriteString (w , "Connection: close\r\n" ); err != nil {
return err
}
if trace != nil && trace .WroteHeaderField != nil {
trace .WroteHeaderField ("Connection" , []string {"close" })
}
}
if t .shouldSendContentLength () {
if _ , err := io .WriteString (w , "Content-Length: " ); err != nil {
return err
}
if _ , err := io .WriteString (w , strconv .FormatInt (t .ContentLength , 10 )+"\r\n" ); err != nil {
return err
}
if trace != nil && trace .WroteHeaderField != nil {
trace .WroteHeaderField ("Content-Length" , []string {strconv .FormatInt (t .ContentLength , 10 )})
}
} else if chunked (t .TransferEncoding ) {
if _ , err := io .WriteString (w , "Transfer-Encoding: chunked\r\n" ); err != nil {
return err
}
if trace != nil && trace .WroteHeaderField != nil {
trace .WroteHeaderField ("Transfer-Encoding" , []string {"chunked" })
}
}
if t .Trailer != nil {
keys := make ([]string , 0 , len (t .Trailer ))
for k := range t .Trailer {
k = CanonicalHeaderKey (k )
switch k {
case "Transfer-Encoding" , "Trailer" , "Content-Length" :
return badStringError ("invalid Trailer key" , k )
}
keys = append (keys , k )
}
if len (keys ) > 0 {
sort .Strings (keys )
if _ , err := io .WriteString (w , "Trailer: " +strings .Join (keys , "," )+"\r\n" ); err != nil {
return err
}
if trace != nil && trace .WroteHeaderField != nil {
trace .WroteHeaderField ("Trailer" , keys )
}
}
}
return nil
}
func (t *transferWriter ) writeBody (w io .Writer ) (err error ) {
var ncopy int64
closed := false
defer func () {
if closed || t .BodyCloser == nil {
return
}
if closeErr := t .BodyCloser .Close (); closeErr != nil && err == nil {
err = closeErr
}
}()
if t .Body != nil {
var body = t .unwrapBody ()
if chunked (t .TransferEncoding ) {
if bw , ok := w .(*bufio .Writer ); ok && !t .IsResponse {
w = &internal .FlushAfterChunkWriter {Writer : bw }
}
cw := internal .NewChunkedWriter (w )
_, err = t .doBodyCopy (cw , body )
if err == nil {
err = cw .Close ()
}
} else if t .ContentLength == -1 {
dst := w
if t .Method == "CONNECT" {
dst = bufioFlushWriter {dst }
}
ncopy , err = t .doBodyCopy (dst , body )
} else {
ncopy , err = t .doBodyCopy (w , io .LimitReader (body , t .ContentLength ))
if err != nil {
return err
}
var nextra int64
nextra , err = t .doBodyCopy (io .Discard , body )
ncopy += nextra
}
if err != nil {
return err
}
}
if t .BodyCloser != nil {
closed = true
if err := t .BodyCloser .Close (); err != nil {
return err
}
}
if !t .ResponseToHEAD && t .ContentLength != -1 && t .ContentLength != ncopy {
return fmt .Errorf ("http: ContentLength=%d with Body length %d" ,
t .ContentLength , ncopy )
}
if chunked (t .TransferEncoding ) {
if t .Trailer != nil {
if err := t .Trailer .Write (w ); err != nil {
return err
}
}
_, err = io .WriteString (w , "\r\n" )
}
return err
}
func (t *transferWriter ) doBodyCopy (dst io .Writer , src io .Reader ) (n int64 , err error ) {
n , err = io .Copy (dst , src )
if err != nil && err != io .EOF {
t .bodyReadError = err
}
return
}
func (t *transferWriter ) unwrapBody () io .Reader {
if r , ok := unwrapNopCloser (t .Body ); ok {
return r
}
if r , ok := t .Body .(*readTrackingBody ); ok {
r .didRead = true
return r .ReadCloser
}
return t .Body
}
type transferReader struct {
Header Header
StatusCode int
RequestMethod string
ProtoMajor int
ProtoMinor int
Body io .ReadCloser
ContentLength int64
Chunked bool
Close bool
Trailer Header
}
func (t *transferReader ) protoAtLeast (m , n int ) bool {
return t .ProtoMajor > m || (t .ProtoMajor == m && t .ProtoMinor >= n )
}
func bodyAllowedForStatus (status int ) bool {
switch {
case status >= 100 && status <= 199 :
return false
case status == 204 :
return false
case status == 304 :
return false
}
return true
}
var (
suppressedHeaders304 = []string {"Content-Type" , "Content-Length" , "Transfer-Encoding" }
suppressedHeadersNoBody = []string {"Content-Length" , "Transfer-Encoding" }
excludedHeadersNoBody = map [string ]bool {"Content-Length" : true , "Transfer-Encoding" : true }
)
func suppressedHeaders (status int ) []string {
switch {
case status == 304 :
return suppressedHeaders304
case !bodyAllowedForStatus (status ):
return suppressedHeadersNoBody
}
return nil
}
func readTransfer (msg any , r *bufio .Reader ) (err error ) {
t := &transferReader {RequestMethod : "GET" }
isResponse := false
switch rr := msg .(type ) {
case *Response :
t .Header = rr .Header
t .StatusCode = rr .StatusCode
t .ProtoMajor = rr .ProtoMajor
t .ProtoMinor = rr .ProtoMinor
t .Close = shouldClose (t .ProtoMajor , t .ProtoMinor , t .Header , true )
isResponse = true
if rr .Request != nil {
t .RequestMethod = rr .Request .Method
}
case *Request :
t .Header = rr .Header
t .RequestMethod = rr .Method
t .ProtoMajor = rr .ProtoMajor
t .ProtoMinor = rr .ProtoMinor
t .StatusCode = 200
t .Close = rr .Close
default :
panic ("unexpected type" )
}
if t .ProtoMajor == 0 && t .ProtoMinor == 0 {
t .ProtoMajor , t .ProtoMinor = 1 , 1
}
if err := t .parseTransferEncoding (); err != nil {
return err
}
realLength , err := fixLength (isResponse , t .StatusCode , t .RequestMethod , t .Header , t .Chunked )
if err != nil {
return err
}
if isResponse && t .RequestMethod == "HEAD" {
if n , err := parseContentLength (t .Header .get ("Content-Length" )); err != nil {
return err
} else {
t .ContentLength = n
}
} else {
t .ContentLength = realLength
}
t .Trailer , err = fixTrailer (t .Header , t .Chunked )
if err != nil {
return err
}
switch msg .(type ) {
case *Response :
if realLength == -1 && !t .Chunked && bodyAllowedForStatus (t .StatusCode ) {
t .Close = true
}
}
switch {
case t .Chunked :
if isResponse && (noResponseBodyExpected (t .RequestMethod ) || !bodyAllowedForStatus (t .StatusCode )) {
t .Body = NoBody
} else {
t .Body = &body {src : internal .NewChunkedReader (r ), hdr : msg , r : r , closing : t .Close }
}
case realLength == 0 :
t .Body = NoBody
case realLength > 0 :
t .Body = &body {src : io .LimitReader (r , realLength ), closing : t .Close }
default :
if t .Close {
t .Body = &body {src : r , closing : t .Close }
} else {
t .Body = NoBody
}
}
switch rr := msg .(type ) {
case *Request :
rr .Body = t .Body
rr .ContentLength = t .ContentLength
if t .Chunked {
rr .TransferEncoding = []string {"chunked" }
}
rr .Close = t .Close
rr .Trailer = t .Trailer
case *Response :
rr .Body = t .Body
rr .ContentLength = t .ContentLength
if t .Chunked {
rr .TransferEncoding = []string {"chunked" }
}
rr .Close = t .Close
rr .Trailer = t .Trailer
}
return nil
}
func chunked (te []string ) bool { return len (te ) > 0 && te [0 ] == "chunked" }
func isIdentity (te []string ) bool { return len (te ) == 1 && te [0 ] == "identity" }
type unsupportedTEError struct {
err string
}
func (uste *unsupportedTEError ) Error () string {
return uste .err
}
func isUnsupportedTEError (err error ) bool {
_ , ok := err .(*unsupportedTEError )
return ok
}
func (t *transferReader ) parseTransferEncoding () error {
raw , present := t .Header ["Transfer-Encoding" ]
if !present {
return nil
}
delete (t .Header , "Transfer-Encoding" )
if !t .protoAtLeast (1 , 1 ) {
return nil
}
if len (raw ) != 1 {
return &unsupportedTEError {fmt .Sprintf ("too many transfer encodings: %q" , raw )}
}
if !ascii .EqualFold (raw [0 ], "chunked" ) {
return &unsupportedTEError {fmt .Sprintf ("unsupported transfer encoding: %q" , raw [0 ])}
}
delete (t .Header , "Content-Length" )
t .Chunked = true
return nil
}
func fixLength (isResponse bool , status int , requestMethod string , header Header , chunked bool ) (int64 , error ) {
isRequest := !isResponse
contentLens := header ["Content-Length" ]
if len (contentLens ) > 1 {
first := textproto .TrimString (contentLens [0 ])
for _ , ct := range contentLens [1 :] {
if first != textproto .TrimString (ct ) {
return 0 , fmt .Errorf ("http: message cannot contain multiple Content-Length headers; got %q" , contentLens )
}
}
header .Del ("Content-Length" )
header .Add ("Content-Length" , first )
contentLens = header ["Content-Length" ]
}
if isResponse && noResponseBodyExpected (requestMethod ) {
return 0 , nil
}
if status /100 == 1 {
return 0 , nil
}
switch status {
case 204 , 304 :
return 0 , nil
}
if chunked {
return -1 , nil
}
var cl string
if len (contentLens ) == 1 {
cl = textproto .TrimString (contentLens [0 ])
}
if cl != "" {
n , err := parseContentLength (cl )
if err != nil {
return -1 , err
}
return n , nil
}
header .Del ("Content-Length" )
if isRequest {
return 0 , nil
}
return -1 , nil
}
func shouldClose (major , minor int , header Header , removeCloseHeader bool ) bool {
if major < 1 {
return true
}
conv := header ["Connection" ]
hasClose := httpguts .HeaderValuesContainsToken (conv , "close" )
if major == 1 && minor == 0 {
return hasClose || !httpguts .HeaderValuesContainsToken (conv , "keep-alive" )
}
if hasClose && removeCloseHeader {
header .Del ("Connection" )
}
return hasClose
}
func fixTrailer (header Header , chunked bool ) (Header , error ) {
vv , ok := header ["Trailer" ]
if !ok {
return nil , nil
}
if !chunked {
return nil , nil
}
header .Del ("Trailer" )
trailer := make (Header )
var err error
for _ , v := range vv {
foreachHeaderElement (v , func (key string ) {
key = CanonicalHeaderKey (key )
switch key {
case "Transfer-Encoding" , "Trailer" , "Content-Length" :
if err == nil {
err = badStringError ("bad trailer key" , key )
return
}
}
trailer [key ] = nil
})
}
if err != nil {
return nil , err
}
if len (trailer ) == 0 {
return nil , nil
}
return trailer , nil
}
type body struct {
src io .Reader
hdr any
r *bufio .Reader
closing bool
doEarlyClose bool
mu sync .Mutex
sawEOF bool
closed bool
earlyClose bool
onHitEOF func ()
}
var ErrBodyReadAfterClose = errors .New ("http: invalid Read on closed Body" )
func (b *body ) Read (p []byte ) (n int , err error ) {
b .mu .Lock ()
defer b .mu .Unlock ()
if b .closed {
return 0 , ErrBodyReadAfterClose
}
return b .readLocked (p )
}
func (b *body ) readLocked (p []byte ) (n int , err error ) {
if b .sawEOF {
return 0 , io .EOF
}
n , err = b .src .Read (p )
if err == io .EOF {
b .sawEOF = true
if b .hdr != nil {
if e := b .readTrailer (); e != nil {
err = e
b .sawEOF = false
b .closed = true
}
b .hdr = nil
} else {
if lr , ok := b .src .(*io .LimitedReader ); ok && lr .N > 0 {
err = io .ErrUnexpectedEOF
}
}
}
if err == nil && n > 0 {
if lr , ok := b .src .(*io .LimitedReader ); ok && lr .N == 0 {
err = io .EOF
b .sawEOF = true
}
}
if b .sawEOF && b .onHitEOF != nil {
b .onHitEOF ()
}
return n , err
}
var (
singleCRLF = []byte ("\r\n" )
doubleCRLF = []byte ("\r\n\r\n" )
)
func seeUpcomingDoubleCRLF (r *bufio .Reader ) bool {
for peekSize := 4 ; ; peekSize ++ {
buf , err := r .Peek (peekSize )
if bytes .HasSuffix (buf , doubleCRLF ) {
return true
}
if err != nil {
break
}
}
return false
}
var errTrailerEOF = errors .New ("http: unexpected EOF reading trailer" )
func (b *body ) readTrailer () error {
buf , err := b .r .Peek (2 )
if bytes .Equal (buf , singleCRLF ) {
b .r .Discard (2 )
return nil
}
if len (buf ) < 2 {
return errTrailerEOF
}
if err != nil {
return err
}
if !seeUpcomingDoubleCRLF (b .r ) {
return errors .New ("http: suspiciously long trailer after chunked body" )
}
hdr , err := textproto .NewReader (b .r ).ReadMIMEHeader ()
if err != nil {
if err == io .EOF {
return errTrailerEOF
}
return err
}
switch rr := b .hdr .(type ) {
case *Request :
mergeSetHeader (&rr .Trailer , Header (hdr ))
case *Response :
mergeSetHeader (&rr .Trailer , Header (hdr ))
}
return nil
}
func mergeSetHeader (dst *Header , src Header ) {
if *dst == nil {
*dst = src
return
}
for k , vv := range src {
(*dst )[k ] = vv
}
}
func (b *body ) unreadDataSizeLocked () int64 {
if lr , ok := b .src .(*io .LimitedReader ); ok {
return lr .N
}
return -1
}
func (b *body ) Close () error {
b .mu .Lock ()
defer b .mu .Unlock ()
if b .closed {
return nil
}
var err error
switch {
case b .sawEOF :
case b .hdr == nil && b .closing :
case b .doEarlyClose :
if lr , ok := b .src .(*io .LimitedReader ); ok && lr .N > maxPostHandlerReadBytes {
b .earlyClose = true
} else {
var n int64
n , err = io .CopyN (io .Discard , bodyLocked {b }, maxPostHandlerReadBytes )
if err == io .EOF {
err = nil
}
if n == maxPostHandlerReadBytes {
b .earlyClose = true
}
}
default :
_, err = io .Copy (io .Discard , bodyLocked {b })
}
b .closed = true
return err
}
func (b *body ) didEarlyClose () bool {
b .mu .Lock ()
defer b .mu .Unlock ()
return b .earlyClose
}
func (b *body ) bodyRemains () bool {
b .mu .Lock ()
defer b .mu .Unlock ()
return !b .sawEOF
}
func (b *body ) registerOnHitEOF (fn func ()) {
b .mu .Lock ()
defer b .mu .Unlock ()
b .onHitEOF = fn
}
type bodyLocked struct {
b *body
}
func (bl bodyLocked ) Read (p []byte ) (n int , err error ) {
if bl .b .closed {
return 0 , ErrBodyReadAfterClose
}
return bl .b .readLocked (p )
}
func parseContentLength (cl string ) (int64 , error ) {
cl = textproto .TrimString (cl )
if cl == "" {
return -1 , nil
}
n , err := strconv .ParseUint (cl , 10 , 63 )
if err != nil {
return 0 , badStringError ("bad Content-Length" , cl )
}
return int64 (n ), nil
}
type finishAsyncByteRead struct {
tw *transferWriter
}
func (fr finishAsyncByteRead ) Read (p []byte ) (n int , err error ) {
if len (p ) == 0 {
return
}
rres := <-fr .tw .ByteReadCh
n , err = rres .n , rres .err
if n == 1 {
p [0 ] = rres .b
}
if err == nil {
err = io .EOF
}
return
}
var nopCloserType = reflect .TypeOf (io .NopCloser (nil ))
var nopCloserWriterToType = reflect .TypeOf (io .NopCloser (struct {
io .Reader
io .WriterTo
}{}))
func unwrapNopCloser (r io .Reader ) (underlyingReader io .Reader , isNopCloser bool ) {
switch reflect .TypeOf (r ) {
case nopCloserType , nopCloserWriterToType :
return reflect .ValueOf (r ).Field (0 ).Interface ().(io .Reader ), true
default :
return nil , false
}
}
func isKnownInMemoryReader (r io .Reader ) bool {
switch r .(type ) {
case *bytes .Reader , *bytes .Buffer , *strings .Reader :
return true
}
if r , ok := unwrapNopCloser (r ); ok {
return isKnownInMemoryReader (r )
}
if r , ok := r .(*readTrackingBody ); ok {
return isKnownInMemoryReader (r .ReadCloser )
}
return false
}
type bufioFlushWriter struct { w io .Writer }
func (fw bufioFlushWriter ) Write (p []byte ) (n int , err error ) {
n , err = fw .w .Write (p )
if bw , ok := fw .w .(*bufio .Writer ); n > 0 && ok {
ferr := bw .Flush ()
if ferr != nil && err == nil {
err = ferr
}
}
return
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .