upstream nginx-0.7.39
[nginx.git] / nginx / src / http / modules / ngx_http_headers_filter_module.c
1
2 /*
3  * Copyright (C) Igor Sysoev
4  */
5
6
7 #include <ngx_config.h>
8 #include <ngx_core.h>
9 #include <ngx_http.h>
10
11
12 typedef struct ngx_http_header_val_s  ngx_http_header_val_t;
13
14 typedef ngx_int_t (*ngx_http_set_header_pt)(ngx_http_request_t *r,
15     ngx_http_header_val_t *hv, ngx_str_t *value);
16
17
18 typedef struct {
19     ngx_str_t                name;
20     ngx_uint_t               offset;
21     ngx_http_set_header_pt   handler;
22 } ngx_http_set_header_t;
23
24
25 struct ngx_http_header_val_s {
26     ngx_table_elt_t          value;
27     ngx_uint_t               offset;
28     ngx_http_set_header_pt   handler;
29     ngx_array_t             *lengths;
30     ngx_array_t             *values;
31 };
32
33
34 #define NGX_HTTP_EXPIRES_OFF       0
35 #define NGX_HTTP_EXPIRES_EPOCH     1
36 #define NGX_HTTP_EXPIRES_MAX       2
37 #define NGX_HTTP_EXPIRES_ACCESS    3
38 #define NGX_HTTP_EXPIRES_MODIFIED  4
39 #define NGX_HTTP_EXPIRES_DAILY     5
40
41
42 typedef struct {
43     ngx_uint_t               expires;
44     time_t                   expires_time;
45     ngx_array_t             *headers;
46 } ngx_http_headers_conf_t;
47
48
49 static ngx_int_t ngx_http_set_expires(ngx_http_request_t *r,
50     ngx_http_headers_conf_t *conf);
51 static ngx_int_t ngx_http_add_cache_control(ngx_http_request_t *r,
52     ngx_http_header_val_t *hv, ngx_str_t *value);
53 static ngx_int_t ngx_http_set_last_modified(ngx_http_request_t *r,
54     ngx_http_header_val_t *hv, ngx_str_t *value);
55
56 static void *ngx_http_headers_create_conf(ngx_conf_t *cf);
57 static char *ngx_http_headers_merge_conf(ngx_conf_t *cf,
58     void *parent, void *child);
59 static ngx_int_t ngx_http_headers_filter_init(ngx_conf_t *cf);
60 static char *ngx_http_headers_expires(ngx_conf_t *cf, ngx_command_t *cmd,
61     void *conf);
62 static char *ngx_http_headers_add(ngx_conf_t *cf, ngx_command_t *cmd,
63     void *conf);
64
65
66 static ngx_http_set_header_t  ngx_http_set_headers[] = {
67
68     { ngx_string("Cache-Control"), 0, ngx_http_add_cache_control },
69
70     { ngx_string("Last-Modified"),
71                  offsetof(ngx_http_headers_out_t, last_modified),
72                  ngx_http_set_last_modified },
73
74     { ngx_null_string, 0, NULL }
75 };
76
77
78 static ngx_command_t  ngx_http_headers_filter_commands[] = {
79
80     { ngx_string("expires"),
81       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
82                         |NGX_CONF_TAKE12,
83       ngx_http_headers_expires,
84       NGX_HTTP_LOC_CONF_OFFSET,
85       0,
86       NULL},
87
88     { ngx_string("add_header"),
89       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
90                         |NGX_CONF_TAKE2,
91       ngx_http_headers_add,
92       NGX_HTTP_LOC_CONF_OFFSET,
93       0,
94       NULL},
95
96       ngx_null_command
97 };
98
99
100 static ngx_http_module_t  ngx_http_headers_filter_module_ctx = {
101     NULL,                                  /* preconfiguration */
102     ngx_http_headers_filter_init,          /* postconfiguration */
103
104     NULL,                                  /* create main configuration */
105     NULL,                                  /* init main configuration */
106
107     NULL,                                  /* create server configuration */
108     NULL,                                  /* merge server configuration */
109
110     ngx_http_headers_create_conf,          /* create location configuration */
111     ngx_http_headers_merge_conf            /* merge location configuration */
112 };
113
114
115 ngx_module_t  ngx_http_headers_filter_module = {
116     NGX_MODULE_V1,
117     &ngx_http_headers_filter_module_ctx,   /* module context */
118     ngx_http_headers_filter_commands,      /* module directives */
119     NGX_HTTP_MODULE,                       /* module type */
120     NULL,                                  /* init master */
121     NULL,                                  /* init module */
122     NULL,                                  /* init process */
123     NULL,                                  /* init thread */
124     NULL,                                  /* exit thread */
125     NULL,                                  /* exit process */
126     NULL,                                  /* exit master */
127     NGX_MODULE_V1_PADDING
128 };
129
130
131 static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
132
133
134 static ngx_int_t
135 ngx_http_headers_filter(ngx_http_request_t *r)
136 {
137     ngx_str_t                 value;
138     ngx_uint_t                i;
139     ngx_http_header_val_t    *h;
140     ngx_http_headers_conf_t  *conf;
141
142     conf = ngx_http_get_module_loc_conf(r, ngx_http_headers_filter_module);
143
144     if ((conf->expires == NGX_HTTP_EXPIRES_OFF && conf->headers == NULL)
145         || r != r->main
146         || (r->headers_out.status != NGX_HTTP_OK
147             && r->headers_out.status != NGX_HTTP_NO_CONTENT
148             && r->headers_out.status != NGX_HTTP_MOVED_PERMANENTLY
149             && r->headers_out.status != NGX_HTTP_MOVED_TEMPORARILY
150             && r->headers_out.status != NGX_HTTP_NOT_MODIFIED))
151     {
152         return ngx_http_next_header_filter(r);
153     }
154
155     if (conf->expires != NGX_HTTP_EXPIRES_OFF) {
156         if (ngx_http_set_expires(r, conf) != NGX_OK) {
157             return NGX_ERROR;
158         }
159     }
160
161     if (conf->headers) {
162         h = conf->headers->elts;
163         for (i = 0; i < conf->headers->nelts; i++) {
164
165             if (h[i].lengths == NULL) {
166                 value = h[i].value.value;
167
168             } else {
169                 if (ngx_http_script_run(r, &value, h[i].lengths->elts, 0,
170                                         h[i].values->elts)
171                     == NULL)
172                 {
173                     return NGX_ERROR;
174                 }
175             }
176
177             if (h[i].handler(r, &h[i], &value) != NGX_OK) {
178                 return NGX_ERROR;
179             }
180         }
181     }
182
183     return ngx_http_next_header_filter(r);
184 }
185
186
187 static ngx_int_t
188 ngx_http_set_expires(ngx_http_request_t *r, ngx_http_headers_conf_t *conf)
189 {
190     size_t            len;
191     time_t            now, expires_time, max_age;
192     ngx_uint_t        i;
193     ngx_table_elt_t  *expires, *cc, **ccp;
194
195     expires = r->headers_out.expires;
196
197     if (expires == NULL) {
198
199         expires = ngx_list_push(&r->headers_out.headers);
200         if (expires == NULL) {
201             return NGX_ERROR;
202         }
203
204         r->headers_out.expires = expires;
205
206         expires->hash = 1;
207         expires->key.len = sizeof("Expires") - 1;
208         expires->key.data = (u_char *) "Expires";
209     }
210
211     len = sizeof("Mon, 28 Sep 1970 06:00:00 GMT");
212     expires->value.len = len - 1;
213
214     ccp = r->headers_out.cache_control.elts;
215
216     if (ccp == NULL) {
217
218         if (ngx_array_init(&r->headers_out.cache_control, r->pool,
219                            1, sizeof(ngx_table_elt_t *))
220             != NGX_OK)
221         {
222             return NGX_ERROR;
223         }
224
225         ccp = ngx_array_push(&r->headers_out.cache_control);
226         if (ccp == NULL) {
227             return NGX_ERROR;
228         }
229
230         cc = ngx_list_push(&r->headers_out.headers);
231         if (cc == NULL) {
232             return NGX_ERROR;
233         }
234
235         cc->hash = 1;
236         cc->key.len = sizeof("Cache-Control") - 1;
237         cc->key.data = (u_char *) "Cache-Control";
238
239         *ccp = cc;
240
241     } else {
242         for (i = 1; i < r->headers_out.cache_control.nelts; i++) {
243             ccp[i]->hash = 0;
244         }
245
246         cc = ccp[0];
247     }
248
249     if (conf->expires == NGX_HTTP_EXPIRES_EPOCH) {
250         expires->value.data = (u_char *) "Thu, 01 Jan 1970 00:00:01 GMT";
251
252         cc->value.len = sizeof("no-cache") - 1;
253         cc->value.data = (u_char *) "no-cache";
254
255         return NGX_OK;
256     }
257
258     if (conf->expires == NGX_HTTP_EXPIRES_MAX) {
259         expires->value.data = (u_char *) "Thu, 31 Dec 2037 23:55:55 GMT";
260
261         /* 10 years */
262         cc->value.len = sizeof("max-age=315360000") - 1;
263         cc->value.data = (u_char *) "max-age=315360000";
264
265         return NGX_OK;
266     }
267
268     expires->value.data = ngx_pnalloc(r->pool, len);
269     if (expires->value.data == NULL) {
270         return NGX_ERROR;
271     }
272
273     if (conf->expires_time == 0) {
274         ngx_memcpy(expires->value.data, ngx_cached_http_time.data,
275                    ngx_cached_http_time.len + 1);
276
277         cc->value.len = sizeof("max-age=0") - 1;
278         cc->value.data = (u_char *) "max-age=0";
279
280         return NGX_OK;
281     }
282
283     now = ngx_time();
284
285     if (conf->expires == NGX_HTTP_EXPIRES_ACCESS
286         || r->headers_out.last_modified_time == -1)
287     {
288         expires_time = now + conf->expires_time;
289         max_age = conf->expires_time;
290
291     } else if (conf->expires == NGX_HTTP_EXPIRES_DAILY) {
292         expires_time = ngx_next_time(conf->expires_time);
293         max_age = expires_time - now;
294
295     } else {
296         expires_time = r->headers_out.last_modified_time + conf->expires_time;
297         max_age = expires_time - now;
298     }
299
300     ngx_http_time(expires->value.data, expires_time);
301
302     if (conf->expires_time < 0) {
303         cc->value.len = sizeof("no-cache") - 1;
304         cc->value.data = (u_char *) "no-cache";
305
306         return NGX_OK;
307     }
308
309     cc->value.data = ngx_pnalloc(r->pool,
310                                  sizeof("max-age=") + NGX_TIME_T_LEN + 1);
311     if (cc->value.data == NULL) {
312         return NGX_ERROR;
313     }
314
315     cc->value.len = ngx_sprintf(cc->value.data, "max-age=%T", max_age)
316                     - cc->value.data;
317
318     return NGX_OK;
319 }
320
321
322 static ngx_int_t
323 ngx_http_add_header(ngx_http_request_t *r, ngx_http_header_val_t *hv,
324     ngx_str_t *value)
325 {
326     ngx_table_elt_t  *h;
327
328     if (value->len) {
329         h = ngx_list_push(&r->headers_out.headers);
330         if (h == NULL) {
331             return NGX_ERROR;
332         }
333
334         h->hash = hv->value.hash;
335         h->key = hv->value.key;
336         h->value = *value;
337     }
338
339     return NGX_OK;
340 }
341
342
343 static ngx_int_t
344 ngx_http_add_cache_control(ngx_http_request_t *r, ngx_http_header_val_t *hv,
345     ngx_str_t *value)
346 {
347     ngx_table_elt_t  *cc, **ccp;
348
349     ccp = r->headers_out.cache_control.elts;
350
351     if (ccp == NULL) {
352
353         if (ngx_array_init(&r->headers_out.cache_control, r->pool,
354                            1, sizeof(ngx_table_elt_t *))
355             != NGX_OK)
356         {
357             return NGX_ERROR;
358         }
359     }
360
361     ccp = ngx_array_push(&r->headers_out.cache_control);
362     if (ccp == NULL) {
363         return NGX_ERROR;
364     }
365
366     cc = ngx_list_push(&r->headers_out.headers);
367     if (cc == NULL) {
368         return NGX_ERROR;
369     }
370
371     cc->hash = 1;
372     cc->key.len = sizeof("Cache-Control") - 1;
373     cc->key.data = (u_char *) "Cache-Control";
374     cc->value = *value;
375
376     *ccp = cc;
377
378     return NGX_OK;
379 }
380
381
382 static ngx_int_t
383 ngx_http_set_last_modified(ngx_http_request_t *r, ngx_http_header_val_t *hv,
384     ngx_str_t *value)
385 {
386     ngx_table_elt_t  *h, **old;
387
388     if (hv->offset) {
389         old = (ngx_table_elt_t **) ((char *) &r->headers_out + hv->offset);
390
391     } else {
392         old = NULL;
393     }
394
395     r->headers_out.last_modified_time = -1;
396
397     if (old == NULL || *old == NULL) {
398
399         if (value->len == 0) {
400             return NGX_OK;
401         }
402
403         h = ngx_list_push(&r->headers_out.headers);
404         if (h == NULL) {
405             return NGX_ERROR;
406         }
407
408     } else {
409         h = *old;
410
411         if (value->len == 0) {
412             h->hash = 0;
413             return NGX_OK;
414         }
415     }
416
417     h->hash = hv->value.hash;
418     h->key = hv->value.key;
419     h->value = *value;
420
421     return NGX_OK;
422 }
423
424
425 static void *
426 ngx_http_headers_create_conf(ngx_conf_t *cf)
427 {
428     ngx_http_headers_conf_t  *conf;
429
430     conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_headers_conf_t));
431     if (conf == NULL) {
432         return NGX_CONF_ERROR;
433     }
434
435     /*
436      * set by ngx_pcalloc():
437      *
438      *     conf->headers = NULL;
439      *     conf->expires_time = 0;
440      */
441
442     conf->expires = NGX_CONF_UNSET_UINT;
443
444     return conf;
445 }
446
447
448 static char *
449 ngx_http_headers_merge_conf(ngx_conf_t *cf, void *parent, void *child)
450 {
451     ngx_http_headers_conf_t *prev = parent;
452     ngx_http_headers_conf_t *conf = child;
453
454     if (conf->expires == NGX_CONF_UNSET_UINT) {
455         conf->expires = prev->expires;
456         conf->expires_time = prev->expires_time;
457
458         if (conf->expires == NGX_CONF_UNSET_UINT) {
459             conf->expires = NGX_HTTP_EXPIRES_OFF;
460         }
461     }
462
463     if (conf->headers == NULL) {
464         conf->headers = prev->headers;
465     }
466
467     return NGX_CONF_OK;
468 }
469
470
471 static ngx_int_t
472 ngx_http_headers_filter_init(ngx_conf_t *cf)
473 {
474     ngx_http_next_header_filter = ngx_http_top_header_filter;
475     ngx_http_top_header_filter = ngx_http_headers_filter;
476
477     return NGX_OK;
478 }
479
480
481 static char *
482 ngx_http_headers_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
483 {
484     ngx_http_headers_conf_t *hcf = conf;
485
486     ngx_uint_t   minus, n;
487     ngx_str_t   *value;
488
489     if (hcf->expires != NGX_CONF_UNSET_UINT) {
490         return "is duplicate";
491     }
492
493     value = cf->args->elts;
494
495     if (cf->args->nelts == 2) {
496
497         if (ngx_strcmp(value[1].data, "epoch") == 0) {
498             hcf->expires = NGX_HTTP_EXPIRES_EPOCH;
499             return NGX_CONF_OK;
500         }
501
502         if (ngx_strcmp(value[1].data, "max") == 0) {
503             hcf->expires = NGX_HTTP_EXPIRES_MAX;
504             return NGX_CONF_OK;
505         }
506
507         if (ngx_strcmp(value[1].data, "off") == 0) {
508             hcf->expires = NGX_HTTP_EXPIRES_OFF;
509             return NGX_CONF_OK;
510         }
511
512         hcf->expires = NGX_HTTP_EXPIRES_ACCESS;
513
514         n = 1;
515
516     } else { /* cf->args->nelts == 3 */
517
518         if (ngx_strcmp(value[1].data, "modified") != 0) {
519             return "invalid value";
520         }
521
522         hcf->expires = NGX_HTTP_EXPIRES_MODIFIED;
523
524         n = 2;
525     }
526
527     if (value[n].data[0] == '@') {
528         value[n].data++;
529         value[n].len--;
530         minus = 0;
531
532         if (hcf->expires == NGX_HTTP_EXPIRES_MODIFIED) {
533             return "daily time can not be used with \"modified\" parameter";
534         }
535
536         hcf->expires = NGX_HTTP_EXPIRES_DAILY;
537
538     } else if (value[n].data[0] == '+') {
539         value[n].data++;
540         value[n].len--;
541         minus = 0;
542
543     } else if (value[n].data[0] == '-') {
544         value[n].data++;
545         value[n].len--;
546         minus = 1;
547
548     } else {
549         minus = 0;
550     }
551
552     hcf->expires_time = ngx_parse_time(&value[n], 1);
553
554     if (hcf->expires_time == NGX_ERROR) {
555         return "invalid value";
556     }
557
558     if (hcf->expires == NGX_HTTP_EXPIRES_DAILY
559         && hcf->expires_time > 24 * 60 * 60)
560     {
561         return "daily time value must be less than 24 hours";
562     }
563
564     if (hcf->expires_time == NGX_PARSE_LARGE_TIME) {
565         return "value must be less than 68 years";
566     }
567
568     if (minus) {
569         hcf->expires_time = - hcf->expires_time;
570     }
571
572     return NGX_CONF_OK;
573 }
574
575
576 static char *
577 ngx_http_headers_add(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
578 {
579     ngx_http_headers_conf_t *hcf = conf;
580
581     ngx_int_t                   n;
582     ngx_str_t                  *value;
583     ngx_uint_t                  i;
584     ngx_http_header_val_t      *h;
585     ngx_http_set_header_t      *sh;
586     ngx_http_script_compile_t   sc;
587
588     value = cf->args->elts;
589
590     if (hcf->headers == NULL) {
591         hcf->headers = ngx_array_create(cf->pool, 1,
592                                         sizeof(ngx_http_header_val_t));
593         if (hcf->headers == NULL) {
594             return NGX_CONF_ERROR;
595         }
596     }
597
598     h = ngx_array_push(hcf->headers);
599     if (h == NULL) {
600         return NGX_CONF_ERROR;
601     }
602
603     h->value.hash = 1;
604     h->value.key = value[1];
605     h->value.value = value[2];
606     h->offset = 0;
607     h->handler = ngx_http_add_header;
608     h->lengths = NULL;
609     h->values = NULL;
610
611     sh = ngx_http_set_headers;
612     for (i = 0; sh[i].name.len; i++) {
613         if (ngx_strcasecmp(value[1].data, sh[i].name.data) != 0) {
614             continue;
615         }
616
617         h->offset = sh[i].offset;
618         h->handler = sh[i].handler;
619         break;
620     }
621
622     n = ngx_http_script_variables_count(&value[2]);
623
624     if (n == 0) {
625         return NGX_CONF_OK;
626     }
627
628     ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
629
630     sc.cf = cf;
631     sc.source = &value[2];
632     sc.lengths = &h->lengths;
633     sc.values = &h->values;
634     sc.variables = n;
635     sc.complete_lengths = 1;
636     sc.complete_values = 1;
637
638     if (ngx_http_script_compile(&sc) != NGX_OK) {
639         return NGX_CONF_ERROR;
640     }
641
642     return NGX_CONF_OK;
643 }