Async Clipboard API'deki temizlenmemiş HTML

Chrome 120'den itibaren, Eşzamansız Klavye Alanı API'sinde yeni bir unsanitized seçeneği kullanılabilir. Bu seçenek, HTML ile ilgili özel durumlarda, panosundaki içeriği kopyalanırken olduğu gibi yapıştırmanız gerektiğinde yardımcı olabilir. Yani, tarayıcılar tarafından yaygın olarak ve iyi nedenlerle uygulanan herhangi bir ara temizleme adımı olmadan. Bu kılavuzu nasıl kullanacağınızı bu kılavuzda bulabilirsiniz.

Asenkron Clipboard API ile çalışırken geliştiricilerin çoğu durumda panosundaki içeriğin bütünlüğü konusunda endişelenmesi gerekmez. Geliştiriciler, panosuna yazdıkları (kopyala) verilerin, panosundaki verileri okudukları (yapıştırma) zaman elde edecekleri verilerle aynı olacağını varsayabilir.

Bu kesinlikle metin için geçerlidir. Aşağıdaki kodu Geliştirici Araçları Konsolu'na yapıştırmayı deneyin ve sonra hemen sayfaya yeniden odaklanın. (setTimeout(), sayfaya odaklanmak için yeterli zamana sahip olmanız gerektiğinden gereklidir. Bu, Async Clipboard API'nin bir ş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);

Resimlerde durum biraz farklıdır. Sıkıştırma bombası saldırılarını önlemek için tarayıcılar, PNG gibi resimleri yeniden kodlar ancak giriş ve çıkış resimleri piksel başına piksel olacak şekilde görsel olarak tamamen aynıdır.

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 metinlerine ne olacak? Tahmin edebileceğiniz gibi, HTML'de durum farklıdır. Burada tarayıcı, kötü şeylerin olmasını önlemek için HTML kodunu temizler. Örneğin, HTML kodundan <script> etiketlerini (ve <meta>, <head> ve <style> gibi diğer etiketleri) kaldırır ve CSS'yi satır içi olarak yerleştirir. Aşağıdaki örneği inceleyin ve DevTools Konsolu'nda deneyin. Çıktının girişten oldukça 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 kendinizi güvenlik sorunlarına maruz bırakmak istemezsiniz. Bununla birlikte, geliştiricinin ne yaptığını tam olarak bildiği ve giriş ile çıkış HTML'sinin bütünlüğünün uygulamanın düzgün çalışması için çok önemli olduğu senaryolar da vardır. Bu durumlarda iki seçeneğiniz vardır:

  1. Hem kopyalama hem de yapıştırma işlemini siz kontrol ediyorsanız (ör. uygulamanızdan kopyalayıp yine uygulamanızda 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ı makaleyi inceleyin.
  2. Kopyalama işlemini yalnızca uygulamanızda kontrol edip kopyalama sonunu kontrol etmiyorsanız kopyalama işlemi, web'deki özel biçimleri desteklemeyen bir yerel uygulamada gerçekleşiyor olabilir. Bu durumun nedeni bu makalenin geri kalanında açıklanan unsanitized seçeneğini kullanmanızdır.

Sanitasyon işleminde script etiketlerinin kaldırılması, stillerin satır içine yerleştirilmesi ve HTML'nin düzgün oluşturulduğundan emin olunması gibi işlemler yer alır. Bu liste tam kapsamlı değildir ve gelecekte daha fazla adım eklenebilir.

Düzeltilmemiş HTML'yi kopyalayıp yapıştırın

Async Clipboard API ile HTML'yi panoya write() (kopyaladığınızda) tarayıcı, bir DOM ayrıştırıcısı üzerinden çalıştırarak ve ortaya çıkan HTML dizesini serileştirerek HTML'nin iyi biçimlendirildiğinden emin olur ancak bu adımda temizleme işlemi yapılmaz. Yapmanız gereken hiçbir şey yoktur. Başka bir uygulama tarafından read() HTML'yi panosuna yerleştirdiğinizde ve web uygulamanız tam doğruluktaki içeriği almaya karar verdiğinde ve kendi kodunuzda herhangi bir temizleme işlemi yapmanız gerektiğinde, read() yöntemine unsanitized mülkü ve ['text/html'] değeri içeren bir seçenekler nesnesi iletebilirsiniz. Tek başına ş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 sefer unsanitized seçeneği kullanılmıştır. 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ışı gözlemlemeye dayanır. Bu nedenle, aşağıdaki örnekte bir <style> etiketinin hayatta kalıp kalmadığı (desteklendiği) veya satır içine alınıp alınmadığı (desteklenmediği) tespit edilir. Bunun çalışması için sayfanın pano iznini 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ğini çalışırken görmek için Glitch'teki demoya göz atın ve kaynak koduna bakın.

Sonuçlar

Giriş bölümünde belirtildiği gibi, çoğu geliştiricinin hiçbir zaman panosunun temizlenmesi konusunda endişelenmesi gerekmez. Geliştiriciler, tarayıcı tarafından yapılan varsayılan temizleme seçenekleriyle çalışabilir. Geliştiricilerin buna dikkat etmesi gereken nadir durumlarda unsanitized seçeneği mevcuttur.

Teşekkür

Bu makale Anupam Snigdha ve Rachel Andrew tarafından incelenmiştir. API, Microsoft Edge ekibi tarafından belirlendi ve uygulandı.