commit c980c94a0e840e1ea2c1e9a596039be08e8e647c
Author: quantumish <freifeld.david@gmail.com>
Date: Sat, 8 Apr 2023 20:55:07 -0700
Initial commit
Diffstat:
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
+
+