10

I am using nginx as reverse proxy and I have 2 rules like:

location ~ ^/indirect {
  rewrite ^/indirect(.*) /foobar$1;
}

location ~ ^/foobar {
  set $url http://example.com/something/index.php?var1=hello&access=$scheme://$host$uri;
  proxy_pass $url;
}

So, as you can see I'm passing the $uri variable as a parameter to the proxied page (the $uri variable is an nginx one, see the http core module documentation).

Problem is, if I visit http://example.com/foobar/hello%20world, the $uri variable contains /foobar/hello world (as you see, the %20 has been substituted with its url-decoded value, a space). And then, nginx returns http status code 400 (bad request) before executing the proxy_pass line (the backend there is not contacted).

There is also available the variable $request_uri, which holds the original request URI as issued by the client, so in this case it would hold the correct value, with the %20 sequence. But I cannot use this because if a client goes through the /indirect path, $request_uri would contain /indirect/... while I want that the access param passed to the backend be always /foobar/....

There are multiple indirect-like rules (this is for a DAV/calDAV/cardDAV server, and there are multiple clients out there that connect to multiple paths, so I need these indirect-like rules), so it is not feasible to do the proxy_pass there, and there are clients that go directly to the /foobar path.

So is there any way to get $uri without url-decoding it?

possible things that aren't acceptable:

  • sending the request double-encoded, as the request may come from a client I have no control of
  • specifying the final url multiple times in eacy indirect rule and also in the "direct" one, as it causes maintenance problems.
3
  • 1
    This whole setup is its own maintenance problem. So I'm not sure why you would reject a solution because it might be a "maintenance problem". Nov 5, 2012 at 16:52
  • @MichaelHampton yes, you are right, but if it's possible I don't want to make it even less maintainable Nov 6, 2012 at 8:15
  • Finally I ended doing external 301 redirects from all the indirect uri's to the direct one, and only having the proxy_pass there. Should I answer my own question with this workaround? Nov 12, 2012 at 9:39

2 Answers 2

3

With nginx/1.2.1, I wasn't able to reproduce your issue of %20, once decoded into a space, of causing any 400 Bad Request within nginx; perhaps that's coming from upstream?

Regardless, it is actually not that difficult to use the finite-state automaton that is provided through the rewrite directive to stop $uri from containing the decoded request, yet still perform all sorts of transformations of the request.

https://stackoverflow.com/questions/28684300/nginx-pass-proxy-subdirectory-without-url-decoding/37584637#37584637

The idea is that when you change $uri in place, it doesn't get re-decoded. And, as you know, we do already have the undecoded one in $request_uri. What's left is to simply set one to the other, and call it a day.

server {
    listen 2012;
    location /a {
        rewrite ^/a(.*) /f$1 last;
    }
    location /i {
        rewrite ^ $request_uri;
        rewrite ^/i(.*) /f$1 last;
        return 400; #if the second rewrite won't match
    }
    location /f {
        set $url http://127.0.0.1:2016/s?v=h&a=$scheme://$host$uri;
        proxy_pass $url;
    }
}

server {
    listen 2016;
    return 200 $request_uri\n;
}

And, yes, the rewrite ^ $request_uri; part above does do the trick:

% echo localhost:2012/{a,i,f}/h%20w | xargs -n1 curl
/s?v=h&a=http://localhost/f/h w
/s?v=h&a=http://localhost/f/h%20w
/s?v=h&a=http://localhost/f/h w
%

(If you want the "direct" thing to not be decoded, either, then it'll probably be easiest to just make it an "indirect" as well.)

2

The only way I found is to use the HttpSetMiscModule like so:

location ~
 ^/indirect {
  set_escape_uri $key $1;
  rewrite ^/indirect(.*) /foobar$key;
}

location ~ ^/foobar {
  set_escape_uri $key $uri;
  set $url http://example.com/something/index.php?var1=hello&access=$scheme://$host$key;
  proxy_pass $url;
}

If anyone knows of a better way (without having to compile nginx with external modules, because I don't have root access) please let me know!

1

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .