Подтвердить что ты не робот

Как заблокировать несколько проходов mod_rewrite (или бесконечных циклов) в контексте .htaccess

Я работаю над сайтом, работающим на общем сервере Apache v2.2, поэтому вся конфигурация осуществляется через файлы .htaccess, и я хотел использовать mod_rewrite для сопоставления URL-адресов в файловой системе менее чем полностью прямолинейно, Например, ради, скажем, что я хотел сделать следующее:

  • URL-адрес карты www.mysite.com/Alice в папку файловой системы /public_html/Bob
  • URL-адрес карты www.mysite.com/Bob в папку файловой системы /public_html/Alice

Теперь, после нескольких часов, тщательно прорабатывайте набор правил (настоящий, а не Алиса/Боб один!) Я поместил все свои тщательно обработанные правила перезаписи в файл .htaccess в /public _html и протестировал его... только для получения ошибки сервера 500!

Я был пойман хорошо документированным "gotcha!". в Apache: Когда правила mod_rewrite используются внутри файла .htaccess, повторно написанный URL повторно отправляется на другой раунд обработки (как если бы это был внешний запрос). Это происходит так, что любые правила в целевой папке повторно написанного запроса могут быть применены, но это может привести к некоторому очень противоречивому поведению веб-сервера!

В приведенном выше примере это означает, что запрос для www.mysite.com/Alice/foo.html переписывается на /Bob/foo.html, а затем повторно отправляется (внутренне) на сервер в качестве запроса для www.mysite.com/Bob/foo.html. Затем он перезаписывается обратно на /Alice/foo.html и повторно отправляется, что заставляет его повторно переписать на /Bob/foo.html и т.д.; возникает бесконечный цикл... ломается только ошибкой таймаута сервера.


Вопрос в том, как обеспечить, чтобы набор правил .htaccess mod_rewrite только применялся ONCE?


Флаг [L] в RewriteRule останавливает дальнейшую переписывание во время одного прохода через набор правил, но не останавливает повторное применение всего набора правил после повторного отправки повторно написанного URL-адреса на сервер. Согласно документации, Apache v2.3.9 + (в настоящее время в бета-версии) содержит флаг [END], который обеспечивает именно эту функциональность. К сожалению, веб-хост все еще использует Apache 2.2, и они отклонили мой вежливый запрос на обновление до бета-версии!

Необходимым является обходное решение, которое обеспечивает аналогичную функциональность для флага [END]. Моя первая мысль заключалась в том, что я мог бы использовать переменную окружения: Установите флаг во время первого переписывающего прохода, который будет передавать последующие пропуски, чтобы больше не переписывать. Если бы я назвал переменную "END" моего флага, код мог бы выглядеть так:

#  Prevent further rewriting if 'END' is flagged
RewriteCond %{ENV:END} =1
RewriteRule .* - [L]

#  Map /Alice to /Bob, and /Bob to /Alice, and flag 'END' when done
RewriteRule ^Alice(/.*)?$ Bob$1 [L,E=END:1]
RewriteRule ^Bob(/.*)?$ Alice$1 [L,E=END:1]

К сожалению, этот код не работает: после нескольких экспериментов я обнаружил, что переменные окружения не переживают процесс повторной отправки переписанного URL-адреса на сервер. Последняя строка на этой странице документации Apache предполагает, что переменные среды должны пережить внутренние переадресации, но я обнаружил, что это не так.

[ EDIT: На некоторых серверах он работает. Если это так, это лучшее решение, чем ниже. Вы должны попробовать это для себя на своем собственном сервере, чтобы видеть.]

Тем не менее, общая идея может быть спасена. После многих часов вытягивания волос и некоторых советов коллеги я понял, что заголовки HTTP-запросов сохраняются во внутренних переадресациях, поэтому, если бы я мог хранить мой флаг в одном из них, он мог бы работать!


Здесь мое решение:


# This header flags that there no more rewriting to be done.
# It a kludge until use of the END flag becomes possible in Apache v2.3.9+
# ######## REMOVE this directive for Apache 2.3.9+, and change all [...,L,E=END:1]
# ######## to just [...,END] in all the rules below!

RequestHeader set SPECIAL-HEADER-STOP-FURTHER-REWRITES-kjhsdf87653vasj 1 env=END


# If our special end-of-rewriting header is set this rule blocks all further rewrites.
# ######## REMOVE this directive for Apache 2.3.9+, and change all [...,L,E=END:1]
# ######## to just [...,END] in all the rules below!

RewriteCond %{HTTP:SPECIAL-HEADER-STOP-FURTHER-REWRITES-kjhsdf87653vasj} =1 [NV]
RewriteRule .* - [L]


#  Map /Alice to /Bob, and /Bob to /Alice, and flag 'END' when done

RewriteRule ^Alice(/.*)?$ Bob$1 [L,E=END:1]
RewriteRule ^Bob(/.*)?$ Alice$1 [L,E=END:1]

... и это сработало! Вот почему: Внутри файла .htaccess директивы, связанные с различными модулями apache, выполняются в порядке модуля, определенном в основной конфигурации Apache (или, что я понимаю, в любом случае...). В этом случае (и критически для успеха этого решения) mod_headers был установлен для выполнения после mod_rewrite, поэтому директива RequestHeader запускается после правил перезаписи. Это означает, что заголовок SPECIAL-HEADER-STOP-FURTHER-REWRITES-kjhsdf87653vasj добавляется в HTTP-запрос, если соответствие RewriteRule с [E = END: 1] в списке флагов. На следующем проходе (после повторной отправки запроса на сервер) первый RewriteRule обнаруживает этот заголовок и прерывает любую последующую переписывание.

Некоторые вещи, которые следует обратить внимание на это решение:

  • Он не будет работать, если Apache настроен на запуск mod_headers до mod_rewrite. (Я не уверен, что это возможно, или если так, как это необычно).

  • Если внешний пользователь включает заголовок SPECIAL-HEADER-STOP-FURTHER-REWRITES-kjhsdf87653vasj в свой HTTP-запрос на сервер, он отключит всеПравила перезаписи URL, и этот пользователь увидит структуру каталогов файловой системы "как есть". Это причина случайной строки символов ascii в конце имени заголовка - это сделать заголовок трудно угадать. Является ли это функцией или уязвимостью безопасности, зависит от вашей точки зрения!

  • Идея здесь была обходным способом, чтобы имитировать использование флага [END] в версиях Apache, которые еще не имеют его. Если бы вы только хотели, чтобы ваш набор правил выполнялся только один раз, независимо от того, какие правила запускаются, вы, вероятно, можете отказаться от использования переменной окружения "END" и просто сделать это:

    RewriteCond %{HTTP:SPECIAL-HEADER-STOP-FURTHER-REWRITES-kjhsdf87653vasj} =1 [NV]
    RewriteRule .* - [L]
    
    RequestHeader set SPECIAL-HEADER-STOP-FURTHER-REWRITES-kjhsdf87653vasj 1
    
    #  Map /Alice to /Bob, and /Bob to /Alice
    RewriteRule ^Alice(/.*)?$ Bob$1 [L]
    RewriteRule ^Bob(/.*)?$ Alice$1 [L]
    

    Или даже лучше, это (хотя переменные REDIRECT_ * плохо документированы в документации Apache v2.2 - они, кажется, упоминаются только здесь) - поэтому я не могу гарантировать, что он будет работать на всех версиях Apache):

    RewriteCond %{ENV:REDIRECT_STATUS} !^$
    RewriteRule .* - [L]. 
    
    #  Map /Alice to /Bob, and /Bob to /Alice
    RewriteRule ^Alice(/.*)?$ Bob$1 [L]
    RewriteRule ^Bob(/.*)?$ Alice$1 [L]
    

    Однако, как только вы запускаете Apache v2.3.9 +, я ожидаю, что использование флага [END] будет более эффективным, чем вышеупомянутое решение, потому что (предположительно) оно вообще избегает переписанного URL-адреса, повторно переданного на сервер для повторного переписывания.

    Обратите внимание, что вы также можете заблокировать переписывание подзапросов, и в этом случае вы можете RewriteCond использовать правило "не делать-еще-переписывать", например:

    RewriteCond %{ENV:REDIRECT_STATUS} !^$ [OR]
    RewriteCond %{IS_SUBREQ} =true
    RewriteRule .* - [L]
    
  • Идея здесь была обходным решением для замены использования флага [END] в версиях Apache, которые еще не имеют его. Но на самом деле вы можете использовать этот общий подход для хранения не только одного флага - вы можете хранить произвольные строки или числа, которые будут сохраняться во внутреннем перенаправлении сервера, и разрабатывать правила перезаписи, чтобы зависеть от них на основе любого из условий теста RuleCond обеспечивает. (Я не могу, с головы до головы, подумать о причине, почему вы хотите это сделать... но эй, чем больше гибкости и контроля у вас есть, тем лучше, правда?)


Я думаю, кто-нибудь, кто зачитал это, выяснил, что я не задаю здесь вопрос. Это больше зависит от того, как я нашел свое решение проблемы, которую у меня было, и хочу опубликовать ее здесь для справки, если кто-то другой столкнулся с той же проблемой. Это большая часть того, для чего этот веб-сайт, правильно?

...

Но так как это должен быть форум вопросов и ответов, я спрошу:

  • Может ли кто-нибудь увидеть какие-либо потенциальные проблемы с этим решением (кроме тех, о которых я уже упоминал)?
  • Или у кого-то есть лучший способ добиться того же самого?
4b9b3361

Ответ 1

В зависимости от вашей сборки Apache это условие может работать (добавьте его в правило "stop-rewriting": т.е. RewriteRule .* - [L].. или просто для конкретного проблемного правила):

RewriteCond %{ENV:REDIRECT_STATUS} ^$

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

К сожалению, он работает на некоторых системах и не работает на других, и я лично не знаю, что несет ответственность за его работу.

Кроме этого, наиболее распространенным является добавление условия перезаписи для проверки исходного URL, например, путем разбора переменной %{THE_REQUEST}, например. RewriteCond %{THE_REQUEST} ^[A-Z]+\s.+\.php\sHTTP/.+ - но это имеет смысл только для отдельных проблемных правил.

В общем - вам следует избегать таких ситуаций "переписывайте A → B, а затем B → A" (я уверен, что вы это знаете).

Что касается вашего собственного решения - "не исправляйте, если он не сломан" - если он работает, тогда это здорово, поскольку я не вижу серьезных проблем с таким подходом.

Ответ 2

Я не слишком уверен, зачем вам это нужно, но я бы предложил пару вещей для пользователей, которые работают в такой ситуации:

  • Как насчет переименования папки Bob в Alice и наоборот? Тогда Apache не должен ничего с ними делать.

  • Если это важно для вашего приложения, вы могли бы просто преобразовать приложение. обнаружить Боба и Элис и просто поменять их в своем приложении. вместо этого?

В PHP это будет примерно так:

if($path == "Bob") {
  $path = "Alice";
}
else if($path == "Alice") {
  $path = "Bob";
}

Готово.

В противном случае может быть полезно добавить другую подпапку. Итак/Боб становится/a/Алиса и/Алиса становятся /b/Bob. Затем вы устраните путаницу. Это также можно сделать с помощью другого параметра (строки запроса), который более или менее то, что вы делаете, установив переменную среды, которую вы тестируете в своем .htaccess.

Ответ 3

Переменные, установленные RewriteRule (тот, который изменил путь), доступны в "следующем раунде" ( "внутренний переадресация" ) с префиксом REDIRECT_ prepanded. Итак, ваш первый фрагмент кода должен выглядеть так:

RewriteCond %{ENV:REDIRECT_END} =1
RewriteRule .* - [L]

Это работает для меня с apache 2.4.10.