החל מגרסה 120 של Chrome, זמינה אפשרות חדשה unsanitized
ב-Async Clipboard API. האפשרות הזו יכולה לעזור במצבים מיוחדים עם HTML, שבהם צריך להדביק את תוכן לוח העריכה בדיוק כפי שהוא היה כשהועתק.
כלומר, ללא שלב ביניים של טיהור, שדפדפנים מחילים בדרך כלל – ומסיבות טובות. במדריך הזה מוסבר איך משתמשים בו.
כשעובדים עם Async Clipboard API, ברוב המקרים המפתחים לא צריכים לדאוג לתקינות התוכן בלוח העריכה, וניתן להניח שהם יקבלו אותו תוכן שהם כתבו בלוח העריכה (העתקה) כשהם יקראו את הנתונים מהלוח (הדבקה).
זה בהחלט נכון לגבי טקסט. אפשר לנסות להדביק את הקוד הבא במסוף DevTools ואז להתמקד בדף באופן מיידי. (ה-setTimeout()
נדרש כדי שיהיה לכם מספיק זמן להתמקד בדף, כנדרש על ידי Async Clipboard API). כפי שאתם רואים, הקלט זהה בדיוק לפלט.
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);
עם תמונות, המצב שונה במקצת. כדי למנוע התקפות שנקראות הפצצה דחוסה, הדפדפנים מקודדים מחדש תמונות כמו קובצי PNG, אבל התמונות בקלט ובפלט חזותית זהות לחלוטין, פיקסל אחרי פיקסל.
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);
מה קורה עם טקסט HTML? כפי שאפשר לנחש, המצב שונה ב-HTML. כאן הדפדפן מנקה את קוד ה-HTML כדי למנוע דברים רעים, למשל על ידי הסרת תגי <script>
מקוד ה-HTML (ותגים אחרים כמו <meta>
, <head>
ו-<style>
) והוספת CSS בקוד.
כדאי לנסות את הדוגמה הבאה במסוף DevTools. תוכלו לראות שהפלט שונה באופן משמעותי מהקלט.
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 הוא דבר טוב. ברוב המקרים, לא כדאי לחשוף את עצמכם לבעיות אבטחה על ידי מתן הרשאה להטמעת HTML ללא סינון. עם זאת, יש תרחישים שבהם המפתח יודע בדיוק מה הוא עושה, ושבהם תקינות ה-HTML של הקלט והפלט חיונית לתפקוד תקין של האפליקציה. במקרים כאלה, יש לכם שתי אפשרויות:
- אם אתם שולטים גם בקצה ההעתקה וגם בקצה הדבקה, למשל, אם אתם מעתיקים מתוך האפליקציה כדי להדביק אותה באותה דרך בתוך האפליקציה, כדאי להשתמש בפורמטים מותאמים אישית לאינטרנט ב-Async Clipboard API. כדאי להפסיק לקרוא כאן ולבדוק את המאמר המקושר.
- אם אתם שולטים רק בקצה ההדבקה באפליקציה, אבל לא בקצה ההעתקה, אולי כי פעולת ההעתקה מתבצעת באפליקציה מקורית שלא תומכת בפורמטים מותאמים אישית לאינטרנט, עליכם להשתמש באפשרות
unsanitized
שמפורטת בהמשך המאמר.
תהליך הניקוי כולל פעולות כמו הסרת תגי script
, הטמעת סגנונות בקוד ולוודא שה-HTML תקין. הרשימה הזו לא מקיפה, וייתכן שיתווספו לה שלבים נוספים בעתיד.
העתקה והדבקה של קוד HTML לא מסונן
כשאתם write()
(מעתיקים) קוד HTML ללוח באמצעות Async Clipboard API, הדפדפן מוודא שהוא תקין על ידי הרצתו באמצעות מנתח DOM וסריאליזציה של מחרוזת ה-HTML שנוצרת, אבל לא מתבצעת ניטרול זיהומים בשלב הזה. אין צורך לעשות שום דבר. כשאתם read()
HTML שמונח בלוח העריכה על ידי אפליקציה אחרת, ואפליקציית האינטרנט שלכם בוחרת לקבל את התוכן ברמת הנאמנות המלאה וצריך לבצע סינון בקוד שלכם, תוכלו להעביר אובייקט אפשרויות לשיטה read()
עם המאפיין unsanitized
והערך ['text/html']
. כשהוא מופיע לבד, הוא נראה כך:
navigator.clipboard.read({ unsanitized: ['text/html'] })
. דוגמת הקוד הבאה היא כמעט זהה לדוגמה שצוינה קודם, אבל הפעם עם האפשרות unsanitized
. כשתנסו את זה במסוף כלי הפיתוח, תראו שהקלט והפלט זהים.
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);
תמיכה בדפדפנים וזיהוי תכונות
אין דרך ישירה לבדוק אם יש תמיכה בתכונה, ולכן זיהוי התכונה מבוסס על התבוננות בהתנהגות. לכן, הדוגמה הבאה מתבססת על זיהוי העובדה אם תג <style>
נשאר, מה שמציין תמיכה, או אם הוא מוטמע, מה שמציין שאין תמיכה. חשוב לזכור: כדי שהפעולה הזו תפעל, הדף כבר צריך לקבל הרשאה להשתמש בלוח.
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);
};
הדגמה (דמו)
כדי לראות את האפשרות unsanitized
בפעולה, אפשר לעיין בדמו ב-Glitch ובקוד המקור שלו.
מסקנות
כפי שצוין בהקדמה, רוב המפתחים לא יצטרכו לדאוג לניקוי הלוח, ויכולים פשוט להשתמש באפשרויות הניקוי שמוגדרות כברירת מחדל בדפדפן. במקרים הנדירים שבהם המפתחים צריכים לדאוג לזה, יש את האפשרות unsanitized
.
קישורים מועילים
תודות
המאמר הזה נבדק על ידי Anupam Snigdha ו-Rachel Andrew. צוות Microsoft Edge הגדיר ושילב את ה-API.