UNPKG

11.7 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.Packager = void 0;
7var _promises = require("node:fs/promises");
8var _nodePath = require("node:path");
9var _archiveFiles = require("@shockpkg/archive-files");
10var _signature = require("./signature.js");
11var _sha = require("./hasher/sha256.js");
12/**
13 * Options for adding resources.
14 */
15
16/**
17 * Packager object.
18 */
19class Packager {
20 /**
21 * Make a debug build.
22 */
23 debug = false;
24
25 /**
26 * Keystore object to use for signing.
27 */
28 keystore = null;
29
30 /**
31 * Timestamp URL.
32 */
33 timestampUrl = null;
34
35 /**
36 * Application descriptor file data.
37 */
38 descriptorData = null;
39
40 /**
41 * Application descriptor file path.
42 */
43 descriptorFile = null;
44
45 /**
46 * File and directory names to exclude when added a directory.
47 */
48 excludes = [/^\./, /^ehthumbs\.db$/i, /^Thumbs\.db$/i];
49
50 /**
51 * Set the nobrowse option on mounted disk images.
52 */
53 nobrowse = false;
54
55 /**
56 * Output path.
57 */
58
59 /**
60 * Open flag.
61 */
62 _isOpen = false;
63
64 /**
65 * Adding a resource flag.
66 */
67 _isAddingResource = false;
68
69 /**
70 * Hasher object.
71 */
72
73 /**
74 * Signature object.
75 */
76 _signature = null;
77
78 /**
79 * Packager constructor.
80 *
81 * @param path Output path.
82 */
83 constructor(path) {
84 this._hasher = this._createHasher();
85 this.path = path;
86 }
87
88 /**
89 * Check if output open.
90 *
91 * @returns Returns true if open, else false.
92 */
93 get isOpen() {
94 return this._isOpen;
95 }
96
97 /**
98 * Open with application descriptor XML data.
99 */
100 async open() {
101 if (this._isOpen) {
102 throw new Error('Already open');
103 }
104 const descriptorData = await this._getDescriptorData();
105 this._applicationInfoInit(descriptorData);
106 await this._open();
107 this._isOpen = true;
108 this._hasher.reset();
109 this._signature = null;
110 if (this.signed) {
111 this._signature = this._createSignature();
112 this._signature.timestampUrl = this.timestampUrl;
113 const keystore = this._getKeystore();
114 this._signature.certificate = keystore.getCertificate();
115 this._signature.privateKey = keystore.getPrivateKey();
116 }
117 await this._addMetaResourcesStart(descriptorData);
118 }
119
120 /**
121 * Close output.
122 */
123 async close() {
124 if (!this._isOpen) {
125 throw new Error('Not open');
126 }
127 await this._addMetaResourcesEnd();
128 await this._close();
129 this._isOpen = false;
130 this._applicationInfoClear();
131 this._hasher.reset();
132 this._signature = null;
133 }
134
135 /**
136 * Run asyncronous function with automatic open and close.
137 *
138 * @param func Async function.
139 * @returns Return value of the async function.
140 */
141 async write(func) {
142 await this.open();
143 let r;
144 try {
145 r = await func.call(this, this);
146 } finally {
147 await this.close();
148 }
149 return r;
150 }
151
152 /**
153 * Check if name is excluded file.
154 *
155 * @param name File name.
156 * @returns Returns true if excluded, else false.
157 */
158 isExcludedFile(name) {
159 for (const exclude of this.excludes) {
160 if (exclude.test(name)) {
161 return true;
162 }
163 }
164 return false;
165 }
166
167 /**
168 * Add resource with file.
169 *
170 * @param source File path.
171 * @param destination Packaged file relative destination.
172 * @param options Resource options.
173 */
174 async addResourceFile(source, destination = null, options = null) {
175 const opts = options || {};
176 const dest = destination === null ? source : destination;
177 const stat = await (0, _promises.lstat)(source);
178
179 // Symlinks would only be allowed in a macOS native extension.
180 // Throw an error like the official packager does.
181 if (stat.isSymbolicLink()) {
182 throw new Error(`Cannot add symlink: ${source}`);
183 }
184
185 // Throw if not a regular file.
186 if (!stat.isFile()) {
187 throw new Error(`Unsupported file type: ${source}`);
188 }
189 let {
190 executable
191 } = opts;
192 if (executable !== true && executable !== false) {
193 // eslint-disable-next-line no-bitwise
194 executable = !!(stat.mode & 0b001000000);
195 }
196 const data = await (0, _promises.readFile)(source);
197 await this.addResource(dest, data, {
198 executable,
199 mtime: opts.mtime || stat.mtime
200 });
201 }
202
203 /**
204 * Add resource with directory.
205 * Walks the directory looking for files to add, skips excluded file names.
206 *
207 * @param source Directory path.
208 * @param destination Packaged directory relative destination.
209 * @param options Resource options.
210 */
211 async addResourceDirectory(source, destination = null, options = null) {
212 const dest = destination === null ? source : destination;
213 await (0, _archiveFiles.fsWalk)(source, async (path, stat) => {
214 // If this name is excluded, skip without descending.
215 if (this.isExcludedFile((0, _nodePath.basename)(path))) {
216 return false;
217 }
218
219 // Ignore directories, but descend into them.
220 // Only files are listed in the ZIP packages.
221 // Created automatically for files in any other package.
222 if (stat.isDirectory()) {
223 return true;
224 }
225
226 // Anything else assume file.
227 await this.addResourceFile((0, _nodePath.join)(source, path), (0, _nodePath.join)(dest, path), options);
228 return true;
229 });
230 }
231
232 /**
233 * Add resource with data.
234 *
235 * @param destination Packaged file relative destination.
236 * @param data Resource data.
237 * @param options Resource options.
238 */
239 async addResource(destination, data, options = null) {
240 if (!this._isOpen) {
241 throw new Error('Not open');
242 }
243 if (this._isAddingResource) {
244 throw new Error('Resources must be added sequentially');
245 }
246 this._isAddingResource = true;
247 await this._addResource((0, _archiveFiles.pathNormalize)(destination), data, options || {}, true, true);
248 this._isAddingResource = false;
249 }
250
251 /**
252 * Create Hasher object.
253 *
254 * @returns Hasher object.
255 */
256 _createHasher() {
257 return new _sha.HasherSha256();
258 }
259
260 /**
261 * Create Signature object.
262 *
263 * @returns Hasher object.
264 */
265 _createSignature() {
266 return new _signature.Signature();
267 }
268
269 /**
270 * Path of the mimetype meta resource.
271 *
272 * @returns Resource path.
273 */
274 get _metaResourceMimetypePath() {
275 return 'mimetype';
276 }
277
278 /**
279 * Path of the application meta resource.
280 *
281 * @returns Resource path.
282 */
283 get _metaResourceApplicationPath() {
284 return 'META-INF/AIR/application.xml';
285 }
286
287 /**
288 * Path of the hash meta resource.
289 *
290 * @returns Resource path.
291 */
292 get _metaResourceHashPath() {
293 return 'META-INF/AIR/hash';
294 }
295
296 /**
297 * Path of the debug meta resource.
298 *
299 * @returns Resource path.
300 */
301 get _metaResourceDebugPath() {
302 return 'META-INF/AIR/debug';
303 }
304
305 /**
306 * Path of the signatures meta resource.
307 *
308 * @returns Resource path.
309 */
310 get _metaResourceSignaturesPath() {
311 return 'META-INF/signatures.xml';
312 }
313
314 /**
315 * Get application descriptor data or throw.
316 *
317 * @returns Application descriptor XML data.
318 */
319 async _getDescriptorData() {
320 const {
321 descriptorData,
322 descriptorFile
323 } = this;
324 if (descriptorData) {
325 switch (typeof descriptorData) {
326 case 'function':
327 {
328 const d = await descriptorData();
329 return typeof d === 'string' ? new TextEncoder().encode(d) : d;
330 }
331 case 'string':
332 {
333 return new TextEncoder().encode(descriptorData);
334 }
335 default:
336 {
337 return descriptorData;
338 }
339 }
340 }
341 if (descriptorFile !== null) {
342 const d = await (0, _promises.readFile)(descriptorFile);
343 return new Uint8Array(d.buffer, d.byteOffset, d.byteLength);
344 }
345 throw new Error('Missing application descriptor data');
346 }
347
348 /**
349 * Get encoded mimetype data.
350 *
351 * @returns Mimetype data.
352 */
353 _getMimetypeData() {
354 // The mimetype is UTF-8.
355 return new TextEncoder().encode(this.mimetype);
356 }
357
358 /**
359 * Get the keystore object.
360 *
361 * @returns Keystore object.
362 */
363 _getKeystore() {
364 const r = this.keystore;
365 if (!r) {
366 throw new Error('A keystore not set');
367 }
368 return r;
369 }
370
371 /**
372 * Add resource with data, with options controlling hashing and signing.
373 *
374 * @param destination Packaged file relative destination.
375 * @param data Resource data.
376 * @param options Resource options.
377 * @param hashed This file is hashed.
378 * @param signed This file is signed.
379 */
380 async _addResource(destination, data, options, hashed, signed) {
381 if (hashed) {
382 this._hasher.update(data);
383 }
384 if (signed) {
385 const signature = this._signature;
386 if (signature) {
387 signature.addFile(destination, data);
388 }
389 }
390 await this._writeResource(destination, data, options);
391 }
392
393 /**
394 * Add meta resources start.
395 *
396 * @param applicationData XML data.
397 */
398 async _addMetaResourcesStart(applicationData) {
399 await this._addMetaResourceMimetype();
400 await this._addMetaResourceApplication(applicationData);
401 await this._addMetaResourceHash();
402 if (this.debug) {
403 await this._addMetaResourceDebug();
404 }
405 }
406
407 /**
408 * Add meta resources end.
409 */
410 async _addMetaResourcesEnd() {
411 if (this.signed) {
412 await this._addMetaResourceSignatures();
413 }
414 }
415
416 /**
417 * Add meta resource for the mimetype.
418 */
419 async _addMetaResourceMimetype() {
420 const path = this._metaResourceMimetypePath;
421 const data = this._getMimetypeData();
422 await this._addResource(path, data, {}, true, true);
423 }
424
425 /**
426 * Add meta resource for the application descriptor.
427 *
428 * @param applicationData The application descriptor data.
429 */
430 async _addMetaResourceApplication(applicationData) {
431 const path = this._metaResourceApplicationPath;
432 await this._addResource(path, applicationData, {}, true, true);
433 }
434
435 /**
436 * Add meta resource for the hash (needs updating on close).
437 */
438 async _addMetaResourceHash() {
439 const path = this._metaResourceHashPath;
440 const data = new Uint8Array(this._hasher.bytes);
441 await this._addResource(path, data, {}, false, false);
442 }
443
444 /**
445 * Add meta resource for debug.
446 */
447 async _addMetaResourceDebug() {
448 const path = this._metaResourceDebugPath;
449 const data = new Uint8Array(0);
450 await this._addResource(path, data, {}, true, true);
451 }
452
453 /**
454 * Add resource for signatures.
455 */
456 async _addMetaResourceSignatures() {
457 const path = this._metaResourceSignaturesPath;
458 const signature = this._signature;
459 if (!signature) {
460 throw new Error('Internal error');
461 }
462 signature.digest();
463 signature.sign();
464 if (signature.timestampUrl) {
465 await signature.timestamp();
466 }
467 const data = signature.encode();
468 await this._addResource(path, data, {}, false, false);
469 }
470
471 /**
472 * Init application info from descriptor data.
473 *
474 * @param applicationData The application descriptor data.
475 */
476 _applicationInfoInit(applicationData) {
477 // Do nothing.
478 }
479
480 /**
481 * Clear application info from descriptor data.
482 */
483 _applicationInfoClear() {
484 // Do nothing.
485 }
486
487 /**
488 * Package mimetype.
489 *
490 * @returns Mimetype string.
491 */
492
493 /**
494 * Package signed.
495 *
496 * @returns Boolean for if package is signed or not.
497 */
498
499 /**
500 * Open implementation.
501 */
502
503 /**
504 * Close implementation.
505 */
506
507 /**
508 * Write resource with data implementation.
509 *
510 * @param destination Packaged file relative destination.
511 * @param data Resource data.
512 * @param options Resource options.
513 */
514}
515exports.Packager = Packager;
516//# sourceMappingURL=packager.js.map
\No newline at end of file