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; }
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) { // HACK: Force the connection to realize it's dead. // Otherwise there is a timeout period of like 15-20 seconds // and we can run out of file descriptors. I suspect this // is a bug with libuv, but I'm not sure. HTTPConnectionWrite(conn, (byte_t const *)STR_LEN("x")); HTTPConnectionFlush(conn); return 0; } if(UV_EMSGSIZE == len) return 414; // Request-URI Too Large if(len < 0) { fprintf(stderr, "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; }
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; }
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; }
static int POST_post(BlogRef const blog, SLNSessionRef const session, HTTPConnectionRef const conn, HTTPMethod const method, strarg_t const URI, HTTPHeadersRef const headers) { if(HTTP_POST != method) return -1; if(0 != uripathcmp("/post", URI, NULL)) return -1; // TODO: CSRF token strarg_t const formtype = HTTPHeadersGet(headers, "content-type"); uv_buf_t boundary[1]; int rc = MultipartBoundaryFromType(formtype, boundary); if(rc < 0) return 400; MultipartFormRef form = NULL; rc = MultipartFormCreate(conn, boundary, &form); if(rc < 0) { return 500; } SLNSubmissionRef sub = NULL; SLNSubmissionRef meta = NULL; str_t *title = NULL; rc = parse_file(blog, session, form, &sub, &meta, &title); if(UV_EACCES == rc) { MultipartFormFree(&form); return 403; } if(rc < 0) { MultipartFormFree(&form); return 500; } SLNSubmissionRef extra = NULL; yajl_gen json = NULL; str_t *target_QSEscaped = NULL; str_t *location = NULL; strarg_t const target = SLNSubmissionGetPrimaryURI(sub); if(!target) rc = UV_ENOMEM; if(rc < 0) goto cleanup; target_QSEscaped = QSEscape(target, strlen(target), true); if(!target_QSEscaped) rc = UV_ENOMEM; if(rc < 0) goto cleanup; rc = SLNSubmissionCreate(session, NULL, target, &extra); if(rc < 0) goto cleanup; rc = SLNSubmissionSetType(extra, SLN_META_TYPE); if(rc < 0) goto cleanup; SLNSubmissionWrite(extra, (byte_t const *)target, strlen(target)); SLNSubmissionWrite(extra, (byte_t const *)STR_LEN("\n\n")); json = yajl_gen_alloc(NULL); if(!json) rc = UV_ENOMEM; if(rc < 0) goto cleanup; yajl_gen_config(json, yajl_gen_print_callback, (void (*)())SLNSubmissionWrite, extra); yajl_gen_config(json, yajl_gen_beautify, (int)true); yajl_gen_map_open(json); if(title) { yajl_gen_string(json, (unsigned char const *)STR_LEN("title")); yajl_gen_string(json, (unsigned char const *)title, strlen(title)); } // TODO: Comment or description? strarg_t const username = SLNSessionGetUsername(session); if(username) { yajl_gen_string(json, (unsigned char const *)STR_LEN("submitter-name")); yajl_gen_string(json, (unsigned char const *)username, strlen(username)); } strarg_t const reponame = SLNRepoGetName(blog->repo); if(reponame) { yajl_gen_string(json, (unsigned char const *)STR_LEN("submitter-repo")); yajl_gen_string(json, (unsigned char const *)reponame, strlen(reponame)); } time_t const now = time(NULL); struct tm t[1]; gmtime_r(&now, t); // TODO: Error checking? str_t tstr[31+1]; size_t const tlen = strftime(tstr, sizeof(tstr), "%FT%TZ", t); // ISO 8601 if(tlen) { yajl_gen_string(json, (unsigned char const *)STR_LEN("submission-time")); yajl_gen_string(json, (unsigned char const *)tstr, tlen); } yajl_gen_string(json, (unsigned char const *)STR_LEN("submission-software")); yajl_gen_string(json, (unsigned char const *)STR_LEN("StrongLink Blog")); str_t *fulltext = aasprintf("%s\n%s", title ?: "", NULL ?: ""); // TODO: Description, GNU-ism if(fulltext) { yajl_gen_string(json, (unsigned char const *)STR_LEN("fulltext")); yajl_gen_string(json, (unsigned char const *)fulltext, strlen(fulltext)); } FREE(&fulltext); yajl_gen_map_close(json); rc = SLNSubmissionEnd(extra); if(rc < 0) goto cleanup; SLNSubmissionRef subs[] = { sub, meta, extra }; rc = SLNSubmissionStoreBatch(subs, numberof(subs)); location = aasprintf("/?q=%s", target_QSEscaped); if(!location) rc = UV_ENOMEM; if(rc < 0) goto cleanup; HTTPConnectionSendRedirect(conn, 303, location); cleanup: if(json) { yajl_gen_free(json); json = NULL; } FREE(&title); SLNSubmissionFree(&sub); SLNSubmissionFree(&meta); SLNSubmissionFree(&extra); MultipartFormFree(&form); FREE(&target_QSEscaped); FREE(&location); if(rc < 0) return 500; return 0; }