github.com/sagernet/quic-go@v0.43.1-beta.1/http3_ech/request_writer.go (about) 1 package http3 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "net" 9 "net/http" 10 "strconv" 11 "strings" 12 "sync" 13 14 "github.com/quic-go/qpack" 15 "github.com/sagernet/quic-go/ech" 16 "golang.org/x/net/http/httpguts" 17 "golang.org/x/net/http2/hpack" 18 "golang.org/x/net/idna" 19 ) 20 21 const bodyCopyBufferSize = 8 * 1024 22 23 type requestWriter struct { 24 mutex sync.Mutex 25 encoder *qpack.Encoder 26 headerBuf *bytes.Buffer 27 } 28 29 func newRequestWriter() *requestWriter { 30 headerBuf := &bytes.Buffer{} 31 encoder := qpack.NewEncoder(headerBuf) 32 return &requestWriter{ 33 encoder: encoder, 34 headerBuf: headerBuf, 35 } 36 } 37 38 func (w *requestWriter) WriteRequestHeader(str quic.Stream, req *http.Request, gzip bool) error { 39 // TODO: figure out how to add support for trailers 40 buf := &bytes.Buffer{} 41 if err := w.writeHeaders(buf, req, gzip); err != nil { 42 return err 43 } 44 _, err := str.Write(buf.Bytes()) 45 return err 46 } 47 48 func (w *requestWriter) writeHeaders(wr io.Writer, req *http.Request, gzip bool) error { 49 w.mutex.Lock() 50 defer w.mutex.Unlock() 51 defer w.encoder.Close() 52 defer w.headerBuf.Reset() 53 54 if err := w.encodeHeaders(req, gzip, "", actualContentLength(req)); err != nil { 55 return err 56 } 57 58 b := make([]byte, 0, 128) 59 b = (&headersFrame{Length: uint64(w.headerBuf.Len())}).Append(b) 60 if _, err := wr.Write(b); err != nil { 61 return err 62 } 63 _, err := wr.Write(w.headerBuf.Bytes()) 64 return err 65 } 66 67 func isExtendedConnectRequest(req *http.Request) bool { 68 return req.Method == http.MethodConnect && req.Proto != "" && req.Proto != "HTTP/1.1" 69 } 70 71 // copied from net/transport.go 72 // Modified to support Extended CONNECT: 73 // Contrary to what the godoc for the http.Request says, 74 // we do respect the Proto field if the method is CONNECT. 75 func (w *requestWriter) encodeHeaders(req *http.Request, addGzipHeader bool, trailers string, contentLength int64) error { 76 host := req.Host 77 if host == "" { 78 host = req.URL.Host 79 } 80 host, err := httpguts.PunycodeHostPort(host) 81 if err != nil { 82 return err 83 } 84 if !httpguts.ValidHostHeader(host) { 85 return errors.New("http3: invalid Host header") 86 } 87 88 // http.NewRequest sets this field to HTTP/1.1 89 isExtendedConnect := isExtendedConnectRequest(req) 90 91 var path string 92 if req.Method != http.MethodConnect || isExtendedConnect { 93 path = req.URL.RequestURI() 94 if !validPseudoPath(path) { 95 orig := path 96 path = strings.TrimPrefix(path, req.URL.Scheme+"://"+host) 97 if !validPseudoPath(path) { 98 if req.URL.Opaque != "" { 99 return fmt.Errorf("invalid request :path %q from URL.Opaque = %q", orig, req.URL.Opaque) 100 } else { 101 return fmt.Errorf("invalid request :path %q", orig) 102 } 103 } 104 } 105 } 106 107 // Check for any invalid headers and return an error before we 108 // potentially pollute our hpack state. (We want to be able to 109 // continue to reuse the hpack encoder for future requests) 110 for k, vv := range req.Header { 111 if !httpguts.ValidHeaderFieldName(k) { 112 return fmt.Errorf("invalid HTTP header name %q", k) 113 } 114 for _, v := range vv { 115 if !httpguts.ValidHeaderFieldValue(v) { 116 return fmt.Errorf("invalid HTTP header value %q for header %q", v, k) 117 } 118 } 119 } 120 121 enumerateHeaders := func(f func(name, value string)) { 122 // 8.1.2.3 Request Pseudo-Header Fields 123 // The :path pseudo-header field includes the path and query parts of the 124 // target URI (the path-absolute production and optionally a '?' character 125 // followed by the query production (see Sections 3.3 and 3.4 of 126 // [RFC3986]). 127 f(":authority", host) 128 f(":method", req.Method) 129 if req.Method != http.MethodConnect || isExtendedConnect { 130 f(":path", path) 131 f(":scheme", req.URL.Scheme) 132 } 133 if isExtendedConnect { 134 f(":protocol", req.Proto) 135 } 136 if trailers != "" { 137 f("trailer", trailers) 138 } 139 140 var didUA bool 141 for k, vv := range req.Header { 142 if strings.EqualFold(k, "host") || strings.EqualFold(k, "content-length") { 143 // Host is :authority, already sent. 144 // Content-Length is automatic, set below. 145 continue 146 } else if strings.EqualFold(k, "connection") || strings.EqualFold(k, "proxy-connection") || 147 strings.EqualFold(k, "transfer-encoding") || strings.EqualFold(k, "upgrade") || 148 strings.EqualFold(k, "keep-alive") { 149 // Per 8.1.2.2 Connection-Specific Header 150 // Fields, don't send connection-specific 151 // fields. We have already checked if any 152 // are error-worthy so just ignore the rest. 153 continue 154 } else if strings.EqualFold(k, "user-agent") { 155 // Match Go's http1 behavior: at most one 156 // User-Agent. If set to nil or empty string, 157 // then omit it. Otherwise if not mentioned, 158 // include the default (below). 159 didUA = true 160 if len(vv) < 1 { 161 continue 162 } 163 vv = vv[:1] 164 if vv[0] == "" { 165 continue 166 } 167 168 } 169 170 for _, v := range vv { 171 f(k, v) 172 } 173 } 174 if shouldSendReqContentLength(req.Method, contentLength) { 175 f("content-length", strconv.FormatInt(contentLength, 10)) 176 } 177 if addGzipHeader { 178 f("accept-encoding", "gzip") 179 } 180 if !didUA { 181 f("user-agent", defaultUserAgent) 182 } 183 } 184 185 // Do a first pass over the headers counting bytes to ensure 186 // we don't exceed cc.peerMaxHeaderListSize. This is done as a 187 // separate pass before encoding the headers to prevent 188 // modifying the hpack state. 189 hlSize := uint64(0) 190 enumerateHeaders(func(name, value string) { 191 hf := hpack.HeaderField{Name: name, Value: value} 192 hlSize += uint64(hf.Size()) 193 }) 194 195 // TODO: check maximum header list size 196 // if hlSize > cc.peerMaxHeaderListSize { 197 // return errRequestHeaderListSize 198 // } 199 200 // trace := httptrace.ContextClientTrace(req.Context()) 201 // traceHeaders := traceHasWroteHeaderField(trace) 202 203 // Header list size is ok. Write the headers. 204 enumerateHeaders(func(name, value string) { 205 name = strings.ToLower(name) 206 w.encoder.WriteField(qpack.HeaderField{Name: name, Value: value}) 207 // if traceHeaders { 208 // traceWroteHeaderField(trace, name, value) 209 // } 210 }) 211 212 return nil 213 } 214 215 // authorityAddr returns a given authority (a host/IP, or host:port / ip:port) 216 // and returns a host:port. The port 443 is added if needed. 217 func authorityAddr(authority string) (addr string) { 218 host, port, err := net.SplitHostPort(authority) 219 if err != nil { // authority didn't have a port 220 port = "443" 221 host = authority 222 } 223 if a, err := idna.ToASCII(host); err == nil { 224 host = a 225 } 226 // IPv6 address literal, without a port: 227 if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") { 228 return host + ":" + port 229 } 230 return net.JoinHostPort(host, port) 231 } 232 233 // validPseudoPath reports whether v is a valid :path pseudo-header 234 // value. It must be either: 235 // 236 // *) a non-empty string starting with '/' 237 // *) the string '*', for OPTIONS requests. 238 // 239 // For now this is only used a quick check for deciding when to clean 240 // up Opaque URLs before sending requests from the Transport. 241 // See golang.org/issue/16847 242 // 243 // We used to enforce that the path also didn't start with "//", but 244 // Google's GFE accepts such paths and Chrome sends them, so ignore 245 // that part of the spec. See golang.org/issue/19103. 246 func validPseudoPath(v string) bool { 247 return (len(v) > 0 && v[0] == '/') || v == "*" 248 } 249 250 // actualContentLength returns a sanitized version of 251 // req.ContentLength, where 0 actually means zero (not unknown) and -1 252 // means unknown. 253 func actualContentLength(req *http.Request) int64 { 254 if req.Body == nil { 255 return 0 256 } 257 if req.ContentLength != 0 { 258 return req.ContentLength 259 } 260 return -1 261 } 262 263 // shouldSendReqContentLength reports whether the http2.Transport should send 264 // a "content-length" request header. This logic is basically a copy of the net/http 265 // transferWriter.shouldSendContentLength. 266 // The contentLength is the corrected contentLength (so 0 means actually 0, not unknown). 267 // -1 means unknown. 268 func shouldSendReqContentLength(method string, contentLength int64) bool { 269 if contentLength > 0 { 270 return true 271 } 272 if contentLength < 0 { 273 return false 274 } 275 // For zero bodies, whether we send a content-length depends on the method. 276 // It also kinda doesn't matter for http2 either way, with END_STREAM. 277 switch method { 278 case "POST", "PUT", "PATCH": 279 return true 280 default: 281 return false 282 } 283 }