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

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

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

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