Newer
Older
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;
}
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
/*!
* \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]);
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
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);
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) {
"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
*/
return NULL;
}
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;
char *var, *val;
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
*/
return NULL;
while ((val = strsep(&buf, "&"))) {
var = strsep(&val, "=");
if (val) {
ast_uri_decode(val, ast_uri_http_legacy);
} else {
val = "";
}
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)
{
char *c;
char *params = uri;
Kevin P. Fleming
committed
struct ast_http_uri *urih = NULL;
struct ast_variable *get_vars = NULL, *v, *prev = NULL;
Russell Bryant
committed
struct http_uri_redirect *redirect;
ast_debug(2, "HTTP Request URI is %s \n", uri);
strsep(¶ms, "?");
/* Extract arguments from the request and store them in variables. */
if (params) {
char *var, *val;
while ((val = strsep(¶ms, "&"))) {
var = strsep(&val, "=");
if (val) {
ast_uri_decode(val, ast_uri_http_legacy);
} else {
val = "";
}
ast_uri_decode(var, ast_uri_http_legacy);
if ((v = ast_variable_new(var, val, ""))) {
if (get_vars) {
prev->next = v;
get_vars = v;
prev = v;
}
}
}
AST_RWLIST_RDLOCK(&uri_redirects);
AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
Russell Bryant
committed
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);
Russell Bryant
committed
break;
}
}
AST_RWLIST_UNLOCK(&uri_redirects);
Russell Bryant
committed
goto cleanup;
Russell Bryant
committed
/* 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 */
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 == '/') {
if (!*c || urih->has_subtree) {
uri = c;
break;
}
}
}
if (urih) {
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);
} else {
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.");
}
Russell Bryant
committed
cleanup:
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, ";"))) {
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;
for (v = headers; v; v = v->next) {
if (!strcasecmp(v->name, "Cookie")) {
ast_variables_destroy(cookies);
cookies = parse_cookies(v->value);
}
}
return cookies;
}
static struct ast_http_auth *auth_create(const char *userid, const char *password)
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
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;
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
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 */
#ifdef AST_DEVMODE
cnt =
#endif /* AST_DEVMODE */
ast_base64decode((unsigned char*)decoded, base64,
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;
}
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
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;
int remaining_headers;
char header_line[MAX_HTTP_LINE_LENGTH];
Richard Mudgett
committed
remaining_headers = MAX_HTTP_REQUEST_HEADERS;
Richard Mudgett
committed
for (;;) {
char *name;
char *value;
if (!fgets(header_line, sizeof(header_line), ser->f)) {
Richard Mudgett
committed
ast_http_error(ser, 400, "Bad Request", "Timeout");
Richard Mudgett
committed
}
/* Trim trailing characters */
ast_trim_blanks(header_line);
if (ast_strlen_zero(header_line)) {
Richard Mudgett
committed
/* A blank line ends the request header section. */
Terry Wilson
committed
value = header_line;
name = strsep(&value, ":");
if (!value) {
continue;
}
Terry Wilson
committed
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");
}
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
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 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);
goto done;
}
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);
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
/* 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;
}