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 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; }