shellinabox/shellinabox/externalfile.c
zodiac@gmail.com adadddf91c Released version 2.8
git-svn-id: https://shellinabox.googlecode.com/svn/trunk@121 0da03de8-d603-11dd-86c2-0f8696b7b6f9
2009-05-23 19:53:09 +00:00

273 lines
9.3 KiB
C

// externalfile.h -- Serve static files through HTTP/HTTPS
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
//
// 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 <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#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;
}