package http3

import (
	
	
	
	
	
	
	
	
	
	

	
	
	

	
	
	
	
)

const bodyCopyBufferSize = 8 * 1024

type requestWriter struct {
	mutex     sync.Mutex
	encoder   *qpack.Encoder
	headerBuf *bytes.Buffer
}

func newRequestWriter() *requestWriter {
	 := &bytes.Buffer{}
	 := qpack.NewEncoder()
	return &requestWriter{
		encoder:   ,
		headerBuf: ,
	}
}

func ( *requestWriter) ( io.Writer,  *http.Request,  bool,  quic.StreamID,  qlogwriter.Recorder) error {
	 := &bytes.Buffer{}
	if  := .writeHeaders(, , , , );  != nil {
		return 
	}
	if ,  := .Write(.Bytes());  != nil {
		return 
	}
	 := httptrace.ContextClientTrace(.Context())
	traceWroteHeaders()
	return nil
}

func ( *requestWriter) ( io.Writer,  *http.Request,  bool,  quic.StreamID,  qlogwriter.Recorder) error {
	.mutex.Lock()
	defer .mutex.Unlock()
	defer .encoder.Close()
	defer .headerBuf.Reset()

	var  string
	if len(.Trailer) > 0 {
		 := make([]string, 0, len(.Trailer))
		for  := range .Trailer {
			if httpguts.ValidTrailerHeader() {
				 = append(, )
			}
		}
		 = strings.Join(, ", ")
	}

	,  := .encodeHeaders(, , , actualContentLength(),  != nil)
	if  != nil {
		return 
	}

	 := make([]byte, 0, 128)
	 = (&headersFrame{Length: uint64(.headerBuf.Len())}).Append()
	if  != nil {
		qlogCreatedHeadersFrame(, , len()+.headerBuf.Len(), .headerBuf.Len(), )
	}
	if ,  := .Write();  != nil {
		return 
	}
	_,  = .Write(.headerBuf.Bytes())
	return 
}

func isExtendedConnectRequest( *http.Request) bool {
	return .Method == http.MethodConnect && .Proto != "" && .Proto != "HTTP/1.1"
}

// copied from net/transport.go
// Modified to support Extended CONNECT:
// Contrary to what the godoc for the http.Request says,
// we do respect the Proto field if the method is CONNECT.
//
// The returned header fields are only set if doQlog is true.
func ( *requestWriter) ( *http.Request,  bool,  string,  int64,  bool) ([]qlog.HeaderField, error) {
	 := .Host
	if  == "" {
		 = .URL.Host
	}
	,  := httpguts.PunycodeHostPort()
	if  != nil {
		return nil, 
	}
	if !httpguts.ValidHostHeader() {
		return nil, errors.New("http3: invalid Host header")
	}

	// http.NewRequest sets this field to HTTP/1.1
	 := isExtendedConnectRequest()

	var  string
	if .Method != http.MethodConnect ||  {
		 = .URL.RequestURI()
		if !validPseudoPath() {
			 := 
			 = strings.TrimPrefix(, .URL.Scheme+"://"+)
			if !validPseudoPath() {
				if .URL.Opaque != "" {
					return nil, fmt.Errorf("invalid request :path %q from URL.Opaque = %q", , .URL.Opaque)
				} else {
					return nil, fmt.Errorf("invalid request :path %q", )
				}
			}
		}
	}

	// Check for any invalid headers and return an error before we
	// potentially pollute our hpack state. (We want to be able to
	// continue to reuse the hpack encoder for future requests)
	for ,  := range .Header {
		if !httpguts.ValidHeaderFieldName() {
			return nil, fmt.Errorf("invalid HTTP header name %q", )
		}
		for ,  := range  {
			if !httpguts.ValidHeaderFieldValue() {
				return nil, fmt.Errorf("invalid HTTP header value %q for header %q", , )
			}
		}
	}

	 := func( func(,  string)) {
		// 8.1.2.3 Request Pseudo-Header Fields
		// The :path pseudo-header field includes the path and query parts of the
		// target URI (the path-absolute production and optionally a '?' character
		// followed by the query production (see Sections 3.3 and 3.4 of
		// [RFC3986]).
		(":authority", )
		(":method", .Method)
		if .Method != http.MethodConnect ||  {
			(":path", )
			(":scheme", .URL.Scheme)
		}
		if  {
			(":protocol", .Proto)
		}
		if  != "" {
			("trailer", )
		}

		var  bool
		for ,  := range .Header {
			if strings.EqualFold(, "host") || strings.EqualFold(, "content-length") {
				// Host is :authority, already sent.
				// Content-Length is automatic, set below.
				continue
			} else if strings.EqualFold(, "connection") || strings.EqualFold(, "proxy-connection") ||
				strings.EqualFold(, "transfer-encoding") || strings.EqualFold(, "upgrade") ||
				strings.EqualFold(, "keep-alive") {
				// Per 8.1.2.2 Connection-Specific Header
				// Fields, don't send connection-specific
				// fields. We have already checked if any
				// are error-worthy so just ignore the rest.
				continue
			} else if strings.EqualFold(, "user-agent") {
				// Match Go's http1 behavior: at most one
				// User-Agent. If set to nil or empty string,
				// then omit it. Otherwise if not mentioned,
				// include the default (below).
				 = true
				if len() < 1 {
					continue
				}
				 = [:1]
				if [0] == "" {
					continue
				}

			}

			for ,  := range  {
				(, )
			}
		}
		if shouldSendReqContentLength(.Method, ) {
			("content-length", strconv.FormatInt(, 10))
		}
		if  {
			("accept-encoding", "gzip")
		}
		if ! {
			("user-agent", defaultUserAgent)
		}
	}

	// Do a first pass over the headers counting bytes to ensure
	// we don't exceed cc.peerMaxHeaderListSize. This is done as a
	// separate pass before encoding the headers to prevent
	// modifying the hpack state.
	 := uint64(0)
	(func(,  string) {
		 := hpack.HeaderField{Name: , Value: }
		 += uint64(.Size())
	})

	// TODO: check maximum header list size
	// if hlSize > cc.peerMaxHeaderListSize {
	// 	return errRequestHeaderListSize
	// }

	 := httptrace.ContextClientTrace(.Context())
	 := traceHasWroteHeaderField()

	// Header list size is ok. Write the headers.
	var  []qlog.HeaderField
	if  {
		 = make([]qlog.HeaderField, 0, len(.Header))
	}
	(func(,  string) {
		 = strings.ToLower()
		.encoder.WriteField(qpack.HeaderField{Name: , Value: })
		if  {
			traceWroteHeaderField(, , )
		}
		if  {
			 = append(, qlog.HeaderField{Name: , Value: })
		}
	})

	return , nil
}

// authorityAddr returns a given authority (a host/IP, or host:port / ip:port)
// and returns a host:port. The port 443 is added if needed.
func authorityAddr( string) ( string) {
	, ,  := net.SplitHostPort()
	if  != nil { // authority didn't have a port
		 = "443"
		 = 
	}
	if ,  := idna.ToASCII();  == nil {
		 = 
	}
	// IPv6 address literal, without a port:
	if strings.HasPrefix(, "[") && strings.HasSuffix(, "]") {
		return  + ":" + 
	}
	return net.JoinHostPort(, )
}

// validPseudoPath reports whether v is a valid :path pseudo-header
// value. It must be either:
//
//	*) a non-empty string starting with '/'
//	*) the string '*', for OPTIONS requests.
//
// For now this is only used a quick check for deciding when to clean
// up Opaque URLs before sending requests from the Transport.
// See golang.org/issue/16847
//
// We used to enforce that the path also didn't start with "//", but
// Google's GFE accepts such paths and Chrome sends them, so ignore
// that part of the spec. See golang.org/issue/19103.
func validPseudoPath( string) bool {
	return (len() > 0 && [0] == '/') ||  == "*"
}

// actualContentLength returns a sanitized version of
// req.ContentLength, where 0 actually means zero (not unknown) and -1
// means unknown.
func actualContentLength( *http.Request) int64 {
	if .Body == nil {
		return 0
	}
	if .ContentLength != 0 {
		return .ContentLength
	}
	return -1
}

// shouldSendReqContentLength reports whether the http2.Transport should send
// a "content-length" request header. This logic is basically a copy of the net/http
// transferWriter.shouldSendContentLength.
// The contentLength is the corrected contentLength (so 0 means actually 0, not unknown).
// -1 means unknown.
func shouldSendReqContentLength( string,  int64) bool {
	if  > 0 {
		return true
	}
	if  < 0 {
		return false
	}
	// For zero bodies, whether we send a content-length depends on the method.
	// It also kinda doesn't matter for http2 either way, with END_STREAM.
	switch  {
	case "POST", "PUT", "PATCH":
		return true
	default:
		return false
	}
}

// WriteRequestTrailer writes HTTP trailers to the stream.
// It should be called after the request body has been fully written.
func ( *requestWriter) ( io.Writer,  *http.Request,  quic.StreamID,  qlogwriter.Recorder) error {
	,  := writeTrailers(, .Trailer, , )
	return 
}