Annotation of embedaddon/nginx/src/http/modules/ngx_http_browser_module.c, revision 1.1.1.1
1.1 misho 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 >= '0' && c <= '9') {
315: ver = ver * 10 + (c - '0');
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] > '0' && 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 >= '0' && c <= '9') {
607: ver = ver * 10 + (c - '0');
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: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>