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; }
int HTTPConnectionWriteChunkFile(HTTPConnectionRef const conn, strarg_t const path) { bool worker = false; uv_file file = -1; byte_t *buf = NULL; int rc; async_pool_enter(NULL); worker = true; rc = async_fs_open(path, O_RDONLY, 0000); if(rc < 0) goto cleanup; file = rc; buf = malloc(BUFFER_SIZE); if(!buf) rc = UV_ENOMEM; if(rc < 0) goto cleanup; uv_buf_t const chunk = uv_buf_init((char *)buf, BUFFER_SIZE); ssize_t len = async_fs_readall_simple(file, &chunk); if(len < 0) rc = len; if(rc < 0) goto cleanup; // Fast path for small files. if(len < BUFFER_SIZE) { str_t pfx[16]; int const pfxlen = snprintf(pfx, sizeof(pfx), "%llx\r\n", (unsigned long long)len); if(pfxlen < 0) rc = UV_UNKNOWN; if(rc < 0) goto cleanup; uv_buf_t parts[] = { uv_buf_init(pfx, pfxlen), uv_buf_init((char *)buf, len), uv_buf_init((char *)STR_LEN("\r\n")), }; async_fs_close(file); file = -1; async_pool_leave(NULL); worker = false; rc = HTTPConnectionWritev(conn, parts, numberof(parts)); goto cleanup; } uv_fs_t req[1]; rc = async_fs_fstat(file, req); if(rc < 0) goto cleanup; if(0 == req->statbuf.st_size) goto cleanup; async_pool_leave(NULL); worker = false; // TODO: HACK, WriteFile continues from where we left off rc = rc < 0 ? rc : HTTPConnectionWriteChunkLength(conn, req->statbuf.st_size); rc = rc < 0 ? rc : HTTPConnectionWritev(conn, &chunk, 1); rc = rc < 0 ? rc : HTTPConnectionWriteFile(conn, file); rc = rc < 0 ? rc : HTTPConnectionWrite(conn, (byte_t const *)STR_LEN("\r\n")); cleanup: FREE(&buf); if(file >= 0) { async_fs_close(file); file = -1; } if(worker) { async_pool_leave(NULL); worker = false; } assert(file < 0); assert(!worker); return rc; }
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) { 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 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; }