HTML ضد عفونی نشده در Async Clipboard API

از Chrome 120، یک گزینه جدید unsanitized در Async Clipboard API موجود است. این گزینه می‌تواند در موقعیت‌های خاص با HTML کمک کند، جایی که باید محتویات کلیپ‌بورد را با نحوه کپی کردن آن جای‌گذاری کنید. یعنی بدون هیچ مرحله بهداشتی میانی که مرورگرها معمولاً - و به دلایل خوب - اعمال می کنند. نحوه استفاده از آن را در این راهنما بیاموزید.

هنگام کار با Async Clipboard API ، در اکثر موارد، توسعه‌دهندگان نیازی به نگرانی در مورد یکپارچگی محتوای کلیپ‌بورد ندارند و می‌توانند فرض کنند که آنچه روی کلیپ‌بورد می‌نویسند (کپی) همان چیزی است که دریافت می‌کنند. وقتی داده ها را از کلیپ بورد می خوانند (رب).

این قطعا برای متن صادق است. کد زیر را در DevTools Console قرار دهید و سپس فوراً صفحه را مجدداً فوکوس کنید. ( setTimeout() ضروری است تا زمان کافی برای تمرکز روی صفحه داشته باشید، که لازمه API Async Clipboard است.) همانطور که می بینید، ورودی دقیقاً همان خروجی است.

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 Console امتحان کنید. متوجه خواهید شد که خروجی به طور قابل توجهی با ورودی متفاوت است.

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 ورودی و خروجی برای عملکرد صحیح برنامه بسیار مهم است. در این شرایط شما دو انتخاب دارید:

  1. اگر هم کپی کردن و هم انتهای چسباندن را کنترل می‌کنید، برای مثال، اگر از داخل برنامه خود کپی می‌کنید و سپس در برنامه خود جای‌گذاری می‌کنید، باید از قالب‌های سفارشی وب برای Async Clipboard API استفاده کنید. خواندن اینجا را متوقف کنید و مقاله مرتبط را بررسی کنید.
  2. اگر فقط انتهای چسباندن را در برنامه خود کنترل می کنید، اما انتهای کپی را کنترل نمی کنید، شاید به این دلیل که عملیات کپی در یک برنامه بومی که از فرمت های سفارشی وب پشتیبانی نمی کند، انجام می شود، باید از گزینه unsanitized استفاده کنید که در ادامه توضیح داده شده است. این مقاله

پاکسازی شامل مواردی مانند حذف تگ های script ، استایل های درون خطی و اطمینان از شکل گیری HTML است. این لیست غیر جامع است و ممکن است در آینده مراحل بیشتری اضافه شود.

کپی و چسباندن HTML غیر بهداشتی

هنگامی که HTML را با استفاده از Async Clipboard API روی کلیپ بورد write() (کپی می کنید)، مرورگر با اجرای آن از طریق تجزیه کننده DOM و سریال سازی رشته HTML حاصل، مطمئن می شود که به خوبی شکل گرفته است، اما هیچ پاکسازی در این مرحله انجام نمی شود. هیچ کاری لازم نیست انجام دهید. هنگامی که HTML read() که توسط برنامه دیگری در کلیپ‌بورد قرار داده شده است، و برنامه وب شما برای دریافت محتوای کامل وفاداری و نیاز به انجام هرگونه پاک‌سازی در کد خود انتخاب می‌کند، می‌توانید یک شی گزینه را به متد read() ارسال کنید. با ویژگی unsanitized و مقدار ['text/html'] . به صورت مجزا، به این شکل به نظر می رسد: navigator.clipboard.read({ unsanitized: ['text/html'] }) . نمونه کد زیر در زیر تقریباً مشابه چیزی است که قبلاً نشان داده شده بود، اما این بار با گزینه unsanitized . وقتی آن را در DevTools Console امتحان کنید، می بینید که ورودی و خروجی یکسان هستند.

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 بررسی شده است. API توسط تیم Microsoft Edge مشخص و پیاده سازی شده است.