Mercurial > hg > nginx-vendor-1-0
diff src/http/modules/ngx_http_browser_module.c @ 236:c982febb7588 NGINX_0_4_3
nginx 0.4.3
*) Change: now the 499 error could not be redirected using an
"error_page" directive.
*) Feature: the Solaris 10 event ports support.
*) Feature: the ngx_http_browser_module.
*) Bugfix: a segmentation fault may occur while redirecting the 400
error to the proxied server using an "proxy_pass" directive.
*) Bugfix: a segmentation fault occurred if an unix domain socket was
used in an "proxy_pass" directive; bug appeared in 0.3.47.
*) Bugfix: SSI did work with memcached and nonbuffered responses.
*) Workaround: of the Sun Studio PAUSE hardware capability bug.
author | Igor Sysoev <http://sysoev.ru> |
---|---|
date | Tue, 26 Sep 2006 00:00:00 +0400 |
parents | |
children | 500a3242dff6 |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/src/http/modules/ngx_http_browser_module.c @@ -0,0 +1,706 @@ + +/* + * 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; +}