Nieoczyszczony kod HTML w interfejsie Async Clipboard API

Od Chrome 120 w schowku asynchronicznym dostępna jest nowa opcja unsanitized API. Ta opcja może być pomocna w szczególnych sytuacjach w kodzie HTML, kiedy trzeba wklejenie zawartości schowka w taki sam sposób, jak w momencie jej kopiowania. Oznacza to, że bez żadnego pośredniego etapu dezynfekcji, który jest często używany przez przeglądarki – z ważnych powodów – zgłoś się. Z tego przewodnika dowiesz się, jak jej używać.

Podczas pracy z Async Clipboard API, w większości przypadków deweloperzy nie muszą martwić się o integralność zawartość schowka i może założyć, że to, co zapisze, schowek (kopia) to ten sam element, z którego otrzymają odczyt danych ze schowka (wklej).

Zdecydowanie dotyczy to także tekstu. Spróbuj wkleić ten kod w Narzędziach deweloperskich Konsola, a następnie natychmiast zmień ostrość strony. (setTimeout() jest wymagany więc masz wystarczająco dużo czasu na skupienie się na stronie, co jest wymagane przez Clipboard API). Jak widać, dane wejściowe są dokładnie takie same jak dane wyjściowe.

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

Z obrazami wygląda to trochę inaczej. Aby temu zapobiec ataki bombami kompresyjnymi, przeglądarki, ponownie kodować obrazy, takie jak PNG, ale obrazy wejściowe i wyjściowe są wizualne dokładnie tyle samo, ile pikseli na piksel.

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

Co jednak się stanie z tekstem HTML? Jak można się domyślić, w języku HTML jest jednak inna. W tym przypadku przeglądarka oczyszcza kod HTML, na przykład przez usunięcie tagów <script> z kodu HTML (i innych, takich jak <meta>, <head> i <style>) oraz przez wstawienie CSS. Zapoznaj się z podanym niżej przykładem i wypróbuj go w konsoli Narzędzi deweloperskich. Ty dane wyjściowe znacznie różnią się od danych wejściowych.

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

Proces oczyszczania kodu HTML jest zwykle bardzo dobry. Nie chcesz się ujawniać na problemy z bezpieczeństwem, zezwalając w większości przypadków na nieoczyszczony kod HTML. OK są scenariuszy, w których programista dokładnie wie, co robi, w których integralność kodu HTML (wejściowego i wyjściowego) jest kluczowa funkcji aplikacji. W takiej sytuacji masz dwie możliwości:

  1. Jeśli kontrolujesz zarówno koniec kopiowania, jak i wklejania, na przykład jeśli kopiujesz a następnie wklejanie w aplikacji, użyj Niestandardowe formaty internetowe dla interfejsu Async Clipboard API. Przestań go czytać i skorzystaj z linku do artykułu.
  2. Jeśli masz kontrolę tylko nad końcem wklejania w aplikacji, ale nie na końcu kopiowania, może to wynikać z faktu, że operacja kopiowania odbywa się w aplikacji natywnej, która nie obsługuje niestandardowych formatów reklam internetowych, użyj opcji unsanitized, która jest co wyjaśniamy w dalszej części tego artykułu.

Proces oczyszczania obejmuje m.in. usunięcie tagów script, stylów wkładek i w celu zadbania o prawidłowy format kodu HTML. Ta lista jest niekompletna i zawiera inne informacje kroki można dodać w przyszłości.

Kopiowanie i wklejanie nieoczyszczonego kodu HTML

Gdy write() (skopiujesz) kod HTML do schowka za pomocą interfejsu Async Clipboard API, przeglądarka sprawdza poprawność składni, uruchamiając ją za pomocą parsera DOM. i serializacji wynikowego ciągu HTML, ale oczyszczanie nie jest przeprowadzane ten krok. Nie musisz nic robić. Gdy umieścisz kod HTML w sekcji read() do schowka przez inną aplikację, a aplikacja internetowa włącza pobieranie czyli dbałości o wysoką jakość treści i nie musisz przeprowadzać procesu dezynfekcji we własnym kodzie. możesz przekazać do metody read() obiekt opcji z właściwością unsanitized i wartości ['text/html']. W izolacji wygląda to tak: navigator.clipboard.read({ unsanitized: ['text/html'] }) Następujący przykładowy kod poniżej jest prawie taki sam jak pokazany wcześniej, ale tym razem z unsanitized . Po wypróbowaniu tej funkcji w konsoli Narzędzi deweloperskich zobaczysz, że dane wejściowe dane wyjściowe są takie same.

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

Obsługa przeglądarek i wykrywanie funkcji

Nie można bezpośrednio sprawdzić, czy dana funkcja jest obsługiwana, jest oparta na obserwacjach zachowań. W związku z tym w przykładzie poniżej opiera się na wykrywaniu faktu, czy tag <style> przetrwa, który wskazuje wsparcie lub jest w tekście, co oznacza brak obsługi. Pamiętaj, że aby to zadziałało, strona musi już mieć dostęp do schowka uprawnienia.

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

Wersja demonstracyjna

Aby zobaczyć, jak działa opcja unsanitized, zapoznaj się z demonstracja usługi Glitch i zobacz kodu źródłowego.

Podsumowanie

Zgodnie z wprowadzeniem większość deweloperów nie będzie musiała martwić się o to, oczyszczania schowka i może działać tylko z domyślnymi opcjami dezynfekcji przez przeglądarkę. W rzadkich przypadkach, gdy deweloperzy muszą mieć na to uwagę, Opcja unsanitized istnieje.

Podziękowania

Ten artykuł zweryfikował Anupam Snigdha i Rachel Andrew Określono interfejs API i wdrożony przez zespół Microsoft Edge.