summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config/PARSING-config.txt56
-rw-r--r--src/config/PARSING-preprocess.txt38
-rw-r--r--src/config/PROTOCOL.txt43
-rw-r--r--src/config/confnode.c35
-rw-r--r--src/config/conftree.c82
-rw-r--r--src/config/defaults.c106
-rw-r--r--src/config/deps-exe/tipidee-config6
-rw-r--r--src/config/deps-exe/tipidee-config-preprocess1
-rw-r--r--src/config/lexparse.c443
-rw-r--r--src/config/tipidee-config-internal.h59
-rw-r--r--src/config/tipidee-config-preprocess.c270
-rw-r--r--src/config/tipidee-config.c135
-rw-r--r--src/include/tipidee/body.h25
-rw-r--r--src/include/tipidee/conf.h44
-rw-r--r--src/include/tipidee/headers.h38
-rw-r--r--src/include/tipidee/method.h32
-rw-r--r--src/include/tipidee/response.h36
-rw-r--r--src/include/tipidee/rql.h31
-rw-r--r--src/include/tipidee/tipidee.h15
-rw-r--r--src/include/tipidee/uri.h31
-rw-r--r--src/libtipidee/deps-lib/tipidee24
-rw-r--r--src/libtipidee/tipidee_chunked_read.c41
-rw-r--r--src/libtipidee/tipidee_conf_free.c10
-rw-r--r--src/libtipidee/tipidee_conf_get.c22
-rw-r--r--src/libtipidee/tipidee_conf_get_argv.c32
-rw-r--r--src/libtipidee/tipidee_conf_get_content_type.c22
-rw-r--r--src/libtipidee/tipidee_conf_get_redirection.c35
-rw-r--r--src/libtipidee/tipidee_conf_get_string.c17
-rw-r--r--src/libtipidee/tipidee_conf_get_uint32.c19
-rw-r--r--src/libtipidee/tipidee_conf_init.c10
-rw-r--r--src/libtipidee/tipidee_headers_get_content_length.c17
-rw-r--r--src/libtipidee/tipidee_headers_init.c29
-rw-r--r--src/libtipidee/tipidee_headers_parse.c210
-rw-r--r--src/libtipidee/tipidee_headers_search.c13
-rw-r--r--src/libtipidee/tipidee_method_conv_table.c19
-rw-r--r--src/libtipidee/tipidee_method_tonum.c12
-rw-r--r--src/libtipidee/tipidee_method_tostr.c8
-rw-r--r--src/libtipidee/tipidee_response_error.c41
-rw-r--r--src/libtipidee/tipidee_response_header_builtin.c40
-rw-r--r--src/libtipidee/tipidee_response_header_common_put.c23
-rw-r--r--src/libtipidee/tipidee_response_header_date_fmt.c24
-rw-r--r--src/libtipidee/tipidee_response_status.c27
-rw-r--r--src/libtipidee/tipidee_rql_read.c85
-rw-r--r--src/libtipidee/tipidee_uri_parse.c184
-rw-r--r--src/tipideed/cgi.c381
-rw-r--r--src/tipideed/deps-exe/tipideed11
-rw-r--r--src/tipideed/harden.c50
-rw-r--r--src/tipideed/log.c59
-rw-r--r--src/tipideed/options.c25
-rw-r--r--src/tipideed/regular.c48
-rw-r--r--src/tipideed/responses.c64
-rw-r--r--src/tipideed/send_file.c123
-rw-r--r--src/tipideed/tipideed-internal.h147
-rw-r--r--src/tipideed/tipideed.c514
-rw-r--r--src/tipideed/trace.c67
55 files changed, 3979 insertions, 0 deletions
diff --git a/src/config/PARSING-config.txt b/src/config/PARSING-config.txt
new file mode 100644
index 0000000..b1be2df
--- /dev/null
+++ b/src/config/PARSING-config.txt
@@ -0,0 +1,56 @@
+global verbosity 1
+global read_timeout 60000
+global index index.cgi index.html
+
+content-type application/pdf .pdf
+content-type text/plain .c .h .txt
+
+
+domain example.com
+
+nph-prefix nph-
+
+redirect rickroll.html 307 https://www.youtube.com/watch?v=dQw4w9WgXcQ
+redirect community/ 308 https://example.org/
+
+cgi /cgi-bin/
+nph /cgi-bin/nph/
+
+cgi /some/script
+nph /some/otherscript
+
+noncgi /cgi-bin/file.html
+noncgi /cgi-bin/directory
+
+file-type /source/ text/plain
+file-type /source/file.html text/html
+
+basic-auth /protected.html
+basic-auth /protected/
+
+
+class | 0 1 2 3 4
+st\ev | \0 space # \n other
+
+START | P np
+00 | END SPACE COMMENT START WORD
+
+COMMENT | P
+01 | END COMMENT COMMENT START COMMENT
+
+SPACE | P P np
+02 | END SPACE COMMENT START WORD
+
+WORD | 0P 0 p 0P p
+03 | END SPACE WORD START WORD
+
+END: 04
+X: 05
+
+states: 3 bits
+actions: 4 bits
+
+0x10 n new word
+0x20 p push cur
+0x40 0 push \0
+0x80 P process line
diff --git a/src/config/PARSING-preprocess.txt b/src/config/PARSING-preprocess.txt
new file mode 100644
index 0000000..e81c86e
--- /dev/null
+++ b/src/config/PARSING-preprocess.txt
@@ -0,0 +1,38 @@
+
+ Automaton for the preprocessor:
+
+
+class | 0 1 2 3 4
+st\ev | \0 \n ! space other
+
+START | print print print
+0 | END START CMD NORMAL NORMAL
+
+NORMAL | print print print print
+1 | END START NORMAL NORMAL NORMAL
+
+CMD | add
+2 | END START IGNORE CMD1 CMD2
+
+IGNORE |
+3 | END START IGNORE IGNORE IGNORE
+
+CMD1 | add
+4 | X X X CMD1 CMD2
+
+CMD2 | idcmd add
+5 | X X X ARG CMD2
+
+ARG | add
+6 | X X ARG1 ARG ARG1
+
+ARG1 | proc proc add add add
+7 | END START ARG1 ARG1 ARG1
+
+states: 0-7 plus END and X -> 4 bits
+actions: 4. -> 8 bits total, fits in a char.
+
+print 0x10 copies the character to stdout
+add 0x20 adds the character to the processing string
+idcmd 0x40 ids the processing string for an !include cmd
+proc 0x80 gets the filename and procs the include
diff --git a/src/config/PROTOCOL.txt b/src/config/PROTOCOL.txt
new file mode 100644
index 0000000..ffeb72f
--- /dev/null
+++ b/src/config/PROTOCOL.txt
@@ -0,0 +1,43 @@
+* Globals
+
+G:verbosity -> 4 bytes
+G:read_timeout -> 4 bytes exit if waiting on client input for read_timeout ms
+G:write_timeout -> 4 bytes die if waiting on flush to client for write_timeout ms
+G:cgi_timeout -> 4 bytes 504 if CGI hasn't finished answering / die if NPH hasn't finished reading
+G:max_request_body_length -> 4 bytes
+G:max_cgi_body_length -> 4 bytes
+G:index_file -> file1\0file2\0file3\0 list of files to attempt when URL is a directory
+
+They all need to exist, tipidee-config creates defaults.
+
+
+* Content-Type
+
+T:extension -> string T:pdf -> application/pdf
+or individual Content-Types in resource attributes
+
+tipidee-config hardcodes a number of default content-types, they
+can be overridden.
+
+
+* Individual redirection
+
+R:vres -> Xurl R:skarnet.org/rickroll.html -> Xhttps://www.youtube.com/watch?v=dQw4w9WgXcQ
+ X = '@' | redir_type (307=0, 308=1, 302=2, 301=3)
+
+
+* Prefix redirection
+
+r:prefix -> Xurlprefix r:s6.org -> Xhttps://skarnet.org/software/s6
+ X = '@' | redir_type (307=0, 308=1, 302=2, 301=3)
+
+* Resource attributes
+
+A:file -> Xstring X = '@' | 1 (iscgi)
+ if string nonempty: it's the content-type for the resource. If empty, default ctype
+
+a:prefix -> Xstring same, but for prefixes
+
+* NPH prefixes
+
+N:domain -> nphprefix N:skarnet.org -> nph- any CGI under skarnet.org starting with nph
diff --git a/src/config/confnode.c b/src/config/confnode.c
new file mode 100644
index 0000000..758e79d
--- /dev/null
+++ b/src/config/confnode.c
@@ -0,0 +1,35 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+
+#include <skalibs/stralloc.h>
+#include <skalibs/strerr.h>
+
+#include "tipidee-config-internal.h"
+
+#define dienomem() strerr_diefu1sys(111, "stralloc_catb")
+#define diestorage() strerr_diefu2x(100, "add node to configuration tree", ": too much data")
+#define diefilepos() strerr_diefu2x(100, "add node to configuration tree", ": file too large")
+
+void confnode_start (confnode *node, char const *key, size_t filepos, uint32_t line)
+{
+ size_t l = strlen(key) ;
+ size_t k = g.storage.len ;
+ if (!stralloc_catb(&g.storage, key, l + 1)) dienomem() ;
+ if (g.storage.len >= UINT32_MAX) diestorage() ;
+ if (filepos > UINT32_MAX) diefilepos() ;
+ node->key = k ;
+ node->keylen = l ;
+ node->data = g.storage.len ;
+ node->datalen = 0 ;
+ node->filepos = filepos ;
+ node->line = line ;
+}
+
+void confnode_add (confnode *node, char const *s, size_t len)
+{
+ if (!stralloc_catb(&g.storage, s, len)) dienomem() ;
+ if (g.storage.len >= UINT32_MAX) strerr_diefu1x(100, "add node to configuration tree: too much data") ;
+ node->datalen += len ;
+}
diff --git a/src/config/conftree.c b/src/config/conftree.c
new file mode 100644
index 0000000..fc0b5bc
--- /dev/null
+++ b/src/config/conftree.c
@@ -0,0 +1,82 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+
+#include <skalibs/gensetdyn.h>
+#include <skalibs/avltree.h>
+#include <skalibs/cdbmake.h>
+#include <skalibs/strerr.h>
+
+#include "tipidee-config-internal.h"
+
+#define dienomem() strerr_diefu1sys(111, "stralloc_catb")
+
+static void *confnode_dtok (uint32_t d, void *data)
+{
+ return g.storage.s + GENSETDYN_P(confnode, (gensetdyn *)data, d)->key ;
+}
+
+static int confnode_cmp (void const *a, void const *b, void *data)
+{
+ (void)data ;
+ return strcmp((char const *)a, (char const *)b) ;
+}
+
+struct nodestore_s
+{
+ gensetdyn set ;
+ avltree tree ;
+} ;
+
+static struct nodestore_s nodestore = \
+{ \
+ .set = GENSETDYN_INIT(confnode, 8, 3, 8), \
+ .tree = AVLTREE_INIT(8, 3, 8, &confnode_dtok, &confnode_cmp, &nodestore.set) \
+} ;
+
+
+confnode const *conftree_search (char const *key)
+{
+ uint32_t i ;
+ return avltree_search(&nodestore.tree, key, &i) ? GENSETDYN_P(confnode const, &nodestore.set, i) : 0 ;
+}
+
+void conftree_add (confnode const *node)
+{
+ uint32_t i ;
+ if (!gensetdyn_new(&nodestore.set, &i)) dienomem() ;
+ *GENSETDYN_P(confnode, &nodestore.set, i) = *node ;
+ if (!avltree_insert(&nodestore.tree, i)) dienomem() ;
+}
+
+void conftree_update (confnode const *node)
+{
+ uint32_t i ;
+ if (avltree_search(&nodestore.tree, g.storage.s + node->key, &i))
+ {
+ if (!avltree_delete(&nodestore.tree, g.storage.s + node->key)) dienomem() ;
+ *GENSETDYN_P(confnode, &nodestore.set, i) = *node ;
+ if (!avltree_insert(&nodestore.tree, i)) dienomem() ;
+ }
+ else return conftree_add(node) ;
+}
+
+static int confnode_write (uint32_t d, unsigned int h, void *data)
+{
+ confnode *node = GENSETDYN_P(confnode, &nodestore.set, d) ;
+ cdbmaker *cm = data ;
+ (void)h ;
+ if ((g.storage.s[node->key] & ~0x20) == 'A')
+ {
+ g.storage.s[++node->data] |= '@' ;
+ node->datalen-- ;
+ }
+ return cdbmake_add(cm, g.storage.s + node->key, node->keylen, g.storage.s + node->data, node->datalen) ;
+}
+
+int conftree_write (cdbmaker *cm)
+{
+ return avltree_iter(&nodestore.tree, &confnode_write, cm) ;
+}
diff --git a/src/config/defaults.c b/src/config/defaults.c
new file mode 100644
index 0000000..5913796
--- /dev/null
+++ b/src/config/defaults.c
@@ -0,0 +1,106 @@
+/* ISC license. */
+
+#include <stddef.h>
+
+#include "tipidee-config-internal.h"
+
+struct defaults_s
+{
+ char const *key ;
+ char const *value ;
+ size_t vlen ;
+} ;
+
+#define RECB(k, v, n) { .key = k, .value = v, .vlen = n }
+#define REC(k, v) RECB(k, v, sizeof(v))
+
+struct defaults_s const defaults[] =
+{
+ RECB("G:verbosity", "\0\0\0\001", 4),
+ RECB("G:read_timeout", "\0\0\0", 4),
+ RECB("G:write_timeout", "\0\0\0", 4),
+ RECB("G:cgi_timeout", "\0\0\0", 4),
+ RECB("G:max_request_body_length", "\0\0 ", 4),
+ RECB("G:max_cgi_body_length", "\0@\0", 4),
+ REC("G:index_file", "index.html"),
+
+ REC("T:html", "text/html"),
+ REC("T:htm", "text/html"),
+ REC("T:txt", "text/plain"),
+ REC("T:h", "text/plain"),
+ REC("T:c", "text/plain"),
+ REC("T:cc", "text/plain"),
+ REC("T:cpp", "text/plain"),
+ REC("T:java", "text/plain"),
+ REC("T:mjs", "text/javascript"),
+ REC("T:css", "text/css"),
+ REC("T:csv", "text/csv"),
+ REC("T:doc", "application/msword"),
+ REC("T:docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"),
+ REC("T:js", "application/javascript"),
+ REC("T:jpg", "image/jpeg"),
+ REC("T:jpeg", "image/jpeg"),
+ REC("T:gif", "image/gif"),
+ REC("T:png", "image/png"),
+ REC("T:bmp", "image/bmp"),
+ REC("T:svg", "image/svg+xml"),
+ REC("T:tif", "image/tiff"),
+ REC("T:tiff", "image/tiff"),
+ REC("T:ico", "image/vnd.microsoft.icon"),
+ REC("T:au", "audio/basic"),
+ REC("T:aac", "audio/aac"),
+ REC("T:wav", "audio/wav"),
+ REC("T:mid", "audio/midi"),
+ REC("T:midi", "audio/midi"),
+ REC("T:mp3", "audio/mpeg"),
+ REC("T:ogg", "audio/ogg"),
+ REC("T:oga", "audio/ogg"),
+ REC("T:ogv", "video/ogg"),
+ REC("T:avi", "video/x-msvideo"),
+ REC("T:wmv", "video/x-ms-wmv"),
+ REC("T:qt", "video/quicktime"),
+ REC("T:mov", "video/quicktime"),
+ REC("T:mpe", "video/mpeg"),
+ REC("T:mpeg", "video/mpeg"),
+ REC("T:mp4", "video/mp4"),
+ REC("T:otf", "font/otf"),
+ REC("T:ttf", "font/ttf"),
+ REC("T:epub", "application/epub+zip"),
+ REC("T:jar", "application/java-archive"),
+ REC("T:json", "application/json"),
+ REC("T:jsonld", "application/ld+json"),
+ REC("T:pdf", "application/pdf"),
+ REC("T:ppt", "application/vnd.ms-powerpoint"),
+ REC("T:pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"),
+ REC("T:odp", "application/vnd.oasis.opendocument.presentation"),
+ REC("T:ods", "application/vnd.oasis.opendocument.spreadsheet"),
+ REC("T:odt", "application/vnd.oasis.opendocument.text"),
+ REC("T:oggx", "application/ogg"),
+ REC("T:rar", "application/vnd.rar"),
+ REC("T:rtf", "application/rtf"),
+ REC("T:sh", "application/x-sh"),
+ REC("T:tar", "application/x-tar"),
+ REC("T:xhtml", "application/xhtml+xml"),
+ REC("T:xls", "application/vnd.ms-excel"),
+ REC("T:xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),
+ REC("T:xml", "application/xml"),
+ REC("T:xul", "application/vnd.mozilla.xul+xml"),
+ REC("T:zip", "application/zip"),
+ REC("T:7z", "application/x-7z-compressed"),
+
+ RECB(0, 0, 0)
+} ;
+
+void conf_defaults (void)
+{
+ for (struct defaults_s const *p = defaults ; p->key ; p++)
+ {
+ if (!conftree_search(p->key))
+ {
+ confnode node ;
+ confnode_start(&node, p->key, 0, 0) ;
+ confnode_add(&node, p->value, p->vlen) ;
+ conftree_add(&node) ;
+ }
+ }
+}
diff --git a/src/config/deps-exe/tipidee-config b/src/config/deps-exe/tipidee-config
new file mode 100644
index 0000000..85a9645
--- /dev/null
+++ b/src/config/deps-exe/tipidee-config
@@ -0,0 +1,6 @@
+confnode.o
+conftree.o
+defaults.o
+lexparse.o
+-lskarnet
+${SPAWN_LIB}
diff --git a/src/config/deps-exe/tipidee-config-preprocess b/src/config/deps-exe/tipidee-config-preprocess
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/config/deps-exe/tipidee-config-preprocess
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/config/lexparse.c b/src/config/lexparse.c
new file mode 100644
index 0000000..f843f97
--- /dev/null
+++ b/src/config/lexparse.c
@@ -0,0 +1,443 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <skalibs/uint32.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/skamisc.h>
+
+#include <tipidee/config.h>
+#include "tipidee-config-internal.h"
+
+#define dienomem() strerr_diefu1sys(111, "stralloc_catb")
+#define dietoobig() strerr_diefu1sys(100, "read configuration")
+
+typedef struct mdt_s mdt, *mdt_ref ;
+struct mdt_s
+{
+ size_t filepos ;
+ uint32_t line ;
+ char linefmt[UINT32_FMT] ;
+} ;
+#define MDT_ZERO { .filepos = 0, .line = 0, .linefmt = "0" }
+
+struct globalkey_s
+{
+ char const *name ;
+ char const *key ;
+ uint32_t type ;
+} ;
+
+static int globalkey_cmp (void const *a, void const *b)
+{
+ return strcmp((char const *)a, ((struct globalkey_s const *)b)->name) ;
+}
+
+enum token_e
+{
+ T_BANG,
+ T_GLOBAL,
+ T_CONTENTTYPE,
+ T_DOMAIN,
+ T_NPHPREFIX,
+ T_REDIRECT,
+ T_CGI,
+ T_NONCGI,
+ T_NPH,
+ T_NONNPH,
+ T_BASICAUTH,
+ T_NOAUTH,
+ T_FILETYPE
+} ;
+
+struct directive_s
+{
+ char const *s ;
+ enum token_e token ;
+} ;
+
+static int directive_cmp (void const *a, void const *b)
+{
+ return strcmp((char const *)a, ((struct directive_s const *)b)->s) ;
+}
+
+static void check_unique (char const *key, mdt const *md)
+{
+ confnode const *node = conftree_search(key) ;
+ if (node)
+ {
+ char fmt[UINT32_FMT] ;
+ fmt[uint32_fmt(fmt, node->line)] = 0 ;
+ strerr_diefn(1, 11, "duplicate key ", key, " in file ", g.storage.s + md->filepos, " line ", md->linefmt, ", previously defined", " in file ", g.storage.s + node->filepos, " line ", fmt) ;
+ }
+}
+
+static void add_unique (char const *key, char const *value, size_t valuelen, mdt const *md)
+{
+ confnode node ;
+ check_unique(key, md) ;
+ confnode_start(&node, key, md->filepos, md->line) ;
+ confnode_add(&node, value, valuelen) ;
+ conftree_add(&node) ;
+}
+
+static inline void parse_global (char const *s, size_t const *word, size_t n, mdt const *md)
+{
+ static struct globalkey_s const globalkeys[] =
+ {
+ { .name = "cgi_timeout", .key = "G:cgi_timeout", .type = 0 },
+ { .name = "index_file", .key = "G:index_file", .type = 1 },
+ { .name = "max_cgi_body_length", .key = "G:max_cgi_body_length", .type = 0 },
+ { .name = "max_request_body_length", .key = "G:max_request_body_length", .type = 0 },
+ { .name = "read_timeout", .key = "G:read_timeout", .type = 0 },
+ { .name = "verbosity", .key = "G:verbosity", .type = 0 },
+ { .name = "write_timeout", .key = "G:write_timeout", .type = 0 }
+ } ;
+ struct globalkey_s *gl ;
+ if (n != 2)
+ strerr_dief8x(1, "too ", n > 2 ? "many" : "few", " arguments to directive ", "global", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ gl = bsearch(s + word[0], globalkeys, sizeof(globalkeys)/sizeof(struct globalkey_s), sizeof(struct globalkey_s), &globalkey_cmp) ;
+ if (!gl) strerr_dief6x(1, "unrecognized global setting ", s + word[0], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ switch (gl->type)
+ {
+ case 0 : /* 4 bytes */
+ {
+ char pack[4] ;
+ uint32_t u ;
+ if (n != 2) strerr_dief7x(1, "too many", " arguments to global setting ", s + word[0], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (!uint320_scan(s + word[1], &u))
+ strerr_dief6x(1, "invalid (non-numeric) value for global setting ", s + word[0], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ uint32_pack_big(pack, u) ;
+ add_unique(gl->key, pack, 4, md) ;
+ break ;
+ }
+ case 1 : /* argv */
+ {
+ confnode node ;
+ check_unique(gl->key, md) ;
+ confnode_start(&node, gl->key, md->filepos, md->line) ;
+ for (size_t i = 1 ; i < n ; i++)
+ confnode_add(&node, s + word[i], strlen(s + word[i]) + 1) ;
+ conftree_add(&node) ;
+ break ;
+ }
+ }
+}
+
+static inline void parse_contenttype (char const *s, size_t const *word, size_t n, mdt const *md)
+{
+ char const *ct ;
+ if (n != 3)
+ strerr_dief8x(1, "too ", n > 3 ? "many" : "few", " arguments to directive ", "redirect", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ ct = s + *word++ ;
+ if (!strchr(ct, '/'))
+ strerr_dief6x(1, "Content-Type must include a slash, ", "check directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ n-- ;
+ for (size_t i = 0 ; i < n ; i++)
+ {
+ size_t len = strlen(s + word[i]) ;
+ char key[len + 2] ;
+ if (s[word[i]] != '.')
+ strerr_dief6x(1, "file extensions must start with a dot, ", "check directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ key[0] = 'T' ;
+ key[1] = ':' ;
+ memcpy(key + 2, s + word[i] + 1, len - 1) ;
+ key[len + 1] = 0 ;
+ add_unique(key, ct, strlen(ct) + 1, md) ;
+ }
+}
+
+static inline void parse_redirect (char const *s, size_t const *word, size_t n, char const *domain, size_t domainlen, mdt const *md)
+{
+ static uint32_t const codes[4] = { 307, 308, 302, 301 } ;
+ uint32_t code ;
+ uint32_t i = 0 ;
+ if (n != 3)
+ strerr_dief8x(1, "too ", n > 3 ? "many" : "few", " arguments to directive ", "redirect", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (!domain)
+ strerr_dief6x(1, "redirection", " without a domain directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (s[word[0]] != '/')
+ strerr_dief5x(1, "redirected resource must start with /", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (!uint320_scan(s + word[1], &code))
+ strerr_dief6x(1, "invalid redirection code ", s + word[1], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ for (; i < 4 ; i++) if (code == codes[i]) break ;
+ if (i >= 4)
+ strerr_dief6x(1, "redirection code ", "must be 301, 302, 307 or 308", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (strncmp(s + word[2], "http://", 7) && strncmp(s + word[2], "https://", 8))
+ strerr_dief5x(1, "redirection target must be a full http:// or https:// target", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ {
+ confnode node ;
+ size_t urlen = strlen(s + word[0]) ;
+ char key[3 + domainlen + urlen] ;
+ if (s[word[0] + urlen - 1] == '/') { key[0] = 'r' ; urlen-- ; } else key[0] = 'R' ;
+ key[1] = ':' ;
+ memcpy(key + 2, domain, domainlen) ;
+ memcpy(key + 2 + domainlen, s + word[0], urlen) ;
+ key[2 + domainlen + urlen] = 0 ;
+ check_unique(key, md) ;
+ confnode_start(&node, key, md->filepos, md->line) ;
+ key[0] = '@' | i ;
+ confnode_add(&node, &key[0], 1) ;
+ confnode_add(&node, s + word[2], strlen(s + word[2]) + 1) ;
+ conftree_add(&node) ;
+ }
+}
+
+static void parse_bitattr (char const *s, size_t const *word, size_t n, char const *domain, size_t domainlen, mdt const *md, unsigned int bit, int set)
+{
+ static char const *attr[3][2] = { { "noncgi", "cgi" }, { "nonnph", "nph", }, { "noauth", "basic-auth" } } ;
+ uint8_t mask = (uint8_t)0x01 << bit ;
+ if (n != 1)
+ strerr_dief8x(1, "too ", n > 1 ? "many" : "few", " arguments to directive ", attr[bit][set], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (!domain)
+ strerr_dief7x(1, "resource attribute ", "definition", " without a domain directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (s[*word] != '/')
+ strerr_dief5x(1, "resource must start with /", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ {
+ confnode const *oldnode ;
+ size_t arglen = strlen(s + *word) ;
+ char key[3 + domainlen + arglen] ;
+ if (s[*word + arglen - 1] == '/') { key[0] = 'a' ; arglen-- ; } else key[0] = 'A' ;
+ key[1] = ':' ;
+ memcpy(key + 2, domain, domainlen) ;
+ memcpy(key + 2 + domainlen, s + *word, arglen) ;
+ key[2 + domainlen + arglen] = 0 ;
+ oldnode = conftree_search(key) ;
+ if (oldnode)
+ if (g.storage.s[oldnode->data] & mask)
+ {
+ char fmt[UINT32_FMT] ;
+ fmt[uint32_fmt(fmt, oldnode->line)] = 0 ;
+ strerr_diefn(1, 13, "resource attribute ", attr[bit][set], " redefinition", " in file ", g.storage.s + md->filepos, " line ", md->linefmt, "; previous definition", " in file ", g.storage.s + oldnode->filepos, " line ", fmt, " or later") ;
+ }
+ else
+ {
+ g.storage.s[oldnode->data] |= mask ;
+ if (set) g.storage.s[oldnode->data + 1] |= mask ;
+ else g.storage.s[oldnode->data + 1] &= ~mask ;
+ }
+ else
+ {
+ confnode node ;
+ char val[3] = { mask, set ? mask : 0, 0 } ;
+ confnode_start(&node, key, md->filepos, md->line) ;
+ confnode_add(&node, val, 3) ;
+ conftree_add(&node) ;
+ }
+ }
+}
+
+static inline void parse_filetype (char const *s, size_t const *word, size_t n, char const *domain, size_t domainlen, mdt const *md)
+{
+ if (n != 2)
+ strerr_dief8x(1, "too ", n > 2 ? "many" : "few", " arguments to directive ", "file-type", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (!domain)
+ strerr_dief7x(1, "file-type", " definition", " without a domain directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (s[word[0]] != '/')
+ strerr_dief5x(1, "resource must start with /", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ {
+ confnode const *oldnode ;
+ size_t arglen = strlen(s + word[0]) ;
+ char key[3 + domainlen + arglen] ;
+ if (s[word[0] + arglen - 1] == '/') { key[0] = 'a' ; arglen-- ; } else key[0] = 'A' ;
+ key[1] = ':' ;
+ memcpy(key + 2, domain, domainlen) ;
+ memcpy(key + 2 + domainlen, s + *word, arglen) ;
+ key[2 + domainlen + arglen] = 0 ;
+ oldnode = conftree_search(key) ;
+ if (oldnode)
+ {
+ if (g.storage.s[oldnode->data] & 0x80)
+ {
+ char fmt[UINT32_FMT] ;
+ fmt[uint32_fmt(fmt, oldnode->line)] = 0 ;
+ strerr_diefn(1, 12, "file-type", " redefinition", " in file ", g.storage.s + md->filepos, " line ", md->linefmt, "; previous definition", " in file ", g.storage.s + oldnode->filepos, " line ", fmt, " or later") ;
+ }
+
+ else
+ {
+ confnode node ;
+ char val[2] = { g.storage.s[oldnode->data] | 0x80, g.storage.s[oldnode->data + 1] } ;
+ confnode_start(&node, key, md->filepos, md->line) ;
+ confnode_add(&node, val, 2) ;
+ confnode_add(&node, s + word[1], strlen(s + word[1]) + 1) ;
+ conftree_update(&node) ;
+ }
+ }
+ else
+ {
+ confnode node ;
+ char val[2] = { 0x80, 0x00 } ;
+ confnode_start(&node, key, md->filepos, md->line) ;
+ confnode_add(&node, val, 2) ;
+ confnode_add(&node, s + word[1], strlen(s + word[1]) + 1) ;
+ conftree_add(&node) ;
+ }
+ }
+}
+
+static inline void process_line (char const *s, size_t const *word, size_t n, stralloc *domain, mdt *md)
+{
+ static struct directive_s const directives[] =
+ {
+ { .s = "!", .token = T_BANG },
+ { .s = "basic-auth", .token = T_BASICAUTH },
+ { .s = "cgi", .token = T_CGI },
+ { .s = "content-type", .token = T_CONTENTTYPE },
+ { .s = "domain", .token = T_DOMAIN },
+ { .s = "file-type", .token = T_FILETYPE },
+ { .s = "global", .token = T_GLOBAL },
+ { .s = "no-auth", .token = T_NOAUTH },
+ { .s = "noncgi", .token = T_NONCGI },
+ { .s = "nonnph", .token = T_NONNPH },
+ { .s = "nph", .token = T_NPH },
+ { .s = "nph-prefix", .token = T_NPHPREFIX },
+ { .s = "redirect", .token = T_REDIRECT },
+ } ;
+ struct directive_s const *directive ;
+ char const *word0 ;
+ if (!n--) return ;
+ word0 = s + *word++ ;
+ directive = bsearch(word0, directives, sizeof(directives)/sizeof(struct directive_s), sizeof(struct directive_s), &directive_cmp) ;
+ if (!directive)
+ strerr_dief6x(1, "unrecognized word ", word0, " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ switch (directive->token)
+ {
+ case T_BANG :
+ {
+ size_t len, r, w ;
+ if (n != 2 || !uint320_scan(s + word[0], &md->line))
+ strerr_dief5x(101, "can't happen: invalid ! control directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ len = strlen(s + word[1]) ;
+ if (!stralloc_readyplus(&g.storage, len + 1)) dienomem() ;
+ if (!string_unquote(g.storage.s + g.storage.len, &w, s + word[1], len, &r))
+ strerr_dief7sys(101, "can't happen: unable to unquote ", s + word[1], " in file ", g.storage.s + md->filepos, " line ", md->linefmt, ", error reported is") ;
+ g.storage.s[g.storage.len + w++] = 0 ;
+ md->filepos = g.storage.len ;
+ g.storage.len += w ;
+ break ;
+ }
+ case T_GLOBAL :
+ parse_global(s, word, n, md) ;
+ break ;
+ case T_CONTENTTYPE :
+ parse_contenttype(s, word, n, md) ;
+ break ;
+ case T_DOMAIN :
+ if (n != 1)
+ strerr_dief8x(1, "too", n > 1 ? "many" : "few", " arguments to directive ", "domain", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ domain->len = 0 ;
+ if (!stralloc_cats(domain, s + *word)) dienomem() ;
+ while (domain->len && (domain->s[domain->len - 1] == '/' || domain->s[domain->len - 1] == '.')) domain->len-- ;
+ break ;
+ case T_NPHPREFIX :
+ if (n != 1)
+ strerr_dief8x(1, "too ", n > 1 ? "many" : "few", " arguments to directive ", "nph-prefix", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (!domain->s)
+ strerr_dief6x(1, "nph prefix definition", "without a domain directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (strchr(s + *word, '/')) strerr_dief5x(1, "invalid / in nph-prefix argument", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ {
+ char key[3 + domain->len] ;
+ key[0] = 'N' ;
+ key[1] = ':' ;
+ memcpy(key + 2, domain->s, domain->len) ;
+ key[2 + domain->len] = 0 ;
+ add_unique(key, s + *word, strlen(s + *word) + 1, md) ;
+ }
+ break ;
+ case T_REDIRECT :
+ parse_redirect(s, word, n, domain->s, domain->len, md) ;
+ break ;
+ case T_CGI :
+ parse_bitattr(s, word, n, domain->s, domain->len, md, 0, 1) ;
+ break ;
+ case T_NONCGI :
+ parse_bitattr(s, word, n, domain->s, domain->len, md, 0, 0) ;
+ break ;
+ case T_NPH :
+ parse_bitattr(s, word, n, domain->s, domain->len, md, 1, 1) ;
+ break ;
+ case T_NONNPH :
+ parse_bitattr(s, word, n, domain->s, domain->len, md, 1, 0) ;
+ break ;
+ case T_BASICAUTH :
+ strerr_warnw5x("file ", g.storage.s + md->filepos, " line ", md->linefmt, ": directive basic-auth not implemented in tipidee-" TIPIDEE_VERSION) ;
+ parse_bitattr(s, word, n, domain->s, domain->len, md, 2, 1) ;
+ break ;
+ case T_NOAUTH :
+ strerr_warnw5x("file ", g.storage.s + md->filepos, " line ", md->linefmt, ": directive basic-auth not implemented in tipidee-" TIPIDEE_VERSION) ;
+ parse_bitattr(s, word, n, domain->s, domain->len, md, 2, 0) ;
+ break ;
+ case T_FILETYPE :
+ parse_filetype(s, word, n, domain->s, domain->len, md) ;
+ break ;
+ }
+}
+
+static inline uint8_t cclass (char c)
+{
+ switch (c)
+ {
+ case 0 : return 0 ;
+ case ' ' :
+ case '\t' :
+ case '\f' :
+ case '\r' : return 1 ;
+ case '#' : return 2 ;
+ case '\n' : return 3 ;
+ default : return 4 ;
+ }
+}
+
+static inline char next (buffer *b, mdt const *md)
+{
+ char c ;
+ ssize_t r = buffer_get(b, &c, 1) ;
+ if (r == -1) strerr_diefu1sys(111, "read from preprocessor") ;
+ if (!r) return 0 ;
+ if (!c) strerr_dief5x(1, "null character", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ return c ;
+}
+
+void conf_lexparse (buffer *b, char const *ifile)
+{
+ static uint8_t const table[4][5] = /* see PARSING.txt */
+ {
+ { 0x04, 0x02, 0x01, 0x80, 0x33 },
+ { 0x04, 0x01, 0x01, 0x80, 0x01 },
+ { 0x84, 0x02, 0x01, 0x80, 0x33 },
+ { 0xc4, 0x42, 0x23, 0xc0, 0x23 }
+ } ;
+ stralloc sa = STRALLOC_ZERO ;
+ genalloc words = GENALLOC_ZERO ; /* size_t */
+ stralloc domain = STRALLOC_ZERO ;
+ mdt md = MDT_ZERO ;
+ uint8_t state = 0 ;
+ if (!stralloc_catb(&g.storage, ifile, strlen(ifile) + 1)) dienomem() ;
+ while (state < 0x04)
+ {
+ char c = next(b, &md) ;
+ uint8_t what = table[state][cclass(c)] ;
+ state = what & 0x07 ;
+ if (what & 0x10) if (!genalloc_catb(size_t, &words, &sa.len, 1)) dienomem() ;
+ if (what & 0x20) if (!stralloc_catb(&sa, &c, 1)) dienomem() ;
+ if (what & 0x40) if (!stralloc_0(&sa)) dienomem() ;
+ if (what & 0x80)
+ {
+ process_line(sa.s, genalloc_s(size_t, &words), genalloc_len(size_t, &words), &domain, &md) ;
+ genalloc_setlen(size_t, &words, 0) ;
+ sa.len = 0 ;
+ md.line++ ;
+ md.linefmt[uint32_fmt(md.linefmt, md.line)] = 0 ;
+ }
+ }
+ stralloc_free(&domain) ;
+ genalloc_free(size_t, &words) ;
+ stralloc_free(&sa) ;
+}
diff --git a/src/config/tipidee-config-internal.h b/src/config/tipidee-config-internal.h
new file mode 100644
index 0000000..e274f94
--- /dev/null
+++ b/src/config/tipidee-config-internal.h
@@ -0,0 +1,59 @@
+/* ISC license. */
+
+#ifndef TIPIDEE_CONFIG_INTERNAL_H
+#define TIPIDEE_CONFIG_INTERNAL_H
+
+#include <stdint.h>
+#include <string.h>
+
+#include <skalibs/buffer.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/cdbmake.h>
+
+typedef struct confnode_s confnode, *confnode_ref ;
+struct confnode_s
+{
+ uint32_t key ;
+ uint32_t keylen ;
+ uint32_t data ;
+ uint32_t datalen ;
+ uint32_t filepos ;
+ uint32_t line ;
+} ;
+#define CONFNODE_ZERO { .key = 0, .keylen = 0, .data = 0, .datalen = 0 }
+
+struct global_s
+{
+ stralloc storage ;
+} ;
+#define GLOBAL_ZERO { .storage = STRALLOC_ZERO }
+
+extern struct global_s g ;
+
+
+ /* confnode */
+
+extern void confnode_start (confnode *, char const *, size_t, uint32_t) ;
+extern void confnode_add (confnode *, char const *, size_t) ;
+#define confnode_adds(node, s) confnode_add(node, (s), strlen(s))
+#define confnode_add0(node) confnode_add((node), "", 1)
+
+
+ /* conftree */
+
+extern confnode const *conftree_search (char const *) ;
+extern void conftree_add (confnode const *) ;
+extern void conftree_update (confnode const *) ;
+extern int conftree_write (cdbmaker *) ;
+
+
+ /* lexparse */
+
+extern void conf_lexparse (buffer *, char const *) ;
+
+
+ /* defaults */
+
+extern void conf_defaults (void) ;
+
+#endif
diff --git a/src/config/tipidee-config-preprocess.c b/src/config/tipidee-config-preprocess.c
new file mode 100644
index 0000000..6ac4812
--- /dev/null
+++ b/src/config/tipidee-config-preprocess.c
@@ -0,0 +1,270 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <skalibs/uint32.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/direntry.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/avltree.h>
+
+#define USAGE "tipidee-config-preprocess file"
+#define dieusage() strerr_dieusage(100, USAGE)
+#define dienomem() strerr_diefu1sys(111, "stralloc_catb") ;
+
+static stralloc sa = STRALLOC_ZERO ;
+static genalloc ga = GENALLOC_ZERO ; /* size_t */
+
+
+ /* Name storage */
+
+static stralloc namesa = STRALLOC_ZERO ;
+
+static void *name_dtok (uint32_t pos, void *aux)
+{
+ return ((stralloc *)aux)->s + pos + 1 ;
+}
+
+static int name_cmp (void const *a, void const *b, void *aux)
+{
+ (void)aux ;
+ return strcmp((char const *)a, (char const *)b) ;
+}
+
+static avltree namemap = AVLTREE_INIT(8, 3, 8, &name_dtok, &name_cmp, &namesa) ;
+
+
+ /* Directory sorting */
+
+static char const *dname_cmp_base ;
+static int dname_cmp (void const *a, void const *b)
+{
+ return strcmp(dname_cmp_base + *(size_t *)a, dname_cmp_base + *(size_t *)b) ;
+}
+
+
+ /* Recursive inclusion functions */
+
+static void includefromhere (char const *) ;
+
+static inline void includecwd (void)
+{
+ DIR *dir ;
+ size_t sabase = sa.len ;
+ size_t gabase = genalloc_len(size_t, &ga) ;
+ if (sagetcwd(&sa) < 0 || !stralloc_0(&sa)) dienomem() ;
+ dir = opendir(".") ;
+ if (!dir) strerr_diefu2sys(111, "opendir ", sa.s + sabase) ;
+
+ for (;;)
+ {
+ direntry *d ;
+ errno = 0 ;
+ d = readdir(dir) ;
+ if (!d) break ;
+ if (d->d_name[0] == '.') continue ;
+ if (!genalloc_catb(size_t, &ga, &sa.len, 1)) dienomem() ;
+ if (!stralloc_catb(&sa, d->d_name, strlen(d->d_name)+1)) dienomem() ;
+ }
+ dir_close(dir) ;
+ if (errno) strerr_diefu2sys(111, "readdir ", sa.s + sabase) ;
+
+ dname_cmp_base = sa.s ;
+ qsort(genalloc_s(size_t, &ga) + gabase, genalloc_len(size_t, &ga) - gabase, sizeof(size_t), &dname_cmp) ;
+
+ for (size_t i = 0 ; i < genalloc_len(size_t, &ga) ; i++)
+ includefromhere(sa.s + genalloc_s(size_t, &ga)[gabase + i]) ;
+
+ genalloc_setlen(size_t, &ga, gabase) ;
+ sa.len = sabase ;
+}
+
+static void include (char const *file)
+{
+ size_t sabase = sa.len ;
+ size_t filelen = strlen(file) ;
+ if (!sadirname(&sa, file, filelen) || !stralloc_0(&sa)) dienomem() ;
+ if (chdir(sa.s + sabase) < 0) strerr_diefu2sys(111, "chdir to ", sa.s + sabase) ;
+ sa.len = sabase ;
+ if (!sabasename(&sa, file, filelen)) dienomem() ;
+ {
+ char fn[sa.len + 1 - sabase] ;
+ memcpy(fn, sa.s + sabase, sa.len - sabase) ;
+ fn[sa.len - sabase] = 0 ;
+ sa.len = sabase ;
+ includefromhere(fn) ;
+ }
+}
+
+static inline int idcmd (char const *s)
+{
+ static char const *commands[] =
+ {
+ "include",
+ "includedir",
+ "included:",
+ 0
+ } ;
+ for (char const **p = commands ; *p ; p++)
+ if (!strcmp(s, *p)) return p - commands ;
+ return -1 ;
+}
+
+static inline unsigned char cclass (char c)
+{
+ static unsigned char const classtable[34] = "0444444443144344444444444444444432" ;
+ return (unsigned char)c < 34 ? classtable[(unsigned char)c] - '0' : 4 ;
+}
+
+static void includefromhere (char const *file)
+{
+ static unsigned char const table[8][5] =
+ {
+ { 0x08, 0x10, 0x02, 0x11, 0x11 },
+ { 0x08, 0x10, 0x11, 0x11, 0x11 },
+ { 0x08, 0x00, 0x03, 0x04, 0x25 },
+ { 0x08, 0x00, 0x03, 0x03, 0x03 },
+ { 0x09, 0x09, 0x09, 0x04, 0x25 },
+ { 0x09, 0x09, 0x09, 0x46, 0x25 },
+ { 0x0a, 0x0a, 0x07, 0x06, 0x27 },
+ { 0x88, 0x80, 0x27, 0x27, 0x27 }
+ } ;
+ size_t sabase = sa.len ;
+ size_t namesabase = namesa.len ;
+ size_t sastart ;
+ int cmd = -1 ;
+ int fd ;
+ buffer b ;
+ uint32_t d ;
+ uint32_t line = 1 ;
+ char buf[4096] ;
+ char linefmt[UINT32_FMT] = "1" ;
+ unsigned char state = 0 ;
+
+ if (!stralloc_catb(&namesa, "\004", 1) || sarealpath(&namesa, file) < 0 || !stralloc_0(&namesa)) dienomem() ;
+ if (avltree_search(&namemap, namesa.s + namesabase + 1, &d))
+ {
+ if (namesa.s[d] & 0x04)
+ strerr_dief3x(3, "file ", namesa.s + namesabase + 1, " is included in a cycle") ;
+ if (!(namesa.s[d] & 0x02))
+ strerr_dief3x(3, "file ", namesa.s + namesabase + 1, " is included twice but does not declare !included: unique or !included: multiple") ;
+ namesa.len = namesabase ;
+ if (namesa.s[d] & 0x01) return ;
+ }
+ else
+ {
+ if (namesabase > UINT32_MAX)
+ strerr_dief3x(3, "in ", namesa.s + d + 1, ": too many, too long filenames") ;
+ d = namesabase ;
+ if (!avltree_insert(&namemap, d)) dienomem() ;
+ }
+
+ if (!string_quote(&sa, namesa.s + d + 1, strlen(namesa.s + d + 1))) dienomem() ;
+ if (!stralloc_0(&sa)) dienomem() ;
+
+ sastart = sa.len ;
+ fd = open_readb(file) ;
+ if (fd < 0) strerr_diefu2sys(111, "open ", namesa.s + d + 1) ;
+ buffer_init(&b, &buffer_read, fd, buf, 4096) ;
+
+ if (buffer_put(buffer_1, "! 0 ", 4) < 4
+ || buffer_puts(buffer_1, sa.s + sabase) < 0
+ || buffer_put(buffer_1, "\n", 1) < 1)
+ strerr_diefu1sys(111, "write to stdout") ;
+
+ while (state < 8)
+ {
+ uint16_t what ;
+ char c = 0 ;
+ if (buffer_get(&b, &c, 1) < 0) strerr_diefu2sys(111, "read from ", namesa.s + d + 1) ;
+ what = table[state][cclass((unsigned char)c)] ;
+ state = what & 0x000f ;
+ if (what & 0x0010) if (buffer_put(buffer_1, &c, 1) < 1) strerr_diefu1sys(111, "write to stdout") ;
+ if (what & 0x0020) if (!stralloc_catb(&sa, &c, 1)) dienomem() ;
+ if (what & 0x0040)
+ {
+ if (!stralloc_0(&sa)) dienomem() ;
+ cmd = idcmd(sa.s + sastart) ;
+ if (cmd == -1)
+ strerr_dief6x(2, "in ", namesa.s + d + 1, " line ", linefmt, ": unrecognized directive: ", sa.s + sastart) ;
+ sa.len = sastart ;
+ }
+ if (what & 0x0080)
+ {
+ if (!stralloc_0(&sa)) dienomem() ;
+ switch (cmd)
+ {
+ case 2 :
+ if (!strcmp(sa.s + sastart, "unique")) namesa.s[d] |= 3 ;
+ else if (!strcmp(sa.s + sastart, "multiple")) namesa.s[d] |= 2 ;
+ else strerr_dief6x(3, "in ", namesa.s + d + 1, " line ", linefmt, "invalid !included: argument: ", sa.s + sastart) ;
+ break ;
+ case 1 :
+ case 0 :
+ {
+ int fdhere = open2(".", O_RDONLY | O_DIRECTORY) ;
+ if (fdhere == -1)
+ strerr_dief3sys(111, "in ", namesa.s + d + 1, ": unable to open base directory: ") ;
+ if (cmd & 1)
+ {
+ if (chdir(sa.s + sastart) == -1)
+ strerr_dief6sys(111, "in ", namesa.s + d + 1, " line ", linefmt, ": unable to chdir to ", sa.s + sastart) ;
+ includecwd() ;
+ }
+ else include(sa.s + sastart) ;
+ if (fchdir(fdhere) == -1)
+ strerr_dief4sys(111, "in ", namesa.s + d + 1, ": unable to fchdir back after including ", sa.s + sastart) ;
+ fd_close(fdhere) ;
+ if (buffer_put(buffer_1, "! ", 2) < 2
+ || buffer_puts(buffer_1, linefmt) < 0
+ || buffer_put(buffer_1, " ", 1) < 1
+ || buffer_puts(buffer_1, sa.s + sabase) < 0
+ || buffer_put(buffer_1, "\n", 1) < 1)
+ strerr_diefu1sys(111, "write to stdout") ;
+ break ;
+ }
+ }
+ sa.len = sastart ;
+ }
+ if (c == '\n' && state <= 8) linefmt[uint32_fmt(linefmt, ++line)] = 0 ;
+ }
+ if (state > 8) strerr_dief5x(2, "in ", namesa.s + d + 1, " line ", linefmt, ": syntax error: invalid ! line") ;
+ fd_close(fd) ;
+ sa.len = sabase ;
+ namesa.s[d] &= ~0x04 ;
+}
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+ PROG = "tipidee-config-preprocess" ;
+ {
+ subgetopt l = SUBGETOPT_ZERO ;
+ for (;;)
+ {
+ int opt = subgetopt_r(argc, argv, "", &l) ;
+ if (opt == -1) break ;
+ switch (opt)
+ {
+ default : dieusage() ;
+ }
+ }
+ argc -= l.ind ; argv += l.ind ;
+ }
+ if (!argc) dieusage() ;
+
+ include(argv[0]) ;
+ if (!buffer_flush(buffer_1))
+ strerr_diefu1sys(111, "write to stdout") ;
+ return 0 ;
+}
diff --git a/src/config/tipidee-config.c b/src/config/tipidee-config.c
new file mode 100644
index 0000000..be13e39
--- /dev/null
+++ b/src/config/tipidee-config.c
@@ -0,0 +1,135 @@
+/* ISC license. */
+
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h> /* rename() */
+#include <errno.h>
+#include <signal.h>
+
+#include <skalibs/posixplz.h>
+#include <skalibs/types.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr.h>
+#include <skalibs/sig.h>
+#include <skalibs/djbunix.h>
+
+#include <tipidee/config.h>
+#include "tipidee-config-internal.h"
+
+#define USAGE "tipidee-config [ -i textfile ] [ -o cdbfile ] [ -m mode ]"
+#define dieusage() strerr_dieusage(100, USAGE)
+
+struct global_s g = GLOBAL_ZERO ;
+
+static pid_t pid = 0 ;
+
+static void sigchld_handler (int sig)
+{
+ (void)sig ;
+ for (;;)
+ {
+ int wstat ;
+ pid_t r = wait_nohang(&wstat) ;
+ if (r == -1 && errno != ECHILD) strerr_diefu1sys(111, "wait") ;
+ else if (r <= 0) break ;
+ else if (r == pid)
+ {
+ if (WIFEXITED(wstat) && !WEXITSTATUS(wstat)) pid = 0 ;
+ else _exit(wait_estatus(wstat)) ;
+ }
+ }
+}
+
+static inline void conf_output (char const *ofile, unsigned int omode)
+{
+ int fdw ;
+ cdbmaker cm = CDBMAKER_ZERO ;
+ size_t olen = strlen(ofile) ;
+ char otmp[olen + 8] ;
+ memcpy(otmp, ofile, olen) ;
+ memcpy(otmp + olen, ":XXXXXX", 8) ;
+ fdw = mkstemp(otmp) ;
+ if (fdw == -1) strerr_diefu3sys(111, "open ", otmp, " for writing") ;
+ if (coe(fdw) == -1)
+ {
+ unlink_void(otmp) ;
+ strerr_diefu2sys(111, "coe ", otmp) ;
+ }
+ if (!cdbmake_start(&cm, fdw))
+ {
+ unlink_void(otmp) ;
+ strerr_diefu2sys(111, "cdmake_start ", otmp) ;
+ }
+ if (!conftree_write(&cm))
+ {
+ unlink_void(otmp) ;
+ strerr_diefu2sys(111, "write config tree into ", otmp) ;
+ }
+ if (!cdbmake_finish(&cm))
+ {
+ unlink_void(otmp) ;
+ strerr_diefu2sys(111, "cdbmake_finish ", otmp) ;
+ }
+ if (fsync(fdw) == -1)
+ {
+ unlink_void(otmp) ;
+ strerr_diefu2sys(111, "fsync ", otmp) ;
+ }
+ if (fchmod(fdw, omode & 0755) == -1)
+ {
+ unlink_void(otmp) ;
+ strerr_diefu2sys(111, "fchmod ", otmp) ;
+ }
+ if (rename(otmp, ofile) == -1)
+ {
+ unlink_void(otmp) ;
+ strerr_diefu4sys(111, "rename ", otmp, " to ", ofile) ;
+ }
+}
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+ char const *ifile = "/etc/tipidee.conf" ;
+ char const *ofile = "/etc/tipidee.conf.cdb" ;
+ unsigned int omode = 0644 ;
+
+ PROG = "tipidee-config" ;
+ {
+ subgetopt l = SUBGETOPT_ZERO ;
+ for (;;)
+ {
+ int opt = subgetopt_r(argc, argv, "i:o:m:", &l) ;
+ if (opt == -1) break ;
+ switch (opt)
+ {
+ case 'i' : ifile = l.arg ; break ;
+ case 'o' : ofile = l.arg ; break ;
+ case 'm' : if (!uint0_oscan(l.arg, &omode)) dieusage() ; break ;
+ default : strerr_dieusage(100, USAGE) ;
+ }
+ }
+ argc -= l.ind ; argv += l.ind ;
+ }
+
+ {
+ int fdr ;
+ buffer b ;
+ char buf[4096] ;
+ sig_block(SIGCHLD) ;
+ if (!sig_catch(SIGCHLD, &sigchld_handler))
+ strerr_diefu1sys(111, "install SIGCHLD handler") ;
+ {
+ char const *ppargv[3] = { TIPIDEE_LIBEXECPREFIX "tipidee-config-preprocess", ifile, 0 } ;
+ pid = child_spawn1_pipe(ppargv[0], ppargv, envp, &fdr, 1) ;
+ if (!pid) strerr_diefu2sys(errno == ENOENT ? 127 : 126, "spawn ", ppargv[0]) ;
+ }
+ sig_unblock(SIGCHLD) ;
+ buffer_init(&b, &buffer_read, fdr, buf, 4096) ;
+ conf_lexparse(&b, ifile) ;
+ }
+ conf_defaults() ;
+ conf_output(ofile, omode) ;
+ return 0 ;
+}
diff --git a/src/include/tipidee/body.h b/src/include/tipidee/body.h
new file mode 100644
index 0000000..fc8bd9a
--- /dev/null
+++ b/src/include/tipidee/body.h
@@ -0,0 +1,25 @@
+/* ISC license. */
+
+#ifndef TIPIDEE_BODY_H
+#define TIPIDEE_BODY_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <skalibs/buffer.h>
+#include <skalibs/tai.h>
+#include <skalibs/stralloc.h>
+
+typedef enum tipidee_transfercoding_e tipidee_transfercoding, *tipidee_transfercoding_ref ;
+enum tipidee_transfercoding_e
+{
+ TIPIDEE_TRANSFERCODING_NONE = 0,
+ TIPIDEE_TRANSFERCODING_FIXED,
+ TIPIDEE_TRANSFERCODING_CHUNKED,
+ TIPIDEE_TRANSFERCODING_UNKNOWN
+} ;
+
+extern int tipidee_chunked_read (buffer *, stralloc *, size_t, tain const *, tain *) ;
+#define tipidee_chunked_read_g(b, sa, max, deadline) tipidee_chunked_read(b, sa, max, (deadline), &STAMP)
+
+#endif
diff --git a/src/include/tipidee/conf.h b/src/include/tipidee/conf.h
new file mode 100644
index 0000000..bc66d76
--- /dev/null
+++ b/src/include/tipidee/conf.h
@@ -0,0 +1,44 @@
+/* ISC license. */
+
+#ifndef TIPIDEE_CONF_H
+#define TIPIDEE_CONF_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <skalibs/uint16.h>
+#include <skalibs/cdb.h>
+
+#include <tipidee/uri.h>
+
+#define TIPIDEE_CONF_KEY_MAXLEN 0x1000U
+
+typedef struct tipidee_conf_s tipidee_conf, *tipidee_conf_ref ;
+struct tipidee_conf_s
+{
+ cdb c ;
+} ;
+#define TIPIDEE_CONF_ZERO { .c = CDB_ZERO }
+
+typedef struct tipidee_redirection_s tipidee_redirection, *tipidee_redirection_ref ;
+struct tipidee_redirection_s
+{
+ char const *location ;
+ char const *sub ;
+ uint32_t type : 2 ;
+} ;
+#define TIPIDEE_REDIRECTION_ZERO { .location = 0, .sub = 0, .type = 0 }
+
+extern void tipidee_conf_free (tipidee_conf *) ;
+extern int tipidee_conf_init (tipidee_conf *, char const *) ;
+
+extern int tipidee_conf_get (tipidee_conf const *, char const *, cdb_data *) ;
+extern char const *tipidee_conf_get_string (tipidee_conf const *, char const *) ;
+extern int tipidee_conf_get_uint32 (tipidee_conf const *, char const *, uint32_t *) ;
+extern unsigned int tipidee_conf_get_argv (tipidee_conf const *, char const *, char const **, unsigned int, size_t *) ;
+
+extern char const *tipidee_conf_get_docroot (tipidee_conf const *, tipidee_uri const *, uint16_t) ;
+extern int tipidee_conf_get_redirection (tipidee_conf const *, char const *, size_t, tipidee_redirection *) ;
+extern char const *tipidee_conf_get_content_type (tipidee_conf const *, char const *) ;
+
+#endif
diff --git a/src/include/tipidee/headers.h b/src/include/tipidee/headers.h
new file mode 100644
index 0000000..420b4c9
--- /dev/null
+++ b/src/include/tipidee/headers.h
@@ -0,0 +1,38 @@
+/* ISC license. */
+
+#ifndef TIPIDEE_HEADERS_H
+#define TIPIDEE_HEADERS_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <skalibs/disize.h>
+#include <skalibs/buffer.h>
+#include <skalibs/tai.h>
+#include <skalibs/avltreen.h>
+
+#define TIPIDEE_HEADERS_MAX 32
+
+typedef struct tipidee_headers_s tipidee_headers, *tipidee_headers_ref ;
+struct tipidee_headers_s
+{
+ char *buf ;
+ size_t max ;
+ size_t len ;
+ disize list[TIPIDEE_HEADERS_MAX] ;
+ avltreen map ;
+ uint32_t map_freelist[TIPIDEE_HEADERS_MAX] ;
+ avlnode map_storage[TIPIDEE_HEADERS_MAX] ;
+ uint32_t n ;
+} ;
+
+extern void tipidee_headers_init (tipidee_headers *, char *, size_t) ;
+
+extern int tipidee_headers_parse_nb (buffer *, tipidee_headers *, disize *, uint32_t *) ;
+extern int tipidee_headers_timed_parse (buffer *, tipidee_headers *, tain const *, tain *) ;
+#define tipidee_headers_timed_parse_g(b, hdr, deadline) tipidee_headers_timed_parse(b, hdr, (deadline), &STAMP)
+
+extern char const *tipidee_headers_search (tipidee_headers const *, char const *) ;
+extern ssize_t tipidee_headers_get_content_length (tipidee_headers const *) ;
+
+#endif
diff --git a/src/include/tipidee/method.h b/src/include/tipidee/method.h
new file mode 100644
index 0000000..05123e8
--- /dev/null
+++ b/src/include/tipidee/method.h
@@ -0,0 +1,32 @@
+/* ISC license. */
+
+#ifndef TIPIDEE_METHOD_H
+#define TIPIDEE_METHOD_H
+
+typedef enum tipidee_method_e tipidee_method, *tipidee_method_ref ;
+enum tipidee_method_e
+{
+ TIPIDEE_METHOD_GET = 0,
+ TIPIDEE_METHOD_HEAD,
+ TIPIDEE_METHOD_OPTIONS,
+ TIPIDEE_METHOD_POST,
+ TIPIDEE_METHOD_PUT,
+ TIPIDEE_METHOD_DELETE,
+ TIPIDEE_METHOD_TRACE,
+ TIPIDEE_METHOD_CONNECT,
+ TIPIDEE_METHOD_PRI,
+ TIPIDEE_METHOD_UNKNOWN
+} ;
+
+typedef struct tipidee_method_conv_s tipidee_method_conv, *tipidee_method_conv_ref ;
+struct tipidee_method_conv_s
+{
+ tipidee_method num ;
+ char const *str ;
+} ;
+
+extern tipidee_method_conv const *tipidee_method_conv_table ;
+extern char const *tipidee_method_tostr (tipidee_method) ;
+extern tipidee_method tipidee_method_tonum (char const *) ;
+
+#endif
diff --git a/src/include/tipidee/response.h b/src/include/tipidee/response.h
new file mode 100644
index 0000000..e0a6177
--- /dev/null
+++ b/src/include/tipidee/response.h
@@ -0,0 +1,36 @@
+/* ISC license. */
+
+#ifndef TIPIDEE_RESPONSE_H
+#define TIPIDEE_RESPONSE_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <skalibs/gccattributes.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr.h>
+#include <skalibs/tai.h>
+
+#include <tipidee/rql.h>
+
+typedef struct tipidee_response_header_builtin_s tipidee_response_header_builtin, *tipidee_response_header_builtin_ref ;
+struct tipidee_response_header_builtin_s
+{
+ char const *key ;
+ char const *value ;
+} ;
+
+extern size_t tipidee_response_status (buffer *, tipidee_rql const *, unsigned int, char const *) ;
+#define tipidee_response_status_line(b, rql, line) tipidee_response_status(b, rql, 0, (line))
+
+extern size_t tipidee_response_header_date_fmt (char *, size_t, tain const *) ;
+#define tipidee_response_header_date_fmt_g(buf, max) tipidee_response_header_date_fmt(buf, (max), &STAMP)
+extern size_t tipidee_response_header_common_put (buffer *, uint32_t, tain const *) ;
+#define tipidee_response_header_common_put_g(b, options) tipidee_response_header_common_put(b, (options), &STAMP)
+
+extern size_t tipidee_response_error (buffer *, tipidee_rql const *, char const *, char const *, uint32_t) ;
+
+extern tipidee_response_header_builtin const *tipidee_response_header_builtin_table ;
+extern char const *tipidee_response_header_builtin_search (char const *) ;
+
+#endif
diff --git a/src/include/tipidee/rql.h b/src/include/tipidee/rql.h
new file mode 100644
index 0000000..471c4ad
--- /dev/null
+++ b/src/include/tipidee/rql.h
@@ -0,0 +1,31 @@
+/* ISC license. */
+
+#ifndef TIPIDEE_RQL_H
+#define TIPIDEE_RQL_H
+
+#include <skalibs/buffer.h>
+#include <skalibs/tai.h>
+
+#include <tipidee/method.h>
+#include <tipidee/uri.h>
+
+typedef struct tipidee_rql_s tipidee_rql, *tipidee_rql_ref ;
+struct tipidee_rql_s
+{
+ tipidee_method m ;
+ unsigned int http_major ;
+ unsigned int http_minor ;
+ tipidee_uri uri ;
+} ;
+#define TIPIDEE_RQL_ZERO \
+{ \
+ .m = TIPIDEE_METHOD_UNKNOWN, \
+ .http_major = 0, \
+ .http_minor = 0, \
+ .uri = TIPIDEE_URI_ZERO \
+}
+
+extern int tipidee_rql_read (buffer *, char *, size_t, size_t *, tipidee_rql *, tain const *, tain *) ;
+#define tipidee_rql_read_g(b, buf, max, w, rql, deadline) tipidee_rql_read(b, buf, max, w, rql, (deadline), &STAMP)
+
+#endif
diff --git a/src/include/tipidee/tipidee.h b/src/include/tipidee/tipidee.h
new file mode 100644
index 0000000..ddd1348
--- /dev/null
+++ b/src/include/tipidee/tipidee.h
@@ -0,0 +1,15 @@
+/* ISC license. */
+
+#ifndef TIPIDEE_H
+#define TIPIDEE_H
+
+#include <tipidee/body.h>
+#include <tipidee/config.h>
+#include <tipidee/conf.h>
+#include <tipidee/headers.h>
+#include <tipidee/method.h>
+#include <tipidee/response.h>
+#include <tipidee/rql.h>
+#include <tipidee/uri.h>
+
+#endif
diff --git a/src/include/tipidee/uri.h b/src/include/tipidee/uri.h
new file mode 100644
index 0000000..6e5148b
--- /dev/null
+++ b/src/include/tipidee/uri.h
@@ -0,0 +1,31 @@
+/* ISC license. */
+
+#ifndef TIPIDEE_URI_H
+#define TIPIDEE_URI_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+typedef struct tipidee_uri_s tipidee_uri, *tipidee_uri_ref ;
+struct tipidee_uri_s
+{
+ char const *host ;
+ char const *path ;
+ char const *query ;
+ size_t lastslash ;
+ uint16_t port ;
+ uint8_t https : 1 ;
+} ;
+#define TIPIDEE_URI_ZERO \
+{ \
+ .host = 0, \
+ .path = 0, \
+ .query = 0, \
+ .lastslash = 0, \
+ .port = 0, \
+ .https = 0 \
+}
+
+extern size_t tipidee_uri_parse (char *, size_t, char const *, tipidee_uri *) ;
+
+#endif
diff --git a/src/libtipidee/deps-lib/tipidee b/src/libtipidee/deps-lib/tipidee
new file mode 100644
index 0000000..d218af1
--- /dev/null
+++ b/src/libtipidee/deps-lib/tipidee
@@ -0,0 +1,24 @@
+tipidee_chunked_read.o
+tipidee_conf_free.o
+tipidee_conf_get.o
+tipidee_conf_get_argv.o
+tipidee_conf_get_content_type.o
+tipidee_conf_get_redirection.o
+tipidee_conf_get_string.o
+tipidee_conf_get_uint32.o
+tipidee_conf_init.o
+tipidee_headers_get_content_length.o
+tipidee_headers_init.o
+tipidee_headers_parse.o
+tipidee_headers_search.o
+tipidee_method_conv_table.o
+tipidee_method_tonum.o
+tipidee_method_tostr.o
+tipidee_response_error.o
+tipidee_response_header_builtin.o
+tipidee_response_header_common_put.o
+tipidee_response_header_date_fmt.o
+tipidee_response_status.o
+tipidee_rql_read.o
+tipidee_uri_parse.o
+-lskarnet
diff --git a/src/libtipidee/tipidee_chunked_read.c b/src/libtipidee/tipidee_chunked_read.c
new file mode 100644
index 0000000..66d5d80
--- /dev/null
+++ b/src/libtipidee/tipidee_chunked_read.c
@@ -0,0 +1,41 @@
+/* ISC license. */
+
+#include <string.h>
+#include <errno.h>
+
+#include <skalibs/types.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/unix-timed.h>
+
+#include <tipidee/body.h>
+
+#include <skalibs/posixishard.h>
+
+int tipidee_chunked_read (buffer *b, stralloc *sa, size_t maxlen, tain const *deadline, tain *stamp)
+{
+ char line[512] ;
+ for (;;)
+ {
+ size_t chunklen, pos, w = 0 ;
+ ssize_t r = timed_getlnmax(b, line, 512, &w, '\n', deadline, stamp) ;
+ if (r < 0) return 0 ;
+ if (!r) return (errno = EPIPE, 0) ;
+ pos = size_scan(line, &chunklen) ;
+ if (!pos) return (errno = EPROTO, 0) ;
+ if (!memchr("\r\n; \t", line[pos], 5)) return (errno = EPROTO, 0) ;
+ if (!chunklen) break ;
+ if (sa->len + chunklen > maxlen) return (errno = EMSGSIZE, 0) ;
+ if (!stralloc_readyplus(sa, chunklen)) return 0 ;
+ if (buffer_timed_get(b, sa->s + sa->len, chunklen, deadline, stamp) < chunklen) return 0 ;
+ sa->len += chunklen ;
+ }
+ for (;;)
+ {
+ size_t w = 0 ;
+ ssize_t r = timed_getlnmax(b, line, 512, &w, '\n', deadline, stamp) ;
+ if (r < 0) return 0 ;
+ if (!r) return (errno = EPIPE, 0) ;
+ if (w == 1 || (w == 2 && line[0] == '\r')) break ;
+ }
+ return 1 ;
+}
diff --git a/src/libtipidee/tipidee_conf_free.c b/src/libtipidee/tipidee_conf_free.c
new file mode 100644
index 0000000..e708d89
--- /dev/null
+++ b/src/libtipidee/tipidee_conf_free.c
@@ -0,0 +1,10 @@
+/* ISC license. */
+
+#include <skalibs/cdb.h>
+
+#include <tipidee/conf.h>
+
+void tipidee_conf_free (tipidee_conf *conf)
+{
+ cdb_free(&conf->c) ;
+}
diff --git a/src/libtipidee/tipidee_conf_get.c b/src/libtipidee/tipidee_conf_get.c
new file mode 100644
index 0000000..866ec99
--- /dev/null
+++ b/src/libtipidee/tipidee_conf_get.c
@@ -0,0 +1,22 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <string.h>
+
+#include <skalibs/cdb.h>
+#include <skalibs/lolstdio.h>
+
+#include <tipidee/conf.h>
+
+int tipidee_conf_get (tipidee_conf const *conf, char const *key, cdb_data *data)
+{
+ size_t keylen = strlen(key) ;
+ if (keylen > TIPIDEE_CONF_KEY_MAXLEN) return (errno = EINVAL, 0) ;
+ LOLDEBUG("tipidee_conf_get: looking up %s", key) ;
+ switch (cdb_find(&conf->c, data, key, keylen))
+ {
+ case -1 : return (errno = EILSEQ, 0) ;
+ case 0 : return (errno = ENOENT, 0) ;
+ default : return 1 ;
+ }
+}
diff --git a/src/libtipidee/tipidee_conf_get_argv.c b/src/libtipidee/tipidee_conf_get_argv.c
new file mode 100644
index 0000000..0dd016e
--- /dev/null
+++ b/src/libtipidee/tipidee_conf_get_argv.c
@@ -0,0 +1,32 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <string.h>
+
+#include <skalibs/cdb.h>
+
+#include <tipidee/conf.h>
+
+#include <skalibs/posixishard.h>
+
+unsigned int tipidee_conf_get_argv (tipidee_conf const *conf, char const *key, char const **argv, unsigned int max, size_t *maxlen)
+{
+ cdb_data data ;
+ size_t curlen = 0 ;
+ unsigned int n = 0, pos = 0 ;
+ if (!tipidee_conf_get(conf, key, &data)) return 0 ;
+ if (data.s[data.len-1]) return (errno = EPROTO, 0) ;
+ while (pos < data.len)
+ {
+ size_t len ;
+ if (n >= max) return (errno = E2BIG, 0) ;
+ argv[n++] = data.s + pos ;
+ len = strlen(data.s + pos) ;
+ if (len > curlen) curlen = len ;
+ pos += len + 1 ;
+ }
+ if (n >= max) return (errno = E2BIG, 0) ;
+ argv[n++] = 0 ;
+ if (maxlen) *maxlen = curlen ;
+ return n ;
+}
diff --git a/src/libtipidee/tipidee_conf_get_content_type.c b/src/libtipidee/tipidee_conf_get_content_type.c
new file mode 100644
index 0000000..7ee8866
--- /dev/null
+++ b/src/libtipidee/tipidee_conf_get_content_type.c
@@ -0,0 +1,22 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <string.h>
+
+#include <tipidee/conf.h>
+
+char const *tipidee_conf_get_content_type (tipidee_conf const *conf, char const *res)
+{
+ char const *ext = strrchr(res, '.') ;
+ if (ext && !strchr(ext, '/'))
+ {
+ char const *value = 0 ;
+ size_t extlen = strlen(ext+1) ;
+ char key[3 + extlen] ;
+ key[0] = 'T' ; key[1] = ':' ;
+ memcpy(key + 2, ext + 1, extlen + 1) ;
+ value = tipidee_conf_get_string(conf, key) ;
+ if (value || errno != ENOENT) return value ;
+ }
+ return "application/octet-stream" ;
+}
diff --git a/src/libtipidee/tipidee_conf_get_redirection.c b/src/libtipidee/tipidee_conf_get_redirection.c
new file mode 100644
index 0000000..62ada34
--- /dev/null
+++ b/src/libtipidee/tipidee_conf_get_redirection.c
@@ -0,0 +1,35 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <string.h>
+
+#include <tipidee/conf.h>
+
+#include <skalibs/posixishard.h>
+
+int tipidee_conf_get_redirection (tipidee_conf const *conf, char const *res, size_t docrootlen, tipidee_redirection *r)
+{
+ size_t reslen = strlen(res) ;
+ size_t l = 2 + reslen ;
+ char const *v = 0 ;
+ char key[3 + reslen] ;
+ key[0] = 'R' ; key[1] = ':' ;
+ memcpy(key + 2, res, reslen) ;
+ key[2 + reslen] = '/' ;
+ errno = ENOENT ;
+ while (!v)
+ {
+ if (errno != ENOENT) return -1 ;
+ while (l > 2 + docrootlen && key[l] != '/') l-- ;
+ if (l <= 2 + docrootlen) break ;
+ key[l--] = 0 ;
+ key[0] = 'r' ;
+ v = tipidee_conf_get_string(conf, key) ;
+ }
+ if (!v) return 0 ;
+ if (v[0] < '@' || v[0] > 'C') return (errno = EPROTO, -1) ;
+ r->type = v[0] & ~'@' ;
+ r->location = v+1 ;
+ r->sub = res + l - 2 ;
+ return 1 ;
+}
diff --git a/src/libtipidee/tipidee_conf_get_string.c b/src/libtipidee/tipidee_conf_get_string.c
new file mode 100644
index 0000000..0ea93cd
--- /dev/null
+++ b/src/libtipidee/tipidee_conf_get_string.c
@@ -0,0 +1,17 @@
+/* ISC license. */
+
+#include <errno.h>
+
+#include <skalibs/cdb.h>
+
+#include <tipidee/conf.h>
+
+#include <skalibs/posixishard.h>
+
+char const *tipidee_conf_get_string (tipidee_conf const *conf, char const *key)
+{
+ cdb_data data ;
+ if (!tipidee_conf_get(conf, key, &data)) return 0 ;
+ if (data.s[data.len-1]) { errno = EPROTO ; return 0 ; }
+ return data.s ;
+}
diff --git a/src/libtipidee/tipidee_conf_get_uint32.c b/src/libtipidee/tipidee_conf_get_uint32.c
new file mode 100644
index 0000000..ad8b21b
--- /dev/null
+++ b/src/libtipidee/tipidee_conf_get_uint32.c
@@ -0,0 +1,19 @@
+/* ISC license. */
+
+#include <errno.h>
+
+#include <skalibs/uint32.h>
+#include <skalibs/cdb.h>
+
+#include <tipidee/conf.h>
+
+#include <skalibs/posixishard.h>
+
+int tipidee_conf_get_uint32 (tipidee_conf const *conf, char const *key, uint32_t *value)
+{
+ cdb_data data ;
+ if (!tipidee_conf_get(conf, key, &data)) return 0 ;
+ if (data.len != 4) return (errno = EPROTO, 0) ;
+ uint32_unpack_big(data.s, value) ;
+ return 1 ;
+}
diff --git a/src/libtipidee/tipidee_conf_init.c b/src/libtipidee/tipidee_conf_init.c
new file mode 100644
index 0000000..3d2e119
--- /dev/null
+++ b/src/libtipidee/tipidee_conf_init.c
@@ -0,0 +1,10 @@
+/* ISC license. */
+
+#include <skalibs/cdb.h>
+
+#include <tipidee/conf.h>
+
+int tipidee_conf_init (tipidee_conf *conf, char const *file)
+{
+ return cdb_init(&conf->c, file) ;
+}
diff --git a/src/libtipidee/tipidee_headers_get_content_length.c b/src/libtipidee/tipidee_headers_get_content_length.c
new file mode 100644
index 0000000..0b29ece
--- /dev/null
+++ b/src/libtipidee/tipidee_headers_get_content_length.c
@@ -0,0 +1,17 @@
+/* ISC license. */
+
+#include <stddef.h>
+#include <limits.h>
+
+#include <skalibs/types.h>
+
+#include <tipidee/headers.h>
+
+ssize_t tipidee_headers_get_content_length (tipidee_headers const *hdr)
+{
+ size_t n ;
+ char const *x = tipidee_headers_search(hdr, "Content-Length") ;
+ if (!x) return 0 ;
+ if (!size0_scan(x, &n) || n > SSIZE_MAX) return -1 ;
+ return n ;
+}
diff --git a/src/libtipidee/tipidee_headers_init.c b/src/libtipidee/tipidee_headers_init.c
new file mode 100644
index 0000000..6c2d336
--- /dev/null
+++ b/src/libtipidee/tipidee_headers_init.c
@@ -0,0 +1,29 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <strings.h>
+
+#include <skalibs/avltreen.h>
+
+#include <tipidee/headers.h>
+
+static void *tipidee_headers_dtok (uint32_t d, void *data)
+{
+ tipidee_headers *hdr = data ;
+ return hdr->buf + hdr->list[d].left ;
+}
+
+static int tipidee_headers_cmp (void const *a, void const *b, void *data)
+{
+ (void)data ;
+ return strcasecmp(a, b) ;
+}
+
+void tipidee_headers_init (tipidee_headers *hdr, char *buf, size_t max)
+{
+ hdr->buf = buf ;
+ hdr->max = max ;
+ hdr->len = 0 ;
+ hdr->n = 0 ;
+ avltreen_init(&hdr->map, hdr->map_storage, hdr->map_freelist, TIPIDEE_HEADERS_MAX, &tipidee_headers_dtok, &tipidee_headers_cmp, hdr) ;
+}
diff --git a/src/libtipidee/tipidee_headers_parse.c b/src/libtipidee/tipidee_headers_parse.c
new file mode 100644
index 0000000..57108bb
--- /dev/null
+++ b/src/libtipidee/tipidee_headers_parse.c
@@ -0,0 +1,210 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+#include <strings.h>
+#include <errno.h>
+
+#include <skalibs/bytestr.h>
+#include <skalibs/buffer.h>
+#include <skalibs/error.h>
+#include <skalibs/avltreen.h>
+#include <skalibs/unix-timed.h>
+// #include <skalibs/lolstdio.h>
+
+#include <tipidee/headers.h>
+
+/*
+
+Reads header lines, separates into \0-terminated keys+values.
+key is at hdr->buf + hdr->list[i].left
+value is at hdr->buf + hdr->list[i].right
+Compresses linear whitespace.
+Does not unquote strings/comments in values.
+
+
+st\ev 0 1 2 3 4 5 6 7
+ CTL CR LF LWS : special normal 8bit
+
+00 kp
+START X END? END X X X K X
+
+01
+END? X X END X X X X X
+
+02 zv p
+K X X X X V1 X K X
+
+03 p p p p
+V1 X V1?? V1? V1 V V V V
+
+04
+V1?? X X V1? X X X X X
+
+05 zn zn znkp
+V1? X END? END V1 X X K X
+
+06 s s s p p p p
+V X V2?? V2? V2 V V V V
+
+07 p p p p
+V2 X V2?? V2? V2 V V V V
+
+08
+V2?? X X V2? X X X X X
+
+09 mzn mzn mznkp
+V2? X END? END V2 X X K X
+
+END = 0a, X = 0b
+
+0x4000 s: write space
+0x2000 m: go back one char
+0x1000 z: write \0
+0x0800 n: cut key/value pair, prepare next
+0x0400 k: start of key
+0x0200 v: start of value
+0x0100 p: write current char
+
+states: 4 bits, actions: 7 bits
+
+*/
+
+
+struct tainp_s
+{
+ tain const *deadline ;
+ tain *stamp ;
+} ;
+
+typedef int get1_func (buffer *, char *, struct tainp_s *) ;
+typedef get1_func *get1_func_ref ;
+
+static int get1_timed (buffer *b, char *c, struct tainp_s *d)
+{
+ return buffer_timed_get(b, c, 1, d->deadline, d->stamp) ;
+}
+
+static int get1_notimed (buffer *b, char *c, struct tainp_s *data)
+{
+ (void)data ;
+ return buffer_get(b, c, 1) == 1 ;
+}
+
+static uint8_t cclass (char c)
+{
+ static uint8_t const ctable[128] = "00000000032001000000000000000000365666665566566566666666664565655666666666666666666666666665556666666666666666666666666666656560" ;
+ return c & 0x80 ? 7 : ctable[(uint8_t)c] - '0' ;
+}
+
+static int needs_processing (char const *s)
+{
+ if (!strcasecmp(s, "Set-Cookie")) return 0 ;
+ if (str_start(s, "X-")) return 0 ;
+ return 1 ;
+}
+
+static int tipidee_headers_parse_with (buffer *b, tipidee_headers *hdr, get1_func_ref next, struct tainp_s *data, disize *header, uint32_t *state)
+{
+ static uint16_t const table[10][8] =
+ {
+ { 0x000b, 0x0001, 0x000a, 0x000b, 0x000b, 0x000b, 0x0502, 0x000b },
+ { 0x000b, 0x000b, 0x000a, 0x000b, 0x000b, 0x000b, 0x000b, 0x000b },
+ { 0x000b, 0x000b, 0x000b, 0x000b, 0x1203, 0x000b, 0x0102, 0x000b },
+ { 0x000b, 0x0004, 0x0005, 0x0003, 0x0106, 0x0106, 0x0106, 0x0106 },
+ { 0x000b, 0x000b, 0x0005, 0x000b, 0x000b, 0x000b, 0x000b, 0x000b },
+ { 0x000b, 0x1801, 0x180a, 0x0003, 0x000b, 0x000b, 0x1d02, 0x000b },
+ { 0x000b, 0x4008, 0x4009, 0x4007, 0x0106, 0x0106, 0x0106, 0x0106 },
+ { 0x000b, 0x0008, 0x0009, 0x0007, 0x0106, 0x0106, 0x0106, 0x0106 },
+ { 0x000b, 0x000b, 0x0009, 0x000b, 0x000b, 0x000b, 0x000b, 0x000b },
+ { 0x000b, 0x3801, 0x380a, 0x0007, 0x000b, 0x000b, 0x3d02, 0x000b },
+ } ;
+ while (*state < 0x0a)
+ {
+ uint16_t c ;
+ char cur ;
+ if (!(*next)(b, &cur, data))
+ return errno == ETIMEDOUT ? 408 : error_isagain(errno) ? -2 : -1 ;
+ c = table[*state][cclass(cur)] ;
+/*
+ {
+
+ char s[2] = { cur, 0 } ;
+ LOLDEBUG("tipidee_headers_parse_with: state %hhu, event %s, newstate %hhu, actions %s%s%s%s%s%s%s",
+ *state,
+ cur == '\r' ? "\\r" : cur == '\n' ? "\\n" : s,
+ c & 0x0f,
+ c & 0x4000 ? "s" : "",
+ c & 0x2000 ? "m" : "",
+ c & 0x1000 ? "z" : "",
+ c & 0x0800 ? "n" : "",
+ c & 0x0400 ? "k" : "",
+ c & 0x0200 ? "v" : "",
+ c & 0x0100 ? "p" : ""
+ ) ;
+ }
+*/
+ *state = c & 0x0f ;
+ if (c & 0x4000) { if (hdr->len >= hdr->max) return 413 ; hdr->buf[hdr->len++] = ' ' ; }
+ if (c & 0x2000) hdr->len-- ;
+ if (c & 0x1000) { if (hdr->len >= hdr->max) return 413 ; hdr->buf[hdr->len++] = 0 ; }
+ if (c & 0x0800)
+ {
+ uint32_t prev ;
+ if (hdr->n >= TIPIDEE_HEADERS_MAX) return 413 ;
+ hdr->list[hdr->n] = *header ;
+ if (needs_processing(hdr->buf + header->left))
+ {
+// LOLDEBUG("tipidee_headers_parse_with: n: adding header %u - key %zu (%s), value %zu (%s)", hdr->n, header->left, hdr->buf + header->left, header->right, hdr->buf + header->right) ;
+ if (avltreen_search(&hdr->map, hdr->buf + header->left, &prev))
+ {
+ size_t start = hdr->list[prev+1].left ;
+ if (prev+1 == hdr->n)
+ {
+ hdr->buf[start - 1] = ',' ;
+ hdr->buf[start] = ' ' ;
+ memcpy(hdr->buf + start + 1, hdr->buf + header->right, hdr->len - header->right) ;
+ }
+ else
+ {
+ size_t len = header->left - start ;
+ size_t offset = hdr->len - header->right + 1 ;
+ char tmp[len] ;
+ memcpy(tmp, hdr->buf + start, len) ;
+ hdr->buf[start - 1] = ',' ;
+ hdr->buf[start] = ' ' ;
+ memcpy(hdr->buf + start + 1, hdr->buf + header->right, hdr->len - header->right) ;
+ memcpy(hdr->buf + start + offset, tmp, len) ;
+ for (uint32_t i = prev + 1 ; i < hdr->n ; i++)
+ {
+ hdr->list[i].left += offset ;
+ hdr->list[i].right += offset ;
+ }
+ }
+ hdr->len -= header->right - header->left - 1 ;
+ hdr->n-- ;
+ }
+ else if (!avltreen_insert(&hdr->map, hdr->n)) return 500 ;
+ }
+ hdr->n++ ;
+ }
+ if (c & 0x0400) header->left = hdr->len ;
+ if (c & 0x0200) header->right = hdr->len ;
+ if (c & 0x0100) { if (hdr->len >= hdr->max) return 413 ; hdr->buf[hdr->len++] = cur ; }
+ }
+ if (*state > 0x0a) return 400 ;
+ return 0 ;
+}
+
+int tipidee_headers_timed_parse (buffer *b, tipidee_headers *hdr, tain const *deadline, tain *stamp)
+{
+ struct tainp_s d = { .deadline = deadline, .stamp = stamp } ;
+ disize header = DISIZE_ZERO ;
+ uint32_t state = 0 ;
+ return tipidee_headers_parse_with(b, hdr, &get1_timed, &d, &header, &state) ;
+}
+
+int tipidee_headers_parse_nb (buffer *b, tipidee_headers *hdr, disize *header, uint32_t *state)
+{
+ return tipidee_headers_parse_with(b, hdr, &get1_notimed, 0, header, state) ;
+}
diff --git a/src/libtipidee/tipidee_headers_search.c b/src/libtipidee/tipidee_headers_search.c
new file mode 100644
index 0000000..553ef65
--- /dev/null
+++ b/src/libtipidee/tipidee_headers_search.c
@@ -0,0 +1,13 @@
+/* ISC license. */
+
+#include <stdint.h>
+
+#include <skalibs/avltreen.h>
+
+#include <tipidee/headers.h>
+
+char const *tipidee_headers_search (tipidee_headers const *hdr, char const *key)
+{
+ uint32_t i ;
+ return avltreen_search(&hdr->map, key, &i) ? hdr->buf + hdr->list[i].right : 0 ;
+}
diff --git a/src/libtipidee/tipidee_method_conv_table.c b/src/libtipidee/tipidee_method_conv_table.c
new file mode 100644
index 0000000..892e5b5
--- /dev/null
+++ b/src/libtipidee/tipidee_method_conv_table.c
@@ -0,0 +1,19 @@
+/* ISC license. */
+
+#include <tipidee/method.h>
+
+static tipidee_method_conv const table[] =
+{
+ { .num = TIPIDEE_METHOD_GET, .str = "GET" },
+ { .num = TIPIDEE_METHOD_HEAD, .str = "HEAD" },
+ { .num = TIPIDEE_METHOD_OPTIONS, .str = "OPTIONS" },
+ { .num = TIPIDEE_METHOD_POST, .str = "POST" },
+ { .num = TIPIDEE_METHOD_PUT, .str = "PUT" },
+ { .num = TIPIDEE_METHOD_DELETE, .str = "DELETE" },
+ { .num = TIPIDEE_METHOD_TRACE, .str = "TRACE" },
+ { .num = TIPIDEE_METHOD_CONNECT, .str = "CONNECT" },
+ { .num = TIPIDEE_METHOD_PRI, .str = "PRI" },
+ { .num = TIPIDEE_METHOD_UNKNOWN, .str = 0 }
+} ;
+
+tipidee_method_conv const *tipidee_method_conv_table = table ;
diff --git a/src/libtipidee/tipidee_method_tonum.c b/src/libtipidee/tipidee_method_tonum.c
new file mode 100644
index 0000000..7c88e45
--- /dev/null
+++ b/src/libtipidee/tipidee_method_tonum.c
@@ -0,0 +1,12 @@
+/* ISC license. */
+
+#include <string.h>
+
+#include <tipidee/method.h>
+
+tipidee_method tipidee_method_tonum (char const *s)
+{
+ tipidee_method_conv const *p = tipidee_method_conv_table ;
+ for (; p->str ; p++) if (!strcmp(s, p->str)) break ;
+ return p->num ;
+}
diff --git a/src/libtipidee/tipidee_method_tostr.c b/src/libtipidee/tipidee_method_tostr.c
new file mode 100644
index 0000000..8a3831d
--- /dev/null
+++ b/src/libtipidee/tipidee_method_tostr.c
@@ -0,0 +1,8 @@
+/* ISC license. */
+
+#include <tipidee/method.h>
+
+char const *tipidee_method_tostr (tipidee_method m)
+{
+ return m < TIPIDEE_METHOD_UNKNOWN ? tipidee_method_conv_table[m].str : 0 ;
+}
diff --git a/src/libtipidee/tipidee_response_error.c b/src/libtipidee/tipidee_response_error.c
new file mode 100644
index 0000000..e2687a4
--- /dev/null
+++ b/src/libtipidee/tipidee_response_error.c
@@ -0,0 +1,41 @@
+/* ISC license. */
+
+#include <stddef.h>
+
+#include <skalibs/types.h>
+#include <skalibs/buffer.h>
+
+#include <tipidee/method.h>
+#include <tipidee/rql.h>
+#include <tipidee/response.h>
+
+size_t tipidee_response_error (buffer *b, tipidee_rql const *rql, char const *rsl, char const *text, uint32_t options)
+{
+ size_t n = 0 ;
+ static char const txt1[] = "<html>\n<head><title>" ;
+ static char const txt2[] = "</title></head>\n<body>\n<h1> " ;
+ static char const txt3[] = " </h1>\n<p>\n" ;
+ static char const txt4[] = "\n</p>\n</body>\n</html>\n" ;
+ n += tipidee_response_status_line(b, rql, rsl) ;
+ n += tipidee_response_header_common_put_g(buffer_1, options) ;
+ if (!(options & 2))
+ {
+ char fmt[SIZE_FMT] ;
+ n += buffer_putsnoflush(buffer_1, "Content-Type: text/html; charset=UTF-8\r\n") ;
+ n += buffer_putsnoflush(buffer_1, "Content-Length: ") ;
+ n += buffer_putnoflush(buffer_1, fmt, size_fmt(fmt, sizeof(txt1) + sizeof(txt2) + sizeof(txt3) + sizeof(txt4) - 4 + 2 * strlen(rsl) + strlen(text))) ;
+ n += buffer_putnoflush(buffer_1, "\r\n", 2) ;
+ }
+ n += buffer_putnoflush(buffer_1, "\r\n", 2) ;
+ if (rql->m != TIPIDEE_METHOD_HEAD)
+ {
+ n += buffer_putsnoflush(buffer_1, txt1) ;
+ n += buffer_putsnoflush(buffer_1, rsl) ;
+ n += buffer_putsnoflush(buffer_1, txt2) ;
+ n += buffer_putsnoflush(buffer_1, rsl) ;
+ n += buffer_putsnoflush(buffer_1, txt3) ;
+ n += buffer_putsnoflush(buffer_1, text) ;
+ n += buffer_putsnoflush(buffer_1, txt4) ;
+ }
+ return n ;
+}
diff --git a/src/libtipidee/tipidee_response_header_builtin.c b/src/libtipidee/tipidee_response_header_builtin.c
new file mode 100644
index 0000000..0125cb8
--- /dev/null
+++ b/src/libtipidee/tipidee_response_header_builtin.c
@@ -0,0 +1,40 @@
+/* ISC license. */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <tipidee/config.h>
+#include <tipidee/response.h>
+
+static tipidee_response_header_builtin const tipidee_response_header_builtin_table_[] =
+{
+ { .key = "Accept-Ranges", .value = "none" },
+ { .key = "Cache-Control", .value = "private" },
+ { .key = "Content-Security-Policy", .value = "default-src 'self'; style-src 'self' 'unsafe-inline';" },
+ { .key = "Referrer-Policy", .value = "no-referrer-when-downgrade" },
+ { .key = "Server", .value = "tipidee/" TIPIDEE_VERSION },
+ { .key = "Vary", .value = "Accept-Encoding" },
+ { .key = "X-Content-Type-Options", .value = "nosniff" },
+ { .key = "X-Frame-Options", .value = "DENY" },
+ { .key = "X-XSS-Protection", .value = "1; mode=block" },
+ { .key = 0, .value = 0 },
+} ;
+
+tipidee_response_header_builtin const *tipidee_response_header_builtin_table = tipidee_response_header_builtin_table_ ;
+
+static int tipidee_response_header_builtin_cmp (void const *a, void const *b)
+{
+ return strcmp((char const *)a, ((tipidee_response_header_builtin const *)b)->key) ;
+}
+
+char const *tipidee_response_header_builtin_search (char const *key)
+{
+ tipidee_response_header_builtin const *p = bsearch(
+ key,
+ tipidee_response_header_builtin_table_,
+ sizeof(tipidee_response_header_builtin_table_) / sizeof(tipidee_response_header_builtin) - 1,
+ sizeof(tipidee_response_header_builtin),
+ &tipidee_response_header_builtin_cmp) ;
+ return p ? p->value : 0 ;
+}
+
diff --git a/src/libtipidee/tipidee_response_header_common_put.c b/src/libtipidee/tipidee_response_header_common_put.c
new file mode 100644
index 0000000..8352ba9
--- /dev/null
+++ b/src/libtipidee/tipidee_response_header_common_put.c
@@ -0,0 +1,23 @@
+/* ISC license. */
+
+#include <stdint.h>
+
+#include <skalibs/buffer.h>
+
+#include <tipidee/config.h>
+#include <tipidee/response.h>
+
+size_t tipidee_response_header_common_put (buffer *b, uint32_t options, tain const *stamp)
+{
+ char fmt[128] ;
+ size_t m = buffer_putnoflush(b, fmt, tipidee_response_header_date_fmt(fmt, 128, stamp)) ;
+ for (tipidee_response_header_builtin const *p = tipidee_response_header_builtin_table ; p->key ; p++)
+ {
+ m += buffer_putsnoflush(b, p->key) ;
+ m += buffer_putnoflush(b, ": ", 2) ;
+ m += buffer_putsnoflush(b, p->value) ;
+ m += buffer_putnoflush(b, "\r\n", 2) ;
+ }
+ if (options & 1) m += buffer_putsnoflush(b, "Connection: close\r\n") ;
+ return m ;
+}
diff --git a/src/libtipidee/tipidee_response_header_date_fmt.c b/src/libtipidee/tipidee_response_header_date_fmt.c
new file mode 100644
index 0000000..df19673
--- /dev/null
+++ b/src/libtipidee/tipidee_response_header_date_fmt.c
@@ -0,0 +1,24 @@
+/* ISC license. */
+
+#include <string.h>
+#include <time.h>
+
+#include <skalibs/tai.h>
+#include <skalibs/djbtime.h>
+
+#include <tipidee/response.h>
+
+size_t tipidee_response_header_date_fmt (char *s, size_t max, tain const *stamp)
+{
+ size_t m = 0, l ;
+ struct tm tm ;
+ if (m + 6 > max) return 0 ;
+ if (!localtm_from_tai(&tm, tain_secp(stamp), 0)) return 0 ;
+ memcpy(s, "Date: ", 6) ; m += 6 ;
+ l = strftime(s + m, max - m, "%a, %d %b %Y %T GMT", &tm) ;
+ if (!l) return 0 ;
+ m += l ;
+ if (m + 2 > max) return 0 ;
+ s[m++] = '\r' ; s[m++] = '\n' ;
+ return m ;
+}
diff --git a/src/libtipidee/tipidee_response_status.c b/src/libtipidee/tipidee_response_status.c
new file mode 100644
index 0000000..aedec39
--- /dev/null
+++ b/src/libtipidee/tipidee_response_status.c
@@ -0,0 +1,27 @@
+/* ISC license. */
+
+#include <skalibs/types.h>
+#include <skalibs/buffer.h>
+
+#include <tipidee/response.h>
+
+size_t tipidee_response_status (buffer *b, tipidee_rql const *rql, unsigned int status, char const *line)
+{
+ size_t n = 0 ;
+ char fmt[UINT_FMT] ;
+ n += buffer_putnoflush(b, "HTTP/", 5) ;
+ n += buffer_putnoflush(b, fmt, uint_fmt(fmt, rql->http_major ? rql->http_major : 1)) ;
+ n += buffer_putnoflush(b, ".", 1) ;
+ n += buffer_putnoflush(b, fmt, uint_fmt(fmt, rql->http_major ? rql->http_minor : 1)) ;
+ n += buffer_putnoflush(b, " ", 1) ;
+ if (status)
+ {
+ char fmt[UINT_FMT] ;
+ size_t m = uint_fmt(fmt, status) ;
+ n += buffer_putnoflush(b, fmt, m) ;
+ n += buffer_putnoflush(b, " ", 1) ;
+ }
+ n += buffer_putsnoflush(b, line) ;
+ n += buffer_putnoflush(b, "\r\n", 2) ;
+ return n ;
+}
diff --git a/src/libtipidee/tipidee_rql_read.c b/src/libtipidee/tipidee_rql_read.c
new file mode 100644
index 0000000..f3508cf
--- /dev/null
+++ b/src/libtipidee/tipidee_rql_read.c
@@ -0,0 +1,85 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+#include <strings.h>
+
+#include <skalibs/types.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/buffer.h>
+#include <skalibs/unix-timed.h>
+// #include <skalibs/lolstdio.h>
+
+#include <tipidee/method.h>
+#include <tipidee/uri.h>
+#include <tipidee/rql.h>
+
+static inline uint8_t tokenize_cclass (char c)
+{
+ switch (c)
+ {
+ case '\0' : return 0 ;
+ case ' ' :
+ case '\t' : return 1 ;
+ default : return 2 ;
+ }
+}
+
+static inline int rql_tokenize (char *s, size_t *tab)
+{
+ uint8_t const table[2][3] =
+ {
+ { 0x02, 0x00, 0x11 },
+ { 0x02, 0x20, 0x01 }
+ } ;
+ size_t i = 0 ;
+ unsigned int tokens = 0 ;
+ uint8_t state = 0 ;
+ for (; state < 2 ; i++)
+ {
+ uint8_t c = table[state][tokenize_cclass(s[i])] ;
+ state = c & 3 ;
+ if (c & 0x10)
+ {
+ if (tokens >= 3) goto err ;
+ tab[tokens++] = i ;
+ }
+ if (c & 0x20) s[i] = 0 ;
+ }
+ return 1 ;
+ err:
+ return 0 ;
+}
+
+static inline int get_version (char const *in, tipidee_rql *rql)
+{
+ size_t l ;
+ if (strncmp(in, "HTTP/", 5)) return 0 ;
+ in += 5 ;
+ l = uint_scan(in, &rql->http_major) ;
+ if (!l) return 0 ;
+ in += l ;
+ if (*in++ != '.') return 0 ;
+ return !!uint0_scan(in, &rql->http_minor) ;
+}
+
+int tipidee_rql_read (buffer *b, char *buf, size_t max, size_t *w, tipidee_rql *rql, tain const *deadline, tain *stamp)
+{
+ size_t pos[3] = { 0 } ;
+ if (timed_getlnmax(b, buf, max, &pos[0], '\n', deadline, stamp) <= 0) return -1 ;
+ buf[--pos[0]] = 0 ;
+ if (buf[pos[0] - 1] == '\r') buf[--pos[0]] = 0 ;
+// LOLDEBUG("tipidee_rql_read: timed_getlnmax: len is %zu, line is %s", pos[0], buf) ;
+ if (!rql_tokenize(buf, pos)) return 400 ;
+// LOLDEBUG("tipidee_rql_read: method: %s, version: %s, uri to parse: %s", buf + pos[0], buf + pos[2], buf + pos[1]) ;
+ rql->m = tipidee_method_tonum(buf + pos[0]) ;
+ if (rql->m == TIPIDEE_METHOD_UNKNOWN) return 400 ;
+ if (!get_version(buf + pos[2], rql)) return 400 ;
+ if (rql->m != TIPIDEE_METHOD_OPTIONS || strcmp(buf + pos[1], "*"))
+ {
+ size_t l = tipidee_uri_parse(buf, max, buf + pos[1], &rql->uri) ;
+ if (!l) return 400 ;
+ *w = l ;
+ }
+ return 0 ;
+}
diff --git a/src/libtipidee/tipidee_uri_parse.c b/src/libtipidee/tipidee_uri_parse.c
new file mode 100644
index 0000000..10b0f91
--- /dev/null
+++ b/src/libtipidee/tipidee_uri_parse.c
@@ -0,0 +1,184 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+
+#include <skalibs/uint16.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/fmtscan.h>
+#include <skalibs/lolstdio.h>
+
+#include <tipidee/uri.h>
+
+
+/*
+
+ Decodes an URI.
+ Accepts absolute (http and https) and local, decodes %-encoding up to ? query.
+
+st\ev 0 1 2 3 4 5 6 7 8 9 a b c d e
+ \0 invalid % ? / : @ h t p s 0-9 a-f other delim
+
+00 Pp
+START X X X X PATH? X X H X X X X X X X
+
+01
+H X X X X X X X X HT X X X X X X
+
+02
+HT X X X X X X X X HTT X X X X X X
+
+03
+HTT X X X X X X X X X HTTP X X X X X
+
+04
+HTTP X X X X X AUTH X X X X HTTPS X X X X
+
+05 s
+HTTPS X X X X X AUTH X X X X X X X X X
+
+06
+AUTH X X X X AUTH/ X X X X X X X X X X
+
+07
+AUTH/ X X X X HOST X X X X X X X X X X
+
+08 H Hp Hp Hp Hp Hp Hp Hp Hp
+HOST X X HQ H1 X X X H1 H1 H1 H1 H1 H1 H1 X
+
+09 p a a
+HQ X X H1 X X X X X X X X HQ1 HQ1 X X
+
+0a ab ab
+HQ1 X X X X X X X X X X X H1 H1 X X
+
+0b p zPp zm p p p p p p p
+H1 END X HQ H1 PATH PORT X H1 H1 H1 H1 H1 H1 H1 X
+
+0c Pp p
+PORT X X X X PATH X X X X X X PORT1 X X X
+
+0d zc zcPp p
+PORT1 END X X X PATH X X X X X X PORT1 X X X
+
+0e p zQ p p p p p p p p p p
+PATH END X Q QUERY PATH PATH PATH PATH PATH PATH PATH PATH PATH PATH X
+
+0f p a a
+Q X X PATH X X X X X X X X Q1 Q1 X X
+
+10 ab ab
+Q1 X X X X X X X X X X X PATH PATH X X
+
+11 p p p p p p p p p p p p p p
+QUERY END X QUERY QUERY QUERY QUERY QUERY QUERY QUERY QUERY QUERY QUERY QUERY QUERY QUERY
+
+12 p zQ p p p p p p p p p
+PATH? END X Q QUERY X PATH PATH PATH PATH PATH PATH PATH PATH PATH X
+
+st\ev 0 1 2 3 4 5 6 7 8 9 a b c d e
+ \0 invalid % ? / : @ h t p s 0-9 a-f other delim
+
+END = 13, X = 14
+
+0x8000 s ssl
+0x4000 H start host
+0x2000 z print \0
+0x1000 m mark
+0x0800 c scan port from mark, reset to mark
+0x0400 P start path
+0x0200 p print cur
+0x0100 Q start query
+0x0080 a push num
+0x0040 b decode num, print num, reinit
+
+*/
+
+static inline uint8_t uridecode_cclass (char c)
+{
+ static uint8_t const table[128] = "01111111111111111111111111111111161162>>>>>=>==4;;;;;;;;;;5>>=>36<<<<<<====================>1>==1<<<<<<=7=======9==:8======111=1" ;
+ return c < 0 ? 1 : table[(uint8_t)c] - '0' ;
+}
+
+size_t tipidee_uri_parse (char *out, size_t max, char const *in, tipidee_uri *uri)
+{
+ static uint16_t const table[19][15] =
+ {
+ { 0x0014, 0x0014, 0x0014, 0x0014, 0x0612, 0x0014, 0x0014, 0x0001, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014 },
+ { 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0002, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014 },
+ { 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0003, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014 },
+ { 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0004, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014 },
+ { 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0006, 0x0014, 0x0014, 0x0014, 0x0014, 0x0005, 0x0014, 0x0014, 0x0014, 0x0014 },
+ { 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x8006, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014 },
+ { 0x0014, 0x0014, 0x0014, 0x0014, 0x0007, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014 },
+ { 0x0014, 0x0014, 0x0014, 0x0014, 0x0008, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014 },
+ { 0x0014, 0x0014, 0x4009, 0x420b, 0x0014, 0x0014, 0x0014, 0x420b, 0x420b, 0x420b, 0x420b, 0x420b, 0x420b, 0x420b, 0x0014 },
+ { 0x0014, 0x0014, 0x020b, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x008a, 0x008a, 0x0014, 0x0014 },
+ { 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x00cb, 0x00cb, 0x0014, 0x0014 },
+ { 0x0213, 0x0014, 0x0009, 0x000b, 0x260e, 0x300c, 0x0014, 0x020b, 0x020b, 0x020b, 0x020b, 0x020b, 0x020b, 0x020b, 0x0014 },
+ { 0x0014, 0x0014, 0x0014, 0x0014, 0x060e, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x020d, 0x0014, 0x0014, 0x0014 },
+ { 0x2813, 0x0014, 0x0014, 0x0014, 0x2e0e, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x020d, 0x0014, 0x0014, 0x0014 },
+ { 0x0213, 0x0014, 0x000f, 0x2111, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x0014 },
+ { 0x0014, 0x0014, 0x020e, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0090, 0x0090, 0x0014, 0x0014 },
+ { 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x00ce, 0x00ce, 0x0014, 0x0014 },
+ { 0x0213, 0x0014, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211, 0x0211 },
+ { 0x0213, 0x0014, 0x000f, 0x2111, 0x0014, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x020e, 0x0014 }
+ } ;
+ size_t w = 0, lastslash = 0, mark = 0 ;
+ char const *host = 0 ;
+ char const *path = 0 ;
+ char const *query = 0 ;
+ uint16_t port = 0 ;
+ uint16_t state = 0 ;
+ unsigned char decoded = 0 ;
+ uint8_t ssl = 0 ;
+ for (; state < 0x13 ; in++)
+ {
+ uint16_t c = table[state][uridecode_cclass(*in)] ;
+/*
+ LOLDEBUG("tipidee_uri_parse: state %hu, event %c, newstate %hu, actions %s%s%s%s%s%s%s%s%s%s", state, *in, c & 0x1f,
+ c & 0x8000 ? "s" : "",
+ c & 0x4000 ? "H" : "",
+ c & 0x2000 ? "z" : "",
+ c & 0x1000 ? "m" : "",
+ c & 0x0800 ? "c" : "",
+ c & 0x0400 ? "P" : "",
+ c & 0x0200 ? "p" : "",
+ c & 0x0100 ? "Q" : "",
+ c & 0x0080 ? "a" : "",
+ c & 0x0040 ? "b" : ""
+ ) ;
+*/
+ state = c & 0x1f ;
+ if (c & 0x8000) ssl = 1 ;
+ if (c & 0x4000) host = out + w ;
+ if (c & 0x2000) { if (w >= max) return 0 ; out[w++] = 0 ; }
+ if (c & 0x1000) mark = w ;
+ if (c & 0x0800) { if (!uint160_scan(out + mark, &port)) return 0 ; w = mark ; }
+ if (c & 0x0400) path = out + w ;
+ if (c & 0x0200) { if (w >= max) return 0 ; out[w++] = *in ; }
+ if (c & 0x0100) query = out + w ;
+ if (c & 0x0080) decoded = (decoded << 4) | fmtscan_num(*in, 16) ;
+ if (c & 0x0040)
+ {
+ if (w >= max) return 0 ;
+ if (decoded == '/') lastslash = w ;
+ out[w++] = decoded ;
+ decoded = 0 ;
+ }
+ }
+ if (state > 0x13) return 0 ;
+ if (path)
+ {
+ size_t len = strlen(path) ;
+ if (len >= 3 && !memcmp(path + len - 3, "/..", 3)) return 0 ;
+ if (strstr(path, "/../")) return 0 ;
+ }
+ uri->host = host ;
+ uri->port = port ;
+ uri->path = path ? path : "/" ;
+ uri->query = query ;
+ uri->lastslash = path ? lastslash - (path - out) : 0 ;
+ uri->https = ssl ;
+ return w ;
+}
diff --git a/src/tipideed/cgi.c b/src/tipideed/cgi.c
new file mode 100644
index 0000000..7a3ca03
--- /dev/null
+++ b/src/tipideed/cgi.c
@@ -0,0 +1,381 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <string.h>
+#include <strings.h>
+#include <errno.h>
+#include <signal.h>
+
+#include <skalibs/gccattributes.h>
+#include <skalibs/posixplz.h>
+#include <skalibs/types.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/buffer.h>
+#include <skalibs/error.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/strerr.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/iopause.h>
+#include <skalibs/env.h>
+#include <skalibs/exec.h>
+#include <skalibs/unix-timed.h>
+#include <skalibs/lolstdio.h>
+
+#include <tipidee/method.h>
+#include <tipidee/headers.h>
+#include <tipidee/response.h>
+#include <tipidee/uri.h>
+#include "tipideed-internal.h"
+
+static void addenv_ (tipidee_rql const *rql, char const *k, char const *v, int slash)
+{
+ if (!stralloc_cats(&g.sa, k)
+ || !stralloc_catb(&g.sa, "=/", 1 + !!slash)
+ || !stralloc_cats(&g.sa, v)
+ || !stralloc_0(&g.sa))
+ die500sys(rql, 111, "stralloc_catb") ;
+}
+
+#define addenv(rql, k, v) addenv_(rql, k, (v), 0)
+#define addenvslash(rql, k, v) addenv_(rql, k, (v), 1)
+
+static void delenv (tipidee_rql const *rql, char const *k)
+{
+ if (!stralloc_cats(&g.sa, k)
+ || !stralloc_0(&g.sa))
+ die500sys(rql, 111, "stralloc_catb") ;
+}
+
+static inline void modify_env (tipidee_rql const *rql, tipidee_headers const *hdr, size_t cl, char const *script, char const *infopath)
+{
+ uint32_t got = 0 ;
+ addenv(rql, "REQUEST_METHOD", tipidee_method_tostr(rql->m)) ;
+ if (cl)
+ {
+ char fmt[SIZE_FMT] ;
+ fmt[size_fmt(fmt, cl)] = 0 ;
+ addenv(rql, "CONTENT_LENGTH", fmt) ;
+ }
+ else delenv(rql, "CONTENT_LENGTH") ;
+
+ if (infopath) addenvslash(rql, "PATH_INFO", infopath) ;
+ else delenv(rql, "PATH_INFO") ;
+ if (rql->uri.query) addenv(rql, "QUERY_STRING", rql->uri.query) ;
+ else delenv(rql, "QUERY_STRING") ;
+ addenv(rql, "SCRIPT_NAME", script) ;
+
+ for (size_t i = 0 ; i < hdr->n ; i++)
+ {
+ char const *key = hdr->buf + hdr->list[i].left ;
+ char const *val = hdr->buf + hdr->list[i].right ;
+ if (!strcasecmp(key, "Authorization"))
+ {
+ size_t n = str_chr(val, ' ') ;
+ if (n)
+ {
+ char scheme[n] ;
+ memcpy(scheme, val, n-1) ;
+ scheme[n-1] = 0 ;
+ addenv(rql, "AUTH_TYPE", scheme) ;
+ got |= 1 ;
+ }
+ }
+ else if (!strcasecmp(key, "Content-Type")) { addenv(rql, "CONTENT_TYPE", val) ; got |= 2 ; }
+ else if (!strcasecmp(key, "Content-Length") || !strcasecmp(key, "Connection")) ;
+ else
+ {
+ size_t len = strlen(key), pos = g.sa.len + 5 ;
+ if (!stralloc_catb(&g.sa, "HTTP_", 5)) die500sys(rql, 111, "stralloc_catb") ;
+ addenv(rql, key, val) ;
+ for (char *s = g.sa.s + pos ; len-- ; s++)
+ if (*s == '-') *s = '_' ;
+ else if (*s >= 'a' && *s <= 'z') *s -= 32 ;
+ }
+ }
+ if (!(got & 1)) delenv(rql, "AUTH_TYPE") ;
+ if (!(got & 2)) delenv(rql, "CONTENT_TYPE") ;
+}
+
+static inline int do_nph (tipidee_rql const *rql, char const *const *argv, char const *const *envp, char const *body, size_t bodylen) gccattr_noreturn ;
+static inline int do_nph (tipidee_rql const *rql, char const *const *argv, char const *const *envp, char const *body, size_t bodylen)
+{
+ int p[2] ;
+ log_nph(argv, envp) ;
+ if (pipe(p) == -1) die500sys(rql, 111, "pipe") ;
+ if (bodylen)
+ {
+ switch (fork())
+ {
+ case -1 : die500sys(rql, 111, "fork") ;
+ case 0 :
+ {
+ tain deadline ;
+ char buf[4096] ;
+ buffer b = BUFFER_INIT(&buffer_write, p[1], buf, 4096) ;
+ PROG = "tipidee (nph helper child)" ;
+ tain_add_g(&deadline, &g.cgitto) ;
+ close(p[0]) ;
+ if (ndelay_on(p[1]) == -1) strerr_diefu1sys(111, "set fd nonblocking") ;
+ if (buffer_timed_put_g(&b, body, bodylen, &deadline) < bodylen
+ || !buffer_timed_flush_g(&b, &deadline))
+ strerr_diefu2sys(111, "write request body to nph ", argv[0]) ;
+ _exit(0) ;
+ }
+ default : break ;
+ }
+ }
+ close(p[1]) ;
+ if (fd_move(0, p[0]) == -1) die500sys(rql, 111, "fd_move") ;
+ exec_e(argv, envp) ;
+ die500sys(rql, errno == ENOENT ? 127 : 126, "exec nph ", argv[0]) ;
+}
+
+static inline int run_cgi (tipidee_rql const *rql, char const *const *argv, char const *const *envp, char const *body, size_t bodylen, tipidee_headers *hdr, stralloc *sa)
+{
+ iopause_fd x[2] = { { .events = IOPAUSE_READ }, { .events = IOPAUSE_WRITE } } ;
+ size_t bodyw = 0 ;
+ unsigned int rstate = 0 ;
+ tain deadline ;
+ pid_t pid ;
+ disize curheader = DISIZE_ZERO ;
+ uint32_t parserstate = 0 ;
+ buffer b ;
+ char buf[4096] ;
+ log_cgi(argv, envp) ;
+ {
+ int fd[2] = { 0, 1 } ;
+ pid = child_spawn2(argv[0], argv, envp, fd) ;
+ if (!pid) die500sys(rql, 111, "spawn ", argv[0]) ;
+ x[0].fd = fd[0] ; x[1].fd = fd[1] ;
+ }
+ if (!bodylen)
+ {
+ close(x[1].fd) ;
+ x[1].fd = -1 ;
+ LOLDEBUG("run_cgi: no request body, closing writing pipe to cgi") ;
+ }
+ buffer_init(&b, &buffer_read, x[0].fd, buf, 4096) ;
+ tain_add_g(&deadline, &g.cgitto) ;
+ while (x[0].fd >= 0)
+ {
+ int r = iopause_g(x, 1 + (x[1].fd >= 0), &deadline) ;
+ if (r == -1) die500sys(rql, 111, "iopause") ;
+ if (!r)
+ {
+ kill(pid, SIGTERM) ;
+ respond_504(rql) ;
+ break ;
+ }
+ if (x[1].fd >= 0 && x[1].revents & (IOPAUSE_WRITE | IOPAUSE_EXCEPT))
+ {
+ size_t len = allwrite(x[1].fd, body + bodyw, bodylen - bodyw) ;
+ if (!len)
+ {
+ if (g.verbosity) strerr_warnwu2sys("write request body to cgi ", argv[0]) ;
+ bodyw = bodylen ;
+ }
+ else bodyw += len ;
+ if (bodyw >= bodylen)
+ {
+ close(x[1].fd) ;
+ x[1].fd = -1 ;
+ LOLDEBUG("run_cgi: finished writing body") ;
+ }
+ }
+ if (x[0].fd >= 0 && x[0].revents & (IOPAUSE_READ | IOPAUSE_EXCEPT))
+ {
+ switch (rstate)
+ {
+ case 0 :
+ {
+ r = tipidee_headers_parse_nb(&b, hdr, &curheader, &parserstate) ;
+ switch (r)
+ {
+ case -2 : break ;
+ case -1 : die500sys(rql, 111, "read from cgi ", argv[0]) ;
+ case 0 :
+ {
+ size_t n = buffer_len(&b) ;
+ if (!stralloc_readyplus(sa, n)) die500sys(rql, 111, "stralloc_readyplus") ;
+ buffer_getnofill(&b, sa->s + sa->len, n) ;
+ sa->len += n ;
+ rstate = 1 ;
+ break ;
+ }
+ case 400 : die502x(rql, 1, "invalid headers", " from cgi ", argv[0]) ;
+ case 413 : die502x(rql, 1, hdr->n >= TIPIDEE_HEADERS_MAX ? "Too many headers" : "Too much header data", " from cgi ", argv[0]) ;
+ case 500 : die500x(rql, 101, "can't happen: ", "avltreen_insert failed", " in do_cgi") ;
+ default : die500x(rql, 101, "can't happen: ", "unknown tipidee_headers_parse return code", " in do_cgi") ;
+ }
+ if (!rstate) break ;
+ }
+ case 1 :
+ {
+ if (!slurpn(x[0].fd, sa, g.maxcgibody))
+ {
+ if (error_isagain(errno)) break ;
+ else if (errno == ENOBUFS) die502x(rql, 1, "Too fat body", " from cgi ", argv[0]) ;
+ else die500sys(rql, 111, "read body", " from cgi ", argv[0]) ;
+ }
+ close(x[0].fd) ;
+ x[0].fd = -1 ;
+ rstate = 2 ;
+ LOLDEBUG("run_cgi: rstate = 2") ;
+ }
+ }
+ }
+ }
+ if (x[1].fd >= 0) close(x[1].fd) ;
+ if (x[0].fd >= 0) close(x[0].fd) ;
+ return rstate == 2 ;
+}
+
+static inline int local_redirect (tipidee_rql *rql, char const *loc, char *uribuf, char const *cginame)
+{
+ size_t n ;
+ size_t hostlen = strlen(rql->uri.host) ;
+ uint16_t port = rql->uri.port ;
+ uint8_t ishttps = rql->uri.https ;
+ char hosttmp[hostlen + 1] ;
+ memcpy(hosttmp, rql->uri.host, hostlen + 1) ;
+ n = tipidee_uri_parse(uribuf, URI_BUFSIZE, loc, &rql->uri) ;
+ if (!n || n + hostlen + 1 > URI_BUFSIZE)
+ die502x(rql, 1, "cgi ", cginame, " returned an invalid ", "Location", " value", " for local redirection") ;
+ memcpy(uribuf + n, hosttmp, hostlen + 1) ;
+ rql->uri.host = uribuf + n ;
+ rql->uri.port = port ;
+ rql->uri.https = ishttps ;
+ return 1 ;
+}
+
+static inline void print_cgi_headers (tipidee_headers const *hdr, size_t rbodylen)
+{
+ static char const *const nope_table[] =
+ {
+ "Connection",
+ "Date",
+ "Status",
+ "Content-Length",
+ 0
+ } ;
+ for (size_t i = 0 ; i < hdr->n ; i++)
+ {
+ char const *key = hdr->buf + hdr->list[i].left ;
+ char const *const *p = nope_table ;
+ if (tipidee_response_header_builtin_search(key)) continue ;
+ if (str_start(key, "X-CGI-")) continue ;
+ for (; *p ; p++) if (!strcasecmp(key, *p)) break ;
+ if (*p) continue ;
+ buffer_putsnoflush(buffer_1, key) ;
+ buffer_putnoflush(buffer_1, ": ", 2) ;
+ buffer_putsnoflush(buffer_1, hdr->buf + hdr->list[i].right) ;
+ buffer_putnoflush(buffer_1, "\r\n", 2) ;
+ }
+ if (rbodylen)
+ {
+ char fmt[SIZE_FMT] ;
+ fmt[size_fmt(fmt, rbodylen)] = 0 ;
+ buffer_putsnoflush(buffer_1, "Content-Length: ") ;
+ buffer_putsnoflush(buffer_1, fmt) ;
+ buffer_putnoflush(buffer_1, "\r\n", 2) ;
+ }
+}
+
+static inline int process_cgi_output (tipidee_rql *rql, tipidee_headers const *hdr, char const *rbody, size_t rbodylen, char *uribuf, char const *cginame)
+{
+ char const *location = tipidee_headers_search(hdr, "Location") ;
+ char const *x = tipidee_headers_search(hdr, "Status") ;
+ char const *reason_phrase = "OK" ;
+ unsigned int status = 0 ;
+ tain deadline ;
+ tain_add_g(&deadline, &g.writetto) ;
+ if (x)
+ {
+ size_t m = uint_scan(x, &status) ;
+ if (!m || x[m] != ' ')
+ die502x(rql, 1, "cgi ", cginame, " returned an invalid ", "Status", " header") ;
+ reason_phrase = x + m + 1 ;
+ if (status >= 300 && status < 399 && !location)
+ die502x(rql, 1, "cgi ", cginame, " returned a 3xx status code without a ", "Location", " header") ;
+ if (status < 100 || status > 999)
+ die502x(rql, 1, "cgi ", cginame, " returned an invalid ", "Status", " value") ;
+ }
+ if (location)
+ {
+ if (!location[0]) die502x(rql, 1, "cgi ", cginame, " returned an invalid ", "Location", " header") ;
+ if (location[0] == '/' && location[1] != '/') return local_redirect(rql, location, uribuf, cginame) ;
+ if (rbodylen)
+ {
+ if (!status)
+ die502x(rql, 1, "cgi ", cginame, " didn't output a ", "Status", " header", " for a client redirect response with document") ;
+ if (status < 300 || status > 399)
+ die502x(rql, 1, "cgi ", cginame, " returned an invalid ", "Status", " value", " for a client redirect response with document") ;
+ }
+ else
+ {
+ for (size_t i = 0 ; i < hdr->n ; i++)
+ {
+ char const *key = hdr->buf + hdr->list[i].left ;
+ if (!strcasecmp(key, "Location")) continue ;
+ if (str_start(key, "X-CGI-")) continue ;
+ die502x(rql, 1, "cgi ", cginame, "returned extra headers", " for a client redirect response without document") ;
+ }
+ status = 302 ;
+ reason_phrase = "Found" ;
+ }
+ }
+ else
+ {
+ if (!status) status = 200 ;
+ if (!tipidee_headers_search(hdr, "Content-Type"))
+ die502x(rql, 1, "cgi ", cginame, " didn't output a ", "Content-Type", " header") ;
+ }
+ x = tipidee_headers_search(hdr, "Content-Length") ;
+ if (x)
+ {
+ size_t cln ;
+ if (!size0_scan(x, &cln))
+ die502x(rql, 1, "cgi ", cginame, " returned an invalid ", "Content-Length", " header") ;
+ if (cln != rbodylen)
+ die502x(rql, 1, "cgi ", cginame, " returned a mismatching ", "Content-Length", " header") ;
+ }
+
+ tipidee_response_status(buffer_1, rql, status, reason_phrase) ;
+ tipidee_response_header_common_put_g(buffer_1, !g.cont) ;
+ print_cgi_headers(hdr, rbodylen) ;
+ if (buffer_timed_put_g(buffer_1, "\r\n", 2, &deadline) < 2)
+ strerr_diefu1sys(111, "write to stdout") ;
+ if (rbodylen)
+ {
+ if (buffer_timed_put_g(buffer_1, rbody, rbodylen, &deadline) < rbodylen)
+ strerr_diefu1sys(111, "write to stdout") ;
+ }
+ if (!buffer_timed_flush_g(buffer_1, &deadline))
+ strerr_diefu1sys(111, "write to stdout") ;
+ return 0 ;
+}
+
+static inline int do_cgi (tipidee_rql *rql, char const *const *argv, char const *const *envp, char const *body, size_t bodylen, char *uribuf)
+{
+ static stralloc sa = STRALLOC_ZERO ;
+ tipidee_headers hdr ;
+ char hdrbuf[2048] ;
+ sa.len = 0 ;
+ tipidee_headers_init(&hdr, hdrbuf, 2048) ;
+ if (!run_cgi(rql, argv, envp, body, bodylen, &hdr, &sa)) return 0 ;
+ return process_cgi_output(rql, &hdr, sa.s, sa.len, uribuf, argv[0]) ;
+}
+
+int respond_cgi (tipidee_rql *rql, char const *fn, size_t docrootlen, char const *infopath, char *uribuf, tipidee_headers const *hdr, tipidee_resattr const *ra, char const *body, size_t bodylen)
+{
+ size_t sabase = g.sa.len ;
+ size_t envmax = g.envlen + 16 + TIPIDEE_HEADERS_MAX ;
+ char const *argv[2] = { fn, 0 } ;
+ char const *envp[envmax] ;
+ modify_env(rql, hdr, bodylen, fn + docrootlen, infopath) ;
+ env_merge(envp, envmax, (char const *const *)environ, g.envlen, g.sa.s + g.cwdlen + 1, g.sa.len - (g.cwdlen+1)) ;
+ g.sa.len = sabase ;
+ return ra->isnph ? do_nph(rql, argv, envp, body, bodylen) :
+ do_cgi(rql, argv, envp, body, bodylen, uribuf) ;
+}
diff --git a/src/tipideed/deps-exe/tipideed b/src/tipideed/deps-exe/tipideed
new file mode 100644
index 0000000..aad1417
--- /dev/null
+++ b/src/tipideed/deps-exe/tipideed
@@ -0,0 +1,11 @@
+cgi.o
+harden.o
+log.o
+options.o
+regular.o
+responses.o
+send_file.o
+tipideed.o
+trace.o
+libtipidee.a.xyzzy
+-lskarnet
diff --git a/src/tipideed/harden.c b/src/tipideed/harden.c
new file mode 100644
index 0000000..5c925f2
--- /dev/null
+++ b/src/tipideed/harden.c
@@ -0,0 +1,50 @@
+/* ISC license. */
+
+#include <skalibs/sysdeps.h>
+#include <skalibs/nonposix.h>
+
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include <skalibs/types.h>
+#include <skalibs/strerr.h>
+
+#include "tipideed-internal.h"
+
+static inline void tipideed_chroot (void)
+{
+#ifdef SKALIBS_HASCHROOT
+ if (chroot(".") == -1) strerr_diefu1sys(111, "chroot") ;
+#else
+ errno = ENOSYS ;
+ strerr_warnwu1sys("chroot") ;
+#endif
+}
+
+static inline void tipideed_dropuidgid (void)
+{
+ uid_t uid = 0 ;
+ gid_t gid = 0 ;
+ char const *gidfmt = getenv("GID") ;
+ char const *uidfmt = getenv("UID") ;
+ if (!uidfmt) strerr_dienotset(100, "UID") ;
+ if (!uid0_scan(uidfmt, &uid)) strerr_dieinvalid(100, "UID") ;
+ if (!gidfmt) strerr_dienotset(100, "GID") ;
+ if (!gid0_scan(gidfmt, &gid)) strerr_dieinvalid(100, "GID") ;
+ if (gid)
+ {
+#ifdef SKALIBS_HASSETGROUPS
+ if (setgroups(1, &gid) == -1) strerr_diefu2sys(111, "setgroups to ", gidfmt) ;
+#endif
+ if (setgid(gid) == -1) strerr_diefu2sys(111, "setgid to ", gidfmt) ;
+ }
+ if (uid)
+ if (setuid(uid) == -1) strerr_diefu2sys(111, "setuid to ", uidfmt) ;
+}
+
+void tipideed_harden (unsigned int h)
+{
+ if (h & 2) tipideed_chroot() ;
+ if (h & 1) tipideed_dropuidgid() ;
+}
diff --git a/src/tipideed/log.c b/src/tipideed/log.c
new file mode 100644
index 0000000..a257ff5
--- /dev/null
+++ b/src/tipideed/log.c
@@ -0,0 +1,59 @@
+/* ISC license. */
+
+#include <unistd.h>
+
+#include <skalibs/uint16.h>
+#include <skalibs/types.h>
+#include <skalibs/strerr.h>
+
+#include <tipidee/method.h>
+#include "tipideed-internal.h"
+
+void log_start (void)
+{
+ if (g.verbosity >= 4)
+ strerr_warni7x("new connection", " from ip ", g.sa.s + g.remoteip, " (", g.sa.s + g.remotehost, ") port ", g.sa.s + g.remoteport) ;
+ else if (g.verbosity >= 3)
+ strerr_warni1x("new connection") ;
+}
+
+void log_and_exit (int e)
+{
+ if (g.verbosity >= 3)
+ {
+ char fmt[INT_FMT] ;
+ fmt[int_fmt(fmt, e)] = 0 ;
+ strerr_warni2x("exiting ", fmt) ;
+ }
+ _exit(e) ;
+}
+
+void log_request (tipidee_rql const *rql)
+{
+ if (g.verbosity >= 2)
+ {
+ char fmt[UINT16_FMT] ;
+ if (rql->uri.port) fmt[uint16_fmt(fmt, rql->uri.port)] = 0 ;
+ strerr_warnin(11, "request ", tipidee_method_tostr(rql->m), " for", rql->uri.host ? " host " : "", rql->uri.host ? rql->uri.host : "", rql->uri.port ? " port " : "", rql->uri.port ? fmt : "", " path ", rql->uri.path, rql->uri.query ? " query " : "", rql->uri.query ? rql->uri.query : "") ;
+ }
+}
+
+void log_regular (char const *fn, char const *sizefmt, int ishead, char const *ct)
+{
+ if (g.verbosity >= 2)
+ strerr_warni8x("sending ", ishead ? "headers for " : "", "regular file ", fn, " (", sizefmt, " bytes) with type ", ct) ;
+}
+
+void log_nph (char const *const *argv, char const *const *envp)
+{
+ if (g.verbosity >= 2)
+ strerr_warni3x("running ", "nph ", argv[0]) ;
+ (void)envp ;
+}
+
+void log_cgi (char const *const *argv, char const *const *envp)
+{
+ if (g.verbosity >= 2)
+ strerr_warni3x("running ", "cgi ", argv[0]) ;
+ (void)envp ;
+}
diff --git a/src/tipideed/options.c b/src/tipideed/options.c
new file mode 100644
index 0000000..d425943
--- /dev/null
+++ b/src/tipideed/options.c
@@ -0,0 +1,25 @@
+/* ISC license. */
+
+#include <string.h>
+
+#include <skalibs/buffer.h>
+#include <skalibs/strerr.h>
+#include <skalibs/tai.h>
+#include <skalibs/unix-timed.h>
+
+#include <tipidee/response.h>
+#include "tipideed-internal.h"
+
+int respond_options (tipidee_rql const *rql, uint32_t flags)
+{
+ tain deadline ;
+ tipidee_response_status_line(buffer_1, rql, "200 OK") ;
+ tipidee_response_header_common_put_g(buffer_1, 0) ;
+ buffer_putsnoflush(buffer_1, "Content-Length: 0\r\nAllow: GET, HEAD") ;
+ if (flags & 1) buffer_putsnoflush(buffer_1, ", POST") ;
+ buffer_putnoflush(buffer_1, "\r\n\r\n", 4) ;
+ tain_add_g(&deadline, &g.writetto) ;
+ if (!buffer_timed_flush_g(buffer_1, &deadline))
+ strerr_diefu1sys(111, "write to stdout") ;
+ return 0 ;
+}
diff --git a/src/tipideed/regular.c b/src/tipideed/regular.c
new file mode 100644
index 0000000..1ac1095
--- /dev/null
+++ b/src/tipideed/regular.c
@@ -0,0 +1,48 @@
+/* ISC license. */
+
+#include <skalibs/uint64.h>
+#include <skalibs/types.h>
+#include <skalibs/buffer.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/strerr.h>
+#include <skalibs/tai.h>
+#include <skalibs/unix-timed.h>
+
+#include <tipidee/method.h>
+#include <tipidee/response.h>
+#include "tipideed-internal.h"
+
+int respond_regular (tipidee_rql const *rql, char const *fn, uint64_t size, tipidee_resattr const *ra)
+{
+ tain deadline ;
+ size_t n = tipidee_response_status_line(buffer_1, rql, "200 OK") ;
+ n += tipidee_response_header_common_put_g(buffer_1, !g.cont) ;
+ n += buffer_putsnoflush(buffer_1, "Content-Type: ") ;
+ n += buffer_putsnoflush(buffer_1, ra->content_type) ;
+ n += buffer_putsnoflush(buffer_1, "\r\nContent-Length: ") ;
+ {
+ char fmt[UINT64_FMT] ;
+ fmt[uint64_fmt(fmt, size)] = 0 ;
+ n += buffer_putsnoflush(buffer_1, fmt) ;
+ log_regular(fn, fmt, rql->m == TIPIDEE_METHOD_HEAD, ra->content_type) ;
+ }
+ n += buffer_putnoflush(buffer_1, "\r\n\r\n", 4) ;
+ if (rql->m == TIPIDEE_METHOD_HEAD)
+ {
+ tain_add_g(&deadline, &g.writetto) ;
+ if (!buffer_timed_flush_g(buffer_1, &deadline))
+ strerr_diefu1sys(111, "write to stdout") ;
+ }
+ else
+ {
+ int fd = open_read(fn) ;
+ if (fd == -1)
+ {
+ buffer_unput(buffer_1, n) ;
+ die500sys(rql, 111, "open ", fn) ;
+ }
+ send_file(fd, size, fn) ;
+ fd_close(fd) ;
+ }
+ return 0 ;
+}
diff --git a/src/tipideed/responses.c b/src/tipideed/responses.c
new file mode 100644
index 0000000..02109b3
--- /dev/null
+++ b/src/tipideed/responses.c
@@ -0,0 +1,64 @@
+/* ISC license. */
+
+#include <unistd.h>
+
+#include <skalibs/buffer.h>
+#include <skalibs/strerr.h>
+#include <skalibs/tai.h>
+#include <skalibs/unix-timed.h>
+
+#include <tipidee/rql.h>
+#include <tipidee/response.h>
+
+#include "tipideed-internal.h"
+
+void response_error (tipidee_rql const *rql, char const *rsl, char const *text, int doclose)
+{
+ tain deadline ;
+ tipidee_response_error(buffer_1, rql, rsl, text, doclose || !g.cont) ;
+ tain_add_g(&deadline, &g.writetto) ;
+ if (!buffer_timed_flush_g(buffer_1, &deadline))
+ strerr_diefu1sys(111, "write to stdout") ;
+}
+
+void response_error_and_exit (tipidee_rql const *rql, char const *rsl, char const *text)
+{
+ response_error(rql, rsl, text, 1) ;
+ log_and_exit(0) ;
+}
+
+void response_error_and_die (tipidee_rql const *rql, int e, char const *rsl, char const *text, char const *const *v, unsigned int n, int dosys)
+{
+ response_error(rql, rsl, text, 1) ;
+ if (dosys) strerr_dievsys(e, v, n) ;
+ else strerr_diev(e, v, n) ;
+}
+
+void exit_405 (tipidee_rql const *rql, uint32_t options)
+{
+ tain deadline ;
+ tipidee_response_status_line(buffer_1, rql, "405 Method Not Allowed") ;
+ tipidee_response_header_common_put_g(buffer_1, 1) ;
+ buffer_putsnoflush(buffer_1, "Allow: GET, HEAD") ;
+ if (options & 1) buffer_putsnoflush(buffer_1, ", POST") ;
+ buffer_putnoflush(buffer_1, "\r\n\r\n", 4) ;
+ tain_add_g(&deadline, &g.writetto) ;
+ if (!buffer_timed_flush_g(buffer_1, &deadline))
+ strerr_diefu1sys(111, "write to stdout") ;
+ log_and_exit(0) ;
+}
+
+void respond_30x (tipidee_rql const *rql, tipidee_redirection const *rd)
+{
+ static char const *rsl[4] = { "307 Temporary Redirect", "308 Permanent Redirect", "302 Found", "301 Moved Permanently" } ;
+ tain deadline ;
+ tipidee_response_status_line(buffer_1, rql, rsl[rd->type]) ;
+ tipidee_response_header_common_put_g(buffer_1, 0) ;
+ buffer_putsnoflush(buffer_1, "Location: ") ;
+ buffer_putsnoflush(buffer_1, rd->location) ;
+ if (rd->sub) buffer_putsnoflush(buffer_1, rd->sub) ;
+ buffer_putnoflush(buffer_1, "\r\n\r\n", 4) ;
+ tain_add_g(&deadline, &g.writetto) ;
+ if (!buffer_timed_flush_g(buffer_1, &deadline))
+ strerr_diefu1sys(111, "write to stdout") ;
+}
diff --git a/src/tipideed/send_file.c b/src/tipideed/send_file.c
new file mode 100644
index 0000000..77b49dd
--- /dev/null
+++ b/src/tipideed/send_file.c
@@ -0,0 +1,123 @@
+/* ISC license. */
+
+#include <skalibs/sysdeps.h>
+
+#ifdef SKALIBS_HASSPLICE
+
+#include <skalibs/nonposix.h>
+
+#include <fcntl.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <skalibs/strerr.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/unix-timed.h>
+
+#include "tipideed-internal.h"
+
+void init_splice_pipe (void)
+{
+ if (pipenbcoe(g.p) == -1)
+ strerr_diefu1sys(111, "pipe2") ;
+}
+
+struct spliceinfo_s
+{
+ ssize_t n ;
+ uint32_t last : 1 ;
+} ;
+
+static int getfd (void *b)
+{
+ (void)b ;
+ return 1 ;
+}
+
+static int isnonempty (void *b)
+{
+ struct spliceinfo_s *si = b ;
+ return !!si->n ;
+}
+
+static int flush (void *b)
+{
+ struct spliceinfo_s *si = b ;
+ while (si->n)
+ {
+ ssize_t r = splice(g.p[0], 0, 1, 0, si->n, SPLICE_F_NONBLOCK | (si->last ? 0 : SPLICE_F_MORE)) ;
+ if (r == -1) return 0 ;
+ if (!r) return 1 ;
+ si->n -= r ;
+ }
+ return 1 ;
+}
+
+void send_file (int fd, uint64_t n, char const *fn)
+{
+ tain deadline ;
+ struct spliceinfo_s si = { .last = 0 } ;
+ tain_add_g(&deadline, &g.writetto) ;
+ if (!buffer_timed_flush_g(buffer_1, &deadline))
+ strerr_diefu2sys(111, "write", " to stdout") ;
+ while (n)
+ {
+ si.n = splice(fd, 0, g.p[1], 0, n, 0) ;
+ if (si.n == -1) strerr_diefu2sys(111, "read from ", fn) ;
+ else if (!si.n) strerr_diefu3x(111, "serve ", fn, ": file was truncated") ;
+ else if (si.n > n)
+ {
+ si.n = n ;
+ if (g.verbosity >= 2)
+ strerr_warnw2x("serving elongated file: ", fn) ;
+ }
+ n -= si.n ;
+ if (!n) si.last = 1 ;
+ tain_add_g(&deadline, &g.writetto) ;
+ if (!timed_flush_g(&si, &getfd, &isnonempty, &flush, &deadline))
+ strerr_diefu2sys(111, "splice", " to stdout") ;
+ }
+}
+
+#else
+
+#include <sys/uio.h>
+
+#include <skalibs/allreadwrite.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr.h>
+#include <skalibs/tai.h>
+
+#include "tipideed-internal.h"
+
+void init_splice_pipe (void)
+{
+}
+
+void send_file (int fd, uint64_t n, char const *fn)
+{
+ tain deadline ;
+ struct iovec v[2] ;
+ while (n)
+ {
+ ssize_t r ;
+ buffer_rpeek(buffer_1, v) ;
+ r = allreadv(fd, v, 2) ;
+ if (r > n)
+ if (r == -1) strerr_diefu2sys(111, "read from ", fn) ;
+ if (!r) strerr_diefu3x(111, "serve ", fn, ": file was truncated") ;
+ if (r > n)
+ {
+ r = n ;
+ if (g.verbosity >= 2)
+ strerr_warnw2x("serving elongated file: ", fn)
+ }
+ buffer_rseek(b, r) ;
+ tain_add_g(&deadline, g.writetto) ;
+ if (!buffer_timed_flush_g(buffer_1, &deadline))
+ strerr_diefu1sys(111, "write to stdout") ;
+ n -= r ;
+ }
+}
+
+#endif
diff --git a/src/tipideed/tipideed-internal.h b/src/tipideed/tipideed-internal.h
new file mode 100644
index 0000000..c4ff928
--- /dev/null
+++ b/src/tipideed/tipideed-internal.h
@@ -0,0 +1,147 @@
+/* ISC license. */
+
+#ifndef TIPIDEED_INTERNAL_H
+#define TIPIDEED_INTERNAL_H
+
+#include <sys/types.h>
+#include <stdint.h>
+
+#include <skalibs/gccattributes.h>
+#include <skalibs/uint64.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/strerr.h>
+#include <skalibs/tai.h>
+
+#include <tipidee/tipidee.h>
+
+#define URI_BUFSIZE 4096
+#define HDR_BUFSIZE 8192
+
+typedef struct tipidee_resattr_s tipidee_resattr, *tipidee_resattr_ref ;
+struct tipidee_resattr_s
+{
+ char const *content_type ;
+ uint32_t iscgi : 1 ;
+ uint32_t isnph : 1 ;
+} ;
+#define TIPIDEE_RESATTR_ZERO { .content_type = 0, .iscgi = 0, .isnph = 0 }
+
+struct global_s
+{
+ tipidee_conf conf ;
+ stralloc sa ;
+ size_t envlen ;
+ size_t localip ;
+ size_t localhost ;
+ size_t localport ;
+ size_t localportlen ;
+ size_t remoteip ;
+ size_t remotehost ;
+ size_t remoteport ;
+ size_t cwdlen ;
+ size_t indexlen ;
+ tain readtto ;
+ tain writetto ;
+ tain cgitto ;
+ char const *indexnames[16] ;
+ int p[2] ;
+ uint32_t maxrqbody ;
+ uint32_t maxcgibody ;
+ uint16_t indexn : 4 ;
+ uint16_t verbosity : 3 ;
+ uint16_t cont : 2 ;
+} ;
+#define GLOBAL_ZERO \
+{ \
+ .conf = TIPIDEE_CONF_ZERO, \
+ .sa = STRALLOC_ZERO, \
+ .envlen = 0, \
+ .localip = 0, \
+ .localhost = 0, \
+ .localport = 0, \
+ .localportlen = 0, \
+ .remoteip = 0, \
+ .remotehost = 0, \
+ .remoteport = 0, \
+ .cwdlen = 1, \
+ .indexlen = 0, \
+ .readtto = TAIN_ZERO, \
+ .writetto = TAIN_ZERO, \
+ .cgitto = TAIN_ZERO, \
+ .indexnames = { 0 }, \
+ .p = { -1, -1 }, \
+ .maxrqbody = 0, \
+ .maxcgibody = 0, \
+ .indexn = 0, \
+ .verbosity = 1, \
+ .cont = 1 \
+}
+
+extern struct global_s g ;
+
+
+ /* uid/gid and chroot */
+
+extern void tipideed_harden (unsigned int) ;
+
+
+ /* Responses */
+
+extern void response_error (tipidee_rql const *, char const *, char const *, int) ;
+extern void response_error_and_exit (tipidee_rql const *, char const *, char const *) gccattr_noreturn ;
+extern void response_error_and_die (tipidee_rql const *, int e, char const *, char const *, char const *const *, unsigned int, int) gccattr_noreturn ;
+
+#define exit_400(r, s) response_error_and_exit(r, "400 Bad Request", s)
+extern void exit_405 (tipidee_rql const *, uint32_t) gccattr_noreturn ;
+#define exit_408(r) response_error_and_exit(r, "408 Request Timeout", "")
+#define exit_413(r, s) response_error_and_exit(r, "413 Request Entity Too Large", s)
+#define exit_501(r, s) response_error_and_exit(r, "501 Not Implemented", s)
+
+#define respond_403(r) response_error(r, "403 Forbidden", "Missing credentials to access the URI.", 0)
+#define respond_404(r) response_error(r, "404 Not Found", "The request URI was not found.", 0)
+#define respond_414(r) response_error(r, "414 URI Too Long", "The request URI had an oversized component.", 0)
+extern void respond_30x (tipidee_rql const *, tipidee_redirection const *) ;
+#define respond_504(r) response_error(r, "504 Gateway Timeout", "The CGI script took too long to answer.", 0)
+
+#define diefx(r, e, rsl, text, ...) response_error_and_die(r, e, rsl, text, strerr_array(PROG, ": fatal: ", __VA_ARGS__), sizeof(strerr_array(__VA_ARGS__))/sizeof(char const *)+2, 0)
+#define diefusys(r, e, rsl, text, ...) response_error_and_die(r, e, rsl, text, strerr_array(PROG, ": fatal: ", "unable to ", __VA_ARGS__), sizeof(strerr_array(__VA_ARGS__))/sizeof(char const *)+3, 1)
+#define die500x(r, e, ...) diefx(r, e, "500 Internal Server Error", "Bad server configuration.", __VA_ARGS__)
+#define die500sys(r, e, ...) diefusys(r, e, "500 Internal Server Error", "System error.", __VA_ARGS__)
+#define die502x(r, e, ...) diefx(r, e, "502 Bad Gateway", "Bad CGI script.", __VA_ARGS__)
+
+ /* Trace */
+
+extern int respond_trace (char const *, tipidee_rql const *, tipidee_headers const *) ;
+
+
+ /* Options */
+
+extern int respond_options (tipidee_rql const *, uint32_t) ;
+
+
+ /* send_file */
+
+extern void init_splice_pipe (void) ;
+extern void send_file (int, uint64_t, char const *) ;
+
+
+ /* regular */
+
+extern int respond_regular (tipidee_rql const *, char const *, uint64_t, tipidee_resattr const *) ;
+
+
+ /* cgi */
+
+extern int respond_cgi (tipidee_rql *, char const *, size_t, char const *, char *, tipidee_headers const *, tipidee_resattr const *, char const *, size_t) ;
+
+
+ /* log */
+
+extern void log_start (void) ;
+extern void log_and_exit (int) gccattr_noreturn ;
+extern void log_request (tipidee_rql const *) ;
+extern void log_regular (char const *, char const *, int, char const *) ;
+extern void log_nph (char const *const *, char const *const *) ;
+extern void log_cgi (char const *const *, char const *const *) ;
+
+#endif
diff --git a/src/tipideed/tipideed.c b/src/tipideed/tipideed.c
new file mode 100644
index 0000000..42e65a6
--- /dev/null
+++ b/src/tipideed/tipideed.c
@@ -0,0 +1,514 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+#include <skalibs/env.h>
+#include <skalibs/uint16.h>
+#include <skalibs/types.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/buffer.h>
+#include <skalibs/error.h>
+#include <skalibs/strerr.h>
+#include <skalibs/tai.h>
+#include <skalibs/ip46.h>
+#include <skalibs/sig.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/avltreen.h>
+#include <skalibs/unix-timed.h>
+#include <skalibs/lolstdio.h>
+
+#include <tipidee/tipidee.h>
+#include "tipideed-internal.h"
+
+#define USAGE "tipideed [ -v verbosity ] [ -f conffile ] [ -R chroot ] [ -U ]"
+#define dieusage() strerr_dieusage(100, USAGE)
+#define dienomem() strerr_diefu1sys(111, "stralloc_catb")
+
+#define ARGV_MAX 128
+
+struct global_s g = GLOBAL_ZERO ;
+
+static void sigchld_handler (int sig)
+{
+ (void)sig ;
+ wait_reap() ;
+}
+
+static inline void prep_env (void)
+{
+ static char const basevars[] = "PROTO\0GATEWAY_INTERFACE=CGI/1.1\0SERVER_PROTOCOL=HTTP/1.1\0SERVER_SOFTWARE=tipidee/" TIPIDEE_VERSION ;
+ static char const sslvars[] = "SSL_PROTOCOL\0SSL_CIPHER\0SSL_TLS_SNI_SERVERNAME\0SSL_PEER_CERT_HASH\0SSL_PEER_CERT_SUBJECT\0HTTPS=on" ;
+ char const *x = getenv("SSL_PROTOCOL") ;
+ if (!stralloc_readyplus(&g.sa, 320)) dienomem() ;
+ if (sagetcwd(&g.sa) == -1) strerr_diefu1sys(111, "getcwd") ;
+ if (g.sa.len == 1) g.sa.len = 0 ;
+ g.cwdlen = g.sa.len ;
+ if (g.cwdlen && !stralloc_0(&g.sa)) dienomem() ;
+ if (!stralloc_catb(&g.sa, basevars, sizeof(basevars))) dienomem() ;
+ if (x && !stralloc_catb(&g.sa, sslvars, sizeof(sslvars))) dienomem() ;
+ x = getenv(basevars) ;
+ if (!x) strerr_dienotset(100, "PROTO") ;
+ {
+ size_t protolen = strlen(x) ;
+ size_t m ;
+ ip46 ip ;
+ uint16_t port ;
+ char fmt[IP46_FMT] ;
+ char var[protolen + 11] ;
+ memcpy(var, x, protolen) ;
+
+ memcpy(var + protolen, "LOCALIP", 8) ;
+ x = getenv(var) ;
+ if (!x) strerr_dienotset(100, var) ;
+ if (!ip46_scan(x, &ip)) strerr_dieinvalid(100, var) ;
+ if (!stralloc_catb(&g.sa, var, protolen + 8)
+ || !stralloc_catb(&g.sa, "SERVER_ADDR=", 12)) dienomem() ;
+ g.localip = g.sa.len ;
+ m = ip46_fmt(fmt, &ip) ; fmt[m++] = 0 ;
+ if (!stralloc_catb(&g.sa, fmt, m)) dienomem() ;
+
+ memcpy(var + protolen, "LOCALHOST", 10) ;
+ x = getenv(var) ;
+ if (!x) strerr_dienotset(100, var) ;
+ if (!stralloc_catb(&g.sa, var, protolen + 10)
+ || !stralloc_catb(&g.sa, "SERVER_NAME=", 12)) dienomem() ;
+ g.localhost = g.sa.len ;
+ if (!stralloc_cats(&g.sa, x) || !stralloc_0(&g.sa)) dienomem() ;
+
+ memcpy(var + protolen, "LOCALPORT", 10) ;
+ x = getenv(var) ;
+ if (!x) strerr_dienotset(100, var) ;
+ if (!uint160_scan(x, &port)) strerr_dieinvalid(100, var) ;
+ if (!stralloc_catb(&g.sa, var, protolen + 10)
+ || !stralloc_catb(&g.sa, "SERVER_PORT=", 12)) dienomem() ;
+ g.localport = g.sa.len ;
+ g.localportlen = uint16_fmt(fmt, port) ; fmt[g.localportlen] = 0 ;
+ if (!stralloc_catb(&g.sa, fmt, g.localportlen + 1)) dienomem() ;
+
+ memcpy(var + protolen, "REMOTEIP", 9) ;
+ x = getenv(var) ;
+ if (!x) strerr_dienotset(100, var) ;
+ if (!ip46_scan(x, &ip)) strerr_dieinvalid(100, var) ;
+ if (!stralloc_catb(&g.sa, var, protolen + 9)
+ || !stralloc_catb(&g.sa, "REMOTE_ADDR=", 12)) dienomem() ;
+ g.remoteip = g.sa.len ;
+ m = ip46_fmt(fmt, &ip) ; fmt[m++] = 0 ;
+ if (!stralloc_catb(&g.sa, fmt, m)) dienomem() ;
+
+ memcpy(var + protolen, "REMOTEHOST", 11) ;
+ x = getenv(var) ;
+ if ((x && !stralloc_catb(&g.sa, var, protolen + 11))
+ || !stralloc_catb(&g.sa, "REMOTE_HOST=", 12)) dienomem() ;
+ g.remotehost = g.sa.len ;
+ if (!stralloc_cats(&g.sa, x ? x : fmt)
+ || !stralloc_0(&g.sa)) dienomem() ;
+
+ memcpy(var + protolen, "REMOTEPORT", 11) ;
+ x = getenv(var) ;
+ if (!x) strerr_dienotset(100, var) ;
+ if (!uint160_scan(x, &port)) strerr_dieinvalid(100, var) ;
+ if (!stralloc_catb(&g.sa, var, protolen + 11)
+ || !stralloc_catb(&g.sa, "REMOTE_PORT=", 12)) dienomem() ;
+ g.remoteport = g.sa.len ;
+ m = uint16_fmt(fmt, port) ; fmt[m++] = 0 ;
+ if (!stralloc_catb(&g.sa, fmt, m)) dienomem() ;
+
+ memcpy(var + protolen, "REMOTEINFO", 11) ;
+ x = getenv(var) ;
+ if (x)
+ if (!stralloc_catb(&g.sa, var, protolen + 11)
+ || !stralloc_catb(&g.sa, "REMOTE_IDENT=", 13)
+ || !stralloc_cats(&g.sa, x) || !stralloc_0(&g.sa)) dienomem() ;
+ }
+}
+
+static uint32_t get_uint32 (char const *key)
+{
+ uint32_t n ;
+ if (!tipidee_conf_get_uint32(&g.conf, key, &n))
+ strerr_diefu2sys(100, "read config value for ", key) ;
+ return n ;
+}
+
+static inline unsigned int indexify (tipidee_rql const *rql, char *s, struct stat *st)
+{
+ size_t len = strlen(s) ;
+ unsigned int i = 0 ;
+ if (s[len - 1] != '/') s[len++] = '/' ;
+ for (; i < g.indexn ; i++)
+ {
+ strcpy(s + len, g.indexnames[i]) ;
+ if (stat(s, st) == 0) break ;
+ switch (errno)
+ {
+ case EACCES : return 403 ;
+ case ENAMETOOLONG : return 414 ;
+ case ENOTDIR : return 404 ;
+ case ENOENT : continue ;
+ default : die500sys(rql, 111, "stat ", s) ;
+ }
+ }
+ if (i >= g.indexn) return 404 ;
+ if (S_ISDIR(st->st_mode)) die500x(rql, 103, "bad document hierarchy: ", s, " is a directory") ;
+ return 0 ;
+}
+
+static inline void get_resattr (tipidee_rql const *rql, char const *res, tipidee_resattr *ra)
+{
+ static stralloc sa = STRALLOC_ZERO ;
+ sa.len = 0 ;
+ if (sarealpath(&sa, res) == -1 || !stralloc_0(&sa)) die500sys(rql, 111, "realpath ", res) ;
+ if (strncmp(sa.s, g.sa.s, g.cwdlen) || sa.s[g.cwdlen] != '/')
+ die500x(rql, 102, "resource ", res, " points outside of the server's root") ;
+
+ {
+ char const *attr = 0 ;
+ size_t len = sa.len - g.cwdlen + 1 ;
+ char key[len + 1] ;
+ key[0] = 'A' ; key[1] = ':' ;
+ memcpy(key + 2, sa.s + 1 + g.cwdlen, sa.len - 1 - g.cwdlen) ;
+ key[len] = '/' ;
+ errno = ENOENT ;
+ while (!attr)
+ {
+ if (errno != ENOENT) die500x(rql, 102, "invalid configuration data for ", key) ;
+ while (len > 2 && key[len] != '/') len-- ;
+ if (len <= 2) break ;
+ key[len--] = 0 ;
+ attr = tipidee_conf_get_string(&g.conf, key) ;
+ key[0] = 'a' ;
+ }
+ if (attr)
+ {
+ if (*attr < '@' || *attr > 'G') die500x(rql, 102, "invalid configuration data for ", key) ;
+ ra->iscgi = *attr & ~'@' & 1 ;
+ if (attr[1]) ra->content_type = attr + 1 ;
+ if (ra->iscgi)
+ {
+ char const *nphprefix ;
+ char *p ;
+ key[0] = 'N' ;
+ p = strchr(key+2, '/') ;
+ if (p) *p = 0 ;
+ nphprefix = tipidee_conf_get_string(&g.conf, key) ;
+ if (nphprefix)
+ {
+ char const *base = strrchr(sa.s + g.cwdlen, '/') ;
+ if (str_start(base + 1, nphprefix)) ra->isnph = 1 ;
+ }
+ }
+ }
+ }
+
+ if (!ra->iscgi && !ra->content_type)
+ {
+ ra->content_type = tipidee_conf_get_content_type(&g.conf, sa.s + g.cwdlen) ;
+ if (!ra->content_type) die500sys(rql, 111, "get content type for ", sa.s + g.cwdlen) ;
+ }
+}
+
+static inline int serve (tipidee_rql *rql, char const *docroot, size_t docrootlen, char *uribuf, tipidee_headers const *hdr, char const *body, size_t bodylen)
+{
+ tipidee_resattr ra = TIPIDEE_RESATTR_ZERO ;
+ size_t pathlen = strlen(rql->uri.path) ;
+ char const *infopath = 0 ;
+ struct stat st ;
+ char fn[docrootlen + pathlen + 2 + g.indexlen] ;
+ memcpy(fn, docroot, docrootlen) ;
+ memcpy(fn + docrootlen, rql->uri.path, pathlen) ;
+ fn[docrootlen + pathlen] = 0 ;
+
+ /* Redirection */
+
+ if (rql->m != TIPIDEE_METHOD_OPTIONS)
+ {
+ tipidee_redirection rd = TIPIDEE_REDIRECTION_ZERO ;
+ int e = tipidee_conf_get_redirection(&g.conf, fn, docrootlen, &rd) ;
+ if (e == -1) die500sys(rql, 111, "get redirection data for ", fn) ;
+ if (e)
+ {
+ respond_30x(rql, &rd) ;
+ return 0 ;
+ }
+ }
+
+ /* Resource in the filesystem */
+
+ if (stat(fn, &st) == -1)
+ {
+ size_t pos = docrootlen + pathlen - 1 ;
+ for (;;)
+ {
+ while (fn[pos] != '/') pos-- ;
+ if (pos <= docrootlen) { respond_404(rql) ; return 0 ; }
+ fn[pos] = 0 ;
+ if (stat(fn, &st) == 0) break ;
+ switch (errno)
+ {
+ case ENOTDIR :
+ case ENOENT : fn[pos--] = '/' ; break ;
+ case EACCES : respond_403(rql) ; return 0 ;
+ case ENAMETOOLONG : respond_414(rql) ; return 0 ;
+ default : die500sys(rql, 111, "stat ", fn) ;
+ }
+ }
+ infopath = fn + pos + 1 ;
+ }
+ if (S_ISDIR(st.st_mode))
+ {
+ if (infopath) { respond_404(rql) ; return 0 ; }
+ switch (indexify(rql, fn, &st))
+ {
+ case 403 : respond_403(rql) ; return 0 ;
+ case 404 : respond_404(rql) ; return 0 ;
+ case 414 : respond_414(rql) ; return 0 ;
+ case 0 : break ;
+ }
+ }
+ LOLDEBUG("serve: %s with %s %s, docroot %s", fn, infopath ? "infopath" : "no", infopath ? infopath : "infopath", docroot) ;
+
+ get_resattr(rql, fn, &ra) ;
+
+ if (!ra.iscgi)
+ {
+ if (infopath) { respond_404(rql) ; return 0 ; }
+ if (rql->m == TIPIDEE_METHOD_POST) exit_405(rql, 0) ;
+ }
+
+ if (rql->m == TIPIDEE_METHOD_OPTIONS)
+ return respond_options(rql, ra.iscgi) ;
+ else if (ra.iscgi)
+ return respond_cgi(rql, fn, docrootlen, infopath, uribuf, hdr, &ra, body, bodylen) ;
+ else
+ return respond_regular(rql, fn, st.st_size, &ra) ;
+}
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+ stralloc bodysa = STRALLOC_ZERO ;
+ char progstr[14 + PID_FMT] = "tipideed: pid " ;
+ progstr[14 + pid_fmt(progstr + 14, getpid())] = 0 ;
+ PROG = progstr ;
+
+ {
+ char const *conffile = "/etc/tipidee.conf.cdb" ;
+ char const *newroot = 0 ;
+ unsigned int h = 0 ;
+ int gotv = 0 ;
+ subgetopt l = SUBGETOPT_ZERO ;
+
+ for (;;)
+ {
+ int opt = subgetopt_r(argc, argv, "v:f:d:RU", &l) ;
+ if (opt == -1) break ;
+ switch (opt)
+ {
+ case 'v' :
+ {
+ unsigned int n ;
+ if (!uint0_scan(l.arg, &n)) dieusage() ;
+ if (n > 7) n = 7 ;
+ g.verbosity = n ;
+ gotv = 1 ;
+ break ;
+ }
+ case 'f' : conffile = l.arg ; break ;
+ case 'd' : newroot = l.arg ; break ;
+ case 'R' : h |= 3 ; break ;
+ case 'U' : h |= 1 ; break ;
+ default : dieusage() ;
+ }
+ }
+ argc -= l.ind ; argv += l.ind ;
+
+ g.envlen = env_len(envp) ;
+ if (!tipidee_conf_init(&g.conf, conffile))
+ strerr_diefu2sys(111, "find configuration in ", conffile) ;
+ if (newroot && chdir(newroot) == -1)
+ strerr_diefu2sys(111, "chdir to ", newroot) ;
+ tipideed_harden(h) ;
+ if (!gotv) g.verbosity = get_uint32("G:verbosity") ;
+ }
+
+ prep_env() ;
+ tain_from_millisecs(&g.readtto, get_uint32("G:read_timeout")) ;
+ tain_from_millisecs(&g.writetto, get_uint32("G:write_timeout")) ;
+ tain_from_millisecs(&g.cgitto, get_uint32("G:cgi_timeout")) ;
+ g.maxrqbody = get_uint32("G:max_request_body_length") ;
+ g.maxcgibody = get_uint32("G:max_cgi_body_length") ;
+ {
+ unsigned int n = tipidee_conf_get_argv(&g.conf, "G:index_file", g.indexnames, 16, &g.indexlen) ;
+ if (!n) strerr_dief3x(100, "bad", " config value for ", "G:index_file") ;
+ g.indexn = n-1 ;
+ }
+
+ if (ndelay_on(0) == -1 || ndelay_on(1) == -1)
+ strerr_diefu1sys(111, "set I/O nonblocking") ;
+ init_splice_pipe() ;
+ if (!sig_catch(SIGCHLD, &sigchld_handler))
+ strerr_diefu1sys(111, "set SIGCHLD handler") ;
+ if (!tain_now_set_stopwatch_g())
+ strerr_diefu1sys(111, "initialize clock") ;
+
+ log_start() ;
+
+
+ /* Main loop */
+
+ while (g.cont)
+ {
+ tain deadline ;
+ tipidee_rql rql = TIPIDEE_RQL_ZERO ;
+ tipidee_headers hdr ;
+ int e ;
+ char const *x ;
+ size_t content_length ;
+ tipidee_transfercoding tcoding = TIPIDEE_TRANSFERCODING_UNKNOWN ;
+ char uribuf[URI_BUFSIZE] ;
+ char hdrbuf[HDR_BUFSIZE] ;
+
+ tain_add_g(&deadline, &g.readtto) ;
+ bodysa.len = 0 ;
+
+ e = tipidee_rql_read_g(buffer_0, uribuf, URI_BUFSIZE, &content_length, &rql, &deadline) ;
+ switch (e)
+ {
+ case -1 : log_and_exit(1) ; /* Timeout, malicious client, or shitty client */
+ case 0 : break ;
+ case 400 : exit_400(&rql, "Syntax error in request line") ;
+ default : strerr_dief2x(101, "can't happen: ", "unknown tipidee_rql_read return code") ;
+ }
+ if (rql.http_major != 1) log_and_exit(1) ;
+ if (rql.http_minor > 2) exit_400(&rql, "Bad HTTP version") ;
+
+ content_length = 0 ;
+ tipidee_headers_init(&hdr, hdrbuf, HDR_BUFSIZE) ;
+ e = tipidee_headers_timed_parse_g(buffer_0, &hdr, &deadline) ;
+ switch (e)
+ {
+ case -1 : log_and_exit(1) ; /* connection issue, client timeout, etc. */
+ case 0 : break ;
+ case 400 : exit_400(&rql, "Syntax error in headers") ;
+ case 408 : exit_408(&rql) ; /* timeout */
+ case 413 : exit_413(&rql, hdr.n >= TIPIDEE_HEADERS_MAX ? "Too many headers" : "Too much header data") ;
+ case 500 : die500x(&rql, 101, "can't happen: ", "avltreen_insert failed") ;
+ default : die500x(&rql, 101, "can't happen: ", "unknown tipidee_headers_parse return code") ;
+ }
+
+ if (rql.http_minor == 0) g.cont = 0 ;
+ else
+ {
+ x = tipidee_headers_search(&hdr, "Connection") ;
+ if (x)
+ {
+ if (strstr(x, "close")) g.cont = 0 ;
+ else if (strstr(x, "keep-alive")) g.cont = 2 ;
+ }
+ }
+
+ x = tipidee_headers_search(&hdr, "Transfer-Encoding") ;
+ if (x)
+ {
+ if (strcmp(x, "chunked")) exit_400(&rql, "unsupported Transfer-Encoding") ;
+ else tcoding = TIPIDEE_TRANSFERCODING_CHUNKED ;
+ }
+ else
+ {
+ x = tipidee_headers_search(&hdr, "Content-Length") ;
+ if (x)
+ {
+ if (!size_scan(x, &content_length)) exit_400(&rql, "Invalid Content-Length") ;
+ else if (content_length) tcoding = TIPIDEE_TRANSFERCODING_FIXED ;
+ else tcoding = TIPIDEE_TRANSFERCODING_NONE ;
+ }
+ else tcoding = TIPIDEE_TRANSFERCODING_NONE ;
+ }
+
+ if (tcoding != TIPIDEE_TRANSFERCODING_NONE && rql.m != TIPIDEE_METHOD_POST)
+ exit_400(&rql, "only POST requests can have an entity body") ;
+
+ switch (rql.m)
+ {
+ case TIPIDEE_METHOD_GET :
+ case TIPIDEE_METHOD_HEAD :
+ case TIPIDEE_METHOD_POST : break ;
+ case TIPIDEE_METHOD_OPTIONS :
+ if (!rql.uri.path) { respond_options(&rql, 1) ; continue ; }
+ break ;
+ case TIPIDEE_METHOD_PUT :
+ case TIPIDEE_METHOD_DELETE : exit_405(&rql, 1) ;
+ case TIPIDEE_METHOD_TRACE : respond_trace(hdrbuf, &rql, &hdr) ; continue ;
+ case TIPIDEE_METHOD_CONNECT : exit_501(&rql, "CONNECT method unsupported") ;
+ case TIPIDEE_METHOD_PRI : exit_501(&rql, "PRI method attempted with HTTP/1.1") ;
+ default : die500x(&rql, 101, "can't happen: unknown HTTP method") ;
+ }
+
+ if (!rql.uri.host)
+ {
+ x = tipidee_headers_search(&hdr, "Host") ;
+ if (x)
+ {
+ char *p = strchr(x, ':') ;
+ if (p)
+ {
+ if (!uint160_scan(p+1, &rql.uri.port)) exit_400(&rql, "Invalid Host header") ;
+ *p = 0 ;
+ }
+ if (!*x || *x == '.') exit_400(&rql, "Invalid Host header") ;
+ rql.uri.host = x ;
+ }
+ else if (!rql.http_minor) rql.uri.host = "@" ;
+ else exit_400(&rql, "Missing Host header") ;
+ }
+
+ {
+ size_t hostlen = strlen(rql.uri.host) ;
+ char docroot[hostlen + g.localportlen + 2] ;
+ if (rql.uri.host[hostlen - 1] == '.') hostlen-- ;
+ memcpy(docroot, rql.uri.host, hostlen) ;
+ docroot[hostlen] = ':' ;
+ memcpy(docroot + hostlen + 1, g.sa.s + g.localport, g.localportlen + 1) ;
+
+ /* All good. Read the body if any */
+
+ switch (tcoding)
+ {
+ case TIPIDEE_TRANSFERCODING_FIXED :
+ {
+ if (content_length > g.maxrqbody) exit_413(&rql, "Request body too large") ;
+ if (!stralloc_ready(&bodysa, content_length)) die500sys(&rql, 111, "stralloc_ready") ;
+ if (buffer_timed_get_g(buffer_0, bodysa.s, content_length, &deadline) < content_length)
+ {
+ if (errno == ETIMEDOUT) exit_408(&rql) ;
+ else exit_400(&rql, "Request body does not match Content-Length") ;
+ }
+ bodysa.len = content_length ;
+ }
+ case TIPIDEE_TRANSFERCODING_CHUNKED :
+ {
+ if (!tipidee_chunked_read_g(buffer_0, &bodysa, g.maxrqbody, &deadline))
+ {
+ if (error_temp(errno)) die500sys(&rql, 111, "decode chunked body") ;
+ else if (errno == EMSGSIZE) exit_413(&rql, "Request body too large") ;
+ else exit_400(&rql, "Invalid chunked body") ;
+ }
+ }
+ default : break ;
+ }
+
+ log_request(&rql) ;
+
+
+ /* And serve the resource. The loop is in case of CGI local-redirection. */
+
+ while (serve(&rql, docroot, hostlen + 1 + g.localportlen, uribuf, &hdr, bodysa.s, bodysa.len)) ;
+ }
+ }
+ log_and_exit(0) ;
+}
diff --git a/src/tipideed/trace.c b/src/tipideed/trace.c
new file mode 100644
index 0000000..4761ea5
--- /dev/null
+++ b/src/tipideed/trace.c
@@ -0,0 +1,67 @@
+/* ISC license. */
+
+#include <string.h>
+
+#include <skalibs/types.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr.h>
+#include <skalibs/tai.h>
+#include <skalibs/unix-timed.h>
+
+#include <tipidee/method.h>
+#include <tipidee/response.h>
+#include "tipideed-internal.h"
+
+int respond_trace (char const *buf, tipidee_rql const *rql, tipidee_headers const *hdr)
+{
+ tain deadline ;
+ size_t cl = 0 ;
+ char fmt[SIZE_FMT] ;
+ tipidee_response_status_line(buffer_1, rql, "200 OK") ;
+ tipidee_response_header_common_put_g(buffer_1, 0) ;
+ buffer_putsnoflush(buffer_1, "Content-Type: message/http\r\nContent-Length: ") ;
+ cl += strlen(tipidee_method_tostr(rql->m)) + 1;
+ if (rql->uri.host) cl += 7 + rql->uri.https + strlen(rql->uri.host) ;
+ cl += strlen(rql->uri.path) + (rql->uri.query ? 1 + strlen(rql->uri.query) : 0) ;
+ cl += 6 + uint_fmt(0, rql->http_major) + 1 + uint_fmt(0, rql->http_minor) + 2 ;
+ for (size_t i = 0 ; i < hdr->n ; i++)
+ cl += strlen(buf + hdr->list[i].left) + 2 + strlen(buf + hdr->list[i].right) + 2 ;
+ cl += 2 ;
+ buffer_putnoflush(buffer_1, fmt, size_fmt(fmt, cl)) ;
+ buffer_putsnoflush(buffer_1, "\r\n\r\n") ;
+
+ buffer_putsnoflush(buffer_1, tipidee_method_tostr(rql->m)) ;
+ buffer_putnoflush(buffer_1, " ", 1) ;
+ if (rql->uri.host)
+ {
+ buffer_putsnoflush(buffer_1, rql->uri.https ? "https://" : "http://") ;
+ buffer_putsnoflush(buffer_1, rql->uri.host) ;
+ }
+ buffer_putsnoflush(buffer_1, rql->uri.path) ;
+ if (rql->uri.query)
+ {
+ buffer_putnoflush(buffer_1, "?", 1) ;
+ buffer_putsnoflush(buffer_1, rql->uri.query) ;
+ }
+ buffer_putsnoflush(buffer_1, " HTTP/") ;
+ buffer_putnoflush(buffer_1, fmt, uint_fmt(fmt, rql->http_major)) ;
+ buffer_putnoflush(buffer_1, ".", 1) ;
+ buffer_putnoflush(buffer_1, fmt, uint_fmt(fmt, rql->http_minor)) ;
+ buffer_putsnoflush(buffer_1, "\r\n") ;
+ for (size_t i = 0 ; i < hdr->n ; i++)
+ {
+ size_t len = strlen(buf + hdr->list[i].left) ;
+ tain_add_g(&deadline, &g.writetto) ;
+ if (buffer_timed_put_g(buffer_1, buf + hdr->list[i].left, len, &deadline) < len) goto err ;
+ if (buffer_timed_put_g(buffer_1, ": ", 2, &deadline) < 2) goto err ;
+ len = strlen(buf + hdr->list[i].right) ;
+ if (buffer_timed_put_g(buffer_1, buf + hdr->list[i].right, len, &deadline) < len) goto err ;
+ if (buffer_timed_put_g(buffer_1, "\r\n", 2, &deadline) < 2) goto err ;
+ }
+ if (buffer_timed_put_g(buffer_1, "\r\n", 2, &deadline) < 2
+ || !buffer_timed_flush_g(buffer_1, &deadline)) goto err ;
+ return 0 ;
+
+ err:
+ strerr_diefu1sys(111, "write to stdout") ;
+}