Nginx misbehaviour in conjunction with non-ASCII characters

Found a bug in implementation of MOVE and COPY (webdav) methods. It
happens
if destination header contains non-ASCII characters (that need to be
escaped
with “%”).

An example:

Rename (=MOVE) file “/TheCore.ogm” to
“/The_Core.ogm”:

Request header:
→ MOVE http://andinas/TheCore.ogm HTTP/1.1
→ Destination: http://andinas/The_Core.ogm
Response header:
→ HTTP/1.1 204 No Content
→ Server: nginx/1.5.6
Result: File renamed to “The_Core.ogm”. Fine!

Now rename (=MOVE) file “/andinas/The_Core.ogm” to
“/andinas/The_ Core.ogm” (notice the blank after the
underscore,
but the same is true for äöüß and the like!):

Request header:
→ MOVE http://andinas/The_Core.ogm HTTP/1.1
→ Destination: http://andinas/The_%20Core.ogm
Response header:
→ HTTP/1.1 204 No Content
→ Server: nginx/1.5.6
Result: File renamed to “The_%20Core.ogm”. Not so fine!

The escaped blank is treated by nginx MOVE as if it was not escaped!

Found a similar issue with this config-file line:
→ if ( $http_destination ~ https?://[^/]+/(.*) ) { set $httpdest
http://localhost:8008/$remote_user/$1; }

(for this (working!) code that makes the destination header ready for
proxy_pass to another webdav server with user dependent base folders
(pyWebDav allows only one user :-/):
→ proxy_set_header Destination $httpdest;
→ proxy_pass http://127.0.0.1:8008/$remote_user$request_uri;
).

Here again, if $http_destination contains the perfectly correct escaped
characters from the webdav client, the resulting $httpdest will
additionally
escape the “escape” characters.

Example:
Destination File = “/The_ Core.ogm”
$http_destination = “http://andinas/The_%20Core.ogm” (correct)
$httpdest = “http://andinas/The_%2520Core.ogm” (wrong!)

Obviously here the highly undesirable transformation happens during the
regex matching. But why? Can I switch that off somehow?

Workaround: Use perl function to replace “%25” by “%”. Use the
undocumented
“r->variable()” for that!

If you are still reading :-): There are two very funny things about the
→ proxy_pass http://127.0.0.1:8008/$remote_user$request_uri;
line:

  1. You cannot use localhost here. nginx needs a resolver as soon as
    there is
    a variable in the string. And at least I didn’t manage to find a
    resolver
    that can resolve localhost.
  2. Nowhere on the internet it’s been said, that you need to add
    $request_uri
    to that line, but you do need! And it even must not be $uri, because
    that
    one has the same escaping issues like the other things I mentioned
    above.

OK, now I’m done ;-). Please help!

best regards
ako673de

Posted at Nginx Forum:

On Wed, Dec 18, 2013 at 06:27:31PM -0500, ako673de wrote:

→ Destination: http://andinas/The_Core.ogm
→ MOVE http://andinas/The_Core.ogm HTTP/1.1
→ Destination: http://andinas/The_%20Core.ogm
Response header:
→ HTTP/1.1 204 No Content
→ Server: nginx/1.5.6
Result: File renamed to “The_%20Core.ogm”. Not so fine!

The escaped blank is treated by nginx MOVE as if it was not escaped!

I have a patch for that, would you like to try it?

Workaround: Use perl function to replace “%25” by “%”. Use the undocumented
“r->variable()” for that!

Don’t use rewrite. nginx’s DAV module supports relative URLs.

Using the following config snippet:

http {
server {
listen 8000;

    location / {
        proxy_set_header Destination /$http_user$http_destination;
        proxy_pass http://127.0.0.1:8001/$http_user$request_uri;
    }
}

}

and the following request:

printf ‘MOVE /foo%%20bar HTTP/1.1\r\nDestination: /bar%%20baz\r\nHost:
127.0.0.1\r\nUser: nobody\r\n\r\n’ | nc 127.0.0.1 8000

I get:

$ nc -l 8001
MOVE /nobody/foo%20bar HTTP/1.0
Destination: /nobody/bar%20baz
Host: 127.0.0.1:8001
Connection: close
User: nobody

On Wed, Dec 18, 2013 at 06:27:31PM -0500, ako673de wrote:
Found a bug in implementation of MOVE and COPY (webdav) methods. It
happens if destination header contains non-ASCII characters (that need
to be
escaped with “%”).

I have a patch for that, would you like to try it?

Well, currently I need to work around the lack of LOCK features with
another
WebDav server (see below) anyway. Therefore I can live without patching
nginx. For me it would be perfectly alright to have it in the next
release.
But maybe someone else out there might need it more urgently…?

Found a similar issue with this config-file line:
→ if ( $http_destination ~ https?://[^/]+/(.*) ) { set $httpdest
http://localhost:8008/$remote_user/$1; }

Don’t use rewrite. nginx’s DAV module supports relative URLs.

With “proxy_set_header Destination /$http_user$http_destination;” …
… I get “Destination: /http://host:port//\r\n” …
… which of course is wrong!

The reason for your snippet giving correct results is simply that your
“printf | nc” is wrong! Webdav clients unfortunately often (or always?)
have
the “http://host:port/” part included in the destination header.

And then you can’t simply add strings together any more but need to
separate
host and path parts in order to insert the user part. I simply don’t
know of
another way to do so except “regex rewrite”. Other ideas?

best regards
ako673de

Posted at Nginx Forum: