Server Fault is a question and answer site for system and network administrators. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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.
share|improve this question
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". – Michael Hampton Nov 5 '12 at 16:52
    
@MichaelHampton yes, you are right, but if it's possible I don't want to make it even less maintainable – Carlos Campderrós Nov 6 '12 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? – Carlos Campderrós Nov 12 '12 at 9:39

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!

share|improve this answer
    
If you're still interested, see my answer above/below, and/or at stackoverflow.com/questions/28684300/…. – cnst Jun 3 at 8:46

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.

http://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.)

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.