Async Clipboard API में सैनिटाइज़ नहीं किया गया एचटीएमएल

Chrome 120 से, एसिंक्रोनस क्लिपबोर्ड एपीआई में unsanitized का नया विकल्प उपलब्ध है. यह विकल्प, एचटीएमएल के साथ खास स्थितियों में मदद कर सकता है. जैसे, जब आपको क्लिपबोर्ड के कॉन्टेंट को वैसे ही चिपकाना हो जैसा वह कॉपी किए जाने के समय था. इसका मतलब है कि ब्राउज़र में आम तौर पर और सही वजहों से, डेटा को सुरक्षित करने के लिए जो चरण अपनाए जाते हैं उन्हें छोड़कर. इस गाइड में, इसका इस्तेमाल करने का तरीका जानें.

Async Clipboard API का इस्तेमाल करते समय, ज़्यादातर मामलों में डेवलपर को क्लिपबोर्ड पर मौजूद कॉन्टेंट की पूरी सुरक्षा के बारे में चिंता करने की ज़रूरत नहीं होती. साथ ही, वे यह मान सकते हैं कि क्लिपबोर्ड पर लिखा गया कॉन्टेंट (कॉपी) और क्लिपबोर्ड से पढ़ा गया कॉन्टेंट (चिपकाया गया कॉन्टेंट) एक ही होगा.

यह बात टेक्स्ट के लिए ज़रूर सही है. इस कोड को DevTools कंसोल में चिपकाएं और पेज पर तुरंत फिर से फ़ोकस करें. (setTimeout() ज़रूरी है, ताकि आपके पास पेज पर फ़ोकस करने के लिए ज़रूरत के मुताबिक समय हो. यह AsyncClipboard 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);

हालांकि, एचटीएमएल टेक्स्ट का क्या होता है? जैसा कि आपको पता है, एचटीएमएल के साथ स्थिति अलग होती है. यहां ब्राउज़र, गलत चीज़ों को रोकने के लिए एचटीएमएल कोड को साफ़ करता है. उदाहरण के लिए, एचटीएमएल कोड (और <meta>, <head>, और <style> जैसे दूसरे) से <script> टैग हटाकर और सीएसएस को इनलाइन करके. नीचे दिया गया उदाहरण देखें और इसे 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);

आम तौर पर, एचटीएमएल को सुरक्षित करना एक अच्छी बात है. ज़्यादातर मामलों में, बिना सुरक्षा जांच वाले एचटीएमएल को अनुमति देकर, आपको सुरक्षा से जुड़ी समस्याओं का सामना नहीं करना पड़े. हालांकि, कुछ मामलों में डेवलपर को पता होता है कि वह क्या कर रहा है. साथ ही, इन मामलों में ऐप्लिकेशन के सही तरीके से काम करने के लिए, इनपुट और आउटपुट एचटीएमएल की पूरी सुरक्षा ज़रूरी होती है. ऐसे में, आपके पास दो विकल्प होते हैं:

  1. अगर कॉपी करने और चिपकाने, दोनों को कंट्रोल किया जाता है, तो आपको Async Clipboard API के लिए वेब के कस्टम फ़ॉर्मैट का इस्तेमाल करना चाहिए. उदाहरण के लिए, अगर आपको अपने ऐप्लिकेशन में कॉपी करके, उसी ऐप्लिकेशन में चिपकाना है. यहां पढ़ना बंद करें और लिंक किए गए लेख को पढ़ें.
  2. अगर आपके ऐप्लिकेशन में सिर्फ़ चिपकाने की प्रोसेस को कंट्रोल किया जाता है, लेकिन कॉपी करने की प्रोसेस को नहीं, तो आपको unsanitized विकल्प का इस्तेमाल करना चाहिए. इस विकल्प के बारे में इस लेख में आगे बताया गया है. ऐसा इसलिए हो सकता है, क्योंकि कॉपी करने की प्रोसेस किसी ऐसे नेटिव ऐप्लिकेशन में होती है जो वेब के कस्टम फ़ॉर्मैट के साथ काम नहीं करता.

सैनिटाइज़ेशन में कई चीज़ें शामिल होती हैं, जैसे कि script टैग हटाना, स्टाइल को इनलाइन करना, और यह पक्का करना कि एचटीएमएल सही तरीके से बनाया गया हो. यह पूरी सूची नहीं है. आने वाले समय में, इसमें और चरण जोड़े जा सकते हैं.

बिना साफ़ किए गए एचटीएमएल को कॉपी करके चिपकाना

जब Async Clipboard API की मदद से, एचटीएमएल को क्लिपबोर्ड पर write() (कॉपी) किया जाता है, तो ब्राउज़र यह पक्का करता है कि वह सही तरीके से फ़ॉर्मैट किया गया हो. इसके लिए, वह एचटीएमएल को डीओएम पार्सर से चलाता है और उससे बनी एचटीएमएल स्ट्रिंग को सीरियलाइज़ करता है. हालांकि, इस चरण में एचटीएमएल को सुरक्षित नहीं किया जाता. आपको कुछ भी करने की ज़रूरत नहीं है. जब किसी दूसरे ऐप्लिकेशन से क्लिपबोर्ड पर read() एचटीएमएल डाला जाता है और आपका वेब ऐप्लिकेशन, पूरी तरह से सटीक कॉन्टेंट पाने के लिए ऑप्ट इन करता है और आपको अपने कोड में कोई सैनिटाइज़ेशन करना होता है, तो read() तरीके में विकल्प ऑब्जेक्ट को, प्रॉपर्टी unsanitized और ['text/html'] की वैल्यू के साथ पास किया जा सकता है. अलग से, यह इस तरह दिखता है: navigator.clipboard.read({ unsanitized: ['text/html'] }). यहां दिया गया कोड सैंपल, पहले दिखाए गए कोड सैंपल से काफ़ी हद तक मिलता-जुलता है. हालांकि, इस बार इसमें unsanitized विकल्प है. 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({
    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 की टीम ने तय करके लागू किया है.