Od wersji 120 Chrome nowa opcja unsanitized
jest dostępna w interfejsie API asynchronicznej funkcji Schowek. Ta opcja może być pomocna w szczególnych sytuacjach w przypadku kodu HTML, kiedy zawartość schowka musi być wklejona w taki sam sposób jak podczas kopiowania.
Oznacza to, że bez żadnego pośredniego etapu dezynfekcji, który często stosuje się w przeglądarkach – i nie bez powodu. W tym przewodniku dowiesz się, jak z niego korzystać.
Podczas pracy z interfejsem Async Clipboard API w większości przypadków deweloperzy nie muszą martwić się o integralność treści w schowku i mogą przyjąć, że zapis w schowku (kopia) jest taki sam, co odczytują dane ze schowka (wklejają).
Zdecydowanie dotyczy to tekstu. Spróbuj wkleić ten kod w Konsoli DevTools, a następnie natychmiast przekieruj fokus na stronę. (Element setTimeout()
jest potrzebny, aby dać użytkownikowi wystarczająco dużo czasu na skupienie się na stronie, co jest wymagane przez interfejs Clipboard API asynchronicznego). Jak widać, dane wejściowe są identyczne z danymi wyjściowymi.
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);
W przypadku obrazów jest to nieco inne. Aby zapobiec tak zwanym atakom bomby kompresji, przeglądarki ponownie kodują obrazy w formacie PNG, ale obrazy wejściowe i wyjściowe są wizualnie dokładnie takie same, piksel po pikselu.
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 się jednak dzieje z tekstem w kodzie HTML? Jak się zapewne domyślasz, w przypadku kodu HTML sytuacja wygląda inaczej. W tym przypadku przeglądarka czyści kod HTML, aby zapobiec niepożądanym działaniom, na przykład usuwając z kodu HTML tagi <script>
(oraz inne, takie jak <meta>
, <head>
i <style>
) oraz wstawiając kod CSS.
Zapoznaj się z tym przykładem i spróbuj go w konsoli DevTools. Zauważysz, że wynik różni się dość znacznie 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);
Sanityzacja kodu HTML jest zwykle korzystna. Większość problemów z zabezpieczeniami wynika z dopuszczania w większości przypadków nieoczyszczonego kodu HTML. Są jednak sytuacje, w których deweloper wie dokładnie, co robi, a całość i struktura kodu HTML na wejściu i na wyjściu są kluczowe dla prawidłowego działania aplikacji. W takich okolicznościach masz 2 możliwości:
- Jeśli kontrolujesz zarówno kopiowanie, jak i wklejanie, np. kopiujesz z aplikacji, a potem wklejasz w tej samej aplikacji, użyj niestandardowych formatów internetowych w interfejsie Async Clipboard API. Nie musisz czytać dalej. Przejdź do powiązanego artykułu.
- Jeśli w swojej aplikacji kontrolujesz tylko wklejanie, a nie kopiowanie,
być może dlatego, że operacja kopiowania odbywa się w natywnej aplikacji, która nie obsługuje formatów internetowych, użyj opcji
unsanitized
, której działanie jest opisane w dalszej części tego artykułu.
Proces oczyszczania obejmuje m.in. usuwanie tagów script
, wstawianie stylów i sprawdzanie poprawności kodu HTML. Ta lista nie jest wyczerpująca. W przyszłości możemy dodać do niej kolejne kroki.
Kopiowanie i wklejanie nieoczyszczonego kodu HTML
Gdy write()
(skopiujesz) kod HTML do schowka za pomocą interfejsu Async Clipboard API, przeglądarka sprawdza, czy kod jest poprawnie sformułowany, uruchamiając go przez parser DOM i zserializując wynikowy ciąg znaków HTML, ale na tym etapie nie odbywa się żadna oczyszczanie. Nie musisz nic robić. Gdy read()
HTML umieszczony w schowku przez inną aplikację, a Twoja aplikacja internetowa chce uzyskać pełną jakość treści i wykonać dezynfekcję w swoim kodzie, możesz przekazać obiekt opcji do metody read()
z własnością unsanitized
i wartością ['text/html']
. Samodzielnie wygląda tak:navigator.clipboard.read({ unsanitized: ['text/html'] })
Poniższy przykład kodu jest prawie taki sam jak poprzedni, ale tym razem z opcją unsanitized
. Gdy spróbujesz tego w konsoli DevTools, zobaczysz, że dane wejściowe i 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 ma bezpośredniego sposobu sprawdzenia, czy dana funkcja jest obsługiwana, więc wykrywanie funkcji opiera się na obserwacji zachowania. Dlatego w tym przykładzie wykrywanie polega na sprawdzaniu, czy tag <style>
jest zachowany (co oznacza, że funkcja jest obsługiwana) czy wbudowany (co oznacza, że funkcja nie jest obsługiwana). Pamiętaj, że aby to działało, strona musi mieć już udzielone uprawnienia do schowka.
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);
};
Prezentacja
Aby zobaczyć opcję unsanitized
w działaniu, obejrzyj demonstrację na Glitch i zapoznaj się z jej kodem źródłowym.
Podsumowanie
Jak wspomniano we wstępie, większość deweloperów nie musi się martwić o czyszczenie schowka i może korzystać z domyślnych opcji czyszczonych przez przeglądarkę. W rzadkich przypadkach, gdy deweloperzy muszą zwrócić na coś uwagę, dostępna jest opcja unsanitized
.
Przydatne linki
Podziękowania
Ten artykuł został sprawdzony przez Anupam Snigdha i Rachel Andrew. Interfejs API został określony i wdrożony przez zespół Microsoft Edge.