HarmonyOS 7+Share Kit:精准碰一碰接收素材文件签名校验与隔离队列【鸿蒙心迹】
一张图片在分享来源里叫 agenda.jpg,类型声明写着 image/jpeg,文件头却是 PNG 的标志字节。接收页面如果只看扩展名和缩略图,可能把它直接存入会议素材库;如果只靠 MIME 字段,又会把来源提供的字符串当成内容事实。对精准碰一碰而言,传输动作可以很快,接收端对文件内容的信任却不应该因此省略。本文做一个不连接真实设备的接收侧演示:TapIntake,页面 InboundCheckPage,批次 TAP-428,示例时间 18:24。四个传入记录中,photo-011.jpg、photo-012.jpg、photo-013.png 通过本地的头部一致性规则,agenda.jpg 因声明 JPEG 但头部匹配 PNG 被隔离,结果显示“通过 3 / 隔离 1”。这里的“通过”只表示本地预检合格,并不意味着内容安全、病毒扫描通过或已自动落盘。
## 一、接收成功并不代表可以立即导入碰一碰让分享动作与接收场景更紧密,特别适合在跨设备会议里快速投递图片。但系统完成分享,与业务把文件纳入工作区,是两个不同事件。前者关心内容能否被传过来,后者还要回答:这个内容属于什么类型?符合业务允许范围吗?文件头是不是与声明一致?出现异常后应放在哪儿?这里不做“万能文件安全检测器”。示例只允许 JPEG 和 PNG 两类图片,使用极少的魔数来检验明显的类型错配。魔数检查不等价于完整文件解析,更不能替代恶意内容扫描;成功匹配一个头部也无法证明后续数据没有损坏。尤其是带混合内容的文件,需要后续交给平台解码器或安全服务做完整验证。另外,精准碰一碰的系统入口、接收回调、授权和沙箱文件可读性会受到目标 SDK 与设备支持边界约束。本文不把 onInboundReady 写成 Share Kit 的真实接口;它只是应用自定义的数据适配函数。真实工程需要按官方分享和文件服务文档,将接收数据转为 InboundMeta 与受限的头部字节,验证结束以后才能执行业务导入。## 二、只收最少信息,不给外部声明额外权力每条传入记录至少有自己的 ID、展示名称、声明 MIME、文件字节长度与可读取的前几个字节。演示中还会保存一个检验决策,但不会把来源路径拼接成目标沙箱路径,更不直接信任对端提供的文件名。比如 photo-011.jpg 在列表上只是可读标签;正式保存时应由本地应用生成新文件名和安全目录。第一段代码是纯 ArkTS 的头部识别器,输入的是适配层已经合法读取到的字节,不是系统事件对象。JPEG 只匹配常见起始标记,PNG 则匹配完整八字节签名。UNKNOWN 的含义是当前小型检查器无法识别,不表示必定恶意。tsexport type MagicKind = 'image/jpeg' | 'image/png' | 'UNKNOWN';export interface InboundMeta { recordId: string; displayName: string; declaredMime: string; byteLength: number; head: Uint8Array;}export function detectMagic(head: Uint8Array): MagicKind { if (head.length >= 8 && head[0] === 0x89 && head[1] === 0x50 && head[2] === 0x4e && head[3] === 0x47 && head[4] === 0x0d && head[5] === 0x0a && head[6] === 0x1a && head[7] === 0x0a) { return 'image/png'; } if (head.length >= 3 && head[0] === 0xff && head[1] === 0xd8 && head[2] === 0xff) { return 'image/jpeg'; } return 'UNKNOWN';}代码特意先检查数组长度,避免不完整的头部被误认为正常。与依赖扩展名相比,读实际字节能发现更多明显错配;但即使魔数相同,也只能给出“允许进入下一步完整解析”的结论,不能直接判定图片可安全渲染。接收适配层读取文件时应设置最大字节数、拒绝不支持的来源 URI,并在文件描述符使用完毕后关闭资源。这些文件访问 API 因工程沙箱和授权路径而不同,本段不编造调用参数。## 三、类型、扩展名和大小要一起判断单独检查文件头还不够。名为 .jpg、声明为 image/jpeg、头部却是 PNG 的记录,应该在导入前就被标记为不一致。反过来,一个文件头为 JPEG、扩展名却是 .png,同样不能默默“纠正”类型并自动入库;它可能是普通用户误命名,也可能是业务数据错误。让接收页展示具体原因,比默认吞掉文件更可解释。下面的第二段代码把三项条件合并为一个可复用门禁,同时设置演示用的 15 MiB 上限。这个上限是 TapIntake 的业务配置,不是 Share Kit 或 HarmonyOS 的平台最大值。tsexport interface IntakeVerdict { recordId: string; status: 'PRECHECK_PASSED' | 'QUARANTINED'; reason: string;}export function checkInbound(meta: InboundMeta): IntakeVerdict { const magic = detectMagic(meta.head); const dot = meta.displayName.lastIndexOf('.'); const ext = dot < 0 ? '' : meta.displayName.slice(dot).toLowerCase(); const wantedExt = magic === 'image/png' ? '.png' : magic === 'image/jpeg' ? '.jpg' : ''; const allowed = magic !== 'UNKNOWN' && meta.byteLength > 0 && meta.byteLength <= 15 * 1024 * 1024; const jpgAlias = magic === 'image/jpeg' && ext === '.jpeg'; let reason: string = 'HEAD_MIME_EXT_MATCH'; if (!allowed) { reason = 'TYPE_OR_SIZE_MISMATCH'; } else if (magic !== meta.declaredMime) { reason = 'MIME_MISMATCH'; } else if (ext !== wantedExt && !jpgAlias) { reason = 'EXT_MISMATCH'; } return { recordId: meta.recordId, status: reason === 'HEAD_MIME_EXT_MATCH' ? 'PRECHECK_PASSED' : 'QUARANTINED', reason: reason };}这里对 jpeg 扩展名做了明确兼容,对大小为零或超过预算的内容拒绝预检。为了让条件易审查,失败原因先收敛为一个代码,界面提示再根据记录详细展示。真实环境应该拆成大小超限、头部未知、类型不匹配、读取失败等精确诊断,避免把所有故障说成“危险文件”。在多记录批次中,一个记录隔离不代表其他已通过记录需要一起失败,除非产品需求规定这是一组不可拆分的原子文件集合。## 四、隔离队列不是“删除或放行”二选一隔离的目标是阻止自动导入,并为用户提供可解释的人工核查路径,而不是悄悄将有问题的文件移动到一个不受控的文件夹。内部队列至少需要记录批次 ID、记录 ID、校验规则版本、失败原因和接收时间。不要把原始字节、外部绝对路径或隐私文件名直接输出到 HiLog。正式环境中的隔离区还应有生命周期与容量预算,避免重复分享堆积占满存储。本例四个记录的预设结果为:photo-011.jpg 与 photo-012.jpg 为 JPEG 头部、声明与扩展名一致;photo-013.png 为 PNG;agenda.jpg 的声明和扩展名都是 JPEG,但头部前八字节为 89 50 4E 47 0D 0A 1A 0A,命中 PNG。最终预检通过三条、隔离一条,状态为 QUARANTINED,不会触发“导入全部”。第三段示例只处理业务统计与页面展示,刻意不伪造分享 SDK 接收回调。页面中示例记录是已知的模拟输入,不能据此宣称真实跨设备传输成功。ts@Entry@Componentstruct InboundCheckPage { @State batchId: string = 'TAP-428'; @State passed: number = 3; @State quarantined: number = 1; @State problem: string = 'agenda.jpg · MIME_MISMATCH'; @State policy: string = '隔离待核查'; build() { Column({ space: 16 }) { Text('TapIntake · 接收素材预检').fontSize(22) .fontWeight(FontWeight.Bold); Text(`批次 ${this.batchId}`); Row({ space: 16 }) { Text(`预检通过 ${this.passed}`).fontColor(Color.Green); Text(`隔离 ${this.quarantined}`).fontColor(Color.Orange); } Text('photo-011.jpg ✓ JPEG'); Text('photo-012.jpg ✓ JPEG'); Text('photo-013.png ✓ PNG'); Text(this.problem).fontColor(Color.Red); Text(this.policy); Button('导入通过项').enabled(this.quarantined === 0); }.padding(20).width('100%'); }}按钮在本 Demo 中故意保持禁用:虽然三条记录单独通过了预检,但这个页面还没有实现完整解码、权限检查、沙箱复制和安全审核。不能为了让演示图看起来“完成”就启用危险的操作。真实业务可以新增“仅导入已完整验证的三条”,但必须在另一个明确的确认步骤里完成,不把预检通过当作导入授权。
## 五、从日志反推验收条件演示日志只需要四条:18:24:01 TAP-428 received=4;18:24:02 scan=4 passed=3 quarantined=1;18:24:02 IMG-028 agenda.jpg declared=image/jpeg detected=image/png;18:24:03 IMG-028 status=QUARANTINED import=false。这些时间、数字与字符串都是预设的产品场景数据,并不是 DevEco Studio 或实机自动打印的记录。验收时要覆盖四类反例。第一,短于签名长度的文件不得冒充 PNG。第二,声明和扩展名虽一致但真实头部不一致,必须隔离。第三,同一批次只有一个坏记录时,统计值应恰好是三比一,不能出现跳过该记录却显示“四条成功”。第四,反复打开页面不得重复把同一预检记录加入持久隔离队列。要进一步检验多设备分享,需要独立确认系统回调对应的文件读取权限、取消行为和每个记录的实际字节长度。
## 六、技术边界比“通过”两个字更重要文件类型的声明、扩展名与签名字节是三种不同证据。门禁通过只证明它们在当前规则下相互一致,不能证明文件内容安全、文件已经被完整写入、图片像素真实有效,也不能证明对端设备可信。业务若处理办公附件,还要面对 PDF、HEIC、动图或复合容器文件,不能把这个只有 JPEG/PNG 的小检查器当成通用扫描引擎。官方 Share Kit 服务概述 与 精准碰一碰参考入口 可用于核对实际触发能力,具体接收代码需要查目标 SDK 的 API 参考;Core File Kit 则用于后续受授权约束的文件读写。这些资料并未给本文纯函数赋予系统级安全保证,所以文章不写未经核实的事件参数和协议字段。本篇真正可复用的不是四条示例文件名,而是分享完成之前后,业务信任边界始终需要独立存在。明确“预检通过”“隔离待查”“已安全导入”的含义,远比在收到碰一碰事件后立刻显示一个成功图标更重要。
更多推荐




所有评论(0)