Async Clipboard API でのサニタイズされていない HTML

Chrome 120 以降、非同期クリップボードで新しい unsanitized オプションが利用可能に APIこのオプションは、HTML で次のような特殊な状況で役立つことがあります。 コピーしたときと同じクリップボードの内容を貼り付けます。 つまり、一般的なブラウザのような中間的なサニタイズを行わなくても、 正当な理由がありますので、お申し込みください。このガイドではその使用方法について説明します。

使用する Async Clipboard API、 ほとんどの場合、デベロッパーはシステムやインフラストラクチャの クリップボードの内容を推測し、クリップボードに書き込んだ クリップボード(コピー)は、データを読み取るときに得られるのと同じ 貼り付けます。

これは間違いなくテキストの場合に当てはまります。DevTools に次のコードを貼り付けてみてください すぐにページを再度フォーカスします。(setTimeout() は必須です) 非同期 API の要件である、ページに集中する十分な時間を確保できます。 Clipboard 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);

では HTML テキストはどうなるでしょうか。ご想像のとおり、HTML では 場合によって異なります。ここでは、不正な動作を防ぐために、ブラウザが HTML コードをサニタイズします。 たとえば、HTML から <script> タグを削除するなどして、 (および <meta><head><style> など)を使用し、CSS をインライン化します。 次の例について考え、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);

HTML のサニタイズは、一般的には良いことです。自分をさらけ出したくない ほとんどのケースで HTML をサニタイズしないまま使用することで、セキュリティの問題に対処できます。そこで、 開発者が作業内容を正確に把握し 入力と出力の HTML の整合性が重要であり、 アプリの動作を保証します。この場合は、次の 2 つの選択肢があります。

  1. たとえば、コピーと貼り付けの両方を管理している場合、たとえば、 貼り付ける場合は、 Async Clipboard API 用のウェブカスタム形式 これを読むのをやめて、リンク先の記事を確認してください。
  2. アプリで貼り付け側のみを制御し、コピー側は制御しない場合、 おそらく、ネイティブ アプリでコピー操作が ウェブ カスタム フォーマットでは、unsanitized オプションを使用する必要があります。 これについては後ほど説明します

サニタイズには、script タグの削除、インライン スタイル、 HTML の形式が正しいことを確認します。このリストはすべてを網羅したものではありません。 今後追加される可能性があります。

サニタイズされていない HTML をコピーして貼り付ける

Async Clipboard API を使用して HTML をクリップボードに write()(コピー)すると、 ブラウザは DOM パーサーを実行することで、形式が正しいことを確かめる 結果の HTML 文字列はシリアル化されますが、ここではサニタイズは行われません。 示します。お客様側でのご対応は必要ございません。read() の場合、HTML を 別のアプリケーションによってクリップボードにコピーされたことがあり、ウェブ アプリが 独自のコードでサニタイズを行う必要がある場合、 プロパティを指定して 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 オプションが存在します。

謝辞

この記事は Anupam Snigdha によってレビューされ、 Rachel Andrew。API が指定され、 Microsoft Edge チームが実装しています。