text: /*
   * HTTP semantics
   *
   * Copyright 2000-2018 Willy Tarreau <w@1wt.eu>
   *
   * This program is free software; you can redistribute it and/or
   * modify it under the terms of the GNU General Public License
   * as published by the Free Software Foundation; either version
   * 2 of the License, or (at your option) any later version.
   *
   */
  
  #include <ctype.h>
  #include <common/config.h>
  #include <common/http.h>
  #include <common/standard.h>
  
  /* It is about twice as fast on recent architectures to lookup a byte in a
   * table than to perform a boolean AND or OR between two tests. Refer to
   * RFC2616/RFC5234/RFC7230 for those chars. A token is any ASCII char that is
   * neither a separator nor a CTL char. An http ver_token is any ASCII which can
   * be found in an HTTP version, which includes 'H', 'T', 'P', '/', '.' and any
   * digit. Note: please do not overwrite values in assignment since gcc-2.95
   * will not handle them correctly. It's worth noting that chars 128..255 are
   * nothing, not even control chars.
   */
  const unsigned char http_char_classes[256] = {
  	[  0] = HTTP_FLG_CTL,
  	[  1] = HTTP_FLG_CTL,
  	[  2] = HTTP_FLG_CTL,
  	[  3] = HTTP_FLG_CTL,
  	[  4] = HTTP_FLG_CTL,
  	[  5] = HTTP_FLG_CTL,
  	[  6] = HTTP_FLG_CTL,
  	[  7] = HTTP_FLG_CTL,
  	[  8] = HTTP_FLG_CTL,
  	[  9] = HTTP_FLG_SPHT | HTTP_FLG_LWS | HTTP_FLG_SEP | HTTP_FLG_CTL,
  	[ 10] = HTTP_FLG_CRLF | HTTP_FLG_LWS | HTTP_FLG_CTL,
  	[ 11] = HTTP_FLG_CTL,
  	[ 12] = HTTP_FLG_CTL,
  	[ 13] = HTTP_FLG_CRLF | HTTP_FLG_LWS | HTTP_FLG_CTL,
  	[ 14] = HTTP_FLG_CTL,
  	[ 15] = HTTP_FLG_CTL,
  	[ 16] = HTTP_FLG_CTL,
  	[ 17] = HTTP_FLG_CTL,
  	[ 18] = HTTP_FLG_CTL,
  	[ 19] = HTTP_FLG_CTL,
  	[ 20] = HTTP_FLG_CTL,
  	[ 21] = HTTP_FLG_CTL,
  	[ 22] = HTTP_FLG_CTL,
  	[ 23] = HTTP_FLG_CTL,
  	[ 24] = HTTP_FLG_CTL,
  	[ 25] = HTTP_FLG_CTL,
  	[ 26] = HTTP_FLG_CTL,
  	[ 27] = HTTP_FLG_CTL,
  	[ 28] = HTTP_FLG_CTL,
  	[ 29] = HTTP_FLG_CTL,
  	[ 30] = HTTP_FLG_CTL,
  	[ 31] = HTTP_FLG_CTL,
  	[' '] = HTTP_FLG_SPHT | HTTP_FLG_LWS | HTTP_FLG_SEP,
  	['!'] = HTTP_FLG_TOK,
  	['"'] = HTTP_FLG_SEP,
  	['#'] = HTTP_FLG_TOK,
  	['$'] = HTTP_FLG_TOK,
  	['%'] = HTTP_FLG_TOK,
  	['&'] = HTTP_FLG_TOK,
  	[ 39] = HTTP_FLG_TOK,
  	['('] = HTTP_FLG_SEP,
  	[')'] = HTTP_FLG_SEP,
  	['*'] = HTTP_FLG_TOK,
  	['+'] = HTTP_FLG_TOK,
  	[','] = HTTP_FLG_SEP,
  	['-'] = HTTP_FLG_TOK,
  	['.'] = HTTP_FLG_TOK | HTTP_FLG_VER,
  	['/'] = HTTP_FLG_SEP | HTTP_FLG_VER,
  	['0'] = HTTP_FLG_TOK | HTTP_FLG_VER | HTTP_FLG_DIG,
  	['1'] = HTTP_FLG_TOK | HTTP_FLG_VER | HTTP_FLG_DIG,
  	['2'] = HTTP_FLG_TOK | HTTP_FLG_VER | HTTP_FLG_DIG,
  	['3'] = HTTP_FLG_TOK | HTTP_FLG_VER | HTTP_FLG_DIG,
  	['4'] = HTTP_FLG_TOK | HTTP_FLG_VER | HTTP_FLG_DIG,
  	['5'] = HTTP_FLG_TOK | HTTP_FLG_VER | HTTP_FLG_DIG,
  	['6'] = HTTP_FLG_TOK | HTTP_FLG_VER | HTTP_FLG_DIG,
  	['7'] = HTTP_FLG_TOK | HTTP_FLG_VER | HTTP_FLG_DIG,
  	['8'] = HTTP_FLG_TOK | HTTP_FLG_VER | HTTP_FLG_DIG,
  	['9'] = HTTP_FLG_TOK | HTTP_FLG_VER | HTTP_FLG_DIG,
  	[':'] = HTTP_FLG_SEP,
  	[';'] = HTTP_FLG_SEP,
  	['<'] = HTTP_FLG_SEP,
  	['='] = HTTP_FLG_SEP,
  	['>'] = HTTP_FLG_SEP,
  	['?'] = HTTP_FLG_SEP,
  	['@'] = HTTP_FLG_SEP,
  	['A'] = HTTP_FLG_TOK,
  	['B'] = HTTP_FLG_TOK,
  	['C'] = HTTP_FLG_TOK,
  	['D'] = HTTP_FLG_TOK,
  	['E'] = HTTP_FLG_TOK,
  	['F'] = HTTP_FLG_TOK,
  	['G'] = HTTP_FLG_TOK,
  	['H'] = HTTP_FLG_TOK | HTTP_FLG_VER,
  	['I'] = HTTP_FLG_TOK,
  	['J'] = HTTP_FLG_TOK,
  	['K'] = HTTP_FLG_TOK,
  	['L'] = HTTP_FLG_TOK,
  	['M'] = HTTP_FLG_TOK,
  	['N'] = HTTP_FLG_TOK,
  	['O'] = HTTP_FLG_TOK,
  	['P'] = HTTP_FLG_TOK | HTTP_FLG_VER,
  	['Q'] = HTTP_FLG_TOK,
  	['R'] = HTTP_FLG_TOK | HTTP_FLG_VER,
  	['S'] = HTTP_FLG_TOK | HTTP_FLG_VER,
  	['T'] = HTTP_FLG_TOK | HTTP_FLG_VER,
  	['U'] = HTTP_FLG_TOK,
  	['V'] = HTTP_FLG_TOK,
  	['W'] = HTTP_FLG_TOK,
  	['X'] = HTTP_FLG_TOK,
  	['Y'] = HTTP_FLG_TOK,
  	['Z'] = HTTP_FLG_TOK,
  	['['] = HTTP_FLG_SEP,
  	[ 92] = HTTP_FLG_SEP,
  	[']'] = HTTP_FLG_SEP,
  	['^'] = HTTP_FLG_TOK,
  	['_'] = HTTP_FLG_TOK,
  	['`'] = HTTP_FLG_TOK,
  	['a'] = HTTP_FLG_TOK,
  	['b'] = HTTP_FLG_TOK,
  	['c'] = HTTP_FLG_TOK,
  	['d'] = HTTP_FLG_TOK,
  	['e'] = HTTP_FLG_TOK,
  	['f'] = HTTP_FLG_TOK,
  	['g'] = HTTP_FLG_TOK,
  	['h'] = HTTP_FLG_TOK,
  	['i'] = HTTP_FLG_TOK,
  	['j'] = HTTP_FLG_TOK,
  	['k'] = HTTP_FLG_TOK,
  	['l'] = HTTP_FLG_TOK,
  	['m'] = HTTP_FLG_TOK,
  	['n'] = HTTP_FLG_TOK,
  	['o'] = HTTP_FLG_TOK,
  	['p'] = HTTP_FLG_TOK,
  	['q'] = HTTP_FLG_TOK,
  	['r'] = HTTP_FLG_TOK,
  	['s'] = HTTP_FLG_TOK,
  	['t'] = HTTP_FLG_TOK,
  	['u'] = HTTP_FLG_TOK,
  	['v'] = HTTP_FLG_TOK,
  	['w'] = HTTP_FLG_TOK,
  	['x'] = HTTP_FLG_TOK,
  	['y'] = HTTP_FLG_TOK,
  	['z'] = HTTP_FLG_TOK,
  	['{'] = HTTP_FLG_SEP,
  	['|'] = HTTP_FLG_TOK,
  	['}'] = HTTP_FLG_SEP,
  	['~'] = HTTP_FLG_TOK,
  	[127] = HTTP_FLG_CTL,
  };
  
  /* We must put the messages here since GCC cannot initialize consts depending
   * on strlen().
   */
  struct buffer http_err_chunks[HTTP_ERR_SIZE];
  
  const struct ist HTTP_100 = IST("HTTP/1.1 100 Continue\r\n\r\n");
  
  const struct ist HTTP_103 = IST("HTTP/1.1 103 Early Hints\r\n");
  
  /* Warning: no "connection" header is provided with the 3xx messages below */
  const char *HTTP_301 =
  	"HTTP/1.1 301 Moved Permanently\r\n"
  	"Content-length: 0\r\n"
  	"Location: "; /* not terminated since it will be concatenated with the URL */
  
  const char *HTTP_302 =
  	"HTTP/1.1 302 Found\r\n"
  	"Cache-Control: no-cache\r\n"
  	"Content-length: 0\r\n"
  	"Location: "; /* not terminated since it will be concatenated with the URL */
  
  /* same as 302 except that the browser MUST retry with the GET method */
  const char *HTTP_303 =
  	"HTTP/1.1 303 See Other\r\n"
  	"Cache-Control: no-cache\r\n"
  	"Content-length: 0\r\n"
  	"Location: "; /* not terminated since it will be concatenated with the URL */
  
  /* same as 302 except that the browser MUST retry with the same method */
  const char *HTTP_307 =
  	"HTTP/1.1 307 Temporary Redirect\r\n"
  	"Cache-Control: no-cache\r\n"
  	"Content-length: 0\r\n"
  	"Location: "; /* not terminated since it will be concatenated with the URL */
  
  /* same as 301 except that the browser MUST retry with the same method */
  const char *HTTP_308 =
  	"HTTP/1.1 308 Permanent Redirect\r\n"
  	"Content-length: 0\r\n"
  	"Location: "; /* not terminated since it will be concatenated with the URL */
  
  /* Warning: this one is an sprintf() fmt string, with <realm> as its only argument */
  const char *HTTP_401_fmt =
  	"HTTP/1.1 401 Unauthorized\r\n"
  	"Content-length: 112\r\n"
  	"Cache-Control: no-cache\r\n"
  	"Connection: close\r\n"
  	"Content-Type: text/html\r\n"
  	"WWW-Authenticate: Basic realm=\"%s\"\r\n"
  	"\r\n"
  	"<html><body><h1>401 Unauthorized</h1>\nYou need a valid user and password to access this content.\n</body></html>\n";
  
  const char *HTTP_407_fmt =
  	"HTTP/1.1 407 Unauthorized\r\n"
  	"Content-length: 112\r\n"
  	"Cache-Control: no-cache\r\n"
  	"Connection: close\r\n"
  	"Content-Type: text/html\r\n"
  	"Proxy-Authenticate: Basic realm=\"%s\"\r\n"
  	"\r\n"
  	"<html><body><h1>407 Unauthorized</h1>\nYou need a valid user and password to access this content.\n</body></html>\n";
  
  const int http_err_codes[HTTP_ERR_SIZE] = {
  	[HTTP_ERR_200] = 200,  /* used by "monitor-uri" */
  	[HTTP_ERR_400] = 400,
  	[HTTP_ERR_403] = 403,
  	[HTTP_ERR_405] = 405,
  	[HTTP_ERR_408] = 408,
  	[HTTP_ERR_421] = 421,
  	[HTTP_ERR_425] = 425,
  	[HTTP_ERR_429] = 429,
  	[HTTP_ERR_500] = 500,
  	[HTTP_ERR_502] = 502,
  	[HTTP_ERR_503] = 503,
  	[HTTP_ERR_504] = 504,
  };
  
  const char *http_err_msgs[HTTP_ERR_SIZE] = {
  	[HTTP_ERR_200] =
  	"HTTP/1.1 200 OK\r\n"
  	"Content-length: 58\r\n"
  	"Cache-Control: no-cache\r\n"
  	"Connection: close\r\n"
  	"Content-Type: text/html\r\n"
  	"\r\n"
  	"<html><body><h1>200 OK</h1>\nService ready.\n</body></html>\n",
  
  	[HTTP_ERR_400] =
  	"HTTP/1.1 400 Bad request\r\n"
  	"Content-length: 90\r\n"
  	"Cache-Control: no-cache\r\n"
  	"Connection: close\r\n"
  	"Content-Type: text/html\r\n"
  	"\r\n"
  	"<html><body><h1>400 Bad request</h1>\nYour browser sent an invalid request.\n</body></html>\n",
  
  	[HTTP_ERR_403] =
  	"HTTP/1.1 403 Forbidden\r\n"
  	"Content-length: 93\r\n"
  	"Cache-Control: no-cache\r\n"
  	"Connection: close\r\n"
  	"Content-Type: text/html\r\n"
  	"\r\n"
  	"<html><body><h1>403 Forbidden</h1>\nRequest forbidden by administrative rules.\n</body></html>\n",
  
  	[HTTP_ERR_405] =
  	"HTTP/1.1 405 Method Not Allowed\r\n"
  	"Content-length: 146\r\n"
  	"Cache-Control: no-cache\r\n"
  	"Connection: close\r\n"
  	"Content-Type: text/html\r\n"
  	"\r\n"
  	"<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",
  
  	[HTTP_ERR_408] =
  	"HTTP/1.1 408 Request Time-out\r\n"
  	"Content-length: 110\r\n"
  	"Cache-Control: no-cache\r\n"
  	"Connection: close\r\n"
  	"Content-Type: text/html\r\n"
  	"\r\n"
  	"<html><body><h1>408 Request Time-out</h1>\nYour browser didn't send a complete request in time.\n</body></html>\n",
  
  	[HTTP_ERR_421] =
  	"HTTP/1.1 421 Misdirected Request\r\n"
  	"Content-length: 104\r\n"
  	"Cache-Control: no-cache\r\n"
  	"Connection: close\r\n"
  	"Content-Type: text/html\r\n"
  	"\r\n"
  	"<html><body><h1>421 Misdirected Request</h1>\nRequest sent to a non-authoritative server.\n</body></html>\n",
  
  	[HTTP_ERR_425] =
  	"HTTP/1.1 425 Too Early\r\n"
  	"Content-length: 80\r\n"
  	"Cache-Control: no-cache\r\n"
  	"Connection: close\r\n"
  	"Content-Type: text/html\r\n"
  	"\r\n"
  	"<html><body><h1>425 Too Early</h1>\nYour browser sent early data.\n</body></html>\n",
  
  	[HTTP_ERR_429] =
  	"HTTP/1.1 429 Too Many Requests\r\n"
  	"Content-length: 117\r\n"
  	"Cache-Control: no-cache\r\n"
  	"Connection: close\r\n"
  	"Content-Type: text/html\r\n"
  	"\r\n"
  	"<html><body><h1>429 Too Many Requests</h1>\nYou have sent too many requests in a given amount of time.\n</body></html>\n",
  
  	[HTTP_ERR_500] =
  	"HTTP/1.1 500 Internal Server Error\r\n"
  	"Content-length: 96\r\n"
  	"Cache-Control: no-cache\r\n"
  	"Connection: close\r\n"
  	"Content-Type: text/html\r\n"
  	"\r\n"
  	"<html><body><h1>500 Internal Server Error</h1>\nAn internal server error occured.\n</body></html>\n",
  
  	[HTTP_ERR_502] =
  	"HTTP/1.1 502 Bad Gateway\r\n"
  	"Content-length: 107\r\n"
  	"Cache-Control: no-cache\r\n"
  	"Connection: close\r\n"
  	"Content-Type: text/html\r\n"
  	"\r\n"
  	"<html><body><h1>502 Bad Gateway</h1>\nThe server returned an invalid or incomplete response.\n</body></html>\n",
  
  	[HTTP_ERR_503] =
  	"HTTP/1.1 503 Service Unavailable\r\n"
  	"Content-length: 107\r\n"
  	"Cache-Control: no-cache\r\n"
  	"Connection: close\r\n"
  	"Content-Type: text/html\r\n"
  	"\r\n"
  	"<html><body><h1>503 Service Unavailable</h1>\nNo server is available to handle this request.\n</body></html>\n",
  
  	[HTTP_ERR_504] =
  	"HTTP/1.1 504 Gateway Time-out\r\n"
  	"Content-length: 92\r\n"
  	"Cache-Control: no-cache\r\n"
  	"Connection: close\r\n"
  	"Content-Type: text/html\r\n"
  	"\r\n"
  	"<html><body><h1>504 Gateway Time-out</h1>\nThe server didn't respond in time.\n</body></html>\n",
  
  };
  
  const struct ist http_known_methods[HTTP_METH_OTHER] = {
  	[HTTP_METH_OPTIONS] = IST("OPTIONS"),
  	[HTTP_METH_GET]     = IST("GET"),
  	[HTTP_METH_HEAD]    = IST("HEAD"),
  	[HTTP_METH_POST]    = IST("POST"),
  	[HTTP_METH_PUT]     = IST("PUT"),
  	[HTTP_METH_DELETE]  = IST("DELETE"),
  	[HTTP_METH_TRACE]   = IST("TRACE"),
  	[HTTP_METH_CONNECT] = IST("CONNECT"),
  };
  
  /*
   * returns a known method among HTTP_METH_* or HTTP_METH_OTHER for all unknown
   * ones.
   */
  enum http_meth_t find_http_meth(const char *str, const int len)
  {
  	const struct ist m = ist2(str, len);
  
  	if      (isteq(m, ist("GET")))     return HTTP_METH_GET;
  	else if (isteq(m, ist("HEAD")))    return HTTP_METH_HEAD;
  	else if (isteq(m, ist("POST")))    return HTTP_METH_POST;
  	else if (isteq(m, ist("CONNECT"))) return HTTP_METH_CONNECT;
  	else if (isteq(m, ist("PUT")))     return HTTP_METH_PUT;
  	else if (isteq(m, ist("OPTIONS"))) return HTTP_METH_OPTIONS;
  	else if (isteq(m, ist("DELETE")))  return HTTP_METH_DELETE;
  	else if (isteq(m, ist("TRACE")))   return HTTP_METH_TRACE;
  	else                               return HTTP_METH_OTHER;
  }
  
  /* This function returns HTTP_ERR_<num> (enum) matching http status code.
   * Returned value should match codes from http_err_codes.
   */
  int http_get_status_idx(unsigned int status)
  {
  	switch (status) {
  	case 200: return HTTP_ERR_200;
  	case 400: return HTTP_ERR_400;
  	case 403: return HTTP_ERR_403;
  	case 405: return HTTP_ERR_405;
  	case 408: return HTTP_ERR_408;
  	case 421: return HTTP_ERR_421;
  	case 425: return HTTP_ERR_425;
  	case 429: return HTTP_ERR_429;
  	case 500: return HTTP_ERR_500;
  	case 502: return HTTP_ERR_502;
  	case 503: return HTTP_ERR_503;
  	case 504: return HTTP_ERR_504;
  	default: return HTTP_ERR_500;
  	}
  }
  
  /* This function returns a reason associated with the HTTP status.
   * This function never fails, a message is always returned.
   */
  const char *http_get_reason(unsigned int status)
  {
  	switch (status) {
  	case 100: return "Continue";
  	case 101: return "Switching Protocols";
  	case 102: return "Processing";
  	case 200: return "OK";
  	case 201: return "Created";
  	case 202: return "Accepted";
  	case 203: return "Non-Authoritative Information";
  	case 204: return "No Content";
  	case 205: return "Reset Content";
  	case 206: return "Partial Content";
  	case 207: return "Multi-Status";
  	case 210: return "Content Different";
  	case 226: return "IM Used";
  	case 300: return "Multiple Choices";
  	case 301: return "Moved Permanently";
  	case 302: return "Moved Temporarily";
  	case 303: return "See Other";
  	case 304: return "Not Modified";
  	case 305: return "Use Proxy";
  	case 307: return "Temporary Redirect";
  	case 308: return "Permanent Redirect";
  	case 310: return "Too many Redirects";
  	case 400: return "Bad Request";
  	case 401: return "Unauthorized";
  	case 402: return "Payment Required";
  	case 403: return "Forbidden";
  	case 404: return "Not Found";
  	case 405: return "Method Not Allowed";
  	case 406: return "Not Acceptable";
  	case 407: return "Proxy Authentication Required";
  	case 408: return "Request Time-out";
  	case 409: return "Conflict";
  	case 410: return "Gone";
  	case 411: return "Length Required";
  	case 412: return "Precondition Failed";
  	case 413: return "Request Entity Too Large";
  	case 414: return "Request-URI Too Long";
  	case 415: return "Unsupported Media Type";
  	case 416: return "Requested range unsatisfiable";
  	case 417: return "Expectation failed";
  	case 418: return "I'm a teapot";
  	case 421: return "Misdirected Request";
  	case 422: return "Unprocessable entity";
  	case 423: return "Locked";
  	case 424: return "Method failure";
  	case 425: return "Too Early";
  	case 426: return "Upgrade Required";
  	case 428: return "Precondition Required";
  	case 429: return "Too Many Requests";
  	case 431: return "Request Header Fields Too Large";
  	case 449: return "Retry With";
  	case 450: return "Blocked by Windows Parental Controls";
  	case 451: return "Unavailable For Legal Reasons";
  	case 456: return "Unrecoverable Error";
  	case 499: return "client has closed connection";
  	case 500: return "Internal Server Error";
  	case 501: return "Not Implemented";
  	case 502: return "Bad Gateway or Proxy Error";
  	case 503: return "Service Unavailable";
  	case 504: return "Gateway Time-out";
  	case 505: return "HTTP Version not supported";
  	case 506: return "Variant also negociate";
  	case 507: return "Insufficient storage";
  	case 508: return "Loop detected";
  	case 509: return "Bandwidth Limit Exceeded";
  	case 510: return "Not extended";
  	case 511: return "Network authentication required";
  	case 520: return "Web server is returning an unknown error";
  	default:
  		switch (status) {
  		case 100 ... 199: return "Informational";
  		case 200 ... 299: return "Success";
  		case 300 ... 399: return "Redirection";
  		case 400 ... 499: return "Client Error";
  		case 500 ... 599: return "Server Error";
  		default:          return "Other";
  		}
  	}
  }
  
  /* Parse the URI from the given transaction (which is assumed to be in request
   * phase) and look for the "/" beginning the PATH. If not found, ist2(0,0) is
   * returned. Otherwise the pointer and length are returned.
   */
  struct ist http_get_path(const struct ist uri)
  {
  	const char *ptr, *end;
  
  	if (!uri.len)
  		goto not_found;
  
  	ptr = uri.ptr;
  	end = ptr + uri.len;
  
  	/* RFC7230, par. 2.7 :
  	 * Request-URI = "*" | absuri | abspath | authority
  	 */
  
  	if (*ptr == '*')
  		goto not_found;
  
  	if (isalpha((unsigned char)*ptr)) {
  		/* this is a scheme as described by RFC3986, par. 3.1 */
  		ptr++;
  		while (ptr < end &&
  		       (isalnum((unsigned char)*ptr) || *ptr == '+' || *ptr == '-' || *ptr == '.'))
  			ptr++;
  		/* skip '://' */
  		if (ptr == end || *ptr++ != ':')
  			goto not_found;
  		if (ptr == end || *ptr++ != '/')
  			goto not_found;
  		if (ptr == end || *ptr++ != '/')
  			goto not_found;
  	}
  	/* skip [user[:passwd]@]host[:[port]] */
  
  	while (ptr < end && *ptr != '/')
  		ptr++;
  
  	if (ptr == end)
  		goto not_found;
  
  	/* OK, we got the '/' ! */
  	return ist2(ptr, end - ptr);
  
   not_found:
  	return ist2(NULL, 0);
  }
  
  /*
   * Checks if <hdr> is exactly <name> for <len> chars, and ends with a colon.
   * If so, returns the position of the first non-space character relative to
   * <hdr>, or <end>-<hdr> if not found before. If no value is found, it tries
   * to return a pointer to the place after the first space. Returns 0 if the
   * header name does not match. Checks are case-insensitive.
   */
  int http_header_match2(const char *hdr, const char *end,
  		       const char *name, int len)
  {
  	const char *val;
  
  	if (hdr + len >= end)
  		return 0;
  	if (hdr[len] != ':')
  		return 0;
  	if (strncasecmp(hdr, name, len) != 0)
  		return 0;
  	val = hdr + len + 1;
  	while (val < end && HTTP_IS_SPHT(*val))
  		val++;
  	if ((val >= end) && (len + 2 <= end - hdr))
  		return len + 2; /* we may replace starting from second space */
  	return val - hdr;
  }
  
  /* Find the end of the header value contained between <s> and <e>. See RFC7230,
   * par 3.2 for more information. Note that it requires a valid header to return
   * a valid result. This works for headers defined as comma-separated lists.
   */
  char *http_find_hdr_value_end(char *s, const char *e)
  {
  	int quoted, qdpair;
  
  	quoted = qdpair = 0;
  
  #if defined(__x86_64__) ||						\
      defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) || \
      defined(__ARM_ARCH_7A__)
  	/* speedup: skip everything not a comma nor a double quote */
  	for (; s <= e - sizeof(int); s += sizeof(int)) {
  		unsigned int c = *(int *)s; // comma
  		unsigned int q = c;         // quote
  
  		c ^= 0x2c2c2c2c; // contains one zero on a comma
  		q ^= 0x22222222; // contains one zero on a quote
  
  		c = (c - 0x01010101) & ~c; // contains 0x80 below a comma
  		q = (q - 0x01010101) & ~q; // contains 0x80 below a quote
  
  		if ((c | q) & 0x80808080)
  			break; // found a comma or a quote
  	}
  #endif
  	for (; s < e; s++) {
  		if (qdpair)                    qdpair = 0;
  		else if (quoted) {
  			if (*s == '\\')        qdpair = 1;
  			else if (*s == '"')    quoted = 0;
  		}
  		else if (*s == '"')            quoted = 1;
  		else if (*s == ',')            return s;
  	}
  	return s;
  }
  
  /* Find the end of a cookie value contained between <s> and <e>. It works the
   * same way as with headers above except that the semi-colon also ends a token.
   * See RFC2965 for more information. Note that it requires a valid header to
   * return a valid result.
   */
  char *http_find_cookie_value_end(char *s, const char *e)
  {
  	int quoted, qdpair;
  
  	quoted = qdpair = 0;
  	for (; s < e; s++) {
  		if (qdpair)                    qdpair = 0;
  		else if (quoted) {
  			if (*s == '\\')        qdpair = 1;
  			else if (*s == '"')    quoted = 0;
  		}
  		else if (*s == '"')            quoted = 1;
  		else if (*s == ',' || *s == ';') return s;
  	}
  	return s;
  }
  
  /* Try to find the next occurrence of a cookie name in a cookie header value.
   * The lookup begins at <hdr>. The pointer and size of the next occurrence of
   * the cookie value is returned into *value and *value_l, and the function
   * returns a pointer to the next pointer to search from if the value was found.
   * Otherwise if the cookie was not found, NULL is returned and neither value
   * nor value_l are touched. The input <hdr> string should first point to the
   * header's value, and the <hdr_end> pointer must point to the first character
   * not part of the value. <list> must be non-zero if value may represent a list
   * of values (cookie headers). This makes it faster to abort parsing when no
   * list is expected.
   */
  char *http_extract_cookie_value(char *hdr, const char *hdr_end,
                                  char *cookie_name, size_t cookie_name_l,
                                  int list, char **value, size_t *value_l)
  {
  	char *equal, *att_end, *att_beg, *val_beg, *val_end;
  	char *next;
  
  	/* we search at least a cookie name followed by an equal, and more
  	 * generally something like this :
  	 * Cookie:    NAME1  =  VALUE 1  ; NAME2 = VALUE2 ; NAME3 = VALUE3\r\n
  	 */
  	for (att_beg = hdr; att_beg + cookie_name_l + 1 < hdr_end; att_beg = next + 1) {
  		/* Iterate through all cookies on this line */
  
  		while (att_beg < hdr_end && HTTP_IS_SPHT(*att_beg))
  			att_beg++;
  
  		/* find att_end : this is the first character after the last non
  		 * space before the equal. It may be equal to hdr_end.
  		 */
  		equal = att_end = att_beg;
  
  		while (equal < hdr_end) {
  			if (*equal == '=' || *equal == ';' || (list && *equal == ','))
  				break;
  			if (HTTP_IS_SPHT(*equal++))
  				continue;
  			att_end = equal;
  		}
  
  		/* here, <equal> points to '=', a delimitor or the end. <att_end>
  		 * is between <att_beg> and <equal>, both may be identical.
  		 */
  
  		/* look for end of cookie if there is an equal sign */
  		if (equal < hdr_end && *equal == '=') {
  			/* look for the beginning of the value */
  			val_beg = equal + 1;
  			while (val_beg < hdr_end && HTTP_IS_SPHT(*val_beg))
  				val_beg++;
  
  			/* find the end of the value, respecting quotes */
  			next = http_find_cookie_value_end(val_beg, hdr_end);
  
  			/* make val_end point to the first white space or delimitor after the value */
  			val_end = next;
  			while (val_end > val_beg && HTTP_IS_SPHT(*(val_end - 1)))
  				val_end--;
  		} else {
  			val_beg = val_end = next = equal;
  		}
  
  		/* We have nothing to do with attributes beginning with '$'. However,
  		 * they will automatically be removed if a header before them is removed,
  		 * since they're supposed to be linked together.
  		 */
  		if (*att_beg == '$')
  			continue;
  
  		/* Ignore cookies with no equal sign */
  		if (equal == next)
  			continue;
  
  		/* Now we have the cookie name between att_beg and att_end, and
  		 * its value between val_beg and val_end.
  		 */
  
  		if (att_end - att_beg == cookie_name_l &&
  		    memcmp(att_beg, cookie_name, cookie_name_l) == 0) {
  			/* let's return this value and indicate where to go on from */
  			*value = val_beg;
  			*value_l = val_end - val_beg;
  			return next + 1;
  		}
  
  		/* Set-Cookie headers only have the name in the first attr=value part */
  		if (!list)
  			break;
  	}
  
  	return NULL;
  }
  
  /* Parses a qvalue and returns it multiplied by 1000, from 0 to 1000. If the
   * value is larger than 1000, it is bound to 1000. The parser consumes up to
   * 1 digit, one dot and 3 digits and stops on the first invalid character.
   * Unparsable qvalues return 1000 as "q=1.000".
   */
  int http_parse_qvalue(const char *qvalue, const char **end)
  {
  	int q = 1000;
  
  	if (!isdigit((unsigned char)*qvalue))
  		goto out;
  	q = (*qvalue++ - '0') * 1000;
  
  	if (*qvalue++ != '.')
  		goto out;
  
  	if (!isdigit((unsigned char)*qvalue))
  		goto out;
  	q += (*qvalue++ - '0') * 100;
  
  	if (!isdigit((unsigned char)*qvalue))
  		goto out;
  	q += (*qvalue++ - '0') * 10;
  
  	if (!isdigit((unsigned char)*qvalue))
  		goto out;
  	q += (*qvalue++ - '0') * 1;
   out:
  	if (q > 1000)
  		q = 1000;
  	if (end)
  		*end = qvalue;
  	return q;
  }
  
  /*
   * Given a url parameter, find the starting position of the first occurrence,
   * or NULL if the parameter is not found.
   *
   * Example: if query_string is "yo=mama;ye=daddy" and url_param_name is "ye",
   * the function will return query_string+8.
   *
   * Warning: this function returns a pointer that can point to the first chunk
   * or the second chunk. The caller must be check the position before using the
   * result.
   */
  const char *http_find_url_param_pos(const char **chunks,
                                      const char* url_param_name, size_t url_param_name_l,
                                      char delim)
  {
  	const char *pos, *last, *equal;
  	const char **bufs = chunks;
  	int l1, l2;
  
  
  	pos  = bufs[0];
  	last = bufs[1];
  	while (pos < last) {
  		/* Check the equal. */
  		equal = pos + url_param_name_l;
  		if (fix_pointer_if_wrap(chunks, &equal)) {
  			if (equal >= chunks[3])
  				return NULL;
  		} else {
  			if (equal >= chunks[1])
  				return NULL;
  		}
  		if (*equal == '=') {
  			if (pos + url_param_name_l > last) {
  				/* process wrap case, we detect a wrap. In this case, the
  				 * comparison is performed in two parts.
  				 */
  
  				/* This is the end, we dont have any other chunk. */
  				if (bufs != chunks || !bufs[2])
  					return NULL;
  
  				/* Compute the length of each part of the comparison. */
  				l1 = last - pos;
  				l2 = url_param_name_l - l1;
  
  				/* The second buffer is too short to contain the compared string. */
  				if (bufs[2] + l2 > bufs[3])
  					return NULL;
  
  				if (memcmp(pos,     url_param_name,    l1) == 0 &&
  				    memcmp(bufs[2], url_param_name+l1, l2) == 0)
  					return pos;
  
  				/* Perform wrapping and jump the string who fail the comparison. */
  				bufs += 2;
  				pos = bufs[0] + l2;
  				last = bufs[1];
  
  			} else {
  				/* process a simple comparison. */
  				if (memcmp(pos, url_param_name, url_param_name_l) == 0)
  					return pos;
  				pos += url_param_name_l + 1;
  				if (fix_pointer_if_wrap(chunks, &pos))
  					last = bufs[2];
  			}
  		}
  
  		while (1) {
  			/* Look for the next delimiter. */
  			while (pos < last && !http_is_param_delimiter(*pos, delim))
  				pos++;
  			if (pos < last)
  				break;
  			/* process buffer wrapping. */
  			if (bufs != chunks || !bufs[2])
  				return NULL;
  			bufs += 2;
  			pos = bufs[0];
  			last = bufs[1];
  		}
  		pos++;
  	}
  	return NULL;
  }
  
  /*
   * Given a url parameter name and a query string, find the next value.
   * An empty url_param_name matches the first available parameter.
   * If the parameter is found, 1 is returned and *vstart / *vend are updated to
   * respectively provide a pointer to the value and its end.
   * Otherwise, 0 is returned and vstart/vend are not modified.
   */
  int http_find_next_url_param(const char **chunks,
                               const char* url_param_name, size_t url_param_name_l,
                               const char **vstart, const char **vend, char delim)
  {
  	const char *arg_start, *qs_end;
  	const char *value_start, *value_end;
  
  	arg_start = chunks[0];
  	qs_end = chunks[1];
  	if (url_param_name_l) {
  		/* Looks for an argument name. */
  		arg_start = http_find_url_param_pos(chunks,
  		                                    url_param_name, url_param_name_l,
  		                                    delim);
  		/* Check for wrapping. */
  		if (arg_start >= qs_end)
  			qs_end = chunks[3];
  	}
  	if (!arg_start)
  		return 0;
  
  	if (!url_param_name_l) {
  		while (1) {
  			/* looks for the first argument. */
  			value_start = memchr(arg_start, '=', qs_end - arg_start);
  			if (!value_start) {
  				/* Check for wrapping. */
  				if (arg_start >= chunks[0] &&
  				    arg_start < chunks[1] &&
  				    chunks[2]) {
  					arg_start = chunks[2];
  					qs_end = chunks[3];
  					continue;
  				}
  				return 0;
  			}
  			break;
  		}
  		value_start++;
  	}
  	else {
  		/* Jump the argument length. */
  		value_start = arg_start + url_param_name_l + 1;
  
  		/* Check for pointer wrapping. */
  		if (fix_pointer_if_wrap(chunks, &value_start)) {
  			/* Update the end pointer. */
  			qs_end = chunks[3];
  
  			/* Check for overflow. */
  			if (value_start >= qs_end)
  				return 0;
  		}
  	}
  
  	value_end = value_start;
  
  	while (1) {
  		while ((value_end < qs_end) && !http_is_param_delimiter(*value_end, delim))
  			value_end++;
  		if (value_end < qs_end)
  			break;
  		/* process buffer wrapping. */
  		if (value_end >= chunks[0] &&
  		    value_end < chunks[1] &&
  		    chunks[2]) {
  			value_end = chunks[2];
  			qs_end = chunks[3];
  			continue;
  		}
  		break;
  	}
  
  	*vstart = value_start;
  	*vend = value_end;
  	return 1;
  }
  
  /* Parses a single header line (without the CRLF) and splits it into its name
   * and its value. The parsing is pretty naive and just skip spaces.
   */
  int http_parse_header(const struct ist hdr, struct ist *name, struct ist *value)
  {
          char *p   = hdr.ptr;
          char *end = p + hdr.len;
  
          name->len = value->len = 0;
  
          /* Skip leading spaces */
          for (; p < end && HTTP_IS_SPHT(*p); p++);
  
          /* Set the header name */
          name->ptr = p;
          for (; p < end && HTTP_IS_TOKEN(*p); p++);
          name->len = p - name->ptr;
  
          /* Skip the ':' and spaces before and after it */
          for (; p < end && HTTP_IS_SPHT(*p); p++);
          if (p < end && *p == ':') p++;
          for (; p < end && HTTP_IS_SPHT(*p); p++);
  
          /* Set the header value */
          value->ptr = p;
          value->len = end - p;
  
          return 1;
  }
  
  /* Parses a single start line (without the CRLF) and splits it into 3 parts. The
   * parsing is pretty naive and just skip spaces.
   */
  int http_parse_stline(const struct ist line, struct ist *p1, struct ist *p2, struct ist *p3)
  {
          char *p   = line.ptr;
          char *end = p + line.len;
  
          p1->len = p2->len = p3->len = 0;
  
          /* Skip leading spaces */
          for (; p < end && HTTP_IS_SPHT(*p); p++);
  
          /* Set the first part */
          p1->ptr = p;
          for (; p < end && HTTP_IS_TOKEN(*p); p++);
          p1->len = p - p1->ptr;
  
          /* Skip spaces between p1 and p2 */
          for (; p < end && HTTP_IS_SPHT(*p); p++);
  
          /* Set the second part */
          p2->ptr = p;
          for (; p < end && !HTTP_IS_SPHT(*p); p++);
          p2->len = p - p2->ptr;
  
          /* Skip spaces between p2 and p3 */
          for (; p < end && HTTP_IS_SPHT(*p); p++);
  
          /* The remaing is the third value */
          p3->ptr = p;
          p3->len = end - p;
  
          return 1;
  }
  
  
  /* post-initializes the HTTP parts. Returns zero on error, with <err>
   * pointing to the error message.
   */
  int init_http(char **err)
  {
  	int msg;
  
  	for (msg = 0; msg < HTTP_ERR_SIZE; msg++) {
  		if (!http_err_msgs[msg]) {
  			memprintf(err, "Internal error: no message defined for HTTP return code %d", msg);
  			return 0;
  		}
  
  		http_err_chunks[msg].area = (char *)http_err_msgs[msg];
  		http_err_chunks[msg].data = strlen(http_err_msgs[msg]);
  	}
  	return 1;
  }
  