mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-11-04 05:15:22 +00:00 
			
		
		
		
	Replace minimime with superior GMime library so that the entire contents of an http post are not read into memory.
This does introduce a dependency on the GMime library for handling HTTP POSTs, but it is available in most distros. If the library is present, then the compile flag for ENABLE_UPLOADS is enabled by default in menuselect. git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@109229 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
		
							
								
								
									
										213
									
								
								main/http.c
									
									
									
									
									
								
							
							
						
						
									
										213
									
								
								main/http.c
									
									
									
									
									
								
							@@ -40,7 +40,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 | 
			
		||||
#include <sys/signal.h>
 | 
			
		||||
#include <fcntl.h>
 | 
			
		||||
 | 
			
		||||
#include "minimime/mm.h"
 | 
			
		||||
#ifdef ENABLE_UPLOADS
 | 
			
		||||
#include <gmime/gmime.h>
 | 
			
		||||
#endif /* ENABLE_UPLOADS */
 | 
			
		||||
 | 
			
		||||
#include "asterisk/cli.h"
 | 
			
		||||
#include "asterisk/tcptls.h"
 | 
			
		||||
@@ -88,6 +90,7 @@ static struct server_args https_desc = {
 | 
			
		||||
 | 
			
		||||
static AST_RWLIST_HEAD_STATIC(uris, ast_http_uri);	/*!< list of supported handlers */
 | 
			
		||||
 | 
			
		||||
#ifdef ENABLE_UPLOADS
 | 
			
		||||
struct ast_http_post_mapping {
 | 
			
		||||
	AST_RWLIST_ENTRY(ast_http_post_mapping) entry;
 | 
			
		||||
	char *from;
 | 
			
		||||
@@ -96,6 +99,12 @@ struct ast_http_post_mapping {
 | 
			
		||||
 | 
			
		||||
static AST_RWLIST_HEAD_STATIC(post_mappings, ast_http_post_mapping);
 | 
			
		||||
 | 
			
		||||
struct mime_cbinfo {
 | 
			
		||||
	int count;
 | 
			
		||||
	const char *post_dir;
 | 
			
		||||
};
 | 
			
		||||
#endif /* ENABLE_UPLOADS */
 | 
			
		||||
 | 
			
		||||
/* all valid URIs must be prepended by the string in prefix. */
 | 
			
		||||
static char prefix[MAX_PREFIX];
 | 
			
		||||
static int enablestatic;
 | 
			
		||||
@@ -325,6 +334,7 @@ void ast_http_uri_unlink(struct ast_http_uri *urih)
 | 
			
		||||
	AST_RWLIST_UNLOCK(&uris);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef ENABLE_UPLOADS
 | 
			
		||||
/*! \note This assumes that the post_mappings list is locked */
 | 
			
		||||
static struct ast_http_post_mapping *find_post_mapping(const char *uri)
 | 
			
		||||
{
 | 
			
		||||
@@ -347,64 +357,115 @@ static struct ast_http_post_mapping *find_post_mapping(const char *uri)
 | 
			
		||||
	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)
 | 
			
		||||
static void post_raw(GMimePart *part, const char *post_dir, const char *fn)
 | 
			
		||||
{
 | 
			
		||||
	char filename[PATH_MAX];
 | 
			
		||||
	FILE *f;
 | 
			
		||||
	const char *body;
 | 
			
		||||
	size_t body_len;
 | 
			
		||||
	GMimeDataWrapper *content;
 | 
			
		||||
	GMimeStream *stream;
 | 
			
		||||
	int fd;
 | 
			
		||||
 | 
			
		||||
	snprintf(filename, sizeof(filename), "%s/%s", post_dir, fn);
 | 
			
		||||
 | 
			
		||||
	ast_debug(1, "Posting raw data to %s\n", filename);
 | 
			
		||||
 | 
			
		||||
	if (!(f = fopen(filename, "w"))) {
 | 
			
		||||
	if ((fd = open(filename, O_CREAT | O_WRONLY, 0666)) == -1) {
 | 
			
		||||
		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);
 | 
			
		||||
	stream = g_mime_stream_fs_new(fd);
 | 
			
		||||
 | 
			
		||||
	content = g_mime_part_get_content_object(part);
 | 
			
		||||
	g_mime_data_wrapper_write_to_stream(content, stream);
 | 
			
		||||
	g_mime_stream_flush(stream);
 | 
			
		||||
 | 
			
		||||
	g_object_unref(content);
 | 
			
		||||
	g_object_unref(stream);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static GMimeMessage *parse_message(FILE *f)
 | 
			
		||||
{
 | 
			
		||||
	GMimeMessage *message;
 | 
			
		||||
	GMimeParser *parser;
 | 
			
		||||
	GMimeStream *stream;
 | 
			
		||||
 | 
			
		||||
	stream = g_mime_stream_file_new(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);
 | 
			
		||||
 | 
			
		||||
	g_object_unref(parser);
 | 
			
		||||
 | 
			
		||||
	return message;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void process_message_callback(GMimeObject *part, gpointer user_data)
 | 
			
		||||
{
 | 
			
		||||
	struct mime_cbinfo *cbinfo = user_data;
 | 
			
		||||
 | 
			
		||||
	cbinfo->count++;
 | 
			
		||||
 | 
			
		||||
	/* We strip off the headers before we get here, so should only see GMIME_IS_PART */
 | 
			
		||||
	if (GMIME_IS_MESSAGE_PART(part)) {
 | 
			
		||||
		ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MESSAGE_PART\n");
 | 
			
		||||
		return;
 | 
			
		||||
	} else if (GMIME_IS_MESSAGE_PARTIAL(part)) {
 | 
			
		||||
		ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MESSAGE_PARTIAL\n");
 | 
			
		||||
		return;
 | 
			
		||||
	} else if (GMIME_IS_MULTIPART(part)) {
 | 
			
		||||
		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 if (GMIME_IS_PART(part)) {
 | 
			
		||||
		const char *filename;
 | 
			
		||||
 | 
			
		||||
		ast_debug(3, "Got mime part\n");
 | 
			
		||||
		if (ast_strlen_zero(filename = g_mime_part_get_filename(GMIME_PART(part)))) {
 | 
			
		||||
			ast_debug(1, "Skipping part with no filename\n");
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		post_raw(GMIME_PART(part), cbinfo->post_dir, filename);
 | 
			
		||||
	} else {
 | 
			
		||||
		ast_log(LOG_ERROR, "Encountered unknown MIME part. This should never happen!\n");
 | 
			
		||||
	}
 | 
			
		||||
	body_len = mm_mimepart_getlength(part);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	ast_debug(1, "Body length is %ld\n", (long int)body_len);
 | 
			
		||||
static int process_message(GMimeMessage *message, const char *post_dir)
 | 
			
		||||
{
 | 
			
		||||
	struct mime_cbinfo cbinfo = {
 | 
			
		||||
		.count = 0,
 | 
			
		||||
		.post_dir = post_dir,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	fwrite(body, 1, body_len, f);
 | 
			
		||||
	g_mime_message_foreach_part(message, process_message_callback, &cbinfo);
 | 
			
		||||
 | 
			
		||||
	fclose(f);
 | 
			
		||||
	return cbinfo.count;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct ast_str *handle_post(struct ast_tcptls_session_instance *ser, char *uri, 
 | 
			
		||||
	int *status, char **title, int *contentlength, struct ast_variable *headers,
 | 
			
		||||
	struct ast_variable *cookies)
 | 
			
		||||
{
 | 
			
		||||
	char buf;
 | 
			
		||||
	char buf[4096];
 | 
			
		||||
	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;
 | 
			
		||||
	GMimeMessage *message;
 | 
			
		||||
	int message_count = 0;
 | 
			
		||||
 | 
			
		||||
	for (var = cookies; var; var = var->next) {
 | 
			
		||||
		if (strcasecmp(var->name, "mansession_id"))
 | 
			
		||||
@@ -445,11 +506,11 @@ static struct ast_str *handle_post(struct ast_tcptls_session_instance *ser, char
 | 
			
		||||
			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;
 | 
			
		||||
	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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (fseek(f, SEEK_SET, 0)) {
 | 
			
		||||
@@ -462,7 +523,6 @@ static struct ast_str *handle_post(struct ast_tcptls_session_instance *ser, char
 | 
			
		||||
	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.");
 | 
			
		||||
@@ -473,66 +533,27 @@ static struct ast_str *handle_post(struct ast_tcptls_session_instance *ser, char
 | 
			
		||||
 | 
			
		||||
	ast_debug(1, "Going to post files to dir %s\n", post_dir);
 | 
			
		||||
 | 
			
		||||
	if (!(ctx = mm_context_new())) {
 | 
			
		||||
		fclose(f);
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
	message = parse_message(f); /* Takes ownership and will close f */
 | 
			
		||||
 | 
			
		||||
	mm_res = mm_parse_fileptr(ctx, f, MM_PARSE_LOOSE, 0);
 | 
			
		||||
	fclose(f);
 | 
			
		||||
	if (mm_res == -1) {
 | 
			
		||||
	if (!message) {
 | 
			
		||||
		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) {
 | 
			
		||||
	if (!(message_count = process_message(message, post_dir))) {
 | 
			
		||||
		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.");
 | 
			
		||||
}
 | 
			
		||||
#endif /* ENABLE_UPLOADS */
 | 
			
		||||
 | 
			
		||||
static struct ast_str *handle_uri(struct ast_tcptls_session_instance *ser, char *uri, int *status, 
 | 
			
		||||
	char **title, int *contentlength, struct ast_variable **cookies, 
 | 
			
		||||
@@ -773,15 +794,21 @@ static void *httpd_helper_thread(void *data)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!*uri)
 | 
			
		||||
	if (!*uri) {
 | 
			
		||||
		out = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
 | 
			
		||||
	else if (!strcasecmp(buf, "post")) 
 | 
			
		||||
	} else if (!strcasecmp(buf, "post")) {
 | 
			
		||||
#ifdef ENABLE_UPLOADS
 | 
			
		||||
		out = handle_post(ser, uri, &status, &title, &contentlength, headers, vars);
 | 
			
		||||
	else if (strcasecmp(buf, "get")) 
 | 
			
		||||
#else
 | 
			
		||||
		out = ast_http_error(501, "Not Implemented", NULL,
 | 
			
		||||
			"Attempt to use unimplemented / unsupported method");
 | 
			
		||||
	else	/* try to serve it */
 | 
			
		||||
#endif /* ENABLE_UPLOADS */
 | 
			
		||||
	} 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, uri, &status, &title, &contentlength, &vars, &static_content);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* If they aren't mopped up already, clean up the cookies */
 | 
			
		||||
	if (vars)
 | 
			
		||||
@@ -887,6 +914,7 @@ static void add_redirect(const char *value)
 | 
			
		||||
	AST_RWLIST_UNLOCK(&uri_redirects);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef ENABLE_UPLOADS
 | 
			
		||||
static void destroy_post_mapping(struct ast_http_post_mapping *post_map)
 | 
			
		||||
{
 | 
			
		||||
	if (post_map->from)
 | 
			
		||||
@@ -927,6 +955,7 @@ static void add_post_mapping(const char *from, const char *to)
 | 
			
		||||
	AST_RWLIST_INSERT_TAIL(&post_mappings, post_map, entry);
 | 
			
		||||
	AST_RWLIST_UNLOCK(&post_mappings);
 | 
			
		||||
}
 | 
			
		||||
#endif /* ENABLE_UPLOADS */
 | 
			
		||||
 | 
			
		||||
static int __ast_http_load(int reload)
 | 
			
		||||
{
 | 
			
		||||
@@ -964,7 +993,9 @@ static int __ast_http_load(int reload)
 | 
			
		||||
		ast_free(redirect);
 | 
			
		||||
	AST_RWLIST_UNLOCK(&uri_redirects);
 | 
			
		||||
 | 
			
		||||
#ifdef ENABLE_UPLOADS
 | 
			
		||||
	destroy_post_mappings();
 | 
			
		||||
#endif /* ENABLE_UPLOADS */
 | 
			
		||||
 | 
			
		||||
	if (cfg) {
 | 
			
		||||
		v = ast_variable_browse(cfg, "general");
 | 
			
		||||
@@ -1013,8 +1044,10 @@ static int __ast_http_load(int reload)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
#ifdef ENABLE_UPLOADS
 | 
			
		||||
		for (v = ast_variable_browse(cfg, "post_mappings"); v; v = v->next)
 | 
			
		||||
			add_post_mapping(v->name, v->value);
 | 
			
		||||
#endif /* ENABLE_UPLOADS */
 | 
			
		||||
 | 
			
		||||
		ast_config_destroy(cfg);
 | 
			
		||||
	}
 | 
			
		||||
@@ -1036,7 +1069,11 @@ static char *handle_show_http(struct ast_cli_entry *e, int cmd, struct ast_cli_a
 | 
			
		||||
{
 | 
			
		||||
	struct ast_http_uri *urih;
 | 
			
		||||
	struct http_uri_redirect *redirect;
 | 
			
		||||
 | 
			
		||||
#ifdef ENABLE_UPLOADS
 | 
			
		||||
	struct ast_http_post_mapping *post_map;
 | 
			
		||||
#endif /* ENABLE_UPLOADS */
 | 
			
		||||
 | 
			
		||||
	switch (cmd) {
 | 
			
		||||
	case CLI_INIT:
 | 
			
		||||
		e->command = "http show status";
 | 
			
		||||
@@ -1083,12 +1120,15 @@ static char *handle_show_http(struct ast_cli_entry *e, int cmd, struct ast_cli_a
 | 
			
		||||
	AST_RWLIST_UNLOCK(&uri_redirects);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#ifdef ENABLE_UPLOADS
 | 
			
		||||
	ast_cli(a->fd, "\nPOST mappings:\n");
 | 
			
		||||
	AST_RWLIST_RDLOCK(&post_mappings);
 | 
			
		||||
	AST_LIST_TRAVERSE(&post_mappings, post_map, entry)
 | 
			
		||||
	AST_LIST_TRAVERSE(&post_mappings, post_map, entry) {
 | 
			
		||||
		ast_cli(a->fd, "%s/%s => %s\n", prefix, post_map->from, post_map->to);
 | 
			
		||||
	}
 | 
			
		||||
	ast_cli(a->fd, "%s\n", AST_LIST_EMPTY(&post_mappings) ? "None.\n" : "");
 | 
			
		||||
	AST_RWLIST_UNLOCK(&post_mappings);
 | 
			
		||||
#endif /* ENABLE_UPLOADS */
 | 
			
		||||
 | 
			
		||||
	return CLI_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
@@ -1104,8 +1144,9 @@ static struct ast_cli_entry cli_http[] = {
 | 
			
		||||
 | 
			
		||||
int ast_http_init(void)
 | 
			
		||||
{
 | 
			
		||||
	mm_library_init();
 | 
			
		||||
	mm_codec_registerdefaultcodecs();
 | 
			
		||||
#ifdef ENABLE_UPLOADS
 | 
			
		||||
	g_mime_init(0);
 | 
			
		||||
#endif /* ENABLE_UPLOADS */
 | 
			
		||||
 | 
			
		||||
	ast_http_uri_link(&statusuri);
 | 
			
		||||
	ast_http_uri_link(&staticuri);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user