adadddf91c
git-svn-id: https://shellinabox.googlecode.com/svn/trunk@121 0da03de8-d603-11dd-86c2-0f8696b7b6f9
273 lines
9.3 KiB
C
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;
|
|
}
|