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 an 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 && ISBLANK(*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 */
|
---|