Od wersji 120 Chrome nowa opcja unsanitized
jest dostępna w interfejsie API asynchronicznej funkcji Schowek. Ta opcja może być przydatna w szczególnych sytuacjach związanych z HTML, gdy musisz wkleić zawartość schowka w takim samym formacie, w jakim została skopiowana.
Oznacza to, że nie ma żadnego pośredniego etapu sterylizacji, który przeglądarki często i z powodu dobrych powodów stosują. W tym przewodniku dowiesz się, jak z niego korzystać.
Podczas pracy z niesynchronizowanym interfejsem API Schowka w większości przypadków programiści nie muszą się martwić o integralność treści na schowku i mogą założyć, że to, co napiszą na schowku (kopia), jest takie samo, co otrzymają, gdy odczytują dane ze schowka (wklejanie).
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 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ż, że wynik 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);
Sanityzacja kodu HTML jest zazwyczaj dobrym rozwiązaniem. Większość problemów z bezpieczeństwem wynika z dostępu do nieoczyszczonego kodu HTML. Są jednak sytuacje, w których deweloper wie dokładnie, co robi, a nienaruszanie integralności kodu HTML na wejściu i na wyjściu jest 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 niestandardowych w internecie, użyj opcji
unsanitized
, której działanie jest opisane w dalszej części tego artykułu.
Sanityzacja obejmuje m.in. usuwanie tagów script
, wstawianie stylów wbudowanych oraz sprawdzanie, czy kod HTML jest poprawny. 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()
(kopiujesz) kod HTML do schowka za pomocą interfejsu Async Clipboard API, przeglądarka sprawdza, czy jest on poprawnie sformatowany, przesyłając go do parsowania DOM i serializując wynikowy ciąg znaków HTML. W tym kroku nie jest jednak przeprowadzana żadna sanityzacja. 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 musi przeprowadzić dezynfekcję w swoim kodzie, możesz przekazać obiekt opcji do metody read()
z właściwoś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>
przetrwał, co oznacza, że jest obsługiwany, czy został wstawiony, co oznacza, że nie jest obsługiwany. 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 programiści muszą się czymś zająć, 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.