RFC-0179:基本剪贴板服务 | |
---|---|
状态 | 已接受 |
领域 |
|
说明 | 这项提案旨在推出一项基本剪贴板服务,让用户能够在组件之间安全地复制和粘贴文本内容,无论运行程序如何。 |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2022-05-16 |
审核日期(年-月-日) | 2020-07-18 |
摘要
此 RFC 引入了两种新的框架提供的协议,
fuchsia.ui.clipboard.Writer
和 fuchsia.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 环境中,对剪贴板的访问范围可以是 分为三个级别:
- Shell 中介,依赖于焦点
只有在响应明确的 由图形 shell 确定,且仅当 具有输入焦点。 - 依赖于焦点
当组件具有输入焦点时,它可以随时访问剪贴板。 - 无限制
组件可以随时访问剪贴板。
在此 RFC 中,我们仅涵盖 (2) 依赖于焦点的范围。
本次培训中未规划镜 (1) 和 (3) 的设计和实现 时间;那么您将需要提供另一个 RFC。
用例
对于初始 RFC,我们考虑了几个简单但常见的用例:
- 在网络浏览器中,将网址从网页正文复制到地址 条形
- 将 shell 命令从网络浏览器复制到终端
- 将信息从网络浏览器复制到工作站产品的 反馈对话框(在 Flutter 中实现)
协议和服务
我们引入了两种新的可发现 FIDL 协议,
fuchsia.ui.clipboard.FocusedReaderRegistry
和
fuchsia.ui.clipboard.FocusedWriterRegistry
(位于合作伙伴 SDK 中)。这些
将由新组件 clipboard.cm
实现和公开,
在会话领域中运行的应用该组件将包含在工作站中
产品,可在任何其他需要此 ID 的 Fuchsia 产品中使用。
被授予 FocusedWriterRegistry
和
FocusedReaderRegistry
功能将能够请求
fuchsia.ui.clipboard.Writer
和 fuchsia.ui.clipboard.Reader
。
他们可以随时请求这些连接(假定它们具有有效的
ViewRef
),但如果Writer
Reader
客户端的视图没有输入焦点。
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;
};
实现
这一过程分为几个阶段:
- 提交新的
fuchsia.ui.clipboard
FIDL 库(如上文预览所示), API 审核。 - 实现在会话领域中运行的新剪贴板服务器组件,该组件
公开
fuchsia.ui.clipboard.FocusedWriterRegistry
和fuchsia.ui.clipboard.FocusedReaderRegistry
协议。 - 通过一个简单的组件演示与新协议的集成, 管理景观视图
- 将对新协议的支持集成到 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 提供了多个称为“选择”的内容存储区域其中
常见的是 CLIPBOARD
和 PRIMARY
(隐式文本选择剪贴板)。
发送应用程序向 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
实例:strings
、images
、urls
和 colors
每个查询的单个版本,以便只访问
每种类型。
在接收端,您可以通过索引或 类型。
从 iOS 14 开始,检索由其他程序放置在其中的粘贴板内容
应用触发系统通知。减少虚假通知
在实际粘贴之前,iOS 为客户提供了查询功能,
粘贴板上会显示一些数据类型(hasStrings
、hasImages
),
访问数据的过程。
有关详情,请参阅 https://developer.apple.com/documentation/uikit/uipasteboard.
网络 API
虽然网页上的剪贴板互动主要由网页处理 还有 JavaScript API 可让网页与剪贴板互动 必须依赖于直接的用户命令
较旧的 ClipboardEvent
API 允许脚本监听 "cut"
、"copy"
、
或 "paste"
DOM 上的事件Element
,然后访问事件的
clipboardData
字段,允许通过 MIME 调用 setData
或 getData
类型。还可以通过编程方式调用 "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,但须遵守 权限。