Onopgeschoonde HTML in de Async Clipboard API

Vanaf Chrome 120 is er een nieuwe unsanitized optie beschikbaar in de Async Clipboard API. Deze optie kan helpen in speciale situaties met HTML, waarbij u de inhoud van het klembord op dezelfde manier moet plakken als toen deze werd gekopieerd. Dat wil zeggen, zonder enige tussentijdse opschoningsstap die browsers gewoonlijk – en om goede redenen – toepassen. In deze handleiding leest u hoe u het kunt gebruiken.

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 op het klembord schrijven (kopiëren) hetzelfde is als wat ze zullen krijgen wanneer ze de gegevens van het klembord lezen (plakken).

Dit geldt zeker voor tekst. Probeer de volgende code in de DevTools-console te plakken en richt de pagina vervolgens onmiddellijk opnieuw. (De setTimeout() is nodig zodat u voldoende tijd heeft om de pagina scherp te stellen, wat een vereiste is van de Async Clipboard API.) Zoals u ziet, is de invoer precies 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 een beetje anders. Om zogenaamde compressiebomaanvallen te voorkomen, coderen browsers afbeeldingen zoals PNG's opnieuw, maar de invoer- en uitvoerafbeeldingen zijn visueel precies hetzelfde, pixel per 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 met HTML anders. Hier zuivert de browser de HTML-code om te voorkomen dat er slechte dingen gebeuren, door bijvoorbeeld <script> -tags uit de HTML-code te verwijderen (en andere zoals <meta> , <head> en <style> ) en door CSS inline te plaatsen . Bekijk het volgende voorbeeld en probeer het in de DevTools Console. Je zult merken dat de output behoorlijk verschilt van de input.

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);

Het opschonen van HTML is over het algemeen een goede zaak. U wilt uzelf niet blootstellen aan beveiligingsproblemen door in de meeste gevallen onopgeschoonde 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 output-HTML cruciaal is voor het correct functioneren van de app. Onder deze omstandigheden heeft u twee keuzes:

  1. Als u zowel het kopiëren als het plakken beheert, bijvoorbeeld als u vanuit uw app kopieert en vervolgens ook binnen 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 alleen het plakgedeelte in uw app beheert, maar niet het kopieergedeelte, misschien omdat de kopieerbewerking plaatsvindt in een native app die geen aangepaste webformaten ondersteunt, moet u de unsanitized optie gebruiken, die wordt uitgelegd in de rest van dit artikel.

Opschoning omvat zaken als het verwijderen van script , inliningstijlen en ervoor zorgen dat de HTML goed is opgemaakt. Deze lijst is niet volledig en er kunnen in de toekomst meer stappen worden toegevoegd.

Kopieer en plak onopgeschoonde HTML

Wanneer u HTML naar het klembord write() (kopieert) met de Async Clipboard API, zorgt de browser ervoor dat deze goed wordt gevormd door deze door een DOM-parser te laten lopen en de resulterende HTML-tekenreeks te serialiseren, maar er vindt geen opschoning plaats bij deze stap. U hoeft niets te doen. Wanneer u HTML read() die door een andere toepassing op het klembord is geplaatst en uw webapp zich aanmeldt voor het verkrijgen van de volledige inhoud en enige opschoning in uw eigen code moet uitvoeren, kunt u een optieobject doorgeven aan de methode read() met de eigenschap unsanitized en een waarde van ['text/html'] . Op zichzelf ziet het er als volgt uit: navigator.clipboard.read({ unsanitized: ['text/html'] }) . Het volgende codevoorbeeld hieronder is bijna hetzelfde als het codevoorbeeld dat eerder werd weergegeven, maar deze keer met de unsanitized optie. Wanneer je het in de DevTools Console probeert, zul je 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 overleeft, wat ondersteuning aangeeft, of inline is, wat aangeeft dat er geen ondersteuning is. Houd er rekening mee dat om dit te laten werken, de pagina al de klembordtoestemming moet hebben verkregen.

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);
};

Demo

Om de unsanitized optie in actie te zien, bekijk de demo op Glitch en bekijk de broncode .

Conclusies

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

Dankbetuigingen

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