Groonga 3.0.9 Source Code Document
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
ngx_http_browser_module.c
Go to the documentation of this file.
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];
30 
31 
32 typedef struct {
34  size_t skip;
35  size_t add;
36  u_char name[12];
38 
39 
40 typedef struct {
43  uintptr_t data;
45 
46 
47 typedef struct {
52 
53  unsigned modern_unlisted_browsers:1;
54  unsigned netscape4:1;
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,
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"),
86  ngx_http_modern_browser,
88  0,
89  NULL },
90 
91  { ngx_string("ancient_browser"),
93  ngx_http_ancient_browser,
95  0,
96  NULL },
97 
98  { ngx_string("modern_browser_value"),
100  ngx_http_modern_browser_value,
102  0,
103  NULL },
104 
105  { ngx_string("ancient_browser_value"),
107  ngx_http_ancient_browser_value,
109  0,
110  NULL },
111 
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 
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 */
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,
225  { ngx_string("ancient_browser"), ngx_http_browser_variable,
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;
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 
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;
265 
266  if (r->headers_in.user_agent == NULL) {
267  if (cf->modern_unlisted_browsers) {
269  }
270 
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 
295  "browser: \"%s\"", name);
296 
297  name += modern[i].add;
298 
299  if (name >= last) {
300  continue;
301  }
302 
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 
323  "version: \"%ui\" \"%ui\"",
324  modern[i].version, version);
325 
326  if (version > modern[i].version) {
328  }
329 
330  ver = 0;
331  scale /= 100;
332  continue;
333  }
334 
335  break;
336  }
337 
338  version += ver * scale;
339 
341  "version: \"%ui\" \"%ui\"",
342  modern[i].version, version);
343 
344  if (version >= modern[i].version) {
346  }
347 
349  }
350 
351  if (!cf->modern_unlisted_browsers) {
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  {
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  {
373  }
374  }
375  }
376 
377  if (cf->modern_unlisted_browsers) {
379  }
380 
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) {
391  return NGX_OK;
392  }
393 
395  return NGX_OK;
396 }
397 
398 
399 static ngx_int_t
400 ngx_http_browser_add_variable(ngx_conf_t *cf)
401 {
404 
405  for (var = ngx_http_browsers; var->name.len; var++) {
406 
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 {
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;
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,
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) {
512  }
513 
514  if (conf->modern_browser_value == NULL) {
516  }
517 
518  if (conf->ancient_browser_value == NULL) {
520  }
521 
522  if (conf->ancient_browser_value == NULL) {
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 {
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;
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 
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 
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 
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;
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 
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;
712  bcf->ancient_browser_value->data = value[1].data;
713 
714  return NGX_CONF_OK;
715 }