Skip to content

Instantly share code, notes, and snippets.

@agentzh
Created April 24, 2013 00:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save agentzh/5448609 to your computer and use it in GitHub Desktop.
Save agentzh/5448609 to your computer and use it in GitHub Desktop.
bugfix: Modifying the User-Agent request header via ngx.req.set_header or ngx.req.clear_header did not update those special internal flags used in the Nginx internals, like "r->headers_in.msie6" and "r->headers_in.opera". Thanks Matthieu Tourne for the patch.
diff --git a/src/ngx_http_lua_headers_in.c b/src/ngx_http_lua_headers_in.c
index 9dd5a83..f4413b6 100644
--- a/src/ngx_http_lua_headers_in.c
+++ b/src/ngx_http_lua_headers_in.c
@@ -23,6 +23,8 @@ static ngx_int_t ngx_http_set_header_helper(ngx_http_request_t *r,
ngx_table_elt_t **output_header, unsigned no_create);
static ngx_int_t ngx_http_set_builtin_header(ngx_http_request_t *r,
ngx_http_lua_header_val_t *hv, ngx_str_t *value);
+static ngx_int_t ngx_http_set_user_agent_header(ngx_http_request_t *r,
+ ngx_http_lua_header_val_t *hv, ngx_str_t *value);
static ngx_int_t ngx_http_set_content_length_header(ngx_http_request_t *r,
ngx_http_lua_header_val_t *hv, ngx_str_t *value);
static ngx_int_t ngx_http_clear_builtin_header(ngx_http_request_t *r,
@@ -57,7 +59,7 @@ static ngx_http_lua_set_header_t ngx_http_lua_set_handlers[] = {
{ ngx_string("User-Agent"),
offsetof(ngx_http_headers_in_t, user_agent),
- ngx_http_set_builtin_header },
+ ngx_http_set_user_agent_header },
{ ngx_string("Referer"),
offsetof(ngx_http_headers_in_t, referer),
@@ -266,6 +268,81 @@ ngx_http_set_host_header(ngx_http_request_t *r, ngx_http_lua_header_val_t *hv,
}
+/* borrowed the code from ngx_http_request.c:ngx_http_process_user_agent */
+static ngx_int_t
+ngx_http_set_user_agent_header(ngx_http_request_t *r,
+ ngx_http_lua_header_val_t *hv, ngx_str_t *value)
+{
+ u_char *user_agent, *msie;
+
+ /* clear existing settings */
+
+ r->headers_in.msie = 0;
+ r->headers_in.msie6 = 0;
+ r->headers_in.opera = 0;
+ r->headers_in.gecko = 0;
+ r->headers_in.chrome = 0;
+ r->headers_in.safari = 0;
+ r->headers_in.konqueror = 0;
+
+ if (value->len == 0) {
+ return ngx_http_set_builtin_header(r, hv, value);
+ }
+
+ /* check some widespread browsers */
+
+ user_agent = value->data;
+
+ msie = ngx_strstrn(user_agent, "MSIE ", 5 - 1);
+
+ if (msie && msie + 7 < user_agent + value->len) {
+
+ r->headers_in.msie = 1;
+
+ if (msie[6] == '.') {
+
+ switch (msie[5]) {
+ case '4':
+ case '5':
+ r->headers_in.msie6 = 1;
+ break;
+ case '6':
+ if (ngx_strstrn(msie + 8, "SV1", 3 - 1) == NULL) {
+ r->headers_in.msie6 = 1;
+ }
+ break;
+ }
+ }
+ }
+
+ if (ngx_strstrn(user_agent, "Opera", 5 - 1)) {
+ r->headers_in.opera = 1;
+ r->headers_in.msie = 0;
+ r->headers_in.msie6 = 0;
+ }
+
+ if (!r->headers_in.msie && !r->headers_in.opera) {
+
+ if (ngx_strstrn(user_agent, "Gecko/", 6 - 1)) {
+ r->headers_in.gecko = 1;
+
+ } else if (ngx_strstrn(user_agent, "Chrome/", 7 - 1)) {
+ r->headers_in.chrome = 1;
+
+ } else if (ngx_strstrn(user_agent, "Safari/", 7 - 1)
+ && ngx_strstrn(user_agent, "Mac OS X", 8 - 1))
+ {
+ r->headers_in.safari = 1;
+
+ } else if (ngx_strstrn(user_agent, "Konqueror", 9 - 1)) {
+ r->headers_in.konqueror = 1;
+ }
+ }
+
+ return ngx_http_set_builtin_header(r, hv, value);
+}
+
+
static ngx_int_t
ngx_http_set_content_length_header(ngx_http_request_t *r,
ngx_http_lua_header_val_t *hv, ngx_str_t *value)
diff --git a/t/111-req-header-ua.t b/t/111-req-header-ua.t
new file mode 100644
index 0000000..4b1d39c
--- /dev/null
+++ b/t/111-req-header-ua.t
@@ -0,0 +1,677 @@
+# vim:set ft= ts=4 sw=4 et fdm=marker:
+
+use lib 'lib';
+use Test::Nginx::Socket;
+
+#worker_connections(1014);
+#master_process_enabled(1);
+#log_level('warn');
+
+repeat_each(2);
+
+plan tests => repeat_each() * (4 * blocks());
+
+#no_diff();
+no_long_string();
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: clear Opera user-agent
+--- config
+ location /t {
+ rewrite_by_lua '
+ ngx.req.set_header("User-Agent", nil)
+
+ ';
+ echo "User-Agent: $http_user_agent";
+ }
+
+--- request
+GET /t
+
+--- more_headers
+User-Agent: Opera/9.80 (Macintosh; Intel Mac OS X 10.7.4; U; en) Presto/2.10.229 Version/11.62
+
+--- stap
+F(ngx_http_lua_rewrite_by_chunk) {
+ printf("rewrite: opera: %d\n", $r->headers_in->opera)
+}
+
+
+F(ngx_http_core_content_phase) {
+ printf("content: opera: %d\n", $r->headers_in->opera)
+}
+
+--- stap_out
+rewrite: opera: 1
+content: opera: 0
+
+--- response_body
+User-Agent:
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: clear MSIE 4 user-agent
+--- config
+ location /t {
+ rewrite_by_lua '
+ ngx.req.set_header("User-Agent", nil)
+
+ ';
+ echo "User-Agent: $http_user_agent";
+ }
+
+--- request
+GET /t
+
+--- more_headers
+User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows NT 5.0)
+
+--- stap
+F(ngx_http_lua_rewrite_by_chunk) {
+ printf("rewrite: msie=%d msie6=%d\n",
+ $r->headers_in->msie,
+ $r->headers_in->msie6)
+}
+
+F(ngx_http_core_content_phase) {
+ printf("content: msie=%d msie6=%d\n",
+ $r->headers_in->msie,
+ $r->headers_in->msie6)
+}
+
+--- stap_out
+rewrite: msie=1 msie6=1
+content: msie=0 msie6=0
+
+--- response_body
+User-Agent:
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: set custom MSIE 4 user-agent
+--- config
+ location /t {
+ rewrite_by_lua '
+ ngx.req.set_header("User-Agent", "Mozilla/4.0 (compatible; MSIE 4.01; Windows NT 5.0)")
+ ';
+ echo "User-Agent: $http_user_agent";
+ }
+
+--- request
+GET /t
+
+--- stap
+F(ngx_http_lua_rewrite_by_chunk) {
+ printf("rewrite: msie=%d msie6=%d\n",
+ $r->headers_in->msie,
+ $r->headers_in->msie6)
+}
+
+F(ngx_http_core_content_phase) {
+ printf("content: msie=%d msie6=%d\n",
+ $r->headers_in->msie,
+ $r->headers_in->msie6)
+}
+
+--- stap_out
+rewrite: msie=0 msie6=0
+content: msie=1 msie6=1
+
+--- response_body
+User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows NT 5.0)
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: clear MSIE 5 user-agent
+--- config
+ location /t {
+ rewrite_by_lua '
+ ngx.req.set_header("User-Agent", nil)
+
+ ';
+ echo "User-Agent: $http_user_agent";
+ }
+
+--- request
+GET /t
+
+--- more_headers
+User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows 95; MSIECrawler)
+
+--- stap
+F(ngx_http_lua_rewrite_by_chunk) {
+ printf("rewrite: msie=%d msie6=%d\n",
+ $r->headers_in->msie,
+ $r->headers_in->msie6)
+}
+
+F(ngx_http_core_content_phase) {
+ printf("content: msie=%d msie6=%d\n",
+ $r->headers_in->msie,
+ $r->headers_in->msie6)
+}
+
+--- stap_out
+rewrite: msie=1 msie6=1
+content: msie=0 msie6=0
+
+--- response_body
+User-Agent:
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: set custom MSIE 5 user-agent
+--- config
+ location /t {
+ rewrite_by_lua '
+ ngx.req.set_header("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.01; Windows 95; MSIECrawler)")
+ ';
+ echo "User-Agent: $http_user_agent";
+ }
+
+--- request
+GET /t
+
+--- stap
+F(ngx_http_lua_rewrite_by_chunk) {
+ printf("rewrite: msie=%d msie6=%d\n",
+ $r->headers_in->msie,
+ $r->headers_in->msie6)
+}
+
+F(ngx_http_core_content_phase) {
+ printf("content: msie=%d msie6=%d\n",
+ $r->headers_in->msie,
+ $r->headers_in->msie6)
+}
+
+--- stap_out
+rewrite: msie=0 msie6=0
+content: msie=1 msie6=1
+
+--- response_body
+User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows 95; MSIECrawler)
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: clear MSIE 6 (without SV1) user-agent
+--- config
+ location /t {
+ rewrite_by_lua '
+ ngx.req.set_header("User-Agent", nil)
+
+ ';
+ echo "User-Agent: $http_user_agent";
+ }
+
+--- request
+GET /t
+
+--- more_headers
+User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; Google Wireless Transcoder;)
+
+--- stap
+F(ngx_http_lua_rewrite_by_chunk) {
+ printf("rewrite: msie=%d msie6=%d\n",
+ $r->headers_in->msie,
+ $r->headers_in->msie6)
+}
+
+F(ngx_http_core_content_phase) {
+ printf("content: msie=%d msie6=%d\n",
+ $r->headers_in->msie,
+ $r->headers_in->msie6)
+}
+
+--- stap_out
+rewrite: msie=1 msie6=1
+content: msie=0 msie6=0
+
+--- response_body
+User-Agent:
+--- no_error_log
+[error]
+
+
+
+=== TEST 7: set custom MSIE 6 (without SV1) user-agent
+--- config
+ location /t {
+ rewrite_by_lua '
+ ngx.req.set_header("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; Google Wireless Transcoder;)")
+ ';
+ echo "User-Agent: $http_user_agent";
+ }
+
+--- request
+GET /t
+
+--- stap
+F(ngx_http_lua_rewrite_by_chunk) {
+ printf("rewrite: msie=%d msie6=%d\n",
+ $r->headers_in->msie,
+ $r->headers_in->msie6)
+}
+
+F(ngx_http_core_content_phase) {
+ printf("content: msie=%d msie6=%d\n",
+ $r->headers_in->msie,
+ $r->headers_in->msie6)
+}
+
+--- stap_out
+rewrite: msie=0 msie6=0
+content: msie=1 msie6=1
+
+--- response_body
+User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; Google Wireless Transcoder;)
+--- no_error_log
+[error]
+
+
+
+=== TEST 8: clear MSIE 6 (with SV1) user-agent
+--- config
+ location /t {
+ rewrite_by_lua '
+ ngx.req.set_header("User-Agent", nil)
+
+ ';
+ echo "User-Agent: $http_user_agent";
+ }
+
+--- request
+GET /t
+
+--- more_headers
+User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; InfoPath.1)
+
+--- stap
+F(ngx_http_lua_rewrite_by_chunk) {
+ printf("rewrite: msie=%d msie6=%d\n",
+ $r->headers_in->msie,
+ $r->headers_in->msie6)
+}
+
+F(ngx_http_core_content_phase) {
+ printf("content: msie=%d msie6=%d\n",
+ $r->headers_in->msie,
+ $r->headers_in->msie6)
+}
+
+--- stap_out
+rewrite: msie=1 msie6=0
+content: msie=0 msie6=0
+
+--- response_body
+User-Agent:
+--- no_error_log
+[error]
+
+
+
+=== TEST 9: set custom MSIE 6 (with SV1) user-agent
+--- config
+ location /t {
+ rewrite_by_lua '
+ ngx.req.set_header("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; InfoPath.1)")
+ ';
+ echo "User-Agent: $http_user_agent";
+ }
+
+--- request
+GET /t
+
+--- stap
+F(ngx_http_lua_rewrite_by_chunk) {
+ printf("rewrite: msie=%d msie6=%d\n",
+ $r->headers_in->msie,
+ $r->headers_in->msie6)
+}
+
+F(ngx_http_core_content_phase) {
+ printf("content: msie=%d msie6=%d\n",
+ $r->headers_in->msie,
+ $r->headers_in->msie6)
+}
+
+--- stap_out
+rewrite: msie=0 msie6=0
+content: msie=1 msie6=0
+
+--- response_body
+User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; InfoPath.1)
+--- no_error_log
+[error]
+
+
+
+=== TEST 10: set custom MSIE 7 user-agent
+--- config
+ location /t {
+ rewrite_by_lua '
+ ngx.req.set_header("User-Agent", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; winfx; .NET CLR 1.1.4322; .NET CLR 2.0.50727; Zune 2.0)")
+ ';
+ echo "User-Agent: $http_user_agent";
+ }
+
+--- request
+GET /t
+
+--- stap
+F(ngx_http_lua_rewrite_by_chunk) {
+ printf("rewrite: msie=%d msie6=%d\n",
+ $r->headers_in->msie,
+ $r->headers_in->msie6)
+}
+
+F(ngx_http_core_content_phase) {
+ printf("content: msie=%d msie6=%d\n",
+ $r->headers_in->msie,
+ $r->headers_in->msie6)
+}
+
+--- stap_out
+rewrite: msie=0 msie6=0
+content: msie=1 msie6=0
+
+--- response_body
+User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; winfx; .NET CLR 1.1.4322; .NET CLR 2.0.50727; Zune 2.0)
+--- no_error_log
+[error]
+
+
+
+=== TEST 11: clear Gecko user-agent
+--- config
+ location /t {
+ rewrite_by_lua '
+ ngx.req.set_header("User-Agent", nil)
+
+ ';
+ echo "User-Agent: $http_user_agent";
+ }
+
+--- request
+GET /t
+
+--- more_headers
+User-Agent: Mozilla/5.0 (Android; Mobile; rv:13.0) Gecko/13.0 Firefox/13.0
+
+--- stap
+F(ngx_http_lua_rewrite_by_chunk) {
+ printf("rewrite: gecko: %d\n", $r->headers_in->gecko)
+}
+
+
+F(ngx_http_core_content_phase) {
+ printf("content: gecko: %d\n", $r->headers_in->gecko)
+}
+
+--- stap_out
+rewrite: gecko: 1
+content: gecko: 0
+
+--- response_body
+User-Agent:
+--- no_error_log
+[error]
+
+
+
+=== TEST 12: set custom Gecko user-agent
+--- config
+ location /t {
+ rewrite_by_lua '
+ ngx.req.set_header("User-Agent", "Mozilla/5.0 (Android; Mobile; rv:13.0) Gecko/13.0 Firefox/13.0")
+
+ ';
+ echo "User-Agent: $http_user_agent";
+ }
+
+--- request
+GET /t
+
+--- stap
+F(ngx_http_lua_rewrite_by_chunk) {
+ printf("rewrite: gecko: %d\n", $r->headers_in->gecko)
+}
+
+
+F(ngx_http_core_content_phase) {
+ printf("content: gecko: %d\n", $r->headers_in->gecko)
+}
+
+--- stap_out
+rewrite: gecko: 0
+content: gecko: 1
+
+--- response_body
+User-Agent: Mozilla/5.0 (Android; Mobile; rv:13.0) Gecko/13.0 Firefox/13.0
+--- no_error_log
+[error]
+
+
+
+=== TEST 13: clear Chrome user-agent
+--- config
+ location /t {
+ rewrite_by_lua '
+ ngx.req.set_header("User-Agent", nil)
+
+ ';
+ echo "User-Agent: $http_user_agent";
+ }
+
+--- request
+GET /t
+
+--- more_headers
+User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.151 Safari/535.19
+
+--- stap
+F(ngx_http_lua_rewrite_by_chunk) {
+ printf("rewrite: chrome: %d\n", $r->headers_in->chrome)
+}
+
+
+F(ngx_http_core_content_phase) {
+ printf("content: chrome: %d\n", $r->headers_in->chrome)
+}
+
+--- stap_out
+rewrite: chrome: 1
+content: chrome: 0
+
+--- response_body
+User-Agent:
+--- no_error_log
+[error]
+
+
+
+=== TEST 14: set custom Chrome user-agent
+--- config
+ location /t {
+ rewrite_by_lua '
+ ngx.req.set_header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.151 Safari/535.19")
+
+ ';
+ echo "User-Agent: $http_user_agent";
+ }
+
+--- request
+GET /t
+
+--- stap
+F(ngx_http_lua_rewrite_by_chunk) {
+ printf("rewrite: chrome: %d\n", $r->headers_in->chrome)
+}
+
+
+F(ngx_http_core_content_phase) {
+ printf("content: chrome: %d\n", $r->headers_in->chrome)
+}
+
+--- stap_out
+rewrite: chrome: 0
+content: chrome: 1
+
+--- response_body
+User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.151 Safari/535.19
+--- no_error_log
+[error]
+
+
+
+=== TEST 15: clear Safari (Mac OS X) user-agent
+--- config
+ location /t {
+ rewrite_by_lua '
+ ngx.req.set_header("User-Agent", nil)
+
+ ';
+ echo "User-Agent: $http_user_agent";
+ }
+
+--- request
+GET /t
+
+--- more_headers
+User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.2 (KHTML, like Gecko) Safari/125.8
+
+--- stap
+F(ngx_http_lua_rewrite_by_chunk) {
+ printf("rewrite: safari: %d\n", $r->headers_in->safari)
+}
+
+
+F(ngx_http_core_content_phase) {
+ printf("content: safari: %d\n", $r->headers_in->safari)
+}
+
+--- stap_out
+rewrite: safari: 1
+content: safari: 0
+
+--- response_body
+User-Agent:
+--- no_error_log
+[error]
+
+
+
+=== TEST 16: set custom Safari user-agent
+--- config
+ location /t {
+ rewrite_by_lua '
+ ngx.req.set_header("User-Agent", "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.2 (KHTML, like Gecko) Safari/125.8")
+ ';
+ echo "User-Agent: $http_user_agent";
+ }
+
+--- request
+GET /t
+
+--- stap
+F(ngx_http_lua_rewrite_by_chunk) {
+ printf("rewrite: safari: %d\n", $r->headers_in->safari)
+}
+
+
+F(ngx_http_core_content_phase) {
+ printf("content: safari: %d\n", $r->headers_in->safari)
+}
+
+--- stap_out
+rewrite: safari: 0
+content: safari: 1
+
+--- response_body
+User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/125.2 (KHTML, like Gecko) Safari/125.8
+--- no_error_log
+[error]
+
+
+
+=== TEST 17: clear Konqueror user-agent
+--- config
+ location /t {
+ rewrite_by_lua '
+ ngx.req.set_header("User-Agent", nil)
+
+ ';
+ echo "User-Agent: $http_user_agent";
+ }
+
+--- request
+GET /t
+
+--- more_headers
+User-Agent: Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.10 (like Gecko) (Kubuntu)
+
+--- stap
+F(ngx_http_lua_rewrite_by_chunk) {
+ printf("rewrite: konqueror: %d\n", $r->headers_in->konqueror)
+}
+
+
+F(ngx_http_core_content_phase) {
+ printf("content: konqueror: %d\n", $r->headers_in->konqueror)
+}
+
+--- stap_out
+rewrite: konqueror: 1
+content: konqueror: 0
+
+--- response_body
+User-Agent:
+--- no_error_log
+[error]
+
+
+
+=== TEST 18: set custom Konqueror user-agent
+--- config
+ location /t {
+ rewrite_by_lua '
+ ngx.req.set_header("User-Agent", "Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.10 (like Gecko) (Kubuntu)")
+ ';
+ echo "User-Agent: $http_user_agent";
+ }
+
+--- request
+GET /t
+
+--- stap
+F(ngx_http_lua_rewrite_by_chunk) {
+ printf("rewrite: konqueror: %d\n", $r->headers_in->konqueror)
+}
+
+
+F(ngx_http_core_content_phase) {
+ printf("content: konqueror: %d\n", $r->headers_in->konqueror)
+}
+
+--- stap_out
+rewrite: konqueror: 0
+content: konqueror: 1
+
+--- response_body
+User-Agent: Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.10 (like Gecko) (Kubuntu)
+--- no_error_log
+[error]
+
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment