Journal Jupyter et la gestion des caractères de fin de ligne dans les URL de données par Firefox vs Chromium

Posté par  . Licence CC By‑SA.
Étiquettes :
12
19
sept.
2019

Cher journal,
n'étant pas développeur web ce qui suit est peut-être largement connu ; aussi excuse-moi si j'enfonce des portes ouvertes.

J'ai constaté un comportement étrange de Firefox concernant la gestion des caractères de fin de ligne/nouvelle ligne (\n) lorsque ceux-ci sont inclus dans des URL de données. Pour le contexte, il peut arriver, lorsqu'on travaille sur des serveurs Jupyter distants, que l'utilisateur n'ait pas accès à l'espace de fichier où sont stockés les notebooks. Dans ce cas, un des moyens dont dispose le développeur desdits notebooks pour permettre aux utilisateurs de récupérer les données issues de calculs effectués sur le serveur, est de les inclure directement dans une URL de données. Voir cette discussion ou ce billet par exemple (il parait que cela pose des problèmes de sécurité mais ceci dépasse très largement mes compétences).
Donc, ma solution (pompée sur stackoverflow comme il se doit) pour récupérer des arrays Numpy est la suivante:
1. convertir les arrays en chaine de caractères
2. inclure cette chaine de caractères dans le lien de données

En python ça nous fait :

import numpy as np
from IPython.display import HTML
mon_tableau = np.array([[1,2,3],[4,5,6]])
mon_str = np.array2string(mon_tableau)
lien = "<a download='mon_fichier.txt' href='data:text/csv;charset=utf-8;ascii,"
        + mon_str + "'>texte_du_lien</a>"
HTML(lien)

Surprise: dans les données téléchargées avec Firefox il n'y a pas de saut de ligne. Ça apparait comme ça dans Kate :

[[1 2 3] [4 5 6]]

Alors que les mêmes données, téléchargées avec un navigateur basé sur Chromium, s'affichent correctement (toujours dans Kate) :

[[1 2 3]
[4 5 6]]

Il parait que c'est normal et que ce n'est pas possible d'inclure des sauts de ligne lorsque l'encodage est en autre chose que base64 (cf. cette discussion). Bon, soit ! Je m'exécute et je convertis en base64, ce qui, (i) est très moche, (ii) augmente inutilement la longueur du lien (ce qui peut être problématique en particulier si le tableau contient beaucoup de données). Et donc on obtient :

import base64
import numpy as np
from IPython.display import HTML
mon_tableau = np.array([[1,2,3],[4,5,6]])
mon_str = np.array2string(mon_tableau)
mon_str = base64.b64encode(mon_str.encode('ascii')) #conversion vers base64
mon_str = str(mon_str).replace("'","").replace("b","") #supprime le préfixe b (byte) et les guillemets
lien = "<a download='mon_fichier.txt' href='data:text/csv;charset=utf-8;base64,"
        + mon_str + "'>texte_du_lien</a>"
HTML(lien)

En effet ça fonctionne maintenant avec Firefox. Alors, outre le fait que ces conversions string -> base64 -> string m'arrachent l'oeil (je suis preneur d'une solution plus élégante), je m'interroge: pourquoi ça marche dans les navigateurs basés sur Chromium (par exemple Vivaldi ou Chromium lui-même) et pas dans mon navigateur préféré ?

  • # Vous n'avez pas les autorisations nécessaires

    Posté par  (site web personnel) . Évalué à 10.

    Dans la page wikipedia que tu cites, quelques lignes plus haut il est écrit:

    linefeeds within an element attribute value (such as the "src" above) are ignored

    Pour des raisons historiques et de lisibilité, la norme dit d'ignorer les sauts de ligne dans un data URI scheme, ca permet d'avoir un texte long coupé en plusieurs lignes donc plus lisible.

    Après chaque navigateur et moteur de rendu fait ce qu'il veut et ce n'est pas la seule incompatibilité qui existe.

    L'exemple de la même page wikiepdia est:

    <img src="data:image/png;base64,iVBORw0KGgoAAA
    ANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4
    //8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU
    5ErkJggg==" alt="Red dot" />

    Même si tu as l'air de ne pas aimer le base64, et malgré la perte (relative) de place que ca provoque, je pense malgré tout que c'est la meilleure solution pour ton problème. Avec le base64, on est sûr que les données entre la source et la cible seront les mêmes et non interprétées.

    • [^] # Re: Vous n'avez pas les autorisations nécessaires

      Posté par  . Évalué à 2.

      Merci pour cet éclairage. J’avoue que je n’avais pas fait le lien entre le passage que tu cites et le problème du formatage du fichier.

    • [^] # Re: Vous n'avez pas les autorisations nécessaires

      Posté par  (site web personnel) . Évalué à 3.

      Question bête mais avec les mode gzip, l'url n'est pas compressé ? Dans ce cas, il n'y aura quasiment aucune perte de place.

      "La première sécurité est la liberté"

      • [^] # Re: Vous n'avez pas les autorisations nécessaires

        Posté par  (site web personnel) . Évalué à 4.

        Alors non pour deux raisons.

        • La première c'est que le mod gzip (ou deflate, plus mieux) s'applique au body de la réponse, pas à l'url ou aux headers malheureusement. Il faut passer en HTTP/2 ou HTTP/3 pour pouvoir compresser les headers.

        • La seconde c'est que le code présenté s'exécute uniquement coté client, et que donc il n'y a pas d'appel à un serveur HTTP quand on clique sur le lien HREF qui contient un data URI scheme.

        Comme on dit:

        Pas de requête HTTP -> Pas de réponse HTTP
        Pas de réponse HTTP -> Pas de body dans la réponse HTTP
        Pas de body dans la réponse HTTP -> Pas de compression gzip
        Pas de compression gzip -> Pas de compression gzip

        • [^] # Re: Vous n'avez pas les autorisations nécessaires

          Posté par  . Évalué à 4.

          Après, l'url de données, si elle fait partie d'un document envoyé par HTTP avec de la compression, elle sera compressée lors de la transmission comme le reste du document. Ensuite, quand le lien est cliqué, il n'y a pas de transmission, donc on se fiche de la compression, sauf si le navigateur en garde une entrée dans son historique.

  • # Utiliser %0A

    Posté par  . Évalué à 7. Dernière modification le 21 septembre 2019 à 10:12.

    Intrigué, j'ai essayé de remplacer le saut de ligne par un &#13;, puis par un &#10;&#13;, sans succès.

    Ensuite je suis tombé sur https://stackoverflow.com/questions/3871729/transmitting-newline-character-n :

    Code :

    import numpy as np
    mon_tableau = np.array([[1,2,3],[4,5,6]])
    mon_str = np.array2string(mon_tableau)
    lien = "<a download='mon_fichier.txt' href='data:text/csv;charset=utf-8;ascii," + mon_str.replace("\n", "%0A") + "'>texte_du_lien</a>"
    print(lien)

    Résultat :

    <a download='mon_fichier.txt' href='data:text/csv;charset=utf-8;ascii,[[1 2 3]%0A [4 5 6]]'>texte_du_lien</a>
    

    Avec Firefox et Chromium, sur Kate, je me retrouve bien avec une nouvelle ligne. Pas besoin de base64.

    • [^] # Re: Utiliser %0A

      Posté par  . Évalué à 4.

      Pour faire bien les choses, il faudrait d'ailleurs certainement regarder du côté de urllib.parse.quote. Sur cette chaîne, ça échappe la plupart des caractères…

    • [^] # Re: Utiliser %0A

      Posté par  . Évalué à 1.

      Je ne dirai qu'un chose: Merci

    • [^] # Re: Utiliser %0A

      Posté par  (site web personnel) . Évalué à 1.

      Eh oui, Firefox respecte la norme, tout simplement.
      Et il est normal que les caractères spéciaux d’un lien soient encodés (%0A par exemple) ; ça aussi, c’est la norme…

      • [^] # Re: Utiliser %0A

        Posté par  . Évalué à 2. Dernière modification le 30 septembre 2019 à 12:22.

        Yep.

        Tout l’enjeu est de connaître et comprendre la norme (et d’ailleurs, savoir et comprendre quelles normes s’appliquent), ses implications, les défauts et spécificités des implémentations et leurs implications. Ce n’est pas forcément chose simple.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.