diff options
author | Ian C <ianc@noddybox.co.uk> | 2024-04-19 17:28:34 +0100 |
---|---|---|
committer | Ian C <ianc@noddybox.co.uk> | 2024-04-19 17:28:34 +0100 |
commit | 9608c60358c60c6c921a00915f548fefcbcb8757 (patch) | |
tree | 6b8e70467deb91a99821b8a23c119ff0c2378c96 /httpserve.c |
Initial version.
Diffstat (limited to 'httpserve.c')
-rw-r--r-- | httpserve.c | 478 |
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 +*/ |