From cbad5311827b45cbbbce9e99c7cd793cb010ef85 Mon Sep 17 00:00:00 2001 From: kdletters Date: Sat, 20 Jun 2026 15:05:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B6=E7=B4=A7=E7=A7=BB=E5=8A=A8=E5=A3=B3?= =?UTF-8?q?=E6=96=87=E6=9C=AC=E5=AF=BC=E5=87=BA=E6=A0=A1=E9=AA=8C=E9=A1=BA?= =?UTF-8?q?=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移动壳文本导出先校验 MIME 再探测原生分享能力 移动壳文件测试覆盖非法文本 MIME 不触碰原生分享 移动壳配置检查锁定文本导出本地校验顺序 --- apps/mobile-shell/scripts/check-config.mjs | 15 +++++++++++++++ apps/mobile-shell/src/host-bridge/files.test.ts | 17 +++++++++++++++++ apps/mobile-shell/src/host-bridge/files.ts | 5 +++-- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/apps/mobile-shell/scripts/check-config.mjs b/apps/mobile-shell/scripts/check-config.mjs index d580360d3..a4accea7d 100644 --- a/apps/mobile-shell/scripts/check-config.mjs +++ b/apps/mobile-shell/scripts/check-config.mjs @@ -2104,6 +2104,20 @@ if ( ) { throw new Error('mobile shell image export must reject empty and oversized bytes'); } +const exportTextFileBody = extractFunctionBody(filesSource, 'exportTextFile'); +const exportTextMimeValidationIndex = exportTextFileBody.indexOf( + 'HOST_BRIDGE_TEXT_MIME_TYPE_SET.has(mimeType as HostBridgeTextMimeType)', +); +const exportTextSharingAvailabilityIndex = exportTextFileBody.indexOf( + 'await assertMobileFileSharingAvailable()', +); +if ( + exportTextMimeValidationIndex < 0 || + exportTextSharingAvailabilityIndex < 0 || + exportTextMimeValidationIndex > exportTextSharingAvailabilityIndex +) { + throw new Error('mobile shell text export must validate MIME before native sharing checks'); +} for (const [wrapperName, fileCall] of [ ['exportMobileHostBridgeTextFile', 'ok(request, await exportTextFile(request.payload))'], @@ -2126,6 +2140,7 @@ for (const snippet of [ 'exportTextFile({', 'exportImageFile({', 'rejects every export before cache writes when system sharing is unavailable', + 'rejects invalid text export MIME before touching native sharing', 'const exportCases = [', 'expect(writtenFiles).toHaveLength(0)', 'importTextFile()', diff --git a/apps/mobile-shell/src/host-bridge/files.test.ts b/apps/mobile-shell/src/host-bridge/files.test.ts index 1d6c8deda..a202f40db 100644 --- a/apps/mobile-shell/src/host-bridge/files.test.ts +++ b/apps/mobile-shell/src/host-bridge/files.test.ts @@ -185,6 +185,23 @@ describe('mobile HostBridge file actions', () => { expect(shareAsyncMock).not.toHaveBeenCalled(); }); + test('rejects invalid text export MIME before touching native sharing', async () => { + await expect( + exportTextFile({ + content: '泥巴AI', + fileName: '创作记录', + mimeType: 'application/octet-stream', + }), + ).rejects.toMatchObject({ + code: 'invalid_request', + message: 'mimeType must be an allowed text type', + }); + + expect(shareAvailableMock).not.toHaveBeenCalled(); + expect(writtenFiles).toHaveLength(0); + expect(shareAsyncMock).not.toHaveBeenCalled(); + }); + test('maps native sharing availability failures to stable export errors', async () => { shareAvailableMock.mockRejectedValueOnce(new Error('expo sharing crashed')); diff --git a/apps/mobile-shell/src/host-bridge/files.ts b/apps/mobile-shell/src/host-bridge/files.ts index c0c5fd51f..c05252064 100644 --- a/apps/mobile-shell/src/host-bridge/files.ts +++ b/apps/mobile-shell/src/host-bridge/files.ts @@ -109,8 +109,6 @@ export async function exportTextFile( throw invalidRequest('content exceeds file export size limit'); } - await assertMobileFileSharingAvailable(); - const fileName = normalizeHostBridgeExportFileName(exportPayload?.fileName); const rawMimeType = exportPayload?.mimeType; const mimeType = @@ -120,6 +118,9 @@ export async function exportTextFile( if (!HOST_BRIDGE_TEXT_MIME_TYPE_SET.has(mimeType as HostBridgeTextMimeType)) { throw invalidRequest('mimeType must be an allowed text type'); } + + await assertMobileFileSharingAvailable(); + try { const file = new File(Paths.cache, fileName); file.write(content);