4 ポイント 投稿者 GN⁺ 2024-09-02 | 1件のコメント | WhatsAppで共有
  • Webクリップボードは、一度コピーしたデータを複数のMIME表現として同時に保存でき、アプリごとに必要な形式だけを選んで貼り付けられるようにする
  • async Clipboard APIはtext/plaintext/htmlimage/pngを中心に制限されており、application/jsonのようなカスタム型はセキュリティ上の理由で書き込みが拒否される
  • 古いClipboard Events APIは任意の型文字列を扱えるが、実際のシステムクリップボードは信頼されたユーザーイベントの中でのみ変更される
  • Google DocsはexecCommand("copy")で信頼されたcopyイベントを生成し、Figmaはカスタムデータをbase64のHTML属性に入れてブラウザとネイティブアプリ間のコピーを処理する
  • Web Custom Formatsは"web "接頭辞でasync Clipboard APIのカスタム型を開放するが、Chromiumベースのブラウザと更新済みのネイティブアプリが必要になる

Webクリップボードの基本モデル

  • クリップボードは、1回のコピー操作に対して複数の**表現(representation)**をMIMEタイプとともに保存できる
    • text/plain: プレーンテキスト
    • text/html: HTML
    • image/png: PNG画像
  • Google Docsはtext/html表現を読み取り、リンク、文字サイズ、色などの書式を維持する
  • VS Codeのように元のテキストだけが必要なアプリは、text/plain表現だけを読めばよい

async Clipboard APIの型制限

  • navigator.clipboard.read()はクリップボード項目のtypesを確認した後、getType("text/html")のように特定の表現を読む
  • navigator.clipboard.write()は、MIMEタイプをキーにするClipboardItemに複数のBlobを入れて一度に書き込める
  • application/jsonClipboardItemとして書き込もうとすると、Type application/json not supported on write例外が発生する
  • W3C Clipboard仕様は、text/plaintext/htmlimage/png以外の型をwriteで拒否するよう定めている
  • application/jsonは2012年から2021年まで必須データ型一覧に含まれていたが、w3c/clipboard-apis#155で削除された
    • 変更前の必須データ型は、読み取り16個、書き込み8個だった
    • ブラウザがセキュリティ上の懸念から多くの必須型をサポートしなかったため、一覧は縮小された
  • 仕様は、信頼されていないスクリプトがローカルソフトウェアの脆弱性を引き起こすデータをクリップボードに入れられる可能性があるため、許可する型を制限すると警告している

Clipboard Events APIとisTrusted

  • ClipboardEventはcopy、cut、pasteイベントで発生し、clipboardDataプロパティでDataTransferオブジェクトを提供する
  • copyイベントではe.clipboardData.setData("text/plain", "...")setData("text/html", "...")でデータを書き込める
  • pasteイベントではe.clipboardData.getData("text/html")でHTML表現を読み取れる
  • clipboardData.setData("application/json", json)は例外を投げず、pasteハンドラでもapplication/json型とJSON文字列を確認できる
  • clipboardDataはasync Clipboard APIよりはるかに古いインターフェースである
    • async Clipboard APIは2017年に仕様へ追加された
    • clipboardDatasetDatagetDataは2006年のW3C草案にすでに登場している
    • その草案はInternet Explorerに実装されていた機能を大きく反映している
    • Internet Explorer 4は1997年にリリースされたため、clipboardDataインターフェースは執筆時点で少なくとも26年の歴史がある機能と見なせる
  • 2011年仕様からMIMEタイプが導入されたが、setData()の型引数にどんな文字列でも使える特性は現在も維持されている
  • Event.isTrustedは、イベントがユーザーエージェントによって発生した場合にのみtrueになる読み取り専用プロパティである
    • ユーザーがCommand+Cを押したcopyイベントはisTrustedtrue
    • dispatchEvent()で作られた合成イベントはisTrustedfalse
  • 合成copy/cutイベントはシステムクリップボードを変更できず、合成pasteイベントは実際のシステムクリップボードデータにアクセスできない
  • したがってClipboard Events APIは任意の型を扱えるが、実際のクリップボードとの相互作用はユーザーエージェントが発生させたcopy/pasteイベントハンドラ内に限定される

Copyボタンでカスタム型を書く

  • Google Docsの右クリックメニューのCopyボタンは、3つの表現をクリップボードに書き込む
    • text/plain
    • text/html
    • application/x-vnd.google-docs-document-slice-clip+wrapped
  • 3つ目の表現はJSONデータを含むカスタム型であり、async Clipboard APIだけでは書き込めない
  • Google DocsはCopyボタンをクリックするとdocument.execCommand("copy")を呼び出す
  • execCommand("copy")は、ユーザーがコピーコマンドを実行したかのように信頼されたcopyイベントをプログラム的に発生させられる
  • Safariでは、execCommand("copy")がcopyイベントを発生させるにはアクティブな選択範囲が必要である
    • 空でないinputをDOMに追加して選択し、その後execCommand("copy")を呼び出して削除する方法で選択範囲を作れる

Pasteボタンのブラウザ・OS差異

  • Google DocsのPasteボタンは、macOSでは拡張機能のインストールを要求したが、WindowsノートPCでは動作した
  • document.queryCommandSupported("paste")でpasteコマンドのサポート有無を確認できる
    • macOSではChromeとFirefoxがfalse、Safariがtrue
    • WindowsではChromeとEdgeがtrue、Firefoxがfalse
  • Safariはpaste動作を確認するプロンプトを表示し、Webサイトがクリップボードを読むことを明確に示す
  • ChromeがWindowsではexecCommand("paste")を許可し、macOSでは許可しない理由は確認されていない
  • Google DocsはexecCommand("paste")を使えない場合、async Clipboard APIへフォールバックしない
    • async Clipboard APIではapplication/x-vnd.google-[...]表現を読めない
    • HTML表現には内部IDが含まれている

FigmaのHTMLベースのコピー・貼り付け

  • FigmaのCopyボタンは、text/plaintext/htmlの2つの表現をクリップボードに書き込む
  • HTML表現には空のspanが2つあり、それぞれdata-metadatadata-buffer属性を持つ
  • data-metadataはbase64エンコードされたJSON文字列で、デコードするとfileKeypasteIDdataTypeのような値が出てくる
  • data-bufferは空のフレームだけをコピーしても約26,000文字あり、コピーされたコンテンツ量に応じて長さが線形に増えるように見える
  • data-bufferをbase64デコードするとfig-kiwiで始まるバイナリデータが現れ、これはKiwi message formatである
    • KiwiはFigma共同創業者で元CTOのEvan Wallaceが作った形式である
    • .figファイルのエンコーディングに使われている
  • Evan Wallaceが公開した.fig file parserを使うと、data-buffer.figファイルに変換して確認できる
  • Figmaは小さなFigmaファイルを作り、それをbase64でエンコードして、空のHTML spandata-buffer属性に入れ、クリップボードへ保存する

HTML表現を使う理由

  • Windows、macOS、Linuxは、クリップボードへデータを書き込むための異なるOS形式を提供している
    • WindowsはHTML用にCF_HTMLを提供する
    • macOSはNSPasteboard.PasteboardType.htmlを提供する
  • プレーンテキスト、HTML、PNG画像は複数のOSで標準形式にマッピングできる
  • application/foo-barのような任意の型は、OSクリップボードの共通形式とうまく適合しない
  • ブラウザはこのようなカスタム表現をOSの共通クリップボード形式には書き込まず、ブラウザごとのカスタムクリップボード形式に保存する
    • その結果、カスタム型はブラウザタブ間ではコピー・貼り付けできる
    • アプリケーション間ではコピー・貼り付けできない
  • text/htmlを使えばOS共通のクリップボード形式にマッピングされるため、他のアプリケーションが読み取りやすい
  • Figmaはtext/htmlを使って、ブラウザのfigma.comとネイティブのFigmaアプリの間で要素をコピー・貼り付けできる

ブラウザごとのカスタムデータ保存方式

  • macOSでcopyイベント内にtext/plaintext/htmlapplication/jsonfoo bar bazを書き込み、Pasteboard Viewerで確認すると、ブラウザごとに保存方式が異なる
  • ChromeはPasteboardに4項目を追加する
    • public.html: HTML表現
    • public.utf8-plain-text: プレーンテキスト表現
    • org.chromium.web-custom-data: カスタム表現
    • org.chromium.source-url: コピーが実行されたWebページのURL
  • Firefoxもpublic.htmlpublic.utf8-plain-textを作るが、カスタムデータはorg.mozilla.custom-clipdataに書き込む
    • Chromeと異なり、source URLは保存しない
  • Safariもpublic.htmlpublic.utf8-plain-textを作り、カスタムデータはcom.apple.WebKit.custom-pasteboard-dataに書き込む
    • この項目の中に、プレーンテキストとHTMLを含む全表現一覧とsource URLも保存する
  • Safariはsource URLのドメインが同じ場合にのみ、ブラウザタブ間でカスタム型のコピー・貼り付けを許可する
    • ChromeとFirefoxでは同じ制限は見られない

Raw Clipboard AccessとWeb Custom Formats

  • Raw Clipboard Accessは2019年に提案されたAPIで、WebアプリケーションにネイティブOSクリップボードの生の読み書きアクセスを提供しようとした
  • Raw Clipboard Accessは、Webアプリケーションが少数の形式に制限されているため、FigmaやPhotopeaが大半の画像形式と相互運用しにくいという問題を扱っていた
  • この提案は、ネイティブアプリケーションでのリモートコード実行のようなセキュリティ上の懸念により、それ以上進展しなかった
  • Web Custom Formatsは、async Clipboard APIでカスタム型を書き込むための提案であり、Chromiumは2022年にこれを実装した
  • Web Custom Formatsは、MIMEタイプの前に"web "を付けて使う
    • 例: web application/json
    • 読み取り時もasync Clipboard APIのread()getType("web application/json")を使う
  • Web Custom Formatsは、ネイティブOSクリップボードに2種類のデータを書き込む
    • データ型とクリップボード項目名のマッピング
    • 各データ型ごとのクリップボード項目
  • macOSでは、マッピングはorg.w3.web-custom-format.mapに保存される
    • 例: "application/json": "org.w3.web-custom-format.type-0"
  • org.w3.web-custom-format.type-[index]項目には、Blobの未加工データが入る
  • この方式により、Webアプリケーションが望むOSクリップボード形式へ未加工データを直接書き込めなくなり、Raw Clipboard Accessのセキュリティ問題を回避する
  • その代わり、レガシーなネイティブアプリケーションと更新なしで相互運用することは目標としていない
    • ネイティブアプリケーション側がWeb Custom Formats形式を読めるよう更新される必要がある
  • Web Custom Formatsは2022年からChromiumベースのブラウザで利用できるが、他のブラウザはまだ実装していない

unsanitizedオプション

  • async Clipboard APIでクリップボードを読むとき、ブラウザはデータをサニタイズすることがある
    • HTMLから危険な可能性のあるscriptタグを削除することがある
    • zip bomb攻撃を避けるためPNG画像を再エンコードすることがある
  • read()unsanitizedオプションを使うと、サニタイズされていないデータを要求できる
  • このオプションは現在Chromiumベースのブラウザでのみサポートされる
    • 2023年末に追加された
    • 他のブラウザも将来サポートする可能性はあるが、Safariは可能性が低そうだというフィードバックがある

現実的な選択肢

  • すべてのブラウザでうまく動作するカスタムクリップボード型の書き込み方法は、まだ存在しない
  • Figmaのようにbase64文字列をHTML表現の中へ入れる方法は粗削りだが、Clipboard APIのさまざまな制限を回避してカスタムデータをクリップボードで受け渡しできる
  • Web Custom Formatsは、カスタム型をより安全かつ実用的に扱う方向性を示しているが、主要ブラウザ全体での実装が必要である

1件のコメント

 
GN⁺ 2024-09-02
Hacker Newsの意見
  • 良い記事。Mac版FirefoxでGoogle Docsに書式あり/なしで貼り付けがうまく動かなかった理由が理解できた
    Googleがクリップボードに独自のコンテンツ形式を入れようとして廃止されたAPIを使っている点が興味深い

    • いずれGoogleウェブを公式なウェブのフォークとして宣言しなければならないのかもしれない
  • カスタムアプリのデータをクリップボードに送る最も信頼できる方法は、FigmaのようにHTML内にデータを埋め込む方式のようだ
    受け取るアプリが対応していないときに、HTMLメッセージとしてフォールバック動作まで定義できる利点もある

  • WordPressもこの問題に触れている。エディターモードで複数の段落を切り貼りすると大きく壊れることがある
    おそらく段落が実際には別々のDIV領域で、それぞれ異なる制御ロジックや効果の下にあるからだろう
    さらに、マシンが自分ではASCIIだと思っていたものをUTF-8やISO Latin-1にしてしまい、Clippyのように「引用符をもっと見栄えよくしておいたから、あとで感謝してね」みたいな動作をすることがあるが、そんなことは望んでいない。this'が欲しかったなら、'this'と打っていないはずだ

  • 昔学生だったころ、JavaScriptがユーザーの同意なしにクリップボードを読めて、getpastedというウェブサイトを作った
    クリップボードを自動で貼り付けて公開データベースに載せるサイトで、当然それを嫌う人もいた
    それでも、いつでもクリップボードを読めるという事実を知らせるプロジェクトとしては悪くなかった

    • Windows 11ではデフォルトでClipboard History / Cloud Clipboardを通じて、すべてのクリップボードデータがMicrosoftのサーバーに送られると知ったら、人々は驚くだろう
  • Pasteboard Manager[0]の作者であるSindre Sorhusは、Actions[1]ショートカットライブラリや複数のiPhone・Macアプリも作っている人物。HNにいるかどうかは分からない
    0. https://apps.apple.com/us/app/pasteboard-viewer/id1499215709

    1. https://apps.apple.com/us/app/actions/id1586435171
  • 範囲と深さの組み合わせが特に良い。システム・ブラウザーごとの差異まで扱いながら、落とし穴と回避方法も一緒に説明してくれる

    • 記事では過度な細部や脇道を減らそうと努力してきた。長めの記事ではあるが、今回はバランスをうまく取れた気がしてうれしい
  • ブラウザーが「非公開」のデータを取得する話に関連して、自分の銀行のウェブアプリがログイン時に自分のホスト名であるaluminiumを表示し、2段階認証を要求してきた
    どうやって知ったのか分からない。モバイルでアクセスすると、2段階認証コード入りのSMSも読んで自動で貼り付けられた。Pixel+ChromeまたはLinux+Chrome環境だった

    • 2段階認証コードはおそらくChromeの機能である可能性が高い。SafariもSMS認証コードを自動入力候補として表示するし、Android版Chromeならそのまま自動入力できそうだ
      ホスト名のほうはもっと難しい。本当にその情報をユーザー名のような形で銀行に渡していないのか確認したほうがいい。どこの銀行なのかも気になる
  • この記事は、なぜウェブアプリがネイティブアプリと同じくらい良くなれないのかをよく示している
    ウェブアプリは常に「信頼できない」コードなので、ローカルマシンのリソースへのアクセスが恣意的かつ人為的に制限される

    • デフォルトで信頼しないのは機能だ。常に警戒している高度に訓練されたセキュリティ専門家でもない限り、ユーザーが自力で自分を守るのは難しい
      今はスクリプトキディが暴れていた初期インターネット時代でもなければ、変わり者だけが使う空間でもない。政府と結びついた組織が核開発資金を得るために重要インフラへランサムウェア攻撃を仕掛ける時代だ。ローカルマシンの任意のリソースへのアクセスは、まさにそうしたことを可能にする
      最新ブラウザー上のウェブアプリは当然より強いサンドボックス内にあるが、最新のオペレーティングシステムではネイティブアプリも信頼されない対象として扱われる。新しいアプリを実行すると、隔離されたアプリデータディレクトリの外にアクセスする前に確認ダイアログが表示される
  • ところで、MacBookで警告が出ていたChrome拡張機能が何だったのか気になる