#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 { char *id; char *created_at; char *updated_at; char *title; char *body; }; struct entry_query { char *id; char *query; struct entry **entries; size_t num_entries; int status; }; 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, const char *query); void entry_query_cleanup(struct entry_query *eq); int validate_uuid(const char *uuid); int validate_text(struct http_request *req, char *data); int http_ok_resp(struct http_request *req, enum http_status_code status, struct kore_buf *content); int http_err_resp(struct http_request *req, enum http_status_code status); int redirect(struct http_request *req); int get_index(struct http_request *req); int get_entry(struct http_request *req); int edit_entry(struct http_request *req); int update_entry(struct http_request *req); int sql_select(struct entry_query *eq); int sql_update(const char *id, const char *title, const char *body); // int sql_delete(const char *id); // int sql_insert(const char *id, const char *title, const char *body); 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, const char *query) { size_t i = 0; if (id) { eq->id = kore_strdup(id); } else { eq->id = NULL; } // FIXME this is dumb as hell if (query) { eq->query = kore_strdup(query); for (i = 0; i < strlen(eq->query); ++i) { if (eq->query[i] == ' ') { eq->query[i] = '|'; } } } else { eq->query = NULL; } // FIXME: define MAX_ENTRIES and LIMIT queries with it. eq->entries = kore_malloc(sizeof(struct entry *) * 100); eq->num_entries = 0; eq->status = QUERY_STATUS_OK; } void entry_query_cleanup(struct entry_query *eq) { size_t i = 0; if (eq->id) { kore_free((void *) eq->id); } eq->id = NULL; if (eq->query) { kore_free((void *) eq->query); } eq->query = NULL; for (i = 0; i < eq->num_entries; i++) { if (eq->entries[i]->id) kore_free((void *) eq->entries[i]->id); if (eq->entries[i]->title) kore_free((void *) eq->entries[i]->title); if (eq->entries[i]->created_at) kore_free((void *) eq->entries[i]->created_at); if (eq->entries[i]->updated_at) kore_free((void *) eq->entries[i]->updated_at); if (eq->entries[i]->body) kore_free((void *) eq->entries[i]->body); kore_free((void *) eq->entries[i]); } kore_free((void *) eq->entries); eq->entries = NULL; } int validate_uuid(const char *uuid) { size_t 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 validate_text(struct http_request *req, char *data) { kore_log(LOG_NOTICE, "validate_text called with data '%s'", data); return KORE_RESULT_OK; } int http_ok_resp( struct http_request *req, enum http_status_code status, struct kore_buf *content ) { struct kore_buf *resp_buf = NULL; const char *body = NULL; resp_buf = kore_buf_alloc(0); body = kore_buf_stringify(content, NULL); http_response_header(req, "content-type", "text/html; charset=utf-8"); kore_buf_append(resp_buf, asset_html_header_html, asset_len_html_header_html); kore_buf_append(resp_buf, body, strlen(body)); kore_buf_append(resp_buf, asset_html_footer_html, asset_len_html_footer_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 = NULL; resp_buf = kore_buf_alloc(0); http_response_header(req, "content-type", "text/html; charset=utf-8"); kore_buf_append(resp_buf, asset_html_header_html, asset_len_html_header_html); kore_buf_appendf(resp_buf, (const char *) asset_error_html, error_msg[status]); kore_buf_append(resp_buf, asset_html_footer_html, asset_len_html_footer_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 get_index(struct http_request *req) { int err = 0; size_t i = 0; char *query = NULL; struct entry_query eq; struct kore_buf *content = NULL; content = kore_buf_alloc(0); http_populate_get(req); err = http_argument_get_string(req, "query", &query); if (err == KORE_RESULT_OK) { kore_log(LOG_INFO, "query string: '%s'", query); } else { query = NULL; } entry_query_init(&eq, NULL, query); (void) sql_select(&eq); if (eq.status != QUERY_STATUS_OK) { http_err_resp(req, HTTP_STATUS_INTERNAL_ERROR); goto out; } // Write header. kore_buf_append(content, asset_header_html, asset_len_header_html); // Write search form. kore_buf_appendf(content, (const char *) asset_search_html, query ? query : ""); if (eq.num_entries == 0 && query) { kore_buf_appendf(content, (const char *) asset_error_html, "No results found."); } else { kore_buf_append(content, asset_table_header_html, asset_len_table_header_html); for (i = 0; i < eq.num_entries; i++) { kore_buf_appendf( content, (const char *) asset_table_row_html, eq.entries[i]->id, eq.entries[i]->title, eq.entries[i]->created_at, eq.entries[i]->updated_at ); } kore_buf_append(content, asset_table_footer_html, asset_len_table_footer_html); } http_ok_resp(req, HTTP_STATUS_OK, content); out: ; kore_free(content); entry_query_cleanup(&eq); return KORE_RESULT_OK; } int get_entry(struct http_request *req) { int err = 0; struct entry_query eq; struct kore_buf *content = NULL; struct kore_buf *rendered_body = NULL; content = kore_buf_alloc(0); rendered_body = kore_buf_alloc(0); entry_query_init(&eq, req->path + strlen("/entries/"), NULL); // 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(&eq); if (eq.status == QUERY_STATUS_NOT_FOUND) { http_err_resp(req, HTTP_STATUS_NOT_FOUND); goto out; } else if (eq.status == QUERY_STATUS_ERROR) { http_err_resp(req, HTTP_STATUS_INTERNAL_ERROR); goto out; } // Render MD. err = render_md(eq.entries[0]->body, rendered_body); if (err == KORE_RESULT_ERROR) { kore_log(LOG_ERR, "Error rendering markdown for entry %s.", eq.id); goto out; } // Append rendered MD entry. kore_buf_appendf( content, (const char *) asset_entry_html, kore_buf_stringify(rendered_body, NULL) ); http_ok_resp(req, HTTP_STATUS_OK, content); out: ; entry_query_cleanup(&eq); kore_free(rendered_body); kore_free(content); return KORE_RESULT_OK; } int edit_entry(struct http_request *req) { int err = 0; char *id = NULL; struct entry_query eq; struct kore_buf *content = NULL; struct kore_buf *rendered_body = NULL; content = kore_buf_alloc(0); rendered_body = kore_buf_alloc(0); id = kore_strdup(req->path + strlen("/entries/")); id[strlen(id) - strlen("/edit")] = '\0'; entry_query_init(&eq, (const char*) id, NULL); // 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(&eq); if (eq.status == QUERY_STATUS_NOT_FOUND) { http_err_resp(req, HTTP_STATUS_NOT_FOUND); goto out; } else if (eq.status == QUERY_STATUS_ERROR) { http_err_resp(req, HTTP_STATUS_INTERNAL_ERROR); goto out; } kore_buf_appendf( content, (const char *) asset_entry_edit_html, eq.entries[0]->id, eq.entries[0]->id, eq.entries[0]->title, eq.entries[0]->body ); // Render MD. err = render_md(eq.entries[0]->body, rendered_body); if (err == KORE_RESULT_ERROR) { kore_log(LOG_ERR, "Error rendering markdown for entry %s.", eq.id); goto out; } // Append rendered MD entry. kore_buf_appendf( content, (const char *) asset_entry_html, kore_buf_stringify(rendered_body, NULL) ); http_ok_resp(req, HTTP_STATUS_OK, content); out: ; entry_query_cleanup(&eq); kore_free(id); kore_free(rendered_body); kore_free(content); return KORE_RESULT_OK; } int update_entry(struct http_request *req) { int err = 0; char *id = NULL; char *title = NULL; char *body = NULL; http_populate_post(req); if (http_argument_get_string(req, "title", &title)) { kore_log(LOG_DEBUG, "form title %s.", title); } else { kore_log(LOG_ERR, "Error no title"); http_err_resp(req, HTTP_STATUS_BAD_REQUEST); goto out; } if (http_argument_get_string(req, "body", &body)) { kore_log(LOG_DEBUG, "form body %s.", body); } else { kore_log(LOG_ERR, "Error no body"); http_err_resp(req, HTTP_STATUS_BAD_REQUEST); goto out; } id = kore_strdup(req->path + strlen("/entries/")); kore_log(LOG_DEBUG, "updating entry %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_update(id, title, body); if (err == KORE_RESULT_ERROR) { kore_log(LOG_ERR, "Error updating entry id %s.", id); http_err_resp(req, HTTP_STATUS_INTERNAL_ERROR); goto out; } http_err_resp(req, HTTP_STATUS_CREATED); out: ; kore_free(id); return KORE_RESULT_OK; } int sql_select(struct entry_query *eq) { int err = KORE_RESULT_OK; size_t i = 0; struct entry *entry = NULL; 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) { // 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 if (eq->query) { err = kore_pgsql_query_params(&sql, q_search_entries, 0, // return string data 2, // param count KORE_PGSQL_PARAM_TEXT(eq->query), KORE_PGSQL_PARAM_TEXT(eq->query) ); } else { // Query for index. err = kore_pgsql_query(&sql, q_select_entries); } 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 && kore_pgsql_ntuples(&sql) == 0) { eq->status = QUERY_STATUS_NOT_FOUND; goto out; } // Iterate over entries and render them. eq->num_entries = kore_pgsql_ntuples(&sql); for (i = 0; i < eq->num_entries; i++) { // Fetch & copy data to entry entry = kore_malloc(sizeof(struct entry)); entry->id = kore_strdup(kore_pgsql_getvalue(&sql, i, 0)); entry->title = kore_strdup(kore_pgsql_getvalue(&sql, i, 1)); entry->created_at = kore_strdup(kore_pgsql_getvalue(&sql, i, 2)); entry->updated_at = kore_strdup(kore_pgsql_getvalue(&sql, i, 3)); entry->body = kore_strdup(kore_pgsql_getvalue(&sql, i, 4)); eq->entries[i] = entry; kore_log( LOG_DEBUG, "id: %s; title %s; created_at %s, updated_at %s", entry->id, entry->title, entry->created_at, entry->updated_at ); } out: ; kore_pgsql_cleanup(&sql); return err; } int sql_update(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, 4, KORE_PGSQL_PARAM_TEXT(title), KORE_PGSQL_PARAM_TEXT(body), 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) { 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); }