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