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_FILE_VERSION(__FILE__, "$Revision$")
#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
/* See http.h for more information about the SSL implementation */
#if defined(HAVE_OPENSSL) && (defined(HAVE_FUNOPEN) || defined(HAVE_FOPENCOOKIE))
#define DO_SSL /* comment in/out if you want to support ssl */
static int session_limit = DEFAULT_SESSION_LIMIT;
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 = {
.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");
return -1;
}
/* 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
}
if ( (http_header = ast_str_create(255)) == NULL) {
return -1;
}
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.");
return -1;
Mark Spencer
committed
out403:
ast_http_error(ser, 403, "Access Denied", "You do not have permission to access the requested URL.");
return -1;
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");
return -1;
}
if ( (out = ast_str_create(512)) == NULL) {
return -1;
ast_str_append(&out, 0,
"<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");
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>\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
};
/* free content variable and close socket*/
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, const int fd,
unsigned int static_content)
{
struct timeval now = ast_tvnow();
struct ast_tm tm;
char timebuf[80];
int content_length = 0;
if (!ser || 0 == ser->f) {
return;
}
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"
"Server: Asterisk/%s\r\n"
"Date: %s\r\n"
"Connection: close\r\n"
"%s"
"Content-Length: %d\r\n"
status_code, status_title ? status_title : "OK",
ast_get_version(),
timebuf,
static_content ? "" : "Cache-Control: no-cache, no-store\r\n",
content_length,
http_header ? ast_str_buffer(http_header) : ""
);
/* send content */
if (method != AST_HTTP_HEAD || status_code >= 400) {
if (content_length) {
if (fwrite(ast_str_buffer(out), content_length, 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));
}
}
}
}
if (http_header) {
ast_free(http_header);
}
if (out) {
ast_free(out);
}
ast_tcptls_close_session_file(ser);
return;
}
/* Send http "401 Unauthorized" responce and close socket*/
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)
{
struct ast_str *http_headers = ast_str_create(128);
struct ast_str *out = ast_str_create(512);
if (!http_headers || !out) {
ast_free(http_headers);
ast_free(out);
return;
ast_str_set(&http_headers, 0,
"WWW-authenticate: Digest algorithm=MD5, realm=\"%s\", nonce=\"%08lx\", qop=\"auth\", opaque=\"%08lx\"%s\r\n"
realm ? realm : "Asterisk",
nonce,
opaque,
stale ? ", stale=true" : "");
ast_str_set(&out, 0,
"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
"<html><head>\r\n"
"<title>401 Unauthorized</title>\r\n"
"</head><body>\r\n"
"<h1>401 Unauthorized</h1>\r\n"
"<p>%s</p>\r\n"
"<hr />\r\n"
"<address>Asterisk Server</address>\r\n"
"</body></html>\r\n",
text ? text : "");
ast_http_send(ser, AST_HTTP_UNKNOWN, 401, "Unauthorized", http_headers, out, 0, 0);
return;
}
/* send http error response and close socket*/
void ast_http_error(struct ast_tcptls_session_instance *ser, int status_code, const char *status_title, const char *text)
{
struct ast_str *http_headers = ast_str_create(40);
struct ast_str *out = ast_str_create(256);
if (!http_headers || !out) {
ast_free(http_headers);
ast_free(out);
return;
}
ast_str_set(&http_headers, 0, "Content-type: text/html\r\n");
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"
"<address>Asterisk Server</address>\r\n"
"</body></html>\r\n",
status_code, status_title, status_title, text);
ast_http_send(ser, AST_HTTP_UNKNOWN, status_code, status_title, http_headers, out, 0, 0);
return;
}
/*! \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);
#define MAX_POST_CONTENT 1025
/*!
* \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.
* \return Value of the Content-Length header.
* \return 0 if header is not present, or 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;
/* atoi() will return 0 for invalid inputs, which is good enough for
* the HTTP parsing. */
return atoi(content_length);
}
/*!
* \brief Returns the value of the Transfer-Encoding header.
*
* \param headers HTTP headers.
* \return Value of the Transfer-Encoding header.
* \return 0 if header is not present, or is invalid.
*/
static const char *get_transfer_encoding(struct ast_variable *headers)
{
return get_header(headers, "Transfer-Encoding");
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
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
/*!
* \brief decode chunked mode hexadecimal value
*
* \param s string to decode
* \param len length of string
* \return integer value or -1 for decode 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--)
{
if (*s == '\x0D') {
return value;
}
value <<= 4;
c = *s++;
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;
}
/*!
* \brief Returns the contents (body) of the HTTP request
*
* \param return_length ptr to int that returns content length
* \param aser 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)
{
const char *transfer_encoding;
int res;
int content_length = 0;
int chunk_length;
char chunk_header[8];
int bufsize = 250;
char *buf;
transfer_encoding = get_transfer_encoding(headers);
if (ast_strlen_zero(transfer_encoding) ||
strcasecmp(transfer_encoding, "chunked") != 0) {
/* handle regular non-chunked content */
content_length = get_content_length(headers);
if (content_length <= 0) {
/* no content - not an error */
return NULL;
}
if (content_length > MAX_POST_CONTENT - 1) {
ast_log(LOG_WARNING,
"Excessively long HTTP content. (%d > %d)\n",
content_length, MAX_POST_CONTENT);
errno = EFBIG;
return NULL;
}
buf = ast_malloc(content_length + 1);
if (!buf) {
/* Malloc sets ENOMEM */
return NULL;
}
res = fread(buf, 1, content_length, ser->f);
if (res < content_length) {
/* Error, distinguishable by ferror() or feof(), but neither
* is good. Treat either one as I/O error */
ast_log(LOG_WARNING, "Short HTTP request body (%d < %d)\n",
res, content_length);
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) {
return NULL;
}
/* handled chunked content */
do {
/* get the line of hexadecimal giving chunk size */
if (!fgets(chunk_header, sizeof(chunk_header), ser->f)) {
ast_log(LOG_WARNING,
"Short HTTP read of chunked header\n");
errno = EIO;
ast_free(buf);
return NULL;
}
chunk_length = chunked_atoh(chunk_header, sizeof(chunk_header));
if (chunk_length < 0) {
ast_log(LOG_WARNING, "Invalid HTTP chunk size\n");
errno = EIO;
ast_free(buf);
return NULL;
}
if (content_length + chunk_length > MAX_POST_CONTENT - 1) {
ast_log(LOG_WARNING,
"Excessively long HTTP chunk. (%d + %d > %d)\n",
content_length, chunk_length, MAX_POST_CONTENT);
errno = EFBIG;
ast_free(buf);
return NULL;
}
/* insure buffer is large enough +1 */
if (content_length + chunk_length >= bufsize)
{
bufsize *= 2;
buf = ast_realloc(buf, bufsize);
if (!buf) {
return NULL;
}
}
/* read the chunk */
res = fread(buf + content_length, 1, chunk_length, ser->f);
if (res < chunk_length) {
ast_log(LOG_WARNING, "Short HTTP chunk read (%d < %d)\n",
res, chunk_length);
errno = EIO;
ast_free(buf);
return NULL;
}
content_length += chunk_length;
/* insure the next 2 bytes are CRLF */
res = fread(chunk_header, 1, 2, ser->f);
if (res < 2) {
ast_log(LOG_WARNING,
"Short HTTP chunk sync read (%d < 2)\n", res);
errno = EIO;
ast_free(buf);
return NULL;
}
if (chunk_header[0] != 0x0D || chunk_header[1] != 0x0A) {
ast_log(LOG_WARNING,
"Post HTTP chunk sync bytes wrong (%d, %d)\n",
chunk_header[0], chunk_header[1]);
errno = EIO;
ast_free(buf);
return NULL;
}
} while (chunk_length);
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 */
return NULL;
}
buf = ast_http_get_contents(&content_length, ser, headers);
if (buf == NULL) {
if (!content_length) {
/* it is not an error to have zero content */
return NULL;
}
body = ast_json_load_buf(buf, content_length, NULL);
if (body == 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_ptr);
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 */
return NULL;
}
buf = ast_http_get_contents(&content_length, ser, headers);
if (buf == NULL) {
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;
int res = -1;
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);
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);