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; }
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; }
uv_file async_fs_open_mkdirp(const char* path, int flags, int mode) { uv_file file = async_fs_open(path, flags, mode); if(UV_ENOENT != file) return file; async_fs_mkdirp_dirname(path, 0700); return async_fs_open(path, flags, mode); }
/*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; }
static int convert(BlogRef const blog, SLNSessionRef const session, char const *const htmlpath, SLNSubmissionRef *const outmeta, strarg_t const URI, SLNFileInfo const *const src, BlogTypeCheck const types, BlogConverter const converter) { int rc = types(src->type); if(rc < 0) return UV_EINVAL; str_t *tmp = NULL; uv_file html = -1; uv_file file = -1; char const *buf = NULL; SLNSubmissionRef meta = NULL; yajl_gen json = NULL; tmp = SLNRepoCopyTempPath(blog->repo); if(!tmp) rc = UV_ENOMEM; if(rc < 0) goto cleanup; rc = async_fs_open_mkdirp(tmp, O_CREAT | O_EXCL | O_WRONLY, 0400); if(rc < 0) goto cleanup; html = rc; rc = async_fs_open(src->path, O_RDONLY, 0000); if(rc < 0) goto cleanup; file = rc; // We use size+1 to get nul-termination. Kind of a hack. buf = mmap(NULL, src->size+1, PROT_READ, MAP_SHARED, file, 0); if(MAP_FAILED == buf) rc = -errno; if(rc < 0) goto cleanup; if('\0' != buf[src->size]) rc = UV_EIO; // Slightly paranoid. if(rc < 0) goto cleanup; async_fs_close(file); file = -1; if(outmeta) { rc = SLNSubmissionCreate(session, NULL, URI, &meta); if(rc < 0) goto cleanup; rc = SLNSubmissionSetType(meta, SLN_META_TYPE); if(rc < 0) goto cleanup; } SLNSubmissionWrite(meta, (byte_t const *)URI, strlen(URI)); SLNSubmissionWrite(meta, (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, meta); yajl_gen_config(json, yajl_gen_beautify, (int)true); async_pool_enter(NULL); yajl_gen_map_open(json); rc = converter(html, json, buf, src->size, src->type); yajl_gen_map_close(json); async_pool_leave(NULL); if(rc < 0) goto cleanup; rc = async_fs_fdatasync(html); if(rc < 0) goto cleanup; rc = async_fs_link_mkdirp(tmp, htmlpath); if(rc < 0) goto cleanup; rc = SLNSubmissionEnd(meta); if(rc < 0) goto cleanup; if(outmeta) { *outmeta = meta; meta = NULL; } cleanup: async_fs_unlink(tmp); FREE(&tmp); if(html >= 0) { async_fs_close(html); html = -1; } if(file >= 0) { async_fs_close(file); file = -1; } if(buf) { munmap((void *)buf, src->size+1); buf = NULL; } if(json) { yajl_gen_free(json); json = NULL; } SLNSubmissionFree(&meta); assert(html < 0); assert(file < 0); return rc; }