Completed
Push — master ( 2a0f4b...be3b46 )
by cam
05:37
created

flock.php ➔ ecrire_fichier_calcule_si_modifie()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 4
nop 4
dl 0
loc 19
rs 9.0111
c 0
b 0
f 0
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 de recherche et d'écriture de répertoire ou fichiers
15
 *
16
 * @package SPIP\Core\Flock
17
 **/
18
19
if (!defined('_ECRIRE_INC_VERSION')) {
20
	return;
21
}
22
23
if (!defined('_TEST_FILE_EXISTS')) {
24
	/** Permettre d'éviter des tests file_exists sur certains hébergeurs */
25
	define('_TEST_FILE_EXISTS', preg_match(',(online|free)[.]fr$,', isset($_ENV["HTTP_HOST"]) ? $_ENV["HTTP_HOST"] : ""));
26
}
27
28
#define('_SPIP_LOCK_MODE',0); // ne pas utiliser de lock (deconseille)
29
#define('_SPIP_LOCK_MODE',1); // utiliser le flock php
30
#define('_SPIP_LOCK_MODE',2); // utiliser le nfslock de spip
31
32
if (_SPIP_LOCK_MODE == 2) {
33
	include_spip('inc/nfslock');
34
}
35
36
$GLOBALS['liste_verrous'] = array();
37
38
/**
39
 * Ouvre un fichier et le vérrouille
40
 *
41
 * @link http://php.net/manual/fr/function.flock.php pour le type de verrou.
42
 * @see  _SPIP_LOCK_MODE
43
 * @see  spip_fclose_unlock()
44
 * @uses spip_nfslock() si _SPIP_LOCK_MODE = 2.
45
 *
46
 * @param string $fichier
47
 *     Chemin du fichier
48
 * @param string $mode
49
 *     Mode d'ouverture du fichier (r,w,...)
50
 * @param string $verrou
51
 *     Type de verrou (avec _SPIP_LOCK_MODE = 1)
52
 * @return Resource
53
 *     Ressource sur le fichier ouvert, sinon false.
54
 **/
55
function spip_fopen_lock($fichier, $mode, $verrou) {
56
	if (_SPIP_LOCK_MODE == 1) {
57
		if ($fl = @fopen($fichier, $mode)) {
58
			// verrou
59
			@flock($fl, $verrou);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
60
		}
61
62
		return $fl;
63
	} elseif (_SPIP_LOCK_MODE == 2) {
64
		if (($verrou = spip_nfslock($fichier)) && ($fl = @fopen($fichier, $mode))) {
65
			$GLOBALS['liste_verrous'][$fl] = array($fichier, $verrou);
66
67
			return $fl;
68
		} else {
69
			return false;
70
		}
71
	}
72
73
	return @fopen($fichier, $mode);
74
}
75
76
/**
77
 * Dévérrouille et ferme un fichier
78
 *
79
 * @see _SPIP_LOCK_MODE
80
 * @see spip_fopen_lock()
81
 *
82
 * @param string $handle
83
 *     Chemin du fichier
84
 * @return bool
85
 *     true si succès, false sinon.
86
 **/
87
function spip_fclose_unlock($handle) {
88
	if (_SPIP_LOCK_MODE == 1) {
89
		@flock($handle, LOCK_UN);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
90
	} elseif (_SPIP_LOCK_MODE == 2) {
91
		spip_nfsunlock(reset($GLOBALS['liste_verrous'][$handle]), end($GLOBALS['liste_verrous'][$handle]));
92
		unset($GLOBALS['liste_verrous'][$handle]);
93
	}
94
95
	return @fclose($handle);
96
}
97
98
99
/**
100
 * Retourne le contenu d'un fichier, même si celui ci est compréssé
101
 * avec une extension en `.gz`
102
 *
103
 * @param string $fichier
104
 *     Chemin du fichier
105
 * @return string
106
 *     Contenu du fichier
107
 **/
108
function spip_file_get_contents($fichier) {
109
	if (substr($fichier, -3) != '.gz') {
110
		if (function_exists('file_get_contents')) {
111
			// quand on est sous windows on ne sait pas si file_get_contents marche
112
			// on essaye : si ca retourne du contenu alors c'est bon
113
			// sinon on fait un file() pour avoir le coeur net
114
			$contenu = @file_get_contents($fichier);
115
			if (!$contenu and _OS_SERVEUR == 'windows') {
116
				$contenu = @file($fichier);
117
			}
118
		} else {
119
			$contenu = @file($fichier);
120
		}
121
	} else {
122
		$contenu = @gzfile($fichier);
123
	}
124
125
	return is_array($contenu) ? join('', $contenu) : (string)$contenu;
126
}
127
128
129
/**
130
 * Lit un fichier et place son contenu dans le paramètre transmis.
131
 *
132
 * Décompresse automatiquement les fichiers `.gz`
133
 *
134
 * @uses spip_fopen_lock()
135
 * @uses spip_file_get_contents()
136
 * @uses spip_fclose_unlock()
137
 *
138
 * @param string $fichier
139
 *     Chemin du fichier
140
 * @param string $contenu
141
 *     Le contenu du fichier sera placé dans cette variable
142
 * @param array $options
143
 *     Options tel que :
144
 *
145
 *     - 'phpcheck' => 'oui' : vérifie qu'on a bien du php
146
 * @return bool
147
 *     true si l'opération a réussie, false sinon.
148
 **/
149
function lire_fichier($fichier, &$contenu, $options = array()) {
150
	$contenu = '';
151
	// inutile car si le fichier n'existe pas, le lock va renvoyer false juste apres
152
	// economisons donc les acces disque, sauf chez free qui rale pour un rien
153
	if (_TEST_FILE_EXISTS and !@file_exists($fichier)) {
154
		return false;
155
	}
156
157
	#spip_timer('lire_fichier');
158
159
	// pas de @ sur spip_fopen_lock qui est silencieux de toute facon
160
	if ($fl = spip_fopen_lock($fichier, 'r', LOCK_SH)) {
161
		// lire le fichier avant tout
162
		$contenu = spip_file_get_contents($fichier);
163
164
		// le fichier a-t-il ete supprime par le locker ?
165
		// on ne verifie que si la tentative de lecture a echoue
166
		// pour discriminer un contenu vide d'un fichier absent
167
		// et eviter un acces disque
168
		if (!$contenu and !@file_exists($fichier)) {
169
			spip_fclose_unlock($fl);
170
171
			return false;
172
		}
173
174
		// liberer le verrou
175
		spip_fclose_unlock($fl);
176
177
		// Verifications
178
		$ok = true;
179
		if (isset($options['phpcheck']) and $options['phpcheck'] == 'oui') {
180
			$ok &= (preg_match(",[?]>\n?$,", $contenu));
181
		}
182
183
		#spip_log("$fread $fichier ".spip_timer('lire_fichier'));
184
		if (!$ok) {
185
			spip_log("echec lecture $fichier");
186
		}
187
188
		return $ok;
189
	}
190
191
	return false;
192
}
193
194
195
/**
196
 * Écrit un fichier de manière un peu sûre
197
 *
198
 * Cette écriture s’exécute de façon sécurisée en posant un verrou sur
199
 * le fichier avant sa modification. Les fichiers .gz sont compressés.
200
 *
201
 * @uses raler_fichier() Si le fichier n'a pu peut être écrit
202
 * @see  lire_fichier()
203
 * @see  supprimer_fichier()
204
 *
205
 * @param string $fichier
206
 *     Chemin du fichier
207
 * @param string $contenu
208
 *     Contenu à écrire
209
 * @param bool $ignorer_echec
210
 *     - true pour ne pas raler en cas d'erreur
211
 *     - false affichera un message si on est webmestre
212
 * @param bool $truncate
213
 *     Écriture avec troncation ?
214
 * @return bool
215
 *     - true si l’écriture s’est déroulée sans problème.
216
 **/
217
function ecrire_fichier($fichier, $contenu, $ignorer_echec = false, $truncate = true) {
218
219
	#spip_timer('ecrire_fichier');
220
221
	// verrouiller le fichier destination
222
	if ($fp = spip_fopen_lock($fichier, 'a', LOCK_EX)) {
223
		// ecrire les donnees, compressees le cas echeant
224
		// (on ouvre un nouveau pointeur sur le fichier, ce qui a l'avantage
225
		// de le recreer si le locker qui nous precede l'avait supprime...)
226
		if (substr($fichier, -3) == '.gz') {
227
			$contenu = gzencode($contenu);
228
		}
229
		// si c'est une ecriture avec troncation , on fait plutot une ecriture complete a cote suivie unlink+rename
230
		// pour etre sur d'avoir une operation atomique
231
		// y compris en NFS : http://www.ietf.org/rfc/rfc1094.txt
232
		// sauf sous wintruc ou ca ne marche pas
233
		$ok = false;
234
		if ($truncate and _OS_SERVEUR != 'windows') {
235
			if (!function_exists('creer_uniqid')) {
236
				include_spip('inc/acces');
237
			}
238
			$id = creer_uniqid();
239
			// on ouvre un pointeur sur un fichier temporaire en ecriture +raz
240
			if ($fp2 = spip_fopen_lock("$fichier.$id", 'w', LOCK_EX)) {
241
				$s = @fputs($fp2, $contenu, $a = strlen($contenu));
242
				$ok = ($s == $a);
243
				spip_fclose_unlock($fp2);
244
				spip_fclose_unlock($fp);
245
				// unlink direct et pas spip_unlink car on avait deja le verrou
246
				// a priori pas besoin car rename ecrase la cible
247
				// @unlink($fichier);
248
				// le rename aussitot, atomique quand on est pas sous windows
249
				// au pire on arrive en second en cas de concourance, et le rename echoue
250
				// --> on a la version de l'autre process qui doit etre identique
251
				@rename("$fichier.$id", $fichier);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
252
				// precaution en cas d'echec du rename
253
				if (!_TEST_FILE_EXISTS or @file_exists("$fichier.$id")) {
254
					@unlink("$fichier.$id");
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
255
				}
256
				if ($ok) {
257
					$ok = file_exists($fichier);
258
				}
259
			} else // echec mais penser a fermer ..
260
			{
261
				spip_fclose_unlock($fp);
262
			}
263
		}
264
		// sinon ou si methode precedente a echoueee
265
		// on se rabat sur la methode ancienne
266
		if (!$ok) {
267
			// ici on est en ajout ou sous windows, cas desespere
268
			if ($truncate) {
269
				@ftruncate($fp, 0);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
270
			}
271
			$s = @fputs($fp, $contenu, $a = strlen($contenu));
272
273
			$ok = ($s == $a);
274
			spip_fclose_unlock($fp);
275
		}
276
277
		// liberer le verrou et fermer le fichier
278
		@chmod($fichier, _SPIP_CHMOD & 0666);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
279
		if ($ok) {
280
			if (strpos($fichier, ".php") !== false) {
281
				spip_clear_opcode_cache(realpath($fichier));
282
			}
283
284
			return $ok;
285
		}
286
	}
287
288
	if (!$ignorer_echec) {
289
		include_spip('inc/autoriser');
290
		if (autoriser('chargerftp')) {
291
			raler_fichier($fichier);
292
		}
293
		spip_unlink($fichier);
294
	}
295
	spip_log("Ecriture fichier $fichier impossible", _LOG_INFO_IMPORTANTE);
296
297
	return false;
298
}
299
300
/**
301
 * Écrire un contenu dans un fichier encapsulé en PHP pour en empêcher l'accès en l'absence
302
 * de fichier htaccess
303
 *
304
 * @uses ecrire_fichier()
305
 *
306
 * @param string $fichier
307
 *     Chemin du fichier
308
 * @param string $contenu
309
 *     Contenu à écrire
310
 * @param bool $ecrire_quand_meme
311
 *     - true pour ne pas raler en cas d'erreur
312
 *     - false affichera un message si on est webmestre
313
 * @param bool $truncate
314
 *     Écriture avec troncation ?
315
 */
316
function ecrire_fichier_securise($fichier, $contenu, $ecrire_quand_meme = false, $truncate = true) {
317
	if (substr($fichier, -4) !== '.php') {
318
		spip_log('Erreur de programmation: ' . $fichier . ' doit finir par .php');
319
	}
320
	$contenu = "<" . "?php die ('Acces interdit'); ?" . ">\n" . $contenu;
321
322
	return ecrire_fichier($fichier, $contenu, $ecrire_quand_meme, $truncate);
323
}
324
325
326
/**
327
 * @param string $fichier
328
 * @param string $contenu
329
 * @param bool $force
330
 * @return bool
331
 */
332
function ecrire_fichier_calcule_si_modifie($fichier, $contenu, $force=false, $use_copy=false) {
333
	$fichier_tmp = $fichier . '.last';
334
	if (!ecrire_fichier($fichier_tmp, $contenu, true)){
335
		return false;
336
	}
337
	if ($force
338
	  or !file_exists($fichier)
339
		or md5_file($fichier) != md5_file($fichier_tmp)) {
340
		if ($use_copy) {
341
			@copy($fichier_tmp, $fichier);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
342
		}
343
		else {
344
			@rename($fichier_tmp, $fichier);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
345
		}
346
		// eviter que PHP ne reserve le vieux timestamp
347
		clearstatcache(true, $fichier);
348
	}
349
	return true;
350
}
351
352
353
/**
354
 * Lire un fichier encapsulé en PHP
355
 *
356
 * @uses lire_fichier()
357
 *
358
 * @param string $fichier
359
 *     Chemin du fichier
360
 * @param string $contenu
361
 *     Le contenu du fichier sera placé dans cette variable
362
 * @param array $options
363
 *     Options tel que :
364
 *
365
 *     - 'phpcheck' => 'oui' : vérifie qu'on a bien du php
366
 * @return bool
367
 *     true si l'opération a réussie, false sinon.
368
 */
369
function lire_fichier_securise($fichier, &$contenu, $options = array()) {
370
	if ($res = lire_fichier($fichier, $contenu, $options)) {
371
		$contenu = substr($contenu, strlen("<" . "?php die ('Acces interdit'); ?" . ">\n"));
372
	}
373
374
	return $res;
375
}
376
377
/**
378
 * Affiche un message d’erreur bloquant, indiquant qu’il n’est pas possible de créer
379
 * le fichier à cause des droits sur le répertoire parent au fichier.
380
 *
381
 * Arrête le script PHP par un exit;
382
 *
383
 * @uses minipres() Pour afficher le message
384
 *
385
 * @param string $fichier
386
 *     Chemin du fichier
387
 **/
388
function raler_fichier($fichier) {
389
	include_spip('inc/minipres');
390
	$dir = dirname($fichier);
391
	http_status(401);
392
	echo minipres(_T('texte_inc_meta_2'), "<h4 style='color: red'>"
393
		. _T('texte_inc_meta_1', array('fichier' => $fichier))
394
		. " <a href='"
395
		. generer_url_ecrire('install', "etape=chmod&test_dir=$dir")
396
		. "'>"
397
		. _T('texte_inc_meta_2')
398
		. "</a> "
399
		. _T('texte_inc_meta_3',
400
			array('repertoire' => joli_repertoire($dir)))
401
		. "</h4>\n");
402
	exit;
403
}
404
405
406
/**
407
 * Teste si un fichier est récent (moins de n secondes)
408
 *
409
 * @param string $fichier
410
 *     Chemin du fichier
411
 * @param int $n
412
 *     Âge testé, en secondes
413
 * @return bool
414
 *     - true si récent, false sinon
415
 */
416
function jeune_fichier($fichier, $n) {
417
	if (!file_exists($fichier)) {
418
		return false;
419
	}
420
	if (!$c = @filemtime($fichier)) {
421
		return false;
422
	}
423
424
	return (time() - $n <= $c);
425
}
426
427
/**
428
 * Supprimer un fichier de manière sympa (flock)
429
 *
430
 * @param string $fichier
431
 *     Chemin du fichier
432
 * @param bool $lock
433
 *     true pour utiliser un verrou
434
 * @return bool|void
435
 *     - true si le fichier n'existe pas
436
 *     - false si on n'arrive pas poser le verrou
437
 *     - void sinon
438
 */
439
function supprimer_fichier($fichier, $lock = true) {
440
	if (!@file_exists($fichier)) {
441
		return true;
442
	}
443
444
	if ($lock) {
445
		// verrouiller le fichier destination
446
		if (!$fp = spip_fopen_lock($fichier, 'a', LOCK_EX)) {
447
			return false;
448
		}
449
450
		// liberer le verrou
451
		spip_fclose_unlock($fp);
452
	}
453
454
	// supprimer
455
	return @unlink($fichier);
456
}
457
458
/**
459
 * Supprimer brutalement un fichier, s'il existe
460
 *
461
 * @param string $f
462
 *     Chemin du fichier
463
 */
464
function spip_unlink($f) {
465
	if (!is_dir($f)) {
466
		supprimer_fichier($f, false);
467
	} else {
468
		@unlink("$f/.ok");
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
469
		@rmdir($f);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
470
	}
471
}
472
473
/**
474
 * Invalidates a PHP file from any active opcode caches.
475
 *
476
 * If the opcode cache does not support the invalidation of individual files,
477
 * the entire cache will be flushed.
478
 * kudo : http://cgit.drupalcode.org/drupal/commit/?id=be97f50
479
 *
480
 * @param string $filepath
481
 *   The absolute path of the PHP file to invalidate.
482
 */
483
function spip_clear_opcode_cache($filepath) {
484
	clearstatcache(true, $filepath);
485
486
	// Zend OPcache
487
	if (function_exists('opcache_invalidate')) {
488
		$invalidate = @opcache_invalidate($filepath, true);
489
		// si l'invalidation a echoue lever un flag
490
		if (!$invalidate and !defined('_spip_attend_invalidation_opcode_cache')) {
491
			define('_spip_attend_invalidation_opcode_cache',true);
492
		}
493
	} elseif (!defined('_spip_attend_invalidation_opcode_cache')) {
494
		// n'agira que si opcache est effectivement actif (il semble qu'on a pas toujours la fonction opcache_invalidate)
495
		define('_spip_attend_invalidation_opcode_cache',true);
496
	}
497
	// APC.
498
	if (function_exists('apc_delete_file')) {
499
		// apc_delete_file() throws a PHP warning in case the specified file was
500
		// not compiled yet.
501
		// @see http://php.net/apc-delete-file
502
		@apc_delete_file($filepath);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
503
	}
504
}
505
506
/**
507
 * Attendre l'invalidation de l'opcache
508
 * 
509
 * Si opcache est actif et en mode `validate_timestamps`,
510
 * le timestamp du fichier ne sera vérifié qu'après une durée 
511
 * en secondes fixée par `revalidate_freq`.
512
 * 
513
 * Il faut donc attendre ce temps là pour être sûr qu'on va bien 
514
 * bénéficier de la recompilation du fichier par l'opcache.
515
 * 
516
 * Ne fait rien en dehors de ce cas
517
 * 
518
 * @note
519
 *     C'est une config foireuse déconseillée de opcode cache mais 
520
 *     malheureusement utilisée par Octave.
521
 * @link http://stackoverflow.com/questions/25649416/when-exactly-does-php-5-5-opcache-check-file-timestamp-based-on-revalidate-freq
522
 * @link http://wiki.mikejung.biz/PHP_OPcache
523
 *
524
 */
525
function spip_attend_invalidation_opcode_cache($timestamp = null) {
526
	if (function_exists('opcache_get_configuration')
527
		and @ini_get('opcache.enable')
528
		and @ini_get('opcache.validate_timestamps')
529
		and ($duree = intval(@ini_get('opcache.revalidate_freq')) or $duree = 2)
530
		and defined('_spip_attend_invalidation_opcode_cache') // des invalidations ont echouees
531
	) {
532
		$wait = $duree + 1;
533
		if ($timestamp) {
534
			$wait -= (time() - $timestamp);
535
			if ($wait<0) {
536
				$wait = 0;
537
			}
538
		}
539
		spip_log('Probleme de configuration opcache.revalidate_freq '. $duree .'s : on attend '.$wait.'s', _LOG_INFO_IMPORTANTE);
540
		if ($wait) {
541
			sleep($duree + 1);
542
		}
543
	}
544
}
545
546
547
/**
548
 * Suppression complete d'un repertoire.
549
 *
550
 * @link http://www.php.net/manual/en/function.rmdir.php#92050
551
 *
552
 * @param string $dir Chemin du repertoire
553
 * @return bool Suppression reussie.
554
 */
555
function supprimer_repertoire($dir) {
556
	if (!file_exists($dir)) {
557
		return true;
558
	}
559
	if (!is_dir($dir) || is_link($dir)) {
560
		return @unlink($dir);
561
	}
562
563
	foreach (scandir($dir) as $item) {
564
		if ($item == '.' || $item == '..') {
565
			continue;
566
		}
567
		if (!supprimer_repertoire($dir . "/" . $item)) {
568
			@chmod($dir . "/" . $item, 0777);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
569
			if (!supprimer_repertoire($dir . "/" . $item)) {
570
				return false;
571
			}
572
		};
573
	}
574
575
	return @rmdir($dir);
576
}
577
578
579
/**
580
 * Crée un sous répertoire
581
 *
582
 * Retourne `$base/${subdir}/` si le sous-repertoire peut être crée
583
 *
584
 * @example
585
 *     ```
586
 *     sous_repertoire(_DIR_CACHE, 'demo');
587
 *     sous_repertoire(_DIR_CACHE . '/demo');
588
 *     ```
589
 *
590
 * @param string $base
591
 *     - Chemin du répertoire parent (avec $subdir)
592
 *     - sinon chemin du répertoire à créer
593
 * @param string $subdir
594
 *     - Nom du sous répertoire à créer,
595
 *     - non transmis, `$subdir` vaut alors ce qui suit le dernier `/` dans `$base`
596
 * @param bool $nobase
597
 *     true pour ne pas avoir le chemin du parent `$base/` dans le retour
598
 * @param bool $tantpis
599
 *     true pour ne pas raler en cas de non création du répertoire
600
 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
601
 *     Chemin du répertoire créé.
602
 **/
603
function sous_repertoire($base, $subdir = '', $nobase = false, $tantpis = false) {
604
	static $dirs = array();
605
606
	$base = str_replace("//", "/", $base);
607
608
	# suppr le dernier caractere si c'est un /
609
	$base = rtrim($base, '/');
610
611
	if (!strlen($subdir)) {
612
		$n = strrpos($base, "/");
613
		if ($n === false) {
614
			return $nobase ? '' : ($base . '/');
615
		}
616
		$subdir = substr($base, $n + 1);
617
		$base = substr($base, 0, $n + 1);
618
	} else {
619
		$base .= '/';
620
		$subdir = str_replace("/", "", $subdir);
621
	}
622
623
	$baseaff = $nobase ? '' : $base;
624
	if (isset($dirs[$base . $subdir])) {
625
		return $baseaff . $dirs[$base . $subdir];
626
	}
627
628
	$path = $base . $subdir; # $path = 'IMG/distant/pdf' ou 'IMG/distant_pdf'
629
630
	if (file_exists("$path/.ok")) {
631
		return $baseaff . ($dirs[$base . $subdir] = "$subdir/");
632
	}
633
634
	@mkdir($path, _SPIP_CHMOD);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
635
	@chmod($path, _SPIP_CHMOD);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
636
637
	if (is_dir($path) && is_writable($path)) {
638
		@touch("$path/.ok");
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
639
		spip_log("creation $base$subdir/");
640
641
		return $baseaff . ($dirs[$base . $subdir] = "$subdir/");
642
	}
643
644
	// en cas d'echec c'est peut etre tout simplement que le disque est plein :
645
	// l'inode du fichier dir_test existe, mais impossible d'y mettre du contenu
646
	spip_log("echec creation $base${subdir}");
647
	if ($tantpis) {
648
		return '';
649
	}
650
	if (!_DIR_RESTREINT) {
651
		$base = preg_replace(',^' . _DIR_RACINE . ',', '', $base);
652
	}
653
	$base .= $subdir;
654
	raler_fichier($base . '/.ok');
655
}
656
657
658
/**
659
 * Parcourt récursivement le repertoire `$dir`, et renvoie les
660
 * fichiers dont le chemin vérifie le pattern (preg) donné en argument.
661
 *
662
 * En cas d'echec retourne un `array()` vide
663
 *
664
 * @example
665
 *     ```
666
 *     $x = preg_files('ecrire/data/', '[.]lock$');
667
 *     // $x array()
668
 *     ```
669
 *
670
 * @note
671
 *   Attention, afin de conserver la compatibilite avec les repertoires '.plat'
672
 *   si `$dir = 'rep/sous_rep_'` au lieu de `rep/sous_rep/` on scanne `rep/` et on
673
 *   applique un pattern `^rep/sous_rep_`
674
 *
675
 * @param string $dir
676
 *     Répertoire à parcourir
677
 * @param int|string $pattern
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $pattern a bit more specific; maybe use integer.
Loading history...
678
 *     Expression régulière pour trouver des fichiers, tel que `[.]lock$`
679
 * @param int $maxfiles
680
 *     Nombre de fichiers maximums retournés
681
 * @param array $recurs
682
 *     false pour ne pas descendre dans les sous répertoires
683
 * @return array
684
 *     Chemins des fichiers trouvés.
685
 **/
686
function preg_files($dir, $pattern = -1 /* AUTO */, $maxfiles = 10000, $recurs = array()) {
687
	$nbfiles = 0;
688
	if ($pattern == -1) {
689
		$pattern = "^$dir";
690
	}
691
	$fichiers = array();
692
	// revenir au repertoire racine si on a recu dossier/truc
693
	// pour regarder dossier/truc/ ne pas oublier le / final
694
	$dir = preg_replace(',/[^/]*$,', '', $dir);
695
	if ($dir == '') {
696
		$dir = '.';
697
	}
698
699
	if (@is_dir($dir) and is_readable($dir) and $d = opendir($dir)) {
700
		while (($f = readdir($d)) !== false && ($nbfiles < $maxfiles)) {
701
			if ($f[0] != '.' # ignorer . .. .svn etc
702
				and $f != 'CVS'
703
				and $f != 'remove.txt'
704
				and is_readable($f = "$dir/$f")
705
			) {
706
				if (is_file($f)) {
707
					if (preg_match(";$pattern;iS", $f)) {
708
						$fichiers[] = $f;
709
						$nbfiles++;
710
					}
711
				} else {
712
					if (is_dir($f) and is_array($recurs)) {
713
						$rp = @realpath($f);
714
						if (!is_string($rp) or !strlen($rp)) {
715
							$rp = $f;
716
						} # realpath n'est peut etre pas autorise
717
						if (!isset($recurs[$rp])) {
718
							$recurs[$rp] = true;
719
							$beginning = $fichiers;
720
							$end = preg_files("$f/", $pattern,
721
								$maxfiles - $nbfiles, $recurs);
722
							$fichiers = array_merge((array)$beginning, (array)$end);
723
							$nbfiles = count($fichiers);
724
						}
725
					}
726
				}
727
			}
728
		}
729
		closedir($d);
730
	}
731
	sort($fichiers);
732
733
	return $fichiers;
734
}
735