// externalfile.h -- Serve static files through HTTP/HTTPS // Copyright (C) 2008-2009 Markus Gutschke // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // // In addition to these license terms, the author grants the following // additional rights: // // If you modify this program, or any covered work, by linking or // combining it with the OpenSSL project's OpenSSL library (or a // modified version of that library), containing parts covered by the // terms of the OpenSSL or SSLeay licenses, the author // grants you additional permission to convey the resulting work. // Corresponding Source for a non-source form of such a combination // shall include the source code for the parts of OpenSSL used as well // as that of the covered work. // // You may at your option choose to remove this additional permission from // the work, or from any part of it. // // It is possible to build this program in a way that it loads OpenSSL // libraries at run-time. If doing so, the following notices are required // by the OpenSSL and SSLeay licenses: // // This product includes software developed by the OpenSSL Project // for use in the OpenSSL Toolkit. (http://www.openssl.org/) // // This product includes cryptographic software written by Eric Young // (eay@cryptsoft.com) // // // The most up-to-date version of this program is always available from // http://shellinabox.com #define _GNU_SOURCE #include "config.h" #include #include #include #include #include #include #include "shellinabox/externalfile.h" #include "shellinabox/service.h" #include "shellinabox/session.h" #include "libhttp/server.h" #include "logging/logging.h" #ifdef HAVE_STRLCAT #define strncat(a,b,c) ({ char *_a = (a); strlcat(_a, (b), (c)+1); _a; }) #endif static int externalFileHttpHandler(HttpConnection *http, void *arg, const char *buf, int len) { checkGraveyard(); struct ExternalFileState *state = (struct ExternalFileState *)httpGetPrivate(http); if (!state) { // Normalize the path info URL *url = newURL(http, buf, len); const char *pathInfo = urlGetPathInfo(url); while (*pathInfo == '/') { pathInfo++; } // Compute file name of external file char *fn; int s_size = strlen((char *)arg) + (*pathInfo ? strlen(pathInfo) + 1 : 0); check(fn = malloc(s_size + 1)); *fn = '\000'; strncat(fn, (char *)arg, s_size); if (*pathInfo) { // Append pathInfo, if available strncat(fn, "/", s_size); const char *ptr = pathInfo; while (*ptr == '/') { ptr++; } strncat(fn, ptr, s_size); // Any files/directories starting with a dot are inaccessible to us do { if (*ptr == '.') { deleteURL(url); free(fn); httpSendReply(http, 404, "File not found", NO_MSG); return HTTP_DONE; } ptr = strchr(ptr + 1, '/'); } while (ptr); } // Open file for reading #ifndef O_LARGEFILE #define O_LARGEFILE 0 #endif int fd = NOINTR(open(fn, O_RDONLY|O_LARGEFILE)); // Recognize a couple of common MIME types static const struct { const char *suffix, *mimeType; } mimeTypes[] = { { "html", "text/html; charset=utf-8" }, { "txt", "text/plain; charset=utf-8" }, { "js", "text/javascript; charset=utf-8" }, { "css", "text/css; charset=utf-8" }, { "ico", "image/x-icon" }, { "jpg", "image/jpeg" }, { "gif", "image/gif" }, { "png", "image/png" }, { "wav", "audio/x-wav" }, { "mp3", "audio/mpeg" }, { "au", "audio/basic" }, { "mid", "audio/midi" }, { NULL, NULL } }; const char *mimeType = "application/octet-stream"; char *suffix = strrchr(fn, '.'); if (!suffix) { suffix = strrchr(urlGetPath(url), '.'); } if (suffix) { suffix++; for (int i = 0; mimeTypes[i].suffix; i++) { if (!strcmp(suffix, mimeTypes[i].suffix)) { mimeType = mimeTypes[i].mimeType; break; } } } deleteURL(url); if (fd < 0) { free(fn); httpSendReply(http, 404, "File not found", NO_MSG); return HTTP_DONE; } // We only serve regular files, and restrict the file size to 100MB. // As a special-case, we also allow access to /dev/null. struct stat sb = { 0 }; if (strcmp(fn, "/dev/null") && (fstat(fd, &sb) || !S_ISREG(sb.st_mode) || sb.st_size > (100 << 20))) { free(fn); NOINTR(close(fd)); httpSendReply(http, 404, "File not found", NO_MSG); return HTTP_DONE; } free(fn); // Set up response header char *response = stringPrintf(NULL, "HTTP/1.1 200 OK\r\n" "Content-Type: %s\r\n" "Content-Length: %d\r\n" "\r\n", mimeType, (int)sb.st_size); int respLen = strlen(response); ssize_t bytes = -1; if (strcmp(httpGetMethod(http), "HEAD")) { // Read at most 64kB in one go ssize_t dataLen = 65536; if (dataLen > sb.st_size) { dataLen = sb.st_size; } check(response = realloc(response, respLen + dataLen + 1)); bytes = NOINTR(read(fd, response + respLen, dataLen)); if (bytes < 0) { free(response); NOINTR(close(fd)); httpSendReply(http, 404, "File not found", NO_MSG); return HTTP_DONE; } } if (bytes < 0 || bytes == sb.st_size) { // Read entire file. Transmit it in one go. httpTransfer(http, response, respLen + (bytes > 0 ? bytes : 0)); NOINTR(close(fd)); return HTTP_DONE; } else { // Transmit partial reply and store state for future calls into the // handler. httpTransferPartialReply(http, response, respLen + bytes); check(state = malloc(sizeof(struct ExternalFileState))); state->fd = fd; state->totalSize = sb.st_size; state->partialSize = bytes; httpSetPrivate(http, state); return HTTP_PARTIAL_REPLY; } } else { // We get called again, because all previously read partial data has now // been sent to the peer. int rc = HTTP_DONE; // If the connection was closed unexpectedly, clean up now if (!buf) { done: NOINTR(close(state->fd)); free(state); httpSetPrivate(http, NULL); return rc; } else { ssize_t dataLen = 65536; if (dataLen > state->totalSize - state->partialSize) { dataLen = state->totalSize - state->partialSize; } char *buf; check(buf = malloc(dataLen)); ssize_t bytes = NOINTR(read(state->fd, buf, dataLen)); if (bytes < 0) { free(buf); rc = HTTP_ERROR; goto done; } state->partialSize += bytes; if (state->partialSize >= state->totalSize) { // Done serving the entire file httpTransfer(http, buf, bytes); goto done; } else { // More partial data pending httpTransferPartialReply(http, buf, bytes); return HTTP_PARTIAL_REPLY; } } } } int registerExternalFiles(void *arg, const char *key, char **value) { Server *server = (Server *)arg; if (*key == '/') { // Absolute URL paths get registered for this particular path, only serverRegisterHttpHandler(server, key, externalFileHttpHandler, *value); } else { // Relative URL paths get registered for each of the services for (int i = 0; i < numServices; i++) { char *path; int s_size = strlen(services[i]->path) + strlen(key) + 1; check(path = malloc(s_size + 1)); *path = '\000'; strncat(path, services[i]->path, s_size); if (!*services[i]->path || strrchr(services[i]->path, '\000')[-1] != '/') { strncat(path, "/", s_size); } strncat(path, key, s_size); serverRegisterHttpHandler(server, path, externalFileHttpHandler, *value); free(path); } } return 1; }