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

थॉमस स्टेनर
थॉमस स्टेनर

Chrome 120 में, एक नया unsanitized विकल्प Async Clipboard API में उपलब्ध है. यह विकल्प एचटीएमएल के साथ खास स्थितियों में मदद कर सकता है, जहां आपको क्लिपबोर्ड की सामग्री को उसी तरह चिपकाने की ज़रूरत होती है जैसा कि इसे कॉपी करते समय था. इसका मतलब यह है कि बीच-बीच में मौजूद सैनिटेशन की प्रक्रिया को, ब्राउज़र में आम तौर पर और सही वजहों से लागू किया जाता है. इस गाइड में इसे इस्तेमाल करने का तरीका जानें.

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

यह टेक्स्ट के लिए बिलकुल सही है. इस कोड को DevTools कंसोल में चिपकाकर देखें और पेज पर तुरंत फिर से फ़ोकस करें. (setTimeout() इसलिए ज़रूरी है, ताकि आपके पास पेज पर फ़ोकस करने के लिए काफ़ी समय हो. यह Async क्लिपबोर्ड एपीआई के लिए ज़रूरी है.) जैसा कि आपको दिख रहा है, इनपुट बिलकुल आउटपुट जैसा ही है.

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