serval-dna/meshmb_restful.c

385 lines
12 KiB
C
Raw Normal View History

2016-10-05 00:17:51 +00:00
#include "serval.h"
#include "dataformats.h"
2016-10-05 00:17:51 +00:00
#include "conf.h"
#include "httpd.h"
#include "str.h"
#include "numeric_str.h"
#include "base64.h"
#include "strbuf_helpers.h"
#include "meshmb.h"
DEFINE_FEATURE(http_rest_meshmb);
static char *PART_MESSAGE = "message";
static int send_part_start(struct http_request *hr)
{
httpd_request *r = (httpd_request *) hr;
assert(r->u.sendmsg.current_part == NULL);
return 0;
}
static int send_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, 400, "Invalid (empty)", PART_MESSAGE, NULL, 0);
r->u.sendmsg.received_message = 1;
DEBUGF(httpd, "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_part_header(struct http_request *hr, const struct mime_part_headers *h)
{
httpd_request *r = (httpd_request *) hr;
if (!h->content_disposition.type[0])
return http_response_content_disposition(r, 415, "Missing", h->content_disposition.type);
if (strcmp(h->content_disposition.type, "form-data") != 0)
return http_response_content_disposition(r, 415, "Unsupported", h->content_disposition.type);
if (strcmp(h->content_disposition.name, PART_MESSAGE) == 0) {
if (r->u.sendmsg.received_message)
return http_response_form_part(r, 400, "Duplicate", PART_MESSAGE, NULL, 0);
r->u.sendmsg.current_part = PART_MESSAGE;
form_buf_malloc_init(&r->u.sendmsg.message, MESSAGE_PLY_MAX_LEN);
}
else
return http_response_form_part(r, 415, "Unsupported", h->content_disposition.name, NULL, 0);
if (!h->content_type.type[0] || !h->content_type.subtype[0])
return http_response_content_type(r, 400, "Missing", &h->content_type);
if (strcmp(h->content_type.type, "text") != 0 || strcmp(h->content_type.subtype, "plain") != 0)
return http_response_content_type(r, 415, "Unsupported", &h->content_type);
if (!h->content_type.charset[0])
return http_response_content_type(r, 400, "Missing charset", &h->content_type);
if (strcmp(h->content_type.charset, "utf-8") != 0)
return http_response_content_type(r, 415, "Unsupported charset", &h->content_type);
return 0;
}
static int send_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 send_content_end(struct http_request *hr)
{
httpd_request *r = (httpd_request *) hr;
if (!r->u.sendmsg.received_message)
return http_response_form_part(r, 400, "Missing", PART_MESSAGE, NULL, 0);
assert(r->u.sendmsg.message.length > 0);
assert(r->u.sendmsg.message.length <= MESSAGE_PLY_MAX_LEN);
keyring_identity *id = keyring_find_identity(keyring, &r->bid);
if (!id){
http_request_simple_response(&r->http, 500, "TODO, detailed errors");
return 500;
}
if (meshmb_send(id, r->u.sendmsg.message.buffer, r->u.sendmsg.message.length, 0, NULL)==-1){
http_request_simple_response(&r->http, 500, "TODO, detailed errors");
return 500;
}
http_request_simple_response(&r->http, 201, "TODO, detailed response");
return 201;
}
static void send_finalise(httpd_request *r)
{
form_buf_malloc_release(&r->u.sendmsg.message);
}
static int restful_meshmb_send(httpd_request *r, const char *remainder)
{
if (*remainder)
return 404;
assert(r->finalise_union == NULL);
r->finalise_union = send_finalise;
// 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_part_start;
r->http.form_data.handle_mime_part_end = send_part_end;
r->http.form_data.handle_mime_part_header = send_part_header;
r->http.form_data.handle_mime_body = send_part_body;
// Send the message once the body has arrived.
r->http.handle_content_end = send_content_end;
return 1;
}
static strbuf position_token_to_str(strbuf b, uint64_t position)
{
uint8_t tmp[12];
char tmp_str[BASE64_ENCODED_LEN(12)+1];
int len = pack_uint(tmp, position);
assert(len <= (int)sizeof tmp);
size_t n = base64url_encode(tmp_str, tmp, len);
tmp_str[n] = '\0';
return strbuf_puts(b, tmp_str);
}
static int strn_to_position_token(const char *str, uint64_t *position, const char **afterp)
{
uint8_t token[12];
size_t token_len = base64url_decode(token, sizeof token, str, 0, afterp, 0, NULL);
int unpacked;
if ((unpacked = unpack_uint(token, token_len, position))!=-1
&& **afterp=='/'){
(*afterp)++;
} else {
*position = 0;
*afterp=str;
}
return 1;
}
static int next_ply_message(httpd_request *r){
if (!message_ply_is_open(&r->u.plylist.ply_reader)){
if (message_ply_read_open(&r->u.plylist.ply_reader, &r->bid)==-1){
r->u.plylist.eof = 1;
return -1;
}
// skip back to where we were
if (r->u.plylist.current_offset)
r->u.plylist.ply_reader.read.offset = r->u.plylist.current_offset;
DEBUGF(httpd, "Opened ply @%"PRIu64, r->u.plylist.ply_reader.read.offset);
}
if (r->u.plylist.current_offset==0){
// enumerate everything from the top
DEBUGF(httpd, "Started reading @%"PRIu64, r->u.plylist.ply_reader.read.length);
r->u.plylist.current_offset =
r->u.plylist.start_offset =
r->u.plylist.ply_reader.read.offset =
r->u.plylist.ply_reader.read.length;
}
while(message_ply_read_prev(&r->u.plylist.ply_reader) == 0){
r->u.plylist.current_offset = r->u.plylist.ply_reader.record_end_offset;
if (r->u.plylist.current_offset <= r->u.plylist.end_offset){
DEBUGF(httpd, "Hit end %"PRIu64" @%"PRIu64,
r->u.plylist.end_offset, r->u.plylist.current_offset);
break;
}
switch(r->u.plylist.ply_reader.type){
case MESSAGE_BLOCK_TYPE_TIME:
if (r->u.plylist.ply_reader.record_length<4){
WARN("Malformed ply, expected 4 byte timestamp");
continue;
}
r->u.plylist.timestamp = read_uint32(r->u.plylist.ply_reader.record);
break;
case MESSAGE_BLOCK_TYPE_MESSAGE:
r->u.plylist.eof = 0;
return 1;
case MESSAGE_BLOCK_TYPE_ACK:
// TODO, link to some other ply?
break;
default:
//ignore unknown types
break;
}
}
r->u.plylist.eof = 1;
return 0;
}
static int restful_meshmb_list_json_content_chunk(struct http_request *hr, strbuf b)
{
httpd_request *r = (httpd_request *) hr;
// The "my_sid" and "their_sid" per-conversation fields allow the same JSON structure to be used
// in a future, non-SID-specific request, eg, to list all conversations for all currently open
// identities.
const char *headers[] = {
"offset",
"token",
"text",
"timestamp"
};
DEBUGF(httpd, "Phase %d", r->u.plylist.phase);
switch (r->u.plylist.phase) {
case LIST_HEADER:
strbuf_puts(b, "{\n");
// open the ply now in order to read the manifest name
if (!message_ply_is_open(&r->u.plylist.ply_reader))
next_ply_message(r);
if (r->u.plylist.ply_reader.name)
strbuf_sprintf(b, "\"name\":\"%s\",\n", r->u.plylist.ply_reader.name);
strbuf_puts(b, "\"header\":[");
unsigned i;
for (i = 0; i != NELS(headers); ++i) {
if (i)
strbuf_putc(b, ',');
strbuf_json_string(b, headers[i]);
}
strbuf_puts(b, "],\n\"rows\":[");
if (!strbuf_overrun(b))
r->u.plylist.phase = LIST_ROWS;
return 1;
ROWS:
case LIST_ROWS:
case LIST_FIRST:
if (!message_ply_is_open(&r->u.plylist.ply_reader)){
// re-load the current message text
if (next_ply_message(r)!=1)
goto END;
} else if (r->u.plylist.eof)
goto END;
if (r->u.plylist.rowcount!=0)
strbuf_putc(b, ',');
strbuf_puts(b, "\n[");
strbuf_sprintf(b, "%"PRIu64, r->u.plylist.current_offset);
strbuf_puts(b, ",\"");
position_token_to_str(b, r->u.plylist.current_offset);
strbuf_puts(b, "\",");
strbuf_json_string(b, (const char *)r->u.plylist.ply_reader.record);
strbuf_putc(b, ',');
strbuf_sprintf(b, "%d", r->u.plylist.timestamp);
strbuf_puts(b, "]");
if (!strbuf_overrun(b)) {
++r->u.plylist.rowcount;
if (next_ply_message(r)!=1)
r->u.plylist.phase = LIST_END;
}
return 1;
END:
r->u.plylist.phase = LIST_END;
case LIST_END:
{
time_ms_t now;
// during a new-since request, we don't really want to end until the time limit has elapsed
if (r->u.plylist.end_time && (now = gettime_ms()) < r->u.plylist.end_time) {
// where we started this time, will become where we end on the next pass;
r->u.plylist.end_offset = r->u.plylist.start_offset;
r->u.plylist.current_offset = 0;
r->u.plylist.phase = LIST_ROWS;
if (r->u.plylist.ply_reader.read.length > r->u.plylist.start_offset && next_ply_message(r)==1)
// new content arrived while we were iterating, we can resume immediately
goto ROWS;
message_ply_read_close(&r->u.plylist.ply_reader);
http_request_pause_response(&r->http, r->u.plylist.end_time);
return 0;
}
}
strbuf_puts(b, "\n]\n}\n");
if (!strbuf_overrun(b))
r->u.plylist.phase = LIST_DONE;
// fall through...
case LIST_DONE:
return 0;
}
abort();
return 0;
}
static int restful_meshmb_list_json_content(struct http_request *hr, unsigned char *buf, size_t bufsz, struct http_content_generator_result *result)
{
return generate_http_content_from_strbuf_chunks(hr, (char *)buf, bufsz, result, restful_meshmb_list_json_content_chunk);
}
static void list_on_rhizome_add(httpd_request *r, rhizome_manifest *m)
{
if (strcmp(m->service, RHIZOME_SERVICE_MESHMB) == 0
&& cmp_rhizome_bid_t(&m->keypair.public_key, &r->bid)==0) {
message_ply_read_close(&r->u.plylist.ply_reader);
http_request_resume_response(&r->http);
}
}
static void list_finalise(httpd_request *r)
{
message_ply_read_close(&r->u.plylist.ply_reader);
}
static int restful_meshmb_list(httpd_request *r, const char *remainder)
{
if (*remainder)
return 404;
assert(r->finalise_union == NULL);
r->finalise_union = list_finalise;
r->trigger_rhizome_bundle_added = list_on_rhizome_add;
r->u.plylist.phase = LIST_HEADER;
r->u.plylist.rowcount = 0;
r->u.plylist.end_offset = r->ui64;
http_request_response_generated(&r->http, 200, CONTENT_TYPE_JSON, restful_meshmb_list_json_content);
return 1;
}
static int restful_meshmb_newsince_list(httpd_request *r, const char *remainder)
{
int ret;
if ((ret = restful_meshmb_list(r, remainder))==1){
r->u.plylist.end_time = gettime_ms() + config.api.restful.newsince_timeout * 1000;
}
return ret;
}
2016-10-05 00:17:51 +00:00
DECLARE_HANDLER("/restful/meshmb/", restful_meshmb_);
static int restful_meshmb_(httpd_request *r, const char *remainder)
{
r->http.response.header.content_type = CONTENT_TYPE_JSON;
if (!is_rhizome_http_enabled())
return 404;
int ret = authorize_restful(&r->http);
if (ret)
return ret;
const char *verb = HTTP_VERB_GET;
HTTP_HANDLER *handler = NULL;
const char *end;
if (strn_to_identity_t(&r->bid, remainder, &end) != -1) {
remainder = end;
if (strcmp(remainder, "/sendmessage") == 0) {
handler = restful_meshmb_send;
verb = HTTP_VERB_POST;
remainder = "";
} else if (strcmp(remainder, "/messagelist.json") == 0) {
handler = restful_meshmb_list;
remainder = "";
r->ui64 = 0;
} else if ( str_startswith(remainder, "/newsince/", &end)
&& strn_to_position_token(end, &r->ui64, &end)
&& strcmp(end, "messagelist.json") == 0) {
handler = restful_meshmb_newsince_list;
remainder = "";
2016-10-05 00:17:51 +00:00
}
}
if (handler == NULL)
return 404;
if (r->http.verb != verb)
return 405;
return handler(r, remainder);
}