1 | /*************************************************************************** |
2 | * _ _ ____ _ |
3 | * Project ___| | | | _ \| | |
4 | * / __| | | | |_) | | |
5 | * | (__| |_| | _ <| |___ |
6 | * \___|\___/|_| \_\_____| |
7 | * |
8 | * Copyright (C) 1998 - 2022, Daniel Stenberg, <[email protected]>, et al. |
9 | * |
10 | * This software is licensed as described in the file COPYING, which |
11 | * you should have received as part of this distribution. The terms |
12 | * are also available at https://curl.se/docs/copyright.html. |
13 | * |
14 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
15 | * copies of the Software, and permit persons to whom the Software is |
16 | * furnished to do so, under the terms of the COPYING file. |
17 | * |
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
19 | * KIND, either express or implied. |
20 | * |
21 | * SPDX-License-Identifier: curl |
22 | * |
23 | ***************************************************************************/ |
24 | |
25 | #include "curl_setup.h" |
26 | #include "urldata.h" |
27 | #include "h2h3.h" |
28 | #include "transfer.h" |
29 | #include "sendf.h" |
30 | #include "strcase.h" |
31 | |
32 | /* The last 3 #include files should be in this order */ |
33 | #include "curl_printf.h" |
34 | #include "curl_memory.h" |
35 | #include "memdebug.h" |
36 | |
37 | /* |
38 | * Curl_pseudo_headers() creates the array with pseudo headers to be |
39 | * used in a HTTP/2 or HTTP/3 request. |
40 | */ |
41 | |
42 | #if defined(USE_NGHTTP2) || defined(ENABLE_QUIC) |
43 | |
44 | /* Index where :authority header field will appear in request header |
45 | field list. */ |
46 | #define AUTHORITY_DST_IDX 3 |
47 | |
48 | /* USHRT_MAX is 65535 == 0xffff */ |
49 | #define HEADER_OVERFLOW(x) \ |
50 | (x.namelen > 0xffff || x.valuelen > 0xffff - x.namelen) |
51 | |
52 | /* |
53 | * Check header memory for the token "trailers". |
54 | * Parse the tokens as separated by comma and surrounded by whitespace. |
55 | * Returns TRUE if found or FALSE if not. |
56 | */ |
57 | static bool contains_trailers(const char *p, size_t len) |
58 | { |
59 | const char *end = p + len; |
60 | for(;;) { |
61 | for(; p != end && (*p == ' ' || *p == '\t'); ++p) |
62 | ; |
63 | if(p == end || (size_t)(end - p) < sizeof("trailers" ) - 1) |
64 | return FALSE; |
65 | if(strncasecompare("trailers" , p, sizeof("trailers" ) - 1)) { |
66 | p += sizeof("trailers" ) - 1; |
67 | for(; p != end && (*p == ' ' || *p == '\t'); ++p) |
68 | ; |
69 | if(p == end || *p == ',') |
70 | return TRUE; |
71 | } |
72 | /* skip to next token */ |
73 | for(; p != end && *p != ','; ++p) |
74 | ; |
75 | if(p == end) |
76 | return FALSE; |
77 | ++p; |
78 | } |
79 | } |
80 | |
81 | typedef enum { |
82 | /* Send header to server */ |
83 | HEADERINST_FORWARD, |
84 | /* Don't send header to server */ |
85 | HEADERINST_IGNORE, |
86 | /* Discard header, and replace it with "te: trailers" */ |
87 | HEADERINST_TE_TRAILERS |
88 | } header_instruction; |
89 | |
90 | /* Decides how to treat given header field. */ |
91 | static header_instruction inspect_header(const char *name, size_t namelen, |
92 | const char *value, size_t valuelen) { |
93 | switch(namelen) { |
94 | case 2: |
95 | if(!strncasecompare("te" , name, namelen)) |
96 | return HEADERINST_FORWARD; |
97 | |
98 | return contains_trailers(value, valuelen) ? |
99 | HEADERINST_TE_TRAILERS : HEADERINST_IGNORE; |
100 | case 7: |
101 | return strncasecompare("upgrade" , name, namelen) ? |
102 | HEADERINST_IGNORE : HEADERINST_FORWARD; |
103 | case 10: |
104 | return (strncasecompare("connection" , name, namelen) || |
105 | strncasecompare("keep-alive" , name, namelen)) ? |
106 | HEADERINST_IGNORE : HEADERINST_FORWARD; |
107 | case 16: |
108 | return strncasecompare("proxy-connection" , name, namelen) ? |
109 | HEADERINST_IGNORE : HEADERINST_FORWARD; |
110 | case 17: |
111 | return strncasecompare("transfer-encoding" , name, namelen) ? |
112 | HEADERINST_IGNORE : HEADERINST_FORWARD; |
113 | default: |
114 | return HEADERINST_FORWARD; |
115 | } |
116 | } |
117 | |
118 | CURLcode Curl_pseudo_headers(struct Curl_easy *data, |
119 | const char *mem, /* the request */ |
120 | const size_t len /* size of request */, |
121 | struct h2h3req **hp) |
122 | { |
123 | struct connectdata *conn = data->conn; |
124 | size_t nheader = 0; |
125 | size_t i; |
126 | size_t authority_idx; |
127 | char *hdbuf = (char *)mem; |
128 | char *end, *line_end; |
129 | struct h2h3pseudo *nva = NULL; |
130 | struct h2h3req *hreq = NULL; |
131 | char *vptr; |
132 | |
133 | /* Calculate number of headers contained in [mem, mem + len). Assumes a |
134 | correctly generated HTTP header field block. */ |
135 | for(i = 1; i < len; ++i) { |
136 | if(hdbuf[i] == '\n' && hdbuf[i - 1] == '\r') { |
137 | ++nheader; |
138 | ++i; |
139 | } |
140 | } |
141 | if(nheader < 2) { |
142 | goto fail; |
143 | } |
144 | /* We counted additional 2 \r\n in the first and last line. We need 3 |
145 | new headers: :method, :path and :scheme. Therefore we need one |
146 | more space. */ |
147 | nheader += 1; |
148 | hreq = malloc(sizeof(struct h2h3req) + |
149 | sizeof(struct h2h3pseudo) * (nheader - 1)); |
150 | if(!hreq) { |
151 | goto fail; |
152 | } |
153 | |
154 | nva = &hreq->header[0]; |
155 | |
156 | /* Extract :method, :path from request line |
157 | We do line endings with CRLF so checking for CR is enough */ |
158 | line_end = memchr(hdbuf, '\r', len); |
159 | if(!line_end) { |
160 | goto fail; |
161 | } |
162 | |
163 | /* Method does not contain spaces */ |
164 | end = memchr(hdbuf, ' ', line_end - hdbuf); |
165 | if(!end || end == hdbuf) |
166 | goto fail; |
167 | nva[0].name = H2H3_PSEUDO_METHOD; |
168 | nva[0].namelen = sizeof(H2H3_PSEUDO_METHOD) - 1; |
169 | nva[0].value = hdbuf; |
170 | nva[0].valuelen = (size_t)(end - hdbuf); |
171 | |
172 | hdbuf = end + 1; |
173 | |
174 | /* Path may contain spaces so scan backwards */ |
175 | end = NULL; |
176 | for(i = (size_t)(line_end - hdbuf); i; --i) { |
177 | if(hdbuf[i - 1] == ' ') { |
178 | end = &hdbuf[i - 1]; |
179 | break; |
180 | } |
181 | } |
182 | if(!end || end == hdbuf) |
183 | goto fail; |
184 | nva[1].name = H2H3_PSEUDO_PATH; |
185 | nva[1].namelen = sizeof(H2H3_PSEUDO_PATH) - 1; |
186 | nva[1].value = hdbuf; |
187 | nva[1].valuelen = (end - hdbuf); |
188 | |
189 | nva[2].name = H2H3_PSEUDO_SCHEME; |
190 | nva[2].namelen = sizeof(H2H3_PSEUDO_SCHEME) - 1; |
191 | vptr = Curl_checkheaders(data, STRCONST(H2H3_PSEUDO_SCHEME)); |
192 | if(vptr) { |
193 | vptr += sizeof(H2H3_PSEUDO_SCHEME); |
194 | while(*vptr && ISSPACE(*vptr)) |
195 | vptr++; |
196 | nva[2].value = vptr; |
197 | infof(data, "set pseudo header %s to %s" , H2H3_PSEUDO_SCHEME, vptr); |
198 | } |
199 | else { |
200 | if(conn->handler->flags & PROTOPT_SSL) |
201 | nva[2].value = "https" ; |
202 | else |
203 | nva[2].value = "http" ; |
204 | } |
205 | nva[2].valuelen = strlen((char *)nva[2].value); |
206 | |
207 | authority_idx = 0; |
208 | i = 3; |
209 | while(i < nheader) { |
210 | size_t hlen; |
211 | |
212 | hdbuf = line_end + 2; |
213 | |
214 | /* check for next CR, but only within the piece of data left in the given |
215 | buffer */ |
216 | line_end = memchr(hdbuf, '\r', len - (hdbuf - (char *)mem)); |
217 | if(!line_end || (line_end == hdbuf)) |
218 | goto fail; |
219 | |
220 | /* header continuation lines are not supported */ |
221 | if(*hdbuf == ' ' || *hdbuf == '\t') |
222 | goto fail; |
223 | |
224 | for(end = hdbuf; end < line_end && *end != ':'; ++end) |
225 | ; |
226 | if(end == hdbuf || end == line_end) |
227 | goto fail; |
228 | hlen = end - hdbuf; |
229 | |
230 | if(hlen == 4 && strncasecompare("host" , hdbuf, 4)) { |
231 | authority_idx = i; |
232 | nva[i].name = H2H3_PSEUDO_AUTHORITY; |
233 | nva[i].namelen = sizeof(H2H3_PSEUDO_AUTHORITY) - 1; |
234 | } |
235 | else { |
236 | nva[i].namelen = (size_t)(end - hdbuf); |
237 | /* Lower case the header name for HTTP/3 */ |
238 | Curl_strntolower((char *)hdbuf, hdbuf, nva[i].namelen); |
239 | nva[i].name = hdbuf; |
240 | } |
241 | hdbuf = end + 1; |
242 | while(*hdbuf == ' ' || *hdbuf == '\t') |
243 | ++hdbuf; |
244 | end = line_end; |
245 | |
246 | switch(inspect_header((const char *)nva[i].name, nva[i].namelen, hdbuf, |
247 | end - hdbuf)) { |
248 | case HEADERINST_IGNORE: |
249 | /* skip header fields prohibited by HTTP/2 specification. */ |
250 | --nheader; |
251 | continue; |
252 | case HEADERINST_TE_TRAILERS: |
253 | nva[i].value = "trailers" ; |
254 | nva[i].valuelen = sizeof("trailers" ) - 1; |
255 | break; |
256 | default: |
257 | nva[i].value = hdbuf; |
258 | nva[i].valuelen = (end - hdbuf); |
259 | } |
260 | |
261 | ++i; |
262 | } |
263 | |
264 | /* :authority must come before non-pseudo header fields */ |
265 | if(authority_idx && authority_idx != AUTHORITY_DST_IDX) { |
266 | struct h2h3pseudo authority = nva[authority_idx]; |
267 | for(i = authority_idx; i > AUTHORITY_DST_IDX; --i) { |
268 | nva[i] = nva[i - 1]; |
269 | } |
270 | nva[i] = authority; |
271 | } |
272 | |
273 | /* Warn stream may be rejected if cumulative length of headers is too |
274 | large. */ |
275 | #define MAX_ACC 60000 /* <64KB to account for some overhead */ |
276 | { |
277 | size_t acc = 0; |
278 | |
279 | for(i = 0; i < nheader; ++i) { |
280 | acc += nva[i].namelen + nva[i].valuelen; |
281 | |
282 | infof(data, "h2h3 [%.*s: %.*s]" , |
283 | (int)nva[i].namelen, nva[i].name, |
284 | (int)nva[i].valuelen, nva[i].value); |
285 | } |
286 | |
287 | if(acc > MAX_ACC) { |
288 | infof(data, "http_request: Warning: The cumulative length of all " |
289 | "headers exceeds %d bytes and that could cause the " |
290 | "stream to be rejected." , MAX_ACC); |
291 | } |
292 | } |
293 | |
294 | hreq->entries = nheader; |
295 | *hp = hreq; |
296 | |
297 | return CURLE_OK; |
298 | |
299 | fail: |
300 | free(hreq); |
301 | return CURLE_OUT_OF_MEMORY; |
302 | } |
303 | |
304 | void Curl_pseudo_free(struct h2h3req *hp) |
305 | { |
306 | free(hp); |
307 | } |
308 | |
309 | #endif /* USE_NGHTTP2 or HTTP/3 enabled */ |
310 | |