Async Clipboard API'deki temizlenmemiş HTML

Chrome 120'den itibaren Async Clipboard API'de yeni bir unsanitized seçeneği mevcuttur. Bu seçenek, HTML ile ilgili özel durumlarda işe yarayabilir. Bu durumlarda pano içeriğini, kopyalandığı zaman olduğu şekliyle yapıştırmanız gerekir. Diğer bir deyişle, tarayıcıların genellikle (ve haklı nedenlerle) uyguladığı herhangi bir ara temizleme adımı yoktur. Bu kılavuzu nasıl kullanacağınızı öğrenmek için bu kılavuza göz atın.

Async Clipboard API ile çalışırken, çoğu durumda geliştiriciler panodaki içeriğin bütünlüğü konusunda endişe duymazlar ve panodaki verileri okuduklarında (yapıştırma) olacaklarıyla aynı olduklarını varsayabilirler.

Bu durum metin için kesinlikle geçerlidir. Aşağıdaki kodu Geliştirici Araçları Konsolu'na yapıştırdıktan sonra sayfaya yeniden odaklamayı deneyin. (setTimeout(), sayfaya odaklanmak için yeterli zamanınız olması açısından gereklidir. Bu, Async Clipboard API'nin şartıdır.) Gördüğünüz gibi giriş, çıkışla tam olarak aynıdır.

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);

Görsellerde bu durum biraz farklıdır. Tarayıcılar, sıkıştırma bombası saldırıları olarak adlandırılan saldırıları önlemek için PNG gibi resimleri yeniden kodlar ancak giriş ve çıktı resimleri görsel olarak tamamen aynıdır (piksel başına piksel).

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);

Peki HTML metnine ne olur? Tahmin edebileceğiniz gibi, HTML'de durum farklıdır. Burada tarayıcı, kötü şeylerin olmasını önlemek için HTML kodunu arındırır. Örneğin, <script> etiketlerini HTML kodundan (ve <meta>, <head> ve <style> gibi diğer etiketlerden) çıkarır ve CSS'yi satır içine alır. Aşağıdaki örneği inceleyip Geliştirici Araçları Konsolu'nda deneyin. Çıkışın, giriştekinden önemli ölçüde farklı olduğunu fark edeceksiniz.

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 temizleme işlemi genellikle iyi bir şeydir. Çoğu durumda, temizlenmemiş HTML'ye izin vererek güvenlik sorunlarına maruz kalmak istemezsiniz. Yine de geliştiricinin ne yaptığını tam olarak bildiği ve uygulamanın doğru çalışması için giriş ve çıkış HTML'sinin bütünlüğünün nerede kritik önem taşıdığını gösteren senaryolar vardır. Bu durumlarda iki seçeneğiniz vardır:

  1. Hem kopyalama hem de yapıştırma işini kontrol ediyorsanız (örneğin, kopyalayıp uygulamanızın içinden kopyalayıp aynı şekilde uygulamanızın içinde yapıştırıyorsanız) Async Clipboard API için web özel biçimlerini kullanmanız gerekir. Burada okumayı bırakıp bağlantılı makaleye göz atın.
  2. Kopyalama işlemi web'de özel biçimleri desteklemeyen yerel bir uygulamada gerçekleştiği için uygulamanızda yalnızca yapıştırma işini kontrol ediyor, kopyalama işini siz kontrol etmiyorsanız bu makalenin geri kalanında açıklanan unsanitized seçeneğini kullanmanız gerekir.

Temizlik işlemine script etiketlerini kaldırma, stilleri satır içine alma ve HTML'nin doğru biçimlendirildiğinden emin olma gibi işlemler dahildir. Bu liste tam kapsamlı değildir ve gelecekte daha fazla adım eklenebilir.

Temizlenmemiş HTML'yi kopyalayıp yapıştırın

Async Clipboard API ile HTML'yi panoya write() (kopyaladığınızda) tarayıcı, DOM ayrıştırıcı ile çalıştırıp ortaya çıkan HTML dizesini serileştirerek doğru biçimlendirildiğinden emin olur. Ancak bu adımda herhangi bir temizleme işlemi gerçekleşmez. Yapmanız gereken bir işlem yoktur. read() HTML'yi panoya başka bir uygulama tarafından yerleştirdiğinizde ve web uygulamanız tam kaliteli içeriği almayı tercih ettiğinde ve kendi kodunuzda temizleme işlemini gerçekleştirmeyi tercih ettiğinde unsanitized özelliğine ve ['text/html'] değerine sahip bir seçenek nesnesini read() yöntemine aktarabilirsiniz. Ayrı olarak şu şekilde görünür: navigator.clipboard.read({ unsanitized: ['text/html'] }). Aşağıdaki kod örneği, daha önce gösterilenle neredeyse aynıdır, ancak bu kez unsanitized seçeneği mevcuttur. Bu aracı Geliştirici Araçları Konsolu'nda denediğinizde giriş ve çıkışın aynı olduğunu göreceksiniz.

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);

Tarayıcı desteği ve özellik algılama

Özelliğin desteklenip desteklenmediğini kontrol etmenin doğrudan bir yolu yoktur, bu nedenle özellik algılama davranışın gözlemlenmesine dayanır. Bu nedenle, aşağıdaki örnek, <style> etiketinin hayatta kalıp kalmadığını (desteklendiğini veya satır içine alınıp alınmadığını) belirtir. Bu işlevin çalışması için sayfanın önceden pano izni almış olması gerektiğini unutmayın.

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);
};

Demo

unsanitized seçeneğinin nasıl çalıştığını görmek için Glitch'teki demoyu izleyin ve kaynak koduna göz atın.

Sonuçlar

Giriş bölümünde belirtildiği gibi, çoğu geliştirici hiçbir zaman pano temizleme konusunda endişe duymaz ve yalnızca tarayıcının yaptığı varsayılan temizleme seçimleriyle çalışabilir. Geliştiricilerin ilgilenmesi gereken nadir durumlar için unsanitized seçeneği mevcuttur.

Teşekkür

Bu makale Anupam Snigdha ve Rachel Andrew tarafından incelendi. API, Microsoft Edge ekibi tarafından belirlenmiş ve uygulanmıştır.