Экранирование слэша (%2F) и WSGI

Если так случилось, что идентификаторы в вашем приложении содержат “слэш” (“/”), и вы решили написать для него веб-интерфейс на базе WSGI, скорее всего, у вас серьезные проблемы.

В одном из моих проектов - Milk Price Report - есть такая сущность как “продукт”. Ключи к объектам продуктов допускают наличие в них “слэша”. Например, продукт

Молоко Красная Цена у/паст. 3.2% 1000г

содержит в названии (и в ключе ZODB, так как у меня ключ=название) “слэш”. Недавно я решил начать писать веб-интерфейс для этого проекта на Pyramid. Так как Pyramid из коробки работает на WSGI-сервере Waitress, я столкнулся с крайне неприятной проблемой - адрес с экранированным "слэшем"

http://localhost:6543/Product/Молоко Красная Цена у%2Fпаст. 3.2% 1000г

не ведет на нужный ресурс (я использую traversal) и выдается ошибка 404.

Получается что %2F распознается как обычный “слэш” в адресе и система ищет ресурс http://localhost:6543/Product/Молоко Красная Цена у, которого, конечно же, в базе нет.

После длительных поисков решения проблемы без замены ключей в базе я выяснил следующие факты:

  • декодирование %2F происходит на уровне WSGI-сервера, а не на уровне фреймворка (CherryPy, Pyramid, Django, Flask)
  • это поведение досталось WSGI “в наследство” от CGI-спецификации, требующей декодирования PATH_INFO
  • предложение об исправлении этого поведения добавлено в WSGI 2.0
  • элегантного решения нет

Что можно сделать?

  • В настройках Apache есть опция AllowEncodedSlashes, но я, увы, работаю не с Apache
  • Двойное кодирование. Если честно, я так и не понял как это реализовать в моем проекте. Вложенное использование urlib.quote(urlib.quote(title)) привело к неудобоваримому результату, мягко говоря.
  • Не допускать “слэш” в идентификаторе

К счастью, в моем проекте продукт с проблемным названием один, и я легко могу заменить его ключ. Поэтому я решил включить замену слэшей на дефисы в процесс генерации ключа.

Но я бы не позавидовал себе, если бы ключей со слэшем было больше!

Note

Буду благодарен любым дополнениям или корректировкам по теме этой статьи.

UPD: Для эксперимента я задал slug к этой статье в виде %2F. Если на локальном хосте под управлением http-server урл http://localhos:8080/%2F.html открывался нормально, то в продакшн-окружении на NGINX снова всплыла эта же проблема. NGINX тоже декодирует %2F и опции для отключения этого вроде как нет. Пришлось переименовать slug в "forward_slash_wsgi" :)

comments powered by Disqus