@aikdna/kdna-core 0.37.1-rc.browser.1 → 0.37.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -11
- package/package.json +6 -1
- package/src/public-contract/browser-native/sections/contract.json +2 -2
- package/src/public-contract/browser-native/whole.js +30 -1
- package/src/public-contract/browserwhole06/contract.json +2 -2
- package/src/public-contract/creation06/contract.json +2 -2
- package/src/public-contract/creation06/generation-manifest.json +6 -6
- package/src/public-contract/execution06/contract.json +2 -2
- package/src/public-contract/execution06/generation-manifest.json +6 -6
- package/src/public-contract/issuer-contract.generated.json +4 -4
- package/src/public-contract/issuer-validators.generated.js +7 -7
- package/src/public-contract/key-grant-issuer-node.d.ts +1 -1
- package/src/public-contract/package-set-contract.generated.json +5 -5
- package/src/public-contract/package-set-node.d.ts +1 -1
- package/src/public-contract/package-set-validators.generated.js +9 -9
- package/src/public-contract/packageset06/contract.json +2 -2
- package/src/public-contract/packageset06/generation-manifest.json +6 -6
- package/src/public-contract/protected-browser-contract/contract.json +4 -4
- package/src/public-contract/protected-browser-contract/descriptor.json +1 -1
- package/src/public-contract/protected-source-contract.generated.json +6 -6
- package/src/public-contract/protected-source-node.d.ts +1 -1
- package/src/public-contract/protection-contract.generated.json +7 -7
- package/src/public-contract/protection-node.d.ts +2 -2
- package/src/public-contract/protection-validators.generated.js +5 -5
- package/src/public-contract/public-contract.d.ts +13 -0
- package/src/public-contract/public-contract.js +32 -0
- package/src/public-contract/public-contract.mjs +2 -0
- package/src/public-contract/retained/contract.json +2 -2
- package/src/public-contract/retained/generation-manifest.json +6 -6
- package/src/public-contract/retained-browser06/contract.json +2 -2
- package/src/public-contract/sectionbytes/contract.json +2 -2
- package/src/public-contract/sectionbytes/generation-manifest.json +6 -6
- package/src/public-contract/sections/contract.json +2 -2
- package/src/public-contract/sections/generation-manifest.json +6 -6
- package/src/public-contract/sections-bytes-node.js +31 -0
- package/src/public-contract/sections-node.js +36 -1
- package/src/public-contract/source-validators.generated.js +6 -6
- package/src/public-contract/source06/contract.json +2 -2
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type ContractViewKind = 'manifest' | 'payload' | 'judgment' | 'methodComponent' | 'catalogItem';
|
|
2
|
+
export type ContractCoordinate = Readonly<Record<string, string>>;
|
|
3
|
+
export interface ContractView {
|
|
4
|
+
readonly kinds: ReadonlyArray<ContractViewKind>;
|
|
5
|
+
contractCoordinate(): ContractCoordinate;
|
|
6
|
+
requiredKeys(kind: ContractViewKind): string[];
|
|
7
|
+
optionalKeys(kind: ContractViewKind): string[];
|
|
8
|
+
}
|
|
9
|
+
export declare function buildContractView(contract: unknown): ContractView;
|
|
10
|
+
export declare const kinds: ReadonlyArray<ContractViewKind>;
|
|
11
|
+
export declare function contractCoordinate(): ContractCoordinate;
|
|
12
|
+
export declare function requiredKeys(kind: ContractViewKind): string[];
|
|
13
|
+
export declare function optionalKeys(kind: ContractViewKind): string[];
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Read-only view over the public contract this installation ships. No contract
|
|
3
|
+
// data lives in this file: every readout is derived from the generated contract
|
|
4
|
+
// at call time, so a consumer checks against the contract it actually installed.
|
|
5
|
+
const generated = require('./generated-contract.json');
|
|
6
|
+
const KINDS = Object.freeze(['manifest', 'payload', 'judgment', 'methodComponent', 'catalogItem']);
|
|
7
|
+
const TYPE_FOR_KIND = Object.freeze({
|
|
8
|
+
manifest: 'Manifest',
|
|
9
|
+
payload: 'Payload',
|
|
10
|
+
judgment: 'Judgment',
|
|
11
|
+
methodComponent: 'MethodComponent',
|
|
12
|
+
catalogItem: 'CatalogItem',
|
|
13
|
+
});
|
|
14
|
+
function keysFor(view, kind, which) {
|
|
15
|
+
const name = TYPE_FOR_KIND[kind];
|
|
16
|
+
if (!name) throw new Error('KDNA_CONTRACT_VIEW_UNKNOWN_KIND');
|
|
17
|
+
const type = view && view.types ? view.types[name] : undefined;
|
|
18
|
+
if (!type) throw new Error('KDNA_CONTRACT_VIEW_TYPE_MISSING');
|
|
19
|
+
const required = Array.isArray(type.required) ? [...type.required] : [];
|
|
20
|
+
if (which === 'required') return required;
|
|
21
|
+
return Object.keys(type.properties ?? {}).filter((key) => !required.includes(key));
|
|
22
|
+
}
|
|
23
|
+
function buildContractView(view) {
|
|
24
|
+
if (!view || typeof view !== 'object') throw new Error('KDNA_CONTRACT_VIEW_INPUT_INVALID');
|
|
25
|
+
return Object.freeze({
|
|
26
|
+
kinds: KINDS,
|
|
27
|
+
contractCoordinate: () => structuredClone(view.versionTuple ?? {}),
|
|
28
|
+
requiredKeys: (kind) => keysFor(view, kind, 'required'),
|
|
29
|
+
optionalKeys: (kind) => keysFor(view, kind, 'optional'),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
module.exports = Object.freeze({ buildContractView, ...buildContractView(generated) });
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"candidate_only": true,
|
|
3
3
|
"source": {
|
|
4
4
|
"path": "specs/public-semantic-source.json",
|
|
5
|
-
"bytes":
|
|
6
|
-
"sha256": "
|
|
5
|
+
"bytes": 1506715,
|
|
6
|
+
"sha256": "e1fa081ca757d8ceb2e73910024778e31debe83b16d8b4824a4c66466ec57928"
|
|
7
7
|
},
|
|
8
8
|
"historical_original_source": {
|
|
9
9
|
"scope": "historical_original_contract_source_identity_not_current_generator_input",
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
"candidate_only": true,
|
|
4
4
|
"source": {
|
|
5
5
|
"path": "specs/public-semantic-source.json",
|
|
6
|
-
"bytes":
|
|
7
|
-
"sha256": "
|
|
6
|
+
"bytes": 1506715,
|
|
7
|
+
"sha256": "e1fa081ca757d8ceb2e73910024778e31debe83b16d8b4824a4c66466ec57928"
|
|
8
8
|
},
|
|
9
9
|
"producer": {
|
|
10
10
|
"recipe": {
|
|
11
11
|
"path": "scripts/public-contract/native-output-recipe.json",
|
|
12
12
|
"bytes": 164001,
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "f5258627e45d396f9b088e76bdabe699d9ab845678ce1fa6a855b3fdaec21f22"
|
|
14
14
|
},
|
|
15
15
|
"scripts": [
|
|
16
16
|
{
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"path": "scripts/public-contract/native-renderer.mjs",
|
|
23
|
-
"bytes":
|
|
24
|
-
"sha256": "
|
|
23
|
+
"bytes": 21439,
|
|
24
|
+
"sha256": "750e968d0d88908f7c6b6b3d017039919049e22060203a7c721c3204522c211e"
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"path": "scripts/public-contract/generation-writer.mjs",
|
|
@@ -618,7 +618,7 @@
|
|
|
618
618
|
{
|
|
619
619
|
"path": "packages/kdna-core/src/public-contract/retained/contract.json",
|
|
620
620
|
"bytes": 291233,
|
|
621
|
-
"sha256": "
|
|
621
|
+
"sha256": "c9ece7138801ceed528693d8277ea873e1eee874eb567ef35f0e38e72b238121"
|
|
622
622
|
},
|
|
623
623
|
{
|
|
624
624
|
"path": "packages/kdna-core/src/public-contract/retained/api.d.ts",
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"candidate_only": true,
|
|
3
3
|
"source": {
|
|
4
4
|
"path": "specs/public-semantic-source.json",
|
|
5
|
-
"bytes":
|
|
6
|
-
"sha256": "
|
|
5
|
+
"bytes": 1506715,
|
|
6
|
+
"sha256": "e1fa081ca757d8ceb2e73910024778e31debe83b16d8b4824a4c66466ec57928"
|
|
7
7
|
},
|
|
8
8
|
"historical_original_source": {
|
|
9
9
|
"scope": "historical_original_contract_source_identity_not_current_generator_input",
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"candidate_only": true,
|
|
3
3
|
"source": {
|
|
4
4
|
"path": "specs/public-semantic-source.json",
|
|
5
|
-
"bytes":
|
|
6
|
-
"sha256": "
|
|
5
|
+
"bytes": 1506715,
|
|
6
|
+
"sha256": "e1fa081ca757d8ceb2e73910024778e31debe83b16d8b4824a4c66466ec57928"
|
|
7
7
|
},
|
|
8
8
|
"historical_original_source": {
|
|
9
9
|
"scope": "historical_original_contract_source_identity_not_current_generator_input",
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
"candidate_only": true,
|
|
4
4
|
"source": {
|
|
5
5
|
"path": "specs/public-semantic-source.json",
|
|
6
|
-
"bytes":
|
|
7
|
-
"sha256": "
|
|
6
|
+
"bytes": 1506715,
|
|
7
|
+
"sha256": "e1fa081ca757d8ceb2e73910024778e31debe83b16d8b4824a4c66466ec57928"
|
|
8
8
|
},
|
|
9
9
|
"producer": {
|
|
10
10
|
"recipe": {
|
|
11
11
|
"path": "scripts/public-contract/native-output-recipe.json",
|
|
12
12
|
"bytes": 164001,
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "f5258627e45d396f9b088e76bdabe699d9ab845678ce1fa6a855b3fdaec21f22"
|
|
14
14
|
},
|
|
15
15
|
"scripts": [
|
|
16
16
|
{
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"path": "scripts/public-contract/native-renderer.mjs",
|
|
23
|
-
"bytes":
|
|
24
|
-
"sha256": "
|
|
23
|
+
"bytes": 21439,
|
|
24
|
+
"sha256": "750e968d0d88908f7c6b6b3d017039919049e22060203a7c721c3204522c211e"
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"path": "scripts/public-contract/generation-writer.mjs",
|
|
@@ -124,7 +124,7 @@
|
|
|
124
124
|
{
|
|
125
125
|
"path": "packages/kdna-core/src/public-contract/sectionbytes/contract.json",
|
|
126
126
|
"bytes": 31759,
|
|
127
|
-
"sha256": "
|
|
127
|
+
"sha256": "208423043515f4149dd8d0cbf270ae07dc137bf6f0b79a70bfe61f7996365f9f"
|
|
128
128
|
},
|
|
129
129
|
{
|
|
130
130
|
"path": "packages/kdna-core/src/public-contract/sectionbytes/api.d.ts",
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"candidate_only": true,
|
|
3
3
|
"source": {
|
|
4
4
|
"path": "specs/public-semantic-source.json",
|
|
5
|
-
"bytes":
|
|
6
|
-
"sha256": "
|
|
5
|
+
"bytes": 1506715,
|
|
6
|
+
"sha256": "e1fa081ca757d8ceb2e73910024778e31debe83b16d8b4824a4c66466ec57928"
|
|
7
7
|
},
|
|
8
8
|
"historical_original_source": {
|
|
9
9
|
"scope": "historical_original_contract_source_identity_not_current_generator_input",
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
"candidate_only": true,
|
|
4
4
|
"source": {
|
|
5
5
|
"path": "specs/public-semantic-source.json",
|
|
6
|
-
"bytes":
|
|
7
|
-
"sha256": "
|
|
6
|
+
"bytes": 1506715,
|
|
7
|
+
"sha256": "e1fa081ca757d8ceb2e73910024778e31debe83b16d8b4824a4c66466ec57928"
|
|
8
8
|
},
|
|
9
9
|
"producer": {
|
|
10
10
|
"recipe": {
|
|
11
11
|
"path": "scripts/public-contract/native-output-recipe.json",
|
|
12
12
|
"bytes": 164001,
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "f5258627e45d396f9b088e76bdabe699d9ab845678ce1fa6a855b3fdaec21f22"
|
|
14
14
|
},
|
|
15
15
|
"scripts": [
|
|
16
16
|
{
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"path": "scripts/public-contract/native-renderer.mjs",
|
|
23
|
-
"bytes":
|
|
24
|
-
"sha256": "
|
|
23
|
+
"bytes": 21439,
|
|
24
|
+
"sha256": "750e968d0d88908f7c6b6b3d017039919049e22060203a7c721c3204522c211e"
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"path": "scripts/public-contract/generation-writer.mjs",
|
|
@@ -658,7 +658,7 @@
|
|
|
658
658
|
{
|
|
659
659
|
"path": "packages/kdna-core/src/public-contract/sections/contract.json",
|
|
660
660
|
"bytes": 547981,
|
|
661
|
-
"sha256": "
|
|
661
|
+
"sha256": "f871a8be8a91105f4a1a7bd4930e712bf3b012ce8b6fd17def7cf4eaaef27379"
|
|
662
662
|
},
|
|
663
663
|
{
|
|
664
664
|
"path": "packages/kdna-core/src/public-contract/protected-sections-node.d.ts",
|
|
@@ -33,6 +33,35 @@ function createNativeSectionByteReadAuthority(callback,signaturePolicy){
|
|
|
33
33
|
function classify(code){
|
|
34
34
|
return ['PROTECTION_BINDING_UNIMPLEMENTED','CHECKSUMS_PROFILE_UNSUPPORTED','SIGNATURE_PROFILE_UNSUPPORTED'].includes(code)?'unsupported':'rejected';
|
|
35
35
|
}
|
|
36
|
+
// F2: one bounded metadata classifier for the three public read entries. It reads
|
|
37
|
+
// only two bounded windows - the EOCD tail and the central directory - never the
|
|
38
|
+
// whole container, and it never mutates a shared module. Empty input (no ZIP
|
|
39
|
+
// structure) is reported as READ_INPUT_INVALID; a container that is recognisable
|
|
40
|
+
// but damaged keeps READ_CORE_INVALID (the published Python contract pins that
|
|
41
|
+
// for damaged member streams); a readable container of another family (a
|
|
42
|
+
// payload-family member set) is reported as READ_UNSUPPORTED_VERSION.
|
|
43
|
+
const INPUT_MARKERS={eocd:Uint8Array.from([80,75,5,6]),payload:Uint8Array.from([112,97,121,108,111,97,100,46,107,100,110,97,98]),frames:Uint8Array.from([115,101,99,116,105,111,110,115,47,112,97,99,107,45])};
|
|
44
|
+
const INPUT_TAIL=65557,DIR_SCAN_CAP=1048576;
|
|
45
|
+
function findBytes(bytes,needle,from=0,to=bytes.length){const last=Math.min(to,bytes.length)-needle.length;outer:for(let i=from;i<=last;i++){for(let j=0;j<needle.length;j++)if(bytes[i+j]!==needle[j])continue outer;return i;}return -1;}
|
|
46
|
+
function containerMeta(bytes){
|
|
47
|
+
const view=bytes&&bytes.length>=4?bytes:null;
|
|
48
|
+
const zipMagic=view&&view[0]===0x50&&view[1]===0x4b&&(view[2]===0x03||view[2]===0x05||view[2]===0x07);
|
|
49
|
+
const tailStart=Math.max(0,bytes.length-INPUT_TAIL),eocd=bytes.length>=22?findBytes(bytes,INPUT_MARKERS.eocd,tailStart):-1;
|
|
50
|
+
if(!zipMagic&&eocd===-1)return {kind:'not_container'};
|
|
51
|
+
if(eocd===-1)return {kind:'damaged'};
|
|
52
|
+
const cdStart=(bytes[eocd+16]|(bytes[eocd+17]<<8)|(bytes[eocd+18]<<16)|(bytes[eocd+19]<<24))>>>0;
|
|
53
|
+
const cdSize=(bytes[eocd+12]|(bytes[eocd+13]<<8)|(bytes[eocd+14]<<16)|(bytes[eocd+15]<<24))>>>0;
|
|
54
|
+
if(cdStart+cdSize>bytes.length||cdSize>DIR_SCAN_CAP)return {kind:'damaged'};
|
|
55
|
+
const hasPayload=findBytes(bytes,INPUT_MARKERS.payload,cdStart,cdStart+cdSize)!==-1;
|
|
56
|
+
const hasFrames=findBytes(bytes,INPUT_MARKERS.frames,cdStart,cdStart+cdSize)!==-1;
|
|
57
|
+
return hasPayload&&!hasFrames?{kind:'other_family'}:{kind:'damaged'};
|
|
58
|
+
}
|
|
59
|
+
function inputFailureClass(bytes){
|
|
60
|
+
const meta=containerMeta(bytes);
|
|
61
|
+
if(meta.kind==='not_container')return {reason:'READ_INPUT_INVALID',stage:'input',diagnostic:null};
|
|
62
|
+
if(meta.kind==='other_family')return {reason:'READ_UNSUPPORTED_VERSION',stage:'metadata',diagnostic:{field:'/payload.kdnab',subject:null}};
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
36
65
|
async function admitSectionBytesNode(input,admitted,authority){
|
|
37
66
|
let capture,owned,length=null,stage='input';
|
|
38
67
|
const io=[];
|
|
@@ -84,6 +113,8 @@ async function admitSectionBytesNode(input,admitted,authority){
|
|
|
84
113
|
}catch(error){
|
|
85
114
|
const nativeFailure=C.isFailure(error),semanticFailure=knownSemanticReasons.has(error?.reason);
|
|
86
115
|
const code=nativeFailure?error.code:semanticFailure?error.reason:'READ_CORE_CAPABILITY_UNAVAILABLE';
|
|
116
|
+
const classified=stage==='input'&&code==='READ_CORE_INVALID'?inputFailureClass(owned):null;
|
|
117
|
+
if(classified){stage=classified.stage;return failure(classify(classified.reason),classified.reason,classified.diagnostic);}
|
|
87
118
|
return failure(classify(code),code,nativeFailure||semanticFailure?error?.diagnostic:null);
|
|
88
119
|
}finally{
|
|
89
120
|
if(capture)await capture.close();
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const knownSemanticReasons=new Set(['READ_INPUT_INVALID','READ_CORE_INVALID','READ_CORE_CAPABILITY_UNAVAILABLE','READ_UNSUPPORTED_CRITICAL','READ_INTERPRETATION_INCOMPLETE','READ_STATIC_POLICY_INVALID',...require('./generated-contract.json').types.CoreComponentFailure.properties.code.enum]);
|
|
3
3
|
const {inflateRawSync}=require('node:zlib');
|
|
4
|
+
const fsp=require('node:fs/promises');
|
|
5
|
+
const fsFlags=require('node:fs').constants;
|
|
4
6
|
const C=require('./section-common.js'),{openSectionCapture}=require('./section-capture-node.js'),{parseSectionContainer}=require('./section-container.js'),{restoreWholeFrames}=require('./section-frame-graph.js'),{reconstructPayload}=require('./section-reconstruct.js'),{observeWhole}=require('./section-digests.js');
|
|
5
7
|
const {validateDecodedPayload,assertContentBindings}=require('./semantic-admission.js'),{buildIR}=require('./canonical-ir.js'),{digestCanonical,evidence}=require('./digests.js');
|
|
6
8
|
const {requests,snapshots,byteOrigins}=require('./section-native-state.js'),authorities=new WeakMap();
|
|
@@ -39,7 +41,40 @@ async function executeSectionNode(input,admitted,authority,operation='read',opti
|
|
|
39
41
|
const data={digests,snapshot_id:'section-snapshot:'+globalThis.crypto.randomUUID(),tuple:structuredClone(tuple),asset:structuredClone(payload.asset),ir:derived,runtime_entry_names:observations.E.member_names,verification};C.validate('WholeSectionSnapshotData06',data);
|
|
40
42
|
const view=C.strict.freeze(data),snapshot=Object.freeze({});snapshots.set(snapshot,view);expansion.retain(snapshot,view,capture);
|
|
41
43
|
return Object.freeze({status:'accepted',snapshot,io:C.strict.freeze(structuredClone(capture.io)),input_byte_length:bytes.length});
|
|
42
|
-
}catch(e){if(e?.result_stage)stage=e.result_stage;const nativeFailure=C.isFailure(e),semanticFailure=knownSemanticReasons.has(e?.reason);const code=nativeFailure?e.code:semanticFailure?e.reason:e?.code==='CONTAINER_COMMENT_CAPTURE_UNIMPLEMENTED'?e.code:'READ_CORE_CAPABILITY_UNAVAILABLE';return failure(classify(code),code,nativeFailure||semanticFailure?e?.diagnostic:null,structuredClone(capture?.io??e?.capture_io??[]));}finally{if(ownedSeed)ownedSeed.fill(0);if(capture)await capture.close();}
|
|
44
|
+
}catch(e){if(e?.result_stage)stage=e.result_stage;const nativeFailure=C.isFailure(e),semanticFailure=knownSemanticReasons.has(e?.reason);const code=nativeFailure?e.code:semanticFailure?e.reason:e?.code==='CONTAINER_COMMENT_CAPTURE_UNIMPLEMENTED'?e.code:'READ_CORE_CAPABILITY_UNAVAILABLE';const classified=stage==='input'&&code==='READ_CORE_INVALID'?await pathInputClass(input):null;if(classified){stage=classified.stage;return failure(classify(classified.reason),classified.reason,classified.diagnostic,structuredClone(capture?.io??e?.capture_io??[]));}return failure(classify(code),code,nativeFailure||semanticFailure?e?.diagnostic:null,structuredClone(capture?.io??e?.capture_io??[]));}finally{if(ownedSeed)ownedSeed.fill(0);if(capture)await capture.close();}
|
|
45
|
+
}
|
|
46
|
+
// F2: the same bounded metadata classifier as the two byte entries, expressed for
|
|
47
|
+
// a file path. It reads only the head, the EOCD tail window and the central
|
|
48
|
+
// directory - never content members - and it changes no shared module. Rules:
|
|
49
|
+
// no container structure at all -> READ_INPUT_INVALID; recognisable but damaged
|
|
50
|
+
// (including whole-file truncation) -> READ_CORE_INVALID unchanged; the central
|
|
51
|
+
// directory declaring a payload-family member set -> READ_UNSUPPORTED_VERSION.
|
|
52
|
+
const INPUT_MARKERS={eocd:Uint8Array.from([80,75,5,6]),payload:Uint8Array.from([112,97,121,108,111,97,100,46,107,100,110,97,98]),frames:Uint8Array.from([115,101,99,116,105,111,110,115,47,112,97,99,107,45])};
|
|
53
|
+
const INPUT_TAIL=65557,DIR_SCAN_CAP=1048576;
|
|
54
|
+
function findBytes(buf,needle,from=0,to=buf.length){const last=Math.min(to,buf.length)-needle.length;outer:for(let i=from;i<=last;i++){for(let j=0;j<needle.length;j++)if(buf[i+j]!==needle[j])continue outer;return i;}return -1;}
|
|
55
|
+
function findLastBytes(buf,needle){for(let i=buf.length-needle.length;i>=0;i--){let ok=true;for(let j=0;j<needle.length;j++)if(buf[i+j]!==needle[j]){ok=false;break;}if(ok)return i;}return -1;}
|
|
56
|
+
async function pathInputClass(file){
|
|
57
|
+
try{
|
|
58
|
+
// Classification follows a rejected capture, so the path is still untrusted.
|
|
59
|
+
// A blocking reopen of a FIFO would wait indefinitely for a writer.
|
|
60
|
+
const fh=await fsp.open(file,fsFlags.O_RDONLY|fsFlags.O_NONBLOCK);
|
|
61
|
+
try{
|
|
62
|
+
const st=await fh.stat();
|
|
63
|
+
if(!st.isFile()||!Number.isSafeInteger(st.size)||st.size<0)return {reason:'READ_INPUT_INVALID',stage:'input',diagnostic:null};
|
|
64
|
+
const head=Buffer.alloc(4),{bytesRead}=await fh.read(head,0,Math.min(4,st.size),0);
|
|
65
|
+
const zip=bytesRead===4&&head[0]===0x50&&head[1]===0x4b&&(head[2]===0x03||head[2]===0x05||head[2]===0x07);
|
|
66
|
+
// A recognisable ZIP shorter than an EOCD is damaged, as in both byte entries.
|
|
67
|
+
if(st.size<22)return zip?null:{reason:'READ_INPUT_INVALID',stage:'input',diagnostic:null};
|
|
68
|
+
const tailLen=Math.min(st.size,INPUT_TAIL),tail=Buffer.alloc(tailLen);await fh.read(tail,0,tailLen,st.size-tailLen);
|
|
69
|
+
const eocd=findLastBytes(tail,INPUT_MARKERS.eocd);
|
|
70
|
+
if(eocd===-1)return zip?null:{reason:'READ_INPUT_INVALID',stage:'input',diagnostic:null};
|
|
71
|
+
const cdStart=tail.readUInt32LE(eocd+16),cdSize=tail.readUInt32LE(eocd+12);
|
|
72
|
+
if(cdStart+cdSize>st.size||cdSize===0||cdSize>DIR_SCAN_CAP)return null;
|
|
73
|
+
const dir=Buffer.alloc(cdSize);await fh.read(dir,0,cdSize,cdStart);
|
|
74
|
+
const hasPayload=findBytes(dir,INPUT_MARKERS.payload)!==-1,hasFrames=findBytes(dir,INPUT_MARKERS.frames)!==-1;
|
|
75
|
+
return hasPayload&&!hasFrames?{reason:'READ_UNSUPPORTED_VERSION',stage:'metadata',diagnostic:{field:'/payload.kdnab',subject:null}}:null;
|
|
76
|
+
}finally{await fh.close();}
|
|
77
|
+
}catch{return null;}
|
|
43
78
|
}
|
|
44
79
|
async function admitSectionNode(input,admitted,authority){return executeSectionNode(input,admitted,authority);}
|
|
45
80
|
async function createSectionChecksumsNode(input,admitted,authority){return executeSectionNode(input,admitted,authority,'produce_checksums');}
|