/* 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 */