package http
import (
)
var ErrLineTooLong = internal.ErrLineTooLong
type errorReader struct {
err error
}
func ( errorReader) ( []byte) ( int, error) {
return 0, .err
}
type byteReader struct {
b byte
done bool
}
func ( *byteReader) ( []byte) ( int, error) {
if .done {
return 0, io.EOF
}
if len() == 0 {
return 0, nil
}
.done = true
[0] = .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 ( any) ( *transferWriter, error) {
= &transferWriter{}
:= false
switch rr := .(type) {
case *Request:
if .ContentLength != 0 && .Body == nil {
return nil, fmt.Errorf("http: Request.ContentLength=%d with nil Body", .ContentLength)
}
.Method = valueOrDefault(.Method, "GET")
.Close = .Close
.TransferEncoding = .TransferEncoding
.Header = .Header
.Trailer = .Trailer
.Body = .Body
.BodyCloser = .Body
.ContentLength = .outgoingLength()
if .ContentLength < 0 && len(.TransferEncoding) == 0 && .shouldSendChunkedRequestBody() {
.TransferEncoding = []string{"chunked"}
}
if .ContentLength != 0 && !isKnownInMemoryReader(.Body) {
.FlushHeaders = true
}
= true
case *Response:
.IsResponse = true
if .Request != nil {
.Method = .Request.Method
}
.Body = .Body
.BodyCloser = .Body
.ContentLength = .ContentLength
.Close = .Close
.TransferEncoding = .TransferEncoding
.Header = .Header
.Trailer = .Trailer
= .ProtoAtLeast(1, 1)
.ResponseToHEAD = noResponseBodyExpected(.Method)
}
if .ResponseToHEAD {
.Body = nil
if chunked(.TransferEncoding) {
.ContentLength = -1
}
} else {
if ! || .Body == nil {
.TransferEncoding = nil
}
if chunked(.TransferEncoding) {
.ContentLength = -1
} else if .Body == nil {
.ContentLength = 0
}
}
if !chunked(.TransferEncoding) {
.Trailer = nil
}
return , nil
}
func ( *transferWriter) () bool {
if .ContentLength >= 0 || .Body == nil {
return false
}
if .Method == "CONNECT" {
return false
}
if requestMethodUsuallyLacksBody(.Method) {
.probeRequestBody()
return .Body != nil
}
return true
}
func ( *transferWriter) () {
.ByteReadCh = make(chan readResult, 1)
go func( io.Reader) {
var [1]byte
var readResult
.n, .err = .Read([:])
if .n == 1 {
.b = [0]
}
.ByteReadCh <-
close(.ByteReadCh)
}(.Body)
:= time.NewTimer(200 * time.Millisecond)
select {
case := <-.ByteReadCh:
.Stop()
if .n == 0 && .err == io.EOF {
.Body = nil
.ContentLength = 0
} else if .n == 1 {
if .err != nil {
.Body = io.MultiReader(&byteReader{b: .b}, errorReader{.err})
} else {
.Body = io.MultiReader(&byteReader{b: .b}, .Body)
}
} else if .err != nil {
.Body = errorReader{.err}
}
case <-.C:
.Body = io.MultiReader(finishAsyncByteRead{}, .Body)
.FlushHeaders = true
}
}
func ( string) bool {
return == "HEAD"
}
func ( *transferWriter) () bool {
if chunked(.TransferEncoding) {
return false
}
if .ContentLength > 0 {
return true
}
if .ContentLength < 0 {
return false
}
if .Method == "POST" || .Method == "PUT" || .Method == "PATCH" {
return true
}
if .ContentLength == 0 && isIdentity(.TransferEncoding) {
if .Method == "GET" || .Method == "HEAD" {
return false
}
return true
}
return false
}
func ( *transferWriter) ( io.Writer, *httptrace.ClientTrace) error {
if .Close && !hasToken(.Header.get("Connection"), "close") {
if , := io.WriteString(, "Connection: close\r\n"); != nil {
return
}
if != nil && .WroteHeaderField != nil {
.WroteHeaderField("Connection", []string{"close"})
}
}
if .shouldSendContentLength() {
if , := io.WriteString(, "Content-Length: "); != nil {
return
}
if , := io.WriteString(, strconv.FormatInt(.ContentLength, 10)+"\r\n"); != nil {
return
}
if != nil && .WroteHeaderField != nil {
.WroteHeaderField("Content-Length", []string{strconv.FormatInt(.ContentLength, 10)})
}
} else if chunked(.TransferEncoding) {
if , := io.WriteString(, "Transfer-Encoding: chunked\r\n"); != nil {
return
}
if != nil && .WroteHeaderField != nil {
.WroteHeaderField("Transfer-Encoding", []string{"chunked"})
}
}
if .Trailer != nil {
:= make([]string, 0, len(.Trailer))
for := range .Trailer {
= CanonicalHeaderKey()
switch {
case "Transfer-Encoding", "Trailer", "Content-Length":
return badStringError("invalid Trailer key", )
}
= append(, )
}
if len() > 0 {
sort.Strings()
if , := io.WriteString(, "Trailer: "+strings.Join(, ",")+"\r\n"); != nil {
return
}
if != nil && .WroteHeaderField != nil {
.WroteHeaderField("Trailer", )
}
}
}
return nil
}
func ( *transferWriter) ( io.Writer) ( error) {
var int64
:= false
defer func() {
if || .BodyCloser == nil {
return
}
if := .BodyCloser.Close(); != nil && == nil {
=
}
}()
if .Body != nil {
var = .unwrapBody()
if chunked(.TransferEncoding) {
if , := .(*bufio.Writer); && !.IsResponse {
= &internal.FlushAfterChunkWriter{Writer: }
}
:= internal.NewChunkedWriter()
_, = .doBodyCopy(, )
if == nil {
= .Close()
}
} else if .ContentLength == -1 {
:=
if .Method == "CONNECT" {
= bufioFlushWriter{}
}
, = .doBodyCopy(, )
} else {
, = .doBodyCopy(, io.LimitReader(, .ContentLength))
if != nil {
return
}
var int64
, = .doBodyCopy(io.Discard, )
+=
}
if != nil {
return
}
}
if .BodyCloser != nil {
= true
if := .BodyCloser.Close(); != nil {
return
}
}
if !.ResponseToHEAD && .ContentLength != -1 && .ContentLength != {
return fmt.Errorf("http: ContentLength=%d with Body length %d",
.ContentLength, )
}
if chunked(.TransferEncoding) {
if .Trailer != nil {
if := .Trailer.Write(); != nil {
return
}
}
_, = io.WriteString(, "\r\n")
}
return
}
func ( *transferWriter) ( io.Writer, io.Reader) ( int64, error) {
, = io.Copy(, )
if != nil && != io.EOF {
.bodyReadError =
}
return
}
func ( *transferWriter) () io.Reader {
if reflect.TypeOf(.Body) == nopCloserType {
return reflect.ValueOf(.Body).Field(0).Interface().(io.Reader)
}
if , := .Body.(*readTrackingBody); {
.didRead = true
return .ReadCloser
}
return .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 ( *transferReader) (, int) bool {
return .ProtoMajor > || (.ProtoMajor == && .ProtoMinor >= )
}
func ( int) bool {
switch {
case >= 100 && <= 199:
return false
case == 204:
return false
case == 304:
return false
}
return true
}
var (
suppressedHeaders304 = []string{"Content-Type", "Content-Length", "Transfer-Encoding"}
suppressedHeadersNoBody = []string{"Content-Length", "Transfer-Encoding"}
)
func ( int) []string {
switch {
case == 304:
return suppressedHeaders304
case !bodyAllowedForStatus():
return suppressedHeadersNoBody
}
return nil
}
func ( any, *bufio.Reader) ( error) {
:= &transferReader{RequestMethod: "GET"}
:= false
switch rr := .(type) {
case *Response:
.Header = .Header
.StatusCode = .StatusCode
.ProtoMajor = .ProtoMajor
.ProtoMinor = .ProtoMinor
.Close = shouldClose(.ProtoMajor, .ProtoMinor, .Header, true)
= true
if .Request != nil {
.RequestMethod = .Request.Method
}
case *Request:
.Header = .Header
.RequestMethod = .Method
.ProtoMajor = .ProtoMajor
.ProtoMinor = .ProtoMinor
.StatusCode = 200
.Close = .Close
default:
panic("unexpected type")
}
if .ProtoMajor == 0 && .ProtoMinor == 0 {
.ProtoMajor, .ProtoMinor = 1, 1
}
if := .parseTransferEncoding(); != nil {
return
}
, := fixLength(, .StatusCode, .RequestMethod, .Header, .Chunked)
if != nil {
return
}
if && .RequestMethod == "HEAD" {
if , := parseContentLength(.Header.get("Content-Length")); != nil {
return
} else {
.ContentLength =
}
} else {
.ContentLength =
}
.Trailer, = fixTrailer(.Header, .Chunked)
if != nil {
return
}
switch .(type) {
case *Response:
if == -1 && !.Chunked && bodyAllowedForStatus(.StatusCode) {
.Close = true
}
}
switch {
case .Chunked:
if noResponseBodyExpected(.RequestMethod) || !bodyAllowedForStatus(.StatusCode) {
.Body = NoBody
} else {
.Body = &body{src: internal.NewChunkedReader(), hdr: , r: , closing: .Close}
}
case == 0:
.Body = NoBody
case > 0:
.Body = &body{src: io.LimitReader(, ), closing: .Close}
default:
if .Close {
.Body = &body{src: , closing: .Close}
} else {
.Body = NoBody
}
}
switch rr := .(type) {
case *Request:
.Body = .Body
.ContentLength = .ContentLength
if .Chunked {
.TransferEncoding = []string{"chunked"}
}
.Close = .Close
.Trailer = .Trailer
case *Response:
.Body = .Body
.ContentLength = .ContentLength
if .Chunked {
.TransferEncoding = []string{"chunked"}
}
.Close = .Close
.Trailer = .Trailer
}
return nil
}
func ( []string) bool { return len() > 0 && [0] == "chunked" }
func ( []string) bool { return len() == 1 && [0] == "identity" }
type unsupportedTEError struct {
err string
}
func ( *unsupportedTEError) () string {
return .err
}
func ( error) bool {
, := .(*unsupportedTEError)
return
}
func ( *transferReader) () error {
, := .Header["Transfer-Encoding"]
if ! {
return nil
}
delete(.Header, "Transfer-Encoding")
if !.protoAtLeast(1, 1) {
return nil
}
if len() != 1 {
return &unsupportedTEError{fmt.Sprintf("too many transfer encodings: %q", )}
}
if !ascii.EqualFold(textproto.TrimString([0]), "chunked") {
return &unsupportedTEError{fmt.Sprintf("unsupported transfer encoding: %q", [0])}
}
delete(.Header, "Content-Length")
.Chunked = true
return nil
}
func ( bool, int, string, Header, bool) (int64, error) {
:= !
:= ["Content-Length"]
if len() > 1 {
:= textproto.TrimString([0])
for , := range [1:] {
if != textproto.TrimString() {
return 0, fmt.Errorf("http: message cannot contain multiple Content-Length headers; got %q", )
}
}
.Del("Content-Length")
.Add("Content-Length", )
= ["Content-Length"]
}
if noResponseBodyExpected() {
if && len() > 0 && !(len() == 1 && [0] == "0") {
return 0, fmt.Errorf("http: method cannot contain a Content-Length; got %q", )
}
return 0, nil
}
if /100 == 1 {
return 0, nil
}
switch {
case 204, 304:
return 0, nil
}
if {
return -1, nil
}
var string
if len() == 1 {
= textproto.TrimString([0])
}
if != "" {
, := parseContentLength()
if != nil {
return -1,
}
return , nil
}
.Del("Content-Length")
if {
return 0, nil
}
return -1, nil
}
func (, int, Header, bool) bool {
if < 1 {
return true
}
:= ["Connection"]
:= httpguts.HeaderValuesContainsToken(, "close")
if == 1 && == 0 {
return || !httpguts.HeaderValuesContainsToken(, "keep-alive")
}
if && {
.Del("Connection")
}
return
}
func ( Header, bool) (Header, error) {
, := ["Trailer"]
if ! {
return nil, nil
}
if ! {
return nil, nil
}
.Del("Trailer")
:= make(Header)
var error
for , := range {
foreachHeaderElement(, func( string) {
= CanonicalHeaderKey()
switch {
case "Transfer-Encoding", "Trailer", "Content-Length":
if == nil {
= badStringError("bad trailer key", )
return
}
}
[] = nil
})
}
if != nil {
return nil,
}
if len() == 0 {
return nil, nil
}
return , 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 ( *body) ( []byte) ( int, error) {
.mu.Lock()
defer .mu.Unlock()
if .closed {
return 0, ErrBodyReadAfterClose
}
return .readLocked()
}
func ( *body) ( []byte) ( int, error) {
if .sawEOF {
return 0, io.EOF
}
, = .src.Read()
if == io.EOF {
.sawEOF = true
if .hdr != nil {
if := .readTrailer(); != nil {
=
.sawEOF = false
.closed = true
}
.hdr = nil
} else {
if , := .src.(*io.LimitedReader); && .N > 0 {
= io.ErrUnexpectedEOF
}
}
}
if == nil && > 0 {
if , := .src.(*io.LimitedReader); && .N == 0 {
= io.EOF
.sawEOF = true
}
}
if .sawEOF && .onHitEOF != nil {
.onHitEOF()
}
return ,
}
var (
singleCRLF = []byte("\r\n")
doubleCRLF = []byte("\r\n\r\n")
)
func ( *bufio.Reader) bool {
for := 4; ; ++ {
, := .Peek()
if bytes.HasSuffix(, doubleCRLF) {
return true
}
if != nil {
break
}
}
return false
}
var errTrailerEOF = errors.New("http: unexpected EOF reading trailer")
func ( *body) () error {
, := .r.Peek(2)
if bytes.Equal(, singleCRLF) {
.r.Discard(2)
return nil
}
if len() < 2 {
return errTrailerEOF
}
if != nil {
return
}
if !seeUpcomingDoubleCRLF(.r) {
return errors.New("http: suspiciously long trailer after chunked body")
}
, := textproto.NewReader(.r).ReadMIMEHeader()
if != nil {
if == io.EOF {
return errTrailerEOF
}
return
}
switch rr := .hdr.(type) {
case *Request:
mergeSetHeader(&.Trailer, Header())
case *Response:
mergeSetHeader(&.Trailer, Header())
}
return nil
}
func ( *Header, Header) {
if * == nil {
* =
return
}
for , := range {
(*)[] =
}
}
func ( *body) () int64 {
if , := .src.(*io.LimitedReader); {
return .N
}
return -1
}
func ( *body) () error {
.mu.Lock()
defer .mu.Unlock()
if .closed {
return nil
}
var error
switch {
case .sawEOF:
case .hdr == nil && .closing:
case .doEarlyClose:
if , := .src.(*io.LimitedReader); && .N > maxPostHandlerReadBytes {
.earlyClose = true
} else {
var int64
, = io.CopyN(io.Discard, bodyLocked{}, maxPostHandlerReadBytes)
if == io.EOF {
= nil
}
if == maxPostHandlerReadBytes {
.earlyClose = true
}
}
default:
_, = io.Copy(io.Discard, bodyLocked{})
}
.closed = true
return
}
func ( *body) () bool {
.mu.Lock()
defer .mu.Unlock()
return .earlyClose
}
func ( *body) () bool {
.mu.Lock()
defer .mu.Unlock()
return !.sawEOF
}
func ( *body) ( func()) {
.mu.Lock()
defer .mu.Unlock()
.onHitEOF =
}
type bodyLocked struct {
b *body
}
func ( bodyLocked) ( []byte) ( int, error) {
if .b.closed {
return 0, ErrBodyReadAfterClose
}
return .b.readLocked()
}
func ( string) (int64, error) {
= textproto.TrimString()
if == "" {
return -1, nil
}
, := strconv.ParseUint(, 10, 63)
if != nil {
return 0, badStringError("bad Content-Length", )
}
return int64(), nil
}
type finishAsyncByteRead struct {
tw *transferWriter
}
func ( finishAsyncByteRead) ( []byte) ( int, error) {
if len() == 0 {
return
}
:= <-.tw.ByteReadCh
, = .n, .err
if == 1 {
[0] = .b
}
if == nil {
= io.EOF
}
return
}
var nopCloserType = reflect.TypeOf(io.NopCloser(nil))
func ( io.Reader) bool {
switch .(type) {
case *bytes.Reader, *bytes.Buffer, *strings.Reader:
return true
}
if reflect.TypeOf() == nopCloserType {
return (reflect.ValueOf().Field(0).Interface().(io.Reader))
}
if , := .(*readTrackingBody); {
return (.ReadCloser)
}
return false
}
type bufioFlushWriter struct{ w io.Writer }
func ( bufioFlushWriter) ( []byte) ( int, error) {
, = .w.Write()
if , := .w.(*bufio.Writer); > 0 && {
:= .Flush()
if != nil && == nil {
=
}
}
return
}