Skip to content
Snippets Groups Projects
http.c 61.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	while (len--) {
    		c = *s++;
    		if (c == '\x0D') {
    
    		if (c == ';') {
    			/* We have a chunk-extension that we don't care about. */
    			while (len--) {
    				if (*s++ == '\x0D') {
    					return value;
    				}
    			}
    			break;
    		}
    
    		value <<= 4;
    		if (c >= '0' && c <= '9') {
    			value += c - '0';
    			continue;
    		}
    		if (c >= 'a' && c <= 'f') {
    			value += 10 + c - 'a';
    			continue;
    		}
    		if (c >= 'A' && c <= 'F') {
    			value += 10 + c - 'A';
    			continue;
    		}
    		/* invalid character */
    		return -1;
    	}
    	/* end of string */
    	return -1;
    }
    
    
    /*!
     * \internal
     * \brief Read and convert the chunked body header length.
     * \since 12.4.0
     *
     * \param ser HTTP TCP/TLS session object.
     *
     * \retval length Size of chunk to expect.
     * \retval -1 on error.
     */
    static int http_body_get_chunk_length(struct ast_tcptls_session_instance *ser)
    {
    	int length;
    	char header_line[MAX_HTTP_LINE_LENGTH];
    
    	/* get the line of hexadecimal giving chunk-size w/ optional chunk-extension */
    	if (!fgets(header_line, sizeof(header_line), ser->f)) {
    		ast_log(LOG_WARNING, "Short HTTP read of chunked header\n");
    		return -1;
    	}
    	length = chunked_atoh(header_line, strlen(header_line));
    	if (length < 0) {
    		ast_log(LOG_WARNING, "Invalid HTTP chunk size\n");
    		return -1;
    	}
    	return length;
    }
    
    /*!
     * \internal
     * \brief Read and check the chunk contents line termination.
     * \since 12.4.0
     *
     * \param ser HTTP TCP/TLS session object.
     *
     * \retval 0 on success.
     * \retval -1 on error.
     */
    static int http_body_check_chunk_sync(struct ast_tcptls_session_instance *ser)
    {
    	int res;
    	char chunk_sync[2];
    
    
    	/*
    	 * NOTE: Because ser->f is a non-standard FILE *, fread() does not behave as
    	 * documented.
    	 */
    
    
    	/* Stay in fread until get the expected CRLF or timeout. */
    	res = fread(chunk_sync, sizeof(chunk_sync), 1, ser->f);
    	if (res < 1) {
    		ast_log(LOG_WARNING, "Short HTTP chunk sync read (Wanted %zu)\n",
    			sizeof(chunk_sync));
    		return -1;
    	}
    	if (chunk_sync[0] != 0x0D || chunk_sync[1] != 0x0A) {
    
    		ast_log(LOG_WARNING, "HTTP chunk sync bytes wrong (0x%02hhX, 0x%02hhX)\n",
    			(unsigned char) chunk_sync[0], (unsigned char) chunk_sync[1]);
    
    		return -1;
    	}
    
    	return 0;
    }
    
    /*!
     * \internal
     * \brief Read and discard any chunked trailer entity-header lines.
     * \since 12.4.0
     *
     * \param ser HTTP TCP/TLS session object.
     *
     * \retval 0 on success.
     * \retval -1 on error.
     */
    static int http_body_discard_chunk_trailer_headers(struct ast_tcptls_session_instance *ser)
    {
    	char header_line[MAX_HTTP_LINE_LENGTH];
    
    	for (;;) {
    		if (!fgets(header_line, sizeof(header_line), ser->f)) {
    			ast_log(LOG_WARNING, "Short HTTP read of chunked trailer header\n");
    			return -1;
    		}
    
    		/* Trim trailing whitespace */
    		ast_trim_blanks(header_line);
    		if (ast_strlen_zero(header_line)) {
    			/* A blank line ends the chunked-body */
    			break;
    		}
    	}
    	return 0;
    }
    
    int ast_http_body_discard(struct ast_tcptls_session_instance *ser)
    {
    	struct http_worker_private_data *request;
    
    	request = ser->private_data;
    	if (!ast_test_flag(&request->flags, HTTP_FLAG_HAS_BODY)
    		|| ast_test_flag(&request->flags, HTTP_FLAG_BODY_READ)) {
    		/* No body to read or it has already been read. */
    		return 0;
    	}
    	ast_set_flag(&request->flags, HTTP_FLAG_BODY_READ);
    
    	ast_debug(1, "HTTP discarding unused request body\n");
    
    	ast_assert(request->body_length != 0);
    	if (0 < request->body_length) {
    		if (http_body_discard_contents(ser, request->body_length, "body")) {
    			ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
    			return -1;
    		}
    		return 0;
    	}
    
    	/* parse chunked-body */
    	for (;;) {
    		int length;
    
    		length = http_body_get_chunk_length(ser);
    		if (length < 0) {
    			ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
    			return -1;
    		}
    		if (length == 0) {
    			/* parsed last-chunk */
    			break;
    		}
    
    		if (http_body_discard_contents(ser, length, "chunk-data")
    			|| http_body_check_chunk_sync(ser)) {
    			ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
    			return -1;
    		}
    	}
    
    	/* Read and discard any trailer entity-header lines. */
    	if (http_body_discard_chunk_trailer_headers(ser)) {
    		ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
    		return -1;
    	}
    	return 0;
    }
    
    
    /*!
     * \brief Returns the contents (body) of the HTTP request
     *
     * \param return_length ptr to int that returns content length
    
     * \param ser HTTP TCP/TLS session object
    
     * \param headers List of HTTP headers
     * \return ptr to content (zero terminated) or NULL on failure
     * \note Since returned ptr is malloc'd, it should be free'd by caller
     */
    static char *ast_http_get_contents(int *return_length,
    	struct ast_tcptls_session_instance *ser, struct ast_variable *headers)
    {
    
    	struct http_worker_private_data *request;
    	int content_length;
    	int bufsize;
    
    	request = ser->private_data;
    	if (!ast_test_flag(&request->flags, HTTP_FLAG_HAS_BODY)) {
    		/* no content - not an error */
    		return NULL;
    	}
    	if (ast_test_flag(&request->flags, HTTP_FLAG_BODY_READ)) {
    		/* Already read the body.  Cannot read again.  Assume no content. */
    		ast_assert(0);
    		return NULL;
    	}
    	ast_set_flag(&request->flags, HTTP_FLAG_BODY_READ);
    
    	ast_debug(2, "HTTP consuming request body\n");
    
    	ast_assert(request->body_length != 0);
    	if (0 < request->body_length) {
    
    		/* handle regular non-chunked content */
    
    		content_length = request->body_length;
    		if (content_length > MAX_CONTENT_LENGTH) {
    			ast_log(LOG_WARNING, "Excessively long HTTP content. (%d > %d)\n",
    				content_length, MAX_CONTENT_LENGTH);
    			ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
    
    			errno = EFBIG;
    			return NULL;
    		}
    		buf = ast_malloc(content_length + 1);
    		if (!buf) {
    			/* Malloc sets ENOMEM */
    
    			ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
    
    
    		if (http_body_read_contents(ser, buf, content_length, "body")) {
    			ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
    
    			errno = EIO;
    			ast_free(buf);
    			return NULL;
    		}
    
    		buf[content_length] = 0;
    		*return_length = content_length;
    		return buf;
    	}
    
    	/* pre-allocate buffer */
    
    	buf = ast_malloc(bufsize);
    	if (!buf) {
    
    		ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
    
    	/* parse chunked-body */
    	content_length = 0;
    	for (;;) {
    		int chunk_length;
    
    		chunk_length = http_body_get_chunk_length(ser);
    
    		if (chunk_length < 0) {
    
    			ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
    
    			errno = EIO;
    			ast_free(buf);
    			return NULL;
    		}
    
    		if (chunk_length == 0) {
    			/* parsed last-chunk */
    			break;
    		}
    		if (content_length + chunk_length > MAX_CONTENT_LENGTH) {
    
    			ast_log(LOG_WARNING,
    
    				"Excessively long HTTP accumulated chunked body. (%d + %d > %d)\n",
    				content_length, chunk_length, MAX_CONTENT_LENGTH);
    			ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
    
    			errno = EFBIG;
    			ast_free(buf);
    			return NULL;
    		}
    
    		/* insure buffer is large enough +1 */
    
    		if (content_length + chunk_length >= bufsize) {
    			char *new_buf;
    
    			/* Increase bufsize until it can handle the expected data. */
    			do {
    				bufsize *= 2;
    			} while (content_length + chunk_length >= bufsize);
    
    			new_buf = ast_realloc(buf, bufsize);
    			if (!new_buf) {
    				ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
    				ast_free(buf);
    
    		if (http_body_read_contents(ser, buf + content_length, chunk_length, "chunk-data")
    			|| http_body_check_chunk_sync(ser)) {
    			ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
    
    			errno = EIO;
    			ast_free(buf);
    			return NULL;
    		}
    		content_length += chunk_length;
    
    	/*
    	 * Read and discard any trailer entity-header lines
    	 * which we don't care about.
    	 *
    	 * XXX In the future we may need to add the trailer headers
    	 * to the passed in headers list rather than discarding them.
    	 */
    	if (http_body_discard_chunk_trailer_headers(ser)) {
    		ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
    		errno = EIO;
    		ast_free(buf);
    		return NULL;
    	}
    
    
    	buf[content_length] = 0;
    	*return_length = content_length;
    	return buf;
    }
    
    
    struct ast_json *ast_http_get_json(
    	struct ast_tcptls_session_instance *ser, struct ast_variable *headers)
    {
    	int content_length = 0;
    	struct ast_json *body;
    	RAII_VAR(char *, buf, NULL, ast_free);
    
    	RAII_VAR(char *, type, get_content_type(headers), ast_free);
    
    
    	/* Use errno to distinguish errors from no body */
    	errno = 0;
    
    
    	if (ast_strlen_zero(type) || strcasecmp(type, "application/json")) {
    
    		/* Content type is not JSON.  Don't read the body. */
    
    	buf = ast_http_get_contents(&content_length, ser, headers);
    
    	if (!buf || !content_length) {
    		/*
    		 * errno already set
    		 * or it is not an error to have zero content
    		 */
    
    	body = ast_json_load_buf(buf, content_length, NULL);
    
    		/* Failed to parse JSON; treat as an I/O error */
    		errno = EIO;
    		return NULL;
    	}
    
    	return body;
    }
    
    
    /*
     * get post variables from client Request Entity-Body, if content type is
     * application/x-www-form-urlencoded
     */
    struct ast_variable *ast_http_get_post_vars(
    	struct ast_tcptls_session_instance *ser, struct ast_variable *headers)
    {
    	int content_length = 0;
    	struct ast_variable *v, *post_vars=NULL, *prev = NULL;
    
    	RAII_VAR(char *, buf, NULL, ast_free);
    
    	RAII_VAR(char *, type, get_content_type(headers), ast_free);
    
    	/* Use errno to distinguish errors from no params */
    	errno = 0;
    
    
    	if (ast_strlen_zero(type) ||
    	    strcasecmp(type, "application/x-www-form-urlencoded")) {
    
    		/* Content type is not form data.  Don't read the body. */
    
    	buf = ast_http_get_contents(&content_length, ser, headers);
    
    	if (!buf || !content_length) {
    		/*
    		 * errno already set
    		 * or it is not an error to have zero content
    		 */
    
    	while ((val = strsep(&buf, "&"))) {
    		var = strsep(&val, "=");
    		if (val) {
    
    			ast_uri_decode(val, ast_uri_http_legacy);
    
    		ast_uri_decode(var, ast_uri_http_legacy);
    
    		if ((v = ast_variable_new(var, val, ""))) {
    			if (post_vars) {
    				prev->next = v;
    			} else {
    				post_vars = v;
    			}
    			prev = v;
    		}
    	}
    
    	return post_vars;
    }
    
    static int handle_uri(struct ast_tcptls_session_instance *ser, char *uri,
    	enum ast_http_method method, struct ast_variable *headers)
    
    	struct ast_variable *get_vars = NULL, *v, *prev = NULL;
    
    	ast_debug(2, "HTTP Request URI is %s \n", uri);
    
    	strsep(&params, "?");
    	/* Extract arguments from the request and store them in variables. */
    	if (params) {
    		char *var, *val;
    
    		while ((val = strsep(&params, "&"))) {
    			var = strsep(&val, "=");
    			if (val) {
    
    				ast_uri_decode(val, ast_uri_http_legacy);
    
    			ast_uri_decode(var, ast_uri_http_legacy);
    
    			if ((v = ast_variable_new(var, val, ""))) {
    				if (get_vars) {
    					prev->next = v;
    
    	AST_RWLIST_RDLOCK(&uri_redirects);
    	AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
    
    		if (!strcasecmp(uri, redirect->target)) {
    
    			struct ast_str *http_header = ast_str_create(128);
    
    
    			if (!http_header) {
    				ast_http_request_close_on_completion(ser);
    				ast_http_error(ser, 500, "Server Error", "Out of memory");
    				break;
    			}
    
    			ast_str_set(&http_header, 0, "Location: %s\r\n", redirect->dest);
    			ast_http_send(ser, method, 302, "Moved Temporarily", http_header, NULL, 0, 0);
    
    	AST_RWLIST_UNLOCK(&uri_redirects);
    
    	/* We want requests to start with the (optional) prefix and '/' */
    
    	l = strlen(prefix);
    	if (!strncasecmp(uri, prefix, l) && uri[l] == '/') {
    
    		/* scan registered uris to see if we match one. */
    
    		AST_RWLIST_RDLOCK(&uris);
    		AST_RWLIST_TRAVERSE(&uris, urih, entry) {
    
    			l = strlen(urih->uri);
    			c = uri + l;	/* candidate */
    
    Matthew Jordan's avatar
    Matthew Jordan committed
    			ast_debug(2, "match request [%s] with handler [%s] len %d\n", uri, urih->uri, l);
    
    			if (strncasecmp(urih->uri, uri, l) /* no match */
    			    || (*c && *c != '/')) { /* substring */
    
    			if (!*c || urih->has_subtree) {
    
    		AST_RWLIST_UNLOCK(&uris);
    
    Matthew Jordan's avatar
    Matthew Jordan committed
    		ast_debug(1, "Match made with [%s]\n", urih->uri);
    
    		if (!urih->no_decode_uri) {
    			ast_uri_decode(uri, ast_uri_http_legacy);
    		}
    
    		res = urih->callback(ser, urih, uri, method, get_vars, headers);
    
    Matthew Jordan's avatar
    Matthew Jordan committed
    		ast_debug(1, "Requested URI [%s] has no handler\n", uri);
    
    		ast_http_error(ser, 404, "Not Found", "The requested URL was not found on this server.");
    
    	ast_variables_destroy(get_vars);
    	return res;
    
    static struct ast_variable *parse_cookies(const char *cookies)
    
    	char *parse = ast_strdupa(cookies);
    
    	char *cur;
    	struct ast_variable *vars = NULL, *var;
    
    
    	while ((cur = strsep(&parse, ";"))) {
    
    		char *name, *val;
    
    		name = val = cur;
    		strsep(&val, "=");
    
    		if (ast_strlen_zero(name) || ast_strlen_zero(val)) {
    			continue;
    		}
    
    		name = ast_strip(name);
    		val = ast_strip_quoted(val, "\"", "\"");
    
    		if (ast_strlen_zero(name) || ast_strlen_zero(val)) {
    			continue;
    		}
    
    
    		ast_debug(1, "HTTP Cookie, Name: '%s'  Value: '%s'\n", name, val);
    
    
    		var = ast_variable_new(name, val, __FILE__);
    		var->next = vars;
    		vars = var;
    	}
    
    	return vars;
    }
    
    
    /* get cookie from Request headers */
    struct ast_variable *ast_http_get_cookies(struct ast_variable *headers)
    {
    
    	struct ast_variable *v, *cookies = NULL;
    
    		if (!strcasecmp(v->name, "Cookie")) {
    
    			ast_variables_destroy(cookies);
    			cookies = parse_cookies(v->value);
    
    static struct ast_http_auth *auth_create(const char *userid, const char *password)
    
    David M. Lee's avatar
    David M. Lee committed
    {
    
    	struct ast_http_auth *auth;
    
    David M. Lee's avatar
    David M. Lee committed
    	size_t userid_len;
    	size_t password_len;
    
    	if (!userid || !password) {
    		ast_log(LOG_ERROR, "Invalid userid/password\n");
    		return NULL;
    	}
    
    	userid_len = strlen(userid) + 1;
    	password_len = strlen(password) + 1;
    
    	/* Allocate enough room to store everything in one memory block */
    	auth = ao2_alloc(sizeof(*auth) + userid_len + password_len, NULL);
    	if (!auth) {
    		return NULL;
    	}
    
    	/* Put the userid right after the struct */
    	auth->userid = (char *)(auth + 1);
    	strcpy(auth->userid, userid);
    
    	/* Put the password right after the userid */
    	auth->password = auth->userid + userid_len;
    	strcpy(auth->password, password);
    
    	return auth;
    }
    
    #define BASIC_PREFIX "Basic "
    #define BASIC_LEN 6 /*!< strlen(BASIC_PREFIX) */
    
    struct ast_http_auth *ast_http_get_auth(struct ast_variable *headers)
    {
    	struct ast_variable *v;
    
    	for (v = headers; v; v = v->next) {
    		const char *base64;
    		char decoded[256] = {};
    		char *username;
    		char *password;
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    #ifdef AST_DEVMODE
    
    David M. Lee's avatar
    David M. Lee committed
    		int cnt;
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    #endif /* AST_DEVMODE */
    
    David M. Lee's avatar
    David M. Lee committed
    
    		if (strcasecmp("Authorization", v->name) != 0) {
    			continue;
    		}
    
    		if (!ast_begins_with(v->value, BASIC_PREFIX)) {
    			ast_log(LOG_DEBUG,
    				"Unsupported Authorization scheme\n");
    			continue;
    		}
    
    		/* Basic auth header parsing. RFC 2617, section 2.
    		 *   credentials = "Basic" basic-credentials
    		 *   basic-credentials = base64-user-pass
    		 *   base64-user-pass  = <base64 encoding of user-pass,
    		 *                        except not limited to 76 char/line>
    		 *   user-pass   = userid ":" password
    		 */
    
    		base64 = v->value + BASIC_LEN;
    
    		/* This will truncate "userid:password" lines to
    		 * sizeof(decoded). The array is long enough that this shouldn't
    		 * be a problem */
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    #ifdef AST_DEVMODE
    		cnt =
    #endif /* AST_DEVMODE */
    		ast_base64decode((unsigned char*)decoded, base64,
    
    David M. Lee's avatar
    David M. Lee committed
    			sizeof(decoded) - 1);
    		ast_assert(cnt < sizeof(decoded));
    
    		/* Split the string at the colon */
    		password = decoded;
    		username = strsep(&password, ":");
    		if (!password) {
    			ast_log(LOG_WARNING, "Invalid Authorization header\n");
    			return NULL;
    		}
    
    		return auth_create(username, password);
    	}
    
    	return NULL;
    }
    
    int ast_http_response_status_line(const char *buf, const char *version, int code)
    {
    	int status_code;
    	size_t size = strlen(version);
    
    	if (strncmp(buf, version, size) || buf[size] != ' ') {
    		ast_log(LOG_ERROR, "HTTP version not supported - "
    			"expected %s\n", version);
    		return -1;
    	}
    
    	/* skip to status code (version + space) */
    	buf += size + 1;
    
    	if (sscanf(buf, "%d", &status_code) != 1) {
    		ast_log(LOG_ERROR, "Could not read HTTP status code - "
    			"%s\n", buf);
    		return -1;
    	}
    
    	return status_code;
    }
    
    static void remove_excess_lws(char *s)
    {
    	char *p, *res = s;
    	char *buf = ast_malloc(strlen(s) + 1);
    	char *buf_end;
    
    	if (!buf) {
    		return;
    	}
    
    	buf_end = buf;
    
    	while (*s && *(s = ast_skip_blanks(s))) {
    		p = s;
    		s = ast_skip_nonblanks(s);
    
    		if (buf_end != buf) {
    			*buf_end++ = ' ';
    		}
    
    		memcpy(buf_end, p, s - p);
    		buf_end += s - p;
    	}
    	*buf_end = '\0';
    	/* safe since buf will always be less than or equal to res */
    	strcpy(res, buf);
    	ast_free(buf);
    }
    
    int ast_http_header_parse(char *buf, char **name, char **value)
    {
    	ast_trim_blanks(buf);
    	if (ast_strlen_zero(buf)) {
    		return -1;
    	}
    
    	*value = buf;
    	*name = strsep(value, ":");
    	if (!*value) {
    		return 1;
    	}
    
    	*value = ast_skip_blanks(*value);
    	if (ast_strlen_zero(*value) || ast_strlen_zero(*name)) {
    		return 1;
    	}
    
    	remove_excess_lws(*value);
    	return 0;
    }
    
    int ast_http_header_match(const char *name, const char *expected_name,
    			  const char *value, const char *expected_value)
    {
    	if (strcasecmp(name, expected_name)) {
    		/* no value to validate if names don't match */
    		return 0;
    	}
    
    	if (strcasecmp(value, expected_value)) {
    		ast_log(LOG_ERROR, "Invalid header value - expected %s "
    			"received %s", value, expected_value);
    		return -1;
    	}
    	return 1;
    }
    
    int ast_http_header_match_in(const char *name, const char *expected_name,
    			     const char *value, const char *expected_value)
    {
    	if (strcasecmp(name, expected_name)) {
    		/* no value to validate if names don't match */
    		return 0;
    	}
    
    	if (!strcasestr(expected_value, value)) {
    		ast_log(LOG_ERROR, "Header '%s' - could not locate '%s' "
    			"in '%s'\n", name, value, expected_value);
    		return -1;
    
    	}
    	return 1;
    }
    
    
    /*! Limit the number of request headers in case the sender is being ridiculous. */
    #define MAX_HTTP_REQUEST_HEADERS	100
    
    
    /*!
     * \internal
     * \brief Read the request headers.
     * \since 12.4.0
     *
     * \param ser HTTP TCP/TLS session object.
     * \param headers Where to put the request headers list pointer.
     *
     * \retval 0 on success.
     * \retval -1 on error.
     */
    static int http_request_headers_get(struct ast_tcptls_session_instance *ser, struct ast_variable **headers)
    
    	struct ast_variable *tail = *headers;
    
    	char header_line[MAX_HTTP_LINE_LENGTH];
    
    	remaining_headers = MAX_HTTP_REQUEST_HEADERS;
    
    		if (!fgets(header_line, sizeof(header_line), ser->f)) {
    
    			ast_http_error(ser, 400, "Bad Request", "Timeout");
    
    		/* Trim trailing characters */
    
    		ast_trim_blanks(header_line);
    		if (ast_strlen_zero(header_line)) {
    
    			/* A blank line ends the request header section. */
    
    		value = header_line;
    		name = strsep(&value, ":");
    		if (!value) {
    			continue;
    		}
    
    		value = ast_skip_blanks(value);
    		if (ast_strlen_zero(value) || ast_strlen_zero(name)) {
    			continue;
    		}
    
    		ast_trim_blanks(name);
    
    
    		if (!remaining_headers--) {
    			/* Too many headers. */
    			ast_http_error(ser, 413, "Request Entity Too Large", "Too many headers");
    
    		if (!*headers) {
    			*headers = ast_variable_new(name, value, __FILE__);
    			tail = *headers;
    
    		} else {
    			tail->next = ast_variable_new(name, value, __FILE__);
    			tail = tail->next;
    
    		if (!tail) {
    			/*
    			 * Variable allocation failure.
    			 * Try to make some room.
    			 */
    
    			ast_variables_destroy(*headers);
    			*headers = NULL;
    
    
    			ast_http_error(ser, 500, "Server Error", "Out of memory");
    
    	return 0;
    }
    
    /*!
     * \internal
     * \brief Process a HTTP request.
     * \since 12.4.0
     *
     * \param ser HTTP TCP/TLS session object.
     *
     * \retval 0 Continue and process the next HTTP request.
     * \retval -1 Fatal HTTP connection error.  Force the HTTP connection closed.
     */
    static int httpd_process_request(struct ast_tcptls_session_instance *ser)
    {
    	RAII_VAR(struct ast_variable *, headers, NULL, ast_variables_destroy);
    	char *uri;
    	char *method;
    	const char *transfer_encoding;
    	struct http_worker_private_data *request;
    	enum ast_http_method http_method = AST_HTTP_UNKNOWN;
    	int res;
    	char request_line[MAX_HTTP_LINE_LENGTH];
    
    	if (!fgets(request_line, sizeof(request_line), ser->f)) {
    		return -1;
    	}
    
    	/* Re-initialize the request body tracking data. */
    	request = ser->private_data;
    	http_request_tracking_init(request);
    
    	/* Get method */
    	method = ast_skip_blanks(request_line);
    	uri = ast_skip_nonblanks(method);
    	if (*uri) {
    		*uri++ = '\0';
    	}
    
    	if (!strcasecmp(method,"GET")) {
    		http_method = AST_HTTP_GET;
    	} else if (!strcasecmp(method,"POST")) {
    		http_method = AST_HTTP_POST;
    	} else if (!strcasecmp(method,"HEAD")) {
    		http_method = AST_HTTP_HEAD;
    	} else if (!strcasecmp(method,"PUT")) {
    		http_method = AST_HTTP_PUT;
    	} else if (!strcasecmp(method,"DELETE")) {
    		http_method = AST_HTTP_DELETE;
    	} else if (!strcasecmp(method,"OPTIONS")) {
    		http_method = AST_HTTP_OPTIONS;
    	}
    
    	uri = ast_skip_blanks(uri);	/* Skip white space */
    	if (*uri) {			/* terminate at the first blank */
    		char *c = ast_skip_nonblanks(uri);
    
    		if (*c) {
    			*c = '\0';
    		}
    	} else {
    		ast_http_error(ser, 400, "Bad Request", "Invalid Request");
    		return -1;
    	}
    
    
    	if (ast_shutdown_final()) {
    		ast_http_error(ser, 503, "Service Unavailable", "Shutdown in progress");
    		return -1;
    	}
    
    
    	/* process "Request Headers" lines */
    	if (http_request_headers_get(ser, &headers)) {
    		return -1;
    	}
    
    
    	transfer_encoding = get_transfer_encoding(headers);
    	/* Transfer encoding defaults to identity */
    	if (!transfer_encoding) {
    		transfer_encoding = "identity";
    	}
    
    	/*
    	 * RFC 2616, section 3.6, we should respond with a 501 for any transfer-
    	 * codings we don't understand.
    	 */
    
    	if (strcasecmp(transfer_encoding, "identity") != 0 &&
    		strcasecmp(transfer_encoding, "chunked") != 0) {
    
    		/* Transfer encodings not supported */
    		ast_http_error(ser, 501, "Unimplemented", "Unsupported Transfer-Encoding.");
    
    		return -1;
    	}
    
    	if (http_request_tracking_setup(ser, headers)
    		|| handle_uri(ser, uri, http_method, headers)
    		|| ast_test_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION)) {
    		res = -1;
    	} else {
    		res = 0;
    	}
    	return res;
    }
    
    static void *httpd_helper_thread(void *data)
    {
    	struct ast_tcptls_session_instance *ser = data;
    
    	int flags = 1;
    
    	int timeout;
    
    	if (!ser || !ser->f) {
    		ao2_cleanup(ser);
    		return NULL;
    	}
    
    	if (ast_atomic_fetchadd_int(&session_count, +1) >= session_limit) {
    		ast_log(LOG_WARNING, "HTTP session count exceeded %d sessions.\n",
    			session_limit);
    
    	ast_debug(1, "HTTP opening session.  Top level\n");
    
    	/*
    	 * Here we set TCP_NODELAY on the socket to disable Nagle's algorithm.
    	 * This is necessary to prevent delays (caused by buffering) as we
    	 * write to the socket in bits and pieces.
    	 */
    
    	if (setsockopt(ser->fd, IPPROTO_TCP, TCP_NODELAY, (char *) &flags, sizeof(flags)) < 0) {
    		ast_log(LOG_WARNING, "Failed to set TCP_NODELAY on HTTP connection: %s\n", strerror(errno));
    
    	}
    
    	/* make sure socket is non-blocking */
    
    	ast_fd_set_flags(ser->fd, O_NONBLOCK);
    
    
    	/* Setup HTTP worker private data to keep track of request body reading. */
    	ao2_cleanup(ser->private_data);
    	ser->private_data = ao2_alloc_options(sizeof(struct http_worker_private_data), NULL,
    		AO2_ALLOC_OPT_LOCK_NOLOCK);
    	if (!ser->private_data) {
    		ast_http_error(ser, 500, "Server Error", "Out of memory");
    		goto done;
    	}
    	http_request_tracking_init(ser->private_data);
    
    	/* Determine initial HTTP request wait timeout. */
    	timeout = session_keep_alive;
    	if (timeout <= 0) {
    		/* Persistent connections not enabled. */
    		timeout = session_inactivity;
    	}
    	if (timeout < MIN_INITIAL_REQUEST_TIMEOUT) {
    		timeout = MIN_INITIAL_REQUEST_TIMEOUT;
    	}
    
    	/* We can let the stream wait for data to arrive. */
    	ast_tcptls_stream_set_exclusive_input(ser->stream_cookie, 1);
    
    	for (;;) {
    		int ch;
    
    		/* Wait for next potential HTTP request message. */
    		ast_tcptls_stream_set_timeout_inactivity(ser->stream_cookie, timeout);
    		ch = fgetc(ser->f);
    		if (ch == EOF || ungetc(ch, ser->f) == EOF) {
    			/* Between request idle timeout */
    			ast_debug(1, "HTTP idle timeout or peer closed connection.\n");
    			break;
    		}
    
    		ast_tcptls_stream_set_timeout_inactivity(ser->stream_cookie, session_inactivity);
    		if (httpd_process_request(ser) || !ser->f || feof(ser->f)) {
    			/* Break the connection or the connection closed */
    			break;
    		}