static int accept_sub(SLNSessionRef const session, strarg_t const knownURI, HTTPConnectionRef const conn, HTTPHeadersRef const headers) { strarg_t const type = HTTPHeadersGet(headers, "Content-Type"); if(!type) return 415; // Unsupported Media Type SLNSubmissionRef sub = NULL; int rc = SLNSubmissionCreate(session, knownURI, type, &sub); if(rc < 0) goto cleanup; for(;;) { uv_buf_t buf[1] = {}; rc = HTTPConnectionReadBody(conn, buf); if(rc < 0) goto cleanup; if(0 == buf->len) break; rc = SLNSubmissionWrite(sub, (byte_t const *)buf->base, buf->len); if(rc < 0) goto cleanup; } rc = SLNSubmissionEnd(sub); if(rc < 0) goto cleanup; rc = SLNSubmissionStoreBatch(&sub, 1); if(rc < 0) goto cleanup; strarg_t const location = SLNSubmissionGetPrimaryURI(sub); if(!location) rc = UV_ENOMEM; if(rc < 0) goto cleanup; created(location, conn); cleanup: SLNSubmissionFree(&sub); if(UV_EACCES == rc) return 403; // Forbidden if(UV_UNKNOWN == rc) return 400; // Bad Request if(SLN_HASHMISMATCH == rc) return 409; // Conflict if(rc < 0) return 500; return 0; }
static int POST_post(BlogRef const blog, SLNSessionRef const session, HTTPConnectionRef const conn, HTTPMethod const method, strarg_t const URI, HTTPHeadersRef const headers) { if(HTTP_POST != method) return -1; if(0 != uripathcmp("/post", URI, NULL)) return -1; // TODO: CSRF token strarg_t const formtype = HTTPHeadersGet(headers, "content-type"); uv_buf_t boundary[1]; int rc = MultipartBoundaryFromType(formtype, boundary); if(rc < 0) return 400; MultipartFormRef form = NULL; rc = MultipartFormCreate(conn, boundary, &form); if(rc < 0) { return 500; } SLNSubmissionRef sub = NULL; SLNSubmissionRef meta = NULL; str_t *title = NULL; rc = parse_file(blog, session, form, &sub, &meta, &title); if(UV_EACCES == rc) { MultipartFormFree(&form); return 403; } if(rc < 0) { MultipartFormFree(&form); return 500; } SLNSubmissionRef extra = NULL; yajl_gen json = NULL; str_t *target_QSEscaped = NULL; str_t *location = NULL; strarg_t const target = SLNSubmissionGetPrimaryURI(sub); if(!target) rc = UV_ENOMEM; if(rc < 0) goto cleanup; target_QSEscaped = QSEscape(target, strlen(target), true); if(!target_QSEscaped) rc = UV_ENOMEM; if(rc < 0) goto cleanup; rc = SLNSubmissionCreate(session, NULL, target, &extra); if(rc < 0) goto cleanup; rc = SLNSubmissionSetType(extra, SLN_META_TYPE); if(rc < 0) goto cleanup; SLNSubmissionWrite(extra, (byte_t const *)target, strlen(target)); SLNSubmissionWrite(extra, (byte_t const *)STR_LEN("\n\n")); json = yajl_gen_alloc(NULL); if(!json) rc = UV_ENOMEM; if(rc < 0) goto cleanup; yajl_gen_config(json, yajl_gen_print_callback, (void (*)())SLNSubmissionWrite, extra); yajl_gen_config(json, yajl_gen_beautify, (int)true); yajl_gen_map_open(json); if(title) { yajl_gen_string(json, (unsigned char const *)STR_LEN("title")); yajl_gen_string(json, (unsigned char const *)title, strlen(title)); } // TODO: Comment or description? strarg_t const username = SLNSessionGetUsername(session); if(username) { yajl_gen_string(json, (unsigned char const *)STR_LEN("submitter-name")); yajl_gen_string(json, (unsigned char const *)username, strlen(username)); } strarg_t const reponame = SLNRepoGetName(blog->repo); if(reponame) { yajl_gen_string(json, (unsigned char const *)STR_LEN("submitter-repo")); yajl_gen_string(json, (unsigned char const *)reponame, strlen(reponame)); } time_t const now = time(NULL); struct tm t[1]; gmtime_r(&now, t); // TODO: Error checking? str_t tstr[31+1]; size_t const tlen = strftime(tstr, sizeof(tstr), "%FT%TZ", t); // ISO 8601 if(tlen) { yajl_gen_string(json, (unsigned char const *)STR_LEN("submission-time")); yajl_gen_string(json, (unsigned char const *)tstr, tlen); } yajl_gen_string(json, (unsigned char const *)STR_LEN("submission-software")); yajl_gen_string(json, (unsigned char const *)STR_LEN("StrongLink Blog")); str_t *fulltext = aasprintf("%s\n%s", title ?: "", NULL ?: ""); // TODO: Description, GNU-ism if(fulltext) { yajl_gen_string(json, (unsigned char const *)STR_LEN("fulltext")); yajl_gen_string(json, (unsigned char const *)fulltext, strlen(fulltext)); } FREE(&fulltext); yajl_gen_map_close(json); rc = SLNSubmissionEnd(extra); if(rc < 0) goto cleanup; SLNSubmissionRef subs[] = { sub, meta, extra }; rc = SLNSubmissionStoreBatch(subs, numberof(subs)); location = aasprintf("/?q=%s", target_QSEscaped); if(!location) rc = UV_ENOMEM; if(rc < 0) goto cleanup; HTTPConnectionSendRedirect(conn, 303, location); cleanup: if(json) { yajl_gen_free(json); json = NULL; } FREE(&title); SLNSubmissionFree(&sub); SLNSubmissionFree(&meta); SLNSubmissionFree(&extra); MultipartFormFree(&form); FREE(&target_QSEscaped); FREE(&location); if(rc < 0) return 500; return 0; }
static int parse_file(BlogRef const blog, SLNSessionRef const session, MultipartFormRef const form, SLNSubmissionRef *const outfile, SLNSubmissionRef *const outmeta, str_t **const outname) { assert(form); SLNSubmissionRef file = NULL; SLNSubmissionRef meta = NULL; SLNFileInfo src[1] = {}; str_t *htmlpath = NULL; int rc = 0; strarg_t const fields[] = { "content-type", "content-disposition", }; char content_type[100]; char content_disposition[1024]; uv_buf_t values[] = { uv_buf_init(BUF_LEN(content_type)), uv_buf_init(BUF_LEN(content_disposition)), }; assert(numberof(fields) == numberof(values)); rc = MultipartFormReadHeadersStatic(form, values, fields, numberof(values)); if(rc < 0) goto cleanup; strarg_t type; if(0 == strcmp("form-data; name=\"markdown\"", content_disposition)) { type = "text/markdown; charset=utf-8"; } else { type = content_type; static strarg_t const f[] = { "filename", "filename*" }; str_t *v[numberof(f)] = {}; ContentDispositionParse(content_disposition, NULL, v, f, numberof(f)); if(v[1]) { *outname = v[1]; v[1] = NULL; } else { *outname = v[0]; v[0] = NULL; } for(size_t i = 0; i < numberof(v); i++) FREE(&v[i]); } rc = SLNSubmissionCreate(session, NULL, NULL, &file); if(rc < 0) goto cleanup; rc = SLNSubmissionSetType(file, type); if(rc < 0) goto cleanup; for(;;) { uv_buf_t buf[1]; rc = MultipartFormReadData(form, buf); if(rc < 0) goto cleanup; if(0 == buf->len) break; rc = SLNSubmissionWrite(file, (byte_t const *)buf->base, buf->len); if(rc < 0) goto cleanup; } rc = SLNSubmissionEnd(file); if(rc < 0) goto cleanup; rc = SLNSubmissionGetFileInfo(file, src); if(rc < 0) goto cleanup; htmlpath = BlogCopyPreviewPath(blog, src->hash); if(!htmlpath) rc = UV_ENOMEM; if(rc < 0) goto cleanup; strarg_t const URI = SLNSubmissionGetPrimaryURI(file); (void)BlogConvert(blog, session, htmlpath, &meta, URI, src); // We don't actually care about failure here? // Even if no preview and no meta-file can be generated, that's fine. *outfile = file; file = NULL; *outmeta = meta; meta = NULL; cleanup: SLNSubmissionFree(&file); SLNSubmissionFree(&meta); SLNFileInfoCleanup(src); FREE(&htmlpath); return rc; }