void BlogFree(BlogRef *const blogptr) { BlogRef blog = *blogptr; if(!blog) return; blog->repo = NULL; FREE(&blog->dir); FREE(&blog->cacheDir); TemplateFree(&blog->header); TemplateFree(&blog->footer); TemplateFree(&blog->entry_start); TemplateFree(&blog->entry_end); TemplateFree(&blog->preview); TemplateFree(&blog->empty); TemplateFree(&blog->compose); TemplateFree(&blog->upload); TemplateFree(&blog->login); TemplateFree(&blog->notfound); TemplateFree(&blog->noresults); async_mutex_destroy(blog->pending_mutex); async_cond_destroy(blog->pending_cond); assert_zeroed(blog, 1); FREE(blogptr); blog = NULL; }
void SLNSubmissionFree(SLNSubmissionRef *const subptr) { SLNSubmissionRef sub = *subptr; if(!sub) return; sub->session = NULL; FREE(&sub->knownURI); FREE(&sub->type); if(sub->tmppath) async_fs_unlink(sub->tmppath); FREE(&sub->tmppath); if(sub->tmpfile >= 0) async_fs_close(sub->tmpfile); sub->tmpfile = 0; sub->size = 0; SLNHasherFree(&sub->hasher); sub->metaFileID = 0; if(sub->URIs) for(size_t i = 0; sub->URIs[i]; ++i) FREE(&sub->URIs[i]); FREE(&sub->URIs); FREE(&sub->primaryURI); FREE(&sub->internalHash); assert_zeroed(sub, 1); FREE(subptr); sub = NULL; }
void SLNFilterPositionCleanup(SLNFilterPosition *const pos) { assert(pos); pos->dir = 0; FREE(&pos->URI); pos->sortID = 0; pos->fileID = 0; assert_zeroed(pos, 1); }
void SLNFileInfoCleanup(SLNFileInfo *const info) { if(!info) return; FREE(&info->hash); FREE(&info->path); FREE(&info->type); info->size = 0; assert_zeroed(info, 1); }
void HTTPServerFree(HTTPServerRef *const serverptr) { HTTPServerRef server = *serverptr; if(!server) return; HTTPServerClose(server); server->listener = NULL; server->context = NULL; assert_zeroed(server, 1); FREE(serverptr); server = NULL; }
void MultipartFormFree(MultipartFormRef *const formptr) { MultipartFormRef form = *formptr; if(!form) return; form->conn = NULL; multipart_parser_free(form->parser); form->parser = NULL; form->type = MultipartNothing; *form->out = uv_buf_init(NULL, 0); form->flags = 0; assert_zeroed(form, 1); FREE(formptr); form = NULL; }
void HTTPHeadersFree(HTTPHeadersRef *const hptr) { HTTPHeadersRef h = *hptr; if(!h) return; FREE(&h->fields); for(uint16_t i = 0; i < h->count; i++) FREE(&h->values[i]); h->count = 0; h->offset = 0; h->total = 0; assert_zeroed(h, 1); FREE(hptr); h = NULL; }
void SLNJSONFilterParserFree(SLNJSONFilterParserRef *const parserptr) { SLNJSONFilterParserRef parser = *parserptr; if(!parser) return; parser->session = NULL; if(parser->JSONParser) { yajl_free(parser->JSONParser); parser->JSONParser = NULL; } for(size_t i = 0; i < DEPTH_MAX; ++i) { SLNFilterFree(&parser->stack[i]); } assert_zeroed(parser, 1); FREE(parserptr); parser = NULL; }
void SocketFree(SocketRef *const socketptr) { SocketRef socket = *socketptr; if(!socket) return; if(socket->secure) tls_close(socket->secure); tls_free(socket->secure); socket->secure = NULL; async_close((uv_handle_t *)socket->stream); FREE(&socket->rdmem); socket->rd->base = NULL; socket->rd->len = 0; FREE(&socket->wr->base); socket->wr->len = 0; socket->err = 0; assert_zeroed(socket, 1); FREE(socketptr); socket = NULL; }
void kvs_helper_txn_abort(KVS_helper_txn *const helper) { if(!helper) return; if(helper->child) { kvs_txn_abort(helper->child); helper->child = NULL; } kvs_cursor_close(helper->cursor); helper->cursor = NULL; if(helper->parent) { kvs_txn_set_config(helper->parent, KVS_TXN_CHILD, NULL); } helper->env = NULL; helper->parent = NULL; helper->flags = 0; assert_zeroed(helper, 1); }
void SLNSessionRelease(SLNSessionRef *const sessionptr) { SLNSessionRef session = *sessionptr; if(!session) return; assert(session->refcount); if(--session->refcount) return; session->cache = NULL; session->sessionID = 0; FREE(&session->sessionKeyRaw); FREE(&session->sessionKeyEnc); session->userID = 0; session->mode = 0; FREE(&session->username); assert_zeroed(session, 1); FREE(sessionptr); session = NULL; }
ssize_t SLNFilterWriteURIBatch(SLNFilterRef const filter, SLNSessionRef const session, SLNFilterPosition *const pos, bool const meta, uint64_t const max, SLNFilterWriteCB const writecb, void *ctx) { str_t *URIs[BATCH_SIZE]; ssize_t const count = SLNFilterCopyURIs(filter, session, pos, pos->dir, meta, URIs, MIN(max, BATCH_SIZE)); if(count <= 0) return count; uv_buf_t parts[BATCH_SIZE*2]; for(size_t i = 0; i < count; i++) { parts[i*2+0] = uv_buf_init((char *)URIs[i], strlen(URIs[i])); parts[i*2+1] = uv_buf_init((char *)STR_LEN("\r\n")); } int rc = writecb(ctx, parts, count*2); for(size_t i = 0; i < count; i++) FREE(&URIs[i]); assert_zeroed(URIs, count); if(rc < 0) return rc; return count; }
void EFSSyncFree(EFSSyncRef *const syncptr) { assert(syncptr); EFSSyncRef sync = *syncptr; if(!sync) return; sync->stop = true; // TODO: Join db_thread sync->session = NULL; FREE(&sync->syncID); async_mutex_destroy(sync->mutex); async_cond_destroy(sync->cond); sync->cur = NULL; // TODO: Clear queues assert_zeroed(sync, 1); FREE(syncptr); sync = NULL; }
void SLNPullFree(SLNPullRef *const pullptr) { SLNPullRef pull = *pullptr; if(!pull) return; SLNPullStop(pull); pull->pullID = 0; SLNSessionRelease(&pull->session); FREE(&pull->host); FREE(&pull->cookie); // FREE(&pull->query); async_mutex_destroy(pull->connlock); async_mutex_destroy(pull->mutex); async_cond_destroy(pull->cond); pull->stop = false; assert_zeroed(pull, 1); FREE(pullptr); pull = NULL; }
int SLNSubmissionParseMetaFile(SLNSubmissionRef const sub, uint64_t const fileID, DB_txn *const txn, uint64_t *const out) { assert(out); if(!sub) return DB_EINVAL; if(!fileID) return DB_EINVAL; if(!txn) return DB_EINVAL; strarg_t const type = SLNSubmissionGetType(sub); if(!type) return 0; if(0 != strcasecmp(SLN_META_TYPE, type) && 0 != strcasecmp("text/x-sln-meta+json; charset=utf-8", type) && 0 != strcasecmp("text/efs-meta+json; charset=utf-8", type)) return 0; // TODO: Get rid of these obsolete types. uv_file const fd = SLNSubmissionGetFile(sub); if(fd < 0) return DB_EINVAL; int rc = 0; uv_buf_t buf[1] = {}; DB_txn *subtxn = NULL; parser_t ctx[1] = {}; yajl_handle parser = NULL; buf->base = malloc(BUF_LEN); if(!buf->base) { rc = DB_ENOMEM; goto cleanup; } buf->len = URI_MAX; int64_t pos = 0; ssize_t len = async_fs_read(fd, buf, 1, pos); if(len < 0) { fprintf(stderr, "Submission meta-file read error (%s)\n", sln_strerror(len)); rc = DB_EIO; goto cleanup; } size_t i; for(i = 0; i < len; i++) { char const c = buf->base[i]; if('\r' == c || '\n' == c) break; } if(i >= len) { fprintf(stderr, "Submission meta-file parse error (invalid target URI)\n"); rc = DB_EIO; goto cleanup; } assert(i < len); str_t targetURI[URI_MAX]; memcpy(targetURI, buf->base, i); targetURI[i] = '\0'; pos += i; // TODO: Support sub-transactions in LevelDB backend. // TODO: db_txn_begin should support NULL env. // rc = db_txn_begin(NULL, txn, DB_RDWR, &subtxn); // if(rc < 0) goto cleanup; subtxn = txn; uint64_t const metaFileID = add_metafile(subtxn, fileID, targetURI); if(!metaFileID) goto cleanup; // Duplicate meta-file, not an error. // TODO: Unless the previous version wasn't actually a meta-file. ctx->txn = subtxn; ctx->metaFileID = metaFileID; ctx->targetURI = targetURI; ctx->depth = -1; parser = yajl_alloc(&callbacks, NULL, ctx); if(!parser) rc = DB_ENOMEM; if(rc < 0) goto cleanup; yajl_config(parser, yajl_allow_partial_values, (int)true); yajl_status status = yajl_status_ok; for(;;) { buf->len = MIN(BUF_LEN, PARSE_MAX-pos); if(!buf->len) break; len = async_fs_read(fd, buf, 1, pos); if(len < 0) { fprintf(stderr, "Submission meta-file read error (%s)\n", sln_strerror(len)); rc = DB_EIO; goto cleanup; } if(0 == len) break; status = yajl_parse(parser, (byte_t const *)buf->base, len); if(yajl_status_ok != status) break; pos += len; } status = yajl_complete_parse(parser); if(yajl_status_ok != status) { unsigned char *msg = yajl_get_error(parser, true, (byte_t const *)buf->base, len); fprintf(stderr, "%s", msg); yajl_free_error(parser, msg); msg = NULL; rc = DB_EIO; goto cleanup; } assert(-1 == ctx->depth); // rc = db_txn_commit(subtxn); subtxn = NULL; if(rc < 0) goto cleanup; *out = metaFileID; cleanup: // db_txn_abort(subtxn); subtxn = NULL; FREE(&buf->base); if(parser) yajl_free(parser); parser = NULL; assert_zeroed(ctx->fields, DEPTH_MAX); return rc; }
int HTTPConnectionCreateOutgoing(strarg_t const domain, unsigned const flags, HTTPConnectionRef *const out) { str_t host[1023+1]; str_t service[15+1]; host[0] = '\0'; service[0] = '\0'; int matched = sscanf(domain, "%1023[^:]:%15[0-9]", host, service); if(matched < 1) return UV_EINVAL; if('\0' == host[0]) return UV_EINVAL; static struct addrinfo const hints = { .ai_flags = AI_V4MAPPED | AI_ADDRCONFIG | AI_NUMERICSERV, .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_protocol = 0, // ??? }; struct addrinfo *info = NULL; HTTPConnectionRef conn = NULL; int rc; rc = async_getaddrinfo(host, service[0] ? service : "80", &hints, &info); if(rc < 0) goto cleanup; conn = calloc(1, sizeof(struct HTTPConnection)); if(!conn) rc = UV_ENOMEM; if(rc < 0) goto cleanup; rc = UV_EADDRNOTAVAIL; for(struct addrinfo *each = info; each; each = each->ai_next) { rc = uv_tcp_init(async_loop, conn->stream); if(rc < 0) break; rc = async_tcp_connect(conn->stream, each->ai_addr); if(rc >= 0) break; async_close((uv_handle_t *)conn->stream); } if(rc < 0) goto cleanup; http_parser_init(conn->parser, HTTP_RESPONSE); conn->parser->data = conn; *out = conn; conn = NULL; cleanup: uv_freeaddrinfo(info); info = NULL; HTTPConnectionFree(&conn); return rc; } void HTTPConnectionFree(HTTPConnectionRef *const connptr) { HTTPConnectionRef conn = *connptr; if(!conn) return; async_close((uv_handle_t *)conn->stream); // http_parser does not need to be freed, closed or destroyed. memset(conn->parser, 0, sizeof(*conn->parser)); FREE(&conn->buf); *conn->raw = uv_buf_init(NULL, 0); conn->type = HTTPNothing; *conn->out = uv_buf_init(NULL, 0); conn->flags = 0; assert_zeroed(conn, 1); FREE(connptr); conn = NULL; }
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 void writer(SLNPullRef const pull) { SLNSubmissionRef queue[QUEUE_SIZE]; size_t count = 0; size_t skipped = 0; double time = uv_now(async_loop) / 1000.0; for(;;) { if(pull->stop) goto stop; async_mutex_lock(pull->mutex); while(0 == count || (count < QUEUE_SIZE && pull->count > 0)) { size_t const pos = pull->cur; while(!pull->filled[pos]) { async_cond_wait(pull->cond, pull->mutex); if(pull->stop) { async_mutex_unlock(pull->mutex); goto stop; } if(!count) time = uv_now(async_loop) / 1000.0; } assert(pull->filled[pos]); // Skip any bubbles in the queue. if(pull->queue[pos]) queue[count++] = pull->queue[pos]; else skipped++; pull->queue[pos] = NULL; pull->filled[pos] = false; pull->cur = (pull->cur + 1) % QUEUE_SIZE; pull->count--; async_cond_broadcast(pull->cond); } async_mutex_unlock(pull->mutex); assert(count <= QUEUE_SIZE); for(;;) { int rc = SLNSubmissionStoreBatch(queue, count); if(rc >= 0) break; alogf("Submission error: %s (%d)\n", sln_strerror(rc), rc); async_sleep(1000 * 5); } for(size_t i = 0; i < count; ++i) { SLNSubmissionFree(&queue[i]); } double const now = uv_now(async_loop) / 1000.0; alogf("Pulled %f files per second\n", count / (now - time)); time = now; count = 0; skipped = 0; } stop: for(size_t i = 0; i < count; ++i) { SLNSubmissionFree(&queue[i]); } assert_zeroed(queue, count); async_mutex_lock(pull->mutex); assertf(pull->stop, "Writer ended early"); assert(pull->tasks > 0); pull->tasks--; async_cond_broadcast(pull->cond); async_mutex_unlock(pull->mutex); }
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; }