about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--doc/tipidee.conf.html26
-rw-r--r--src/config/headers.c23
-rw-r--r--src/config/lexparse.c93
-rw-r--r--src/libtipidee/tipidee_response_header_preparebuiltin.c14
-rw-r--r--src/libtipidee/tipidee_response_header_writeall.c1
-rw-r--r--src/libtipidee/tipidee_response_header_writemerge.c9
6 files changed, 101 insertions, 65 deletions
diff --git a/doc/tipidee.conf.html b/doc/tipidee.conf.html
index 7ead60f..685b454 100644
--- a/doc/tipidee.conf.html
+++ b/doc/tipidee.conf.html
@@ -448,21 +448,31 @@ serving files with uncommon extensions or have specific needs. </li>
  <tt>custom-header</tt> is global directive, introduced by the
 keyword <tt>custom-header</tt>. It allows
 the user to define custom headers that are to be added to every response.
+It takes a subcommand, that is used to define what should be done with
+the header:
 </p>
 
 <p>
- <code> custom-header <em>option</em> <em>key</em> <em>value</em> </code>
+ <code> custom-header add <em>name</em> <em>value</em> </code>
+ <code> custom-header always <em>name</em> <em>value</em> </code>
+ <code> custom-header remove <em>name</em> </code>
+ <code> custom-header never <em>name</em> </code>
 </p>
 
 <ul>
- <li> <em>option</em> is <tt>weak</tt> or <tt>strong</tt>, and determines what happens when a CGI script
-provides a header with the same name. If <tt>weak</tt>, the CGI value has precedence; if <tt>strong</tt>,
-<em>value</em> has precedence. </li>
- <li> <em>key</em> is the name of the custom header, such as <tt>Content-Security-Policy</tt> or <tt>X-Tacky-Header</tt>.
-It is case-insensitive; its spelling will be canonicalized when processed. </li>
- <li> <em>value</em> is the content of the header. Any whitespace given in <em>value</em> will all register as a single space. </li>
+ <li> <tt>custom-header add</tt> tells tipidee to add a header named <em>name</em>
+with the value <em>value</em> to all its answers. A CGI script can override <em>value</em>
+by providing its own <em>name</em> header. </li>
+ <li> <tt>custom-header always</tt> is like <tt>custom-header add</tt>, except a CGI script
+cannot override <em>value</em>, which will always be used. </li>
+ <li> <tt>custom-header remove</tt> tells tipidee to <em>not</em> provide a <em>name</em>
+header. This is only useful for a few overridable headers that tipidee provides by default,
+such as <tt>Content-Security-Policy</tt> or <tt>Referrer-Policy</tt>. If a CGI script
+provides such a header, the CGI header will be used despite the directive. </li>
+ <li> <tt>custom-header never</tt> is like <tt>custom-header remove</tt>, except even a
+CGI script cannot provide a <em>name</em> header, which will be stripped from its output. </li>
  <li> Some headers cannot be customized. <a href="tipidee-config.html">tipidee-config</a>
-will reject an attempt to define a <tt>Connection</tt> or a <tt>Date</tt> header, for instance. </li>
+will reject an attempt to add, or remove, a <tt>Connection</tt> header, for instance. </li>
 </ul>
 
 <div id="local">
diff --git a/src/config/headers.c b/src/config/headers.c
index 829b898..9ea069f 100644
--- a/src/config/headers.c
+++ b/src/config/headers.c
@@ -68,16 +68,19 @@ node const *headers_search (char const *key)
 void headers_addv (char const *key, uint8_t options, char const *s, size_t const *word, size_t n, size_t filepos, uint32_t line)
 {
   node node ;
-  if (!n) return ;
+  char c = !s << 7 | (options & 1) ;
   node_start(&headers_storage, &node, key, filepos, line) ;
-  node_add(&headers_storage, &node, options & 1 ? "\1" : "", 1) ;
-  node_add(&headers_storage, &node, s + word[0], strlen(s + word[0])) ;
-  for (size_t i = 1 ; i < n ; i++)
+  node_add(&headers_storage, &node, &c, 1) ;
+  if (s)
   {
-    node_add(&headers_storage, &node, " ", 1) ;
-    node_add(&headers_storage, &node, s + word[i], strlen(s + word[i])) ;
+    node_add(&headers_storage, &node, s + word[0], strlen(s + word[0])) ;
+    for (size_t i = 1 ; i < n ; i++)
+    {
+      node_add(&headers_storage, &node, " ", 1) ;
+      node_add(&headers_storage, &node, s + word[i], strlen(s + word[i])) ;
+    }
+    node_add(&headers_storage, &node, "", 1) ;
   }
-  node_add(&headers_storage, &node, "", 1) ;
   repo_add(&headers, &node) ;
 }
 
@@ -87,11 +90,11 @@ static inline void headers_defaults (void)
   {
     node node ;
     struct builtinheaders_s const *p = builtinheaders + i ;
-    if (!p->value) continue ;
+    char c = (!p->value << 7) | p->overridable ;
     if (p->overridable && headers_search(p->key)) continue ;
     node_start(&headers_storage, &node, p->key, 0, 0) ;
-    node_add(&headers_storage, &node, p->overridable ? "\1" : "", 1) ;
-    node_add(&headers_storage, &node, p->value, strlen(p->value) + 1) ;
+    node_add(&headers_storage, &node, &c, 1) ;
+    if (p->value) node_add(&headers_storage, &node, p->value, strlen(p->value) + 1) ;
     repo_add(&headers, &node) ;
   }
 }
diff --git a/src/config/lexparse.c b/src/config/lexparse.c
index 650e8a1..b75ccd6 100644
--- a/src/config/lexparse.c
+++ b/src/config/lexparse.c
@@ -27,13 +27,13 @@ struct mdt_s
 } ;
 #define MDT_ZERO { .filepos = 0, .line = 0, .linefmt = "0" }
 
-struct logkey_s
+struct namevalue_s
 {
   char const *name ;
   uint32_t value ;
 } ;
 
-enum token_e
+enum directivevalue_e
 {
   T_BANG,
   T_GLOBAL,
@@ -54,10 +54,12 @@ enum token_e
   T_CUSTOMRESPONSE
 } ;
 
-struct directive_s
+enum customheaderoptionvalue_e
 {
-  char const *s ;
-  enum token_e token ;
+  CHO_ADD,
+  CHO_ALWAYS,
+  CHO_REMOVE,
+  CHO_NEVER
 } ;
 
 static void conftree_checkunique (char const *key, mdt const *md)
@@ -134,7 +136,7 @@ static inline void parse_indexfile (char const *s, size_t const *word, size_t n,
 
 static inline void parse_log (char const *s, size_t const *word, size_t n, mdt const *md)
 {
-  static struct logkey_s const logkeys[] =
+  static struct namevalue_s const logkeys[] =
   {
     { .name = "answer", .value = TIPIDEE_LOG_ANSWER },
     { .name = "answer_size", .value = TIPIDEE_LOG_SIZE },
@@ -155,7 +157,7 @@ static inline void parse_log (char const *s, size_t const *word, size_t n, mdt c
     strerr_dief8x(1, "too ", "few", " arguments to directive ", "log", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
   for (size_t i = 0 ; i < n ; i++)
   {
-    struct logkey_s const *l = BSEARCH(struct logkey_s, s + word[i], logkeys) ;
+    struct namevalue_s const *l = BSEARCH(struct namevalue_s, s + word[i], logkeys) ;
     if (!l) strerr_dief6x(1, "unrecognized log setting ", s + word[i], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
     if (l->value) v |= l->value ;
     else if (n == 1) v = 0 ;
@@ -191,12 +193,20 @@ static inline void parse_contenttype (char const *s, size_t const *word, size_t
 
 static inline void parse_customheader (char const *s, size_t const *word, size_t n, mdt const *md)
 {
-  uint8_t options = 0 ;
-  if (n < 3)
+  static struct namevalue_s const choptions[] =
+  {
+    { .name = "add", .value = CHO_ADD },
+    { .name = "always", .value = CHO_ALWAYS },
+    { .name = "never", .value = CHO_NEVER },
+    { .name = "remove", .value = CHO_REMOVE },
+  } ;
+  struct namevalue_s const *p ;
+  if (n < 2)
     strerr_dief8x(1, "too ", "few", " arguments to directive ", "custom-header", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
-  if (!strcmp(s + word[0], "weak")) options |= 1 ;
-  else if (!strcmp(s + word[0], "strong")) options &= ~1 ;
-  else strerr_dief7x(1, "type should be weak or strong for", " directive ", "custom-header", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+  p = BSEARCH(struct namevalue_s, s + *word, choptions) ;
+  if (!p) strerr_dief7x(1, "type should be weak or strong for", " directive ", "custom-header", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+  if ((p->value == CHO_ADD || p->value == CHO_ALWAYS) == (n == 2))
+    strerr_dief10x(1, "too ", n == 2 ? "few" : "many", " arguments to directive ", "custom-header", " ", p->name, " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
   word++ ; n-- ;
   {
     size_t len = strlen(s + *word) ;
@@ -206,7 +216,22 @@ static inline void parse_customheader (char const *s, size_t const *word, size_t
     if (!header_allowed(key))
       strerr_dief7x(1, "header ", key, " cannot be customized", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
     headers_checkunique(key, md) ;
-    headers_addv(key, options, s, word, n, md->filepos, md->line) ;
+    switch (p->value)
+    {
+      case CHO_ADD :
+        headers_addv(key, 1, s, word, n, md->filepos, md->line) ;
+        break ;
+      case CHO_ALWAYS :
+        headers_addv(key, 0, s, word, n, md->filepos, md->line) ;
+        break ;
+      case CHO_NEVER :
+        headers_addv(key, 0, 0, 0, 0, md->filepos, md->line) ;
+        break ;
+      case CHO_REMOVE :
+        headers_addv(key, 1, 0, 0, 0, md->filepos, md->line) ;
+        break ;
+      default : strerr_dief1x(101, "can't happen: unknown value in parse_customheader") ;
+    }
   }
 }
 
@@ -369,34 +394,34 @@ static inline void parse_customresponse (char const *s, size_t const *word, size
 
 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[] =
+  static struct namevalue_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 = "custom-header", .token = T_CUSTOMHEADER },
-    { .s = "custom-response", .token = T_CUSTOMRESPONSE },
-    { .s = "domain", .token = T_DOMAIN },
-    { .s = "file-type", .token = T_FILETYPE },
-    { .s = "global", .token = T_GLOBAL },
-    { .s = "index-file", .token = T_INDEXFILE },
-    { .s = "log", .token = T_LOG },
-    { .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 },
+    { .name = "!", .value = T_BANG },
+    { .name = "basic-auth", .value = T_BASICAUTH },
+    { .name = "cgi", .value = T_CGI },
+    { .name = "content-type", .value = T_CONTENTTYPE },
+    { .name = "custom-header", .value = T_CUSTOMHEADER },
+    { .name = "custom-response", .value = T_CUSTOMRESPONSE },
+    { .name = "domain", .value = T_DOMAIN },
+    { .name = "file-type", .value = T_FILETYPE },
+    { .name = "global", .value = T_GLOBAL },
+    { .name = "index-file", .value = T_INDEXFILE },
+    { .name = "log", .value = T_LOG },
+    { .name = "no-auth", .value = T_NOAUTH },
+    { .name = "noncgi", .value = T_NONCGI },
+    { .name = "nonnph", .value = T_NONNPH },
+    { .name = "nph", .value = T_NPH },
+    { .name = "nph-prefix", .value = T_NPHPREFIX },
+    { .name = "redirect", .value = T_REDIRECT },
   } ;
-  struct directive_s const *directive ;
+  struct namevalue_s const *directive ;
   char const *word0 ;
   if (!n--) return ;
   word0 = s + *word++ ;
-  directive = BSEARCH(struct directive_s, word0, directives) ;
+  directive = BSEARCH(struct namevalue_s, word0, directives) ;
   if (!directive)
     strerr_dief6x(1, "unrecognized word ", word0, " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
-  switch (directive->token)
+  switch (directive->value)
   {
     case T_BANG :
     {
diff --git a/src/libtipidee/tipidee_response_header_preparebuiltin.c b/src/libtipidee/tipidee_response_header_preparebuiltin.c
index a04b666..56978c7 100644
--- a/src/libtipidee/tipidee_response_header_preparebuiltin.c
+++ b/src/libtipidee/tipidee_response_header_preparebuiltin.c
@@ -18,11 +18,15 @@ int tipidee_response_header_preparebuiltin (tipidee_response_header *tab, uint32
     if (pos++ >= len) return 0 ;
     tab[i].options = (uint8_t)s[pos] ;
     if (pos++ >= len) return 0 ;
-    tab[i].value = s + pos ;
-    next = memchr(s + pos, 0, len - pos) ;
-    if (!next) return 0 ;
-    pos = next - s ;
-    if (pos++ >= len) return 0 ;
+    if (tab[i].options & 0x80) tab[i].value = 0 ;
+    else
+    {
+      tab[i].value = s + pos ;
+      next = memchr(s + pos, 0, len - pos) ;
+      if (!next) return 0 ;
+      pos = next - s ;
+      if (pos++ >= len) return 0 ;
+    }
   }
   return pos == len ;
 }
diff --git a/src/libtipidee/tipidee_response_header_writeall.c b/src/libtipidee/tipidee_response_header_writeall.c
index a61a80d..b94578d 100644
--- a/src/libtipidee/tipidee_response_header_writeall.c
+++ b/src/libtipidee/tipidee_response_header_writeall.c
@@ -11,6 +11,7 @@ size_t tipidee_response_header_writeall (buffer *b, tipidee_response_header cons
   if (options & 1) m += buffer_putsnoflush(b, "Connection: close\r\n") ;
   for (uint32_t i = 0 ; i < rhdrn ; i++)
   {
+    if (!rhdr[i].value) continue ;
     m += buffer_putsnoflush(b, rhdr[i].key) ;
     m += buffer_putnoflush(b, ": ", 2) ;
     m += buffer_putsnoflush(b, rhdr[i].value) ;
diff --git a/src/libtipidee/tipidee_response_header_writemerge.c b/src/libtipidee/tipidee_response_header_writemerge.c
index b8b1e10..d31aadc 100644
--- a/src/libtipidee/tipidee_response_header_writemerge.c
+++ b/src/libtipidee/tipidee_response_header_writemerge.c
@@ -16,19 +16,13 @@ static int tipidee_response_header_cmp (void const *a, void const *b)
 
 size_t tipidee_response_header_writemerge (buffer *b, tipidee_response_header const *rhdr, uint32_t rhdrn, tipidee_headers const *hdr, uint32_t options, tain const *stamp)
 {
-  static char const *const nope_table[] =
-  {
-    "Connection",
-    "Content-Length",
-    "Date",
-    "Status"
-  } ;
   char fmt[128] ;
   size_t m = buffer_putnoflush(b, fmt, tipidee_response_header_date(fmt, 128, stamp)) ;
   if (options & 1) m += buffer_putsnoflush(b, "Connection: close\r\n") ;
 
   for (uint32_t i = 0 ; i < rhdrn ; i++)
   {
+    if (!rhdr[i].value) continue ;
     if (rhdr[i].options & 1 && tipidee_headers_search(hdr, rhdr[i].key)) continue ;
     m += buffer_putsnoflush(b, rhdr[i].key) ;
     m += buffer_putnoflush(b, ": ", 2) ;
@@ -41,7 +35,6 @@ size_t tipidee_response_header_writemerge (buffer *b, tipidee_response_header co
     tipidee_response_header const *p ;
     char const *key = hdr->buf + hdr->list[i].left ;
     if (!strncasecmp(key, "X-CGI-", 6)) continue ;
-    if (bsearch(key, nope_table, sizeof(nope_table) / sizeof(char const *const), sizeof(char const *const), (int (*)(void const *, void const *))&strcasecmp)) continue ;
     p = bsearch(key, rhdr, rhdrn, sizeof(tipidee_response_header), &tipidee_response_header_cmp) ;
     if (p && !(p->options & 1)) continue ;
     m += buffer_putsnoflush(b, key) ;