202 lines
6.0 KiB
C
202 lines
6.0 KiB
C
|
|
/* httpd.c - Web server.
|
||
|
|
*
|
||
|
|
* Copyright 2022 Rob Landley <rob@landley.net>
|
||
|
|
*
|
||
|
|
* See https://www.ietf.org/rfc/rfc2616.txt
|
||
|
|
*
|
||
|
|
* TODO: multiple domains, https, actual inetd with ratelimit...
|
||
|
|
* range, gzip, ETag (If-None-Match:, Last-Modified:), Date:
|
||
|
|
* "Accept-Ranges: bytes"/"Range: bytes=xxx-[yyy]"
|
||
|
|
* .htaccess (auth, forward)
|
||
|
|
* optional conf file, error pages
|
||
|
|
* -ifv -p [IP:]PORT -u [USER][:GRP] -c CFGFILE
|
||
|
|
* cgi: SERVER_PORT SERVER_NAME REMOTE_ADDR REMOTE_HOST REQUEST_METHOD
|
||
|
|
|
||
|
|
USE_HTTPD(NEWTOY(httpd, ">1v", TOYFLAG_USR|TOYFLAG_BIN))
|
||
|
|
|
||
|
|
config HTTPD
|
||
|
|
bool "httpd"
|
||
|
|
default y
|
||
|
|
help
|
||
|
|
usage: httpd [-e STR] [DIR]
|
||
|
|
|
||
|
|
Serve contents of directory as static web pages.
|
||
|
|
|
||
|
|
-e Escape STR as URL, printing result and exiting.
|
||
|
|
-d Decode escaped STR, printing result and exiting.
|
||
|
|
-v Verbose
|
||
|
|
*/
|
||
|
|
|
||
|
|
#define FOR_httpd
|
||
|
|
#include "toys.h"
|
||
|
|
|
||
|
|
char *rfc1123(char *buf, time_t t)
|
||
|
|
{
|
||
|
|
strftime(buf, 64, "%a, %d %b %Y %T GMT", gmtime(&t));
|
||
|
|
|
||
|
|
return buf;
|
||
|
|
}
|
||
|
|
|
||
|
|
// She never told me...
|
||
|
|
char *mime(char *file)
|
||
|
|
{
|
||
|
|
char *s = strrchr(file, '.'), *types[] = {
|
||
|
|
"asc\0text/plain", "bin\0application/octet-stream", "bmp\0image/bmp",
|
||
|
|
"cpio\0application/x-cpio", "css\0text/css", "doc\0application/msword",
|
||
|
|
"dtd\0text/xml", "dvi\0application/x-dvi", "gif\0image/gif",
|
||
|
|
"htm\0text/html", "html\0text/html", "jar\0applicat/x-java-archive",
|
||
|
|
"jpeg\0image/jpeg", "jpg\0image/jpeg", "js\0application/x-javascript",
|
||
|
|
"mp3\0audio/mpeg", "mp4\0video/mp4", "mpg\0video/mpeg",
|
||
|
|
"ogg\0application/ogg", "pbm\0image/x-portable-bitmap",
|
||
|
|
"pdf\0application/pdf", "png\0image/png",
|
||
|
|
"ppt\0application/vnd.ms-powerpoint", "ps\0application/postscript",
|
||
|
|
"rtf\0text/rtf", "sgml\0text/sgml", "svg\0image/svg+xml",
|
||
|
|
"tar\0application/x-tar", "tex\0application/x-tex", "tiff\0image/tiff",
|
||
|
|
"txt\0text/plain", "wav\0audio/x-wav", "xls\0application/vnd.ms-excel",
|
||
|
|
"xml\0tet/xml", "zip\0application/zip"
|
||
|
|
};
|
||
|
|
int i;
|
||
|
|
|
||
|
|
strcpy(toybuf, "text/plain");
|
||
|
|
if (s++) for (i = 0; i<ARRAY_LEN(types); i++) {
|
||
|
|
if (strcasecmp(s, types[i])) continue;
|
||
|
|
strcpy(toybuf, types[i]+strlen(types[i])+1);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (!strncmp(toybuf, "text/", 5)) strcat(toybuf, "; charset=UTF-8");
|
||
|
|
|
||
|
|
return toybuf;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Stop: header time.
|
||
|
|
static void header_time(int stat, char *str, char *more)
|
||
|
|
{
|
||
|
|
char buf[64];
|
||
|
|
|
||
|
|
if (!more) more = "";
|
||
|
|
if (FLAG(v)) dprintf(2, "REPLY: %d %s\n%s\n", stat, str, more);
|
||
|
|
xprintf("HTTP/1.1 %d %s\r\nServer: toybox httpd/%s\r\nDate: %s\r\n%s"
|
||
|
|
"Connection: close\r\n\r\n", stat, str, TOYBOX_VERSION,
|
||
|
|
rfc1123(buf, time(0)), more);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void error_time(int stat, char *str)
|
||
|
|
{
|
||
|
|
header_time(stat, str, 0);
|
||
|
|
xprintf("<html><head><title>%d %s</title></head>"
|
||
|
|
"<body><h3>%d %s</h3></body></html>", stat, str, stat, str);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int isunder(char *file, char *dir)
|
||
|
|
{
|
||
|
|
char *s1 = xabspath(dir, ABS_FILE), *s2 = xabspath(file, 0), *ss = s2;
|
||
|
|
int rc = s1 && s2 && strstart(&ss, s1) && (!*ss || *ss=='/' || ss[-1]=='/');
|
||
|
|
|
||
|
|
free(s2);
|
||
|
|
free(s1);
|
||
|
|
|
||
|
|
return rc;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Handle a connection on fd
|
||
|
|
void handle(int infd, int outfd)
|
||
|
|
{
|
||
|
|
FILE *fp = fdopen(infd, "r");
|
||
|
|
char *s = xgetline(fp), *cut, *ss, *esc, *path, *word[3];
|
||
|
|
int i = sizeof(toybuf), fd;
|
||
|
|
|
||
|
|
if (!s) return;
|
||
|
|
|
||
|
|
if (!getsockname(0, (void *)&toybuf, &i)) {
|
||
|
|
if (FLAG(v))
|
||
|
|
dprintf(2, "Hello %s\n%s\n", ntop((void *)toybuf), s);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Split line into method/path/protocol
|
||
|
|
for (i = 0, ss = s;;) {
|
||
|
|
word[i++] = ss;
|
||
|
|
while (*ss && !strchr(" \r\n", *ss)) ss++;
|
||
|
|
while (*ss && strchr(" \r\n", *ss)) *(ss++) = 0;
|
||
|
|
if (i==3) break;
|
||
|
|
if (!*ss) return header_time(400, "Bad Request", 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Process additional http/1.1 lines
|
||
|
|
while ((ss = xgetline(fp))) {
|
||
|
|
i = *chomp(ss);
|
||
|
|
if (FLAG(v)) dprintf(2, "%s\n", ss);
|
||
|
|
// TODO: any of
|
||
|
|
//User-Agent: Wget/1.20.1 (linux-gnu) - do we want to log anything?
|
||
|
|
//Accept: */* - 406 Too Snobbish
|
||
|
|
//Accept-Encoding: identity - we can gzip?
|
||
|
|
//Host: landley.net - we could handle multiple domains?
|
||
|
|
//Connection: Keep-Alive - probably don't care
|
||
|
|
|
||
|
|
free(ss);
|
||
|
|
if (!i) break;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!strcasecmp(word[0], "get")) {
|
||
|
|
struct stat st;
|
||
|
|
|
||
|
|
if (*(ss = word[1])!='/') error_time(400, "Bad Request");
|
||
|
|
while (*ss=='/') ss++;
|
||
|
|
if (!*ss) ss = "./";
|
||
|
|
else if ((cut = unescape_url(ss, 1))) setenv("QUERY_STRING", cut, 1);
|
||
|
|
|
||
|
|
// TODO domain.com:/path/to/blah domain2.com:/path/to/that
|
||
|
|
// TODO cgi PATH_INFO /path/to/filename.cgi/and/more/stuff?path&info
|
||
|
|
if (!isunder(ss, ".") || stat(ss, &st)) error_time(404, "Not Found");
|
||
|
|
else if (-1 == (fd = open(ss, O_RDONLY))) error_time(403, "Forbidden");
|
||
|
|
else if (!S_ISDIR(st.st_mode)) {
|
||
|
|
char buf[64];
|
||
|
|
file:
|
||
|
|
header_time(200, "Ok", ss = xmprintf("Content-Type: %s\r\n"
|
||
|
|
"Content-Length: %lld\r\nLast-Modified: %s\r\n",
|
||
|
|
mime(ss), (long long)st.st_size, rfc1123(buf, st.st_mtime)));
|
||
|
|
free(ss);
|
||
|
|
xsendfile(fd, outfd);
|
||
|
|
} else if (ss[strlen(ss)-1]!='/') {
|
||
|
|
header_time(302, "Found", path = xmprintf("Location: %s/\r\n", word[1]));
|
||
|
|
free(path);
|
||
|
|
} else {
|
||
|
|
DIR *dd;
|
||
|
|
struct dirent *dir;
|
||
|
|
|
||
|
|
// Do we have an index.html?
|
||
|
|
path = ss;
|
||
|
|
ss = "index.html";
|
||
|
|
path = xmprintf("%s%s", path, ss);
|
||
|
|
if (stat(path, &st) || !S_ISREG(st.st_mode)) i = -1;
|
||
|
|
else if (-1 == (i = open(path, O_RDONLY))) error_time(403, "Forbidden");
|
||
|
|
free(path);
|
||
|
|
if (i != -1) {
|
||
|
|
close(fd);
|
||
|
|
fd = i;
|
||
|
|
|
||
|
|
goto file;
|
||
|
|
}
|
||
|
|
|
||
|
|
// List directory contents
|
||
|
|
header_time(200, "Ok", "Content-Type: text/html\r\n");
|
||
|
|
dprintf(outfd, "<html><head><title>Index of %s</title></head>\n"
|
||
|
|
"<body><h3>Index of %s</h3></body>\n", word[1], word[1]);
|
||
|
|
for (dd = fdopendir(fd); (dir = readdir(dd));) {
|
||
|
|
esc = escape_url(dir->d_name, "<>&\"");
|
||
|
|
dprintf(outfd, "<a href=\"%s\">%s</a><br />\n", esc, esc);
|
||
|
|
free(esc);
|
||
|
|
}
|
||
|
|
dprintf(outfd, "</body></html>\n");
|
||
|
|
}
|
||
|
|
} else error_time(501, "Not Implemented");
|
||
|
|
free(s);
|
||
|
|
}
|
||
|
|
|
||
|
|
void httpd_main(void)
|
||
|
|
{
|
||
|
|
if (toys.optc && chdir(*toys.optargs))
|
||
|
|
return error_time(500, "Internal Error");
|
||
|
|
// inetd only at the moment
|
||
|
|
handle(0, 1);
|
||
|
|
}
|