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