/*
httpserve - Serve a directory over HTTP
Copyright (C) 2024 Ian Cowburn (ianc@noddybox.co.uk)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
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, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* ---------------------------------------------------------------------------
TYPES AND GLOBALS
---------------------------------------------------------------------------
*/
typedef struct
{
int fd;
magic_t magic;
} thread_arg_t;
/* ---------------------------------------------------------------------------
BASE ROUTINES
---------------------------------------------------------------------------
*/
static char *Strdup(const char *p)
{
char *ret = strdup(p);
if (!ret)
{
perror("strdup");
exit(EXIT_FAILURE);
}
return ret;
}
static void *Malloc(size_t s)
{
void *ret = malloc(s);
if (!ret)
{
perror("malloc");
exit(EXIT_FAILURE);
}
return ret;
}
static void *Realloc(void *p, size_t s)
{
void *ret = realloc(p, s);
if (!ret)
{
perror("realloc");
exit(EXIT_FAILURE);
}
return ret;
}
static char *PrintStream(char *stream, const char *format, ...)
{
char buff[4096];
va_list va;
size_t stream_len;
size_t buff_len;
va_start(va, format);
vsnprintf(buff, sizeof buff, format, va);
va_end(va);
buff_len = strlen(buff);
if (stream)
{
stream_len = strlen(stream);
}
else
{
stream_len = 0;
}
stream = Realloc(stream, stream_len + buff_len + 1);
strcpy(stream + stream_len, buff);
return stream;
}
/* ---------------------------------------------------------------------------
SOCKET ROUTINES
---------------------------------------------------------------------------
*/
static int Listen(int port)
{
struct sockaddr_in addr;
int fd;
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
return -1;
}
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
if (bind(fd, (void*)&addr, sizeof(addr)) == -1)
{
perror("bind");
return -1;
}
if (listen(fd, 5) == -1)
{
perror("listen");
return -1;
}
return fd;
}
static int Read(int fd, char *buff, size_t max)
{
int rd;
size_t tot = 0;
while(tot < max)
{
rd = read(fd, buff + tot, max - tot);
if (rd < 1)
{
return 0;
}
tot += rd;
if (tot < max)
{
buff[tot] = 0;
if (tot > 4 && buff[tot - 4] == '\r' && buff[tot - 3] == '\n' &&
buff[tot - 2] == '\r' && buff[tot - 1] == '\n')
{
return 1;
}
}
}
fprintf(stderr, "HTTP request too big\n");
return 0;
}
/* ---------------------------------------------------------------------------
HTML ROUTINES
---------------------------------------------------------------------------
*/
static void GenerateListing(int fd, const char *protocol)
{
glob_t gl;
char *stream = NULL;
int f;
glob("*", 0, NULL, &gl);
stream = PrintStream(stream, "\n");
for(f = 0; f < gl.gl_pathc; f++)
{
stream = PrintStream(stream, "%s
",
gl.gl_pathv[f], gl.gl_pathv[f]);
}
stream = PrintStream(stream, "");
dprintf(fd, "%s 200 OK\r\n"
"Content-Type: text/html\r\n"
"Content-Length: %lu\r\n"
"\r\n"
"%s", protocol, strlen(stream), stream);
globfree(&gl);
free(stream);
}
static void GenerateStatus(int fd,
int status,
const char *status_text,
const char *protocol)
{
dprintf(fd, "%s %d %s\r\nContent-Length: 0\r\n\r\n",
protocol, status, status_text);
}
static void GenerateFile(int fd,
magic_t magic,
const char *path,
const char *protocol)
{
char buff[4096];
int file;
off_t size;
ssize_t len;
file = open(path, O_RDONLY);
if (file == -1)
{
perror("open");
GenerateStatus(fd, 404, "Not found", protocol);
return;
}
size = lseek(file, 0, SEEK_END);
lseek(file, 0, SEEK_SET);
dprintf(fd, "%s 200 OK\r\n"
"Content-Type: %s\r\n"
"Content-Length: %lu\r\n"
"\r\n", protocol, magic_file(magic, path), (unsigned long)size);
while((len = read(file, buff, sizeof buff)) > 0)
{
write(fd, buff, len);
}
close(file);
}
/* ---------------------------------------------------------------------------
HTTP ROUTINES
---------------------------------------------------------------------------
*/
static void DecodeURL(char *dest, const char *url)
{
while(*url)
{
char num[3]={0};
switch(*url)
{
case '%':
num[0] = url[1];
num[1] = url[2];
*dest++ = (char)strtol(num, NULL, 16);
url += 3;
break;
case '+':
*dest++ = ' ';
url++;
break;
default:
*dest++ = *url++;
break;
}
}
*dest = 0;
}
static int ParseRequest(char *request,
char **method,
char **path,
char **protocol,
int *close)
{
char *tok[3] = {0};
char *last;
int f;
*method = NULL;
*path = NULL;
*protocol = NULL;
*close = 0;
if ((tok[0] = strtok_r(request, " ", &last)) &&
(tok[1] = strtok_r(NULL, " ", &last)) &&
(tok[2] = strtok_r(NULL, " \r\n", &last)))
{
char *tmppath;
*method = tok[0];
*path = tok[1];
*protocol = tok[2];
/* This is slightly dodgy as hell, but should work
*/
*close = (strstr(*protocol + strlen(*protocol) + 3,
"Connection: close\r\n") ||
strcmp(*protocol, "HTTP/1.0") == 0);
tmppath = Strdup(*path);
DecodeURL(*path, tmppath);
free(tmppath);
return 1;
}
return 0;
}
/* ---------------------------------------------------------------------------
THREAD CODE
---------------------------------------------------------------------------
*/
static void *ThreadCode(void *p)
{
char buff[4096];
char *method = NULL;
char *path = NULL;
char *protocol = NULL;
int close_connection = 0;
thread_arg_t *args = p;
while (Read(args->fd, buff, sizeof buff) && !close_connection)
{
if (ParseRequest(buff, &method, &path,
&protocol, &close_connection))
{
if (strcmp(method, "GET") == 0)
{
printf("%s %s %s\n", method, path, protocol);
path = basename(path);
if (strcmp(path, "/") == 0)
{
printf("Generate listing for /\n");
GenerateListing(args->fd, protocol);
}
else if (access(path, R_OK) == 0)
{
printf("Generating download for %s\n", path);
GenerateFile(args->fd, args->magic, path, protocol);
}
else
{
printf("File not found - returning 404\n");
GenerateStatus(args->fd, 404, "Not found", protocol);
}
}
else
{
printf("Method %s not supported\n", method);
GenerateStatus(args->fd, 501, "Not implemented", protocol);
}
}
else
{
printf("Bad request %s\n", method);
}
}
printf("Connection closed on %d\n", args->fd);
close(args->fd);
return NULL;
}
/* ---------------------------------------------------------------------------
MAIN
---------------------------------------------------------------------------
*/
int main(int argc, char *argv[])
{
magic_t magic;
int port = 80;
int opt;
int sock;
const char *dir=".";
while((opt = getopt(argc, argv, "p:d:")) != -1)
{
switch(opt)
{
case 'p':
port = atoi(optarg);
break;
case 'd':
dir = optarg;
break;
default:
fprintf(stderr, "%s: usage %s [-p port] [-d dir]\n",
argv[0], argv[0]);
return EXIT_FAILURE;
}
}
magic = magic_open(MAGIC_MIME_TYPE);
if (!magic)
{
fprintf(stderr, "%s: Failed to open magic library\n", argv[0]);
}
magic_load(magic, NULL);
if ((sock = Listen(port)) == -1)
{
return EXIT_FAILURE;
}
chdir(dir);
printf("Serving up %s on %d\n", dir, port);
while(1)
{
pthread_t thread;
thread_arg_t *thread_args;
int fd;
if ((fd = accept(sock, NULL, 0)) == -1)
{
perror("accept");
close(sock);
return EXIT_FAILURE;
}
printf("Accepted connection on %d\n", fd);
thread_args = Malloc(sizeof *thread_args);
thread_args->fd = fd;
thread_args->magic = magic;
pthread_create(&thread, NULL, ThreadCode, thread_args);
pthread_detach(thread);
}
return EXIT_SUCCESS;
}
/*
vim: ai sw=4 ts=8 expandtab
*/