summaryrefslogtreecommitdiff
path: root/httpserve.c
diff options
context:
space:
mode:
Diffstat (limited to 'httpserve.c')
-rw-r--r--httpserve.c478
1 files changed, 478 insertions, 0 deletions
diff --git a/httpserve.c b/httpserve.c
new file mode 100644
index 0000000..946f4ae
--- /dev/null
+++ b/httpserve.c
@@ -0,0 +1,478 @@
+/*
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+#include <stdlib.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+
+#include <magic.h>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netdb.h>
+#include <libgen.h>
+#include <glob.h>
+#include <fcntl.h>
+
+/* ---------------------------------------------------------------------------
+ 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, "<!DOCTYPE html>\n<html><head></head><body>");
+
+ for(f = 0; f < gl.gl_pathc; f++)
+ {
+ stream = PrintStream(stream, "<a href=\"%s\">%s</a><br>",
+ gl.gl_pathv[f], gl.gl_pathv[f]);
+ }
+
+ stream = PrintStream(stream, "</body></html>");
+
+ 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
+*/