bunkum

an old and silly c99 web server with some fun features
Log | Files | Refs | README

commit c980c94a0e840e1ea2c1e9a596039be08e8e647c
Author: quantumish <freifeld.david@gmail.com>
Date:   Sat,  8 Apr 2023 20:55:07 -0700

Initial commit

Diffstat:
Ahttp.c | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahttp/response.c | 34++++++++++++++++++++++++++++++++++
Ahttp/response.h | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Autils/hashmap.c | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autils/hashmap.h | 30++++++++++++++++++++++++++++++
Autils/llist.c | 4++++
Autils/shitvec.c | 47+++++++++++++++++++++++++++++++++++++++++++++++
Autils/shitvec.h | 24++++++++++++++++++++++++
8 files changed, 483 insertions(+), 0 deletions(-)

diff --git a/http.c b/http.c @@ -0,0 +1,152 @@ +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <stdbool.h> +#include <unistd.h> +#include <string.h> +#include <assert.h> + +#include <sys/stat.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <sys/types.h> +#include <arpa/inet.h> + +#include <pthread.h> + +#include <zlib.h> + +#include "utils/shitvec.h" +#include "http/response.h" + +void die(char* p) { + perror(p); + exit(1); +} + +void scuffed_htmlescape(char* dst, char* src, size_t sz) { + strcpy(dst, "<pre>\n"); + int offset = 6; + int i; + for (i = 0; i < sz; i++) { + switch (src[i]) { + case '<': + strcpy(dst+i+offset, "&lt;"); + offset += 3; + break; + case '>': + strcpy(dst+i+offset, "&gt;"); + offset += 3; + break; + case '&': + strcpy(dst+i+offset, "&amp;"); + offset += 4; + break; + default: + dst[i+offset] = src[i]; + } + } + strcpy(dst+i+offset, "\n</pre>"); +} + +#define MAX_PATH_SZ 32 +void* handle_conn(void* ctxt) { + int ns = *(int*)ctxt; + char pathbuf[MAX_PATH_SZ]; + char msgbuf[512] = {0}; + while(true) { + if (recv(ns, msgbuf, 512, 0) == 0) return 0x0; + if (strlen(msgbuf) > 0) { + printf("%s", msgbuf); + sscanf(msgbuf, "GET %s HTTP/1.1", (char*)&pathbuf); + printf("%s\n\n", pathbuf); + + int fd = open(pathbuf+1, O_RDONLY); + if (fd != -1) { + response_t r = resp_new(OK); + + struct stat st; + fstat(fd, &st); + char* fbuf = malloc(st.st_size); + size_t i; + for (i = 0; read(fd, fbuf+(i*4096), 4096) > 0; i++); + printf("%ld\n", i); + + bool escape = false; + bool compression = true; + char* ext = strchr(pathbuf+1, '.')+1; + // TODO clean this up some + if (strcmp(ext, "html") == 0) { + resp_add_hdr(&r, "Content-Type", "text/html"); + } else if (strcmp(ext, "png") == 0) { + compression = false; + resp_add_hdr(&r, "Content-Type", "image/png"); + } else if (strcmp(ext, "svg") == 0) { + resp_add_hdr(&r, "Content-Type", "image/svg+xml"); + } else if (strcmp(ext, "jpeg") == 0) { + resp_add_hdr(&r, "Content-Type", "image/jpeg"); + } else escape = true; + if (escape) { + char* efbuf = malloc((size_t)(st.st_size * 2)); // FIXME dubious assumption + scuffed_htmlescape(efbuf, fbuf, st.st_size); + free(fbuf); + resp_add_content(&r, fbuf, strlen(efbuf)); + free(efbuf); + } else if (compression) { + resp_add_hdr(&r, "Content-Encoding", "deflate"); + size_t clen = compressBound(st.st_size); + char* cbuf = malloc(clen); + compress((Bytef*)cbuf, &clen, (Bytef*)fbuf, st.st_size); + free(fbuf); + resp_add_content(&r, cbuf, clen); + free(cbuf); + } else { + resp_add_content(&r, fbuf, st.st_size); + } + send(ns, r.content, r.sz, 0); + printf("%s\n", r.content); + free(r.content); + } else { + response_t r = __resp_new(NotFound, "Fuck off."); + resp_add_hdr(&r, "Content-Type", "text/html"); + resp_add_content(&r, "Fuck off.", 9); + send(ns, r.content, r.sz, 0); + free(r.content); + } + printf("Done.\n"); + } + } +} + +int main() { + int s = socket(AF_INET, SOCK_STREAM, 0); + if (s == -1) die("socket"); + + struct sockaddr_in name; + name.sin_family = AF_INET; + name.sin_port = htons(8082); + name.sin_addr.s_addr = htonl(INADDR_ANY); + + if (bind(s, (struct sockaddr*)&name, sizeof(name)) < 0) { + name.sin_port = htons(8081); + if (bind(s, (struct sockaddr*)&name, sizeof(name)) < 0) { + die("bind"); + } + } + + shitvec_t paths = shitvec_new(MAX_PATH_SZ); + shitvec_subpush(&paths, "hi", 4); + shitvec_subpush(&paths, "http.c", 6); + + int namelen; + char pathbuf[MAX_PATH_SZ]; + struct sockaddr_in client; + char msgbuf[512] = {0}; + while (true) { + listen(s, 1); + int ns = accept(s, (struct sockaddr*)&client, (socklen_t*)&namelen); + + pthread_t thread; + pthread_create(&thread, NULL, handle_conn, &ns); + } +} diff --git a/http/response.c b/http/response.c @@ -0,0 +1,34 @@ +#include <stddef.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include "../utils/hashmap.h" +#include "response.h" + +response_t __resp_new(enum StatusCode code, char* code_name) { + response_t resp; + memset(resp.header, 0, 512); + sprintf(resp.header, "HTTP/1.1 %d %s\r\n", code, code_name); + return resp; +} + +void resp_add_hdr(response_t* r, char* hdr, char* val) { + char header[64]; + sprintf(header, "%s: %s\r\n", hdr, val); + strcat(r->header, header); +} + +void resp_add_content(response_t* r, char* content, size_t content_len) { + char length[8]; + sprintf(length, "%ld", content_len); + resp_add_hdr(r, "Content-Length", length); + + strcat(r->header, "\r\n"); + size_t header_len = strlen(r->header); + + r->sz = strlen(r->header)+content_len; + r->content = malloc(r->sz); + strcpy(r->content, r->header); + memcpy(r->content+header_len, content, content_len); +} diff --git a/http/response.h b/http/response.h @@ -0,0 +1,50 @@ + +#ifndef RESPONSE_H +#define RESPONSE_H + +#include <stddef.h> +#include <stdbool.h> + +#define INNER_STRINGIZE(s) #s +#define STRINGIZE(s) INNER_STRINGIZE(s) + +enum StatusCode { + Continue = 100, + SwitchingProtocols = 101, + OK = 200, + Created = 201, + Accepted = 202, + NonAuthoritativeInformation = 203, + NoContent = 204, + ResetContent = 205, + PartialContent = 206, + MultipleChoices = 300, + MovedPermanently = 301, + Found = 302, + SeeOther = 303, + NotModified = 304, + UseProxy = 305, + TemporaryRedirect = 307, + Unauthorized = 401, + PaymentRequired = 402, + Forbidden = 403, + NotFound = 404, + MethodNotAllowed = 405, + NotAcceptable = 406, + InternalServerError = 500, + NotImplemented = 501, +}; + +typedef struct response { + char header[512]; + char* content; + size_t sz; +} response_t; + +response_t __resp_new(enum StatusCode code, char* code_name); +#define resp_new(code) __resp_new(code, STRINGIZE(code)) + +void resp_add_hdr(response_t* r, char* hdr, char* val); +void resp_add_content(response_t* r, char* content, size_t content_len); + +#endif diff --git a/utils/hashmap.c b/utils/hashmap.c @@ -0,0 +1,142 @@ +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <stdint.h> +#include <stdbool.h> + +#include "hashmap.h" + +#define HASHMAP_INIT_SIZE 16 + +// djb2 hash +// http://www.cse.yorku.ca/~oz/hash.html +size_t hash(char* p, size_t sz) { + unsigned long hash = 5381; + int c; + + for (int i = 0; i < sz; i++) { + c = *p++; + hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ + } + return hash; +} + +// Check if arbitrary block of memory is zero +bool iszero(void *p, size_t len) { + bool iszero = true; + for (int i = 0; i < len; i++){ + if (*(char*)p != 0) { + iszero = false; + break; + } + p++; + } + return iszero; +} + + +// Initialize the hashmap pointed to by h. +hashmap_t hashmap_new(size_t ksize, size_t vsize) { + hashmap_t h; + // Keys and vals are in different arrays so unnecessary things aren't in cache + h.keys = calloc(HASHMAP_INIT_SIZE, ksize); + h.vals = malloc(vsize * HASHMAP_INIT_SIZE); + h.k_sz = ksize; + h.v_sz = vsize; + h.len = HASHMAP_INIT_SIZE; + h.filled = 0; // Store # of filled keys for load factor calculation + return h; +} + +void hashmap_resize(hashmap_t* h) { + void* tempk = h->keys; + void* tempv = h->vals; + h->len *= 2; + // Simple to use calloc here because keys need to be initialized to zero. + h->keys = calloc(h->len, h->k_sz); + h->vals = malloc(h->len * h->v_sz); + // Copy all keys and values + for (size_t off = 0; off < (h->len / 2); off++) { + // Don't bother copying entries with empty keys + if (!iszero(tempk + (off*h->k_sz), h->k_sz)) { + hashmap_set(h, tempk + (off*h->k_sz), tempv + (off*h->v_sz)); + h->filled--; + } + } + free(tempk); + free(tempv); +} + +int hashmap_set(hashmap_t* h, void* k, void* v) { + // Check if load factor too high + + if ((float)h->filled / h->len > (float)2/3) hashmap_resize(h); + size_t index = hash(k, h->k_sz) % h->len; + int looped_once = 0; + // Start at the hashed index, iterate until own key or empty key is found + for (size_t off = index; off < h->len; off+=1) { + // Check if stored key at hashed index is equal to key: if so, replace value + if (memcmp(h->keys + (off*h->k_sz), k, h->k_sz) == 0) { + memcpy(h->vals + (off*h->v_sz), v, h->v_sz); + return 0x0; + } + // Check if stored key is zero: if so, copy key and value over + if (iszero(h->keys + (off*h->k_sz), h->k_sz)) { + h->filled++; + memcpy(h->vals + (off*h->v_sz), v, h->v_sz); + memcpy(h->keys + (off*h->k_sz), k, h->k_sz); + return 0x0; + } + if (off == h->len-1 && looped_once == 0) { + off = 0; + looped_once = 1; + } + } + // Hashmap is full (this shouldn't ever happen unless default size is really low) + return 0x1; +} + +// Returns address of value if it exists, otherwise returns 0x0 for no value or 0x1 for full table. +void* hashmap_get(hashmap_t* h, void* k) { + size_t index = hash(k, h->k_sz) % h->len; + int looped_once = 0; + // Start at the hashed index, iterate until wanted key or empty key is found + for (size_t off = index; off < h->len; off+=1) { + // Check if entry has correct key + if (memcmp(h->keys + (off * h->k_sz), k, h->k_sz) == 0) { + return h->vals + (off * h->v_sz); + } + // Empty entry means this key doesn't exist: return not found + if (iszero(h->keys + (off * h->k_sz), h->k_sz)) { + return 0x0; + } + // If we hit the end, wrap back around the hashmap. Only do this once. + if (off == h->len-1 && looped_once == 0) { + off = 0; + looped_once = 1; + } + } +} + +int hashmap_del(hashmap_t* h, void* k) { + size_t index = hash(k, h->k_sz) % h->len; + int looped_once = 0; + for (size_t off = index; off < h->len; off+=1) { + if (memcmp(h->keys + (off * h->k_sz), k, h->k_sz) == 0) { + memset(h->keys + (off * h->v_sz), 0, h->k_sz); + // No reason to delete value, will just be overwritten next time. + return 0; + } + if (iszero(h->keys + (off * h->k_sz), h->k_sz)) return 1; + if (off == h->len-1 && looped_once == 0) { + off = 0; + looped_once = 1; + } + } + return 1; +} + +void hashmap_free(hashmap_t* h) { + free(h->keys); + free(h->vals); +} diff --git a/utils/hashmap.h b/utils/hashmap.h @@ -0,0 +1,30 @@ +#ifndef HASHMAP_H +#define HASHMAP_H + +#include <stddef.h> + +typedef struct hashmap_t { + void* keys; + void* vals; + size_t k_sz; + size_t v_sz; + size_t len; + size_t filled; +} hashmap_t; + +// Generates a new hashmap_t. +hashmap_t hashmap_new(size_t ksize, size_t vsize); + +// Sets a key-value pair in the hashmap_t passed to it. Returns 0x0 for success, 0x1 for failure. +int hashmap_set(hashmap_t* h, void* k, void* v); + +// Returns address of value associated with key in hashmap if it exists, otherwise returns 0x0 for no value or 0x1 for full table. +void* hashmap_get(hashmap_t* h, void* k); + +// Deletes key in hashmap (but not value). +int hashmap_del(hashmap_t* h, void* k); + +// Frees a hashmap. +void hashmap_free(hashmap_t* h); + +#endif diff --git a/utils/llist.c b/utils/llist.c @@ -0,0 +1,4 @@ + +struct node { + struct node* next; +}; diff --git a/utils/shitvec.c b/utils/shitvec.c @@ -0,0 +1,47 @@ + +#include <stdlib.h> +#include <string.h> + +#include "shitvec.h" + +shitvec_t shitvec_new(size_t e_sz) { + shitvec_t sv; + sv.alloc_sz = SHITVEC_INIT_SZ; + sv.vec_sz = 0; + sv.e_sz = e_sz; + sv.arr = malloc(sv.alloc_sz); + memset(sv.arr, 0, sv.alloc_sz); + return sv; +} + +void* shitvec_get(shitvec_t* sv, size_t index) { + if (index > sv->vec_sz) return 0x0; + return sv->arr+(sv->e_sz * index); +} + +void shitvec_push(shitvec_t* sv, void* item) { + sv->vec_sz += 1; + if ((sv->arr+(sv->e_sz * sv->vec_sz)) > sv->arr+sv->alloc_sz) { + sv->arr = realloc(sv->arr, sv->alloc_sz * 2); + } + memcpy(sv->arr+(sv->vec_sz * sv->e_sz), item, sv->e_sz); +} + + +void shitvec_subpush(shitvec_t* sv, void* item, size_t sz) { + sv->vec_sz += 1; + if ((sv->arr+(sv->e_sz * sv->vec_sz)) > sv->arr+sv->alloc_sz) { + sv->arr = realloc(sv->arr, sv->alloc_sz * 2); + } + memcpy(sv->arr+(sv->vec_sz * sv->e_sz), item, sz); +} + + +bool shitvec_check(shitvec_t* sv, void* item, int(cmp)(void*, void*)) { + for (size_t i = 0; i < sv->vec_sz; i++) { + if (cmp(sv->arr+(i*sv->e_sz), item) == 0) { + return true; + } + } + return false; +} diff --git a/utils/shitvec.h b/utils/shitvec.h @@ -0,0 +1,24 @@ +#ifndef SHITVEC_H +#define SHITVEC_H + +#include <stddef.h> +#include <stdbool.h> + +#define SHITVEC_INIT_SZ 1024 + +typedef struct shitvec { + void* arr; + size_t e_sz; + size_t alloc_sz; + size_t vec_sz; +} shitvec_t; + +shitvec_t shitvec_new(size_t e_sz); +void* shitvec_get(shitvec_t* sv, size_t index); +void shitvec_push(shitvec_t* sv, void* item); +void shitvec_subpush(shitvec_t* sv, void* item, size_t sz); +bool shitvec_check(shitvec_t* sv, void* item, int(cmp)(void*, void*)); + +#endif + +