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ın genellikle (ve haklı nedenlerle) uyguladığı ara temizleme adımı olmadan. Bu kılavuzda bu özelliği nasıl kullanacağınızı öğrenebilirsiniz.

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 durum metinler için kesinlikle geçerlidir. Aşağıdaki kodu DevTools Konsolu'na yapıştırmayı ve ardından sayfayı hemen yeniden odaklamayı deneyin. (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. Tarayıcılar, sıkıştırma bombası saldırılarını önlemek için PNG gibi resimleri yeniden kodlar ancak giriş ve çıkış resimleri, piksel piksel 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 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 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. Uygulamanızda kopyalama işlemini değil, yalnızca yapıştırma işlemini kontrol ediyorsanız (ör. kopyalama işlemi, web'de özel biçimleri desteklemeyen yerel bir uygulamada gerçekleştiği için) bu makalenin geri kalanında açıklanan unsanitized seçeneğini kullanmanız gerekir.

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.

Sanitasyon uygulanmamış HTML'nin kopyalanması ve yapıştırılması

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 gerçekleştirmesi 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. Bunu DevTools Console'da denediğinizde giriş ve çıkışın aynı olduğunu görürsünüz.

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 anlamına gelir) veya satır içi olarak eklenip eklenmediği (desteklenmediği anlamına gelir) tespit edilir. Bunun işe yaraması için sayfanın zaten panosuna erişim 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ğ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 dikkat etmesi gereken nadir durumlar için unsanitized seçeneği mevcuttur.

Teşekkür ederiz

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