收紧媒体导出载荷边界

将图片和音频导出载荷预校验提升到共享 HostBridge 契约
让 H5 facade 按共享媒体导出边界归一化文件名、MIME 和 base64 载荷
保留 Expo 与 Tauri 壳对真实字节和 MIME 的二次校验
增加媒体导出载荷测试和三端壳总门禁反查
同步宿主壳协议文档和共享决策记录
This commit is contained in:
2026-06-19 16:18:54 +08:00
parent 14da1a7e12
commit 71510ef797
7 changed files with 187 additions and 8 deletions
+23 -3
View File
@@ -1053,8 +1053,8 @@ describe('hostBridge', () => {
).resolves.toBe(false);
await expect(
exportHostImageFile({
fileName: '分享卡.png',
base64Data: 'c2hhcmUtY2FyZA==',
fileName: ' ../分享:卡?.png ',
base64Data: ' c2hhcmUtY2FyZA== ',
mimeType: 'image/png',
}),
).resolves.toBe(true);
@@ -1220,7 +1220,7 @@ describe('hostBridge', () => {
request: expect.objectContaining({
method: 'file.exportImage',
payload: {
fileName: '分享.png',
fileName: '分享-卡-.png',
base64Data: 'c2hhcmUtY2FyZA==',
mimeType: 'image/png',
},
@@ -1536,6 +1536,16 @@ describe('hostBridge', () => {
timeoutMs: HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS,
}),
});
invoke.mockClear();
await expect(
exportHostImageFile({
fileName: 'bad.gif',
base64Data: 'c2hhcmUtY2FyZA==',
mimeType: 'image/gif' as 'image/png',
}),
).resolves.toBe(false);
expect(invoke).not.toHaveBeenCalled();
});
test('原生 App 宿主不支持文本导出时回退 H5', async () => {
@@ -1897,6 +1907,16 @@ describe('hostBridge', () => {
timeoutMs: HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS,
}),
});
invoke.mockClear();
await expect(
exportHostAudioFile({
fileName: 'empty.wav',
base64Data: ' ',
mimeType: 'audio/wav',
}),
).resolves.toBe(false);
expect(invoke).not.toHaveBeenCalled();
});
test('原生 App 宿主取消导入音频时回退为 false', async () => {
+14 -2
View File
@@ -43,6 +43,8 @@ import {
normalizeHostBridgeClipboardText,
normalizeHostBridgeColorScheme,
normalizeHostBridgeConnectionType,
normalizeHostBridgeExportAudioPayload,
normalizeHostBridgeExportImagePayload,
normalizeHostBridgeExternalUrlPayload,
normalizeHostBridgeHapticsImpactStyle,
normalizeHostBridgeLifecycleState,
@@ -854,10 +856,15 @@ export async function exportHostImageFile(
return false;
}
const normalizedPayload = normalizeHostBridgeExportImagePayload(params);
if (!normalizedPayload) {
return false;
}
try {
return await requestNativeAppHostBridge<FileExportImageResult>(
'file.exportImage',
params,
normalizedPayload,
{ timeoutMs: HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS },
);
} catch (error) {
@@ -875,10 +882,15 @@ export async function exportHostAudioFile(
return false;
}
const normalizedPayload = normalizeHostBridgeExportAudioPayload(params);
if (!normalizedPayload) {
return false;
}
try {
return await requestNativeAppHostBridge<FileExportAudioResult>(
'file.exportAudio',
params,
normalizedPayload,
{ timeoutMs: HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS },
);
} catch (error) {