Je viens de trouver un bogue dans Osmose, sur son serveur Backend.

==== Premier problème (le plus sérieux) ===

Cela se situe ici:

https://gitorious.org/~frodrigo/osmose/frodrigo-osmose-backend/blobs/master/modules/OsmSax.py

Aux deux lignes suivantes du module SaxWriter lorsqu'il génère le code
des valeurs d'attributs des élements XML :

369                 self._write(' %s=%s' % (name, quoteattr(value)))
378                 self._write(' %s=%s' % (name, quoteattr(value)))

En effet, la fonction Python quoteattr() ne représente pas
correctement le caractère "&" qu'il laisse sous cette forme, alors
qu'il FAUT le réencoder sous la forme "&"

La fonction quoteattr() est importée depuis le module Python
"sax.saxutils", absent dans les sources GIT d'Osmose. C'est elle qui
est ici en cause.

Cela affecte la modification des éléments contenant un caractère "&"
dans leur valeur (par exemple les relations contenant un tag "url", ou
certains tags "name" parfaitement valides).

L'effet de ce bogue est que le XML reçu par le client et éditable par
"rawedit" est invalide, et ne peut pas être revalidé tel quel !
Sinon on reçoit un message affiché en rouge en haut de l'écran
rawedit: "XML parser can't parse this data", et les données ne sont
pas enregistrées.

Pour contourner le problème, il faut soit même corriger le code XML
qui a été reçu en remplaçant les "&" affichés dans l'éditeur dans les
valeurs de tags par "&" avant de valider, même si on n'a pas
réellement touché à la valeur.

Le risque subsiste que même sans y toucher, l'absence de modification
manuelle pour faire la correction risque parfois d'être acceptés comme
du XML valide (par exemple quand un "&" littéral est suivi de
quelquechose qui ressemble à une référence numérique de caractère ou
une référene d'entité XML définie dans le schéma XML utilisé), ce qui
corrompra les données qui n'avaient pas lieu d'être modifiées.

Je ne sais pas si c'est la fonction quoteattr() du module sax.saxutils
qui devrait être corrigée, ou si une autre fonction devrait plutôt
être définie ou utilisée ici, ou si un paramètre supplémentaire
optionnel de quoteattr() permet de préciser la conformité avec XML
(car quoteattr() n'est pas obligé de remplacer ces "&" si la fonction
est utilisée pour générer du HTML ou du SGML): dans ce cas il faut
passer ce paramètre oublié.

A ce sujet, quoteattr() recode toutes les apostrophes ASCII (') sous
la forme ' alors que c'est inutile (et peu lisible) ici, étant
donné que la chaine retournée sera encadrée de guillemets doubles
ASCII ("). En revanche les quatre caractères suivants doivent être mis
sous forme de référence à une des quatre seules entité de caractères
XML prédéfinie :

- les guillemets doubles ASCII (") sous la forme "
- le signe inférieur (<) sous la forme &lt;
- le signe supérieur (>) sous la forme &gt;
- le signe et commercial (&) sous la forme &amp;

Et c'est tout ! Tout le reste peut (et devrait) rester sous leur forme
littérale (même l'apostrophe ici, bien que XML prédéfinisse aussi une
cinquième entité "&apos;" puisque la syntaxe XML permet aussi aux
valeurs d'attributs d'être encadrées par des apostrophes ASCII au lieu
de guillemets ASCII).


==== Deuxième problème (lié au premier) ===

Enfin je note que le code Javascript envoyé au client utilise le
constructeur: new XMLHttpRequest(), mais sans préciser le jeu de
caractères qui sera utilisé pour dialoguer avec le serveur :

 function ApiDo(action)
  {
    var myReq = new XMLHttpRequest();
    if (myReq) {
      myReq.onreadystatechange = function (evnt) { if(myReq.readyState == 4) {
        res = myReq.responseText.split('\n');
        document.getElementById('osm_msg').innerHTML = res[0];
        tmp = res.shift();
        document.getElementById('osm_data').value    = res.join('\n');
        } }
      myReq.open('POST', '/' + action + '/' + osm_type + '/' + osm_id, true);
      myReq.setRequestHeader("Content-type",
"application/x-www-form-urlencoded");
      poststr = "osm_data=" +
encodeURIComponent(document.getElementById("osm_data").value );
      myReq.send(poststr);
    }
  }

Rien n'oblige actuellement le navigateur à supposer que cette requête
se fera en codage UTF-8, même si la page web et le code javascript
sont eux-même codés en UTF-8. Dans les faits, cette requête telle
qu'elle est envoyée au serveur ne précise rien du tout, ce qui demande
alors que les données incluses dans la valeur "osm_data=" ne soient
que de l'ASCII pur.

Ici le problème est que pour s'assurer que ce sera uniquement de
l'ASCII, ce javascript utilise encodeURIComponent(), dont le
comportement dépend du navigateur : la source est bien de l'UTF-16,
son résultat sera bien de l'ASCII compatible avec une syntaxe de
composant URI, mais asolument rien n'indique dans quel jeu de
caractères se fera la conversion de la chaine source UTF-16 vers un
jeu restreint à 8 bits (avant ensuite de convertir les espaces ASCII
sous la forme "+", et les octets non ASCII et non imprimables ou
réservés par la syntaxe des URI sous la forme "%nn").

En effet, le constructeur XMLHttpRequest crée un nouveau document
totalement séparé du document source (hormi son domaine de sécurité)
et communique même avec un serveur backend distinct du serveur
frontend affichant la carte Osmose et l'interface d'édition. Il n'y a
strictement rien dans le code qui indique que le backend utilise
UTF-8, et cela pourrait expliquer pourquoi la base OSM contient
maintenant des caractères mal réencodés (il suffit par exemple qu'un
utilisateur utilise un navigateur configuré tel que UTF-8 n'est pas
son codage par défaut, mais utilise par défaut ISO-8859-1,
Windows-1252 voire aussi Mac-Roman).

Il faudrait donc que le javascript précise bien le codage utilisé
avant de les mettre dans les données qu'il envoie en POST-Data dans la
valeur "osmdata", sous forme URL-encodée (même si la requête ici
indique au serveur que les données sont supposées bien être au format
"application/x-www-form-urlencoded", cela ne dispense pas le
javascript de coder correctement sour cette forme).

Ce problème existe même avec les navigateurs récents, pour peu que
l'utilisateur n'a pas configuré son navigateur pour utiliser UTF-8
comme codage par défaut, et selon d'autres réglages possibles de la
façon d'interpréter les URI présentes dans de vieilles pages HTML non
conformes XML/XHTML ni HTML5.

Il n'est pas inintéressant de lire la doc officielle de la norme
ECMAScript au sujet des mises en garde sur l'effet et la compatibilité
de la fonction encodeURIComponent(), dès lors que la chaîne source en
paramètre ne contient PAS UNIQUEMENT des caractères ASCII mais
n'importe quel autre "caractère"


=== Note 1 : question de compatiblité des chaines en Javascript/ECMAScript ===

Note : les "caractères" en Javascript, ceux qu'on énumère dans une
chaine avec la méthode .charAt(n), ou qu'on compte avec la méthode
.length(), ne sont PAS les caractères au sens Unicode, mais seulement
des "unités de code" arbitraires sur 16 bits.

Javascript n'oblige même pas en fait à ce que ses chaines soit
nécessairement codées de façon interne en UTF-16, car ce pourrait
aussi bien être un codage JIS sur 16 bits, voire n'importe quelle
valeur binaire non textuelle, comme aussi en Java : il est
parfaitement valide en Javascript d'insérer des codets nuls dans ces
"chaines" ou un codet égal à 0xFFFF, qui ne correspond à rien de
valide en UTF-16, ou encode d'y mettre un codet égal à 0xD800 isolé
non suivi d'un codet entre 0xDC00 et DFFF, là encore invalide en
UTF-16, et les chaines Javascript font la distinction entre différents
codages de sauts de lignes. Javascript initialement n'obligeait même
pas à ce que son codage interne soit avec des unités de code sur au
moins 16 bits (ce n'est plus le cas depuis la norme ECMAScript qui
précise bien que ces unités de code DOIVENT être sur 16 bits, mais
n'oblige toujours pas à ce que ce soit uniquement de l'UTF-16 valide).

Bref attention à la fonction encodeURIComponent(string) ! Lire à ce
sujet ce qui a été fait dans jQuery pour résoudre ce problème de
compatibilité entre navigateurs et selon leur configuration: je
suggère donc d'utiliser jQuery dans le frontend, car il résoud bien
des difficultés, et fournit une bibliothèque standard pour effectuer
des requêtes XML fiables avec un serveur backend.


=== Note 2 : XMLHTtpRequest, et suggestion pour la sécurité ===

D'ailleurs le constructeur XMLHtppRequest() a aussi des problèmes de
sécurité répertoriés, comme aussi des problèmes mieux connus de
compatiblité avec différents types de navigateurs.

jQuery fournit une solution stable et sure, et même les organismes de
surveillance de la sécurité sur le web recommandent de ne plus
l'utiliser du tout et de passer aux requêtes JSON dès que possible car
elles incluent une implémentation native avec des contraintes bien
plus forte de sécurité (qui évitent par exemple des attaques de type
"XSS", Cross-Site Scripting, ou des attaques directement sur la
plateforme native du navigateur client, surtout avec les vieilles
versions d'Internet Explorer où XMLHttpRequest n'existait que par un
composant externe ActiveX).

_______________________________________________
Talk-fr mailing list
Talk-fr@openstreetmap.org
http://lists.openstreetmap.org/listinfo/talk-fr

Répondre à