收紧原生壳载荷边界门禁
导出 HostBridge 文件和通知载荷边界常量 校验移动壳文件大小和 MIME 清单与共享契约一致 校验桌面壳载荷限制和 MIME 清单与共享契约一致 更新原生壳方案和共享决策记录
This commit is contained in:
@@ -645,7 +645,7 @@ function extractStringArrayExport(source, exportName, seen = new Set()) {
|
||||
}
|
||||
|
||||
const match = source.match(
|
||||
new RegExp(`export const ${exportName}[^=]*= \\[([\\s\\S]*?)\\](?: as const)?;`),
|
||||
new RegExp(`export const ${exportName}[^=]*= \\[([\\s\\S]*?)\\][^;]*;`),
|
||||
);
|
||||
if (!match) {
|
||||
throw new Error(`unable to read ${exportName}`);
|
||||
@@ -690,14 +690,14 @@ function extractRustStringConst(source, constName) {
|
||||
function extractRustNumberConst(source, constName) {
|
||||
const match = source.match(
|
||||
new RegExp(
|
||||
`(?:pub\\(crate\\)\\s+)?const ${constName}\\s*:\\s*(?:u8|u16|u32|usize|i64|i32)\\s*=\\s*(\\d+);`,
|
||||
`(?:pub\\(crate\\)\\s+)?const ${constName}\\s*:\\s*(?:u8|u16|u32|u64|usize|i64|i32)\\s*=\\s*([^;]+);`,
|
||||
),
|
||||
);
|
||||
if (!match) {
|
||||
throw new Error(`unable to read Rust const ${constName}`);
|
||||
}
|
||||
|
||||
return Number(match[1]);
|
||||
return evaluateNumberExpression(match[1]);
|
||||
}
|
||||
|
||||
function extractTsStringConst(source, constName) {
|
||||
@@ -713,13 +713,54 @@ function extractTsStringConst(source, constName) {
|
||||
|
||||
function extractTsNumberConst(source, constName) {
|
||||
const match = source.match(
|
||||
new RegExp(`export const ${constName}\\s*=\\s*(\\d+);`),
|
||||
new RegExp(`export const ${constName}\\s*=\\s*([^;]+);`),
|
||||
);
|
||||
if (!match) {
|
||||
throw new Error(`unable to read TypeScript const ${constName}`);
|
||||
}
|
||||
|
||||
return Number(match[1]);
|
||||
return evaluateNumberExpression(match[1]);
|
||||
}
|
||||
|
||||
function evaluateNumberExpression(expression) {
|
||||
const tokens = expression
|
||||
.split('*')
|
||||
.map((token) => token.trim())
|
||||
.filter(Boolean);
|
||||
if (
|
||||
tokens.length === 0 ||
|
||||
tokens.some((token) => !/^\d+$/.test(token))
|
||||
) {
|
||||
throw new Error(`unsupported numeric expression ${expression}`);
|
||||
}
|
||||
|
||||
return tokens.reduce((value, token) => value * Number(token), 1);
|
||||
}
|
||||
|
||||
function extractRustStringMatchArms(source, functionName) {
|
||||
const match = source.match(
|
||||
new RegExp(
|
||||
`(?:pub\\(crate\\)\\s+)?fn ${functionName}[^\\{]*\\{[\\s\\S]*?match [^\\{]*\\{([\\s\\S]*?)\\n \\}`,
|
||||
),
|
||||
);
|
||||
if (!match) {
|
||||
throw new Error(`unable to read Rust match function ${functionName}`);
|
||||
}
|
||||
|
||||
return [...match[1].matchAll(/"([^"]+)"\s*=>/g)].map((entry) => entry[1]);
|
||||
}
|
||||
|
||||
function extractRustSomeStringValues(source, functionName) {
|
||||
const match = source.match(
|
||||
new RegExp(`(?:pub\\(crate\\)\\s+)?fn ${functionName}[^\\{]*\\{([\\s\\S]*?)\\n\\}`),
|
||||
);
|
||||
if (!match) {
|
||||
throw new Error(`unable to read Rust function ${functionName}`);
|
||||
}
|
||||
|
||||
return [...match[1].matchAll(/=>\s*Some\("([^"]+)"\)/g)].map(
|
||||
(entry) => entry[1],
|
||||
);
|
||||
}
|
||||
|
||||
function extractDesktopCapabilities(source) {
|
||||
@@ -891,6 +932,106 @@ const sharedHostBridgeVersion = extractTsNumberConst(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_VERSION',
|
||||
);
|
||||
const sharedHostBridgePayloadLimits = {
|
||||
HOST_BRIDGE_REQUEST_ID_MAX_LENGTH: extractTsNumberConst(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_REQUEST_ID_MAX_LENGTH',
|
||||
),
|
||||
HOST_BRIDGE_BADGE_COUNT_MAX: extractTsNumberConst(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_BADGE_COUNT_MAX',
|
||||
),
|
||||
HOST_BRIDGE_CLIPBOARD_TEXT_MAX_LENGTH: extractTsNumberConst(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_CLIPBOARD_TEXT_MAX_LENGTH',
|
||||
),
|
||||
HOST_BRIDGE_LOCAL_NOTIFICATION_TITLE_MAX_LENGTH: extractTsNumberConst(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_LOCAL_NOTIFICATION_TITLE_MAX_LENGTH',
|
||||
),
|
||||
HOST_BRIDGE_LOCAL_NOTIFICATION_BODY_MAX_LENGTH: extractTsNumberConst(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_LOCAL_NOTIFICATION_BODY_MAX_LENGTH',
|
||||
),
|
||||
HOST_BRIDGE_FILE_NAME_MAX_LENGTH: extractTsNumberConst(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_FILE_NAME_MAX_LENGTH',
|
||||
),
|
||||
HOST_BRIDGE_EXPORT_TEXT_MAX_BYTES: extractTsNumberConst(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_EXPORT_TEXT_MAX_BYTES',
|
||||
),
|
||||
HOST_BRIDGE_IMPORT_TEXT_MAX_BYTES: extractTsNumberConst(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_IMPORT_TEXT_MAX_BYTES',
|
||||
),
|
||||
HOST_BRIDGE_EXPORT_IMAGE_MAX_BYTES: extractTsNumberConst(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_EXPORT_IMAGE_MAX_BYTES',
|
||||
),
|
||||
HOST_BRIDGE_IMPORT_IMAGE_MAX_BYTES: extractTsNumberConst(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_IMPORT_IMAGE_MAX_BYTES',
|
||||
),
|
||||
HOST_BRIDGE_EXPORT_AUDIO_MAX_BYTES: extractTsNumberConst(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_EXPORT_AUDIO_MAX_BYTES',
|
||||
),
|
||||
HOST_BRIDGE_IMPORT_AUDIO_MAX_BYTES: extractTsNumberConst(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_IMPORT_AUDIO_MAX_BYTES',
|
||||
),
|
||||
};
|
||||
const desktopHostBridgePayloadLimits = {
|
||||
HOST_BRIDGE_REQUEST_ID_MAX_LENGTH: extractRustNumberConst(
|
||||
rustHostSource,
|
||||
'HOST_BRIDGE_REQUEST_ID_MAX_LENGTH',
|
||||
),
|
||||
HOST_BRIDGE_BADGE_COUNT_MAX: extractRustNumberConst(
|
||||
rustHostSource,
|
||||
'BADGE_COUNT_MAX',
|
||||
),
|
||||
HOST_BRIDGE_CLIPBOARD_TEXT_MAX_LENGTH: extractRustNumberConst(
|
||||
rustHostSource,
|
||||
'CLIPBOARD_TEXT_MAX_LENGTH',
|
||||
),
|
||||
HOST_BRIDGE_LOCAL_NOTIFICATION_TITLE_MAX_LENGTH: extractRustNumberConst(
|
||||
rustHostSource,
|
||||
'LOCAL_NOTIFICATION_TITLE_MAX_LENGTH',
|
||||
),
|
||||
HOST_BRIDGE_LOCAL_NOTIFICATION_BODY_MAX_LENGTH: extractRustNumberConst(
|
||||
rustHostSource,
|
||||
'LOCAL_NOTIFICATION_BODY_MAX_LENGTH',
|
||||
),
|
||||
HOST_BRIDGE_FILE_NAME_MAX_LENGTH: extractRustNumberConst(
|
||||
rustHostSource,
|
||||
'EXPORT_FILE_NAME_MAX_LENGTH',
|
||||
),
|
||||
HOST_BRIDGE_EXPORT_TEXT_MAX_BYTES: extractRustNumberConst(
|
||||
rustHostSource,
|
||||
'EXPORT_TEXT_MAX_BYTES',
|
||||
),
|
||||
HOST_BRIDGE_IMPORT_TEXT_MAX_BYTES: extractRustNumberConst(
|
||||
rustHostSource,
|
||||
'IMPORT_TEXT_MAX_BYTES',
|
||||
),
|
||||
HOST_BRIDGE_EXPORT_IMAGE_MAX_BYTES: extractRustNumberConst(
|
||||
rustHostSource,
|
||||
'EXPORT_IMAGE_MAX_BYTES',
|
||||
),
|
||||
HOST_BRIDGE_IMPORT_IMAGE_MAX_BYTES: extractRustNumberConst(
|
||||
rustHostSource,
|
||||
'IMPORT_IMAGE_MAX_BYTES',
|
||||
),
|
||||
HOST_BRIDGE_EXPORT_AUDIO_MAX_BYTES: extractRustNumberConst(
|
||||
rustHostSource,
|
||||
'EXPORT_AUDIO_MAX_BYTES',
|
||||
),
|
||||
HOST_BRIDGE_IMPORT_AUDIO_MAX_BYTES: extractRustNumberConst(
|
||||
rustHostSource,
|
||||
'IMPORT_AUDIO_MAX_BYTES',
|
||||
),
|
||||
};
|
||||
const desktopMethods = extractRustStringArrayConst(rustHostSource, 'HOST_BRIDGE_METHODS');
|
||||
const desktopHostBridgeProtocol = extractRustStringConst(
|
||||
rustHostSource,
|
||||
@@ -919,6 +1060,62 @@ if (desktopHostBridgeVersion !== sharedHostBridgeVersion) {
|
||||
);
|
||||
}
|
||||
|
||||
for (const [limitName, sharedLimit] of Object.entries(
|
||||
sharedHostBridgePayloadLimits,
|
||||
)) {
|
||||
const desktopLimit = desktopHostBridgePayloadLimits[limitName];
|
||||
if (desktopLimit !== sharedLimit) {
|
||||
throw new Error(
|
||||
`desktop shell ${limitName} drifted: expected ${sharedLimit} but got ${desktopLimit}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
extractRustStringConst(rustHostSource, 'EXPORT_FILE_NAME_FALLBACK') !==
|
||||
extractTsStringConst(sharedContractSource, 'HOST_BRIDGE_FILE_NAME_FALLBACK')
|
||||
) {
|
||||
throw new Error('desktop shell file name fallback drifted from shared HostBridge contract');
|
||||
}
|
||||
|
||||
const sharedTextMimeTypes = extractStringArrayExport(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_TEXT_MIME_TYPES',
|
||||
);
|
||||
const sharedImageMimeTypes = extractStringArrayExport(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_IMAGE_MIME_TYPES',
|
||||
);
|
||||
const sharedAudioMimeTypes = extractStringArrayExport(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_AUDIO_MIME_TYPES',
|
||||
);
|
||||
assertSameList(
|
||||
extractRustSomeStringValues(rustHostSource, 'import_text_mime_type'),
|
||||
sharedTextMimeTypes,
|
||||
'desktop shell text MIME types',
|
||||
);
|
||||
assertSameList(
|
||||
extractRustStringMatchArms(rustHostSource, 'export_image_extension'),
|
||||
sharedImageMimeTypes,
|
||||
'desktop shell export image MIME types',
|
||||
);
|
||||
assertSameList(
|
||||
extractRustSomeStringValues(rustHostSource, 'import_image_mime_type'),
|
||||
sharedImageMimeTypes,
|
||||
'desktop shell import image MIME types',
|
||||
);
|
||||
assertSameList(
|
||||
extractRustStringMatchArms(rustHostSource, 'export_audio_extension'),
|
||||
sharedAudioMimeTypes,
|
||||
'desktop shell export audio MIME types',
|
||||
);
|
||||
assertSameList(
|
||||
extractRustSomeStringValues(rustHostSource, 'import_audio_mime_type'),
|
||||
sharedAudioMimeTypes,
|
||||
'desktop shell import audio MIME types',
|
||||
);
|
||||
|
||||
assertSameList(desktopMethods, sharedMethods, 'desktop shell HostBridge method whitelist');
|
||||
const unknownHandledDesktopMethods = desktopHandledMethods.filter(
|
||||
(method) => !sharedMethods.includes(method),
|
||||
|
||||
Reference in New Issue
Block a user