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