From fd86a3d17f8aa1035c26dfdf948cb59602d7992f Mon Sep 17 00:00:00 2001 From: Andrew Bettison Date: Wed, 5 Feb 2014 14:28:15 +1030 Subject: [PATCH] Implement HTTP POST /restful/meshms///sendmessage Rename struct meshms_ply fields from "buffer" to "record" for consistency with comment terminology --- httpd.c | 77 ++++++++++++++++++++++++++++++++++++ httpd.h | 32 +++++++++++++-- meshms.c | 96 ++++++++++++++++++++++++++------------------- meshms.h | 16 ++++++-- meshms_restful.c | 99 ++++++++++++++++++++++++++++++++++++++++++++++- rhizome_restful.c | 68 +++++--------------------------- strbuf_helpers.c | 53 +++++++++++++++---------- strbuf_helpers.h | 3 +- tests/rhizomehttp | 24 +++++++++++- 9 files changed, 340 insertions(+), 128 deletions(-) diff --git a/httpd.c b/httpd.c index 2691f392..52e9c50d 100644 --- a/httpd.c +++ b/httpd.c @@ -350,6 +350,83 @@ int authorize(struct http_request *r) return 0; } +int accumulate_text(httpd_request *r, const char *partname, char *textbuf, size_t textsiz, size_t *textlenp, const char *buf, size_t len) +{ + if (len) { + size_t newlen = *textlenp + len; + if (newlen > textsiz) { + if (config.debug.httpd) + DEBUGF("Form part \"%s\" too long, %zu bytes overflows maximum %zu by %zu", + partname, newlen, textsiz, (size_t)(newlen - textsiz) + ); + strbuf msg = strbuf_alloca(100); + strbuf_sprintf(msg, "Overflow in \"%s\" form part", partname); + http_request_simple_response(&r->http, 403, strbuf_str(msg)); + return 0; + } + memcpy(textbuf + *textlenp, buf, len); + *textlenp = newlen; + } + return 1; +} + +int form_buf_malloc_init(struct form_buf_malloc *f, size_t size_limit) +{ + assert(f->buffer == NULL); + assert(f->buffer_alloc_size == 0); + assert(f->length == 0); + f->size_limit = size_limit; + return 0; +} + +int form_buf_malloc_accumulate(httpd_request *r, const char *partname, struct form_buf_malloc *f, const char *buf, size_t len) +{ + if (len == 0) + return 0; + size_t newlen = f->length + len; + if (newlen > f->size_limit) { + if (config.debug.httpd) + DEBUGF("form part \"%s\" overflow, %zu bytes exceeds limit %zu by %zu", + partname, newlen, f->size_limit, (size_t)(newlen - f->size_limit) + ); + strbuf msg = strbuf_alloca(100); + strbuf_sprintf(msg, "Overflow in \"%s\" form part", partname); + http_request_simple_response(&r->http, 403, strbuf_str(msg)); + return 403; + } + if (newlen > f->buffer_alloc_size) { + if ((f->buffer = erealloc(f->buffer, newlen)) == NULL) { + http_request_simple_response(&r->http, 500, NULL); + return 500; + } + f->buffer_alloc_size = newlen; + } + memcpy(f->buffer + f->length, buf, len); + f->length = newlen; + return 0; +} + +void form_buf_malloc_release(struct form_buf_malloc *f) +{ + if (f->buffer) { + free(f->buffer); + f->buffer = NULL; + } + f->buffer_alloc_size = 0; + f->length = 0; + f->size_limit = 0; +} + +int http_response_form_part(httpd_request *r, const char *what, const char *partname, const char *text, size_t textlen) +{ + if (config.debug.httpd) + DEBUGF("%s \"%s\" form part %s", what, partname, text ? alloca_toprint(-1, text, textlen) : ""); + strbuf msg = strbuf_alloca(100); + strbuf_sprintf(msg, "%s \"%s\" form part", what, partname); + http_request_simple_response(&r->http, 403, strbuf_str(msg)); + return 403; +} + static int root_page(httpd_request *r, const char *remainder) { if (*remainder) diff --git a/httpd.h b/httpd.h index 7f02eacc..d8f4b26a 100644 --- a/httpd.h +++ b/httpd.h @@ -34,6 +34,19 @@ extern unsigned int httpd_request_count; enum list_phase { LIST_HEADER = 0, LIST_ROWS, LIST_END, LIST_DONE }; +struct form_buf_malloc { + char *buffer; + size_t size_limit; // == 0 means no limit + size_t buffer_alloc_size; + size_t length; +}; + +struct httpd_request; + +int form_buf_malloc_init(struct form_buf_malloc *, size_t size_limit); +int form_buf_malloc_accumulate(struct httpd_request *, const char *partname, struct form_buf_malloc *, const char *, size_t); +void form_buf_malloc_release(struct form_buf_malloc *); + typedef struct httpd_request { struct http_request http; // MUST BE FIRST ELEMENT @@ -108,9 +121,7 @@ typedef struct httpd_request size_t force_new_text_len; bool_t force_new; // For storing the manifest text (malloc/realloc) as we receive it - char *manifest_text; - size_t manifest_text_size; - size_t manifest_len; + struct form_buf_malloc manifest; // For receiving the payload enum rhizome_payload_status payload_status; uint64_t payload_size; @@ -159,6 +170,18 @@ typedef struct httpd_request } msglist; + /* For responses that send a MeshMS message. + */ + struct { + // Which part is currently being received + const char *current_part; + // Which parts have already been received + bool_t received_message; + // The text of the message to send + struct form_buf_malloc message; + } + sendmsg; + } u; } httpd_request; @@ -169,6 +192,9 @@ typedef int HTTP_HANDLER(httpd_request *r, const char *remainder); int is_http_header_complete(const char *buf, size_t len, size_t read_since_last_call); int authorize(struct http_request *r); +int http_response_form_part(httpd_request *r, const char *what, const char *partname, const char *text, size_t textlen); +int accumulate_text(httpd_request *r, const char *partname, char *textbuf, size_t textsiz, size_t *textlenp, const char *buf, size_t len); + int rhizome_response_content_init_filehash(httpd_request *r, const rhizome_filehash_t *hash); int rhizome_response_content_init_payload(httpd_request *r, rhizome_manifest *); HTTP_CONTENT_GENERATOR rhizome_payload_content; diff --git a/meshms.c b/meshms.c index a905cd91..a602e8a3 100644 --- a/meshms.c +++ b/meshms.c @@ -29,7 +29,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "dataformats.h" #define MESHMS_BLOCK_TYPE_ACK 0x01 -#define MESHMS_BLOCK_TYPE_MESSAGE 0x02 +#define MESHMS_BLOCK_TYPE_MESSAGE 0x02 // NUL-terminated UTF8 string #define MESHMS_BLOCK_TYPE_BID_REFERENCE 0x03 void meshms_free_conversations(struct meshms_conversations *conv) @@ -206,10 +206,11 @@ static int create_ply(const sid_t *my_sid, struct meshms_conversations *conv, rh return 0; } -static int append_footer(unsigned char *buffer, char type, int payload_len) +static int append_footer(unsigned char *buffer, char type, size_t message_len) { - payload_len = (payload_len << 4) | (type&0xF); - write_uint16(buffer, payload_len); + assert(message_len <= MESHMS_MESSAGE_MAX_LEN); + message_len = (message_len << 4) | (type&0xF); + write_uint16(buffer, message_len); return 2; } @@ -231,11 +232,11 @@ static int ply_read_open(struct meshms_ply_read *ply, const rhizome_bid_t *bid, static void ply_read_close(struct meshms_ply_read *ply) { - if (ply->buffer){ - free(ply->buffer); - ply->buffer=NULL; + if (ply->record){ + free(ply->record); + ply->record=NULL; } - ply->buffer_size=0; + ply->record_size=0; ply->buff.len=0; rhizome_read_close(&ply->read); } @@ -248,7 +249,7 @@ static int ply_read_prev(struct meshms_ply_read *ply) unsigned char footer[2]; if (ply->read.offset <= sizeof footer) { if (config.debug.meshms) - DEBUGF("EOF"); + DEBUG("EOF"); return 1; } ply->read.offset -= sizeof footer; @@ -271,14 +272,14 @@ static int ply_read_prev(struct meshms_ply_read *ply) } ply->read.offset -= ply->record_length + sizeof(footer); uint64_t record_start = ply->read.offset; - if (ply->buffer_size < ply->record_length){ - ply->buffer_size = ply->record_length; - unsigned char *b=realloc(ply->buffer, ply->buffer_size); + if (ply->record_size < ply->record_length){ + ply->record_size = ply->record_length; + unsigned char *b = erealloc(ply->record, ply->record_size); if (!b) - return WHY("realloc() failed"); - ply->buffer = b; + return -1; + ply->record = b; } - read = rhizome_read_buffered(&ply->read, &ply->buff, ply->buffer, ply->record_length); + read = rhizome_read_buffered(&ply->read, &ply->buff, ply->record, ply->record_length); if (read == -1) return WHYF("rhizome_read_buffered() failed"); if ((size_t) read != ply->record_length) @@ -416,7 +417,7 @@ static int update_conversation(const sid_t *my_sid, struct meshms_conversations goto end; if (ret==0){ - if (unpack_uint(ply.buffer, ply.record_length, &previous_ack) == -1) + if (unpack_uint(ply.record, ply.record_length, &previous_ack) == -1) previous_ack=0; } if (config.debug.meshms) @@ -740,7 +741,7 @@ int meshms_message_iterator_open(struct meshms_message_iterator *iter, const sid // Find their latest ACK so we know which of my messages have been delivered. int r = ply_find_prev(&iter->_their_reader, MESHMS_BLOCK_TYPE_ACK); if (r == 0) { - if (unpack_uint(iter->_their_reader.buffer, iter->_their_reader.record_length, &iter->latest_ack_my_offset) == -1) + if (unpack_uint(iter->_their_reader.record, iter->_their_reader.record_length, &iter->latest_ack_my_offset) == -1) iter->latest_ack_my_offset = 0; else iter->latest_ack_offset = iter->_their_reader.record_end_offset; @@ -804,15 +805,23 @@ int meshms_message_iterator_prev(struct meshms_message_iterator *iter) iter->type = ACK_RECEIVED; iter->offset = iter->_their_reader.record_end_offset; iter->text = NULL; - if (unpack_uint(iter->_their_reader.buffer, iter->_their_reader.record_length, &iter->ack_offset) == -1) + iter->text_length = 0; + if (unpack_uint(iter->_their_reader.record, iter->_their_reader.record_length, &iter->ack_offset) == -1) iter->ack_offset = 0; return 0; case MESHMS_BLOCK_TYPE_MESSAGE: iter->type = MESSAGE_RECEIVED; iter->offset = iter->_their_reader.record_end_offset; - iter->text = (const char *)iter->_their_reader.buffer; - iter->read = iter->_their_reader.record_end_offset <= iter->_conv->read_offset; - return 0; + iter->text = (const char *)iter->_their_reader.record; + iter->text_length = iter->_their_reader.record_length; + if ( iter->_their_reader.record_length != 0 + && iter->_their_reader.record[iter->_their_reader.record_length - 1] == '\0' + ) { + iter->read = iter->_their_reader.record_end_offset <= iter->_conv->read_offset; + return 0; + } + WARN("Malformed MeshMS2 ply journal, missing NUL terminator"); + break; } continue; } @@ -827,11 +836,11 @@ int meshms_message_iterator_prev(struct meshms_message_iterator *iter) case MESHMS_BLOCK_TYPE_ACK: // Read the received messages up to the ack'ed offset if (iter->_conv->found_their_ply) { - int ofs = unpack_uint(iter->_my_reader.buffer, iter->_my_reader.record_length, (uint64_t*)&iter->_their_reader.read.offset); + int ofs = unpack_uint(iter->_my_reader.record, iter->_my_reader.record_length, (uint64_t*)&iter->_their_reader.read.offset); if (ofs == -1) return WHYF("Malformed ACK"); uint64_t end_range; - int x = unpack_uint(iter->_my_reader.buffer + ofs, iter->_my_reader.record_length - ofs, &end_range); + int x = unpack_uint(iter->_my_reader.record + ofs, iter->_my_reader.record_length - ofs, &end_range); if (x == -1) iter->_end_range = 0; else @@ -846,7 +855,8 @@ int meshms_message_iterator_prev(struct meshms_message_iterator *iter) case MESHMS_BLOCK_TYPE_MESSAGE: iter->type = MESSAGE_SENT; iter->offset = iter->_my_reader.record_end_offset; - iter->text = (const char *)iter->_my_reader.buffer; + iter->text = (const char *)iter->_my_reader.record; + iter->text_length = iter->_my_reader.record_length; iter->delivered = iter->latest_ack_my_offset && iter->_my_reader.record_end_offset <= iter->latest_ack_my_offset; return 0; } @@ -855,6 +865,27 @@ int meshms_message_iterator_prev(struct meshms_message_iterator *iter) return ret; } +int meshms_send_message(const sid_t *sender, const sid_t *recipient, const char *message, size_t message_len) +{ + assert(message_len != 0); + if (message_len > MESHMS_MESSAGE_MAX_LEN) + return WHY("message too long"); + struct meshms_conversations *conv = find_or_create_conv(sender, recipient); + if (!conv) + return -1; + // construct a message payload + // TODO, new format here. + unsigned char buffer[message_len + 4]; + strncpy((char*)buffer, message, message_len); + // ensure message is NUL terminated + if (message[message_len - 1] != '\0') + buffer[message_len++] = '\0'; + message_len += append_footer(buffer + message_len, MESHMS_BLOCK_TYPE_MESSAGE, message_len); + int ret = append_meshms_buffer(sender, conv, buffer, message_len); + meshms_free_conversations(conv); + return ret; +} + // output the list of existing conversations for a given local identity int app_meshms_conversations(const struct cli_parsed *parsed, struct cli_context *context) { @@ -934,21 +965,8 @@ int app_meshms_send_message(const struct cli_parsed *parsed, struct cli_context return WHY("invalid sender SID"); if (str_to_sid_t(&their_sid, their_sidhex) == -1) return WHY("invalid recipient SID"); - struct meshms_conversations *conv = find_or_create_conv(&my_sid, &their_sid); - if (!conv) { - keyring_free(keyring); - return -1; - } - // construct a message payload - int message_len = strlen(message)+1; - - // TODO, new format here. - unsigned char buffer[message_len+3]; - strcpy((char*)buffer, message); // message - message_len+=append_footer(buffer+message_len, MESHMS_BLOCK_TYPE_MESSAGE, message_len); - int ret = append_meshms_buffer(&my_sid, conv, buffer, message_len); - - meshms_free_conversations(conv); + // include terminating NUL + int ret = meshms_send_message(&my_sid, &their_sid, message, strlen(message) + 1); keyring_free(keyring); return ret; } diff --git a/meshms.h b/meshms.h index 13693585..3579247a 100644 --- a/meshms.h +++ b/meshms.h @@ -23,6 +23,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "serval.h" #include "rhizome.h" +#define MESHMS_MESSAGE_MAX_LEN 4095 + // the manifest details for one half of a conversation struct meshms_ply { rhizome_bid_t bundle_id; @@ -65,10 +67,10 @@ struct meshms_ply_read { // details of the current record uint64_t record_end_offset; uint16_t record_length; - size_t buffer_size; + size_t record_size; char type; // raw record data - unsigned char *buffer; + unsigned char *record; }; /* Fetch the list of all MeshMS conversations into a binary tree whose nodes @@ -124,7 +126,8 @@ struct meshms_message_iterator { // (mine). For MESSAGE_RECEIVED and ACK_RECEIVED, it is the byte position // within the remote ply (theirs). uint64_t offset; - const char *text; // NUL terminated text of message + const char *text; // text of UTF8 message (NUL terminated) + size_t text_length; // excluding terminating NUL union { bool_t delivered; // for MESSAGE_SENT bool_t read; // for MESSAGE_RECEIVED @@ -145,4 +148,11 @@ int meshms_message_iterator_is_open(const struct meshms_message_iterator *); void meshms_message_iterator_close(struct meshms_message_iterator *); int meshms_message_iterator_prev(struct meshms_message_iterator *); +/* Append a message ('message_len' bytes of UTF8 at 'message') to the sender's + * ply in the conversation between 'sender' and 'recipient'. If no + * conversation (ply bundle) exists, then create it. Returns 0 on success, -1 + * on error (already logged). + */ +int meshms_send_message(const sid_t *sender, const sid_t *recipient, const char *message, size_t message_len); + #endif // __SERVAL_DNA__MESHMS_H diff --git a/meshms_restful.c b/meshms_restful.c index d8fabc2a..14395073 100644 --- a/meshms_restful.c +++ b/meshms_restful.c @@ -33,6 +33,11 @@ static void finalise_union_meshms_messagelist(httpd_request *r) meshms_message_iterator_close(&r->u.msglist.iter); } +static void finalise_union_meshms_sendmessage(httpd_request *r) +{ + form_buf_malloc_release(&r->u.sendmsg.message); +} + #define MESHMS_TOKEN_STRLEN (BASE64_ENCODED_LEN(sizeof(rhizome_bid_t) + sizeof(uint64_t))) #define alloca_meshms_token(bid, offset) meshms_token_to_str(alloca(MESHMS_TOKEN_STRLEN + 1), (bid), (offset)) @@ -62,12 +67,14 @@ static int strn_to_meshms_token(const char *str, rhizome_bid_t *bidp, uint64_t * static HTTP_HANDLER restful_meshms_conversationlist_json; static HTTP_HANDLER restful_meshms_messagelist_json; static HTTP_HANDLER restful_meshms_newsince_messagelist_json; +static HTTP_HANDLER restful_meshms_sendmessage; int restful_meshms_(httpd_request *r, const char *remainder) { r->http.response.header.content_type = "application/json"; if (!is_rhizome_http_enabled()) return 403; + const char *verb = HTTP_VERB_GET; HTTP_HANDLER *handler = NULL; const char *end; if (strn_to_sid_t(&r->sid1, remainder, &end) != -1) { @@ -88,11 +95,16 @@ int restful_meshms_(httpd_request *r, const char *remainder) handler = restful_meshms_newsince_messagelist_json; remainder = ""; } + else if (strcmp(remainder, "/sendmessage") == 0) { + handler = restful_meshms_sendmessage; + verb = HTTP_VERB_POST; + remainder = ""; + } } } if (handler == NULL) return 404; - if (r->http.verb != HTTP_VERB_GET) + if (r->http.verb != verb) return 405; int ret = authorize(&r->http); if (ret) @@ -407,3 +419,88 @@ static int restful_meshms_messagelist_json_content_chunk(struct http_request *hr return 0; } +static HTTP_REQUEST_PARSER restful_meshms_sendmessage_end; +static int send_mime_part_start(struct http_request *); +static int send_mime_part_end(struct http_request *); +static int send_mime_part_header(struct http_request *, const struct mime_part_headers *); +static int send_mime_part_body(struct http_request *, char *, size_t); + +static int restful_meshms_sendmessage(httpd_request *r, const char *remainder) +{ + if (*remainder) + return 404; + assert(r->finalise_union == NULL); + r->finalise_union = finalise_union_meshms_sendmessage; + // Parse the request body as multipart/form-data. + assert(r->u.sendmsg.current_part == NULL); + assert(!r->u.sendmsg.received_message); + r->http.form_data.handle_mime_part_start = send_mime_part_start; + r->http.form_data.handle_mime_part_end = send_mime_part_end; + r->http.form_data.handle_mime_part_header = send_mime_part_header; + r->http.form_data.handle_mime_body = send_mime_part_body; + // Send the message once the body has arrived. + r->http.handle_content_end = restful_meshms_sendmessage_end; + return 1; +} + +static char PART_MESSAGE[] = "message"; + +static int send_mime_part_start(struct http_request *hr) +{ + httpd_request *r = (httpd_request *) hr; + assert(r->u.sendmsg.current_part == NULL); + return 0; +} + +static int send_mime_part_end(struct http_request *hr) +{ + httpd_request *r = (httpd_request *) hr; + if (r->u.sendmsg.current_part == PART_MESSAGE) { + if (r->u.sendmsg.message.length == 0) + return http_response_form_part(r, "Invalid (empty)", PART_MESSAGE, NULL, 0); + r->u.sendmsg.received_message = 1; + if (config.debug.httpd) + DEBUGF("received %s = %s", PART_MESSAGE, alloca_toprint(-1, r->u.sendmsg.message.buffer, r->u.sendmsg.message.length)); + } else + FATALF("current_part = %s", alloca_str_toprint(r->u.sendmsg.current_part)); + r->u.sendmsg.current_part = NULL; + return 0; +} + +static int send_mime_part_header(struct http_request *hr, const struct mime_part_headers *h) +{ + httpd_request *r = (httpd_request *) hr; + if (strcmp(h->content_disposition.name, PART_MESSAGE) == 0) { + if (r->u.sendmsg.received_message) + return http_response_form_part(r, "Duplicate", PART_MESSAGE, NULL, 0); + r->u.sendmsg.current_part = PART_MESSAGE; + form_buf_malloc_init(&r->u.sendmsg.message, MESHMS_MESSAGE_MAX_LEN); + } + else + return http_response_form_part(r, "Unsupported", h->content_disposition.name, NULL, 0); + return 0; +} + +static int send_mime_part_body(struct http_request *hr, char *buf, size_t len) +{ + httpd_request *r = (httpd_request *) hr; + if (r->u.sendmsg.current_part == PART_MESSAGE) { + form_buf_malloc_accumulate(r, PART_MESSAGE, &r->u.sendmsg.message, buf, len); + } else + FATALF("current_part = %s", alloca_str_toprint(r->u.sendmsg.current_part)); + return 0; +} + +static int restful_meshms_sendmessage_end(struct http_request *hr) +{ + httpd_request *r = (httpd_request *) hr; + if (!r->u.sendmsg.received_message) + return http_response_form_part(r, "Missing", PART_MESSAGE, NULL, 0); + assert(r->u.sendmsg.message.length > 0); + assert(r->u.sendmsg.message.length <= MESHMS_MESSAGE_MAX_LEN); + int ret = meshms_send_message(&r->sid1, &r->sid2, r->u.sendmsg.message.buffer, r->u.sendmsg.message.length); + if (ret == -1) + return 500; + http_request_simple_response(&r->http, 201, "Message sent"); + return 201; +} diff --git a/rhizome_restful.c b/rhizome_restful.c index de83f4c1..120dbbe0 100644 --- a/rhizome_restful.c +++ b/rhizome_restful.c @@ -31,10 +31,7 @@ static void finalise_union_read_state(httpd_request *r) static void finalise_union_rhizome_insert(httpd_request *r) { - if (r->u.insert.manifest_text) { - free(r->u.insert.manifest_text); - r->u.insert.manifest_text = NULL; - } + form_buf_malloc_release(&r->u.insert.manifest); if (r->u.insert.write.blob_fd != -1) rhizome_fail_write(&r->u.insert.write); } @@ -284,26 +281,16 @@ static int insert_mime_part_start(struct http_request *hr) return 0; } -static int http_response_form_part(httpd_request *r, const char *what, const char *partname, const char *text, size_t textlen) -{ - if (config.debug.rhizome) - DEBUGF("%s \"%s\" form part %s", what, partname, text ? alloca_toprint(-1, text, textlen) : ""); - strbuf msg = strbuf_alloca(100); - strbuf_sprintf(msg, "%s \"%s\" form part", what, partname); - http_request_simple_response(&r->http, 403, strbuf_str(msg)); - return 403; -} - static int insert_make_manifest(httpd_request *r) { if (!r->u.insert.received_manifest) return http_response_form_part(r, "Missing", PART_MANIFEST, NULL, 0); if ((r->manifest = rhizome_new_manifest())) { - if (r->u.insert.manifest_len == 0) + if (r->u.insert.manifest.length == 0) return 0; - assert(r->u.insert.manifest_len <= sizeof r->manifest->manifestdata); - memcpy(r->manifest->manifestdata, r->u.insert.manifest_text, r->u.insert.manifest_len); - r->manifest->manifest_all_bytes = r->u.insert.manifest_len; + assert(r->u.insert.manifest.length <= sizeof r->manifest->manifestdata); + memcpy(r->manifest->manifestdata, r->u.insert.manifest.buffer, r->u.insert.manifest.length); + r->manifest->manifest_all_bytes = r->u.insert.manifest.length; int n = rhizome_manifest_parse(r->manifest); switch (n) { case -1: @@ -330,19 +317,19 @@ static int insert_mime_part_header(struct http_request *hr, const struct mime_pa if (r->u.insert.received_author) return http_response_form_part(r, "Duplicate", PART_AUTHOR, NULL, 0); r->u.insert.current_part = PART_AUTHOR; + assert(r->u.insert.author_hex_len == 0); } else if (strcmp(h->content_disposition.name, PART_SECRET) == 0) { if (r->u.insert.received_secret) return http_response_form_part(r, "Duplicate", PART_SECRET, NULL, 0); r->u.insert.current_part = PART_SECRET; + assert(r->u.insert.secret_hex_len == 0); } else if (strcmp(h->content_disposition.name, PART_MANIFEST) == 0) { // Reject a request if it has a repeated manifest part. if (r->u.insert.received_manifest) return http_response_form_part(r, "Duplicate", PART_MANIFEST, NULL, 0); - assert(r->u.insert.manifest_text == NULL); - assert(r->u.insert.manifest_text_size == 0); - assert(r->u.insert.manifest_len == 0); + form_buf_malloc_init(&r->u.insert.manifest, MAX_MANIFEST_BYTES); if ( strcmp(h->content_type.type, "rhizome-manifest") != 0 || strcmp(h->content_type.subtype, "text") != 0 ) @@ -384,26 +371,6 @@ static int insert_mime_part_header(struct http_request *hr, const struct mime_pa return 0; } -static int accumulate_text(httpd_request *r, const char *partname, char *textbuf, size_t textsiz, size_t *textlenp, const char *buf, size_t len) -{ - if (len) { - size_t newlen = *textlenp + len; - if (newlen > textsiz) { - if (config.debug.rhizome) - DEBUGF("Form part \"%s\" too long, %zu bytes overflows maximum %zu by %zu", - partname, newlen, textsiz, (size_t)(newlen - textsiz) - ); - strbuf msg = strbuf_alloca(100); - strbuf_sprintf(msg, "Overflow in \"%s\" form part", partname); - http_request_simple_response(&r->http, 403, strbuf_str(msg)); - return 0; - } - memcpy(textbuf + *textlenp, buf, len); - *textlenp = newlen; - } - return 1; -} - static int insert_mime_part_body(struct http_request *hr, char *buf, size_t len) { httpd_request *r = (httpd_request *) hr; @@ -422,24 +389,7 @@ static int insert_mime_part_body(struct http_request *hr, char *buf, size_t len) buf, len); } else if (r->u.insert.current_part == PART_MANIFEST) { - if (len == 0) - return 0; - size_t newlen = r->u.insert.manifest_len + len; - if (newlen > MAX_MANIFEST_BYTES) { - if (config.debug.rhizome) - DEBUGF("manifest too large, %zu bytes overflows maximum %zu by %zu", - newlen, (size_t)MAX_MANIFEST_BYTES, (size_t)(newlen - MAX_MANIFEST_BYTES) - ); - http_request_simple_response(&r->http, 403, "Manifest size overflow"); - return 403; - } - if (newlen > r->u.insert.manifest_text_size) { - if ((r->u.insert.manifest_text = erealloc(r->u.insert.manifest_text, newlen)) == NULL) - return 500; - r->u.insert.manifest_text_size = newlen; - } - memcpy(r->u.insert.manifest_text + r->u.insert.manifest_len, buf, len); - r->u.insert.manifest_len = newlen; + form_buf_malloc_accumulate(r, PART_MANIFEST, &r->u.insert.manifest, buf, len); } else if (r->u.insert.current_part == PART_PAYLOAD) { r->u.insert.payload_size += len; diff --git a/strbuf_helpers.c b/strbuf_helpers.c index 50eaee1c..f57a82de 100644 --- a/strbuf_helpers.c +++ b/strbuf_helpers.c @@ -471,36 +471,49 @@ strbuf strbuf_json_boolean(strbuf sb, int boolean) return sb; } +static void _json_char(strbuf sb, char c) +{ + if (c == '"' || c == '\\') { + strbuf_putc(sb, '\\'); + strbuf_putc(sb, c); + } + else if (c == '\b') + strbuf_puts(sb, "\\b"); + else if (c == '\f') + strbuf_puts(sb, "\\f"); + else if (c == '\n') + strbuf_puts(sb, "\\n"); + else if (c == '\r') + strbuf_puts(sb, "\\r"); + else if (c == '\t') + strbuf_puts(sb, "\\t"); + else if (iscntrl(c)) + strbuf_sprintf(sb, "\\u%04X", (unsigned char) c); + else + strbuf_putc(sb, c); +} + strbuf strbuf_json_string(strbuf sb, const char *str) { if (str) { strbuf_putc(sb, '"'); - for (; *str; ++str) { - if (*str == '"' || *str == '\\') { - strbuf_putc(sb, '\\'); - strbuf_putc(sb, *str); - } - else if (*str == '\b') - strbuf_puts(sb, "\\b"); - else if (*str == '\f') - strbuf_puts(sb, "\\f"); - else if (*str == '\n') - strbuf_puts(sb, "\\n"); - else if (*str == '\r') - strbuf_puts(sb, "\\r"); - else if (*str == '\t') - strbuf_puts(sb, "\\t"); - else if (iscntrl(*str)) - strbuf_sprintf(sb, "\\u%04X", (unsigned char) *str); - else - strbuf_putc(sb, *str); - } + for (; *str; ++str) + _json_char(sb, *str); strbuf_putc(sb, '"'); } else strbuf_json_null(sb); return sb; } +strbuf strbuf_json_string_len(strbuf sb, const char *str, size_t strlen) +{ + strbuf_putc(sb, '"'); + for (; strlen; --strlen, ++str) + _json_char(sb, *str); + strbuf_putc(sb, '"'); + return sb; +} + strbuf strbuf_json_hex(strbuf sb, const unsigned char *buf, size_t len) { if (buf) { diff --git a/strbuf_helpers.h b/strbuf_helpers.h index 2a7a4a1a..1aa82e0c 100644 --- a/strbuf_helpers.h +++ b/strbuf_helpers.h @@ -167,7 +167,8 @@ strbuf strbuf_append_quoted_string(strbuf sb, const char *str); */ strbuf strbuf_json_null(strbuf sb); strbuf strbuf_json_boolean(strbuf sb, int boolean); -strbuf strbuf_json_string(strbuf sb, const char *str); +strbuf strbuf_json_string(strbuf sb, const char *str); // str can be NULL +strbuf strbuf_json_string_len(strbuf sb, const char *str, size_t strlen); // str cannot be NULL strbuf strbuf_json_hex(strbuf sb, const unsigned char *buf, size_t len); /* Append a representation of a struct http_range[] array. diff --git a/tests/rhizomehttp b/tests/rhizomehttp index 4118315f..c897627a 100755 --- a/tests/rhizomehttp +++ b/tests/rhizomehttp @@ -1196,8 +1196,28 @@ teardown_MeshmsListMessagesNewSinceArrival() { } doc_MeshmsSend="HTTP RESTful send MeshMS message" -Xtest_MeshmsSend() { - : +setup_MeshmsSend() { + IDENTITY_COUNT=2 + setup +} +test_MeshmsSend() { + executeOk curl \ + --silent --fail --show-error \ + --output sendmessage.json \ + --basic --user harry:potter \ + --form "message=Hello World" \ + "http://$addr_localhost:$PORTA/restful/meshms/$SIDA1/$SIDA2/sendmessage" + executeOk_servald meshms list messages $SIDA1 $SIDA2 + assertStdoutGrep --matches=1 ':>:Hello World' + executeOk curl \ + --silent --fail --show-error \ + --output sendmessage.json \ + --basic --user ron:weasley \ + --form "message=Hello back!" \ + "http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage" + executeOk_servald meshms list messages $SIDA1 $SIDA2 + assertStdoutGrep --matches=1 ':>:Hello World$' + assertStdoutGrep --matches=1 ':<:Hello back!$' } runTests "$@"