Eine Falle aus mod_rewrite, Referrer und Weiterleitungen
Robert Bienert
- webserver
Moinsen!
Nachdem ich vorgestern einem simplen Problem einiges an Zeit gewidmet habe, wollte ich dieses inklusive Lösung hier mal für die Nachwelt darlegen:
Ich will in Abhängigkeit von einem bestimmten Refer(r)er per mod_rewrite eine andere Seite (mit der gleichen URI) ausliefern lassen. Diese Seite steckt in einem Unterverzeichnis, also erster Ansatz:
RewriteCond %{HTTP_REFERER} ^http\:\/\/www\.example\.org\/path\/to\/file.html
RewriteRule "^(.*)$" localdir
Hierbei darf hinter dem Verzeichnisnamen _kein_ '/' angegeben werden, weil der Apache sonst mit einem 500er den Dienst quittiert. In der Dokumentation von RewriteRule findet man dazu, dass die Ersetzung ein Dateipfad, ein URL-Pfad, eine absolute URL oder gar nichts ('-') sein kann. Ein Verzeichnisname zwecks Auliefern der darin enthaltenen index.script scheint damit nicht gemeint zu sein. Obige „Lösung“ ist allerdings keine, denn der Apache erkennt, dass localdir ein Verzeichnis ist und leitet von sich aus auf localdir/ weiter, wobei der Referrer allerdings erhalten bleibt. Damit haben wir dann eine schöne Endlosschleife gebaut.
Richtig funktioniert das ganze so, indem man den impliziten Zugriff auf die index.script explizit auflöst:
RewriteCond %{HTTP_REFERER} ^http\:\/\/www\.example\.org\/path\/to\/file.html
RewriteRule "^(.*)$" localdir/index.script
Viele Grüße,
Robert
Hallo Robert,
RewriteCond %{HTTP_REFERER} ^http://www.example.org/path/to/file.html
RewriteRule "^(.*)$" localdir
>
> Hierbei darf hinter dem Verzeichnisnamen \_kein\_ '/' angegeben werden, weil der Apache sonst mit einem 500er den Dienst quittiert.
Klar, Du verursachst mit / am Ende auch eine Endlosschleife.
Ohne / am Ende passiert nämlich das (bei Zugriff auf '/foo'):
a. 'foo' wird von .\* gematcht, wird umgeschrieben auf 'localdir'
b. 'localdir' wird von .\* gemacht, soll umgeschrieben werden auf
'localdir'
-> Apache erkennt Umschreibequelle und -ziel sind gleich
-> automatischer Abbruch von mod\_rewrite
c. mod\_dir erkennt Request auf /foo ohne / -> leitet Weiter per 301 auf
mit /
Mit / am Ende passiert folgendes (bei Zugriff auf '/foo'):
a. 'foo' wird von .\* gematcht, wird umgeschrieben auf 'localdir/'
b. 'localdir/' wird von .\* gemacht, soll umgeschrieben werden auf
'localdir/'
-> Apache erkennt Umschreibequelle und -ziel sind gleich
-> automatischer Abbruch von mod\_rewrite
c. Aufruf auf 'localdir/' führt durch mod\_dir zu einem Subrequest auf
'localdir/index.php'
-> mod\_rewrite wird wieder aktiv
-> 'localdir/index.php' wird von .\* gematcht, wird umgeschrieben
auf 'localdir/'
-> weiter bei Schritt b.
-> Endlosschleife
-> 500er
Wenn Du das vermeiden willst, aber gleichzeitig auf ein Verzeichnis umschreiben willst, wo Du die Indexseite nicht zwangsläufig kennst, dann nimm:
~~~apache
RewriteCond %{REQUEST_URI} !^/localdir/
RewriteCond %{HTTP_REFERER} ^http://www\.example\.org/path/to/file.html
RewriteRule "^(.*)$" localdir/
Das prüft, ob Du nicht schon bereits auf /localdir/ zugreifen willst, bevor die Umschreiberegel aktiv wird. Damit wird die Endlosschleife verhindert.
Alternativ kannst Du auch [NS] verwenden:
RewriteCond %{HTTP_REFERER} ^http://www\.example\.org/path/to/file.html
RewriteRule "^(.*)$" localdir/ [NS]
Das [NS] verhindert, dass mod_rewrite bei Subrequests aktiv wird. Damit umgehst Du die Problematik genauso.
Übrigens: / und : musst Du in regulären Ausdrücken nicht escapen. : sowieso nicht und / nur, wenn der bei Perl-Ausdrücken als Delimiter auftaucht - Apache akzeptiert jedoch POSIX und da gibt's keine Delimiter.
Viele Grüße,
Christian
Moin Christian!
Ich wusste doch, dass ich noch etwas dazulernen kann :-)
Und diese Lösung ist ja mal schön kurz und knackig, genau nach meinem Geschmack:
RewriteCond %{HTTP_REFERER} ^http://www.example.org/path/to/file.html
RewriteRule "^(.*)$" localdir/ [NS]
>
> Das [NS] verhindert, dass mod\_rewrite bei Subrequests aktiv wird. Damit umgehst Du die Problematik genauso.
Ich frage mich allerdings gerade, wieso ich das in der Manual übersehen habe. Wahrscheinlich war mir die Bedeutung des Terms »Subrequest« nicht in der Art bewusst.
Bezüglich der regulären Ausdrücke habe ich noch eine Frage:
> Übrigens: / […] musst Du in regulären Ausdrücken nicht escapen. […] / nur, wenn der bei Perl-Ausdrücken als Delimiter auftaucht - Apache akzeptiert jedoch POSIX und da gibt's keine Delimiter.
Was heißt „akzeptiert“? Benutzt der Apache POSIX- oder Perl-kompatible reguläre Ausdrücke? Hat das was mit den Anführungszeichen darum zu tun?
Viele Grüße,
Robert
Hallo Robert,
Übrigens: / […] musst Du in regulären Ausdrücken nicht escapen. […] / nur, wenn der bei Perl-Ausdrücken als Delimiter auftaucht - Apache akzeptiert jedoch POSIX und da gibt's keine Delimiter.
Was heißt „akzeptiert“?
Benutzt.
Benutzt der Apache POSIX- oder Perl-kompatible reguläre Ausdrücke?
Nochmal nachgelesen: Bis Version 1.3 POSIX, ab Version 2.0 PCRE (d.h. Perl-kompatibel). Allerdings ohne Delimiter.
Ok, um die Delimiter-Geschichte nochmal zu entdröseln (ich hab's vorher auch etwas falsch gesagt):
Reguläre Ausdrücke sind ja eigentlich nur sowas wie ^hallo.*$ oder ähnliches. In einigen Fällen (Perl, PHP mit PCRE, awk, sed, less, ...) schreibt man zusätzlich noch Delimiter um die Ausdrücke herum (/^hallo.*$/).
Also, wenn man irgend etwas hat, wo man Delimiter für die Ausdrücke benutzt, dann muss man die auch escapen, wenn sie im Pattern vorkommen. Egal welche Regexp-Syntax da jetzt nun verwendet wird. Ferner muss man nur den verwendeten Delimiter escapen, d.h. / wenn man / verwendet, ~ wenn man ~ verwendet, etc.
Apache nutzt zwar ab 2.0 PCRE, allerdings ohne Delimiter - und damit muss man auch keinerlei Delimiter escapen, wenn man bei Apache reguläre Ausdrücke notiert. Daher kann man / immer direkt notieren in regulären Ausdrücken vom Apache.
Hat das was mit den Anführungszeichen darum zu tun?
Nein, die kann man übrigens auch weglassen. Die haben nichts mit dem regulären Ausdruck zu tun, die werden vom Config-Parser des Apache interpretiert, bevor er den Ausdruck an mod_rewrite weitergibt (die braucht man übrigens eigentlich auch nur, wenn man Leerzeichen im Pattern hat, was bei URIs eigentlich selten vorkommen sollte).
Ich hoffe, das ganze ist jetzt etwas klarer?
Viele Grüße,
Christian
Hallo Christian!
Ich hoffe, das ganze ist jetzt etwas klarer?
Definitiv. Vielen Dank für die Erläuterung!
Viele Grüße,
Robert
Hi,
^http://www.example.org/path/to/file.html
wird die Seite denn tatsächlich nur mit www-Subdomain ausgeliefert?
freundliche Grüße
Ingo
Moin!
^http://www.example.org/path/to/file.html
wird die Seite denn tatsächlich nur mit www-Subdomain ausgeliefert?
Danach sieht es aus. Dahinter stecken in meinem Fall SEOs, die werden sich hüten gleiche Inhalte unter verschiedenen Domains vorzuhalten.
Viele Grüße,
Robert
Hi,
wird die Seite denn tatsächlich nur mit www-Subdomain ausgeliefert?
Danach sieht es aus. Dahinter stecken in meinem Fall SEOs, die werden sich hüten gleiche Inhalte unter verschiedenen Domains vorzuhalten.
Eine www-Subdomain sieht Google (mit einigen unbedeutenden Ausnahmen) nicht als verschiedene Domain an.
freundliche Grüße
Ingo
Hi,
Eine www-Subdomain sieht Google (mit einigen unbedeutenden Ausnahmen) nicht als verschiedene Domain an.
Mein Browser, und damit auch sein Cache, aber. Und deshalb nervt's auch, wenn Domains von unterschiedlichen Stellen mal so, mal so verlinkt werden.
Also, weg mit der doppelten Aufrufmoeglichkeit und fuer eine entscheiden, Danke :-)
MfG ChrisB
Hi,
Also, weg mit der doppelten Aufrufmoeglichkeit und fuer eine entscheiden, Danke :-)
dann aber weg mit den www-Subdomains und nicht umgekehrt. Warum solle eine Domain nicht erreichbar sein?
freundliche Grüße
Ingo