From b37e27f5dab5acfb5d8f932b2af9396bd6fecc15 Mon Sep 17 00:00:00 2001 From: Andrew Bettison Date: Sat, 21 Dec 2013 23:22:47 +1030 Subject: [PATCH] Improve HTTP server multipart body parsing Every handler function can return an HTTP response status code to terminate request parsing and start the response. --- http_server.c | 91 +++++++++++++++++++++---------------------- http_server.h | 16 +++++--- rhizome_direct_http.c | 22 ++++++----- 3 files changed, 67 insertions(+), 62 deletions(-) diff --git a/http_server.c b/http_server.c index e275de4c..5fe5a45f 100644 --- a/http_server.c +++ b/http_server.c @@ -1134,7 +1134,33 @@ malformed: return 1; } -static void http_request_form_data_start_part(struct http_request *r, int b) +#define _HANDLER_RESULT(result) do { \ + if (r->phase != RECEIVE) \ + return 1; \ + if (result) { \ + assert((result) >= 400); \ + assert((result) < 600); \ + return (result); \ + } \ + } while (0) +#define _INVOKE_HANDLER_VOID(FUNC) do { \ + if (r->form_data.FUNC) { \ + if (r->debug_flag && *r->debug_flag) \ + DEBUGF(#FUNC "()"); \ + int result = r->form_data.FUNC(r); \ + _HANDLER_RESULT(result); \ + } \ + } while (0) +#define _INVOKE_HANDLER_BUF_LEN(FUNC, START, END) do { \ + if (r->form_data.FUNC && (START) != (END)) { \ + if (r->debug_flag && *r->debug_flag) \ + DEBUGF(#FUNC "(%s length=%zu)", alloca_toprint(50, (START), (END) - (START)), (END) - (START)); \ + int result = r->form_data.FUNC(r, (START), (END) - (START)); \ + _HANDLER_RESULT(result); \ + } \ + } while (0) + +static int http_request_form_data_start_part(struct http_request *r, int b) { switch (r->form_data_state) { case BODY: @@ -1148,11 +1174,7 @@ static void http_request_form_data_start_part(struct http_request *r, int b) } // fall through... case HEADER: - if (r->form_data.handle_mime_part_end) { - if (r->debug_flag && *r->debug_flag) - DEBUGF("handle_mime_part_end()"); - r->form_data.handle_mime_part_end(r); - } + _INVOKE_HANDLER_VOID(handle_mime_part_end); break; default: break; @@ -1162,13 +1184,10 @@ static void http_request_form_data_start_part(struct http_request *r, int b) bzero(&r->part_header, sizeof r->part_header); r->part_body_length = 0; r->part_header.content_length = CONTENT_LENGTH_UNKNOWN; - if (r->form_data.handle_mime_part_start) { - if (r->debug_flag && *r->debug_flag) - DEBUGF("handle_mime_part_start()"); - r->form_data.handle_mime_part_start(r); - } + _INVOKE_HANDLER_VOID(handle_mime_part_start); } else r->form_data_state = EPILOGUE; + return 0; } /* If parsing completes (ie, parsed to end of epilogue), then sets r->parser to NULL and returns 0, @@ -1204,16 +1223,10 @@ static int http_request_parse_body_form_data(struct http_request *r) int b; if ((b = _skip_mime_boundary(r))) { assert(end_preamble >= r->parsed); - if (r->form_data.handle_mime_preamble && end_preamble != r->parsed) { - if (r->debug_flag && *r->debug_flag) - DEBUGF("handle_mime_preamble(%s length=%zu)", - alloca_toprint(50, r->parsed, end_preamble - r->parsed), end_preamble - r->parsed); - r->form_data.handle_mime_preamble(r, r->parsed, end_preamble - r->parsed); - } + _INVOKE_HANDLER_BUF_LEN(handle_mime_preamble, r->parsed, end_preamble); _rewind_crlf(r); _commit(r); - http_request_form_data_start_part(r, b); - return 0; + return http_request_form_data_start_part(r, b); } } if (_end_of_content(r)) { @@ -1223,12 +1236,8 @@ static int http_request_parse_body_form_data(struct http_request *r) } _rewind_optional_cr(r); _commit(r); - if (r->parsed > start && r->form_data.handle_mime_preamble) { - if (r->debug_flag && *r->debug_flag) - DEBUGF("handle_mime_preamble(%s length=%zu)", - alloca_toprint(50, start, r->parsed - start), r->parsed - start); - r->form_data.handle_mime_preamble(r, start, r->parsed - start); - } + assert(r->parsed >= start); + _INVOKE_HANDLER_BUF_LEN(handle_mime_preamble, start, r->parsed); } return 100; // need more data case HEADER: { @@ -1256,9 +1265,9 @@ static int http_request_parse_body_form_data(struct http_request *r) alloca_mime_content_type(&r->part_header.content_type), alloca_mime_content_disposition(&r->part_header.content_disposition) ); - r->form_data.handle_mime_part_header(r, &r->part_header); + int result = r->form_data.handle_mime_part_header(r, &r->part_header); + _HANDLER_RESULT(result); \ } - r->form_data_state = BODY; return 0; } @@ -1273,8 +1282,7 @@ static int http_request_parse_body_form_data(struct http_request *r) _commit(r); // A boundary in the middle of headers finishes the current part and starts a new part. // An end boundary terminates the current part and starts the epilogue. - http_request_form_data_start_part(r, b); - return 0; + return http_request_form_data_start_part(r, b); } if (_run_out(r)) return 100; // read more and try again @@ -1369,13 +1377,8 @@ static int http_request_parse_body_form_data(struct http_request *r) _commit(r); assert(end_body >= start); r->part_body_length += end_body - start; - if (end_body > start && r->form_data.handle_mime_body) { - if (r->debug_flag && *r->debug_flag) - DEBUGF("handle_mime_body(%s length=%zu)", alloca_toprint(80, start, end_body - start), end_body - start); - r->form_data.handle_mime_body(r, start, end_body - start); // excluding CRLF at end - } - http_request_form_data_start_part(r, b); - return 0; + _INVOKE_HANDLER_BUF_LEN(handle_mime_body, start, end_body); // excluding CRLF at end + return http_request_form_data_start_part(r, b); } } if (_end_of_content(r)) { @@ -1387,27 +1390,21 @@ static int http_request_parse_body_form_data(struct http_request *r) _commit(r); assert(r->parsed >= start); r->part_body_length += r->parsed - start; - if (r->parsed > start && r->form_data.handle_mime_body) { - if (r->debug_flag && *r->debug_flag) - DEBUGF("handle_mime_body(%s length=%zu)", alloca_toprint(80, start, r->parsed - start), r->parsed - start); - r->form_data.handle_mime_body(r, start, r->parsed - start); - } + _INVOKE_HANDLER_BUF_LEN(handle_mime_body, start, r->parsed); return 100; // need more data case EPILOGUE: if (config.debug.httpd) DEBUGF("EPILOGUE"); r->cursor = r->end; - if (r->form_data.handle_mime_epilogue && r->cursor != r->parsed) { - if (r->debug_flag && *r->debug_flag) - DEBUGF("handle_mime_epilogue(%s length=%zu)", - alloca_toprint(50, r->parsed, r->cursor - r->parsed), r->cursor - r->parsed); - r->form_data.handle_mime_epilogue(r, r->parsed, r->cursor - r->parsed); - } + assert(r->cursor >= r->parsed); + _INVOKE_HANDLER_BUF_LEN(handle_mime_epilogue, r->parsed, r->cursor); _commit(r); assert(_run_out(r)); if (_end_of_content(r)) return 0; // done return 100; // need more data + default: + FATALF("form_data_state = %d", r->form_data_state); } abort(); // not reached } diff --git a/http_server.h b/http_server.h index 19b2ffa1..9402dc80 100644 --- a/http_server.h +++ b/http_server.h @@ -129,12 +129,16 @@ struct mime_part_headers { }; struct http_mime_handler { - void (*handle_mime_preamble)(struct http_request *, const char *, size_t); - void (*handle_mime_part_start)(struct http_request *); - void (*handle_mime_part_header)(struct http_request *, const struct mime_part_headers *); - void (*handle_mime_body)(struct http_request *, const char *, size_t); - void (*handle_mime_part_end)(struct http_request *); - void (*handle_mime_epilogue)(struct http_request *, const char *, size_t); + // All these functions may abort the request processing by returning an HTTP + // filure status code in the range 400-599 or by initiating an HTTP response + // directly (changing the phase from RECEIVE to TRANSMIT). They can return + // zero to indicate that parsing should proceed. + int (*handle_mime_preamble)(struct http_request *, const char *, size_t); + int (*handle_mime_part_start)(struct http_request *); + int (*handle_mime_part_header)(struct http_request *, const struct mime_part_headers *); + int (*handle_mime_body)(struct http_request *, const char *, size_t); + int (*handle_mime_part_end)(struct http_request *); + int (*handle_mime_epilogue)(struct http_request *, const char *, size_t); }; struct http_request; diff --git a/rhizome_direct_http.c b/rhizome_direct_http.c index db2f15f0..06ef638f 100644 --- a/rhizome_direct_http.c +++ b/rhizome_direct_http.c @@ -285,21 +285,22 @@ static int rhizome_direct_addfile_end(struct http_request *hr) } } -static void rhizome_direct_process_mime_start(struct http_request *hr) +static int rhizome_direct_process_mime_start(struct http_request *hr) { rhizome_http_request *r = (rhizome_http_request *) hr; assert(r->current_part == NONE); assert(r->part_fd == -1); + return 0; } -static void rhizome_direct_process_mime_end(struct http_request *hr) +static int rhizome_direct_process_mime_end(struct http_request *hr) { rhizome_http_request *r = (rhizome_http_request *) hr; if (r->part_fd != -1) { if (close(r->part_fd) == -1) { WHYF_perror("close(%d)", r->part_fd); http_request_simple_response(&r->http, 500, "Internal Error: Close temporary file failed"); - return; + return 500; } r->part_fd = -1; } @@ -314,9 +315,10 @@ static void rhizome_direct_process_mime_end(struct http_request *hr) break; } r->current_part = NONE; + return 0; } -static void rhizome_direct_process_mime_part_header(struct http_request *hr, const struct mime_part_headers *h) +static int rhizome_direct_process_mime_part_header(struct http_request *hr, const struct mime_part_headers *h) { rhizome_http_request *r = (rhizome_http_request *) hr; if (strcmp(h->content_disposition.name, "data") == 0) { @@ -326,28 +328,30 @@ static void rhizome_direct_process_mime_part_header(struct http_request *hr, con else if (strcmp(h->content_disposition.name, "manifest") == 0) { r->current_part = MANIFEST; } else - return; + return 0; char path[512]; if (form_temporary_file_path(r, path, h->content_disposition.name) == -1) { http_request_simple_response(&r->http, 500, "Internal Error: Buffer overrun"); - return; + return 0; } if ((r->part_fd = open(path, O_WRONLY | O_CREAT, 0666)) == -1) { WHYF_perror("open(%s,O_WRONLY|O_CREAT,0666)", alloca_str_toprint(path)); http_request_simple_response(&r->http, 500, "Internal Error: Create temporary file failed"); - return; + return 0; } + return 0; } -static void rhizome_direct_process_mime_body(struct http_request *hr, const char *buf, size_t len) +static int rhizome_direct_process_mime_body(struct http_request *hr, const char *buf, size_t len) { rhizome_http_request *r = (rhizome_http_request *) hr; if (r->part_fd != -1) { if (write_all(r->part_fd, buf, len) == -1) { http_request_simple_response(&r->http, 500, "Internal Error: Write temporary file failed"); - return; + return 500; } } + return 0; } int rhizome_direct_import(rhizome_http_request *r, const char *remainder)