#include #include #include #include #include #include "../lib/md4c/src/entity.h" #include "../lib/md4c/src/entity.c" #include "../lib/md4c/src/md4c.h" #include "../lib/md4c/src/md4c.c" #include "../lib/md4c/src/md4c-html.h" #include "../lib/md4c/src/md4c-html.c" #include "assets.h" #include "queries.h" // FIXME: Compilation fail if this is a .c file. KORE_SECCOMP_FILTER("clog", KORE_SYSCALL_ALLOW(bind), KORE_SYSCALL_ALLOW(getdents64), KORE_SYSCALL_ALLOW(newfstatat), KORE_SYSCALL_ALLOW(sendmmsg), KORE_SYSCALL_ALLOW(uname) ) enum query_status { QUERY_STATUS_OK, QUERY_STATUS_ERROR, QUERY_STATUS_NOT_FOUND }; struct entry_query { char *id; int status; struct kore_buf *result; }; static const char *database = "db"; static const char * const error_msg[] = { [HTTP_STATUS_OK] = "OK", // 200 [HTTP_STATUS_CREATED] = "Resource created successfully.", // 201 [HTTP_STATUS_BAD_REQUEST] = "There was an error processing the request data.", // 400 [HTTP_STATUS_NOT_FOUND] = "Resource not found.", // 404 [HTTP_STATUS_INTERNAL_ERROR] = "There was an error processing the request.", // 500 }; void entry_query_init(struct entry_query *eq, const char *id); void entry_query_cleanup(struct entry_query *eq); int validate_uuid(const char *uuid); int http_ok_resp(struct http_request *req, enum http_status_code status, struct kore_buf *result); int http_err_resp( struct http_request *req, enum http_status_code status ); int redirect(struct http_request *req); int get_entries(struct http_request *req); int get_entry(struct http_request *req); int post_entry(struct http_request *req); int put_entry(struct http_request *req); int delete_entry(struct http_request *req); int v_example_func(struct http_request *req, char *data); int get_entry_form(struct http_request *req); int post_entry_form(struct http_request *req); int sql_select_entries(struct entry_query *eq); int sql_update_entry(const char *id, const char *title, const char *body); int sql_delete_entry(const char *id); int sql_insert_entry(const char *id, const char *title, const char *body); int sql_render_entry(struct kore_pgsql *sql, struct entry_query *eq); int sql_render_index(struct kore_pgsql *sql, struct entry_query *eq); static void process_md_output(const MD_CHAR *html, MD_SIZE size, void *buf); static int render_md(const char *in, struct kore_buf *out); void entry_query_init(struct entry_query *eq, const char *id) { if (id != NULL) eq->id = kore_strdup(id); else eq->id = NULL; eq->status = QUERY_STATUS_OK; eq->result = kore_buf_alloc(0); } void entry_query_cleanup(struct entry_query *eq) { if (eq->id != NULL) kore_free((void *) eq->id); eq->id = NULL; if (eq->result != NULL) kore_buf_free(eq->result); eq->result = NULL; } int validate_uuid(const char *uuid) { int i = 0; if (strlen(uuid) != 36) return KORE_RESULT_ERROR; for (i = 0; i <= 36; i++) { if ((i == 8) || (i == 13) || (i == 18) || (i == 23)) { if (uuid[i] != '-') return KORE_RESULT_ERROR; continue; } if (i == 36) { if (uuid[i] != '\0') return KORE_RESULT_ERROR; continue; } if (!isxdigit(uuid[i])) return KORE_RESULT_ERROR; } return KORE_RESULT_OK; } int http_ok_resp( struct http_request *req, enum http_status_code status, struct kore_buf *result ) { struct kore_buf *resp_buf = kore_buf_alloc(0); const char *body = kore_buf_stringify(result, NULL); http_response_header(req, "content-type", "text/html; charset=utf-8"); kore_buf_append(resp_buf, asset_index_begin_html, asset_len_index_begin_html); kore_buf_append(resp_buf, body, strlen(body)); kore_buf_append(resp_buf, asset_index_end_html, asset_len_index_end_html); http_response(req, status, resp_buf->data, resp_buf->offset); kore_buf_free(resp_buf); return KORE_RESULT_OK; } int http_err_resp( struct http_request *req, enum http_status_code status ) { struct kore_buf *resp_buf = kore_buf_alloc(0); http_response_header(req, "content-type", "text/html; charset=utf-8"); kore_buf_append(resp_buf, asset_index_begin_html, asset_len_index_begin_html); kore_buf_appendf(resp_buf, (const char *) asset_error_html, error_msg[status]); kore_buf_append(resp_buf, asset_index_end_html, asset_len_index_end_html); http_response(req, status, resp_buf->data, resp_buf->offset); kore_buf_free(resp_buf); return KORE_RESULT_OK; } int redirect(struct http_request *req) { http_response_header(req, "Location", "/"); http_response(req, HTTP_STATUS_TEMPORARY_REDIRECT, NULL, 0); return KORE_RESULT_OK; } int v_example_func(struct http_request *req, char *data) { kore_log(LOG_NOTICE, "v_example_func called"); return KORE_RESULT_OK; } int get_entry_form(struct http_request *req) { struct kore_buf *resp_buf = kore_buf_alloc(0); kore_buf_appendf(resp_buf, (const char *) asset_entry_edit_html, "", "", ""); http_ok_resp(req, HTTP_STATUS_OK, resp_buf); kore_buf_free(resp_buf); return KORE_RESULT_OK; } int post_entry_form(struct http_request *req) { int err = 0; struct kore_buf *resp_buf = NULL; char *id = NULL; char *title = NULL; char *body = NULL; http_populate_post(req); if (http_argument_get_string(req, "id", &id)) { kore_log(LOG_INFO, "form id %s.", id); } if (http_argument_get_string(req, "title", &title)) { kore_log(LOG_INFO, "form title %s.", title); } if (http_argument_get_string(req, "body", &body)) { kore_log(LOG_INFO, "form body %s.", body); } resp_buf = kore_buf_alloc(0); // Append form. kore_buf_appendf( resp_buf, (const char *) asset_entry_edit_html, id ? id : "", title ? title: "", body ? body : "" ); // Render MD. err = render_md(body, resp_buf); if (err == KORE_RESULT_ERROR) { kore_buf_append(resp_buf, "Error rendering markdown.", 25); kore_log(LOG_ERR, "Error rendering markdown."); } http_ok_resp(req, HTTP_STATUS_OK, resp_buf); kore_buf_free(resp_buf); return KORE_RESULT_OK; } int get_entries(struct http_request *req) { struct entry_query eq; entry_query_init(&eq, NULL); (void) sql_select_entries(&eq); if (eq.status != QUERY_STATUS_OK) http_err_resp(req, HTTP_STATUS_INTERNAL_ERROR); else http_ok_resp(req, HTTP_STATUS_OK, eq.result); entry_query_cleanup(&eq); return KORE_RESULT_OK; } int get_entry(struct http_request *req) { int err = 0; struct entry_query eq; entry_query_init(&eq, req->path + strlen("/entries/")); // Check for valid resource UUID kore_log(LOG_DEBUG, "Resource id /entries/%s.", eq.id); err = validate_uuid(eq.id); if (err == KORE_RESULT_ERROR) { kore_log(LOG_ERR, "Invalid entry id %s.", eq.id); http_err_resp(req, HTTP_STATUS_NOT_FOUND); goto out; } (void) sql_select_entries(&eq); if (eq.status == QUERY_STATUS_NOT_FOUND) http_err_resp(req, HTTP_STATUS_NOT_FOUND); else if (eq.status == QUERY_STATUS_ERROR) http_err_resp(req, HTTP_STATUS_INTERNAL_ERROR); else http_ok_resp(req, HTTP_STATUS_OK, eq.result); out: ; entry_query_cleanup(&eq); return KORE_RESULT_OK; } int post_entry(struct http_request *req) { int err = 0; int status = HTTP_STATUS_CREATED; const char *id = NULL; const char *title = NULL; const char *body = NULL; struct kore_json_item *item = NULL; struct kore_json json; kore_json_init(&json, req->http_body->data, req->http_body->length); if (!kore_json_parse(&json)) { status = HTTP_STATUS_BAD_REQUEST; kore_log(LOG_ERR, "error parsing json: %s\n", kore_json_strerror()); goto out; } item = kore_json_find_string(json.root, "id"); if (item != NULL) { id = item->data.string; kore_log(LOG_INFO, "id = '%s'\n", id); // Check for valid resource ID/UUID err = validate_uuid(id); if (err == KORE_RESULT_ERROR) { status = HTTP_STATUS_BAD_REQUEST; kore_log(LOG_ERR, "Invalid entry UUID %s.", id); goto out; } } item = kore_json_find_string(json.root, "title"); if (item != NULL) { title = item->data.string; kore_log(LOG_INFO, "title = '%s'\n", title); } else { status = HTTP_STATUS_BAD_REQUEST; kore_log(LOG_ERR, "error parsing title: %s\n", kore_json_strerror()); goto out; } item = kore_json_find_string(json.root, "body"); if (item != NULL) { body = item->data.string; kore_log(LOG_INFO, "body = '%s'\n", body); } else { status = HTTP_STATUS_BAD_REQUEST; kore_log(LOG_ERR, "error parsing body: %s\n", kore_json_strerror()); goto out; } err = sql_insert_entry(id, title, body); if (err == KORE_RESULT_ERROR) { status = HTTP_STATUS_INTERNAL_ERROR; goto out; } out: ; http_err_resp(req, status); kore_json_cleanup(&json); return KORE_RESULT_OK; } int put_entry(struct http_request *req) { int err = 0; int status = HTTP_STATUS_OK; const char *id = NULL; const char *title = NULL; const char *body = NULL; struct kore_json_item *item = NULL; struct kore_json json; kore_json_init(&json, req->http_body->data, req->http_body->length); id = req->path + strlen("/entries/"); // Check for valid resource UUID kore_log(LOG_DEBUG, "Resource id /entries/%s.", id); err = validate_uuid(id); if (err == KORE_RESULT_ERROR) { kore_log(LOG_ERR, "Invalid entry id %s.", id); http_err_resp(req, HTTP_STATUS_NOT_FOUND); goto out; } if (!kore_json_parse(&json)) { status = HTTP_STATUS_BAD_REQUEST; kore_log(LOG_ERR, "error parsing json: %s\n", kore_json_strerror()); goto out; } item = kore_json_find_string(json.root, "title"); if (item != NULL) { title = item->data.string; kore_log(LOG_INFO, "title = '%s'\n", title); } else { status = HTTP_STATUS_BAD_REQUEST; kore_log(LOG_ERR, "Error parsing title: %s\n", kore_json_strerror()); goto out; } item = kore_json_find_string(json.root, "body"); if (item != NULL) { body = item->data.string; kore_log(LOG_INFO, "body = '%s'\n", body); } else { status = HTTP_STATUS_BAD_REQUEST; kore_log(LOG_ERR, "Error parsing body: %s\n", kore_json_strerror()); goto out; } err = sql_update_entry(id, title, body); if (err == KORE_RESULT_ERROR) { status = HTTP_STATUS_INTERNAL_ERROR; goto out; } out: ; http_err_resp(req, status); kore_json_cleanup(&json); return KORE_RESULT_OK; } int sql_select_entries(struct entry_query *eq) { int err = KORE_RESULT_OK; struct kore_pgsql sql; kore_pgsql_init(&sql); eq->status = QUERY_STATUS_OK; // Set up synchronous database handle. err = kore_pgsql_setup(&sql, database, KORE_PGSQL_SYNC); if (err == KORE_RESULT_ERROR) { eq->status = QUERY_STATUS_ERROR; kore_pgsql_logerror(&sql); goto out; } // Query for entries, check for error. if (eq->id != NULL) { // Query an entry. err = kore_pgsql_query_params( &sql, q_select_entry, 0, // return string data 1, // param count KORE_PGSQL_PARAM_TEXT(eq->id) ); } else { // Query for index. err = kore_pgsql_query( &sql, q_select_index ); } if (err == KORE_RESULT_ERROR) { eq->status = QUERY_STATUS_ERROR; kore_pgsql_logerror(&sql); goto out; } // TODO: Add test for this; When database is empty, don't return 404 for base request. if (eq->id != NULL && kore_pgsql_ntuples(&sql) == 0) { eq->status = QUERY_STATUS_NOT_FOUND; goto out; } if (eq->id) { (void) sql_render_entry(&sql, eq); } else { (void) sql_render_index(&sql, eq); } out: ; kore_pgsql_cleanup(&sql); return err; } int delete_entry(struct http_request *req) { int err = 0; const char *id = req->path + strlen("/entries/"); // Check for valid resource UUID kore_log(LOG_DEBUG, "Resource id /entries/%s.", id); err = validate_uuid(id); if (err == KORE_RESULT_ERROR) { kore_log(LOG_ERR, "Invalid entry id %s.", id); http_err_resp(req, HTTP_STATUS_NOT_FOUND); goto out; } err = sql_delete_entry(id); // FIXME: should 404 if id doesn't exist. // if (eq.status == QUERY_STATUS_NOT_FOUND) // http_err_resp(req, HTTP_STATUS_NOT_FOUND); if (err == KORE_RESULT_ERROR) http_err_resp(req, HTTP_STATUS_INTERNAL_ERROR); else // TODO: test this; the test should explode. http_err_resp(req, HTTP_STATUS_OK); out: ; return KORE_RESULT_OK; } int sql_render_entry(struct kore_pgsql *sql, struct entry_query *eq) { int err = KORE_RESULT_OK; int row = 0, rows = 0; struct kore_buf *html_buf = NULL; const char *id = NULL; const char *title = NULL; const char *created_at = NULL; const char *body = NULL; // Allocate a buffer to render the markdown as HTML into. html_buf = kore_buf_alloc(0); // Iterate over entries and render them. rows = kore_pgsql_ntuples(sql); for (row = 0; row < rows; row++) { // Reset buffer. kore_buf_cleanup(html_buf); // Fetch data. id = kore_pgsql_getvalue(sql, row, 0); title = kore_pgsql_getvalue(sql, row, 1); created_at = kore_pgsql_getvalue(sql, row, 2); body = kore_pgsql_getvalue(sql, row, 3); kore_log(LOG_DEBUG, "id: %s; title %s; created_at %s", id, title, created_at); // Render MD. err = render_md(body, html_buf); if (err == KORE_RESULT_ERROR) { kore_log(LOG_ERR, "Error rendering markdown for entry %s.", id); continue; } // Append rendered MD entry. kore_buf_appendf( eq->result, (const char *) asset_entry_html, kore_buf_stringify(html_buf, NULL) ); } kore_buf_free(html_buf); return KORE_RESULT_OK; } int sql_render_index(struct kore_pgsql *sql, struct entry_query *eq) { int row = 0, rows = 0; struct kore_buf *html_buf = NULL; const char *id = NULL; const char *title = NULL; const char *created_at = NULL; const char *updated_at = NULL; // Allocate a buffer to render the markdown as HTML into. html_buf = kore_buf_alloc(0); // Write table header. kore_buf_append( eq->result, (const char *) "

clog.bunkergate.org

\r\n\r\n", 94 ); // Iterate over entries and render them. rows = kore_pgsql_ntuples(sql); for (row = 0; row < rows; row++) { // Reset buffer. kore_buf_cleanup(html_buf); // Fetch data. id = kore_pgsql_getvalue(sql, row, 0); title = kore_pgsql_getvalue(sql, row, 1); created_at = kore_pgsql_getvalue(sql, row, 2); updated_at = kore_pgsql_getvalue(sql, row, 3); kore_log(LOG_DEBUG, "id: '%s'; title '%s'", id, title); // Append rendered MD entry. kore_buf_appendf( eq->result, (const char *) "\r\n", id, title, created_at, updated_at ); } // Write table footer. kore_buf_append( eq->result, (const char *) "
TitleCreatedUpdated
%s%s%s
\r\n", 10 ); kore_buf_free(html_buf); return KORE_RESULT_OK; } int sql_insert_entry(const char *id, const char *title, const char *body) { int err = KORE_RESULT_OK; struct kore_pgsql sql; kore_pgsql_init(&sql); err = kore_pgsql_setup(&sql, database, KORE_PGSQL_SYNC); if (err == KORE_RESULT_ERROR) { kore_pgsql_logerror(&sql); goto out; } if (id == NULL) { err = kore_pgsql_query_params( &sql, q_insert_entry, 0, 2, KORE_PGSQL_PARAM_TEXT(title), KORE_PGSQL_PARAM_TEXT(body) ); } else { err = kore_pgsql_query_params( &sql, q_insert_entry_with_id, 0, 3, KORE_PGSQL_PARAM_TEXT(id), KORE_PGSQL_PARAM_TEXT(title), KORE_PGSQL_PARAM_TEXT(body) ); } if (err == KORE_RESULT_ERROR) { kore_pgsql_logerror(&sql); goto out; } out: ; kore_pgsql_cleanup(&sql); return err; } int sql_update_entry(const char *id, const char *title, const char *body) { int err = KORE_RESULT_OK; struct kore_pgsql sql; kore_pgsql_init(&sql); err = kore_pgsql_setup(&sql, database, KORE_PGSQL_SYNC); if (err == KORE_RESULT_ERROR) { kore_pgsql_logerror(&sql); goto out; } err = kore_pgsql_query_params( &sql, q_update_entry, 0, 3, KORE_PGSQL_PARAM_TEXT(title), KORE_PGSQL_PARAM_TEXT(body), KORE_PGSQL_PARAM_TEXT(id) ); if (err == KORE_RESULT_ERROR) { kore_pgsql_logerror(&sql); goto out; } out: ; kore_pgsql_cleanup(&sql); return err; } int sql_delete_entry(const char *id) { int err = KORE_RESULT_OK; struct kore_pgsql sql; kore_pgsql_init(&sql); err = kore_pgsql_setup(&sql, database, KORE_PGSQL_SYNC); if (err == KORE_RESULT_ERROR) { kore_pgsql_logerror(&sql); goto out; } err = kore_pgsql_query_params( &sql, q_delete_entry, 0, 1, KORE_PGSQL_PARAM_TEXT(id) ); if (err == KORE_RESULT_ERROR) { kore_pgsql_logerror(&sql); goto out; } out: ; kore_pgsql_cleanup(&sql); return err; } static int render_md(const char *in, struct kore_buf *out) { // Not a kore err. int err = 0; static unsigned parser_flags = 0; static unsigned renderer_flags = 0; parser_flags |= MD_FLAG_TABLES; parser_flags |= MD_FLAG_STRIKETHROUGH; // parser_flags |= MD_FLAG_TASKLISTS; // render_flags |= MD_HTML_FLAG_DEBUG; err = md_html( in, (MD_SIZE) strlen(in), process_md_output, (void*) out, parser_flags, renderer_flags ); if(err != 0) { kore_log(LOG_ERR, "Parsing Markdown failed."); return KORE_RESULT_ERROR; } return KORE_RESULT_OK; } static void process_md_output(const MD_CHAR *html, MD_SIZE size, void *buf) { kore_buf_append((struct kore_buf *) buf, (const void *) html, (size_t) size); }