summaryrefslogtreecommitdiff
path: root/src/libtipidee
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2023-08-05 11:51:25 +0000
committerLaurent Bercot <ska@appnovation.com>2023-08-05 11:51:25 +0000
commit17c382d1c9d7236c101418060758d2296cc5e17e (patch)
treefd00e58df0d9d3c70ddd1accfec9e819249c672a /src/libtipidee
downloadtipidee-17c382d1c9d7236c101418060758d2296cc5e17e.tar.xz
Initial commit
Signed-off-by: Laurent Bercot <ska@appnovation.com>
Diffstat (limited to 'src/libtipidee')
-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
24 files changed, 964 insertions, 0 deletions
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 ;
+}