Insérer des données dans une requête.
On génère en PHP une chaîne de caractères qu'on envoit ensuite à un SGBD indépendant pour gérer la requête ainsi construite.
Tout comme il existe des types en PHP, il y aussi des types en SQL, mais ceux-ci ne sont pas vraiment liés.
Par exemple une chaîne de caractères, en SQL, doit se trouver entre en guillemets simples.
Ainsi, la requête
SELECT titre FROM livres WHERE auteur = 'Simone Weil'
est valide syntaxiquement.
En PHP, sa déclaration devient
$sql = 'SELECT titre FROM livres WHERE auteur = \'Simone Weil\'';
ou
$sql = "SELECT titre FROM livres WHERE auteur = 'Simone Weil'";
selon que l'on choisisse d'utiliser les simples ou double guillemets pour déclarer la chaîne de caractères.
Bien entendu, il faut qu'il soit possible que cette chaîne contienne elle-même un guillemet simple. Le système d'échappement dépend du SGBD, mais il existe globalement deux écoles : soit on échappe avec un \, soit avec un autre '.
L'idéal, pour échapper, étant d'utiliser la fonction spécifique fournie par le pilote du SGBD (mysql_escape_string, sqlite_escape_string, etc.)
Voyons voir ce que ça donne, naïvement (sans échappement), avec des variables en GET.
$sql = "SELECT titre FROM livres WHERE auteur = '{$_GET['auteur']}'";
(interprétation de la variable avec les guillemets doubles)
ou
$sql = 'SELECT titre FROM livres WHERE auteur = \''.$_GET['auteur'].'\'';
(concaténation, guillemets simples)
Le plus souvent on retrouve apparement de la concaténation avec des guillemets doubles, probablement parce que les gens ne savent pas interpréter des tableaux à l'intérieur de ces derniers..
Si vous mettez auteur à O'malley puis tentez d'executer la requête, vous aurez une syntax error de la part du SGBD.
Dans ce cas, ce n'est pas dangereux, c'est juste un bug, mais dans d'autres cela peut produire une grave faille de sécurité.
Comme la plupart des débutants font comme dans les exemples (mauvais) cités ci-dessus, PHP a inventé un mécanisme pour protéger les newbies de cette faille, que l'on appelle l'injection SQL. Ce système, c'est le magic_quotes_gpc.
S'il est activé, il réalise tout bêtement un addslashes (ce qui correspond au moyen d'échappement de mysql) sur toutes les variables GET, POST et Cookie. Effectivement, ça protège. Mais c'est peu élégant, pas portable et surtout pas logique. Je suis désolé, moi quand je récupère $_GET['auteur'], je veux le contenu du paramètre passé dans l'url, pas un truc pré-encodé pour un certain SGBD.
Il est donc nécessaire, au début de chaque script PHP, de détecter si magic_quotes_gpc est activé et si c'est le cas, effectuer un stripslashes récursivement sur toutes les valeurs de GET, POST et Cookie. Plus d'informations sur cette opération sur la documentation de la fonction get_magic_quotes_gpc().
On obtient donc au final
$sql = 'SELECT titre FROM livres WHERE auteur = \''.la_fonction_dechappement_qui_va_bien($_GET['auteur']).'\'';
(on est ici obligé d'utiliser la concaténation).
Je conseille personnellement d'utiliser une classe générique pour faire de l'SQL et de créer alors une méthode "escape".
Bon, d'accord, c'est moche et pas vraiment pratique à taper.
Il est néanmoins facile de faire une fonction du genre
$sql = uneFonctionGeniale('SELECT titre FROM livres WHERE auteur = ?', $_GET['auteur']);
qui s'occupe alors, en fonction du type de la variable fournie, de convertir en type de la variable SQL et d'échapper si nécessaire.
Note sur les types SQL numériques.
Je vois souvent des trucs du genre
SELECT titre FROM livres WHERE id = '6'
Si id est déclaré comme étant un type numérique (int, smallint et compagnie) alors il ne devrait pas y avoir de guillemets simples. Cela fonctionne tout de même car la plupart des SGBD acceptent le transtypage.
L'idéal est de faire
SELECT titre FROM livres WHERE id = 6
Attention, cela nécessite précaution si on utilise des variables PHP en GET qui sont des chaînes.
Par exemple faire
$sql = 'SELECT titre FROM livres WHERE id = '.$_GET['id'];
est dangereux (dans ce cas précis, à part produire des erreurs de syntaxe ou des comportements étranges, rien de critique)
Il faudrait plutôt faire
$sql = 'SELECT titre FROM livres WHERE id = '.intval($_GET['id']);
L'idéal étant, encore une fois, de faire
$sql = uneFonctionGeniale('SELECT titre FROM livres WHERE id = ?', $_GET['id']);
(pas optimisé mais pas de problème de sécurité) ou mieux
$sql = uneFonctionGeniale('SELECT titre FROM livres WHERE id = ?', intval($_GET['id']));
Certains préféreront forcer le type avec des masques genre %d ou %s à la place du ?, à la printf.
Bon, je pense avoir répondu, dans la globalité, au problème.
Si ça ne suffit pas ou si certains points sont obscurs, merci de me le signaler.
Afficher des données dans une page HTML
HTML étant un sous-langage de SGML (XHTML étant un sous-langage de XML, modernisation de SGML), il est nécessaire de respecter sa syntaxe. En particulier, <, > et & sont des caractères réservés (pour définir des éléments et des entités), il faut donc les échapper avec un mécanisme particulier.
De même, à l'intérieur des attributs, qu'on délimité généralement par des guillemets doubles, il faut échapper les guillemets doubles.
En effet il ne faut pas écrire
<p>Regarde ça >> ;), c'est cool & cie.</p>
mais
<p>Regarde ça >> ;), c'est cool & cie.</p>
Ou encore, il ne faut pas écrire
<input type="text" value="Mon nom est Bond, "James" Bond">
mais
<input type="text" value="Mon nom est Bond, "James" Bond">
En PHP, tout cela se fait avec la fonction htmlspecialchars().
La fonction htmlentities() réalise aussi un travail similaire mais en plus convertit les caractères non ASCII en entités, ce qui, en plus d'être inutile, est totalement stupide, du moins si l'on est capable de gérer son charset (ce qui devrait être le cas quand on rédige une application de gestion de contenu).
Pour du texte normal (du texte dans un élément, pas dans un attribut quoi) il faut utiliser htmlspecialchars avec l'option ENT_NOQUOTES. On peut aussi l'utiliser avec les autres options, mais ce sont des octets gaspillés pour rien.
Pour un attribut, on utilise htmlspecialchars avec l'option ENT_COMPAT, qui est l'option par défaut. Si on n'utilise pas les doubles guillemets mais les simples, il faut spécifier une autre option. Tout cela est documenté.
Partager