zuzu-js 0.1.0
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/LICENSE +5 -0
- package/README.md +113 -0
- package/bin/zuzu +17 -0
- package/bin/zuzu-build-browser-bundle +57 -0
- package/bin/zuzu-generate-browser-stdlib +584 -0
- package/bin/zuzu-js +23 -0
- package/bin/zuzu-js-compile +152 -0
- package/bin/zuzu-js-electron +19 -0
- package/dist/zuzu-browser-worker.js +45574 -0
- package/dist/zuzu-browser.js +45362 -0
- package/lib/browser-bundle-entry.js +160 -0
- package/lib/browser-gui-renderer.js +387 -0
- package/lib/browser-runtime.js +167 -0
- package/lib/browser-worker-entry.js +413 -0
- package/lib/browser-ztests/runner.html +103 -0
- package/lib/browser-ztests/runner.js +369 -0
- package/lib/cli.js +350 -0
- package/lib/collections.js +367 -0
- package/lib/compiler.js +303 -0
- package/lib/electron/launcher.js +70 -0
- package/lib/electron/main.js +956 -0
- package/lib/electron/preload.js +80 -0
- package/lib/electron/renderer.html +122 -0
- package/lib/electron/renderer.js +24 -0
- package/lib/execution-metadata.js +18 -0
- package/lib/gui/dom-renderer.js +778 -0
- package/lib/host/browser-host.js +278 -0
- package/lib/host/capabilities.js +47 -0
- package/lib/host/electron-host.js +15 -0
- package/lib/host/node-host.js +74 -0
- package/lib/paths.js +150 -0
- package/lib/runtime-entrypoints.js +60 -0
- package/lib/runtime-helpers.js +886 -0
- package/lib/runtime.js +3529 -0
- package/lib/tap.js +37 -0
- package/lib/transpiler-new/ast.js +23 -0
- package/lib/transpiler-new/codegen.js +2455 -0
- package/lib/transpiler-new/errors.js +28 -0
- package/lib/transpiler-new/index.js +26 -0
- package/lib/transpiler-new/lexer.js +834 -0
- package/lib/transpiler-new/parser.js +2332 -0
- package/lib/transpiler-new/validate-bindings.js +326 -0
- package/lib/transpiler-utils.js +95 -0
- package/lib/transpiler.js +33 -0
- package/lib/zuzu.js +53 -0
- package/modules/javascript.js +193 -0
- package/modules/std/archive.js +603 -0
- package/modules/std/clib.js +338 -0
- package/modules/std/data/csv.js +1331 -0
- package/modules/std/data/json.js +531 -0
- package/modules/std/data/xml.js +441 -0
- package/modules/std/data/yaml.js +256 -0
- package/modules/std/db-worker.js +250 -0
- package/modules/std/db.js +664 -0
- package/modules/std/digest/_hash.js +443 -0
- package/modules/std/digest/md5.js +26 -0
- package/modules/std/digest/sha.js +72 -0
- package/modules/std/eval.js +10 -0
- package/modules/std/gui/objects.js +1519 -0
- package/modules/std/internals.js +571 -0
- package/modules/std/io/socks-worker.js +318 -0
- package/modules/std/io/socks.js +186 -0
- package/modules/std/io.js +475 -0
- package/modules/std/marshal/cbor.js +463 -0
- package/modules/std/marshal/graph.js +1624 -0
- package/modules/std/marshal.js +87 -0
- package/modules/std/math/bignum.js +91 -0
- package/modules/std/math.js +79 -0
- package/modules/std/net/dns.js +306 -0
- package/modules/std/net/http.js +820 -0
- package/modules/std/net/smtp.js +943 -0
- package/modules/std/net/url.js +109 -0
- package/modules/std/proc.js +602 -0
- package/modules/std/secure.js +3724 -0
- package/modules/std/string/base64.js +138 -0
- package/modules/std/string.js +299 -0
- package/modules/std/task.js +914 -0
- package/modules/std/time.js +579 -0
- package/modules/std/tui.js +188 -0
- package/modules/std/worker-thread.js +246 -0
- package/modules/std/worker.js +790 -0
- package/package.json +67 -0
- package/stdlib/modules/javascript.zzm +99 -0
- package/stdlib/modules/perl.zzm +105 -0
- package/stdlib/modules/std/archive.zzm +132 -0
- package/stdlib/modules/std/cache/lru.zzm +174 -0
- package/stdlib/modules/std/clib.zzm +112 -0
- package/stdlib/modules/std/colour.zzm +220 -0
- package/stdlib/modules/std/config.zzm +818 -0
- package/stdlib/modules/std/data/cbor.zzm +497 -0
- package/stdlib/modules/std/data/csv.zzm +285 -0
- package/stdlib/modules/std/data/ini.zzm +472 -0
- package/stdlib/modules/std/data/json/schema/core.zzm +573 -0
- package/stdlib/modules/std/data/json/schema/format.zzm +581 -0
- package/stdlib/modules/std/data/json/schema/model.zzm +255 -0
- package/stdlib/modules/std/data/json/schema/output.zzm +272 -0
- package/stdlib/modules/std/data/json/schema/relative_pointer.zzm +299 -0
- package/stdlib/modules/std/data/json/schema/validation.zzm +1503 -0
- package/stdlib/modules/std/data/json/schema.zzm +306 -0
- package/stdlib/modules/std/data/json.zzm +102 -0
- package/stdlib/modules/std/data/kdl/json.zzm +460 -0
- package/stdlib/modules/std/data/kdl/xml.zzm +387 -0
- package/stdlib/modules/std/data/kdl.zzm +1631 -0
- package/stdlib/modules/std/data/toml.zzm +756 -0
- package/stdlib/modules/std/data/toon.zzm +1017 -0
- package/stdlib/modules/std/data/xml/escape.zzm +156 -0
- package/stdlib/modules/std/data/xml.zzm +276 -0
- package/stdlib/modules/std/data/yaml.zzm +94 -0
- package/stdlib/modules/std/db.zzm +173 -0
- package/stdlib/modules/std/defer.zzm +75 -0
- package/stdlib/modules/std/digest/crc32.zzm +196 -0
- package/stdlib/modules/std/digest/md5.zzm +54 -0
- package/stdlib/modules/std/digest/sha.zzm +83 -0
- package/stdlib/modules/std/dump.zzm +317 -0
- package/stdlib/modules/std/eval.zzm +63 -0
- package/stdlib/modules/std/getopt.zzm +432 -0
- package/stdlib/modules/std/gui/dialogue.zzm +592 -0
- package/stdlib/modules/std/gui/objects.zzm +123 -0
- package/stdlib/modules/std/gui.zzm +1914 -0
- package/stdlib/modules/std/internals.zzm +139 -0
- package/stdlib/modules/std/io/socks.zzm +139 -0
- package/stdlib/modules/std/io.zzm +157 -0
- package/stdlib/modules/std/lingua/en.zzm +347 -0
- package/stdlib/modules/std/log.zzm +169 -0
- package/stdlib/modules/std/mail.zzm +2726 -0
- package/stdlib/modules/std/marshal.zzm +138 -0
- package/stdlib/modules/std/math/bignum.zzm +98 -0
- package/stdlib/modules/std/math/range.zzm +116 -0
- package/stdlib/modules/std/math/roman.zzm +156 -0
- package/stdlib/modules/std/math.zzm +141 -0
- package/stdlib/modules/std/net/dns.zzm +93 -0
- package/stdlib/modules/std/net/http.zzm +278 -0
- package/stdlib/modules/std/net/smtp.zzm +257 -0
- package/stdlib/modules/std/net/url.zzm +69 -0
- package/stdlib/modules/std/path/jsonpointer.zzm +526 -0
- package/stdlib/modules/std/path/kdl.zzm +1003 -0
- package/stdlib/modules/std/path/simple.zzm +520 -0
- package/stdlib/modules/std/path/z/context.zzm +147 -0
- package/stdlib/modules/std/path/z/evaluate.zzm +549 -0
- package/stdlib/modules/std/path/z/functions.zzm +874 -0
- package/stdlib/modules/std/path/z/lexer.zzm +490 -0
- package/stdlib/modules/std/path/z/node.zzm +1455 -0
- package/stdlib/modules/std/path/z/operators.zzm +445 -0
- package/stdlib/modules/std/path/z/parser.zzm +359 -0
- package/stdlib/modules/std/path/z.zzm +403 -0
- package/stdlib/modules/std/path/zz/functions.zzm +828 -0
- package/stdlib/modules/std/path/zz/operators.zzm +1036 -0
- package/stdlib/modules/std/path/zz.zzm +100 -0
- package/stdlib/modules/std/proc.zzm +155 -0
- package/stdlib/modules/std/result.zzm +149 -0
- package/stdlib/modules/std/secure.zzm +606 -0
- package/stdlib/modules/std/string/base64.zzm +66 -0
- package/stdlib/modules/std/string/quoted_printable.zzm +485 -0
- package/stdlib/modules/std/string.zzm +179 -0
- package/stdlib/modules/std/task.zzm +221 -0
- package/stdlib/modules/std/template/z.zzm +531 -0
- package/stdlib/modules/std/template/zz.zzm +62 -0
- package/stdlib/modules/std/time.zzm +188 -0
- package/stdlib/modules/std/tui.zzm +89 -0
- package/stdlib/modules/std/uuid.zzm +223 -0
- package/stdlib/modules/std/web/session.zzm +388 -0
- package/stdlib/modules/std/web/static.zzm +329 -0
- package/stdlib/modules/std/web.zzm +1942 -0
- package/stdlib/modules/std/worker.zzm +202 -0
- package/stdlib/modules/std/zuzuzoo.zzm +3960 -0
- package/stdlib/modules/test/more.zzm +528 -0
- package/stdlib/modules/test/parser.zzm +209 -0
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require( 'node:fs' );
|
|
4
|
+
const path = require( 'node:path' );
|
|
5
|
+
const zlib = require( 'node:zlib' );
|
|
6
|
+
|
|
7
|
+
const AdmZip = require( 'adm-zip' );
|
|
8
|
+
const compressjs = require( 'compressjs' );
|
|
9
|
+
|
|
10
|
+
const { BinaryString } = require( '../../lib/runtime-helpers' );
|
|
11
|
+
const { Path } = require( './io.js' );
|
|
12
|
+
|
|
13
|
+
function typeName( value ) {
|
|
14
|
+
if ( value == null ) {
|
|
15
|
+
return 'Null';
|
|
16
|
+
}
|
|
17
|
+
if ( value && value.bytes instanceof Uint8Array ) {
|
|
18
|
+
return 'BinaryString';
|
|
19
|
+
}
|
|
20
|
+
if ( Array.isArray( value ) ) {
|
|
21
|
+
return 'Array';
|
|
22
|
+
}
|
|
23
|
+
if ( typeof value === 'string' ) {
|
|
24
|
+
return 'String';
|
|
25
|
+
}
|
|
26
|
+
if ( value.constructor && value.constructor.name ) {
|
|
27
|
+
return value.constructor.name === 'Object' ? 'Dict' : value.constructor.name;
|
|
28
|
+
}
|
|
29
|
+
return typeof value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isPlainObject( value ) {
|
|
33
|
+
return value != null
|
|
34
|
+
&& typeof value === 'object'
|
|
35
|
+
&& !Array.isArray( value )
|
|
36
|
+
&& !( value instanceof Path )
|
|
37
|
+
&& !( value && value.bytes instanceof Uint8Array );
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isPath( value ) {
|
|
41
|
+
return value instanceof Path;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function assertBinaryString( value, label ) {
|
|
45
|
+
if ( value && value.bytes instanceof Uint8Array ) {
|
|
46
|
+
return Buffer.from( value.bytes );
|
|
47
|
+
}
|
|
48
|
+
throw new Error( `TypeException: ${label} expects BinaryString, got ${typeName( value )}` );
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function pathFromObject( value, methodName ) {
|
|
52
|
+
if ( isPath( value ) ) {
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
throw new Error( `TypeException: ${methodName} expects Path as first argument` );
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function normalizeFormat( raw ) {
|
|
59
|
+
if ( raw == null || raw === '' ) {
|
|
60
|
+
return 'auto';
|
|
61
|
+
}
|
|
62
|
+
const format = String( raw ).toLowerCase().replace( /^\./u, '' );
|
|
63
|
+
const aliases = {
|
|
64
|
+
auto: 'auto',
|
|
65
|
+
zip: 'zip',
|
|
66
|
+
tar: 'tar',
|
|
67
|
+
'tar.gz': 'tar.gz',
|
|
68
|
+
tgz: 'tar.gz',
|
|
69
|
+
'gzip+tar': 'tar.gz',
|
|
70
|
+
'tar.bz2': 'tar.bz2',
|
|
71
|
+
tbz: 'tar.bz2',
|
|
72
|
+
tbz2: 'tar.bz2',
|
|
73
|
+
'bzip2+tar': 'tar.bz2',
|
|
74
|
+
gz: 'gz',
|
|
75
|
+
gzip: 'gz',
|
|
76
|
+
bz2: 'bz2',
|
|
77
|
+
bzip2: 'bz2',
|
|
78
|
+
};
|
|
79
|
+
if ( Object.prototype.hasOwnProperty.call( aliases, format ) ) {
|
|
80
|
+
return aliases[format];
|
|
81
|
+
}
|
|
82
|
+
throw new Error( `Unsupported archive format '${raw}'` );
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function formatFromName( name ) {
|
|
86
|
+
if ( name == null || name === '' ) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const lower = String( name ).toLowerCase();
|
|
90
|
+
if ( /\.tar\.gz$/u.test( lower ) || /\.tgz$/u.test( lower ) ) {
|
|
91
|
+
return 'tar.gz';
|
|
92
|
+
}
|
|
93
|
+
if ( /\.tar\.bz2$/u.test( lower ) || /\.tbz2?$/u.test( lower ) ) {
|
|
94
|
+
return 'tar.bz2';
|
|
95
|
+
}
|
|
96
|
+
if ( /\.zip$/u.test( lower ) ) {
|
|
97
|
+
return 'zip';
|
|
98
|
+
}
|
|
99
|
+
if ( /\.tar$/u.test( lower ) ) {
|
|
100
|
+
return 'tar';
|
|
101
|
+
}
|
|
102
|
+
if ( /\.gz$/u.test( lower ) ) {
|
|
103
|
+
return 'gz';
|
|
104
|
+
}
|
|
105
|
+
if ( /\.bz2$/u.test( lower ) ) {
|
|
106
|
+
return 'bz2';
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function looksLikeTar( bytes ) {
|
|
112
|
+
const buffer = Buffer.from( bytes );
|
|
113
|
+
return buffer.length >= 512
|
|
114
|
+
&& buffer.subarray( 257, 262 ).toString( 'ascii' ) === 'ustar';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function detectFormatFromBytes( bytes ) {
|
|
118
|
+
const buffer = Buffer.from( bytes );
|
|
119
|
+
if ( buffer.length >= 4 && buffer[0] === 0x50 && buffer[1] === 0x4b ) {
|
|
120
|
+
return 'zip';
|
|
121
|
+
}
|
|
122
|
+
if ( buffer.length >= 2 && buffer[0] === 0x1f && buffer[1] === 0x8b ) {
|
|
123
|
+
const raw = gunzipBytes( buffer ).data;
|
|
124
|
+
return looksLikeTar( raw ) ? 'tar.gz' : 'gz';
|
|
125
|
+
}
|
|
126
|
+
if ( buffer.length >= 3 && buffer[0] === 0x42 && buffer[1] === 0x5a && buffer[2] === 0x68 ) {
|
|
127
|
+
const raw = bunzip2Bytes( buffer );
|
|
128
|
+
return looksLikeTar( raw ) ? 'tar.bz2' : 'bz2';
|
|
129
|
+
}
|
|
130
|
+
if ( looksLikeTar( buffer ) ) {
|
|
131
|
+
return 'tar';
|
|
132
|
+
}
|
|
133
|
+
throw new Error( 'Could not detect archive format from bytes' );
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function safeArchivePath( raw, label, allowEmpty = false ) {
|
|
137
|
+
const value = raw == null ? '' : String( raw );
|
|
138
|
+
if ( value === '' ) {
|
|
139
|
+
if ( allowEmpty ) {
|
|
140
|
+
return '';
|
|
141
|
+
}
|
|
142
|
+
throw new Error( `${label} requires path` );
|
|
143
|
+
}
|
|
144
|
+
const normalized = value.replace( /\\/gu, '/' );
|
|
145
|
+
if ( normalized.startsWith( '/' ) ) {
|
|
146
|
+
throw new Error( `${label} requires relative paths` );
|
|
147
|
+
}
|
|
148
|
+
const parts = normalized.split( '/' );
|
|
149
|
+
for ( const part of parts ) {
|
|
150
|
+
if ( part === '' || part === '.' || part === '..' ) {
|
|
151
|
+
throw new Error( `${label} requires safe relative paths` );
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return parts.join( '/' );
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function archiveEntryName( raw, label ) {
|
|
158
|
+
if ( raw == null || String( raw ) === '' ) {
|
|
159
|
+
throw new Error( `${label} requires path` );
|
|
160
|
+
}
|
|
161
|
+
return String( raw );
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function archiveToValue( format, entries ) {
|
|
165
|
+
return { format, entries };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function entryDataBytes( entry, label ) {
|
|
169
|
+
if ( entry.data_value != null ) {
|
|
170
|
+
return assertBinaryString( entry.data_value, `${label}.data` );
|
|
171
|
+
}
|
|
172
|
+
if ( entry.data_from_path != null ) {
|
|
173
|
+
const sourcePath = entry.data_from_path;
|
|
174
|
+
return Buffer.from( fs.readFileSync( sourcePath.to_String() ) );
|
|
175
|
+
}
|
|
176
|
+
throw new Error( `TypeException: ${label} expects BinaryString data or Path data_from` );
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function entriesFromArchiveValue( archive, label ) {
|
|
180
|
+
if ( !isPlainObject( archive ) ) {
|
|
181
|
+
throw new Error( `TypeException: ${label} expects Dict archive, got ${typeName( archive )}` );
|
|
182
|
+
}
|
|
183
|
+
if ( !Array.isArray( archive.entries ) ) {
|
|
184
|
+
throw new Error( `TypeException: ${label} expects archive.entries to be an Array` );
|
|
185
|
+
}
|
|
186
|
+
const entries = archive.entries.map( (entry, idx) => {
|
|
187
|
+
if ( !isPlainObject( entry ) ) {
|
|
188
|
+
throw new Error( `TypeException: ${label} expects archive.entries[${idx}] to be a Dict` );
|
|
189
|
+
}
|
|
190
|
+
const normalized = {
|
|
191
|
+
path: entry.path == null ? null : String( entry.path ),
|
|
192
|
+
label: `${label} archive.entries[${idx}]`,
|
|
193
|
+
};
|
|
194
|
+
if ( entry.data != null ) {
|
|
195
|
+
normalized.data_value = entry.data;
|
|
196
|
+
}
|
|
197
|
+
else if ( entry.data_from != null ) {
|
|
198
|
+
normalized.data_from_path = pathFromObject( entry.data_from, `${label} archive.entries[${idx}].data_from` );
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
throw new Error( `TypeException: ${label} archive.entries[${idx}] expects BinaryString data or Path data_from` );
|
|
202
|
+
}
|
|
203
|
+
return normalized;
|
|
204
|
+
} );
|
|
205
|
+
return [ entries, normalizeFormat( archive.format ) ];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function binaryValue( bytes ) {
|
|
209
|
+
return new BinaryString( Uint8Array.from( bytes ) );
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function decodeZipBytes( bytes ) {
|
|
213
|
+
try {
|
|
214
|
+
const zip = new AdmZip( Buffer.from( bytes ) );
|
|
215
|
+
return zip.getEntries()
|
|
216
|
+
.filter( (entry) => !entry.isDirectory )
|
|
217
|
+
.map( (entry) => ( {
|
|
218
|
+
path: entry.entryName,
|
|
219
|
+
data: binaryValue( entry.getData() ),
|
|
220
|
+
} ) );
|
|
221
|
+
}
|
|
222
|
+
catch ( err ) {
|
|
223
|
+
throw new Error( `Archive decode failed: ${err.message}` );
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function encodeZipBytes( entries ) {
|
|
228
|
+
try {
|
|
229
|
+
const zip = new AdmZip();
|
|
230
|
+
for ( const entry of entries ) {
|
|
231
|
+
const name = archiveEntryName( entry.path, 'Archive.encode' );
|
|
232
|
+
zip.addFile( name, entryDataBytes( entry, entry.label ) );
|
|
233
|
+
}
|
|
234
|
+
return zip.toBuffer();
|
|
235
|
+
}
|
|
236
|
+
catch ( err ) {
|
|
237
|
+
if ( /^Archive\.encode/u.test( err.message ) ) {
|
|
238
|
+
throw err;
|
|
239
|
+
}
|
|
240
|
+
throw new Error( `Archive encode failed: ${err.message}` );
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function tarString( buffer, start, length ) {
|
|
245
|
+
const slice = buffer.subarray( start, start + length );
|
|
246
|
+
const end = slice.indexOf( 0 );
|
|
247
|
+
return slice.subarray( 0, end < 0 ? slice.length : end ).toString( 'utf8' );
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function tarOctal( buffer, start, length ) {
|
|
251
|
+
const text = tarString( buffer, start, length ).trim();
|
|
252
|
+
return text === '' ? 0 : Number.parseInt( text, 8 );
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function writeTarString( header, start, length, value ) {
|
|
256
|
+
const bytes = Buffer.from( value, 'utf8' );
|
|
257
|
+
if ( bytes.length > length ) {
|
|
258
|
+
throw new Error( 'Archive.encode tar path is too long' );
|
|
259
|
+
}
|
|
260
|
+
bytes.copy( header, start );
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function writeTarOctal( header, start, length, value ) {
|
|
264
|
+
const text = value.toString( 8 ).padStart( length - 1, '0' );
|
|
265
|
+
header.write( text.slice( -( length - 1 ) ), start, length - 1, 'ascii' );
|
|
266
|
+
header[start + length - 1] = 0;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function splitTarPath( raw ) {
|
|
270
|
+
const nameBytes = Buffer.byteLength( raw, 'utf8' );
|
|
271
|
+
if ( nameBytes <= 100 ) {
|
|
272
|
+
return { name: raw, prefix: '' };
|
|
273
|
+
}
|
|
274
|
+
const parts = raw.split( '/' );
|
|
275
|
+
for ( let index = 1; index < parts.length; index++ ) {
|
|
276
|
+
const prefix = parts.slice( 0, index ).join( '/' );
|
|
277
|
+
const name = parts.slice( index ).join( '/' );
|
|
278
|
+
if (
|
|
279
|
+
Buffer.byteLength( prefix, 'utf8' ) <= 155
|
|
280
|
+
&& Buffer.byteLength( name, 'utf8' ) <= 100
|
|
281
|
+
) {
|
|
282
|
+
return { name, prefix };
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
throw new Error( 'Archive.encode tar path is too long' );
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function createTarHeader( name, data ) {
|
|
289
|
+
const header = Buffer.alloc( 512, 0 );
|
|
290
|
+
const split = splitTarPath( name );
|
|
291
|
+
writeTarString( header, 0, 100, split.name );
|
|
292
|
+
writeTarOctal( header, 100, 8, 0o644 );
|
|
293
|
+
writeTarOctal( header, 108, 8, 0 );
|
|
294
|
+
writeTarOctal( header, 116, 8, 0 );
|
|
295
|
+
writeTarOctal( header, 124, 12, data.length );
|
|
296
|
+
writeTarOctal( header, 136, 12, Math.floor( Date.now() / 1000 ) );
|
|
297
|
+
header.fill( 0x20, 148, 156 );
|
|
298
|
+
header[156] = 0x30;
|
|
299
|
+
header.write( 'ustar\0', 257, 6, 'ascii' );
|
|
300
|
+
header.write( '00', 263, 2, 'ascii' );
|
|
301
|
+
writeTarString( header, 345, 155, split.prefix );
|
|
302
|
+
let checksum = 0;
|
|
303
|
+
for ( const byte of header ) {
|
|
304
|
+
checksum += byte;
|
|
305
|
+
}
|
|
306
|
+
const checksumText = checksum.toString( 8 ).padStart( 6, '0' );
|
|
307
|
+
header.write( checksumText.slice( -6 ), 148, 6, 'ascii' );
|
|
308
|
+
header[154] = 0;
|
|
309
|
+
header[155] = 0x20;
|
|
310
|
+
return header;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function encodeTarBytes( entries, format ) {
|
|
314
|
+
const chunks = [];
|
|
315
|
+
for ( const entry of entries ) {
|
|
316
|
+
const name = archiveEntryName( entry.path, 'Archive.encode' );
|
|
317
|
+
const data = entryDataBytes( entry, entry.label );
|
|
318
|
+
chunks.push( createTarHeader( name, data ) );
|
|
319
|
+
chunks.push( data );
|
|
320
|
+
const padding = ( 512 - ( data.length % 512 ) ) % 512;
|
|
321
|
+
if ( padding > 0 ) {
|
|
322
|
+
chunks.push( Buffer.alloc( padding, 0 ) );
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
chunks.push( Buffer.alloc( 1024, 0 ) );
|
|
326
|
+
const tarBytes = Buffer.concat( chunks );
|
|
327
|
+
if ( format === 'tar.gz' ) {
|
|
328
|
+
return gzipBytes( tarBytes );
|
|
329
|
+
}
|
|
330
|
+
if ( format === 'tar.bz2' ) {
|
|
331
|
+
return bzip2Bytes( tarBytes );
|
|
332
|
+
}
|
|
333
|
+
return tarBytes;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function decodeTarBytes( bytes, format ) {
|
|
337
|
+
let buffer = Buffer.from( bytes );
|
|
338
|
+
if ( format === 'tar.gz' ) {
|
|
339
|
+
buffer = gunzipBytes( buffer ).data;
|
|
340
|
+
}
|
|
341
|
+
else if ( format === 'tar.bz2' ) {
|
|
342
|
+
buffer = bunzip2Bytes( buffer );
|
|
343
|
+
}
|
|
344
|
+
const entries = [];
|
|
345
|
+
for ( let offset = 0; offset + 512 <= buffer.length; ) {
|
|
346
|
+
const header = buffer.subarray( offset, offset + 512 );
|
|
347
|
+
offset += 512;
|
|
348
|
+
if ( header.every( (byte) => byte === 0 ) ) {
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
const size = tarOctal( header, 124, 12 );
|
|
352
|
+
const type = header[156];
|
|
353
|
+
const name = tarString( header, 0, 100 );
|
|
354
|
+
const prefix = tarString( header, 345, 155 );
|
|
355
|
+
const fullName = prefix === '' ? name : `${prefix}/${name}`;
|
|
356
|
+
const data = buffer.subarray( offset, offset + size );
|
|
357
|
+
offset += size + ( ( 512 - ( size % 512 ) ) % 512 );
|
|
358
|
+
if ( type === 0 || type === 0x30 ) {
|
|
359
|
+
entries.push( {
|
|
360
|
+
path: fullName,
|
|
361
|
+
data: binaryValue( data ),
|
|
362
|
+
} );
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return entries;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const CRC32_TABLE = (() => {
|
|
369
|
+
const table = new Uint32Array( 256 );
|
|
370
|
+
for ( let i = 0; i < 256; i++ ) {
|
|
371
|
+
let crc = i;
|
|
372
|
+
for ( let bit = 0; bit < 8; bit++ ) {
|
|
373
|
+
crc = ( crc & 1 ) ? ( 0xedb88320 ^ ( crc >>> 1 ) ) : ( crc >>> 1 );
|
|
374
|
+
}
|
|
375
|
+
table[i] = crc >>> 0;
|
|
376
|
+
}
|
|
377
|
+
return table;
|
|
378
|
+
})();
|
|
379
|
+
|
|
380
|
+
function crc32( bytes ) {
|
|
381
|
+
let crc = 0xffffffff;
|
|
382
|
+
for ( const byte of bytes ) {
|
|
383
|
+
crc = CRC32_TABLE[( crc ^ byte ) & 0xff] ^ ( crc >>> 8 );
|
|
384
|
+
}
|
|
385
|
+
return ( crc ^ 0xffffffff ) >>> 0;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function gzipBytes( bytes, fileName = null ) {
|
|
389
|
+
const data = Buffer.from( bytes );
|
|
390
|
+
const parts = [
|
|
391
|
+
Buffer.from( [ 0x1f, 0x8b, 0x08, fileName ? 0x08 : 0x00, 0, 0, 0, 0, 0, 0xff ] ),
|
|
392
|
+
];
|
|
393
|
+
if ( fileName ) {
|
|
394
|
+
parts.push( Buffer.from( fileName, 'utf8' ), Buffer.from( [ 0 ] ) );
|
|
395
|
+
}
|
|
396
|
+
parts.push( zlib.deflateRawSync( data ) );
|
|
397
|
+
const trailer = Buffer.alloc( 8 );
|
|
398
|
+
trailer.writeUInt32LE( crc32( data ), 0 );
|
|
399
|
+
trailer.writeUInt32LE( data.length >>> 0, 4 );
|
|
400
|
+
parts.push( trailer );
|
|
401
|
+
return Buffer.concat( parts );
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function gunzipBytes( bytes ) {
|
|
405
|
+
const buffer = Buffer.from( bytes );
|
|
406
|
+
return {
|
|
407
|
+
data: zlib.gunzipSync( buffer ),
|
|
408
|
+
name: parseGzipName( buffer ),
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function bzip2Bytes( bytes ) {
|
|
413
|
+
return Buffer.from( compressjs.Bzip2.compressFile( Buffer.from( bytes ) ) );
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function bunzip2Bytes( bytes ) {
|
|
417
|
+
try {
|
|
418
|
+
return Buffer.from( compressjs.Bzip2.decompressFile( Buffer.from( bytes ) ) );
|
|
419
|
+
}
|
|
420
|
+
catch ( err ) {
|
|
421
|
+
throw new Error( `Archive decode failed: ${err.message}` );
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function parseGzipName( bytes ) {
|
|
426
|
+
if ( bytes.length < 10 || bytes[0] !== 0x1f || bytes[1] !== 0x8b ) {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
const flags = bytes[3];
|
|
430
|
+
let index = 10;
|
|
431
|
+
if ( flags & 0x04 ) {
|
|
432
|
+
if ( index + 1 >= bytes.length ) {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
const xlen = bytes[index] | ( bytes[index + 1] << 8 );
|
|
436
|
+
index += 2 + xlen;
|
|
437
|
+
}
|
|
438
|
+
if ( flags & 0x08 ) {
|
|
439
|
+
let end = index;
|
|
440
|
+
while ( end < bytes.length && bytes[end] !== 0x00 ) {
|
|
441
|
+
end++;
|
|
442
|
+
}
|
|
443
|
+
if ( end <= bytes.length ) {
|
|
444
|
+
return Buffer.from( bytes.slice( index, end ) ).toString( 'utf8' );
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function decodeSingleCompressedBytes( bytes, format, defaultName = null ) {
|
|
451
|
+
if ( format === 'gz' ) {
|
|
452
|
+
const decoded = gunzipBytes( bytes );
|
|
453
|
+
return [
|
|
454
|
+
{
|
|
455
|
+
path: decoded.name || defaultName,
|
|
456
|
+
data: binaryValue( decoded.data ),
|
|
457
|
+
},
|
|
458
|
+
];
|
|
459
|
+
}
|
|
460
|
+
return [
|
|
461
|
+
{
|
|
462
|
+
path: defaultName,
|
|
463
|
+
data: binaryValue( bunzip2Bytes( bytes ) ),
|
|
464
|
+
},
|
|
465
|
+
];
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function encodeSingleCompressedBytes( entries, format ) {
|
|
469
|
+
if ( entries.length !== 1 ) {
|
|
470
|
+
throw new Error( `Archive.encode with format '${format}' expects exactly one entry` );
|
|
471
|
+
}
|
|
472
|
+
const entry = entries[0];
|
|
473
|
+
const fileName = entry.path == null || entry.path === ''
|
|
474
|
+
? 'payload.bin'
|
|
475
|
+
: path.basename( safeArchivePath( entry.path, 'Archive.encode' ) );
|
|
476
|
+
const data = entryDataBytes( entry, entry.label );
|
|
477
|
+
if ( format === 'gz' ) {
|
|
478
|
+
return gzipBytes( data, fileName );
|
|
479
|
+
}
|
|
480
|
+
return bzip2Bytes( data );
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function deriveSingleEntryNameFromPath( pathObj, format ) {
|
|
484
|
+
let name = path.basename( pathObj.to_String() );
|
|
485
|
+
if ( format === 'gz' ) {
|
|
486
|
+
name = name.replace( /\.(?:gz|gzip)$/iu, '' );
|
|
487
|
+
}
|
|
488
|
+
else if ( format === 'bz2' ) {
|
|
489
|
+
name = name.replace( /\.bz2$/iu, '' );
|
|
490
|
+
}
|
|
491
|
+
return name === '' ? null : name;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function resolveFormatForEncode( pathName, archiveFormat, givenFormat ) {
|
|
495
|
+
const pathFormat = formatFromName( pathName );
|
|
496
|
+
let format = normalizeFormat( givenFormat );
|
|
497
|
+
if ( format === 'auto' && archiveFormat !== 'auto' ) {
|
|
498
|
+
format = archiveFormat;
|
|
499
|
+
}
|
|
500
|
+
if ( format === 'auto' && pathFormat != null ) {
|
|
501
|
+
format = pathFormat;
|
|
502
|
+
}
|
|
503
|
+
if ( format === 'auto' ) {
|
|
504
|
+
throw new Error( 'Archive format is required when it cannot be inferred from path' );
|
|
505
|
+
}
|
|
506
|
+
return format;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
class Archive {
|
|
510
|
+
static decode( bytesValue, formatValue = null ) {
|
|
511
|
+
const bytes = assertBinaryString( bytesValue, 'Archive.decode' );
|
|
512
|
+
let format = normalizeFormat( formatValue );
|
|
513
|
+
if ( format === 'auto' ) {
|
|
514
|
+
format = detectFormatFromBytes( bytes );
|
|
515
|
+
}
|
|
516
|
+
if ( format === 'zip' ) {
|
|
517
|
+
return archiveToValue( format, decodeZipBytes( bytes ) );
|
|
518
|
+
}
|
|
519
|
+
if ( format === 'tar' || format === 'tar.gz' || format === 'tar.bz2' ) {
|
|
520
|
+
return archiveToValue( format, decodeTarBytes( bytes, format ) );
|
|
521
|
+
}
|
|
522
|
+
if ( format === 'gz' || format === 'bz2' ) {
|
|
523
|
+
return archiveToValue( format, decodeSingleCompressedBytes( bytes, format ) );
|
|
524
|
+
}
|
|
525
|
+
throw new Error( `Unsupported archive format '${format}'` );
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
static encode( archiveValue, formatValue = null ) {
|
|
529
|
+
const [ entries, archiveFormat ] = entriesFromArchiveValue( archiveValue, 'Archive.encode' );
|
|
530
|
+
let format = normalizeFormat( formatValue );
|
|
531
|
+
if ( format === 'auto' && archiveFormat !== 'auto' ) {
|
|
532
|
+
format = archiveFormat;
|
|
533
|
+
}
|
|
534
|
+
if ( format === 'auto' ) {
|
|
535
|
+
throw new Error( 'Archive.encode requires an explicit format' );
|
|
536
|
+
}
|
|
537
|
+
if ( format === 'zip' ) {
|
|
538
|
+
return binaryValue( encodeZipBytes( entries ) );
|
|
539
|
+
}
|
|
540
|
+
if ( format === 'tar' || format === 'tar.gz' || format === 'tar.bz2' ) {
|
|
541
|
+
return binaryValue( encodeTarBytes( entries, format ) );
|
|
542
|
+
}
|
|
543
|
+
if ( format === 'gz' || format === 'bz2' ) {
|
|
544
|
+
return binaryValue( encodeSingleCompressedBytes( entries, format ) );
|
|
545
|
+
}
|
|
546
|
+
throw new Error( `Unsupported archive format '${format}'` );
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
static load( pathValue, formatValue = null ) {
|
|
550
|
+
const archivePath = pathFromObject( pathValue, 'Archive.load' );
|
|
551
|
+
const bytes = fs.readFileSync( archivePath.to_String() );
|
|
552
|
+
let format = normalizeFormat( formatValue );
|
|
553
|
+
if ( format === 'auto' ) {
|
|
554
|
+
format = formatFromName( archivePath.to_String() ) || detectFormatFromBytes( bytes );
|
|
555
|
+
}
|
|
556
|
+
if ( format === 'zip' ) {
|
|
557
|
+
return archiveToValue( format, decodeZipBytes( bytes ) );
|
|
558
|
+
}
|
|
559
|
+
if ( format === 'tar' || format === 'tar.gz' || format === 'tar.bz2' ) {
|
|
560
|
+
return archiveToValue( format, decodeTarBytes( bytes, format ) );
|
|
561
|
+
}
|
|
562
|
+
if ( format === 'gz' || format === 'bz2' ) {
|
|
563
|
+
return archiveToValue(
|
|
564
|
+
format,
|
|
565
|
+
decodeSingleCompressedBytes(
|
|
566
|
+
bytes,
|
|
567
|
+
format,
|
|
568
|
+
deriveSingleEntryNameFromPath( archivePath, format )
|
|
569
|
+
)
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
throw new Error( `Unsupported archive format '${format}'` );
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
static dump( pathValue, archiveValue, formatValue = null ) {
|
|
576
|
+
const archivePath = pathFromObject( pathValue, 'Archive.dump' );
|
|
577
|
+
const [ entries, archiveFormat ] = entriesFromArchiveValue( archiveValue, 'Archive.dump' );
|
|
578
|
+
const format = resolveFormatForEncode(
|
|
579
|
+
archivePath.to_String(),
|
|
580
|
+
archiveFormat,
|
|
581
|
+
formatValue
|
|
582
|
+
);
|
|
583
|
+
let bytes;
|
|
584
|
+
if ( format === 'zip' ) {
|
|
585
|
+
bytes = encodeZipBytes( entries );
|
|
586
|
+
}
|
|
587
|
+
else if ( format === 'tar' || format === 'tar.gz' || format === 'tar.bz2' ) {
|
|
588
|
+
bytes = encodeTarBytes( entries, format );
|
|
589
|
+
}
|
|
590
|
+
else if ( format === 'gz' || format === 'bz2' ) {
|
|
591
|
+
bytes = encodeSingleCompressedBytes( entries, format );
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
throw new Error( `Unsupported archive format '${format}'` );
|
|
595
|
+
}
|
|
596
|
+
fs.writeFileSync( archivePath.to_String(), bytes );
|
|
597
|
+
return archivePath;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
module.exports = {
|
|
602
|
+
Archive,
|
|
603
|
+
};
|