Completed
Push — master ( 37f24f...c12d0a )
by cam
04:22
created

connect_sql.php ➔ query_echappe_textes()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 8
nop 1
dl 0
loc 37
rs 8.0835
c 0
b 0
f 0
1
<?php
2
3
/***************************************************************************\
4
 *  SPIP, Système de publication pour l'internet                           *
5
 *                                                                         *
6
 *  Copyright © avec tendresse depuis 2001                                 *
7
 *  Arnaud Martin, Antoine Pitrou, Philippe Rivière, Emmanuel Saint-James  *
8
 *                                                                         *
9
 *  Ce programme est un logiciel libre distribué sous licence GNU/GPL.     *
10
 *  Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne.   *
11
\***************************************************************************/
12
13
/**
14
 * Utilitaires indispensables autour des serveurs SQL
15
 *
16
 * @package SPIP\Core\SQL
17
 **/
18
if (!defined('_ECRIRE_INC_VERSION')) {
19
	return;
20
}
21
require_once _ROOT_RESTREINT . 'base/objets.php';
22
23
24
/**
25
 * Connexion à un serveur de base de données
26
 *
27
 * On charge le fichier `config/$serveur` (`$serveur='connect'` pour le principal)
28
 * qui est censé initaliser la connexion en appelant la fonction `spip_connect_db`
29
 * laquelle met dans la globale `db_ok` la description de la connexion.
30
 *
31
 * On la mémorise dans un tableau pour permettre plusieurs serveurs.
32
 *
33
 * À l'installation, il faut simuler l'existence de ce fichier.
34
 *
35
 * @uses spip_connect_main()
36
 *
37
 * @param string $serveur Nom du connecteur
38
 * @param string $version Version de l'API SQL
39
 * @return bool|array
40
 *     - false si la connexion a échouée,
41
 *     - tableau décrivant la connexion sinon
42
 **/
43
function spip_connect($serveur = '', $version = '') {
44
45
	$serveur = !is_string($serveur) ? '' : strtolower($serveur);
46
	$index = $serveur ? $serveur : 0;
47
	if (!$version) {
48
		$version = $GLOBALS['spip_sql_version'];
49
	}
50
	if (isset($GLOBALS['connexions'][$index][$version])) {
51
		return $GLOBALS['connexions'][$index];
52
	}
53
54
	include_spip('base/abstract_sql');
55
	$install = (_request('exec') == 'install');
56
57
	// Premiere connexion ?
58
	if (!($old = isset($GLOBALS['connexions'][$index]))) {
59
		$f = (!preg_match('/^[\w\.]*$/', $serveur))
60
			? '' // nom de serveur mal ecrit
61
			: ($serveur ?
62
				(_DIR_CONNECT . $serveur . '.php') // serveur externe
63
				: (_FILE_CONNECT ? _FILE_CONNECT // serveur principal ok
64
					: ($install ? _FILE_CONNECT_TMP // init du serveur principal
65
						: ''))); // installation pas faite
66
67
		unset($GLOBALS['db_ok']);
68
		unset($GLOBALS['spip_connect_version']);
69
		if ($f) {
70
			if (is_readable($f)) {
71
				include($f);
72
			} elseif ($serveur and !$install) {
73
				// chercher une declaration de serveur dans le path
74
				// qui pourra un jour servir a declarer des bases sqlite
75
				// par des plugins. Et sert aussi aux boucles POUR.
76
				find_in_path("$serveur.php", 'connect/', true);
77
			}
78
		}
79
		if (!isset($GLOBALS['db_ok'])) {
80
			// fera mieux la prochaine fois
81
			if ($install) {
82
				return false;
83
			}
84
			if ($f and is_readable($f)) {
85
				spip_log("spip_connect: fichier de connexion '$f' OK.", _LOG_INFO_IMPORTANTE);
86
			} else {
87
				spip_log("spip_connect: fichier de connexion '$f' non trouve", _LOG_INFO_IMPORTANTE);
88
			}
89
			spip_log("spip_connect: echec connexion ou serveur $index mal defini dans '$f'.", _LOG_HS);
90
91
			// ne plus reessayer si ce n'est pas l'install
92
			return $GLOBALS['connexions'][$index] = false;
93
		}
94
		$GLOBALS['connexions'][$index] = $GLOBALS['db_ok'];
95
	}
96
	// si la connexion a deja ete tentee mais a echoue, le dire!
97
	if (!$GLOBALS['connexions'][$index]) {
98
		return false;
99
	}
100
101
	// la connexion a reussi ou etait deja faite.
102
	// chargement de la version du jeu de fonctions
103
	// si pas dans le fichier par defaut
104
	$type = $GLOBALS['db_ok']['type'];
105
	$jeu = 'spip_' . $type . '_functions_' . $version;
106
	if (!isset($GLOBALS[$jeu])) {
107
		if (!find_in_path($type . '_' . $version . '.php', 'req/', true)) {
108
			spip_log("spip_connect: serveur $index version '$version' non defini pour '$type'", _LOG_HS);
109
110
			// ne plus reessayer
111
			return $GLOBALS['connexions'][$index][$version] = array();
112
		}
113
	}
114
	$GLOBALS['connexions'][$index][$version] = $GLOBALS[$jeu];
115
	if ($old) {
116
		return $GLOBALS['connexions'][$index];
117
	}
118
119
	$GLOBALS['connexions'][$index]['spip_connect_version'] = isset($GLOBALS['spip_connect_version']) ? $GLOBALS['spip_connect_version'] : 0;
120
121
	// initialisation de l'alphabet utilise dans les connexions SQL
122
	// si l'installation l'a determine.
123
	// Celui du serveur principal l'impose aux serveurs secondaires
124
	// s'ils le connaissent
125
126
	if (!$serveur) {
127
		$charset = spip_connect_main($GLOBALS[$jeu], $GLOBALS['db_ok']['charset']);
128
		if (!$charset) {
129
			unset($GLOBALS['connexions'][$index]);
130
			spip_log("spip_connect: absence de charset", _LOG_AVERTISSEMENT);
131
132
			return false;
133
		}
134
	} else {
135
		if ($GLOBALS['db_ok']['charset']) {
136
			$charset = $GLOBALS['db_ok']['charset'];
137
		}
138
		// spip_meta n'existe pas toujours dans la base
139
		// C'est le cas d'un dump sqlite par exemple 
140
		elseif ($GLOBALS['connexions'][$index]['spip_connect_version']
141
			and sql_showtable('spip_meta', true, $serveur)
142
			and $r = sql_getfetsel('valeur', 'spip_meta', "nom='charset_sql_connexion'", '', '', '', '', $serveur)
143
		) {
144
			$charset = $r;
145
		} else {
146
			$charset = -1;
147
		}
148
	}
149
	if ($charset != -1) {
150
		$f = $GLOBALS[$jeu]['set_charset'];
151
		if (function_exists($f)) {
152
			$f($charset, $serveur);
153
		}
154
	}
155
156
	return $GLOBALS['connexions'][$index];
157
}
158
159
/**
160
 * Log la dernière erreur SQL présente sur la connexion indiquée
161
 *
162
 * @param string $serveur Nom du connecteur de bdd utilisé
163
 **/
164
function spip_sql_erreur($serveur = '') {
165
	$connexion = spip_connect($serveur);
166
	$e = sql_errno($serveur);
167
	$t = (isset($connexion['type']) ? $connexion['type'] : 'sql');
168
	$m = "Erreur $e de $t: " . sql_error($serveur) . "\nin " . sql_error_backtrace() . "\n" . trim($connexion['last']);
169
	$f = $t . $serveur;
170
	spip_log($m, $f . '.' . _LOG_ERREUR);
171
}
172
173
/**
174
 * Retourne le nom de la fonction adaptée de l'API SQL en fonction du type de serveur
175
 *
176
 * Cette fonction ne doit être appelée qu'à travers la fonction sql_serveur
177
 * définie dans base/abstract_sql
178
 *
179
 * Elle existe en tant que gestionnaire de versions,
180
 * connue seulement des convertisseurs automatiques
181
 *
182
 * @param string $version Numéro de version de l'API SQL
183
 * @param string $ins Instruction de l'API souhaitée, tel que 'allfetsel'
184
 * @param string $serveur Nom du connecteur
185
 * @param bool $continue true pour continuer même si le serveur SQL ou l'instruction est indisponible
186
 * @return array|bool|string
187
 *     - string : nom de la fonction à utiliser,
188
 *     - false : si la connexion a échouée
189
 *     - array : description de la connexion, si l'instruction sql est indisponible pour cette connexion
190
 **/
191
function spip_connect_sql($version, $ins = '', $serveur = '', $continue = false) {
192
	$desc = spip_connect($serveur, $version);
193
	if (function_exists($f = @$desc[$version][$ins])) {
194
		return $f;
195
	}
196
	if ($continue) {
197
		return $desc;
198
	}
199
	if ($ins) {
200
		spip_log("Le serveur '$serveur' version $version n'a pas '$ins'", _LOG_ERREUR);
201
	}
202
	include_spip('inc/minipres');
203
	echo minipres(_T('info_travaux_titre'), _T('titre_probleme_technique'), array('status' => 503));
204
	exit;
205
}
206
207
/**
208
 * Fonction appelée par le fichier connecteur de base de données
209
 * crée dans `config/` à l'installation.
210
 *
211
 * Il contient un appel direct à cette fonction avec comme arguments
212
 * les identifants de connexion.
213
 *
214
 * Si la connexion reussit, la globale `db_ok` mémorise sa description.
215
 * C'est un tableau également retourné en valeur, pour les appels
216
 * lors de l'installation.
217
 *
218
 * @param string $host Adresse du serveur de base de données
219
 * @param string $port Port utilisé pour la connexion
220
 * @param string $login Identifiant de connexion à la base de données
221
 * @param string $pass Mot de passe pour cet identifiant
222
 * @param string $db Nom de la base de données à utiliser
223
 * @param string $type Type de base de données tel que 'mysql', 'sqlite3' (cf ecrire/req/)
224
 * @param string $prefixe Préfixe des tables SPIP
225
 * @param string $auth Type d'authentification (cas si 'ldap')
226
 * @param string $charset Charset de la connexion SQL (optionnel)
227
 * @return array          Description de la connexion
0 ignored issues
show
Documentation introduced by
Should the return type not be null|array<string,array|string>?

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...
228
 */
229
function spip_connect_db(
230
	$host,
231
	$port,
232
	$login,
233
	$pass,
234
	$db = '',
235
	$type = 'mysql',
236
	$prefixe = '',
237
	$auth = '',
238
	$charset = ''
239
) {
240
	// temps avant nouvelle tentative de connexion
241
	// suite a une connection echouee
242
	if (!defined('_CONNECT_RETRY_DELAY')) {
243
		define('_CONNECT_RETRY_DELAY', 30);
244
	}
245
246
	$f = "";
247
	// un fichier de identifiant par combinaison (type,host,port,db)
248
	// pour ne pas declarer tout indisponible d'un coup
249
	// si en cours d'installation ou si db=@test@ on ne pose rien
250
	// car c'est un test de connexion
251
	if (!defined('_ECRIRE_INSTALL') and $db !== "@test@") {
252
		$f = _DIR_TMP . $type . '.' . substr(md5($host . $port . $db), 0, 8) . '.out';
253
	} elseif ($db == '@test@') {
254
		$db = '';
255
	}
256
257
	if ($f
258
		and @file_exists($f)
259
		and (time() - @filemtime($f) < _CONNECT_RETRY_DELAY)
260
	) {
261
		spip_log("Echec : $f recent. Pas de tentative de connexion", _LOG_HS);
262
263
		return;
264
	}
265
266
	if (!$prefixe) {
267
		$prefixe = isset($GLOBALS['table_prefix'])
268
			? $GLOBALS['table_prefix'] : $db;
269
	}
270
	$h = charger_fonction($type, 'req', true);
271
	if (!$h) {
272
		spip_log("les requetes $type ne sont pas fournies", _LOG_HS);
273
274
		return;
275
	}
276
	if ($g = $h($host, $port, $login, $pass, $db, $prefixe)) {
277
278
		if (!is_array($auth)) {
279
			// compatibilite version 0.7 initiale
280
			$g['ldap'] = $auth;
281
			$auth = array('ldap' => $auth);
282
		}
283
		$g['authentification'] = $auth;
284
		$g['type'] = $type;
285
		$g['charset'] = $charset;
286
287
		return $GLOBALS['db_ok'] = $g;
288
	}
289
	// En cas d'indisponibilite du serveur, eviter de le bombarder
290
	if ($f) {
291
		@touch($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...
292
		spip_log("Echec connexion serveur $type : host[$host] port[$port] login[$login] base[$db]", $type . '.' . _LOG_HS);
293
	}
294
}
295
296
297
/**
298
 * Première connexion au serveur principal de base de données
299
 *
300
 * Retourner le charset donnée par la table principale
301
 * mais vérifier que le fichier de connexion n'est pas trop vieux
302
 *
303
 * @note
304
 *   Version courante = 0.8
305
 *
306
 *   - La version 0.8 indique un charset de connexion comme 9e arg
307
 *   - La version 0.7 indique un serveur d'authentification comme 8e arg
308
 *   - La version 0.6 indique le prefixe comme 7e arg
309
 *   - La version 0.5 indique le serveur comme 6e arg
310
 *
311
 *   La version 0.0 (non numerotée) doit être refaite par un admin.
312
 *   Les autres fonctionnent toujours, même si :
313
 *
314
 *   - la version 0.1 est moins performante que la 0.2
315
 *   - la 0.2 fait un include_ecrire('inc_db_mysql.php3').
316
 *
317
 * @param array $connexion Description de la connexion
318
 * @param string $charset_sql_connexion charset de connexion fourni dans l'appal a spip_connect_db
319
 * @return string|bool|int
320
 *     - false si pas de charset connu pour la connexion
321
 *     - -1 charset non renseigné
322
 *     - nom du charset sinon
323
 **/
324
function spip_connect_main($connexion, $charset_sql_connexion = '') {
325
	if ($GLOBALS['spip_connect_version'] < 0.1 and _DIR_RESTREINT) {
326
		include_spip('inc/headers');
327
		redirige_url_ecrire('upgrade', 'reinstall=oui');
328
	}
329
330
	if (!($f = $connexion['select'])) {
331
		return false;
332
	}
333
	// si le charset est fourni, l'utiliser
334
	if ($charset_sql_connexion) {
335
		return $charset_sql_connexion;
336
	}
337
	// sinon on regarde la table spip_meta
338
	// en cas d'erreur select retourne la requette (is_string=true donc)
339
	if (!$r = $f('valeur', 'spip_meta', "nom='charset_sql_connexion'")
340
		or is_string($r)
341
	) {
342
		return false;
343
	}
344
	if (!($f = $connexion['fetch'])) {
345
		return false;
346
	}
347
	$r = $f($r);
348
349
	return (isset($r['valeur']) && $r['valeur']) ? $r['valeur'] : -1;
350
}
351
352
/**
353
 * Connection à LDAP
354
 *
355
 * Fonction présente pour compatibilité
356
 *
357
 * @deprecated Utiliser l'authentification LDAP de auth/ldap
358
 * @uses auth_ldap_connect()
359
 *
360
 * @param string $serveur Nom du connecteur
361
 * @return array
362
 */
363
function spip_connect_ldap($serveur = '') {
364
	include_spip('auth/ldap');
365
366
	return auth_ldap_connect($serveur);
367
}
368
369
/**
370
 * Échappement d'une valeur sous forme de chaîne PHP
371
 *
372
 * Échappe une valeur (num, string, array) pour en faire une chaîne pour PHP.
373
 * Un `array(1,'a',"a'")` renvoie la chaine `"'1','a','a\''"`
374
 *
375
 * @note
376
 *   L'usage comme échappement SQL est déprécié, à remplacer par sql_quote().
377
 *
378
 * @param num|string|array $a Valeur à échapper
379
 * @return string Valeur échappée.
380
 **/
381
function _q($a) {
382
	return (is_numeric($a)) ? strval($a) :
383
		(!is_array($a) ? ("'" . addslashes($a) . "'")
384
			: join(",", array_map('_q', $a)));
385
}
386
387
/**
388
 * Echapper les textes entre ' ' ou " " d'une requête SQL
389
 * avant son pre-traitement
390
 *
391
 * On renvoi la query sans textes et les textes séparés, dans
392
 * leur ordre d'apparition dans la query
393
 *
394
 * @see query_reinjecte_textes()
395
 *
396
 * @param string $query
397
 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string|false|array>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
398
 */
399
function query_echappe_textes($query) {
400
	static $codeEchappements = array("''" => "\x1@##@\x1", "\'" => "\x2@##@\x2", "\\\"" => "\x3@##@\x3");
401
	$query = str_replace(array_keys($codeEchappements), array_values($codeEchappements), $query);
402
	if (preg_match_all("/((['])[^']*(\\2))|(([\"])[^\"]*(\\5))/S", $query, $textes)) {
403
		$textes = reset($textes); // indice 0 du match
404
		switch (count($textes)) {
405
			case 0:
406
				$replace = array();
407
				break;
408
			case 1:
409
				$replace = array('%1$s');
410
				break;
411
			case 2:
412
				$replace = array('%1$s', '%2$s');
413
				break;
414
			case 3:
415
				$replace = array('%1$s', '%2$s', '%3$s');
416
				break;
417
			case 4:
418
				$replace = array('%1$s', '%2$s', '%3$s', '%4$s');
419
				break;
420
			case 5:
421
				$replace = array('%1$s', '%2$s', '%3$s', '%4$s', '%5$s');
422
				break;
423
			default:
424
				$replace = range(1, count($textes));
425
				$replace = '%' . implode('$s,%', $replace) . '$s';
426
				$replace = explode(',', $replace);
427
				break;
428
		}
429
		$query = str_replace($textes, $replace, $query);
430
	} else {
431
		$textes = array();
432
	}
433
434
	return array($query, $textes);
435
}
436
437
/**
438
 * Réinjecter les textes d'une requete SQL à leur place initiale,
439
 * après traitement de la requête
440
 *
441
 * @see query_echappe_textes()
442
 *
443
 * @param string $query
444
 * @param array $textes
445
 * @return string
446
 */
447
function query_reinjecte_textes($query, $textes) {
448
	static $codeEchappements = array("''" => "\x1@##@\x1", "\'" => "\x2@##@\x2", "\\\"" => "\x3@##@\x3");
449
	# debug de la substitution
450
	#if (($c1=substr_count($query,"%"))!=($c2=count($textes))){
451
	#	spip_log("$c1 ::". $query,"tradquery"._LOG_ERREUR);
452
	#	spip_log("$c2 ::". var_export($textes,1),"tradquery"._LOG_ERREUR);
453
	#	spip_log("ini ::". $qi,"tradquery"._LOG_ERREUR);
454
	#}
455
	switch (count($textes)) {
456
		case 0:
457
			break;
458
		case 1:
459
			$query = sprintf($query, $textes[0]);
460
			break;
461
		case 2:
462
			$query = sprintf($query, $textes[0], $textes[1]);
463
			break;
464
		case 3:
465
			$query = sprintf($query, $textes[0], $textes[1], $textes[2]);
466
			break;
467 View Code Duplication
		case 4:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
468
			$query = sprintf($query, $textes[0], $textes[1], $textes[2], $textes[3]);
469
			break;
470 View Code Duplication
		case 5:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
471
			$query = sprintf($query, $textes[0], $textes[1], $textes[2], $textes[3], $textes[4]);
472
			break;
473
		default:
474
			array_unshift($textes, $query);
475
			$query = call_user_func_array('sprintf', $textes);
476
			break;
477
	}
478
479
	$query = str_replace(array_values($codeEchappements), array_keys($codeEchappements), $query);
480
481
	return $query;
482
}
483
484
485
/**
486
 * Exécute une requête sur le serveur SQL
487
 *
488
 * @note Ne génère pas d’erreur fatale si la connexion à la BDD n’existe pas
489
 * @see sql_query()
490
 * @deprecated  Pour compatibilité. Utiliser `sql_query()` ou l'API `sql_*`.
491
 *
492
 * @param string $query Texte de la requête
493
 * @param string $serveur Nom du connecteur pour la base de données
494
 * @return bool|mixed
495
 *     - false si on ne peut pas exécuter la requête
496
 *     - indéfini sinon.
497
 **/
498
function spip_query($query, $serveur = '') {
499
500
	$f = spip_connect_sql($GLOBALS['spip_sql_version'], 'query', $serveur, true);
501
502
	return function_exists($f) ? $f($query, $serveur) : false;
503
}
504