2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2006, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
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.
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.
21 * \brief HTTP POST upload support for Asterisk HTTP server
23 * \author Terry Wilson <twilson@digium.com
25 * \ref AstHTTP - AMI over the http protocol
29 <depend>gmime</depend>
35 ASTERISK_FILE_VERSION(__FILE__
, "$Revision: 111213 $")
39 #include <gmime/gmime.h>
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"
52 /* just a little structure to hold callback info for gmime */
58 /* all valid URIs must be prepended by the string in prefix. */
59 static char prefix
[MAX_PREFIX
];
61 static void post_raw(GMimePart
*part
, const char *post_dir
, const char *fn
)
63 char filename
[PATH_MAX
];
64 GMimeDataWrapper
*content
;
68 snprintf(filename
, sizeof(filename
), "%s/%s", post_dir
, fn
);
70 ast_debug(1, "Posting raw data to %s\n", filename
);
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
);
78 stream
= g_mime_stream_fs_new(fd
);
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
);
84 g_object_unref(content
);
85 g_object_unref(stream
);
88 static GMimeMessage
*parse_message(FILE *f
)
90 GMimeMessage
*message
;
94 stream
= g_mime_stream_file_new(f
);
96 parser
= g_mime_parser_new_with_stream(stream
);
97 g_mime_parser_set_respect_content_length(parser
, 1);
99 g_object_unref(stream
);
101 message
= g_mime_parser_construct_message(parser
);
103 g_object_unref(parser
);
108 static void process_message_callback(GMimeObject
*part
, gpointer user_data
)
110 struct mime_cbinfo
*cbinfo
= user_data
;
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");
118 } else if (GMIME_IS_MESSAGE_PARTIAL(part
)) {
119 ast_log(LOG_WARNING
, "Got unexpected GMIME_IS_MESSAGE_PARTIAL\n");
121 } else if (GMIME_IS_MULTIPART(part
)) {
124 ast_log(LOG_WARNING
, "Got unexpected GMIME_IS_MULTIPART, trying to process subparts\n");
125 l
= GMIME_MULTIPART(part
)->subparts
;
127 process_message_callback(l
->data
, cbinfo
);
130 } else if (GMIME_IS_PART(part
)) {
131 const char *filename
;
133 if (ast_strlen_zero(filename
= g_mime_part_get_filename(GMIME_PART(part
)))) {
134 ast_debug(1, "Skipping part with no filename\n");
138 post_raw(GMIME_PART(part
), cbinfo
->post_dir
, filename
);
140 ast_log(LOG_ERROR
, "Encountered unknown MIME part. This should never happen!\n");
144 static int process_message(GMimeMessage
*message
, const char *post_dir
)
146 struct mime_cbinfo cbinfo
= {
148 .post_dir
= post_dir
,
151 g_mime_message_foreach_part(message
, process_message_callback
, &cbinfo
);
156 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
)
158 struct ast_variable
*var
;
159 unsigned long ident
= 0;
164 struct ast_str
*post_dir
;
165 GMimeMessage
*message
;
166 int message_count
= 0;
169 return ast_http_error((*status
= 400),
170 (*title
= ast_strdup("Missing URI handle")),
171 NULL
, "There was an error parsing the request");
174 for (var
= vars
; var
; var
= var
->next
) {
175 if (strcasecmp(var
->name
, "mansession_id")) {
179 if (sscanf(var
->value
, "%lx", &ident
) != 1) {
180 return ast_http_error((*status
= 400),
181 (*title
= ast_strdup("Bad Request")),
182 NULL
, "The was an error parsing the request.");
185 if (!astman_verify_session_writepermissions(ident
, EVENT_FLAG_CONFIG
)) {
186 return ast_http_error((*status
= 401),
187 (*title
= ast_strdup("Unauthorized")),
188 NULL
, "You are not authorized to make this request.");
195 return ast_http_error((*status
= 401),
196 (*title
= ast_strdup("Unauthorized")),
197 NULL
, "You are not authorized to make this request.");
200 if (!(f
= tmpfile())) {
201 ast_log(LOG_ERROR
, "Could not create temp file.\n");
205 for (var
= headers
; var
; var
= var
->next
) {
206 if (!strcasecmp(var
->name
, "Content-Length")) {
207 if ((sscanf(var
->value
, "%u", &content_len
)) != 1) {
208 ast_log(LOG_ERROR
, "Invalid Content-Length in POST request!\n");
213 ast_debug(1, "Got a Content-Length of %d\n", content_len
);
214 } else if (!strcasecmp(var
->name
, "Content-Type")) {
215 fprintf(f
, "Content-Type: %s\r\n\r\n", var
->value
);
219 for (res
= sizeof(buf
); content_len
; content_len
-= res
) {
220 if (content_len
< res
) {
223 fread(buf
, 1, res
, ser
->f
);
224 fwrite(buf
, 1, res
, f
);
227 if (fseek(f
, SEEK_SET
, 0)) {
228 ast_log(LOG_ERROR
, "Failed to seek temp file back to beginning.\n");
234 post_dir
= urih
->data
;
236 message
= parse_message(f
); /* Takes ownership and will close f */
239 ast_log(LOG_ERROR
, "Error parsing MIME data\n");
241 return ast_http_error((*status
= 400),
242 (*title
= ast_strdup("Bad Request")),
243 NULL
, "The was an error parsing the request.");
246 if (!(message_count
= process_message(message
, post_dir
->str
))) {
247 ast_log(LOG_ERROR
, "Invalid MIME data, found no parts!\n");
248 g_object_unref(message
);
249 return ast_http_error((*status
= 400),
250 (*title
= ast_strdup("Bad Request")),
251 NULL
, "The was an error parsing the request.");
254 g_object_unref(message
);
256 return ast_http_error((*status
= 200),
257 (*title
= ast_strdup("OK")),
258 NULL
, "File successfully uploaded.");
261 static int __ast_http_post_load(int reload
)
263 struct ast_config
*cfg
;
264 struct ast_variable
*v
;
265 struct ast_flags config_flags
= { reload
? CONFIG_FLAG_FILEUNCHANGED
: 0 };
267 if ((cfg
= ast_config_load2("http.conf", "http", config_flags
)) == CONFIG_STATUS_FILEUNCHANGED
) {
272 ast_http_uri_unlink_all_with_key(__FILE__
);
276 for (v
= ast_variable_browse(cfg
, "general"); v
; v
= v
->next
) {
277 if (!strcasecmp(v
->name
, "prefix")) {
278 ast_copy_string(prefix
, v
->value
, sizeof(prefix
));
279 if (prefix
[strlen(prefix
)] == '/') {
280 prefix
[strlen(prefix
)] = '\0';
285 for (v
= ast_variable_browse(cfg
, "post_mappings"); v
; v
= v
->next
) {
286 struct ast_http_uri
*urih
;
289 if (!(urih
= ast_calloc(sizeof(*urih
), 1))) {
293 if (!(ds
= ast_str_create(32)))
297 urih
->description
= ast_strdup("HTTP POST mapping");
298 urih
->uri
= ast_strdup(v
->name
);
299 ast_str_set(&ds
, 0, "%s/%s", prefix
, v
->value
);
301 urih
->has_subtree
= 0;
302 urih
->supports_get
= 0;
303 urih
->supports_post
= 1;
304 urih
->callback
= http_post_callback
;
305 urih
->key
= __FILE__
;
307 ast_http_uri_link(urih
);
310 ast_config_destroy(cfg
);
315 static int unload_module(void)
317 ast_http_uri_unlink_all_with_key(__FILE__
);
322 static int reload(void)
325 __ast_http_post_load(1);
327 return AST_MODULE_LOAD_SUCCESS
;
330 static int load_module(void)
334 __ast_http_post_load(0);
336 return AST_MODULE_LOAD_SUCCESS
;
339 AST_MODULE_INFO(ASTERISK_GPL_KEY
, AST_MODFLAG_DEFAULT
, "HTTP POST support",
341 .unload
= unload_module
,