如何阻止多个mod_rewrite的通行证(或无限循环)在htaccess的背景下 [英] How to block multiple mod_rewrite passes (or infinite loops) in a .htaccess context

查看:241
本文介绍了如何阻止多个mod_rewrite的通行证(或无限循环)在htaccess的背景下的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我工作在一个共享的Apache V2.2服务器上运行的网站,这样所有的配置是通过.htaccess文件,我想使用mod_rewrite映射在低于完全,直接的方式的URL到文件系统。只是举例的缘故,让我们说,我想要做的是这样的:

  • 地图网址 www.mysite.com/Alice 对文件系统的文件夹 /的public_html /鲍勃·
  • 地图网址 www.mysite.com/Bob 对文件系统的文件夹 /的public_html /爱丽丝

现在,经过数个小时的工作精心设计的规则集(真钞,不翘/鲍勃之一!)我把在/的public_html .htaccess文件我都精心打造的重写规则,并测试了它......只得到500服务器错误!

我被抓出一个有据可查的疑难杂症!在Apache的:当mod_rewrite规则用于内部.htaccess文件,重新编写的网址是重新提交的新一轮处理(就好像它是一个外部请求)。出现这种情况,以便在重新写入请求的目标文件夹的任何规则可以适用,但它可能会导致通过网络服务器一些非常反直觉的行为!

在上面的例子中,这意味着,对于 www.mysite.com/Alice/foo.html 被改写为 /鲍勃请求/ foo.html ,然后重新提交(内部)到服务器的 www.mysite.com/Bob/foo.html 的请求。这是然后重新改写回 /Alice/foo.html 并重新提交,这会导致它被重新重新改写为 /鲍勃/ foo.html ,依此类推;一个无限循环随之而来......只有一台服务器超时错误中断。


的问题是,如何保证一个的.htaccess mod_rewrite的规则集只被应用一次?


在[L]标志的重写规则将停止所有进一步改写的在一个单一的闯关规则集的,但是的的阻止被重新应用整个规则集后重新写入URL被重新提交到服务器。根据该文件,阿帕奇v2.3.9 +(目前处于测试阶段)包含一个[结束]标志,提供precisely此功能。不幸的是,虚拟主机仍然使用Apache 2.2,和他们拒绝我彬彬有礼的要求升级到公测版本!

我们需要的是一个解决方法,它提供的功能类似于[结束]标志。我首先想到的是,我可以使用一个环境变量:第一个重写通将告诉以后的通行证做任何进一步的改写过程中设置一个标志。如果我打电话给我的标志变量END中,code可能是这样的:

 #prevent进一步改写,如果END被标记
的RewriteCond%{ENV:END} = 1
重写规则。*  -  [L]

#地图/爱丽丝/鲍勃,和/鲍勃/爱丽丝,和标记END完成时
重写规则^爱丽丝(/.*)?$鲍勃$ 1 [L,E = END:1]
重写规则^鲍勃(/.*)?$爱丽丝$ 1 [L,E = END:1]
 

Unforunately这code不起作用:经过一些实验,我发现环境变量不重新提交URL重写的过程中生存下来到服务器。在此Apache文档页面最后一行表明,环境变量应该的生存内部重定向,但我发现,不是这样的。

[编辑:在某些服务器上,它的确实的工作。如果是这样,它比如下下面更好的解决方案。你必须自己试一试自己的服务器上看到的。]

不过,总体思路可以废物利用。许多个小时的拔头发,有的建议从同事后,我意识到,HTTP请求头的的pserved跨内部重定向,所以如果我可以存储我的标志,其中之一$ P $,它可能工作!


下面是我的解决办法:


 #这头标志,有没有更多的改写工作要做。
#这是一个杂牌组装电脑中的Apache v2.3.9直到使用结束标志成为可能+
#########删除此指令为Apache 2.3.9+,并改变所有的[...,L,E = END:1]
#########刚刚[...,结束]下面所有的规则!

RequestHeader设置特殊的头-STOP-进一步-重写-kjhsdf87653vasj 1 ENV = END


#如果我们的特别结束的重写标头设置此规则将阻止所有进一步重写。
#########删除此指令为Apache 2.3.9+,并改变所有的[...,L,E = END:1]
#########刚刚[...,结束]下面所有的规则!

的RewriteCond%{HTTP:特殊头-STOP-进一步-重写-kjhsdf87653vasj} = 1 [NV]
重写规则。*  -  [L]


#地图/爱丽丝/鲍勃,和/鲍勃/爱丽丝,和标记END完成时

重写规则^爱丽丝(/.*)?$鲍勃$ 1 [L,E = END:1]
重写规则^鲍勃(/.*)?$爱丽丝$ 1 [L,E = END:1]
 

...而且,它的工作!这里的原因:内部.htaccess文件,与各种Apache模块相关的指令在执行的的模块顺序的主Apache配置定义(或者,这是我的理解,反正...)。在此情况下(和批判对这一解决方案的成功)mod_headers中设置的mod_rewrite后执行,所以RequestHeader指令被执行的的重写规则。这意味着在 SPECIAL头-STOP-进一步-重写-kjhsdf87653vasj 头被添加到HTTP请求当且仅当有一个重写规则[E = END:1]在它的标志列表得到匹配。在下一阶段(后重新写入请求重新提交给服务器)的第一个重写规则检测到这个标题,并中止任何进一步重写。

有些事情需要注意该解决方案是:

  1. 如果Apache配置为运行mod_headers中的的mod_rewrite的它不会工作。 (我不知道这甚至有可能,如果是这样,如何​​不寻常的它会是)。

  2. 如果外部用户包括 SPECIAL头-STOP-进一步-重写-kjhsdf87653vasj 报头中的HTTP请求到服务器,它会禁用所有 URL重写规则,并且用户将看到文件系统的目录结构原样。这对随机字符串在头名的末尾ascii字符的原因 - 这是使头很难猜测。这是否是一个功能或安全漏洞,取决于你的观点!

  3. 这里的想法是一种变通方法来模拟使用[结束]标志的Apache的版本还没有拥有它。如果你想要的是,以确保您的规则集只运行一次,无论哪个规则被触发,那么你很可能跌落使用END环境变量,只是这样做:

     的RewriteCond%{HTTP:特殊头-STOP-进一步-重写-kjhsdf87653vasj} = 1 [NV]
    重写规则。*  -  [L]
    
    RequestHeader设置特殊的头-STOP-进一步-重写-kjhsdf87653vasj 1
    
    #地图/爱丽丝/鲍勃,和/鲍勃/爱丽丝
    重写规则^爱丽丝(/.*)?$鲍勃$ 1 [L]
    重写规则^鲍勃(/.*)?$爱丽丝$ 1 [L]
     

    甚至更好,这(虽然REDIRECT_ *变量都记录在Apache V2.2 documetation很差 - 他们似乎只提及的这里) - 所以我不能保证它会在所有的版本的Apache):

     的RewriteCond%!{ENV:REDIRECT_STATUS} ^ $
    重写规则。*  -  [L]。
    
    #地图/爱丽丝/鲍勃,和/鲍勃/爱丽丝
    重写规则^爱丽丝(/.*)?$鲍勃$ 1 [L]
    重写规则^鲍勃(/.*)?$爱丽丝$ 1 [L]
     

    不过,一旦你运行Apache v2.3.9 +,我想到的是使用[结束]标记会比上面的解决方案更有效,因为(presumably)它完全避免了重写URL为重另一个重写通提交到服务器。

    请注意,您可能还需要阻止重写子请求,在这种情况下,您可以在的RewriteCond 的鸵鸟政策DO-任何-更重写规则,像这样的:

     的RewriteCond%{ENV:REDIRECT_STATUS}!^ $ [OR]
    的RewriteCond%{IS_SUBREQ} =真
    重写规则。*  -  [L]
     

  4. 这里的想法是一种变通方法,以取代使用[结束]标志的Apache的版本还没有拥有它。但实际上,你可以使用这个通用的方法来存储更多的不仅仅是一个标志 - 你可以存储任意字符串或数字,将持续整个内部服务器重定向,并设计你的重写规则基于任何的测试条件依赖于它们RuleCond提供。 (我不能把我的头顶部,想出一个理由的为什么的你会想这样做......但是,嘿,更多的灵活性和控制你有更好的,正确的?)


我猜是谁读到这里,已经想通了,我真的不问一个问题在这里任何人。它更是我已经找到了我自己的解决问题的办法我有,并希望将它张贴在了这里,以防别人参考的问题遇到了同样的问题。这是什么这个网站是一个重要组成部分,对吧?

...

但由于这的的应该是一个问题和回答的论坛上,我会问:

  • 在任何人都可以看到这个解决方案的潜在问题(比我已经提到的其他)?
  • 或者有没有人有实现同样的事情的一个更好的办法?
解决方案

根据您的Apache编译,这种情况可能工作(添加停止重写的规则:即重写规则* - [ L] ..或者只是针对特定问题的规则):

 的RewriteCond%{ENV:REDIRECT_STATUS} ^ $
 

REDIRECT_STATUS 将是空的第一个/初始重写,并有 200 (或者其他值值还有 - 有没有检查过深),在任何后续的周期

不幸的是它的工作原理在某些系统上,并没有别人,我个人不知道什么是负责制作它的工作。

除了这个最常见的就是通过解析%{THE_REQUEST} 变量,例如添加重写条件检查原始URL,例如。的RewriteCond%{THE_REQUEST} ^ [AZ] + \ S + \ PHP \ SHTTP /.+ - 但这仅仅是有道理的个别问题的规则。

在一般 - 你应该避免这样的改写A - > B,然后乙 - >甲的情况(我是pretty的一定要意识到这一点)

至于自己的解决方案 - 不解决,如果它不破。 - 如果它的工作原理则是伟大的,因为我没有看到任何重大的问题,这样的方法

I'm working on a website running on a shared Apache v2.2 server, so all configuration is via .htaccess files, and I wanted to use mod_rewrite to map URLs to the filesystem in less-than-completely-straightforward way. Just for example's sake, let's say that what I wanted to do was this:

  • Map URL www.mysite.com/Alice to filesystem folder /public_html/Bob
  • Map URL www.mysite.com/Bob to filesystem folder /public_html/Alice

Now, after several hours work carefully designing the ruleset (the real one, not the Alice/Bob one!) I put all my carefully crafted rewriting rules in a .htaccess file in /public_html, and tested it out ...only to get a 500 server error!

I'd been caught out by a well documented "gotcha!" in Apache: When mod_rewrite rules are used inside a .htaccess file, a re-written URL is re-submitted for another round of processing (as if it were an external request). That happens so that any rules in the target folder of the re-written request can be applied, but it can result in some very counter-intuitive behaviour by the webserver!

In the above example, that means that a request for www.mysite.com/Alice/foo.html gets rewritten to /Bob/foo.html, and then resubmitted (internally) to the server as a request for www.mysite.com/Bob/foo.html. This is then re-rewritten back to /Alice/foo.html and resubmitted, which causes it to get re-re-rewritten to /Bob/foo.html, and so on; an infinite loop ensues... broken only by a server timeout error.


The question is, how to ensure that a .htaccess mod_rewrite ruleset only gets applied ONCE?


The [L] flag in a RewriteRule stops all further rewriting during a single pass through the ruleset, but doesn't stop the entire ruleset from being re-applied after the re-written URL is resubmitted to the server. According to the documentation, Apache v2.3.9+ (currently in Beta) contains an [END] flag that provides precisely this functionality. Unfortunately, the web host is still using Apache 2.2, and they declined my polite request to upgrade to the beta version!

What's needed is a workaround that provides similar functionality to the [END] flag. My first thought was that I could use an environment variable: Set a flag during the first rewriting pass that would tell subsequent passes to do no further rewriting. If I called my flag variable 'END', the code might look like this:

#  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]

Unforunately this code doesn't work: After a bit of experimentation, I discovered that environment variables don't survive the process of re-submitting the rewritten URL to the server. The last line on this Apache documentation page suggests that environment variables ought to survive internal redirects, but I found that not to be the case.

[EDIT: On some servers, it does work. If so, it's a better solution than what follows below. You'll have to try it for yourself on your own server to see.]

Still, the general idea can be salvaged. After many hours of hair-pulling, and some advice from a colleague, I realised that HTTP request headers are preserved across internal redirects, so if I could store my flag in one of those, it might work!


Here's my solution:


# This header flags that there's no more rewriting to be done.
# It's 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]

...and, it worked! Here's why: Inside a .htaccess file, directives associated with various apache modules execute in the module order defined in the main Apache configuration (or, that's my understanding, anyway...). In this case (and critically for the success of this solution) mod_headers was set to execute after mod_rewrite, so the RequestHeader directive gets executed after the rewrite rules. That means the the SPECIAL-HEADER-STOP-FURTHER-REWRITES-kjhsdf87653vasj header gets added to the HTTP request iff a RewriteRule with [E=END:1] in its flag list gets matched. On the next pass (after the re-written request is resubmitted to the server) the first RewriteRule detects this header, and aborts any further rewriting.

Some things to note about this solution are:

  1. It won't work if Apache is configured to run mod_headers before mod_rewrite. (I'm not sure if that's even possible, or if so, how unusual it'd be).

  2. If an external user includes a SPECIAL-HEADER-STOP-FURTHER-REWRITES-kjhsdf87653vasj header in their HTTP request to the server, it'll disable all URL rewriting rules, and that user will see the filesystem directory structure "as-is". That's the reason for the random string of ascii characters at the end of the header name - it's to make the header hard to guess. Whether this is a feature or a security vulnerability depends on your point of view!

  3. The idea here was a workaround to mimic the use of the [END] flag in Apache versions that don't yet have it. If all you wanted was to ensure your ruleset only runs once, regardless of which rules are triggered, then you could probably drop the use of the 'END' environment variable and just do this:

    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]
    

    Or even better, this (though the REDIRECT_* variables are poorly documented in the Apache v2.2 documetation - they seem to be only mentioned here) - so I can't guarantee it'd work on all versions of 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]
    

    However, once you're running Apache v2.3.9+, I expect that using the [END] flag would be more efficient than the above solution, because (presumably) it altogether avoids the rewritten URL being re-submitted to the server for another rewriting pass.

    Note that you may also want to block rewriting of subrequests, in which case you can a RewriteCond to the don't-do-any-more-rewriting rule, like this:

    RewriteCond %{ENV:REDIRECT_STATUS} !^$ [OR]
    RewriteCond %{IS_SUBREQ} =true
    RewriteRule .* - [L]
    

  4. The idea here was a workaround to replace the use of the [END] flag in Apache versions that don't yet have it. But in fact you can use this general approach to store more than just a single flag - you could store arbitrary strings or numbers that would persist across an internal server redirect, and design your rewrite rules to depend on them based on any of the test conditions RuleCond provides. (I can't, off the top of my head, think of a reason why you'd want to do that... but hey, the more flexibility and control you have, the better, right?)


I guess anyone who's read this far has figured out that I'm not really asking a question here. It's more a matter of my having found my own solution to a problem I had, and wanting to post it up here for reference in case anyone else has run into the same problem. That's a big part of what this webiste is for, right?

...

But since this is supposed to be a question-and-answer forum, I'll ask:

  • Can anyone see any potential problems with this solution (other than those I've already mentioned)?
  • Or does anyone have a better way of achieving the same thing?

解决方案

Depending on your Apache build, this condition may work (add it to "stop-rewriting" rule: i.e. RewriteRule .* - [L] .. or just for specific problematic rule):

RewriteCond %{ENV:REDIRECT_STATUS} ^$

REDIRECT_STATUS will be empty of very first / initial rewrite and will have value of 200 (or maybe other value as well -- have not checked that deep) on any subsequent cycle.

Unfortunately it works on some systems and does not on others and I personally have no idea what is responsible for making it working.

Other than this the most common thing is to add rewrite condition to check the original URL, for example by parsing %{THE_REQUEST} variable e.g. RewriteCond %{THE_REQUEST} ^[A-Z]+\s.+\.php\sHTTP/.+ -- but this only makes sense for individual problematic rules.

In general -- you should avoid such "rewrite A -> B and then B -> A" situations (I'm pretty sure you are aware of that).

As for your own solution -- "don't fix if it ain't broken" -- if it works then it's great as I do not see any major problems with such approach.

这篇关于如何阻止多个mod_rewrite的通行证(或无限循环)在htaccess的背景下的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆