diff --git a/http_server.c b/http_server.c index 87645cd8..00890b97 100644 --- a/http_server.c +++ b/http_server.c @@ -281,11 +281,13 @@ static const char * _reserve(struct http_request *r, struct substring str) return ret; } +#if 0 static const char * _reserve_str(struct http_request *r, const char *str) { struct substring sub = { .start = str, .end = str + strlen(str) }; return _reserve(r, sub); } +#endif static inline int _end_of_content(struct http_request *r) { @@ -556,6 +558,60 @@ static unsigned _parse_ranges(struct http_request *r, struct http_range *range, return i; } +static int _parse_content_type(struct http_request *r, struct mime_content_type *ct) +{ + size_t n = _parse_token(r, ct->type, sizeof ct->type); + if (n == 0) + return 0; + if (n >= sizeof ct->type) { + WARNF("HTTP Content-Type type truncated: %s", alloca_str_toprint(ct->type)); + return 0; + } + if (!_skip_literal(r, "/")) + return 0; + n = _parse_token(r, ct->subtype, sizeof ct->subtype); + if (n == 0) + return 0; + if (n >= sizeof ct->subtype) { + WARNF("HTTP Content-Type subtype truncated: %s", alloca_str_toprint(ct->subtype)); + return 0; + } + while (_skip_optional_space(r) && _skip_literal(r, ";") && _skip_optional_space(r)) { + const char *start = r->cursor; + if (_skip_literal(r, "charset=")) { + size_t n = _parse_token_or_quoted_string(r, ct->charset, sizeof ct->charset); + if (n == 0) + return 0; + if (n >= sizeof ct->charset) { + WARNF("HTTP Content-Type charset truncated: %s", alloca_str_toprint(ct->charset)); + return 0; + } + continue; + } + r->cursor = start; + if (_skip_literal(r, "boundary=")) { + size_t n = _parse_token_or_quoted_string(r, ct->multipart_boundary, sizeof ct->multipart_boundary); + if (n == 0) + return 0; + if (n >= sizeof ct->multipart_boundary) { + WARNF("HTTP Content-Type boundary truncated: %s", alloca_str_toprint(ct->multipart_boundary)); + return 0; + } + continue; + } + r->cursor = start; + struct substring param; + if (_skip_token(r, ¶m) && _skip_literal(r, "=") && _parse_token_or_quoted_string(r, NULL, 0)) { + if (r->debug_flag && *r->debug_flag) + DEBUGF("Skipping HTTP Content-Type parameter: %s", alloca_substring_toprint(param)); + continue; + } + WARNF("Malformed HTTP Content-Type: %s", alloca_toprint(50, r->cursor, r->end - r->cursor)); + return 0; + } + return 1; +} + static int _parse_quoted_rfc822_time(struct http_request *r, time_t *timep) { char datestr[40]; @@ -743,6 +799,13 @@ static int http_request_parse_header(struct http_request *r) _rewind(r); const char *const sol = r->cursor; if (_skip_literal_nocase(r, "Content-Length:")) { + if (r->request_header.content_length != CONTENT_LENGTH_UNKNOWN) { + if (r->debug_flag && *r->debug_flag) + DEBUGF("Skipping duplicate HTTP header Content-Length: %s", alloca_toprint(50, sol, r->end - sol)); + r->cursor = nextline; + _commit(r); + return 0; + } _skip_optional_space(r); http_size_t length; if (_parse_http_size_t(r, &length) && _skip_optional_space(r) && r->cursor == eol) { @@ -757,50 +820,35 @@ static int http_request_parse_header(struct http_request *r) } _rewind(r); if (_skip_literal_nocase(r, "Content-Type:")) { + if (r->request_header.content_type.type[0]) { + if (r->debug_flag && *r->debug_flag) + DEBUGF("Skipping duplicate HTTP header Content-Type: %s", alloca_toprint(50, sol, r->end - sol)); + r->cursor = nextline; + _commit(r); + return 0; + } _skip_optional_space(r); - struct substring type = substring_NULL; - struct substring subtype = substring_NULL; - char boundary[BOUNDARY_STRING_MAXLEN + 1]; - boundary[0] = '\0'; - if (_skip_token(r, &type) && _skip_literal(r, "/") && _skip_token(r, &subtype)) { - // Parse zero or more content-type parameters. - for (_skip_optional_space(r); r->cursor < eol && _skip_literal(r, ";"); _skip_optional_space(r)) { - _skip_optional_space(r); - const char *startparam = r->cursor; - if (_skip_literal(r, "boundary=")) { - size_t n = _parse_token_or_quoted_string(r, boundary, sizeof boundary); - if (n == 0 || n >= sizeof boundary || !is_valid_http_boundary_string(boundary)) - goto malformed; - continue; - } - // Silently ignore unrecognised parameters (eg, charset=) if they are well formed. - r->cursor = startparam; // partial rewind - if (_skip_token(r, NULL) && _skip_literal(r, "=") && _parse_token_or_quoted_string(r, NULL, 0)) - continue; - break; - } - if (r->cursor == eol) { - r->cursor = nextline; - _commit(r); - if ( (r->request_header.content_type = _reserve(r, type)) == NULL - || (r->request_header.content_subtype = _reserve(r, subtype)) == NULL - || (boundary[0] && (r->request_header.boundary = _reserve_str(r, boundary)) == NULL) - ) - return 0; // error - if (r->debug_flag && *r->debug_flag) - DEBUGF("Parsed HTTP request Content-type: %s/%s%s%s", - r->request_header.content_type, - r->request_header.content_subtype, - r->request_header.boundary ? "; boundary=" : "", - r->request_header.boundary ? alloca_str_toprint(r->request_header.boundary) : "" - ); - return 0; - } + if ( _parse_content_type(r, &r->request_header.content_type) + && _skip_optional_space(r) + && r->cursor == eol + ) { + r->cursor = nextline; + _commit(r); + if (r->debug_flag && *r->debug_flag) + DEBUGF("Parsed HTTP request Content-type: %s", alloca_mime_content_type(&r->request_header.content_type)); + return 0; } goto malformed; } _rewind(r); if (_skip_literal_nocase(r, "Range:")) { + if (r->request_header.content_range_count) { + if (r->debug_flag && *r->debug_flag) + DEBUGF("Skipping duplicate HTTP header Range: %s", alloca_toprint(50, sol, r->end - sol)); + r->cursor = nextline; + _commit(r); + return 0; + } _skip_optional_space(r); unsigned int n; if ( _skip_literal(r, "bytes=") @@ -858,7 +906,7 @@ static int http_request_start_body(struct http_request *r) DEBUGF("Malformed HTTP %s request: non-zero Content-Length not allowed", r->verb); return 400; } - if (r->request_header.content_type) { + if (r->request_header.content_type.type) { if (r->debug_flag && *r->debug_flag) DEBUGF("Malformed HTTP %s request: Content-Type not allowed", r->verb); return 400; @@ -871,26 +919,28 @@ static int http_request_start_body(struct http_request *r) DEBUGF("Malformed HTTP %s request: missing Content-Length header", r->verb); return 411; } - if (r->request_header.content_type == NULL) { + if (r->request_header.content_type.type == NULL) { if (r->debug_flag && *r->debug_flag) DEBUGF("Malformed HTTP %s request: missing Content-Type header", r->verb); return 400; } - if ( strcmp(r->request_header.content_type, "multipart") == 0 - && strcmp(r->request_header.content_subtype, "form-data") == 0 + if ( strcmp(r->request_header.content_type.type, "multipart") == 0 + && strcmp(r->request_header.content_type.subtype, "form-data") == 0 ) { - if (r->request_header.boundary == NULL || r->request_header.boundary[0] == '\0') { + if ( r->request_header.content_type.multipart_boundary == NULL + || r->request_header.content_type.multipart_boundary[0] == '\0' + ) { if (r->debug_flag && *r->debug_flag) DEBUGF("Malformed HTTP %s request: Content-Type %s/%s missing boundary parameter", - r->verb, r->request_header.content_type, r->request_header.content_subtype); + r->verb, r->request_header.content_type.type, r->request_header.content_type.subtype); return 400; } r->parser = http_request_parse_body_form_data; r->form_data_state = START; } else { if (r->debug_flag && *r->debug_flag) - DEBUGF("Unsupported HTTP %s request: Content-Type %s/%s not supported", - r->verb, r->request_header.content_type, r->request_header.content_subtype); + DEBUGF("Unsupported HTTP %s request: Content-Type %s not supported", + r->verb, alloca_mime_content_type(&r->request_header.content_type)); return 415; } } @@ -909,7 +959,7 @@ static int http_request_start_body(struct http_request *r) */ static int _skip_mime_boundary(struct http_request *r) { - if (!_skip_literal(r, "--") || !_skip_literal(r, r->request_header.boundary)) + if (!_skip_literal(r, "--") || !_skip_literal(r, r->request_header.content_type.multipart_boundary)) return 0; if (_skip_literal(r, "--") && _skip_crlf(r)) return 2; @@ -988,6 +1038,43 @@ malformed: return 1; } +static void http_request_form_data_start_part(struct http_request *r, int b) +{ + switch (r->form_data_state) { + case BODY: + if ( r->part_header.content_length != CONTENT_LENGTH_UNKNOWN + && r->part_body_length != r->part_header.content_length + ) { + WARNF("HTTP multipart part body length (%"PRIhttp_size_t") does not match Content-Length header (%"PRIhttp_size_t")", + r->part_body_length, + r->part_header.content_length + ); + } + // 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); + } + break; + default: + break; + } + if (b == 1) { + r->form_data_state = HEADER; + 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); + } + } else + r->form_data_state = EPILOGUE; +} + /* If parsing completes (ie, parsed to end of epilogue), then sets r->parser to NULL and returns 0, * so this function will not be called again. If parsing cannot complete due to running out of * data, returns 100, so this function will not be called again until more data has been read. @@ -1029,15 +1116,7 @@ static int http_request_parse_body_form_data(struct http_request *r) } _rewind_crlf(r); _commit(r); - if (b == 1) { - r->form_data_state = HEADER; - 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); - } - } else - r->form_data_state = EPILOGUE; + http_request_form_data_start_part(r, b); return 0; } } @@ -1074,6 +1153,16 @@ static int http_request_parse_body_form_data(struct http_request *r) // A blank line finishes the headers. The CRLF does not form part of the body. if (_skip_crlf(r)) { _commit(r); + if (r->form_data.handle_mime_part_header) { + if (r->debug_flag && *r->debug_flag) + DEBUGF("handle_mime_part_header(Content-Length: %"PRIhttp_size_t", Content-Type: %s, Content-Disposition: %s)", + r->part_header.content_length, + 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); + } + r->form_data_state = BODY; return 0; } @@ -1086,23 +1175,9 @@ static int http_request_parse_body_form_data(struct http_request *r) if ((b = _skip_mime_boundary(r))) { _rewind_crlf(r); _commit(r); - 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); - } // 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. - if (b == 1) { - r->form_data_state = HEADER; - 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); - } - } - else - r->form_data_state = EPILOGUE; + http_request_form_data_start_part(r, b); return 0; } if (_run_out(r)) @@ -1115,27 +1190,54 @@ static int http_request_parse_body_form_data(struct http_request *r) strncpy(labelstr, label.start, labellen)[labellen] = '\0'; str_tolower_inplace(labelstr); const char *value = r->cursor; - if (strcmp(labelstr, "content-disposition") == 0) { - struct mime_content_disposition cd; - bzero(&cd, sizeof cd); - if (_parse_content_disposition(r, &cd) && _skip_optional_space(r) && _skip_crlf(r)) { + if (strcmp(labelstr, "content-length") == 0) { + if (r->part_header.content_length != CONTENT_LENGTH_UNKNOWN) { + if (r->debug_flag && *r->debug_flag) + DEBUGF("Skipping duplicate HTTP multipart header Content-Length: %s", alloca_toprint(50, sol, r->end - sol)); + return 400; + } + http_size_t length; + if (_parse_http_size_t(r, &length) && _skip_optional_space(r) && _skip_crlf(r)) { _rewind_crlf(r); _commit(r); - if (r->form_data.handle_mime_content_disposition) { - if (r->debug_flag && *r->debug_flag) - DEBUGF("handle_mime_content_disposition(%s)", alloca_mime_content_disposition(&cd)); - r->form_data.handle_mime_content_disposition(r, &cd); - } + r->part_header.content_length = length; + if (r->debug_flag && *r->debug_flag) + DEBUGF("Parsed HTTP multipart header Content-Length: %"PRIhttp_size_t, r->part_header.content_length); + return 0; + } + } + else if (strcmp(labelstr, "content-type") == 0) { + if (r->part_header.content_type.type[0]) { + if (r->debug_flag && *r->debug_flag) + DEBUGF("Skipping duplicate HTTP multipart header Content-Type: %s", alloca_toprint(50, sol, r->end - sol)); + return 400; + } + if (_parse_content_type(r, &r->part_header.content_type) && _skip_optional_space(r) && _skip_crlf(r)) { + _rewind_crlf(r); + _commit(r); + if (r->debug_flag && *r->debug_flag) + DEBUGF("Parsed HTTP multipart header Content-Type: %s", alloca_mime_content_type(&r->part_header.content_type)); + return 0; + } + } + else if (strcmp(labelstr, "content-disposition") == 0) { + if (r->part_header.content_disposition.type[0]) { + if (r->debug_flag && *r->debug_flag) + DEBUGF("Skipping duplicate HTTP multipart header Content-Disposition: %s", alloca_toprint(50, sol, r->end - sol)); + return 400; + } + if (_parse_content_disposition(r, &r->part_header.content_disposition) && _skip_optional_space(r) && _skip_crlf(r)) { + _rewind_crlf(r); + _commit(r); + if (r->debug_flag && *r->debug_flag) + DEBUGF("Parsed HTTP multipart header Content-Disposition: %s", alloca_mime_content_disposition(&r->part_header.content_disposition)); return 0; } } else if (_skip_to_crlf(r)) { _commit(r); - if (r->form_data.handle_mime_header) { - if (r->debug_flag && *r->debug_flag) - DEBUGF("handle_mime_header(%s, %s)", alloca_str_toprint(labelstr), alloca_toprint(-1, value, value - r->cursor)); - r->form_data.handle_mime_header(r, labelstr, value, value - r->cursor); // excluding CRLF at end - } + if (r->debug_flag && *r->debug_flag) + DEBUGF("Skip HTTP multipart header: %s: %s", alloca_str_toprint(labelstr), alloca_toprint(-1, value, value - r->cursor)); return 0; } } @@ -1169,25 +1271,14 @@ static int http_request_parse_body_form_data(struct http_request *r) if ((b = _skip_mime_boundary(r))) { _rewind_crlf(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 } - 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); - } - r->form_data_state = EPILOGUE; - if (b == 1) { - r->form_data_state = HEADER; - 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); - } - } + http_request_form_data_start_part(r, b); return 0; } } @@ -1198,6 +1289,8 @@ static int http_request_parse_body_form_data(struct http_request *r) } _rewind_optional_cr(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); @@ -1538,18 +1631,6 @@ static const char *httpResultString(int response_code) } } -static strbuf strbuf_append_quoted_string(strbuf sb, const char *str) -{ - strbuf_putc(sb, '"'); - for (; *str; ++str) { - if (*str == '"' || *str == '\\') - strbuf_putc(sb, '\\'); - strbuf_putc(sb, *str); - } - strbuf_putc(sb, '"'); - return sb; -} - /* Render the HTTP response into the current response buffer. Return 1 if it fits, 0 if it does * not. The buffer response_pointer may be NULL, in which case no response is rendered, but the * content_length is still computed diff --git a/http_server.h b/http_server.h index a647c30e..be6d43c1 100644 --- a/http_server.h +++ b/http_server.h @@ -56,11 +56,16 @@ http_size_t http_range_bytes(const struct http_range *range, unsigned nranges); #define CONTENT_LENGTH_UNKNOWN UINT64_MAX +struct mime_content_type { + char type[64]; + char subtype[64]; + char multipart_boundary[71]; + char charset[31]; +}; + struct http_request_headers { http_size_t content_length; - const char *content_type; - const char *content_subtype; - const char *boundary; + struct mime_content_type content_type; unsigned short content_range_count; struct http_range content_ranges[5]; }; @@ -94,11 +99,16 @@ struct mime_content_disposition { time_t read_date; }; +struct mime_part_headers { + http_size_t content_length; + struct mime_content_type content_type; + struct mime_content_disposition content_disposition; +}; + 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_content_disposition)(struct http_request *, const struct mime_content_disposition *); - void (*handle_mime_header)(struct http_request *, const char *label, const char *, size_t); + 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); @@ -118,31 +128,49 @@ typedef int (*HTTP_REQUEST_PARSER)(struct http_request *); struct http_request { struct sched_ent alarm; // MUST BE FIRST ELEMENT + // The following control the lifetime of this struct. enum http_request_phase { RECEIVE, TRANSMIT, DONE } phase; - bool_t *debug_flag; - bool_t *disable_tx_flag; - time_ms_t initiate_time; // time connection was initiated - time_ms_t idle_timeout; // disconnect if no bytes received for this long - struct sockaddr_in client_sockaddr_in; - HTTP_REQUEST_PARSER parser; // current parser function - HTTP_REQUEST_PARSER handle_first_line; // called after first line is parsed - HTTP_REQUEST_PARSER handle_headers; // called after all headers are parsed - HTTP_REQUEST_PARSER handle_content_end; // called after all content is received - enum mime_state { START, PREAMBLE, HEADER, BODY, EPILOGUE } form_data_state; - struct http_mime_handler form_data; // called to parse multipart/form-data body void (*finalise)(struct http_request *); void (*free)(void*); + // These can be set up to point to config flags, to allow debug to be + // enabled indpendently for different instances HTTP server instances + // that use this code. + bool_t *debug_flag; + bool_t *disable_tx_flag; + // The following are used for parsing the HTTP request. + time_ms_t initiate_time; // time connection was initiated + time_ms_t idle_timeout; // disconnect if no bytes received for this long + struct sockaddr_in client_sockaddr_in; // caller may supply this + // The parsed HTTP request is accumulated into the following fields. const char *verb; // points to nul terminated static string, "GET", "PUT", etc. const char *path; // points into buffer; nul terminated uint8_t version_major; // m from from HTTP/m.n uint8_t version_minor; // n from HTTP/m.n struct http_request_headers request_header; + // Parsing is done by setting 'parser' to point to a series of parsing + // functions as the parsing state progresses. + HTTP_REQUEST_PARSER parser; // current parser function + // The caller may set these up, and they are invoked by the parser as request + // parsing reaches different stages. + HTTP_REQUEST_PARSER handle_first_line; // called after first line is parsed + HTTP_REQUEST_PARSER handle_headers; // called after all HTTP headers are parsed + HTTP_REQUEST_PARSER handle_content_end; // called after all content is received + // The following are used for managing the buffer during RECEIVE phase. const char *received; // start of received data in buffer[] const char *end; // end of received data in buffer[] const char *parsed; // start of unparsed data in buffer[] const char *cursor; // for parsing http_size_t request_content_remaining; + // The following are used for parsing a multipart body. + enum mime_state { START, PREAMBLE, HEADER, BODY, EPILOGUE } form_data_state; + struct http_mime_handler form_data; + struct mime_part_headers part_header; + http_size_t part_body_length; + // The following are used for constructing the response that will be sent in + // TRANSMIT phase. struct http_response response; + // The following are used during TRANSMIT phase to control buffering and + // sending. http_size_t response_length; // total response bytes (header + content) http_size_t response_sent; // for counting up to response_length char *response_buffer; @@ -150,6 +178,7 @@ struct http_request { size_t response_buffer_length; size_t response_buffer_sent; void (*response_free_buffer)(void*); + // This buffer is used during RECEIVE and TRANSMIT phase. char buffer[8 * 1024]; }; diff --git a/rhizome_direct_http.c b/rhizome_direct_http.c index 220fbcbd..b3bfd201 100644 --- a/rhizome_direct_http.c +++ b/rhizome_direct_http.c @@ -54,7 +54,7 @@ static void rhizome_direct_clear_temporary_files(rhizome_http_request *r) } } -int rhizome_direct_import_end(struct http_request *hr) +static int rhizome_direct_import_end(struct http_request *hr) { rhizome_http_request *r = (rhizome_http_request *) hr; if (!r->received_manifest) { @@ -168,7 +168,7 @@ int rhizome_direct_enquiry_end(struct http_request *hr) return 0; } -int rhizome_direct_addfile_end(struct http_request *hr) +static int rhizome_direct_addfile_end(struct http_request *hr) { rhizome_http_request *r = (rhizome_http_request *) hr; // If given a file without a manifest, we should only accept if it we are configured to do so, and @@ -271,14 +271,14 @@ int rhizome_direct_addfile_end(struct http_request *hr) } } -void rhizome_direct_process_mime_start(struct http_request *hr) +static void 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); } -void rhizome_direct_process_mime_end(struct http_request *hr) +static void rhizome_direct_process_mime_end(struct http_request *hr) { rhizome_http_request *r = (rhizome_http_request *) hr; if (r->part_fd != -1) { @@ -302,19 +302,19 @@ void rhizome_direct_process_mime_end(struct http_request *hr) r->current_part = NONE; } -void rhizome_direct_process_mime_content_disposition(struct http_request *hr, const struct mime_content_disposition *cd) +static void 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(cd->name, "data") == 0) { + if (strcmp(h->content_disposition.name, "data") == 0) { r->current_part = DATA; - strncpy(r->data_file_name, cd->filename, sizeof r->data_file_name)[sizeof r->data_file_name - 1] = '\0'; + strncpy(r->data_file_name, h->content_disposition.filename, sizeof r->data_file_name)[sizeof r->data_file_name - 1] = '\0'; } - else if (strcmp(cd->name, "manifest") == 0) { + else if (strcmp(h->content_disposition.name, "manifest") == 0) { r->current_part = MANIFEST; } else return; char path[512]; - if (form_temporary_file_path(r, path, cd->name) == -1) { + if (form_temporary_file_path(r, path, h->content_disposition.name) == -1) { http_request_simple_response(&r->http, 500, "Internal Error: Buffer overrun"); return; } @@ -325,7 +325,7 @@ void rhizome_direct_process_mime_content_disposition(struct http_request *hr, co } } -void rhizome_direct_process_mime_body(struct http_request *hr, const char *buf, size_t len) +static void 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) { @@ -344,7 +344,7 @@ int rhizome_direct_import(rhizome_http_request *r, const char *remainder) } r->http.form_data.handle_mime_part_start = rhizome_direct_process_mime_start; r->http.form_data.handle_mime_part_end = rhizome_direct_process_mime_end; - r->http.form_data.handle_mime_content_disposition = rhizome_direct_process_mime_content_disposition; + r->http.form_data.handle_mime_part_header = rhizome_direct_process_mime_part_header; r->http.form_data.handle_mime_body = rhizome_direct_process_mime_body; r->http.handle_content_end = rhizome_direct_import_end; r->current_part = NONE; @@ -361,7 +361,7 @@ int rhizome_direct_enquiry(rhizome_http_request *r, const char *remainder) } r->http.form_data.handle_mime_part_start = rhizome_direct_process_mime_start; r->http.form_data.handle_mime_part_end = rhizome_direct_process_mime_end; - r->http.form_data.handle_mime_content_disposition = rhizome_direct_process_mime_content_disposition; + r->http.form_data.handle_mime_part_header = rhizome_direct_process_mime_part_header; r->http.form_data.handle_mime_body = rhizome_direct_process_mime_body; r->http.handle_content_end = rhizome_direct_enquiry_end; r->current_part = NONE; @@ -394,7 +394,7 @@ int rhizome_direct_addfile(rhizome_http_request *r, const char *remainder) } r->http.form_data.handle_mime_part_start = rhizome_direct_process_mime_start; r->http.form_data.handle_mime_part_end = rhizome_direct_process_mime_end; - r->http.form_data.handle_mime_content_disposition = rhizome_direct_process_mime_content_disposition; + r->http.form_data.handle_mime_part_header = rhizome_direct_process_mime_part_header; r->http.form_data.handle_mime_body = rhizome_direct_process_mime_body; r->http.handle_content_end = rhizome_direct_addfile_end; r->current_part = NONE; diff --git a/strbuf_helpers.c b/strbuf_helpers.c index de8446d6..7d04446b 100644 --- a/strbuf_helpers.c +++ b/strbuf_helpers.c @@ -402,6 +402,18 @@ strbuf strbuf_append_iovec(strbuf sb, const struct iovec *iov, int iovcnt) return sb; } +strbuf strbuf_append_quoted_string(strbuf sb, const char *str) +{ + strbuf_putc(sb, '"'); + for (; *str; ++str) { + if (*str == '"' || *str == '\\') + strbuf_putc(sb, '\\'); + strbuf_putc(sb, *str); + } + strbuf_putc(sb, '"'); + return sb; +} + strbuf strbuf_append_http_ranges(strbuf sb, const struct http_range *ranges, unsigned nels) { unsigned i; @@ -427,31 +439,47 @@ strbuf strbuf_append_http_ranges(strbuf sb, const struct http_range *ranges, uns return sb; } -strbuf strbuf_append_mime_content_disposition(strbuf sb, const struct mime_content_disposition *cd) +strbuf strbuf_append_mime_content_type(strbuf sb, const struct mime_content_type *ct) { - strbuf_puts(sb, "type="); - strbuf_toprint_quoted(sb, "``", cd->type); - strbuf_puts(sb, " name="); - strbuf_toprint_quoted(sb, "``", cd->name); - strbuf_puts(sb, " filename="); - strbuf_toprint_quoted(sb, "``", cd->filename); - strbuf_puts(sb, " size="); - strbuf_sprintf(sb, "%"PRIhttp_size_t, cd->size); - struct tm tm; - strbuf_puts(sb, " creation_date="); - if (cd->creation_date) - strbuf_append_strftime(sb, "%a, %d %b %Y %T %z", gmtime_r(&cd->creation_date, &tm)); - else - strbuf_puts(sb, "0"); - strbuf_puts(sb, " modification_date="); - if (cd->modification_date) - strbuf_append_strftime(sb, "%a, %d %b %Y %T %z", gmtime_r(&cd->modification_date, &tm)); - else - strbuf_puts(sb, "0"); - strbuf_puts(sb, " read_date="); - if (cd->read_date) - strbuf_append_strftime(sb, "%a, %d %b %Y %T %z", gmtime_r(&cd->read_date, &tm)); - else - strbuf_puts(sb, "0"); + strbuf_puts(sb, ct->type); + strbuf_putc(sb, '/'); + strbuf_puts(sb, ct->subtype); + if (ct->charset) { + strbuf_puts(sb, "; charset="); + strbuf_append_quoted_string(sb, ct->charset); + } + if (ct->multipart_boundary) { + strbuf_puts(sb, "; boundary="); + strbuf_append_quoted_string(sb, ct->multipart_boundary); + } + return sb; +} + +strbuf strbuf_append_mime_content_disposition(strbuf sb, const struct mime_content_disposition *cd) +{ + strbuf_puts(sb, cd->type); + if (cd->name) { + strbuf_puts(sb, "; name="); + strbuf_append_quoted_string(sb, cd->name); + } + if (cd->filename) { + strbuf_puts(sb, "; filename="); + strbuf_append_quoted_string(sb, cd->filename); + } + if (cd->size) + strbuf_sprintf(sb, "; size=%"PRIhttp_size_t, cd->size); + struct tm tm; + if (cd->creation_date) { + strbuf_puts(sb, " creation_date="); + strbuf_append_strftime(sb, "\"%a, %d %b %Y %T %z\"", gmtime_r(&cd->creation_date, &tm)); + } + if (cd->modification_date) { + strbuf_puts(sb, " modification_date="); + strbuf_append_strftime(sb, "\"%a, %d %b %Y %T %z\"", gmtime_r(&cd->modification_date, &tm)); + } + if (cd->read_date) { + strbuf_puts(sb, " read_date="); + strbuf_append_strftime(sb, "\"%a, %d %b %Y %T %z\"", gmtime_r(&cd->read_date, &tm)); + } return sb; } diff --git a/strbuf_helpers.h b/strbuf_helpers.h index 289c7ddf..fb003dd0 100644 --- a/strbuf_helpers.h +++ b/strbuf_helpers.h @@ -145,6 +145,12 @@ struct iovec; strbuf strbuf_append_iovec(strbuf sb, const struct iovec *iov, int iovcnt); #define alloca_iovec(iov,cnt) strbuf_str(strbuf_append_iovec(strbuf_alloca(200), (iov), (cnt))) +/* Append a string using HTTP quoted-string format: delimited by double quotes (") and + * internal double quotes and backslash escaped by leading backslash. + * @author Andrew Bettison + */ +strbuf strbuf_append_quoted_string(strbuf sb, const char *str); + /* Append a representation of a struct http_range[] array. * @author Andrew Bettison */ @@ -152,7 +158,14 @@ struct http_range; strbuf strbuf_append_http_ranges(strbuf sb, const struct http_range *ranges, unsigned nels); #define alloca_http_ranges(ra) strbuf_str(strbuf_append_http_ranges(strbuf_alloca(25*NELS(ra)), (ra), NELS(ra))) -/* Append a representation of a struct mime_content_disposition struct. +/* Append a representation of a struct mime_content_type in HTTP header format. + * @author Andrew Bettison + */ +struct mime_content_type; +strbuf strbuf_append_mime_content_type(strbuf, const struct mime_content_type *); +#define alloca_mime_content_type(ct) strbuf_str(strbuf_append_mime_content_type(strbuf_alloca(500), (ct))) + +/* Append a representation of a struct mime_content_disposition, in HTTP header format. * @author Andrew Bettison */ struct mime_content_disposition;