RFC-0179:基本的剪贴板服务

RFC-0179:基本剪贴板服务
状态已接受
领域
  • HCI
说明

这项提案旨在推出一项基本剪贴板服务,让用户能够在组件之间安全地复制和粘贴文本内容,无论运行程序如何。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2022-05-16
审核日期(年-月-日)2020-07-18

摘要

此 RFC 引入了两种新的框架提供的协议, fuchsia.ui.clipboard.Writerfuchsia.ui.clipboard.Reader,以及服务 来实现这些对象,让用户能够执行复制和粘贴操作 可对文本内容执行各种操作。

设计初衷

许多面向用户的现代操作系统都带有图形外壳, 剪贴板功能(请参阅先前图片),允许 用户以交互方式将数据复制到系统提供的内存缓冲区或其他 之后再将这些数据粘贴到其他位置

过去,Fuchsia 有基本的剪贴板协议 作为模块化代理实现的,但此代码已于 2019 年移除。

在此 RFC 中,我们提议引入新的剪贴板协议和实现 与哪些 Fuchsia 产品集成。最迫切的 需要的就是复制和粘贴 Unicode 文本的功能, 进行迭代。

许多现有操作系统的剪贴板工具 最初设计时没有安全规定, 允许任何进程随时观察和/或修改剪贴板 用户认知或意图在 Fuchsia 上,我们的目标是在设计时充分考虑安全性,具体做法如下:

  • 在精细功能背后保护剪贴板访问,并遵循 最小权限原则
  • 尝试限制剪贴板访问(视输入焦点而定) 前景窗口(在 Fuchsia 风景术语中,“视图”)
  • 仅在万不得已时才提供后台剪贴板访问

利益相关方

教员

davemoore@google.com

审核人

Fuchsia HCI:neelsa@google.com、quiche@google.com

安全:palmer@google.com

隐私权:enoharemaien@google.com

Chromium:wez@google.com

Flutter:jmccandless@google.com

已咨询

azaslavsky、carolineliu@google.com、chaopeng@google.com、cpu@google.com、 ddorwin@google.com、fmil@google.com、jsankey@google.com、tjdetwiler@google.com

社交

  • 在 Fuchsia Input Team 文档审核中讨论
  • 在 Fuchsia 安全咨询交流时间讨论

设计

访问权限级别

在图形 shell 环境中,对剪贴板的访问范围可以是 分为三个级别:

  1. Shell 中介,依赖于焦点
    只有在响应明确的 由图形 shell 确定,且仅当 具有输入焦点。
  2. 依赖于焦点
    当组件具有输入焦点时,它可以随时访问剪贴板。
  3. 无限制
    组件可以随时访问剪贴板。

在此 RFC 中,我们仅涵盖 (2) 依赖于焦点的范围。

本次培训中未规划镜 (1) 和 (3) 的设计和实现 时间;那么您将需要提供另一个 RFC。

用例

对于初始 RFC,我们考虑了几个简单但常见的用例:

  • 在网络浏览器中,将网址从网页正文复制到地址 条形
  • 将 shell 命令从网络浏览器复制到终端
  • 将信息从网络浏览器复制到工作站产品的 反馈对话框(在 Flutter 中实现)

协议和服务

我们引入了两种新的可发现 FIDL 协议, fuchsia.ui.clipboard.FocusedReaderRegistryfuchsia.ui.clipboard.FocusedWriterRegistry(位于合作伙伴 SDK 中)。这些 将由新组件 clipboard.cm 实现和公开, 在会话领域中运行的应用该组件将包含在工作站中 产品,可在任何其他需要此 ID 的 Fuchsia 产品中使用。

被授予 FocusedWriterRegistryFocusedReaderRegistry功能将能够请求 fuchsia.ui.clipboard.Writerfuchsia.ui.clipboard.Reader。 他们可以随时请求这些连接(假定它们具有有效的 ViewRef),但如果WriterReader 客户端的视图没有输入焦点。

library fuchsia.ui.clipboard;

/// A protocol that allows graphical clients that own
/// [`ViewRef`s](https://cs.opensource.google/fuchsia/fuchsia/+/main:/src/development/graphics/scenic/concepts/view_ref) to request read ("paste")
/// access to the clipboard. Clients can register for access at any time, but `GetItem` calls will
/// only succeed while the view has input focus.
@discoverable
protocol FocusedReaderRegistry {
    /// If the `ViewRef` is valid, the clipboard server will allow the client to send commands using
    /// the given `Reader`. If the `ViewRef` later becomes invalid, the `Reader`'s channel will be
    /// closed.
    RequestReader(resource table {
        1: view_ref fuchsia.ui.views.ViewRef;
        2: reader_request server_end:Reader;
    }) -> (table {}) error ClipboardError;
};

/// A protocol that allows graphical clients that own `ViewRef`s to request write ("copy") access to
/// the clipboard. Clients can register for access at any time, but `SetItem` calls will only
/// succeed while the view has input focus.
@discoverable
protocol FocusedWriterRegistry {
    /// If the `ViewRef` is valid, the clipboard server will allow the client to send commands using
    /// the given `Writer`. If the `ViewRef` later becomes invalid, the `Writer`'s channel will be
    /// closed.
    RequestWriter(resource table {
        1: view_ref fuchsia.ui.views.ViewRef;
        2: writer_request server_end:Writer;
    }) -> (table {}) error ClipboardError;
};

/// Allows data to be read from the clipboard, i.e. pasted.
protocol Reader {
    /// Reads a single item from the clipboard. If the client's `View` does not have input focus, an
    /// error will be returned. If there is no item on the clipboard, `ClipboardError.EMPTY` will
    /// be returned.
    GetItem(table {}) -> (ClipboardItem) error ClipboardError;
};

/// Allows data to be written to the clipboard, i.e. copied.
protocol Writer {
    /// Writes a single item to the clipboard. If the client's `View` does not have input focus, an
    /// error will be returned.
    SetItem(ClipboardItem) -> (table {}) error ClipboardError;

    /// Clears the contents of the clipboard. If the client's `View` does not have input focus, an
    /// error will be returned.
    Clear(table {}) -> (table {}) error ClipboardError;
};

/// Set of errors that can be returned by the clipboard server.
type ClipboardError = flexible enum {
    /// An internal error occurred. All the client can do is try again later.
    INTERNAL = 1;

    /// The clipboard was empty, or the requested item(s) were not present on the clipboard.
    EMPTY = 2;

    /// The client sent an invalid request, e.g. missing requiring fields.
    INVALID_REQUEST = 3;

    /// The client sent the server an invalid `ViewRef` or a `ViewRef` that is already associated
    /// with another client.
    INVALID_VIEW_REF = 4;

    /// The client attempted to perform an operation that requires input focus, at a moment when
    /// it did not have input focus. The client should wait until it has focus again before
    /// retrying.
    UNAUTHORIZED = 5;
};

在初始版本中,剪贴板仅支持复制和粘贴操作 UTF-8 字符串,大小不得超过 32 KB。客户端可以指定 MIME 类型 数据;默认为 "text/plain;charset=UTF-8"

后续修订版本将添加对 VMO 的支持,从而实现 您可以复制和粘贴任意数据。

/// The maximum length of a plain-text clipboard item in bytes. Although FIDL messages support
/// larger messages, this limit allows space to be reserved for potential other fields in the
/// message. Larger payloads will be supported by VMOs in `ClipboardItemData` in future revisions.
const MAX_TEXT_LENGTH uint32 = 32768;

/// The maximum length of a MIME Type identifier. Per
/// [IETF RFC 4288](https://datatracker.ietf.org/doc/html/rfc4288#section-4.2), a MIME type may have
/// up to 127 characters before and 127 characters after the slash, for a total of 255.
const MAX_MIME_TYPE_LENGTH uint32 = 255;

/// A single item on the clipboard, consisting of a MIME type hint and a payload.
type ClipboardItem = resource table {
    /// MIME type of the data, according to the client that placed the data on the clipboard.
    /// *Note:* The clipboard service does not validate clipboard items and does not guarantee that
    /// they conform to the given MIME type's specifications.
    1: mime_type_hint string:MAX_MIME_TYPE_LENGTH;
    /// The payload of the clipboard item.
    2: payload ClipboardItemData;
};

/// The payload of a `ClipboardItem`. Future expansions will support additional transport formats.
type ClipboardItemData = flexible resource union {
    /// A UTF-8 string.
    1: text string:MAX_TEXT_LENGTH;
};

实现

这一过程分为几个阶段:

  1. 提交新的 fuchsia.ui.clipboard FIDL 库(如上文预览所示), API 审核。
  2. 实现在会话领域中运行的新剪贴板服务器组件,该组件 公开 fuchsia.ui.clipboard.FocusedWriterRegistryfuchsia.ui.clipboard.FocusedReaderRegistry 协议。
  3. 通过一个简单的组件演示与新协议的集成, 管理景观视图
  4. 将对新协议的支持集成到 Chromium 和 Flutter 中 跑步者。

性能

添加新服务也会使用额外的存储空间来存储二进制文件, 用于存储二进制文件和剪贴板内容的内存区域每个使用 剪贴板访问将通过保留开放的 Zircon 通道来消耗资源。

安全注意事项

需要进行安全审核

跨组件通信

剪贴板服务的引入构成了一种新的跨组件 通信渠道。这为 有意或无意地利用彼此的漏洞。

不受信任的内容

剪贴板服务无法保证 ClipboardItem 数据或 MIME 类型提示。因此,客户端不应信任 设备收到的数据,并且应验证数据是否适合其使用 这种情况。

具体而言,客户端应执行任何解析、解译或转换 存储在低权限“沙盒”环境中这个过程中 比 C/C++更安全(请参阅 2 法则 了解详情。)

对于 ClipboardItemData.text 变体,系统会自动进行 UTF-8 验证 由每个客户端中使用的 FIDL 库执行(以及剪贴板中) 服务)。

但是,即使使用纯文本、有效的 UTF-8 文本,一个组件 通过剪贴板向另一个人发送任意文本 包括:

  • 文本 widget 中的溢出 bug
  • 文本渲染堆栈中的 bug
  • 同形异构攻击 (使用外观上相似的字形来欺骗用户,实际上 字符,例如暗中将用户转到钓鱼式攻击网域)
  • 特定于应用的文本解析中的 bug
  • 意外的代码粘贴到命令提示符中

其中大多数问题已经是处理或 显示任何第三方内容或用户提供的内容,但剪贴板会呈现 更多挑战。恶意应用程序可以对有效用户做出响应 复制命令,将意外数据(未显而易见选择的数据) 剪贴板,诱骗用户充当 混淆代理 粘贴。

未经授权的访问

上文所述,一个组件可能需要 在未经授权或用户不知情的情况下将内容写入剪贴板。

可通过以下方式缓解这种情况:

  • 可用于授予或拒绝复制和粘贴权限的协议 精细功能
  • fuchsia.ui.clipboard.Focused* 协议中, 只能向具有输入焦点的前景视图授予剪贴板访问权限

将来扩展剪贴板 API 时,我们可能会提供 用于观察剪贴板 API 事件的协议,系统 shell 可以使用此类事件来 在每次访问剪贴板时显示可视通知。

未来,随着不受信任的组件的增加和新的剪贴板使用 我们将不得不重新考虑是否应该 静默返回空的剪贴板内容,而不是 ClipboardError.UNAUTHORIZED, 来减少披露的有关剪贴板访问权限的信息。

ViewRef 和焦点验证

剪贴板服务依赖于景观的 焦点链系统 来确定当前获得焦点的视图,并因此有权 访问剪贴板。因此,剪贴板确定剪贴板 焦点的可靠性和景观对输入焦点的确定同等重要, 存在一些缺陷:

  • ViewRef, 可轻松克隆并从一个组件发送到 另一个。通过这种机制,恶意组件可以与 相互假冒,以获取输入焦点。(尽管如此, 要求 ViewRef 的原所有者至少信任 克隆的 ViewRef 的接收者。)
  • 焦点变更会受制于 竞态条件

安全审核结果

  • 此 MVP API 有一定的限制,这限制了攻击途径。
  • 我们依赖底层 FIDL 反序列化来正确验证 UTF-8 字符串。这是攻击 但我们认为这是安全的 反序列化依赖于 Rust 的 std::str::String 实现, 在 Fuchsia 和更广泛的 Rust 中,均具有内存安全且经过全面测试 生态系统。
  • 我们认为,ViewRef 解决方案是跟踪观看情况的良好基础 用户意图以及 上述注意事项

隐私注意事项

需要进行隐私权审核。

未经授权粘贴

在未经授权的情况下访问剪贴板中的内容会带来隐私风险, 这会允许恶意组件获取用户拥有的任何私有数据 。如上文所述, fuchsia.ui.clipboard.Focused* 协议可请求查看,以降低此风险 至少要有输入焦点(因此在前台可见),以便 访问剪贴板。不过,这也意味着 一旦获得焦点,就会立即剪切剪贴板中的内容,即使 这不是用户的意图。将来,系统会 当组件读取 剪贴板。

边信道攻击

在对剪贴板服务的未来迭代中,添加 数据类型和长度,就有可能存在通过内存分析发现 可提示其内容的信息(例如剪贴板缓冲区大小)。

保留剪贴板内容

现阶段,仅支持复制短字符串、剪贴板内容 会存储在内存中,而不是磁盘上。

剪贴板内容不会通过 检查

跨安全上下文的访问权限

Fuchsia 产品可能有不同的界面元素,分别在不同的安全环境中运行, 例如或预身份验证情境。紫红色 必须防止跨安全上下文共享剪贴板内容 边界。例如,如果已登录用户复制其密码 锁定屏幕,则密码必须已无法粘贴 屏幕对话框。

可以通过运行剪贴板的单独实例来实现这种分离 安全上下文。

测试

将通过单元测试和集成测试对此功能进行测试:

  • 剪贴板服务中的单元测试
  • 对整个剪贴板服务进行的集成测试
  • 用于对剪贴板服务、Scape、 输入流水线,以及 Flutter 或 Chromium 运行程序。

文档

fuchsia.ui.clipboard API 将随 fidldoc 而记录。

我们将通过一个注释很好的简单组件说明协议的用法 (请参阅实现)。

缺点、替代方案和未知问题

要在 面向用户的操作系统运行程序无法提供此功能 因为他们无法将数据 不同运行时。

在上述设计方案中,另一种方法是 先采用限制性更强的“Shell 中介、依赖于焦点”剪贴板 协议或完全不受限制的协议(请参阅 访问权限级别)。

shell 中介方法虽然更加安全,但可能过于严格,以至于 在许多应用场景中都非常实用例如,

  • 阻止应用在上下文菜单中提供复制和粘贴命令
  • 干扰基于 Chromium 的网络剪贴板 API 的功能 跑步者

不受限制的方法,虽然对一些小众应用很有用, 隐私风险太大,无法作为默认选项。

我们先根据输入侧重点设置中等访问权限级别, 能够:

  • 优先考虑剪贴板服务中的某些安全和隐私保障 从头开始
  • 鼓励运行程序的最小权限原则,集成了 Fuchsia 剪贴板
  • 避免施加过于繁重的限制来实际集成 现有跑步者

未来工作

  • 提供用于观察剪贴板 API 事件(读取和写入)的协议 系统 Shell 可以用来在 访问了剪贴板。

  • 扩充支持的数据格式和载荷大小, 通过使用发送客户端拥有的 VMO 发送。

先验技术和参考资料

紫红色

Fuchsia 之前 最小的剪贴板 API, 作为模块化框架代理实现的,该代理允许任何带有 fuchsia.modular.Clipboard 功能,用于存储或检索 UTF-8 字符串。 (此功能已于 2019 年 11 月被完全清除。)

Linux:X11

X11 提供了多个称为“选择”的内容存储区域其中 常见的是 CLIPBOARDPRIMARY(隐式文本选择剪贴板)。

发送应用程序向 X 服务器通知它自己“拥有”其中一项 在特定窗口中以给定数据格式选择的选项 (XSetSelectionOwner())。然后等待进一步的事件。

接收应用程序在其一个窗口中请求 转换为其支持的特定格式 (XConvertSelection())。

X 服务器将请求转发给发送应用程序,如果它 支持请求的格式,通过 X 服务器将数据发送到 接收方应用如果内容很大,必须将其分成多个部分 大小不超过 256 KB 的片段

如果源窗口被销毁,则选中项会丢失,因此在实践中 (1) 大多数应用会将其选择保存在用户 无法关闭且 (2) 常见的 Linux 发行版包含一个剪贴板管理器, 会取得选定项的所有权,以保持该选定项的有效性,即使原始所有者也是如此 应用退出。

有关详情,请参阅 https://www.uninformativ.de/blog/postings/2017-04-02/0/POSTING-en.html.

Linux:Wayland

发送应用(必须处于聚焦状态)会通知合成器, 具有 wl_data_source,指示数据源支持的 MIME 类型; 并注册事件监听器。然后等待 send 事件。

接收方应用,而该应用在尝试 粘贴,监听数据 offer 事件,以确定是否已将剪贴板 填充。当想要粘贴时,它会调用 wl_data_offer_receive,并传入 请求的 MIME 类型和文件描述符(通常是管道的写入端)。

发送应用收到 send 事件并写入指定文件 描述符;接收应用读取另一端的数据。

有关详情,请参阅 https://emersion.fr/blog/2020/wayland-clipboard-drag-and-drop/.

Windows (win32)

可通过调用 OpenClipboard() 并传入 当前窗口的句柄发送应用会通过以下方式清除任何现有数据: 先调用 EmptyClipboard(),然后调用 SetClipboardData(),并传入 整数数据类型 ID 和数据本身。要发送的数据的内存 需要使用 GlobalAlloc() 进行分配。

有几种标准的剪贴板数据类型:或者,也可以 为自定义全局格式调用 RegisterClipboardFormat()(显然 永久保留到重新启动),或使用特定范围内的 ID 来指示 私有剪贴板格式。对于非私有格式,操作系统会获得 该对象会被传入并负责其最终销毁; 对于私有格式,在 剪贴板已销毁。对于延迟格式转化,来源窗口 可以将 NULL 数据值传递给 SetClipboardData(),并在之后响应 WM_RENDERFORMAT,呈现请求的格式,并将占位符替换为 对 SetClipboardData() 的另一个调用。建议开发者设置剪贴板 数据格式。

接收应用还检索全局剪贴板的句柄 检查可用格式(包括明确显示 以及为自动转化提供的 由操作系统提供),调用 GetClipboardData() 以获取剪贴板对象的句柄 然后按 GlobalLock() 以锁定该全局资源 并获取访问其内容的权限。

还为窗口提供注册方法,以监控对 剪贴板中的内容

有关详情,请参阅 https://docs.microsoft.com/zh-cn/windows/win32/dataxchg/using-the-clipboard 和 https://docs.microsoft.com/en-us/windows/win32/dataxchg/clipboard-operations.

Android

发送应用会创建一个 ClipData 对象,其中包含支持的 MIME 类型,并使用一项或多项内容(可以是ClipData 字符串、指向任何数据的内容 URI 或 快捷方式)。然后,发送应用程序获取对全局 ClipboardManager 对象,并将 ClipData 对象传入 setPrimaryClip()

如果复制内容 URI,发送方应用必须导出 ContentProvider

接收应用获取对全局 ClipboardManager 的引用, 检查其是否有主要剪辑,然后检查其是否支持 任何 ClipData.Item。如果粘贴的是普通字符串, 只需调用 getText() 即可。如果是从内容 URI 粘贴, 接收方应用必须创建一个 ContentResolver 实例,query() 然后,从返回的 Cursor 中检索数据。

从 Android 12 开始,当一个应用访问消息框时,操作系统会显示消息框消息 ClipData

有关详情,请参阅 https://developer.android.com/guide/topics/text/copy-paste.

MacOS

系统级“粘贴板”通过 NSPasteboard.general 访问 字段。

发送应用通过传入 writeObjects() 方法复制商品 实现 NSPasteboardWriting 协议的对象数组。 实现人员还包括字符串和其他常见数据类型 NSPasteboardItem,为自定义数据类型提供封装容器。 NSPasteboardWriting 提供了受支持的统一类型标识符列表 (与 MIME 类型等效的 UTI),以及数据是否可用 或“承诺”。NSPasteboardItem 可以直接换行, 数据或数据提供商。

在接收端,应用可以查询常规 NSPasteboard,以获取 包括可由系统自动转换的类型, 过滤服务。然后,它可以选择朗读全部或部分所选内容 并粘贴到粘贴板上

有关详情,请参阅 ​​https://developer.apple.com/documentation/appkit/nspasteboard.

iOS

iOS Clipboard API 类似于 MacOS 的剪贴板 API。系统级粘贴板 通过 UIPasteboard.general 访问。

添加一个或多个项的方法有很多种, 贴上了 UTI 类型标签还可以将 NSItemProviders,用于延迟提供值。为方便起见, 标准数据类型在 UIPasteboard 实例:stringsimagesurlscolors 每个查询的单个版本,以便只访问 每种类型。

在接收端,您可以通过索引或 类型。

从 iOS 14 开始,检索由其他程序放置在其中的粘贴板内容 应用触发系统通知。减少虚假通知 在实际粘贴之前,iOS 为客户提供了查询功能, 粘贴板上会显示一些数据类型(hasStringshasImages), 访问数据的过程。

有关详情,请参阅 https://developer.apple.com/documentation/uikit/uipasteboard.

网络 API

虽然网页上的剪贴板互动主要由网页处理 还有 JavaScript API 可让网页与剪贴板互动 必须依赖于直接的用户命令

较旧的 ClipboardEvent API 允许脚本监听 "cut""copy"、 或 "paste" DOM 上的事件Element,然后访问事件的 clipboardData 字段,允许通过 MIME 调用 setDatagetData 类型。还可以通过编程方式调用 "cut""copy""paste"。出于隐私保护考虑 在"cut""copy"中,无法再程序化粘贴 事件,则无法读取剪贴板内容。

现已推出新的异步 Clipboard API,由每个网站的用户进行保护 权限。如果用户授予权限,脚本可以访问 navigator.clipboard,然后按 writeText()readText()write() ClipboardItem,包含一个或多个由 MIME 类型键控的 blob。(非图片 MIME 类型在某些浏览器中仍处于实验阶段。)

有关详情,请参阅 https://whatwebcando.today/clipboard.html 和 https://developer.mozilla.org/en-US/docs/Web/API/Clipboard.

ChromeOS

Chrome 扩展程序可以使用上述 Clipboard API,但须遵守 权限。