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

اعتبارًا من الإصدار 120 من Chrome، يتوفّر خيار unsanitized جديد في واجهة برمجة التطبيقات Async Clipboard API. يمكن أن يساعدك هذا الخيار في حالات خاصة تتعلّق بتنسيق HTML، حيث تحتاج إلى لصق محتويات الحافظة بالطريقة نفسها التي تم بها نسخها. وهذا يعني أنّه بدون أي خطوة تطهير وسيطة تطبّقها المتصفّحات عادةً لأسباب وجيهة. يمكنك الاطّلاع على كيفية استخدامها في هذا الدليل.

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

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

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 غير المحوَّل

عند 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.