- Le presse-papiers web peut stocker, pour une même opération de copie, plusieurs représentations MIME d’une donnée, afin que chaque application puisse coller uniquement le format dont elle a besoin
- L’API asynchrone Clipboard est principalement limitée à
text/plain,text/htmletimage/png, et refuse l’écriture de types personnalisés commeapplication/jsonpour des raisons de sécurité - L’ancienne API Clipboard Events peut manipuler des chaînes de type arbitraires, mais le véritable presse-papiers système n’est modifié que dans le cadre d’événements utilisateur de confiance
- Google Docs génère un événement copy de confiance avec
execCommand("copy"), et Figma place des données personnalisées dans des attributs HTML en base64 pour gérer la copie entre navigateur et application native - Web Custom Formats ouvre les types personnalisés dans l’API Clipboard asynchrone avec le préfixe
"web ", mais nécessite un navigateur basé sur Chromium et des applications natives mises à jour
Modèle de base du presse-papiers web
- Le presse-papiers peut stocker, pour une seule opération de copie, plusieurs représentations associées à des types MIME
text/plain: texte bruttext/html: HTMLimage/png: image PNG
- Google Docs lit la représentation
text/htmlpour conserver la mise en forme comme les liens, la taille de police ou la couleur - Une application comme VS Code, qui n’a besoin que du texte source, peut se contenter de lire la représentation
text/plain
Limitations de types dans l’API Clipboard asynchrone
navigator.clipboard.read()vérifie lestypesd’un élément du presse-papiers, puis lit une représentation donnée avecgetType("text/html"), par exemplenavigator.clipboard.write()peut écrire en une fois plusieursBlobdans unClipboardItemdont les clés sont des types MIME- Si l’on tente d’écrire
application/jsondans unClipboardItem, une exceptionType application/json not supported on writeest levée - La spécification W3C Clipboard impose de refuser dans
writeles types autres quetext/plain,text/htmletimage/png application/jsonfigurait dans la liste des types de données obligatoires de 2012 à 2021, avant d’être retiré dans w3c/clipboard-apis#155- Avant ce changement, la liste obligatoire comptait 16 types en lecture et 8 en écriture
- Cette liste a été réduite car les navigateurs ne prenaient pas en charge de nombreux types obligatoires en raison de préoccupations de sécurité
- La spécification avertit qu’un script non fiable pourrait placer dans le presse-papiers des données susceptibles de déclencher des vulnérabilités dans des logiciels locaux, d’où la restriction des types autorisés
Clipboard Events API et isTrusted
ClipboardEventse déclenche sur les événements copy, cut et paste, et fournit un objetDataTransfervia la propriétéclipboardData- Lors d’un événement copy, on peut écrire des données avec
e.clipboardData.setData("text/plain", "...")etsetData("text/html", "...") - Lors d’un événement paste, on peut lire la représentation HTML avec
e.clipboardData.getData("text/html") clipboardData.setData("application/json", json)ne lève pas d’exception, et un gestionnaire paste peut aussi voir le typeapplication/jsonet sa chaîne JSONclipboardDataest une interface bien plus ancienne que l’API Clipboard asynchrone- l’API Clipboard asynchrone a été ajoutée à la spécification en 2017
clipboardData,setDataetgetDataapparaissaient déjà dans un brouillon W3C de 2006- ce brouillon reflétait largement des fonctionnalités implémentées dans Internet Explorer
- comme Internet Explorer 4 est sorti en 1997, on peut considérer que l’interface
clipboardDataa au moins 26 ans au moment de la rédaction
- Les types MIME ont été introduits dans la spécification en 2011, mais le fait que
setData()accepte toujours n’importe quelle chaîne comme argument de type est resté inchangé Event.isTrustedest une propriété en lecture seule qui vauttrueuniquement quand l’événement est généré par l’agent utilisateur- un événement copy déclenché lorsque l’utilisateur appuie sur Command+C a
isTrustedàtrue - un événement synthétique créé avec
dispatchEvent()aisTrustedàfalse
- un événement copy déclenché lorsque l’utilisateur appuie sur Command+C a
- Les événements copy/cut synthétiques ne peuvent pas modifier le presse-papiers système, et les événements paste synthétiques ne peuvent pas accéder aux données réelles du presse-papiers système
- Ainsi, l’API Clipboard Events peut manipuler des types arbitraires, mais l’interaction avec le vrai presse-papiers reste limitée aux gestionnaires d’événements copy/paste déclenchés par l’agent utilisateur
Écrire un type personnalisé depuis un bouton Copy
- Le bouton Copy du menu contextuel de Google Docs écrit trois représentations dans le presse-papiers
text/plaintext/htmlapplication/x-vnd.google-docs-document-slice-clip+wrapped
- La troisième représentation est un type personnalisé contenant des données JSON, qu’il n’est pas possible d’écrire uniquement avec l’API Clipboard asynchrone
- Lorsque l’utilisateur clique sur le bouton Copy, Google Docs appelle
document.execCommand("copy") execCommand("copy")peut déclencher par programmation un événement copy de confiance, comme si l’utilisateur avait exécuté une commande de copie- Dans Safari,
execCommand("copy")nécessite une sélection active pour déclencher l’événement copy- on peut créer une zone de sélection en ajoutant au DOM un champ input non vide, en le sélectionnant, puis en appelant
execCommand("copy")avant de le retirer
- on peut créer une zone de sélection en ajoutant au DOM un champ input non vide, en le sélectionnant, puis en appelant
Différences navigateur/OS pour le bouton Paste
- Le bouton Paste de Google Docs demandait l’installation d’une extension sur macOS, mais fonctionnait sur un portable Windows
document.queryCommandSupported("paste")permet de vérifier si la commande paste est prise en charge- sur macOS, Chrome et Firefox renvoient
false, Safari renvoietrue - sur Windows, Chrome et Edge renvoient
true, Firefox renvoiefalse
- sur macOS, Chrome et Firefox renvoient
- Safari affiche une invite de confirmation avant le collage, ce qui montre clairement que le site web va lire le presse-papiers
- On ne sait pas pourquoi Chrome autorise
execCommand("paste")sous Windows mais pas sous macOS - Google Docs n’utilise pas l’API Clipboard asynchrone comme solution de repli quand
execCommand("paste")n’est pas disponible- l’API Clipboard asynchrone ne permet pas de lire la représentation
application/x-vnd.google-[...] - la représentation HTML contient des identifiants internes
- l’API Clipboard asynchrone ne permet pas de lire la représentation
Le copier-coller HTML de Figma
- Le bouton Copy de Figma écrit deux représentations dans le presse-papiers
text/plaintext/html
- La représentation HTML contient deux
spanvides, avec respectivement les attributsdata-metadataetdata-buffer data-metadataest une chaîne JSON encodée en base64 ; une fois décodée, on y trouve des valeurs commefileKey,pasteIDetdataTypedata-bufferfait environ 26 000 caractères même lorsqu’on ne copie qu’une frame vide, et sa longueur semble croître linéairement avec la quantité de contenu copié- Si l’on décode
data-bufferen base64, on obtient des données binaires commençant parfig-kiwi, au format Kiwi message format- Kiwi est un format créé par Evan Wallace, cofondateur et ancien CTO de Figma
- il est utilisé pour l’encodage des fichiers
.fig
- Le parseur de fichiers
.figpublié par Evan Wallace permet de convertirdata-bufferen fichier.figpour l’inspecter - Figma crée un petit fichier Figma, l’encode en base64, puis le place dans l’attribut
data-bufferd’unspanHTML vide avant de le stocker dans le presse-papiers
Pourquoi utiliser la représentation HTML
- Windows, macOS et Linux proposent chacun des formats OS différents pour écrire des données dans le presse-papiers
- Windows fournit
CF_HTMLpour le HTML - macOS fournit
NSPasteboard.PasteboardType.html
- Windows fournit
- Le texte brut, le HTML et les images PNG peuvent être mappés vers des formats standard sur plusieurs systèmes d’exploitation
- Des types arbitraires comme
application/foo-bars’accordent mal avec les formats communs du presse-papiers des OS - Les navigateurs n’écrivent pas ces représentations personnalisées dans les formats communs du presse-papiers de l’OS, mais dans un format personnalisé propre à chaque navigateur
- en conséquence, ces types personnalisés peuvent être copiés-collés entre onglets d’un même navigateur
- mais pas entre applications
- En utilisant
text/html, on bénéficie d’un mapping vers un format commun de l’OS, donc plus facile à lire par d’autres applications - Figma utilise
text/htmlpour permettre le copier-coller d’éléments entrefigma.comdans le navigateur et l’application native Figma
Comment les navigateurs stockent les données personnalisées
- Sur macOS, si l’on écrit
text/plain,text/html,application/jsonetfoo bar bazdans un événement copy puis qu’on inspecte le tout avec Pasteboard Viewer, chaque navigateur les stocke différemment - Chrome ajoute quatre éléments dans le Pasteboard
public.html: représentation HTMLpublic.utf8-plain-text: représentation en texte brutorg.chromium.web-custom-data: représentations personnaliséesorg.chromium.source-url: URL de la page web depuis laquelle la copie a été faite
- Firefox crée aussi
public.htmletpublic.utf8-plain-text, mais écrit les données personnalisées dansorg.mozilla.custom-clipdata- contrairement à Chrome, il ne stocke pas l’URL source
- Safari crée aussi
public.htmletpublic.utf8-plain-text, et écrit les données personnalisées danscom.apple.WebKit.custom-pasteboard-data- cet élément contient aussi la liste complète des représentations, y compris le texte brut et le HTML, ainsi que l’URL source
- Safari n’autorise le copier-coller de types personnalisés entre onglets que si le domaine de l’URL source est identique
- cette restriction ne semble pas exister dans Chrome ni Firefox
Raw Clipboard Access et Web Custom Formats
- Raw Clipboard Access est une API proposée en 2019 pour donner aux applications web un accès brut en lecture/écriture au presse-papiers natif de l’OS
- Raw Clipboard Access visait à résoudre le problème d’applications web comme Figma et Photopea, limitées à un petit nombre de formats et donc peu interopérables avec la plupart des formats d’image
- La proposition n’a pas avancé davantage en raison de préoccupations de sécurité, notamment le risque d’exécution de code à distance dans des applications natives
- Web Custom Formats est une proposition permettant d’écrire des types personnalisés dans l’API Clipboard asynchrone, implémentée par Chromium en 2022
- Web Custom Formats utilise le préfixe
"web "devant le type MIME- par exemple :
web application/json - en lecture, on utilise aussi l’API Clipboard asynchrone avec
read()etgetType("web application/json")
- par exemple :
- Web Custom Formats écrit deux catégories de données dans le presse-papiers natif de l’OS
- une correspondance entre types de données et noms d’éléments du presse-papiers
- un élément du presse-papiers pour chaque type de données
- Sur macOS, cette correspondance est stockée dans
org.w3.web-custom-format.map- par exemple :
"application/json": "org.w3.web-custom-format.type-0"
- par exemple :
- Les éléments
org.w3.web-custom-format.type-[index]contiennent les données brutes duBlob - Cette approche empêche les applications web d’écrire directement des données brutes dans n’importe quel format du presse-papiers de l’OS, ce qui évite les problèmes de sécurité de Raw Clipboard Access
- En revanche, l’objectif n’est pas l’interopérabilité immédiate avec les anciennes applications natives
- les applications natives doivent être mises à jour pour savoir lire le format Web Custom Formats
- Web Custom Formats est disponible dans les navigateurs basés sur Chromium depuis 2022, mais n’est pas encore implémenté par les autres navigateurs
Option unsanitized
- Lors de la lecture du presse-papiers avec l’API Clipboard asynchrone, le navigateur peut assainir les données
- il peut supprimer des balises script potentiellement dangereuses du HTML
- il peut réencoder des images PNG pour éviter des attaques de type zip bomb
- L’option
unsanitizedderead()permet de demander des données non assainies - Cette option n’est actuellement prise en charge que dans les navigateurs basés sur Chromium
- elle a été ajoutée fin 2023
- d’autres navigateurs pourraient l’ajouter plus tard, mais des retours laissent penser que Safari a peu de chances de le faire
Les options réalistes aujourd’hui
- Il n’existe pas encore de méthode vraiment fiable pour écrire des types de presse-papiers personnalisés qui fonctionne bien dans tous les navigateurs
- L’approche de Figma, qui consiste à placer une chaîne base64 dans une représentation HTML, est rudimentaire mais permet de contourner plusieurs limitations de l’API Clipboard pour transmettre des données personnalisées via le presse-papiers
- Web Custom Formats offre une voie plus sûre et plus pratique pour gérer les types personnalisés, mais cela nécessite une implémentation dans l’ensemble des principaux navigateurs
1 commentaires
Avis sur Hacker News
Bon article. Je comprends pourquoi, dans Firefox sur Mac, le collage avec/sans mise en forme fonctionne mal dans Google Docs.
Il est intéressant de voir que Google utilise une API obsolète pour essayer de placer son propre format de contenu dans le presse-papiers.
La méthode la plus fiable pour envoyer des données applicatives personnalisées vers le presse-papiers semble être, comme Figma, d’intégrer les données dans le HTML.
Cela présente aussi l’avantage de pouvoir définir un comportement d’échec via un message HTML lorsque l’application destinataire ne le prend pas en charge.
WordPress touche aussi à ce problème. En mode éditeur, couper-coller plusieurs paragraphes peut tout casser assez sévèrement.
C’est sans doute parce que les paragraphes sont en réalité dans des zones DIV différentes, soumises à des logiques de contrôle et à des effets différents.
Et puis la machine prend ce que je pensais être de l’ASCII et le transforme en UTF-8 ou en ISO Latin-1, avec un comportement à la Clippy du genre « j’ai rendu tes guillemets plus jolis, tu me remercieras plus tard », alors que je n’ai rien demandé. Si j’avais voulu `this', je n’aurais pas tapé 'this'.
Quand j’étais étudiant, JavaScript pouvait lire le presse-papiers sans le consentement de l’utilisateur, et j’avais créé un site appelé getpasted.
C’était un site qui collait automatiquement le contenu du presse-papiers et l’envoyait dans une base de données publique ; évidemment, certaines personnes n’aimaient pas ça.
Mais comme projet pour faire savoir qu’on pouvait lire le presse-papiers à tout moment, c’était plutôt efficace.
Sindre Sorhus, le créateur de Pasteboard Manager[0], est aussi l’auteur de la bibliothèque de raccourcis Actions[1] et de plusieurs apps iPhone et Mac. Je ne sais pas s’il est sur HN.
0. https://apps.apple.com/us/app/pasteboard-viewer/id1499215709
https://www.npmjs.com/~sindresorhus
La combinaison de portée et de profondeur est particulièrement réussie. L’article couvre même les différences entre systèmes et navigateurs, tout en expliquant les pièges et contournements.
À propos de la récupération de données « privées » dans le navigateur, la webapp de ma banque affichait mon nom d’hôte, aluminium, au moment de la connexion et demandait une authentification à deux facteurs.
Je ne sais pas comment elle l’a obtenu. En me connectant sur mobile, elle pouvait aussi voir le SMS contenant le code 2FA et le coller automatiquement. C’était dans un environnement Pixel+Chrome ou Linux+Chrome.
Le nom d’hôte est plus compliqué. Il faudrait vérifier que tu n’as vraiment jamais fourni cette information à la banque sous une forme proche d’un nom d’utilisateur. Je suis aussi curieux de savoir de quelle banque il s’agit.
Cet article montre bien pourquoi les webapps ne pourront jamais être aussi bonnes que les apps natives.
Les webapps restent toujours du code « non fiable », si bien que leur accès aux ressources de la machine locale est limité de façon arbitraire et artificielle.
Nous ne sommes plus à l’époque des débuts d’Internet, avec des script kiddies qui faisaient n’importe quoi, ni dans un espace réservé aux geeks. Nous sommes à une époque où des organisations liées à des États lancent des attaques par ransomware contre des infrastructures critiques pour financer des programmes nucléaires. L’accès arbitraire aux ressources de la machine locale est précisément ce qui rend ce genre de choses possible.
Dans les navigateurs modernes, les webapps sont naturellement enfermées dans un bac à sable plus strict, mais dans les systèmes d’exploitation modernes, les apps natives sont elles aussi traitées comme non fiables. Quand on lance une nouvelle app, une boîte de dialogue de confirmation apparaît avant qu’elle puisse accéder à autre chose que son répertoire de données isolé.
Cela dit, je me demande quelle était cette extension Chrome qui a déclenché l’avertissement sur le MacBook.