From: Jeroen van der Heijden Date: Mon, 6 Jan 2020 15:44:20 +0000 (+0100) Subject: start work on json api X-Git-Tag: archive/raspbian/2.0.44-1+rpi1~1^2~3^2~5^2~39 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=616ccc49a44aa532ee86ed6283cc093022085ceb;p=siridb-server.git start work on json api --- diff --git a/include/base64/base64.h b/include/base64/base64.h new file mode 100644 index 00000000..b663b2c7 --- /dev/null +++ b/include/base64/base64.h @@ -0,0 +1,13 @@ +/* + * base64.h - Base64 encoding/decoding + */ +#ifndef SIRI_BASE64_H_ +#define SIRI_BASE64_H_ + +#include + +char * base64_decode(const void * data, size_t n, size_t * size); +char * base64_encode(const void * data, size_t n, size_t * size); + + +#endif /* SIRI_BASE64_H_ */ diff --git a/include/qpjson/qpjson.h b/include/qpjson/qpjson.h new file mode 100644 index 00000000..e69de29b diff --git a/include/siri/api.h b/include/siri/api.h new file mode 100644 index 00000000..8a57d4dc --- /dev/null +++ b/include/siri/api.h @@ -0,0 +1,62 @@ +/* + * api.h - SiriDB HTTP API. + */ +#ifndef SIRI_API_H_ +#define SIRI_API_H_ + +#include +#include + +#define SIRIDB_API_FLAG 1<<29 + +typedef enum +{ + SIRIDB_API_STATE_NONE, + SIRIDB_API_STATE_CONTENT_TYPE, + SIRIDB_API_STATE_AUTHORIZATION, +} siridb_api_state_t; + +typedef enum +{ + SIRIDB_API_CT_TEXT, + SIRIDB_API_CT_JSON, +} siridb_api_content_t; + +typedef enum +{ + SIRIDB_API_FLAG_IS_CLOSED =1<<0, + SIRIDB_API_FLAG_IN_USE =1<<1, + SIRIDB_API_FLAG_JSON_BEAUTY =1<<2, + SIRIDB_API_FLAG_JSON_UTF8 =1<<3, +} siridb_api_flags_t; + +typedef struct siri_api_request_s siri_api_request_t; + +int siri_api_init(void); +void siri_ali_close(siri_api_request_t * web_request); +static inline _Bool siri_api_is_handle(uv_handle_t * handle); + +struct siri_api_request_s +{ + uint32_t ref_; /* maps to sirnet_stream_t for cleanup */ + siridb_api_flags_t flags; + siridb_api_state_t state; + siridb_api_content_t content_type; + size_t content_n; + uv_write_t req; + uv_stream_t uvstream; + http_parser parser; + char * content; + siridb_t * siridb; + siridb_user_t * user; +}; + +static inline _Bool siri_api_is_handle(uv_handle_t * handle) +{ + return + handle->type == UV_TCP && + handle->data && + (((siri_api_request_t *) handle->data)->ref_ & SIRIDB_API_FLAG); +} + +#endif /* SIRI_API_H_ */ diff --git a/include/siri/cfg/cfg.h b/include/siri/cfg/cfg.h index 88a5d1ed..d6215401 100644 --- a/include/siri/cfg/cfg.h +++ b/include/siri/cfg/cfg.h @@ -27,7 +27,7 @@ struct siri_cfg_s uint16_t max_open_files; uint16_t http_status_port; - uint16_t pad0_; + uint16_t http_api_port; uint8_t pipe_support; uint8_t ip_support; uint8_t shard_compression; diff --git a/include/siri/db/users.h b/include/siri/db/users.h index db1a958f..aae5818d 100644 --- a/include/siri/db/users.h +++ b/include/siri/db/users.h @@ -23,6 +23,10 @@ siridb_user_t * siridb_users_get_user( siridb_t * siridb, const char * username, const char * password); +siridb_user_t * siridb_users_get_user_from_basic( + siridb_t * siridb, + const char * data, + size_t n); int siridb_users_save(siridb_t * siridb); ssize_t siridb_users_get_file(char ** buffer, siridb_t * siridb); diff --git a/src/base64/base64.c b/src/base64/base64.c new file mode 100644 index 00000000..c64b9f94 --- /dev/null +++ b/src/base64/base64.c @@ -0,0 +1,105 @@ +/* + * base64.c - Base64 encoding/decoding + */ +#include + + +static const int base64__idx[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 62, 63, 62, 62, 63, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 +}; + +static const unsigned char base64__table[65] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +char * base64_decode(const void * data, size_t n, size_t * size) +{ + const unsigned char * p = data; + int pad = n > 0 && (n % 4 || p[n - 1] == '='); + size_t i, j, L = ((n + 3) / 4 - pad) * 4; + char * out = malloc((L / 4 * 3) + (pad + ? (n > 1 && (n % 4 == 3 || p[n - 2] != '=')) + 2 + : 1)); + if (!out) + return NULL; + + for (i = 0, j = 0; i < L; i += 4) + { + int nn = base64__idx[p[i]] << 18 | \ + base64__idx[p[i + 1]] << 12 | \ + base64__idx[p[i + 2]] << 6 | \ + base64__idx[p[i + 3]]; + out[j++] = nn >> 16; + out[j++] = nn >> 8 & 0xFF; + out[j++] = nn & 0xFF; + } + + if (pad) + { + int nn = base64__idx[p[L]] << 18 | base64__idx[p[L + 1]] << 12; + out[j++] = nn >> 16; + + if (n > L + 2 && p[L + 2] != '=') + { + nn |= base64__idx[p[L + 2]] << 6; + out[j++] = nn >> 8 & 0xFF; + } + } + + *size = j; + out[*size] = '\0'; + return out; +} + + +char * base64_encode(const void * data, size_t n, size_t * size) +{ + unsigned char * out; + unsigned char * pos; + const unsigned char * end, * in; + size_t olen = 4 * ((n + 2) / 3); /* 3-byte blocks to 4-byte */ + + if (olen < n || !(out = malloc(olen + 1))) + /* integer overflow or allocation error */ + return NULL; + + end = data + n; + in = data; + pos = out; + + while (end - in >= 3) + { + *pos++ = base64__table[in[0] >> 2]; + *pos++ = base64__table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; + *pos++ = base64__table[((in[1] & 0x0f) << 2) | (in[2] >> 6)]; + *pos++ = base64__table[in[2] & 0x3f]; + in += 3; + } + + if (end - in) + { + *pos++ = base64__table[in[0] >> 2]; + if (end - in == 1) + { + *pos++ = base64__table[(in[0] & 0x03) << 4]; + *pos++ = '='; + } + else + { + *pos++ = base64__table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; + *pos++ = base64__table[(in[1] & 0x0f) << 2]; + } + *pos++ = '='; + } + + *size = pos - out; + out[*size] = '\0'; + return (char *) out; +} + + diff --git a/src/qpjson/qpjson.c b/src/qpjson/qpjson.c new file mode 100644 index 00000000..e69de29b diff --git a/src/siri/api.c b/src/siri/api.c new file mode 100644 index 00000000..8e92dc88 --- /dev/null +++ b/src/siri/api.c @@ -0,0 +1,415 @@ +/* + * api.c + */ +#include +#include + +#define API__HEADER_MAX_SZ 256 +#define CONTENT_TYPE_JSON "application/json" + +static uv_tcp_t api__uv_server; +static http_parser_settings api__settings; + +static const char api__content_type[2][20] = { + "text/plain", + "application/json", +}; + +static const char api__html_header[8][32] = { + "200 OK", + "400 Bad Request", + "401 Unauthorized", + "403 Forbidden", + "404 Not Found", + "415 Unsupported Media Type", + "500 Internal Server Error", + "503 Service Unavailable", +}; + +static const char api__default_body[8][30] = { + "OK\r\n", + "BAD REQUEST\r\n", + "UNAUTHORIZED\r\n", + "FORBIDDEN\r\n", + "NOT FOUND\r\n", + "UNSUPPORTED MEDIA TYPE\r\n", + "INTERNAL SERVER ERROR\r\n", + "SERVICE UNAVAILABLE\r\n", +}; + +typedef enum +{ + E200_OK, + E400_BAD_REQUEST, + E401_UNAUTHORIZED, + E403_FORBIDDEN, + E404_NOT_FOUND, + E415_UNSUPPORTED_MEDIA_TYPE, + E500_INTERNAL_SERVER_ERROR, + E503_SERVICE_UNAVAILABLE +} api__header_t; + +static inline _Bool api__starts_with( + const char ** str, + size_t * strn, + const char * with, + size_t withn) +{ + if (*strn < withn || strncasecmp(*str, with, withn)) + return false; + *str += withn; + *strn -= withn; + return true; +} + +static void api__close_cb(uv_handle_t * handle) +{ + siri_api_request_t * ar = handle->data; + if (ar->siridb) + siridb_decref(ar->siridb); + if (ar->user) + siridb_user_decref(ar->user); + free(ar->content); + free(ar); +} + +static void api__alloc_cb( + uv_handle_t * UNUSED(handle), + size_t UNUSED(sugsz), + uv_buf_t * buf) +{ + buf->base = malloc(HTTP_MAX_HEADER_SIZE); + buf->len = buf->base ? HTTP_MAX_HEADER_SIZE-1 : 0; +} + +static void api__data_cb( + uv_stream_t * uvstream, + ssize_t n, + const uv_buf_t * buf) +{ + size_t parsed; + siri_api_request_t * ar = uvstream->data; + + if (ar->flags & SIRIDB_API_FLAG_IS_CLOSED) + goto done; + + if (n < 0) + { + if (n != UV_EOF) + log_error(uv_strerror(n)); + + ti_api_close(ar); + goto done; + } + + buf->base[HTTP_MAX_HEADER_SIZE-1] = '\0'; + + parsed = http_parser_execute( + &ar->parser, + &api__settings, + buf->base, n); + + if (ar->parser.upgrade) + { + /* TODO: do we need to do something? */ + log_debug("upgrade to a new protocol"); + } + else if (parsed != (size_t) n) + { + log_warning("error parsing HTTP API request"); + ti_api_close(ar); + } + +done: + free(buf->base); +} + +static int api__headers_complete_cb(http_parser * parser) +{ + siri_api_request_t * ar = parser->data; + + assert (!ar->content); + + ar->content = malloc(parser->content_length); + if (ar->content) + ar->content_n = parser->content_length; + + return 0; +} + +static int api__url_cb(http_parser * parser, const char * at, size_t n) +{ + siri_api_request_t * ar = parser->data; + + + + return 0; +} + +static void api__connection_cb(uv_stream_t * server, int status) +{ + int rc; + siri_api_request_t * ar; + + if (status < 0) + { + log_error("HTTP API connection error: `%s`", uv_strerror(status)); + return; + } + + log_debug("received a HTTP API call"); + + ar = calloc(1, sizeof(siri_api_request_t)); + if (!ar) + { + ERR_ALLOC + return; + } + + (void) uv_tcp_init(siri.loop, (uv_tcp_t *) &ar->uvstream); + + ar->flags |= SIRIDB_API_FLAG; + ar->uvstream.data = ar; + ar->parser.data = ar; + + rc = uv_accept(server, &ar->uvstream); + if (rc) + { + log_error("cannot accept HTTP API request: `%s`", uv_strerror(rc)); + ti_api_close(ar); + return; + } + + http_parser_init(&ar->parser, HTTP_REQUEST); + + rc = uv_read_start(&ar->uvstream, api__alloc_cb, api__data_cb); + if (rc) + { + log_error("cannot read HTTP API request: `%s`", uv_strerror(rc)); + ti_api_close(ar); + return; + } +} + +static int api__header_field_cb(http_parser * parser, const char * at, size_t n) +{ + siri_api_request_t * ar = parser->data; + + if (API__ICMP_WITH(at, n, "content-type")) + { + ar->state = SIRIDB_API_STATE_CONTENT_TYPE; + return 0; + } + + if (API__ICMP_WITH(at, n, "authorization")) + { + ar->state = SIRIDB_API_STATE_AUTHORIZATION; + return 0; + } + + ar->state = SIRIDB_API_STATE_NONE; + return 0; +} + +static int api__header_value_cb(http_parser * parser, const char * at, size_t n) +{ + siri_api_request_t * ar = parser->data; + + switch (ar->state) + { + case SIRIDB_API_STATE_NONE: + break; + + case SIRIDB_API_STATE_CONTENT_TYPE: + if (API__ICMP_WITH(at, n, CONTENT_TYPE_JSON)) + { + ar->content_type = SIRIDB_API_CT_JSON; + break; + } + if (API__ICMP_WITH(at, n, "text/json")) + { + ar->content_type = SIRIDB_API_CT_JSON; + break; + } + + /* invalid content type */ + log_debug("unsupported content-type: %.*s", (int) n, at); + break; + + case SIRIDB_API_STATE_AUTHORIZATION: + if (api__starts_with(&at, &n, "token ", strlen("token "))) + { + log_debug("token authorization is not supported yet"); + } + + if (api__starts_with(&at, &n, "basic ", strlen("basic "))) + { + ar->user = siridb_users_get_user_from_basic(ar->siridb, at, n); + + if (ar->user) + siridb_user_incref(ar->user); + + break; + } + + log_debug("invalid authorization type: %.*s", (int) n, at); + break; + } + return 0; +} + +static int api__body_cb(http_parser * parser, const char * at, size_t n) +{ + size_t offset; + siri_api_request_t * ar = parser->data; + + if (!n || !ar->content_n) + return 0; + + offset = ar->content_n - (parser->content_length + n); + assert (offset + n <= ar->content_n); + memcpy(ar->content + offset, at, n); + + return 0; +} + +static void api__write_cb(uv_write_t * req, int status) +{ + if (status) + log_error( + "error writing HTTP API response: `%s`", + uv_strerror(status)); + + ti_api_close((siri_api_request_t *) req->handle->data); +} + +static int api__plain_response(siri_api_request_t * ar, const api__header_t ht) +{ + const char * body = api__default_body[ht]; + char header[API__HEADER_MAX_SZ]; + size_t body_size; + int header_size; + + body_size = strlen(body); + header_size = api__header(header, ht, SIRIDB_API_CT_TEXT, body_size); + if (header_size > 0) + { + uv_buf_t uvbufs[2] = { + uv_buf_init(header, (size_t) header_size), + uv_buf_init((char *) body, body_size), + }; + + (void) uv_write( + &ar->req, + &ar->uvstream, + uvbufs, 2, + api__write_cb); + return 0; + } + return -1; +} + +static int api__message_complete_cb(http_parser * parser) +{ + siri_api_request_t * ar = parser->data; + + if (!ar->user) + return api__plain_response(ar, E401_UNAUTHORIZED); + + switch (ar->content_type) + { + case SIRIDB_API_CT_JSON: + { + char * data; + size_t size; + if (qpjson_json_to_qp(ar->content, ar->content_n, &data, &size)) + return api__plain_response(ar, E400_BAD_REQUEST); + + free(ar->content); + ar->content = data; + ar->content_n = size; + } + } + + return api__plain_response(ar, E500_INTERNAL_SERVER_ERROR); +} + +static int api__chunk_header_cb(http_parser * parser) +{ + LOGC("Chunk header\n Content-Length: %zu", parser->content_length); + return 0; +} + +static int api__chunk_complete_cb(http_parser * parser) +{ + LOGC("Chunk complete\n Content-Length: %zu", parser->content_length); + return 0; +} + +int siri_api_init(void) +{ + int rc; + struct sockaddr_storage addr = {0}; + uint16_t port = siri.cfg->http_api_port; + + if (port == 0) + return 0; + + (void) uv_ip6_addr("::", (int) port, (struct sockaddr_in6 *) &addr); + + api__settings.on_url = api__url_cb; + api__settings.on_header_field = api__header_field_cb; + api__settings.on_header_value = api__header_value_cb; + api__settings.on_message_complete = api__message_complete_cb; + api__settings.on_body = api__body_cb; + api__settings.on_chunk_header = api__chunk_header_cb; + api__settings.on_chunk_complete = api__chunk_complete_cb; + api__settings.on_headers_complete = api__headers_complete_cb; + + if ( + (rc = uv_tcp_init(siri.loop, &api__uv_server)) || + (rc = uv_tcp_bind( + &api__uv_server, + (const struct sockaddr *) &addr, + 0)) || + (rc = uv_listen( + (uv_stream_t *) &api__uv_server, + 128, + api__connection_cb))) + { + log_error("error initializing HTTP API on port %u: `%s`", + port, + uv_strerror(rc)); + return -1; + } + + log_info("start listening for HTTP API requests on TCP port %u", port); + return 0; +} + +siri_api_request_t * siri_api_acquire(siri_api_request_t * ar) +{ + ar->flags |= SIRIDB_API_FLAG_IN_USE; + return ar; +} + +void ti_api_release(siri_api_request_t * ar) +{ + ar->flags &= ~SIRIDB_API_FLAG_IN_USE; + + if (ar->flags & SIRIDB_API_FLAG_IS_CLOSED) + uv_close((uv_handle_t *) &ar->uvstream, api__close_cb); +} + +void ti_api_close(siri_api_request_t * ar) +{ + if (!ar || (ar->flags & SIRIDB_API_FLAG_IS_CLOSED)) + return; + + ar->flags |= SIRIDB_API_FLAG_IS_CLOSED; + + if (ar->flags & SIRIDB_API_FLAG_IN_USE) + return; + + uv_close((uv_handle_t *) &ar->uvstream, api__close_cb); +} diff --git a/src/siri/db/users.c b/src/siri/db/users.c index 8a8d663e..85e5de45 100644 --- a/src/siri/db/users.c +++ b/src/siri/db/users.c @@ -194,7 +194,7 @@ int siridb_users_add_user( } /* - * Returns NULL when the user is not found of when the given password is + * Returns NULL when the user is not found or when the given password is * incorrect. When *password is NULL the password will NOT be checked and * the user will be returned when found. */ @@ -249,6 +249,28 @@ siridb_user_t * siridb_users_get_user( return NULL; } +siridb_user_t * siridb_users_get_user_from_basic( + siridb_t * siridb, + const char * data, + size_t n) +{ + size_t size; + char * b64 = base64_decode(data, n, &size); + + for (size_t nn = 0, end = size; n < end; ++nn) + { + if (b64[nn] == ':') + { + b64[nn] = '\0'; + + if (++nn > end) + return NULL; + + return siridb_users_get_user(siridb, b64, b64 + nn); + } + } +} + /* * We get and remove the user in one code block so we do not need a dropped * flag on the user object. diff --git a/src/siri/health.c b/src/siri/health.c index 79d6e152..b32744dd 100644 --- a/src/siri/health.c +++ b/src/siri/health.c @@ -1,5 +1,5 @@ /* - * health.h + * health.c */ #include #include diff --git a/src/siri/siri.c b/src/siri/siri.c index 2b521798..6049816a 100644 --- a/src/siri/siri.c +++ b/src/siri/siri.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -167,6 +168,7 @@ int siri_start(void) /* initialize the back-end-, client- server and load databases */ if ( (siri.cfg->http_status_port && (rc = siri_health_init())) || + (siri.cfg->http_api_port && (rc = siri_api_init())) || (rc = siri_service_account_init(&siri)) || (rc = siri_service_request_init()) || (rc = sirinet_bserver_init(&siri)) || @@ -512,6 +514,10 @@ static void SIRI_walk_close_handlers( { siri_health_close((siri_health_request_t *) handle->data); } + else if (siri_api_is_handle(handle)) + { + siri_api_close((siri_api_request_t *) handle->data); + } else { sirinet_stream_decref((sirinet_stream_t *) handle->data);