static int GET_account(BlogRef const blog, SLNSessionRef const session, HTTPConnectionRef const conn, HTTPMethod const method, strarg_t const URI, HTTPHeadersRef const headers) { if(HTTP_GET != method && HTTP_HEAD != method) return -1; if(0 != uripathcmp("/account", URI, NULL)) return -1; str_t *reponame_HTMLSafe = htmlenc(SLNRepoGetName(blog->repo)); if(!reponame_HTMLSafe) return 500; TemplateStaticArg const args[] = { {"reponame", reponame_HTMLSafe}, {"token", "asdf"}, // TODO {"userlen", "32"}, {"passlen", "64"}, {NULL, NULL}, }; HTTPConnectionWriteResponse(conn, 200, "OK"); HTTPConnectionWriteHeader(conn, "Content-Type", "text/html; charset=utf-8"); HTTPConnectionWriteHeader(conn, "Transfer-Encoding", "chunked"); HTTPConnectionBeginBody(conn); if(HTTP_HEAD != method) { TemplateWriteHTTPChunk(blog->login, &TemplateStaticCBs, args, conn); HTTPConnectionWriteChunkEnd(conn); } HTTPConnectionEnd(conn); FREE(&reponame_HTMLSafe); return 0; }
static int GET_upload(BlogRef const blog, SLNSessionRef const session, HTTPConnectionRef const conn, HTTPMethod const method, strarg_t const URI, HTTPHeadersRef const headers) { if(HTTP_GET != method) return -1; if(!URIPath(URI, "/upload", NULL)) return -1; if(!SLNSessionHasPermission(session, SLN_WRONLY)) return 403; str_t *reponame_HTMLSafe = htmlenc(SLNRepoGetName(blog->repo)); if(!reponame_HTMLSafe) return 500; TemplateStaticArg const args[] = { {"reponame", reponame_HTMLSafe}, {"token", "asdf"}, {NULL, NULL}, }; HTTPConnectionWriteResponse(conn, 200, "OK"); HTTPConnectionWriteHeader(conn, "Content-Type", "text/html; charset=utf-8"); HTTPConnectionWriteHeader(conn, "Transfer-Encoding", "chunked"); HTTPConnectionBeginBody(conn); TemplateWriteHTTPChunk(blog->upload, &TemplateStaticCBs, args, conn); HTTPConnectionWriteChunkEnd(conn); HTTPConnectionEnd(conn); FREE(&reponame_HTMLSafe); return 0; }
static bool_t getPage(EFSRepoRef const repo, HTTPConnectionRef const conn, HTTPMethod const method, strarg_t const URI) { if(HTTP_GET != method) return false; size_t pathlen = prefix("/", URI); if(!pathlen) return false; if(!pathterm(URI, (size_t)pathlen)) return false; EFSSessionRef const session = auth(repo, conn, method, URI+pathlen); if(!session) { HTTPConnectionSendStatus(conn, 403); return true; } // TODO: Parse querystring `q` parameter EFSFilterRef const filter = EFSFilterCreate(EFSTypeFilter); EFSFilterAddStringArg(filter, "text/html; charset=utf-8"); EFSFileInfo *const files = EFSSessionCreateFileInfoList(session, filter, RESULTS_MAX); HTTPConnectionWriteResponse(conn, 200, "OK"); HTTPConnectionWriteHeader(conn, "Content-Type", "text/html; charset=utf-8"); HTTPConnectionWriteHeader(conn, "Transfer-Encoding", "chunked"); HTTPConnectionBeginBody(conn); // TODO: Page header uv_fs_t req = { .data = co_active(); } for(index_t i = 0; i < files->count; ++i) { uv_fs_open(loop, &req, path, O_RDONLY, 0400, async_fs_cb); co_switch(yield); uv_fs_req_cleanup(&req); uv_file const file = req.result; if(file < 0) continue; HTTPConnectionWriteChunkLength(conn, files->items[i].size); HTTPConnectionWriteFile(conn, file); uv_fs_close(loop, &req, file, async_fs_cb); co_switch(yield); uv_fs_req_cleanup(&req); HTTPConnectionWrite(conn, "\r\n", 2); } // TODO: Page trailer HTTPConnectionWriteChunkLength(conn, 0); HTTPConnectionWrite(conn, "\r\n", 2); HTTPConnectionEnd(conn); EFSFileInfoListFree(files); return true; }
int HTTPConnectionSendFile(HTTPConnectionRef const conn, strarg_t const path, strarg_t const type, int64_t size) { int rc = async_fs_open(path, O_RDONLY, 0000); if(UV_ENOENT == rc) return HTTPConnectionSendStatus(conn, 404); if(rc < 0) return HTTPConnectionSendStatus(conn, 400); // TODO: Error conversion. uv_file file = rc; rc = 0; if(size < 0) { uv_fs_t req[1]; rc = async_fs_fstat(file, req); if(rc < 0) { rc = HTTPConnectionSendStatus(conn, 400); goto cleanup; } if(S_ISDIR(req->statbuf.st_mode)) { rc = UV_EISDIR; goto cleanup; } if(!S_ISREG(req->statbuf.st_mode)) { rc = HTTPConnectionSendStatus(conn, 403); goto cleanup; } size = req->statbuf.st_size; } rc = rc < 0 ? rc : HTTPConnectionWriteResponse(conn, 200, "OK"); rc = rc < 0 ? rc : HTTPConnectionWriteContentLength(conn, size); // TODO: Caching and other headers. if(type) rc = rc < 0 ? rc : HTTPConnectionWriteHeader(conn, "Content-Type", type); rc = rc < 0 ? rc : HTTPConnectionBeginBody(conn); rc = rc < 0 ? rc : HTTPConnectionWriteFile(conn, file); rc = rc < 0 ? rc : HTTPConnectionEnd(conn); cleanup: async_fs_close(file); file = -1; return rc; }
static void created(strarg_t const URI, HTTPConnectionRef const conn) { HTTPConnectionWriteResponse(conn, 201, "Created"); HTTPConnectionWriteHeader(conn, "X-Location", URI); // TODO: X-Content-Address or something? Or X-Name? HTTPConnectionWriteContentLength(conn, 0); HTTPConnectionBeginBody(conn); HTTPConnectionEnd(conn); }
static int reconnect(SLNPullRef const pull) { int rc; HTTPConnectionFree(&pull->conn); rc = HTTPConnectionCreateOutgoing(pull->host, 0, &pull->conn); if(rc < 0) { alogf("Pull couldn't connect to %s (%s)\n", pull->host, sln_strerror(rc)); return rc; } // str_t path[URI_MAX]; // str_t *query_encoded = NULL; // if(pull->query) query_encoded = QSEscape(pull->query, strlen(pull->query), true); // snprintf(path, sizeof(path), "/sln/query-obsolete?q=%s", query_encoded ?: ""); // FREE(&query_encoded); HTTPConnectionWriteRequest(pull->conn, HTTP_GET, "/sln/all", pull->host); // TODO // - New API /sln/query and /sln/metafiles // - Pagination ?start=[last URI seen] // - Error handling // - Query string formatter HTTPConnectionWriteHeader(pull->conn, "Cookie", pull->cookie); HTTPConnectionBeginBody(pull->conn); rc = HTTPConnectionEnd(pull->conn); if(rc < 0) { alogf("Pull couldn't connect to %s (%s)\n", pull->host, sln_strerror(rc)); return rc; } int const status = HTTPConnectionReadResponseStatus(pull->conn); if(status < 0) { alogf("Pull connection error: %s\n", sln_strerror(status)); return status; } if(403 == status) { alogf("Pull connection authentication failed\n"); return UV_EACCES; } if(status < 200 || status >= 300) { alogf("Pull connection error: %d\n", status); return UV_EPROTO; } // TODO: All this does is scan past the headers. // We don't actually use them... HTTPHeadersRef headers; rc = HTTPHeadersCreateFromConnection(pull->conn, &headers); assert(rc >= 0); // TODO HTTPHeadersFree(&headers); /* rc = HTTPConnectionReadHeaders(pull->conn, NULL, NULL, 0); if(rc < 0) { alogf("Pull connection error %s\n", sln_strerror(rc)); return rc; }*/ return 0; }
int HTTPConnectionSendRedirect(HTTPConnectionRef const conn, uint16_t const status, strarg_t const location) { int rc = 0; strarg_t const str = statusstr(status); rc = rc < 0 ? rc : HTTPConnectionWriteResponse(conn, status, str); rc = rc < 0 ? rc : HTTPConnectionWriteHeader(conn, "Location", location); rc = rc < 0 ? rc : HTTPConnectionWriteContentLength(conn, 0); rc = rc < 0 ? rc : HTTPConnectionBeginBody(conn); rc = rc < 0 ? rc : HTTPConnectionEnd(conn); return rc; }
static int POST_auth(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("/auth", URI, NULL)) return -1; // TODO: Check that Content-Type is application/x-www-form-urlencoded. str_t formdata[AUTH_FORM_MAX]; ssize_t len = HTTPConnectionReadBodyStatic(conn, (byte_t *)formdata, sizeof(formdata)-1); if(UV_EMSGSIZE == len) return 413; // Request Entity Too Large if(len < 0) return 500; formdata[len] = '\0'; SLNSessionCacheRef const cache = SLNRepoGetSessionCache(blog->repo); static strarg_t const fields[] = { "action-login", "action-register", "user", "pass", "token", // TODO: CSRF protection }; str_t *values[numberof(fields)] = {}; QSValuesParse(formdata, values, fields, numberof(fields)); if(values[1]) { QSValuesCleanup(values, numberof(values)); return 501; // TODO: Not Implemented } if(!values[0]) { QSValuesCleanup(values, numberof(values)); return 400; // Not login? } SLNSessionRef s; int rc = SLNSessionCacheCreateSession(cache, values[2], values[3], &s); // TODO QSValuesCleanup(values, numberof(values)); if(rc < 0) { HTTPConnectionSendRedirect(conn, 303, "/account?err=1"); return 0; } str_t *cookie = SLNSessionCopyCookie(s); SLNSessionRelease(&s); if(!cookie) return 500; HTTPConnectionWriteResponse(conn, 303, "See Other"); HTTPConnectionWriteHeader(conn, "Location", "/"); HTTPConnectionWriteSetCookie(conn, cookie, "/", 60 * 60 * 24 * 365); HTTPConnectionWriteContentLength(conn, 0); HTTPConnectionBeginBody(conn); HTTPConnectionEnd(conn); FREE(&cookie); return 0; }
int HTTPConnectionSendMessage(HTTPConnectionRef const conn, uint16_t const status, strarg_t const str) { size_t const len = strlen(str); int rc = 0; rc = rc < 0 ? rc : HTTPConnectionWriteResponse(conn, status, str); rc = rc < 0 ? rc : HTTPConnectionWriteHeader(conn, "Content-Type", "text/plain; charset=utf-8"); rc = rc < 0 ? rc : HTTPConnectionWriteContentLength(conn, len+1); rc = rc < 0 ? rc : HTTPConnectionBeginBody(conn); // TODO: Check how HEAD responses should look. if(HTTP_HEAD != conn->parser->method) { // TODO: Expose method? What? rc = rc < 0 ? rc : HTTPConnectionWrite(conn, (byte_t const *)str, len); rc = rc < 0 ? rc : HTTPConnectionWrite(conn, (byte_t const *)STR_LEN("\n")); } rc = rc < 0 ? rc : HTTPConnectionEnd(conn); // if(status >= 400) fprintf(stderr, "%s: %d %s\n", HTTPConnectionGetRequestURI(conn), (int)status, str); return rc; }
int HTTPConnectionSendFile(HTTPConnectionRef const conn, strarg_t const path, strarg_t const type, int64_t size) { uv_file const file = async_fs_open(path, O_RDONLY, 0000); if(UV_ENOENT == file) return HTTPConnectionSendStatus(conn, 404); if(file < 0) return HTTPConnectionSendStatus(conn, 400); // TODO: Error conversion. int rc = 0; if(size < 0) { uv_fs_t req[1]; rc = async_fs_fstat(file, req); if(rc < 0) return HTTPConnectionSendStatus(conn, 400); size = req->statbuf.st_size; } rc = rc < 0 ? rc : HTTPConnectionWriteResponse(conn, 200, "OK"); rc = rc < 0 ? rc : HTTPConnectionWriteContentLength(conn, size); // TODO: Caching and other headers. if(type) rc = rc < 0 ? rc : HTTPConnectionWriteHeader(conn, "Content-Type", type); rc = rc < 0 ? rc : HTTPConnectionBeginBody(conn); rc = rc < 0 ? rc : HTTPConnectionWriteFile(conn, file); rc = rc < 0 ? rc : HTTPConnectionEnd(conn); async_fs_close(file); return rc; }
static int GET_query(BlogRef const blog, SLNSessionRef const session, HTTPConnectionRef const conn, HTTPMethod const method, strarg_t const URI, HTTPHeadersRef const headers) { if(HTTP_GET != method && HTTP_HEAD != method) return -1; strarg_t qs = NULL; if(0 != uripathcmp("/", URI, &qs)) return -1; if(HTTP_HEAD == method) return 501; // TODO // TODO: This is the most complicated function in the whole program. // It's unbearable. str_t *query = NULL; str_t *query_HTMLSafe = NULL; str_t *parsed_HTMLSafe = NULL; SLNFilterRef filter = NULL; int rc; static strarg_t const fields[] = { "q", }; str_t *values[numberof(fields)] = {}; QSValuesParse(qs, values, fields, numberof(fields)); query = values[0]; values[0] = NULL; query_HTMLSafe = htmlenc(values[0]); rc = SLNUserFilterParse(session, query, &filter); QSValuesCleanup(values, numberof(values)); if(DB_EACCES == rc) { FREE(&query); FREE(&query_HTMLSafe); return 403; } // SLNFilterPrintSexp(filter, stderr, 0); // DEBUG if(DB_EINVAL == rc) rc = SLNFilterCreate(session, SLNVisibleFilterType, &filter); if(rc < 0) { FREE(&query); FREE(&query_HTMLSafe); return 500; } str_t tmp[URI_MAX]; tmp[0] = '\0'; // fmemopen shim ignores mode. FILE *parsed = fmemopen(tmp, sizeof(tmp), "w"); if(!parsed) { FREE(&query); FREE(&query_HTMLSafe); return 500; } SLNFilterPrintUser(filter, parsed, 0); fclose(parsed); tmp[sizeof(tmp)-1] = '\0'; // fmemopen(3) says this isn't guaranteed. parsed_HTMLSafe = htmlenc(tmp); SLNFilterPosition pos[1] = {{ .dir = -1 }}; str_t *URIs[RESULTS_MAX]; uint64_t max = numberof(URIs); int outdir = -1; SLNFilterParseOptions(qs, pos, &max, &outdir, NULL); if(max < 1) max = 1; if(max > numberof(URIs)) max = numberof(URIs); bool const has_start = !!pos->URI; uint64_t const t1 = uv_hrtime(); ssize_t const count = SLNFilterCopyURIs(filter, session, pos, outdir, false, URIs, (size_t)max); SLNFilterPositionCleanup(pos); if(count < 0) { FREE(&query); FREE(&query_HTMLSafe); FREE(&parsed_HTMLSafe); SLNFilterFree(&filter); if(DB_NOTFOUND == count) { // Possibly a filter age-function bug. alogf("Invalid start parameter? %s\n", URI); return 500; } alogf("Filter error: %s\n", sln_strerror(count)); return 500; } SLNFilterFree(&filter); uint64_t const t2 = uv_hrtime(); str_t *reponame_HTMLSafe = htmlenc(SLNRepoGetName(blog->repo)); snprintf(tmp, sizeof(tmp), "Queried in %.6f seconds", (t2-t1) / 1e9); str_t *querytime_HTMLSafe = htmlenc(tmp); str_t *account_HTMLSafe; if(0 == SLNSessionGetUserID(session)) { account_HTMLSafe = htmlenc("Log In"); } else { strarg_t const user = SLNSessionGetUsername(session); snprintf(tmp, sizeof(tmp), "Account: %s", user); account_HTMLSafe = htmlenc(tmp); } // TODO: Write a real function for building query strings // Don't use ?: GNUism // Preserve other query parameters like `dir` str_t *query_encoded = !query ? NULL : QSEscape(query, strlen(query), true); FREE(&query); str_t *firstpage_HTMLSafe = NULL; str_t *prevpage_HTMLSafe = NULL; str_t *nextpage_HTMLSafe = NULL; str_t *lastpage_HTMLSafe = NULL; snprintf(tmp, sizeof(tmp), "?q=%s&start=-", query_encoded ?: ""); firstpage_HTMLSafe = htmlenc(tmp); str_t *p = !count ? NULL : URIs[outdir > 0 ? 0 : count-1]; str_t *n = !count ? NULL : URIs[outdir > 0 ? count-1 : 0]; if(p) p = QSEscape(p, strlen(p), 1); if(n) n = QSEscape(n, strlen(n), 1); snprintf(tmp, sizeof(tmp), "?q=%s&start=%s", query_encoded ?: "", p ?: ""); prevpage_HTMLSafe = htmlenc(tmp); snprintf(tmp, sizeof(tmp), "?q=%s&start=-%s", query_encoded ?: "", n ?: ""); nextpage_HTMLSafe = htmlenc(tmp); snprintf(tmp, sizeof(tmp), "?q=%s", query_encoded ?: ""); lastpage_HTMLSafe = htmlenc(tmp); FREE(&query_encoded); FREE(&p); FREE(&n); str_t *qs_HTMLSafe = htmlenc(qs); TemplateStaticArg const args[] = { {"reponame", reponame_HTMLSafe}, {"querytime", querytime_HTMLSafe}, {"account", account_HTMLSafe}, {"query", query_HTMLSafe}, {"parsed", parsed_HTMLSafe}, {"firstpage", firstpage_HTMLSafe}, {"prevpage", prevpage_HTMLSafe}, {"nextpage", nextpage_HTMLSafe}, {"lastpage", lastpage_HTMLSafe}, {"qs", qs_HTMLSafe}, {NULL, NULL}, }; if(count > 0) { HTTPConnectionWriteResponse(conn, 200, "OK"); } else { HTTPConnectionWriteResponse(conn, 404, "Not Found"); } HTTPConnectionWriteHeader(conn, "Content-Type", "text/html; charset=utf-8"); HTTPConnectionWriteHeader(conn, "Transfer-Encoding", "chunked"); if(0 == SLNSessionGetUserID(session)) { HTTPConnectionWriteHeader(conn, "Cache-Control", "no-cache, public"); } else { HTTPConnectionWriteHeader(conn, "Cache-Control", "no-cache, private"); } HTTPConnectionBeginBody(conn); TemplateWriteHTTPChunk(blog->header, &TemplateStaticCBs, args, conn); if(0 == count) { TemplateWriteHTTPChunk(blog->noresults, &TemplateStaticCBs, args, conn); } for(size_t i = 0; i < count; i++) { str_t algo[SLN_ALGO_SIZE]; // SLN_INTERNAL_ALGO str_t hash[SLN_HASH_SIZE]; SLNParseURI(URIs[i], algo, hash); str_t *previewPath = BlogCopyPreviewPath(blog, hash); rc = send_preview(blog, conn, session, URIs[i], previewPath); FREE(&previewPath); if(rc < 0) break; } // TODO: HACK // Hide the pagination buttons when there are less than one full page of results. if(count >= max || has_start) { TemplateWriteHTTPChunk(blog->footer, &TemplateStaticCBs, args, conn); } FREE(&reponame_HTMLSafe); FREE(&querytime_HTMLSafe); FREE(&account_HTMLSafe); FREE(&query_HTMLSafe); FREE(&parsed_HTMLSafe); FREE(&firstpage_HTMLSafe); FREE(&prevpage_HTMLSafe); FREE(&nextpage_HTMLSafe); FREE(&lastpage_HTMLSafe); FREE(&qs_HTMLSafe); HTTPConnectionWriteChunkEnd(conn); HTTPConnectionEnd(conn); for(size_t i = 0; i < count; i++) FREE(&URIs[i]); assert_zeroed(URIs, count); return 0; }
static int import(SLNPullRef const pull, strarg_t const URI, size_t const pos, HTTPConnectionRef *const conn) { if(!pull) return 0; // TODO: Even if there's nothing to do, we have to enqueue something to fill up our reserved slots. I guess it's better than doing a lot of work inside the connection lock, but there's got to be a better way. SLNSubmissionRef sub = NULL; HTTPHeadersRef headers = NULL; if(!URI) goto enqueue; str_t algo[SLN_ALGO_SIZE]; str_t hash[SLN_HASH_SIZE]; if(SLNParseURI(URI, algo, hash) < 0) goto enqueue; int rc = SLNSessionGetFileInfo(pull->session, URI, NULL); if(rc >= 0) goto enqueue; db_assertf(DB_NOTFOUND == rc, "Database error: %s", sln_strerror(rc)); // TODO: We're logging out of order when we do it like this... // alogf("Pulling %s\n", URI); if(!*conn) { rc = HTTPConnectionCreateOutgoing(pull->host, 0, conn); if(rc < 0) { alogf("Pull import connection error: %s\n", sln_strerror(rc)); goto fail; } } str_t *path = aasprintf("/sln/file/%s/%s", algo, hash); if(!path) { alogf("Pull aasprintf error\n"); goto fail; } rc = HTTPConnectionWriteRequest(*conn, HTTP_GET, path, pull->host); assert(rc >= 0); // TODO FREE(&path); HTTPConnectionWriteHeader(*conn, "Cookie", pull->cookie); HTTPConnectionBeginBody(*conn); rc = HTTPConnectionEnd(*conn); if(rc < 0) { alogf("Pull import request error: %s\n", sln_strerror(rc)); goto fail; } int const status = HTTPConnectionReadResponseStatus(*conn); if(status < 0) { alogf("Pull import response error: %s\n", sln_strerror(status)); goto fail; } if(status < 200 || status >= 300) { alogf("Pull import status error: %d\n", status); goto fail; } rc = HTTPHeadersCreateFromConnection(*conn, &headers); assert(rc >= 0); // TODO /* if(rc < 0) { alogf("Pull import headers error %s\n", sln_strerror(rc)); goto fail; }*/ strarg_t const type = HTTPHeadersGet(headers, "content-type"); rc = SLNSubmissionCreate(pull->session, URI, &sub); if(rc < 0) { alogf("Pull submission error: %s\n", sln_strerror(rc)); goto fail; } rc = SLNSubmissionSetType(sub, type); if(rc < 0) { alogf("Pull submission type error: %s\n", sln_strerror(rc)); goto fail; } for(;;) { if(pull->stop) goto fail; uv_buf_t buf[1] = {}; rc = HTTPConnectionReadBody(*conn, buf); if(rc < 0) { alogf("Pull download error: %s\n", sln_strerror(rc)); goto fail; } if(0 == buf->len) break; rc = SLNSubmissionWrite(sub, (byte_t *)buf->base, buf->len); if(rc < 0) { alogf("Pull write error\n"); goto fail; } } rc = SLNSubmissionEnd(sub); if(rc < 0) { alogf("Pull submission error: %s\n", sln_strerror(rc)); goto fail; } enqueue: HTTPHeadersFree(&headers); async_mutex_lock(pull->mutex); pull->queue[pos] = sub; sub = NULL; pull->filled[pos] = true; async_cond_broadcast(pull->cond); async_mutex_unlock(pull->mutex); return 0; fail: HTTPHeadersFree(&headers); SLNSubmissionFree(&sub); HTTPConnectionFree(conn); return -1; }
static int GET_query(BlogRef const blog, SLNSessionRef const session, HTTPConnectionRef const conn, HTTPMethod const method, strarg_t const URI, HTTPHeadersRef const headers) { if(HTTP_GET != method) return -1; strarg_t qs = NULL; if(!URIPath(URI, "/", &qs)) return -1; // TODO: This is the most complicated function in the whole program. // It's unbearable. str_t *query = NULL; str_t *query_HTMLSafe = NULL; SLNFilterRef filter = NULL; int rc; static strarg_t const fields[] = { "q", }; str_t *values[numberof(fields)] = {}; QSValuesParse(qs, values, fields, numberof(fields)); query = values[0] ? strdup(values[0]) : NULL; query_HTMLSafe = htmlenc(values[0]); rc = SLNUserFilterParse(session, values[0], &filter); QSValuesCleanup(values, numberof(values)); if(DB_EACCES == rc) { FREE(&query); FREE(&query_HTMLSafe); return 403; } if(DB_EINVAL == rc) rc = SLNFilterCreate(session, SLNVisibleFilterType, &filter); if(rc < 0) { FREE(&query); FREE(&query_HTMLSafe); return 500; } str_t tmp[URI_MAX]; SLNFilterToUserFilterString(filter, tmp, sizeof(tmp), 0); str_t *parsed_HTMLSafe = htmlenc(tmp); str_t *primaryURI = NULL; SLNFilterRef core = SLNFilterUnwrap(filter); SLNFilterType const filtertype = SLNFilterGetType(core); if(SLNURIFilterType == filtertype) { primaryURI = strdup(SLNFilterGetStringArg(core, 0)); assert(primaryURI); // TODO SLNFilterRef alt; rc = SLNFilterCreate(session, SLNLinksToFilterType, &alt); assert(rc >= 0); // TODO SLNFilterAddStringArg(alt, primaryURI, -1); SLNFilterFree(&filter); filter = alt; alt = NULL; } core = NULL; // SLNFilterPrint(filter, 0); // DEBUG SLNFilterPosition pos[1] = {{ .dir = -1 }}; uint64_t max = RESULTS_MAX; int outdir = -1; SLNFilterParseOptions(qs, pos, &max, &outdir, NULL); if(max < 1) max = 1; if(max > RESULTS_MAX) max = RESULTS_MAX; uint64_t const t1 = uv_hrtime(); str_t *URIs[RESULTS_MAX]; ssize_t const count = SLNFilterCopyURIs(filter, session, pos, outdir, false, URIs, (size_t)max); SLNFilterPositionCleanup(pos); if(count < 0) { fprintf(stderr, "Filter error: %s\n", sln_strerror(count)); FREE(&query); FREE(&query_HTMLSafe); SLNFilterFree(&filter); return 500; } SLNFilterFree(&filter); uint64_t const t2 = uv_hrtime(); str_t *reponame_HTMLSafe = htmlenc(SLNRepoGetName(blog->repo)); snprintf(tmp, sizeof(tmp), "Queried in %.3f seconds", (t2-t1) / 1e9); str_t *querytime_HTMLSafe = htmlenc(tmp); str_t *account_HTMLSafe; if(0 == SLNSessionGetUserID(session)) { account_HTMLSafe = htmlenc("Log In"); } else { strarg_t const user = SLNSessionGetUsername(session); snprintf(tmp, sizeof(tmp), "Account: %s", user); account_HTMLSafe = htmlenc(tmp); } // TODO: Write a real function for building query strings // Don't use ?: GNUism // Preserve other query parameters like `dir` str_t *query_encoded = !query ? NULL : QSEscape(query, strlen(query), true); FREE(&query); str_t *firstpage_HTMLSafe = NULL; str_t *prevpage_HTMLSafe = NULL; str_t *nextpage_HTMLSafe = NULL; str_t *lastpage_HTMLSafe = NULL; snprintf(tmp, sizeof(tmp), "?q=%s&start=-", query_encoded ?: ""); firstpage_HTMLSafe = htmlenc(tmp); str_t *p = !count ? NULL : URIs[outdir > 0 ? 0 : count-1]; str_t *n = !count ? NULL : URIs[outdir > 0 ? count-1 : 0]; if(p) p = QSEscape(p, strlen(p), 1); if(n) n = QSEscape(n, strlen(n), 1); snprintf(tmp, sizeof(tmp), "?q=%s&start=%s", query_encoded ?: "", p ?: ""); prevpage_HTMLSafe = htmlenc(tmp); snprintf(tmp, sizeof(tmp), "?q=%s&start=-%s", query_encoded ?: "", n ?: ""); nextpage_HTMLSafe = htmlenc(tmp); snprintf(tmp, sizeof(tmp), "?q=%s", query_encoded ?: ""); lastpage_HTMLSafe = htmlenc(tmp); FREE(&query_encoded); FREE(&p); FREE(&n); TemplateStaticArg const args[] = { {"reponame", reponame_HTMLSafe}, {"querytime", querytime_HTMLSafe}, {"account", account_HTMLSafe}, {"query", query_HTMLSafe}, {"parsed", parsed_HTMLSafe}, {"firstpage", firstpage_HTMLSafe}, {"prevpage", prevpage_HTMLSafe}, {"nextpage", nextpage_HTMLSafe}, {"lastpage", lastpage_HTMLSafe}, {NULL, NULL}, }; HTTPConnectionWriteResponse(conn, 200, "OK"); HTTPConnectionWriteHeader(conn, "Content-Type", "text/html; charset=utf-8"); HTTPConnectionWriteHeader(conn, "Transfer-Encoding", "chunked"); if(0 == SLNSessionGetUserID(session)) { HTTPConnectionWriteHeader(conn, "Cache-Control", "no-cache, public"); } else { HTTPConnectionWriteHeader(conn, "Cache-Control", "no-cache, private"); } HTTPConnectionBeginBody(conn); TemplateWriteHTTPChunk(blog->header, &TemplateStaticCBs, args, conn); if(primaryURI) { SLNFileInfo info[1]; rc = SLNSessionGetFileInfo(session, primaryURI, info); if(rc >= 0) { str_t *preferredURI = SLNFormatURI(SLN_INTERNAL_ALGO, info->hash); str_t *previewPath = BlogCopyPreviewPath(blog, info->hash); send_preview(blog, conn, session, preferredURI, previewPath); FREE(&preferredURI); FREE(&previewPath); SLNFileInfoCleanup(info); } else if(DB_NOTFOUND == rc) { TemplateWriteHTTPChunk(blog->notfound, &TemplateStaticCBs, args, conn); } if(count) { TemplateWriteHTTPChunk(blog->backlinks, &TemplateStaticCBs, args, conn); } } bool const broadest_possible_filter = SLNVisibleFilterType == filtertype || SLNAllFilterType == filtertype; if(0 == count && !primaryURI && !broadest_possible_filter) { TemplateWriteHTTPChunk(blog->noresults, &TemplateStaticCBs, args, conn); } for(size_t i = 0; i < count; i++) { str_t algo[SLN_ALGO_SIZE]; // SLN_INTERNAL_ALGO str_t hash[SLN_HASH_SIZE]; SLNParseURI(URIs[i], algo, hash); str_t *previewPath = BlogCopyPreviewPath(blog, hash); rc = send_preview(blog, conn, session, URIs[i], previewPath); FREE(&previewPath); if(rc < 0) break; } FREE(&primaryURI); TemplateWriteHTTPChunk(blog->footer, &TemplateStaticCBs, args, conn); FREE(&reponame_HTMLSafe); FREE(&querytime_HTMLSafe); FREE(&account_HTMLSafe); FREE(&query_HTMLSafe); FREE(&parsed_HTMLSafe); FREE(&firstpage_HTMLSafe); FREE(&prevpage_HTMLSafe); FREE(&nextpage_HTMLSafe); FREE(&lastpage_HTMLSafe); HTTPConnectionWriteChunkEnd(conn); HTTPConnectionEnd(conn); for(size_t i = 0; i < count; i++) FREE(&URIs[i]); assert_zeroed(URIs, count); return 0; }
static void SLNFilterResultsWrite(SLNSessionRef const session, SLNFilterRef const filter, SLNFilterOpts *const opts, HTTPConnectionRef const conn) { // TODO: Accept count and use it for the total number of results. opts->count = 0; // We're sending a series of batches, so reversing one batch // doesn't make sense. opts->outdir = opts->dir; static strarg_t const fields[] = { "wait" }; str_t *values[numberof(fields)] = {}; QSValuesParse(qs, values, fields, numberof(fields)); bool const wait = parse_wait(values[0]); QSValuesCleanup(values, numberof(values)); // I'm aware that we're abusing HTTP for sending real-time push data. // I'd also like to support WebSocket at some point, but this is simpler // and frankly probably more widely supported. // Note that the protocol doesn't really break even if this data is // cached. It DOES break if a proxy tries to buffer the whole response // before passing it back to the client. I'd be curious to know whether // such proxies still exist in 2015. HTTPConnectionWriteResponse(conn, 200, "OK"); HTTPConnectionWriteHeader(conn, "Transfer-Encoding", "chunked"); HTTPConnectionWriteHeader(conn, "Content-Type", "text/uri-list; charset=utf-8"); HTTPConnectionWriteHeader(conn, "Cache-Control", "no-store"); HTTPConnectionWriteHeader(conn, "Vary", "*"); HTTPConnectionBeginBody(conn); int rc; for(;;) { rc = sendURIBatch(session, filter, opts, conn); if(DB_NOTFOUND == rc) break; if(DB_SUCCESS == rc) continue; fprintf(stderr, "Query error: %s\n", db_strerror(rc)); goto cleanup; } if(!wait || opts->dir < 0) goto cleanup; SLNRepoRef const repo = SLNSessionGetRepo(session); for(;;) { uint64_t const timeout = uv_now(async_loop)+(1000 * 30); rc = SLNRepoSubmissionWait(repo, opts->sortID, timeout); if(UV_ETIMEDOUT == rc) { uv_buf_t const parts[] = { uv_buf_init((char *)STR_LEN("\r\n")) }; rc = HTTPConnectionWriteChunkv(conn, parts, numberof(parts)); if(rc < 0) break; continue; } assert(rc >= 0); // TODO: Handle cancellation? for(;;) { rc = sendURIBatch(session, filter, opts, conn); if(DB_NOTFOUND == rc) break; if(DB_SUCCESS == rc) continue; fprintf(stderr, "Query error: %s\n", db_strerror(rc)); goto cleanup; } } cleanup: HTTPConnectionWriteChunkEnd(conn); HTTPConnectionEnd(conn); SLNFilterOptsCleanup(opts); }
/*static int POST_auth(SLNRepoRef const repo, 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("/sln/auth", URI, NULL)) return -1; str_t formdata[AUTH_FORM_MAX]; ssize_t len = HTTPConnectionReadBodyStatic(conn, (byte_t *)formdata, sizeof(formdata)-1); if(UV_EMSGSIZE == len) return 413; // Request Entity Too Large if(len < 0) return 500; formdata[len] = '\0'; SLNSessionCacheRef const cache = SLNRepoGetSessionCache(repo); static strarg_t const fields[] = { "user", "pass", "token", // TODO: CSRF protection }; str_t *values[numberof(fields)] = {}; QSValuesParse(formdata, values, fields, numberof(fields)); SLNSessionRef s; int rc = SLNSessionCacheCreateSession(cache, values[0], values[1], &s); QSValuesCleanup(values, numberof(values)); if(rc < 0) return 403; str_t *cookie = SLNSessionCopyCookie(s); SLNSessionRelease(&s); if(!cookie) return 500; HTTPConnectionWriteResponse(conn, 200, "OK"); HTTPConnectionWriteSetCookie(conn, cookie, "/", 60 * 60 * 24 * 365); HTTPConnectionWriteContentLength(conn, 0); HTTPConnectionBeginBody(conn); HTTPConnectionEnd(conn); FREE(&cookie); return 0; }*/ static int GET_file(SLNRepoRef const repo, SLNSessionRef const session, HTTPConnectionRef const conn, HTTPMethod const method, strarg_t const URI, HTTPHeadersRef const headers) { if(HTTP_GET != method && HTTP_HEAD != method) return -1; int len = 0; str_t algo[SLN_ALGO_SIZE]; str_t hash[SLN_HASH_SIZE]; algo[0] = '\0'; hash[0] = '\0'; sscanf(URI, "/sln/file/" SLN_ALGO_FMT "/" SLN_HASH_FMT "%n", algo, hash, &len); if(!algo[0] || !hash[0]) return -1; if('\0' != URI[len] && '?' != URI[len]) return -1; // TODO: Check for conditional get headers and return 304 Not Modified. str_t fileURI[SLN_URI_MAX]; int rc = snprintf(fileURI, sizeof(fileURI), "hash://%s/%s", algo, hash); if(rc < 0 || rc >= sizeof(fileURI)) return 500; SLNFileInfo info[1]; rc = SLNSessionGetFileInfo(session, fileURI, info); if(DB_EACCES == rc) return 403; if(DB_NOTFOUND == rc) return 404; if(rc < 0) return 500; uv_file file = async_fs_open(info->path, O_RDONLY, 0000); if(UV_ENOENT == file) { SLNFileInfoCleanup(info); return 410; // Gone } if(file < 0) { SLNFileInfoCleanup(info); return 500; } // TODO: Hosting untrusted data is really hard. // The same origin policy isn't doing us any favors, because most // simple installations won't have a second domain to use. // - For loopback, we can use an alternate loopback IP, but that // still only covers a limited range of use cases. // - We would really like suborigins, if they're ever widely supported. // <http://www.chromium.org/developers/design-documents/per-page-suborigins> // - We could report untrusted types as "text/plain" or // "application/binary", which would be inconvenient but // very reliable. // TODO: Use Content-Disposition to suggest a filename, for file types // that aren't useful to view inline. HTTPConnectionWriteResponse(conn, 200, "OK"); HTTPConnectionWriteContentLength(conn, info->size); HTTPConnectionWriteHeader(conn, "Content-Type", info->type); HTTPConnectionWriteHeader(conn, "Cache-Control", "max-age=31536000"); HTTPConnectionWriteHeader(conn, "ETag", "1"); // HTTPConnectionWriteHeader(conn, "Accept-Ranges", "bytes"); // TODO HTTPConnectionWriteHeader(conn, "Content-Security-Policy", "'none'"); HTTPConnectionWriteHeader(conn, "X-Content-Type-Options", "nosniff"); // HTTPConnectionWriteHeader(conn, "Vary", "Accept, Accept-Ranges"); // TODO: Double check Vary header syntax. // Also do we need to change the ETag? HTTPConnectionBeginBody(conn); if(HTTP_HEAD != method) { HTTPConnectionWriteFile(conn, file); } HTTPConnectionEnd(conn); SLNFileInfoCleanup(info); async_fs_close(file); return 0; }