Newer
Older
Jason Parker
committed
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 1999 - 2006, Digium, Inc.
*
* Mark Spencer <markster@digium.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
* \file
*
* This program implements a tiny http server
* and was inspired by micro-httpd by Jef Poskanzer
*
* GMime http://spruce.sourceforge.net/gmime/
/*! \li \ref http.c uses the configuration file \ref http.conf
* \addtogroup configuration_file
*/
/*! \page http.conf http.conf
* \verbinclude http.conf.sample
*/
/*** MODULEINFO
<support_level>core</support_level>
***/
Kevin P. Fleming
committed
#include "asterisk.h"
ASTERISK_REGISTER_FILE()
Kevin P. Fleming
committed
#include <time.h>
#include <sys/time.h>
Mark Spencer
committed
#include <sys/stat.h>
#include <sys/signal.h>
#include <fcntl.h>
Kevin P. Fleming
committed
#include "asterisk/paths.h" /* use ast_config_AST_DATA_DIR */
#include "asterisk/cli.h"
#include "asterisk/http.h"
#include "asterisk/utils.h"
#include "asterisk/strings.h"
Mark Spencer
committed
#include "asterisk/config.h"
Russell Bryant
committed
#include "asterisk/stringfields.h"
Russell Bryant
committed
#include "asterisk/ast_version.h"
Kevin P. Fleming
committed
#include "asterisk/_private.h"
#include "asterisk/netsock2.h"
#define MAX_PREFIX 80
#define DEFAULT_PORT 8088
#define DEFAULT_TLS_PORT 8089
#define DEFAULT_SESSION_LIMIT 100
/*! (ms) Idle time waiting for data. */
#define DEFAULT_SESSION_INACTIVITY 30000
/*! (ms) Min timeout for initial HTTP request to start coming in. */
#define MIN_INITIAL_REQUEST_TIMEOUT 10000
/*! (ms) Idle time between HTTP requests */
#define DEFAULT_SESSION_KEEP_ALIVE 15000
Ashley Sanders
committed
/*! Max size for the http server name */
#define MAX_SERVER_NAME_LENGTH 128
/*! Max size for the http response header */
#define DEFAULT_RESPONSE_HEADER_LENGTH 512
/*! Maximum application/json or application/x-www-form-urlencoded body content length. */
#if !defined(LOW_MEMORY)
#define MAX_CONTENT_LENGTH 4096
#else
#define MAX_CONTENT_LENGTH 1024
#endif /* !defined(LOW_MEMORY) */
/*! Maximum line length for HTTP requests. */
#if !defined(LOW_MEMORY)
#define MAX_HTTP_LINE_LENGTH 4096
#else
#define MAX_HTTP_LINE_LENGTH 1024
#endif /* !defined(LOW_MEMORY) */
Ashley Sanders
committed
static char http_server_name[MAX_SERVER_NAME_LENGTH];
static int session_limit = DEFAULT_SESSION_LIMIT;
Richard Mudgett
committed
static int session_inactivity = DEFAULT_SESSION_INACTIVITY;
static int session_keep_alive = DEFAULT_SESSION_KEEP_ALIVE;
static int session_count = 0;
static struct ast_tls_config http_tls_cfg;
static void *httpd_helper_thread(void *arg);
/*!
* we have up to two accepting threads, one for http, one for https
*/
static struct ast_tcptls_session_args http_desc = {
.accept_fd = -1,
.master = AST_PTHREADT_NULL,
.tls_cfg = NULL,
.poll_timeout = -1,
.name = "http server",
.accept_fn = ast_tcptls_server_root,
.worker_fn = httpd_helper_thread,
static struct ast_tcptls_session_args https_desc = {
.accept_fd = -1,
.master = AST_PTHREADT_NULL,
.tls_cfg = &http_tls_cfg,
.poll_timeout = -1,
.name = "https server",
.accept_fn = ast_tcptls_server_root,
.worker_fn = httpd_helper_thread,
static AST_RWLIST_HEAD_STATIC(uris, ast_http_uri); /*!< list of supported handlers */
/* all valid URIs must be prepended by the string in prefix. */
static char prefix[MAX_PREFIX];
Mark Spencer
committed
/*! \brief Limit the kinds of files we're willing to serve up */
Mark Spencer
committed
static struct {
const char *ext;
const char *mtype;
Mark Spencer
committed
} mimetypes[] = {
{ "png", "image/png" },
{ "xml", "text/xml" },
Mark Spencer
committed
{ "jpg", "image/jpeg" },
{ "js", "application/x-javascript" },
{ "wav", "audio/x-wav" },
{ "mp3", "audio/mpeg" },
{ "html", "text/html" },
{ "htm", "text/html" },
{ "cnf", "text/plain" },
{ "cfg", "text/plain" },
{ "bin", "application/octet-stream" },
{ "sbn", "application/octet-stream" },
{ "ld", "application/octet-stream" },
Mark Spencer
committed
};
Russell Bryant
committed
struct http_uri_redirect {
AST_LIST_ENTRY(http_uri_redirect) entry;
Russell Bryant
committed
char *dest;
char target[0];
Russell Bryant
committed
};
static AST_RWLIST_HEAD_STATIC(uri_redirects, http_uri_redirect);
Russell Bryant
committed
static const struct ast_cfhttp_methods_text {
enum ast_http_method method;
} ast_http_methods_text[] = {
{ AST_HTTP_UNKNOWN, "UNKNOWN" },
{ AST_HTTP_GET, "GET" },
{ AST_HTTP_POST, "POST" },
{ AST_HTTP_HEAD, "HEAD" },
{ AST_HTTP_PUT, "PUT" },
{ AST_HTTP_DELETE, "DELETE" },
{ AST_HTTP_OPTIONS, "OPTIONS" },
};
const char *ast_get_http_method(enum ast_http_method method)
{
int x;
for (x = 0; x < ARRAY_LEN(ast_http_methods_text); x++) {
if (ast_http_methods_text[x].method == method) {
return ast_http_methods_text[x].text;
}
}
return NULL;
}
const char *ast_http_ftype2mtype(const char *ftype)
Mark Spencer
committed
{
int x;
Mark Spencer
committed
if (ftype) {
for (x = 0; x < ARRAY_LEN(mimetypes); x++) {
if (!strcasecmp(ftype, mimetypes[x].ext)) {
Mark Spencer
committed
return mimetypes[x].mtype;
Mark Spencer
committed
}
}
return NULL;
Mark Spencer
committed
}
uint32_t ast_http_manid_from_vars(struct ast_variable *headers)
{
uint32_t mngid = 0;
struct ast_variable *v, *cookies;
Brett Bryant
committed
cookies = ast_http_get_cookies(headers);
for (v = cookies; v; v = v->next) {
if (!strcasecmp(v->name, "mansession_id")) {
break;
}
}
ast_variables_destroy(cookies);
Brett Bryant
committed
return mngid;
}
Terry Wilson
committed
void ast_http_prefix(char *buf, int len)
{
if (buf) {
ast_copy_string(buf, prefix, len);
}
}
static int static_callback(struct ast_tcptls_session_instance *ser,
const struct ast_http_uri *urih, const char *uri,
enum ast_http_method method, struct ast_variable *get_vars,
struct ast_variable *headers)
Mark Spencer
committed
{
char *path;
const char *ftype;
Mark Spencer
committed
char wkspace[80];
struct stat st;
int len;
int fd;
struct ast_str *http_header;
struct timeval tv;
Tilghman Lesher
committed
struct ast_tm tm;
char timebuf[80], etag[23];
struct ast_variable *v;
int not_modified = 0;
Mark Spencer
committed
if (method != AST_HTTP_GET && method != AST_HTTP_HEAD) {
ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
}
/* Yuck. I'm not really sold on this, but if you don't deliver static content it makes your configuration
Mark Spencer
committed
substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
if (!enablestatic || ast_strlen_zero(uri)) {
Mark Spencer
committed
goto out403;
/* Disallow any funny filenames at all (checking first character only??) */
if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0])) {
Mark Spencer
committed
goto out403;
}
if (strstr(uri, "/..")) {
Mark Spencer
committed
goto out403;
if ((ftype = strrchr(uri, '.'))) {
Mark Spencer
committed
ftype++;
if (!(mtype = ast_http_ftype2mtype(ftype))) {
snprintf(wkspace, sizeof(wkspace), "text/%s", S_OR(ftype, "plain"));
}
Mark Spencer
committed
/* Cap maximum length */
Terry Wilson
committed
if ((len = strlen(uri) + strlen(ast_config_AST_DATA_DIR) + strlen("/static-http/") + 5) > 1024) {
Mark Spencer
committed
goto out403;
Joshua Colp
committed
sprintf(path, "%s/static-http/%s", ast_config_AST_DATA_DIR, uri);
Mark Spencer
committed
goto out404;
}
if (S_ISDIR(st.st_mode)) {
Mark Spencer
committed
goto out404;
if (strstr(path, "/private/") && !astman_is_authed(ast_http_manid_from_vars(headers))) {
Mark Spencer
committed
goto out403;
fd = open(path, O_RDONLY);
if (fd < 0) {
Brett Bryant
committed
goto out403;
}
/* make "Etag:" http header value */
snprintf(etag, sizeof(etag), "\"%ld\"", (long)st.st_mtime);
/* make "Last-Modified:" http header value */
tv.tv_sec = st.st_mtime;
tv.tv_usec = 0;
ast_strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", ast_localtime(&tv, &tm, "GMT"));
/* check received "If-None-Match" request header and Etag value for file */
for (v = headers; v; v = v->next) {
if (!strcasecmp(v->name, "If-None-Match")) {
if (!strcasecmp(v->value, etag)) {
not_modified = 1;
}
break;
Kevin P. Fleming
committed
}
http_header = ast_str_create(255);
if (!http_header) {
ast_http_request_close_on_completion(ser);
ast_http_error(ser, 500, "Server Error", "Out of memory");
ast_str_set(&http_header, 0, "Content-type: %s\r\n"
"ETag: %s\r\n"
mtype,
etag,
timebuf);
/* ast_http_send() frees http_header, so we don't need to do it before returning */
if (not_modified) {
ast_http_send(ser, method, 304, "Not Modified", http_header, NULL, 0, 1);
} else {
ast_http_send(ser, method, 200, NULL, http_header, NULL, fd, 1); /* static content flag is set */
}
close(fd);
return 0;
Mark Spencer
committed
out404:
ast_http_error(ser, 404, "Not Found", "The requested URL was not found on this server.");
Mark Spencer
committed
out403:
ast_http_request_close_on_completion(ser);
ast_http_error(ser, 403, "Access Denied", "You do not have permission to access the requested URL.");
Mark Spencer
committed
}
static int httpstatus_callback(struct ast_tcptls_session_instance *ser,
const struct ast_http_uri *urih, const char *uri,
enum ast_http_method method, struct ast_variable *get_vars,
struct ast_variable *headers)
{
struct ast_str *out;
struct ast_variable *v, *cookies = NULL;
if (method != AST_HTTP_GET && method != AST_HTTP_HEAD) {
ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
}
out = ast_str_create(512);
if (!out) {
ast_http_request_close_on_completion(ser);
ast_http_error(ser, 500, "Server Error", "Out of memory");
return 0;
ast_str_append(&out, 0,
"<html><title>Asterisk HTTP Status</title>\r\n"
"<body bgcolor=\"#ffffff\">\r\n"
"<table bgcolor=\"#f1f1f1\" align=\"center\"><tr><td bgcolor=\"#e0e0ff\" colspan=\"2\" width=\"500\">\r\n"
"<h2> Asterisk™ HTTP Status</h2></td></tr>\r\n");
Ashley Sanders
committed
ast_str_append(&out, 0, "<tr><td><i>Server</i></td><td><b>%s</b></td></tr>\r\n", http_server_name);
ast_str_append(&out, 0, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix);
ast_str_append(&out, 0, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
ast_sockaddr_stringify_addr(&http_desc.old_address));
ast_str_append(&out, 0, "<tr><td><i>Bind Port</i></td><td><b>%s</b></td></tr>\r\n",
ast_sockaddr_stringify_port(&http_desc.old_address));
if (http_tls_cfg.enabled) {
ast_str_append(&out, 0, "<tr><td><i>SSL Bind Port</i></td><td><b>%s</b></td></tr>\r\n",
ast_sockaddr_stringify_port(&https_desc.old_address));
ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
for (v = get_vars; v; v = v->next) {
ast_str_append(&out, 0, "<tr><td><i>Submitted GET Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
Mark Spencer
committed
}
ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
cookies = ast_http_get_cookies(headers);
for (v = cookies; v; v = v->next) {
ast_str_append(&out, 0, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
}
ast_variables_destroy(cookies);
ast_str_append(&out, 0, "</table><center><font size=\"-1\"><i>Asterisk and Digium are registered trademarks of Digium, Inc.</i></font></center></body></html>\r\n");
ast_http_send(ser, method, 200, NULL, NULL, out, 0, 0);
return 0;
}
static struct ast_http_uri statusuri = {
.callback = httpstatus_callback,
.description = "Asterisk HTTP General Status",
.uri = "httpstatus",
.has_subtree = 0,
.data = NULL,
.key = __FILE__,
};
Mark Spencer
committed
static struct ast_http_uri staticuri = {
.callback = static_callback,
.description = "Asterisk HTTP Static Delivery",
.uri = "static",
.has_subtree = 1,
.data = NULL,
.key= __FILE__,
Mark Spencer
committed
};
enum http_private_flags {
/*! TRUE if the HTTP request has a body. */
HTTP_FLAG_HAS_BODY = (1 << 0),
/*! TRUE if the HTTP request body has been read. */
HTTP_FLAG_BODY_READ = (1 << 1),
/*! TRUE if the HTTP request must close when completed. */
HTTP_FLAG_CLOSE_ON_COMPLETION = (1 << 2),
};
/*! HTTP tcptls worker_fn private data. */
struct http_worker_private_data {
/*! Body length or -1 if chunked. Valid if HTTP_FLAG_HAS_BODY is TRUE. */
int body_length;
/*! HTTP body tracking flags */
struct ast_flags flags;
};
void ast_http_send(struct ast_tcptls_session_instance *ser,
enum ast_http_method method, int status_code, const char *status_title,
struct ast_str *http_header, struct ast_str *out, int fd,
unsigned int static_content)
{
struct timeval now = ast_tvnow();
struct ast_tm tm;
char timebuf[80];
int content_length = 0;
Ashley Sanders
committed
struct ast_str *server_header_field = ast_str_create(MAX_SERVER_NAME_LENGTH);
Ashley Sanders
committed
if (!ser || !ser->f || !server_header_field) {
/* The connection is not open. */
ast_free(http_header);
ast_free(out);
Ashley Sanders
committed
ast_free(server_header_field);
return;
}
Ashley Sanders
committed
if(!ast_strlen_zero(http_server_name)) {
ast_str_set(&server_header_field,
0,
"Server: %s\r\n",
http_server_name);
}
/*
* We shouldn't be sending non-final status codes to this
* function because we may close the connection before
* returning.
*/
ast_assert(200 <= status_code);
if (session_keep_alive <= 0) {
close_connection = 1;
} else {
struct http_worker_private_data *request;
request = ser->private_data;
if (!request
|| ast_test_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION)
|| ast_http_body_discard(ser)) {
close_connection = 1;
} else {
close_connection = 0;
}
}
ast_strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", ast_localtime(&now, &tm, "GMT"));
if (out) {
content_length += ast_str_strlen(out);
}
if (fd) {
content_length += lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
}
/* send http header */
fprintf(ser->f,
"HTTP/1.1 %d %s\r\n"
Ashley Sanders
committed
"%s"
"Date: %s\r\n"
"%s"
"%s"
"Content-Length: %d\r\n"
status_code, status_title ? status_title : "OK",
Ashley Sanders
committed
ast_str_buffer(server_header_field),
timebuf,
close_connection ? "Connection: close\r\n" : "",
static_content ? "" : "Cache-Control: no-cache, no-store\r\n",
http_header ? ast_str_buffer(http_header) : "",
content_length
);
/* send content */
if (method != AST_HTTP_HEAD || status_code >= 400) {
if (out && ast_str_strlen(out)) {
if (fwrite(ast_str_buffer(out), ast_str_strlen(out), 1, ser->f) != 1) {
ast_log(LOG_ERROR, "fwrite() failed: %s\n", strerror(errno));
}
if (fd) {
char buf[256];
int len;
while ((len = read(fd, buf, sizeof(buf))) > 0) {
if (fwrite(buf, len, 1, ser->f) != 1) {
ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
}
}
}
}
ast_free(http_header);
ast_free(out);
Ashley Sanders
committed
ast_free(server_header_field);
if (close_connection) {
ast_debug(1, "HTTP closing session. status_code:%d\n", status_code);
ast_tcptls_close_session_file(ser);
} else {
ast_debug(1, "HTTP keeping session open. status_code:%d\n", status_code);
}
}
Ashley Sanders
committed
void ast_http_create_response(struct ast_tcptls_session_instance *ser, int status_code,
const char *status_title, struct ast_str *http_header_data, const char *text)
Ashley Sanders
committed
char server_name[MAX_SERVER_NAME_LENGTH];
struct ast_str *server_address = ast_str_create(MAX_SERVER_NAME_LENGTH);
struct ast_str *out = ast_str_create(MAX_CONTENT_LENGTH);
Ashley Sanders
committed
if (!http_header_data || !server_address || !out) {
ast_free(http_header_data);
ast_free(server_address);
ast_free(out);
Ashley Sanders
committed
ast_debug(1, "HTTP closing session. OOM.\n");
ast_tcptls_close_session_file(ser);
}
return;
Ashley Sanders
committed
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
if(!ast_strlen_zero(http_server_name)) {
ast_xml_escape(http_server_name, server_name, sizeof(server_name));
ast_str_set(&server_address,
0,
"<address>%s</address>\r\n",
server_name);
}
ast_str_set(&out,
0,
"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
"<html><head>\r\n"
"<title>%d %s</title>\r\n"
"</head><body>\r\n"
"<h1>%s</h1>\r\n"
"<p>%s</p>\r\n"
"<hr />\r\n"
"%s"
"</body></html>\r\n",
status_code,
status_title,
status_title,
text ? text : "",
ast_str_buffer(server_address));
ast_free(server_address);
ast_http_send(ser,
AST_HTTP_UNKNOWN,
status_code,
status_title,
http_header_data,
out,
0,
0);
}
Ashley Sanders
committed
void ast_http_auth(struct ast_tcptls_session_instance *ser, const char *realm,
const unsigned long nonce, const unsigned long opaque, int stale,
const char *text)
Ashley Sanders
committed
int status_code = 401;
char *status_title = "Unauthorized";
struct ast_str *http_header_data = ast_str_create(DEFAULT_RESPONSE_HEADER_LENGTH);
if (http_header_data) {
ast_str_set(&http_header_data,
0,
"WWW-authenticate: Digest algorithm=MD5, realm=\"%s\", nonce=\"%08lx\", qop=\"auth\", opaque=\"%08lx\"%s\r\n"
"Content-type: text/html\r\n",
realm ? realm : "Asterisk",
nonce,
opaque,
stale ? ", stale=true" : "");
}
ast_http_create_response(ser,
status_code,
status_title,
http_header_data,
text);
}
Ashley Sanders
committed
void ast_http_error(struct ast_tcptls_session_instance *ser, int status_code,
const char *status_title, const char *text)
{
struct ast_str *http_header_data = ast_str_create(DEFAULT_RESPONSE_HEADER_LENGTH);
Ashley Sanders
committed
if (http_header_data) {
ast_str_set(&http_header_data, 0, "Content-type: text/html\r\n");
}
Ashley Sanders
committed
ast_http_create_response(ser,
status_code,
status_title,
http_header_data,
text);
}
/*!
* \brief Link the new uri into the list.
* the string, not alphabetically. Duplicate entries are not replaced,
* but the insertion order (using <= and not just <) makes sure that
* more recent insertions hide older ones.
* On a lookup, we just scan the list and stop at the first matching entry.
*/
int ast_http_uri_link(struct ast_http_uri *urih)
{
Russell Bryant
committed
struct ast_http_uri *uri;
int len = strlen(urih->uri);
Russell Bryant
committed
if ( AST_RWLIST_EMPTY(&uris) || strlen(AST_RWLIST_FIRST(&uris)->uri) <= len ) {
AST_RWLIST_INSERT_HEAD(&uris, urih, entry);
AST_RWLIST_UNLOCK(&uris);
Russell Bryant
committed
return 0;
}
Russell Bryant
committed
AST_RWLIST_TRAVERSE(&uris, uri, entry) {
Kevin P. Fleming
committed
if (AST_RWLIST_NEXT(uri, entry) &&
strlen(AST_RWLIST_NEXT(uri, entry)->uri) <= len) {
AST_RWLIST_INSERT_AFTER(&uris, uri, urih, entry);
AST_RWLIST_UNLOCK(&uris);
Russell Bryant
committed
return 0;
}
}
AST_RWLIST_INSERT_TAIL(&uris, urih, entry);
Russell Bryant
committed
return 0;
void ast_http_uri_unlink(struct ast_http_uri *urih)
{
AST_RWLIST_WRLOCK(&uris);
AST_RWLIST_REMOVE(&uris, urih, entry);
AST_RWLIST_UNLOCK(&uris);
}
void ast_http_uri_unlink_all_with_key(const char *key)
struct ast_http_uri *urih;
AST_RWLIST_WRLOCK(&uris);
AST_RWLIST_TRAVERSE_SAFE_BEGIN(&uris, urih, entry) {
if (!strcmp(urih->key, key)) {
AST_RWLIST_REMOVE_CURRENT(entry);
if (urih->dmallocd) {
ast_free(urih->data);
}
if (urih->mallocd) {
ast_free(urih);
}
Tilghman Lesher
committed
AST_RWLIST_TRAVERSE_SAFE_END;
Brett Bryant
committed
AST_RWLIST_UNLOCK(&uris);
/*!
* \brief Retrieves the header with the given field name.
*
* \param headers Headers to search.
* \param field_name Name of the header to find.
* \return Associated header value.
* \return \c NULL if header is not present.
*/
static const char *get_header(struct ast_variable *headers, const char *field_name)
{
struct ast_variable *v;
for (v = headers; v; v = v->next) {
if (!strcasecmp(v->name, field_name)) {
return v->value;
}
}
return NULL;
}
/*!
* \brief Retrieves the content type specified in the "Content-Type" header.
*
* This function only returns the "type/subtype" and any trailing parameter is
* not included.
*
* \note the return value is an allocated string that needs to be freed.
*
* \retval the content type/subtype or NULL if the header is not found.
*/
static char *get_content_type(struct ast_variable *headers)
const char *content_type = get_header(headers, "Content-Type");
const char *param;
size_t size;
if (!content_type) {
return NULL;
param = strchr(content_type, ';');
size = param ? param - content_type : strlen(content_type);
return ast_strndup(content_type, size);
/*!
* \brief Returns the value of the Content-Length header.
*
* \param headers HTTP headers.
*
* \retval length Value of the Content-Length header.
* \retval 0 if header is not present.
* \retval -1 if header is invalid.
static int get_content_length(struct ast_variable *headers)
{
const char *content_length = get_header(headers, "Content-Length");
if (!content_length) {
/* Missing content length; assume zero */
return 0;
length = 0;
if (sscanf(content_length, "%30d", &length) != 1) {
/* Invalid Content-Length value */
length = -1;
}
return length;
}
/*!
* \brief Returns the value of the Transfer-Encoding header.
*
* \param headers HTTP headers.
* \retval string Value of the Transfer-Encoding header.
* \retval NULL if header is not present.
*/
static const char *get_transfer_encoding(struct ast_variable *headers)
{
return get_header(headers, "Transfer-Encoding");
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
* \internal
* \brief Determine if the HTTP peer wants the connection closed.
*
* \param headers List of HTTP headers
*
* \retval 0 keep connection open.
* \retval -1 close connection.
*/
static int http_check_connection_close(struct ast_variable *headers)
{
const char *connection = get_header(headers, "Connection");
int close_connection = 0;
if (connection && !strcasecmp(connection, "close")) {
close_connection = -1;
}
return close_connection;
}
void ast_http_request_close_on_completion(struct ast_tcptls_session_instance *ser)
{
struct http_worker_private_data *request = ser->private_data;
ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
}
/*!
* \internal
* \brief Initialize the request tracking information in case of early failure.
* \since 12.4.0
*
* \param request Request tracking information.
*
* \return Nothing
*/
static void http_request_tracking_init(struct http_worker_private_data *request)
{
ast_set_flags_to(&request->flags,
HTTP_FLAG_HAS_BODY | HTTP_FLAG_BODY_READ | HTTP_FLAG_CLOSE_ON_COMPLETION,
/* Assume close in case request fails early */
HTTP_FLAG_CLOSE_ON_COMPLETION);
}
/*!
* \internal
* \brief Setup the HTTP request tracking information.
* \since 12.4.0
*
* \param ser HTTP TCP/TLS session object.
* \param headers List of HTTP headers.
*
* \retval 0 on success.
* \retval -1 on error.
*/
static int http_request_tracking_setup(struct ast_tcptls_session_instance *ser, struct ast_variable *headers)
{
struct http_worker_private_data *request = ser->private_data;
const char *transfer_encoding;
ast_set_flags_to(&request->flags,
HTTP_FLAG_HAS_BODY | HTTP_FLAG_BODY_READ | HTTP_FLAG_CLOSE_ON_COMPLETION,
http_check_connection_close(headers) ? HTTP_FLAG_CLOSE_ON_COMPLETION : 0);
transfer_encoding = get_transfer_encoding(headers);
if (transfer_encoding && !strcasecmp(transfer_encoding, "chunked")) {
request->body_length = -1;
ast_set_flag(&request->flags, HTTP_FLAG_HAS_BODY);
return 0;
}
request->body_length = get_content_length(headers);
if (0 < request->body_length) {
ast_set_flag(&request->flags, HTTP_FLAG_HAS_BODY);
} else if (request->body_length < 0) {
/* Invalid Content-Length */
ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
ast_http_error(ser, 400, "Bad Request", "Invalid Content-Length in request!");
return -1;
}
return 0;
}
void ast_http_body_read_status(struct ast_tcptls_session_instance *ser, int read_success)
{
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. */
return;
}
ast_set_flag(&request->flags, HTTP_FLAG_BODY_READ);
if (!read_success) {
ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
}
}
/*!
* \internal
* \brief Read the next length bytes from the HTTP body.
* \since 12.4.0
*
* \param ser HTTP TCP/TLS session object.
* \param buf Where to put the contents reading.
* \param length How much contents to read.
* \param what_getting Name of the contents reading.
*
* \retval 0 on success.
* \retval -1 on error.
*/
static int http_body_read_contents(struct ast_tcptls_session_instance *ser, char *buf, int length, const char *what_getting)
{
int res;
/* Stay in fread until get all the expected data or timeout. */
res = fread(buf, length, 1, ser->f);
if (res < 1) {
ast_log(LOG_WARNING, "Short HTTP request %s (Wanted %d)\n",
what_getting, length);
return -1;
}
return 0;
}
/*!
* \internal
* \brief Read and discard the next length bytes from the HTTP body.
* \since 12.4.0
*
* \param ser HTTP TCP/TLS session object.
* \param length How much contents to discard
* \param what_getting Name of the contents discarding.
*
* \retval 0 on success.
* \retval -1 on error.
*/
static int http_body_discard_contents(struct ast_tcptls_session_instance *ser, int length, const char *what_getting)
{
int res;
char buf[MAX_HTTP_LINE_LENGTH];/* Discard buffer */
/* Stay in fread until get all the expected data or timeout. */
while (sizeof(buf) < length) {
res = fread(buf, sizeof(buf), 1, ser->f);
if (res < 1) {
ast_log(LOG_WARNING, "Short HTTP request %s (Wanted %zu of remaining %d)\n",
what_getting, sizeof(buf), length);
return -1;
}
length -= sizeof(buf);
}
res = fread(buf, length, 1, ser->f);
if (res < 1) {
ast_log(LOG_WARNING, "Short HTTP request %s (Wanted %d of remaining %d)\n",
what_getting, length, length);
return -1;
}
return 0;
}
/*!
* \internal
* \brief decode chunked mode hexadecimal value
*
* \param s string to decode
* \param len length of string
*
* \retval length on success.
* \retval -1 on error.
*/
static int chunked_atoh(const char *s, int len)
{
int value = 0;
char c;
if (*s < '0') {
/* zero value must be 0\n not just \n */
return -1;
}
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;
}