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 |
15 |
16 |
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 |
15 |
16 |
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 |
22 |
23 |
24 |
25 |
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 |
15 |
16 |
24 |
25 |
26 |
27 |
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 |
--------------------------------------------------------------------------------