blob: 7c3f6bbcfe6835c24dbd4c5021aac5b195b2beaa [file] [log] [blame]
Willy Tarreau35b51c62018-09-10 15:38:55 +02001/*
2 * HTTP semantics
3 *
4 * Copyright 2000-2018 Willy Tarreau <w@1wt.eu>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 *
11 */
12
13#include <ctype.h>
14#include <common/config.h>
15#include <common/http.h>
Willy Tarreau04f1e2d2018-09-10 18:04:24 +020016#include <common/standard.h>
Willy Tarreau35b51c62018-09-10 15:38:55 +020017
18/* It is about twice as fast on recent architectures to lookup a byte in a
19 * table than to perform a boolean AND or OR between two tests. Refer to
20 * RFC2616/RFC5234/RFC7230 for those chars. A token is any ASCII char that is
21 * neither a separator nor a CTL char. An http ver_token is any ASCII which can
22 * be found in an HTTP version, which includes 'H', 'T', 'P', '/', '.' and any
23 * digit. Note: please do not overwrite values in assignment since gcc-2.95
24 * will not handle them correctly. It's worth noting that chars 128..255 are
25 * nothing, not even control chars.
26 */
27const unsigned char http_char_classes[256] = {
28 [ 0] = HTTP_FLG_CTL,
29 [ 1] = HTTP_FLG_CTL,
30 [ 2] = HTTP_FLG_CTL,
31 [ 3] = HTTP_FLG_CTL,
32 [ 4] = HTTP_FLG_CTL,
33 [ 5] = HTTP_FLG_CTL,
34 [ 6] = HTTP_FLG_CTL,
35 [ 7] = HTTP_FLG_CTL,
36 [ 8] = HTTP_FLG_CTL,
37 [ 9] = HTTP_FLG_SPHT | HTTP_FLG_LWS | HTTP_FLG_SEP | HTTP_FLG_CTL,
38 [ 10] = HTTP_FLG_CRLF | HTTP_FLG_LWS | HTTP_FLG_CTL,
39 [ 11] = HTTP_FLG_CTL,
40 [ 12] = HTTP_FLG_CTL,
41 [ 13] = HTTP_FLG_CRLF | HTTP_FLG_LWS | HTTP_FLG_CTL,
42 [ 14] = HTTP_FLG_CTL,
43 [ 15] = HTTP_FLG_CTL,
44 [ 16] = HTTP_FLG_CTL,
45 [ 17] = HTTP_FLG_CTL,
46 [ 18] = HTTP_FLG_CTL,
47 [ 19] = HTTP_FLG_CTL,
48 [ 20] = HTTP_FLG_CTL,
49 [ 21] = HTTP_FLG_CTL,
50 [ 22] = HTTP_FLG_CTL,
51 [ 23] = HTTP_FLG_CTL,
52 [ 24] = HTTP_FLG_CTL,
53 [ 25] = HTTP_FLG_CTL,
54 [ 26] = HTTP_FLG_CTL,
55 [ 27] = HTTP_FLG_CTL,
56 [ 28] = HTTP_FLG_CTL,
57 [ 29] = HTTP_FLG_CTL,
58 [ 30] = HTTP_FLG_CTL,
59 [ 31] = HTTP_FLG_CTL,
60 [' '] = HTTP_FLG_SPHT | HTTP_FLG_LWS | HTTP_FLG_SEP,
61 ['!'] = HTTP_FLG_TOK,
62 ['"'] = HTTP_FLG_SEP,
63 ['#'] = HTTP_FLG_TOK,
64 ['$'] = HTTP_FLG_TOK,
65 ['%'] = HTTP_FLG_TOK,
66 ['&'] = HTTP_FLG_TOK,
67 [ 39] = HTTP_FLG_TOK,
68 ['('] = HTTP_FLG_SEP,
69 [')'] = HTTP_FLG_SEP,
70 ['*'] = HTTP_FLG_TOK,
71 ['+'] = HTTP_FLG_TOK,
72 [','] = HTTP_FLG_SEP,
73 ['-'] = HTTP_FLG_TOK,
74 ['.'] = HTTP_FLG_TOK | HTTP_FLG_VER,
75 ['/'] = HTTP_FLG_SEP | HTTP_FLG_VER,
76 ['0'] = HTTP_FLG_TOK | HTTP_FLG_VER | HTTP_FLG_DIG,
77 ['1'] = HTTP_FLG_TOK | HTTP_FLG_VER | HTTP_FLG_DIG,
78 ['2'] = HTTP_FLG_TOK | HTTP_FLG_VER | HTTP_FLG_DIG,
79 ['3'] = HTTP_FLG_TOK | HTTP_FLG_VER | HTTP_FLG_DIG,
80 ['4'] = HTTP_FLG_TOK | HTTP_FLG_VER | HTTP_FLG_DIG,
81 ['5'] = HTTP_FLG_TOK | HTTP_FLG_VER | HTTP_FLG_DIG,
82 ['6'] = HTTP_FLG_TOK | HTTP_FLG_VER | HTTP_FLG_DIG,
83 ['7'] = HTTP_FLG_TOK | HTTP_FLG_VER | HTTP_FLG_DIG,
84 ['8'] = HTTP_FLG_TOK | HTTP_FLG_VER | HTTP_FLG_DIG,
85 ['9'] = HTTP_FLG_TOK | HTTP_FLG_VER | HTTP_FLG_DIG,
86 [':'] = HTTP_FLG_SEP,
87 [';'] = HTTP_FLG_SEP,
88 ['<'] = HTTP_FLG_SEP,
89 ['='] = HTTP_FLG_SEP,
90 ['>'] = HTTP_FLG_SEP,
91 ['?'] = HTTP_FLG_SEP,
92 ['@'] = HTTP_FLG_SEP,
93 ['A'] = HTTP_FLG_TOK,
94 ['B'] = HTTP_FLG_TOK,
95 ['C'] = HTTP_FLG_TOK,
96 ['D'] = HTTP_FLG_TOK,
97 ['E'] = HTTP_FLG_TOK,
98 ['F'] = HTTP_FLG_TOK,
99 ['G'] = HTTP_FLG_TOK,
100 ['H'] = HTTP_FLG_TOK | HTTP_FLG_VER,
101 ['I'] = HTTP_FLG_TOK,
102 ['J'] = HTTP_FLG_TOK,
103 ['K'] = HTTP_FLG_TOK,
104 ['L'] = HTTP_FLG_TOK,
105 ['M'] = HTTP_FLG_TOK,
106 ['N'] = HTTP_FLG_TOK,
107 ['O'] = HTTP_FLG_TOK,
108 ['P'] = HTTP_FLG_TOK | HTTP_FLG_VER,
109 ['Q'] = HTTP_FLG_TOK,
110 ['R'] = HTTP_FLG_TOK | HTTP_FLG_VER,
111 ['S'] = HTTP_FLG_TOK | HTTP_FLG_VER,
112 ['T'] = HTTP_FLG_TOK | HTTP_FLG_VER,
113 ['U'] = HTTP_FLG_TOK,
114 ['V'] = HTTP_FLG_TOK,
115 ['W'] = HTTP_FLG_TOK,
116 ['X'] = HTTP_FLG_TOK,
117 ['Y'] = HTTP_FLG_TOK,
118 ['Z'] = HTTP_FLG_TOK,
119 ['['] = HTTP_FLG_SEP,
120 [ 92] = HTTP_FLG_SEP,
121 [']'] = HTTP_FLG_SEP,
122 ['^'] = HTTP_FLG_TOK,
123 ['_'] = HTTP_FLG_TOK,
124 ['`'] = HTTP_FLG_TOK,
125 ['a'] = HTTP_FLG_TOK,
126 ['b'] = HTTP_FLG_TOK,
127 ['c'] = HTTP_FLG_TOK,
128 ['d'] = HTTP_FLG_TOK,
129 ['e'] = HTTP_FLG_TOK,
130 ['f'] = HTTP_FLG_TOK,
131 ['g'] = HTTP_FLG_TOK,
132 ['h'] = HTTP_FLG_TOK,
133 ['i'] = HTTP_FLG_TOK,
134 ['j'] = HTTP_FLG_TOK,
135 ['k'] = HTTP_FLG_TOK,
136 ['l'] = HTTP_FLG_TOK,
137 ['m'] = HTTP_FLG_TOK,
138 ['n'] = HTTP_FLG_TOK,
139 ['o'] = HTTP_FLG_TOK,
140 ['p'] = HTTP_FLG_TOK,
141 ['q'] = HTTP_FLG_TOK,
142 ['r'] = HTTP_FLG_TOK,
143 ['s'] = HTTP_FLG_TOK,
144 ['t'] = HTTP_FLG_TOK,
145 ['u'] = HTTP_FLG_TOK,
146 ['v'] = HTTP_FLG_TOK,
147 ['w'] = HTTP_FLG_TOK,
148 ['x'] = HTTP_FLG_TOK,
149 ['y'] = HTTP_FLG_TOK,
150 ['z'] = HTTP_FLG_TOK,
151 ['{'] = HTTP_FLG_SEP,
152 ['|'] = HTTP_FLG_TOK,
153 ['}'] = HTTP_FLG_SEP,
154 ['~'] = HTTP_FLG_TOK,
155 [127] = HTTP_FLG_CTL,
156};
157
Willy Tarreau04f1e2d2018-09-10 18:04:24 +0200158/* We must put the messages here since GCC cannot initialize consts depending
159 * on strlen().
160 */
161struct buffer http_err_chunks[HTTP_ERR_SIZE];
162
163const struct ist HTTP_100 = IST("HTTP/1.1 100 Continue\r\n\r\n");
164
Frédéric Lécaille9ca51aa2018-11-12 10:06:54 +0100165const struct ist HTTP_103 = IST("HTTP/1.1 103 Early Hints\r\n");
166
Willy Tarreau04f1e2d2018-09-10 18:04:24 +0200167/* Warning: no "connection" header is provided with the 3xx messages below */
168const char *HTTP_301 =
169 "HTTP/1.1 301 Moved Permanently\r\n"
170 "Content-length: 0\r\n"
171 "Location: "; /* not terminated since it will be concatenated with the URL */
172
173const char *HTTP_302 =
174 "HTTP/1.1 302 Found\r\n"
175 "Cache-Control: no-cache\r\n"
176 "Content-length: 0\r\n"
177 "Location: "; /* not terminated since it will be concatenated with the URL */
178
179/* same as 302 except that the browser MUST retry with the GET method */
180const char *HTTP_303 =
181 "HTTP/1.1 303 See Other\r\n"
182 "Cache-Control: no-cache\r\n"
183 "Content-length: 0\r\n"
184 "Location: "; /* not terminated since it will be concatenated with the URL */
185
186/* same as 302 except that the browser MUST retry with the same method */
187const char *HTTP_307 =
188 "HTTP/1.1 307 Temporary Redirect\r\n"
189 "Cache-Control: no-cache\r\n"
190 "Content-length: 0\r\n"
191 "Location: "; /* not terminated since it will be concatenated with the URL */
192
193/* same as 301 except that the browser MUST retry with the same method */
194const char *HTTP_308 =
195 "HTTP/1.1 308 Permanent Redirect\r\n"
196 "Content-length: 0\r\n"
197 "Location: "; /* not terminated since it will be concatenated with the URL */
198
199/* Warning: this one is an sprintf() fmt string, with <realm> as its only argument */
200const char *HTTP_401_fmt =
201 "HTTP/1.0 401 Unauthorized\r\n"
202 "Cache-Control: no-cache\r\n"
203 "Connection: close\r\n"
204 "Content-Type: text/html\r\n"
205 "WWW-Authenticate: Basic realm=\"%s\"\r\n"
206 "\r\n"
207 "<html><body><h1>401 Unauthorized</h1>\nYou need a valid user and password to access this content.\n</body></html>\n";
208
209const char *HTTP_407_fmt =
210 "HTTP/1.0 407 Unauthorized\r\n"
211 "Cache-Control: no-cache\r\n"
212 "Connection: close\r\n"
213 "Content-Type: text/html\r\n"
214 "Proxy-Authenticate: Basic realm=\"%s\"\r\n"
215 "\r\n"
216 "<html><body><h1>407 Unauthorized</h1>\nYou need a valid user and password to access this content.\n</body></html>\n";
217
218const int http_err_codes[HTTP_ERR_SIZE] = {
219 [HTTP_ERR_200] = 200, /* used by "monitor-uri" */
220 [HTTP_ERR_400] = 400,
221 [HTTP_ERR_403] = 403,
222 [HTTP_ERR_405] = 405,
223 [HTTP_ERR_408] = 408,
224 [HTTP_ERR_421] = 421,
225 [HTTP_ERR_425] = 425,
226 [HTTP_ERR_429] = 429,
227 [HTTP_ERR_500] = 500,
228 [HTTP_ERR_502] = 502,
229 [HTTP_ERR_503] = 503,
230 [HTTP_ERR_504] = 504,
231};
232
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100233const char *http_err_msgs[HTTP_ERR_SIZE] = {
Willy Tarreau04f1e2d2018-09-10 18:04:24 +0200234 [HTTP_ERR_200] =
235 "HTTP/1.0 200 OK\r\n"
236 "Cache-Control: no-cache\r\n"
237 "Connection: close\r\n"
238 "Content-Type: text/html\r\n"
239 "\r\n"
240 "<html><body><h1>200 OK</h1>\nService ready.\n</body></html>\n",
241
242 [HTTP_ERR_400] =
243 "HTTP/1.0 400 Bad request\r\n"
244 "Cache-Control: no-cache\r\n"
245 "Connection: close\r\n"
246 "Content-Type: text/html\r\n"
247 "\r\n"
248 "<html><body><h1>400 Bad request</h1>\nYour browser sent an invalid request.\n</body></html>\n",
249
250 [HTTP_ERR_403] =
251 "HTTP/1.0 403 Forbidden\r\n"
252 "Cache-Control: no-cache\r\n"
253 "Connection: close\r\n"
254 "Content-Type: text/html\r\n"
255 "\r\n"
256 "<html><body><h1>403 Forbidden</h1>\nRequest forbidden by administrative rules.\n</body></html>\n",
257
258 [HTTP_ERR_405] =
259 "HTTP/1.0 405 Method Not Allowed\r\n"
260 "Cache-Control: no-cache\r\n"
261 "Connection: close\r\n"
262 "Content-Type: text/html\r\n"
263 "\r\n"
264 "<html><body><h1>405 Method Not Allowed</h1>\nA request was made of a resource using a request method not supported by that resource\n</body></html>\n",
265
266 [HTTP_ERR_408] =
267 "HTTP/1.0 408 Request Time-out\r\n"
268 "Cache-Control: no-cache\r\n"
269 "Connection: close\r\n"
270 "Content-Type: text/html\r\n"
271 "\r\n"
272 "<html><body><h1>408 Request Time-out</h1>\nYour browser didn't send a complete request in time.\n</body></html>\n",
273
274 [HTTP_ERR_421] =
275 "HTTP/1.0 421 Misdirected Request\r\n"
276 "Cache-Control: no-cache\r\n"
277 "Connection: close\r\n"
278 "Content-Type: text/html\r\n"
279 "\r\n"
280 "<html><body><h1>421 Misdirected Request</h1>\nRequest sent to a non-authoritative server.\n</body></html>\n",
281
282 [HTTP_ERR_425] =
283 "HTTP/1.0 425 Too Early\r\n"
284 "Cache-Control: no-cache\r\n"
285 "Connection: close\r\n"
286 "Content-Type: text/html\r\n"
287 "\r\n"
288 "<html><body><h1>425 Too Early</h1>\nYour browser sent early data.\n</body></html>\n",
289
290 [HTTP_ERR_429] =
291 "HTTP/1.0 429 Too Many Requests\r\n"
292 "Cache-Control: no-cache\r\n"
293 "Connection: close\r\n"
294 "Content-Type: text/html\r\n"
295 "\r\n"
296 "<html><body><h1>429 Too Many Requests</h1>\nYou have sent too many requests in a given amount of time.\n</body></html>\n",
297
298 [HTTP_ERR_500] =
299 "HTTP/1.0 500 Internal Server Error\r\n"
300 "Cache-Control: no-cache\r\n"
301 "Connection: close\r\n"
302 "Content-Type: text/html\r\n"
303 "\r\n"
304 "<html><body><h1>500 Internal Server Error</h1>\nAn internal server error occured.\n</body></html>\n",
305
306 [HTTP_ERR_502] =
307 "HTTP/1.0 502 Bad Gateway\r\n"
308 "Cache-Control: no-cache\r\n"
309 "Connection: close\r\n"
310 "Content-Type: text/html\r\n"
311 "\r\n"
312 "<html><body><h1>502 Bad Gateway</h1>\nThe server returned an invalid or incomplete response.\n</body></html>\n",
313
314 [HTTP_ERR_503] =
315 "HTTP/1.0 503 Service Unavailable\r\n"
316 "Cache-Control: no-cache\r\n"
317 "Connection: close\r\n"
318 "Content-Type: text/html\r\n"
319 "\r\n"
320 "<html><body><h1>503 Service Unavailable</h1>\nNo server is available to handle this request.\n</body></html>\n",
321
322 [HTTP_ERR_504] =
323 "HTTP/1.0 504 Gateway Time-out\r\n"
324 "Cache-Control: no-cache\r\n"
325 "Connection: close\r\n"
326 "Content-Type: text/html\r\n"
327 "\r\n"
328 "<html><body><h1>504 Gateway Time-out</h1>\nThe server didn't respond in time.\n</body></html>\n",
329
330};
331
Willy Tarreau35b51c62018-09-10 15:38:55 +0200332const struct ist http_known_methods[HTTP_METH_OTHER] = {
333 [HTTP_METH_OPTIONS] = IST("OPTIONS"),
334 [HTTP_METH_GET] = IST("GET"),
335 [HTTP_METH_HEAD] = IST("HEAD"),
336 [HTTP_METH_POST] = IST("POST"),
337 [HTTP_METH_PUT] = IST("PUT"),
338 [HTTP_METH_DELETE] = IST("DELETE"),
339 [HTTP_METH_TRACE] = IST("TRACE"),
340 [HTTP_METH_CONNECT] = IST("CONNECT"),
341};
342
343/*
344 * returns a known method among HTTP_METH_* or HTTP_METH_OTHER for all unknown
345 * ones.
346 */
347enum http_meth_t find_http_meth(const char *str, const int len)
348{
349 const struct ist m = ist2(str, len);
350
351 if (isteq(m, ist("GET"))) return HTTP_METH_GET;
352 else if (isteq(m, ist("HEAD"))) return HTTP_METH_HEAD;
353 else if (isteq(m, ist("POST"))) return HTTP_METH_POST;
354 else if (isteq(m, ist("CONNECT"))) return HTTP_METH_CONNECT;
355 else if (isteq(m, ist("PUT"))) return HTTP_METH_PUT;
356 else if (isteq(m, ist("OPTIONS"))) return HTTP_METH_OPTIONS;
357 else if (isteq(m, ist("DELETE"))) return HTTP_METH_DELETE;
358 else if (isteq(m, ist("TRACE"))) return HTTP_METH_TRACE;
359 else return HTTP_METH_OTHER;
360}
Willy Tarreau6b952c82018-09-10 17:45:34 +0200361
Willy Tarreau04f1e2d2018-09-10 18:04:24 +0200362/* This function returns HTTP_ERR_<num> (enum) matching http status code.
363 * Returned value should match codes from http_err_codes.
364 */
Willy Tarreau8de1df92019-04-15 21:27:18 +0200365int http_get_status_idx(unsigned int status)
Willy Tarreau04f1e2d2018-09-10 18:04:24 +0200366{
367 switch (status) {
368 case 200: return HTTP_ERR_200;
369 case 400: return HTTP_ERR_400;
370 case 403: return HTTP_ERR_403;
371 case 405: return HTTP_ERR_405;
372 case 408: return HTTP_ERR_408;
373 case 421: return HTTP_ERR_421;
374 case 425: return HTTP_ERR_425;
375 case 429: return HTTP_ERR_429;
376 case 500: return HTTP_ERR_500;
377 case 502: return HTTP_ERR_502;
378 case 503: return HTTP_ERR_503;
379 case 504: return HTTP_ERR_504;
380 default: return HTTP_ERR_500;
381 }
382}
383
384/* This function returns a reason associated with the HTTP status.
385 * This function never fails, a message is always returned.
386 */
387const char *http_get_reason(unsigned int status)
388{
389 switch (status) {
390 case 100: return "Continue";
391 case 101: return "Switching Protocols";
392 case 102: return "Processing";
393 case 200: return "OK";
394 case 201: return "Created";
395 case 202: return "Accepted";
396 case 203: return "Non-Authoritative Information";
397 case 204: return "No Content";
398 case 205: return "Reset Content";
399 case 206: return "Partial Content";
400 case 207: return "Multi-Status";
401 case 210: return "Content Different";
402 case 226: return "IM Used";
403 case 300: return "Multiple Choices";
404 case 301: return "Moved Permanently";
405 case 302: return "Moved Temporarily";
406 case 303: return "See Other";
407 case 304: return "Not Modified";
408 case 305: return "Use Proxy";
409 case 307: return "Temporary Redirect";
410 case 308: return "Permanent Redirect";
411 case 310: return "Too many Redirects";
412 case 400: return "Bad Request";
413 case 401: return "Unauthorized";
414 case 402: return "Payment Required";
415 case 403: return "Forbidden";
416 case 404: return "Not Found";
417 case 405: return "Method Not Allowed";
418 case 406: return "Not Acceptable";
419 case 407: return "Proxy Authentication Required";
420 case 408: return "Request Time-out";
421 case 409: return "Conflict";
422 case 410: return "Gone";
423 case 411: return "Length Required";
424 case 412: return "Precondition Failed";
425 case 413: return "Request Entity Too Large";
426 case 414: return "Request-URI Too Long";
427 case 415: return "Unsupported Media Type";
428 case 416: return "Requested range unsatisfiable";
429 case 417: return "Expectation failed";
430 case 418: return "I'm a teapot";
431 case 421: return "Misdirected Request";
432 case 422: return "Unprocessable entity";
433 case 423: return "Locked";
434 case 424: return "Method failure";
435 case 425: return "Too Early";
436 case 426: return "Upgrade Required";
437 case 428: return "Precondition Required";
438 case 429: return "Too Many Requests";
439 case 431: return "Request Header Fields Too Large";
440 case 449: return "Retry With";
441 case 450: return "Blocked by Windows Parental Controls";
442 case 451: return "Unavailable For Legal Reasons";
443 case 456: return "Unrecoverable Error";
444 case 499: return "client has closed connection";
445 case 500: return "Internal Server Error";
446 case 501: return "Not Implemented";
447 case 502: return "Bad Gateway or Proxy Error";
448 case 503: return "Service Unavailable";
449 case 504: return "Gateway Time-out";
450 case 505: return "HTTP Version not supported";
451 case 506: return "Variant also negociate";
452 case 507: return "Insufficient storage";
453 case 508: return "Loop detected";
454 case 509: return "Bandwidth Limit Exceeded";
455 case 510: return "Not extended";
456 case 511: return "Network authentication required";
457 case 520: return "Web server is returning an unknown error";
458 default:
459 switch (status) {
460 case 100 ... 199: return "Informational";
461 case 200 ... 299: return "Success";
462 case 300 ... 399: return "Redirection";
463 case 400 ... 499: return "Client Error";
464 case 500 ... 599: return "Server Error";
465 default: return "Other";
466 }
467 }
468}
469
Willy Tarreau6b952c82018-09-10 17:45:34 +0200470/* Parse the URI from the given transaction (which is assumed to be in request
471 * phase) and look for the "/" beginning the PATH. If not found, ist2(0,0) is
472 * returned. Otherwise the pointer and length are returned.
473 */
474struct ist http_get_path(const struct ist uri)
475{
476 const char *ptr, *end;
477
478 if (!uri.len)
479 goto not_found;
480
481 ptr = uri.ptr;
482 end = ptr + uri.len;
483
484 /* RFC7230, par. 2.7 :
485 * Request-URI = "*" | absuri | abspath | authority
486 */
487
488 if (*ptr == '*')
489 goto not_found;
490
491 if (isalpha((unsigned char)*ptr)) {
492 /* this is a scheme as described by RFC3986, par. 3.1 */
493 ptr++;
494 while (ptr < end &&
495 (isalnum((unsigned char)*ptr) || *ptr == '+' || *ptr == '-' || *ptr == '.'))
496 ptr++;
497 /* skip '://' */
498 if (ptr == end || *ptr++ != ':')
499 goto not_found;
500 if (ptr == end || *ptr++ != '/')
501 goto not_found;
502 if (ptr == end || *ptr++ != '/')
503 goto not_found;
504 }
505 /* skip [user[:passwd]@]host[:[port]] */
506
507 while (ptr < end && *ptr != '/')
508 ptr++;
509
510 if (ptr == end)
511 goto not_found;
512
513 /* OK, we got the '/' ! */
514 return ist2(ptr, end - ptr);
515
516 not_found:
517 return ist2(NULL, 0);
518}
Willy Tarreau04f1e2d2018-09-10 18:04:24 +0200519
Willy Tarreauab813a42018-09-10 18:41:28 +0200520/*
521 * Checks if <hdr> is exactly <name> for <len> chars, and ends with a colon.
522 * If so, returns the position of the first non-space character relative to
523 * <hdr>, or <end>-<hdr> if not found before. If no value is found, it tries
524 * to return a pointer to the place after the first space. Returns 0 if the
525 * header name does not match. Checks are case-insensitive.
526 */
527int http_header_match2(const char *hdr, const char *end,
528 const char *name, int len)
529{
530 const char *val;
531
532 if (hdr + len >= end)
533 return 0;
534 if (hdr[len] != ':')
535 return 0;
536 if (strncasecmp(hdr, name, len) != 0)
537 return 0;
538 val = hdr + len + 1;
539 while (val < end && HTTP_IS_SPHT(*val))
540 val++;
541 if ((val >= end) && (len + 2 <= end - hdr))
542 return len + 2; /* we may replace starting from second space */
543 return val - hdr;
544}
545
546/* Find the end of the header value contained between <s> and <e>. See RFC7230,
547 * par 3.2 for more information. Note that it requires a valid header to return
548 * a valid result. This works for headers defined as comma-separated lists.
549 */
550char *http_find_hdr_value_end(char *s, const char *e)
551{
552 int quoted, qdpair;
553
554 quoted = qdpair = 0;
555
556#if defined(__x86_64__) || \
557 defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) || \
558 defined(__ARM_ARCH_7A__)
559 /* speedup: skip everything not a comma nor a double quote */
560 for (; s <= e - sizeof(int); s += sizeof(int)) {
561 unsigned int c = *(int *)s; // comma
562 unsigned int q = c; // quote
563
564 c ^= 0x2c2c2c2c; // contains one zero on a comma
565 q ^= 0x22222222; // contains one zero on a quote
566
567 c = (c - 0x01010101) & ~c; // contains 0x80 below a comma
568 q = (q - 0x01010101) & ~q; // contains 0x80 below a quote
569
570 if ((c | q) & 0x80808080)
571 break; // found a comma or a quote
572 }
573#endif
574 for (; s < e; s++) {
575 if (qdpair) qdpair = 0;
576 else if (quoted) {
577 if (*s == '\\') qdpair = 1;
578 else if (*s == '"') quoted = 0;
579 }
580 else if (*s == '"') quoted = 1;
581 else if (*s == ',') return s;
582 }
583 return s;
584}
585
586/* Find the end of a cookie value contained between <s> and <e>. It works the
587 * same way as with headers above except that the semi-colon also ends a token.
588 * See RFC2965 for more information. Note that it requires a valid header to
589 * return a valid result.
590 */
591char *http_find_cookie_value_end(char *s, const char *e)
592{
593 int quoted, qdpair;
594
595 quoted = qdpair = 0;
596 for (; s < e; s++) {
597 if (qdpair) qdpair = 0;
598 else if (quoted) {
599 if (*s == '\\') qdpair = 1;
600 else if (*s == '"') quoted = 0;
601 }
602 else if (*s == '"') quoted = 1;
603 else if (*s == ',' || *s == ';') return s;
604 }
605 return s;
606}
607
608/* Try to find the next occurrence of a cookie name in a cookie header value.
609 * The lookup begins at <hdr>. The pointer and size of the next occurrence of
610 * the cookie value is returned into *value and *value_l, and the function
611 * returns a pointer to the next pointer to search from if the value was found.
612 * Otherwise if the cookie was not found, NULL is returned and neither value
613 * nor value_l are touched. The input <hdr> string should first point to the
614 * header's value, and the <hdr_end> pointer must point to the first character
615 * not part of the value. <list> must be non-zero if value may represent a list
616 * of values (cookie headers). This makes it faster to abort parsing when no
617 * list is expected.
618 */
619char *http_extract_cookie_value(char *hdr, const char *hdr_end,
620 char *cookie_name, size_t cookie_name_l,
621 int list, char **value, size_t *value_l)
622{
623 char *equal, *att_end, *att_beg, *val_beg, *val_end;
624 char *next;
625
626 /* we search at least a cookie name followed by an equal, and more
627 * generally something like this :
628 * Cookie: NAME1 = VALUE 1 ; NAME2 = VALUE2 ; NAME3 = VALUE3\r\n
629 */
630 for (att_beg = hdr; att_beg + cookie_name_l + 1 < hdr_end; att_beg = next + 1) {
631 /* Iterate through all cookies on this line */
632
633 while (att_beg < hdr_end && HTTP_IS_SPHT(*att_beg))
634 att_beg++;
635
636 /* find att_end : this is the first character after the last non
637 * space before the equal. It may be equal to hdr_end.
638 */
639 equal = att_end = att_beg;
640
641 while (equal < hdr_end) {
642 if (*equal == '=' || *equal == ';' || (list && *equal == ','))
643 break;
644 if (HTTP_IS_SPHT(*equal++))
645 continue;
646 att_end = equal;
647 }
648
649 /* here, <equal> points to '=', a delimitor or the end. <att_end>
650 * is between <att_beg> and <equal>, both may be identical.
651 */
652
653 /* look for end of cookie if there is an equal sign */
654 if (equal < hdr_end && *equal == '=') {
655 /* look for the beginning of the value */
656 val_beg = equal + 1;
657 while (val_beg < hdr_end && HTTP_IS_SPHT(*val_beg))
658 val_beg++;
659
660 /* find the end of the value, respecting quotes */
661 next = http_find_cookie_value_end(val_beg, hdr_end);
662
663 /* make val_end point to the first white space or delimitor after the value */
664 val_end = next;
665 while (val_end > val_beg && HTTP_IS_SPHT(*(val_end - 1)))
666 val_end--;
667 } else {
668 val_beg = val_end = next = equal;
669 }
670
671 /* We have nothing to do with attributes beginning with '$'. However,
672 * they will automatically be removed if a header before them is removed,
673 * since they're supposed to be linked together.
674 */
675 if (*att_beg == '$')
676 continue;
677
678 /* Ignore cookies with no equal sign */
679 if (equal == next)
680 continue;
681
682 /* Now we have the cookie name between att_beg and att_end, and
683 * its value between val_beg and val_end.
684 */
685
686 if (att_end - att_beg == cookie_name_l &&
687 memcmp(att_beg, cookie_name, cookie_name_l) == 0) {
688 /* let's return this value and indicate where to go on from */
689 *value = val_beg;
690 *value_l = val_end - val_beg;
691 return next + 1;
692 }
693
694 /* Set-Cookie headers only have the name in the first attr=value part */
695 if (!list)
696 break;
697 }
698
699 return NULL;
700}
701
Joseph Herlant942eea32018-11-15 13:57:22 -0800702/* Parses a qvalue and returns it multiplied by 1000, from 0 to 1000. If the
Willy Tarreauab813a42018-09-10 18:41:28 +0200703 * value is larger than 1000, it is bound to 1000. The parser consumes up to
704 * 1 digit, one dot and 3 digits and stops on the first invalid character.
705 * Unparsable qvalues return 1000 as "q=1.000".
706 */
707int http_parse_qvalue(const char *qvalue, const char **end)
708{
709 int q = 1000;
710
711 if (!isdigit((unsigned char)*qvalue))
712 goto out;
713 q = (*qvalue++ - '0') * 1000;
714
715 if (*qvalue++ != '.')
716 goto out;
717
718 if (!isdigit((unsigned char)*qvalue))
719 goto out;
720 q += (*qvalue++ - '0') * 100;
721
722 if (!isdigit((unsigned char)*qvalue))
723 goto out;
724 q += (*qvalue++ - '0') * 10;
725
726 if (!isdigit((unsigned char)*qvalue))
727 goto out;
728 q += (*qvalue++ - '0') * 1;
729 out:
730 if (q > 1000)
731 q = 1000;
732 if (end)
733 *end = qvalue;
734 return q;
735}
736
737/*
Joseph Herlant942eea32018-11-15 13:57:22 -0800738 * Given a url parameter, find the starting position of the first occurrence,
Willy Tarreauab813a42018-09-10 18:41:28 +0200739 * or NULL if the parameter is not found.
740 *
741 * Example: if query_string is "yo=mama;ye=daddy" and url_param_name is "ye",
742 * the function will return query_string+8.
743 *
744 * Warning: this function returns a pointer that can point to the first chunk
745 * or the second chunk. The caller must be check the position before using the
746 * result.
747 */
748const char *http_find_url_param_pos(const char **chunks,
749 const char* url_param_name, size_t url_param_name_l,
750 char delim)
751{
752 const char *pos, *last, *equal;
753 const char **bufs = chunks;
754 int l1, l2;
755
756
757 pos = bufs[0];
758 last = bufs[1];
759 while (pos < last) {
760 /* Check the equal. */
761 equal = pos + url_param_name_l;
762 if (fix_pointer_if_wrap(chunks, &equal)) {
763 if (equal >= chunks[3])
764 return NULL;
765 } else {
766 if (equal >= chunks[1])
767 return NULL;
768 }
769 if (*equal == '=') {
770 if (pos + url_param_name_l > last) {
771 /* process wrap case, we detect a wrap. In this case, the
772 * comparison is performed in two parts.
773 */
774
775 /* This is the end, we dont have any other chunk. */
776 if (bufs != chunks || !bufs[2])
777 return NULL;
778
779 /* Compute the length of each part of the comparison. */
780 l1 = last - pos;
781 l2 = url_param_name_l - l1;
782
783 /* The second buffer is too short to contain the compared string. */
784 if (bufs[2] + l2 > bufs[3])
785 return NULL;
786
787 if (memcmp(pos, url_param_name, l1) == 0 &&
788 memcmp(bufs[2], url_param_name+l1, l2) == 0)
789 return pos;
790
791 /* Perform wrapping and jump the string who fail the comparison. */
792 bufs += 2;
793 pos = bufs[0] + l2;
794 last = bufs[1];
795
796 } else {
797 /* process a simple comparison. */
798 if (memcmp(pos, url_param_name, url_param_name_l) == 0)
799 return pos;
800 pos += url_param_name_l + 1;
801 if (fix_pointer_if_wrap(chunks, &pos))
802 last = bufs[2];
803 }
804 }
805
806 while (1) {
807 /* Look for the next delimiter. */
808 while (pos < last && !http_is_param_delimiter(*pos, delim))
809 pos++;
810 if (pos < last)
811 break;
812 /* process buffer wrapping. */
813 if (bufs != chunks || !bufs[2])
814 return NULL;
815 bufs += 2;
816 pos = bufs[0];
817 last = bufs[1];
818 }
819 pos++;
820 }
821 return NULL;
822}
823
824/*
825 * Given a url parameter name and a query string, find the next value.
826 * An empty url_param_name matches the first available parameter.
827 * If the parameter is found, 1 is returned and *vstart / *vend are updated to
828 * respectively provide a pointer to the value and its end.
829 * Otherwise, 0 is returned and vstart/vend are not modified.
830 */
831int http_find_next_url_param(const char **chunks,
832 const char* url_param_name, size_t url_param_name_l,
833 const char **vstart, const char **vend, char delim)
834{
835 const char *arg_start, *qs_end;
836 const char *value_start, *value_end;
837
838 arg_start = chunks[0];
839 qs_end = chunks[1];
840 if (url_param_name_l) {
841 /* Looks for an argument name. */
842 arg_start = http_find_url_param_pos(chunks,
843 url_param_name, url_param_name_l,
844 delim);
845 /* Check for wrapping. */
846 if (arg_start >= qs_end)
847 qs_end = chunks[3];
848 }
849 if (!arg_start)
850 return 0;
851
852 if (!url_param_name_l) {
853 while (1) {
854 /* looks for the first argument. */
855 value_start = memchr(arg_start, '=', qs_end - arg_start);
856 if (!value_start) {
857 /* Check for wrapping. */
858 if (arg_start >= chunks[0] &&
859 arg_start < chunks[1] &&
860 chunks[2]) {
861 arg_start = chunks[2];
862 qs_end = chunks[3];
863 continue;
864 }
865 return 0;
866 }
867 break;
868 }
869 value_start++;
870 }
871 else {
872 /* Jump the argument length. */
873 value_start = arg_start + url_param_name_l + 1;
874
875 /* Check for pointer wrapping. */
876 if (fix_pointer_if_wrap(chunks, &value_start)) {
877 /* Update the end pointer. */
878 qs_end = chunks[3];
879
880 /* Check for overflow. */
881 if (value_start >= qs_end)
882 return 0;
883 }
884 }
885
886 value_end = value_start;
887
888 while (1) {
889 while ((value_end < qs_end) && !http_is_param_delimiter(*value_end, delim))
890 value_end++;
891 if (value_end < qs_end)
892 break;
893 /* process buffer wrapping. */
894 if (value_end >= chunks[0] &&
895 value_end < chunks[1] &&
896 chunks[2]) {
897 value_end = chunks[2];
898 qs_end = chunks[3];
899 continue;
900 }
901 break;
902 }
903
904 *vstart = value_start;
905 *vend = value_end;
906 return 1;
907}
908
Christopher Faulet8277ca72018-10-22 15:12:04 +0200909/* Parses a single header line (without the CRLF) and splits it into its name
910 * and its value. The parsing is pretty naive and just skip spaces.
911 */
912int http_parse_header(const struct ist hdr, struct ist *name, struct ist *value)
913{
914 char *p = hdr.ptr;
915 char *end = p + hdr.len;
916
917 name->len = value->len = 0;
918
919 /* Skip leading spaces */
920 for (; p < end && HTTP_IS_SPHT(*p); p++);
921
922 /* Set the header name */
923 name->ptr = p;
924 for (; p < end && HTTP_IS_TOKEN(*p); p++);
925 name->len = p - name->ptr;
926
927 /* Skip the ':' and spaces before and after it */
928 for (; p < end && HTTP_IS_SPHT(*p); p++);
929 if (p < end && *p == ':') p++;
930 for (; p < end && HTTP_IS_SPHT(*p); p++);
931
932 /* Set the header value */
933 value->ptr = p;
934 value->len = end - p;
935
936 return 1;
937}
938
939/* Parses a single start line (without the CRLF) and splits it into 3 parts. The
940 * parsing is pretty naive and just skip spaces.
941 */
942int http_parse_stline(const struct ist line, struct ist *p1, struct ist *p2, struct ist *p3)
943{
944 char *p = line.ptr;
945 char *end = p + line.len;
946
947 p1->len = p2->len = p3->len = 0;
948
949 /* Skip leading spaces */
950 for (; p < end && HTTP_IS_SPHT(*p); p++);
951
952 /* Set the first part */
953 p1->ptr = p;
954 for (; p < end && HTTP_IS_TOKEN(*p); p++);
955 p1->len = p - p1->ptr;
956
957 /* Skip spaces between p1 and p2 */
958 for (; p < end && HTTP_IS_SPHT(*p); p++);
959
960 /* Set the second part */
961 p2->ptr = p;
962 for (; p < end && !HTTP_IS_SPHT(*p); p++);
963 p2->len = p - p2->ptr;
964
965 /* Skip spaces between p2 and p3 */
966 for (; p < end && HTTP_IS_SPHT(*p); p++);
967
968 /* The remaing is the third value */
969 p3->ptr = p;
970 p3->len = end - p;
971
972 return 1;
973}
974
Willy Tarreauab813a42018-09-10 18:41:28 +0200975
Tim Duesterhus3f024f32018-09-16 00:42:30 +0200976/* post-initializes the HTTP parts. Returns zero on error, with <err>
Willy Tarreau04f1e2d2018-09-10 18:04:24 +0200977 * pointing to the error message.
978 */
979int init_http(char **err)
980{
981 int msg;
982
983 for (msg = 0; msg < HTTP_ERR_SIZE; msg++) {
984 if (!http_err_msgs[msg]) {
985 memprintf(err, "Internal error: no message defined for HTTP return code %d", msg);
986 return 0;
987 }
988
989 http_err_chunks[msg].area = (char *)http_err_msgs[msg];
990 http_err_chunks[msg].data = strlen(http_err_msgs[msg]);
991 }
992 return 1;
993}