لم يتم التحقق من ترميز HTML في واجهة برمجة تطبيقات الحافظة غير المتزامنة.

من Chrome 120، يتوفّر خيار unsanitized جديد في "الحافظة غير المتزامنة" واجهة برمجة التطبيقات. يمكن الاستفادة من هذا الخيار في حالات خاصة مع HTML، حيث تحتاج إلى الصق محتويات الحافظة المتماثلة مع ما كانت عليه عند نسخها. أي بدون أي خطوة تعقيم متوسطة تستخدمها المتصفحات بشكل شائع لأسباب وجيهة، التقدّم بطلب تعرف على كيفية استخدامها في هذا الدليل.

عند العمل مع Async Clipboard API، في معظم الحالات، لا يحتاج المطوّرون إلى القلق بشأن سلامة المحتوى الموجود في الحافظة ويمكن أن يفترض أن ما يكتبونه على الحافظة (النسخ) هي نفس ما سيحصلون عليه عند قراءة البيانات من الحافظة (لصق).

وهذا ينطبق بالتأكيد على النص. يُرجى محاولة لصق الرمز التالي في "أدوات مطوري البرامج". وحدة التحكّم ثم إعادة التركيز على الصفحة على الفور. (setTimeout() ضروري بحيث يكون لديك ما يكفي من الوقت للتركيز على الصفحة، وهو متطلب 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. يُرجى الاطّلاع على المثال التالي وتجربته في وحدة التحكّم في أدوات مطوّري البرامج. وسوف ملاحظة أن المخرجات تختلف بشكل كبير عن المدخلات.

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 داخل ومخرجات HTML مهمة جدًا بشكل أفضل تشغيل التطبيق. وفي هذه الظروف، لديك خياران:

  1. إذا كنت تتحكم في كل من نهاية النسخ واللصق، على سبيل المثال، إذا قمت بنسخ من داخل التطبيق ثم اللصق فيه كذلك، يجب استخدام تنسيقات مخصّصة للويب لواجهة برمجة التطبيقات Async Clipboard API. يُرجى التوقف عن القراءة هنا والاطّلاع على المقالة المرتبطة.
  2. إذا كنت تتحكّم فقط في نهاية اللصق في تطبيقك، ولكن ليس في نهاية النسخ، ربما بسبب حدوث عملية النسخ في تطبيق محلي غير متوافق بتنسيقات مخصصة للويب، يجب استخدام الخيار 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).

شكر وتقدير

تمت مراجعة هذه المقالة من قِبل أنوبام سنيغدها و راشيل أندرو. تم تحديد واجهة برمجة التطبيقات من قبل فريق Microsoft Edge.