parse_url pour de vrai

Eléphant du PHP | 121 Messages

20 avr. 2008, 15:33

Pour ceux qui ne la connaissent pas, la fonction parse_url « analyse une URL et retourne un tableau associatif contenant tous les éléments qui y sont présents » (dixit sa documentation). Ce que la documentation dit moins c'est que la fonction s'endort un peu en cours de route en ce qui concerne la requête (les trucs après le point d'interrogation). Ainsi, lorsque l'on utilise la fonction avec l'URL – totalement bidon, mais intéressante:

Code : Tout sélectionner

http://www.weirdog.com/blog/?tag=XML&chars=%E9%E9%E0%E0
on se retrouve avec le tableau suivant:

Code : Tout sélectionner

Array ( [scheme] => http [host] => www.weirdog.com [path] => /blog/ [query] => tag=XML&chars=%E9%E9%E0%E0 )
Bon, c'est sympa, mais quid de query ? Ce serait chouette de l'avoir aussi sous forme de tableau, parce que là elle ne ressemble pas à grand chose.

J'ai crée la fonction wd_parse_url que s'occupe donc de tout mettre sous forme de tableau, et de décoder les paramètres dans le jeu de caractère de votre choix (mieux que urldecode(), chouette).

Code : Tout sélectionner

Array ( [scheme] => http [host] => www.weirdog.com [path] => /blog/ [query] => Array ( [tag] => XML [chars] => ééàà ) )
Pour ceux que ça intéresse je vous invite à lire le billet sur mon blog.
Modifié en dernier par Gofromiel le 08 mai 2008, 14:10, modifié 1 fois.

Administrateur PHPfrance
Administrateur PHPfrance | 3088 Messages

20 avr. 2008, 17:43

En règle générale, je déconseille d'essayer d'être plus malin que PHP et faire un couteau suisse de n'importe quelle fonction. En l'occurence, il y a plusieurs points qui me chagrinent.

La fonction de parse_url() est de décomposer une URL, pas de l'interpréter. Si on se réfère à la RFC 2616 section 3.2.2, une URL est composée de la sorte :
http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query
Tout ce qui se trouve après le ? est le "query string". La RFC 2616 se garde d'indiquer comment construire ce(tte) query string, à chaque langage de définir sa méthode. La plupart des gens sous PHP utilisent le caractère & pour séparer les variables, mais ce n'est pas quelque chose d'obligatoire. On peut notamment le modifier via le réglage arg_separator.input de php.ini. D'ailleurs, PHP offre déjà une fonction pour interpréter ce query string, parse_str().

De plus, dans ta fonction wd_parse_url() tu tentes de deviner si l'URL passée a été contient des entités HTML. C'est une autre pratique que je déconseille, parce que deviner implique de se tromper parfois. Si tu as une URL avec du HTML dedans, "décode" le HTML et passe le résultat à la fonction. Sinon, on court le risque de faux-positif.

Finalement, la fonction propose d'encoder le résultat dans l'encodage de son choix... mais à partir de quel encodage ? Sans connaître l'encodage de départ, impossible de convertir. J'ai peur que ce genre d'options renforce la croyance erronée qu'il est possible de connaître l'encodage d'une chaîne en PHP<6. Malheureusement, ça ne l'est pas et il n'existe pas, à ma connaissance, de méthode pour connaître l'encodage d'une requête GET. (à part les méthodes de détection heuristique comme mb_detect_encoding())

C'est pour toutes ces raisons que je déconseille d'en faire plus que nécessaire. PHP propose un outil pour chaque situation, essayer d'en faire une sorte de super-couteau suisse rend au final les choses plus compliquées et peut s'avérer dangereux.

Désolé de paraître si négatif, hein :roll:

Eléphant du PHP | 121 Messages

20 avr. 2008, 20:01

Tu n'es pas du tout négatif, je trouve tes remarques très constructives.

Le cas de la détection du '&' m'a aussi posé un problème. La fonction initiale permettait de définir le séparateur. J'ai finalement décidé de _deviner_ pour que ce soit plus simple. C'est surement une erreur.

Quand à l'encodage des caractères, lorsque l'on ne défini pas le format source, la fonction mb_convert_encoding() utilise celui par défaut. C'est donc iso-8859-1 pour PHP < 6 et utf-8 pour PHP6. Cela ne devrait donc pas poser de problème. Mais tu as raison, on est jamais assez prudent.

J'ai ajouté une partie "pourquoi je n'utilise pas parse_str", voici les deux principales raisons :

1. La fonction ne supporte évidement pas l'utf-8
2. Elle ajoute des guillemets magiques, même lorsque l'on s'en ai débarrassé plus tôt.

Et puis une troisième : elle a un nom débile (j'aurais préféré 'parse_query') ;-)

Administrateur PHPfrance
Administrateur PHPfrance | 3088 Messages

20 avr. 2008, 20:26

Pour parse_str(), il ne devrait y avoir aucun problème avec l'UTF-8. As-tu un exemple de query string qui ne fonctionnerait pas avec ?

En réalité, une grande partie des fonctions habituelles fonctionnent avec l'UTF-8. Les seules qui posent vraiment problèmes sont celles qui doivent interpréter le contenu d'une chaîne, eg strtolower() ou dans une moindre mesure substr(). Mais une fonction comme parse_str() devrait produire une résultat valide (entrée UTF-8 valide => sortie UTF-8 valide) pour peu que le séparateur soit un caractère ASCII (& ou ; par exemple).

Administrateur PHPfrance
Administrateur PHPfrance | 3131 Messages

20 avr. 2008, 22:41

Pour parse_str(), il ne devrait y avoir aucun problème avec l'UTF-8. As-tu un exemple de query string qui ne fonctionnerait pas avec ?
En fait si, il y a un gros souci :) Il semble faire un utf8_encode dans tous les cas, même si la query_string est déjà en UTF-8.
Du coup il faut détecter l'encodage de urldecode($query_string) pour ensuite utf8_decode()r le résultat de parse_str() si c'était de l'UTF-8. Pas glop, mais bon ça se gère quand-même très simplement (ça reste beaucoup plus simple que de faire un parseur).

Administrateur PHPfrance
Administrateur PHPfrance | 3088 Messages

20 avr. 2008, 22:57

Je vois mal PHP spontanément encoder les données entrante en UTF-8 (comment PHP saurait que tu vas utiliser de l'UTF-8 de toutes façons ?) donc le problème que tu as pu rencontrer provenait probablement d'autre chose. Voici un exemple tout simple que je viens de tester :
<?php
header('Content-Type: text/html;charset=utf-8');

echo "<html><body><p><a href='test.php?str=\xC3\xA9'>\xC3\xA9</a></p><p>str = ", bin2hex($_GET['str']), "</p></body></html>";
Dans cet exemple, je passe une variable "str" dont le contenu est le caractère é encodé en UTF-8. Le é s'affiche bien à l'écran et dans la barre de status de Firefox, même s'il l'encode lors de son utilisation dans l'URL. Sinon, aucun problème.

En général, les problèmes d'encodages surviennent quand on en mélange plusieurs, le plus souvent par erreur, sinon ce n'est pas un problème.

Eléphant du PHP | 121 Messages

20 avr. 2008, 23:23

comment PHP saurait que tu vas utiliser de l'UTF-8 de toutes façons ?
En regardant le Content-Type de la requête ?

C'est tout de même un mystère cette fonction parse_str. Peut-être vaut-il mieux utiliser la fonction mb_parse_str() ?

@Hubert Roksor: Cela dit, ton exemple ne me semble pas valide puisque les caractères que tu échappes sont échappés pour PHP, pas pour l'URL qui récupère les caractères dé-échappés par PHP, mais non encodés par urlencode()...

Quand on voit tous les exemples que l'on trouve en tapant 'urlencode utf8' il doit bien y avoir quelque chose de louche...

Administrateur PHPfrance
Administrateur PHPfrance | 3088 Messages

20 avr. 2008, 23:57

Non, PHP n'inspecte pas ses en-têtes sortant pour définir le fonctionnement de parse_str(). Ma question était essentiellement rhétorique, PHP ne sait pas si tu traites de l'UTF-8, de l'ASCII ou juste une image JPEG qui ressemble à du japonais.
C'est tout de même un mystère cette fonction parse_str. Peut-être vaut-il mieux utiliser la fonction mb_parse_str() ?
Ben euh... non, ce n'est pas un mystère. parse_str() fait exactement ce que dis le manuel.

Concernant mon exemple, je te rassure il est valide et produit de l'UTF-8 identique à ce que ferait un éditeur bien configuré. J'utilise les caractères échappés pour garantir que si quelqu'un teste chez lui, ses résultats ne seront pas influencés par sa configuration. Ce script sort donc bien de l'UTF-8, ensuite le navigateur en fait ce qu'il veut. Apparemment, Firefox et Opera URL-encodent les octets pouvant poser problème, ce qui semble être une bonne idée. En tout cas, ce n'est pas du ressort de PHP.

En entrée, PHP reçoit des données URL-encodées et les décodent normalement. Le résultat représente bien une chaîne UTF-8 identique à celle utilisée dans la page.
Quand on voit tous les exemples que l'on trouve en tapant 'urlencode utf8' il doit bien y avoir quelque chose de louche...
Je ne sais pas de quels exemples tu parles, mais si tu postent des URLs j'essaierai d'y jeter un oeil. Il n'y a rien de "louche" concernant l'UTF-8 ou sa gestion dans PHP, juste un grand nombre de personnes qui se font des idées dessus et utilisent certaines fonctions au hasard. Ici même, on voit régulièrement des gens ajouter des utf8_encode() un peu n'importe où dans l'espoir de "faire marcher" leurs scripts.

Normalement, UTF-8 est simple à utiliser (à part les fonctions qui ne marchent pas, comme strtolower()) du moment qu'on ne s'emmêle pas les pinceaux dans les encodages et qu'on utilise le même partout.