Add patch to handle how IE7 issues POST requests using Window path spec including...
[asterisk/asterisk.git] / res / res_http_post.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2006, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*!
20  * \file 
21  * \brief HTTP POST upload support for Asterisk HTTP server
22  *
23  * \author Terry Wilson <twilson@digium.com
24  *
25  * \ref AstHTTP - AMI over the http protocol
26  */
27
28 /*** MODULEINFO
29         <depend>gmime</depend>
30  ***/
31
32
33 #include "asterisk.h"
34
35 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 111213 $")
36
37 #include <sys/stat.h>
38 #include <fcntl.h>
39 #include <gmime/gmime.h>
40
41 #include "asterisk/linkedlists.h"
42 #include "asterisk/http.h"
43 #include "asterisk/paths.h"     /* use ast_config_AST_DATA_DIR */
44 #include "asterisk/tcptls.h"
45 #include "asterisk/manager.h"
46 #include "asterisk/cli.h"
47 #include "asterisk/module.h"
48 #include "asterisk/ast_version.h"
49
50 #define MAX_PREFIX 80
51
52 /* just a little structure to hold callback info for gmime */
53 struct mime_cbinfo {
54         int count;
55         const char *post_dir;
56 };
57
58 /* all valid URIs must be prepended by the string in prefix. */
59 static char prefix[MAX_PREFIX];
60
61 static void post_raw(GMimePart *part, const char *post_dir, const char *fn)
62 {
63         char filename[PATH_MAX];
64         GMimeDataWrapper *content;
65         GMimeStream *stream;
66         int fd;
67
68         snprintf(filename, sizeof(filename), "%s/%s", post_dir, fn);
69
70         ast_debug(1, "Posting raw data to %s\n", filename);
71
72         if ((fd = open(filename, O_CREAT | O_WRONLY, 0666)) == -1) {
73                 ast_log(LOG_WARNING, "Unable to open %s for writing file from a POST!\n", filename);
74
75                 return;
76         }
77
78         stream = g_mime_stream_fs_new(fd);
79
80         content = g_mime_part_get_content_object(part);
81         g_mime_data_wrapper_write_to_stream(content, stream);
82         g_mime_stream_flush(stream);
83
84         g_object_unref(content);
85         g_object_unref(stream);
86 }
87
88 static GMimeMessage *parse_message(FILE *f)
89 {
90         GMimeMessage *message;
91         GMimeParser *parser;
92         GMimeStream *stream;
93
94         stream = g_mime_stream_file_new(f);
95
96         parser = g_mime_parser_new_with_stream(stream);
97         g_mime_parser_set_respect_content_length(parser, 1);
98         
99         g_object_unref(stream);
100
101         message = g_mime_parser_construct_message(parser);
102
103         g_object_unref(parser);
104
105         return message;
106 }
107
108 static void process_message_callback(GMimeObject *part, gpointer user_data)
109 {
110         struct mime_cbinfo *cbinfo = user_data;
111
112         cbinfo->count++;
113
114         /* We strip off the headers before we get here, so should only see GMIME_IS_PART */
115         if (GMIME_IS_MESSAGE_PART(part)) {
116                 ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MESSAGE_PART\n");
117                 return;
118         } else if (GMIME_IS_MESSAGE_PARTIAL(part)) {
119                 ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MESSAGE_PARTIAL\n");
120                 return;
121         } else if (GMIME_IS_MULTIPART(part)) {
122                 GList *l;
123                 
124                 ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MULTIPART, trying to process subparts\n");
125                 l = GMIME_MULTIPART(part)->subparts;
126                 while (l) {
127                         process_message_callback(l->data, cbinfo);
128                         l = l->next;
129                 }
130         } else if (GMIME_IS_PART(part)) {
131                 const char *filename;
132
133                 if (ast_strlen_zero(filename = g_mime_part_get_filename(GMIME_PART(part)))) {
134                         ast_debug(1, "Skipping part with no filename\n");
135                         return;
136                 }
137
138                 post_raw(GMIME_PART(part), cbinfo->post_dir, filename);
139         } else {
140                 ast_log(LOG_ERROR, "Encountered unknown MIME part. This should never happen!\n");
141         }
142 }
143
144 static int process_message(GMimeMessage *message, const char *post_dir)
145 {
146         struct mime_cbinfo cbinfo = {
147                 .count = 0,
148                 .post_dir = post_dir,
149         };
150
151         g_mime_message_foreach_part(message, process_message_callback, &cbinfo);
152
153         return cbinfo.count;
154 }
155
156
157 /* Find a sequence of bytes within a binary array. */
158 static int find_sequence(char * inbuf, int inlen, char * matchbuf, int matchlen)
159 {
160         int current;
161         int comp;
162         int found = 0;
163
164         for (current = 0; current < inlen-matchlen; current++, inbuf++) {
165                 if (*inbuf == *matchbuf) {
166                         found=1;
167                         for (comp = 1; comp < matchlen; comp++) {
168                                 if (inbuf[comp] != matchbuf[comp]) {
169                                         found = 0;
170                                         break;
171                                 }
172                         }
173                         if (found) {
174                                 break;
175                         }
176                 }
177         }
178         if (found) {
179                 return current;
180         } else {
181                 return -1;
182         }
183 }
184
185 /*
186 * The following is a work around to deal with how IE7 embeds the local file name
187 * within the Mime header using full WINDOWS file path with backslash directory delimiters.
188 * This section of code attempts to isolate the directory path and remove it
189 * from what is written into the output file.  In addition, it changes
190 * esc chars (i.e. backslashes) to forward slashes.
191 * This function has two modes.  The first to find a boundary marker.  The
192 * second is to find the filename immediately after the boundary.
193 */
194 static int readmimefile(FILE * fin, FILE * fout, char * boundary, int contentlen)
195 {
196         int find_filename = 0;
197         char buf[4096];
198         int marker;
199         int x;
200         int char_in_buf = 0;
201         int num_to_read;
202         int boundary_len;
203         char * path_end, * path_start, * filespec;
204
205         if (NULL == fin || NULL == fout || NULL == boundary || 0 >= contentlen) {
206                 return -1;
207         }
208
209         boundary_len = strlen(boundary);
210         while (0 < contentlen || 0 < char_in_buf) {
211                 /* determine how much I will read into the buffer */
212                 if (contentlen > sizeof(buf) - char_in_buf) {
213                         num_to_read = sizeof(buf)- char_in_buf;
214                 } else {
215                         num_to_read = contentlen;
216                 }
217
218                 if(0 < num_to_read) {
219                         fread(&(buf[char_in_buf]), 1, num_to_read, fin);
220                         contentlen -= num_to_read;
221                         char_in_buf += num_to_read;
222                 }
223                 /* If I am looking for the filename spec */
224                 if (find_filename) {
225                         path_end = filespec = NULL;
226                         x = strlen("filename=\"");
227                         marker = find_sequence(buf, char_in_buf, "filename=\"", x );
228                         if (0 <= marker) {
229                                 marker += x;  /* Index beyond the filename marker */
230                                 path_start = &buf[marker];
231                                 for (path_end = path_start, x = 0; x < char_in_buf-marker; x++, path_end++) {
232                                         if ('\\' == *path_end) {        /* convert backslashses to forward slashes */
233                                                 *path_end = '/';
234                                         }
235                                         if ('\"' == *path_end) {        /* If at the end of the file name spec */
236                                                 *path_end = '\0';               /* temporarily null terminate the file spec for basename */
237                                                 filespec = basename(path_start);
238                                                 *path_end = '\"';
239                                                 break;
240                                         }
241                                 }
242                         }
243                         if (filespec) { /* If the file name path was found in the header */
244                                 fwrite(buf, 1, marker, fout);
245                                 x = (int)(path_end+1 - filespec);
246                                 fwrite(filespec, 1, x, fout);
247                                 x = (int)(path_end+1 - buf);
248                                 memmove(buf, &(buf[x]), char_in_buf-x);
249                                 char_in_buf -= x;
250                         }
251                         find_filename = 0;
252                 } else { /* I am looking for the boundary marker */
253                         marker = find_sequence(buf, char_in_buf, boundary, boundary_len);
254                         if (0 > marker) {
255                                 if (char_in_buf < (boundary_len)) {
256                                         /*no possibility to find the boundary, write all you have */
257                                         fwrite(buf, 1, char_in_buf, fout);
258                                         char_in_buf = 0;
259                                 } else {
260                                         /* write all except for area where the boundary marker could be */
261                                         fwrite(buf, 1, char_in_buf -(boundary_len -1), fout);
262                                         x = char_in_buf -(boundary_len -1);
263                                         memmove(buf, &(buf[x]), char_in_buf-x);
264                                         char_in_buf = (boundary_len -1);
265                                 }
266                         } else {
267                                 /* write up through the boundary, then look for filename in the rest */
268                                 fwrite(buf, 1, marker + boundary_len, fout);
269                                 x = marker + boundary_len;
270                                 memmove(buf, &(buf[x]), char_in_buf-x);
271                                 char_in_buf -= marker + boundary_len;
272                                 find_filename =1;
273                         }
274                 }
275         }
276         return 0;
277 }
278
279
280 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)
281 {
282         struct ast_variable *var;
283         unsigned long ident = 0;
284         FILE *f;
285         int content_len = 0;
286         struct ast_str *post_dir;
287         GMimeMessage *message;
288         int message_count = 0;
289         char * boundary_marker = NULL;
290
291         if (!urih) {
292                 return ast_http_error((*status = 400),
293                            (*title = ast_strdup("Missing URI handle")),
294                            NULL, "There was an error parsing the request");
295         }
296
297         for (var = vars; var; var = var->next) {
298                 if (strcasecmp(var->name, "mansession_id")) {
299                         continue;
300                 }
301
302                 if (sscanf(var->value, "%lx", &ident) != 1) {
303                         return ast_http_error((*status = 400),
304                                               (*title = ast_strdup("Bad Request")),
305                                               NULL, "The was an error parsing the request.");
306                 }
307
308                 if (!astman_verify_session_writepermissions(ident, EVENT_FLAG_CONFIG)) {
309                         return ast_http_error((*status = 401),
310                                               (*title = ast_strdup("Unauthorized")),
311                                               NULL, "You are not authorized to make this request.");
312                 }
313
314                 break;
315         }
316
317         if (!var) {
318                 return ast_http_error((*status = 401),
319                                       (*title = ast_strdup("Unauthorized")),
320                                       NULL, "You are not authorized to make this request.");
321         }
322
323         if (!(f = tmpfile())) {
324                 ast_log(LOG_ERROR, "Could not create temp file.\n");
325                 return NULL;
326         }
327
328         for (var = headers; var; var = var->next) {
329                 fprintf(f, "%s: %s\r\n", var->name, var->value);
330
331                 if (!strcasecmp(var->name, "Content-Length")) {
332                         if ((sscanf(var->value, "%u", &content_len)) != 1) {
333                                 ast_log(LOG_ERROR, "Invalid Content-Length in POST request!\n");
334                                 fclose(f);
335
336                                 return NULL;
337                         }
338                         ast_debug(1, "Got a Content-Length of %d\n", content_len);
339                 } else if (!strcasecmp(var->name, "Content-Type")) {
340                         boundary_marker = strstr(var->value, "boundary=");
341                         if (boundary_marker) {
342                                 boundary_marker += strlen("boundary=");
343                         }
344                 }
345         }
346
347         fprintf(f, "\r\n");
348
349         if (0 > readmimefile(ser->f, f, boundary_marker, content_len)) {
350                 if (option_debug) {
351                         ast_log(LOG_DEBUG, "Cannot find boundary marker in POST request.\n");
352                 }
353                 fclose(f);
354                 
355                 return NULL;
356         }
357
358         if (fseek(f, SEEK_SET, 0)) {
359                 ast_log(LOG_ERROR, "Failed to seek temp file back to beginning.\n");
360                 fclose(f);
361
362                 return NULL;
363         }
364
365         post_dir = urih->data;
366
367         message = parse_message(f); /* Takes ownership and will close f */
368
369         if (!message) {
370                 ast_log(LOG_ERROR, "Error parsing MIME data\n");
371
372                 return ast_http_error((*status = 400),
373                                       (*title = ast_strdup("Bad Request")),
374                                       NULL, "The was an error parsing the request.");
375         }
376
377         if (!(message_count = process_message(message, post_dir->str))) {
378                 ast_log(LOG_ERROR, "Invalid MIME data, found no parts!\n");
379                 g_object_unref(message);
380                 return ast_http_error((*status = 400),
381                                       (*title = ast_strdup("Bad Request")),
382                                       NULL, "The was an error parsing the request.");
383         }
384
385         g_object_unref(message);
386
387         return ast_http_error((*status = 200),
388                               (*title = ast_strdup("OK")),
389                               NULL, "File successfully uploaded.");
390 }
391
392 static int __ast_http_post_load(int reload)
393 {
394         struct ast_config *cfg;
395         struct ast_variable *v;
396         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
397
398         cfg = ast_config_load2("http.conf", "http", config_flags);
399         if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
400                 return 0;
401         }
402
403         if (reload) {
404                 ast_http_uri_unlink_all_with_key(__FILE__);
405         }
406
407         if (cfg) {
408                 for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
409                         if (!strcasecmp(v->name, "prefix")) {
410                                 ast_copy_string(prefix, v->value, sizeof(prefix));
411                                 if (prefix[strlen(prefix)] == '/') {
412                                         prefix[strlen(prefix)] = '\0';
413                                 }
414                         }
415                 }
416
417                 for (v = ast_variable_browse(cfg, "post_mappings"); v; v = v->next) {
418                         struct ast_http_uri *urih;
419                         struct ast_str *ds;
420
421                         if (!(urih = ast_calloc(sizeof(*urih), 1))) {
422                                 ast_config_destroy(cfg);
423                                 return -1;
424                         }
425
426                         if (!(ds = ast_str_create(32))) {
427                                 ast_free(urih);
428                                 ast_config_destroy(cfg);
429                                 return -1;
430                         }
431
432                         urih->description = ast_strdup("HTTP POST mapping");
433                         urih->uri = ast_strdup(v->name);
434                         ast_str_set(&ds, 0, "%s/%s", prefix, v->value);
435                         urih->data = ds;
436                         urih->has_subtree = 0;
437                         urih->supports_get = 0;
438                         urih->supports_post = 1;
439                         urih->callback = http_post_callback;
440                         urih->key = __FILE__;
441                         urih->mallocd = urih->dmallocd = 1;
442
443                         ast_http_uri_link(urih);
444                 }
445
446                 ast_config_destroy(cfg);
447         }
448         return 0;
449 }
450
451 static int unload_module(void)
452 {
453         ast_http_uri_unlink_all_with_key(__FILE__);
454
455         return 0;
456 }
457
458 static int reload(void)
459 {
460         __ast_http_post_load(1);
461
462         return AST_MODULE_LOAD_SUCCESS;
463 }
464
465 static int load_module(void)
466 {
467         g_mime_init(0);
468
469         __ast_http_post_load(0);
470
471         return AST_MODULE_LOAD_SUCCESS;
472 }
473
474 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "HTTP POST support",
475         .load = load_module,
476         .unload = unload_module,
477         .reload = reload,
478 );