about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2023-10-30 11:16:55 +0000
committerLaurent Bercot <ska@appnovation.com>2023-10-30 11:16:55 +0000
commit1c5f682f4dcbca5afa9dd4a9688bde40efaeb12c (patch)
tree9c5ac88f1c430686d45de44ee36f29fe26be42b1 /src
parentad88c5ec68b1cfd47017face422132ab8c7b2874 (diff)
downloadtipidee-1c5f682f4dcbca5afa9dd4a9688bde40efaeb12c.tar.gz
tipidee-1c5f682f4dcbca5afa9dd4a9688bde40efaeb12c.tar.xz
tipidee-1c5f682f4dcbca5afa9dd4a9688bde40efaeb12c.zip
Revamp (and hopefully fix) resattr management
Signed-off-by: Laurent Bercot <ska@appnovation.com>
Diffstat (limited to 'src')
-rw-r--r--src/config/PROTOCOL.txt42
-rw-r--r--src/config/conftree.c5
-rw-r--r--src/config/headers.c13
-rw-r--r--src/config/lexparse.c41
-rw-r--r--src/config/resattr.c56
-rw-r--r--src/include/tipidee/conf.h5
-rw-r--r--src/include/tipidee/log.h10
-rw-r--r--src/include/tipidee/resattr.h23
-rw-r--r--src/include/tipidee/tipidee.h1
-rw-r--r--src/libtipidee/deps-lib/tipidee2
-rw-r--r--src/libtipidee/tipidee_conf_get_resattr.c62
-rw-r--r--src/libtipidee/tipidee_conf_get_resattr1.c36
-rw-r--r--src/libtipidee/tipidee_log_resource.c5
-rw-r--r--src/tipideed/cgi.c5
-rw-r--r--src/tipideed/tipideed.c52
15 files changed, 228 insertions, 130 deletions
diff --git a/src/config/PROTOCOL.txt b/src/config/PROTOCOL.txt
deleted file mode 100644
index f6cc304..0000000
--- a/src/config/PROTOCOL.txt
+++ /dev/null
@@ -1,42 +0,0 @@
-* Globals
-
-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/conftree.c b/src/config/conftree.c
index d4b01ef..a146188 100644
--- a/src/config/conftree.c
+++ b/src/config/conftree.c
@@ -42,11 +42,6 @@ static int confnode_write (uint32_t d, unsigned int h, void *data)
 {
   node *nod = genalloc_s(node, &conftree.ga) + d ;
   (void)h ;
-  if ((conftree.storage->s[nod->key] & ~0x20) == 'A')
-  {
-    conftree.storage->s[++nod->data] |= '@' ;
-    nod->datalen-- ;
-  }
   return cdbmake_add((cdbmaker *)data, conftree.storage->s + nod->key, nod->keylen, conftree.storage->s + nod->data, nod->datalen) ;
 }
 
diff --git a/src/config/headers.c b/src/config/headers.c
index 31d3e69..4fcdf7d 100644
--- a/src/config/headers.c
+++ b/src/config/headers.c
@@ -25,11 +25,8 @@ static struct builtinheaders_s const builtinheaders[] =
   { .key = "Allow", .value = 0, .overridable = 0 },
   { .key = "Cache-Control", .value = "private", .overridable = 1 },
   { .key = "Connection", .value = 0, .overridable = 0 },
-  { .key = "Content-Length", .value = 0, .overridable = 0 },
   { .key = "Content-Security-Policy", .value = "default-src 'self'; style-src 'self' 'unsafe-inline';", .overridable = 1 },
-  { .key = "Content-Type", .value = 0, .overridable = 0 },
   { .key = "Date", .value = 0, .overridable = 0 },
-  { .key = "Location", .value = 0, .overridable = 0 },
   { .key = "Referrer-Policy", .value = "no-referrer-when-downgrade", .overridable = 1 },
   { .key = "Server", .value = "tipidee/" TIPIDEE_VERSION, .overridable = 0 },
   { .key = "Status", .value = 0, .overridable = 0 },
@@ -49,7 +46,15 @@ static repo headers = \
 
 int header_allowed (char const *key)
 {
-  struct builtinheaders_s const *p = BSEARCH(struct builtinheaders_s, key, builtinheaders) ;
+  static char const *const nope[] =
+  {
+    "Content-Length",
+    "Content-Type",
+    "Location"
+  } ;
+  struct builtinheaders_s const *p ;
+  if (BSEARCH(char const *, key, nope)) return 0 ;
+  p = BSEARCH(struct builtinheaders_s, key, builtinheaders) ;
   return !p || p->overridable ;
 }
 
diff --git a/src/config/lexparse.c b/src/config/lexparse.c
index b72182e..afd22a5 100644
--- a/src/config/lexparse.c
+++ b/src/config/lexparse.c
@@ -273,12 +273,11 @@ static inline void parse_redirect (char const *s, size_t const *word, size_t n,
   }
 }
 
-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 void parse_bitattr (char const *s, size_t const *word, size_t n, char const *domain, size_t domainlen, mdt const *md, uint8_t bit, int h)
 {
   static char const *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) ;
+    strerr_dief8x(1, "too ", n > 1 ? "many" : "few", " arguments to directive ", attr[bit][h], " 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] != '/')
@@ -294,24 +293,31 @@ static void parse_bitattr (char const *s, size_t const *word, size_t n, char con
     key[2 + domainlen + arglen] = 0 ;
     oldnode = conftree_search(key) ;
     if (oldnode)
-      if (g.storage.s[oldnode->data] & mask)
+    {
+      uint32_t flags, mask ;
+      uint32_unpack_big(g.storage.s + oldnode->data, &flags) ;
+      uint32_unpack_big(g.storage.s + oldnode->data + 4, &mask) ;
+      if (mask & 1 << bit)
       {
         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 ;
+        strerr_diefn(1, 13, "resource attribute ", attr[bit][h], " redefinition", " in file ", g.storage.s + md->filepos, " line ", md->linefmt, "; previous definition", " in file ", g.storage.s + oldnode->filepos, " line ", fmt, " or later") ;
       }
+      mask |= 1 << bit ;
+      if (h) flags |= 1 << bit ; else flags &= ~(1 << bit) ;
+      uint32_pack_big(g.storage.s + oldnode->data, flags) ;
+      uint32_pack_big(g.storage.s + oldnode->data + 4, mask) ;
+    }
     else
     {
       node node ;
-      char val[3] = { mask, set ? mask : 0, 0 } ;
+      uint32_t flags = h ? 1 << bit : 0 ;
+      uint32_t mask = 1 << bit ;
+      char val[9] = "\0\0\0\0\0\0\0\0" ;
+      uint32_pack_big(val, flags) ;
+      uint32_pack_big(val + 4, mask) ;
       confnode_start(&node, key, md->filepos, md->line) ;
-      confnode_add(&node, val, 3) ;
+      confnode_add(&node, val, 9) ;
       conftree_add(&node) ;
     }
   }
@@ -337,19 +343,17 @@ static inline void parse_filetype (char const *s, size_t const *word, size_t n,
     oldnode = conftree_search(key) ;
     if (oldnode)
     {
-      if (g.storage.s[oldnode->data] & 0x80)
+      if (g.storage.s[oldnode->data + 8])
       {
         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
       {
         node 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, g.storage.s + oldnode->data, 8) ;
         confnode_add(&node, s + word[1], strlen(s + word[1]) + 1) ;
         conftree_update(&node) ;
       }
@@ -357,9 +361,8 @@ static inline void parse_filetype (char const *s, size_t const *word, size_t n,
     else
     {
       node node ;
-      char val[2] = { 0x80, 0x00 } ;
       confnode_start(&node, key, md->filepos, md->line) ;
-      confnode_add(&node, val, 2) ;
+      confnode_add(&node, "\0\0\0\0\0\0\0", 8) ;
       confnode_add(&node, s + word[1], strlen(s + word[1]) + 1) ;
       conftree_add(&node) ;
     }
diff --git a/src/config/resattr.c b/src/config/resattr.c
new file mode 100644
index 0000000..80bd0e0
--- /dev/null
+++ b/src/config/resattr.c
@@ -0,0 +1,56 @@
+/* ISC license. */
+
+#include <skalibs/genalloc.h>
+#include <skalibs/avltree.h>
+#include <skalibs/cdbmake.h>
+
+#include "tipidee-config-internal.h"
+
+static repo resattr = \
+{ \
+  .ga = GENALLOC_ZERO, \
+  .tree = AVLTREE_INIT(8, 3, 8, &node_dtok, &node_cmp, &resattr.ga), \
+  .storage = &g.storage \
+} ;
+
+void confnode_start (node *node, char const *key, size_t filepos, uint32_t line)
+{
+  return node_start(&g.storage, node, key, filepos, line) ;
+}
+
+void confnode_add (node *node, char const *s, size_t len)
+{
+  return node_add(&g.storage, node, s, len) ;
+}
+
+node const *conftree_search (char const *key)
+{
+  return repo_search(&conftree, key) ;
+}
+
+void conftree_add (node const *node)
+{
+  return repo_add(&conftree, node) ;
+}
+
+void conftree_update (node const *node)
+{
+  return repo_update(&conftree, node) ;
+}
+
+static int confnode_write (uint32_t d, unsigned int h, void *data)
+{
+  node *nod = genalloc_s(node, &conftree.ga) + d ;
+  (void)h ;
+  if ((conftree.storage->s[nod->key] & ~0x20) == 'A')
+  {
+    conftree.storage->s[++nod->data] |= '@' ;
+    nod->datalen-- ;
+  }
+  return cdbmake_add((cdbmaker *)data, conftree.storage->s + nod->key, nod->keylen, conftree.storage->s + nod->data, nod->datalen) ;
+}
+
+int conftree_write (cdbmaker *cm)
+{
+  return avltree_iter(&conftree.tree, &confnode_write, cm) ;
+}
diff --git a/src/include/tipidee/conf.h b/src/include/tipidee/conf.h
index 5f3b697..aa8cb45 100644
--- a/src/include/tipidee/conf.h
+++ b/src/include/tipidee/conf.h
@@ -7,8 +7,10 @@
 #include <stdint.h>
 
 #include <skalibs/uint16.h>
+#include <skalibs/uint32.h>
 #include <skalibs/cdb.h>
 
+#include <tipidee/resattr.h>
 #include <tipidee/uri.h>
 
 #define TIPIDEE_CONF_KEY_MAXLEN 0x1000U
@@ -44,4 +46,7 @@ extern int tipidee_conf_get_redirection (tipidee_conf const *, char const *, siz
 extern char const *tipidee_conf_get_content_type (tipidee_conf const *, char const *) ;
 extern char const *tipidee_conf_get_errorfile (tipidee_conf const *, char const *, unsigned int) ;
 
+extern int tipidee_conf_get_resattr1 (tipidee_conf const *, char const *, tipidee_resattr *) ; /* direct key lookup */
+extern int tipidee_conf_get_resattr (tipidee_conf const *, char const *, tipidee_resattr *) ; /* high-level */
+
 #endif
diff --git a/src/include/tipidee/log.h b/src/include/tipidee/log.h
index 0904ca4..93ee386 100644
--- a/src/include/tipidee/log.h
+++ b/src/include/tipidee/log.h
@@ -11,6 +11,7 @@
 
 #include <tipidee/rql.h>
 #include <tipidee/headers.h>
+#include <tipidee/resattr.h>
 
 #define TIPIDEE_LOG_REQUEST 0x0001
 #define TIPIDEE_LOG_REFERRER 0x0002
@@ -26,15 +27,6 @@
 
 #define TIPIDEE_LOG_DEFAULT (TIPIDEE_LOG_REQUEST | TIPIDEE_LOG_ANSWER | TIPIDEE_LOG_SIZE)
 
-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 }
-
 extern void tipidee_log_start (uint32_t, char const *, char const *) ;
 extern void tipidee_log_exit (uint32_t, unsigned int) ;
 
diff --git a/src/include/tipidee/resattr.h b/src/include/tipidee/resattr.h
new file mode 100644
index 0000000..edd0a41
--- /dev/null
+++ b/src/include/tipidee/resattr.h
@@ -0,0 +1,23 @@
+/* ISC license. */
+
+#ifndef TIPIDEE_RESATTR_H
+#define TIPIDEE_RESATTR_H
+
+#include <stdint.h>
+
+#define TIPIDEE_RA_FLAG_CGI 0x0001
+#define TIPIDEE_RA_FLAG_NPH 0x0002
+#define TIPIDEE_RA_FLAG_BA 0x0004
+
+#define TIPIDEE_RA_BITS 3
+
+typedef struct tipidee_resattr_s tipidee_resattr, *tipidee_resattr_ref ;
+struct tipidee_resattr_s
+{
+  uint32_t flags ;
+  uint32_t mask ;
+  char const *content_type ;
+} ;
+#define TIPIDEE_RESATTR_ZERO { .flags = 0, .mask = 0, .content_type = 0 }
+
+#endif
diff --git a/src/include/tipidee/tipidee.h b/src/include/tipidee/tipidee.h
index 7153b7d..7c5154c 100644
--- a/src/include/tipidee/tipidee.h
+++ b/src/include/tipidee/tipidee.h
@@ -8,6 +8,7 @@
 #include <tipidee/headers.h>
 #include <tipidee/log.h>
 #include <tipidee/method.h>
+#include <tipidee/resattr.h>
 #include <tipidee/response.h>
 #include <tipidee/rql.h>
 #include <tipidee/uri.h>
diff --git a/src/libtipidee/deps-lib/tipidee b/src/libtipidee/deps-lib/tipidee
index b20898b..d71ef89 100644
--- a/src/libtipidee/deps-lib/tipidee
+++ b/src/libtipidee/deps-lib/tipidee
@@ -4,6 +4,8 @@ tipidee_conf_get_argv.o
 tipidee_conf_get_content_type.o
 tipidee_conf_get_errorfile.o
 tipidee_conf_get_redirection.o
+tipidee_conf_get_resattr.o
+tipidee_conf_get_resattr1.o
 tipidee_conf_get_responseheaders.o
 tipidee_conf_get_string.o
 tipidee_conf_get_uint32.o
diff --git a/src/libtipidee/tipidee_conf_get_resattr.c b/src/libtipidee/tipidee_conf_get_resattr.c
new file mode 100644
index 0000000..9b33208
--- /dev/null
+++ b/src/libtipidee/tipidee_conf_get_resattr.c
@@ -0,0 +1,62 @@
+/* ISC license. */
+
+#include <string.h>
+
+#include <skalibs/bytestr.h>
+
+#include <tipidee/conf.h>
+#include <tipidee/resattr.h>
+
+#define FULLMASK ((1 << TIPIDEE_RA_BITS) - 1)
+
+int tipidee_conf_get_resattr (tipidee_conf const *conf, char const *res, tipidee_resattr *ra)
+{
+  tipidee_resattr rra = TIPIDEE_RESATTR_ZERO ;
+  size_t len = strlen(res) + 2 ;
+  char key[len + 1] ;
+  key[0] = 'A' ; key[1] = ':' ;
+  memcpy(key + 2, res, len - 2) ;
+  key[len] = '/' ;
+  while (len > 2 && (rra.mask & FULLMASK) != FULLMASK)
+  {
+    tipidee_resattr atom ;
+    int r ;
+    while (len > 2 && key[len] != '/') len-- ;
+    if (len <= 2) break ;
+    key[len--] = 0 ;
+    r = tipidee_conf_get_resattr1(conf, key, &atom) ;
+    if (r == -1) return 0 ;
+    if (r)
+    {
+      rra.flags = (~rra.mask & atom.mask & atom.flags) | ((rra.mask | ~atom.mask) & rra.flags) ;  /* yup */
+      rra.mask |= atom.mask ;
+      if (!rra.content_type) rra.content_type = atom.content_type ;
+    }
+    key[0] = 'a' ;
+  }
+
+  if (!(rra.mask & TIPIDEE_RA_FLAG_NPH))
+  {
+    char const *nphprefix ;
+    char *p = strchr(key+2, '/') ;
+    if (p) *p = 0 ;
+    key[0] = 'N' ;
+    nphprefix = tipidee_conf_get_string(conf, key) ;
+    if (nphprefix)
+    {
+      char const *base = strrchr(res, '/') ;
+      if (base && str_start(base + 1, nphprefix)) rra.flags |= 1 << TIPIDEE_RA_FLAG_NPH ;
+      else rra.flags &= ~(1 << TIPIDEE_RA_FLAG_NPH) ;
+      rra.mask |= 1 << TIPIDEE_RA_FLAG_NPH ;
+    }
+  }
+
+  if (!(rra.flags & TIPIDEE_RA_FLAG_CGI) && !rra.content_type)
+  {
+    rra.content_type = tipidee_conf_get_content_type(conf, res) ;
+    if (!rra.content_type) return 0 ;
+  }
+
+  *ra = rra ;
+  return 1 ;
+}
diff --git a/src/libtipidee/tipidee_conf_get_resattr1.c b/src/libtipidee/tipidee_conf_get_resattr1.c
new file mode 100644
index 0000000..182a7ba
--- /dev/null
+++ b/src/libtipidee/tipidee_conf_get_resattr1.c
@@ -0,0 +1,36 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <errno.h>
+#include <string.h>
+
+#include <skalibs/uint32.h>
+#include <skalibs/cdb.h>
+
+#include <tipidee/resattr.h>
+#include <tipidee/conf.h>
+
+#include <skalibs/posixishard.h>
+
+int tipidee_conf_get_resattr1 (tipidee_conf const *conf, char const *key, tipidee_resattr *ra)
+{
+  tipidee_resattr atom = TIPIDEE_RESATTR_ZERO ;
+  cdb_data data ;
+  if (!tipidee_conf_get(conf, key, &data)) return errno == ENOENT ? 0 : -1 ;
+  if (data.len < 9 || data.s[data.len - 1]) return (errno = EPROTO, -1) ;
+  uint32_unpack_big(data.s, &atom.flags) ;
+  uint32_unpack_big(data.s + 4, &atom.mask) ;
+  data.s += 8 ; data.len -= 8 ;
+  if (*data.s)
+  {
+    size_t len = strlen(data.s) + 1 ;
+    if (len > data.len) return (errno = EPROTO, -1) ;
+    atom.content_type = data.s ;
+    data.s += len ;
+    data.len -= len ;
+  }
+  else data.len-- ;
+  if (data.len) return (errno = EPROTO, -1) ;
+  *ra = atom ;
+  return 1 ;
+}
diff --git a/src/libtipidee/tipidee_log_resource.c b/src/libtipidee/tipidee_log_resource.c
index 75196b0..efdea69 100644
--- a/src/libtipidee/tipidee_log_resource.c
+++ b/src/libtipidee/tipidee_log_resource.c
@@ -4,6 +4,7 @@
 
 #include <skalibs/strerr.h>
 
+#include <tipidee/resattr.h>
 #include <tipidee/log.h>
 
 void tipidee_log_resource (uint32_t v, tipidee_rql const *rql, char const *file, tipidee_resattr const *ra, char const *infopath)
@@ -19,8 +20,8 @@ void tipidee_log_resource (uint32_t v, tipidee_rql const *rql, char const *file,
   a[m++] = " resource " ;
   a[m++] = file ;
   a[m++] = " type " ;
-  a[m++] = ra->iscgi ? ra->isnph ? "nph" : "cgi" : ra->content_type ;
-  if (ra->iscgi && infopath)
+  a[m++] = ra->flags & TIPIDEE_RA_FLAG_CGI ? ra->flags & TIPIDEE_RA_FLAG_NPH ? "nph" : "cgi" : ra->content_type ;
+  if (ra->flags & TIPIDEE_RA_FLAG_CGI && infopath)
   {
     a[m++] = " path_info /" ;
     a[m++] = infopath ;
diff --git a/src/tipideed/cgi.c b/src/tipideed/cgi.c
index 9751911..43fb98d 100644
--- a/src/tipideed/cgi.c
+++ b/src/tipideed/cgi.c
@@ -364,6 +364,7 @@ int respond_cgi (tipidee_rql *rql, char const *docroot, char const *fn, size_t d
   modify_env(rql, docroot, 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, docroot, argv, envp, body, bodylen) :
-                     do_cgi(rql, docroot, argv, envp, body, bodylen, uribuf) ;
+  return ra->flags & TIPIDEE_RA_FLAG_NPH ?
+    do_nph(rql, docroot, argv, envp, body, bodylen) :
+    do_cgi(rql, docroot, argv, envp, body, bodylen, uribuf) ;
 }
diff --git a/src/tipideed/tipideed.c b/src/tipideed/tipideed.c
index da4724b..4df6e8f 100644
--- a/src/tipideed/tipideed.c
+++ b/src/tipideed/tipideed.c
@@ -199,50 +199,8 @@ static inline void get_resattr (tipidee_rql const *rql, char const *docroot, cha
   if (sarealpath(&g.sa, res) == -1 || !stralloc_0(&g.sa)) die500sys(rql, 111, docroot, "realpath ", res) ;
   if (strncmp(g.sa.s + pos, g.sa.s, g.cwdlen) || g.sa.s[pos + g.cwdlen] != '/')
     die500x(rql, 102, docroot, "resource ", res, " points outside of the server's root") ;
-
-  {
-    char const *attr = 0 ;
-    size_t len = g.sa.len - pos - g.cwdlen + 1 ;
-    char key[len + 1] ;
-    key[0] = 'A' ; key[1] = ':' ;
-    memcpy(key + 2, g.sa.s + pos + 1 + g.cwdlen, len - 2) ;
-    key[len] = '/' ;
-    errno = ENOENT ;
-    while (!attr)
-    {
-      if (errno != ENOENT) die500x(rql, 102, docroot, "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, docroot, "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(g.sa.s + pos + 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, g.sa.s + pos + g.cwdlen) ;
-    if (!ra->content_type) die500sys(rql, 111, docroot, "get content type for ", g.sa.s + pos + g.cwdlen) ;
-  }
+  if (!tipidee_conf_get_resattr(&g.conf, g.sa.s + pos + g.cwdlen + 1, ra))
+    die500sys(rql, 102, docroot, "look up resource attributes for ", g.sa.s + pos + g.cwdlen + 1) ;
   g.sa.len = pos ;
 }
 
@@ -321,18 +279,18 @@ static inline int serve (tipidee_rql *rql, char const *docroot, char *uribuf, ti
 
   get_resattr(rql, docroot, fn, &ra) ;
 
-  if (!ra.iscgi)
+  if (!ra.flags & TIPIDEE_RA_FLAG_CGI)
   {
     if (infopath) { respond_404(rql, docroot) ; return 0 ; }
     if (rql->m == TIPIDEE_METHOD_POST) exit_405(rql) ;
   }
 
   if (rql->m == TIPIDEE_METHOD_OPTIONS)
-    return respond_options(rql, 2 | ra.iscgi) ;
+    return respond_options(rql, 2 | !!(ra.flags & TIPIDEE_RA_FLAG_CGI)) ;
 
   tipidee_log_resource(g.logv, rql, fn, &ra, infopath) ;
 
-  if (ra.iscgi)
+  if (ra.flags & TIPIDEE_RA_FLAG_CGI)
     return respond_cgi(rql, docroot, fn, docrootlen, infopath, uribuf, hdr, &ra, body, bodylen) ;
 
   infopath = tipidee_headers_search(hdr, "If-Modified-Since") ;