mirror of
https://github.com/servalproject/serval-dna.git
synced 2024-12-29 17:38:59 +00:00
c35af0617a
- make "debug.h" a private header so that the DEBUG() macro does not interfere with Xcode Debug builds, which set DEBUG=1 - move all #include "debug.h" from headers into .c files - move 'struct idebug' into a new public header "idebug.h" so that "log.h" can be public - move HTTPD port number defs from "httpd.h" to "constants.h", so that "httpd.h" can be private and "conf.h" can be public - add missing nested includes so each header compiles stand-alone without error - #include "sodium.h" instead of <sodium.h> - #include "sqlite3.h" instead of <sqlite3.h> - add header guard to "fifo.h" - fix header guard in "sync_keys.h"
738 lines
26 KiB
C
738 lines
26 KiB
C
/*
|
|
Serval DNA MeshMS HTTP RESTful interface
|
|
Copyright (C) 2013,2014 Serval Project Inc.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "serval.h"
|
|
#include "conf.h"
|
|
#include "httpd.h"
|
|
#include "str.h"
|
|
#include "numeric_str.h"
|
|
#include "base64.h"
|
|
#include "strbuf_helpers.h"
|
|
#include "debug.h"
|
|
|
|
DEFINE_FEATURE(http_rest_meshms);
|
|
|
|
DECLARE_HANDLER("/restful/meshms/", restful_meshms_);
|
|
|
|
static void on_rhizome_bundle_added(httpd_request *r, rhizome_manifest *m);
|
|
|
|
static void finalise_union_meshms_conversationlist(httpd_request *r)
|
|
{
|
|
meshms_free_conversations(r->u.mclist.conv);
|
|
r->u.mclist.conv = NULL;
|
|
}
|
|
|
|
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 MAX_TOKEN_LEN 21
|
|
#define MESHMS_TOKEN_STRLEN (BASE64_ENCODED_LEN(MAX_TOKEN_LEN))
|
|
#define alloca_meshms_token(pos) meshms_token_to_str(alloca(MESHMS_TOKEN_STRLEN + 1), (pos))
|
|
|
|
static char *meshms_token_to_str(char *buf, struct meshms_position *pos)
|
|
{
|
|
uint8_t tmp[MAX_TOKEN_LEN];
|
|
int ofs = 0;
|
|
tmp[ofs++]=pos->which_ply;
|
|
ofs += pack_uint(tmp + ofs, pos->offset);
|
|
ofs += pack_uint(tmp + ofs, pos->their_ack);
|
|
assert(ofs <= MAX_TOKEN_LEN);
|
|
size_t n = base64url_encode(buf, tmp, ofs);
|
|
assert(n <= MESHMS_TOKEN_STRLEN);
|
|
buf[n] = '\0';
|
|
return buf;
|
|
}
|
|
|
|
static int strn_to_meshms_token(const char *str, struct meshms_position *pos, const char **afterp)
|
|
{
|
|
uint8_t token[MAX_TOKEN_LEN];
|
|
size_t token_len = base64url_decode(token, sizeof token, str, 0, afterp, 0, NULL);
|
|
if (token_len < 3)
|
|
goto blank;
|
|
|
|
size_t ofs=0;
|
|
pos->which_ply = token[ofs++];
|
|
int unpacked;
|
|
if ((unpacked = unpack_uint(token + ofs, token_len - ofs, &pos->offset))==-1)
|
|
goto blank;
|
|
|
|
ofs += unpacked;
|
|
if ((unpacked = unpack_uint(token + ofs, token_len - ofs, &pos->their_ack))==-1)
|
|
goto blank;
|
|
|
|
(*afterp)++;
|
|
return 1;
|
|
|
|
blank:
|
|
// don't skip the token
|
|
*afterp=str;
|
|
pos->which_ply = MY_PLY;
|
|
pos->offset = 0;
|
|
pos->their_ack = 0;
|
|
return 1;
|
|
}
|
|
|
|
static int http_request_meshms_response(struct httpd_request *r, uint16_t result, const char *message, enum meshms_status status)
|
|
{
|
|
uint16_t meshms_result = 0;
|
|
const char *meshms_message = NULL;
|
|
switch (status) {
|
|
case MESHMS_STATUS_OK:
|
|
meshms_result = 200;
|
|
break;
|
|
case MESHMS_STATUS_UPDATED:
|
|
meshms_result = 201; // Created
|
|
meshms_message = "Updated";
|
|
break;
|
|
case MESHMS_STATUS_SID_LOCKED:
|
|
meshms_result = 419; // Authentication Timeout
|
|
meshms_message = "Identity locked";
|
|
break;
|
|
case MESHMS_STATUS_PROTOCOL_FAULT:
|
|
meshms_result = 500;
|
|
meshms_message = "MeshMS protocol fault";
|
|
break;
|
|
case MESHMS_STATUS_ERROR:
|
|
meshms_result = 500;
|
|
break;
|
|
}
|
|
if (meshms_result == 0) {
|
|
WHYF("Invalid MeshMS status code %d", status);
|
|
meshms_result = 500;
|
|
}
|
|
r->http.response.result_extra[0].label = "meshms_status_code";
|
|
r->http.response.result_extra[0].value.type = JSON_INTEGER;
|
|
r->http.response.result_extra[0].value.u.integer = status;
|
|
const char *status_message = meshms_status_message(status);
|
|
if (status_message) {
|
|
r->http.response.result_extra[1].label = "meshms_status_message";
|
|
r->http.response.result_extra[1].value.type = JSON_STRING_NULTERM;
|
|
r->http.response.result_extra[1].value.u.string.content = status_message;
|
|
}
|
|
if (meshms_result > result) {
|
|
result = meshms_result;
|
|
message = meshms_message;
|
|
}
|
|
assert(result != 0);
|
|
http_request_simple_response(&r->http, result, message);
|
|
return result;
|
|
}
|
|
|
|
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;
|
|
static HTTP_HANDLER restful_meshms_read_all_conversations;
|
|
static HTTP_HANDLER restful_meshms_read_all_messages;
|
|
static HTTP_HANDLER restful_meshms_read_to_offset;
|
|
|
|
static int restful_meshms_(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_size_t content_length = CONTENT_LENGTH_UNKNOWN;
|
|
HTTP_HANDLER *handler = NULL;
|
|
const char *end;
|
|
if (parse_sid_t(&r->sid1, remainder, -1, &end) != -1) {
|
|
remainder = end;
|
|
if (strcmp(remainder, "/conversationlist.json") == 0) {
|
|
handler = restful_meshms_conversationlist_json;
|
|
remainder = "";
|
|
}
|
|
else if (strcmp(remainder, "/readall") == 0) {
|
|
handler = restful_meshms_read_all_conversations;
|
|
verb = HTTP_VERB_POST;
|
|
content_length = 0;
|
|
remainder = "";
|
|
}
|
|
else if (*remainder == '/' && parse_sid_t(&r->sid2, remainder + 1, -1, &end) != -1) {
|
|
remainder = end;
|
|
if (strcmp(remainder, "/messagelist.json") == 0) {
|
|
handler = restful_meshms_messagelist_json;
|
|
remainder = "";
|
|
}
|
|
else if (strcmp(remainder, "/newsince/messagelist.json") == 0){
|
|
handler = restful_meshms_newsince_messagelist_json;
|
|
remainder = "";
|
|
r->u.msglist.token.which_ply = MY_PLY;
|
|
r->u.msglist.token.offset = 0;
|
|
r->u.msglist.token.their_ack = 0;
|
|
}
|
|
else if ( str_startswith(remainder, "/newsince/", &end)
|
|
&& strn_to_meshms_token(end, &r->u.msglist.token, &end)
|
|
&& strcmp(end, "messagelist.json") == 0
|
|
) {
|
|
handler = restful_meshms_newsince_messagelist_json;
|
|
remainder = "";
|
|
}
|
|
else if (strcmp(remainder, "/sendmessage") == 0) {
|
|
handler = restful_meshms_sendmessage;
|
|
verb = HTTP_VERB_POST;
|
|
remainder = "";
|
|
}
|
|
else if (strcmp(remainder, "/readall") == 0) {
|
|
handler = restful_meshms_read_all_messages;
|
|
verb = HTTP_VERB_POST;
|
|
content_length = 0;
|
|
remainder = "";
|
|
}
|
|
else if (str_startswith(remainder, "/recv/", &end)) {
|
|
remainder = end;
|
|
if (str_to_uint64(remainder, 10, &r->ui64, &end)) {
|
|
remainder = end;
|
|
if (strcmp(remainder, "/read") == 0) {
|
|
handler = restful_meshms_read_to_offset;
|
|
verb = HTTP_VERB_POST;
|
|
content_length = 0;
|
|
remainder = "";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (handler == NULL)
|
|
return 404;
|
|
if ( content_length != CONTENT_LENGTH_UNKNOWN
|
|
&& r->http.request_header.content_length != CONTENT_LENGTH_UNKNOWN
|
|
&& r->http.request_header.content_length != content_length) {
|
|
http_request_simple_response(&r->http, 400, "Bad content length");
|
|
return 400;
|
|
}
|
|
if (r->http.verb != verb)
|
|
return 405;
|
|
return handler(r, remainder);
|
|
}
|
|
|
|
static HTTP_CONTENT_GENERATOR restful_meshms_conversationlist_json_content;
|
|
|
|
static int restful_meshms_conversationlist_json(httpd_request *r, const char *remainder)
|
|
{
|
|
if (*remainder)
|
|
return 404;
|
|
assert(r->finalise_union == NULL);
|
|
r->finalise_union = finalise_union_meshms_conversationlist;
|
|
r->u.mclist.phase = LIST_HEADER;
|
|
r->u.mclist.rowcount = 0;
|
|
r->u.mclist.conv = NULL;
|
|
enum meshms_status status;
|
|
if (meshms_failed(status = meshms_conversations_list(NULL, &r->sid1, &r->u.mclist.conv)))
|
|
return http_request_meshms_response(r, 0, NULL, status);
|
|
if (r->u.mclist.conv != NULL)
|
|
meshms_conversation_iterator_start(&r->u.mclist.iter, r->u.mclist.conv);
|
|
http_request_response_generated(&r->http, 200, &CONTENT_TYPE_JSON, restful_meshms_conversationlist_json_content);
|
|
return 1;
|
|
}
|
|
|
|
static HTTP_CONTENT_GENERATOR_STRBUF_CHUNKER restful_meshms_conversationlist_json_content_chunk;
|
|
|
|
static int restful_meshms_conversationlist_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_meshms_conversationlist_json_content_chunk);
|
|
}
|
|
|
|
static int restful_meshms_conversationlist_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[] = {
|
|
"_id",
|
|
"my_sid",
|
|
"their_sid",
|
|
"read",
|
|
"last_message",
|
|
"read_offset"
|
|
};
|
|
switch (r->u.mclist.phase) {
|
|
case LIST_HEADER:
|
|
strbuf_puts(b, "{\n\"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.mclist.phase = LIST_FIRST;
|
|
return 1;
|
|
|
|
case LIST_ROWS:
|
|
case LIST_FIRST:
|
|
if (r->u.mclist.conv == NULL || r->u.mclist.iter.current == NULL) {
|
|
r->u.mclist.phase = LIST_END;
|
|
// fall through...
|
|
} else {
|
|
if (r->u.mclist.rowcount!=0)
|
|
strbuf_putc(b, ',');
|
|
strbuf_puts(b, "\n[");
|
|
strbuf_sprintf(b, "%zu", r->u.mclist.rowcount);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_hex(b, r->sid1.binary, sizeof r->sid1.binary);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_hex(b, r->u.mclist.iter.current->them.binary, sizeof r->u.mclist.iter.current->them.binary);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_boolean(b, r->u.mclist.iter.current->metadata.read_offset >= r->u.mclist.iter.current->metadata.their_last_message);
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%"PRIu64, r->u.mclist.iter.current->metadata.their_last_message);
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%"PRIu64, r->u.mclist.iter.current->metadata.read_offset);
|
|
strbuf_puts(b, "]");
|
|
if (!strbuf_overrun(b)) {
|
|
r->u.mclist.phase=LIST_ROWS;
|
|
meshms_conversation_iterator_advance(&r->u.mclist.iter);
|
|
++r->u.mclist.rowcount;
|
|
}
|
|
return 1;
|
|
}
|
|
// fall through...
|
|
case LIST_END:
|
|
strbuf_puts(b, "\n]\n}\n");
|
|
if (!strbuf_overrun(b))
|
|
r->u.mclist.phase = LIST_DONE;
|
|
// fall through...
|
|
case LIST_DONE:
|
|
return 0;
|
|
}
|
|
abort();
|
|
return 0;
|
|
}
|
|
|
|
static HTTP_CONTENT_GENERATOR restful_meshms_messagelist_json_content;
|
|
|
|
static enum meshms_status reopen_meshms_message_iterator(httpd_request *r)
|
|
{
|
|
if (r->u.msglist.dirty) {
|
|
meshms_message_iterator_close(&r->u.msglist.iter);
|
|
r->u.msglist.dirty = 0;
|
|
enum meshms_status status;
|
|
if ( meshms_failed(status = meshms_message_iterator_open(&r->u.msglist.iter, &r->sid1, &r->sid2))
|
|
|| meshms_failed(status = meshms_message_iterator_prev(&r->u.msglist.iter))
|
|
)
|
|
return status;
|
|
r->u.msglist.finished = status != MESHMS_STATUS_UPDATED;
|
|
if (!r->u.msglist.finished) {
|
|
r->u.msglist.latest.which_ply = r->u.msglist.iter.which_ply;
|
|
r->u.msglist.latest.offset = r->u.msglist.iter.which_ply == MY_PLY ? r->u.msglist.iter.my_offset : r->u.msglist.iter.their_offset;
|
|
r->u.msglist.current.their_ack = r->u.msglist.latest.their_ack = r->u.msglist.iter.metadata.their_last_ack;
|
|
}
|
|
}
|
|
return MESHMS_STATUS_OK;
|
|
}
|
|
|
|
static int restful_meshms_messagelist_json(httpd_request *r, const char *remainder)
|
|
{
|
|
if (*remainder)
|
|
return 404;
|
|
assert(r->finalise_union == NULL);
|
|
r->finalise_union = finalise_union_meshms_messagelist;
|
|
r->u.msglist.rowcount = 0;
|
|
r->u.msglist.phase = LIST_HEADER;
|
|
r->u.msglist.token.which_ply = NEITHER_PLY;
|
|
r->u.msglist.token.offset = 0;
|
|
r->u.msglist.end_time = 0;
|
|
r->u.msglist.dirty = 1;
|
|
enum meshms_status status;
|
|
if (meshms_failed(status = reopen_meshms_message_iterator(r)))
|
|
return http_request_meshms_response(r, 0, NULL, status);
|
|
http_request_response_generated(&r->http, 200, &CONTENT_TYPE_JSON, restful_meshms_messagelist_json_content);
|
|
return 1;
|
|
}
|
|
|
|
static int restful_meshms_newsince_messagelist_json(httpd_request *r, const char *remainder)
|
|
{
|
|
if (*remainder)
|
|
return 404;
|
|
assert(r->finalise_union == NULL);
|
|
r->finalise_union = finalise_union_meshms_messagelist;
|
|
r->trigger_rhizome_bundle_added = on_rhizome_bundle_added;
|
|
r->u.msglist.rowcount = 0;
|
|
r->u.msglist.phase = LIST_HEADER;
|
|
r->u.msglist.dirty = 1;
|
|
enum meshms_status status;
|
|
if (meshms_failed(status = reopen_meshms_message_iterator(r)))
|
|
return http_request_meshms_response(r, 0, NULL, status);
|
|
r->u.msglist.end_time = gettime_ms() + config.api.restful.newsince_timeout * 1000;
|
|
http_request_response_generated(&r->http, 200, &CONTENT_TYPE_JSON, restful_meshms_messagelist_json_content);
|
|
return 1;
|
|
}
|
|
|
|
static void on_rhizome_bundle_added(httpd_request *r, rhizome_manifest *m)
|
|
{
|
|
if (strcmp(m->service, RHIZOME_SERVICE_MESHMS2) == 0) {
|
|
if ( (cmp_sid_t(&m->sender, &r->sid1) == 0 && cmp_sid_t(&m->recipient, &r->sid2) == 0)
|
|
|| (cmp_sid_t(&m->sender, &r->sid2) == 0 && cmp_sid_t(&m->recipient, &r->sid1) == 0)
|
|
) {
|
|
r->u.msglist.dirty = 1;
|
|
http_request_resume_response(&r->http);
|
|
}
|
|
}
|
|
}
|
|
|
|
static HTTP_CONTENT_GENERATOR_STRBUF_CHUNKER restful_meshms_messagelist_json_content_chunk;
|
|
|
|
static int restful_meshms_messagelist_json_content(struct http_request *hr, unsigned char *buf, size_t bufsz, struct http_content_generator_result *result)
|
|
{
|
|
httpd_request *r = (httpd_request *) hr;
|
|
if (meshms_failed(reopen_meshms_message_iterator(r)))
|
|
return -1;
|
|
return generate_http_content_from_strbuf_chunks(hr, (char *)buf, bufsz, result, restful_meshms_messagelist_json_content_chunk);
|
|
}
|
|
|
|
static void _messagelist_json_ack(struct httpd_request *r, strbuf b);
|
|
|
|
static int restful_meshms_messagelist_json_content_chunk(struct http_request *hr, strbuf b)
|
|
{
|
|
httpd_request *r = (httpd_request *) hr;
|
|
// Include "my_sid" and "their_sid" per-message, so that the same JSON structure can be used by a
|
|
// future, non-SID-specific request (eg, to get all messages for all currently open identities).
|
|
const char *headers[] = {
|
|
"type",
|
|
"my_sid",
|
|
"their_sid",
|
|
"my_offset",
|
|
"their_offset",
|
|
"token",
|
|
"text",
|
|
"delivered",
|
|
"read",
|
|
"timestamp",
|
|
"ack_offset"
|
|
};
|
|
switch (r->u.msglist.phase) {
|
|
case LIST_HEADER:
|
|
strbuf_puts(b, "{\n");
|
|
if (!r->u.msglist.end_time) {
|
|
strbuf_sprintf(b, "\"read_offset\":%"PRIu64",\n\"latest_ack_offset\":%"PRIu64",\n",
|
|
r->u.msglist.iter.metadata.read_offset,
|
|
r->u.msglist.iter.metadata.their_last_ack
|
|
);
|
|
}
|
|
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.msglist.phase = LIST_ROWS;
|
|
return 1;
|
|
case LIST_FIRST:
|
|
case LIST_ROWS:
|
|
{
|
|
r->u.msglist.current.which_ply = r->u.msglist.iter.which_ply;
|
|
r->u.msglist.current.offset = (r->u.msglist.iter.which_ply == MY_PLY) ? r->u.msglist.iter.my_offset : r->u.msglist.iter.their_offset;
|
|
if ( r->u.msglist.finished
|
|
|| (r->u.msglist.token.which_ply == r->u.msglist.current.which_ply && r->u.msglist.current.offset <= r->u.msglist.token.offset)
|
|
) {
|
|
time_ms_t now;
|
|
if (r->u.msglist.end_time && (now = gettime_ms()) < r->u.msglist.end_time) {
|
|
// If messages before the requested token have now been acked, add an extra ack record to the end
|
|
if (r->u.msglist.token.their_ack != r->u.msglist.latest.their_ack){
|
|
r->u.msglist.current.their_ack = r->u.msglist.latest.their_ack;
|
|
_messagelist_json_ack(r, b);
|
|
if (strbuf_overrun(b))
|
|
return 1;
|
|
++r->u.msglist.rowcount;
|
|
}
|
|
r->u.msglist.token = r->u.msglist.latest;
|
|
meshms_message_iterator_close(&r->u.msglist.iter);
|
|
r->u.msglist.dirty = 1;
|
|
http_request_pause_response(&r->http, r->u.msglist.end_time);
|
|
return 0;
|
|
}
|
|
r->u.msglist.phase = LIST_END;
|
|
} else {
|
|
int rows=0;
|
|
|
|
switch (r->u.msglist.iter.type) {
|
|
case MESSAGE_SENT:
|
|
// if you haven't seen the current ack && this is the message that was acked.
|
|
// output the ack now
|
|
if (r->u.msglist.token.their_ack != r->u.msglist.latest.their_ack &&
|
|
r->u.msglist.current.offset <= r->u.msglist.latest.their_ack){
|
|
_messagelist_json_ack(r, b);
|
|
if (!strbuf_overrun(b)){
|
|
++r->u.msglist.rowcount;
|
|
r->u.msglist.token.their_ack = r->u.msglist.latest.their_ack;
|
|
r->u.msglist.current.their_ack = 0;
|
|
}
|
|
return 1;
|
|
}
|
|
if (r->u.msglist.rowcount != 0)
|
|
strbuf_putc(b, ',');
|
|
strbuf_puts(b, "\n[");
|
|
strbuf_json_string(b, ">");
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_hex(b, r->u.msglist.iter.my_sid.binary, sizeof r->u.msglist.iter.my_sid.binary);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_hex(b, r->u.msglist.iter.their_sid.binary, sizeof r->u.msglist.iter.their_sid.binary);
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%"PRIu64, r->u.msglist.iter.my_offset);
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%"PRIu64, r->u.msglist.iter.their_offset);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_string(b, alloca_meshms_token(&r->u.msglist.current));
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_string(b, r->u.msglist.iter.text);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_boolean(b, r->u.msglist.iter.delivered);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_boolean(b, 0);
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%d", r->u.msglist.iter.timestamp);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_null(b);
|
|
strbuf_puts(b, "]");
|
|
rows++;
|
|
break;
|
|
case MESSAGE_RECEIVED:
|
|
if (r->u.msglist.rowcount != 0)
|
|
strbuf_putc(b, ',');
|
|
strbuf_puts(b, "\n[");
|
|
strbuf_json_string(b, "<");
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_hex(b, r->u.msglist.iter.my_sid.binary, sizeof r->u.msglist.iter.my_sid.binary);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_hex(b, r->u.msglist.iter.their_sid.binary, sizeof r->u.msglist.iter.their_sid.binary);
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%"PRIu64, r->u.msglist.iter.my_offset);
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%"PRIu64, r->u.msglist.iter.their_offset);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_string(b, alloca_meshms_token(&r->u.msglist.current));
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_string(b, r->u.msglist.iter.text);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_boolean(b, 1);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_boolean(b, r->u.msglist.iter.read);
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%d", r->u.msglist.iter.timestamp);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_null(b);
|
|
strbuf_puts(b, "]");
|
|
rows++;
|
|
break;
|
|
case ACK_RECEIVED:
|
|
break;
|
|
}
|
|
if (!strbuf_overrun(b)) {
|
|
r->u.msglist.rowcount+=rows;
|
|
enum meshms_status status;
|
|
if (meshms_failed(status = meshms_message_iterator_prev(&r->u.msglist.iter)))
|
|
return http_request_meshms_response(r, 0, NULL, status);
|
|
r->u.msglist.finished = status != MESHMS_STATUS_UPDATED;
|
|
}
|
|
return 1;
|
|
}
|
|
r->u.msglist.phase = LIST_END;
|
|
}
|
|
// fall through...
|
|
case LIST_END:
|
|
strbuf_puts(b, "\n]\n}\n");
|
|
if (!strbuf_overrun(b))
|
|
r->u.msglist.phase = LIST_DONE;
|
|
// fall through...
|
|
case LIST_DONE:
|
|
return 0;
|
|
}
|
|
abort();
|
|
return 0;
|
|
}
|
|
|
|
static void _messagelist_json_ack(struct httpd_request *r, strbuf b)
|
|
{
|
|
if (r->u.msglist.rowcount != 0)
|
|
strbuf_putc(b, ',');
|
|
strbuf_puts(b, "\n[");
|
|
strbuf_json_string(b, "ACK");
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_hex(b, r->u.msglist.iter.my_sid.binary, sizeof r->u.msglist.iter.my_sid.binary);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_hex(b, r->u.msglist.iter.their_sid.binary, sizeof r->u.msglist.iter.their_sid.binary);
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%"PRIu64, r->u.msglist.iter.my_offset);
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%"PRIu64, r->u.msglist.iter.metadata.their_last_ack_offset);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_string(b, alloca_meshms_token(&r->u.msglist.current));
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_null(b);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_boolean(b, 1);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_boolean(b, 0);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_null(b); // no timestamp on ACKs
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%"PRIu64, r->u.msglist.iter.metadata.their_last_ack);
|
|
strbuf_puts(b, "]");
|
|
}
|
|
|
|
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, 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_mime_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_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, 400, "Missing", PART_MESSAGE, NULL, 0);
|
|
assert(r->u.sendmsg.message.length > 0);
|
|
assert(r->u.sendmsg.message.length <= MESSAGE_PLY_MAX_LEN);
|
|
enum meshms_status status;
|
|
if (meshms_failed(status = meshms_send_message(&r->sid1, &r->sid2, r->u.sendmsg.message.buffer, r->u.sendmsg.message.length)))
|
|
return http_request_meshms_response(r, 0, NULL, status);
|
|
return http_request_meshms_response(r, 201, "Message sent", status);
|
|
}
|
|
|
|
static int restful_meshms_read_all_conversations(httpd_request *r, const char *remainder)
|
|
{
|
|
if (*remainder)
|
|
return 404;
|
|
assert(r->finalise_union == NULL);
|
|
enum meshms_status status;
|
|
if (meshms_failed(status = meshms_mark_read(&r->sid1, NULL, UINT64_MAX)))
|
|
return http_request_meshms_response(r, 0, NULL, status);
|
|
if (status == MESHMS_STATUS_UPDATED)
|
|
return http_request_meshms_response(r, 201, "Read offsets updated", status);
|
|
return http_request_meshms_response(r, 200, "Read offsets unchanged", status);
|
|
}
|
|
|
|
static int restful_meshms_read_all_messages(httpd_request *r, const char *remainder)
|
|
{
|
|
if (*remainder)
|
|
return 404;
|
|
assert(r->finalise_union == NULL);
|
|
enum meshms_status status;
|
|
if (meshms_failed(status = meshms_mark_read(&r->sid1, &r->sid2, UINT64_MAX)))
|
|
return http_request_meshms_response(r, 0, NULL, status);
|
|
if (status == MESHMS_STATUS_UPDATED)
|
|
return http_request_meshms_response(r, 201, "Read offset updated", status);
|
|
return http_request_meshms_response(r, 200, "Read offset unchanged", status);
|
|
}
|
|
|
|
static int restful_meshms_read_to_offset(httpd_request *r, const char *remainder)
|
|
{
|
|
if (*remainder)
|
|
return 404;
|
|
assert(r->finalise_union == NULL);
|
|
enum meshms_status status;
|
|
if (meshms_failed(status = meshms_mark_read(&r->sid1, &r->sid2, r->ui64)))
|
|
return http_request_meshms_response(r, 0, NULL, status);
|
|
if (status == MESHMS_STATUS_UPDATED)
|
|
return http_request_meshms_response(r, 201, "Read offset updated", status);
|
|
return http_request_meshms_response(r, 200, "Read offset unchanged", status);
|
|
}
|