├── .gitignore ├── Changelog.md ├── test ├── Gemfile ├── spec_helper.rb ├── Gemfile.lock ├── nginx_configuration.rb └── check_sorted_querystring_spec.rb ├── config ├── LICENSE ├── nginx.conf ├── README.md └── ngx_http_sorted_querystring_module.c /.gitignore: -------------------------------------------------------------------------------- 1 | .cproject 2 | .project 3 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | ### 0.3 2 | - Add support to the module works as a dynamic module 3 | 4 | ### 0.2 5 | - [bug fix] Correct the variable length when all parameters were filtered 6 | 7 | ### 0.1 8 | - Initial release 9 | -------------------------------------------------------------------------------- /test/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | ruby '3.2.0' 4 | 5 | gem 'rake' 6 | 7 | group :test do 8 | gem 'rspec' 9 | gem 'nginx_test_helper', '~> 0.4.0' 10 | gem 'em-eventsource' 11 | 12 | gem 'byebug' 13 | end 14 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_sorted_querystring_module 2 | SORTED_QUERYSTRING_SRCS="$ngx_addon_dir/ngx_http_sorted_querystring_module.c" 3 | 4 | if [ -n "$ngx_module_link" ]; then 5 | ngx_module_type=HTTP 6 | ngx_module_name="$ngx_addon_name" 7 | ngx_module_srcs="$SORTED_QUERYSTRING_SRCS" 8 | 9 | . auto/module 10 | else 11 | HTTP_MODULES="$HTTP_MODULES $ngx_addon_name" 12 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $SORTED_QUERYSTRING_SRCS" 13 | fi 14 | -------------------------------------------------------------------------------- /test/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | # Set up gems listed in the Gemfile. 4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', File.dirname(__FILE__)) 5 | 6 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 7 | Bundler.require(:default, :test) if defined?(Bundler) 8 | 9 | require File.expand_path('nginx_configuration', File.dirname(__FILE__)) 10 | 11 | Signal.trap("CLD", "IGNORE") 12 | 13 | RSpec.configure do |config| 14 | config.after(:each) do 15 | NginxTestHelper::Config.delete_config_and_log_files(config_id) if has_passed? 16 | end 17 | config.order = "random" 18 | end 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Wandenberg Peixoto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /test/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | Platform (0.4.2) 5 | addressable (2.8.1) 6 | public_suffix (>= 2.0.2, < 6.0) 7 | byebug (11.1.3) 8 | cookiejar (0.3.3) 9 | diff-lcs (1.5.0) 10 | em-eventsource (0.3.2) 11 | em-http-request (~> 1.0) 12 | eventmachine (~> 1.0) 13 | em-http-request (1.1.7) 14 | addressable (>= 2.3.4) 15 | cookiejar (!= 0.3.1) 16 | em-socksify (>= 0.3) 17 | eventmachine (>= 1.0.3) 18 | http_parser.rb (>= 0.6.0) 19 | em-socksify (0.3.2) 20 | eventmachine (>= 1.0.0.beta.4) 21 | eventmachine (1.2.7) 22 | http_parser.rb (0.8.0) 23 | nginx_test_helper (0.4.1) 24 | popen4 25 | open4 (1.3.4) 26 | popen4 (0.1.2) 27 | Platform (>= 0.4.0) 28 | open4 (>= 0.4.0) 29 | public_suffix (5.0.1) 30 | rake (13.0.6) 31 | rspec (3.12.0) 32 | rspec-core (~> 3.12.0) 33 | rspec-expectations (~> 3.12.0) 34 | rspec-mocks (~> 3.12.0) 35 | rspec-core (3.12.0) 36 | rspec-support (~> 3.12.0) 37 | rspec-expectations (3.12.2) 38 | diff-lcs (>= 1.2.0, < 2.0) 39 | rspec-support (~> 3.12.0) 40 | rspec-mocks (3.12.3) 41 | diff-lcs (>= 1.2.0, < 2.0) 42 | rspec-support (~> 3.12.0) 43 | rspec-support (3.12.0) 44 | 45 | PLATFORMS 46 | x86_64-linux 47 | 48 | DEPENDENCIES 49 | byebug 50 | em-eventsource 51 | nginx_test_helper (~> 0.4.0) 52 | rake 53 | rspec 54 | 55 | RUBY VERSION 56 | ruby 3.2.0p0 57 | 58 | BUNDLED WITH 59 | 2.4.1 60 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | pid logs/nginx.pid; 2 | error_log logs/nginx-main_error.log debug; 3 | 4 | # Development Mode 5 | master_process off; 6 | daemon off; 7 | worker_processes 2; 8 | 9 | events { 10 | worker_connections 1024; 11 | #use kqueue; # MacOS 12 | use epoll; # Linux 13 | } 14 | 15 | http { 16 | default_type text/plain; 17 | 18 | types { 19 | text/html html; 20 | } 21 | 22 | log_format main '[$time_local] $host "$request" $request_time s ' 23 | '$status $body_bytes_sent "$http_referer" "$http_user_agent" ' 24 | 'cache_status: "$upstream_cache_status" args: "$args ' 25 | 'sorted_args: "$sorted_querystring_args" '; 26 | 27 | access_log logs/nginx-http_access.log; 28 | 29 | proxy_cache_path /tmp/cache levels=1:2 keys_zone=zone:10m inactive=10d max_size=100m; 30 | 31 | server { 32 | listen 8080; 33 | server_name localhost; 34 | 35 | access_log logs/nginx-http_access.log main; 36 | 37 | location /filtered { 38 | sorted_querysting_filter_parameter v _ v time b; 39 | 40 | proxy_set_header Host "static_files_server"; 41 | proxy_pass http://localhost:8081; 42 | 43 | proxy_cache zone; 44 | proxy_cache_key "$sorted_querystring_args"; 45 | proxy_cache_valid 200 1m; 46 | } 47 | 48 | location / { 49 | proxy_pass http://localhost:8081; 50 | 51 | proxy_cache zone; 52 | proxy_cache_key "$sorted_querystring_args"; 53 | proxy_cache_valid 200 10m; 54 | } 55 | } 56 | 57 | server { 58 | listen 8081; 59 | 60 | location / { 61 | return 200 "$args\n"; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /test/nginx_configuration.rb: -------------------------------------------------------------------------------- 1 | require 'nginx_test_helper' 2 | module NginxConfiguration 3 | def self.default_configuration 4 | { 5 | disable_start_stop_server: false, 6 | master_process: 'off', 7 | daemon: 'off', 8 | 9 | filter_parameter: nil, 10 | filter_parameter2: nil, 11 | } 12 | end 13 | 14 | 15 | def self.template_configuration 16 | %( 17 | pid <%= pid_file %>; 18 | error_log <%= error_log %> debug; 19 | 20 | # Development Mode 21 | master_process <%= master_process %>; 22 | daemon <%= daemon %>; 23 | 24 | worker_processes <%= nginx_workers %>; 25 | 26 | events { 27 | worker_connections 1024; 28 | use <%= (RUBY_PLATFORM =~ /darwin/) ? 'kqueue' : 'epoll' %>; 29 | } 30 | 31 | http { 32 | access_log <%= access_log %>; 33 | 34 | proxy_cache_path <%= File.expand_path(nginx_tests_tmp_dir) %>/cache levels=1:2 keys_zone=zone:10m inactive=10d max_size=100m; 35 | 36 | server { 37 | listen <%= nginx_port %>; 38 | server_name <%= nginx_host %>; 39 | 40 | <%= write_directive("sorted_querysting_filter_parameter", filter_parameter) %> 41 | 42 | location / { 43 | proxy_set_header Host "static_files_server"; 44 | proxy_pass http://<%= nginx_host %>:<%= nginx_port %>; 45 | 46 | proxy_cache zone; 47 | proxy_cache_key "$uri$is_args$sorted_querystring_args"; 48 | proxy_cache_valid 200 1m; 49 | } 50 | } 51 | 52 | server { 53 | listen <%= nginx_port %>; 54 | server_name static_files_server; 55 | 56 | <%= write_directive("sorted_querysting_filter_parameter", filter_parameter) %> 57 | 58 | location /overwrite { 59 | <%= write_directive("sorted_querysting_filter_parameter", filter_parameter2) %> 60 | 61 | return 200 '{"args": "$args", "sorted_args": "$sorted_querystring_args"}'; 62 | } 63 | 64 | location / { 65 | return 200 '{"args": "$args", "sorted_args": "$sorted_querystring_args"}'; 66 | } 67 | } 68 | } 69 | ) 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Nginx Sorted Querystring Module 2 | =============================== 3 | 4 | This Nginx module orders the querystring parameters of an HTTP request alphanumerically and makes the sorted key-value pairs accessible using an Nginx variable. 5 | 6 | Requests like `/index.html?b=2&a=1&c=3`, `/index.html?b=2&c=3&a=1`, `/index.html?c=3&a=1&b=2`, `/index.html?c=3&b=2&a=1` will produce the same normalized querystring `a=1&b=2&c=3` which can be accessed within Nginx using the `$sorted_querystring_args` variable. 7 | 8 | This is especially useful if you want to normalize the querystring to be used in a cache key, for example when used with the `proxy_cache_key` directive. 9 | 10 | It is also possible to remove one or more undesired query parameters by defining their name with the `sorted_querysting_filter_parameter` directive, like `sorted_querystring_filter_parameter [ ...];`. 11 | 12 | _This module is not distributed with the Nginx source. See [the installation instructions](#installation)._ 13 | 14 | 15 | Configuration 16 | ------------- 17 | 18 | An example: 19 | 20 | ``` 21 | pid logs/nginx.pid; 22 | error_log logs/nginx-main_error.log debug; 23 | 24 | worker_processes 2; 25 | 26 | events { 27 | worker_connections 1024; 28 | #use kqueue; # MacOS 29 | use epoll; # Linux 30 | } 31 | 32 | http { 33 | default_type text/plain; 34 | 35 | types { 36 | text/html html; 37 | } 38 | 39 | log_format main '[$time_local] $host "$request" $request_time s ' 40 | '$status $body_bytes_sent "$http_referer" "$http_user_agent" ' 41 | 'cache_status: "$upstream_cache_status" args: "$args ' 42 | 'sorted_args: "$sorted_querystring_args" '; 43 | 44 | access_log logs/nginx-http_access.log; 45 | 46 | proxy_cache_path /tmp/cache levels=1:2 keys_zone=zone:10m inactive=10d max_size=100m; 47 | 48 | server { 49 | listen 8080; 50 | server_name localhost; 51 | 52 | access_log logs/nginx-http_access.log main; 53 | 54 | location /filtered { 55 | sorted_querysting_filter_parameter v _ time b; 56 | 57 | proxy_set_header Host "static_files_server"; 58 | proxy_pass http://localhost:8081; 59 | 60 | proxy_cache zone; 61 | proxy_cache_key "$sorted_querystring_args"; 62 | proxy_cache_valid 200 1m; 63 | } 64 | 65 | location / { 66 | proxy_pass http://localhost:8081; 67 | 68 | proxy_cache zone; 69 | proxy_cache_key "$sorted_querystring_args"; 70 | proxy_cache_valid 200 10m; 71 | } 72 | } 73 | 74 | server { 75 | listen 8081; 76 | 77 | location / { 78 | return 200 "$args\n"; 79 | } 80 | } 81 | } 82 | ``` 83 | 84 | Variables 85 | --------- 86 | 87 | * **$sorted_querystring_args** - just list the IP considered as remote IP on the connection 88 | 89 | 90 | Directives 91 | ---------- 92 | 93 | * **sorted_querystring_filter_parameter** - list parameters to be filtered while using the `$sorted_querystring_args` variable. 94 | 95 | 96 | Installation Instructions 97 | -------------------------------------------------- 98 | 99 | [Download Nginx Stable](http://nginx.org/en/download.html) source and uncompress it (ex.: to ../nginx). You must then run ./configure with --add-module pointing to this project as usual. Something in the lines of: 100 | 101 | $ ./configure \ 102 | --add-module=../nginx-sorted-querystring-module \ 103 | --prefix=/home/user/dev-workspace/nginx 104 | $ make 105 | $ make install 106 | 107 | 108 | Running Tests 109 | ------------- 110 | 111 | This project uses [nginx_test_helper](https://github.com/wandenberg/nginx_test_helper) on the test suite. So, after you've installed the module, you can just download the necessary gems: 112 | 113 | $ cd test 114 | $ bundle install 115 | 116 | And run rspec pointing to where your Nginx binary is (default: /usr/local/nginx/sbin/nginx): 117 | 118 | $ NGINX_EXEC=../path/to/my/nginx rspec . 119 | 120 | 121 | Changelog 122 | --------- 123 | 124 | This is still a work in progress. Be the change. And take a look on the Changelog file. 125 | -------------------------------------------------------------------------------- /test/check_sorted_querystring_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('spec_helper', File.dirname(__FILE__)) 2 | 3 | describe "check sorted querystring module" do 4 | it "should expose the querystring args ordered in '$sorted_querystring_args' variable" do 5 | nginx_run_server do 6 | EventMachine.run do 7 | req = EventMachine::HttpRequest.new("#{nginx_address}/?c=3&=6&a=1&=5&b=2").get 8 | req.callback do 9 | expect(req).to be_http_status(200) 10 | expect(req.response).to be === '{"args": "c=3&=6&a=1&=5&b=2", "sorted_args": "=5&=6&a=1&b=2&c=3"}' 11 | EventMachine.stop 12 | end 13 | end 14 | end 15 | end 16 | 17 | it "should support arrays like parameters" do 18 | nginx_run_server do 19 | EventMachine.run do 20 | req = EventMachine::HttpRequest.new("#{nginx_address}/?a=2&c[]=3&=6&a=1&=5&b=2&c[]=1&c[]=2").get 21 | req.callback do 22 | expect(req).to be_http_status(200) 23 | expect(req.response).to be === '{"args": "a=2&c[]=3&=6&a=1&=5&b=2&c[]=1&c[]=2", "sorted_args": "=5&=6&a=1&a=2&b=2&c[]=1&c[]=2&c[]=3"}' 24 | EventMachine.stop 25 | end 26 | end 27 | end 28 | end 29 | 30 | it "should filter specified parameters" do 31 | nginx_run_server({filter_parameter: ["c", "_"]}) do 32 | EventMachine.run do 33 | req = EventMachine::HttpRequest.new("#{nginx_address}/?c=3&=6&a=1&=5&b=2&_=12323&c=7").get 34 | req.callback do 35 | expect(req).to be_http_status(200) 36 | expect(req.response).to be === '{"args": "c=3&=6&a=1&=5&b=2&_=12323&c=7", "sorted_args": "=5&=6&a=1&b=2"}' 37 | EventMachine.stop 38 | end 39 | end 40 | end 41 | end 42 | 43 | it "should not return error if there isn't a parameter" do 44 | nginx_run_server do 45 | EventMachine.run do 46 | req = EventMachine::HttpRequest.new("#{nginx_address}/").get 47 | req.callback do 48 | expect(req).to be_http_status(200) 49 | expect(req.response).to be === '{"args": "", "sorted_args": ""}' 50 | EventMachine.stop 51 | end 52 | end 53 | end 54 | end 55 | 56 | it "should not return error if all parameters where filtered" do 57 | nginx_run_server({filter_parameter: ["c", "_"]}) do 58 | EventMachine.run do 59 | req = EventMachine::HttpRequest.new("#{nginx_address}/?c=3&_=12323&c=7").get 60 | req.callback do 61 | expect(req).to be_http_status(200) 62 | expect(req.response).to be === '{"args": "c=3&_=12323&c=7", "sorted_args": ""}' 63 | EventMachine.stop 64 | end 65 | end 66 | end 67 | end 68 | 69 | it "should be possible use the variable as cache_key" do 70 | nginx_run_server({filter_parameter: ["c", "_"]}) do 71 | EventMachine.run do 72 | req = EventMachine::HttpRequest.new("#{nginx_address}/?c=3&=6&a=1&=5&b=2&_=12323&c=7").get 73 | req.callback do 74 | expect(req).to be_http_status(200) 75 | expect(req.response).to be === '{"args": "c=3&=6&a=1&=5&b=2&_=12323&c=7", "sorted_args": "=5&=6&a=1&b=2"}' 76 | 77 | req = EventMachine::HttpRequest.new("#{nginx_address}/?b=2&c=3&=6&=5&_=12323&c=7&a=1").get 78 | req.callback do 79 | expect(req).to be_http_status(200) 80 | expect(req.response).to be === '{"args": "c=3&=6&a=1&=5&b=2&_=12323&c=7", "sorted_args": "=5&=6&a=1&b=2"}' 81 | EventMachine.stop 82 | end 83 | end 84 | end 85 | end 86 | end 87 | 88 | it "should be possible overwrite the filter parameter list by each location" do 89 | nginx_run_server({filter_parameter: ["c", "_"], filter_parameter2: ["a", "b"]}) do 90 | EventMachine.run do 91 | req = EventMachine::HttpRequest.new("#{nginx_address}/?c=3&=6&a=1&=5&b=2&_=12323&c=7").get 92 | req.callback do 93 | expect(req).to be_http_status(200) 94 | expect(req.response).to be === '{"args": "c=3&=6&a=1&=5&b=2&_=12323&c=7", "sorted_args": "=5&=6&a=1&b=2"}' 95 | 96 | req = EventMachine::HttpRequest.new("#{nginx_address}/overwrite?c=3&=6&a=1&=5&b=2&_=12323&c=7").get 97 | req.callback do 98 | expect(req).to be_http_status(200) 99 | expect(req.response).to be === '{"args": "c=3&=6&a=1&=5&b=2&_=12323&c=7", "sorted_args": "=5&=6&_=12323&c=3&c=7"}' 100 | EventMachine.stop 101 | end 102 | end 103 | end 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /ngx_http_sorted_querystring_module.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | ngx_int_t ngx_http_sorted_querystring_pre_config(ngx_conf_t *cf); 8 | void *ngx_http_sorted_querystring_create_loc_conf(ngx_conf_t *cf); 9 | char *ngx_http_sorted_querystring_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); 10 | char *ngx_conf_set_parameters_to_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 11 | ngx_int_t ngx_http_sorted_querystring_args_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); 12 | ngx_int_t ngx_http_sorted_querystring_cmp_parameters(const ngx_queue_t *one, const ngx_queue_t *two); 13 | 14 | 15 | typedef struct { 16 | ngx_array_t *parameters_to_filter; 17 | } ngx_http_sorted_querystring_loc_conf_t; 18 | 19 | 20 | typedef struct { 21 | ngx_queue_t args_queue; 22 | } ngx_http_sorted_querystring_ctx_t; 23 | 24 | 25 | typedef struct { 26 | ngx_queue_t queue; 27 | ngx_str_t key; 28 | ngx_str_t complete; 29 | } ngx_http_sorted_querystring_parameter_t; 30 | 31 | 32 | static ngx_http_variable_t ngx_http_sorted_querystring_vars[] = { 33 | { ngx_string("sorted_querystring_args"), 34 | NULL, 35 | ngx_http_sorted_querystring_args_variable, 36 | 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, 37 | 38 | { ngx_null_string, NULL, NULL, 0, 0, 0 } 39 | }; 40 | 41 | 42 | static ngx_command_t ngx_http_sorted_querystring_commands[] = { 43 | { ngx_string("sorted_querysting_filter_parameter"), 44 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_1MORE, 45 | ngx_conf_set_parameters_to_filter, 46 | NGX_HTTP_LOC_CONF_OFFSET, 47 | offsetof(ngx_http_sorted_querystring_loc_conf_t, parameters_to_filter), 48 | NULL }, 49 | 50 | ngx_null_command 51 | }; 52 | 53 | 54 | static ngx_http_module_t ngx_http_sorted_querystring_module_ctx = { 55 | ngx_http_sorted_querystring_pre_config, /* preconfiguration */ 56 | NULL, /* postconfiguration */ 57 | 58 | NULL, /* create main configuration */ 59 | NULL, /* init main configuration */ 60 | 61 | NULL, /* create server configuration */ 62 | NULL, /* merge server configuration */ 63 | 64 | ngx_http_sorted_querystring_create_loc_conf, /* create location configuration */ 65 | ngx_http_sorted_querystring_merge_loc_conf /* merge location configuration */ 66 | }; 67 | 68 | 69 | ngx_module_t ngx_http_sorted_querystring_module = { 70 | NGX_MODULE_V1, 71 | &ngx_http_sorted_querystring_module_ctx, /* module context */ 72 | ngx_http_sorted_querystring_commands, /* module directives */ 73 | NGX_HTTP_MODULE, /* module type */ 74 | NULL, /* init master */ 75 | NULL, /* init module */ 76 | NULL, /* init process */ 77 | NULL, /* init thread */ 78 | NULL, /* exit thread */ 79 | NULL, /* exit process */ 80 | NULL, /* exit master */ 81 | NGX_MODULE_V1_PADDING 82 | }; 83 | 84 | 85 | ngx_int_t 86 | ngx_http_sorted_querystring_pre_config(ngx_conf_t *cf) 87 | { 88 | ngx_http_variable_t *var, *v; 89 | 90 | for (v = ngx_http_sorted_querystring_vars; v->name.len; v++) { 91 | var = ngx_http_add_variable(cf, &v->name, v->flags); 92 | if (var == NULL) { 93 | return NGX_ERROR; 94 | } 95 | 96 | var->get_handler = v->get_handler; 97 | var->data = v->data; 98 | } 99 | 100 | return NGX_OK; 101 | } 102 | 103 | 104 | void * 105 | ngx_http_sorted_querystring_create_loc_conf(ngx_conf_t *cf) 106 | { 107 | ngx_http_sorted_querystring_loc_conf_t *conf; 108 | 109 | conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_sorted_querystring_loc_conf_t)); 110 | if (conf == NULL) { 111 | return NULL; 112 | } 113 | 114 | conf->parameters_to_filter = NGX_CONF_UNSET_PTR; 115 | 116 | return conf; 117 | } 118 | 119 | 120 | char * 121 | ngx_http_sorted_querystring_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) 122 | { 123 | ngx_http_sorted_querystring_loc_conf_t *prev = parent; 124 | ngx_http_sorted_querystring_loc_conf_t *conf = child; 125 | 126 | ngx_conf_merge_ptr_value(conf->parameters_to_filter, prev->parameters_to_filter, NULL); 127 | 128 | return NGX_CONF_OK; 129 | } 130 | 131 | 132 | char * 133 | ngx_conf_set_parameters_to_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 134 | { 135 | char *p = conf; 136 | 137 | ngx_array_t **field; 138 | ngx_str_t *value, *s; 139 | ngx_uint_t i, j; 140 | ngx_flag_t exists; 141 | 142 | field = (ngx_array_t **) (p + cmd->offset); 143 | 144 | if (*field != NGX_CONF_UNSET_PTR) { 145 | return "is duplicate"; 146 | } 147 | 148 | *field = ngx_array_create(cf->pool, cf->args->nelts, sizeof(ngx_str_t)); 149 | if (*field == NULL) { 150 | return NGX_CONF_ERROR; 151 | } 152 | 153 | value = cf->args->elts; 154 | 155 | for (i = 1; i < cf->args->nelts; i++) { 156 | exists = 0; 157 | s = (*field)->elts; 158 | for (j = 0; j < (*field)->nelts; j++) { 159 | if ((value[i].len == s[j].len) && ngx_strncasecmp(value[i].data, s[j].data, value[i].len) == 0) { 160 | exists = 1; 161 | break; 162 | } 163 | } 164 | 165 | if (!exists) { 166 | s = ngx_array_push(*field); 167 | if (s == NULL) { 168 | return NGX_CONF_ERROR; 169 | } 170 | 171 | *s = value[i]; 172 | } 173 | } 174 | 175 | return NGX_CONF_OK; 176 | } 177 | 178 | 179 | ngx_int_t 180 | ngx_http_sorted_querystring_args_variable(ngx_http_request_t *r, ngx_http_variable_value_t *var, uintptr_t data) 181 | { 182 | ngx_http_sorted_querystring_loc_conf_t *sqlc = ngx_http_get_module_loc_conf(r, ngx_http_sorted_querystring_module); 183 | ngx_http_sorted_querystring_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_sorted_querystring_module); 184 | ngx_http_sorted_querystring_parameter_t *param; 185 | u_char *p, *ampersand, *equal, *last; 186 | ngx_queue_t *q; 187 | ngx_flag_t filter; 188 | ngx_str_t *value; 189 | ngx_uint_t i; 190 | 191 | if (r->args.len == 0) { 192 | var->len = 0; 193 | var->data = (u_char*) ""; 194 | return NGX_OK; 195 | } 196 | 197 | if (var->len > 0) { 198 | return NGX_OK; 199 | } 200 | 201 | if (ctx == NULL) { 202 | ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_sorted_querystring_ctx_t)); 203 | if (ctx == NULL) { 204 | return NGX_ERROR; 205 | } 206 | ngx_http_set_ctx(r, ctx, ngx_http_sorted_querystring_module); 207 | 208 | ngx_queue_init(&ctx->args_queue); 209 | 210 | p = r->args.data; 211 | last = p + r->args.len; 212 | 213 | for ( /* void */ ; p < last; p++) { 214 | param = ngx_pcalloc(r->pool, sizeof(ngx_http_sorted_querystring_parameter_t)); 215 | if (param == NULL) { 216 | return NGX_ERROR; 217 | } 218 | 219 | ampersand = ngx_strlchr(p, last, '&'); 220 | if (ampersand == NULL) { 221 | ampersand = last; 222 | } 223 | 224 | equal = ngx_strlchr(p, last, '='); 225 | if ((equal == NULL) || (equal > ampersand)) { 226 | equal = ampersand; 227 | } 228 | 229 | param->key.data = p; 230 | param->key.len = equal - p; 231 | 232 | param->complete.data = p; 233 | param->complete.len = ampersand - p; 234 | 235 | ngx_queue_insert_tail(&ctx->args_queue, ¶m->queue); 236 | 237 | p = ampersand; 238 | } 239 | 240 | ngx_queue_sort(&ctx->args_queue, ngx_http_sorted_querystring_cmp_parameters); 241 | } 242 | 243 | var->data = ngx_pcalloc(r->pool, r->args.len + 2); // 1 char for extra ampersand and 1 for the \0 244 | if (var->data == NULL) { 245 | return NGX_ERROR; 246 | } 247 | 248 | p = var->data; 249 | for (q = ngx_queue_head(&ctx->args_queue); q != ngx_queue_sentinel(&ctx->args_queue); q = ngx_queue_next(q)) { 250 | param = ngx_queue_data(q, ngx_http_sorted_querystring_parameter_t, queue); 251 | 252 | filter = 0; 253 | if (sqlc->parameters_to_filter && (param->key.len > 0)) { 254 | value = sqlc->parameters_to_filter->elts; 255 | for (i = 0; i < sqlc->parameters_to_filter->nelts; i++) { 256 | if ((param->key.len == value[i].len) && ngx_strncasecmp(param->key.data, value[i].data, param->key.len) == 0) { 257 | filter = 1; 258 | break; 259 | } 260 | } 261 | 262 | } 263 | 264 | if (!filter) { 265 | p = ngx_sprintf(p, "%V&", ¶m->complete); 266 | } 267 | } 268 | 269 | var->len = (p > var->data) ? p - var->data - 1 : 0; 270 | 271 | return NGX_OK; 272 | } 273 | 274 | 275 | ngx_int_t 276 | ngx_http_sorted_querystring_cmp_parameters(const ngx_queue_t *one, const ngx_queue_t *two) 277 | { 278 | ngx_http_sorted_querystring_parameter_t *first, *second; 279 | ngx_int_t rc; 280 | 281 | first = ngx_queue_data(one, ngx_http_sorted_querystring_parameter_t, queue); 282 | second = ngx_queue_data(two, ngx_http_sorted_querystring_parameter_t, queue); 283 | 284 | rc = ngx_strncasecmp(first->key.data, second->key.data, ngx_min(first->key.len, second->key.len)); 285 | if (rc == 0) { 286 | rc = ngx_strncasecmp(first->complete.data, second->complete.data, ngx_min(first->complete.len, second->complete.len)); 287 | if (rc == 0) { 288 | rc = -1; 289 | } 290 | } 291 | 292 | return rc; 293 | } 294 | --------------------------------------------------------------------------------