Onopgeschoonde HTML in de Async Clipboard API

Vanaf Chrome 120 is er een nieuwe optie unsanitized beschikbaar in de Async Clipboard API. Deze optie kan nuttig zijn in speciale situaties met HTML, waarbij u de inhoud van het klembord precies zo moet plakken als toen deze werd gekopieerd. Dat wil zeggen, zonder de tussenliggende desinfectiestap die browsers doorgaans – en terecht – toepassen. Leer hoe u deze optie gebruikt in deze handleiding.

Bij het werken met de Async Clipboard API hoeven ontwikkelaars zich in de meeste gevallen geen zorgen te maken over de integriteit van de inhoud op het klembord en kunnen ze ervan uitgaan dat wat ze naar het klembord schrijven (kopiëren) hetzelfde is als wat ze krijgen als ze de gegevens van het klembord lezen (plakken).

Dit geldt zeker voor tekst. Probeer de volgende code in de DevTools Console te plakken en de pagina vervolgens direct opnieuw te focussen. (De setTimeout() is nodig zodat u voldoende tijd hebt om de pagina te focussen, wat een vereiste is van de Async Clipboard API.) Zoals u ziet, is de invoer exact hetzelfde als de uitvoer.

setTimeout(async () => {
  const input = 'Hello';
  await navigator.clipboard.writeText(input);
  const output = await navigator.clipboard.readText();
  console.log(input, output, input === output);
  // Logs "Hello Hello true".
}, 3000);

Met afbeeldingen is het net iets anders. Om zogenaamde compressiebomaanvallen te voorkomen, hercoderen browsers afbeeldingen zoals PNG's, maar de invoer- en uitvoerafbeeldingen zijn visueel exact hetzelfde, pixel voor pixel.

setTimeout(async () => {
  const dataURL =
    'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=';
  const input = await fetch(dataURL).then((response) => response.blob());
  await navigator.clipboard.write([
    new ClipboardItem({
      [input.type]: input,
    }),
  ]);
  const [clipboardItem] = await navigator.clipboard.read();
  const output = await clipboardItem.getType(input.type);
  console.log(input.size, output.size, input.type === output.type);
  // Logs "68 161 true".
}, 3000);

Wat gebeurt er echter met HTML-tekst? Zoals je misschien al geraden hebt, is de situatie bij HTML anders. Hier reinigt de browser de HTML-code om te voorkomen dat er iets misgaat, bijvoorbeeld door <script> -tags uit de HTML-code te verwijderen (en andere tags zoals <meta> , <head> en <style> ) en door CSS in te voegen. Bekijk het volgende voorbeeld en probeer het eens in de DevTools Console. Je zult merken dat de uitvoer aanzienlijk verschilt van de invoer.

setTimeout(async () => {
  const input = `<html>  
  <head>  
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  
    <meta name="ProgId" content="Excel.Sheet" />  
    <meta name="Generator" content="Microsoft Excel 15" />  
    <style>  
      body {  
        font-family: HK Grotesk;  
        background-color: var(--color-bg);  
      }  
    </style>  
  </head>  
  <body>  
    <div>hello</div>  
  </body>  
</html>`;
  const inputBlob = new Blob([input], { type: 'text/html' });
  await navigator.clipboard.write([
    new ClipboardItem({
      'text/html': inputBlob,
    }),
  ]);
  const [clipboardItem] = await navigator.clipboard.read();
  const outputBlob = await clipboardItem.getType('text/html');
  const output = await outputBlob.text();
  console.log(input, output);
}, 3000);

HTML-opschoning is over het algemeen een goede zaak. Je wilt jezelf niet blootstellen aan beveiligingsproblemen door in de meeste gevallen ongeopende HTML toe te staan. Er zijn echter scenario's waarin de ontwikkelaar precies weet wat hij doet en waarbij de integriteit van de in- en uitvoer-HTML cruciaal is voor de correcte werking van de app. In deze gevallen heb je twee keuzes:

  1. Als u zowel het kopiëren als het plakken beheert, bijvoorbeeld als u vanuit uw app kopieert en vervolgens weer in uw app plakt, moet u aangepaste webformaten gebruiken voor de Async Clipboard API . Stop hier met lezen en bekijk het gelinkte artikel.
  2. Als u in uw app alleen het plakken wilt regelen, maar niet het kopiëren, omdat de kopieerbewerking mogelijk plaatsvindt in een native app die geen aangepaste webindelingen ondersteunt, kunt u het beste de optie unsanitized gebruiken. Deze wordt in de rest van dit artikel uitgelegd.

Sanering omvat zaken als het verwijderen van script , het inlinen van stijlen en het zorgen voor een correcte HTML-opmaak. Deze lijst is niet volledig; in de toekomst worden mogelijk meer stappen toegevoegd.

Kopiëren en plakken van onhygiënische HTML

Wanneer u HTML naar het klembord write() (kopieert) met de Async Clipboard API, zorgt de browser ervoor dat de tekst correct wordt gevormd door deze door een DOM-parser te halen en de resulterende HTML-string te serialiseren. Er vindt in deze stap echter geen opschoning plaats. U hoeft niets te doen. Wanneer u HTML read() die door een andere applicatie naar het klembord is geplaatst, en uw webapplicatie ervoor kiest om de volledige getrouwheid van de content te ontvangen en opschoning in uw eigen code moet uitvoeren, kunt u een options-object aan de read() methode doorgeven met de eigenschap ' unsanitized ' en de waarde ['text/html'] . Op zichzelf ziet het er zo uit: navigator.clipboard.read({ unsanitized: ['text/html'] }) . Het volgende codevoorbeeld hieronder is vrijwel hetzelfde als het eerder getoonde, maar dit keer met de optie unsanitized . Wanneer u het in de DevTools Console probeert, zult u zien dat de invoer en de uitvoer hetzelfde zijn.

setTimeout(async () => {
  const input = `<html>  
  <head>  
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  
    <meta name="ProgId" content="Excel.Sheet" />  
    <meta name="Generator" content="Microsoft Excel 15" />  
    <style>  
      body {  
        font-family: HK Grotesk;  
        background-color: var(--color-bg);  
      }  
    </style>  
  </head>  
  <body>  
    <div>hello</div>  
  </body>  
</html>`;
  const inputBlob = new Blob([input], { type: 'text/html' });
  await navigator.clipboard.write([
    new ClipboardItem({
      'text/html': inputBlob,
    }),
  ]);
  const [clipboardItem] = await navigator.clipboard.read({
    unsanitized: ['text/html'],
  });
  const outputBlob = await clipboardItem.getType('text/html');
  const output = await outputBlob.text();
  console.log(input, output);
}, 3000);

Browserondersteuning en functiedetectie

Er is geen directe manier om te controleren of de functie wordt ondersteund, dus functiedetectie is gebaseerd op het observeren van het gedrag. Daarom is het volgende voorbeeld afhankelijk van de detectie van het feit of een <style> -tag blijft bestaan, wat ondersteuning aangeeft, of wordt ingevoegd, wat niet-ondersteuning aangeeft. Merk op dat de pagina hiervoor al klembordrechten moet hebben.

const supportsUnsanitized = async () => {
  const input = `<style>p{color:red}</style><p>a`;
  const inputBlob = new Blob([input], { type: 'text/html' });
  await navigator.clipboard.write([
    new ClipboardItem({
      'text/html': inputBlob,
    }),
  ]);
  const [clipboardItem] = await navigator.clipboard.read({
    unsanitized: ['text/html],
  });
  const outputBlob = await clipboardItem.getType('text/html');
  const output = await outputBlob.text();
  return /<style>/.test(output);
};

Demonstratie

Om de unsanitized optie in actie te zien, bekijkt u de demo en controleert u de broncode .

Conclusies

Zoals in de inleiding aangegeven, hoeven de meeste ontwikkelaars zich nooit zorgen te maken over het opschonen van het klembord en kunnen ze gewoon werken met de standaardopties van de browser. Voor de zeldzame gevallen waarin ontwikkelaars zich er wel zorgen over moeten maken, bestaat de optie unsanitized .

Dankbetuigingen

Dit artikel is beoordeeld door Anupam Snigdha en Rachel Andrew . De API is gespecificeerd en geïmplementeerd door het Microsoft Edge-team.