github.com/sagernet/quic-go@v0.43.1-beta.1/http3_ech/headers.go (about) 1 package http3 2 3 import ( 4 "errors" 5 "fmt" 6 "net/http" 7 "net/url" 8 "strconv" 9 "strings" 10 11 "github.com/quic-go/qpack" 12 "golang.org/x/net/http/httpguts" 13 ) 14 15 type header struct { 16 // Pseudo header fields defined in RFC 9114 17 Path string 18 Method string 19 Authority string 20 Scheme string 21 Status string 22 // for Extended connect 23 Protocol string 24 // parsed and deduplicated 25 ContentLength int64 26 // all non-pseudo headers 27 Headers http.Header 28 } 29 30 func parseHeaders(headers []qpack.HeaderField, isRequest bool) (header, error) { 31 hdr := header{Headers: make(http.Header, len(headers))} 32 var readFirstRegularHeader, readContentLength bool 33 var contentLengthStr string 34 for _, h := range headers { 35 // field names need to be lowercase, see section 4.2 of RFC 9114 36 if strings.ToLower(h.Name) != h.Name { 37 return header{}, fmt.Errorf("header field is not lower-case: %s", h.Name) 38 } 39 if !httpguts.ValidHeaderFieldValue(h.Value) { 40 return header{}, fmt.Errorf("invalid header field value for %s: %q", h.Name, h.Value) 41 } 42 if h.IsPseudo() { 43 if readFirstRegularHeader { 44 // all pseudo headers must appear before regular header fields, see section 4.3 of RFC 9114 45 return header{}, fmt.Errorf("received pseudo header %s after a regular header field", h.Name) 46 } 47 var isResponsePseudoHeader bool // pseudo headers are either valid for requests or for responses 48 switch h.Name { 49 case ":path": 50 hdr.Path = h.Value 51 case ":method": 52 hdr.Method = h.Value 53 case ":authority": 54 hdr.Authority = h.Value 55 case ":protocol": 56 hdr.Protocol = h.Value 57 case ":scheme": 58 hdr.Scheme = h.Value 59 case ":status": 60 hdr.Status = h.Value 61 isResponsePseudoHeader = true 62 default: 63 return header{}, fmt.Errorf("unknown pseudo header: %s", h.Name) 64 } 65 if isRequest && isResponsePseudoHeader { 66 return header{}, fmt.Errorf("invalid request pseudo header: %s", h.Name) 67 } 68 if !isRequest && !isResponsePseudoHeader { 69 return header{}, fmt.Errorf("invalid response pseudo header: %s", h.Name) 70 } 71 } else { 72 if !httpguts.ValidHeaderFieldName(h.Name) { 73 return header{}, fmt.Errorf("invalid header field name: %q", h.Name) 74 } 75 readFirstRegularHeader = true 76 switch h.Name { 77 case "content-length": 78 // Ignore duplicate Content-Length headers. 79 // Fail if the duplicates differ. 80 if !readContentLength { 81 readContentLength = true 82 contentLengthStr = h.Value 83 } else if contentLengthStr != h.Value { 84 return header{}, fmt.Errorf("contradicting content lengths (%s and %s)", contentLengthStr, h.Value) 85 } 86 default: 87 hdr.Headers.Add(h.Name, h.Value) 88 } 89 } 90 } 91 if len(contentLengthStr) > 0 { 92 // use ParseUint instead of ParseInt, so that parsing fails on negative values 93 cl, err := strconv.ParseUint(contentLengthStr, 10, 63) 94 if err != nil { 95 return header{}, fmt.Errorf("invalid content length: %w", err) 96 } 97 hdr.Headers.Set("Content-Length", contentLengthStr) 98 hdr.ContentLength = int64(cl) 99 } 100 return hdr, nil 101 } 102 103 func requestFromHeaders(headerFields []qpack.HeaderField) (*http.Request, error) { 104 hdr, err := parseHeaders(headerFields, true) 105 if err != nil { 106 return nil, err 107 } 108 // concatenate cookie headers, see https://tools.ietf.org/html/rfc6265#section-5.4 109 if len(hdr.Headers["Cookie"]) > 0 { 110 hdr.Headers.Set("Cookie", strings.Join(hdr.Headers["Cookie"], "; ")) 111 } 112 113 isConnect := hdr.Method == http.MethodConnect 114 // Extended CONNECT, see https://datatracker.ietf.org/doc/html/rfc8441#section-4 115 isExtendedConnected := isConnect && hdr.Protocol != "" 116 if isExtendedConnected { 117 if hdr.Scheme == "" || hdr.Path == "" || hdr.Authority == "" { 118 return nil, errors.New("extended CONNECT: :scheme, :path and :authority must not be empty") 119 } 120 } else if isConnect { 121 if hdr.Path != "" || hdr.Authority == "" { // normal CONNECT 122 return nil, errors.New(":path must be empty and :authority must not be empty") 123 } 124 } else if len(hdr.Path) == 0 || len(hdr.Authority) == 0 || len(hdr.Method) == 0 { 125 return nil, errors.New(":path, :authority and :method must not be empty") 126 } 127 128 if !isExtendedConnected && len(hdr.Protocol) > 0 { 129 return nil, errors.New(":protocol must be empty") 130 } 131 132 var u *url.URL 133 var requestURI string 134 135 protocol := "HTTP/3.0" 136 137 if isConnect { 138 u = &url.URL{} 139 if isExtendedConnected { 140 u, err = url.ParseRequestURI(hdr.Path) 141 if err != nil { 142 return nil, err 143 } 144 protocol = hdr.Protocol 145 } else { 146 u.Path = hdr.Path 147 } 148 u.Scheme = hdr.Scheme 149 u.Host = hdr.Authority 150 requestURI = hdr.Authority 151 } else { 152 u, err = url.ParseRequestURI(hdr.Path) 153 if err != nil { 154 return nil, fmt.Errorf("invalid content length: %w", err) 155 } 156 requestURI = hdr.Path 157 } 158 159 return &http.Request{ 160 Method: hdr.Method, 161 URL: u, 162 Proto: protocol, 163 ProtoMajor: 3, 164 ProtoMinor: 0, 165 Header: hdr.Headers, 166 Body: nil, 167 ContentLength: hdr.ContentLength, 168 Host: hdr.Authority, 169 RequestURI: requestURI, 170 }, nil 171 } 172 173 func hostnameFromURL(url *url.URL) string { 174 if url != nil { 175 return url.Host 176 } 177 return "" 178 } 179 180 func responseFromHeaders(headerFields []qpack.HeaderField) (*http.Response, error) { 181 hdr, err := parseHeaders(headerFields, false) 182 if err != nil { 183 return nil, err 184 } 185 if hdr.Status == "" { 186 return nil, errors.New("missing status field") 187 } 188 rsp := &http.Response{ 189 Proto: "HTTP/3.0", 190 ProtoMajor: 3, 191 Header: hdr.Headers, 192 ContentLength: hdr.ContentLength, 193 } 194 status, err := strconv.Atoi(hdr.Status) 195 if err != nil { 196 return nil, fmt.Errorf("invalid status code: %w", err) 197 } 198 rsp.StatusCode = status 199 rsp.Status = hdr.Status + " " + http.StatusText(status) 200 return rsp, nil 201 }