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> 태그를 제거하면 CSS를 인라인 처리하여 (예: <meta>, <head>, <style>) 다음 예를 살펴보고 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의 무결성이 올바른 앱의 작동에 영향을 줍니다. 이러한 상황에서는 다음 두 가지 옵션 중 하나를 선택할 수 있습니다.

  1. 복사와 붙여넣기를 모두 제어하는 경우(예: 같은 방법으로 앱에 붙여넣으려면 Async Clipboard API의 웹 맞춤 형식 여기를 읽지 않고 링크된 도움말을 확인하세요.
  2. 앱의 붙여넣기 끝만 제어하고 복사 끝은 제어하지 않는 경우 지원되지 않는 기본 앱에서 복사 작업이 발생하기 때문일 수 있습니다. 웹 맞춤 형식을 사용하려면 unsanitized 옵션을 사용해야 합니다. 이 도움말의 나머지 부분에 설명되어 있습니다.

정리에는 script 태그 삭제, 인라인 스타일 지정, HTML 형식이 올바른지 확인해야 합니다. 포괄적이지 않은 목록 등 향후 추가될 수 있습니다.

정리되지 않은 HTML을 복사하여 붙여넣기

Async Clipboard API를 사용하여 HTML을 클립보드에 write() (복사)할 때 브라우저는 DOM 파서를 통해 실행하여 형식이 올바른지 확인합니다. 결과 HTML 문자열을 직렬화하는 동시에 다음 단계를 따르세요. 별도의 조치가 필요하지 않습니다. HTML 페이지에 배치된 read()를 클립보드를 사용하고 웹 앱이 자체 코드에서 정리 작업을 수행해야 하는 경우 속성을 사용하여 옵션 객체를 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에서 검토했으며 레이첼 앤드류. API가 지정되었으며 Microsoft Edge팀에서 구현합니다.