modern-tar 0.3.0 → 0.3.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 CHANGED
@@ -246,7 +246,7 @@ const entries = [
246
246
  const tarBuffer = await packTar(entries);
247
247
  ```
248
248
 
249
- #### `unpackTar(archive: ArrayBuffer | Uint8Array, options?: UnpackOptions): Promise<ParsedTarEntryWithData[]>`
249
+ #### `unpackTar(archive: ArrayBuffer | Uint8Array | ReadableStream<Uint8Array>, options?: UnpackOptions): Promise<ParsedTarEntryWithData[]>`
250
250
 
251
251
  Extract all entries from a tar archive buffer with optional filtering and transformation.
252
252
 
@@ -287,7 +287,7 @@ const stream2 = controller.add({ name: "file2.txt", size: 4 });
287
287
  controller.finalize();
288
288
  ```
289
289
 
290
- #### `createTarDecoder(): TransformStream<Uint8Array, ParsedTarEntry>`
290
+ #### `createTarDecoder(options?: DecoderOptions): TransformStream<Uint8Array, ParsedTarEntry>`
291
291
 
292
292
  Create a transform stream that parses tar bytes into entries.
293
293
 
@@ -387,6 +387,7 @@ const tarStream = createReadStream('backup.tar');
387
387
  const extractStream = unpackTar('/restore/location', {
388
388
  strip: 1,
389
389
  fmode: 0o644, // Set consistent file permissions
390
+ strict: true, // Enable strict validation
390
391
  });
391
392
  await pipeline(tarStream, extractStream);
392
393
  ```
@@ -455,8 +456,13 @@ interface ParsedTarEntryWithData {
455
456
  }
456
457
 
457
458
  // Platform-neutral configuration for unpacking
458
- interface UnpackOptions {
459
- /** Number of leading path components to strip from entry names (e.g., strip: 1 removes first directory) */
459
+ interface DecoderOptions {
460
+ /** Enable strict validation (e.g., throw on invalid checksums) */
461
+ strict?: boolean;
462
+ }
463
+
464
+ interface UnpackOptions extends DecoderOptions {
465
+ /** Number of leading path components to strip from entry names (e.g., strip: 1 removes first directory) */
460
466
  strip?: number;
461
467
  /** Filter function to include/exclude entries (return false to skip) */
462
468
  filter?: (header: TarHeader) => boolean;
@@ -1,4 +1,4 @@
1
- import { TarEntryData, TarHeader, UnpackOptions } from "../index-C8X7IkYR.js";
1
+ import { TarEntryData, TarHeader, UnpackOptions } from "../index-1Ec89lu7.js";
2
2
  import { Stats } from "node:fs";
3
3
  import { Readable, Writable } from "node:stream";
4
4
 
package/dist/fs/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { BLOCK_SIZE, createTarDecoder, createTarHeader, createTarOptionsTransformer, createTarPacker, encoder, generatePax } from "../web-LcCN87Qy.js";
1
+ import { BLOCK_SIZE, createTarDecoder, createTarHeader, createTarOptionsTransformer, createTarPacker, encoder, generatePax } from "../web-Daso6SHn.js";
2
2
  import { createReadStream, createWriteStream } from "node:fs";
3
3
  import * as fs from "node:fs/promises";
4
4
  import * as path from "node:path";
@@ -269,7 +269,7 @@ function unpackTar(directoryPath, options = {}) {
269
269
  const resolvedDestDir = normalizePath(path.resolve(directoryPath));
270
270
  const validatedDirs = new Set([resolvedDestDir]);
271
271
  await fs.mkdir(resolvedDestDir, { recursive: true });
272
- const reader = readable.pipeThrough(createTarDecoder()).pipeThrough(createTarOptionsTransformer(options)).getReader();
272
+ const reader = readable.pipeThrough(createTarDecoder(options)).pipeThrough(createTarOptionsTransformer(options)).getReader();
273
273
  try {
274
274
  const maxDepth = options.maxDepth ?? 1024;
275
275
  while (true) {
@@ -142,13 +142,26 @@ interface ParsedTarEntryWithData {
142
142
  header: TarHeader;
143
143
  data: Uint8Array;
144
144
  }
145
+ /**
146
+ * Configuration options for creating a tar decoder stream.
147
+ */
148
+ interface DecoderOptions {
149
+ /**
150
+ * Enable strict validation of the tar archive.
151
+ * When true, the decoder will throw errors for data corruption issues:
152
+ * - Invalid checksums (indicates header corruption)
153
+ * - Invalid USTAR magic string (format violation)
154
+ * @default false
155
+ */
156
+ strict?: boolean;
157
+ }
145
158
  /**
146
159
  * Platform-neutral configuration options for extracting tar archives.
147
160
  *
148
161
  * These options work with any tar extraction implementation and are not tied
149
162
  * to specific platforms like Node.js filesystem APIs.
150
163
  */
151
- interface UnpackOptions {
164
+ interface UnpackOptions extends DecoderOptions {
152
165
  /** Number of leading path components to strip from entry names (e.g., strip: 1 removes first directory) */
153
166
  strip?: number;
154
167
  /** Filter function to include/exclude entries (return false to skip) */
@@ -372,6 +385,7 @@ declare function createTarPacker(): {
372
385
  /**
373
386
  * Create a transform stream that parses tar bytes into entries.
374
387
  *
388
+ * @param options - Optional configuration for the decoder using {@link DecoderOptions}.
375
389
  * @returns `TransformStream` that converts tar archive bytes to {@link ParsedTarEntry} objects.
376
390
  * @example
377
391
  * ```typescript
@@ -385,6 +399,6 @@ declare function createTarPacker(): {
385
399
  * // Process entry.body stream as needed
386
400
  * }
387
401
  */
388
- declare function createTarDecoder(): TransformStream<Uint8Array, ParsedTarEntry>;
402
+ declare function createTarDecoder(options?: DecoderOptions): TransformStream<Uint8Array, ParsedTarEntry>;
389
403
  //#endregion
390
- export { type ParsedTarEntry, type ParsedTarEntryWithData, type TarEntry, type TarEntryData, type TarHeader, type TarPackController, type UnpackOptions, createGzipDecoder, createGzipEncoder, createTarDecoder, createTarOptionsTransformer, createTarPacker, packTar, unpackTar };
404
+ export { type DecoderOptions, type ParsedTarEntry, type ParsedTarEntryWithData, type TarEntry, type TarEntryData, type TarHeader, type TarPackController, type UnpackOptions, createGzipDecoder, createGzipEncoder, createTarDecoder, createTarOptionsTransformer, createTarPacker, packTar, unpackTar };
@@ -1,2 +1,2 @@
1
- import { ParsedTarEntry, ParsedTarEntryWithData, TarEntry, TarEntryData, TarHeader, TarPackController, UnpackOptions, createGzipDecoder, createGzipEncoder, createTarDecoder, createTarOptionsTransformer, createTarPacker, packTar, unpackTar } from "../index-C8X7IkYR.js";
2
- export { ParsedTarEntry, ParsedTarEntryWithData, TarEntry, TarEntryData, TarHeader, TarPackController, UnpackOptions, createGzipDecoder, createGzipEncoder, createTarDecoder, createTarOptionsTransformer, createTarPacker, packTar, unpackTar };
1
+ import { DecoderOptions, ParsedTarEntry, ParsedTarEntryWithData, TarEntry, TarEntryData, TarHeader, TarPackController, UnpackOptions, createGzipDecoder, createGzipEncoder, createTarDecoder, createTarOptionsTransformer, createTarPacker, packTar, unpackTar } from "../index-1Ec89lu7.js";
2
+ export { DecoderOptions, ParsedTarEntry, ParsedTarEntryWithData, TarEntry, TarEntryData, TarHeader, TarPackController, UnpackOptions, createGzipDecoder, createGzipEncoder, createTarDecoder, createTarOptionsTransformer, createTarPacker, packTar, unpackTar };
package/dist/web/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { createGzipDecoder, createGzipEncoder, createTarDecoder, createTarOptionsTransformer, createTarPacker, packTar, unpackTar } from "../web-LcCN87Qy.js";
1
+ import { createGzipDecoder, createGzipEncoder, createTarDecoder, createTarOptionsTransformer, createTarPacker, packTar, unpackTar } from "../web-Daso6SHn.js";
2
2
 
3
3
  export { createGzipDecoder, createGzipEncoder, createTarDecoder, createTarOptionsTransformer, createTarPacker, packTar, unpackTar };
@@ -291,7 +291,7 @@ function readString(view, offset, size) {
291
291
  */
292
292
  function readOctal(view, offset, size) {
293
293
  const octalString = readString(view, offset, size).trim();
294
- return octalString ? Number.parseInt(octalString, 8) : 0;
294
+ return octalString ? parseInt(octalString, 8) : 0;
295
295
  }
296
296
  /**
297
297
  * Reads a numeric field that can be octal or POSIX base-256.
@@ -557,6 +557,7 @@ const metaEntryParsers = {
557
557
  /**
558
558
  * Create a transform stream that parses tar bytes into entries.
559
559
  *
560
+ * @param options - Optional configuration for the decoder using {@link DecoderOptions}.
560
561
  * @returns `TransformStream` that converts tar archive bytes to {@link ParsedTarEntry} objects.
561
562
  * @example
562
563
  * ```typescript
@@ -570,7 +571,8 @@ const metaEntryParsers = {
570
571
  * // Process entry.body stream as needed
571
572
  * }
572
573
  */
573
- function createTarDecoder() {
574
+ function createTarDecoder(options = {}) {
575
+ const strict = options.strict ?? false;
574
576
  let buffer = new Uint8Array(0);
575
577
  let currentEntry = null;
576
578
  let paxGlobals = {};
@@ -607,7 +609,7 @@ function createTarDecoder() {
607
609
  return;
608
610
  }
609
611
  }
610
- const header = parseUstarHeader(headerBlock);
612
+ const header = parseUstarHeader(headerBlock, strict);
611
613
  const metaParser = metaEntryParsers[header.type];
612
614
  if (metaParser) {
613
615
  const dataSize = header.size;
@@ -650,18 +652,22 @@ function createTarDecoder() {
650
652
  if (offset > 0) buffer = buffer.slice(offset);
651
653
  },
652
654
  flush(controller) {
653
- if (currentEntry) {
654
- const error = /* @__PURE__ */ new Error("Tar archive is truncated.");
655
+ if (currentEntry) if (strict) {
656
+ const error = /* @__PURE__ */ new Error(`Tar archive is truncated. Expected ${currentEntry.header.size} bytes but received ${currentEntry.header.size - currentEntry.bytesLeft}.`);
655
657
  currentEntry.controller.error(error);
656
658
  controller.error(error);
657
- }
658
- if (buffer.some((b) => b !== 0)) controller.error(/* @__PURE__ */ new Error("Unexpected data at end of archive."));
659
+ } else try {
660
+ currentEntry.controller.close();
661
+ } catch {}
662
+ if (strict && buffer.some((b) => b !== 0)) controller.error(/* @__PURE__ */ new Error("Unexpected data at end of archive."));
659
663
  }
660
664
  });
661
665
  }
662
- function parseUstarHeader(block) {
663
- if (!validateChecksum(block)) throw new Error("Invalid tar header checksum.");
666
+ function parseUstarHeader(block, strict) {
667
+ if (strict && !validateChecksum(block)) throw new Error("Invalid tar header checksum.");
664
668
  const typeflag = readString(block, USTAR.typeflag.offset, USTAR.typeflag.size);
669
+ const magic = readString(block, USTAR.magic.offset, USTAR.magic.size);
670
+ if (strict && magic !== "ustar") throw new Error(`Invalid USTAR magic: expected "ustar", got "${magic}"`);
665
671
  return {
666
672
  name: readString(block, USTAR.name.offset, USTAR.name.size),
667
673
  mode: readOctal(block, USTAR.mode.offset, USTAR.mode.size),
@@ -672,7 +678,7 @@ function parseUstarHeader(block) {
672
678
  checksum: readOctal(block, USTAR.checksum.offset, USTAR.checksum.size),
673
679
  type: FLAGTYPE[typeflag] || "file",
674
680
  linkname: readString(block, USTAR.linkname.offset, USTAR.linkname.size),
675
- magic: readString(block, USTAR.magic.offset, USTAR.magic.size),
681
+ magic,
676
682
  uname: readString(block, USTAR.uname.offset, USTAR.uname.size),
677
683
  gname: readString(block, USTAR.gname.offset, USTAR.gname.size),
678
684
  prefix: readString(block, USTAR.prefix.offset, USTAR.prefix.size)
@@ -685,7 +691,7 @@ function parsePax(buffer) {
685
691
  while (offset < buffer.length) {
686
692
  const spaceIndex = buffer.indexOf(32, offset);
687
693
  if (spaceIndex === -1) break;
688
- const length = Number.parseInt(decoder.decode(buffer.subarray(offset, spaceIndex)), 10);
694
+ const length = parseInt(decoder.decode(buffer.subarray(offset, spaceIndex)), 10);
689
695
  if (Number.isNaN(length) || length === 0) break;
690
696
  const recordEnd = offset + length;
691
697
  const [key, value] = decoder.decode(buffer.subarray(spaceIndex + 1, recordEnd - 1)).split("=", 2);
@@ -699,16 +705,16 @@ function parsePax(buffer) {
699
705
  overrides.linkname = value;
700
706
  break;
701
707
  case "size":
702
- overrides.size = Number.parseInt(value, 10);
708
+ overrides.size = parseInt(value, 10);
703
709
  break;
704
710
  case "mtime":
705
- overrides.mtime = Number.parseFloat(value);
711
+ overrides.mtime = parseFloat(value);
706
712
  break;
707
713
  case "uid":
708
- overrides.uid = Number.parseInt(value, 10);
714
+ overrides.uid = parseInt(value, 10);
709
715
  break;
710
716
  case "gid":
711
- overrides.gid = Number.parseInt(value, 10);
717
+ overrides.gid = parseInt(value, 10);
712
718
  break;
713
719
  case "uname":
714
720
  overrides.uname = value;
@@ -853,7 +859,7 @@ async function unpackTar(archive, options = {}) {
853
859
  controller.close();
854
860
  } });
855
861
  const results = [];
856
- const reader = sourceStream.pipeThrough(createTarDecoder()).pipeThrough(createTarOptionsTransformer(options)).getReader();
862
+ const reader = sourceStream.pipeThrough(createTarDecoder(options)).pipeThrough(createTarOptionsTransformer(options)).getReader();
857
863
  try {
858
864
  while (true) {
859
865
  const { done, value: entry } = await reader.read();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modern-tar",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Zero dependency streaming tar parser and writer for JavaScript.",
5
5
  "author": "Ayuhito <hello@ayuhito.com>",
6
6
  "license": "MIT",