├── .gitignore ├── freerss.png ├── www ├── testsite │ ├── freerss.png │ ├── cgi-bin │ │ ├── testcgi.pl │ │ └── index.pl │ ├── style.css │ ├── about.html │ ├── latest.html │ └── default.htm ├── littlekitten │ └── index.html └── sites.conf ├── testcgi.pl ├── lktest.conf ├── Makefile ├── LICENSE ├── t.c ├── backup ├── setopt.c └── tserv.c ├── lkreflist.c ├── lkhttpcgiparser.c ├── README.md ├── lkalloc.c ├── lkstringlist.c ├── style.css ├── lkstringtable.c ├── lktables.h ├── index.html ├── lkbuffer.c ├── spec.txt ├── tclient.c ├── lklib.c ├── lklib.h ├── lkcontext.c ├── lkws.c ├── lkhttprequestparser.c ├── lkstring.c ├── lknet.h ├── lkconfig.c ├── lktest.c ├── lknet.c └── lkhttpserver.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | t 3 | lkws 4 | tclient 5 | lktest 6 | 7 | -------------------------------------------------------------------------------- /freerss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robdelacruz/lkwebserver/HEAD/freerss.png -------------------------------------------------------------------------------- /www/testsite/freerss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robdelacruz/lkwebserver/HEAD/www/testsite/freerss.png -------------------------------------------------------------------------------- /testcgi.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | STDOUT->autoflush(1); 4 | print "Hello from perl script.\n"; 5 | #sleep 1; 6 | print "Bye from perl script.\n"; 7 | 8 | -------------------------------------------------------------------------------- /www/testsite/cgi-bin/testcgi.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | STDOUT->autoflush(1); 4 | print "Hello from perl script.\n"; 5 | #sleep 1; 6 | print "Bye from perl script.\n"; 7 | 8 | -------------------------------------------------------------------------------- /www/littlekitten/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Little Kitten Website 7 | 9 | 10 | 11 |

This is the Little Kitten Webserver homepage!

12 |

Welcome to the Little Kitten Webserver homepage.

13 | 14 | 15 | -------------------------------------------------------------------------------- /www/testsite/cgi-bin/index.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | my $starthtml = <<"EOT"; 4 | 5 | 6 | perl cgi test 7 | 8 |

Perl CGI Test

9 | EOT 10 | 11 | my $endhtml = <<"EOT"; 12 | 13 | EOT 14 | 15 | print "Content-Type: text/html\n"; 16 | print "Status: 200\n"; 17 | print "\n"; 18 | print $starthtml; 19 | 20 | print "\n"; 25 | 26 | print $endhtml; 27 | 28 | -------------------------------------------------------------------------------- /www/sites.conf: -------------------------------------------------------------------------------- 1 | # *** little kitten config file *** 2 | 3 | serverhost=127.0.0.1 4 | port=5000 5 | 6 | # fallthrough 7 | hostname * 8 | homedir=www/testsite 9 | cgidir=cgi-bin 10 | alias latest=latest.html 11 | alias about=about.html 12 | alias cgitest=cgi-bin/index.pl 13 | 14 | # testsite.xyz 15 | hostname testsite.xyz 16 | homedir=www/testsite 17 | cgidir=cgi-bin 18 | alias latest=latest.html 19 | alias about=about.html 20 | alias cgitest=cgi-bin/index.pl 21 | 22 | # Redirect newsboard.littlekitten.xyz to localhost:8001 server 23 | hostname newsboard.littlekitten.xyz 24 | proxyhost=localhost:8001 25 | 26 | # littlekitten.xyz 27 | hostname littlekitten.xyz 28 | homedir=www/littlekitten 29 | 30 | -------------------------------------------------------------------------------- /lktest.conf: -------------------------------------------------------------------------------- 1 | # *** lktest config file *** 2 | 3 | serverhost=127.0.0.1 4 | port=5000 5 | 6 | # Matches all other hostnames 7 | hostname * 8 | homedir=/var/www/testsite 9 | alias latest=latest.html 10 | 11 | # Matches http://localhost 12 | hostname localhost 13 | homedir=/var/www/testsite 14 | 15 | # http://littlekitten.xyz 16 | hostname littlekitten.xyz 17 | homedir=/var/www/testsite 18 | cgidir=cgi-bin 19 | alias latest=latest.html 20 | alias about=about.html 21 | alias guestbook=cgi-bin/guestbook.pl 22 | alias blog=cgi-bin/blog.pl 23 | 24 | # Redirect newsboard.littlekitten.xyz to localhost:8001 server 25 | hostname newsboard.littlekitten.xyz 26 | proxyhost=localhost:8001 27 | 28 | hostname blog.littlekitten.xyz 29 | homedir=/var/www/blog 30 | cgidir=. 31 | alias latest=latest.pl 32 | alias login=login.pl 33 | 34 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-g -Wall 2 | LIBS= 3 | LKLIB_SRC=lklib.c lkstring.c lkstringtable.c lkbuffer.c lknet.c lkstringlist.c lkreflist.c lkalloc.c 4 | LKNET_SRC=lkhttpserver.c lkcontext.c lkhttprequestparser.c lkhttpcgiparser.c lkconfig.c 5 | #DEFINES=-DDEBUGALLOC 6 | DEFINES= 7 | 8 | all: lkws tclient lktest 9 | 10 | lkws: lkws.c $(LKLIB_SRC) $(LKNET_SRC) 11 | gcc -o lkws lkws.c $(LKLIB_SRC) $(LKNET_SRC) $(DEFINES) $(CFLAGS) $(LIBS) 12 | 13 | tclient: tclient.c $(LKLIB_SRC) $(LKNET_SRC) 14 | gcc -o tclient tclient.c $(LKLIB_SRC) $(LKNET_SRC) $(DEFINES) $(CFLAGS) $(LIBS) 15 | 16 | lktest: lktest.c $(LKLIB_SRC) $(LKNET_SRC) 17 | gcc -o lktest lktest.c $(LKLIB_SRC) $(LKNET_SRC) $(DEFINES) $(CFLAGS) $(LIBS) 18 | 19 | t: t.c $(LKLIB_SRC) $(LKNET_SRC) 20 | gcc -o t t.c $(LKLIB_SRC) $(LKNET_SRC) $(DEFINES) $(CFLAGS) $(LIBS) 21 | 22 | clean: 23 | rm -rf t lkws tclient lktest 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 rob 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /t.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "lklib.h" 17 | #include "lknet.h" 18 | #include "lktables.h" 19 | 20 | // tbl is a null-terminated array of char* key-value pairs 21 | // Ex. tbl: {"key1", "val1", "key2", "val2", "key3", "val3", NULL} 22 | // where key1/val1, key2/val2, key3/val3 are the key-value pairs. 23 | // Returns matching val to testk, or NULL if no match. 24 | void *lookup(void **tbl, char *testk) { 25 | void **p = tbl; 26 | while (*p != NULL) { 27 | char *k = *p; 28 | if (k == NULL) break; 29 | if (!strcmp(k, testk)) { 30 | return *(p+1); // val 31 | } 32 | p += 2; // next key 33 | } 34 | return NULL; 35 | } 36 | 37 | int main(int argc, char *argv[]) { 38 | char *v1 = lookup(mimetypes_tbl, "123"); 39 | char *v2 = lookup(mimetypes_tbl, "bz"); 40 | char *v3 = lookup(mimetypes_tbl, "htm"); 41 | 42 | printf("v1: '%s' v2: '%s' v3: '%s'\n", v1, v2, v3); 43 | } 44 | 45 | -------------------------------------------------------------------------------- /backup/setopt.c: -------------------------------------------------------------------------------- 1 | // setopt pattern sample: 2 | 3 | void lk_httpserver_setopt(LKHttpServer *server, LKHttpServerOpt opt, ...) { 4 | char *homedir; 5 | char *port; 6 | char *host; 7 | char *cgidir; 8 | char *k, *v; 9 | LKHttpServerSettings *settings = server->settings; 10 | 11 | va_list args; 12 | va_start(args, opt); 13 | 14 | switch(opt) { 15 | case LKHTTPSERVEROPT_HOMEDIR: 16 | homedir = va_arg(args, char*); 17 | lk_string_assign(settings->homedir, homedir); 18 | lk_string_chop_end(settings->homedir, "/"); 19 | break; 20 | case LKHTTPSERVEROPT_PORT: 21 | port = va_arg(args, char*); 22 | lk_string_assign(settings->port, port); 23 | break; 24 | case LKHTTPSERVEROPT_HOST: 25 | host = va_arg(args, char*); 26 | lk_string_assign(settings->host, host); 27 | break; 28 | case LKHTTPSERVEROPT_CGIDIR: 29 | cgidir = va_arg(args, char*); 30 | if (strlen(cgidir) == 0) { 31 | break; 32 | } 33 | lk_string_assign(settings->cgidir, cgidir); 34 | 35 | // Surround cgi dir with slashes: /cgi-bin/ for easy uri matching. 36 | if (!lk_string_starts_with(settings->cgidir, "/")) { 37 | lk_string_prepend(settings->cgidir, "/"); 38 | } 39 | if (!lk_string_ends_with(settings->cgidir, "/")) { 40 | lk_string_append(settings->cgidir, "/"); 41 | } 42 | break; 43 | case LKHTTPSERVEROPT_ALIAS: 44 | k = va_arg(args, char*); 45 | v = va_arg(args, char*); 46 | lk_stringtable_set(settings->aliases, k, v); 47 | break; 48 | case LKHTTPSERVEROPT_PROXYPASS: 49 | k = va_arg(args, char*); 50 | v = va_arg(args, char*); 51 | lk_stringtable_set(settings->proxypass, k, v); 52 | break; 53 | default: 54 | break; 55 | } 56 | 57 | va_end(args); 58 | } 59 | 60 | 61 | -------------------------------------------------------------------------------- /lkreflist.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "lklib.h" 10 | 11 | #define N_GROW_REFLIST 10 12 | 13 | LKRefList *lk_reflist_new() { 14 | LKRefList *l = lk_malloc(sizeof(LKRefList), "lk_reflist_new"); 15 | l->items_size = N_GROW_REFLIST; 16 | l->items_len = 0; 17 | l->items_cur = 0; 18 | 19 | l->items = lk_malloc(l->items_size * sizeof(void*), "lk_reflist_new_items"); 20 | memset(l->items, 0, l->items_size * sizeof(void*)); 21 | return l; 22 | } 23 | 24 | void lk_reflist_free(LKRefList *l) { 25 | assert(l->items != NULL); 26 | 27 | memset(l->items, 0, l->items_size * sizeof(void*)); 28 | lk_free(l->items); 29 | l->items = NULL; 30 | lk_free(l); 31 | } 32 | 33 | void lk_reflist_append(LKRefList *l, void *p) { 34 | assert(l->items_len <= l->items_size); 35 | 36 | if (l->items_len == l->items_size) { 37 | void **pitems = lk_realloc(l->items, (l->items_size+N_GROW_REFLIST) * sizeof(void*), "lk_reflist_append"); 38 | if (pitems == NULL) { 39 | return; 40 | } 41 | l->items = pitems; 42 | l->items_size += N_GROW_REFLIST; 43 | } 44 | l->items[l->items_len] = p; 45 | l->items_len++; 46 | 47 | assert(l->items_len <= l->items_size); 48 | } 49 | 50 | void *lk_reflist_get(LKRefList *l, unsigned int i) { 51 | if (l->items_len == 0) return NULL; 52 | if (i >= l->items_len) return NULL; 53 | return l->items[i]; 54 | } 55 | 56 | void *lk_reflist_get_cur(LKRefList *l) { 57 | if (l->items_cur >= l->items_len) { 58 | return NULL; 59 | } 60 | return l->items[l->items_cur]; 61 | } 62 | 63 | void lk_reflist_remove(LKRefList *l, unsigned int i) { 64 | if (l->items_len == 0) return; 65 | if (i >= l->items_len) return; 66 | 67 | int num_items_after = l->items_len-i-1; 68 | memmove(l->items+i, l->items+i+1, num_items_after * sizeof(void*)); 69 | memset(l->items+l->items_len-1, 0, sizeof(void*)); 70 | l->items_len--; 71 | } 72 | 73 | void lk_reflist_clear(LKRefList *l) { 74 | memset(l->items, 0, l->items_size * sizeof(void*)); 75 | l->items_len = 0; 76 | l->items_cur = 0; 77 | } 78 | 79 | -------------------------------------------------------------------------------- /lkhttpcgiparser.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "lklib.h" 10 | #include "lknet.h" 11 | 12 | void parse_cgi_header_line(char *line, LKHttpResponse *resp); 13 | 14 | void parse_cgi_output(LKBuffer *buf, LKHttpResponse *resp) { 15 | char cgiline[LK_BUFSIZE_MEDIUM]; 16 | int has_crlf = 0; 17 | 18 | // Parse cgi_outputbuf line by line into http response. 19 | while (buf->bytes_cur < buf->bytes_len) { 20 | lk_buffer_readline(buf, cgiline, sizeof(cgiline)); 21 | lk_chomp(cgiline); 22 | 23 | // Empty CRLF line ends the headers section 24 | if (is_empty_line(cgiline)) { 25 | has_crlf = 1; 26 | break; 27 | } 28 | parse_cgi_header_line(cgiline, resp); 29 | } 30 | if (buf->bytes_cur < buf->bytes_len) { 31 | lk_buffer_append( 32 | resp->body, 33 | buf->bytes + buf->bytes_cur, 34 | buf->bytes_len - buf->bytes_cur 35 | ); 36 | } 37 | 38 | char *content_type = lk_stringtable_get(resp->headers, "Content-Type"); 39 | 40 | // If cgi error 41 | // copy cgi output as is to display the error messages. 42 | if (!has_crlf && content_type == NULL) { 43 | lk_stringtable_set(resp->headers, "Content-Type", "text/plain"); 44 | lk_buffer_clear(resp->body); 45 | lk_buffer_append(resp->body, buf->bytes, buf->bytes_len); 46 | } 47 | } 48 | 49 | // Parse header line in the format Ex. User-Agent: browser 50 | void parse_cgi_header_line(char *line, LKHttpResponse *resp) { 51 | char *saveptr; 52 | char *delim = ":"; 53 | 54 | char *linetmp = lk_strdup(line, "parse_cgi_header_line"); 55 | lk_chomp(linetmp); 56 | char *k = strtok_r(linetmp, delim, &saveptr); 57 | if (k == NULL) { 58 | lk_free(linetmp); 59 | return; 60 | } 61 | char *v = strtok_r(NULL, delim, &saveptr); 62 | if (v == NULL) { 63 | v = ""; 64 | } 65 | 66 | // skip over leading whitespace 67 | while (*v == ' ' || *v == '\t') { 68 | v++; 69 | } 70 | lk_httpresponse_add_header(resp, k, v); 71 | 72 | if (!strcasecmp(k, "Status")) { 73 | int status = atoi(v); 74 | resp->status = status; 75 | } 76 | 77 | lk_free(linetmp); 78 | } 79 | 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Little Kitten Web Server 2 | 3 | A little web server written in C for Linux. 4 | 5 | - No external library dependencies 6 | - Single threaded using I/O multiplexing (select) 7 | - Supports CGI interface 8 | - Supports reverse proxy 9 | - lklib and lknet code available to create your own http server or client 10 | - Free to use and modify (MIT License) 11 | 12 | ## Usage 13 | 14 | To compile: 15 | 16 | $ make lkws 17 | 18 | Usage: 19 | 20 | lkws [homedir] [port] [host] [-f configfile] [--cgidir=cgifolder] 21 | 22 | configfile = configuration file containing site settings 23 | see sample configuration file below 24 | homedir = absolute or relative path to a home directory 25 | defaults to current working directory if not specified 26 | port = port number to bind to server 27 | defaults to 8000 28 | host = IP address to bind to server 29 | defaults to localhost 30 | cgifolder = parent directory of cgi scripts 31 | defaults to cgi-bin if not specified 32 | 33 | Examples: 34 | lkws ./testsite/ 8080 35 | lkws /var/www/testsite/ 8080 127.0.0.1 36 | lkws /var/www/testsite/ --cgidir=cgi-bin 37 | lkws -f sites.conf 38 | 39 | Sample configuration file: 40 | 41 | serverhost=127.0.0.1 42 | port=5000 43 | 44 | # Matches all other hostnames 45 | hostname * 46 | homedir=/var/www/testsite 47 | alias latest=latest.html 48 | 49 | # Matches http://localhost 50 | hostname localhost 51 | homedir=/var/www/testsite 52 | 53 | # http://littlekitten.xyz 54 | hostname littlekitten.xyz 55 | homedir=/var/www/testsite 56 | cgidir=cgi-bin 57 | alias latest=latest.html 58 | alias about=about.html 59 | alias guestbook=cgi-bin/guestbook.pl 60 | alias blog=cgi-bin/blog.pl 61 | 62 | # http://newsboard.littlekitten.xyz 63 | hostname newsboard.littlekitten.xyz 64 | proxyhost=localhost:8001 65 | 66 | # Format description: 67 | # 68 | # The host and port number is defined first, followed by one or more 69 | # host config sections. 70 | # 71 | # The host config section always starts with the 'hostname ' 72 | # line followed by the settings for that hostname. The section ends 73 | # on either EOF or when a new 'hostname ' line is read, 74 | # indicating the start of the next host config section. 75 | 76 | 77 | Compiles and runs only on Linux (sorry, no Windows version... yet) 78 | 79 | ## Todo 80 | 81 | - add logging 82 | - add perf tests for many simultaneous clients 83 | 84 | -------------------------------------------------------------------------------- /lkalloc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define ALLOCITEMS_SIZE 8192 9 | struct allocitem { 10 | void *p; 11 | char *label; 12 | }; 13 | static struct allocitem allocitems[ALLOCITEMS_SIZE]; 14 | 15 | void lk_alloc_init() { 16 | memset(allocitems, 0, sizeof(allocitems)); 17 | } 18 | 19 | // Add p to end of allocitems[]. 20 | static void add_p(void *p, char *label) { 21 | int i; 22 | for (i=0; i < ALLOCITEMS_SIZE; i++) { 23 | if (allocitems[i].p == NULL) { 24 | allocitems[i].p = p; 25 | allocitems[i].label = label; 26 | break; 27 | } 28 | } 29 | assert(i < ALLOCITEMS_SIZE); 30 | } 31 | 32 | #if 0 33 | // Replace matching allocitems[] p with newp. 34 | static void replace_p(void *p, void *newp, char *label) { 35 | int i; 36 | for (i=0; i < ALLOCITEMS_SIZE; i++) { 37 | if (allocitems[i].p == p) { 38 | allocitems[i].p = newp; 39 | allocitems[i].label = label; 40 | break; 41 | } 42 | } 43 | assert(i < ALLOCITEMS_SIZE); 44 | } 45 | #endif 46 | 47 | // Clear matching allocitems[] p. 48 | static void clear_p(void *p) { 49 | int i; 50 | for (i=0; i < ALLOCITEMS_SIZE; i++) { 51 | if (allocitems[i].p == p) { 52 | allocitems[i].p = NULL; 53 | allocitems[i].label = NULL; 54 | break; 55 | } 56 | } 57 | if (i >= ALLOCITEMS_SIZE) { 58 | printf("clear_p i: %d\n", i); 59 | } 60 | assert(i < ALLOCITEMS_SIZE); 61 | } 62 | 63 | void *lk_malloc(size_t size, char *label) { 64 | void *p = malloc(size); 65 | add_p(p, label); 66 | return p; 67 | } 68 | 69 | void *lk_realloc(void *p, size_t size, char *label) { 70 | clear_p(p); 71 | void *newp = realloc(p, size); 72 | add_p(newp, label); 73 | return newp; 74 | } 75 | 76 | void lk_free(void *p) { 77 | clear_p(p); 78 | free(p); 79 | } 80 | 81 | char *lk_strdup(const char *s, char *label) { 82 | char *sdup = strdup(s); 83 | add_p(sdup, label); 84 | return sdup; 85 | } 86 | 87 | char *lk_strndup(const char *s, size_t n, char *label) { 88 | char *sdup = strndup(s, n); 89 | add_p(sdup, label); 90 | return sdup; 91 | } 92 | 93 | void lk_print_allocitems() { 94 | printf("allocitems[] labels:\n"); 95 | for (int i=0; i < ALLOCITEMS_SIZE; i++) { 96 | if (allocitems[i].p != NULL) { 97 | printf("%s\n", allocitems[i].label); 98 | } 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /lkstringlist.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "lklib.h" 10 | 11 | #define N_GROW_STRINGLIST 10 12 | 13 | LKStringList *lk_stringlist_new() { 14 | LKStringList *sl = lk_malloc(sizeof(LKStringList), "lk_stringlist_new"); 15 | sl->items_size = N_GROW_STRINGLIST; 16 | sl->items_len = 0; 17 | 18 | sl->items = lk_malloc(sl->items_size * sizeof(LKString*), "lk_stringlist_new_items"); 19 | memset(sl->items, 0, sl->items_size * sizeof(LKString*)); 20 | return sl; 21 | } 22 | 23 | void lk_stringlist_free(LKStringList *sl) { 24 | assert(sl->items != NULL); 25 | 26 | for (int i=0; i < sl->items_len; i++) { 27 | lk_string_free(sl->items[i]); 28 | } 29 | memset(sl->items, 0, sl->items_size * sizeof(LKString*)); 30 | 31 | lk_free(sl->items); 32 | sl->items = NULL; 33 | lk_free(sl); 34 | } 35 | 36 | void lk_stringlist_append_lkstring(LKStringList *sl, LKString *lks) { 37 | assert(sl->items_len <= sl->items_size); 38 | 39 | if (sl->items_len == sl->items_size) { 40 | LKString **pitems = lk_realloc(sl->items, (sl->items_size+N_GROW_STRINGLIST) * sizeof(LKString*), "lk_stringlist_append_lkstring"); 41 | if (pitems == NULL) { 42 | return; 43 | } 44 | sl->items = pitems; 45 | sl->items_size += N_GROW_STRINGLIST; 46 | } 47 | sl->items[sl->items_len] = lks; 48 | sl->items_len++; 49 | 50 | assert(sl->items_len <= sl->items_size); 51 | } 52 | 53 | void lk_stringlist_append(LKStringList *sl, char *s) { 54 | lk_stringlist_append_lkstring(sl, lk_string_new(s)); 55 | } 56 | 57 | void lk_stringlist_append_sprintf(LKStringList *sl, const char *fmt, ...) { 58 | va_list args; 59 | char *ps; 60 | va_start(args, fmt); 61 | int z = vasprintf(&ps, fmt, args); 62 | va_end(args); 63 | if (z == -1) return; 64 | 65 | lk_stringlist_append(sl, ps); 66 | free(ps); 67 | } 68 | 69 | LKString *lk_stringlist_get(LKStringList *sl, unsigned int i) { 70 | if (sl->items_len == 0) return NULL; 71 | if (i >= sl->items_len) return NULL; 72 | return sl->items[i]; 73 | } 74 | 75 | void lk_stringlist_remove(LKStringList *sl, unsigned int i) { 76 | if (sl->items_len == 0) return; 77 | if (i >= sl->items_len) return; 78 | 79 | lk_string_free(sl->items[i]); 80 | 81 | int num_items_after = sl->items_len-i-1; 82 | memmove(sl->items+i, sl->items+i+1, num_items_after * sizeof(LKString*)); 83 | memset(sl->items+sl->items_len-1, 0, sizeof(LKString*)); 84 | sl->items_len--; 85 | } 86 | 87 | 88 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, hgroup, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | font-size: 100%; 18 | font: inherit; 19 | vertical-align: baseline; 20 | } 21 | 22 | html { 23 | background-color: #1a202c; 24 | color: #edf2f7; 25 | 26 | background-color: #202124; 27 | color: #bdc1c6; 28 | 29 | font-size: 1rem; 30 | line-height: 1.5rem; 31 | font-family: ui-sans-serif, system-ui, "Helvetica Neue", "Arial", "Noto Sans", sans-serif; 32 | } 33 | html, a { 34 | color: #bdc1c6; 35 | } 36 | 37 | a:hover { 38 | color: gold; 39 | } 40 | 41 | body { 42 | max-width: 800px; 43 | box-sizing: border-box; 44 | margin: 2em auto; 45 | } 46 | 47 | .masthead h1 { 48 | font-weight: 600; 49 | font-size: 2rem; 50 | margin-bottom: 0.75rem; 51 | } 52 | 53 | .hmenu { 54 | display: flex; 55 | flex-direction: row; 56 | } 57 | .hmenu > * { 58 | margin-right: 0.5rem; 59 | font-weight: 600; 60 | margin: 0 7px; 61 | } 62 | .hmenu > *:first-child { 63 | margin-left: 0px; 64 | } 65 | 66 | article { 67 | margin: 1rem 0 2rem; 68 | } 69 | article h1 { 70 | font-weight: 600; 71 | font-size: 1.125rem; 72 | line-height: 1.75rem; 73 | margin: 0 0 0.75rem; 74 | } 75 | article h1, article h1 a { 76 | color: limegreen; 77 | } 78 | article p { 79 | margin: 0 0 0.75rem 0; 80 | padding: 0; 81 | } 82 | article p a { 83 | color: #8ab4f8; 84 | } 85 | 86 | .box { 87 | width: 20rem; 88 | padding: 0.5rem 1rem; 89 | font-size: 0.875rem; 90 | line-height: 1.25rem; 91 | background-color: rgba(45,55,72,1); 92 | } 93 | .box h2 { 94 | font-size: 1rem; 95 | font-weight: 600; 96 | margin-bottom: 1rem; 97 | padding-bottom: 0.25rem; 98 | border-bottom: 1px solid; 99 | } 100 | .box h2 a, .box .linklist a { 101 | text-decoration: none; 102 | } 103 | 104 | .fg-content { 105 | color: rgba(107, 114, 128, 1); 106 | } 107 | 108 | .linklist { 109 | } 110 | .linklist li { 111 | list-style: none; 112 | margin-bottom: 0.5rem; 113 | line-height: 1.25; 114 | } 115 | .linklist a { 116 | } 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /www/testsite/style.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, hgroup, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | font-size: 100%; 18 | font: inherit; 19 | vertical-align: baseline; 20 | } 21 | 22 | html { 23 | background-color: #1a202c; 24 | color: #edf2f7; 25 | 26 | background-color: #202124; 27 | color: #bdc1c6; 28 | 29 | font-size: 1rem; 30 | line-height: 1.5rem; 31 | font-family: ui-sans-serif, system-ui, "Helvetica Neue", "Arial", "Noto Sans", sans-serif; 32 | } 33 | html, a { 34 | color: #bdc1c6; 35 | } 36 | 37 | a:hover { 38 | color: gold; 39 | } 40 | 41 | body { 42 | max-width: 800px; 43 | box-sizing: border-box; 44 | margin: 2em auto; 45 | } 46 | 47 | .masthead h1 { 48 | font-weight: 600; 49 | font-size: 2rem; 50 | margin-bottom: 0.75rem; 51 | } 52 | 53 | .hmenu { 54 | display: flex; 55 | flex-direction: row; 56 | } 57 | .hmenu > * { 58 | margin-right: 0.5rem; 59 | font-weight: 600; 60 | margin: 0 7px; 61 | } 62 | .hmenu > *:first-child { 63 | margin-left: 0px; 64 | } 65 | 66 | article { 67 | margin: 1rem 0 2rem; 68 | } 69 | article h1 { 70 | font-weight: 600; 71 | font-size: 1.125rem; 72 | line-height: 1.75rem; 73 | margin: 0 0 0.75rem; 74 | } 75 | article h1, article h1 a { 76 | color: limegreen; 77 | } 78 | article p { 79 | margin: 0 0 0.75rem 0; 80 | padding: 0; 81 | } 82 | article p a { 83 | color: #8ab4f8; 84 | } 85 | 86 | .box { 87 | width: 20rem; 88 | padding: 0.5rem 1rem; 89 | font-size: 0.875rem; 90 | line-height: 1.25rem; 91 | background-color: rgba(45,55,72,1); 92 | } 93 | .box h2 { 94 | font-size: 1rem; 95 | font-weight: 600; 96 | margin-bottom: 1rem; 97 | padding-bottom: 0.25rem; 98 | border-bottom: 1px solid; 99 | } 100 | h1 a, .box h2 a, .box .linklist a { 101 | text-decoration: none; 102 | } 103 | 104 | .fg-content { 105 | color: rgba(107, 114, 128, 1); 106 | } 107 | 108 | .linklist { 109 | } 110 | .linklist li { 111 | list-style: none; 112 | margin-bottom: 0.5rem; 113 | line-height: 1.25; 114 | } 115 | .linklist a { 116 | } 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /lkstringtable.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "lklib.h" 10 | 11 | LKStringTable *lk_stringtable_new() { 12 | LKStringTable *st = lk_malloc(sizeof(LKStringTable), "lk_stringtable_new"); 13 | st->items_size = 1; // start with room for n items 14 | st->items_len = 0; 15 | 16 | st->items = lk_malloc(st->items_size * sizeof(LKStringTableItem), "lk_stringtable_new_items"); 17 | memset(st->items, 0, st->items_size * sizeof(LKStringTableItem)); 18 | return st; 19 | } 20 | 21 | void lk_stringtable_free(LKStringTable *st) { 22 | assert(st->items != NULL); 23 | 24 | for (int i=0; i < st->items_len; i++) { 25 | lk_string_free(st->items[i].k); 26 | lk_string_free(st->items[i].v); 27 | } 28 | memset(st->items, 0, st->items_size * sizeof(LKStringTableItem)); 29 | 30 | lk_free(st->items); 31 | st->items = NULL; 32 | lk_free(st); 33 | } 34 | 35 | void lk_stringtable_set(LKStringTable *st, char *ks, char *v) { 36 | assert(st->items_size >= st->items_len); 37 | 38 | // If item already exists, overwrite it. 39 | for (int i=0; i < st->items_len; i++) { 40 | if (lk_string_sz_equal(st->items[i].k, ks)) { 41 | lk_string_assign(st->items[i].v, v); 42 | return; 43 | } 44 | } 45 | 46 | // If reached capacity, expand the array and add new item. 47 | if (st->items_len == st->items_size) { 48 | st->items_size += 10; 49 | st->items = lk_realloc(st->items, st->items_size * sizeof(LKStringTableItem), "lk_stringtable_set"); 50 | memset(st->items + st->items_len, 0, 51 | (st->items_size - st->items_len) * sizeof(LKStringTableItem)); 52 | } 53 | 54 | st->items[st->items_len].k = lk_string_new(ks); 55 | st->items[st->items_len].v = lk_string_new(v); 56 | st->items_len++; 57 | } 58 | 59 | char *lk_stringtable_get(LKStringTable *st, char *ks) { 60 | for (int i=0; i < st->items_len; i++) { 61 | if (lk_string_sz_equal(st->items[i].k, ks)) { 62 | return st->items[i].v->s; 63 | } 64 | } 65 | return NULL; 66 | } 67 | 68 | void lk_stringtable_remove(LKStringTable *st, char *ks) { 69 | for (int i=0; i < st->items_len; i++) { 70 | if (lk_string_sz_equal(st->items[i].k, ks)) { 71 | lk_string_free(st->items[i].k); 72 | lk_string_free(st->items[i].v); 73 | 74 | int num_items_after = st->items_len-i-1; 75 | memmove(st->items+i, st->items+i+1, num_items_after * sizeof(LKStringTableItem)); 76 | memset(st->items+st->items_len-1, 0, sizeof(LKStringTableItem)); 77 | st->items_len--; 78 | return; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /www/testsite/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | robdelacruz-xyz 7 | 8 | 10 | 11 | 12 |
13 |

Rob's Homepage

14 |
15 | 16 |
17 | latest 18 | programming 19 | articles 20 | weblog 21 | about 22 | cgi test 23 |
24 | 25 |
26 |

About Rob's Homepage

27 |

This is the homepage for such little known projects such as newsboard, fortune2, freerss, little kitten web server, and others. 28 |

29 |
30 | 31 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /lktables.h: -------------------------------------------------------------------------------- 1 | #ifndef LKTABLES_H 2 | #define LKTABLES_H 3 | 4 | // Lookup table for MIME media types 5 | void *mimetypes_tbl[] = { 6 | "aac", "audio/aac", 7 | "abw", "application/x-abiword", 8 | "arc", "application/x-freearc", 9 | "avif", "image/avif", 10 | "avi", "video/x-msvideo", 11 | "azw", "application/vnd.amazon.ebook", 12 | "bin", "application/octet-stream", 13 | "bmp", "image/bmp", 14 | "bz", "application/x-bzip", 15 | "bz2", "application/x-bzip2", 16 | "cda", "application/x-cdf", 17 | "csh", "application/x-csh", 18 | "css", "text/css", 19 | "csv", "text/csv", 20 | "doc", "application/msword", 21 | "docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", 22 | "eot", "application/vnd.ms-fontobject", 23 | "epub", "application/epub+zip", 24 | "gz", "application/gzip", 25 | "gif", "image/gif", 26 | "htm", "text/html", 27 | "html", "text/html", 28 | "ico", "image/vnd.microsoft.icon", 29 | "ics", "text/calendar", 30 | "jar", "application/java-archive", 31 | "jpeg", "image/jpeg", 32 | "jpg", "image/jpeg", 33 | "js", "text/javascript", 34 | "json", "application/json", 35 | "jsonld","application/ld+json", 36 | "mid", "audio/midi", 37 | "midi", "audio/midi", 38 | "mjs", "text/javascript", 39 | "mp3", "audio/mpeg", 40 | "mp4", "video/mp4", 41 | "mpeg", "video/mpeg", 42 | "mpkg", "application/vnd.apple.installer+xml", 43 | "odp", "application/vnd.oasis.opendocument.presentation", 44 | "ods", "application/vnd.oasis.opendocument.spreadsheet", 45 | "odt", "application/vnd.oasis.opendocument.text", 46 | "oga", "audio/ogg", 47 | "ogv", "video/ogg", 48 | "ogx", "application/ogg", 49 | "opus", "audio/opus", 50 | "otf", "font/otf", 51 | "png", "image/png", 52 | "pdf", "application/pdf", 53 | "php", "application/x-httpd-php", 54 | "ppt", "application.vnd.openxmlformats-officedocument.presentationml.presentation", 55 | "rar", "application/vnd.rar", 56 | "rtf", "application/rtf", 57 | "svg", "image/svg+xml", 58 | "tar", "application/x-tar", 59 | "tif", "image/tiff", 60 | "tiff", "image/tiff", 61 | "ts", "video/mp2t", 62 | "ttf", "font/ttf", 63 | "txt", "text/plain", 64 | "vsd", "application/vnd.visio", 65 | "wav", "audio/wav", 66 | "weba", "audio/webm", 67 | "webm", "audio/webm", 68 | "webp", "audio/webp", 69 | "woff2","font/woff2", 70 | "xhtml","application/xhtml+xml", 71 | "xls", "application/vnd.ms-excel", 72 | "xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", 73 | "xml", "application/xml", 74 | "xul", "application/vnd.mozilla.xul+xml", 75 | "zip", "application/zip", 76 | "3gp", "video/3gpp", 77 | "3g2", "video/3gpp", 78 | "7z", "application/x-7z-compressed", 79 | NULL 80 | 81 | }; 82 | 83 | #endif 84 | 85 | -------------------------------------------------------------------------------- /www/testsite/latest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | robdelacruz-xyz 7 | 8 | 10 | 11 | 12 |
13 |

Rob's Homepage

14 |
15 | 16 |
17 | latest 18 | programming 19 | articles 20 | weblog 21 | about 22 | cgi test 23 |
24 | 25 |
26 |

Latest Stuff

27 |

Here you will find all the latest stuff in Rob's Homepage. 28 |

29 |
    30 |
  • Little Kitten Web Server now supports aliases.
  • 31 |
  • Will work on CGI support next.
  • 32 |
  • Man, this is gonna be an actual web server.
  • 33 |
34 |
35 | 36 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | robdelacruz-xyz 7 | 8 | 10 | 11 | 12 |
13 |

Rob's Homepage

14 |
15 | 16 |
17 | latest 18 | programming 19 | articles 20 | weblog 21 |
22 | 23 |
24 |

FreeRSS: Web-based RSS Viewer and Portal

25 | freerss screenshot 26 |

Browse through RSS feeds in the browser. Just click 'add widget', paste in the url of the website containing the RSS feed and FreeRSS will auto-detect the RSS feed. You can drag and drop RSS widgets to arrange them. 27 |

28 |

FreeRSS was inspired by the old iGoogle home page (now defunct). It's what I personally use everyday to keep track of the latest posts from the websites I follow. It's really too bad that RSS has been falling in popularity, replaced by walled gardens such as Twitter and Facebook. The great thing about RSS is that it's an open standard, and can be used for web mashups. Lots of good stuff from the early web and internet has been forgotten, replaced by more commercial and less interesting properties such as social media feeds et al. 29 |

30 |
31 | 32 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /www/testsite/default.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | robdelacruz-xyz 7 | 8 | 10 | 11 | 12 |
13 |

Rob's Homepage

14 |
15 | 16 |
17 | latest 18 | programming 19 | articles 20 | about 21 | cgi-bin index.pl 22 | cgi test 23 |
24 | 25 |
26 |

FreeRSS: Web-based RSS Viewer and Portal

27 | freerss screenshot 28 |

Browse through RSS feeds in the browser. Just click 'add widget', paste in the url of the website containing the RSS feed and FreeRSS will auto-detect the RSS feed. You can drag and drop RSS widgets to arrange them. 29 |

30 |

FreeRSS was inspired by the old iGoogle home page (now defunct). It's what I personally use everyday to keep track of the latest posts from the websites I follow. It's really too bad that RSS has been falling in popularity, replaced by walled gardens such as Twitter and Facebook. The great thing about RSS is that it's an open standard, and can be used for web mashups. Lots of good stuff from the early web and internet has been forgotten, replaced by more commercial and less interesting properties such as social media feeds et al. 31 |

32 |
33 | 34 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /lkbuffer.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "lklib.h" 10 | 11 | LKBuffer *lk_buffer_new(size_t bytes_size) { 12 | if (bytes_size == 0) { 13 | bytes_size = LK_BUFSIZE_SMALL; 14 | } 15 | 16 | LKBuffer *buf = lk_malloc(sizeof(LKBuffer), "lk_buffer_new"); 17 | buf->bytes_cur = 0; 18 | buf->bytes_len = 0; 19 | buf->bytes_size = bytes_size; 20 | buf->bytes = lk_malloc(buf->bytes_size, "lk_buffer_new_bytes"); 21 | return buf; 22 | } 23 | 24 | void lk_buffer_free(LKBuffer *buf) { 25 | assert(buf->bytes != NULL); 26 | lk_free(buf->bytes); 27 | buf->bytes = NULL; 28 | lk_free(buf); 29 | } 30 | 31 | void lk_buffer_resize(LKBuffer *buf, size_t bytes_size) { 32 | buf->bytes_size = bytes_size; 33 | if (buf->bytes_len > buf->bytes_size) { 34 | buf->bytes_len = buf->bytes_size; 35 | } 36 | if (buf->bytes_cur >= buf->bytes_len) { 37 | buf->bytes_cur = buf->bytes_len-1; 38 | } 39 | buf->bytes = lk_realloc(buf->bytes, buf->bytes_size, "lk_buffer_resize"); 40 | } 41 | 42 | void lk_buffer_clear(LKBuffer *buf) { 43 | memset(buf->bytes, 0, buf->bytes_len); 44 | buf->bytes_len = 0; 45 | buf->bytes_cur = 0; 46 | } 47 | 48 | int lk_buffer_append(LKBuffer *buf, char *bytes, size_t len) { 49 | // If not enough capacity to append bytes, expand the bytes buffer. 50 | if (len > buf->bytes_size - buf->bytes_len) { 51 | char *bs = lk_realloc(buf->bytes, buf->bytes_size + len, "lk_buffer_append"); 52 | if (bs == NULL) { 53 | return -1; 54 | } 55 | buf->bytes = bs; 56 | buf->bytes_size += len; 57 | } 58 | memcpy(buf->bytes + buf->bytes_len, bytes, len); 59 | buf->bytes_len += len; 60 | 61 | assert(buf->bytes != NULL); 62 | return 0; 63 | } 64 | 65 | int lk_buffer_append_sz(LKBuffer *buf, char *s) { 66 | return lk_buffer_append(buf, s, strlen(s)); 67 | } 68 | 69 | void lk_buffer_append_sprintf(LKBuffer *buf, const char *fmt, ...) { 70 | char sbuf[LK_BUFSIZE_MEDIUM]; 71 | 72 | // Try to use static buffer first, to avoid allocation. 73 | va_list args; 74 | va_start(args, fmt); 75 | int z = vsnprintf(sbuf, sizeof(sbuf), fmt, args); 76 | if (z < 0) return; 77 | va_end(args); 78 | 79 | // If snprintf() truncated output to sbuf due to space, 80 | // use asprintf() instead. 81 | if (z >= sizeof(sbuf)) { 82 | va_list args; 83 | char *ps; 84 | va_start(args, fmt); 85 | int z = vasprintf(&ps, fmt, args); 86 | if (z == -1) return; 87 | va_end(args); 88 | 89 | lk_buffer_append(buf, ps, z); 90 | free(ps); 91 | return; 92 | } 93 | 94 | lk_buffer_append(buf, sbuf, z); 95 | } 96 | 97 | // Read CR-terminated line from buffer starting from buf->bytes_cur position. 98 | // buf->bytes_cur is updated to point to the first char of next line. 99 | // If dst doesn't have enough chars for line, partial line is copied. 100 | // Returns number of bytes read. 101 | size_t lk_buffer_readline(LKBuffer *buf, char *dst, size_t dst_len) { 102 | assert(dst_len > 2); // Reserve space for \n and \0. 103 | 104 | size_t nread = 0; 105 | while (nread < dst_len-1) { // leave space for null terminator 106 | if (buf->bytes_cur >= buf->bytes_len) { 107 | break; 108 | } 109 | dst[nread] = buf->bytes[buf->bytes_cur]; 110 | nread++; 111 | buf->bytes_cur++; 112 | 113 | if (dst[nread-1] == '\n') { 114 | break; 115 | } 116 | } 117 | 118 | assert(nread <= dst_len-1); 119 | dst[nread] = '\0'; 120 | return nread; 121 | } 122 | 123 | -------------------------------------------------------------------------------- /spec.txt: -------------------------------------------------------------------------------- 1 | Todo: 2 | - support cgi request post with body content 3 | - specify chunked transfer encoding 4 | 5 | config file: 6 | ------------ 7 | serverhost=127.0.0.1 8 | port=5000 9 | 10 | # testsite.xyz 11 | hostname testsite.xyz 12 | homedir=www/testsite 13 | cgidir=cgi-bin 14 | alias latest=latest.html 15 | alias about=about.html 16 | alias cgitest=cgi-bin/index.pl 17 | 18 | # Redirect newsboard.littlekitten.xyz to localhost:8001 server 19 | hostname newsboard.littlekitten.xyz 20 | proxyhost=localhost:8001 21 | 22 | # littlekitten.xyz 23 | hostname littlekitten.xyz 24 | homedir=www/littlekitten 25 | 26 | cgi post docs: 27 | https://www.oreilly.com/library/view/cgi-programming-on/9781565921689/07_chapter-04.html 28 | 29 | 30 | python simplehttpserver returns the following: 31 | 127.0.0.1 - - [11/Mar/2023 14:05:46] "GET /index3.html HTTP/1.1" 200 - 32 | 33 | 127.0.0.1 - - [11/Mar/2023 14:05:46] code 501, message Unsupported method ('POST') 34 | 127.0.0.1 - - [11/Mar/2023 14:05:46] "POST /index3.html HTTP/1.1" 501 - 35 | 36 | 127.0.0.1 - - [11/Mar/2023 14:05:46] "HEAD /index3.html HTTP/1.1" 200 - 37 | 38 | 39 | curl --head output: 40 | // html file 41 | HTTP/1.0 200 OK 42 | Server: SimpleHTTP/0.6 Python/3.10.6 43 | Date: Sat, 11 Mar 2023 06:12:05 GMT 44 | Content-Type: text/html 45 | Content-Length: 1219 46 | Last-Modified: Wed, 04 Jan 2023 07:35:30 GMT 47 | 48 | // image file 49 | HTTP/1.0 200 OK 50 | Server: SimpleHTTP/0.6 Python/3.10.6 51 | Date: Sat, 11 Mar 2023 06:12:05 GMT 52 | Content-Type: image/jpeg 53 | Content-Length: 95714 54 | Last-Modified: Sat, 11 Mar 2003 06:16:50 GMT 55 | 56 | // file not found 57 | HTTP/1.0 404 File not found 58 | Server: SimpleHTTP/0.6 Python/3.10.6 59 | Date: Sat, 11 Mar 2023 06:12:05 GMT 60 | Connection: close 61 | Content-Type: text/html;charset=utf-8 62 | Content-Length: 469 63 | 64 | handle POST? 65 | 405 Method Not Allowed 66 | 501 Not Implemented 67 | with basic html containing error message: 68 | Error response 69 |

Error response

70 |

Error code: 501

71 |

Message: Unsupported method ('POST').

72 | 73 | curl post test: 74 | curl -X POST -H "Content-Type: application/json" -d '{"id": 123, "name": "rob"}' http://localhost:5000 75 | 76 | nginx: 77 | Server: nginx/1.18.0 78 | 79 | Content-Type possibilities: 80 | https://stackoverflow.com/questions/23714383/what-are-all-the-possible-values-for-http-content-type-header 81 | 82 | text/css csv html javascript(obsolete) plain xml 83 | image/gif jpeg png tiff svg+xml 84 | application/javascript json xml zip pdf 85 | audio/mpeg 86 | video/mpeg mp4 quicktime webm 87 | 88 | cgi: 89 | https://docstore.mik.ua/orelly/linux/cgi/ch03_03.htm 90 | The Status header is different than the other headers because it does not map directly to an HTTP header, although it is associated with the status line. This field is used only to exchange information between the CGI script and the web server. It specifies the status code the server should include in the status line of the request. This field is optional: if you do not print it, the web server will automatically add a status of 200 OK to your output if you print a Content-type header, and a status of 302 Found if you print a Location header. 91 | 92 | https://www.tutorialspoint.com/perl-cgi-environment-variables 93 | https://www.oreilly.com/openbook/cgi/ch02_02.html 94 | http://www.cgi101.com/book/ch3/text.html 95 | 96 | SERVER_NAME = robdelacruz.xyz // hostname()) 97 | SERVER_SOFTWARE = nginx/1.18.0 98 | DOCUMENT_ROOT = /var/www/robdelacruz.xyz // realpath() 99 | SERVER_PROTOCOL = HTTP/1.1 100 | SERVER_PORT = 443 // request header Host: robdelacruz.xyz:5000 101 | 102 | HTTP_USER_AGENT = Chrome/108.0.0.0 // request header User-Agent 103 | HTTP_HOST = robdelacruz.xyz // request header Host: robdelacruz.xyz:5000 104 | REQUEST_METHOD = GET 105 | SCRIPT_FILENAME = /var/www/robdelacruz.xyz/cgi-bin/rob.pl // DOCUMENT_ROOT + uri 106 | SCRIPT_NAME = /cgi-bin/rob.pl // uri 107 | REQUEST_URI = /cgi-bin/rob.pl?abc=123 // uri 108 | QUERY_STRING = abc=123&def=45 // uri 109 | CONTENT_TYPE = ??? (mime type of query data such as "text/html") 110 | CONTENT_LENGTH = ??? (length of data in bytes passed to cgi program through std input) 111 | 112 | REMOTE_ADDR = 180.190.63.221 (visitor's ip addr) 113 | REMOTE_PORT = 15046 114 | HTTP_COOKIE 115 | HTTP_REFERER 116 | HTTPS ("on" if https) 117 | PATH 118 | 119 | -------------------------------------------------------------------------------- /tclient.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "lklib.h" 14 | #include "lknet.h" 15 | 16 | ssize_t sendbytes(int sock, char *buf, size_t count); 17 | ssize_t recvbytes(int sock, char *buf, size_t count); 18 | 19 | int main(int argc, char *argv[]) { 20 | int z; 21 | int sock; 22 | 23 | if (argc < 3) { 24 | printf("Usage: tclient \n"); 25 | printf("Ex. tclient 127.0.0.1 5000\n"); 26 | exit(1); 27 | } 28 | 29 | char *server_domain = argv[1]; 30 | char *server_port = argv[2]; 31 | struct addrinfo hints, *servaddr; 32 | memset(&hints, 0, sizeof(hints)); 33 | hints.ai_family = AF_UNSPEC; 34 | hints.ai_socktype = SOCK_STREAM; 35 | hints.ai_flags = 0; 36 | z = getaddrinfo(server_domain, server_port, &hints, &servaddr); 37 | if (z != 0) { 38 | lk_exit_err("getaddrinfo()"); 39 | } 40 | 41 | // Server socket 42 | sock = socket(servaddr->ai_family, servaddr->ai_socktype, servaddr->ai_protocol); 43 | if (sock == -1) { 44 | lk_exit_err("socket()"); 45 | } 46 | 47 | z = connect(sock, servaddr->ai_addr, servaddr->ai_addrlen); 48 | if (z != 0) { 49 | lk_exit_err("connect()"); 50 | } 51 | 52 | freeaddrinfo(servaddr); 53 | servaddr = NULL; 54 | 55 | printf("Connected to %s:%s\n", server_domain, server_port); 56 | 57 | char *reqmsg = 58 | "GET /style.css HTTP/1.0\r\n" 59 | "From: rob@robdelacruz.xyz\r\n" 60 | "User-Agent: tclient/1.0\r\n" 61 | "\r\n" 62 | "message body: chunk 1 " 63 | "message body: chunk 2 " 64 | "message body: chunk 3\n"; 65 | 66 | char respmsg[5000]; 67 | 68 | printf("Sending request...\n"); 69 | z = sendbytes(sock, reqmsg, strlen(reqmsg)); 70 | if (z == -1) { 71 | lk_exit_err("sendbytes()"); 72 | } 73 | 74 | printf("Receiving response...\n"); 75 | z = recvbytes(sock, respmsg, sizeof(respmsg)); 76 | if (z == -1) { 77 | lk_exit_err("recvbytes()"); 78 | } 79 | respmsg[z] = '\0'; 80 | printf("%s", respmsg); 81 | 82 | z = close(sock); 83 | if (z == -1) { 84 | lk_exit_err("close()"); 85 | } 86 | 87 | return 0; 88 | } 89 | 90 | // Send count buf bytes into sock. 91 | // Returns num bytes sent or -1 for error 92 | ssize_t sendbytes(int sock, char *buf, size_t count) { 93 | int nsent = 0; 94 | while (nsent < count) { 95 | int z = send(sock, buf+nsent, count-nsent, 0); 96 | printf("sendbytes() z: %d\n", z); 97 | // socket closed, no more data 98 | if (z == 0) { 99 | // socket closed 100 | break; 101 | } 102 | // interrupt occured during send, retry send. 103 | if (z == -1 && errno == EINTR) { 104 | continue; 105 | } 106 | // no data available at the moment, just return what we have. 107 | if (z == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { 108 | break; 109 | } 110 | // any other error 111 | if (z == -1) { 112 | return z; 113 | } 114 | nsent += z; 115 | } 116 | 117 | return nsent; 118 | } 119 | 120 | // Receive count bytes into buf. 121 | // Returns num bytes received or -1 for error. 122 | ssize_t recvbytes(int sock, char *buf, size_t count) { 123 | memset(buf, '*', count); // initialize for debugging purposes. 124 | 125 | int nread = 0; 126 | while (nread < count) { 127 | int z = recv(sock, buf+nread, count-nread, 0); 128 | // socket closed, no more data 129 | if (z == 0) { 130 | break; 131 | } 132 | // interrupt occured during read, retry read. 133 | if (z == -1 && errno == EINTR) { 134 | continue; 135 | } 136 | // no data available at the moment, just return what we have. 137 | if (z == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { 138 | break; 139 | } 140 | // any other error 141 | if (z == -1) { 142 | return z; 143 | } 144 | nread += z; 145 | } 146 | 147 | return nread; 148 | } 149 | 150 | -------------------------------------------------------------------------------- /lklib.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "lklib.h" 8 | 9 | // forward declarations 10 | void close_pipes(int pair1[2], int pair2[2], int pair3[2]); 11 | 12 | // Print the last error message corresponding to errno. 13 | void lk_print_err(char *s) { 14 | fprintf(stderr, "%s: %s\n", s, strerror(errno)); 15 | } 16 | 17 | void lk_exit_err(char *s) { 18 | lk_print_err(s); 19 | exit(1); 20 | } 21 | 22 | // Return whether line is empty, ignoring whitespace chars ' ', \r, \n 23 | int is_empty_line(char *s) { 24 | int slen = strlen(s); 25 | for (int i=0; i < slen; i++) { 26 | // Not an empty line if non-whitespace char is present. 27 | if (s[i] != ' ' && s[i] != '\n' && s[i] != '\r') { 28 | return 0; 29 | } 30 | } 31 | return 1; 32 | } 33 | 34 | // Return whether string ends with \n char. 35 | int ends_with_newline(char *s) { 36 | int slen = strlen(s); 37 | if (slen == 0) { 38 | return 0; 39 | } 40 | if (s[slen-1] == '\n') { 41 | return 1; 42 | } 43 | return 0; 44 | } 45 | 46 | 47 | // Like popen() but returning input, output, error fds for cmd. 48 | int lk_popen3(char *cmd, int *fd_in, int *fd_out, int *fd_err) { 49 | int z; 50 | int in[2] = {0, 0}; 51 | int out[2] = {0, 0}; 52 | int err[2] = {0, 0}; 53 | 54 | z = pipe(in); 55 | if (z == -1) { 56 | return z; 57 | } 58 | z = pipe(out); 59 | if (z == -1) { 60 | close_pipes(in, out, err); 61 | return z; 62 | } 63 | z = pipe(err); 64 | if (z == -1) { 65 | close_pipes(in, out, err); 66 | return z; 67 | } 68 | 69 | int pid = fork(); 70 | if (pid == 0) { 71 | // child proc 72 | z = dup2(in[0], STDIN_FILENO); 73 | if (z == -1) { 74 | close_pipes(in, out, err); 75 | return z; 76 | } 77 | z = dup2(out[1], STDOUT_FILENO); 78 | if (z == -1) { 79 | close_pipes(in, out, err); 80 | return z; 81 | } 82 | // If fd_err parameter provided, use separate fd for stderr. 83 | // If fd_err is NULL, combine stdout and stderr into fd_out. 84 | if (fd_err != NULL) { 85 | z = dup2(err[1], STDERR_FILENO); 86 | if (z == -1) { 87 | close_pipes(in, out, err); 88 | return z; 89 | } 90 | } else { 91 | z = dup2(out[1], STDERR_FILENO); 92 | if (z == -1) { 93 | close_pipes(in, out, err); 94 | return z; 95 | } 96 | } 97 | 98 | close_pipes(in, out, err); 99 | z = execl("/bin/sh", "sh", "-c", cmd, NULL); 100 | return z; 101 | } 102 | 103 | // parent proc 104 | if (fd_in != NULL) { 105 | *fd_in = in[1]; // return the other end of the dup2() pipe 106 | } 107 | close(in[0]); 108 | 109 | if (fd_out != NULL) { 110 | *fd_out = out[0]; 111 | } 112 | close(out[1]); 113 | 114 | if (fd_err != NULL) { 115 | *fd_err = err[0]; 116 | } 117 | close(err[1]); 118 | 119 | return 0; 120 | } 121 | 122 | void close_pipes(int pair1[2], int pair2[2], int pair3[2]) { 123 | int z; 124 | int tmp_errno = errno; 125 | if (pair1[0] != 0) { 126 | z = close(pair1[0]); 127 | if (z == 0) { 128 | pair1[0] = 0; 129 | } 130 | } 131 | if (pair1[1] != 0) { 132 | z = close(pair1[1]); 133 | if (z == 0) { 134 | pair1[1] = 0; 135 | } 136 | } 137 | if (pair2[0] != 0) { 138 | z = close(pair2[0]); 139 | if (z == 0) { 140 | pair2[0] = 0; 141 | } 142 | } 143 | if (pair2[1] != 0) { 144 | z = close(pair2[1]); 145 | if (z == 0) { 146 | pair2[1] = 0; 147 | } 148 | } 149 | if (pair3[0] != 0) { 150 | z = close(pair3[0]); 151 | if (z == 0) { 152 | pair3[0] = 0; 153 | } 154 | } 155 | if (pair3[1] != 0) { 156 | z = close(pair3[1]); 157 | if (z == 0) { 158 | pair3[1] = 0; 159 | } 160 | } 161 | errno = tmp_errno; 162 | return; 163 | } 164 | 165 | void get_localtime_string(char *time_str, size_t time_str_len) { 166 | time_t t = time(NULL); 167 | struct tm tmtime; 168 | void *pz = localtime_r(&t, &tmtime); 169 | if (pz != NULL) { 170 | int z = strftime(time_str, time_str_len, "%d/%b/%Y %H:%M:%S", &tmtime); 171 | if (z == 0) { 172 | sprintf(time_str, "???"); 173 | } 174 | } else { 175 | sprintf(time_str, "???"); 176 | } 177 | } 178 | 179 | // Return matching item in lookup table given testk. 180 | // tbl is a null-terminated array of char* key-value pairs 181 | // Ex. tbl = {"key1", "val1", "key2", "val2", "key3", "val3", NULL}; 182 | // where key1/val1, key2/val2, key3/val3 are the key-value pairs. 183 | void **lk_lookup(void **tbl, char *testk) { 184 | void **p = tbl; 185 | while (*p != NULL) { 186 | char *k = *p; 187 | if (k == NULL) break; 188 | if (!strcmp(k, testk)) { 189 | return *(p+1); // val 190 | } 191 | p += 2; // next key 192 | } 193 | return NULL; 194 | } 195 | 196 | -------------------------------------------------------------------------------- /lklib.h: -------------------------------------------------------------------------------- 1 | #ifndef LKLIB_H 2 | #define LKLIB_H 3 | 4 | // Predefined buffer sizes. 5 | // Sample use: char buf[LK_BUFSIZE_SMALL] 6 | #define LK_BUFSIZE_SMALL 512 7 | #define LK_BUFSIZE_MEDIUM 1024 8 | #define LK_BUFSIZE_LARGE 2048 9 | #define LK_BUFSIZE_XL 4096 10 | #define LK_BUFSIZE_XXL 8192 11 | 12 | #ifdef DEBUGALLOC 13 | #define malloc(size) (lk_malloc((size), "malloc")) 14 | #define realloc(p, size) (lk_realloc((p), (size), "realloc")) 15 | #define free(p) (lk_free((p))) 16 | #define strdup(s) (lk_strdup((s), "strdup")) 17 | #define strndup(s, n) (lk_strndup((s), (n), "strndup")) 18 | #endif 19 | 20 | void lk_print_err(char *s); 21 | void lk_exit_err(char *s); 22 | char *lk_vasprintf(char *fmt, va_list args); 23 | int is_empty_line(char *s); 24 | int ends_with_newline(char *s); 25 | int lk_popen3(char *cmd, int *fd_in, int *fd_out, int *fd_err); 26 | 27 | // Return localtime in server format: 11/Mar/2023 14:05:46 28 | // Usage: 29 | // char timestr[TIME_STRING_SIZE]; 30 | // get_localtime_string(timestr, sizeof(timestr)) 31 | #define TIME_STRING_SIZE 25 32 | void get_localtime_string(char *time_str, size_t time_str_len); 33 | 34 | void lk_alloc_init(); 35 | void *lk_malloc(size_t size, char *label); 36 | void *lk_realloc(void *p, size_t size, char *label); 37 | void lk_free(void *p); 38 | char *lk_strdup(const char *s, char *label); 39 | char *lk_strndup(const char *s, size_t n, char *label); 40 | void lk_print_allocitems(); 41 | // vasprintf(&ps, fmt, args); //$$ lk_vasprintf()? 42 | 43 | // Return matching item in lookup table given testk. 44 | // tbl is a null-terminated array of char* key-value pairs 45 | // Ex. tbl = {"key1", "val1", "key2", "val2", "key3", "val3", NULL}; 46 | // where key1/val1, key2/val2, key3/val3 are the key-value pairs. 47 | void **lk_lookup(void **tbl, char *testk); 48 | 49 | /*** LKString ***/ 50 | typedef struct { 51 | char *s; 52 | size_t s_len; 53 | size_t s_size; 54 | } LKString; 55 | 56 | typedef struct lkstringlist LKStringList; 57 | 58 | LKString *lk_string_new(char *s); 59 | LKString *lk_string_size_new(size_t size); 60 | void lk_string_free(LKString *lks); 61 | void lk_string_voidp_free(void *plkstr); 62 | 63 | void lk_string_assign(LKString *lks, char *s); 64 | void lk_string_assign_sprintf(LKString *lks, char *fmt, ...); 65 | void lk_string_append(LKString *lks, char *s); 66 | void lk_string_append_sprintf(LKString *lks, char *fmt, ...); 67 | void lk_string_append_char(LKString *lks, char c); 68 | 69 | void lk_string_prepend(LKString *lks, char *s); 70 | 71 | int lk_string_sz_equal(LKString *lks, char *s); 72 | int lk_string_equal(LKString *lks1, LKString *lks2); 73 | int lk_string_starts_with(LKString *lks, char *s); 74 | int lk_string_ends_with(LKString *lks, char *s); 75 | 76 | void lk_string_trim(LKString *lks); 77 | void lk_string_chop_start(LKString *lks, char *s); 78 | void lk_string_chop_end(LKString *lks, char *s); 79 | LKStringList *lk_string_split(LKString *lks, char *delim); 80 | void lk_string_split_assign(LKString *s, char *delim, LKString *k, LKString *v); 81 | void sz_string_split_assign(char *s, char *delim, LKString *k, LKString *v); 82 | 83 | 84 | /*** LKStringTable ***/ 85 | typedef struct { 86 | LKString *k; 87 | LKString *v; 88 | } LKStringTableItem; 89 | 90 | typedef struct { 91 | LKStringTableItem *items; 92 | size_t items_len; 93 | size_t items_size; 94 | } LKStringTable; 95 | 96 | LKStringTable *lk_stringtable_new(); 97 | void lk_stringtable_free(LKStringTable *sm); 98 | void lk_stringtable_set(LKStringTable *sm, char *ks, char *v); 99 | char *lk_stringtable_get(LKStringTable *sm, char *ks); 100 | void lk_stringtable_remove(LKStringTable *sm, char *ks); 101 | 102 | 103 | /*** LKStringList ***/ 104 | typedef struct lkstringlist { 105 | LKString **items; 106 | size_t items_len; 107 | size_t items_size; 108 | } LKStringList; 109 | 110 | LKStringList *lk_stringlist_new(); 111 | void lk_stringlist_free(LKStringList *sl); 112 | void lk_stringlist_append_lkstring(LKStringList *sl, LKString *lks); 113 | void lk_stringlist_append(LKStringList *sl, char *s); 114 | void lk_stringlist_append_sprintf(LKStringList *sl, const char *fmt, ...); 115 | LKString *lk_stringlist_get(LKStringList *sl, unsigned int i); 116 | void lk_stringlist_remove(LKStringList *sl, unsigned int i); 117 | 118 | 119 | /*** LKBuffer - Dynamic bytes buffer ***/ 120 | typedef struct { 121 | char *bytes; // bytes buffer 122 | size_t bytes_cur; // index to current buffer position 123 | size_t bytes_len; // length of buffer 124 | size_t bytes_size; // capacity of buffer in bytes 125 | } LKBuffer; 126 | 127 | LKBuffer *lk_buffer_new(size_t bytes_size); 128 | void lk_buffer_free(LKBuffer *buf); 129 | void lk_buffer_resize(LKBuffer *buf, size_t bytes_size); 130 | void lk_buffer_clear(LKBuffer *buf); 131 | int lk_buffer_append(LKBuffer *buf, char *bytes, size_t len); 132 | int lk_buffer_append_sz(LKBuffer *buf, char *s); 133 | void lk_buffer_append_sprintf(LKBuffer *buf, const char *fmt, ...); 134 | size_t lk_buffer_readline(LKBuffer *buf, char *dst, size_t dst_len); 135 | 136 | 137 | /*** LKRefList ***/ 138 | typedef struct { 139 | void **items; 140 | size_t items_len; 141 | size_t items_size; 142 | size_t items_cur; 143 | } LKRefList; 144 | 145 | LKRefList *lk_reflist_new(); 146 | void lk_reflist_free(LKRefList *l); 147 | void lk_reflist_append(LKRefList *l, void *p); 148 | void *lk_reflist_get(LKRefList *l, unsigned int i); 149 | void *lk_reflist_get_cur(LKRefList *l); 150 | void lk_reflist_remove(LKRefList *l, unsigned int i); 151 | void lk_reflist_clear(LKRefList *l); 152 | 153 | #endif 154 | 155 | -------------------------------------------------------------------------------- /lkcontext.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "lklib.h" 17 | #include "lknet.h" 18 | 19 | /*** LKContext functions ***/ 20 | LKContext *lk_context_new() { 21 | LKContext *ctx = lk_malloc(sizeof(LKContext), "lk_context_new"); 22 | ctx->selectfd = 0; 23 | ctx->clientfd = 0; 24 | ctx->type = 0; 25 | ctx->next = NULL; 26 | 27 | ctx->client_ipaddr = NULL; 28 | ctx->client_port = 0; 29 | 30 | ctx->req_line = NULL; 31 | ctx->req_buf = NULL; 32 | ctx->sr = NULL; 33 | ctx->reqparser = NULL; 34 | ctx->req = NULL; 35 | ctx->resp = NULL; 36 | ctx->buflist = NULL; 37 | 38 | ctx->cgifd = 0; 39 | ctx->cgi_outputbuf = NULL; 40 | ctx->cgi_inputbuf = NULL; 41 | 42 | ctx->proxyfd = 0; 43 | ctx->proxy_respbuf = NULL; 44 | 45 | return ctx; 46 | } 47 | 48 | LKContext *create_initial_context(int fd, struct sockaddr_in *sa) { 49 | LKContext *ctx = lk_malloc(sizeof(LKContext), "create_initial_context"); 50 | ctx->selectfd = fd; 51 | ctx->clientfd = fd; 52 | ctx->type = CTX_READ_REQ; 53 | ctx->next = NULL; 54 | 55 | ctx->client_sa = *sa; 56 | ctx->client_ipaddr = lk_get_ipaddr_string((struct sockaddr *) sa); 57 | ctx->client_port = lk_get_sockaddr_port((struct sockaddr *) sa); 58 | 59 | ctx->req_line = lk_string_new(""); 60 | ctx->req_buf = lk_buffer_new(0); 61 | ctx->sr = lk_socketreader_new(fd, 0); 62 | ctx->reqparser = lk_httprequestparser_new(); 63 | ctx->req = lk_httprequest_new(); 64 | ctx->resp = lk_httpresponse_new(); 65 | ctx->buflist = lk_reflist_new(); 66 | 67 | ctx->cgifd = 0; 68 | ctx->cgi_outputbuf = NULL; 69 | ctx->cgi_inputbuf = NULL; 70 | 71 | ctx->proxyfd = 0; 72 | ctx->proxy_respbuf = NULL; 73 | 74 | return ctx; 75 | } 76 | 77 | void lk_context_free(LKContext *ctx) { 78 | if (ctx->client_ipaddr) { 79 | lk_string_free(ctx->client_ipaddr); 80 | } 81 | if (ctx->req_line) { 82 | lk_string_free(ctx->req_line); 83 | } 84 | if (ctx->req_buf) { 85 | lk_buffer_free(ctx->req_buf); 86 | } 87 | if (ctx->sr) { 88 | lk_socketreader_free(ctx->sr); 89 | } 90 | if (ctx->reqparser) { 91 | lk_httprequestparser_free(ctx->reqparser); 92 | } 93 | if (ctx->req) { 94 | lk_httprequest_free(ctx->req); 95 | } 96 | if (ctx->resp) { 97 | lk_httpresponse_free(ctx->resp); 98 | } 99 | if (ctx->buflist) { 100 | lk_reflist_free(ctx->buflist); 101 | } 102 | if (ctx->cgi_outputbuf) { 103 | lk_buffer_free(ctx->cgi_outputbuf); 104 | } 105 | if (ctx->cgi_inputbuf) { 106 | lk_buffer_free(ctx->cgi_inputbuf); 107 | } 108 | if (ctx->proxy_respbuf) { 109 | lk_buffer_free(ctx->proxy_respbuf); 110 | } 111 | 112 | ctx->selectfd = 0; 113 | ctx->clientfd = 0; 114 | ctx->next = NULL; 115 | memset(&ctx->client_sa, 0, sizeof(struct sockaddr_in)); 116 | ctx->client_ipaddr = NULL; 117 | ctx->req_line = NULL; 118 | ctx->req_buf = NULL; 119 | ctx->sr = NULL; 120 | ctx->reqparser = NULL; 121 | ctx->req = NULL; 122 | ctx->resp = NULL; 123 | ctx->buflist = NULL; 124 | ctx->cgifd = 0; 125 | ctx->cgi_outputbuf = NULL; 126 | ctx->cgi_inputbuf = NULL; 127 | ctx->proxyfd = 0; 128 | ctx->proxy_respbuf = NULL; 129 | lk_free(ctx); 130 | } 131 | 132 | // Add new client ctx to end of ctx linked list. 133 | // Skip if ctx clientfd already in list. 134 | void add_new_client_context(LKContext **pphead, LKContext *ctx) { 135 | assert(pphead != NULL); 136 | 137 | if (*pphead == NULL) { 138 | // first client 139 | *pphead = ctx; 140 | } else { 141 | // add client to end of clients list 142 | LKContext *p = *pphead; 143 | while (p->next != NULL) { 144 | // ctx fd already exists 145 | if (p->clientfd == ctx->clientfd) { 146 | return; 147 | } 148 | p = p->next; 149 | } 150 | p->next = ctx; 151 | } 152 | } 153 | 154 | // Add ctx to end of ctx linked list, allowing duplicate clientfds. 155 | void add_context(LKContext **pphead, LKContext *ctx) { 156 | assert(pphead != NULL); 157 | 158 | if (*pphead == NULL) { 159 | // first client 160 | *pphead = ctx; 161 | } else { 162 | // add to end of clients list 163 | LKContext *p = *pphead; 164 | while (p->next != NULL) { 165 | p = p->next; 166 | } 167 | p->next = ctx; 168 | } 169 | } 170 | 171 | 172 | // Delete first ctx having clientfd from linked list. 173 | // Returns 1 if context was deleted, 0 if no deletion made. 174 | int remove_client_context(LKContext **pphead, int clientfd) { 175 | assert(pphead != NULL); 176 | 177 | if (*pphead == NULL) { 178 | return 0; 179 | } 180 | // remove head ctx 181 | if ((*pphead)->clientfd == clientfd) { 182 | LKContext *tmp = *pphead; 183 | *pphead = (*pphead)->next; 184 | lk_context_free(tmp); 185 | return 1; 186 | } 187 | 188 | LKContext *p = *pphead; 189 | LKContext *prev = NULL; 190 | while (p != NULL) { 191 | if (p->clientfd == clientfd) { 192 | assert(prev != NULL); 193 | prev->next = p->next; 194 | lk_context_free(p); 195 | return 1; 196 | } 197 | 198 | prev = p; 199 | p = p->next; 200 | } 201 | 202 | return 0; 203 | } 204 | 205 | // Delete all ctx's having clientfd. 206 | //$$ todo: unused, remove this? 207 | void remove_client_contexts(LKContext **pphead, int clientfd) { 208 | int z = 1; 209 | // Keep trying to remove matching clientfd's until none left. 210 | while (z != 0) { 211 | z = remove_client_context(pphead, clientfd); 212 | } 213 | } 214 | 215 | // Return ctx matching selectfd. 216 | LKContext *match_select_ctx(LKContext *phead, int selectfd) { 217 | LKContext *ctx = phead; 218 | while (ctx != NULL) { 219 | if (ctx->selectfd == selectfd) { 220 | break; 221 | } 222 | ctx = ctx->next; 223 | } 224 | return ctx; 225 | } 226 | 227 | // Delete first ctx having selectfd from linked list. 228 | // Returns 1 if context was deleted, 0 if no deletion made. 229 | int remove_selectfd_context(LKContext **pphead, int selectfd) { 230 | assert(pphead != NULL); 231 | 232 | if (*pphead == NULL) { 233 | return 0; 234 | } 235 | // remove head ctx 236 | if ((*pphead)->selectfd == selectfd) { 237 | LKContext *tmp = *pphead; 238 | *pphead = (*pphead)->next; 239 | lk_context_free(tmp); 240 | return 1; 241 | } 242 | 243 | LKContext *p = *pphead; 244 | LKContext *prev = NULL; 245 | while (p != NULL) { 246 | if (p->selectfd == selectfd) { 247 | assert(prev != NULL); 248 | prev->next = p->next; 249 | lk_context_free(p); 250 | return 1; 251 | } 252 | 253 | prev = p; 254 | p = p->next; 255 | } 256 | 257 | return 0; 258 | } 259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /lkws.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "lklib.h" 19 | #include "lknet.h" 20 | 21 | void handle_sigint(int sig); 22 | void handle_sigchld(int sig); 23 | int parse_args(int argc, char *argv[], LKConfig *cfg); 24 | void print_help(); 25 | void print_sample_config(); 26 | 27 | LKHttpServer *httpserver; 28 | 29 | // lkws [homedir] [port] [host] [-f configfile] [--cgidir=cgifolder] 30 | // 31 | // configfile = configuration file containing site settings 32 | // see sample configuration file below 33 | // homedir = absolute or relative path to a home directory 34 | // defaults to current working directory if not specified 35 | // port = port number to bind to server 36 | // defaults to 8000 37 | // host = IP address to bind to server 38 | // defaults to localhost 39 | // Examples: 40 | // lkws ./testsite/ 8080 41 | // lkws /var/www/testsite/ 8080 127.0.0.1 42 | // lkws /var/www/testsite/ --cgidir=cgi-bin 43 | // lkws -f sites.conf 44 | int main(int argc, char *argv[]) { 45 | int z; 46 | signal(SIGPIPE, SIG_IGN); // Don't abort on SIGPIPE 47 | signal(SIGINT, handle_sigint); // exit on CTRL-C 48 | signal(SIGCHLD, handle_sigchld); 49 | 50 | 51 | lk_alloc_init(); 52 | LKConfig *cfg = lk_config_new(); 53 | z = parse_args(argc, argv, cfg); 54 | if (z == 1) { 55 | lk_config_free(cfg); 56 | print_help(); 57 | exit(0); 58 | } 59 | 60 | printf("Little Kitten Web Server version 0.9 (%s -h for instructions)\n", argv[0]); 61 | httpserver = lk_httpserver_new(cfg); 62 | 63 | z = lk_httpserver_serve(httpserver); 64 | if (z == -1) { 65 | return z; 66 | } 67 | 68 | // Code doesn't reach here. 69 | lk_httpserver_free(httpserver); 70 | return 0; 71 | } 72 | 73 | void handle_sigint(int sig) { 74 | printf("SIGINT received\n"); 75 | fflush(stdout); 76 | lk_httpserver_free(httpserver); 77 | 78 | lk_print_allocitems(); 79 | exit(0); 80 | } 81 | 82 | void handle_sigchld(int sig) { 83 | int tmp_errno = errno; 84 | while (waitpid(-1, NULL, WNOHANG) > 0) { 85 | } 86 | errno = tmp_errno; 87 | } 88 | 89 | void print_help() { 90 | printf( 91 | "Usage:\n" 92 | "lkws [homedir] [port] [host] [-f configfile] [--cgidir=cgifolder]\n" 93 | "\n" 94 | "configfile = configuration file containing site settings\n" 95 | " see sample configuration file below\n" 96 | "homedir = absolute or relative path to a home directory\n" 97 | " defaults to current working directory if not specified\n" 98 | "port = port number to bind to server\n" 99 | " defaults to 8000\n" 100 | "host = IP address to bind to server\n" 101 | " defaults to localhost\n" 102 | "Examples:\n" 103 | "lkws ./testsite/ 8080\n" 104 | "lkws /var/www/testsite/ 8080 127.0.0.1\n" 105 | "lkws /var/www/testsite/ --cgidir=cgi-bin\n" 106 | "lkws -f sites.conf\n" 107 | "\n" 108 | "Source code and docs at https://github.com/robdelacruz/lkwebserver\n" 109 | "\n" 110 | ); 111 | } 112 | 113 | void print_sample_config() { 114 | printf( 115 | "Sample config file:\n" 116 | "\n" 117 | "serverhost=127.0.0.1\n" 118 | "port=5000\n" 119 | "\n" 120 | "# Matches all other hostnames\n" 121 | "hostname *\n" 122 | "homedir=/var/www/testsite\n" 123 | "alias latest=latest.html\n" 124 | "\n" 125 | "# Matches http://localhost\n" 126 | "hostname localhost\n" 127 | "homedir=/var/www/testsite\n" 128 | "\n" 129 | "# http://littlekitten.xyz\n" 130 | "hostname littlekitten.xyz\n" 131 | "homedir=/var/www/testsite\n" 132 | "cgidir=cgi-bin\n" 133 | "alias latest=latest.html\n" 134 | "alias about=about.html\n" 135 | "alias guestbook=cgi-bin/guestbook.pl\n" 136 | "alias blog=cgi-bin/blog.pl\n" 137 | "\n" 138 | "# http://newsboard.littlekitten.xyz\n" 139 | "hostname newsboard.littlekitten.xyz\n" 140 | "proxyhost=localhost:8001\n" 141 | "\n" 142 | "# Format description:\n" 143 | "# The host and port number is defined first, followed by one or more\n" 144 | "# host config sections. The host config section always starts with the\n" 145 | "# 'hostname ' line followed by the settings for that hostname.\n" 146 | "# The section ends on either EOF or when a new 'hostname ' line\n" 147 | "# is read, indicating the start of the next host config section.\n" 148 | "\n" 149 | ); 150 | } 151 | 152 | typedef enum {PA_NONE, PA_FILE} ParseArgsState; 153 | 154 | // lkws [homedir] [port] [host] [--cgidir=] [-f ] 155 | // homedir = absolute or relative path to a home directory 156 | // defaults to current working directory if not specified 157 | // port = port number to bind to server 158 | // defaults to 8000 159 | // host = IP address to bind to server 160 | // defaults to localhost 161 | // cgidir = root directory for cgi files, relative path to homedir 162 | // defaults to cgi-bin 163 | // configfile = plaintext file containing configuration options 164 | // 165 | // Examples: 166 | // lkws ./testsite/ 8080 167 | // lkws /var/www/testsite/ 8080 127.0.0.1 168 | // lkws /var/www/testsite/ --cgidir=guestbook 169 | // lkws -f littlekitten.conf 170 | int parse_args(int argc, char *argv[], LKConfig *cfg) { 171 | ParseArgsState state = PA_NONE; 172 | int is_homedir_set = 0; 173 | int is_port_set = 0; 174 | int is_host_set = 0; 175 | 176 | for (int i=1; i < argc; i++) { 177 | char *arg = argv[i]; 178 | if (state == PA_NONE && !strcmp(arg, "-f")) { 179 | state = PA_FILE; 180 | continue; 181 | } 182 | if (state == PA_NONE && 183 | (!strcmp(arg, "-h") || !strcmp(arg, "--help"))) { 184 | return 1; 185 | } 186 | // --cgidir=cgifolder 187 | if (state == PA_NONE && !strncmp(arg, "--cgidir=", 9)) { 188 | LKHostConfig *default_hc = lk_config_create_get_hostconfig(cfg, "*"); 189 | LKString *v = lk_string_new(""); 190 | sz_string_split_assign(arg, "=", NULL, v); 191 | lk_string_assign(default_hc->cgidir, v->s); 192 | lk_string_free(v); 193 | continue; 194 | } 195 | if (state == PA_FILE) { 196 | lk_config_read_configfile(cfg, arg); 197 | state = PA_NONE; 198 | continue; 199 | } 200 | assert(state == PA_NONE); 201 | if (!is_homedir_set) { 202 | LKHostConfig *hc0 = lk_config_create_get_hostconfig(cfg, "*"); 203 | lk_string_assign(hc0->homedir, arg); 204 | is_homedir_set = 1; 205 | 206 | // cgidir default to cgi-bin 207 | if (hc0->cgidir->s_len == 0) { 208 | lk_string_assign(hc0->cgidir, "/cgi-bin/"); 209 | } 210 | } else if (!is_port_set) { 211 | lk_string_assign(cfg->port, arg); 212 | is_port_set = 1; 213 | } else if (!is_host_set) { 214 | lk_string_assign(cfg->serverhost, arg); 215 | is_host_set = 1; 216 | } 217 | } 218 | return 0; 219 | } 220 | 221 | -------------------------------------------------------------------------------- /lkhttprequestparser.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "lklib.h" 10 | #include "lknet.h" 11 | 12 | void parse_line(LKHttpRequestParser *parser, char *line, LKHttpRequest *req); 13 | void parse_request_line(char *line, LKHttpRequest *req); 14 | static void parse_header_line(LKHttpRequestParser *parser, char *line, LKHttpRequest *req); 15 | void parse_uri(LKString *lks_uri, LKString *lks_path, LKString *lks_filename, LKString *lks_qs); 16 | 17 | /*** LKHttpRequestParser functions ***/ 18 | LKHttpRequestParser *lk_httprequestparser_new() { 19 | LKHttpRequestParser *parser = lk_malloc(sizeof(LKHttpRequestParser), "lk_httprequest_parser_new"); 20 | parser->partial_line = lk_string_new(""); 21 | parser->nlinesread = 0; 22 | parser->content_length = 0; 23 | parser->head_complete = 0; 24 | parser->body_complete = 0; 25 | return parser; 26 | } 27 | 28 | void lk_httprequestparser_free(LKHttpRequestParser *parser) { 29 | lk_string_free(parser->partial_line); 30 | parser->partial_line = NULL; 31 | lk_free(parser); 32 | } 33 | 34 | // Clear any pending state. 35 | void lk_httprequestparser_reset(LKHttpRequestParser *parser) { 36 | lk_string_assign(parser->partial_line, ""); 37 | parser->nlinesread = 0; 38 | parser->content_length = 0; 39 | parser->head_complete = 0; 40 | parser->body_complete = 0; 41 | } 42 | 43 | // Parse one line and cumulatively compile results into req. 44 | // You can check the state of the parser through the following fields: 45 | // parser->head_complete Request Line and Headers complete 46 | // parser->body_complete httprequest is complete 47 | void lk_httprequestparser_parse_line(LKHttpRequestParser *parser, LKString *line, LKHttpRequest *req) { 48 | // If there's a previous partial line, combine it with current line. 49 | if (parser->partial_line->s_len > 0) { 50 | lk_string_append(parser->partial_line, line->s); 51 | if (lk_string_ends_with(parser->partial_line, "\n")) { 52 | parse_line(parser, parser->partial_line->s, req); 53 | lk_string_assign(parser->partial_line, ""); 54 | } 55 | return; 56 | } 57 | 58 | // If current line is only partial line (not newline terminated), remember it for 59 | // next read. 60 | if (!lk_string_ends_with(line, "\n")) { 61 | lk_string_assign(parser->partial_line, line->s); 62 | return; 63 | } 64 | 65 | parse_line(parser, line->s, req); 66 | } 67 | 68 | void parse_line(LKHttpRequestParser *parser, char *line, LKHttpRequest *req) { 69 | // First line: parse initial request line. 70 | if (parser->nlinesread == 0) { 71 | parse_request_line(line, req); 72 | parser->nlinesread++; 73 | return; 74 | } 75 | 76 | parser->nlinesread++; 77 | 78 | // Header lines 79 | if (!parser->head_complete) { 80 | // Empty CRLF line ends the headers section 81 | if (is_empty_line(line)) { 82 | parser->head_complete = 1; 83 | 84 | // No body to read (Content-Length: 0) 85 | if (parser->content_length == 0) { 86 | parser->body_complete = 1; 87 | } 88 | return; 89 | } 90 | parse_header_line(parser, line, req); 91 | return; 92 | } 93 | } 94 | 95 | // Parse initial request line in the format: 96 | // GET /path/to/index.html HTTP/1.0 97 | void parse_request_line(char *line, LKHttpRequest *req) { 98 | char *toks[3]; 99 | int ntoksread = 0; 100 | 101 | char *saveptr; 102 | char *delim = " \t"; 103 | char *linetmp = lk_strdup(line, "parse_request_line"); 104 | lk_chomp(linetmp); 105 | char *p = linetmp; 106 | while (ntoksread < 3) { 107 | toks[ntoksread] = strtok_r(p, delim, &saveptr); 108 | if (toks[ntoksread] == NULL) { 109 | break; 110 | } 111 | ntoksread++; 112 | p = NULL; // for the next call to strtok_r() 113 | } 114 | 115 | char *method = ""; 116 | char *uri = ""; 117 | char *version = ""; 118 | 119 | if (ntoksread > 0) method = toks[0]; 120 | if (ntoksread > 1) uri = toks[1]; 121 | if (ntoksread > 2) version = toks[2]; 122 | 123 | lk_string_assign(req->method, method); 124 | lk_string_assign(req->uri, uri); 125 | lk_string_assign(req->version, version); 126 | 127 | parse_uri(req->uri, req->path, req->filename, req->querystring); 128 | 129 | lk_free(linetmp); 130 | } 131 | 132 | // Parse uri into its components. 133 | // Given: lks_uri = "/path/blog/file1.html?a=1&b=2" 134 | // lks_path = "/path/blog/file1.html" 135 | // lks_filename = "file1.html" 136 | // lks_qs = "a=1&b=2" 137 | void parse_uri(LKString *lks_uri, LKString *lks_path, LKString *lks_filename, LKString *lks_qs) { 138 | // Get path and querystring 139 | // "/path/blog/file1.html?a=1&b=2" ==> "/path/blog/file1.html" and "a=1&b=2" 140 | LKStringList *uri_ss = lk_string_split(lks_uri, "?"); 141 | lk_string_assign(lks_path, uri_ss->items[0]->s); 142 | if (uri_ss->items_len > 1) { 143 | lk_string_assign(lks_qs, uri_ss->items[1]->s); 144 | } else { 145 | lk_string_assign(lks_qs, ""); 146 | } 147 | 148 | // Remove any trailing slash from uri. "/path/blog/" ==> "/path/blog" 149 | lk_string_chop_end(lks_path, "/"); 150 | 151 | // Extract filename from path. "/path/blog/file1.html" ==> "file1.html" 152 | LKStringList *path_ss = lk_string_split(lks_path, "/"); 153 | assert(path_ss->items_len > 0); 154 | lk_string_assign(lks_filename, path_ss->items[path_ss->items_len-1]->s); 155 | 156 | lk_stringlist_free(uri_ss); 157 | lk_stringlist_free(path_ss); 158 | } 159 | 160 | 161 | // Parse header line in the format Ex. User-Agent: browser 162 | static void parse_header_line(LKHttpRequestParser *parser, char *line, LKHttpRequest *req) { 163 | char *saveptr; 164 | char *delim = ":"; 165 | 166 | char *linetmp = lk_strdup(line, "parse_header_line"); 167 | lk_chomp(linetmp); 168 | char *k = strtok_r(linetmp, delim, &saveptr); 169 | if (k == NULL) { 170 | lk_free(linetmp); 171 | return; 172 | } 173 | char *v = strtok_r(NULL, delim, &saveptr); 174 | if (v == NULL) { 175 | v = ""; 176 | } 177 | 178 | // skip over leading whitespace 179 | while (*v == ' ' || *v == '\t') { 180 | v++; 181 | } 182 | lk_httprequest_add_header(req, k, v); 183 | 184 | if (!strcasecmp(k, "Content-Length")) { 185 | int content_length = atoi(v); 186 | parser->content_length = content_length; 187 | } 188 | 189 | lk_free(linetmp); 190 | } 191 | 192 | 193 | // Parse sequence of bytes into request body. Compile results into req. 194 | // You can check the state of the parser through the following fields: 195 | // parser->head_complete Request Line and Headers complete 196 | // parser->body_complete httprequest is complete 197 | void lk_httprequestparser_parse_bytes(LKHttpRequestParser *parser, LKBuffer *buf, LKHttpRequest *req) { 198 | // Head should be parsed line by line. Call parse_line() instead. 199 | if (!parser->head_complete) { 200 | return; 201 | } 202 | if (parser->body_complete) { 203 | return; 204 | } 205 | 206 | lk_buffer_append(req->body, buf->bytes, buf->bytes_len); 207 | if (req->body->bytes_len >= parser->content_length) { 208 | parser->body_complete = 1; 209 | } 210 | } 211 | 212 | -------------------------------------------------------------------------------- /lkstring.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "lklib.h" 11 | 12 | // Zero out entire size 13 | void zero_s(LKString *lks) { 14 | memset(lks->s, 0, lks->s_size+1); 15 | } 16 | // Zero out the free unused space 17 | void zero_unused_s(LKString *lks) { 18 | memset(lks->s + lks->s_len, '\0', lks->s_size+1 - lks->s_len); 19 | } 20 | 21 | LKString *lk_string_new(char *s) { 22 | if (s == NULL) { 23 | s = ""; 24 | } 25 | size_t s_len = strlen(s); 26 | 27 | LKString *lks = lk_malloc(sizeof(LKString), "lk_string_new"); 28 | lks->s_len = s_len; 29 | lks->s_size = s_len; 30 | lks->s = lk_malloc(lks->s_size+1, "lk_string_new_s"); 31 | zero_s(lks); 32 | strncpy(lks->s, s, s_len); 33 | 34 | return lks; 35 | } 36 | LKString *lk_string_size_new(size_t size) { 37 | LKString *lks = lk_malloc(sizeof(LKString), "lk_string_size_new"); 38 | 39 | lks->s_len = 0; 40 | lks->s_size = size; 41 | lks->s = lk_malloc(lks->s_size+1, "lk_string_size_new_s"); 42 | zero_s(lks); 43 | 44 | return lks; 45 | } 46 | void lk_string_free(LKString *lks) { 47 | assert(lks->s != NULL); 48 | 49 | zero_s(lks); 50 | lk_free(lks->s); 51 | lks->s = NULL; 52 | lk_free(lks); 53 | } 54 | void lk_string_voidp_free(void *plkstr) { 55 | lk_string_free((LKString *) plkstr); 56 | } 57 | 58 | void lk_string_assign(LKString *lks, char *s) { 59 | size_t s_len = strlen(s); 60 | if (s_len > lks->s_size) { 61 | lks->s_size = s_len; 62 | lks->s = lk_realloc(lks->s, lks->s_size+1, "lk_string_assign"); 63 | } 64 | zero_s(lks); 65 | strncpy(lks->s, s, s_len); 66 | lks->s_len = s_len; 67 | } 68 | 69 | void lk_string_assign_sprintf(LKString *lks, char *fmt, ...) { 70 | char sbuf[LK_BUFSIZE_MEDIUM]; 71 | 72 | // Try to use static buffer first, to avoid allocation. 73 | va_list args; 74 | va_start(args, fmt); 75 | int z = vsnprintf(sbuf, sizeof(sbuf), fmt, args); 76 | va_end(args); 77 | if (z < 0) return; 78 | 79 | // If snprintf() truncated output to sbuf due to space, 80 | // use asprintf() instead. 81 | if (z >= sizeof(sbuf)) { 82 | va_list args; 83 | char *ps; 84 | va_start(args, fmt); 85 | int z = vasprintf(&ps, fmt, args); 86 | va_end(args); 87 | if (z == -1) return; 88 | 89 | lk_string_assign(lks, ps); 90 | free(ps); 91 | return; 92 | } 93 | 94 | lk_string_assign(lks, sbuf); 95 | } 96 | 97 | //$$ Duplicate code from lk_string_sprintf(). 98 | void lk_string_append_sprintf(LKString *lks, char *fmt, ...) { 99 | char sbuf[LK_BUFSIZE_MEDIUM]; 100 | 101 | // Try to use static buffer first, to avoid allocation. 102 | va_list args; 103 | va_start(args, fmt); 104 | int z = vsnprintf(sbuf, sizeof(sbuf), fmt, args); 105 | va_end(args); 106 | if (z < 0) return; 107 | 108 | // If snprintf() truncated output to sbuf due to space, 109 | // use asprintf() instead. 110 | if (z >= sizeof(sbuf)) { 111 | va_list args; 112 | char *ps; 113 | va_start(args, fmt); 114 | int z = vasprintf(&ps, fmt, args); 115 | va_end(args); 116 | if (z == -1) return; 117 | 118 | lk_string_assign(lks, ps); 119 | free(ps); 120 | return; 121 | } 122 | 123 | lk_string_append(lks, sbuf); 124 | } 125 | 126 | void lk_string_append(LKString *lks, char *s) { 127 | size_t s_len = strlen(s); 128 | if (lks->s_len + s_len > lks->s_size) { 129 | lks->s_size = lks->s_len + s_len; 130 | lks->s = lk_realloc(lks->s, lks->s_size+1, "lk_string_append"); 131 | zero_unused_s(lks); 132 | } 133 | 134 | strncpy(lks->s + lks->s_len, s, s_len); 135 | lks->s_len = lks->s_len + s_len; 136 | } 137 | 138 | void lk_string_append_char(LKString *lks, char c) { 139 | if (lks->s_len + 1 > lks->s_size) { 140 | // Grow string by ^2 141 | lks->s_size = lks->s_len + ((lks->s_len+1) * 2); 142 | lks->s = lk_realloc(lks->s, lks->s_size+1, "lk_string_append_char"); 143 | zero_unused_s(lks); 144 | } 145 | 146 | lks->s[lks->s_len] = c; 147 | lks->s[lks->s_len+1] = '\0'; 148 | lks->s_len++; 149 | } 150 | 151 | void lk_string_prepend(LKString *lks, char *s) { 152 | size_t s_len = strlen(s); 153 | if (lks->s_len + s_len > lks->s_size) { 154 | lks->s_size = lks->s_len + s_len; 155 | lks->s = lk_realloc(lks->s, lks->s_size+1, "lk_string_prepend"); 156 | zero_unused_s(lks); 157 | } 158 | 159 | memmove(lks->s + s_len, lks->s, lks->s_len); // shift string to right 160 | strncpy(lks->s, s, s_len); // prepend s to string 161 | lks->s_len = lks->s_len + s_len; 162 | } 163 | 164 | 165 | int lk_string_sz_equal(LKString *lks1, char *s2) { 166 | if (!strcmp(lks1->s, s2)) { 167 | return 1; 168 | } 169 | return 0; 170 | } 171 | 172 | int lk_string_equal(LKString *lks1, LKString *lks2) { 173 | return lk_string_sz_equal(lks1, lks2->s); 174 | } 175 | 176 | // Return if string starts with s. 177 | int lk_string_starts_with(LKString *lks, char *s) { 178 | size_t s_len = strlen(s); 179 | if (s_len > lks->s_len) { 180 | return 0; 181 | } 182 | if (!strncmp(lks->s, s, s_len)) { 183 | return 1; 184 | } 185 | return 0; 186 | } 187 | 188 | // Return if string ends with s. 189 | int lk_string_ends_with(LKString *lks, char *s) { 190 | size_t s_len = strlen(s); 191 | if (s_len > lks->s_len) { 192 | return 0; 193 | } 194 | size_t i = lks->s_len - s_len; 195 | if (!strncmp(lks->s + i, s, s_len)) { 196 | return 1; 197 | } 198 | return 0; 199 | } 200 | 201 | // Remove leading and trailing white from string. 202 | void lk_string_trim(LKString *lks) { 203 | if (lks->s_len == 0) { 204 | return; 205 | } 206 | 207 | // starti: index to first non-whitespace char 208 | // endi: index to last non-whitespace char 209 | int starti = 0; 210 | int endi = lks->s_len-1; 211 | assert(endi >= starti); 212 | 213 | for (int i=0; i < lks->s_len; i++) { 214 | if (!isspace(lks->s[i])) { 215 | break; 216 | } 217 | starti++; 218 | } 219 | // All chars are whitespace. 220 | if (starti >= lks->s_len) { 221 | lk_string_assign(lks, ""); 222 | return; 223 | } 224 | for (int i=lks->s_len-1; i >= 0; i--) { 225 | if (!isspace(lks->s[i])) { 226 | break; 227 | } 228 | endi--; 229 | } 230 | assert(endi >= starti); 231 | 232 | size_t new_len = endi-starti+1; 233 | memmove(lks->s, lks->s + starti, new_len); 234 | memset(lks->s + new_len, 0, lks->s_len - new_len); 235 | lks->s_len = new_len; 236 | } 237 | 238 | void lk_string_chop_start(LKString *lks, char *s) { 239 | size_t s_len = strlen(s); 240 | if (s_len > lks->s_len) { 241 | return; 242 | } 243 | if (strncmp(lks->s, s, s_len)) { 244 | return; 245 | } 246 | 247 | size_t new_len = lks->s_len - s_len; 248 | memmove(lks->s, lks->s + s_len, lks->s_len - s_len); 249 | memset(lks->s + new_len, 0, lks->s_len - new_len); 250 | lks->s_len = new_len; 251 | } 252 | 253 | void lk_string_chop_end(LKString *lks, char *s) { 254 | size_t s_len = strlen(s); 255 | if (s_len > lks->s_len) { 256 | return; 257 | } 258 | if (strncmp(lks->s + lks->s_len - s_len, s, s_len)) { 259 | return; 260 | } 261 | 262 | size_t new_len = lks->s_len - s_len; 263 | memset(lks->s + new_len, 0, s_len); 264 | lks->s_len = new_len; 265 | } 266 | 267 | // Return whether delim matches the first delim_len chars of s. 268 | int match_delim(char *s, char *delim, size_t delim_len) { 269 | return !strncmp(s, delim, delim_len); 270 | } 271 | 272 | LKStringList *lk_string_split(LKString *lks, char *delim) { 273 | LKStringList *sl = lk_stringlist_new(); 274 | size_t delim_len = strlen(delim); 275 | 276 | LKString *segment = lk_string_new(""); 277 | for (int i=0; i < lks->s_len; i++) { 278 | if (match_delim(lks->s + i, delim, delim_len)) { 279 | lk_stringlist_append_lkstring(sl, segment); 280 | segment = lk_string_new(""); 281 | i += delim_len-1; // need to -1 to account for for loop incrementor i++ 282 | continue; 283 | } 284 | 285 | lk_string_append_char(segment, lks->s[i]); 286 | } 287 | lk_stringlist_append_lkstring(sl, segment); 288 | 289 | return sl; 290 | } 291 | 292 | // Given a "kv" string, assign k and v. 293 | void lk_string_split_assign(LKString *s, char *delim, LKString *k, LKString *v) { 294 | LKStringList *ss = lk_string_split(s, delim); 295 | if (k != NULL) { 296 | lk_string_assign(k, ss->items[0]->s); 297 | } 298 | if (v != NULL) { 299 | if (ss->items_len >= 2) { 300 | lk_string_assign(v, ss->items[1]->s); 301 | } else { 302 | lk_string_assign(v, ""); 303 | } 304 | } 305 | lk_stringlist_free(ss); 306 | } 307 | 308 | void sz_string_split_assign(char *s, char *delim, LKString *k, LKString *v) { 309 | LKString *lks = lk_string_new(s); 310 | lk_string_split_assign(lks, delim, k, v); 311 | lk_string_free(lks); 312 | } 313 | 314 | -------------------------------------------------------------------------------- /lknet.h: -------------------------------------------------------------------------------- 1 | #ifndef LKNET_H 2 | #define LKNET_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "lklib.h" 11 | 12 | /*** LKHttpRequest - HTTP Request struct ***/ 13 | typedef struct { 14 | LKString *method; // GET 15 | LKString *uri; // "/path/to/index.html?p=1&start=5" 16 | LKString *path; // "/path/to/index.html" 17 | LKString *filename; // "index.html" 18 | LKString *querystring; // "p=1&start=5" 19 | LKString *version; // HTTP/1.0 20 | LKStringTable *headers; 21 | LKBuffer *head; 22 | LKBuffer *body; 23 | } LKHttpRequest; 24 | 25 | LKHttpRequest *lk_httprequest_new(); 26 | void lk_httprequest_free(LKHttpRequest *req); 27 | void lk_httprequest_add_header(LKHttpRequest *req, char *k, char *v); 28 | void lk_httprequest_append_body(LKHttpRequest *req, char *bytes, int bytes_len); 29 | void lk_httprequest_finalize(LKHttpRequest *req); 30 | void lk_httprequest_debugprint(LKHttpRequest *req); 31 | 32 | 33 | /*** LKHttpResponse - HTTP Response struct ***/ 34 | typedef struct { 35 | int status; // 404 36 | LKString *statustext; // File not found 37 | LKString *version; // HTTP/1.0 38 | LKStringTable *headers; 39 | LKBuffer *head; 40 | LKBuffer *body; 41 | } LKHttpResponse; 42 | 43 | LKHttpResponse *lk_httpresponse_new(); 44 | void lk_httpresponse_free(LKHttpResponse *resp); 45 | void lk_httpresponse_add_header(LKHttpResponse *resp, char *k, char *v); 46 | void lk_httpresponse_finalize(LKHttpResponse *resp); 47 | void lk_httpresponse_debugprint(LKHttpResponse *resp); 48 | 49 | 50 | /*** LKSocketReader - Buffered input for sockets ***/ 51 | typedef struct { 52 | int sock; 53 | LKBuffer *buf; 54 | int sockclosed; 55 | } LKSocketReader; 56 | 57 | LKSocketReader *lk_socketreader_new(int sock, size_t initial_size); 58 | void lk_socketreader_free(LKSocketReader *sr); 59 | int lk_socketreader_readline(LKSocketReader *sr, LKString *line); 60 | int lk_socketreader_recv(LKSocketReader *sr, LKBuffer *buf); 61 | void lk_socketreader_debugprint(LKSocketReader *sr); 62 | 63 | 64 | /*** LKHttpRequestParser ***/ 65 | typedef struct { 66 | LKString *partial_line; 67 | unsigned int nlinesread; 68 | int head_complete; // flag indicating header lines complete 69 | int body_complete; // flag indicating request body complete 70 | unsigned int content_length; // value of Content-Length header 71 | } LKHttpRequestParser; 72 | 73 | LKHttpRequestParser *lk_httprequestparser_new(); 74 | void lk_httprequestparser_free(LKHttpRequestParser *parser); 75 | void lk_httprequestparser_reset(LKHttpRequestParser *parser); 76 | void lk_httprequestparser_parse_line(LKHttpRequestParser *parser, LKString *line, LKHttpRequest *req); 77 | void lk_httprequestparser_parse_bytes(LKHttpRequestParser *parser, LKBuffer *buf, LKHttpRequest *req); 78 | 79 | /*** CGI Parser ***/ 80 | void parse_cgi_output(LKBuffer *buf, LKHttpResponse *resp); 81 | 82 | 83 | /*** LKContext ***/ 84 | typedef enum { 85 | CTX_READ_REQ, 86 | CTX_READ_CGI_OUTPUT, 87 | CTX_WRITE_CGI_INPUT, 88 | CTX_WRITE_RESP, 89 | CTX_PROXY_WRITE_REQ, 90 | CTX_PROXY_PIPE_RESP, 91 | } LKContextType; 92 | 93 | typedef struct lkcontext_s { 94 | int selectfd; 95 | int clientfd; 96 | LKContextType type; 97 | struct lkcontext_s *next; // link to next ctx 98 | 99 | // Used by CTX_READ_REQ: 100 | struct sockaddr_in client_sa; // client address 101 | LKString *client_ipaddr; // client ip address string 102 | unsigned short client_port; // client port number 103 | LKString *req_line; // current request line 104 | LKBuffer *req_buf; // current request bytes buffer 105 | LKSocketReader *sr; // input buffer for reading lines 106 | LKHttpRequestParser *reqparser; // parser for httprequest 107 | LKHttpRequest *req; // http request in process 108 | 109 | // Used by CTX_WRITE_REQ: 110 | LKHttpResponse *resp; // http response to be sent 111 | LKRefList *buflist; // Buffer list of things to send/recv 112 | 113 | // Used by CTX_READ_CGI: 114 | int cgifd; 115 | LKBuffer *cgi_outputbuf; // receive cgi stdout bytes here 116 | LKBuffer *cgi_inputbuf; // input bytes to pass to cgi stdin 117 | 118 | // Used by CTX_PROXY_WRITE_REQ: 119 | int proxyfd; 120 | LKBuffer *proxy_respbuf; 121 | } LKContext; 122 | 123 | LKContext *lk_context_new(); 124 | LKContext *create_initial_context(int fd, struct sockaddr_in *sa); 125 | void lk_context_free(LKContext *ctx); 126 | 127 | void add_new_client_context(LKContext **pphead, LKContext *ctx); 128 | void add_context(LKContext **pphead, LKContext *ctx); 129 | int remove_client_context(LKContext **pphead, int clientfd); 130 | void remove_client_contexts(LKContext **pphead, int clientfd); 131 | LKContext *match_select_ctx(LKContext *phead, int selectfd); 132 | int remove_selectfd_context(LKContext **pphead, int selectfd); 133 | 134 | 135 | /*** LKConfig ***/ 136 | typedef struct { 137 | LKString *hostname; 138 | LKString *homedir; 139 | LKString *homedir_abspath; 140 | LKString *cgidir; 141 | LKString *cgidir_abspath; 142 | LKStringTable *aliases; 143 | LKString *proxyhost; 144 | } LKHostConfig; 145 | 146 | typedef struct { 147 | LKString *serverhost; 148 | LKString *port; 149 | LKHostConfig **hostconfigs; 150 | size_t hostconfigs_len; 151 | size_t hostconfigs_size; 152 | } LKConfig; 153 | 154 | LKConfig *lk_config_new(); 155 | void lk_config_free(LKConfig *cfg); 156 | int lk_config_read_configfile(LKConfig *cfg, char *configfile); 157 | void lk_config_print(LKConfig *cfg); 158 | LKHostConfig *lk_config_add_hostconfig(LKConfig *cfg, LKHostConfig *hc); 159 | LKHostConfig *lk_config_find_hostconfig(LKConfig *cfg, char *hostname); 160 | LKHostConfig *lk_config_create_get_hostconfig(LKConfig *cfg, char *hostname); 161 | void lk_config_finalize(LKConfig *cfg); 162 | 163 | LKHostConfig *lk_hostconfig_new(char *hostname); 164 | void lk_hostconfig_free(LKHostConfig *hc); 165 | 166 | 167 | typedef struct { 168 | LKConfig *cfg; 169 | LKContext *ctxhead; 170 | fd_set readfds; 171 | fd_set writefds; 172 | int maxfd; 173 | } LKHttpServer; 174 | 175 | typedef enum { 176 | LKHTTPSERVEROPT_HOMEDIR, 177 | LKHTTPSERVEROPT_PORT, 178 | LKHTTPSERVEROPT_HOST, 179 | LKHTTPSERVEROPT_CGIDIR, 180 | LKHTTPSERVEROPT_ALIAS, 181 | LKHTTPSERVEROPT_PROXYPASS 182 | } LKHttpServerOpt; 183 | 184 | LKHttpServer *lk_httpserver_new(LKConfig *cfg); 185 | void lk_httpserver_free(LKHttpServer *server); 186 | void lk_httpserver_setopt(LKHttpServer *server, LKHttpServerOpt opt, ...); 187 | int lk_httpserver_serve(LKHttpServer *server); 188 | 189 | 190 | /*** Helper functions ***/ 191 | int lk_open_listen_socket(char *host, char *port, int backlog, struct sockaddr *psa); 192 | int lk_open_connect_socket(char *host, char *port, struct sockaddr *psa); 193 | void lk_set_sock_timeout(int sock, int nsecs, int ms); 194 | void lk_set_sock_nonblocking(int sock); 195 | LKString *lk_get_ipaddr_string(struct sockaddr *sa); 196 | unsigned short lk_get_sockaddr_port(struct sockaddr *sa); 197 | int nonblocking_error(int z); 198 | 199 | // Function return values: 200 | // Z_OPEN (fd still open) 201 | // Z_EOF (end of file) 202 | // Z_ERR (errno set with error detail) 203 | // Z_BLOCK (fd blocked, no data) 204 | #define Z_OPEN 1 205 | #define Z_EOF 0 206 | #define Z_ERR -1 207 | #define Z_BLOCK -2 208 | 209 | typedef enum {FD_SOCK, FD_FILE} FDType; 210 | typedef enum {FD_READ, FD_WRITE, FD_READWRITE} FDAction; 211 | 212 | // Read nonblocking fd count bytes to buf. 213 | // Returns one of the following: 214 | // 1 (Z_OPEN) for socket open (data available) 215 | // 0 (Z_EOF) for EOF 216 | // -1 (Z_ERR) for error 217 | // -2 (Z_BLOCK) for blocked socket (no data) 218 | // On return, nbytes contains the number of bytes read. 219 | int lk_read(int fd, FDType fd_type, LKBuffer *buf, size_t count, size_t *nbytes); 220 | int lk_read_sock(int fd, LKBuffer *buf, size_t count, size_t *nbytes); 221 | int lk_read_file(int fd, LKBuffer *buf, size_t count, size_t *nbytes); 222 | 223 | // Read all available nonblocking fd bytes to buffer. 224 | // Returns one of the following: 225 | // 0 (Z_EOF) for EOF 226 | // -1 (Z_ERR) for error 227 | // -2 (Z_BLOCK) for blocked socket (no data) 228 | // 229 | // Note: This keeps track of last buf position read. 230 | // Used to cumulatively read data into buf. 231 | int lk_read_all(int fd, FDType fd_type, LKBuffer *buf); 232 | int lk_read_all_sock(int fd, LKBuffer *buf); 233 | int lk_read_all_file(int fd, LKBuffer *buf); 234 | 235 | // Write count buf bytes to nonblocking fd. 236 | // Returns one of the following: 237 | // 1 (Z_OPEN) for socket open 238 | // -1 (Z_ERR) for error 239 | // -2 (Z_BLOCK) for blocked socket (no data) 240 | // On return, nbytes contains the number of bytes written. 241 | int lk_write(int fd, FDType fd_type, LKBuffer *buf, size_t count, size_t *nbytes); 242 | int lk_write_sock(int fd, LKBuffer *buf, size_t count, size_t *nbytes); 243 | int lk_write_file(int fd, LKBuffer *buf, size_t count, size_t *nbytes); 244 | 245 | // Write buf bytes to nonblocking fd. 246 | // Returns one of the following: 247 | // 1 (Z_OPEN) for socket open 248 | // -1 (Z_ERR) for error 249 | // -2 (Z_BLOCK) for blocked socket (no data) 250 | // 251 | // Note: This keeps track of last buf position written. 252 | // Used to cumulatively write data into buf. 253 | int lk_write_all(int fd, FDType fd_type, LKBuffer *buf); 254 | int lk_write_all_sock(int fd, LKBuffer *buf); 255 | int lk_write_all_file(int fd, LKBuffer *buf); 256 | 257 | // Similar to lk_write_all(), but sending buflist buf's sequentially. 258 | int lk_buflist_write_all(int fd, FDType fd_type, LKRefList *buflist); 259 | 260 | // Pipe all available nonblocking readfd bytes into writefd. 261 | // Uses buf as buffer for queued up bytes waiting to be written. 262 | // Returns one of the following: 263 | // 0 (Z_EOF) for read/write complete. 264 | // 1 (Z_OPEN) for writefd socket open 265 | // -1 (Z_ERR) for read/write error. 266 | // -2 (Z_BLOCK) for blocked readfd/writefd socket 267 | int lk_pipe_all(int readfd, int writefd, FDType fd_type, LKBuffer *buf); 268 | 269 | // Remove trailing CRLF or LF (\n) from string. 270 | void lk_chomp(char* s); 271 | // Read entire file into buf. 272 | ssize_t lk_readfile(char *filepath, LKBuffer *buf); 273 | // Read entire file descriptor contents into buf. 274 | ssize_t lk_readfd(int fd, LKBuffer *buf); 275 | // Append src to dest, allocating new memory in dest if needed. 276 | // Return new pointer to dest. 277 | char *lk_astrncat(char *dest, char *src, size_t src_len); 278 | // Return whether file exists. 279 | int lk_file_exists(char *filename); 280 | 281 | #endif 282 | 283 | -------------------------------------------------------------------------------- /lkconfig.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "lklib.h" 10 | #include "lknet.h" 11 | 12 | #define HOSTCONFIGS_INITIAL_SIZE 10 13 | 14 | LKConfig *lk_config_new() { 15 | LKConfig *cfg = lk_malloc(sizeof(LKConfig), "lk_config_new"); 16 | cfg->serverhost = lk_string_new(""); 17 | cfg->port = lk_string_new(""); 18 | cfg->hostconfigs = lk_malloc(sizeof(LKHostConfig*) * HOSTCONFIGS_INITIAL_SIZE, "lk_config_new_hostconfigs"); 19 | cfg->hostconfigs_len = 0; 20 | cfg->hostconfigs_size = HOSTCONFIGS_INITIAL_SIZE; 21 | return cfg; 22 | } 23 | 24 | void lk_config_free(LKConfig *cfg) { 25 | lk_string_free(cfg->serverhost); 26 | lk_string_free(cfg->port); 27 | for (int i=0; i < cfg->hostconfigs_len; i++) { 28 | LKHostConfig *hc = cfg->hostconfigs[i]; 29 | lk_hostconfig_free(hc); 30 | } 31 | memset(cfg->hostconfigs, 0, sizeof(LKHostConfig*) * cfg->hostconfigs_size); 32 | lk_free(cfg->hostconfigs); 33 | 34 | cfg->serverhost = NULL; 35 | cfg->port = NULL; 36 | cfg->hostconfigs = NULL; 37 | 38 | lk_free(cfg); 39 | } 40 | 41 | 42 | #define CONFIG_LINE_SIZE 255 43 | 44 | // Read config file and set config structure. 45 | // 46 | // Config file format: 47 | // ------------------- 48 | // serverhost=127.0.0.1 49 | // port=5000 50 | // 51 | // # Matches all other hostnames 52 | // hostname * 53 | // homedir=/var/www/testsite 54 | // alias latest=latest.html 55 | // 56 | // # Matches http://localhost 57 | // hostname localhost 58 | // homedir=/var/www/testsite 59 | // 60 | // # http://littlekitten.xyz 61 | // hostname littlekitten.xyz 62 | // homedir=/var/www/testsite 63 | // cgidir=cgi-bin 64 | // alias latest=latest.html 65 | // alias about=about.html 66 | // alias guestbook=cgi-bin/guestbook.pl 67 | // alias blog=cgi-bin/blog.pl 68 | // 69 | // # http://newsboard.littlekitten.xyz 70 | // hostname newsboard.littlekitten.xyz 71 | // proxyhost=localhost:8001 72 | // 73 | // Format description: 74 | // The host and port number is defined first, followed by one or more 75 | // host config sections. The host config section always starts with the 76 | // 'hostname ' line followed by the settings for that hostname. 77 | // The section ends on either EOF or when a new 'hostname ' line 78 | // is read, indicating the start of the next host config section. 79 | // 80 | typedef enum {CFG_ROOT, CFG_HOSTSECTION} ParseCfgState; 81 | int lk_config_read_configfile(LKConfig *cfg, char *configfile) { 82 | FILE *f = fopen(configfile, "r"); 83 | if (f == NULL) { 84 | lk_print_err("lk_read_configfile fopen()"); 85 | return -1; 86 | } 87 | 88 | char line[CONFIG_LINE_SIZE]; 89 | ParseCfgState state = CFG_ROOT; 90 | LKString *l = lk_string_new(""); 91 | LKString *k = lk_string_new(""); 92 | LKString *v = lk_string_new(""); 93 | LKString *aliask = lk_string_new(""); 94 | LKString *aliasv = lk_string_new(""); 95 | LKHostConfig *hc = NULL; 96 | 97 | while (1) { 98 | char *pz = fgets(line, sizeof(line), f); 99 | if (pz == NULL) { 100 | break; 101 | } 102 | lk_string_assign(l, line); 103 | lk_string_trim(l); 104 | 105 | // Skip # comment line. 106 | if (lk_string_starts_with(l, "#")) { 107 | continue; 108 | } 109 | 110 | // hostname littlekitten.xyz 111 | lk_string_split_assign(l, " ", k, v); // l:"k v", assign k and v 112 | if (lk_string_sz_equal(k, "hostname")) { 113 | // hostname littlekitten.xyz 114 | hc = lk_config_create_get_hostconfig(cfg, v->s); 115 | state = CFG_HOSTSECTION; 116 | continue; 117 | } 118 | 119 | if (state == CFG_ROOT) { 120 | assert(hc == NULL); 121 | 122 | // serverhost=127.0.0.1 123 | // port=8000 124 | lk_string_split_assign(l, "=", k, v); // l:"k=v", assign k and v 125 | if (lk_string_sz_equal(k, "serverhost")) { 126 | lk_string_assign(cfg->serverhost, v->s); 127 | continue; 128 | } else if (lk_string_sz_equal(k, "port")) { 129 | lk_string_assign(cfg->port, v->s); 130 | continue; 131 | } 132 | continue; 133 | } 134 | if (state == CFG_HOSTSECTION) { 135 | assert(hc != NULL); 136 | 137 | // homedir=testsite 138 | // cgidir=cgi-bin 139 | // proxyhost=localhost:8001 140 | lk_string_split_assign(l, "=", k, v); 141 | if (lk_string_sz_equal(k, "homedir")) { 142 | lk_string_assign(hc->homedir, v->s); 143 | continue; 144 | } else if (lk_string_sz_equal(k, "cgidir")) { 145 | lk_string_assign(hc->cgidir, v->s); 146 | continue; 147 | } else if (lk_string_sz_equal(k, "proxyhost")) { 148 | lk_string_assign(hc->proxyhost, v->s); 149 | continue; 150 | } 151 | // alias latest=latest.html 152 | lk_string_split_assign(l, " ", k, v); 153 | if (lk_string_sz_equal(k, "alias")) { 154 | lk_string_split_assign(v, "=", aliask, aliasv); 155 | if (!lk_string_starts_with(aliask, "/")) { 156 | lk_string_prepend(aliask, "/"); 157 | } 158 | if (!lk_string_starts_with(aliasv, "/")) { 159 | lk_string_prepend(aliasv, "/"); 160 | } 161 | lk_stringtable_set(hc->aliases, aliask->s, aliasv->s); 162 | continue; 163 | } 164 | continue; 165 | } 166 | } 167 | 168 | lk_string_free(l); 169 | lk_string_free(k); 170 | lk_string_free(v); 171 | lk_string_free(aliask); 172 | lk_string_free(aliasv); 173 | 174 | fclose(f); 175 | return 0; 176 | } 177 | 178 | LKHostConfig *lk_config_add_hostconfig(LKConfig *cfg, LKHostConfig *hc) { 179 | assert(cfg->hostconfigs_len <= cfg->hostconfigs_size); 180 | 181 | // Increase size if no more space. 182 | if (cfg->hostconfigs_len == cfg->hostconfigs_size) { 183 | cfg->hostconfigs_size++; 184 | cfg->hostconfigs = lk_realloc(cfg->hostconfigs, sizeof(LKHostConfig*) * cfg->hostconfigs_size, "lk_config_add_hostconfig"); 185 | } 186 | cfg->hostconfigs[cfg->hostconfigs_len] = hc; 187 | cfg->hostconfigs_len++; 188 | 189 | return hc; 190 | } 191 | 192 | // Return hostconfig matching hostname, 193 | // or if hostname parameter is NULL, return hostconfig matching "*". 194 | // Return NULL if no matching hostconfig. 195 | LKHostConfig *lk_config_find_hostconfig(LKConfig *cfg, char *hostname) { 196 | if (hostname != NULL) { 197 | for (int i=0; i < cfg->hostconfigs_len; i++) { 198 | LKHostConfig *hc = cfg->hostconfigs[i]; 199 | if (lk_string_sz_equal(hc->hostname, hostname)) { 200 | return hc; 201 | } 202 | } 203 | } 204 | // If hostname not found, return hostname * (fallthrough hostname). 205 | for (int i=0; i < cfg->hostconfigs_len; i++) { 206 | LKHostConfig *hc = cfg->hostconfigs[i]; 207 | if (lk_string_sz_equal(hc->hostname, "*")) { 208 | return hc; 209 | } 210 | } 211 | return NULL; 212 | } 213 | 214 | // Return hostconfig with hostname or NULL if not found. 215 | LKHostConfig *get_hostconfig(LKConfig *cfg, char *hostname) { 216 | for (int i=0; i < cfg->hostconfigs_len; i++) { 217 | LKHostConfig *hc = cfg->hostconfigs[i]; 218 | if (lk_string_sz_equal(hc->hostname, hostname)) { 219 | return hc; 220 | } 221 | } 222 | return NULL; 223 | } 224 | 225 | // Return hostconfig with hostname if it exists. 226 | // Create new hostconfig with hostname if not found. Never returns null. 227 | LKHostConfig *lk_config_create_get_hostconfig(LKConfig *cfg, char *hostname) { 228 | LKHostConfig *hc = get_hostconfig(cfg, hostname); 229 | if (hc == NULL) { 230 | hc = lk_hostconfig_new(hostname); 231 | lk_config_add_hostconfig(cfg, hc); 232 | } 233 | return hc; 234 | } 235 | 236 | void lk_config_print(LKConfig *cfg) { 237 | printf("serverhost: %s\n", cfg->serverhost->s); 238 | printf("port: %s\n", cfg->port->s); 239 | 240 | for (int i=0; i < cfg->hostconfigs_len; i++) { 241 | LKHostConfig *hc = cfg->hostconfigs[i]; 242 | printf("%2d. hostname %s\n", i+1, hc->hostname->s); 243 | if (hc->homedir->s_len > 0) { 244 | printf(" homedir: %s\n", hc->homedir->s); 245 | } 246 | if (hc->cgidir->s_len > 0) { 247 | printf(" cgidir: %s\n", hc->cgidir->s); 248 | } 249 | if (hc->proxyhost->s_len > 0) { 250 | printf(" proxyhost: %s\n", hc->proxyhost->s); 251 | } 252 | for (int j=0; j < hc->aliases->items_len; j++) { 253 | printf(" alias %s=%s\n", hc->aliases->items[j].k->s, hc->aliases->items[j].v->s); 254 | } 255 | } 256 | printf("\n"); 257 | } 258 | 259 | // Fill in default values for unspecified settings. 260 | void lk_config_finalize(LKConfig *cfg) { 261 | // serverhost defaults to localhost if not specified. 262 | if (cfg->serverhost->s_len == 0) { 263 | lk_string_assign(cfg->serverhost, "localhost"); 264 | } 265 | // port defaults to 8000 if not specified. 266 | if (cfg->port->s_len == 0) { 267 | lk_string_assign(cfg->port, "8000"); 268 | } 269 | 270 | // Get current working directory. 271 | LKString *current_dir = lk_string_new(""); 272 | char *s = get_current_dir_name(); 273 | if (s != NULL) { 274 | lk_string_assign(current_dir, s); 275 | free(s); 276 | } else { 277 | lk_string_assign(current_dir, "."); 278 | } 279 | 280 | // Set fallthrough defaults only if no other hostconfigs specified 281 | // Note: If other hostconfigs are set, such as in a config file, 282 | // the fallthrough '*' hostconfig should be set explicitly. 283 | if (cfg->hostconfigs_len == 0) { 284 | // homedir default to current directory 285 | LKHostConfig *hc = lk_config_create_get_hostconfig(cfg, "*"); 286 | lk_string_assign(hc->homedir, current_dir->s); 287 | 288 | // cgidir default to cgi-bin 289 | if (hc->cgidir->s_len == 0) { 290 | lk_string_assign(hc->cgidir, "/cgi-bin/"); 291 | } 292 | } 293 | 294 | // Set homedir absolute paths for hostconfigs. 295 | // Adjust /cgi-bin/ paths. 296 | char homedir_abspath[PATH_MAX]; 297 | for (int i=0; i < cfg->hostconfigs_len; i++) { 298 | LKHostConfig *hc = cfg->hostconfigs[i]; 299 | 300 | // Skip hostconfigs that don't have have homedir. 301 | if (hc->homedir->s_len == 0) { 302 | continue; 303 | } 304 | 305 | // Set absolute path to homedir 306 | char *pz = realpath(hc->homedir->s, homedir_abspath); 307 | if (pz == NULL) { 308 | lk_print_err("realpath()"); 309 | homedir_abspath[0] = '\0'; 310 | } 311 | lk_string_assign(hc->homedir_abspath, homedir_abspath); 312 | 313 | // Adjust cgidir paths. 314 | if (hc->cgidir->s_len > 0) { 315 | if (!lk_string_starts_with(hc->cgidir, "/")) { 316 | lk_string_prepend(hc->cgidir, "/"); 317 | } 318 | if (!lk_string_ends_with(hc->cgidir, "/")) { 319 | lk_string_append(hc->cgidir, "/"); 320 | } 321 | 322 | lk_string_assign(hc->cgidir_abspath, hc->homedir_abspath->s); 323 | lk_string_append(hc->cgidir_abspath, hc->cgidir->s); 324 | } 325 | } 326 | 327 | lk_string_free(current_dir); 328 | } 329 | 330 | 331 | LKHostConfig *lk_hostconfig_new(char *hostname) { 332 | LKHostConfig *hc = lk_malloc(sizeof(LKHostConfig), "lk_hostconfig_new"); 333 | 334 | hc->hostname = lk_string_new(hostname); 335 | hc->homedir = lk_string_new(""); 336 | hc->homedir_abspath = lk_string_new(""); 337 | hc->cgidir = lk_string_new(""); 338 | hc->cgidir_abspath = lk_string_new(""); 339 | hc->aliases = lk_stringtable_new(); 340 | hc->proxyhost = lk_string_new(""); 341 | 342 | return hc; 343 | } 344 | 345 | void lk_hostconfig_free(LKHostConfig *hc) { 346 | lk_string_free(hc->hostname); 347 | lk_string_free(hc->homedir); 348 | lk_string_free(hc->homedir_abspath); 349 | lk_string_free(hc->cgidir); 350 | lk_string_free(hc->cgidir_abspath); 351 | lk_stringtable_free(hc->aliases); 352 | lk_string_free(hc->proxyhost); 353 | 354 | hc->hostname = NULL; 355 | hc->homedir = NULL; 356 | hc->homedir_abspath = NULL; 357 | hc->cgidir = NULL; 358 | hc->cgidir_abspath = NULL; 359 | hc->aliases = NULL; 360 | hc->proxyhost = NULL; 361 | 362 | lk_free(hc); 363 | } 364 | 365 | -------------------------------------------------------------------------------- /lktest.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "lklib.h" 8 | #include "lknet.h" 9 | 10 | void lkstring_test(); 11 | void lkstringmap_test(); 12 | void lkbuffer_test(); 13 | void lkstringlist_test(); 14 | void lkreflist_test(); 15 | void lkconfig_test(); 16 | 17 | int main(int argc, char *argv[]) { 18 | lk_alloc_init(); 19 | 20 | lkstring_test(); 21 | lkstringmap_test(); 22 | lkbuffer_test(); 23 | lkstringlist_test(); 24 | lkreflist_test(); 25 | lkconfig_test(); 26 | 27 | lk_print_allocitems(); 28 | 29 | return 0; 30 | } 31 | 32 | void lkstring_test() { 33 | LKString *lks, *lks2; 34 | 35 | printf("Running LKString tests... "); 36 | lks = lk_string_new(""); 37 | assert(lks->s_len == 0); 38 | assert(lks->s_size >= lks->s_len); 39 | assert(strcmp(lks->s, "") == 0); 40 | lk_string_free(lks); 41 | 42 | lks = lk_string_size_new(10); 43 | assert(lks->s_len == 0); 44 | assert(lks->s_size == 10); 45 | assert(strcmp(lks->s, "") == 0); 46 | lk_string_free(lks); 47 | 48 | // lks, lks2 49 | lks = lk_string_new("abc def"); 50 | lks2 = lk_string_new("abc "); 51 | assert(lks->s_len == 7); 52 | assert(lks->s_size >= lks->s_len); 53 | assert(!lk_string_equal(lks, lks2)); 54 | assert(strcmp(lks->s, "abc ")); 55 | assert(strcmp(lks->s, "abc def2")); 56 | assert(!strcmp(lks->s, "abc def")); 57 | 58 | lk_string_append(lks2, "def"); 59 | assert(!strcmp(lks->s, lks2->s)); 60 | assert(lk_string_equal(lks, lks2)); 61 | 62 | lk_string_assign(lks2, "abc def"); 63 | assert(lk_string_equal(lks, lks2)); 64 | 65 | lk_string_assign(lks2, ""); 66 | assert(!lk_string_equal(lks, lks2)); 67 | 68 | lk_string_assign_sprintf(lks2, "abc %s", "def"); 69 | assert(!strcmp(lks->s, lks2->s)); 70 | assert(lk_string_equal(lks, lks2)); 71 | 72 | lk_string_assign_sprintf(lks, "abc%ddef%d", 123, 456); 73 | lk_string_assign_sprintf(lks2, "%s123%s456", "abc", "def"); 74 | assert(!strcmp(lks->s, lks2->s)); 75 | assert(lk_string_equal(lks, lks2)); 76 | lk_string_free(lks); 77 | lk_string_free(lks2); 78 | 79 | lks = lk_string_new("prompt: "); 80 | lk_string_append_sprintf(lks, "a:%d, b:%d, c:%d", 1, 2, 3); 81 | assert(lk_string_sz_equal(lks, "prompt: a:1, b:2, c:3")); 82 | assert(!lk_string_sz_equal(lks, "prompt: a: 1, b:2, c:3")); 83 | lk_string_free(lks); 84 | 85 | // Test very large strings. 86 | lks = lk_string_new(""); 87 | char sbuf[LK_BUFSIZE_XXL]; 88 | memset(sbuf, 'a', sizeof(sbuf)); 89 | sbuf[sizeof(sbuf)-1] = '\0'; 90 | lk_string_assign(lks, sbuf); 91 | 92 | assert(lks->s_len == sizeof(sbuf)-1); 93 | for (int i=0; i < sizeof(sbuf)-1; i++) { 94 | assert(lks->s[i] == 'a'); 95 | } 96 | 97 | lk_string_assign_sprintf(lks, "sbuf: %s", sbuf); 98 | assert(lks->s_len == sizeof(sbuf)-1 + 6); 99 | assert(strncmp(lks->s, "sbuf: aaaaa", 11) == 0); 100 | assert(lks->s[lks->s_len-2] == 'a'); 101 | assert(lks->s[lks->s_len-3] == 'a'); 102 | assert(lks->s[lks->s_len-4] == 'a'); 103 | lk_string_free(lks); 104 | 105 | lks = lk_string_new("abcdefghijkl mnopqrst"); 106 | assert(lk_string_starts_with(lks, "abc")); 107 | assert(lk_string_starts_with(lks, "abcdefghijkl mnopqrst")); 108 | assert(!lk_string_starts_with(lks, "abcdefghijkl mnopqrstuvwxyz")); 109 | assert(!lk_string_starts_with(lks, "abcdefghijkl mnopqrst ")); 110 | assert(lk_string_starts_with(lks, "")); 111 | 112 | assert(lk_string_ends_with(lks, "rst")); 113 | assert(lk_string_ends_with(lks, " mnopqrst")); 114 | assert(lk_string_ends_with(lks, "abcdefghijkl mnopqrst")); 115 | assert(!lk_string_ends_with(lks, " abcdefghijkl mnopqrst")); 116 | assert(lk_string_ends_with(lks, "")); 117 | 118 | lk_string_assign(lks, ""); 119 | assert(lk_string_starts_with(lks, "")); 120 | assert(lk_string_ends_with(lks, "")); 121 | assert(!lk_string_starts_with(lks, "a")); 122 | assert(!lk_string_ends_with(lks, "a")); 123 | lk_string_free(lks); 124 | 125 | lks = lk_string_new(" abc "); 126 | lk_string_trim(lks); 127 | assert(lk_string_sz_equal(lks, "abc")); 128 | 129 | lk_string_assign(lks, " \tabc \n "); 130 | lk_string_trim(lks); 131 | assert(lk_string_sz_equal(lks, "abc")); 132 | 133 | lk_string_assign(lks, " "); 134 | lk_string_trim(lks); 135 | assert(lk_string_sz_equal(lks, "")); 136 | lk_string_free(lks); 137 | 138 | lks = lk_string_new("/testsite/"); 139 | lk_string_chop_start(lks, "abc"); 140 | assert(lk_string_sz_equal(lks, "/testsite/")); 141 | lk_string_chop_start(lks, ""); 142 | assert(lk_string_sz_equal(lks, "/testsite/")); 143 | lk_string_chop_start(lks, "///"); 144 | assert(lk_string_sz_equal(lks, "/testsite/")); 145 | 146 | lk_string_chop_start(lks, "/"); 147 | assert(lk_string_sz_equal(lks, "testsite/")); 148 | lk_string_chop_start(lks, "test"); 149 | assert(lk_string_sz_equal(lks, "site/")); 150 | lk_string_chop_start(lks, "si1"); 151 | assert(lk_string_sz_equal(lks, "site/")); 152 | 153 | lk_string_assign(lks, "/testsite/"); 154 | lk_string_chop_end(lks, "abc"); 155 | assert(lk_string_sz_equal(lks, "/testsite/")); 156 | lk_string_chop_end(lks, ""); 157 | assert(lk_string_sz_equal(lks, "/testsite/")); 158 | lk_string_chop_end(lks, "///"); 159 | assert(lk_string_sz_equal(lks, "/testsite/")); 160 | 161 | lk_string_chop_end(lks, "/"); 162 | assert(lk_string_sz_equal(lks, "/testsite")); 163 | lk_string_chop_end(lks, "site"); 164 | assert(lk_string_sz_equal(lks, "/test")); 165 | lk_string_chop_end(lks, "1st"); 166 | assert(lk_string_sz_equal(lks, "/test")); 167 | 168 | lk_string_assign(lks, ""); 169 | lk_string_chop_start(lks, ""); 170 | assert(lk_string_sz_equal(lks, "")); 171 | lk_string_chop_end(lks, ""); 172 | assert(lk_string_sz_equal(lks, "")); 173 | lk_string_chop_start(lks, "abc"); 174 | assert(lk_string_sz_equal(lks, "")); 175 | lk_string_chop_end(lks, "def"); 176 | assert(lk_string_sz_equal(lks, "")); 177 | lk_string_free(lks); 178 | 179 | lks = lk_string_new("abc"); 180 | assert(lk_string_sz_equal(lks, "abc")); 181 | lk_string_append_char(lks, 'd'); 182 | assert(lk_string_sz_equal(lks, "abcd")); 183 | lk_string_append_char(lks, 'e'); 184 | lk_string_append_char(lks, 'f'); 185 | lk_string_append_char(lks, 'g'); 186 | assert(lk_string_sz_equal(lks, "abcdefg")); 187 | assert(lks->s_len == 7); 188 | lk_string_free(lks); 189 | 190 | lks = lk_string_new("abc"); 191 | lk_string_prepend(lks, "def "); 192 | assert(lk_string_sz_equal(lks, "def abc")); 193 | lk_string_free(lks); 194 | 195 | lks = lk_string_size_new(25); 196 | lk_string_prepend(lks, " World"); 197 | lk_string_prepend(lks, "Hello"); 198 | lk_string_prepend(lks, "1. "); 199 | assert(lk_string_sz_equal(lks, "1. Hello World")); 200 | lk_string_free(lks); 201 | 202 | lks = lk_string_size_new(2); 203 | lk_string_assign(lks, "Little Kitten Webserver written in C"); 204 | lk_string_prepend(lks, "A "); 205 | lk_string_append(lks, "."); 206 | assert(lk_string_sz_equal(lks, "A Little Kitten Webserver written in C.")); 207 | lk_string_free(lks); 208 | 209 | lks = lk_string_new(""); 210 | LKStringList *parts = lk_string_split(lks, "a"); 211 | assert(parts->items_len == 1); 212 | assert(lk_string_sz_equal(parts->items[0], "")); 213 | lk_stringlist_free(parts); 214 | 215 | parts = lk_string_split(lks, "abc"); 216 | assert(parts->items_len == 1); 217 | assert(lk_string_sz_equal(parts->items[0], "")); 218 | lk_stringlist_free(parts); 219 | 220 | parts = lk_string_split(lks, ""); 221 | assert(parts->items_len == 1); 222 | assert(lk_string_sz_equal(parts->items[0], "")); 223 | lk_stringlist_free(parts); 224 | 225 | lk_string_assign(lks, "abc def ghijkl"); 226 | parts = lk_string_split(lks, " "); 227 | assert(parts->items_len == 3); 228 | assert(lk_string_sz_equal(parts->items[0], "abc")); 229 | assert(lk_string_sz_equal(parts->items[1], "def")); 230 | assert(lk_string_sz_equal(parts->items[2], "ghijkl")); 231 | lk_stringlist_free(parts); 232 | parts = lk_string_split(lks, "--"); 233 | assert(parts->items_len == 1); 234 | assert(lk_string_sz_equal(parts->items[0], "abc def ghijkl")); 235 | lk_stringlist_free(parts); 236 | 237 | lk_string_assign(lks, "12 little kitten 12 web server 12 written in C"); 238 | parts = lk_string_split(lks, "12 "); 239 | assert(parts->items_len == 4); 240 | assert(lk_string_sz_equal(parts->items[0], "")); 241 | assert(lk_string_sz_equal(parts->items[1], "little kitten ")); 242 | assert(lk_string_sz_equal(parts->items[2], "web server ")); 243 | assert(lk_string_sz_equal(parts->items[3], "written in C")); 244 | lk_stringlist_free(parts); 245 | 246 | lk_string_assign(lks, "12 little kitten 12 web server 12 written in C 12 "); 247 | parts = lk_string_split(lks, "12 "); 248 | assert(parts->items_len == 5); 249 | assert(lk_string_sz_equal(parts->items[0], "")); 250 | assert(lk_string_sz_equal(parts->items[1], "little kitten ")); 251 | assert(lk_string_sz_equal(parts->items[2], "web server ")); 252 | assert(lk_string_sz_equal(parts->items[3], "written in C ")); 253 | assert(lk_string_sz_equal(parts->items[4], "")); 254 | lk_stringlist_free(parts); 255 | lk_string_free(lks); 256 | 257 | printf("Done.\n"); 258 | } 259 | 260 | void lkstringmap_test() { 261 | LKStringTable *st; 262 | void *v; 263 | 264 | printf("Running LKStringTable tests... "); 265 | st = lk_stringtable_new(); 266 | assert(st->items_len == 0); 267 | 268 | lk_stringtable_set(st, "abc", "ABC"); 269 | lk_stringtable_set(st, "def", "DEF"); 270 | lk_stringtable_set(st, "ghi", "GHI"); 271 | lk_stringtable_set(st, "hij", "HIJ"); 272 | lk_stringtable_set(st, "klm", "KLM"); 273 | assert(st->items_len == 5); 274 | 275 | lk_stringtable_remove(st, "ab"); 276 | assert(st->items_len == 5); 277 | lk_stringtable_remove(st, ""); 278 | assert(st->items_len == 5); 279 | lk_stringtable_remove(st, "ghi"); 280 | assert(st->items_len == 4); 281 | lk_stringtable_remove(st, "klm"); 282 | assert(st->items_len == 3); 283 | lk_stringtable_remove(st, "hij"); 284 | lk_stringtable_remove(st, "hij"); 285 | assert(st->items_len == 2); 286 | 287 | lk_stringtable_set(st, "123", "one two three"); 288 | lk_stringtable_set(st, " ", "space space"); 289 | lk_stringtable_set(st, "", "(blank)"); 290 | assert(st->items_len == 5); 291 | 292 | v = lk_stringtable_get(st, "abc"); 293 | assert(!strcmp(v, "ABC")); 294 | 295 | lk_stringtable_set(st, "abc", "A B C"); 296 | v = lk_stringtable_get(st, "abc"); 297 | assert(st->items_len == 5); 298 | assert(strcmp(v, "ABC")); 299 | assert(!strcmp(v, "A B C")); 300 | 301 | lk_stringtable_set(st, "abc ", "ABC(space)"); 302 | assert(st->items_len == 6); 303 | v = lk_stringtable_get(st, "abc "); 304 | assert(!strcmp(v, "ABC(space)")); 305 | v = lk_stringtable_get(st, "abc"); 306 | assert(!strcmp(v, "A B C")); 307 | 308 | v = lk_stringtable_get(st, "ABC"); 309 | assert(v == NULL); 310 | v = lk_stringtable_get(st, "123"); 311 | assert(!strcmp(v, "one two three")); 312 | v = lk_stringtable_get(st, " "); 313 | assert(!strcmp(v, "space space")); 314 | v = lk_stringtable_get(st, " "); 315 | assert(v == NULL); 316 | v = lk_stringtable_get(st, ""); 317 | assert(!strcmp(v, "(blank)")); 318 | 319 | lk_stringtable_free(st); 320 | printf("Done.\n"); 321 | } 322 | 323 | void lkbuffer_test() { 324 | LKBuffer *buf; 325 | 326 | printf("Running LKBuffer tests... "); 327 | buf = lk_buffer_new(0); 328 | assert(buf->bytes_size > 0); 329 | lk_buffer_free(buf); 330 | 331 | buf = lk_buffer_new(10); 332 | assert(buf->bytes_size == 10); 333 | lk_buffer_append(buf, "1234567", 7); 334 | assert(buf->bytes_len == 7); 335 | assert(buf->bytes_size == 10); 336 | assert(buf->bytes[0] == '1'); 337 | assert(buf->bytes[1] == '2'); 338 | assert(buf->bytes[2] == '3'); 339 | assert(buf->bytes[5] == '6'); 340 | 341 | lk_buffer_append(buf, "890", 3); 342 | assert(buf->bytes_len == 10); 343 | assert(buf->bytes_size == 10); 344 | assert(buf->bytes[7] == '8'); 345 | assert(buf->bytes[8] == '9'); 346 | assert(buf->bytes[9] == '0'); 347 | lk_buffer_free(buf); 348 | 349 | buf = lk_buffer_new(0); 350 | lk_buffer_append(buf, "12345", 5); 351 | assert(buf->bytes_len == 5); 352 | assert(!strncmp(buf->bytes+0, "12345", 5)); 353 | lk_buffer_append_sprintf(buf, "abc %d def %d", 123, 456); 354 | assert(buf->bytes_len == 20); 355 | assert(!strncmp(buf->bytes+5, "abc 123", 7)); 356 | assert(!strncmp(buf->bytes+5+8, "def 456", 7)); 357 | lk_buffer_free(buf); 358 | 359 | buf = lk_buffer_new(0); 360 | lk_buffer_append(buf, "12345", 5); 361 | assert(buf->bytes_len == 5); 362 | assert(!strncmp(buf->bytes+0, "12345", 5)); 363 | lk_buffer_append_sprintf(buf, "abc %d def %d", 123, 456); 364 | assert(buf->bytes_len == 20); 365 | assert(!strncmp(buf->bytes+5, "abc 123", 7)); 366 | assert(!strncmp(buf->bytes+5+8, "def 456", 7)); 367 | lk_buffer_free(buf); 368 | 369 | // Test buffer append on very large strings. 370 | buf = lk_buffer_new(0); 371 | char sbuf[LK_BUFSIZE_XXL]; 372 | memset(sbuf, 'a', sizeof(sbuf)); 373 | lk_buffer_append(buf, sbuf, sizeof(sbuf)); 374 | 375 | assert(buf->bytes_len == sizeof(sbuf)); 376 | for (int i=0; i < sizeof(sbuf); i++) { 377 | assert(buf->bytes[i] == 'a'); 378 | } 379 | 380 | lk_buffer_free(buf); 381 | 382 | buf = lk_buffer_new(0); 383 | sbuf[sizeof(sbuf)-1] = '\0'; 384 | lk_buffer_append_sprintf(buf, "buf: %s", sbuf); 385 | assert(buf->bytes_len == sizeof(sbuf)-1 + 5); 386 | assert(strncmp(buf->bytes, "buf: aaaaa", 10) == 0); 387 | assert(buf->bytes[buf->bytes_len-1] == 'a'); 388 | assert(buf->bytes[buf->bytes_len-2] == 'a'); 389 | assert(buf->bytes[buf->bytes_len-3] == 'a'); 390 | lk_buffer_free(buf); 391 | 392 | printf("Done.\n"); 393 | } 394 | 395 | void lkstringlist_test() { 396 | LKStringList *sl; 397 | 398 | printf("Running LKStringList tests... "); 399 | sl = lk_stringlist_new(); 400 | assert(sl->items_len == 0); 401 | lk_stringlist_append(sl, "abc"); 402 | lk_stringlist_append_sprintf(sl, "Hello %s", "abc"); 403 | LKString *lks123 = lk_string_new("123"); 404 | lk_stringlist_append_lkstring(sl, lks123); 405 | assert(sl->items_len == 3); 406 | 407 | LKString *s0 = lk_stringlist_get(sl, 0); 408 | LKString *s1 = lk_stringlist_get(sl, 1); 409 | LKString *s2 = lk_stringlist_get(sl, 2); 410 | LKString *s3 = lk_stringlist_get(sl, 3); 411 | assert(s0 != NULL); 412 | assert(s1 != NULL); 413 | assert(s2 != NULL); 414 | assert(s3 == NULL); 415 | assert(!strcmp(s0->s, "abc")); 416 | assert(!strcmp(s1->s, "Hello abc")); 417 | assert(!strcmp(s2->s, "123")); 418 | 419 | lk_stringlist_remove(sl, 0); // remove "abc" 420 | assert(sl->items_len == 2); 421 | s0 = lk_stringlist_get(sl, 0); 422 | s1 = lk_stringlist_get(sl, 1); 423 | s2 = lk_stringlist_get(sl, 2); 424 | assert(s0 != NULL); 425 | assert(s1 != NULL); 426 | assert(s2 == NULL); 427 | assert(!strcmp(s0->s, "Hello abc")); 428 | assert(!strcmp(s1->s, "123")); 429 | 430 | lk_stringlist_remove(sl, 1); // remove "123" 431 | assert(sl->items_len == 1); 432 | s0 = lk_stringlist_get(sl, 0); 433 | s1 = lk_stringlist_get(sl, 1); 434 | s2 = lk_stringlist_get(sl, 2); 435 | assert(s0 != NULL); 436 | assert(s1 == NULL); 437 | assert(s2 == NULL); 438 | assert(!strcmp(s0->s, "Hello abc")); 439 | 440 | lk_stringlist_free(sl); 441 | printf("Done.\n"); 442 | } 443 | 444 | void lkreflist_test() { 445 | printf("Running LKRefList tests... "); 446 | 447 | LKString *abc = lk_string_new("abc"); 448 | LKString *def = lk_string_new("def"); 449 | LKString *ghi = lk_string_new("ghi"); 450 | LKString *jkl = lk_string_new("jkl"); 451 | LKString *mno = lk_string_new("mno"); 452 | LKRefList *l = lk_reflist_new(); 453 | lk_reflist_append(l, abc); 454 | lk_reflist_append(l, def); 455 | lk_reflist_append(l, ghi); 456 | lk_reflist_append(l, jkl); 457 | lk_reflist_append(l, mno); 458 | assert(l->items_len == 5); 459 | 460 | LKString *s = lk_reflist_get(l, 0); 461 | assert(lk_string_sz_equal(s, "abc")); 462 | s = lk_reflist_get(l, 5); 463 | assert(s == NULL); 464 | s = lk_reflist_get(l, 4); 465 | assert(lk_string_sz_equal(s, "mno")); 466 | s = lk_reflist_get(l, 3); 467 | assert(lk_string_sz_equal(s, "jkl")); 468 | lk_reflist_remove(l, 3); 469 | assert(l->items_len == 4); 470 | s = lk_reflist_get(l, 3); 471 | assert(lk_string_sz_equal(s, "mno")); 472 | 473 | lk_string_free(abc); 474 | lk_string_free(def); 475 | lk_string_free(ghi); 476 | lk_string_free(jkl); 477 | lk_string_free(mno); 478 | lk_reflist_free(l); 479 | printf("Done.\n"); 480 | } 481 | 482 | void lkconfig_test() { 483 | printf("Running LKConfig tests... \n"); 484 | 485 | LKConfig *cfg = lk_config_new(); 486 | lk_config_read_configfile(cfg, "lktest.conf"); 487 | assert(cfg != NULL); 488 | lk_config_print(cfg); 489 | lk_config_free(cfg); 490 | 491 | printf("Done.\n"); 492 | } 493 | 494 | -------------------------------------------------------------------------------- /backup/tserv.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "lknet.h" 19 | 20 | #define LISTEN_PORT "5000" 21 | 22 | typedef struct clientctx { 23 | int sock; // client socket 24 | LKSocketReader *sr; // input buffer for reading lines 25 | LKHttpRequestParser *reqparser; // parser for httprequest 26 | LKString *partial_line; 27 | LKHttpResponse *resp; // http response to be sent 28 | struct clientctx *next; // link to next client 29 | } clientctx_t; 30 | 31 | clientctx_t *clientctx_new(int sock); 32 | void clientctx_free(clientctx_t *ctx); 33 | void add_clientctx(clientctx_t **pphead, clientctx_t *ctx); 34 | void remove_clientctx(clientctx_t **pphead, int sock); 35 | 36 | void print_err(char *s); 37 | void exit_err(char *s); 38 | int ends_with_newline(char *s); 39 | char *append_string(char *dst, char *s); 40 | void *addrinfo_sin_addr(struct addrinfo *addr); 41 | void handle_sigint(int sig); 42 | void handle_sigchld(int sig); 43 | 44 | void read_request_from_client(int clientfd); 45 | void process_line(clientctx_t *ctx, char *line); 46 | void process_req(LKHttpRequest *req, LKHttpResponse *resp); 47 | int is_valid_http_method(char *method); 48 | char *fileext(char *filepath); 49 | 50 | void send_response_to_client(int clientfd); 51 | void send_buf_bytes(int sock, LKBuffer *buf); 52 | void terminate_client_session(int clientfd); 53 | 54 | void serve_file_handler(LKHttpRequest *req, LKHttpResponse *resp); 55 | 56 | clientctx_t *ctxhead = NULL; 57 | fd_set readfds; 58 | fd_set writefds; 59 | 60 | lk_http_handler_func default_handler = serve_file_handler; 61 | 62 | int main(int argc, char *argv[]) { 63 | int z; 64 | 65 | signal(SIGINT, handle_sigint); 66 | signal(SIGCHLD, handle_sigchld); 67 | 68 | // Get this server's address. 69 | struct addrinfo hints, *servaddr; 70 | memset(&hints, 0, sizeof(hints)); 71 | hints.ai_family = AF_INET; 72 | hints.ai_socktype = SOCK_STREAM; 73 | hints.ai_flags = AI_PASSIVE; 74 | z = getaddrinfo("localhost", LISTEN_PORT, &hints, &servaddr); 75 | if (z != 0) { 76 | exit_err("getaddrinfo()"); 77 | } 78 | 79 | // Get human readable IP address string in servipstr. 80 | char servipstr[INET6_ADDRSTRLEN]; 81 | const char *pz = inet_ntop(servaddr->ai_family, addrinfo_sin_addr(servaddr), servipstr, sizeof(servipstr)); 82 | if (pz ==NULL) { 83 | exit_err("inet_ntop()"); 84 | } 85 | 86 | int s0 = socket(servaddr->ai_family, servaddr->ai_socktype, servaddr->ai_protocol); 87 | if (s0 == -1) { 88 | exit_err("socket()"); 89 | } 90 | 91 | int yes=1; 92 | z = setsockopt(s0, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); 93 | if (s0 == -1) { 94 | exit_err("setsockopt(SO_REUSEADDR)"); 95 | } 96 | 97 | z = bind(s0, servaddr->ai_addr, servaddr->ai_addrlen); 98 | if (z != 0) { 99 | exit_err("bind()"); 100 | } 101 | 102 | freeaddrinfo(servaddr); 103 | servaddr = NULL; 104 | 105 | z = listen(s0, 5); 106 | if (z != 0) { 107 | exit_err("listen()"); 108 | } 109 | printf("Listening on %s:%s...\n", servipstr, LISTEN_PORT); 110 | 111 | FD_ZERO(&readfds); 112 | FD_ZERO(&writefds); 113 | 114 | FD_SET(s0, &readfds); 115 | int maxfd = s0; 116 | 117 | while (1) { 118 | // readfds contain the master list of read sockets 119 | fd_set cur_readfds = readfds; 120 | fd_set cur_writefds = writefds; 121 | z = select(maxfd+1, &cur_readfds, &cur_writefds, NULL, NULL); 122 | if (z == -1) { 123 | exit_err("select()"); 124 | } 125 | if (z == 0) { 126 | // timeout returned 127 | continue; 128 | } 129 | 130 | // fds now contain list of clients with data available to be read. 131 | for (int i=0; i <= maxfd; i++) { 132 | if (FD_ISSET(i, &cur_readfds)) { 133 | // New client connection 134 | if (i == s0) { 135 | struct sockaddr_in a; 136 | socklen_t a_len = sizeof(a); 137 | int newclientsock = accept(s0, (struct sockaddr*)&a, &a_len); 138 | if (newclientsock == -1) { 139 | print_err("accept()"); 140 | continue; 141 | } 142 | 143 | // Add new client socket to list of read sockets. 144 | FD_SET(newclientsock, &readfds); 145 | if (newclientsock > maxfd) { 146 | maxfd = newclientsock; 147 | } 148 | 149 | printf("read fd: %d\n", newclientsock); 150 | clientctx_t *ctx = clientctx_new(newclientsock); 151 | add_clientctx(&ctxhead, ctx); 152 | continue; 153 | } else { 154 | // i contains client socket with data available to be read. 155 | int clientfd = i; 156 | read_request_from_client(clientfd); 157 | } 158 | } else if (FD_ISSET(i, &cur_writefds)) { 159 | printf("write fd %d\n", i); 160 | // i contains client socket ready to be written to. 161 | int clientfd = i; 162 | send_response_to_client(clientfd); 163 | } 164 | } 165 | } // while (1) 166 | 167 | // Code doesn't reach here. 168 | close(s0); 169 | return 0; 170 | } 171 | 172 | clientctx_t *clientctx_new(int sock) { 173 | clientctx_t *ctx = malloc(sizeof(clientctx_t)); 174 | ctx->sock = sock; 175 | ctx->sr = lk_socketreader_new(sock, 0); 176 | ctx->reqparser = lk_httprequestparser_new(); 177 | ctx->partial_line = lk_string_new(""); 178 | ctx->resp = NULL; 179 | ctx->next = NULL; 180 | return ctx; 181 | } 182 | 183 | void clientctx_free(clientctx_t *ctx) { 184 | lk_socketreader_free(ctx->sr); 185 | lk_httprequestparser_free(ctx->reqparser); 186 | lk_string_free(ctx->partial_line); 187 | if (ctx->resp) { 188 | lk_httpresponse_free(ctx->resp); 189 | } 190 | 191 | ctx->sr = NULL; 192 | ctx->reqparser = NULL; 193 | ctx->resp = NULL; 194 | ctx->partial_line = NULL; 195 | ctx->next = NULL; 196 | free(ctx); 197 | } 198 | 199 | // Add ctx to end of clientctx linked list. Skip if ctx sock already in list. 200 | void add_clientctx(clientctx_t **pphead, clientctx_t *ctx) { 201 | assert(pphead != NULL); 202 | 203 | if (*pphead == NULL) { 204 | // first client 205 | *pphead = ctx; 206 | } else { 207 | // add client to end of clients list 208 | clientctx_t *p = *pphead; 209 | while (p->next != NULL) { 210 | // ctx sock already exists 211 | if (p->sock == ctx->sock) { 212 | return; 213 | } 214 | p = p->next; 215 | } 216 | p->next = ctx; 217 | } 218 | } 219 | 220 | // Remove ctx sock from clientctx linked list. 221 | void remove_clientctx(clientctx_t **pphead, int sock) { 222 | assert(pphead != NULL); 223 | 224 | if (*pphead == NULL) { 225 | return; 226 | } 227 | // remove head ctx 228 | if ((*pphead)->sock == sock) { 229 | clientctx_t *tmp = *pphead; 230 | *pphead = (*pphead)->next; 231 | clientctx_free(tmp); 232 | return; 233 | } 234 | 235 | clientctx_t *p = *pphead; 236 | clientctx_t *prev = NULL; 237 | while (p != NULL) { 238 | if (p->sock == sock) { 239 | assert(prev != NULL); 240 | prev->next = p->next; 241 | clientctx_free(p); 242 | return; 243 | } 244 | 245 | prev = p; 246 | p = p->next; 247 | } 248 | } 249 | 250 | // Print the last error message corresponding to errno. 251 | void print_err(char *s) { 252 | fprintf(stderr, "%s: %s\n", s, strerror(errno)); 253 | } 254 | void exit_err(char *s) { 255 | print_err(s); 256 | exit(1); 257 | } 258 | 259 | // Return whether string ends with \n char. 260 | int ends_with_newline(char *s) { 261 | int slen = strlen(s); 262 | if (slen == 0) { 263 | return 0; 264 | } 265 | if (s[slen-1] == '\n') { 266 | return 1; 267 | } 268 | return 0; 269 | } 270 | 271 | // Append s to dst, returning new ptr to dst. 272 | char *append_string(char *dst, char *s) { 273 | int slen = strlen(s); 274 | if (slen == 0) { 275 | return dst; 276 | } 277 | 278 | int dstlen = strlen(dst); 279 | dst = realloc(dst, dstlen + slen + 1); 280 | strncpy(dst + dstlen, s, slen+1); 281 | assert(dst[dstlen+slen] == '\0'); 282 | 283 | return dst; 284 | } 285 | 286 | // Return sin_addr or sin6_addr depending on address family. 287 | void *addrinfo_sin_addr(struct addrinfo *addr) { 288 | // addr->ai_addr is either struct sockaddr_in* or sockaddr_in6* depending on ai_family 289 | if (addr->ai_family == AF_INET) { 290 | struct sockaddr_in *p = (struct sockaddr_in*) addr->ai_addr; 291 | return &(p->sin_addr); 292 | } else { 293 | struct sockaddr_in6 *p = (struct sockaddr_in6*) addr->ai_addr; 294 | return &(p->sin6_addr); 295 | } 296 | } 297 | 298 | void handle_sigint(int sig) { 299 | printf("SIGINT received\n"); 300 | fflush(stdout); 301 | exit(0); 302 | } 303 | 304 | void handle_sigchld(int sig) { 305 | int tmp_errno = errno; 306 | while (waitpid(-1, NULL, WNOHANG) > 0) { 307 | } 308 | errno = tmp_errno; 309 | } 310 | 311 | void read_request_from_client(int clientfd) { 312 | clientctx_t *ctx = ctxhead; 313 | while (ctx != NULL && ctx->sock != clientfd) { 314 | ctx = ctx->next; 315 | } 316 | if (ctx == NULL) { 317 | printf("read_request_from_client() clientfd %d not in clients list\n", clientfd); 318 | return; 319 | } 320 | assert(ctx->sock == clientfd); 321 | assert(ctx->sr->sock == clientfd); 322 | 323 | while (1) { 324 | char buf[1000]; 325 | int z = lk_socketreader_readline(ctx->sr, buf, sizeof buf); 326 | if (z == 0) { 327 | break; 328 | } 329 | if (z == -1) { 330 | print_err("lksocketreader_readline()"); 331 | break; 332 | } 333 | assert(buf[z] == '\0'); 334 | 335 | // If there's a previous partial line, combine it with current line. 336 | if (ctx->partial_line->s_len > 0) { 337 | lk_string_append(ctx->partial_line, buf); 338 | if (ends_with_newline(ctx->partial_line->s)) { 339 | process_line(ctx, ctx->partial_line->s); 340 | lk_string_assign(ctx->partial_line, ""); 341 | } 342 | continue; 343 | } 344 | 345 | // If current line is only partial line (not newline terminated), remember it for 346 | // next read. 347 | if (!ends_with_newline(buf)) { 348 | lk_string_assign(ctx->partial_line, buf); 349 | continue; 350 | } 351 | 352 | // Current line is complete. 353 | process_line(ctx, buf); 354 | } 355 | } 356 | 357 | void process_line(clientctx_t *ctx, char *line) { 358 | lk_httprequestparser_parse_line(ctx->reqparser, line); 359 | if (ctx->reqparser->body_complete) { 360 | LKHttpRequest *req = ctx->reqparser->req; 361 | printf("127.0.0.1 [11/Mar/2023 14:05:46] \"%s %s HTTP/1.1\" %d\n", 362 | req->method->s, req->uri->s, 200); 363 | 364 | ctx->resp = lk_httpresponse_new(); 365 | if (default_handler) { 366 | (*default_handler)(req, ctx->resp); 367 | } else { 368 | // If no handler, return default 200 OK response. 369 | ctx->resp->status = 200; 370 | lk_string_assign(ctx->resp->statustext, "Default OK"); 371 | lk_string_assign(ctx->resp->version, "HTTP/1.0"); 372 | lk_httpresponse_gen_headbuf(ctx->resp); 373 | } 374 | FD_SET(ctx->sock, &writefds); 375 | return; 376 | } 377 | } 378 | 379 | // Generate an http response to an http request. 380 | void serve_file_handler(LKHttpRequest *req, LKHttpResponse *resp) { 381 | int z; 382 | 383 | static char *html_error_start = 384 | "\n" 385 | "\n" 386 | "Error response\n" 387 | "

Error response

\n"; 388 | static char *html_error_end = 389 | "\n"; 390 | 391 | static char *html_sample = 392 | "\n" 393 | "\n" 394 | "Little Kitten\n" 395 | "

Little Kitten webserver

\n" 396 | "

Hello Little Kitten!

\n" 397 | "\n"; 398 | 399 | char *method = req->method->s; 400 | char *uri = req->uri->s; 401 | 402 | //$$ instead of this hack, check index.html, index.html, default.html if exists 403 | if (!strcmp(uri, "/")) { 404 | uri = "/index.html"; 405 | } 406 | 407 | if (!is_valid_http_method(method)) { 408 | resp->status = 501; 409 | lk_string_sprintf(resp->statustext, "Unsupported method ('%s')", method); 410 | lk_string_assign(resp->version, "HTTP/1.0"); 411 | 412 | lk_buffer_append(resp->body, html_error_start, strlen(html_error_start)); 413 | lk_buffer_append_sprintf(resp->body, "

%d %s

\n", resp->status, resp->statustext->s); 414 | lk_buffer_append(resp->body, html_error_end, strlen(html_error_end)); 415 | lk_httpresponse_gen_headbuf(resp); 416 | return; 417 | } 418 | if (!strcmp(method, "GET")) { 419 | resp->status = 200; 420 | lk_string_assign(resp->statustext, "OK"); 421 | lk_string_assign(resp->version, "HTTP/1.0"); 422 | 423 | // /littlekitten sample page 424 | if (!strcmp(uri, "/littlekitten")) { 425 | lk_httpresponse_add_header(resp, "Content-Type", "text/html"); 426 | lk_buffer_append(resp->body, html_sample, strlen(html_sample)); 427 | lk_httpresponse_gen_headbuf(resp); 428 | return; 429 | } 430 | 431 | LKString *content_type = lk_string_new(""); 432 | lk_string_sprintf(content_type, "text/%s;", fileext(uri)); 433 | lk_httpresponse_add_header(resp, "Content-Type", content_type->s); 434 | lk_string_free(content_type); 435 | 436 | // uri_filepath = current dir + uri 437 | // Ex. "/path/to" + "/index.html" 438 | char *tmp_currentdir = get_current_dir_name(); 439 | LKString *uri_filepath = lk_string_new(tmp_currentdir); 440 | lk_string_append(uri_filepath, uri); 441 | free(tmp_currentdir); 442 | 443 | printf("uri_filepath: %s\n", uri_filepath->s); 444 | z = readfile(uri_filepath->s, resp->body); 445 | if (z == -1) { 446 | print_err("readfile()"); 447 | } 448 | lk_string_free(uri_filepath); 449 | 450 | lk_httpresponse_gen_headbuf(resp); 451 | return; 452 | } 453 | 454 | // Return default response 200 OK. 455 | resp->status = 200; 456 | lk_string_assign(resp->statustext, "Default OK"); 457 | lk_string_assign(resp->version, "HTTP/1.0"); 458 | lk_httpresponse_gen_headbuf(resp); 459 | } 460 | 461 | int is_valid_http_method(char *method) { 462 | if (method == NULL) { 463 | return 0; 464 | } 465 | 466 | if (!strcasecmp(method, "GET") || 467 | !strcasecmp(method, "POST") || 468 | !strcasecmp(method, "PUT") || 469 | !strcasecmp(method, "DELETE") || 470 | !strcasecmp(method, "HEAD")) { 471 | return 1; 472 | } 473 | 474 | return 0; 475 | } 476 | 477 | // Return ptr to start of file extension within filepath. 478 | // Ex. "path/to/index.html" returns "index.html" 479 | char *fileext(char *filepath) { 480 | int filepath_len = strlen(filepath); 481 | // filepath of "" returns ext of "". 482 | if (filepath_len == 0) { 483 | return filepath; 484 | } 485 | 486 | char *p = filepath + strlen(filepath) - 1; 487 | while (p >= filepath) { 488 | if (*p == '.') { 489 | return p+1; 490 | } 491 | p--; 492 | } 493 | return filepath; 494 | } 495 | 496 | void close_client(clientctx_t *ctx) { 497 | // Remove client from client list. 498 | clientctx_t *prev = NULL; 499 | clientctx_t *p = ctxhead; 500 | while (p != NULL) { 501 | if (p == ctx) { 502 | if (prev == NULL) { 503 | ctxhead = p->next; 504 | } else { 505 | prev->next = p->next; 506 | } 507 | break; 508 | } 509 | prev = p; 510 | p = p->next; 511 | } 512 | 513 | // Free ctx resources 514 | close(ctx->sock); 515 | clientctx_free(ctx); 516 | } 517 | 518 | int is_supported_http_method(char *method) { 519 | if (method == NULL) { 520 | return 0; 521 | } 522 | 523 | if (!strcasecmp(method, "GET") || 524 | !strcasecmp(method, "HEAD")) { 525 | return 1; 526 | } 527 | 528 | return 0; 529 | } 530 | 531 | void send_response_to_client(int clientfd) { 532 | clientctx_t *ctx = ctxhead; 533 | while (ctx != NULL && ctx->sock != clientfd) { 534 | ctx = ctx->next; 535 | } 536 | if (ctx == NULL) { 537 | printf("send_response_to_client() clientfd %d not in clients list\n", clientfd); 538 | return; 539 | } 540 | assert(ctx->sock == clientfd); 541 | assert(ctx->sr->sock == clientfd); 542 | assert(ctx->resp != NULL); 543 | assert(ctx->resp->head != NULL); 544 | 545 | LKHttpResponse *resp = ctx->resp; 546 | 547 | // Send as much response bytes as the client will receive. 548 | // Send response head bytes first, then response body bytes. 549 | if (resp->head->bytes_cur < resp->head->bytes_len) { 550 | send_buf_bytes(ctx->sock, resp->head); 551 | } else if (resp->body->bytes_cur < resp->body->bytes_len) { 552 | send_buf_bytes(ctx->sock, resp->body); 553 | } else { 554 | // Completed sending http response. 555 | terminate_client_session(ctx->sock); 556 | } 557 | } 558 | 559 | // Send buf data into sock, keeping track of last buffer position. 560 | // Used to cumulatively send buffer data with multiple sends. 561 | void send_buf_bytes(int sock, LKBuffer *buf) { 562 | size_t nsent = 0; 563 | int z = sock_send(sock, 564 | buf->bytes + buf->bytes_cur, 565 | buf->bytes_len - buf->bytes_cur, 566 | &nsent); 567 | if (z == -1 && errno == EPIPE) { 568 | // client socket was shutdown 569 | terminate_client_session(sock); 570 | return; 571 | } 572 | buf->bytes_cur += nsent; 573 | } 574 | 575 | // Disconnect from client. 576 | void terminate_client_session(int clientfd) { 577 | printf("terminate_client fd %d\n", clientfd); 578 | 579 | // Remove select() read and write I/O 580 | FD_CLR(clientfd, &readfds); 581 | FD_CLR(clientfd, &writefds); 582 | 583 | // Close read and writes. 584 | int z= shutdown(clientfd, SHUT_RDWR); 585 | if (z == -1) { 586 | print_err("shutdown()"); 587 | } 588 | z = close(clientfd); 589 | if (z == -1) { 590 | print_err("close()"); 591 | } 592 | // Remove client from clientctx list. 593 | remove_clientctx(&ctxhead, clientfd); 594 | } 595 | 596 | 597 | -------------------------------------------------------------------------------- /lknet.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "lklib.h" 12 | #include "lknet.h" 13 | 14 | int lk_open_listen_socket(char *host, char *port, int backlog, struct sockaddr *psa) { 15 | int z; 16 | 17 | struct addrinfo hints, *ai; 18 | memset(&hints, 0, sizeof(hints)); 19 | hints.ai_family = AF_INET; 20 | hints.ai_socktype = SOCK_STREAM; 21 | hints.ai_flags = AI_PASSIVE; 22 | z = getaddrinfo(host, port, &hints, &ai); 23 | if (z != 0) { 24 | printf("getaddrinfo(): %s\n", gai_strerror(z)); 25 | errno = EINVAL; 26 | return -1; 27 | } 28 | if (psa != NULL) { 29 | memcpy(psa, ai->ai_addr, ai->ai_addrlen); 30 | } 31 | 32 | int fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); 33 | if (fd == -1) { 34 | lk_print_err("socket()"); 35 | z = -1; 36 | goto error_return; 37 | } 38 | int yes=1; 39 | z = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); 40 | if (z == -1) { 41 | lk_print_err("setsockopt()"); 42 | goto error_return; 43 | } 44 | z = bind(fd, ai->ai_addr, ai->ai_addrlen); 45 | if (z == -1) { 46 | lk_print_err("bind()"); 47 | goto error_return; 48 | } 49 | z = listen(fd, backlog); 50 | if (z == -1) { 51 | lk_print_err("listen()"); 52 | goto error_return; 53 | } 54 | 55 | freeaddrinfo(ai); 56 | return fd; 57 | 58 | error_return: 59 | freeaddrinfo(ai); 60 | return z; 61 | } 62 | 63 | // You can specify the host and port in two ways: 64 | // 1. host="littlekitten.xyz", port="5001" (separate host and port) 65 | // 2. host="littlekitten.xyz:5001", port="" (combine host:port in host parameter) 66 | int lk_open_connect_socket(char *host, char *port, struct sockaddr *psa) { 67 | int z; 68 | 69 | // If host is of the form "host:port", parse it. 70 | LKString *lksport = lk_string_new(host); 71 | LKStringList *ss = lk_string_split(lksport, ":"); 72 | if (ss->items_len == 2) { 73 | host = ss->items[0]->s; 74 | port = ss->items[1]->s; 75 | } 76 | 77 | struct addrinfo hints, *ai; 78 | memset(&hints, 0, sizeof(hints)); 79 | hints.ai_family = AF_INET; 80 | hints.ai_socktype = SOCK_STREAM; 81 | hints.ai_flags = AI_PASSIVE; 82 | z = getaddrinfo(host, port, &hints, &ai); 83 | if (z != 0) { 84 | printf("getaddrinfo(): %s\n", gai_strerror(z)); 85 | errno = EINVAL; 86 | return -1; 87 | } 88 | if (psa != NULL) { 89 | memcpy(psa, ai->ai_addr, ai->ai_addrlen); 90 | } 91 | 92 | lk_stringlist_free(ss); 93 | lk_string_free(lksport); 94 | 95 | int fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); 96 | if (fd == -1) { 97 | lk_print_err("socket()"); 98 | z = -1; 99 | goto error_return; 100 | } 101 | int yes=1; 102 | z = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); 103 | if (z == -1) { 104 | lk_print_err("setsockopt()"); 105 | goto error_return; 106 | } 107 | z = connect(fd, ai->ai_addr, ai->ai_addrlen); 108 | if (z == -1) { 109 | lk_print_err("connect()"); 110 | goto error_return; 111 | } 112 | 113 | freeaddrinfo(ai); 114 | return fd; 115 | 116 | error_return: 117 | freeaddrinfo(ai); 118 | return z; 119 | } 120 | 121 | 122 | // Remove trailing CRLF or LF (\n) from string. 123 | void lk_chomp(char* s) { 124 | int slen = strlen(s); 125 | for (int i=slen-1; i >= 0; i--) { 126 | if (s[i] != '\n' && s[i] != '\r') { 127 | break; 128 | } 129 | // Replace \n and \r with null chars. 130 | s[i] = '\0'; 131 | } 132 | } 133 | 134 | void lk_set_sock_timeout(int sock, int nsecs, int ms) { 135 | struct timeval tv; 136 | tv.tv_sec = nsecs; 137 | tv.tv_usec = ms * 1000; // convert milliseconds to microseconds 138 | setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); 139 | } 140 | 141 | void lk_set_sock_nonblocking(int sock) { 142 | fcntl(sock, F_SETFL, O_NONBLOCK); 143 | } 144 | 145 | // Return sin_addr or sin6_addr depending on address family. 146 | void *sockaddr_sin_addr(struct sockaddr *sa) { 147 | // addr->ai_addr is either struct sockaddr_in* or sockaddr_in6* depending on ai_family 148 | if (sa->sa_family == AF_INET) { 149 | struct sockaddr_in *p = (struct sockaddr_in*) sa; 150 | return &(p->sin_addr); 151 | } else { 152 | struct sockaddr_in6 *p = (struct sockaddr_in6*) sa; 153 | return &(p->sin6_addr); 154 | } 155 | } 156 | // Return sin_port or sin6_port depending on address family. 157 | unsigned short lk_get_sockaddr_port(struct sockaddr *sa) { 158 | // addr->ai_addr is either struct sockaddr_in* or sockaddr_in6* depending on ai_family 159 | if (sa->sa_family == AF_INET) { 160 | struct sockaddr_in *p = (struct sockaddr_in*) sa; 161 | return ntohs(p->sin_port); 162 | } else { 163 | struct sockaddr_in6 *p = (struct sockaddr_in6*) sa; 164 | return ntohs(p->sin6_port); 165 | } 166 | } 167 | 168 | // Return human readable IP address from sockaddr 169 | LKString *lk_get_ipaddr_string(struct sockaddr *sa) { 170 | char servipstr[INET6_ADDRSTRLEN]; 171 | const char *pz = inet_ntop(sa->sa_family, sockaddr_sin_addr(sa), 172 | servipstr, sizeof(servipstr)); 173 | if (pz == NULL) { 174 | lk_print_err("inet_ntop()"); 175 | return lk_string_new(""); 176 | } 177 | return lk_string_new(servipstr); 178 | } 179 | 180 | int nonblocking_error(int z) { 181 | if (z == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { 182 | return 1; 183 | } 184 | return 0; 185 | } 186 | 187 | // Read nonblocking fd count bytes to buf. 188 | // Returns one of the following: 189 | // 1 (Z_OPEN) for socket open 190 | // 0 (Z_EOF) for EOF 191 | // -1 (Z_ERR) for error 192 | // -2 (Z_BLOCK) for blocked socket (no data) 193 | // On return, nbytes contains the number of bytes read. 194 | int lk_read(int fd, FDType fd_type, LKBuffer *buf, size_t count, size_t *nbytes) { 195 | int z; 196 | char readbuf[LK_BUFSIZE_LARGE]; 197 | 198 | size_t nread = 0; 199 | while (nread < count) { 200 | // read minimum of sizeof(readbuf) and count-nread 201 | int nblock = count-nread; 202 | if (nblock > sizeof(readbuf)) nblock = sizeof(readbuf); 203 | 204 | if (fd_type == FD_SOCK) { 205 | z = recv(fd, readbuf, nblock, MSG_DONTWAIT); 206 | } else { 207 | z = read(fd, readbuf, nblock); 208 | } 209 | // EOF 210 | if (z == 0) { 211 | z = Z_EOF; 212 | break; 213 | } 214 | // interrupt occured during read, retry read. 215 | if (z == -1 && errno == EINTR) { 216 | continue; 217 | } 218 | if (z == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { 219 | z = Z_BLOCK; 220 | break; 221 | } 222 | if (z == -1) { 223 | // errno is set to EPIPE if socket was shutdown 224 | z = Z_ERR; 225 | break; 226 | } 227 | assert(z > 0); 228 | lk_buffer_append(buf, readbuf, z); 229 | nread += z; 230 | } 231 | if (z > 0) { 232 | z = Z_OPEN; 233 | } 234 | *nbytes = nread; 235 | return z; 236 | } 237 | int lk_read_sock(int fd, LKBuffer *buf, size_t count, size_t *nbytes) { 238 | return lk_read(fd, FD_SOCK, buf, count, nbytes); 239 | } 240 | int lk_read_file(int fd, LKBuffer *buf, size_t count, size_t *nbytes) { 241 | return lk_read(fd, FD_FILE, buf, count, nbytes); 242 | } 243 | 244 | // Read all available nonblocking fd bytes to buffer. 245 | // Returns one of the following: 246 | // 0 (Z_EOF) for EOF 247 | // -1 (Z_ERR) for error 248 | // -2 (Z_BLOCK) for blocked socket (no data) 249 | // 250 | // Note: This keeps track of last buf position read. 251 | // Used to cumulatively read data into buf. 252 | int lk_read_all(int fd, FDType fd_type, LKBuffer *buf) { 253 | int z; 254 | char readbuf[LK_BUFSIZE_LARGE]; 255 | while (1) { 256 | if (fd_type == FD_SOCK) { 257 | z = recv(fd, readbuf, sizeof(readbuf), MSG_DONTWAIT); 258 | } else { 259 | z = read(fd, readbuf, sizeof(readbuf)); 260 | } 261 | // EOF 262 | if (z == 0) { 263 | z = Z_EOF; 264 | break; 265 | } 266 | if (z == -1 && errno == EINTR) { 267 | continue; 268 | } 269 | if (z == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { 270 | z = Z_BLOCK; 271 | break; 272 | } 273 | if (z == -1) { 274 | // errno is set to EPIPE if socket was shutdown 275 | z = Z_ERR; 276 | break; 277 | } 278 | assert(z > 0); 279 | lk_buffer_append(buf, readbuf, z); 280 | } 281 | assert(z <= 0); 282 | return z; 283 | } 284 | int lk_read_all_sock(int fd, LKBuffer *buf) { 285 | return lk_read_all(fd, FD_SOCK, buf); 286 | } 287 | int lk_read_all_file(int fd, LKBuffer *buf) { 288 | return lk_read_all(fd, FD_FILE, buf); 289 | } 290 | 291 | 292 | // Write count buf bytes to nonblocking fd. 293 | // Returns one of the following: 294 | // 1 (Z_OPEN) for socket open 295 | // -1 (Z_ERR) for error 296 | // -2 (Z_BLOCK) for blocked socket (no data) 297 | // On return, nbytes contains the number of bytes written. 298 | // 299 | // Note: use open(O_NONBLOCK) to open nonblocking file. 300 | int lk_write(int fd, FDType fd_type, LKBuffer *buf, size_t count, size_t *nbytes) { 301 | int z; 302 | if (count > buf->bytes_len) { 303 | count = buf->bytes_len; 304 | } 305 | size_t nwrite = 0; 306 | while (nwrite < count) { 307 | if (fd_type == FD_SOCK) { 308 | z = send(fd, buf->bytes+nwrite, count-nwrite, MSG_DONTWAIT | MSG_NOSIGNAL); 309 | } else { 310 | z = write(fd, buf->bytes+nwrite, count-nwrite); 311 | } 312 | // interrupt occured during read, retry read. 313 | if (z == -1 && errno == EINTR) { 314 | continue; 315 | } 316 | if (z == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { 317 | z = Z_BLOCK; 318 | break; 319 | } 320 | if (z == -1) { 321 | // errno is set to EPIPE if socket was shutdown 322 | z = Z_ERR; 323 | break; 324 | } 325 | assert(z >= 0); 326 | nwrite += z; 327 | } 328 | if (z >= 0) { 329 | z = Z_OPEN; 330 | } 331 | *nbytes = nwrite; 332 | return z; 333 | } 334 | int lk_write_sock(int fd, LKBuffer *buf, size_t count, size_t *nbytes) { 335 | return lk_write(fd, FD_SOCK, buf, count, nbytes); 336 | } 337 | int lk_write_file(int fd, LKBuffer *buf, size_t count, size_t *nbytes) { 338 | return lk_write(fd, FD_FILE, buf, count, nbytes); 339 | } 340 | 341 | // Write buf bytes to nonblocking fd. 342 | // Returns one of the following: 343 | // 0 (Z_EOF) for all buf bytes sent 344 | // 1 (Z_OPEN) for socket open 345 | // -1 (Z_ERR) for error 346 | // -2 (Z_BLOCK) for blocked socket (no data) 347 | // 348 | // Note: This keeps track of last buf position written. 349 | // Used to cumulatively write data into buf. 350 | int lk_write_all(int fd, FDType fd_type, LKBuffer *buf) { 351 | int z; 352 | while (1) { 353 | int nwrite = buf->bytes_len - buf->bytes_cur; 354 | if (nwrite <= 0) { 355 | z = Z_EOF; 356 | break; 357 | } 358 | if (fd_type == FD_SOCK) { 359 | z = send(fd, 360 | buf->bytes + buf->bytes_cur, 361 | buf->bytes_len - buf->bytes_cur, 362 | MSG_DONTWAIT | MSG_NOSIGNAL); 363 | } else { 364 | z = write(fd, 365 | buf->bytes + buf->bytes_cur, 366 | buf->bytes_len - buf->bytes_cur); 367 | } 368 | // interrupt occured during read, retry read. 369 | if (z == -1 && errno == EINTR) { 370 | continue; 371 | } 372 | if (z == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { 373 | z = Z_BLOCK; 374 | break; 375 | } 376 | if (z == -1) { 377 | // errno is set to EPIPE if socket was shutdown 378 | z = Z_ERR; 379 | break; 380 | } 381 | assert(z >= 0); 382 | buf->bytes_cur += z; 383 | } 384 | if (z > 0) { 385 | z = Z_OPEN; 386 | } 387 | return z; 388 | } 389 | int lk_write_all_sock(int fd, LKBuffer *buf) { 390 | return lk_write_all(fd, FD_SOCK, buf); 391 | } 392 | int lk_write_all_file(int fd, LKBuffer *buf) { 393 | return lk_write_all(fd, FD_FILE, buf); 394 | } 395 | 396 | // Similar to lk_write_all(), but sending buflist buf's sequentially. 397 | int lk_buflist_write_all(int fd, FDType fd_type, LKRefList *buflist) { 398 | if (buflist->items_cur >= buflist->items_len) { 399 | return Z_EOF; 400 | } 401 | 402 | LKBuffer *buf = lk_reflist_get_cur(buflist); 403 | assert(buf != NULL); 404 | int z = lk_write_all(fd, fd_type, buf); 405 | if (z == Z_EOF) { 406 | buflist->items_cur++; 407 | z = Z_OPEN; 408 | } 409 | return z; 410 | } 411 | 412 | // Pipe all available nonblocking readfd bytes into writefd. 413 | // Uses buf as buffer for queued up bytes waiting to be written. 414 | // Returns one of the following: 415 | // 0 (Z_EOF) for read/write complete. 416 | // 1 (Z_OPEN) for writefd socket open 417 | // -1 (Z_ERR) for read/write error. 418 | // -2 (Z_BLOCK) for blocked readfd/writefd socket 419 | int lk_pipe_all(int readfd, int writefd, FDType fd_type, LKBuffer *buf) { 420 | int readz, writez; 421 | 422 | readz = lk_read_all(readfd, fd_type, buf); 423 | if (readz == Z_ERR) { 424 | return readz; 425 | } 426 | assert(readz == Z_EOF || readz == Z_BLOCK); 427 | 428 | writez = lk_write_all(writefd, fd_type, buf); 429 | if (writez == Z_ERR) { 430 | return writez; 431 | } 432 | 433 | // Pipe complete if no more data to read and all bytes sent. 434 | if (readz == Z_EOF && writez == Z_EOF) { 435 | return Z_EOF; 436 | } 437 | 438 | if (readz == Z_BLOCK) { 439 | return Z_BLOCK; 440 | } 441 | if (writez == Z_EOF) { 442 | writez = Z_OPEN; 443 | } 444 | return writez; 445 | } 446 | 447 | /** lksocketreader functions **/ 448 | 449 | LKSocketReader *lk_socketreader_new(int sock, size_t buf_size) { 450 | LKSocketReader *sr = lk_malloc(sizeof(LKSocketReader), "lk_socketreader_new"); 451 | if (buf_size == 0) { 452 | buf_size = 1024; 453 | } 454 | sr->sock = sock; 455 | sr->buf = lk_buffer_new(buf_size); 456 | sr->sockclosed = 0; 457 | return sr; 458 | } 459 | 460 | void lk_socketreader_free(LKSocketReader *sr) { 461 | lk_buffer_free(sr->buf); 462 | sr->buf = NULL; 463 | lk_free(sr); 464 | } 465 | 466 | // Read one line from buffered socket including the \n char if present. 467 | // Function return values: 468 | // Z_OPEN (fd still open) 469 | // Z_EOF (end of file) 470 | // Z_ERR (errno set with error detail) 471 | // Z_BLOCK (fd blocked, no data) 472 | int lk_socketreader_readline(LKSocketReader *sr, LKString *line) { 473 | int z = Z_OPEN; 474 | lk_string_assign(line, ""); 475 | 476 | if (sr->sockclosed) { 477 | return Z_EOF; 478 | } 479 | LKBuffer *buf = sr->buf; 480 | 481 | while (1) { // leave space for null terminator 482 | // If no buffer chars available, read from socket. 483 | if (buf->bytes_cur >= buf->bytes_len) { 484 | memset(buf->bytes, '*', buf->bytes_size); // initialize for debugging purposes. 485 | z = recv(sr->sock, buf->bytes, buf->bytes_size, MSG_DONTWAIT | MSG_NOSIGNAL); 486 | // socket closed, no more data 487 | if (z == 0) { 488 | sr->sockclosed = 1; 489 | z = Z_EOF; 490 | break; 491 | } 492 | // any other error 493 | if (z == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { 494 | return Z_BLOCK; 495 | } 496 | if (z == -1 && errno != EAGAIN && errno != EWOULDBLOCK) { 497 | return Z_ERR; 498 | } 499 | assert(z > 0); 500 | buf->bytes_len = z; 501 | buf->bytes_cur = 0; 502 | z = Z_OPEN; 503 | } 504 | 505 | // Copy unread buffer bytes into dst until a '\n' char. 506 | while (1) { 507 | if (buf->bytes_cur >= buf->bytes_len) { 508 | break; 509 | } 510 | char ch = buf->bytes[buf->bytes_cur]; 511 | lk_string_append_char(line, ch); 512 | buf->bytes_cur++; 513 | 514 | if (ch == '\n') { 515 | goto readline_end; 516 | } 517 | } 518 | } 519 | 520 | readline_end: 521 | assert(z <= Z_OPEN); 522 | return z; 523 | } 524 | 525 | int lk_socketreader_recv(LKSocketReader *sr, LKBuffer *buf_dest) { 526 | lk_buffer_clear(buf_dest); 527 | LKBuffer *buf = sr->buf; 528 | 529 | // Append any unread buffer bytes into buf_dest. 530 | if (buf->bytes_cur < buf->bytes_len) { 531 | int ncopy = buf->bytes_len - buf->bytes_cur; 532 | lk_buffer_append(buf_dest, buf->bytes + buf->bytes_cur, ncopy); 533 | buf->bytes_cur += ncopy; 534 | } 535 | 536 | int z = lk_read_all_sock(sr->sock, buf_dest); 537 | if (z == Z_EOF) { 538 | sr->sockclosed = 1; 539 | } 540 | return z; 541 | } 542 | 543 | void debugprint_buf(char *buf, size_t buf_size) { 544 | printf("buf: "); 545 | for (int i=0; i < buf_size; i++) { 546 | putchar(buf[i]); 547 | } 548 | putchar('\n'); 549 | printf("buf_size: %ld\n", buf_size); 550 | } 551 | 552 | void lk_socketreader_debugprint(LKSocketReader *sr) { 553 | printf("buf_size: %ld\n", sr->buf->bytes_size); 554 | printf("buf_len: %ld\n", sr->buf->bytes_len); 555 | printf("buf cur: %ld\n", sr->buf->bytes_cur); 556 | printf("sockclosed: %d\n", sr->sockclosed); 557 | debugprint_buf(sr->buf->bytes, sr->buf->bytes_size); 558 | printf("\n"); 559 | } 560 | 561 | 562 | /*** LKHttpRequest functions ***/ 563 | LKHttpRequest *lk_httprequest_new() { 564 | LKHttpRequest *req = lk_malloc(sizeof(LKHttpRequest), "lk_httprequest_new"); 565 | req->method = lk_string_new(""); 566 | req->uri = lk_string_new(""); 567 | req->path = lk_string_new(""); 568 | req->filename = lk_string_new(""); 569 | req->querystring = lk_string_new(""); 570 | req->version = lk_string_new(""); 571 | req->headers = lk_stringtable_new(); 572 | req->head = lk_buffer_new(0); 573 | req->body = lk_buffer_new(0); 574 | return req; 575 | } 576 | 577 | void lk_httprequest_free(LKHttpRequest *req) { 578 | lk_string_free(req->method); 579 | lk_string_free(req->uri); 580 | lk_string_free(req->path); 581 | lk_string_free(req->filename); 582 | lk_string_free(req->querystring); 583 | lk_string_free(req->version); 584 | lk_stringtable_free(req->headers); 585 | lk_buffer_free(req->head); 586 | lk_buffer_free(req->body); 587 | 588 | req->method = NULL; 589 | req->uri = NULL; 590 | req->path = NULL; 591 | req->filename = NULL; 592 | req->querystring = NULL; 593 | req->version = NULL; 594 | req->headers = NULL; 595 | req->head = NULL; 596 | req->body = NULL; 597 | lk_free(req); 598 | } 599 | 600 | void lk_httprequest_add_header(LKHttpRequest *req, char *k, char *v) { 601 | lk_stringtable_set(req->headers, k, v); 602 | } 603 | 604 | void lk_httprequest_append_body(LKHttpRequest *req, char *bytes, int bytes_len) { 605 | lk_buffer_append(req->body, bytes, bytes_len); 606 | } 607 | 608 | void lk_httprequest_finalize(LKHttpRequest *req) { 609 | lk_buffer_clear(req->head); 610 | 611 | // Default to HTTP version. 612 | if (lk_string_sz_equal(req->version, "")) { 613 | lk_string_assign(req->version, "HTTP/1.0"); 614 | } 615 | lk_buffer_append_sprintf(req->head, "%s %s %s\n", req->method->s, req->uri->s, req->version->s); 616 | if (req->body->bytes_len > 0) { 617 | lk_buffer_append_sprintf(req->head, "Content-Length: %ld\n", req->body->bytes_len); 618 | } 619 | for (int i=0; i < req->headers->items_len; i++) { 620 | lk_buffer_append_sprintf(req->head, "%s: %s\n", req->headers->items[i].k->s, req->headers->items[i].v->s); 621 | } 622 | lk_buffer_append(req->head, "\r\n", 2); 623 | } 624 | 625 | 626 | void lk_httprequest_debugprint(LKHttpRequest *req) { 627 | assert(req->method != NULL); 628 | assert(req->uri != NULL); 629 | assert(req->version != NULL); 630 | assert(req->head != NULL); 631 | assert(req->body != NULL); 632 | 633 | printf("method: %s\n", req->method->s); 634 | printf("uri: %s\n", req->uri->s); 635 | printf("version: %s\n", req->version->s); 636 | 637 | printf("headers_size: %ld\n", req->headers->items_size); 638 | printf("headers_len: %ld\n", req->headers->items_len); 639 | 640 | printf("Headers:\n"); 641 | for (int i=0; i < req->headers->items_len; i++) { 642 | LKString *v = req->headers->items[i].v; 643 | printf("%s: %s\n", req->headers->items[i].k->s, v->s); 644 | } 645 | 646 | printf("Body:\n---\n"); 647 | for (int i=0; i < req->body->bytes_len; i++) { 648 | putchar(req->body->bytes[i]); 649 | } 650 | printf("\n---\n"); 651 | } 652 | 653 | 654 | /** httpresp functions **/ 655 | LKHttpResponse *lk_httpresponse_new() { 656 | LKHttpResponse *resp = lk_malloc(sizeof(LKHttpResponse), "lk_httpresponse_new"); 657 | resp->status = 0; 658 | resp->statustext = lk_string_new(""); 659 | resp->version = lk_string_new(""); 660 | resp->headers = lk_stringtable_new(); 661 | resp->head = lk_buffer_new(0); 662 | resp->body = lk_buffer_new(0); 663 | return resp; 664 | } 665 | 666 | void lk_httpresponse_free(LKHttpResponse *resp) { 667 | lk_string_free(resp->statustext); 668 | lk_string_free(resp->version); 669 | lk_stringtable_free(resp->headers); 670 | lk_buffer_free(resp->head); 671 | lk_buffer_free(resp->body); 672 | 673 | resp->statustext = NULL; 674 | resp->version = NULL; 675 | resp->headers = NULL; 676 | resp->head = NULL; 677 | resp->body = NULL; 678 | lk_free(resp); 679 | } 680 | 681 | void lk_httpresponse_add_header(LKHttpResponse *resp, char *k, char *v) { 682 | lk_stringtable_set(resp->headers, k, v); 683 | } 684 | 685 | // Finalize the http response by setting head buffer. 686 | // Writes the status line, headers and CRLF blank string to head buffer. 687 | void lk_httpresponse_finalize(LKHttpResponse *resp) { 688 | lk_buffer_clear(resp->head); 689 | 690 | // Default to 200 OK if no status set. 691 | if (resp->status == 0) { 692 | resp->status = 200; 693 | lk_string_assign(resp->statustext, "OK"); 694 | } 695 | // Default to HTTP version. 696 | if (lk_string_sz_equal(resp->version, "")) { 697 | lk_string_assign(resp->version, "HTTP/1.0"); 698 | } 699 | lk_buffer_append_sprintf(resp->head, "%s %d %s\n", resp->version->s, resp->status, resp->statustext->s); 700 | lk_buffer_append_sprintf(resp->head, "Content-Length: %ld\n", resp->body->bytes_len); 701 | for (int i=0; i < resp->headers->items_len; i++) { 702 | lk_buffer_append_sprintf(resp->head, "%s: %s\n", resp->headers->items[i].k->s, resp->headers->items[i].v->s); 703 | } 704 | lk_buffer_append(resp->head, "\r\n", 2); 705 | } 706 | 707 | void lk_httpresponse_debugprint(LKHttpResponse *resp) { 708 | assert(resp->statustext != NULL); 709 | assert(resp->version != NULL); 710 | assert(resp->headers != NULL); 711 | assert(resp->head); 712 | assert(resp->body); 713 | 714 | printf("status: %d\n", resp->status); 715 | printf("statustext: %s\n", resp->statustext->s); 716 | printf("version: %s\n", resp->version->s); 717 | printf("headers_size: %ld\n", resp->headers->items_size); 718 | printf("headers_len: %ld\n", resp->headers->items_len); 719 | 720 | printf("Headers:\n"); 721 | for (int i=0; i < resp->headers->items_len; i++) { 722 | LKString *v = resp->headers->items[i].v; 723 | printf("%s: %s\n", resp->headers->items[i].k->s, v->s); 724 | } 725 | 726 | printf("Body:\n---\n"); 727 | for (int i=0; i < resp->body->bytes_len; i++) { 728 | putchar(resp->body->bytes[i]); 729 | } 730 | printf("\n---\n"); 731 | } 732 | 733 | // Open and read entire file contents into buf. 734 | // Return number of bytes read or -1 for error. 735 | ssize_t lk_readfile(char *filepath, LKBuffer *buf) { 736 | int fd = open(filepath, O_RDONLY); 737 | if (fd == -1) { 738 | return -1; 739 | } 740 | int z = lk_readfd(fd, buf); 741 | if (z == -1) { 742 | int tmperrno = errno; 743 | close(fd); 744 | errno = tmperrno; 745 | return z; 746 | } 747 | 748 | close(fd); 749 | return z; 750 | } 751 | 752 | // Read entire file descriptor contents into buf. 753 | // Return number of bytes read or -1 for error. 754 | #define TMPBUF_SIZE 512 755 | ssize_t lk_readfd(int fd, LKBuffer *buf) { 756 | char tmpbuf[TMPBUF_SIZE]; 757 | 758 | int nread = 0; 759 | while (1) { 760 | int z = read(fd, tmpbuf, TMPBUF_SIZE); 761 | if (z == -1 && errno == EINTR) { 762 | continue; 763 | } 764 | if (z == -1) { 765 | return z; 766 | } 767 | if (z == 0) { 768 | break; 769 | } 770 | lk_buffer_append(buf, tmpbuf, z); 771 | nread += z; 772 | } 773 | return nread; 774 | } 775 | 776 | // Append src to dest, allocating new memory in dest if needed. 777 | // Return new pointer to dest. 778 | char *lk_astrncat(char *dest, char *src, size_t src_len) { 779 | int dest_len = strlen(dest); 780 | dest = lk_realloc(dest, dest_len + src_len + 1, "lk_astrncat"); 781 | strncat(dest, src, src_len); 782 | return dest; 783 | } 784 | 785 | // Return whether file exists. 786 | int lk_file_exists(char *filename) { 787 | struct stat statbuf; 788 | int z = stat(filename, &statbuf); 789 | return !z; 790 | } 791 | 792 | -------------------------------------------------------------------------------- /lkhttpserver.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "lktables.h" 22 | #include "lklib.h" 23 | #include "lknet.h" 24 | 25 | // local functions 26 | void FD_SET_READ(int fd, LKHttpServer *server); 27 | void FD_SET_WRITE(int fd, LKHttpServer *server); 28 | void FD_CLR_READ(int fd, LKHttpServer *server); 29 | void FD_CLR_WRITE(int fd, LKHttpServer *server); 30 | 31 | void read_request(LKHttpServer *server, LKContext *ctx); 32 | void read_cgi_output(LKHttpServer *server, LKContext *ctx); 33 | void write_cgi_input(LKHttpServer *server, LKContext *ctx); 34 | void process_request(LKHttpServer *server, LKContext *ctx); 35 | 36 | void serve_files(LKHttpServer *server, LKContext *ctx, LKHostConfig *hc); 37 | void serve_cgi(LKHttpServer *server, LKContext *ctx, LKHostConfig *hc); 38 | void process_response(LKHttpServer *server, LKContext *ctx); 39 | void process_error_response(LKHttpServer *server, LKContext *ctx, int status, char *msg); 40 | 41 | void set_cgi_env1(LKHttpServer *server); 42 | void set_cgi_env2(LKHttpServer *server, LKContext *ctx, LKHostConfig *hc); 43 | 44 | void get_localtime_string(char *time_str, size_t time_str_len); 45 | int open_path_file(char *home_dir, char *path); 46 | int read_path_file(char *home_dir, char *path, LKBuffer *buf); 47 | char *fileext(char *filepath); 48 | 49 | void write_response(LKHttpServer *server, LKContext *ctx); 50 | int terminate_fd(int fd, FDType fd_type, FDAction fd_action, LKHttpServer *server); 51 | void terminate_client_session(LKHttpServer *server, LKContext *ctx); 52 | 53 | void serve_proxy(LKHttpServer *server, LKContext *ctx, char *targethost); 54 | void write_proxy_request(LKHttpServer *server, LKContext *ctx); 55 | void pipe_proxy_response(LKHttpServer *server, LKContext *ctx); 56 | 57 | 58 | /*** LKHttpServer functions ***/ 59 | 60 | LKHttpServer *lk_httpserver_new(LKConfig *cfg) { 61 | LKHttpServer *server = lk_malloc(sizeof(LKHttpServer), "lk_httpserver_new"); 62 | server->cfg = cfg; 63 | server->ctxhead = NULL; 64 | server->maxfd = 0; 65 | return server; 66 | } 67 | 68 | void lk_httpserver_free(LKHttpServer *server) { 69 | lk_config_free(server->cfg); 70 | 71 | // Free ctx linked list 72 | LKContext *ctx = server->ctxhead; 73 | while (ctx != NULL) { 74 | LKContext *ptmp = ctx; 75 | ctx = ctx->next; 76 | lk_context_free(ptmp); 77 | } 78 | 79 | memset(server, 0, sizeof(LKHttpServer)); 80 | lk_free(server); 81 | } 82 | 83 | void FD_SET_READ(int fd, LKHttpServer *server) { 84 | FD_SET(fd, &server->readfds); 85 | if (fd > server->maxfd) { 86 | server->maxfd = fd; 87 | } 88 | } 89 | void FD_SET_WRITE(int fd, LKHttpServer *server) { 90 | FD_SET(fd, &server->writefds); 91 | if (fd > server->maxfd) { 92 | server->maxfd = fd; 93 | } 94 | } 95 | void FD_CLR_READ(int fd, LKHttpServer *server) { 96 | FD_CLR(fd, &server->readfds); 97 | } 98 | void FD_CLR_WRITE(int fd, LKHttpServer *server) { 99 | FD_CLR(fd, &server->writefds); 100 | } 101 | 102 | int lk_httpserver_serve(LKHttpServer *server) { 103 | int z; 104 | LKConfig *cfg = server->cfg; 105 | lk_config_finalize(cfg); 106 | 107 | int backlog = 50; 108 | struct sockaddr sa; 109 | int s0 = lk_open_listen_socket(cfg->serverhost->s, cfg->port->s, backlog, &sa); 110 | if (s0 == -1) { 111 | lk_print_err("lk_open_listen_socket() failed"); 112 | return -1; 113 | } 114 | 115 | LKString *server_ipaddr_str = lk_get_ipaddr_string(&sa); 116 | printf("Serving HTTP on %s port %s...\n", server_ipaddr_str->s, cfg->port->s); 117 | lk_string_free(server_ipaddr_str); 118 | 119 | clearenv(); 120 | set_cgi_env1(server); 121 | 122 | FD_ZERO(&server->readfds); 123 | FD_ZERO(&server->writefds); 124 | FD_SET_READ(s0, server); 125 | 126 | while (1) { 127 | // readfds contain the master list of read sockets 128 | fd_set cur_readfds = server->readfds; 129 | fd_set cur_writefds = server->writefds; 130 | z = select(server->maxfd+1, &cur_readfds, &cur_writefds, NULL, NULL); 131 | if (z == -1 && errno == EINTR) { 132 | continue; 133 | } 134 | if (z == -1) { 135 | lk_print_err("select()"); 136 | return z; 137 | } 138 | if (z == 0) { 139 | // timeout returned 140 | continue; 141 | } 142 | 143 | // fds now contain list of clients with data available to be read. 144 | for (int i=0; i <= server->maxfd; i++) { 145 | if (FD_ISSET(i, &cur_readfds)) { 146 | // New client connection 147 | if (i == s0) { 148 | socklen_t sa_len = sizeof(struct sockaddr_in); 149 | struct sockaddr_in sa; 150 | int clientfd = accept(s0, (struct sockaddr*)&sa, &sa_len); 151 | if (clientfd == -1) { 152 | lk_print_err("accept()"); 153 | continue; 154 | } 155 | 156 | // Add new client socket to list of read sockets. 157 | FD_SET_READ(clientfd, server); 158 | 159 | LKContext *ctx = create_initial_context(clientfd, &sa); 160 | add_new_client_context(&server->ctxhead, ctx); 161 | continue; 162 | } else { 163 | //printf("read fd %d\n", i); 164 | 165 | int selectfd = i; 166 | LKContext *ctx = match_select_ctx(server->ctxhead, selectfd); 167 | if (ctx == NULL) { 168 | printf("read selectfd %d not in ctx list\n", selectfd); 169 | terminate_fd(selectfd, FD_SOCK, FD_READ, server); 170 | continue; 171 | } 172 | 173 | if (ctx->type == CTX_READ_REQ) { 174 | read_request(server, ctx); 175 | } else if (ctx->type == CTX_READ_CGI_OUTPUT) { 176 | read_cgi_output(server, ctx); 177 | } else if (ctx->type == CTX_PROXY_PIPE_RESP) { 178 | pipe_proxy_response(server, ctx); 179 | } else { 180 | printf("read selectfd %d with unknown ctx type %d\n", selectfd, ctx->type); 181 | } 182 | } 183 | } else if (FD_ISSET(i, &cur_writefds)) { 184 | //printf("write fd %d\n", i); 185 | 186 | int selectfd = i; 187 | LKContext *ctx = match_select_ctx(server->ctxhead, selectfd); 188 | if (ctx == NULL) { 189 | printf("write selectfd %d not in ctx list\n", selectfd); 190 | terminate_fd(selectfd, FD_SOCK, FD_WRITE, server); 191 | continue; 192 | } 193 | 194 | if (ctx->type == CTX_WRITE_RESP) { 195 | assert(ctx->resp != NULL); 196 | assert(ctx->resp->head != NULL); 197 | write_response(server, ctx); 198 | } else if (ctx->type == CTX_WRITE_CGI_INPUT) { 199 | write_cgi_input(server, ctx); 200 | } else if (ctx->type == CTX_PROXY_WRITE_REQ) { 201 | assert(ctx->req != NULL); 202 | assert(ctx->req->head != NULL); 203 | write_proxy_request(server, ctx); 204 | } else { 205 | printf("write selectfd %d with unknown ctx type %d\n", selectfd, ctx->type); 206 | } 207 | } 208 | } 209 | } // while (1) 210 | 211 | return 0; 212 | } 213 | 214 | // Sets the cgi environment variables that stay the same across http requests. 215 | void set_cgi_env1(LKHttpServer *server) { 216 | int z; 217 | LKConfig *cfg = server->cfg; 218 | 219 | char hostname[LK_BUFSIZE_SMALL]; 220 | z = gethostname(hostname, sizeof(hostname)-1); 221 | if (z == -1) { 222 | lk_print_err("gethostname()"); 223 | hostname[0] = '\0'; 224 | } 225 | hostname[sizeof(hostname)-1] = '\0'; 226 | 227 | setenv("SERVER_NAME", hostname, 1); 228 | setenv("SERVER_SOFTWARE", "littlekitten/0.1", 1); 229 | setenv("SERVER_PROTOCOL", "HTTP/1.0", 1); 230 | setenv("SERVER_PORT", cfg->port->s, 1); 231 | 232 | } 233 | 234 | // Sets the cgi environment variables that vary for each http request. 235 | void set_cgi_env2(LKHttpServer *server, LKContext *ctx, LKHostConfig *hc) { 236 | LKHttpRequest *req = ctx->req; 237 | 238 | setenv("DOCUMENT_ROOT", hc->homedir_abspath->s, 1); 239 | 240 | char *http_user_agent = lk_stringtable_get(req->headers, "User-Agent"); 241 | if (!http_user_agent) http_user_agent = ""; 242 | setenv("HTTP_USER_AGENT", http_user_agent, 1); 243 | 244 | char *http_host = lk_stringtable_get(req->headers, "Host"); 245 | if (!http_host) http_host = ""; 246 | setenv("HTTP_HOST", http_host, 1); 247 | 248 | LKString *lkscript_filename = lk_string_new(hc->homedir_abspath->s); 249 | lk_string_append(lkscript_filename, req->path->s); 250 | setenv("SCRIPT_FILENAME", lkscript_filename->s, 1); 251 | lk_string_free(lkscript_filename); 252 | 253 | setenv("REQUEST_METHOD", req->method->s, 1); 254 | setenv("SCRIPT_NAME", req->path->s, 1); 255 | setenv("REQUEST_URI", req->uri->s, 1); 256 | setenv("QUERY_STRING", req->querystring->s, 1); 257 | 258 | char *content_type = lk_stringtable_get(req->headers, "Content-Type"); 259 | if (content_type == NULL) { 260 | content_type = ""; 261 | } 262 | setenv("CONTENT_TYPE", content_type, 1); 263 | 264 | char content_length[10]; 265 | snprintf(content_length, sizeof(content_length), "%ld", req->body->bytes_len); 266 | content_length[sizeof(content_length)-1] = '\0'; 267 | setenv("CONTENT_LENGTH", content_length, 1); 268 | 269 | setenv("REMOTE_ADDR", ctx->client_ipaddr->s, 1); 270 | char portstr[10]; 271 | snprintf(portstr, sizeof(portstr), "%d", ctx->client_port); 272 | setenv("REMOTE_PORT", portstr, 1); 273 | } 274 | 275 | void read_request(LKHttpServer *server, LKContext *ctx) { 276 | int z = 0; 277 | 278 | while (1) { 279 | if (!ctx->reqparser->head_complete) { 280 | z = lk_socketreader_readline(ctx->sr, ctx->req_line); 281 | if (z == Z_ERR) { 282 | lk_print_err("lksocketreader_readline()"); 283 | break; 284 | } 285 | lk_httprequestparser_parse_line(ctx->reqparser, ctx->req_line, ctx->req); 286 | } else { 287 | z = lk_socketreader_recv(ctx->sr, ctx->req_buf); 288 | if (z == Z_ERR) { 289 | lk_print_err("lksocketreader_readbytes()"); 290 | break; 291 | } 292 | lk_httprequestparser_parse_bytes(ctx->reqparser, ctx->req_buf, ctx->req); 293 | } 294 | // No more data coming in. 295 | if (ctx->sr->sockclosed) { 296 | ctx->reqparser->body_complete = 1; 297 | } 298 | if (ctx->reqparser->body_complete) { 299 | FD_CLR_READ(ctx->selectfd, server); 300 | shutdown(ctx->selectfd, SHUT_RD); 301 | process_request(server, ctx); 302 | break; 303 | } 304 | if (z != Z_OPEN) { 305 | break; 306 | } 307 | } 308 | } 309 | 310 | // Send cgi_inputbuf input bytes to cgi program stdin set in selectfd. 311 | void write_cgi_input(LKHttpServer *server, LKContext *ctx) { 312 | assert(ctx->cgi_inputbuf != NULL); 313 | int z = lk_write_all_file(ctx->selectfd, ctx->cgi_inputbuf); 314 | if (z == Z_BLOCK) { 315 | return; 316 | } 317 | if (z == Z_ERR) { 318 | lk_print_err("write_cgi_input lk_write_all_file()"); 319 | z = terminate_fd(ctx->cgifd, FD_FILE, FD_WRITE, server); 320 | if (z == 0) { 321 | ctx->cgifd = 0; 322 | } 323 | remove_selectfd_context(&server->ctxhead, ctx->selectfd); 324 | return; 325 | } 326 | if (z == Z_EOF) { 327 | // Completed writing input bytes. 328 | FD_CLR_WRITE(ctx->selectfd, server); 329 | shutdown(ctx->selectfd, SHUT_WR); 330 | remove_selectfd_context(&server->ctxhead, ctx->selectfd); 331 | } 332 | } 333 | 334 | // Read cgi output to cgi_outputbuf. 335 | void read_cgi_output(LKHttpServer *server, LKContext *ctx) { 336 | int z = lk_read_all_file(ctx->selectfd, ctx->cgi_outputbuf); 337 | if (z == Z_BLOCK) { 338 | return; 339 | } 340 | if (z == Z_ERR) { 341 | lk_print_err("lk_read_all_file()"); 342 | z = terminate_fd(ctx->cgifd, FD_FILE, FD_READ, server); 343 | if (z == 0) { 344 | ctx->cgifd = 0; 345 | } 346 | process_error_response(server, ctx, 500, "Error processing CGI output."); 347 | return; 348 | } 349 | 350 | // EOF - finished reading cgi output. 351 | assert(z == 0); 352 | 353 | // Remove cgi output from read list. 354 | z = terminate_fd(ctx->cgifd, FD_FILE, FD_READ, server); 355 | if (z == 0) { 356 | ctx->cgifd = 0; 357 | } 358 | 359 | parse_cgi_output(ctx->cgi_outputbuf, ctx->resp); 360 | process_response(server, ctx); 361 | } 362 | 363 | void process_request(LKHttpServer *server, LKContext *ctx) { 364 | char *hostname = lk_stringtable_get(ctx->req->headers, "Host"); 365 | LKHostConfig *hc = lk_config_find_hostconfig(server->cfg, hostname); 366 | if (hc == NULL) { 367 | process_error_response(server, ctx, 404, "LittleKitten webserver: hostconfig not found."); 368 | return; 369 | } 370 | 371 | // Forward request to proxyhost if proxyhost specified. 372 | if (hc->proxyhost->s_len > 0) { 373 | serve_proxy(server, ctx, hc->proxyhost->s); 374 | return; 375 | } 376 | 377 | if (hc->homedir->s_len == 0) { 378 | process_error_response(server, ctx, 404, "LittleKitten webserver: hostconfig homedir not specified."); 379 | return; 380 | } 381 | 382 | // Replace path with any matching alias. 383 | char *match = lk_stringtable_get(hc->aliases, ctx->req->path->s); 384 | if (match != NULL) { 385 | lk_string_assign(ctx->req->path, match); 386 | } 387 | 388 | // Run cgi script if uri falls under cgidir 389 | if (hc->cgidir->s_len > 0 && lk_string_starts_with(ctx->req->path, hc->cgidir->s)) { 390 | serve_cgi(server, ctx, hc); 391 | return; 392 | } 393 | 394 | serve_files(server, ctx, hc); 395 | process_response(server, ctx); 396 | } 397 | 398 | // Generate an http response to an http request. 399 | #define POSTTEST 400 | void serve_files(LKHttpServer *server, LKContext *ctx, LKHostConfig *hc) { 401 | int z; 402 | static char *html_error_start = 403 | "\n" 404 | "\n" 405 | "Error response\n" 406 | "

Error response

\n"; 407 | static char *html_error_end = 408 | "\n"; 409 | 410 | LKHttpRequest *req = ctx->req; 411 | LKHttpResponse *resp = ctx->resp; 412 | LKString *method = req->method; 413 | LKString *path = req->path; 414 | 415 | if (lk_string_sz_equal(method, "GET") || lk_string_sz_equal(method, "HEAD")) { 416 | // For root, default to index.html, ... 417 | if (path->s_len == 0) { 418 | char *default_files[] = {"/index.html", "/index.htm", "/default.html", "/default.htm"}; 419 | for (int i=0; i < sizeof(default_files) / sizeof(char *); i++) { 420 | z = read_path_file(hc->homedir_abspath->s, default_files[i], resp->body); 421 | if (z >= 0) { 422 | lk_httpresponse_add_header(resp, "Content-Type", "text/html"); 423 | break; 424 | } 425 | // Update path with default file for File not found error message. 426 | lk_string_assign(path, default_files[i]); 427 | } 428 | } else { 429 | z = read_path_file(hc->homedir_abspath->s, path->s, resp->body); 430 | char *content_type = (char *) lk_lookup(mimetypes_tbl, fileext(path->s)); 431 | if (content_type == NULL) { 432 | content_type = "text/plain"; 433 | } 434 | lk_httpresponse_add_header(resp, "Content-Type", content_type); 435 | } 436 | if (z == -1) { 437 | // path not found 438 | resp->status = 404; 439 | lk_string_assign_sprintf(resp->statustext, "File not found '%s'", path->s); 440 | lk_httpresponse_add_header(resp, "Content-Type", "text/plain"); 441 | lk_buffer_append_sprintf(resp->body, "File not found '%s'\n", path->s); 442 | } 443 | return; 444 | } 445 | #ifdef POSTTEST 446 | if (lk_string_sz_equal(method, "POST")) { 447 | static char *html_start = 448 | "\n" 449 | "\n" 450 | "Little Kitten Sample Response\n" 451 | "\n"; 452 | static char *html_end = 453 | "\n"; 454 | 455 | lk_httpresponse_add_header(resp, "Content-Type", "text/html"); 456 | lk_buffer_append(resp->body, html_start, strlen(html_start)); 457 | lk_buffer_append_sz(resp->body, "
\n");
458 |         lk_buffer_append(resp->body, req->body->bytes, req->body->bytes_len);
459 |         lk_buffer_append_sz(resp->body, "\n
\n"); 460 | lk_buffer_append(resp->body, html_end, strlen(html_end)); 461 | return; 462 | } 463 | #endif 464 | 465 | resp->status = 501; 466 | lk_string_assign_sprintf(resp->statustext, "Unsupported method ('%s')", method); 467 | 468 | lk_httpresponse_add_header(resp, "Content-Type", "text/html"); 469 | lk_buffer_append(resp->body, html_error_start, strlen(html_error_start)); 470 | lk_buffer_append_sprintf(resp->body, "

Error code %d.

\n", resp->status); 471 | lk_buffer_append_sprintf(resp->body, "

Message: Unsupported method ('%s').

\n", resp->statustext->s); 472 | lk_buffer_append(resp->body, html_error_end, strlen(html_error_end)); 473 | } 474 | 475 | void serve_cgi(LKHttpServer *server, LKContext *ctx, LKHostConfig *hc) { 476 | LKHttpRequest *req = ctx->req; 477 | LKHttpResponse *resp = ctx->resp; 478 | char *path = req->path->s; 479 | 480 | LKString *cgifile = lk_string_new(hc->homedir_abspath->s); 481 | lk_string_append(cgifile, req->path->s); 482 | 483 | // Expand "/../", etc. into real_path. 484 | char real_path[PATH_MAX]; 485 | char *pz = realpath(cgifile->s, real_path); 486 | lk_string_free(cgifile); 487 | 488 | // real_path should start with cgidir_abspath 489 | // real_path file should exist 490 | if (pz == NULL || strncmp(real_path, hc->cgidir_abspath->s, hc->cgidir_abspath->s_len) || !lk_file_exists(real_path)) { 491 | resp->status = 404; 492 | lk_string_assign_sprintf(resp->statustext, "File not found '%s'", path); 493 | lk_httpresponse_add_header(resp, "Content-Type", "text/plain"); 494 | lk_buffer_append_sprintf(resp->body, "File not found '%s'\n", path); 495 | 496 | process_response(server, ctx); 497 | return; 498 | } 499 | 500 | set_cgi_env2(server, ctx, hc); 501 | 502 | // cgi stdout and stderr are streamed to fd_out. 503 | //$$todo pass any request body to fd_in. 504 | int fd_in, fd_out; 505 | int z = lk_popen3(real_path, &fd_in, &fd_out, NULL); 506 | if (z == -1) { 507 | resp->status = 500; 508 | lk_string_assign_sprintf(resp->statustext, "Server error '%s'", strerror(errno)); 509 | lk_httpresponse_add_header(resp, "Content-Type", "text/plain"); 510 | lk_buffer_append_sprintf(resp->body, "Server error '%s'\n", strerror(errno)); 511 | process_response(server, ctx); 512 | return; 513 | } 514 | 515 | close(fd_in); 516 | 517 | // Read cgi output in select() 518 | ctx->selectfd = fd_out; 519 | ctx->cgifd = fd_out; 520 | ctx->type = CTX_READ_CGI_OUTPUT; 521 | ctx->cgi_outputbuf = lk_buffer_new(0); 522 | FD_SET_READ(ctx->selectfd, server); 523 | 524 | // If req is POST with body, pass it to cgi process stdin. 525 | if (req->body->bytes_len > 0) { 526 | LKContext *ctx_in = lk_context_new(); 527 | add_context(&server->ctxhead, ctx_in); 528 | 529 | ctx_in->selectfd = fd_in; 530 | ctx_in->cgifd = fd_in; 531 | ctx_in->clientfd = ctx->clientfd; 532 | ctx_in->type = CTX_WRITE_CGI_INPUT; 533 | 534 | ctx_in->cgi_inputbuf = lk_buffer_new(0); 535 | lk_buffer_append(ctx_in->cgi_inputbuf, req->body->bytes, req->body->bytes_len); 536 | 537 | FD_SET_WRITE(ctx_in->selectfd, server); 538 | } 539 | } 540 | 541 | void process_response(LKHttpServer *server, LKContext *ctx) { 542 | LKHttpRequest *req = ctx->req; 543 | LKHttpResponse *resp = ctx->resp; 544 | 545 | lk_httpresponse_finalize(resp); 546 | 547 | // Clear response body on HEAD request. 548 | if (lk_string_sz_equal(req->method, "HEAD")) { 549 | lk_buffer_clear(resp->body); 550 | } 551 | 552 | char time_str[TIME_STRING_SIZE]; 553 | get_localtime_string(time_str, sizeof(time_str)); 554 | printf("%s [%s] \"%s %s %s\" %d\n", 555 | ctx->client_ipaddr->s, time_str, 556 | req->method->s, req->uri->s, resp->version->s, 557 | resp->status); 558 | if (resp->status >= 500 && resp->status < 600 && resp->statustext->s_len > 0) { 559 | printf("%s [%s] %d - %s\n", 560 | ctx->client_ipaddr->s, time_str, 561 | resp->status, resp->statustext->s); 562 | } 563 | 564 | ctx->selectfd = ctx->clientfd; 565 | ctx->type = CTX_WRITE_RESP; 566 | FD_SET_WRITE(ctx->selectfd, server); 567 | lk_reflist_clear(ctx->buflist); 568 | lk_reflist_append(ctx->buflist, resp->head); 569 | lk_reflist_append(ctx->buflist, resp->body); 570 | return; 571 | } 572 | 573 | void process_error_response(LKHttpServer *server, LKContext *ctx, int status, char *msg) { 574 | LKHttpResponse *resp = ctx->resp; 575 | resp->status = status; 576 | lk_string_assign(resp->statustext, msg); 577 | lk_httpresponse_add_header(resp, "Content-Type", "text/plain"); 578 | lk_buffer_append_sz(resp->body, msg); 579 | 580 | process_response(server, ctx); 581 | } 582 | 583 | // Open / file in nonblocking mode. 584 | // Returns 0 for success, -1 for error. 585 | int open_path_file(char *home_dir, char *path) { 586 | // full_path = home_dir + path 587 | // Ex. "/path/to" + "/index.html" 588 | LKString *full_path = lk_string_new(home_dir); 589 | lk_string_append(full_path, path); 590 | 591 | int z = open(full_path->s, O_RDONLY | O_NONBLOCK); 592 | lk_string_free(full_path); 593 | return z; 594 | } 595 | 596 | // Read / file into buffer. 597 | // Return number of bytes read or -1 for error. 598 | int read_path_file(char *home_dir, char *path, LKBuffer *buf) { 599 | int z; 600 | // full_path = home_dir + path 601 | // Ex. "/path/to" + "/index.html" 602 | LKString *full_path = lk_string_new(home_dir); 603 | lk_string_append(full_path, path); 604 | 605 | // Expand "/../", etc. into real_path. 606 | char real_path[PATH_MAX]; 607 | char *pz = realpath(full_path->s, real_path); 608 | // real_path should start with home_dir 609 | if (pz == NULL || strncmp(real_path, home_dir, strlen(home_dir))) { 610 | lk_string_free(full_path); 611 | z = -1; 612 | errno = EPERM; 613 | return z; 614 | } 615 | 616 | z = lk_readfile(real_path, buf); 617 | lk_string_free(full_path); 618 | return z; 619 | } 620 | 621 | int is_valid_http_method(char *method) { 622 | if (method == NULL) { 623 | return 0; 624 | } 625 | 626 | if (!strcasecmp(method, "GET") || 627 | !strcasecmp(method, "POST") || 628 | !strcasecmp(method, "PUT") || 629 | !strcasecmp(method, "DELETE") || 630 | !strcasecmp(method, "HEAD")) { 631 | return 1; 632 | } 633 | 634 | return 0; 635 | } 636 | 637 | // Return ptr to start of file extension within filepath. 638 | // Ex. "path/to/index.html" returns "index.html" 639 | char *fileext(char *filepath) { 640 | int filepath_len = strlen(filepath); 641 | // filepath of "" returns ext of "". 642 | if (filepath_len == 0) { 643 | return filepath; 644 | } 645 | 646 | char *p = filepath + strlen(filepath) - 1; 647 | while (p >= filepath) { 648 | if (*p == '.') { 649 | return p+1; 650 | } 651 | p--; 652 | } 653 | return filepath; 654 | } 655 | 656 | void write_response(LKHttpServer *server, LKContext *ctx) { 657 | int z = lk_buflist_write_all(ctx->selectfd, FD_SOCK, ctx->buflist); 658 | if (z == Z_BLOCK) { 659 | return; 660 | } 661 | if (z == Z_ERR) { 662 | lk_print_err("write_response lk_buflist_write_all()"); 663 | terminate_client_session(server, ctx); 664 | return; 665 | } 666 | if (z == Z_EOF) { 667 | // Completed sending http response. 668 | terminate_client_session(server, ctx); 669 | } 670 | } 671 | 672 | void serve_proxy(LKHttpServer *server, LKContext *ctx, char *targethost) { 673 | int proxyfd = lk_open_connect_socket(targethost, "", NULL); 674 | if (proxyfd == -1) { 675 | lk_print_err("lk_open_connect_socket()"); 676 | process_error_response(server, ctx, 500, "Error opening proxy socket."); 677 | return; 678 | } 679 | 680 | lk_httprequest_finalize(ctx->req); 681 | ctx->proxyfd = proxyfd; 682 | ctx->selectfd = proxyfd; 683 | ctx->type = CTX_PROXY_WRITE_REQ; 684 | FD_SET_WRITE(proxyfd, server); 685 | lk_reflist_clear(ctx->buflist); 686 | lk_reflist_append(ctx->buflist, ctx->req->head); 687 | lk_reflist_append(ctx->buflist, ctx->req->body); 688 | } 689 | 690 | void write_proxy_request(LKHttpServer *server, LKContext *ctx) { 691 | int z = lk_buflist_write_all(ctx->selectfd, FD_SOCK, ctx->buflist); 692 | if (z == Z_BLOCK) { 693 | return; 694 | } 695 | if (z == Z_ERR) { 696 | lk_print_err("write_proxy_request lk_buflist_write_all"); 697 | z = terminate_fd(ctx->proxyfd, FD_SOCK, FD_WRITE, server); 698 | if (z == 0) { 699 | ctx->proxyfd = 0; 700 | } 701 | process_error_response(server, ctx, 500, "Error forwarding request to proxy."); 702 | return; 703 | } 704 | if (z == Z_EOF) { 705 | // Completed sending http request. 706 | FD_CLR_WRITE(ctx->selectfd, server); 707 | shutdown(ctx->selectfd, SHUT_WR); 708 | 709 | // Pipe proxy response from ctx->proxyfd to ctx->clientfd 710 | ctx->type = CTX_PROXY_PIPE_RESP; 711 | ctx->proxy_respbuf = lk_buffer_new(0); 712 | FD_SET_READ(ctx->selectfd, server); 713 | } 714 | } 715 | 716 | void pipe_proxy_response(LKHttpServer *server, LKContext *ctx) { 717 | int z = lk_pipe_all(ctx->proxyfd, ctx->clientfd, FD_SOCK, ctx->proxy_respbuf); 718 | if (z == Z_OPEN || z == Z_BLOCK) { 719 | return; 720 | } 721 | if (z == Z_ERR) { 722 | lk_print_err("pipe_proxy_response lk_pipe_all()"); 723 | z = terminate_fd(ctx->proxyfd, FD_SOCK, FD_READ, server); 724 | if (z == 0) { 725 | ctx->proxyfd = 0; 726 | } 727 | process_error_response(server, ctx, 500, "Error reading/writing proxy response."); 728 | return; 729 | } 730 | 731 | // EOF - finished reading/writing proxy response. 732 | assert(z == Z_EOF); 733 | 734 | // Remove proxy from read list. 735 | z = terminate_fd(ctx->proxyfd, FD_SOCK, FD_READ, server); 736 | if (z == 0) { 737 | ctx->proxyfd = 0; 738 | } 739 | 740 | LKHttpRequest *req = ctx->req; 741 | char time_str[TIME_STRING_SIZE]; 742 | get_localtime_string(time_str, sizeof(time_str)); 743 | printf("%s [%s] \"%s %s\" --> proxyhost\n", 744 | ctx->client_ipaddr->s, time_str, req->method->s, req->uri->s); 745 | 746 | // Completed sending proxy response. 747 | terminate_client_session(server, ctx); 748 | } 749 | 750 | //$$ read_proxy_response() and write_response() were 751 | // replaced by pipe_proxy_response(). 752 | #if 0 753 | void read_proxy_response(LKHttpServer *server, LKContext *ctx) { 754 | int z = lk_read_all_sock(ctx->selectfd, ctx->proxy_respbuf); 755 | if (z == Z_BLOCK) { 756 | return; 757 | } 758 | if (z == Z_ERR) { 759 | lk_print_err("lk_read_all_sock()"); 760 | z = terminate_fd(ctx->proxyfd, FD_SOCK, FD_READ, server); 761 | if (z == 0) { 762 | ctx->proxyfd = 0; 763 | } 764 | process_error_response(server, ctx, 500, "Error reading proxy response."); 765 | return; 766 | } 767 | 768 | // EOF - finished reading proxy response. 769 | assert(z == 0); 770 | 771 | // Remove proxy from read list. 772 | z = terminate_fd(ctx->proxyfd, FD_SOCK, FD_READ, server); 773 | if (z == 0) { 774 | ctx->proxyfd = 0; 775 | } 776 | 777 | LKHttpRequest *req = ctx->req; 778 | char time_str[TIME_STRING_SIZE]; 779 | get_localtime_string(time_str, sizeof(time_str)); 780 | printf("%s [%s] \"%s %s\" --> proxyhost\n", 781 | ctx->client_ipaddr->s, time_str, req->method->s, req->uri->s); 782 | 783 | ctx->type = CTX_PROXY_WRITE_RESP; 784 | ctx->selectfd = ctx->clientfd; 785 | FD_SET_WRITE(ctx->clientfd, server); 786 | } 787 | 788 | void write_proxy_response(LKHttpServer *server, LKContext *ctx) { 789 | assert(ctx->proxy_respbuf != NULL); 790 | int z = lk_write_all_sock(ctx->selectfd, ctx->proxy_respbuf); 791 | if (z == Z_BLOCK) { 792 | return; 793 | } 794 | if (z == Z_ERR) { 795 | lk_print_err("write_proxy_response lk_write_all_sock()"); 796 | process_error_response(server, ctx, 500, "Error sending proxy response."); 797 | return; 798 | } 799 | if (z == Z_EOF) { 800 | // Completed sending http response. 801 | terminate_client_session(server, ctx); 802 | } 803 | } 804 | #endif 805 | 806 | // Clear fd from select()'s, shutdown, and close. 807 | int terminate_fd(int fd, FDType fd_type, FDAction fd_action, LKHttpServer *server) { 808 | int z; 809 | if (fd_action == FD_READ || fd_action == FD_READWRITE) { 810 | FD_CLR_READ(fd, server); 811 | } 812 | if (fd_action == FD_WRITE || fd_action == FD_READWRITE) { 813 | FD_CLR_WRITE(fd, server); 814 | } 815 | 816 | if (fd_type == FD_SOCK) { 817 | if (fd_action == FD_READ) { 818 | z = shutdown(fd, SHUT_RD); 819 | } else if (fd_action == FD_WRITE) { 820 | z = shutdown(fd, SHUT_WR); 821 | } else { 822 | z = shutdown(fd, SHUT_RDWR); 823 | } 824 | if (z == -1) { 825 | //$$ Suppress logging shutdown error because remote sockets close themselves. 826 | //lk_print_err("terminate_fd shutdown()"); 827 | } 828 | } 829 | z = close(fd); 830 | if (z == -1) { 831 | lk_print_err("close()"); 832 | } 833 | return z; 834 | } 835 | 836 | // Disconnect from client. 837 | void terminate_client_session(LKHttpServer *server, LKContext *ctx) { 838 | if (ctx->clientfd) { 839 | terminate_fd(ctx->clientfd, FD_SOCK, FD_READWRITE, server); 840 | } 841 | if (ctx->cgifd) { 842 | terminate_fd(ctx->cgifd, FD_FILE, FD_READWRITE, server); 843 | } 844 | if (ctx->proxyfd) { 845 | terminate_fd(ctx->proxyfd, FD_SOCK, FD_READWRITE, server); 846 | } 847 | // Remove from linked list and free ctx. 848 | remove_client_context(&server->ctxhead, ctx->clientfd); 849 | } 850 | 851 | --------------------------------------------------------------------------------