[SPIP] v3.2.1-->v3.2.2
[lhc/web/www.git] / www / ecrire / public / sandbox.php
1 <?php
2
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2019 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
8 * *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
12
13 /**
14 * Gestion d'une sécurisation des squelettes
15 *
16 * Une surcharge de ce fichier pourrait permettre :
17 *
18 * - de limiter l'utilisation des filtres à l'aide d'une liste blanche ou liste noire,
19 * - de rendre inactif le PHP écrit dans les squelettes
20 * - de refuser l'inclusion de fichier PHP dans les squelettes
21 *
22 * @package SPIP\Core\Compilateur\Sandbox
23 **/
24
25 if (!defined('_ECRIRE_INC_VERSION')) {
26 return;
27 }
28
29 /**
30 * Composer le code d'exécution d'un texte
31 *
32 * En principe juste un echappement de guillemets
33 * sauf si on veut aussi echapper et interdire les scripts serveurs
34 * dans les squelettes
35 *
36 * @param string $texte
37 * Texte à composer
38 * @param Champ $p
39 * Balise qui appelle ce texte
40 * @return string
41 * Texte
42 */
43 function sandbox_composer_texte($texte, &$p) {
44 $code = "'" . str_replace(array("\\", "'"), array("\\\\", "\\'"), $texte) . "'";
45
46 return $code;
47 }
48
49
50 /**
51 * Composer le code d'exécution d'un filtre
52 *
53 * @param string $fonc
54 * @param string $code
55 * @param string $arglist
56 * @param Champ $p
57 * Balise qui appelle ce filtre
58 * @return string
59 */
60 function sandbox_composer_filtre($fonc, $code, $arglist, &$p) {
61 if (isset($GLOBALS['spip_matrice'][$fonc])) {
62 $code = "filtrer('$fonc',$code$arglist)";
63 }
64
65 // le filtre est defini sous forme de fonction ou de methode
66 // par ex. dans inc_texte, inc_filtres ou mes_fonctions
67 elseif ($f = chercher_filtre($fonc)) {
68
69 // cas particulier : le filtre |set doit acceder a la $Pile
70 // proto: filtre_set(&$Pile, $val, $args...)
71 if (strpbrk($f, ':')) { // Class::method
72 $refl = new ReflectionMethod($f);
73 } else {
74 $refl = new ReflectionFunction($f);
75 }
76 $refs = $refl->getParameters();
77 if (isset($refs[0]) and $refs[0]->name == 'Pile') {
78 $code = "$f(\$Pile,$code$arglist)";
79 } else {
80 $code = "$f($code$arglist)";
81 }
82 }
83 // le filtre n'existe pas,
84 // on le notifie
85 else {
86 erreur_squelette(array('zbug_erreur_filtre', array('filtre' => texte_script($fonc))), $p);
87 }
88
89 return $code;
90 }
91
92 // Calculer un <INCLURE(xx.php)>
93 // La constante ci-dessous donne le code general quand il s'agit d'un script.
94 define('CODE_INCLURE_SCRIPT', 'if (!($path = %s) OR !is_readable($path))
95 erreur_squelette(array("fichier_introuvable", array("fichier" => "%s")), array(%s));
96 else {
97 $contexte_inclus = %s;
98 include $path;
99 }
100 '
101 );
102
103 /**
104 * Composer le code d'inclusion PHP
105 *
106 * @param string $fichier
107 * @param Champ $p
108 * Balise créant l'inclusion
109 * @param array $_contexte
110 * @return string
111 */
112 function sandbox_composer_inclure_php($fichier, &$p, $_contexte) {
113 $compil = texte_script(memoriser_contexte_compil($p));
114 // si inexistant, on essaiera a l'execution
115 if ($path = find_in_path($fichier)) {
116 $path = "\"$path\"";
117 } else {
118 $path = "find_in_path(\"$fichier\")";
119 }
120
121 return sprintf(CODE_INCLURE_SCRIPT, $path, $fichier, $compil, $_contexte);
122 }
123
124 /**
125 * Composer le code de sécurisation anti script
126 *
127 * @param string $code
128 * @param Champ $p
129 * Balise sur laquelle s'applique le filtre
130 * @return string
131 */
132 function sandbox_composer_interdire_scripts($code, &$p) {
133 // Securite
134 if ($p->interdire_scripts
135 and $p->etoile != '**'
136 ) {
137 if (!preg_match("/^sinon[(](.*),'([^']*)'[)]$/", $code, $r)) {
138 $code = "interdire_scripts($code)";
139 } else {
140 $code = interdire_scripts($r[2]);
141 $code = "sinon(interdire_scripts($r[1]),'$code')";
142 }
143 }
144
145 return $code;
146 }
147
148
149 /**
150 * Appliquer des filtres sur un squelette complet
151 *
152 * La fonction accèpte plusieurs tableaux de filtres à partir du 3ème argument
153 * qui seront appliqués dans l'ordre
154 *
155 * @uses echapper_php_callback()
156 *
157 * @param array $skel
158 * @param string $corps
159 * @param array $filtres
160 * Tableau de filtres à appliquer.
161 * @return mixed|string
162 */
163 function sandbox_filtrer_squelette($skel, $corps, $filtres) {
164 $series_filtres = func_get_args();
165 array_shift($series_filtres);// skel
166 array_shift($series_filtres);// corps
167
168 // proteger les <INCLUDE> et tous les morceaux de php licites
169 if ($skel['process_ins'] == 'php') {
170 $corps = preg_replace_callback(',<[?](\s|php|=).*[?]>,UimsS', 'echapper_php_callback', $corps);
171 }
172
173 // recuperer les couples de remplacement
174 $replace = echapper_php_callback();
175
176 foreach ($series_filtres as $filtres) {
177 if (count($filtres)) {
178 foreach ($filtres as $filtre) {
179 if ($filtre and $f = chercher_filtre($filtre)) {
180 $corps = $f($corps);
181 }
182 }
183 }
184 }
185
186 // restaurer les echappements
187 return str_replace($replace[0], $replace[1], $corps);
188 }
189
190
191 /**
192 * Callback pour échapper du code PHP (les séquences `<?php ... ?>`)
193 *
194 * Rappeler la fonction sans paramètre pour obtenir les substitutions réalisées.
195 *
196 * @see sandbox_filtrer_squelette()
197 *
198 * @param array|null $r
199 * - array : ce sont les captures de la regex à échapper
200 * - NULL : demande à dépiler tous les échappements réalisés
201 * @return string|array
202 * - string : hash de substitution du code php lorsque `$r` est un array
203 * - array : Liste( liste des codes PHP, liste des substitutions )
204 **/
205 function echapper_php_callback($r = null) {
206 static $src = array();
207 static $dst = array();
208
209 // si on recoit un tableau, on est en mode echappement
210 // on enregistre le code a echapper dans dst, et le code echappe dans src
211 if (is_array($r)) {
212 $dst[] = $r[0];
213
214 return $src[] = '___' . md5($r[0]) . '___';
215 }
216
217 // si on recoit pas un tableau, on renvoit les couples de substitution
218 // et on RAZ les remplacements
219 $r = array($src, $dst);
220 $src = $dst = array();
221
222 return $r;
223 }