]> granicus.if.org Git - apache/blob - modules/metadata/mod_headers.c
Move the POSIX reg* implementations into the ap_* namespace;
[apache] / modules / metadata / mod_headers.c
1 /* Copyright 1999-2005 The Apache Software Foundation or its licensors, as
2  * applicable.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * mod_headers.c: Add/append/remove HTTP response headers
19  *     Written by Paul Sutton, paul@ukweb.com, 1 Oct 1996
20  *
21  * The Header directive can be used to add/replace/remove HTTP headers
22  * within the response message.  The RequestHeader directive can be used
23  * to add/replace/remove HTTP headers before a request message is processed.
24  * Valid in both per-server and per-dir configurations.
25  *
26  * Syntax is:
27  *
28  *   Header action header value
29  *   RequestHeader action header value
30  *
31  * Where action is one of:
32  *     set    - set this header, replacing any old value
33  *     add    - add this header, possible resulting in two or more
34  *              headers with the same name
35  *     append - append this text onto any existing header of this same
36  *     unset  - remove this header
37  *
38  * Where action is unset, the third argument (value) should not be given.
39  * The header name can include the colon, or not.
40  *
41  * The Header and RequestHeader directives can only be used where allowed
42  * by the FileInfo override.
43  *
44  * When the request is processed, the header directives are processed in
45  * this order: firstly, the main server, then the virtual server handling
46  * this request (if any), then any <Directory> sections (working downwards 
47  * from the root dir), then an <Location> sections (working down from 
48  * shortest URL component), the any <File> sections. This order is
49  * important if any 'set' or 'unset' actions are used. For example,
50  * the following two directives have different effect if applied in
51  * the reverse order:
52  *
53  *   Header append Author "John P. Doe"
54  *   Header unset Author
55  *
56  * Examples:
57  *
58  *  To set the "Author" header, use
59  *     Header add Author "John P. Doe"
60  *
61  *  To remove a header:
62  *     Header unset Author
63  *
64  */
65
66 #include "apr.h"
67 #include "apr_lib.h"
68 #include "apr_strings.h"
69 #include "apr_buckets.h"
70
71 #include "apr_hash.h"
72 #define APR_WANT_STRFUNC
73 #include "apr_want.h"
74
75 #include "httpd.h"
76 #include "http_config.h"
77 #include "http_request.h"
78 #include "http_log.h"
79 #include "util_filter.h"
80 #include "http_protocol.h"
81
82 #include "mod_ssl.h" /* for the ssl_var_lookup optional function defn */
83
84 /* format_tag_hash is initialized during pre-config */
85 static apr_hash_t *format_tag_hash;
86
87 typedef enum {
88     hdr_add = 'a',              /* add header (could mean multiple hdrs) */
89     hdr_set = 's',              /* set (replace old value) */
90     hdr_append = 'm',           /* append (merge into any old value) */
91     hdr_unset = 'u',            /* unset header */
92     hdr_echo = 'e'              /* echo headers from request to response */
93 } hdr_actions;
94
95 /*
96  * magic cmd->info values
97  */
98 static char hdr_in  = '0';  /* RequestHeader */
99 static char hdr_out = '1';  /* Header onsuccess */
100 static char hdr_err = '2';  /* Header always */
101
102 /*
103  * There is an array of struct format_tag per Header/RequestHeader 
104  * config directive
105  */
106 typedef struct {
107     const char* (*func)(request_rec *r,char *arg);
108     char *arg;
109 } format_tag;
110
111 /* 'Magic' condition_var value to run action in post_read_request */
112 static const char* condition_early = "early";
113 /*
114  * There is one "header_entry" per Header/RequestHeader config directive
115  */
116 typedef struct {
117     hdr_actions action;
118     const char *header;
119     apr_array_header_t *ta;   /* Array of format_tag structs */
120     ap_regex_t *regex;
121     const char *condition_var;
122 } header_entry;
123
124 /* echo_do is used for Header echo to iterate through the request headers*/
125 typedef struct {
126     request_rec *r;
127     header_entry *hdr;
128 } echo_do;
129
130 /*
131  * headers_conf is our per-module configuration. This is used as both
132  * a per-dir and per-server config
133  */
134 typedef struct {
135     apr_array_header_t *fixup_in;
136     apr_array_header_t *fixup_out;
137     apr_array_header_t *fixup_err;
138 } headers_conf;
139
140 module AP_MODULE_DECLARE_DATA headers_module;
141
142 /* Pointer to ssl_var_lookup, if available. */
143 static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *header_ssl_lookup = NULL;
144
145 /*
146  * Tag formatting functions
147  */
148 static const char *constant_item(request_rec *r, char *stuff)
149 {
150     return stuff;
151 }
152 static const char *header_request_duration(request_rec *r, char *a)
153 {
154     return apr_psprintf(r->pool, "D=%" APR_TIME_T_FMT, 
155                         (apr_time_now() - r->request_time)); 
156 }
157 static const char *header_request_time(request_rec *r, char *a)
158 {
159     return apr_psprintf(r->pool, "t=%" APR_TIME_T_FMT, r->request_time);
160 }
161
162 /* unwrap_header returns HDR with any newlines converted into
163  * whitespace if necessary. */
164 static const char *unwrap_header(apr_pool_t *p, const char *hdr)
165 {
166     if (ap_strchr_c(hdr, APR_ASCII_LF) || ap_strchr_c(hdr, APR_ASCII_CR)) {
167         char *ptr;
168         
169         hdr = ptr = apr_pstrdup(p, hdr);
170         
171         do {
172             if (*ptr == APR_ASCII_LF || *ptr == APR_ASCII_CR)
173                 *ptr = APR_ASCII_BLANK;
174         } while (*ptr++);
175     }
176     return hdr;
177 }
178
179 static const char *header_request_env_var(request_rec *r, char *a)
180 {
181     const char *s = apr_table_get(r->subprocess_env,a);
182
183     if (s)
184         return unwrap_header(r->pool, s);
185     else
186         return "(null)";
187 }
188
189 static const char *header_request_ssl_var(request_rec *r, char *name)
190 {
191     if (header_ssl_lookup) {
192         const char *val = header_ssl_lookup(r->pool, r->server, 
193                                             r->connection, r, name);
194         if (val && val[0])
195             return unwrap_header(r->pool, val);
196         else
197             return "(null)";
198     }
199     else {
200         return "(null)";
201     }
202 }
203
204 /*
205  * Config routines
206  */
207
208 static void *create_headers_dir_config(apr_pool_t *p, char *d)
209 {
210     headers_conf *conf = apr_pcalloc(p, sizeof(*conf));
211
212     conf->fixup_in = apr_array_make(p, 2, sizeof(header_entry));
213     conf->fixup_out = apr_array_make(p, 2, sizeof(header_entry));
214     conf->fixup_err = apr_array_make(p, 2, sizeof(header_entry));
215
216     return conf;
217 }
218
219 static void *merge_headers_config(apr_pool_t *p, void *basev, void *overridesv)
220 {
221     headers_conf *newconf = apr_pcalloc(p, sizeof(*newconf));
222     headers_conf *base = basev;
223     headers_conf *overrides = overridesv;
224
225     newconf->fixup_in = apr_array_append(p, base->fixup_in,
226                                          overrides->fixup_in);
227     newconf->fixup_out = apr_array_append(p, base->fixup_out,
228                                           overrides->fixup_out);
229     newconf->fixup_err = apr_array_append(p, base->fixup_err,
230                                           overrides->fixup_err);
231
232     return newconf;
233 }
234  
235 static char *parse_misc_string(apr_pool_t *p, format_tag *tag, const char **sa)
236 {
237     const char *s;
238     char *d;
239
240     tag->func = constant_item;
241
242     s = *sa;
243     while (*s && *s != '%') {
244         s++;
245     }
246     /*
247      * This might allocate a few chars extra if there's a backslash
248      * escape in the format string.
249      */
250     tag->arg = apr_palloc(p, s - *sa + 1);
251
252     d = tag->arg;
253     s = *sa;
254     while (*s && *s != '%') {
255         if (*s != '\\') {
256             *d++ = *s++;
257         }
258         else {
259             s++;
260             switch (*s) {
261             case '\\':
262                 *d++ = '\\';
263                 s++;
264                 break;
265             case 'r':
266                 *d++ = '\r';
267                 s++;
268                 break;
269             case 'n':
270                 *d++ = '\n';
271                 s++;
272                 break;
273             case 't':   
274                 *d++ = '\t';
275                 s++;
276                 break;
277             default:
278                 /* copy verbatim */
279                 *d++ = '\\';
280                 /*
281                  * Allow the loop to deal with this *s in the normal
282                  * fashion so that it handles end of string etc.
283                  * properly.
284                  */
285                 break;
286             }
287         }
288     }
289     *d = '\0';
290
291     *sa = s;
292     return NULL;
293 }
294
295 static char *parse_format_tag(apr_pool_t *p, format_tag *tag, const char **sa)
296
297     const char *s = *sa;
298     const char * (*tag_handler)(request_rec *,char *);
299
300     /* Handle string literal/conditionals */
301     if (*s != '%') {
302         return parse_misc_string(p, tag, sa);
303     }
304     s++; /* skip the % */
305
306     /* Pass through %% as % */
307     if (*s == '%') {
308         tag->func = constant_item;
309         tag->arg = "%";
310         *sa = ++s;
311         return NULL;
312     }
313
314     tag->arg = '\0';
315     /* grab the argument if there is one */
316     if (*s == '{') {
317         ++s;
318         tag->arg = ap_getword(p,&s,'}');
319     }
320
321     tag_handler = (const char * (*)(request_rec *,char *))apr_hash_get(format_tag_hash, s++, 1);
322
323     if (!tag_handler) {
324         char dummy[2];
325         dummy[0] = s[-1];
326         dummy[1] = '\0';
327         return apr_pstrcat(p, "Unrecognized header format %", dummy, NULL);
328     }
329     tag->func = tag_handler;
330
331     *sa = s;
332     return NULL;
333 }
334
335 /*
336  * A format string consists of white space, text and optional format 
337  * tags in any order. E.g., 
338  *
339  * Header add MyHeader "Free form text %D %t more text"
340  *
341  * Decompose the format string into its tags. Each tag (struct format_tag)
342  * contains a pointer to the function used to format the tag. Then save each 
343  * tag in the tag array anchored in the header_entry.
344  */
345 static char *parse_format_string(apr_pool_t *p, header_entry *hdr, const char *s)
346 {
347     char *res;
348
349     /* No string to parse with unset and echo commands */
350     if (hdr->action == hdr_unset ||
351         hdr->action == hdr_echo) {
352         return NULL;
353     }
354
355     hdr->ta = apr_array_make(p, 10, sizeof(format_tag));
356
357     while (*s) {
358         if ((res = parse_format_tag(p, (format_tag *) apr_array_push(hdr->ta), &s))) {
359             return res;
360         }
361     }
362     return NULL;
363 }
364
365 /* handle RequestHeader and Header directive */
366 static APR_INLINE const char *header_inout_cmd(cmd_parms *cmd,
367                                                void *indirconf,
368                                                const char *action,
369                                                const char *hdr,
370                                                const char *value,
371                                                const char* envclause)
372 {
373     headers_conf *dirconf = indirconf;
374     const char *condition_var = NULL;
375     const char *colon;
376     header_entry *new;
377
378     apr_array_header_t *fixup = (cmd->info == &hdr_in)
379         ? dirconf->fixup_in   : (cmd->info == &hdr_err)
380         ? dirconf->fixup_err
381         : dirconf->fixup_out;
382
383     new = (header_entry *) apr_array_push(fixup);
384
385     if (!strcasecmp(action, "set"))
386         new->action = hdr_set;
387     else if (!strcasecmp(action, "add"))
388         new->action = hdr_add;
389     else if (!strcasecmp(action, "append"))
390         new->action = hdr_append;
391     else if (!strcasecmp(action, "unset"))
392         new->action = hdr_unset;
393     else if (!strcasecmp(action, "echo"))
394         new->action = hdr_echo;
395     else
396         return "first argument must be 'add', 'set', 'append', 'unset' or "
397                "'echo'.";
398
399     if (new->action == hdr_unset) {
400         if (value) {
401             if (envclause) {
402                 return "header unset takes two arguments";
403             }
404             envclause = value;
405             value = NULL;
406         }
407     }
408     else if (new->action == hdr_echo) {
409         ap_regex_t *regex;
410
411         if (value) {
412             if (envclause) {
413                 return "Header echo takes two arguments";
414             }
415             envclause = value;
416             value = NULL;
417         }
418         if (cmd->info != &hdr_out && cmd->info != &hdr_err)
419             return "Header echo only valid on Header "
420                    "directives";
421         else {
422             regex = ap_pregcomp(cmd->pool, hdr, AP_REG_EXTENDED | AP_REG_NOSUB);
423             if (regex == NULL) {
424                 return "Header echo regex could not be compiled";
425             }
426         }
427         new->regex = regex;
428     }
429     else if (!value)
430         return "Header requires three arguments";
431
432     /* Handle the envclause on Header */
433     if (envclause != NULL) {
434         if (strcasecmp(envclause, "early") == 0) {
435             condition_var = condition_early;
436         }
437         else {
438             if (strncasecmp(envclause, "env=", 4) != 0) {
439                 return "error: envclause should be in the form env=envar";
440             }
441             if ((envclause[4] == '\0')
442                 || ((envclause[4] == '!') && (envclause[5] == '\0'))) {
443                 return "error: missing environment variable name. "
444                     "envclause should be in the form env=envar ";
445             }
446             condition_var = envclause + 4;
447         }
448     }
449     
450     if ((colon = ap_strchr_c(hdr, ':'))) {
451         hdr = apr_pstrmemdup(cmd->pool, hdr, colon-hdr);
452     }
453
454     new->header = hdr;
455     new->condition_var = condition_var;
456
457     return parse_format_string(cmd->pool, new, value);
458 }
459
460 /* Handle all (xxx)Header directives */
461 static const char *header_cmd(cmd_parms *cmd, void *indirconf,
462                               const char *args)
463 {
464     const char *action;
465     const char *hdr;
466     const char *val;
467     const char *envclause;
468
469     action = ap_getword_conf(cmd->pool, &args);
470     if (cmd->info == &hdr_out) {
471         if (!strcasecmp(action, "always")) {
472             cmd->info = &hdr_err;
473             action = ap_getword_conf(cmd->pool, &args);
474         }
475         else if (!strcasecmp(action, "onsuccess")) {
476             action = ap_getword_conf(cmd->pool, &args);
477         }
478     }
479     hdr = ap_getword_conf(cmd->pool, &args);
480     val = *args ? ap_getword_conf(cmd->pool, &args) : NULL;
481     envclause = *args ? ap_getword_conf(cmd->pool, &args) : NULL;
482
483     if (*args) {
484         return apr_pstrcat(cmd->pool, cmd->cmd->name,
485                            " has too many arguments", NULL);
486     }
487
488     return header_inout_cmd(cmd, indirconf, action, hdr, val, envclause);
489 }
490
491 /*
492  * Process the tags in the format string. Tags may be format specifiers 
493  * (%D, %t, etc.), whitespace or text strings. For each tag, run the handler
494  * (formatter) specific to the tag. Handlers return text strings.
495  * Concatenate the return from each handler into one string that is 
496  * returned from this call.
497  */
498 static char* process_tags(header_entry *hdr, request_rec *r) 
499 {
500     int i;
501     const char *s;
502     char *str = NULL;
503
504     format_tag *tag = (format_tag*) hdr->ta->elts;
505  
506     for (i = 0; i < hdr->ta->nelts; i++) {
507         s = tag[i].func(r, tag[i].arg);
508         if (str == NULL) 
509             str = apr_pstrdup(r->pool, s);
510         else
511             str = apr_pstrcat(r->pool, str, s, NULL);
512     }
513     return str ? str : "";
514 }
515
516 static int echo_header(echo_do *v, const char *key, const char *val)
517 {
518     /* If the input header (key) matches the regex, echo it intact to 
519      * r->headers_out.
520      */
521     if (!ap_regexec(v->hdr->regex, key, 0, NULL, 0)) {
522         apr_table_add(v->r->headers_out, key, val);
523     }
524
525     return 1;
526 }
527
528 static void do_headers_fixup(request_rec *r, apr_table_t *headers,
529                              apr_array_header_t *fixup, int early)
530 {
531     int i;
532
533     for (i = 0; i < fixup->nelts; ++i) {
534         header_entry *hdr = &((header_entry *) (fixup->elts))[i];
535         const char *envar = hdr->condition_var;
536
537         /* ignore early headers in late calls */
538         if (!early && (envar == condition_early)) {
539             continue;
540         }
541         /* ignore late headers in early calls */
542         else if (early && (envar != condition_early)) {
543             continue;
544         }
545         /* Have any conditional envar-controlled Header processing to do? */
546         else if (envar && !early) {
547             if (*envar != '!') {
548                 if (apr_table_get(r->subprocess_env, envar) == NULL)
549                     continue;
550             }
551             else {
552                 if (apr_table_get(r->subprocess_env, &envar[1]) != NULL)
553                     continue;
554             }
555         }
556
557         switch (hdr->action) {
558         case hdr_add:
559             apr_table_addn(headers, hdr->header, process_tags(hdr, r));
560             break;
561         case hdr_append:
562             apr_table_mergen(headers, hdr->header, process_tags(hdr, r));
563             break;
564         case hdr_set:
565             apr_table_setn(headers, hdr->header, process_tags(hdr, r));
566             break;
567         case hdr_unset:
568             apr_table_unset(headers, hdr->header);
569             break;
570         case hdr_echo:
571         {
572             echo_do v;
573             v.r = r;
574             v.hdr = hdr;
575             apr_table_do((int (*) (void *, const char *, const char *)) 
576                          echo_header, (void *) &v, r->headers_in, NULL);
577             break;
578         }
579         }
580     }
581 }
582
583 static void ap_headers_insert_output_filter(request_rec *r)
584 {
585     headers_conf *dirconf = ap_get_module_config(r->per_dir_config,
586                                                  &headers_module);
587
588     if (dirconf->fixup_out->nelts || dirconf->fixup_err->nelts) {
589         ap_add_output_filter("FIXUP_HEADERS_OUT", NULL, r, r->connection);
590     }
591 }
592
593 /*
594  * Make sure our error-path filter is in place.
595  */
596 static void ap_headers_insert_error_filter(request_rec *r)
597 {
598     headers_conf *dirconf = ap_get_module_config(r->per_dir_config,
599                                                  &headers_module);
600
601     if (dirconf->fixup_err->nelts) {
602         ap_add_output_filter("FIXUP_HEADERS_ERR", NULL, r, r->connection);
603     }
604 }
605
606 static apr_status_t ap_headers_output_filter(ap_filter_t *f,
607                                              apr_bucket_brigade *in)
608 {
609     headers_conf *dirconf = ap_get_module_config(f->r->per_dir_config,
610                                                  &headers_module);
611
612     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, f->r->server,
613                  "headers: ap_headers_output_filter()");
614
615     /* do the fixup */
616     do_headers_fixup(f->r, f->r->err_headers_out, dirconf->fixup_err, 0);
617     do_headers_fixup(f->r, f->r->headers_out, dirconf->fixup_out, 0);
618
619     /* remove ourselves from the filter chain */
620     ap_remove_output_filter(f);
621
622     /* send the data up the stack */
623     return ap_pass_brigade(f->next,in);
624 }
625
626 /*
627  * Make sure we propagate any "Header always" settings on the error
628  * path through http_protocol.c.
629  */
630 static apr_status_t ap_headers_error_filter(ap_filter_t *f,
631                                             apr_bucket_brigade *in)
632 {
633     headers_conf *dirconf;
634
635     dirconf = ap_get_module_config(f->r->per_dir_config,
636                                     &headers_module);
637     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, f->r->server,
638                  "headers: ap_headers_error_filter()");
639
640     /*
641      * Add any header fields defined by "Header always" to r->err_headers_out.
642      * Server-wide first, then per-directory to allow overriding.
643      */
644     do_headers_fixup(f->r, f->r->err_headers_out, dirconf->fixup_err, 0);
645
646     /*
647      * We've done our bit; remove ourself from the filter chain so there's
648      * no possibility we'll be called again.
649      */
650     ap_remove_output_filter(f);
651
652     /*
653      * Pass the buck.  (euro?)
654      */
655     return ap_pass_brigade(f->next, in);
656 }
657
658 static apr_status_t ap_headers_fixup(request_rec *r)
659 {
660     headers_conf *dirconf = ap_get_module_config(r->per_dir_config,
661                                                  &headers_module);
662
663     /* do the fixup */
664     if (dirconf->fixup_in->nelts) {
665         do_headers_fixup(r, r->headers_in, dirconf->fixup_in, 0);
666     }
667
668     return DECLINED;
669 }
670 static apr_status_t ap_headers_early(request_rec *r)
671 {
672     headers_conf *dirconf = ap_get_module_config(r->per_dir_config,
673                                                  &headers_module);
674
675     /* do the fixup */
676     if (dirconf->fixup_in->nelts) {
677         do_headers_fixup(r, r->headers_in, dirconf->fixup_in, 1);
678     }
679     if (dirconf->fixup_err->nelts) {
680         do_headers_fixup(r, r->err_headers_out, dirconf->fixup_err, 1);
681     }
682     if (dirconf->fixup_out->nelts) {
683         do_headers_fixup(r, r->headers_out, dirconf->fixup_out, 1);
684     }
685
686     return DECLINED;
687 }
688
689 static const command_rec headers_cmds[] =
690 {
691     AP_INIT_RAW_ARGS("Header", header_cmd, &hdr_out, OR_FILEINFO,
692                      "an optional condition, an action, header and value "
693                      "followed by optional env clause"),
694     AP_INIT_RAW_ARGS("RequestHeader", header_cmd, &hdr_in, OR_FILEINFO,
695                      "an action, header and value followed by optional env "
696                      "clause"),
697     {NULL}
698 };
699
700 static void register_format_tag_handler(const char *tag,
701                                         const void *tag_handler)
702 {
703     apr_hash_set(format_tag_hash, tag, 1, tag_handler);
704 }
705
706 static int header_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp)
707 {
708     format_tag_hash = apr_hash_make(p);
709     register_format_tag_handler("D", (const void *)header_request_duration);
710     register_format_tag_handler("t", (const void *)header_request_time);
711     register_format_tag_handler("e", (const void *)header_request_env_var);
712     register_format_tag_handler("s", (const void *)header_request_ssl_var);
713
714     return OK;
715 }
716
717 static int header_post_config(apr_pool_t *pconf, apr_pool_t *plog,
718                               apr_pool_t *ptemp, server_rec *s)
719 {
720     header_ssl_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);
721     return OK;
722 }
723
724 static void register_hooks(apr_pool_t *p)
725 {
726     ap_register_output_filter("FIXUP_HEADERS_OUT", ap_headers_output_filter,
727                               NULL, AP_FTYPE_CONTENT_SET);
728     ap_register_output_filter("FIXUP_HEADERS_ERR", ap_headers_error_filter,
729                               NULL, AP_FTYPE_CONTENT_SET);
730     ap_hook_pre_config(header_pre_config,NULL,NULL,APR_HOOK_MIDDLE);
731     ap_hook_post_config(header_post_config,NULL,NULL,APR_HOOK_MIDDLE);
732     ap_hook_insert_filter(ap_headers_insert_output_filter, NULL, NULL, APR_HOOK_LAST);
733     ap_hook_insert_error_filter(ap_headers_insert_error_filter,
734                                 NULL, NULL, APR_HOOK_LAST);
735     ap_hook_fixups(ap_headers_fixup, NULL, NULL, APR_HOOK_LAST);
736     ap_hook_post_read_request(ap_headers_early, NULL, NULL, APR_HOOK_FIRST);
737 }
738
739 module AP_MODULE_DECLARE_DATA headers_module =
740 {
741     STANDARD20_MODULE_STUFF,
742     create_headers_dir_config,  /* dir config creater */
743     merge_headers_config,       /* dir merger --- default is to override */
744     NULL,                       /* server config */
745     NULL,                       /* merge server configs */
746     headers_cmds,               /* command apr_table_t */
747     register_hooks              /* register hooks */
748 };