Merge in ast_strftime branch, which changes timestamps to be accurate to the microsec...
[asterisk/asterisk.git] / main / http.c
index 007b60f..b61858c 100644 (file)
@@ -21,8 +21,9 @@
  * \brief http server for AMI access
  *
  * \author Mark Spencer <markster@digium.com>
- * This program implements a tiny http server supporting the "get" method
- * only and was inspired by micro-httpd by Jef Poskanzer 
+ *
+ * This program implements a tiny http server
+ * and was inspired by micro-httpd by Jef Poskanzer 
  * 
  * \ref AstHTTP - AMI over the http protocol
  */
@@ -47,6 +48,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include <fcntl.h>
 #include <pthread.h>
 
+#include "minimime/mm.h"
+
 #include "asterisk/cli.h"
 #include "asterisk/http.h"
 #include "asterisk/utils.h"
@@ -55,6 +58,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/config.h"
 #include "asterisk/stringfields.h"
 #include "asterisk/version.h"
+#include "asterisk/manager.h"
 
 #define MAX_PREFIX 80
 #define DEFAULT_PREFIX "/asterisk"
@@ -93,14 +97,22 @@ static struct server_args https_desc = {
 
 static AST_RWLIST_HEAD_STATIC(uris, ast_http_uri);     /*!< list of supported handlers */
 
+struct ast_http_post_mapping {
+       AST_RWLIST_ENTRY(ast_http_post_mapping) entry;
+       char *from;
+       char *to;
+};
+
+static AST_RWLIST_HEAD_STATIC(post_mappings, ast_http_post_mapping);
+
 /* all valid URIs must be prepended by the string in prefix. */
 static char prefix[MAX_PREFIX];
 static int enablestatic;
 
 /*! \brief Limit the kinds of files we're willing to serve up */
 static struct {
-       char *ext;
-       char *mtype;
+       const char *ext;
+       const char *mtype;
 } mimetypes[] = {
        { "png", "image/png" },
        { "jpg", "image/jpeg" },
@@ -108,6 +120,8 @@ static struct {
        { "wav", "audio/x-wav" },
        { "mp3", "audio/mpeg" },
        { "svg", "image/svg+xml" },
+       { "svgz", "image/svg+xml" },
+       { "gif", "image/gif" },
 };
 
 struct http_uri_redirect {
@@ -118,7 +132,7 @@ struct http_uri_redirect {
 
 static AST_RWLIST_HEAD_STATIC(uri_redirects, http_uri_redirect);
 
-static char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen)
+static const char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen)
 {
        int x;
        if (ftype) {
@@ -131,27 +145,18 @@ static char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen)
        return wkspace;
 }
 
-/* like ast_uri_decode, but replace '+' with ' ' */
-static char *uri_decode(char *buf)
+static struct ast_str *static_callback(struct server_instance *ser, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
 {
-       char *c;
-       ast_uri_decode(buf);
-       for (c = buf; *c; c++) {
-               if (*c == '+')
-                       *c = ' ';
-       }
-       return buf;
-}
-
-static struct ast_str *static_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
-{
-       struct ast_str *result;
        char *path;
-       char *ftype, *mtype;
+       char *ftype;
+       const char *mtype;
        char wkspace[80];
        struct stat st;
        int len;
        int fd;
+       struct timeval tv = ast_tvnow();
+       char buf[256];
+       struct ast_tm tm;
 
        /* Yuck.  I'm not really sold on this, but if you don't deliver static content it makes your configuration 
           substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
@@ -165,7 +170,7 @@ static struct ast_str *static_callback(struct sockaddr_in *req, const char *uri,
                
        if ((ftype = strrchr(uri, '.')))
                ftype++;
-       mtype=ftype2mtype(ftype, wkspace, sizeof(wkspace));
+       mtype = ftype2mtype(ftype, wkspace, sizeof(wkspace));
        
        /* Cap maximum length */
        len = strlen(uri) + strlen(ast_config_AST_DATA_DIR) + strlen("/static-http/") + 5;
@@ -182,35 +187,35 @@ static struct ast_str *static_callback(struct sockaddr_in *req, const char *uri,
        if (fd < 0)
                goto out403;
 
-       len = st.st_size + strlen(mtype) + 40;
-       result = ast_str_create(len);
-       if (result == NULL)     /* XXX not really but... */
-               goto out403;
+       ast_strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
+       fprintf(ser->f, "HTTP/1.1 200 OK\r\n"
+               "Server: Asterisk/%s\r\n"
+               "Date: %s\r\n"
+               "Connection: close\r\n"
+               "Cache-Control: no-cache, no-store\r\n"
+               "Content-Length: %d\r\n"
+               "Content-type: %s\r\n\r\n",
+               ASTERISK_VERSION, buf, (int) st.st_size, mtype);
+
+       while ((len = read(fd, buf, sizeof(buf))) > 0)
+               fwrite(buf, 1, len, ser->f);
 
-       ast_str_append(&result, 0, "Content-type: %s\r\n\r\n", mtype);
-       *contentlength = read(fd, result->str + result->used, st.st_size);
-       if (*contentlength < 0) {
-               close(fd);
-               free(result);
-               goto out403;
-       }
-       result->used += *contentlength;
        close(fd);
-       return result;
+       return NULL;
 
 out404:
        *status = 404;
-       *title = strdup("Not Found");
+       *title = ast_strdup("Not Found");
        return ast_http_error(404, "Not Found", NULL, "Nothing to see here.  Move along.");
 
 out403:
        *status = 403;
-       *title = strdup("Access Denied");
+       *title = ast_strdup("Access Denied");
        return ast_http_error(403, "Access Denied", NULL, "Sorry, I cannot let you do that, Dave.");
 }
 
 
-static struct ast_str *httpstatus_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
+static struct ast_str *httpstatus_callback(struct server_instance *ser, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
 {
        struct ast_str *out = ast_str_create(512);
        struct ast_variable *v;
@@ -259,6 +264,7 @@ static struct ast_http_uri staticuri = {
        .description = "Asterisk HTTP Static Delivery",
        .uri = "static",
        .has_subtree = 1,
+       .static_content = 1,
 };
        
 struct ast_str *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
@@ -328,7 +334,218 @@ void ast_http_uri_unlink(struct ast_http_uri *urih)
        AST_RWLIST_UNLOCK(&uris);
 }
 
-static struct ast_str *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **title, int *contentlength, struct ast_variable **cookies)
+/*! \note This assumes that the post_mappings list is locked */
+static struct ast_http_post_mapping *find_post_mapping(const char *uri)
+{
+       struct ast_http_post_mapping *post_map;
+
+       if (!ast_strlen_zero(prefix) && strncmp(prefix, uri, strlen(prefix))) {
+               ast_debug(1, "URI %s does not have prefix %s\n", uri, prefix);
+               return NULL;
+       }
+
+       uri += strlen(prefix);
+       if (*uri == '/')
+               uri++;
+       
+       AST_RWLIST_TRAVERSE(&post_mappings, post_map, entry) {
+               if (!strcmp(uri, post_map->from))
+                       return post_map;
+       }
+
+       return NULL;
+}
+
+static int get_filename(struct mm_mimepart *part, char *fn, size_t fn_len)
+{
+       const char *filename;
+
+       filename = mm_content_getdispositionparambyname(part->type, "filename");
+
+       if (ast_strlen_zero(filename))
+               return -1;
+
+       ast_copy_string(fn, filename, fn_len);
+
+       return 0;
+}
+
+static void post_raw(struct mm_mimepart *part, const char *post_dir, const char *fn)
+{
+       char filename[PATH_MAX];
+       FILE *f;
+       const char *body;
+       size_t body_len;
+
+       snprintf(filename, sizeof(filename), "%s/%s", post_dir, fn);
+
+       ast_debug(1, "Posting raw data to %s\n", filename);
+
+       if (!(f = fopen(filename, "w"))) {
+               ast_log(LOG_WARNING, "Unable to open %s for writing file from a POST!\n", filename);
+               return;
+       }
+
+       if (!(body = mm_mimepart_getbody(part, 0))) {
+               ast_debug(1, "Couldn't get the mimepart body\n");
+               fclose(f);
+               return;
+       }
+       body_len = mm_mimepart_getlength(part);
+
+       ast_debug(1, "Body length is %ld\n", (long int)body_len);
+
+       fwrite(body, 1, body_len, f);
+
+       fclose(f);
+}
+
+static struct ast_str *handle_post(struct server_instance *ser, char *uri, 
+       int *status, char **title, int *contentlength, struct ast_variable *headers,
+       struct ast_variable *cookies)
+{
+       char buf;
+       FILE *f;
+       size_t res;
+       struct ast_variable *var;
+       int content_len = 0;
+       MM_CTX *ctx;
+       int mm_res, i;
+       struct ast_http_post_mapping *post_map;
+       const char *post_dir;
+       unsigned long ident = 0;
+
+       for (var = cookies; var; var = var->next) {
+               if (strcasecmp(var->name, "mansession_id"))
+                       continue;
+
+               if (sscanf(var->value, "%lx", &ident) != 1) {
+                       *status = 400;
+                       *title = ast_strdup("Bad Request");
+                       return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
+               }
+
+               if (!astman_verify_session_writepermissions(ident, EVENT_FLAG_CONFIG)) {
+                       *status = 401;
+                       *title = ast_strdup("Unauthorized");
+                       return ast_http_error(401, "Unauthorized", NULL, "You are not authorized to make this request.");
+               }
+
+               break;
+       }
+       if (!var) {
+               *status = 401;
+               *title = ast_strdup("Unauthorized");
+               return ast_http_error(401, "Unauthorized", NULL, "You are not authorized to make this request.");
+       }
+
+       if (!(f = tmpfile()))
+               return NULL;
+
+       for (var = headers; var; var = var->next) {
+               if (!strcasecmp(var->name, "Content-Length")) {
+                       if ((sscanf(var->value, "%u", &content_len)) != 1) {
+                               ast_log(LOG_ERROR, "Invalid Content-Length in POST request!\n");
+                               fclose(f);
+                               return NULL;
+                       }
+                       ast_debug(1, "Got a Content-Length of %d\n", content_len);
+               } else if (!strcasecmp(var->name, "Content-Type"))
+                       fprintf(f, "Content-Type: %s\r\n\r\n", var->value);
+       }
+
+       while ((res = fread(&buf, 1, 1, ser->f))) {
+               fwrite(&buf, 1, 1, f);
+               content_len--;
+               if (!content_len)
+                       break;
+       }
+
+       if (fseek(f, SEEK_SET, 0)) {
+               ast_debug(1, "Failed to seek temp file back to beginning.\n");
+               fclose(f);
+               return NULL;
+       }
+
+       AST_RWLIST_RDLOCK(&post_mappings);
+       if (!(post_map = find_post_mapping(uri))) {
+               ast_debug(1, "%s is not a valid URI for POST\n", uri);
+               AST_RWLIST_UNLOCK(&post_mappings);
+               fclose(f);
+               *status = 404;
+               *title = ast_strdup("Not Found");
+               return ast_http_error(404, "Not Found", NULL, "The requested URL was not found on this server.");
+       }
+       post_dir = ast_strdupa(post_map->to);
+       post_map = NULL;
+       AST_RWLIST_UNLOCK(&post_mappings);
+
+       ast_debug(1, "Going to post files to dir %s\n", post_dir);
+
+       if (!(ctx = mm_context_new())) {
+               fclose(f);
+               return NULL;
+       }
+
+       mm_res = mm_parse_fileptr(ctx, f, MM_PARSE_LOOSE, 0);
+       fclose(f);
+       if (mm_res == -1) {
+               ast_log(LOG_ERROR, "Error parsing MIME data\n");
+               mm_context_free(ctx);
+               *status = 400;
+               *title = ast_strdup("Bad Request");
+               return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
+       }
+
+       mm_res = mm_context_countparts(ctx);
+       if (!mm_res) {
+               ast_log(LOG_ERROR, "Invalid MIME data, found no parts!\n");
+               mm_context_free(ctx);
+               *status = 400;
+               *title = ast_strdup("Bad Request");
+               return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
+       }
+
+       if (option_debug) {
+               if (mm_context_iscomposite(ctx))
+                       ast_debug(1, "Found %d MIME parts\n", mm_res - 1);
+               else
+                       ast_debug(1, "We have a flat (not multi-part) message\n");
+       }
+
+       for (i = 1; i < mm_res; i++) {
+               struct mm_mimepart *part;
+               char fn[PATH_MAX];
+
+               if (!(part = mm_context_getpart(ctx, i))) {
+                       ast_debug(1, "Failed to get mime part num %d\n", i);
+                       continue;
+               }
+
+               if (get_filename(part, fn, sizeof(fn))) {
+                       ast_debug(1, "Failed to retrieve a filename for part num %d\n", i);
+                       continue;
+               }
+       
+               if (!part->type) {
+                       ast_debug(1, "This part has no content struct?\n");
+                       continue;
+               }
+
+               /* XXX This assumes the MIME part body is not encoded! */
+               post_raw(part, post_dir, fn);
+       }
+
+       mm_context_free(ctx);
+
+       *status = 200;
+       *title = ast_strdup("OK");
+       return ast_http_error(200, "OK", NULL, "File successfully uploaded.");
+}
+
+static struct ast_str *handle_uri(struct server_instance *ser, char *uri, int *status, 
+       char **title, int *contentlength, struct ast_variable **cookies, 
+       unsigned int *static_content)
 {
        char *c;
        struct ast_str *out = NULL;
@@ -346,7 +563,7 @@ static struct ast_str *handle_uri(struct sockaddr_in *sin, char *uri, int *statu
                while ((val = strsep(&params, "&"))) {
                        var = strsep(&val, "=");
                        if (val)
-                               uri_decode(val);
+                               ast_uri_decode(val);
                        else 
                                val = "";
                        ast_uri_decode(var);
@@ -379,7 +596,7 @@ static struct ast_str *handle_uri(struct sockaddr_in *sin, char *uri, int *statu
                        out = ast_http_error(302, "Moved Temporarily", buf,
                                "There is no spoon...");
                        *status = 302;
-                       *title = strdup("Moved Temporarily");
+                       *title = ast_strdup("Moved Temporarily");
                        break;
                }
        }
@@ -410,13 +627,15 @@ static struct ast_str *handle_uri(struct sockaddr_in *sin, char *uri, int *statu
                        AST_RWLIST_UNLOCK(&uris);
        }
        if (urih) {
-               out = urih->callback(sin, uri, vars, status, title, contentlength);
+               if (urih->static_content)
+                       *static_content = 1;
+               out = urih->callback(ser, uri, vars, status, title, contentlength);
                AST_RWLIST_UNLOCK(&uris);
        } else {
                out = ast_http_error(404, "Not Found", NULL,
                        "The requested URL was not found on this server.");
                *status = 404;
-               *title = strdup("Not Found");
+               *title = ast_strdup("Not Found");
        }
 
 cleanup:
@@ -508,7 +727,7 @@ static void *make_file_from_fd(void *data)
        if (!ser->f) {
                close(ser->fd);
                ast_log(LOG_WARNING, "FILE * open failed!\n");
-               free(ser);
+               ast_free(ser);
                return NULL;
        }
        return ser->parent->worker_fn(ser);
@@ -519,10 +738,11 @@ static void *httpd_helper_thread(void *data)
        char buf[4096];
        char cookie[4096];
        struct server_instance *ser = data;
-       struct ast_variable *var, *prev=NULL, *vars=NULL;
+       struct ast_variable *var, *prev=NULL, *vars=NULL, *headers = NULL;
        char *uri, *title=NULL;
        int status = 200, contentlength = 0;
        struct ast_str *out = NULL;
+       unsigned int static_content = 0;
 
        if (!fgets(buf, sizeof(buf), ser->f))
                goto done;
@@ -548,8 +768,23 @@ static void *httpd_helper_thread(void *data)
                ast_trim_blanks(cookie);
                if (ast_strlen_zero(cookie))
                        break;
-               if (strncasecmp(cookie, "Cookie: ", 8))
+               if (strncasecmp(cookie, "Cookie: ", 8)) {
+                       char *name, *value;
+
+                       value = ast_strdupa(cookie);
+                       name = strsep(&value, ":");
+                       if (!value)
+                               continue;
+                       value = ast_skip_blanks(value);
+                       if (ast_strlen_zero(value))
+                               continue;
+                       var = ast_variable_new(name, value);
+                       if (!var)
+                               continue;
+                       var->next = headers;
+                       headers = var;
                        continue;
+               }
 
                /* TODO - The cookie parsing code below seems to work   
                   in IE6 and FireFox 1.5.  However, it is not entirely 
@@ -595,28 +830,31 @@ static void *httpd_helper_thread(void *data)
 
        if (!*uri)
                out = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
+       else if (!strcasecmp(buf, "post")) 
+               out = handle_post(ser, uri, &status, &title, &contentlength, headers, vars);
        else if (strcasecmp(buf, "get")) 
                out = ast_http_error(501, "Not Implemented", NULL,
                        "Attempt to use unimplemented / unsupported method");
        else    /* try to serve it */
-               out = handle_uri(&ser->requestor, uri, &status, &title, &contentlength, &vars);
+               out = handle_uri(ser, uri, &status, &title, &contentlength, &vars, &static_content);
 
        /* If they aren't mopped up already, clean up the cookies */
        if (vars)
                ast_variables_destroy(vars);
 
-       if (out == NULL)
-               out = ast_http_error(500, "Internal Error", NULL, "Internal Server Error");
        if (out) {
-               time_t t = time(NULL);
+               struct timeval tv = ast_tvnow();
                char timebuf[256];
+               struct ast_tm tm;
 
-               strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
+               ast_strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
                fprintf(ser->f, "HTTP/1.1 %d %s\r\n"
                                "Server: Asterisk/%s\r\n"
                                "Date: %s\r\n"
-                               "Connection: close\r\n",
-                       status, title ? title : "OK", ASTERISK_VERSION, timebuf);
+                               "Connection: close\r\n"
+                               "%s",
+                       status, title ? title : "OK", ASTERISK_VERSION, timebuf,
+                       static_content ? "" : "Cache-Control: no-cache, no-store\r\n");
                if (!contentlength) {   /* opaque body ? just dump it hoping it is properly formatted */
                        fprintf(ser->f, "%s", out->str);
                } else {
@@ -629,14 +867,14 @@ static void *httpd_helper_thread(void *data)
                                fwrite(tmp + 4, 1, contentlength, ser->f);
                        }
                }
-               free(out);
+               ast_free(out);
        }
        if (title)
-               free(title);
+               ast_free(title);
 
 done:
        fclose(ser->f);
-       free(ser);
+       ast_free(ser);
        return NULL;
 }
 
@@ -648,7 +886,6 @@ void *server_root(void *data)
        socklen_t sinlen;
        struct server_instance *ser;
        pthread_t launched;
-       pthread_attr_t attr;
        
        for (;;) {
                int i, flags;
@@ -676,17 +913,13 @@ void *server_root(void *data)
                ser->fd = fd;
                ser->parent = desc;
                memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
-
-               pthread_attr_init(&attr);
-               pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
                        
-               if (ast_pthread_create_background(&launched, &attr, make_file_from_fd, ser)) {
+               if (ast_pthread_create_detached_background(&launched, NULL, make_file_from_fd, ser)) {
                        ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
                        close(ser->fd);
-                       free(ser);
+                       ast_free(ser);
                }
 
-               pthread_attr_destroy(&attr);
        }
        return NULL;
 }
@@ -737,8 +970,7 @@ void server_start(struct server_args *desc)
        
        /* Do nothing if nothing has changed */
        if (!memcmp(&desc->oldsin, &desc->sin, sizeof(desc->oldsin))) {
-               if (option_debug)
-                       ast_log(LOG_DEBUG, "Nothing changed in %s\n", desc->name);
+               ast_debug(1, "Nothing changed in %s\n", desc->name);
                return;
        }
        
@@ -850,6 +1082,47 @@ static void add_redirect(const char *value)
        AST_RWLIST_UNLOCK(&uri_redirects);
 }
 
+static void destroy_post_mapping(struct ast_http_post_mapping *post_map)
+{
+       if (post_map->from)
+               ast_free(post_map->from);
+       if (post_map->to)
+               ast_free(post_map->to);
+       ast_free(post_map);
+}
+
+static void destroy_post_mappings(void)
+{
+       struct ast_http_post_mapping *post_map;
+
+       AST_RWLIST_WRLOCK(&post_mappings);
+       while ((post_map = AST_RWLIST_REMOVE_HEAD(&post_mappings, entry)))
+               destroy_post_mapping(post_map);
+       AST_RWLIST_UNLOCK(&post_mappings);
+}
+
+static void add_post_mapping(const char *from, const char *to)
+{
+       struct ast_http_post_mapping *post_map;
+
+       if (!(post_map = ast_calloc(1, sizeof(*post_map))))
+               return;
+
+       if (!(post_map->from = ast_strdup(from))) {
+               destroy_post_mapping(post_map);
+               return;
+       }
+
+       if (!(post_map->to = ast_strdup(to))) {
+               destroy_post_mapping(post_map);
+               return;
+       }
+
+       AST_RWLIST_WRLOCK(&post_mappings);
+       AST_RWLIST_INSERT_TAIL(&post_mappings, post_map, entry);
+       AST_RWLIST_UNLOCK(&post_mappings);
+}
+
 static int __ast_http_load(int reload)
 {
        struct ast_config *cfg;
@@ -868,21 +1141,24 @@ static int __ast_http_load(int reload)
 
        memset(&https_desc.sin, 0, sizeof(https_desc.sin));
        https_desc.sin.sin_port = htons(8089);
+
        strcpy(newprefix, DEFAULT_PREFIX);
 
        http_tls_cfg.enabled = 0;
        if (http_tls_cfg.certfile)
-               free(http_tls_cfg.certfile);
+               ast_free(http_tls_cfg.certfile);
        http_tls_cfg.certfile = ast_strdup(AST_CERTFILE);
        if (http_tls_cfg.cipher)
-               free(http_tls_cfg.cipher);
+               ast_free(http_tls_cfg.cipher);
        http_tls_cfg.cipher = ast_strdup("");
 
        AST_RWLIST_WRLOCK(&uri_redirects);
        while ((redirect = AST_RWLIST_REMOVE_HEAD(&uri_redirects, entry)))
-               free(redirect);
+               ast_free(redirect);
        AST_RWLIST_UNLOCK(&uri_redirects);
 
+       destroy_post_mappings();
+
        cfg = ast_config_load("http.conf");
        if (cfg) {
                v = ast_variable_browse(cfg, "general");
@@ -894,10 +1170,10 @@ static int __ast_http_load(int reload)
                        else if (!strcasecmp(v->name, "sslbindport"))
                                https_desc.sin.sin_port = htons(atoi(v->value));
                        else if (!strcasecmp(v->name, "sslcert")) {
-                               free(http_tls_cfg.certfile);
+                               ast_free(http_tls_cfg.certfile);
                                http_tls_cfg.certfile = ast_strdup(v->value);
                        } else if (!strcasecmp(v->name, "sslcipher")) {
-                               free(http_tls_cfg.cipher);
+                               ast_free(http_tls_cfg.cipher);
                                http_tls_cfg.cipher = ast_strdup(v->value);
                        }
                        else if (!strcasecmp(v->name, "enablestatic"))
@@ -930,6 +1206,10 @@ static int __ast_http_load(int reload)
                                ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
                        }
                }
+
+               for (v = ast_variable_browse(cfg, "post_mappings"); v; v = v->next)
+                       add_post_mapping(v->name, v->value);
+
                ast_config_destroy(cfg);
        }
        if (!have_sslbindaddr)
@@ -942,6 +1222,7 @@ static int __ast_http_load(int reload)
        server_start(&http_desc);
        if (ssl_setup(https_desc.tls_cfg))
                server_start(&https_desc);
+
        return 0;
 }
 
@@ -949,6 +1230,7 @@ static int handle_show_http(int fd, int argc, char *argv[])
 {
        struct ast_http_uri *urih;
        struct http_uri_redirect *redirect;
+       struct ast_http_post_mapping *post_map;
 
        if (argc != 3)
                return RESULT_SHOWUSAGE;
@@ -985,6 +1267,14 @@ static int handle_show_http(int fd, int argc, char *argv[])
                ast_cli(fd, "  None.\n");
        AST_RWLIST_UNLOCK(&uri_redirects);
 
+
+       ast_cli(fd, "\nPOST mappings:\n");
+       AST_RWLIST_RDLOCK(&post_mappings);
+       AST_LIST_TRAVERSE(&post_mappings, post_map, entry)
+               ast_cli(fd, "%s/%s => %s\n", prefix, post_map->from, post_map->to);
+       ast_cli(fd, "%s\n", AST_LIST_EMPTY(&post_mappings) ? "None.\n" : "");
+       AST_RWLIST_UNLOCK(&post_mappings);
+
        return RESULT_SUCCESS;
 }
 
@@ -1005,8 +1295,12 @@ static struct ast_cli_entry cli_http[] = {
 
 int ast_http_init(void)
 {
+       mm_library_init();
+       mm_codec_registerdefaultcodecs();
+
        ast_http_uri_link(&statusuri);
        ast_http_uri_link(&staticuri);
        ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
+
        return __ast_http_load(0);
 }