Was bewirkt beim Festlegen von Umgebungsvariablen in Apache RewriteRule-Anweisungen, dass dem Variablennamen das Präfix "REDIRECT_" vorangestellt wird?

72

Ich versuche, Apache-Umgebungsvariablen (zur Verwendung in PHP) mit dem [E=VAR:VAL]Flag für RewriteRule-Regeln in einer .htaccess-Datei festzulegen.

Ich habe bereits festgestellt, dass auf die Variablen in PHP $_SERVEReher als Servervariablen als $_ENV(was einen gewissen Sinn macht) zugegriffen wird . Mein Problem ist jedoch, dass bei einigen Regeln das [E=VAR:VAL]Flag wie erwartet funktioniert und ich eine Variable erhalte $_SERVER['VAR'], bei anderen Regeln jedoch eine Variable $_SERVER['REDIRECT_VAR']oder $_SERVER['REDIRECT_REDIRECT_VAR']usw.

A. Was bewirkt, dass eine in Apache mithilfe des [E=VAR:VAL]Flags festgelegte Umgebungsvariable umbenannt wird, indem dem Variablennamen "REDIRECT_" vorangestellt wird?

B. Was kann ich tun, um sicherzustellen, dass ich eine Umgebungsvariable mit einem unveränderten Namen $_SERVER['VAR']erhalte, damit ich in PHP darauf zugreifen kann, ohne nach Variationen des Variablennamens suchen zu müssen, denen eine oder mehrere Instanzen von "REDIRECT_" vorangestellt sind dazu?

Teillösung gefunden . Wenn Sie am Anfang der Umschreiberegeln Folgendes hinzufügen, wird die ursprüngliche ENV neu erstellt: VAR bei jeder Umleitung (und die REDIRECT_VAR-Versionen dort belassen), falls erforderlich:

RewriteCond %{ENV:REDIRECT_VAR} !^$
RewriteRule .* - [E=VAR:%{ENV:REDIRECT_VAR}]
Kelle
quelle
Ich benutze immer getenv () - php.net/manual/en/function.getenv.php und habe noch keine seltsamen Probleme festgestellt .
Chaoley

Antworten:

74

Dieses Verhalten ist unglücklich und scheint nicht einmal dokumentiert zu sein.

.htaccess Per-Dir-Kontext

Folgendes scheint im .htaccessKontext pro Verzeichnis (pro Verzeichnis) zu passieren :

Angenommen, Apache verarbeitet eine .htaccessDatei, die Anweisungen zum Umschreiben enthält.

  1. Apache füllt seine Umgebungsvariablenzuordnung mit allen Standard-CGI / Apache-Variablen

  2. Das Umschreiben beginnt

  3. Umgebungsvariablen werden in RewriteRuleDirektiven festgelegt

  4. Wenn Apache die Verarbeitung der RewriteRuleAnweisungen beendet (aufgrund eines LFlags oder des Endes des Regelsatzes) und die URL durch a geändert wurde RewriteRule, startet Apache die Anforderungsverarbeitung neu.

    Wenn Sie mit diesem Teil nicht vertraut sind, lesen Sie die LFlag-Dokumentation :

    Somit kann der Regelsatz von Anfang an erneut ausgeführt werden. Am häufigsten geschieht dies, wenn eine der Regeln eine interne oder externe Umleitung verursacht und der Anforderungsprozess von vorne beginnt.
  5. Soweit ich beobachten kann, glaube ich, dass, wenn # 4 passiert, # 1 wiederholt wird, die Umgebungsvariablen, die in RewriteRuleDirektiven festgelegt wurden, vorangestellt REDIRECT_und der Umgebungsvariablenzuordnung hinzugefügt werden (nicht unbedingt in dieser Reihenfolge, aber das Endergebnis besteht aus dieser Kombination).

    In diesem Schritt werden die ausgewählten Variablennamen gelöscht, und ich werde gleich erklären, warum dies so wichtig und unpraktisch ist.

Variablennamen wiederherstellen

Als ich ursprünglich auf dieses Problem gestoßen bin, habe ich in .htaccess(vereinfacht) Folgendes getan :

RewriteCond %{HTTP_HOST} (.+)\.projects\.

RewriteRule (.*) subdomains/%1/docroot/$1

RewriteRule (.+/docroot)/ - [L,E=EFFECTIVE_DOCUMENT_ROOT:$1]

Wenn ich die Umgebungsvariable in der ersten festlegen RewriteRulewürde, würde Apache den Umschreibungsprozess neu starten und der Variablen voranstellen REDIRECT_(Schritte 4 und 5 oben), sodass ich den Zugriff über den von mir zugewiesenen Namen verlieren würde.

In diesem Fall RewriteRuleändert der erste die URL. Nachdem beide RewriteRules verarbeitet wurden, startet Apache die Prozedur neu und verarbeitet sie .htaccesserneut. Beim zweiten Mal wird das erste RewriteRuleaufgrund der RewriteCondDirektive übersprungen , aber das zweite RewriteRulestimmt überein, legt die Umgebungsvariable (erneut) fest und ändert vor allem die URL nicht . Der Anforderungs- / Umschreibungsprozess beginnt also nicht von vorne, und der von mir gewählte Variablenname bleibt erhalten. In diesem Fall habe ich tatsächlich beide REDIRECT_EFFECTIVE_DOCUMENT_ROOTund EFFECTIVE_DOCUMENT_ROOT. Wenn ich Lbeim ersten Mal eine Flagge benutzen RewriteRulewürde, hätte ich nur EFFECTIVE_DOCUMENT_ROOT.

Die Teillösung von @ trowel funktioniert ähnlich: Die Anweisungen zum Umschreiben werden erneut verarbeitet, die umbenannte Variable wird erneut dem ursprünglichen Namen zugewiesen, und wenn sich die URL nicht ändert, ist der Prozess beendet und der zugewiesene Variablenname bleibt erhalten.

Warum diese Techniken unzureichend sind

Beide Techniken weisen einen großen Fehler auf: Wenn die Umschreiberegeln in der .htaccessDatei, in der Sie Umgebungsvariablen festlegen, die URL in ein tiefer verschachteltes Verzeichnis .htaccessumschreiben, in dem sich eine Datei befindet, die neu geschrieben werden kann, wird der zugewiesene Variablenname erneut gelöscht.

Angenommen, Sie haben ein Verzeichnislayout wie das folgende:

docroot/
        .htaccess
        A.php
        B.php
        sub/
                .htaccess
                A.php
                B.php

Und so docroot/.htaccessähnlich:

RewriteRule ^A\.php sub/B.php [L]

RewriteRule .* - [E=MAJOR:flaw]

Sie fordern also /A.phpan und es wird umgeschrieben sub/B.php. Sie haben noch Ihre MAJORVariable.

Wenn Sie jedoch Anweisungen zum Umschreiben in docroot/sub/.htaccess(auch nur RewriteEngine Offoder RewriteEngine On) haben, MAJORverschwindet Ihre Variable. Dies liegt daran, dass, sobald die URL neu geschrieben sub/B.phpund docroot/sub/.htaccessverarbeitet wurde und wenn sie Anweisungen zum Umschreiben enthält, Anweisungen zum Umschreiben in docroot/.htaccessnicht mehr verarbeitet werden. Wenn ein REDIRECT_MAJORAfter docroot/.htaccessverarbeitet wurde (z. B. wenn Sie das LFlag vom ersten weglassen RewriteRule), haben Sie es immer noch, aber diese Anweisungen werden nicht erneut ausgeführt, um den von Ihnen gewählten Variablennamen festzulegen.

Erbe

Sagen Sie also, Sie möchten:

  1. Umgebungsvariablen in RewriteRuleDirektiven auf einer bestimmten Ebene des Verzeichnisbaums festlegen (wie docroot/.htaccess)

  2. haben sie in Skripten auf tieferen Ebenen verfügbar

  3. Halten Sie sie mit den zugewiesenen Namen bereit

  4. in der Lage sein, Anweisungen in tiefer verschachtelten .htaccessDateien neu zu schreiben

Eine mögliche Lösung besteht darin, RewriteOptions inheritAnweisungen in den tiefer verschachtelten .htaccessDateien zu verwenden. Auf diese Weise können Sie die Anweisungen zum Umschreiben in weniger tief verschachtelten Dateien erneut ausführen und die oben beschriebenen Techniken verwenden, um die Variablen mit den ausgewählten Namen festzulegen. Beachten Sie jedoch, dass dies die Komplexität erhöht, da Sie die Anweisungen zum Umschreiben in den weniger tief verschachtelten Dateien sorgfältiger gestalten müssen, damit sie keine Probleme verursachen, wenn sie erneut aus den tiefer verschachtelten Verzeichnissen ausgeführt werden. Ich glaube, Apache entfernt das Per-Dir-Präfix für das tiefer verschachtelte Verzeichnis und führt die Rewrite-Anweisungen in den weniger tief verschachtelten Dateien für diesen Wert aus.

@ Kelle Technik

Soweit ich sehen kann, scheint die Unterstützung für die Verwendung eines Konstrukts wie %{ENV:REDIRECT_VAR}in der Wertekomponente eines RewriteRule EFlags (z. B. [E=VAR:%{ENV:REDIRECT_VAR}]) nicht dokumentiert zu sein :

VAL kann Rückreferenzen ($ N oder% N) enthalten, die erweitert werden.

Es scheint zu funktionieren, aber wenn Sie vermeiden möchten, sich auf etwas Undokumentiertes zu verlassen (bitte korrigieren Sie mich, wenn ich mich irre), können Sie dies einfach auf folgende Weise tun:

RewriteCond %{ENV:REDIRECT_VAR} (.+)
RewriteRule .* - [E=VAR:%1]

SetEnvIf

Ich empfehle nicht, sich darauf zu verlassen, da es nicht mit dem dokumentierten Verhalten (siehe unten) docroot/.htaccessübereinzustimmen scheint , aber dies (in , mit Apache 2.2.20) funktioniert für mich:

SetEnvIf REDIRECT_VAR (.+) VAR=$1

Auf diese Weise stehen nur die Umgebungsvariablen zum Testen zur Verfügung, die durch frühere SetEnvIf [NoCase] ​​-Anweisungen definiert wurden.

Warum?

Ich weiß nicht, warum diese Namen vorangestellt werden sollen REDIRECT_- nicht überraschend, da sie in den Apache-Dokumentationsabschnitten für mod_rewrite-Direktiven , RewriteRuleFlags oder Umgebungsvariablen anscheinend nicht erwähnt werden .

Im Moment scheint es mir ein großes Ärgernis zu sein, da es keine Erklärung dafür gibt, warum es besser ist, als die zugewiesenen Namen in Ruhe zu lassen. Der Mangel an Dokumentation trägt nur zu meiner Skepsis bei.

Die Möglichkeit, Umgebungsvariablen in Umschreiberegeln zuzuweisen, ist nützlich oder zumindest nützlich. Die Nützlichkeit wird jedoch durch dieses namenändernde Verhalten stark beeinträchtigt. Die Komplexität dieses Beitrags zeigt, wie verrückt dieses Verhalten ist und welche Reifen durchgesprungen werden müssen, um es zu überwinden.

JMM
quelle
Kam über eine andere Regel, die unter die Kategorie der nervigen undokumentierten Verrücktheit fällt. Auf einigen Servern müssen Umgebungsvariablennamen HTTP_ vorangestellt werden. ( stackoverflow.com/questions/17073144/… )
Chris
Entschuldigung, eines habe ich nicht verstanden. In Ihrem Beispiel, wenn die Regeln sind: RewriteRule ^A\.php sub/B.php [L]und RewriteRule .* - [E=MAJOR:flaw]wenn die erste Regel übereinstimmt, MAJORwird sie nicht festgelegt, da die erste Regel übereinstimmt und die letzte ist (L-Flag). Die URL wird neu geschrieben und Apache wiederholt sich über die mod_rewriteinnere Schleife des Benutzers. Diesmal stimmt sie überein RewriteRule .* - [E=MAJOR:flaw], legt die Umgebungsvariable fest und geht erst dann nach dem nächsten nach .htaccessinnen /sub. Ist es richtig?
Tonix
@tonix Nur von dem, was ich hier geschrieben habe, nein, das klingt nicht richtig. So wie ich es beschrieben habe, RewriteRuleschreibt der URL zuerst die URL um sub/B.phpund dann ist das Spiel vorbei für docroot/.htaccess: Die env-Variable wird nie wirklich gesetzt. (Das zu beschreiben, da die env-Variable "verschwindet" möglicherweise nicht der beste Weg ist, es auszudrücken.) Auch dies ist der Fall, wenn es eine gibt, die eine sub/.htaccessAnweisung zum Umschreiben enthält.
JMM
Entnommen von hier: stackoverflow.com/questions/11484739/…: **If the URI changed L will re-inject into the the next round (the outer loop)** So I guess that if a URL is rewritten with [L] flag set, mod_rewrite will pass to the next round, but this time taking into consideration the most inner .htaccess` Datei innerhalb des neu geschriebenen Pfads (in diesem Fall sub/.htaccess). Zumindest verstehe ich das aus Ihrer Antwort und der Antwort, die ich verlinkt habe.
Tonix
2
FWIW, hier sind einige Apache-Dokumente, die explizit über das Präfix "REDIRECT_" sprechen: httpd.apache.org/docs/2.0/custom-error.html . Ich sympathisiere damit, wie nervig es ist - ich bin selbst davon gebissen worden.
JWD
8

Ich habe dies überhaupt nicht getestet und weiß, dass es die Punkte A oder B nicht behandelt, aber es gibt eine Beschreibung dieses Problems in den Kommentaren in der PHP-Dokumentation und einige mögliche Lösungen für den Zugriff auf diese Variablen mit $_SERVER['VAR']:

http://www.php.net/manual/en/reserved.variables.php#79811

EDIT - einige weitere Antworten auf die angebotene Frage:

A: Die Umgebungsvariablen werden von Apache umbenannt, wenn sie an einer Umleitung beteiligt sind. Zum Beispiel, wenn Sie die folgende Regel haben:

RewriteRule ^index.php - [E=VAR1:'hello',E=VAR2:'world']

Dann können Sie mit $_SERVER['VAR1']und auf VAR1 und VAR2 zugreifen $_SERVER['VAR2']. Wenn Sie die Seite jedoch wie folgt umleiten:

RewriteRule ^index.php index2.php [E=VAR1:'hello',E=VAR2:'world']

Dann müssen Sie $_SERVER['REDIRECT_VAR1']usw. verwenden.

B: Der beste Weg, um dieses Problem zu lösen, besteht darin, die Variablen zu verarbeiten, die Sie mit PHP interessieren. Erstellen Sie eine Funktion, die das $_SERVERArray durchläuft und die benötigten Elemente findet. Sie könnten sogar eine Funktion wie diese verwenden:

function myGetEnv($key) {
    $prefix = "REDIRECT_";
    if(array_key_exists($key, $_SERVER))
        return $_SERVER[$key];
    foreach($_SERVER as $k=>$v) {
        if(substr($k, 0, strlen($prefix)) == $prefix) {
            if(substr($k, -(strlen($key))) == $key)
                return $v;
        }
    }
    return null;
}
Thetaiko
quelle
Danke für den Link zu den Kommentaren. Sie bestätigen das Problem und bieten eine Problemumgehung, aber es verschiebt das Problem nur von PHP auf zusätzlichen Code im .htaccess. Ich hoffe, dass ein Apache-Guru weiß, wie der Name bestehen bleibt, sodass kein Problemumgehungscode erforderlich ist! (Ich würde Ihnen +1 geben, aber nicht genug Wiederholung, um es zu tun)
Kelle
@ Trowel - siehe Änderungen an meiner Antwort
Thetaiko
Dank Thetaiko stellt sich heraus, dass das Umbenennen eine in Apache 2.0 eingeführte Funktion ist, mit der Variablen durch Weiterleitungen verfolgt werden können. Gute PHP-Funktion, aber ich würde wahrscheinlich mit einem preg_match beginnen, das mit / ^ ($ REDIRECT _) * beginnt, um eine beliebige Anzahl von Weiterleitungen mit einem Treffer abzufangen. Am Ende der Dinge habe ich eine Lösung gefunden (zur Frage hinzugefügt)
Kelle
0

Da ich keinen meiner Codes ändern möchte (und auch nicht den Code der verwendeten Bibliotheken ändern kann) , habe ich den folgenden Ansatz gewählt: Beim Bootstrapping meiner Anwendung - z. B. in meiner index.php- überarbeite ich die $_ENVSuperglobale so, dass Variablen mit dem Präfix REDIRECT_are sind umgeschrieben auf ihren normalen beabsichtigten Namen:

// Fix ENV vars getting prepended with `REDIRECT_` by Apache
foreach ($_ENV as $key => $value) {
    if (substr($key, 0, 9) === 'REDIRECT_') {
        $_ENV[str_replace('REDIRECT_', '', $key)] = $value;
        putenv(str_replace('REDIRECT_', '', $key) . '=' . $value);
    }
}

Wir setzen es nicht nur direkt ein $_ENV, sondern speichern es auch mit putenv(). Auf diese Weise können vorhandener Code und Bibliotheken - die möglicherweise verwendet werden getenv()- einwandfrei funktionieren.


Nebenbei bemerkt: Wenn Sie Header wie HTTP_AUTHORIZATIONin Ihrem Code extrahieren , müssen Sie die gleiche Art von Manipulation durchführen an $_SERVER:

foreach ($_SERVER as $key => $value) {
    if (substr($key, 0, 9) === 'REDIRECT_') {
        $_SERVER[str_replace('REDIRECT_', '', $key)] = $value;
    }
}
Bramus
quelle