Re: Проблема с двойным кодированием заголовка при проксировании

Maxim Dounin mdounin на mdounin.ru
Ср Авг 31 17:58:47 UTC 2016


Hello!

On Wed, Aug 31, 2016 at 06:54:08PM +0300, Alexander Gerasiov wrote:

> Использую nginx в качестве forward proxy к apache с mod_svn.
> 
> Известный трюк: для работы методов COPY MV требуется переписывать
> заголок Destination, чтобы поменять схему с https на http
> 
>         set $fixed_destination $http_destination;
>         if ($http_destination ~* ^https(.*)$)
>         {
>                 set $fixed_destination http$1;
>         }
>         location / {
>                 proxy_pass              http://svn:80/;
>                 proxy_set_header        Destination        $fixed_destination;
> 		proxy_set_header        Host     $host;
> 
>         }
> 
> 
> Это работает, но есть нюанс: если перемещаемый файл содержит
> кириллицу в имени, то он оказывается дважды перекодирован:
> 
> Приходит в nginx он в виде:
> 
> Destination: https://host/test/!svn/txr/1-1/dir2/%D1%80%D1%83%D1%81%D1%81%D0%BA%D0%B8%D0%B9
> 
> Дальше выделяется при помощи регулярного выражения, конструируется
> новый заголовок, оперируя всё еще закодированной строкой.
> 
> И дальше nginx _еще_раз_ кодирует этот заголовок при отправке, так
> что в apache уже приходит
> 
> Destination: http://host/test/!svn/txr/1-1/dir2/%25D1%2580%25D1%2583%25D1%2581%25D1%2581%25D0%25BA%25D0%25B8%25D0%25B9
> 
> 
> В результате имя файла при копировании/перемещении бьется.
> 
> Вопрос: что с этим делать? Как сказать nginx, чтобы он не кодировал заголовок
> при помощи urlencode? Или может как декодировать строку перед передачей
> ее в proxy_set_header?

Проблема в том, что "set" при использовании переменных $1 .. $9 
делает ровно то же, что и rewrite, а именно - экранирует 
специальные символы, предполагая, что исходной строкой был URI 
запроса без экранировки.  При этом nginx знает, были ли в URI 
запроса специальные символы, и если их не было - то экранирование 
не делается.

Это, очевидно, баг, и когда нибудь его исправят, но пока этого не 
случилось (в первую очередь потому, что там очень легко что-нибудь 
сломать неосторожным движением).

Простейшее решение на текущий момент - вместо $1 использовать 
именованные выделения, т.е. так:

        if ($http_destination ~* ^https(?<foo>.*)$)
        {
                set $fixed_destination http$foo;
        }

Тогда экранирование делаться не будет в любом случае, и всё будет 
работать как ожидается.

-- 
Maxim Dounin
http://nginx.org/



Подробная информация о списке рассылки nginx-ru