|  | 
 | /* | 
 |  * Copyright (C) Igor Sysoev | 
 |  */ | 
 |  | 
 |  | 
 | #include <ngx_config.h> | 
 | #include <ngx_core.h> | 
 | #include <ngx_http.h> | 
 |  | 
 |  | 
 | /* | 
 |  * The module can check browser versions conforming to the following formats: | 
 |  * X, X.X, X.X.X, and X.X.X.X.  The maximum values of each format may be | 
 |  * 4000, 4000.99, 4000.99.99, and 4000.99.99.99. | 
 |  */ | 
 |  | 
 |  | 
 | #define  NGX_HTTP_MODERN_BROWSER   0 | 
 | #define  NGX_HTTP_ANCIENT_BROWSER  1 | 
 |  | 
 |  | 
 | typedef struct { | 
 |     u_char                      browser[12]; | 
 |     size_t                      skip; | 
 |     size_t                      add; | 
 |     u_char                      name[12]; | 
 | } ngx_http_modern_browser_mask_t; | 
 |  | 
 |  | 
 | typedef struct { | 
 |     ngx_uint_t                  version; | 
 |     size_t                      skip; | 
 |     size_t                      add; | 
 |     u_char                      name[12]; | 
 | } ngx_http_modern_browser_t; | 
 |  | 
 |  | 
 | typedef struct { | 
 |     ngx_str_t                   name; | 
 |     ngx_http_get_variable_pt    handler; | 
 |     uintptr_t                   data; | 
 | } ngx_http_browser_variable_t; | 
 |  | 
 |  | 
 | typedef struct { | 
 |     ngx_array_t                *modern_browsers; | 
 |     ngx_array_t                *ancient_browsers; | 
 |     ngx_http_variable_value_t  *modern_browser_value; | 
 |     ngx_http_variable_value_t  *ancient_browser_value; | 
 |  | 
 |     unsigned                    modern_unlisted_browsers:1; | 
 |     unsigned                    netscape4:1; | 
 | } ngx_http_browser_conf_t; | 
 |  | 
 |  | 
 | static ngx_int_t ngx_http_msie_variable(ngx_http_request_t *r, | 
 |     ngx_http_variable_value_t *v, uintptr_t data); | 
 | static ngx_int_t ngx_http_browser_variable(ngx_http_request_t *r, | 
 |     ngx_http_variable_value_t *v, uintptr_t data); | 
 |  | 
 | static ngx_uint_t ngx_http_browser(ngx_http_request_t *r, | 
 |     ngx_http_browser_conf_t *cf); | 
 |  | 
 | static ngx_int_t ngx_http_browser_add_variable(ngx_conf_t *cf); | 
 | static void *ngx_http_browser_create_conf(ngx_conf_t *cf); | 
 | static char *ngx_http_browser_merge_conf(ngx_conf_t *cf, void *parent, | 
 |     void *child); | 
 | static int ngx_libc_cdecl ngx_http_modern_browser_sort(const void *one, | 
 |     const void *two); | 
 | static char *ngx_http_modern_browser(ngx_conf_t *cf, ngx_command_t *cmd, | 
 |     void *conf); | 
 | static char *ngx_http_ancient_browser(ngx_conf_t *cf, ngx_command_t *cmd, | 
 |     void *conf); | 
 | static char *ngx_http_modern_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, | 
 |     void *conf); | 
 | static char *ngx_http_ancient_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, | 
 |     void *conf); | 
 |  | 
 |  | 
 | static ngx_command_t  ngx_http_browser_commands[] = { | 
 |  | 
 |     { ngx_string("modern_browser"), | 
 |       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12, | 
 |       ngx_http_modern_browser, | 
 |       NGX_HTTP_LOC_CONF_OFFSET, | 
 |       0, | 
 |       NULL }, | 
 |  | 
 |     { ngx_string("ancient_browser"), | 
 |       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, | 
 |       ngx_http_ancient_browser, | 
 |       NGX_HTTP_LOC_CONF_OFFSET, | 
 |       0, | 
 |       NULL }, | 
 |  | 
 |     { ngx_string("modern_browser_value"), | 
 |       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, | 
 |       ngx_http_modern_browser_value, | 
 |       NGX_HTTP_LOC_CONF_OFFSET, | 
 |       0, | 
 |       NULL }, | 
 |  | 
 |     { ngx_string("ancient_browser_value"), | 
 |       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, | 
 |       ngx_http_ancient_browser_value, | 
 |       NGX_HTTP_LOC_CONF_OFFSET, | 
 |       0, | 
 |       NULL }, | 
 |  | 
 |       ngx_null_command | 
 | }; | 
 |  | 
 |  | 
 | static ngx_http_module_t  ngx_http_browser_module_ctx = { | 
 |     ngx_http_browser_add_variable,         /* preconfiguration */ | 
 |     NULL,                                  /* postconfiguration */ | 
 |  | 
 |     NULL,                                  /* create main configuration */ | 
 |     NULL,                                  /* init main configuration */ | 
 |  | 
 |     NULL,                                  /* create server configuration */ | 
 |     NULL,                                  /* merge server configuration */ | 
 |  | 
 |     ngx_http_browser_create_conf,          /* create location configuration */ | 
 |     ngx_http_browser_merge_conf            /* merge location configuration */ | 
 | }; | 
 |  | 
 |  | 
 | ngx_module_t  ngx_http_browser_module = { | 
 |     NGX_MODULE_V1, | 
 |     &ngx_http_browser_module_ctx,          /* module context */ | 
 |     ngx_http_browser_commands,             /* module directives */ | 
 |     NGX_HTTP_MODULE,                       /* module type */ | 
 |     NULL,                                  /* init master */ | 
 |     NULL,                                  /* init module */ | 
 |     NULL,                                  /* init process */ | 
 |     NULL,                                  /* init thread */ | 
 |     NULL,                                  /* exit thread */ | 
 |     NULL,                                  /* exit process */ | 
 |     NULL,                                  /* exit master */ | 
 |     NGX_MODULE_V1_PADDING | 
 | }; | 
 |  | 
 |  | 
 | static ngx_http_modern_browser_mask_t  ngx_http_modern_browser_masks[] = { | 
 |  | 
 |     /* Opera must be the first browser to check */ | 
 |  | 
 |     /* | 
 |      * "Opera/7.50 (X11; FreeBSD i386; U)  [en]" | 
 |      * "Mozilla/5.0 (X11; FreeBSD i386; U) Opera 7.50  [en]" | 
 |      * "Mozilla/4.0 (compatible; MSIE 6.0; X11; FreeBSD i386) Opera 7.50  [en]" | 
 |      * "Opera/8.0 (Windows NT 5.1; U; ru)" | 
 |      * "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; en) Opera 8.0" | 
 |      * "Opera/9.01 (X11; FreeBSD 6 i386; U; en)" | 
 |      */ | 
 |  | 
 |     { "opera", | 
 |       0, | 
 |       sizeof("Opera ") - 1, | 
 |       "Opera"}, | 
 |  | 
 |     /* "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)" */ | 
 |  | 
 |     { "msie", | 
 |       sizeof("Mozilla/4.0 (compatible; ") - 1, | 
 |       sizeof("MSIE ") - 1, | 
 |       "MSIE "}, | 
 |  | 
 |     /* | 
 |      * "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.0.0) Gecko/20020610" | 
 |      * "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU; rv:1.5) Gecko/20031006" | 
 |      * "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU; rv:1.6) Gecko/20040206 | 
 |      *              Firefox/0.8" | 
 |      * "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU; rv:1.7.8) | 
 |      *              Gecko/20050511 Firefox/1.0.4" | 
 |      * "Mozilla/5.0 (X11; U; FreeBSD i386; en-US; rv:1.8.0.5) Gecko/20060729 | 
 |      *              Firefox/1.5.0.5" | 
 |      */ | 
 |  | 
 |     { "gecko", | 
 |       sizeof("Mozilla/5.0 (") - 1, | 
 |       sizeof("rv:") - 1, | 
 |       "rv:"}, | 
 |  | 
 |     /* | 
 |      * "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ru-ru) AppleWebKit/125.2 | 
 |      *              (KHTML, like Gecko) Safari/125.7" | 
 |      * "Mozilla/5.0 (SymbianOS/9.1; U; en-us) AppleWebKit/413 | 
 |      *              (KHTML, like Gecko) Safari/413" | 
 |      * "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418 | 
 |      *              (KHTML, like Gecko) Safari/417.9.3" | 
 |      * "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; ru-ru) AppleWebKit/418.8 | 
 |      *              (KHTML, like Gecko) Safari/419.3" | 
 |      */ | 
 |  | 
 |     { "safari", | 
 |       sizeof("Mozilla/5.0 (") - 1, | 
 |       sizeof("Safari/") - 1, | 
 |       "Safari/"}, | 
 |  | 
 |     /* | 
 |      * "Mozilla/5.0 (compatible; Konqueror/3.1; Linux)" | 
 |      * "Mozilla/5.0 (compatible; Konqueror/3.4; Linux) KHTML/3.4.2 (like Gecko)" | 
 |      * "Mozilla/5.0 (compatible; Konqueror/3.5; FreeBSD) KHTML/3.5.1 | 
 |      *              (like Gecko)" | 
 |      */ | 
 |  | 
 |     { "konqueror", | 
 |       sizeof("Mozilla/5.0 (compatible; ") - 1, | 
 |       sizeof("Konqueror/") - 1, | 
 |       "Konqueror/"}, | 
 |  | 
 |     { "", 0, 0, "" } | 
 |  | 
 | }; | 
 |  | 
 |  | 
 | static ngx_http_browser_variable_t  ngx_http_browsers[] = { | 
 |     { ngx_string("msie"), ngx_http_msie_variable, 0 }, | 
 |     { ngx_string("modern_browser"), ngx_http_browser_variable, | 
 |           NGX_HTTP_MODERN_BROWSER }, | 
 |     { ngx_string("ancient_browser"), ngx_http_browser_variable, | 
 |           NGX_HTTP_ANCIENT_BROWSER }, | 
 |     { ngx_null_string, NULL, 0 } | 
 | }; | 
 |  | 
 |  | 
 | static ngx_int_t | 
 | ngx_http_browser_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, | 
 |     uintptr_t data) | 
 | { | 
 |     ngx_uint_t                rc; | 
 |     ngx_http_browser_conf_t  *cf; | 
 |  | 
 |     cf = ngx_http_get_module_loc_conf(r, ngx_http_browser_module); | 
 |  | 
 |     rc = ngx_http_browser(r, cf); | 
 |  | 
 |     if (data == NGX_HTTP_MODERN_BROWSER && rc == NGX_HTTP_MODERN_BROWSER) { | 
 |         *v = *cf->modern_browser_value; | 
 |         return NGX_OK; | 
 |     } | 
 |  | 
 |     if (data == NGX_HTTP_ANCIENT_BROWSER && rc == NGX_HTTP_ANCIENT_BROWSER) { | 
 |         *v = *cf->ancient_browser_value; | 
 |         return NGX_OK; | 
 |     } | 
 |  | 
 |     *v = ngx_http_variable_null_value; | 
 |     return NGX_OK; | 
 | } | 
 |  | 
 |  | 
 | static ngx_uint_t | 
 | ngx_http_browser(ngx_http_request_t *r, ngx_http_browser_conf_t *cf) | 
 | { | 
 |     size_t                      len; | 
 |     u_char                     *name, *ua, *last, c; | 
 |     ngx_str_t                  *ancient; | 
 |     ngx_uint_t                  i, version, ver, scale; | 
 |     ngx_http_modern_browser_t  *modern; | 
 |  | 
 |     if (r->headers_in.user_agent == NULL) { | 
 |         if (cf->modern_unlisted_browsers) { | 
 |             return NGX_HTTP_MODERN_BROWSER; | 
 |         } | 
 |  | 
 |         return NGX_HTTP_ANCIENT_BROWSER; | 
 |     } | 
 |  | 
 |     ua = r->headers_in.user_agent->value.data; | 
 |     len = r->headers_in.user_agent->value.len; | 
 |     last = ua + len; | 
 |  | 
 |     if (cf->modern_browsers) { | 
 |         modern = cf->modern_browsers->elts; | 
 |  | 
 |         for (i = 0; i < cf->modern_browsers->nelts; i++) { | 
 |             name = ua + modern[i].skip; | 
 |  | 
 |             if (name >= last) { | 
 |                 continue; | 
 |             } | 
 |  | 
 |             name = (u_char *) ngx_strstr(name, modern[i].name); | 
 |  | 
 |             if (name == NULL) { | 
 |                 continue; | 
 |             } | 
 |  | 
 |             ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, | 
 |                            "browser: \"%s\"", name); | 
 |  | 
 |             name += modern[i].add; | 
 |  | 
 |             if (name >= last) { | 
 |                 continue; | 
 |             } | 
 |  | 
 |             ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, | 
 |                            "version: \"%ui\" \"%s\"", modern[i].version, name); | 
 |  | 
 |             version = 0; | 
 |             ver = 0; | 
 |             scale = 1000000; | 
 |  | 
 |             while (name < last) { | 
 |  | 
 |                 c = *name++; | 
 |  | 
 |                 if (c >= '0' && c <= '9') { | 
 |                     ver = ver * 10 + (c - '0'); | 
 |                     continue; | 
 |                 } | 
 |  | 
 |                 if (c == '.') { | 
 |                     version += ver * scale; | 
 |  | 
 |                     if (version > modern[i].version) { | 
 |                         return NGX_HTTP_MODERN_BROWSER; | 
 |                     } | 
 |  | 
 |                     ver = 0; | 
 |                     scale /= 100; | 
 |                     continue; | 
 |                 } | 
 |  | 
 |                 break; | 
 |             } | 
 |  | 
 |             version += ver * scale; | 
 |  | 
 |             ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, | 
 |                            "version: \"%ui\" \"%ui\"", | 
 |                            modern[i].version, version); | 
 |  | 
 |             if (version >= modern[i].version) { | 
 |                 return NGX_HTTP_MODERN_BROWSER; | 
 |             } | 
 |         } | 
 |  | 
 |         if (!cf->modern_unlisted_browsers) { | 
 |             return NGX_HTTP_ANCIENT_BROWSER; | 
 |         } | 
 |     } | 
 |  | 
 |     if (cf->netscape4) { | 
 |         if (len > sizeof("Mozilla/4.72 ") - 1 | 
 |             && ngx_strncmp(ua, "Mozilla/", sizeof("Mozilla/") - 1) == 0 | 
 |             && ua[8] > '0' && ua[8] < '5') | 
 |         { | 
 |             return NGX_HTTP_ANCIENT_BROWSER; | 
 |         } | 
 |     } | 
 |  | 
 |     if (cf->ancient_browsers) { | 
 |         ancient = cf->ancient_browsers->elts; | 
 |  | 
 |         for (i = 0; i < cf->ancient_browsers->nelts; i++) { | 
 |             if (len >= ancient[i].len | 
 |                 && ngx_strstr(ua, ancient[i].data) != NULL) | 
 |             { | 
 |                 return NGX_HTTP_ANCIENT_BROWSER; | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     if (cf->modern_unlisted_browsers) { | 
 |         return NGX_HTTP_MODERN_BROWSER; | 
 |     } | 
 |  | 
 |     return NGX_HTTP_ANCIENT_BROWSER; | 
 | } | 
 |  | 
 |  | 
 | static ngx_int_t | 
 | ngx_http_msie_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, | 
 |     uintptr_t data) | 
 | { | 
 |     if (r->headers_in.msie) { | 
 |         *v = ngx_http_variable_true_value; | 
 |         return NGX_OK; | 
 |     } | 
 |  | 
 |     *v = ngx_http_variable_null_value; | 
 |     return NGX_OK; | 
 | } | 
 |  | 
 |  | 
 | static ngx_int_t | 
 | ngx_http_browser_add_variable(ngx_conf_t *cf) | 
 | { | 
 |     ngx_http_browser_variable_t   *var; | 
 |     ngx_http_variable_t           *v; | 
 |  | 
 |     for (var = ngx_http_browsers; var->name.len; var++) { | 
 |  | 
 |         v = ngx_http_add_variable(cf, &var->name, NGX_HTTP_VAR_CHANGABLE); | 
 |         if (v == NULL) { | 
 |             return NGX_ERROR; | 
 |         } | 
 |  | 
 |         v->get_handler = var->handler; | 
 |         v->data = var->data; | 
 |     } | 
 |  | 
 |     return NGX_OK; | 
 | } | 
 |  | 
 |  | 
 | static void * | 
 | ngx_http_browser_create_conf(ngx_conf_t *cf) | 
 | { | 
 |     ngx_http_browser_conf_t  *conf; | 
 |  | 
 |     conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_browser_conf_t)); | 
 |     if (conf == NULL) { | 
 |         return NGX_CONF_ERROR; | 
 |     } | 
 |  | 
 |     /* | 
 |      * set by ngx_pcalloc(): | 
 |      * | 
 |      *     conf->modern_browsers = NULL; | 
 |      *     conf->ancient_browsers = NULL; | 
 |      *     conf->modern_browser_value = NULL; | 
 |      *     conf->ancient_browser_value = NULL; | 
 |      * | 
 |      *     conf->modern_unlisted_browsers = 0; | 
 |      *     conf->netscape4 = 0; | 
 |      */ | 
 |  | 
 |     return conf; | 
 | } | 
 |  | 
 |  | 
 | static char * | 
 | ngx_http_browser_merge_conf(ngx_conf_t *cf, void *parent, void *child) | 
 | { | 
 |     ngx_http_browser_conf_t *prev = parent; | 
 |     ngx_http_browser_conf_t *conf = child; | 
 |  | 
 |     ngx_uint_t                  i, n; | 
 |     ngx_http_modern_browser_t  *browsers, *opera; | 
 |  | 
 |     /* | 
 |      * At the merge the skip field is used to store the browser slot, | 
 |      * it will be used in sorting and then will overwritten | 
 |      * with a real skip value.  The zero value means Opera. | 
 |      */ | 
 |  | 
 |     if (conf->modern_browsers == NULL) { | 
 |         conf->modern_browsers = prev->modern_browsers; | 
 |  | 
 |     } else { | 
 |         browsers = conf->modern_browsers->elts; | 
 |  | 
 |         for (i = 0; i < conf->modern_browsers->nelts; i++) { | 
 |             if (browsers[i].skip == 0) { | 
 |                 goto found; | 
 |             } | 
 |         } | 
 |  | 
 |         /* | 
 |          * Opera may contain MSIE string, so if Opera was not enumerated | 
 |          * as modern browsers, then add it and set a unreachable version | 
 |          */ | 
 |  | 
 |         opera = ngx_array_push(conf->modern_browsers); | 
 |         if (opera == NULL) { | 
 |             return NGX_CONF_ERROR; | 
 |         } | 
 |  | 
 |         opera->skip = 0; | 
 |         opera->version = 4001000000U; | 
 |  | 
 |         browsers = conf->modern_browsers->elts; | 
 |  | 
 | found: | 
 |  | 
 |         ngx_qsort(browsers, (size_t) conf->modern_browsers->nelts, | 
 |                   sizeof(ngx_http_modern_browser_t), | 
 |                   ngx_http_modern_browser_sort); | 
 |  | 
 |         for (i = 0; i < conf->modern_browsers->nelts; i++) { | 
 |              n = browsers[i].skip; | 
 |  | 
 |              browsers[i].skip = ngx_http_modern_browser_masks[n].skip; | 
 |              browsers[i].add = ngx_http_modern_browser_masks[n].add; | 
 |              (void) ngx_cpystrn(browsers[i].name, | 
 |                                 ngx_http_modern_browser_masks[n].name, 12); | 
 |         } | 
 |     } | 
 |  | 
 |     if (conf->ancient_browsers == NULL) { | 
 |         conf->ancient_browsers = prev->ancient_browsers; | 
 |     } | 
 |  | 
 |     if (conf->modern_browser_value == NULL) { | 
 |         conf->modern_browser_value = prev->modern_browser_value; | 
 |     } | 
 |  | 
 |     if (conf->modern_browser_value == NULL) { | 
 |         conf->modern_browser_value = &ngx_http_variable_true_value; | 
 |     } | 
 |  | 
 |     if (conf->ancient_browser_value == NULL) { | 
 |         conf->ancient_browser_value = prev->ancient_browser_value; | 
 |     } | 
 |  | 
 |     if (conf->ancient_browser_value == NULL) { | 
 |         conf->ancient_browser_value = &ngx_http_variable_true_value; | 
 |     } | 
 |  | 
 |     return NGX_CONF_OK; | 
 | } | 
 |  | 
 |  | 
 | static int ngx_libc_cdecl | 
 | ngx_http_modern_browser_sort(const void *one, const void *two) | 
 | { | 
 |     ngx_http_modern_browser_t *first = (ngx_http_modern_browser_t *) one; | 
 |     ngx_http_modern_browser_t *second = (ngx_http_modern_browser_t *) two; | 
 |  | 
 |     return (first->skip - second->skip); | 
 | } | 
 |  | 
 |  | 
 | static char * | 
 | ngx_http_modern_browser(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) | 
 | { | 
 |     ngx_http_browser_conf_t *bcf = conf; | 
 |  | 
 |     u_char                           c; | 
 |     ngx_str_t                       *value; | 
 |     ngx_uint_t                       i, n, version, ver, scale; | 
 |     ngx_http_modern_browser_t       *browser; | 
 |     ngx_http_modern_browser_mask_t  *mask; | 
 |  | 
 |     value = cf->args->elts; | 
 |  | 
 |     if (cf->args->nelts == 2) { | 
 |         if (ngx_strcmp(value[1].data, "unlisted") == 0) { | 
 |             bcf->modern_unlisted_browsers = 1; | 
 |             return NGX_CONF_OK; | 
 |         } | 
 |  | 
 |         return NGX_CONF_ERROR; | 
 |     } | 
 |  | 
 |     if (bcf->modern_browsers == NULL) { | 
 |         bcf->modern_browsers = ngx_array_create(cf->pool, 5, | 
 |                                             sizeof(ngx_http_modern_browser_t)); | 
 |         if (bcf->modern_browsers == NULL) { | 
 |             return NGX_CONF_ERROR; | 
 |         } | 
 |     } | 
 |  | 
 |     browser = ngx_array_push(bcf->modern_browsers); | 
 |     if (browser == NULL) { | 
 |         return NGX_CONF_ERROR; | 
 |     } | 
 |  | 
 |     mask = ngx_http_modern_browser_masks; | 
 |  | 
 |     for (n = 0; mask[n].browser[0] != '\0'; n++) { | 
 |         if (ngx_strcasecmp(mask[n].browser, value[1].data) == 0) { | 
 |             goto found; | 
 |         } | 
 |     } | 
 |  | 
 |     ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | 
 |                        "unknown browser name \"%V\"", &value[1]); | 
 |  | 
 |     return NGX_CONF_ERROR; | 
 |  | 
 | found: | 
 |  | 
 |     /* | 
 |      * at this stage the skip field is used to store the browser slot, | 
 |      * it will be used in sorting in merge stage and then will overwritten | 
 |      * with a real value | 
 |      */ | 
 |  | 
 |     browser->skip = n; | 
 |  | 
 |     version = 0; | 
 |     ver = 0; | 
 |     scale = 1000000; | 
 |  | 
 |     for (i = 0; i < value[2].len; i++) { | 
 |  | 
 |         c = value[2].data[i]; | 
 |  | 
 |         if (c >= '0' && c <= '9') { | 
 |             ver = ver * 10 + (c - '0'); | 
 |             continue; | 
 |         } | 
 |  | 
 |         if (c == '.') { | 
 |             version += ver * scale; | 
 |             ver = 0; | 
 |             scale /= 100; | 
 |             continue; | 
 |         } | 
 |  | 
 |         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, | 
 |                            "invalid browser version \"%V\"", &value[2]); | 
 |  | 
 |         return NGX_CONF_ERROR; | 
 |     } | 
 |  | 
 |     version += ver * scale; | 
 |  | 
 |     browser->version = version; | 
 |  | 
 |     return NGX_CONF_OK; | 
 | } | 
 |  | 
 |  | 
 | static char * | 
 | ngx_http_ancient_browser(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) | 
 | { | 
 |     ngx_http_browser_conf_t *bcf = conf; | 
 |  | 
 |     ngx_str_t   *value, *browser; | 
 |     ngx_uint_t   i; | 
 |  | 
 |     value = cf->args->elts; | 
 |  | 
 |     for (i = 1; i < cf->args->nelts; i++) { | 
 |         if (ngx_strcmp(value[i].data, "netscape4") == 0) { | 
 |             bcf->netscape4 = 1; | 
 |             continue; | 
 |         } | 
 |  | 
 |         if (bcf->ancient_browsers == NULL) { | 
 |             bcf->ancient_browsers = ngx_array_create(cf->pool, 4, | 
 |                                                      sizeof(ngx_str_t)); | 
 |             if (bcf->ancient_browsers == NULL) { | 
 |                 return NGX_CONF_ERROR; | 
 |             } | 
 |         } | 
 |  | 
 |         browser = ngx_array_push(bcf->ancient_browsers); | 
 |         if (browser == NULL) { | 
 |             return NGX_CONF_ERROR; | 
 |         } | 
 |  | 
 |         *browser = value[i]; | 
 |     } | 
 |  | 
 |     return NGX_CONF_OK; | 
 | } | 
 |  | 
 |  | 
 | static char * | 
 | ngx_http_modern_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) | 
 | { | 
 |     ngx_http_browser_conf_t *bcf = conf; | 
 |  | 
 |     ngx_str_t  *value; | 
 |  | 
 |     bcf->modern_browser_value = ngx_palloc(cf->pool, | 
 |                                            sizeof(ngx_http_variable_value_t)); | 
 |     if (bcf->modern_browser_value == NULL) { | 
 |         return NGX_CONF_ERROR; | 
 |     } | 
 |  | 
 |     value = cf->args->elts; | 
 |  | 
 |     bcf->modern_browser_value->len = value[1].len; | 
 |     bcf->modern_browser_value->valid = 1; | 
 |     bcf->modern_browser_value->no_cachable = 0; | 
 |     bcf->modern_browser_value->not_found = 0; | 
 |     bcf->modern_browser_value->data = value[1].data; | 
 |  | 
 |     return NGX_CONF_OK; | 
 | } | 
 |  | 
 |  | 
 | static char * | 
 | ngx_http_ancient_browser_value(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) | 
 | { | 
 |     ngx_http_browser_conf_t *bcf = conf; | 
 |  | 
 |     ngx_str_t  *value; | 
 |  | 
 |     bcf->ancient_browser_value = ngx_palloc(cf->pool, | 
 |                                             sizeof(ngx_http_variable_value_t)); | 
 |     if (bcf->ancient_browser_value == NULL) { | 
 |         return NGX_CONF_ERROR; | 
 |     } | 
 |  | 
 |     value = cf->args->elts; | 
 |  | 
 |     bcf->ancient_browser_value->len = value[1].len; | 
 |     bcf->ancient_browser_value->valid = 1; | 
 |     bcf->ancient_browser_value->no_cachable = 0; | 
 |     bcf->ancient_browser_value->not_found = 0; | 
 |     bcf->ancient_browser_value->data = value[1].data; | 
 |  | 
 |     return NGX_CONF_OK; | 
 | } |