/* * Create a new connection cache. */ struct http_connection_cache * _http_connection_cache_create(struct pevent_ctx *ctx, u_int max_num, u_int max_idle) { struct http_connection_cache *cache; /* Sanity check */ if (max_num == 0 || max_idle == 0) { errno = EINVAL; return (NULL); } /* Create new cache */ if ((cache = MALLOC(MEM_TYPE_CACHE, sizeof(*cache))) == NULL) { alogf(LOG_ERR, "%s: %m", "malloc"); return (NULL); } /* Initialize it */ memset(cache, 0, sizeof(*cache)); TAILQ_INIT(&cache->list); cache->ctx = ctx; cache->max_num = max_num; cache->max_idle = max_idle; /* Initialize mutex */ if ((errno = pthread_mutex_init(&cache->mutex, NULL)) != 0) { alogf(LOG_ERR, "%s: %m", "pthread_mutex_init"); FREE(MEM_TYPE_CACHE, cache); return (NULL); } /* Done */ return (cache); }
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; }
static int init_https(void) { if(!SERVER_PORT_TLS) return 0; struct tls_config *config = tls_config_new(); if(!config) { alogf("TLS config error: %s\n", strerror(errno)); return -1; } int rc = tls_config_set_ciphers(config, TLS_CIPHERS); if(0 != rc) { alogf("TLS ciphers error: %s\n", strerror(errno)); tls_config_free(config); config = NULL; return -1; } tls_config_set_protocols(config, TLS_PROTOCOLS); str_t pemfile[PATH_MAX]; snprintf(pemfile, sizeof(pemfile), "%s/key.pem", path); rc = tls_config_set_key_file(config, pemfile); if(0 != rc) { alogf("TLS key file error: %s\n", strerror(errno)); tls_config_free(config); config = NULL; return -1; } snprintf(pemfile, sizeof(pemfile), "%s/crt.pem", path); rc = tls_config_set_cert_file(config, pemfile); if(0 != rc) { alogf("TLS crt file error: %s\n", strerror(errno)); tls_config_free(config); config = NULL; return -1; } struct tls *tls = tls_server(); if(!tls) { alogf("TLS engine error: %s\n", strerror(errno)); tls_config_free(config); config = NULL; return -1; } rc = tls_configure(tls, config); tls_config_free(config); config = NULL; if(0 != rc) { alogf("TLS config error: %s\n", tls_error(tls)); tls_free(tls); tls = NULL; return -1; } server_tls = HTTPServerCreate((HTTPListener)listener, blog); if(!server_tls) { alogf("HTTPS server could not be initialized\n"); tls_free(tls); tls = NULL; return -1; } rc = HTTPServerListenSecure(server_tls, SERVER_ADDRESS, SERVER_PORT_TLS, &tls); tls_free(tls); tls = NULL; if(rc < 0) { alogf("HTTPS server could not be started: %s\n", sln_strerror(rc)); return -1; } int const port = SERVER_PORT_TLS; alogf("StrongLink server running at https://localhost:%d/\n", port); return 0; }
static bool load_template(BlogRef const blog, strarg_t const name, TemplateRef *const out) { assert(out); assert(blog->dir); str_t path[PATH_MAX]; int rc = snprintf(path, sizeof(path), "%s/template/%s", blog->dir, name); if(rc < 0 || rc >= sizeof(path)) return false; TemplateRef t = TemplateCreateFromPath(path); if(!t) { alogf("Couldn't load blog template at '%s'\n", path); alogf("(Try reinstalling the resource files.)\n"); return false; } *out = t; return true; }
int MultipartFormPeek(MultipartFormRef const form, MultipartEvent *const type, uv_buf_t *const buf) { if(!form) return UV_EINVAL; if(!type) return UV_EINVAL; if(!buf) return UV_EINVAL; uv_buf_t raw[1]; HTTPEvent t; int rc; ssize_t len; for(;;) { if(MultipartNothing != form->type) break; rc = HTTPConnectionPeek(form->conn, &t, raw); if(rc < 0) return rc; if(HTTPMessageEnd == t) { *raw = uv_buf_init(NULL, 0); t = HTTPBody; } if(HTTPBody != t) { assertf(0, "Multipart unexpected HTTP event: %d\n", t); return UV_UNKNOWN; } len = multipart_parser_execute(form->parser, raw->base, raw->len); if(len < 0) { alogf("Multipart parse error\n"); return -1; } HTTPConnectionPop(form->conn, len); } assertf(MultipartNothing != form->type, "MultipartFormPeek must return an event"); *type = form->type; *buf = *form->out; return 0; }
static int init_http(void) { if(!SERVER_PORT_RAW) return 0; server_raw = HTTPServerCreate((HTTPListener)listener, blog); if(!server_raw) { alogf("HTTP server could not be initialized\n"); return -1; } int rc = HTTPServerListen(server_raw, SERVER_ADDRESS, SERVER_PORT_RAW); if(rc < 0) { alogf("HTTP server could not be started: %s\n", sln_strerror(rc)); return -1; } strarg_t const port = SERVER_PORT_RAW; alogf("StrongLink server running at http://localhost:%s/\n", port); return 0; }
// TODO: Basically identical to version in RSSServer.c. static int load_template(BlogRef const blog, strarg_t const name, TemplateRef *const out) { assert(out); assert(blog->dir); str_t path[PATH_MAX]; int rc = snprintf(path, sizeof(path), "%s/template/%s", blog->dir, name); if(rc < 0) return rc; // TODO: snprintf(3) error reporting? if(rc >= sizeof(path)) return UV_ENAMETOOLONG; TemplateRef t = NULL; rc = TemplateCreateFromPath(path, &t); if(rc < 0) { alogf("Error loading template at '%s': %s\n", path, uv_strerror(rc)); if(UV_ENOENT == rc) alogf("(Try reinstalling the resource files.)\n"); return rc; } *out = t; t = NULL; return 0; }
static void init(void *const unused) { int rc = async_random((byte_t *)&SLNSeed, sizeof(SLNSeed)); if(rc < 0) { alogf("Random seed error\n"); return; } uv_signal_init(async_loop, sigpipe); uv_signal_start(sigpipe, ignore, SIGPIPE); uv_unref((uv_handle_t *)sigpipe); str_t *tmp = strdup(path); strarg_t const reponame = basename(tmp); // TODO rc = SLNRepoCreate(path, reponame, &repo); FREE(&tmp); if(rc < 0) { alogf("Repository could not be opened: %s\n", sln_strerror(rc)); return; } blog = BlogCreate(repo); if(!blog) { alogf("Blog server could not be initialized\n"); return; } rc = RSSServerCreate(repo, &rss); if(rc < 0) { alogf("RSS server error: %s\n", sln_strerror(rc)); return; } if(init_http() < 0 || init_https() < 0) { HTTPServerClose(server_raw); HTTPServerClose(server_tls); return; } // SLNRepoPullsStart(repo); uv_signal_init(async_loop, sigint); uv_signal_start(sigint, stop, SIGINT); uv_unref((uv_handle_t *)sigint); }
int main(int const argc, char const *const *const argv) { // Depending on how async_pool and async_fs are configured, we might be // using our own thread pool heavily or not. However, at the minimum, // uv_getaddrinfo uses the libuv thread pool, and it blocks on the // network, so don't set this number too low. if(!getenv("UV_THREADPOOL_SIZE")) putenv((char *)"UV_THREADPOOL_SIZE=4"); raiserlimit(); async_init(); int rc = tls_init(); if(rc < 0) { alogf("TLS initialization error: %s\n", strerror(errno)); return 1; } if(2 != argc || '-' == argv[1][0]) { alogf("Usage:\n\t" "%s repo\n", argv[0]); return 1; } path = argv[1]; // Even our init code wants to use async I/O. async_spawn(STACK_DEFAULT, init, NULL); uv_run(async_loop, UV_RUN_DEFAULT); async_spawn(STACK_DEFAULT, term, NULL); uv_run(async_loop, UV_RUN_DEFAULT); // cleanup is separate from term because connections might // still be active. async_spawn(STACK_DEFAULT, cleanup, NULL); uv_run(async_loop, UV_RUN_DEFAULT); async_destroy(); // TODO: Windows? if(sig) raise(sig); return 0; }
int BlogDispatch(BlogRef const blog, SLNSessionRef const session, HTTPConnectionRef const conn, HTTPMethod const method, strarg_t const URI, HTTPHeadersRef const headers) { int rc = -1; rc = rc >= 0 ? rc : GET_query(blog, session, conn, method, URI, headers); rc = rc >= 0 ? rc : GET_compose(blog, session, conn, method, URI, headers); rc = rc >= 0 ? rc : GET_upload(blog, session, conn, method, URI, headers); rc = rc >= 0 ? rc : POST_post(blog, session, conn, method, URI, headers); rc = rc >= 0 ? rc : GET_account(blog, session, conn, method, URI, headers); rc = rc >= 0 ? rc : POST_auth(blog, session, conn, method, URI, headers); if(403 == rc) { HTTPConnectionSendRedirect(conn, 303, "/account"); return 0; } if(rc >= 0) return rc; // TODO: Pretty 404 pages, etc. if(HTTP_GET != method && HTTP_HEAD != method) return -1; str_t path[PATH_MAX]; size_t const len = strlen(URI); if(0 == len) return 400; strarg_t const qs = strchr(URI, '?'); size_t const pathlen = qs ? qs-URI : len; str_t *URI_raw = QSUnescape(URI, pathlen, false); if('/' == URI[len-1]) { str_t const index[] = "index.html"; rc = snprintf(path, sizeof(path), "%s/static/%s%s", blog->dir, URI_raw, index); } else { rc = snprintf(path, sizeof(path), "%s/static/%s", blog->dir, URI_raw); } FREE(&URI_raw); if(rc >= sizeof(path)) return 414; // Request-URI Too Large if(rc < 0) return 500; if(strstr(path, "..")) return 403; // Security critical. strarg_t const ext = strrchr(path, '.'); strarg_t const type = exttype(ext); rc = HTTPConnectionSendFile(conn, path, type, -1); if(UV_EISDIR == rc) { str_t location[URI_MAX]; rc = snprintf(location, sizeof(location), "%s/", URI); if(rc >= sizeof(location)) return 414; // Request-URI Too Large if(rc < 0) return 500; HTTPConnectionSendRedirect(conn, 301, location); return 0; } if(rc < 0 && UV_EPIPE != rc) { alogf("Error sending file %s: %s\n", URI, uv_strerror(rc)); } return 0; }
int SLNSubmissionEnd(SLNSubmissionRef const sub) { if(!sub) return 0; if(sub->size <= 0) return UV_EINVAL; assert(sub->tmppath); assert(sub->tmpfile >= 0); assert(sub->type); sub->URIs = SLNHasherEnd(sub->hasher); sub->internalHash = strdup(SLNHasherGetInternalHash(sub->hasher)); SLNHasherFree(&sub->hasher); if(!sub->URIs || !sub->internalHash) return UV_ENOMEM; SLNRepoRef const repo = SLNSubmissionGetRepo(sub); str_t *internalPath = NULL; bool worker = false; int rc = 0; rc = verify(sub); if(rc < 0) goto cleanup; internalPath = SLNRepoCopyInternalPath(repo, sub->internalHash); if(!internalPath) rc = UV_ENOMEM; if(rc < 0) goto cleanup; async_pool_enter(NULL); worker = true; rc = async_fs_fdatasync(sub->tmpfile); if(rc < 0) goto cleanup; // We use link(2) rather than rename(2) because link gives an error // if there's a name collision, rather than overwriting. We want to // keep the oldest file for any given hash, rather than the newest. rc = async_fs_link_mkdirp(sub->tmppath, internalPath); if(UV_EEXIST == rc) { rc = 0; goto cleanup; } if(rc < 0) { alogf("SLNSubmission couldn't move '%s' to '%s' (%s)\n", sub->tmppath, internalPath, sln_strerror(rc)); goto cleanup; } rc = async_fs_sync_dirname(internalPath); cleanup: if(worker) { async_pool_leave(NULL); worker = false; } FREE(&internalPath); async_fs_unlink(sub->tmppath); FREE(&sub->tmppath); return rc; }
static int tls_poll(uv_stream_t *const stream, int const event) { int rc; if(TLS_READ_AGAIN == event) { uv_buf_t buf; rc = async_read(stream, 0, &buf); if(UV_ENOBUFS == rc) rc = 0; if(rc < 0) alogf("tls_poll read %s\n", uv_strerror(rc)); rc = 0; } else if(TLS_WRITE_AGAIN == event) { // TODO: libuv provides NO WAY to wait until a stream is // writable! Even our zero-length write hack doesn't work. // uv_poll can't be used on uv's own stream fds. rc = async_sleep(50); // uv_buf_t buf = uv_buf_init(NULL, 0); // rc = async_write(stream, &buf, 1); if(rc < 0) alogf("tls_poll write %s\n", uv_strerror(rc)); rc = 0; } else { rc = -errno; // TODO: Might have problems on Windows? if(rc >= 0) rc = UV_EOF; // Most common case, is this guaranteed? } return rc; }
static int listener0(void *ctx, HTTPServerRef const server, HTTPConnectionRef const conn) { HTTPMethod method; str_t URI[URI_MAX]; ssize_t len = HTTPConnectionReadRequest(conn, &method, URI, sizeof(URI)); if(UV_EOF == len) return 0; if(UV_ECONNRESET == len) return 0; if(UV_EMSGSIZE == len) return 414; // Request-URI Too Large if(len < 0) { alogf("Request error: %s\n", uv_strerror(len)); return 500; } HTTPHeadersRef headers; int rc = HTTPHeadersCreateFromConnection(conn, &headers); if(UV_EMSGSIZE == rc) return 431; // Request Header Fields Too Large if(rc < 0) return 500; strarg_t const host = HTTPHeadersGet(headers, "host"); str_t domain[1023+1]; domain[0] = '\0'; if(host) sscanf(host, "%1023[^:]", domain); // TODO: Verify Host header to prevent DNS rebinding. if(SERVER_PORT_TLS && server == server_raw) { // Redirect from HTTP to HTTPS if('\0' == domain[0]) return 400; strarg_t const port = SERVER_PORT_TLS; str_t loc[URI_MAX]; rc = snprintf(loc, sizeof(loc), "https://%s:%s%s", domain, port, URI); if(rc >= sizeof(loc)) 414; // Request-URI Too Large if(rc < 0) return 500; HTTPConnectionSendRedirect(conn, 301, loc); return 0; } strarg_t const cookie = HTTPHeadersGet(headers, "cookie"); SLNSessionCacheRef const cache = SLNRepoGetSessionCache(repo); SLNSessionRef session = NULL; rc = SLNSessionCacheCopyActiveSession(cache, cookie, &session); if(rc < 0) return 500; // Note: null session is valid (zero permissions). rc = -1; rc = rc >= 0 ? rc : SLNServerDispatch(repo, session, conn, method, URI, headers); rc = rc >= 0 ? rc : BlogDispatch(blog, session, conn, method, URI, headers); SLNSessionRelease(&session); HTTPHeadersFree(&headers); return rc; }
int SLNSubmissionStore(SLNSubmissionRef const sub, DB_txn *const txn) { assert(sub); assert(txn); assert(!sub->tmppath); // Session permissions were already checked when the sub was created. int64_t fileID = db_next_id(SLNFileByID, txn); int rc; DB_val dupFileID_val[1]; SLNFileIDByInfoValPack(dupFileID_val, txn, fileID); DB_val fileInfo_key[1]; SLNFileIDByInfoKeyPack(fileInfo_key, txn, sub->internalHash, sub->type); rc = db_put(txn, fileInfo_key, dupFileID_val, DB_NOOVERWRITE); if(rc >= 0) { DB_val fileID_key[1]; SLNFileByIDKeyPack(fileID_key, txn, fileID); DB_val file_val[1]; SLNFileByIDValPack(file_val, txn, sub->internalHash, sub->type, sub->size); rc = db_put(txn, fileID_key, file_val, DB_NOOVERWRITE_FAST); if(rc < 0) return rc; } else if(DB_KEYEXIST == rc) { fileID = db_read_uint64(dupFileID_val); } else return rc; for(size_t i = 0; sub->URIs[i]; ++i) { strarg_t const URI = sub->URIs[i]; DB_val null = { 0, NULL }; DB_val fwd[1]; SLNFileIDAndURIKeyPack(fwd, txn, fileID, URI); rc = db_put(txn, fwd, &null, DB_NOOVERWRITE_FAST); if(rc < 0 && DB_KEYEXIST != rc) return rc; DB_val rev[1]; SLNURIAndFileIDKeyPack(rev, txn, URI, fileID); rc = db_put(txn, rev, &null, DB_NOOVERWRITE_FAST); if(rc < 0 && DB_KEYEXIST != rc) return rc; } rc = SLNSubmissionParseMetaFile(sub, fileID, txn, &sub->metaFileID); if(rc < 0) { alogf("Submission meta-file error: %s\n", sln_strerror(rc)); return rc; } return 0; }
int SLNSubmissionWrite(SLNSubmissionRef const sub, byte_t const *const buf, size_t const len) { if(!sub) return 0; assert(sub->tmpfile >= 0); uv_buf_t parts[] = { uv_buf_init((char *)buf, len) }; int rc = async_fs_writeall(sub->tmpfile, parts, numberof(parts), -1); if(rc < 0) { alogf("SLNSubmission write error: %s\n", sln_strerror(rc)); return rc; } sub->size += len; SLNHasherWrite(sub->hasher, buf, len); return 0; }
static void term(void *const unused) { fprintf(stderr, "\n"); alogf("Stopping StrongLink server...\n"); uv_ref((uv_handle_t *)sigint); uv_signal_stop(sigint); async_close((uv_handle_t *)sigint); SLNRepoPullsStop(repo); HTTPServerClose(server_raw); HTTPServerClose(server_tls); uv_ref((uv_handle_t *)sigpipe); uv_signal_stop(sigpipe); uv_close((uv_handle_t *)sigpipe, NULL); }
/* * Start the idle timer based on the oldest item in the cache. * * This assumes the cache is locked. */ static void http_connection_cache_start_timer(struct http_connection_cache *cache) { struct cached_connection *const oldest = TAILQ_FIRST(&cache->list); const time_t now = time(NULL); int timeout; /* Don't start timer when not appropriate */ if (cache->num == 0 || cache->max_idle == 0 || cache->timer != NULL) return; /* Get time until oldest connection expires */ timeout = (now < oldest->expiry) ? (oldest->expiry - now) * 1000 : 0; /* Start timer */ if (pevent_register(cache->ctx, &cache->timer, 0, &cache->mutex, http_connection_cache_timeout, cache, PEVENT_TIME, timeout) == -1) alogf(LOG_ERR, "%s: %m", "pevent_register"); }
/* * Same as http_xml_send_xmlrpc() but with user-defined URL path. */ int http_xml_send_xmlrpc2(struct http_client *client, struct in_addr ip, u_int16_t port, int https, const char *username, const char *password, const char *urlpath, const char *methodName, u_int nparams, const struct structs_type **ptypes, const void **pdatas, const struct structs_type *rep_type, void *rep, struct xmlrpc_compact_fault *faultp, structs_xmllog_t *rlogger) { const struct structs_type *const xreq_type = &structs_type_xmlrpc_request; const struct structs_type *const xrep_type = &structs_type_xmlrpc_response; const struct structs_type *const ftype = &structs_type_xmlrpc_compact_fault; struct http_xml_send_xmlrpc_ctx *ctx; struct xmlrpc_compact_fault fault; char ebuf[128]; int ret = -1; int r; /* Get context */ if ((ctx = MALLOC(MEM_TYPE, sizeof(*ctx))) == NULL) { alogf(LOG_ERR, "%s: %m", "malloc"); return (-1); } memset(ctx, 0, sizeof(*ctx)); pthread_cleanup_push(http_xml_send_xmlrpc_cleanup, ctx); /* Build XML-RPC request and reply */ if ((ctx->xreq = structs_xmlrpc_build_request(MEM_TYPE, methodName, nparams, ptypes, pdatas)) == NULL) { alogf(LOG_ERR, "%s: %m", "structs_xmlrpc_build_request"); goto fail; } if ((ctx->xrep = MALLOC(MEM_TYPE, xrep_type->size)) == NULL) { alogf(LOG_ERR, "%s: %m", "malloc"); goto fail; } if (structs_init(xrep_type, NULL, ctx->xrep) == -1) { alogf(LOG_ERR, "%s: %m", "structs_init"); FREE(MEM_TYPE, ctx->xrep); ctx->xrep = NULL; goto fail; } #ifdef XML_RPC_DEBUG printf("%s: sending this XML-RPC request:\n", __FUNCTION__); (void)structs_xml_output(xreq_type, XML_RPC_REQUEST_TAG, NULL, ctx->xreq, stdout, NULL, 0); #endif /* Send request and get reply; note: we could get canceled here. */ r = http_xml_send(client, ip, port, https, urlpath, username, password, XML_RPC_REQUEST_TAG, NULL, xreq_type, ctx->xreq, STRUCTS_XML_FULL, XML_RPC_REPLY_TAG, NULL, NULL, xrep_type, ctx->xrep, 0, rlogger); #ifdef XML_RPC_DEBUG printf("%s: got this XML-RPC reply (error=%s):\n", __FUNCTION__, r == -1 ? strerror(errno) : "none"); (void)structs_xml_output(xrep_type, XML_RPC_REPLY_TAG, NULL, ctx->xrep, stdout, NULL, 0); #endif /* Check error */ if (r == -1) goto fail; /* Check for fault */ if (structs_init(ftype, NULL, &fault) == -1) { alogf(LOG_ERR, "%s: %m", "structs_init"); goto fail; } if (structs_xmlrpc2struct(xrep_type, ctx->xrep, "fault.value", ftype, &fault, NULL, NULL, 0) == 0) { if (faultp != NULL) *faultp = fault; else structs_free(ftype, NULL, &fault); ret = -2; /* -2 indicates fault */ goto fail; } structs_free(ftype, NULL, &fault); /* Extract response (if desired) */ if (rep != NULL) { if (rep_type != NULL) { /* return compact type */ if (structs_xmlrpc2struct(xrep_type, ctx->xrep, "params.0.value", rep_type, rep, NULL, ebuf, sizeof(ebuf)) == -1) { (*rlogger)(LOG_ERR, "error decoding XML-RPC" " response: %s", ebuf); goto fail; } } else { /* return exploded type */ if (structs_get(&structs_type_xmlrpc_value, "params.0.value", ctx->xrep, rep) == -1) { alogf(LOG_ERR, "structs_get: %m%s", ""); goto fail; } } } /* OK */ ret = 0; fail:; /* Done */ pthread_cleanup_pop(1); return (ret); }
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 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); }
/* * Send a copy of the message, wait for a reply, and return the reply. * * The "reply" should already be initialized. * * This properly handles the calling thread's being canceled. */ int http_xml_send(struct http_client *client, struct in_addr ip, u_int16_t port, int https, const char *urlpath, const char *username, const char *password, const char *ptag, const char *pattrs, const struct structs_type *ptype, const void *payload, int pflags, const char *rtag, char **rattrsp, const char *rattrs_mtype, const struct structs_type *rtype, void *reply, int rflags, structs_xmllog_t *rlogger) { struct http_client_connection *cc; struct http_request *req; struct http_response *resp; int ret = -1; u_int code; FILE *fp; /* Get HTTP connection */ if ((cc = http_client_connect(client, ip, port, https)) == NULL) { alogf(LOG_ERR, "can't %s for %s:%u: %m", "get HTTP client" _ inet_ntoa(ip) _ port); return -1; } /* Push cleanup hook */ pthread_cleanup_push(http_xml_send_cleanup, cc); /* Set up request */ req = http_client_get_request(cc); if (http_request_set_method(req, payload != NULL ? HTTP_METHOD_POST : HTTP_METHOD_GET) == -1) { alogf(LOG_ERR, "can't %s for %s:%u: %m", "set method" _ inet_ntoa(ip) _ port); goto fail; } if (http_request_set_path(req, urlpath) == -1) { alogf(LOG_ERR, "can't %s for %s:%u: %m", "set path" _ inet_ntoa(ip) _ port); goto fail; } if (http_request_set_header(req, 0, "Content-Type", "text/xml") == -1) { alogf(LOG_ERR, "can't %s for %s:%u: %m", "set content-type" _ inet_ntoa(ip) _ port); goto fail; } if (username != NULL && password != NULL) { char *auth; if ((auth = http_request_encode_basic_auth(TYPED_MEM_TEMP, username, password)) == NULL) { alogf(LOG_ERR, "can't %s for %s:%u: %m", "encode authorization" _ inet_ntoa(ip) _ port); goto fail; } if (http_request_set_header(req, 0, "Authorization", "Basic %s", auth) == -1) { alogf(LOG_ERR, "can't %s for %s:%u: %m", "set authorization header" _ inet_ntoa(ip) _ port); FREE(TYPED_MEM_TEMP, auth); goto fail; } FREE(TYPED_MEM_TEMP, auth); } /* Write XML data to HTTP client output stream */ if (payload != NULL) { if ((fp = http_request_get_output(req, 1)) == NULL) { alogf(LOG_ERR, "can't %s for %s:%u: %m", "get output" _ inet_ntoa(ip) _ port); goto fail; } if (structs_xml_output(ptype, ptag, pattrs, payload, fp, NULL, pflags) == -1) { alogf(LOG_ERR, "can't %s for %s:%u: %m", "write XML" _ inet_ntoa(ip) _ port); goto fail; } } /* Get response */ if ((resp = http_client_get_response(cc)) == NULL) { alogf(LOG_ERR, "can't %s for %s:%u: %m", "get response" _ inet_ntoa(ip) _ port); goto fail; } if ((code = http_response_get_code(resp)) != HTTP_STATUS_OK) { alogf(LOG_ERR, "rec'd HTTP error code %d from" "http%s://%s:%u%s: %s", code _ https ? "s" : "" _ inet_ntoa(ip) _ port _ urlpath _ http_response_status_msg(code)); goto fail; } /* Read XML reply from client input stream */ if ((fp = http_response_get_input(resp)) == NULL) { alogf(LOG_ERR, "can't %s for %s:%u: %m", "get input" _ inet_ntoa(ip) _ port); goto fail; } if (structs_xml_input(rtype, rtag, rattrsp, rattrs_mtype, fp, reply, rflags, rlogger) == -1) { alogf(LOG_ERR, "can't %s for %s:%u: %m", "read XML reply" _ inet_ntoa(ip) _ port); goto fail; } /* OK */ ret = 0; fail:; /* Done */ pthread_cleanup_pop(1); return (ret); }