res_pjsip_session: properly handle SDP from a forked call with early media
[asterisk/asterisk.git] / res / res_http_post.c
index bcf2e0d..44bb8ee 100644 (file)
@@ -17,7 +17,7 @@
  */
 
 /*!
- * \file 
+ * \file
  * \brief HTTP POST upload support for Asterisk HTTP server
  *
  * \author Terry Wilson <twilson@digium.com
 
 /*** MODULEINFO
        <depend>gmime</depend>
+       <support_level>core</support_level>
  ***/
 
 
 #include "asterisk.h"
 
-ASTERISK_FILE_VERSION(__FILE__, "$Revision: 111213 $")
-
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <gmime/gmime.h>
+#if defined (__OpenBSD__) || defined(__FreeBSD__) || defined(__Darwin__)
+#include <libgen.h>
+#endif
 
 #include "asterisk/linkedlists.h"
 #include "asterisk/http.h"
@@ -49,6 +51,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision: 111213 $")
 
 #define MAX_PREFIX 80
 
+/* gmime 2.4 provides a newer interface. */
+#ifdef GMIME_TYPE_CONTENT_TYPE
+#define AST_GMIME_VER_24
+#endif
+#if defined(GMIME_MAJOR_VERSION) && (GMIME_MAJOR_VERSION >= 3)
+#define AST_GMIME_VER_30
+#endif
+
 /* just a little structure to hold callback info for gmime */
 struct mime_cbinfo {
        int count;
@@ -69,7 +79,7 @@ static void post_raw(GMimePart *part, const char *post_dir, const char *fn)
 
        ast_debug(1, "Posting raw data to %s\n", filename);
 
-       if ((fd = open(filename, O_CREAT | O_WRONLY, 0666)) == -1) {
+       if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666)) == -1) {
                ast_log(LOG_WARNING, "Unable to open %s for writing file from a POST!\n", filename);
 
                return;
@@ -77,11 +87,17 @@ static void post_raw(GMimePart *part, const char *post_dir, const char *fn)
 
        stream = g_mime_stream_fs_new(fd);
 
+#ifdef AST_GMIME_VER_30
+       content = g_mime_part_get_content(part);
+#else
        content = g_mime_part_get_content_object(part);
+#endif
        g_mime_data_wrapper_write_to_stream(content, stream);
        g_mime_stream_flush(stream);
 
+#ifndef AST_GMIME_VER_24
        g_object_unref(content);
+#endif
        g_object_unref(stream);
 }
 
@@ -95,17 +111,25 @@ static GMimeMessage *parse_message(FILE *f)
 
        parser = g_mime_parser_new_with_stream(stream);
        g_mime_parser_set_respect_content_length(parser, 1);
-       
+
        g_object_unref(stream);
 
-       message = g_mime_parser_construct_message(parser);
+       message = g_mime_parser_construct_message(parser
+#ifdef AST_GMIME_VER_30
+                       , NULL
+#endif
+       );
 
        g_object_unref(parser);
 
        return message;
 }
 
+#ifdef AST_GMIME_VER_24
+static void process_message_callback(GMimeObject *parent, GMimeObject *part, gpointer user_data)
+#else
 static void process_message_callback(GMimeObject *part, gpointer user_data)
+#endif
 {
        struct mime_cbinfo *cbinfo = user_data;
 
@@ -119,14 +143,18 @@ static void process_message_callback(GMimeObject *part, gpointer user_data)
                ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MESSAGE_PARTIAL\n");
                return;
        } else if (GMIME_IS_MULTIPART(part)) {
+#ifndef AST_GMIME_VER_24
                GList *l;
-               
+
                ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MULTIPART, trying to process subparts\n");
                l = GMIME_MULTIPART(part)->subparts;
                while (l) {
                        process_message_callback(l->data, cbinfo);
                        l = l->next;
                }
+#else
+               ast_log(LOG_WARNING, "Got unexpected MIME subpart.\n");
+#endif
        } else if (GMIME_IS_PART(part)) {
                const char *filename;
 
@@ -148,112 +176,254 @@ static int process_message(GMimeMessage *message, const char *post_dir)
                .post_dir = post_dir,
        };
 
+#ifdef AST_GMIME_VER_24
+       g_mime_message_foreach(message, process_message_callback, &cbinfo);
+#else
        g_mime_message_foreach_part(message, process_message_callback, &cbinfo);
+#endif
 
        return cbinfo.count;
 }
 
-static struct ast_str *http_post_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *vars, struct ast_variable *headers, int *status, char **title, int *contentlength)
+/* Find a sequence of bytes within a binary array. */
+static int find_sequence(char * inbuf, int inlen, char * matchbuf, int matchlen)
 {
-       struct ast_variable *var;
-       unsigned long ident = 0;
+       int current;
+       int comp;
+       int found = 0;
+
+       for (current = 0; current < inlen-matchlen; current++, inbuf++) {
+               if (*inbuf == *matchbuf) {
+                       found=1;
+                       for (comp = 1; comp < matchlen; comp++) {
+                               if (inbuf[comp] != matchbuf[comp]) {
+                                       found = 0;
+                                       break;
+                               }
+                       }
+                       if (found) {
+                               break;
+                       }
+               }
+       }
+       if (found) {
+               return current;
+       } else {
+               return -1;
+       }
+}
+
+/*
+* The following is a work around to deal with how IE7 embeds the local file name
+* within the Mime header using full WINDOWS file path with backslash directory delimiters.
+* This section of code attempts to isolate the directory path and remove it
+* from what is written into the output file.  In addition, it changes
+* esc chars (i.e. backslashes) to forward slashes.
+* This function has two modes.  The first to find a boundary marker.  The
+* second is to find the filename immediately after the boundary.
+*/
+static int readmimefile(struct ast_iostream *in, FILE *fout, char *boundary, int contentlen)
+{
+       int find_filename = 0;
        char buf[4096];
+       int marker;
+       int x;
+       int char_in_buf = 0;
+       int num_to_read;
+       int boundary_len;
+       char * path_end, * path_start, * filespec;
+
+       if (NULL == in || NULL == fout || NULL == boundary || 0 >= contentlen) {
+               return -1;
+       }
+
+       boundary_len = strlen(boundary);
+       while (0 < contentlen || 0 < char_in_buf) {
+               /* determine how much I will read into the buffer */
+               if (contentlen > sizeof(buf) - char_in_buf) {
+                       num_to_read = sizeof(buf)- char_in_buf;
+               } else {
+                       num_to_read = contentlen;
+               }
+
+               if (0 < num_to_read) {
+                       if (ast_iostream_read(in, &(buf[char_in_buf]), num_to_read) < num_to_read) {
+                               ast_log(LOG_WARNING, "read failed: %s\n", strerror(errno));
+                               num_to_read = 0;
+                       }
+                       contentlen -= num_to_read;
+                       char_in_buf += num_to_read;
+               }
+               /* If I am looking for the filename spec */
+               if (find_filename) {
+                       path_end = filespec = NULL;
+                       x = strlen("filename=\"");
+                       marker = find_sequence(buf, char_in_buf, "filename=\"", x );
+                       if (0 <= marker) {
+                               marker += x;  /* Index beyond the filename marker */
+                               path_start = &buf[marker];
+                               for (path_end = path_start, x = 0; x < char_in_buf-marker; x++, path_end++) {
+                                       if ('\\' == *path_end) {        /* convert backslashses to forward slashes */
+                                               *path_end = '/';
+                                       }
+                                       if ('\"' == *path_end) {        /* If at the end of the file name spec */
+                                               *path_end = '\0';               /* temporarily null terminate the file spec for basename */
+                                               filespec = basename(path_start);
+                                               *path_end = '\"';
+                                               break;
+                                       }
+                               }
+                       }
+                       if (filespec) { /* If the file name path was found in the header */
+                               if (fwrite(buf, 1, marker, fout) != marker) {
+                                       ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
+                               }
+                               x = (int)(path_end+1 - filespec);
+                               if (fwrite(filespec, 1, x, fout) != x) {
+                                       ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
+                               }
+                               x = (int)(path_end+1 - buf);
+                               memmove(buf, &(buf[x]), char_in_buf-x);
+                               char_in_buf -= x;
+                       }
+                       find_filename = 0;
+               } else { /* I am looking for the boundary marker */
+                       marker = find_sequence(buf, char_in_buf, boundary, boundary_len);
+                       if (0 > marker) {
+                               if (char_in_buf < (boundary_len)) {
+                                       /*no possibility to find the boundary, write all you have */
+                                       if (fwrite(buf, 1, char_in_buf, fout) != char_in_buf) {
+                                               ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
+                                       }
+                                       char_in_buf = 0;
+                               } else {
+                                       /* write all except for area where the boundary marker could be */
+                                       if (fwrite(buf, 1, char_in_buf -(boundary_len -1), fout) != char_in_buf - (boundary_len - 1)) {
+                                               ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
+                                       }
+                                       x = char_in_buf -(boundary_len -1);
+                                       memmove(buf, &(buf[x]), char_in_buf-x);
+                                       char_in_buf = (boundary_len -1);
+                               }
+                       } else {
+                               /* write up through the boundary, then look for filename in the rest */
+                               if (fwrite(buf, 1, marker + boundary_len, fout) != marker + boundary_len) {
+                                       ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
+                               }
+                               x = marker + boundary_len;
+                               memmove(buf, &(buf[x]), char_in_buf-x);
+                               char_in_buf -= marker + boundary_len;
+                               find_filename =1;
+                       }
+               }
+       }
+       return 0;
+}
+
+static int http_post_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_variable *var;
+       uint32_t ident;
        FILE *f;
-       size_t res;
        int content_len = 0;
        struct ast_str *post_dir;
        GMimeMessage *message;
-       int message_count = 0;
+       char *boundary_marker = NULL;
 
-       if (!urih) {
-               return ast_http_error((*status = 400),
-                          (*title = ast_strdup("Missing URI handle")),
-                          NULL, "There was an error parsing the request");
+       if (method != AST_HTTP_POST) {
+               ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
+               return 0;
        }
 
-       for (var = vars; var; var = var->next) {
-               if (strcasecmp(var->name, "mansession_id")) {
-                       continue;
-               }
-
-               if (sscanf(var->value, "%lx", &ident) != 1) {
-                       return ast_http_error((*status = 400),
-                                             (*title = ast_strdup("Bad Request")),
-                                             NULL, "The was an error parsing the request.");
-               }
-
-               if (!astman_verify_session_writepermissions(ident, EVENT_FLAG_CONFIG)) {
-                       return ast_http_error((*status = 401),
-                                             (*title = ast_strdup("Unauthorized")),
-                                             NULL, "You are not authorized to make this request.");
-               }
+       if (!urih) {
+               ast_http_error(ser, 400, "Missing URI handle", "There was an error parsing the request");
+               return 0;
+       }
 
-               break;
+       ident = ast_http_manid_from_vars(headers);
+       if (!ident || !astman_is_authed(ident)) {
+               ast_http_request_close_on_completion(ser);
+               ast_http_error(ser, 403, "Access Denied", "Sorry, I cannot let you do that, Dave.");
+               return 0;
        }
 
-       if (!var) {
-               return ast_http_error((*status = 401),
-                                     (*title = ast_strdup("Unauthorized")),
-                                     NULL, "You are not authorized to make this request.");
+       if (!astman_verify_session_writepermissions(ident, EVENT_FLAG_CONFIG)) {
+               ast_http_request_close_on_completion(ser);
+               ast_http_error(ser, 401, "Unauthorized", "You are not authorized to make this request.");
+               return 0;
        }
 
        if (!(f = tmpfile())) {
                ast_log(LOG_ERROR, "Could not create temp file.\n");
-               return NULL;
+               ast_http_error(ser, 500, "Internal server error", "Could not create temp file.");
+               return 0;
        }
 
        for (var = headers; var; var = var->next) {
+               fprintf(f, "%s: %s\r\n", var->name, var->value);
+
                if (!strcasecmp(var->name, "Content-Length")) {
-                       if ((sscanf(var->value, "%u", &content_len)) != 1) {
+                       if ((sscanf(var->value, "%30u", &content_len)) != 1) {
                                ast_log(LOG_ERROR, "Invalid Content-Length in POST request!\n");
                                fclose(f);
-
-                               return NULL;
+                               ast_http_request_close_on_completion(ser);
+                               ast_http_error(ser, 400, "Bad Request", "Invalid Content-Length in POST request!");
+                               return 0;
                        }
                        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);
+                       boundary_marker = strstr(var->value, "boundary=");
+                       if (boundary_marker) {
+                               boundary_marker += strlen("boundary=");
+                       }
                }
        }
-
-       for (res = sizeof(buf); content_len; content_len -= res) {
-               if (content_len < res) {
-                       res = content_len;
-               }
-               fread(buf, 1, res, ser->f);
-               fwrite(buf, 1, res, f);
+       fprintf(f, "\r\n");
+
+       /*
+        * Always mark the body read as failed.
+        *
+        * XXX Should change readmimefile() to always be sure to read
+        * the entire body so we can update the read status and
+        * potentially keep the connection open.
+        */
+       ast_http_body_read_status(ser, 0);
+
+       if (0 > readmimefile(ser->stream, f, boundary_marker, content_len)) {
+               ast_debug(1, "Cannot find boundary marker in POST request.\n");
+               fclose(f);
+               ast_http_error(ser, 400, "Bad Request", "Cannot find boundary marker in POST request.");
+               return 0;
        }
 
        if (fseek(f, SEEK_SET, 0)) {
                ast_log(LOG_ERROR, "Failed to seek temp file back to beginning.\n");
                fclose(f);
-
-               return NULL;
+               ast_http_error(ser, 500, "Internal server error", "Failed to seek temp file back to beginning.");
+               return 0;
        }
 
        post_dir = urih->data;
 
        message = parse_message(f); /* Takes ownership and will close f */
-
        if (!message) {
                ast_log(LOG_ERROR, "Error parsing MIME data\n");
 
-               return ast_http_error((*status = 400),
-                                     (*title = ast_strdup("Bad Request")),
-                                     NULL, "The was an error parsing the request.");
+               ast_http_error(ser, 400, "Bad Request", "There was an error parsing the request.");
+               return 0;
        }
 
-       if (!(message_count = process_message(message, post_dir->str))) {
+       if (!process_message(message, ast_str_buffer(post_dir))) {
                ast_log(LOG_ERROR, "Invalid MIME data, found no parts!\n");
-
-               return ast_http_error((*status = 400),
-                                     (*title = ast_strdup("Bad Request")),
-                                     NULL, "The was an error parsing the request.");
+               g_object_unref(message);
+               ast_http_error(ser, 400, "Bad Request", "There was an error parsing the request.");
+               return 0;
        }
+       g_object_unref(message);
 
-       return ast_http_error((*status = 200),
-                             (*title = ast_strdup("OK")),
-                             NULL, "File successfully uploaded.");
+       /* XXX Passing 200 to the error response routine? */
+       ast_http_error(ser, 200, "OK", "File successfully uploaded.");
+       return 0;
 }
 
 static int __ast_http_post_load(int reload)
@@ -262,7 +432,8 @@ static int __ast_http_post_load(int reload)
        struct ast_variable *v;
        struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
 
-       if ((cfg = ast_config_load2("http.conf", "http", config_flags)) == CONFIG_STATUS_FILEUNCHANGED) {
+       cfg = ast_config_load2("http.conf", "http", config_flags);
+       if (!cfg || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
                return 0;
        }
 
@@ -270,43 +441,43 @@ static int __ast_http_post_load(int reload)
                ast_http_uri_unlink_all_with_key(__FILE__);
        }
 
-       if (cfg) {
-               for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
-                       if (!strcasecmp(v->name, "prefix")) {
-                               ast_copy_string(prefix, v->value, sizeof(prefix));
-                               if (prefix[strlen(prefix)] == '/') {
-                                       prefix[strlen(prefix)] = '\0';
-                               }
+       for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
+               if (!strcasecmp(v->name, "prefix")) {
+                       ast_copy_string(prefix, v->value, sizeof(prefix));
+                       if (prefix[strlen(prefix)] == '/') {
+                               prefix[strlen(prefix)] = '\0';
                        }
                }
+       }
 
-               for (v = ast_variable_browse(cfg, "post_mappings"); v; v = v->next) {
-                       struct ast_http_uri *urih;
-                       struct ast_str *ds;
-
-                       if (!(urih = ast_calloc(sizeof(*urih), 1))) {
-                               return -1;
-                       }
-
-                       if (!(ds = ast_str_create(32)))
-                               return -1;
-
+       for (v = ast_variable_browse(cfg, "post_mappings"); v; v = v->next) {
+               struct ast_http_uri *urih;
+               struct ast_str *ds;
 
-                       urih->description = ast_strdup("HTTP POST mapping");
-                       urih->uri = ast_strdup(v->name);
-                       ast_str_set(&ds, 0, "%s/%s", prefix, v->value);
-                       urih->data = ds;
-                       urih->has_subtree = 0;
-                       urih->supports_get = 0;
-                       urih->supports_post = 1;
-                       urih->callback = http_post_callback;
-                       urih->key = __FILE__;
+               if (!(urih = ast_calloc(sizeof(*urih), 1))) {
+                       ast_config_destroy(cfg);
+                       return -1;
+               }
 
-                       ast_http_uri_link(urih);
+               if (!(ds = ast_str_create(32))) {
+                       ast_free(urih);
+                       ast_config_destroy(cfg);
+                       return -1;
                }
 
-               ast_config_destroy(cfg);
+               urih->description = ast_strdup("HTTP POST mapping");
+               urih->uri = ast_strdup(v->name);
+               ast_str_set(&ds, 0, "%s", v->value);
+               urih->data = ds;
+               urih->has_subtree = 0;
+               urih->callback = http_post_callback;
+               urih->key = __FILE__;
+               urih->mallocd = urih->dmallocd = 1;
+
+               ast_http_uri_link(urih);
        }
+
+       ast_config_destroy(cfg);
        return 0;
 }
 
@@ -319,7 +490,6 @@ static int unload_module(void)
 
 static int reload(void)
 {
-
        __ast_http_post_load(1);
 
        return AST_MODULE_LOAD_SUCCESS;
@@ -327,7 +497,11 @@ static int reload(void)
 
 static int load_module(void)
 {
-       g_mime_init(0);
+       g_mime_init(
+#ifndef AST_GMIME_VER_30
+                       0
+#endif
+       );
 
        __ast_http_post_load(0);
 
@@ -335,6 +509,7 @@ static int load_module(void)
 }
 
 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "HTTP POST support",
+       .support_level = AST_MODULE_SUPPORT_CORE,
        .load = load_module,
        .unload = unload_module,
        .reload = reload,