• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, 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  */
contains_trailers(const char * p,size_t len)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. */
inspect_header(const char * name,size_t namelen,const char * value,size_t valuelen)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 
Curl_pseudo_headers(struct Curl_easy * data,const char * mem,const size_t len,size_t * hdrlen,struct h2h3req ** hp)118 CURLcode Curl_pseudo_headers(struct Curl_easy *data,
119                              const char *mem, /* the request */
120                              const size_t len /* size of request */,
121                              size_t* hdrlen /* opt size of headers read */,
122                              struct h2h3req **hp)
123 {
124   struct connectdata *conn = data->conn;
125   size_t nheader = 0;
126   size_t i;
127   size_t authority_idx;
128   char *hdbuf = (char *)mem;
129   char *end, *line_end;
130   struct h2h3pseudo *nva = NULL;
131   struct h2h3req *hreq = NULL;
132   char *vptr;
133 
134   /* Calculate number of headers contained in [mem, mem + len). Assumes a
135      correctly generated HTTP header field block. */
136   for(i = 1; i < len; ++i) {
137     if(hdbuf[i] == '\n' && hdbuf[i - 1] == '\r') {
138       ++nheader;
139       ++i;
140     }
141   }
142   if(nheader < 2) {
143     goto fail;
144   }
145   /* We counted additional 2 \r\n in the first and last line. We need 3
146      new headers: :method, :path and :scheme. Therefore we need one
147      more space. */
148   nheader += 1;
149   hreq = malloc(sizeof(struct h2h3req) +
150                 sizeof(struct h2h3pseudo) * (nheader - 1));
151   if(!hreq) {
152     goto fail;
153   }
154 
155   nva = &hreq->header[0];
156 
157   /* Extract :method, :path from request line
158      We do line endings with CRLF so checking for CR is enough */
159   line_end = memchr(hdbuf, '\r', len);
160   if(!line_end) {
161     goto fail;
162   }
163 
164   /* Method does not contain spaces */
165   end = memchr(hdbuf, ' ', line_end - hdbuf);
166   if(!end || end == hdbuf)
167     goto fail;
168   nva[0].name = H2H3_PSEUDO_METHOD;
169   nva[0].namelen = sizeof(H2H3_PSEUDO_METHOD) - 1;
170   nva[0].value = hdbuf;
171   nva[0].valuelen = (size_t)(end - hdbuf);
172 
173   hdbuf = end + 1;
174 
175   /* Path may contain spaces so scan backwards */
176   end = NULL;
177   for(i = (size_t)(line_end - hdbuf); i; --i) {
178     if(hdbuf[i - 1] == ' ') {
179       end = &hdbuf[i - 1];
180       break;
181     }
182   }
183   if(!end || end == hdbuf)
184     goto fail;
185   nva[1].name = H2H3_PSEUDO_PATH;
186   nva[1].namelen = sizeof(H2H3_PSEUDO_PATH) - 1;
187   nva[1].value = hdbuf;
188   nva[1].valuelen = (end - hdbuf);
189 
190   nva[2].name = H2H3_PSEUDO_SCHEME;
191   nva[2].namelen = sizeof(H2H3_PSEUDO_SCHEME) - 1;
192   vptr = Curl_checkheaders(data, STRCONST(H2H3_PSEUDO_SCHEME));
193   if(vptr) {
194     vptr += sizeof(H2H3_PSEUDO_SCHEME);
195     while(*vptr && ISBLANK(*vptr))
196       vptr++;
197     nva[2].value = vptr;
198     infof(data, "set pseudo header %s to %s", H2H3_PSEUDO_SCHEME, vptr);
199   }
200   else {
201     if(conn->handler->flags & PROTOPT_SSL)
202       nva[2].value = "https";
203     else
204       nva[2].value = "http";
205   }
206   nva[2].valuelen = strlen((char *)nva[2].value);
207 
208   authority_idx = 0;
209   i = 3;
210   while(i < nheader) {
211     size_t hlen;
212 
213     hdbuf = line_end + 2;
214 
215     /* check for next CR, but only within the piece of data left in the given
216        buffer */
217     line_end = memchr(hdbuf, '\r', len - (hdbuf - (char *)mem));
218     if(!line_end || (line_end == hdbuf))
219       goto fail;
220 
221     /* header continuation lines are not supported */
222     if(*hdbuf == ' ' || *hdbuf == '\t')
223       goto fail;
224 
225     for(end = hdbuf; end < line_end && *end != ':'; ++end)
226       ;
227     if(end == hdbuf || end == line_end)
228       goto fail;
229     hlen = end - hdbuf;
230 
231     if(hlen == 4 && strncasecompare("host", hdbuf, 4)) {
232       authority_idx = i;
233       nva[i].name = H2H3_PSEUDO_AUTHORITY;
234       nva[i].namelen = sizeof(H2H3_PSEUDO_AUTHORITY) - 1;
235     }
236     else {
237       nva[i].namelen = (size_t)(end - hdbuf);
238       /* Lower case the header name for HTTP/3 */
239       Curl_strntolower((char *)hdbuf, hdbuf, nva[i].namelen);
240       nva[i].name = hdbuf;
241     }
242     hdbuf = end + 1;
243     while(*hdbuf == ' ' || *hdbuf == '\t')
244       ++hdbuf;
245     end = line_end;
246 
247     switch(inspect_header((const char *)nva[i].name, nva[i].namelen, hdbuf,
248                           end - hdbuf)) {
249     case HEADERINST_IGNORE:
250       /* skip header fields prohibited by HTTP/2 specification. */
251       --nheader;
252       continue;
253     case HEADERINST_TE_TRAILERS:
254       nva[i].value = "trailers";
255       nva[i].valuelen = sizeof("trailers") - 1;
256       break;
257     default:
258       nva[i].value = hdbuf;
259       nva[i].valuelen = (end - hdbuf);
260     }
261 
262     ++i;
263   }
264 
265   /* :authority must come before non-pseudo header fields */
266   if(authority_idx && authority_idx != AUTHORITY_DST_IDX) {
267     struct h2h3pseudo authority = nva[authority_idx];
268     for(i = authority_idx; i > AUTHORITY_DST_IDX; --i) {
269       nva[i] = nva[i - 1];
270     }
271     nva[i] = authority;
272   }
273 
274   /* Warn stream may be rejected if cumulative length of headers is too
275      large. */
276 #define MAX_ACC 60000  /* <64KB to account for some overhead */
277   {
278     size_t acc = 0;
279 
280     for(i = 0; i < nheader; ++i) {
281       acc += nva[i].namelen + nva[i].valuelen;
282 
283       infof(data, "h2h3 [%.*s: %.*s]",
284             (int)nva[i].namelen, nva[i].name,
285             (int)nva[i].valuelen, nva[i].value);
286     }
287 
288     if(acc > MAX_ACC) {
289       infof(data, "http_request: Warning: The cumulative length of all "
290             "headers exceeds %d bytes and that could cause the "
291             "stream to be rejected.", MAX_ACC);
292     }
293   }
294 
295   if(hdrlen) {
296     /* Skip trailing CRLF */
297     end += 4;
298     *hdrlen = end - mem;
299   }
300 
301   hreq->entries = nheader;
302   *hp = hreq;
303 
304   return CURLE_OK;
305 
306   fail:
307   free(hreq);
308   return CURLE_OUT_OF_MEMORY;
309 }
310 
Curl_pseudo_free(struct h2h3req * hp)311 void Curl_pseudo_free(struct h2h3req *hp)
312 {
313   free(hp);
314 }
315 
316 #endif /* USE_NGHTTP2 or HTTP/3 enabled */
317