083375ba1e73a202ed437f121d66deed0d2f05e5
[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                     if (version > modern[i].version) {
322                         return NGX_HTTP_MODERN_BROWSER;
323                     }
324
325                     ver = 0;
326                     scale /= 100;
327                     continue;
328                 }
329
330                 break;
331             }
332
333             version += ver * scale;
334
335             ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
336                            "version: \"%ui\" \"%ui\"",
337                            modern[i].version, version);
338
339             if (version >= modern[i].version) {
340                 return NGX_HTTP_MODERN_BROWSER;
341             }
342         }
343
344         if (!cf->modern_unlisted_browsers) {
345             return NGX_HTTP_ANCIENT_BROWSER;
346         }
347     }
348
349     if (cf->netscape4) {
350         if (len > sizeof("Mozilla/4.72 ") - 1
351             && ngx_strncmp(ua, "Mozilla/", sizeof("Mozilla/") - 1) == 0
352             && ua[8] > '0' && ua[8] < '5')
353         {
354             return NGX_HTTP_ANCIENT_BROWSER;
355         }
356     }
357
358     if (cf->ancient_browsers) {
359         ancient = cf->ancient_browsers->elts;
360
361         for (i = 0; i < cf->ancient_browsers->nelts; i++) {
362             if (len >= ancient[i].len
363                 && ngx_strstr(ua, ancient[i].data) != NULL)
364             {
365                 return NGX_HTTP_ANCIENT_BROWSER;
366             }
367         }
368     }
369
370     if (cf->modern_unlisted_browsers) {
371         return NGX_HTTP_MODERN_BROWSER;
372     }
373
374     return NGX_HTTP_ANCIENT_BROWSER;
375 }
376
377
378 static ngx_int_t
379 ngx_http_msie_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
380     uintptr_t data)
381 {
382     if (r->headers_in.msie) {
383         *v = ngx_http_variable_true_value;
384         return NGX_OK;
385     }
386
387     *v = ngx_http_variable_null_value;
388     return NGX_OK;
389 }
390
391
392 static ngx_int_t
393 ngx_http_browser_add_variable(ngx_conf_t *cf)
394 {
395     ngx_http_browser_variable_t   *var;
396     ngx_http_variable_t           *v;
397
398     for (var = ngx_http_browsers; var->name.len; var++) {
399
400         v = ngx_http_add_variable(cf, &var->name, NGX_HTTP_VAR_CHANGEABLE);
401         if (v == NULL) {
402             return NGX_ERROR;
403         }
404
405         v->get_handler = var->handler;
406         v->data = var->data;
407     }
408
409     return NGX_OK;
410 }
411
412
413 static void *
414 ngx_http_browser_create_conf(ngx_conf_t *cf)
415 {
416     ngx_http_browser_conf_t  *conf;
417
418     conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_browser_conf_t));
419     if (conf == NULL) {
420         return NGX_CONF_ERROR;
421     }
422
423     /*
424      * set by ngx_pcalloc():
425      *
426      *     conf->modern_browsers = NULL;
427      *     conf->ancient_browsers = NULL;
428      *     conf->modern_browser_value = NULL;
429      *     conf->ancient_browser_value = NULL;
430      *
431      *     conf->modern_unlisted_browsers = 0;
432      *     conf->netscape4 = 0;
433      */
434
435     return conf;
436 }
437
438
439 static char *
440 ngx_http_browser_merge_conf(ngx_conf_t *cf, void *parent, void *child)
441 {
442     ngx_http_browser_conf_t *prev = parent;
443     ngx_http_browser_conf_t *conf = child;
444
445     ngx_uint_t                  i, n;
446     ngx_http_modern_browser_t  *browsers, *opera;
447
448     /*
449      * At the merge the skip field is used to store the browser slot,
450      * it will be used in sorting and then will overwritten
451      * with a real skip value.  The zero value means Opera.
452      */
453
454     if (conf->modern_browsers == NULL) {
455         conf->modern_browsers = prev->modern_browsers;
456
457     } else {
458         browsers = conf->modern_browsers->elts;
459
460         for (i = 0; i < conf->modern_browsers->nelts; i++) {
461             if (browsers[i].skip == 0) {
462                 goto found;
463             }
464         }
465
466         /*
467          * Opera may contain MSIE string, so if Opera was not enumerated
468          * as modern browsers, then add it and set a unreachable version
469          */
470
471         opera = ngx_array_push(conf->modern_browsers);
472         if (opera == NULL) {
473             return NGX_CONF_ERROR;
474         }
475
476         opera->skip = 0;
477         opera->version = 4001000000U;
478
479         browsers = conf->modern_browsers->elts;
480
481 found:
482
483         ngx_qsort(browsers, (size_t) conf->modern_browsers->nelts,
484                   sizeof(ngx_http_modern_browser_t),
485                   ngx_http_modern_browser_sort);
486
487         for (i = 0; i < conf->modern_browsers->nelts; i++) {
488              n = browsers[i].skip;
489
490              browsers[i].skip = ngx_http_modern_browser_masks[n].skip;
491              browsers[i].add = ngx_http_modern_browser_masks[n].add;
492              (void) ngx_cpystrn(browsers[i].name,
493                                 ngx_http_modern_browser_masks[n].name, 12);
494         }
495     }
496
497     if (conf->ancient_browsers == NULL) {
498         conf->ancient_browsers = prev->ancient_browsers;
499     }
500
501     if (conf->modern_browser_value == NULL) {
502         conf->modern_browser_value = prev->modern_browser_value;
503     }
504
505     if (conf->modern_browser_value == NULL) {
506         conf->modern_browser_value = &ngx_http_variable_true_value;
507     }
508
509     if (conf->ancient_browser_value == NULL) {
510         conf->ancient_browser_value = prev->ancient_browser_value;
511     }
512
513     if (conf->ancient_browser_value == NULL) {
514         conf->ancient_browser_value = &ngx_http_variable_true_value;
515     }
516
517     return NGX_CONF_OK;
518 }
519
520
521 static int ngx_libc_cdecl
522 ngx_http_modern_browser_sort(const void *one, const void *two)
523 {
524     ngx_http_modern_browser_t *first = (ngx_http_modern_browser_t *) one;
525     ngx_http_modern_browser_t *second = (ngx_http_modern_browser_t *) two;
526
527     return (first->skip - second->skip);
528 }
529
530
531 static char *
532 ngx_http_modern_browser(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
533 {
534     ngx_http_browser_conf_t *bcf = conf;
535
536     u_char                           c;
537     ngx_str_t                       *value;
538     ngx_uint_t                       i, n, version, ver, scale;
539     ngx_http_modern_browser_t       *browser;
540     ngx_http_modern_browser_mask_t  *mask;
541
542     value = cf->args->elts;
543
544     if (cf->args->nelts == 2) {
545         if (ngx_strcmp(value[1].data, "unlisted") == 0) {
546             bcf->modern_unlisted_browsers = 1;
547             return NGX_CONF_OK;
548         }
549
550         return NGX_CONF_ERROR;
551     }
552
553     if (bcf->modern_browsers == NULL) {
554         bcf->modern_browsers = ngx_array_create(cf->pool, 5,
555                                             sizeof(ngx_http_modern_browser_t));
556         if (bcf->modern_browsers == NULL) {
557             return NGX_CONF_ERROR;
558         }
559     }
560
561     browser = ngx_array_push(bcf->modern_browsers);
562     if (browser == NULL) {
563         return NGX_CONF_ERROR;
564     }
565
566     mask = ngx_http_modern_browser_masks;
567
568     for (n = 0; mask[n].browser[0] != '\0'; n++) {
569         if (ngx_strcasecmp(mask[n].browser, value[1].data) == 0) {
570             goto found;
571         }
572     }
573
574     ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
575                        "unknown browser name \"%V\"", &value[1]);
576
577     return NGX_CONF_ERROR;
578
579 found:
580
581     /*
582      * at this stage the skip field is used to store the browser slot,
583      * it will be used in sorting in merge stage and then will overwritten
584      * with a real value
585      */
586
587     browser->skip = n;
588
589     version = 0;
590     ver = 0;
591     scale = 1000000;
592
593     for (i = 0; i < value[2].len; i++) {
594
595         c = value[2].data[i];
596
597         if (c >= '0' && c <= '9') {
598             ver = ver * 10 + (c - '0');
599             continue;
600         }
601
602         if (c == '.') {
603             version += ver * scale;
604             ver = 0;
605             scale /= 100;
606             continue;
607         }
608
609         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
610                            "invalid browser version \"%V\"", &value[2]);
611
612         return NGX_CONF_ERROR;
613     }
614
615     version += ver * scale;
616
617     browser->version = version;
618
619     return NGX_CONF_OK;
620 }
621
622
623 static char *
624 ngx_http_ancient_browser(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
625 {
626     ngx_http_browser_conf_t *bcf = conf;
627
628     ngx_str_t   *value, *browser;
629     ngx_uint_t   i;
630
631     value = cf->args->elts;
632
633     for (i = 1; i < cf->args->nelts; i++) {
634         if (ngx_strcmp(value[i].data, "netscape4") == 0) {
635             bcf->netscape4 = 1;
636             continue;
637         }
638
639         if (bcf->ancient_browsers == NULL) {
640             bcf->ancient_browsers = ngx_array_create(cf->pool, 4,
641                                                      sizeof(ngx_str_t));
642             if (bcf->ancient_browsers == NULL) {
643                 return NGX_CONF_ERROR;
644             }
645         }
646
647         browser = ngx_array_push(bcf->ancient_browsers);
648         if (browser == NULL) {
649             return NGX_CONF_ERROR;
650         }
651
652         *browser = value[i];
653     }
654
655     return NGX_CONF_OK;
656 }
657
658
659 static char *
660 ngx_http_modern_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
661 {
662     ngx_http_browser_conf_t *bcf = conf;
663
664     ngx_str_t  *value;
665
666     bcf->modern_browser_value = ngx_palloc(cf->pool,
667                                            sizeof(ngx_http_variable_value_t));
668     if (bcf->modern_browser_value == NULL) {
669         return NGX_CONF_ERROR;
670     }
671
672     value = cf->args->elts;
673
674     bcf->modern_browser_value->len = value[1].len;
675     bcf->modern_browser_value->valid = 1;
676     bcf->modern_browser_value->no_cacheable = 0;
677     bcf->modern_browser_value->not_found = 0;
678     bcf->modern_browser_value->data = value[1].data;
679
680     return NGX_CONF_OK;
681 }
682
683
684 static char *
685 ngx_http_ancient_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
686 {
687     ngx_http_browser_conf_t *bcf = conf;
688
689     ngx_str_t  *value;
690
691     bcf->ancient_browser_value = ngx_palloc(cf->pool,
692                                             sizeof(ngx_http_variable_value_t));
693     if (bcf->ancient_browser_value == NULL) {
694         return NGX_CONF_ERROR;
695     }
696
697     value = cf->args->elts;
698
699     bcf->ancient_browser_value->len = value[1].len;
700     bcf->ancient_browser_value->valid = 1;
701     bcf->ancient_browser_value->no_cacheable = 0;
702     bcf->ancient_browser_value->not_found = 0;
703     bcf->ancient_browser_value->data = value[1].data;
704
705     return NGX_CONF_OK;
706 }