upstream nginx-0.7.43
[nginx.git] / nginx / src / http / modules / ngx_http_browser_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 /*
13  * The module can check browser versions conforming to the following formats:
14  * X, X.X, X.X.X, and X.X.X.X.  The maximum values of each format may be
15  * 4000, 4000.99, 4000.99.99, and 4000.99.99.99.
16  */
17
18
19 #define  NGX_HTTP_MODERN_BROWSER   0
20 #define  NGX_HTTP_ANCIENT_BROWSER  1
21
22
23 typedef struct {
24     u_char                      browser[12];
25     size_t                      skip;
26     size_t                      add;
27     u_char                      name[12];
28 } ngx_http_modern_browser_mask_t;
29
30
31 typedef struct {
32     ngx_uint_t                  version;
33     size_t                      skip;
34     size_t                      add;
35     u_char                      name[12];
36 } ngx_http_modern_browser_t;
37
38
39 typedef struct {
40     ngx_str_t                   name;
41     ngx_http_get_variable_pt    handler;
42     uintptr_t                   data;
43 } ngx_http_browser_variable_t;
44
45
46 typedef struct {
47     ngx_array_t                *modern_browsers;
48     ngx_array_t                *ancient_browsers;
49     ngx_http_variable_value_t  *modern_browser_value;
50     ngx_http_variable_value_t  *ancient_browser_value;
51
52     unsigned                    modern_unlisted_browsers:1;
53     unsigned                    netscape4:1;
54 } ngx_http_browser_conf_t;
55
56
57 static ngx_int_t ngx_http_msie_variable(ngx_http_request_t *r,
58     ngx_http_variable_value_t *v, uintptr_t data);
59 static ngx_int_t ngx_http_browser_variable(ngx_http_request_t *r,
60     ngx_http_variable_value_t *v, uintptr_t data);
61
62 static ngx_uint_t ngx_http_browser(ngx_http_request_t *r,
63     ngx_http_browser_conf_t *cf);
64
65 static ngx_int_t ngx_http_browser_add_variable(ngx_conf_t *cf);
66 static void *ngx_http_browser_create_conf(ngx_conf_t *cf);
67 static char *ngx_http_browser_merge_conf(ngx_conf_t *cf, void *parent,
68     void *child);
69 static int ngx_libc_cdecl ngx_http_modern_browser_sort(const void *one,
70     const void *two);
71 static char *ngx_http_modern_browser(ngx_conf_t *cf, ngx_command_t *cmd,
72     void *conf);
73 static char *ngx_http_ancient_browser(ngx_conf_t *cf, ngx_command_t *cmd,
74     void *conf);
75 static char *ngx_http_modern_browser_value(ngx_conf_t *cf, ngx_command_t *cmd,
76     void *conf);
77 static char *ngx_http_ancient_browser_value(ngx_conf_t *cf, ngx_command_t *cmd,
78     void *conf);
79
80
81 static ngx_command_t  ngx_http_browser_commands[] = {
82
83     { ngx_string("modern_browser"),
84       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
85       ngx_http_modern_browser,
86       NGX_HTTP_LOC_CONF_OFFSET,
87       0,
88       NULL },
89
90     { ngx_string("ancient_browser"),
91       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
92       ngx_http_ancient_browser,
93       NGX_HTTP_LOC_CONF_OFFSET,
94       0,
95       NULL },
96
97     { ngx_string("modern_browser_value"),
98       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
99       ngx_http_modern_browser_value,
100       NGX_HTTP_LOC_CONF_OFFSET,
101       0,
102       NULL },
103
104     { ngx_string("ancient_browser_value"),
105       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
106       ngx_http_ancient_browser_value,
107       NGX_HTTP_LOC_CONF_OFFSET,
108       0,
109       NULL },
110
111       ngx_null_command
112 };
113
114
115 static ngx_http_module_t  ngx_http_browser_module_ctx = {
116     ngx_http_browser_add_variable,         /* preconfiguration */
117     NULL,                                  /* postconfiguration */
118
119     NULL,                                  /* create main configuration */
120     NULL,                                  /* init main configuration */
121
122     NULL,                                  /* create server configuration */
123     NULL,                                  /* merge server configuration */
124
125     ngx_http_browser_create_conf,          /* create location configuration */
126     ngx_http_browser_merge_conf            /* merge location configuration */
127 };
128
129
130 ngx_module_t  ngx_http_browser_module = {
131     NGX_MODULE_V1,
132     &ngx_http_browser_module_ctx,          /* module context */
133     ngx_http_browser_commands,             /* module directives */
134     NGX_HTTP_MODULE,                       /* module type */
135     NULL,                                  /* init master */
136     NULL,                                  /* init module */
137     NULL,                                  /* init process */
138     NULL,                                  /* init thread */
139     NULL,                                  /* exit thread */
140     NULL,                                  /* exit process */
141     NULL,                                  /* exit master */
142     NGX_MODULE_V1_PADDING
143 };
144
145
146 static ngx_http_modern_browser_mask_t  ngx_http_modern_browser_masks[] = {
147
148     /* Opera must be the first browser to check */
149
150     /*
151      * "Opera/7.50 (X11; FreeBSD i386; U)  [en]"
152      * "Mozilla/5.0 (X11; FreeBSD i386; U) Opera 7.50  [en]"
153      * "Mozilla/4.0 (compatible; MSIE 6.0; X11; FreeBSD i386) Opera 7.50  [en]"
154      * "Opera/8.0 (Windows NT 5.1; U; ru)"
155      * "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; en) Opera 8.0"
156      * "Opera/9.01 (X11; FreeBSD 6 i386; U; en)"
157      */
158
159     { "opera",
160       0,
161       sizeof("Opera ") - 1,
162       "Opera"},
163
164     /* "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)" */
165
166     { "msie",
167       sizeof("Mozilla/4.0 (compatible; ") - 1,
168       sizeof("MSIE ") - 1,
169       "MSIE "},
170
171     /*
172      * "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.0.0) Gecko/20020610"
173      * "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU; rv:1.5) Gecko/20031006"
174      * "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU; rv:1.6) Gecko/20040206
175      *              Firefox/0.8"
176      * "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU; rv:1.7.8)
177      *              Gecko/20050511 Firefox/1.0.4"
178      * "Mozilla/5.0 (X11; U; FreeBSD i386; en-US; rv:1.8.0.5) Gecko/20060729
179      *              Firefox/1.5.0.5"
180      */
181
182     { "gecko",
183       sizeof("Mozilla/5.0 (") - 1,
184       sizeof("rv:") - 1,
185       "rv:"},
186
187     /*
188      * "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ru-ru) AppleWebKit/125.2
189      *              (KHTML, like Gecko) Safari/125.7"
190      * "Mozilla/5.0 (SymbianOS/9.1; U; en-us) AppleWebKit/413
191      *              (KHTML, like Gecko) Safari/413"
192      * "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418
193      *              (KHTML, like Gecko) Safari/417.9.3"
194      * "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ru-ru) AppleWebKit/418.8
195      *              (KHTML, like Gecko) Safari/419.3"
196      */
197
198     { "safari",
199       sizeof("Mozilla/5.0 (") - 1,
200       sizeof("Safari/") - 1,
201       "Safari/"},
202
203     /*
204      * "Mozilla/5.0 (compatible; Konqueror/3.1; Linux)"
205      * "Mozilla/5.0 (compatible; Konqueror/3.4; Linux) KHTML/3.4.2 (like Gecko)"
206      * "Mozilla/5.0 (compatible; Konqueror/3.5; FreeBSD) KHTML/3.5.1
207      *              (like Gecko)"
208      */
209
210     { "konqueror",
211       sizeof("Mozilla/5.0 (compatible; ") - 1,
212       sizeof("Konqueror/") - 1,
213       "Konqueror/"},
214
215     { "", 0, 0, "" }
216
217 };
218
219
220 static ngx_http_browser_variable_t  ngx_http_browsers[] = {
221     { ngx_string("msie"), ngx_http_msie_variable, 0 },
222     { ngx_string("modern_browser"), ngx_http_browser_variable,
223           NGX_HTTP_MODERN_BROWSER },
224     { ngx_string("ancient_browser"), ngx_http_browser_variable,
225           NGX_HTTP_ANCIENT_BROWSER },
226     { ngx_null_string, NULL, 0 }
227 };
228
229
230 static ngx_int_t
231 ngx_http_browser_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
232     uintptr_t data)
233 {
234     ngx_uint_t                rc;
235     ngx_http_browser_conf_t  *cf;
236
237     cf = ngx_http_get_module_loc_conf(r, ngx_http_browser_module);
238
239     rc = ngx_http_browser(r, cf);
240
241     if (data == NGX_HTTP_MODERN_BROWSER && rc == NGX_HTTP_MODERN_BROWSER) {
242         *v = *cf->modern_browser_value;
243         return NGX_OK;
244     }
245
246     if (data == NGX_HTTP_ANCIENT_BROWSER && rc == NGX_HTTP_ANCIENT_BROWSER) {
247         *v = *cf->ancient_browser_value;
248         return NGX_OK;
249     }
250
251     *v = ngx_http_variable_null_value;
252     return NGX_OK;
253 }
254
255
256 static ngx_uint_t
257 ngx_http_browser(ngx_http_request_t *r, ngx_http_browser_conf_t *cf)
258 {
259     size_t                      len;
260     u_char                     *name, *ua, *last, c;
261     ngx_str_t                  *ancient;
262     ngx_uint_t                  i, version, ver, scale;
263     ngx_http_modern_browser_t  *modern;
264
265     if (r->headers_in.user_agent == NULL) {
266         if (cf->modern_unlisted_browsers) {
267             return NGX_HTTP_MODERN_BROWSER;
268         }
269
270         return NGX_HTTP_ANCIENT_BROWSER;
271     }
272
273     ua = r->headers_in.user_agent->value.data;
274     len = r->headers_in.user_agent->value.len;
275     last = ua + len;
276
277     if (cf->modern_browsers) {
278         modern = cf->modern_browsers->elts;
279
280         for (i = 0; i < cf->modern_browsers->nelts; i++) {
281             name = ua + modern[i].skip;
282
283             if (name >= last) {
284                 continue;
285             }
286
287             name = (u_char *) ngx_strstr(name, modern[i].name);
288
289             if (name == NULL) {
290                 continue;
291             }
292
293             ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
294                            "browser: \"%s\"", name);
295
296             name += modern[i].add;
297
298             if (name >= last) {
299                 continue;
300             }
301
302             ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
303                            "version: \"%ui\" \"%s\"", modern[i].version, name);
304
305             version = 0;
306             ver = 0;
307             scale = 1000000;
308
309             while (name < last) {
310
311                 c = *name++;
312
313                 if (c >= '0' && c <= '9') {
314                     ver = ver * 10 + (c - '0');
315                     continue;
316                 }
317
318                 if (c == '.') {
319                     version += ver * scale;
320
321                     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
322                                    "version: \"%ui\" \"%ui\"",
323                                    modern[i].version, version);
324
325                     if (version > modern[i].version) {
326                         return NGX_HTTP_MODERN_BROWSER;
327                     }
328
329                     ver = 0;
330                     scale /= 100;
331                     continue;
332                 }
333
334                 break;
335             }
336
337             version += ver * scale;
338
339             ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
340                            "version: \"%ui\" \"%ui\"",
341                            modern[i].version, version);
342
343             if (version >= modern[i].version) {
344                 return NGX_HTTP_MODERN_BROWSER;
345             }
346
347             return NGX_HTTP_ANCIENT_BROWSER;
348         }
349
350         if (!cf->modern_unlisted_browsers) {
351             return NGX_HTTP_ANCIENT_BROWSER;
352         }
353     }
354
355     if (cf->netscape4) {
356         if (len > sizeof("Mozilla/4.72 ") - 1
357             && ngx_strncmp(ua, "Mozilla/", sizeof("Mozilla/") - 1) == 0
358             && ua[8] > '0' && ua[8] < '5')
359         {
360             return NGX_HTTP_ANCIENT_BROWSER;
361         }
362     }
363
364     if (cf->ancient_browsers) {
365         ancient = cf->ancient_browsers->elts;
366
367         for (i = 0; i < cf->ancient_browsers->nelts; i++) {
368             if (len >= ancient[i].len
369                 && ngx_strstr(ua, ancient[i].data) != NULL)
370             {
371                 return NGX_HTTP_ANCIENT_BROWSER;
372             }
373         }
374     }
375
376     if (cf->modern_unlisted_browsers) {
377         return NGX_HTTP_MODERN_BROWSER;
378     }
379
380     return NGX_HTTP_ANCIENT_BROWSER;
381 }
382
383
384 static ngx_int_t
385 ngx_http_msie_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
386     uintptr_t data)
387 {
388     if (r->headers_in.msie) {
389         *v = ngx_http_variable_true_value;
390         return NGX_OK;
391     }
392
393     *v = ngx_http_variable_null_value;
394     return NGX_OK;
395 }
396
397
398 static ngx_int_t
399 ngx_http_browser_add_variable(ngx_conf_t *cf)
400 {
401     ngx_http_browser_variable_t   *var;
402     ngx_http_variable_t           *v;
403
404     for (var = ngx_http_browsers; var->name.len; var++) {
405
406         v = ngx_http_add_variable(cf, &var->name, NGX_HTTP_VAR_CHANGEABLE);
407         if (v == NULL) {
408             return NGX_ERROR;
409         }
410
411         v->get_handler = var->handler;
412         v->data = var->data;
413     }
414
415     return NGX_OK;
416 }
417
418
419 static void *
420 ngx_http_browser_create_conf(ngx_conf_t *cf)
421 {
422     ngx_http_browser_conf_t  *conf;
423
424     conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_browser_conf_t));
425     if (conf == NULL) {
426         return NGX_CONF_ERROR;
427     }
428
429     /*
430      * set by ngx_pcalloc():
431      *
432      *     conf->modern_browsers = NULL;
433      *     conf->ancient_browsers = NULL;
434      *     conf->modern_browser_value = NULL;
435      *     conf->ancient_browser_value = NULL;
436      *
437      *     conf->modern_unlisted_browsers = 0;
438      *     conf->netscape4 = 0;
439      */
440
441     return conf;
442 }
443
444
445 static char *
446 ngx_http_browser_merge_conf(ngx_conf_t *cf, void *parent, void *child)
447 {
448     ngx_http_browser_conf_t *prev = parent;
449     ngx_http_browser_conf_t *conf = child;
450
451     ngx_uint_t                  i, n;
452     ngx_http_modern_browser_t  *browsers, *opera;
453
454     /*
455      * At the merge the skip field is used to store the browser slot,
456      * it will be used in sorting and then will overwritten
457      * with a real skip value.  The zero value means Opera.
458      */
459
460     if (conf->modern_browsers == NULL) {
461         conf->modern_browsers = prev->modern_browsers;
462
463     } else {
464         browsers = conf->modern_browsers->elts;
465
466         for (i = 0; i < conf->modern_browsers->nelts; i++) {
467             if (browsers[i].skip == 0) {
468                 goto found;
469             }
470         }
471
472         /*
473          * Opera may contain MSIE string, so if Opera was not enumerated
474          * as modern browsers, then add it and set a unreachable version
475          */
476
477         opera = ngx_array_push(conf->modern_browsers);
478         if (opera == NULL) {
479             return NGX_CONF_ERROR;
480         }
481
482         opera->skip = 0;
483         opera->version = 4001000000U;
484
485         browsers = conf->modern_browsers->elts;
486
487 found:
488
489         ngx_qsort(browsers, (size_t) conf->modern_browsers->nelts,
490                   sizeof(ngx_http_modern_browser_t),
491                   ngx_http_modern_browser_sort);
492
493         for (i = 0; i < conf->modern_browsers->nelts; i++) {
494              n = browsers[i].skip;
495
496              browsers[i].skip = ngx_http_modern_browser_masks[n].skip;
497              browsers[i].add = ngx_http_modern_browser_masks[n].add;
498              (void) ngx_cpystrn(browsers[i].name,
499                                 ngx_http_modern_browser_masks[n].name, 12);
500         }
501     }
502
503     if (conf->ancient_browsers == NULL) {
504         conf->ancient_browsers = prev->ancient_browsers;
505     }
506
507     if (conf->modern_browser_value == NULL) {
508         conf->modern_browser_value = prev->modern_browser_value;
509     }
510
511     if (conf->modern_browser_value == NULL) {
512         conf->modern_browser_value = &ngx_http_variable_true_value;
513     }
514
515     if (conf->ancient_browser_value == NULL) {
516         conf->ancient_browser_value = prev->ancient_browser_value;
517     }
518
519     if (conf->ancient_browser_value == NULL) {
520         conf->ancient_browser_value = &ngx_http_variable_true_value;
521     }
522
523     return NGX_CONF_OK;
524 }
525
526
527 static int ngx_libc_cdecl
528 ngx_http_modern_browser_sort(const void *one, const void *two)
529 {
530     ngx_http_modern_browser_t *first = (ngx_http_modern_browser_t *) one;
531     ngx_http_modern_browser_t *second = (ngx_http_modern_browser_t *) two;
532
533     return (first->skip - second->skip);
534 }
535
536
537 static char *
538 ngx_http_modern_browser(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
539 {
540     ngx_http_browser_conf_t *bcf = conf;
541
542     u_char                           c;
543     ngx_str_t                       *value;
544     ngx_uint_t                       i, n, version, ver, scale;
545     ngx_http_modern_browser_t       *browser;
546     ngx_http_modern_browser_mask_t  *mask;
547
548     value = cf->args->elts;
549
550     if (cf->args->nelts == 2) {
551         if (ngx_strcmp(value[1].data, "unlisted") == 0) {
552             bcf->modern_unlisted_browsers = 1;
553             return NGX_CONF_OK;
554         }
555
556         return NGX_CONF_ERROR;
557     }
558
559     if (bcf->modern_browsers == NULL) {
560         bcf->modern_browsers = ngx_array_create(cf->pool, 5,
561                                             sizeof(ngx_http_modern_browser_t));
562         if (bcf->modern_browsers == NULL) {
563             return NGX_CONF_ERROR;
564         }
565     }
566
567     browser = ngx_array_push(bcf->modern_browsers);
568     if (browser == NULL) {
569         return NGX_CONF_ERROR;
570     }
571
572     mask = ngx_http_modern_browser_masks;
573
574     for (n = 0; mask[n].browser[0] != '\0'; n++) {
575         if (ngx_strcasecmp(mask[n].browser, value[1].data) == 0) {
576             goto found;
577         }
578     }
579
580     ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
581                        "unknown browser name \"%V\"", &value[1]);
582
583     return NGX_CONF_ERROR;
584
585 found:
586
587     /*
588      * at this stage the skip field is used to store the browser slot,
589      * it will be used in sorting in merge stage and then will overwritten
590      * with a real value
591      */
592
593     browser->skip = n;
594
595     version = 0;
596     ver = 0;
597     scale = 1000000;
598
599     for (i = 0; i < value[2].len; i++) {
600
601         c = value[2].data[i];
602
603         if (c >= '0' && c <= '9') {
604             ver = ver * 10 + (c - '0');
605             continue;
606         }
607
608         if (c == '.') {
609             version += ver * scale;
610             ver = 0;
611             scale /= 100;
612             continue;
613         }
614
615         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
616                            "invalid browser version \"%V\"", &value[2]);
617
618         return NGX_CONF_ERROR;
619     }
620
621     version += ver * scale;
622
623     browser->version = version;
624
625     return NGX_CONF_OK;
626 }
627
628
629 static char *
630 ngx_http_ancient_browser(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
631 {
632     ngx_http_browser_conf_t *bcf = conf;
633
634     ngx_str_t   *value, *browser;
635     ngx_uint_t   i;
636
637     value = cf->args->elts;
638
639     for (i = 1; i < cf->args->nelts; i++) {
640         if (ngx_strcmp(value[i].data, "netscape4") == 0) {
641             bcf->netscape4 = 1;
642             continue;
643         }
644
645         if (bcf->ancient_browsers == NULL) {
646             bcf->ancient_browsers = ngx_array_create(cf->pool, 4,
647                                                      sizeof(ngx_str_t));
648             if (bcf->ancient_browsers == NULL) {
649                 return NGX_CONF_ERROR;
650             }
651         }
652
653         browser = ngx_array_push(bcf->ancient_browsers);
654         if (browser == NULL) {
655             return NGX_CONF_ERROR;
656         }
657
658         *browser = value[i];
659     }
660
661     return NGX_CONF_OK;
662 }
663
664
665 static char *
666 ngx_http_modern_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
667 {
668     ngx_http_browser_conf_t *bcf = conf;
669
670     ngx_str_t  *value;
671
672     bcf->modern_browser_value = ngx_palloc(cf->pool,
673                                            sizeof(ngx_http_variable_value_t));
674     if (bcf->modern_browser_value == NULL) {
675         return NGX_CONF_ERROR;
676     }
677
678     value = cf->args->elts;
679
680     bcf->modern_browser_value->len = value[1].len;
681     bcf->modern_browser_value->valid = 1;
682     bcf->modern_browser_value->no_cacheable = 0;
683     bcf->modern_browser_value->not_found = 0;
684     bcf->modern_browser_value->data = value[1].data;
685
686     return NGX_CONF_OK;
687 }
688
689
690 static char *
691 ngx_http_ancient_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
692 {
693     ngx_http_browser_conf_t *bcf = conf;
694
695     ngx_str_t  *value;
696
697     bcf->ancient_browser_value = ngx_palloc(cf->pool,
698                                             sizeof(ngx_http_variable_value_t));
699     if (bcf->ancient_browser_value == NULL) {
700         return NGX_CONF_ERROR;
701     }
702
703     value = cf->args->elts;
704
705     bcf->ancient_browser_value->len = value[1].len;
706     bcf->ancient_browser_value->valid = 1;
707     bcf->ancient_browser_value->no_cacheable = 0;
708     bcf->ancient_browser_value->not_found = 0;
709     bcf->ancient_browser_value->data = value[1].data;
710
711     return NGX_CONF_OK;
712 }