├── test
├── fixtures
│ ├── empty.txt
│ ├── directory
│ │ ├── level0.txt
│ │ └── subdir
│ │ │ ├── level1.txt
│ │ │ └── subsub
│ │ │ └── level2.txt
│ ├── test.txt
│ └── image.png
├── commons.js
├── archive-output-stream.js
├── helpers
│ └── index.js
├── general-purpose-bit.js
├── zip-archive-output-stream.js
└── zip-archive-entry.js
├── .gitignore
├── .prettierignore
├── lib
├── archivers
│ ├── archive-entry.js
│ ├── zip
│ │ ├── unix-stat.js
│ │ ├── util.js
│ │ ├── general-purpose-bit.js
│ │ ├── constants.js
│ │ ├── zip-archive-entry.js
│ │ └── zip-archive-output-stream.js
│ └── archive-output-stream.js
├── compress-commons.js
└── util
│ └── index.js
├── .github
├── workflows
│ ├── release-drafter.yml
│ ├── labels.yml
│ ├── npmpublish.yml
│ └── nodejs.yml
├── release-drafter.yml
└── labels.yml
├── CONTRIBUTING.md
├── renovate.json
├── README.md
├── LICENSE
├── package.json
└── CHANGELOG.md
/test/fixtures/empty.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/directory/level0.txt:
--------------------------------------------------------------------------------
1 | level0
--------------------------------------------------------------------------------
/test/fixtures/test.txt:
--------------------------------------------------------------------------------
1 | this is a text file
--------------------------------------------------------------------------------
/test/fixtures/directory/subdir/level1.txt:
--------------------------------------------------------------------------------
1 | level1
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | npm-debug.log
2 | node_modules/
3 | tmp/
--------------------------------------------------------------------------------
/test/fixtures/directory/subdir/subsub/level2.txt:
--------------------------------------------------------------------------------
1 | level2
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Ignore artifacts:
2 | build
3 | coverage
4 |
--------------------------------------------------------------------------------
/test/fixtures/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/archiverjs/node-compress-commons/HEAD/test/fixtures/image.png
--------------------------------------------------------------------------------
/lib/archivers/archive-entry.js:
--------------------------------------------------------------------------------
1 | export default class ArchiveEntry {
2 | getName() {}
3 | getSize() {}
4 | getLastModifiedDate() {}
5 | isDirectory() {}
6 | }
7 |
--------------------------------------------------------------------------------
/test/commons.js:
--------------------------------------------------------------------------------
1 | import { assert } from "chai";
2 | /*global before,describe,it */
3 |
4 | describe("commons", function () {
5 | // it('will do something some day', function(){
6 | // });
7 | });
8 |
--------------------------------------------------------------------------------
/test/archive-output-stream.js:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import { assert } from "chai";
3 | import { binaryBuffer } from "./helpers/index.js";
4 |
5 | var testBuffer = binaryBuffer(1024 * 16);
6 | describe("ArchiveOutputStream", function () {
7 | describe("#entry", function () {});
8 | });
9 |
--------------------------------------------------------------------------------
/.github/workflows/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name: Release Drafter
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | update_release_draft:
10 | name: Update Release Draft
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: release-drafter/release-drafter@v6
14 | env:
15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
16 |
--------------------------------------------------------------------------------
/lib/archivers/zip/unix-stat.js:
--------------------------------------------------------------------------------
1 | export const PERM_MASK = 4095;
2 | export const FILE_TYPE_FLAG = 61440;
3 | export const LINK_FLAG = 40960;
4 | export const FILE_FLAG = 32768;
5 | export const DIR_FLAG = 16384;
6 | export const DEFAULT_LINK_PERM = 511;
7 | export const DEFAULT_DIR_PERM = 493;
8 | export const DEFAULT_FILE_PERM = 420; // 0644
9 |
10 | export default {
11 | PERM_MASK,
12 | FILE_TYPE_FLAG,
13 | LINK_FLAG,
14 | FILE_FLAG,
15 | DIR_FLAG,
16 | DEFAULT_LINK_PERM,
17 | DEFAULT_DIR_PERM,
18 | DEFAULT_FILE_PERM,
19 | };
20 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contributing
2 |
3 | #### Code Style Guide
4 |
5 | - code should be indented with 2 spaces
6 | - single quotes should be used where feasible
7 | - commas should be followed by a single space (function params, etc)
8 | - variable declaration should include `var`, [no multiple declarations](http://benalman.com/news/2012/05/multiple-var-statements-javascript/)
9 |
10 | #### Tests
11 |
12 | - tests should be added to the nodeunit configs in `tests/`
13 | - tests can be run with `npm test`
14 | - see existing tests for guidance
15 |
--------------------------------------------------------------------------------
/lib/compress-commons.js:
--------------------------------------------------------------------------------
1 | import ArchiveEntry from "./archivers/archive-entry.js";
2 | import ZipArchiveEntry from "./archivers/zip/zip-archive-entry.js";
3 | import ArchiveOutputStream from "./archivers/archive-output-stream.js";
4 | import ZipArchiveOutputStream from "./archivers/zip/zip-archive-output-stream.js";
5 |
6 | export {
7 | ArchiveEntry,
8 | ZipArchiveEntry,
9 | ArchiveOutputStream,
10 | ZipArchiveOutputStream,
11 | };
12 |
13 | export default {
14 | ArchiveEntry,
15 | ZipArchiveEntry,
16 | ArchiveOutputStream,
17 | ZipArchiveOutputStream,
18 | };
19 |
--------------------------------------------------------------------------------
/.github/workflows/labels.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Sync labels
3 |
4 | # yamllint disable-line rule:truthy
5 | on:
6 | push:
7 | branches:
8 | - main
9 | paths:
10 | - .github/labels.yml
11 | workflow_dispatch:
12 |
13 | jobs:
14 | labels:
15 | name: Sync labels
16 | runs-on: ubuntu-latest
17 | steps:
18 | - name: Check out code from GitHub
19 | uses: actions/checkout@v4
20 | - name: Run Label Syncer
21 | uses: micnncim/action-label-syncer@v1.3.0
22 | env:
23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24 |
--------------------------------------------------------------------------------
/lib/util/index.js:
--------------------------------------------------------------------------------
1 | import { Stream } from "stream";
2 | import { PassThrough } from "readable-stream";
3 | import { isStream } from "is-stream";
4 |
5 | export function normalizeInputSource(source) {
6 | if (source === null) {
7 | return Buffer.alloc(0);
8 | } else if (typeof source === "string") {
9 | return Buffer.from(source);
10 | } else if (isStream(source) && !source._readableState) {
11 | var normalized = new PassThrough();
12 | source.pipe(normalized);
13 | return normalized;
14 | }
15 | return source;
16 | }
17 |
18 | export default {
19 | normalizeInputSource,
20 | };
21 |
--------------------------------------------------------------------------------
/.github/workflows/npmpublish.yml:
--------------------------------------------------------------------------------
1 | name: Node Publish Package
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v4.2.1
12 | - uses: actions/setup-node@v4.0.4
13 | with:
14 | node-version: 20
15 | - run: npm ci
16 | - run: npm test
17 |
18 | publish-npm:
19 | needs: build
20 | runs-on: ubuntu-latest
21 | steps:
22 | - uses: actions/checkout@v4.2.1
23 | - uses: actions/setup-node@v4.0.4
24 | with:
25 | node-version: 20
26 | registry-url: https://registry.npmjs.org/
27 | - run: npm ci
28 | - run: npm publish
29 | env:
30 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
31 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": ["config:base", "schedule:daily", ":pinDevDependencies"],
4 | "timezone": "America/Chicago",
5 | "labels": ["dependencies"],
6 | "packageRules": [
7 | {
8 | "matchManagers": ["npm", "nvm"],
9 | "separateMultipleMajor": true
10 | },
11 | {
12 | "matchManagers": ["npm", "nvm"],
13 | "matchUpdateTypes": ["minor", "patch"],
14 | "automerge": true
15 | },
16 | {
17 | "matchManagers": ["github-actions"],
18 | "addLabels": ["ci", "github-actions"],
19 | "rangeStrategy": "pin"
20 | },
21 | {
22 | "matchManagers": ["github-actions"],
23 | "matchUpdateTypes": ["minor", "patch"],
24 | "automerge": true
25 | }
26 | ],
27 | "constraints": {
28 | "npm": "<9"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | name: Node CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - master
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 |
15 | strategy:
16 | matrix:
17 | node-version: [18.x, 20.x]
18 |
19 | steps:
20 | - uses: actions/checkout@v4.2.1
21 | - name: Use Node.js ${{ matrix.node-version }}
22 | uses: actions/setup-node@v4.0.4
23 | with:
24 | node-version: ${{ matrix.node-version }}
25 | - name: npm install and test
26 | run: |
27 | npm ci
28 | npm test
29 | env:
30 | CI: true
31 | - name: Archive production artifacts
32 | uses: actions/upload-artifact@v4
33 | with:
34 | name: tmp-zip-node-v${{ matrix.node-version }}
35 | path: tmp/*.zip
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Compress Commons
2 |
3 | Compress Commons is a library that defines a common interface for working with archive formats within node.
4 |
5 | [](https://nodei.co/npm/compress-commons/)
6 |
7 | ## Install
8 |
9 | ```bash
10 | npm install compress-commons --save
11 | ```
12 |
13 | You can also use `npm install https://github.com/archiverjs/node-compress-commons/archive/master.tar.gz` to test upcoming versions.
14 |
15 | ## Things of Interest
16 |
17 | - [Changelog](https://github.com/archiverjs/node-compress-commons/releases)
18 | - [Contributing](https://github.com/archiverjs/node-compress-commons/blob/master/CONTRIBUTING.md)
19 | - [MIT License](https://github.com/archiverjs/node-compress-commons/blob/master/LICENSE-MIT)
20 |
21 | ## Credits
22 |
23 | Concept inspired by [Apache Commons Compress](http://commons.apache.org/proper/commons-compress/)™.
24 |
25 | Some logic derived from [Apache Commons Compress](http://commons.apache.org/proper/commons-compress/)™ and [OpenJDK 7](http://openjdk.java.net/).
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Chris Talkington, contributors.
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name-template: "$RESOLVED_VERSION"
3 | tag-template: "$RESOLVED_VERSION"
4 | change-template: "- $TITLE @$AUTHOR (#$NUMBER)"
5 | sort-direction: ascending
6 |
7 | categories:
8 | - title: "Breaking changes"
9 | labels:
10 | - "breaking-change"
11 | - title: "New features"
12 | labels:
13 | - "new-feature"
14 | - title: "Bug fixes"
15 | labels:
16 | - "bugfix"
17 | - title: "Enhancements"
18 | labels:
19 | - "enhancement"
20 | - "refactor"
21 | - "performance"
22 | - title: "Maintenance"
23 | labels:
24 | - "maintenance"
25 | - "ci"
26 | - title: "Documentation"
27 | labels:
28 | - "documentation"
29 | - title: "Dependency updates"
30 | labels:
31 | - "dependencies"
32 |
33 | version-resolver:
34 | major:
35 | labels:
36 | - "major"
37 | - "breaking-change"
38 | minor:
39 | labels:
40 | - "minor"
41 | - "new-feature"
42 | patch:
43 | labels:
44 | - "bugfix"
45 | - "chore"
46 | - "ci"
47 | - "dependencies"
48 | - "documentation"
49 | - "enhancement"
50 | - "performance"
51 | - "refactor"
52 | default: patch
53 |
54 | template: |
55 | ## What’s changed
56 |
57 | $CHANGES
58 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "compress-commons",
3 | "version": "7.0.0",
4 | "description": "a library that defines a common interface for working with archive formats within node",
5 | "homepage": "https://github.com/archiverjs/node-compress-commons",
6 | "author": {
7 | "name": "Chris Talkington",
8 | "url": "http://christalkington.com/"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/archiverjs/node-compress-commons.git"
13 | },
14 | "bugs": {
15 | "url": "https://github.com/archiverjs/node-compress-commons/issues"
16 | },
17 | "license": "MIT",
18 | "type": "module",
19 | "exports": "./lib/compress-commons.js",
20 | "files": [
21 | "lib"
22 | ],
23 | "engines": {
24 | "node": ">=18"
25 | },
26 | "scripts": {
27 | "test": "mocha --reporter dot"
28 | },
29 | "dependencies": {
30 | "crc-32": "^1.2.0",
31 | "crc32-stream": "^7.0.1",
32 | "is-stream": "^4.0.0",
33 | "normalize-path": "^3.0.0",
34 | "readable-stream": "^4.0.0"
35 | },
36 | "devDependencies": {
37 | "chai": "5.1.1",
38 | "mkdirp": "3.0.1",
39 | "mocha": "10.7.3",
40 | "prettier": "3.3.3",
41 | "rimraf": "5.0.10"
42 | },
43 | "keywords": [
44 | "compress",
45 | "commons",
46 | "archive"
47 | ]
48 | }
49 |
--------------------------------------------------------------------------------
/test/helpers/index.js:
--------------------------------------------------------------------------------
1 | import crypto from "crypto";
2 | import { WriteStream } from "fs";
3 | import { inherits } from "util";
4 | import { Stream } from "stream";
5 | import { Readable } from "readable-stream";
6 | import { Writable } from "readable-stream";
7 |
8 | export function binaryBuffer(n) {
9 | var buffer = Buffer.alloc(n);
10 | for (var i = 0; i < n; i++) {
11 | buffer.writeUInt8(i & 255, i);
12 | }
13 | return buffer;
14 | }
15 |
16 | export class BinaryStream extends Readable {
17 | constructor(size, options) {
18 | super(options);
19 | var buf = Buffer.alloc(size);
20 | for (var i = 0; i < size; i++) {
21 | buf.writeUInt8(i & 255, i);
22 | }
23 | this.push(buf);
24 | this.push(null);
25 | }
26 |
27 | _read(size) {}
28 | }
29 |
30 | export class DeadEndStream extends Writable {
31 | constructor(options) {
32 | super(options);
33 | }
34 |
35 | _write(chuck, encoding, callback) {
36 | callback();
37 | }
38 | }
39 |
40 | export function fileBuffer(filepath) {
41 | return fs.readFileSync(filepath);
42 | }
43 |
44 | export class UnBufferedStream extends Stream {
45 | constructor() {
46 | super();
47 | this.readable = true;
48 | }
49 | }
50 |
51 | export class WriteHashStream extends WriteStream {
52 | constructor(path, options) {
53 | super(path, options);
54 | this.hash = crypto.createHash("sha1");
55 | this.digest = null;
56 | this.on("close", function () {
57 | this.digest = this.hash.digest("hex");
58 | });
59 | }
60 |
61 | write(chunk) {
62 | if (chunk) {
63 | this.hash.update(chunk);
64 | }
65 | return super.write(chunk);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/test/general-purpose-bit.js:
--------------------------------------------------------------------------------
1 | import { assert } from "chai";
2 | import GeneralPurposeBit from "../lib/archivers/zip/general-purpose-bit.js";
3 | /*global before,describe,it */
4 |
5 | var gpb;
6 | describe("GeneralPurposeBit", function () {
7 | beforeEach(function () {
8 | gpb = new GeneralPurposeBit();
9 | });
10 | describe("#encode", function () {
11 | it("should return a Buffer", function () {
12 | gpb.useDataDescriptor();
13 | assert.ok(Buffer.isBuffer(gpb.encode()));
14 | });
15 | });
16 | describe("#parse", function () {
17 | it.skip("should be tested", function () {});
18 | });
19 | describe("#setNumberOfShannonFanoTrees", function () {
20 | it.skip("should be tested", function () {});
21 | });
22 | describe("#getNumberOfShannonFanoTrees", function () {
23 | it.skip("should be tested", function () {});
24 | });
25 | describe("#setSlidingDictionarySize", function () {
26 | it.skip("should be tested", function () {});
27 | });
28 | describe("#getSlidingDictionarySize", function () {
29 | it.skip("should be tested", function () {});
30 | });
31 | describe("#useDataDescriptor", function () {
32 | it.skip("should be tested", function () {});
33 | });
34 | describe("#usesDataDescriptor", function () {
35 | it.skip("should be tested", function () {});
36 | });
37 | describe("#useEncryption", function () {
38 | it.skip("should be tested", function () {});
39 | });
40 | describe("#usesEncryption", function () {
41 | it.skip("should be tested", function () {});
42 | });
43 | describe("#useStrongEncryption", function () {
44 | it.skip("should be tested", function () {});
45 | });
46 | describe("#usesStrongEncryption", function () {
47 | it.skip("should be tested", function () {});
48 | });
49 | describe("#useUTF8ForNames", function () {
50 | it.skip("should be tested", function () {});
51 | });
52 | describe("#usesUTF8ForNames", function () {
53 | it.skip("should be tested", function () {});
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/lib/archivers/zip/util.js:
--------------------------------------------------------------------------------
1 | export function dateToDos(d, forceLocalTime) {
2 | forceLocalTime = forceLocalTime || false;
3 | var year = forceLocalTime ? d.getFullYear() : d.getUTCFullYear();
4 | if (year < 1980) {
5 | return 2162688; // 1980-1-1 00:00:00
6 | } else if (year >= 2044) {
7 | return 2141175677; // 2043-12-31 23:59:58
8 | }
9 | var val = {
10 | year: year,
11 | month: forceLocalTime ? d.getMonth() : d.getUTCMonth(),
12 | date: forceLocalTime ? d.getDate() : d.getUTCDate(),
13 | hours: forceLocalTime ? d.getHours() : d.getUTCHours(),
14 | minutes: forceLocalTime ? d.getMinutes() : d.getUTCMinutes(),
15 | seconds: forceLocalTime ? d.getSeconds() : d.getUTCSeconds(),
16 | };
17 | return (
18 | ((val.year - 1980) << 25) |
19 | ((val.month + 1) << 21) |
20 | (val.date << 16) |
21 | (val.hours << 11) |
22 | (val.minutes << 5) |
23 | (val.seconds / 2)
24 | );
25 | }
26 | export function dosToDate(dos) {
27 | return new Date(
28 | ((dos >> 25) & 0x7f) + 1980,
29 | ((dos >> 21) & 0x0f) - 1,
30 | (dos >> 16) & 0x1f,
31 | (dos >> 11) & 0x1f,
32 | (dos >> 5) & 0x3f,
33 | (dos & 0x1f) << 1,
34 | );
35 | }
36 | export function fromDosTime(buf) {
37 | return dosToDate(buf.readUInt32LE(0));
38 | }
39 | export function getEightBytes(v) {
40 | var buf = Buffer.alloc(8);
41 | buf.writeUInt32LE(v % 0x0100000000, 0);
42 | buf.writeUInt32LE((v / 0x0100000000) | 0, 4);
43 | return buf;
44 | }
45 | export function getShortBytes(v) {
46 | var buf = Buffer.alloc(2);
47 | buf.writeUInt16LE((v & 0xffff) >>> 0, 0);
48 | return buf;
49 | }
50 | export function getShortBytesValue(buf, offset) {
51 | return buf.readUInt16LE(offset);
52 | }
53 | export function getLongBytes(v) {
54 | var buf = Buffer.alloc(4);
55 | buf.writeUInt32LE((v & 0xffffffff) >>> 0, 0);
56 | return buf;
57 | }
58 | export function getLongBytesValue(buf, offset) {
59 | return buf.readUInt32LE(offset);
60 | }
61 | export function toDosTime(d) {
62 | return getLongBytes(util.dateToDos(d));
63 | }
64 | export default {
65 | dateToDos,
66 | dosToDate,
67 | fromDosTime,
68 | getEightBytes,
69 | getShortBytes,
70 | getShortBytesValue,
71 | getLongBytes,
72 | getLongBytesValue,
73 | toDosTime,
74 | };
75 |
--------------------------------------------------------------------------------
/lib/archivers/zip/general-purpose-bit.js:
--------------------------------------------------------------------------------
1 | import { getShortBytes, getShortBytesValue } from "./util.js";
2 |
3 | var DATA_DESCRIPTOR_FLAG = 1 << 3;
4 | var ENCRYPTION_FLAG = 1 << 0;
5 | var NUMBER_OF_SHANNON_FANO_TREES_FLAG = 1 << 2;
6 | var SLIDING_DICTIONARY_SIZE_FLAG = 1 << 1;
7 | var STRONG_ENCRYPTION_FLAG = 1 << 6;
8 | var UFT8_NAMES_FLAG = 1 << 11;
9 |
10 | export default class GeneralPurposeBit {
11 | constructor() {
12 | this.descriptor = false;
13 | this.encryption = false;
14 | this.utf8 = false;
15 | this.numberOfShannonFanoTrees = 0;
16 | this.strongEncryption = false;
17 | this.slidingDictionarySize = 0;
18 | return this;
19 | }
20 | encode() {
21 | return getShortBytes(
22 | (this.descriptor ? DATA_DESCRIPTOR_FLAG : 0) |
23 | (this.utf8 ? UFT8_NAMES_FLAG : 0) |
24 | (this.encryption ? ENCRYPTION_FLAG : 0) |
25 | (this.strongEncryption ? STRONG_ENCRYPTION_FLAG : 0),
26 | );
27 | }
28 | static parse(buf, offset) {
29 | var flag = getShortBytesValue(buf, offset);
30 | var gbp = new GeneralPurposeBit();
31 | gbp.useDataDescriptor((flag & DATA_DESCRIPTOR_FLAG) !== 0);
32 | gbp.useUTF8ForNames((flag & UFT8_NAMES_FLAG) !== 0);
33 | gbp.useStrongEncryption((flag & STRONG_ENCRYPTION_FLAG) !== 0);
34 | gbp.useEncryption((flag & ENCRYPTION_FLAG) !== 0);
35 | gbp.setSlidingDictionarySize(
36 | (flag & SLIDING_DICTIONARY_SIZE_FLAG) !== 0 ? 8192 : 4096,
37 | );
38 | gbp.setNumberOfShannonFanoTrees(
39 | (flag & NUMBER_OF_SHANNON_FANO_TREES_FLAG) !== 0 ? 3 : 2,
40 | );
41 | return gbp;
42 | }
43 | setNumberOfShannonFanoTrees(n) {
44 | this.numberOfShannonFanoTrees = n;
45 | }
46 | getNumberOfShannonFanoTrees() {
47 | return this.numberOfShannonFanoTrees;
48 | }
49 | setSlidingDictionarySize(n) {
50 | this.slidingDictionarySize = n;
51 | }
52 | getSlidingDictionarySize() {
53 | return this.slidingDictionarySize;
54 | }
55 | useDataDescriptor(b) {
56 | this.descriptor = b;
57 | }
58 | usesDataDescriptor() {
59 | return this.descriptor;
60 | }
61 | useEncryption(b) {
62 | this.encryption = b;
63 | }
64 | usesEncryption() {
65 | return this.encryption;
66 | }
67 | useStrongEncryption(b) {
68 | this.strongEncryption = b;
69 | }
70 | usesStrongEncryption() {
71 | return this.strongEncryption;
72 | }
73 | useUTF8ForNames(b) {
74 | this.utf8 = b;
75 | }
76 | usesUTF8ForNames() {
77 | return this.utf8;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/lib/archivers/archive-output-stream.js:
--------------------------------------------------------------------------------
1 | import { inherits } from "util";
2 | import { isStream } from "is-stream";
3 | import { Transform } from "readable-stream";
4 | import ArchiveEntry from "./archive-entry.js";
5 | import { normalizeInputSource } from "../util/index.js";
6 |
7 | export default class ArchiveOutputStream extends Transform {
8 | constructor(options) {
9 | super(options);
10 |
11 | this.offset = 0;
12 | this._archive = {
13 | finish: false,
14 | finished: false,
15 | processing: false,
16 | };
17 | }
18 |
19 | _appendBuffer(zae, source, callback) {
20 | // scaffold only
21 | }
22 |
23 | _appendStream(zae, source, callback) {
24 | // scaffold only
25 | }
26 |
27 | _emitErrorCallback = function (err) {
28 | if (err) {
29 | this.emit("error", err);
30 | }
31 | };
32 |
33 | _finish(ae) {
34 | // scaffold only
35 | }
36 |
37 | _normalizeEntry(ae) {
38 | // scaffold only
39 | }
40 |
41 | _transform(chunk, encoding, callback) {
42 | callback(null, chunk);
43 | }
44 |
45 | entry(ae, source, callback) {
46 | source = source || null;
47 | if (typeof callback !== "function") {
48 | callback = this._emitErrorCallback.bind(this);
49 | }
50 | if (!(ae instanceof ArchiveEntry)) {
51 | callback(new Error("not a valid instance of ArchiveEntry"));
52 | return;
53 | }
54 | if (this._archive.finish || this._archive.finished) {
55 | callback(new Error("unacceptable entry after finish"));
56 | return;
57 | }
58 | if (this._archive.processing) {
59 | callback(new Error("already processing an entry"));
60 | return;
61 | }
62 | this._archive.processing = true;
63 | this._normalizeEntry(ae);
64 | this._entry = ae;
65 | source = normalizeInputSource(source);
66 | if (Buffer.isBuffer(source)) {
67 | this._appendBuffer(ae, source, callback);
68 | } else if (isStream(source)) {
69 | this._appendStream(ae, source, callback);
70 | } else {
71 | this._archive.processing = false;
72 | callback(
73 | new Error("input source must be valid Stream or Buffer instance"),
74 | );
75 | return;
76 | }
77 | return this;
78 | }
79 |
80 | finish() {
81 | if (this._archive.processing) {
82 | this._archive.finish = true;
83 | return;
84 | }
85 | this._finish();
86 | }
87 |
88 | getBytesWritten() {
89 | return this.offset;
90 | }
91 |
92 | write(chunk, cb) {
93 | if (chunk) {
94 | this.offset += chunk.length;
95 | }
96 | return super.write(chunk, cb);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/.github/labels.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: "breaking-change"
3 | color: ee0701
4 | description: "A breaking change for existing users."
5 | - name: "bugfix"
6 | color: ee0701
7 | description: "Inconsistencies or issues which will cause a problem for users or implementors."
8 | - name: "documentation"
9 | color: 0052cc
10 | description: "Solely about the documentation of the project."
11 | - name: "enhancement"
12 | color: 1d76db
13 | description: "Enhancement of the code, not introducing new features."
14 | - name: "refactor"
15 | color: 1d76db
16 | description: "Improvement of existing code, not introducing new features."
17 | - name: "performance"
18 | color: 1d76db
19 | description: "Improving performance, not introducing new features."
20 | - name: "new-feature"
21 | color: 0e8a16
22 | description: "New features or options."
23 | - name: "maintenance"
24 | color: 2af79e
25 | description: "Generic maintenance tasks."
26 | - name: "ci"
27 | color: 1d76db
28 | description: "Work that improves the continue integration."
29 | - name: "dependencies"
30 | color: 1d76db
31 | description: "Upgrade or downgrade of project dependencies."
32 |
33 | - name: "in-progress"
34 | color: fbca04
35 | description: "Issue is currently being resolved by a developer."
36 | - name: "stale"
37 | color: fef2c0
38 | description: "There has not been activity on this issue or PR for quite some time."
39 | - name: "no-stale"
40 | color: fef2c0
41 | description: "This issue or PR is exempted from the stable bot."
42 |
43 | - name: "security"
44 | color: ee0701
45 | description: "Marks a security issue that needs to be resolved asap."
46 | - name: "incomplete"
47 | color: fef2c0
48 | description: "Marks a PR or issue that is missing information."
49 | - name: "invalid"
50 | color: fef2c0
51 | description: "Marks a PR or issue that is missing information."
52 |
53 | - name: "beginner-friendly"
54 | color: 0e8a16
55 | description: "Good first issue for people wanting to contribute to the project."
56 | - name: "help-wanted"
57 | color: 0e8a16
58 | description: "We need some extra helping hands or expertise in order to resolve this."
59 |
60 | - name: "priority-critical"
61 | color: ee0701
62 | description: "This should be dealt with ASAP. Not fixing this issue would be a serious error."
63 | - name: "priority-high"
64 | color: b60205
65 | description: "After critical issues are fixed, these should be dealt with before any further issues."
66 | - name: "priority-medium"
67 | color: 0e8a16
68 | description: "This issue may be useful, and needs some attention."
69 | - name: "priority-low"
70 | color: e4ea8a
71 | description: "Nice addition, maybe... someday..."
72 |
73 | - name: "major"
74 | color: b60205
75 | description: "This PR causes a major version bump in the version number."
76 | - name: "minor"
77 | color: 0e8a16
78 | description: "This PR causes a minor version bump in the version number."
79 |
--------------------------------------------------------------------------------
/lib/archivers/zip/constants.js:
--------------------------------------------------------------------------------
1 | export const WORD = 4;
2 | export const DWORD = 8;
3 | export const EMPTY = Buffer.alloc(0);
4 | export const SHORT = 2;
5 | export const SHORT_MASK = 0xffff;
6 | export const SHORT_SHIFT = 16;
7 | export const SHORT_ZERO = Buffer.from(Array(2));
8 | export const LONG = 4;
9 | export const LONG_ZERO = Buffer.from(Array(4));
10 | export const MIN_VERSION_INITIAL = 10;
11 | export const MIN_VERSION_DATA_DESCRIPTOR = 20;
12 | export const MIN_VERSION_ZIP64 = 45;
13 | export const VERSION_MADEBY = 45;
14 | export const METHOD_STORED = 0;
15 | export const METHOD_DEFLATED = 8;
16 | export const PLATFORM_UNIX = 3;
17 | export const PLATFORM_FAT = 0;
18 | export const SIG_LFH = 0x04034b50;
19 | export const SIG_DD = 0x08074b50;
20 | export const SIG_CFH = 0x02014b50;
21 | export const SIG_EOCD = 0x06054b50;
22 | export const SIG_ZIP64_EOCD = 0x06064b50;
23 | export const SIG_ZIP64_EOCD_LOC = 0x07064b50;
24 | export const ZIP64_MAGIC_SHORT = 0xffff;
25 | export const ZIP64_MAGIC = 0xffffffff;
26 | export const ZIP64_EXTRA_ID = 0x0001;
27 | export const ZLIB_NO_COMPRESSION = 0;
28 | export const ZLIB_BEST_SPEED = 1;
29 | export const ZLIB_BEST_COMPRESSION = 9;
30 | export const ZLIB_DEFAULT_COMPRESSION = -1;
31 | export const MODE_MASK = 0xfff;
32 | export const DEFAULT_FILE_MODE = 33188;
33 | export const DEFAULT_DIR_MODE = 16877;
34 | export const EXT_FILE_ATTR_DIR = 1106051088;
35 | export const EXT_FILE_ATTR_FILE = 2175008800;
36 | export const S_IFMT = 61440;
37 | export const S_IFIFO = 4096;
38 | export const S_IFCHR = 8192;
39 | export const S_IFDIR = 16384;
40 | export const S_IFBLK = 24576;
41 | export const S_IFREG = 32768;
42 | export const S_IFLNK = 40960;
43 | export const S_IFSOCK = 49152;
44 | export const S_DOS_A = 32;
45 | export const S_DOS_D = 16;
46 | export const S_DOS_V = 8;
47 | export const S_DOS_S = 4;
48 | export const S_DOS_H = 2;
49 | export const S_DOS_R = 1; // 01 Read Only
50 | export default {
51 | WORD,
52 | DWORD,
53 | EMPTY,
54 | SHORT,
55 | SHORT_MASK,
56 | SHORT_SHIFT,
57 | SHORT_ZERO,
58 | LONG,
59 | LONG_ZERO,
60 | MIN_VERSION_INITIAL,
61 | MIN_VERSION_DATA_DESCRIPTOR,
62 | MIN_VERSION_ZIP64,
63 | VERSION_MADEBY,
64 | METHOD_STORED,
65 | METHOD_DEFLATED,
66 | PLATFORM_UNIX,
67 | PLATFORM_FAT,
68 | SIG_LFH,
69 | SIG_DD,
70 | SIG_CFH,
71 | SIG_EOCD,
72 | SIG_ZIP64_EOCD,
73 | SIG_ZIP64_EOCD_LOC,
74 | ZIP64_MAGIC_SHORT,
75 | ZIP64_MAGIC,
76 | ZIP64_EXTRA_ID,
77 | ZLIB_NO_COMPRESSION,
78 | ZLIB_BEST_SPEED,
79 | ZLIB_BEST_COMPRESSION,
80 | ZLIB_DEFAULT_COMPRESSION,
81 | MODE_MASK,
82 | DEFAULT_FILE_MODE,
83 | DEFAULT_DIR_MODE,
84 | EXT_FILE_ATTR_DIR,
85 | EXT_FILE_ATTR_FILE,
86 | S_IFMT,
87 | S_IFIFO,
88 | S_IFCHR,
89 | S_IFDIR,
90 | S_IFBLK,
91 | S_IFREG,
92 | S_IFLNK,
93 | S_IFSOCK,
94 | S_DOS_A,
95 | S_DOS_D,
96 | S_DOS_V,
97 | S_DOS_S,
98 | S_DOS_H,
99 | S_DOS_R,
100 | };
101 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## Changelog
2 |
3 | **7.0.0** — _October 13, 2024_ — [Diff](https://github.com/archiverjs/node-compress-commons/compare/6.0.2...7.0.0)
4 |
5 | **6.0.2** — _March 9, 2024_ — [Diff](https://github.com/archiverjs/node-compress-commons/compare/6.0.1...6.0.2)
6 |
7 | **6.0.1** — _February 29, 2024_ — [Diff](https://github.com/archiverjs/node-compress-commons/compare/6.0.0...6.0.1)
8 |
9 | **6.0.0** — _February 26, 2024_ — [Diff](https://github.com/archiverjs/node-compress-commons/compare/5.0.2...6.0.0)
10 |
11 | **5.0.2** — _February 26, 2024_ — [Diff](https://github.com/archiverjs/node-compress-commons/compare/5.0.1...5.0.2)
12 |
13 | **5.0.1** — _September 3, 2023_ — [Diff](https://github.com/archiverjs/node-compress-commons/compare/5.0.0...5.0.1)
14 |
15 | **5.0.0** — _September 2, 2023_ — [Diff](https://github.com/archiverjs/node-compress-commons/compare/4.1.2...5.0.0)
16 |
17 | **4.1.2** — _September 2, 2023_ — [Diff](https://github.com/archiverjs/node-compress-commons/compare/4.1.1...4.1.2)
18 |
19 | **4.1.1** — _May 30th, 2021_ — [Diff](https://github.com/archiverjs/node-compress-commons/compare/4.1.0...4.1.1)
20 |
21 | ### Maintenance
22 |
23 | - Bump mocha from 8.2.1 to 8.4.0 (#70)
24 | - Bump crc32-stream from 4.0.1 to 4.0.2 (#59)
25 | - Bump y18n from 4.0.0 to 4.0.1 (#69)
26 | - Bump chai from 4.2.0 to 4.3.4 (#67)
27 | - Bump actions/setup-node from 2.1.4 to 2.1.5 (#71)
28 |
29 | **4.1.0** — _March 2, 2021_ — [Diff](https://github.com/archiverjs/node-compress-commons/compare/4.0.1...4.1.0)
30 |
31 | ### Features
32 |
33 | - Allow prepending forward slash in entry name (#63)
34 |
35 | ### Maintenance
36 |
37 | - Bump actions/setup-node from v2.1.2 to v2.1.4 (#58)
38 |
39 | **4.0.1** — _July 20, 2020_ — [Diff](https://github.com/archiverjs/node-compress-commons/compare/4.0.0...4.0.1)
40 |
41 | - Bump crc32-stream from 3.0.1 to 4.0.0 (#43) @dependabot
42 |
43 | **4.0.0** — _July 18, 2020_ — [Diff](https://github.com/archiverjs/node-compress-commons/compare/3.0.0...4.0.0)
44 |
45 | - Bump mocha from 5.2.0 to 8.0.1 (#36) @dependabot
46 | - Bump readable-stream from 2.3.7 to 3.6.0 (#39) @dependabot
47 | - Bump actions/setup-node from v1 to v2.1.0 (#41) @dependabot
48 | - Bump rimraf from 2.7.1 to 3.0.2 (#38) @dependabot
49 | - Bump mkdirp from 0.5.5 to 1.0.4 (#37) @dependabot
50 | - Bump actions/checkout from v1 to v2.3.1 (#40) @dependabot
51 | - remove support for node < 10 (#42) @ctalkington
52 |
53 | **3.0.0** — _April 14, 2020_ — [Diff](https://github.com/archiverjs/node-compress-commons/compare/2.1.1...3.0.0)
54 |
55 | - breaking: slowly catch up with node LTS, remove support for versions under 8.
56 | - update multiple deps.
57 |
58 | **2.1.1** — _August 2, 2019_ — [Diff](https://github.com/archiverjs/node-compress-commons/compare/2.1.0...2.1.1)
59 |
60 | - update crc32-stream to v3.0.1
61 |
62 | **2.1.0** — _August 2, 2019_ — [Diff](https://github.com/archiverjs/node-compress-commons/compare/2.0.0...2.1.0)
63 |
64 | - update crc32-stream to v3.0.0
65 |
66 | **2.0.0** — _July 19, 2019_ — [Diff](https://github.com/archiverjs/node-compress-commons/compare/1.2.2...2.0.0)
67 |
68 | - breaking: follow node LTS, remove support for versions under 6.
69 | - test: now targeting node v10 and v12
70 | - fix: update Buffer calls to alloc/from
71 | - fix: Add offset to buffer call (#31)
72 | - other: update normalize-path@3 (#34)
73 | - other: update dependencies
74 |
75 | [Release Archive](https://github.com/archiverjs/node-compress-commons/releases)
76 |
--------------------------------------------------------------------------------
/test/zip-archive-output-stream.js:
--------------------------------------------------------------------------------
1 | import { createReadStream } from "fs";
2 | import { Stream, Transform } from "stream";
3 | import { assert } from "chai";
4 | import { mkdirp } from "mkdirp";
5 | import { Readable } from "readable-stream";
6 | import { WriteHashStream, binaryBuffer } from "./helpers/index.js";
7 | import {
8 | ZipArchiveEntry,
9 | ZipArchiveOutputStream,
10 | } from "../lib/compress-commons.js";
11 |
12 | var testBuffer = binaryBuffer(1024 * 16);
13 | var testDate = new Date("Jan 03 2013 14:26:38 GMT");
14 |
15 | describe("ZipArchiveOutputStream", function () {
16 | before(function () {
17 | mkdirp.sync("tmp");
18 | });
19 | describe("#entry", function () {
20 | it("should append Buffer sources", function (done) {
21 | var archive = new ZipArchiveOutputStream();
22 | var testStream = new WriteHashStream("tmp/zip-buffer.zip");
23 | var entry = new ZipArchiveEntry("buffer.txt");
24 | testStream.on("close", function () {
25 | done();
26 | });
27 | archive.pipe(testStream);
28 | archive.entry(entry, testBuffer).finish();
29 | });
30 | it("should append Stream sources", function (done) {
31 | var archive = new ZipArchiveOutputStream();
32 | var testStream = new WriteHashStream("tmp/zip-stream.zip");
33 | var entry = new ZipArchiveEntry("stream.txt");
34 | testStream.on("close", function () {
35 | done();
36 | });
37 | archive.pipe(testStream);
38 | archive.entry(entry, createReadStream("test/fixtures/test.txt")).finish();
39 | });
40 | it("should append Stream-like sources", function (done) {
41 | var archive = new ZipArchiveOutputStream();
42 | var testStream = new WriteHashStream("tmp/zip-stream-like.zip");
43 | var entry = new ZipArchiveEntry("stream-like.txt");
44 | testStream.on("close", function () {
45 | done();
46 | });
47 | archive.pipe(testStream);
48 | archive.entry(entry, Readable.from(["test"])).finish();
49 | });
50 | it("should stop streaming on Stream error", function (done) {
51 | var archive = new ZipArchiveOutputStream();
52 | var testStream = new WriteHashStream("tmp/zip-stream.zip");
53 | var entry = new ZipArchiveEntry("stream.txt");
54 | var callbackError = null;
55 | var callbackCalls = 0;
56 | testStream.on("close", function () {
57 | assert.equal(callbackError.message, "something went wrong");
58 | assert.equal(callbackCalls, 1);
59 | done();
60 | });
61 | archive.pipe(testStream);
62 | var file = new Transform();
63 | archive.entry(entry, file, function (err) {
64 | callbackCalls += 1;
65 | callbackError = err;
66 | });
67 | archive.finish();
68 | process.nextTick(function () {
69 | file.emit("error", new Error("something went wrong"));
70 | });
71 | });
72 | it("should append multiple sources", function (done) {
73 | var archive = new ZipArchiveOutputStream();
74 | var testStream = new WriteHashStream("tmp/zip-multiple.zip");
75 | var entry = new ZipArchiveEntry("string.txt");
76 | var entry2 = new ZipArchiveEntry("buffer.txt");
77 | var entry3 = new ZipArchiveEntry("stream.txt");
78 | var entry4 = new ZipArchiveEntry("stream-store.png");
79 | entry4.setMethod(0);
80 | var entry5 = new ZipArchiveEntry("buffer-store.txt");
81 | entry5.setMethod(0);
82 | testStream.on("close", function () {
83 | done();
84 | });
85 | archive.pipe(testStream);
86 | archive.entry(entry, "string", function (err) {
87 | if (err) throw err;
88 | archive.entry(entry2, testBuffer, function (err) {
89 | if (err) throw err;
90 | archive.entry(
91 | entry3,
92 | createReadStream("test/fixtures/test.txt"),
93 | function (err) {
94 | if (err) throw err;
95 | archive.entry(
96 | entry4,
97 | createReadStream("test/fixtures/image.png"),
98 | function (err) {
99 | if (err) throw err;
100 | archive.entry(entry5, testBuffer, function (err) {
101 | if (err) throw err;
102 | archive.finish();
103 | });
104 | },
105 | );
106 | },
107 | );
108 | });
109 | });
110 | });
111 | it("should force ZIP64", function (done) {
112 | var archive = new ZipArchiveOutputStream({
113 | forceZip64: true,
114 | });
115 | var testStream = new WriteHashStream("tmp/zip-stream64.zip");
116 | var entry = new ZipArchiveEntry("stream.txt");
117 | testStream.on("close", function () {
118 | done();
119 | });
120 | archive.pipe(testStream);
121 | archive.entry(entry, createReadStream("test/fixtures/test.txt")).finish();
122 | });
123 | });
124 | });
125 |
--------------------------------------------------------------------------------
/lib/archivers/zip/zip-archive-entry.js:
--------------------------------------------------------------------------------
1 | import { inherits } from "util";
2 | import normalizePath from "normalize-path";
3 | import ArchiveEntry from "../archive-entry.js";
4 | import GeneralPurposeBit from "./general-purpose-bit.js";
5 | import UnixStat from "./unix-stat.js";
6 | import {
7 | EMPTY,
8 | MIN_VERSION_INITIAL,
9 | MODE_MASK,
10 | PLATFORM_FAT,
11 | PLATFORM_UNIX,
12 | S_DOS_A,
13 | S_DOS_D,
14 | S_IFDIR,
15 | S_IFREG,
16 | SHORT_MASK,
17 | SHORT_SHIFT,
18 | ZIP64_MAGIC,
19 | } from "./constants.js";
20 | import { dateToDos, dosToDate } from "./util.js";
21 |
22 | export default class ZipArchiveEntry extends ArchiveEntry {
23 | constructor(name) {
24 | super();
25 | this.platform = PLATFORM_FAT;
26 | this.method = -1;
27 | this.name = null;
28 | this.size = 0;
29 | this.csize = 0;
30 | this.gpb = new GeneralPurposeBit();
31 | this.crc = 0;
32 | this.time = -1;
33 | this.minver = MIN_VERSION_INITIAL;
34 | this.mode = -1;
35 | this.extra = null;
36 | this.exattr = 0;
37 | this.inattr = 0;
38 | this.comment = null;
39 | if (name) {
40 | this.setName(name);
41 | }
42 | }
43 |
44 | /**
45 | * Returns the extra fields related to the entry.
46 | *
47 | * @returns {Buffer}
48 | */
49 | getCentralDirectoryExtra() {
50 | return this.getExtra();
51 | }
52 |
53 | /**
54 | * Returns the comment set for the entry.
55 | *
56 | * @returns {string}
57 | */
58 | getComment() {
59 | return this.comment !== null ? this.comment : "";
60 | }
61 |
62 | /**
63 | * Returns the compressed size of the entry.
64 | *
65 | * @returns {number}
66 | */
67 | getCompressedSize() {
68 | return this.csize;
69 | }
70 |
71 | /**
72 | * Returns the CRC32 digest for the entry.
73 | *
74 | * @returns {number}
75 | */
76 | getCrc() {
77 | return this.crc;
78 | }
79 |
80 | /**
81 | * Returns the external file attributes for the entry.
82 | *
83 | * @returns {number}
84 | */
85 | getExternalAttributes = function () {
86 | return this.exattr;
87 | };
88 |
89 | /**
90 | * Returns the extra fields related to the entry.
91 | *
92 | * @returns {Buffer}
93 | */
94 | getExtra() {
95 | return this.extra !== null ? this.extra : EMPTY;
96 | }
97 |
98 | /**
99 | * Returns the general purpose bits related to the entry.
100 | *
101 | * @returns {GeneralPurposeBit}
102 | */
103 | getGeneralPurposeBit() {
104 | return this.gpb;
105 | }
106 |
107 | /**
108 | * Returns the internal file attributes for the entry.
109 | *
110 | * @returns {number}
111 | */
112 | getInternalAttributes() {
113 | return this.inattr;
114 | }
115 |
116 | /**
117 | * Returns the last modified date of the entry.
118 | *
119 | * @returns {number}
120 | */
121 | getLastModifiedDate() {
122 | return this.getTime();
123 | }
124 |
125 | /**
126 | * Returns the extra fields related to the entry.
127 | *
128 | * @returns {Buffer}
129 | */
130 | getLocalFileDataExtra() {
131 | return this.getExtra();
132 | }
133 |
134 | /**
135 | * Returns the compression method used on the entry.
136 | *
137 | * @returns {number}
138 | */
139 | getMethod() {
140 | return this.method;
141 | }
142 |
143 | /**
144 | * Returns the filename of the entry.
145 | *
146 | * @returns {string}
147 | */
148 | getName() {
149 | return this.name;
150 | }
151 |
152 | /**
153 | * Returns the platform on which the entry was made.
154 | *
155 | * @returns {number}
156 | */
157 | getPlatform() {
158 | return this.platform;
159 | }
160 | /**
161 | * Returns the size of the entry.
162 | *
163 | * @returns {number}
164 | */
165 | getSize() {
166 | return this.size;
167 | }
168 | /**
169 | * Returns a date object representing the last modified date of the entry.
170 | *
171 | * @returns {number|Date}
172 | */
173 | getTime() {
174 | return this.time !== -1 ? dosToDate(this.time) : -1;
175 | }
176 | /**
177 | * Returns the DOS timestamp for the entry.
178 | *
179 | * @returns {number}
180 | */
181 | getTimeDos() {
182 | return this.time !== -1 ? this.time : 0;
183 | }
184 | /**
185 | * Returns the UNIX file permissions for the entry.
186 | *
187 | * @returns {number}
188 | */
189 | getUnixMode() {
190 | return this.platform !== PLATFORM_UNIX
191 | ? 0
192 | : (this.getExternalAttributes() >> SHORT_SHIFT) & SHORT_MASK;
193 | }
194 | /**
195 | * Returns the version of ZIP needed to extract the entry.
196 | *
197 | * @returns {number}
198 | */
199 | getVersionNeededToExtract() {
200 | return this.minver;
201 | }
202 | /**
203 | * Sets the comment of the entry.
204 | *
205 | * @param comment
206 | */
207 | setComment(comment) {
208 | if (Buffer.byteLength(comment) !== comment.length) {
209 | this.getGeneralPurposeBit().useUTF8ForNames(true);
210 | }
211 | this.comment = comment;
212 | }
213 | /**
214 | * Sets the compressed size of the entry.
215 | *
216 | * @param size
217 | */
218 | setCompressedSize(size) {
219 | if (size < 0) {
220 | throw new Error("invalid entry compressed size");
221 | }
222 | this.csize = size;
223 | }
224 | /**
225 | * Sets the checksum of the entry.
226 | *
227 | * @param crc
228 | */
229 | setCrc(crc) {
230 | if (crc < 0) {
231 | throw new Error("invalid entry crc32");
232 | }
233 | this.crc = crc;
234 | }
235 | /**
236 | * Sets the external file attributes of the entry.
237 | *
238 | * @param attr
239 | */
240 | setExternalAttributes(attr) {
241 | this.exattr = attr >>> 0;
242 | }
243 | /**
244 | * Sets the extra fields related to the entry.
245 | *
246 | * @param extra
247 | */
248 | setExtra(extra) {
249 | this.extra = extra;
250 | }
251 | /**
252 | * Sets the general purpose bits related to the entry.
253 | *
254 | * @param gpb
255 | */
256 | setGeneralPurposeBit(gpb) {
257 | if (!(gpb instanceof GeneralPurposeBit)) {
258 | throw new Error("invalid entry GeneralPurposeBit");
259 | }
260 | this.gpb = gpb;
261 | }
262 | /**
263 | * Sets the internal file attributes of the entry.
264 | *
265 | * @param attr
266 | */
267 | setInternalAttributes(attr) {
268 | this.inattr = attr;
269 | }
270 | /**
271 | * Sets the compression method of the entry.
272 | *
273 | * @param method
274 | */
275 | setMethod(method) {
276 | if (method < 0) {
277 | throw new Error("invalid entry compression method");
278 | }
279 | this.method = method;
280 | }
281 | /**
282 | * Sets the name of the entry.
283 | *
284 | * @param name
285 | * @param prependSlash
286 | */
287 | setName(name, prependSlash = false) {
288 | name = normalizePath(name, false)
289 | .replace(/^\w+:/, "")
290 | .replace(/^(\.\.\/|\/)+/, "");
291 | if (prependSlash) {
292 | name = `/${name}`;
293 | }
294 | if (Buffer.byteLength(name) !== name.length) {
295 | this.getGeneralPurposeBit().useUTF8ForNames(true);
296 | }
297 | this.name = name;
298 | }
299 | /**
300 | * Sets the platform on which the entry was made.
301 | *
302 | * @param platform
303 | */
304 | setPlatform(platform) {
305 | this.platform = platform;
306 | }
307 | /**
308 | * Sets the size of the entry.
309 | *
310 | * @param size
311 | */
312 | setSize(size) {
313 | if (size < 0) {
314 | throw new Error("invalid entry size");
315 | }
316 | this.size = size;
317 | }
318 | /**
319 | * Sets the time of the entry.
320 | *
321 | * @param time
322 | * @param forceLocalTime
323 | */
324 | setTime(time, forceLocalTime) {
325 | if (!(time instanceof Date)) {
326 | throw new Error("invalid entry time");
327 | }
328 | this.time = dateToDos(time, forceLocalTime);
329 | }
330 | /**
331 | * Sets the UNIX file permissions for the entry.
332 | *
333 | * @param mode
334 | */
335 | setUnixMode(mode) {
336 | mode |= this.isDirectory() ? S_IFDIR : S_IFREG;
337 | var extattr = 0;
338 | extattr |= (mode << SHORT_SHIFT) | (this.isDirectory() ? S_DOS_D : S_DOS_A);
339 | this.setExternalAttributes(extattr);
340 | this.mode = mode & MODE_MASK;
341 | this.platform = PLATFORM_UNIX;
342 | }
343 | /**
344 | * Sets the version of ZIP needed to extract this entry.
345 | *
346 | * @param minver
347 | */
348 | setVersionNeededToExtract(minver) {
349 | this.minver = minver;
350 | }
351 | /**
352 | * Returns true if this entry represents a directory.
353 | *
354 | * @returns {boolean}
355 | */
356 | isDirectory() {
357 | return this.getName().slice(-1) === "/";
358 | }
359 | /**
360 | * Returns true if this entry represents a unix symlink,
361 | * in which case the entry's content contains the target path
362 | * for the symlink.
363 | *
364 | * @returns {boolean}
365 | */
366 | isUnixSymlink() {
367 | return (
368 | (this.getUnixMode() & UnixStat.FILE_TYPE_FLAG) === UnixStat.LINK_FLAG
369 | );
370 | }
371 |
372 | /**
373 | * Returns true if this entry is using the ZIP64 extension of ZIP.
374 | *
375 | * @returns {boolean}
376 | */
377 | isZip64() {
378 | return this.csize > ZIP64_MAGIC || this.size > ZIP64_MAGIC;
379 | }
380 | }
381 |
--------------------------------------------------------------------------------
/test/zip-archive-entry.js:
--------------------------------------------------------------------------------
1 | import { assert } from "chai";
2 | import { ZipArchiveEntry } from "../lib/compress-commons.js";
3 | import GeneralPurposeBit from "../lib/archivers/zip/general-purpose-bit.js";
4 | import UnixStat from "../lib/archivers/zip/unix-stat.js";
5 | /*global before,describe,it */
6 |
7 | var entry;
8 | // Jan 03 2013 14:26:38 GMT
9 | var testDate = new Date(Date.UTC(2013, 0, 3, 14, 26, 38, 0));
10 |
11 | describe("ZipArchiveEntry", function () {
12 | beforeEach(function () {
13 | entry = new ZipArchiveEntry("file.txt");
14 | });
15 | // Getters
16 | describe("#getCentralDirectoryExtra", function () {
17 | it.skip("should be tested", function () {});
18 | });
19 | describe("#getComment", function () {
20 | it("should return the comment", function () {
21 | entry.setComment("file comment");
22 | assert.equal(entry.getComment(), "file comment");
23 | });
24 | });
25 | describe("#getCompressedSize", function () {
26 | it("should return the compressed size", function () {
27 | entry.csize = 10;
28 | assert.equal(entry.getCompressedSize(), 10);
29 | });
30 | });
31 | describe("#getCrc", function () {
32 | it("should return the CRC32", function () {
33 | entry.crc = 585446183;
34 | assert.equal(entry.getCrc(), 585446183);
35 | });
36 | });
37 | describe("#getExternalAttributes", function () {
38 | it("should return the external attributes", function () {
39 | entry.exattr = 2180972576;
40 | assert.equal(entry.getExternalAttributes(), 2180972576);
41 | });
42 | });
43 | describe("#getExtra", function () {
44 | it.skip("should be tested", function () {});
45 | });
46 | describe("#getGeneralPurposeBit", function () {
47 | it("should return the general purpose bit flag", function () {
48 | var gpb = new GeneralPurposeBit();
49 | gpb.useDataDescriptor(true);
50 | entry.gpb = gpb;
51 | assert.equal(entry.getGeneralPurposeBit(), gpb);
52 | });
53 | });
54 | describe("#getInternalAttributes", function () {
55 | it("should return the internal attributes", function () {
56 | entry.inattr = 2180972576;
57 | assert.equal(entry.getInternalAttributes(), 2180972576);
58 | });
59 | });
60 | describe("#getLastModifiedDate", function () {
61 | it.skip("should be tested", function () {});
62 | });
63 | describe("#getLocalFileDataExtra", function () {
64 | it.skip("should be tested", function () {});
65 | });
66 | describe("#getMethod", function () {
67 | it("should return the compression method", function () {
68 | entry.method = 0;
69 | assert.equal(entry.getMethod(), 0);
70 | });
71 | });
72 | describe("#getName", function () {
73 | it("should return the name", function () {
74 | entry.name = "file.txt";
75 | assert.equal(entry.getName(), "file.txt");
76 | });
77 | });
78 | describe("#getPlatform", function () {
79 | it("should return the platform", function () {
80 | entry.platform = 3;
81 | assert.equal(entry.getPlatform(), 3);
82 | });
83 | });
84 | describe("#getSize", function () {
85 | it("should return the size", function () {
86 | entry.size = 25;
87 | assert.equal(entry.getSize(), 25);
88 | });
89 | });
90 | describe("#getTime", function () {
91 | it("should return a Date object", function () {
92 | entry.time = 1109607251;
93 | assert.typeOf(entry.getTime(), "Date");
94 | });
95 | });
96 | describe("#getTimeDos", function () {
97 | it("should return a number", function () {
98 | entry.time = 1109607251;
99 | assert.typeOf(entry.getTimeDos(), "number");
100 | });
101 | });
102 | describe("#getUnixMode", function () {
103 | it("should return the unix filemode", function () {
104 | entry.mode = 511; // 0777
105 | entry.exattr = 2180972576;
106 | entry.platform = 3;
107 | assert.equal(entry.getUnixMode(), 33279); // 0100777
108 | });
109 | it("should set proper external attributes for an unix directory", function () {
110 | entry = new ZipArchiveEntry("directory/");
111 | entry.setUnixMode(511); // 0777
112 | assert.ok(entry.getPlatform(), 3);
113 | assert.ok(entry.isDirectory());
114 | var exattr = entry.getExternalAttributes() >> 16;
115 | assert.equal(exattr & 16384, 16384); // 040000
116 | });
117 | });
118 | describe("#getVersionNeededToExtract", function () {
119 | it.skip("should be tested", function () {});
120 | });
121 | // Setters
122 | describe("#setComment", function () {
123 | it("should set internal variable", function () {
124 | entry.setComment("file comment");
125 | assert.propertyVal(entry, "comment", "file comment");
126 | });
127 | it("should set utf8 bit when receiving strings byte count != string length", function () {
128 | entry.setComment("ÀÁÂÃÄÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäçèéêëìíîïñòóôõöùúûüýÿ");
129 | assert.ok(entry.getGeneralPurposeBit().usesUTF8ForNames());
130 | });
131 | });
132 | describe("#setCompressedSize", function () {
133 | it("should set internal variable", function () {
134 | entry.setCompressedSize(10);
135 | assert.propertyVal(entry, "csize", 10);
136 | });
137 | });
138 | describe("#setCrc", function () {
139 | it("should set internal variable", function () {
140 | entry.setCrc(585446183);
141 | assert.propertyVal(entry, "crc", 585446183);
142 | });
143 | });
144 | describe("#setExternalAttributes", function () {
145 | it("should set internal variable", function () {
146 | entry.setExternalAttributes(2180972576);
147 | assert.propertyVal(entry, "exattr", 2180972576);
148 | });
149 | });
150 | describe("#setExtra", function () {
151 | it.skip("should be tested", function () {});
152 | });
153 | describe("#setGeneralPurposeBit", function () {
154 | it("should set internal variable", function () {
155 | var gpb = new GeneralPurposeBit();
156 | gpb.useDataDescriptor(true);
157 | entry.setGeneralPurposeBit(gpb);
158 | assert.propertyVal(entry, "gpb", gpb);
159 | });
160 | });
161 | describe("#setInternalAttributes", function () {
162 | it("should set internal variable", function () {
163 | entry.setInternalAttributes(2180972576);
164 | assert.propertyVal(entry, "inattr", 2180972576);
165 | });
166 | });
167 | describe("#setMethod", function () {
168 | it("should set internal variable", function () {
169 | entry.setMethod(8);
170 | assert.propertyVal(entry, "method", 8);
171 | });
172 | });
173 | describe("#setName", function () {
174 | it("should set internal variable", function () {
175 | entry.setName("file.txt");
176 | assert.propertyVal(entry, "name", "file.txt");
177 | });
178 | it("should allow setting prefix of / at the beginning of path", function () {
179 | entry.setName("file.txt", true);
180 | assert.propertyVal(entry, "name", "/file.txt");
181 | });
182 | it("should allow ./ at the beginning of path", function () {
183 | entry.setName("./file.txt");
184 | assert.propertyVal(entry, "name", "./file.txt");
185 | });
186 | it("should clean windows style paths", function () {
187 | entry.setName("\\windows\\file.txt");
188 | assert.propertyVal(entry, "name", "windows/file.txt");
189 | entry.setName("c:\\this\\path\\file.txt");
190 | assert.propertyVal(entry, "name", "this/path/file.txt");
191 | entry.setName("\\\\server\\share\\");
192 | assert.propertyVal(entry, "name", "server/share/");
193 | });
194 | it("should clean multiple forward slashes at beginning of path", function () {
195 | entry.setName("//forward/file.txt");
196 | assert.propertyVal(entry, "name", "forward/file.txt");
197 | });
198 | it("should set utf8 bit when receiving strings byte count != string length", function () {
199 | entry.setName("ÀÁÂÃÄÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäçèéêëìíîïñòóôõöùúûüýÿ.txt");
200 | assert.ok(entry.getGeneralPurposeBit().usesUTF8ForNames());
201 | });
202 | });
203 | describe("#setPlatform", function () {
204 | it("should set internal variable", function () {
205 | entry.setPlatform(3);
206 | assert.propertyVal(entry, "platform", 3);
207 | });
208 | });
209 | describe("#setSize", function () {
210 | it("should set internal variable", function () {
211 | entry.setSize(15);
212 | assert.propertyVal(entry, "size", 15);
213 | });
214 | });
215 | describe("#setTime", function () {
216 | it("should set internal variable", function () {
217 | entry.setTime(testDate);
218 | assert.propertyVal(entry, "time", 1109619539);
219 | });
220 | });
221 | describe("#setUnixMode", function () {
222 | it("should set internal variables", function () {
223 | entry.setUnixMode(511);
224 | assert.propertyVal(entry, "exattr", 2180972576);
225 | assert.propertyVal(entry, "mode", 511); // 0777
226 | assert.equal(entry.getUnixMode(), 33279); // 0100777
227 | });
228 | it("should also preserve filetype information", function () {
229 | entry.setUnixMode(41453);
230 | assert.propertyVal(entry, "exattr", 2716663840);
231 | assert.propertyVal(entry, "mode", 493); // 0755
232 | assert.equal(entry.getUnixMode(), 41453); // 0120755
233 | });
234 | });
235 | describe("#setVersionNeededToExtract", function () {
236 | it.skip("should be tested", function () {});
237 | });
238 | // Others
239 | describe("#isDirectory", function () {
240 | it("should return a boolean based on name of entry", function () {
241 | assert.notOk(entry.isDirectory());
242 | entry.setName("some/directory/");
243 | assert.ok(entry.isDirectory());
244 | });
245 | });
246 | describe("#isUnixSymlink", function () {
247 | it("should return a boolean if the entry is a symlink", function () {
248 | entry.setUnixMode(UnixStat.LINK_FLAG);
249 | assert.ok(entry.isUnixSymlink());
250 | entry.setUnixMode(UnixStat.LINK_FLAG | UnixStat.DIR_FLAG);
251 | assert.notOk(entry.isUnixSymlink());
252 | });
253 | });
254 | });
255 |
--------------------------------------------------------------------------------
/lib/archivers/zip/zip-archive-output-stream.js:
--------------------------------------------------------------------------------
1 | import { inherits } from "util";
2 | import crc32 from "crc-32";
3 | import { CRC32Stream, DeflateCRC32Stream } from "crc32-stream";
4 | import ArchiveOutputStream from "../archive-output-stream.js";
5 | import ZipArchiveEntry from "./zip-archive-entry.js";
6 | import GeneralPurposeBit from "./general-purpose-bit.js";
7 | import {
8 | LONG_ZERO,
9 | METHOD_DEFLATED,
10 | METHOD_STORED,
11 | MIN_VERSION_DATA_DESCRIPTOR,
12 | MIN_VERSION_ZIP64,
13 | SHORT_ZERO,
14 | SIG_EOCD,
15 | SIG_DD,
16 | SIG_CFH,
17 | SIG_LFH,
18 | SIG_ZIP64_EOCD,
19 | SIG_ZIP64_EOCD_LOC,
20 | VERSION_MADEBY,
21 | ZIP64_EXTRA_ID,
22 | ZIP64_MAGIC,
23 | ZIP64_MAGIC_SHORT,
24 | ZLIB_BEST_SPEED,
25 | } from "./constants.js";
26 | import { getEightBytes, getLongBytes, getShortBytes } from "./util.js";
27 |
28 | function _defaults(o) {
29 | if (typeof o !== "object") {
30 | o = {};
31 | }
32 | if (typeof o.zlib !== "object") {
33 | o.zlib = {};
34 | }
35 | if (typeof o.zlib.level !== "number") {
36 | o.zlib.level = ZLIB_BEST_SPEED;
37 | }
38 | o.forceZip64 = !!o.forceZip64;
39 | o.forceLocalTime = !!o.forceLocalTime;
40 | return o;
41 | }
42 |
43 | export default class ZipArchiveOutputStream extends ArchiveOutputStream {
44 | constructor(options) {
45 | const _options = _defaults(options);
46 | super(_options);
47 | this.options = _options;
48 | this._entry = null;
49 | this._entries = [];
50 | this._archive = {
51 | centralLength: 0,
52 | centralOffset: 0,
53 | comment: "",
54 | finish: false,
55 | finished: false,
56 | processing: false,
57 | forceZip64: _options.forceZip64,
58 | forceLocalTime: _options.forceLocalTime,
59 | };
60 | }
61 |
62 | _afterAppend(ae) {
63 | this._entries.push(ae);
64 | if (ae.getGeneralPurposeBit().usesDataDescriptor()) {
65 | this._writeDataDescriptor(ae);
66 | }
67 | this._archive.processing = false;
68 | this._entry = null;
69 | if (this._archive.finish && !this._archive.finished) {
70 | this._finish();
71 | }
72 | }
73 |
74 | _appendBuffer(ae, source, callback) {
75 | if (source.length === 0) {
76 | ae.setMethod(METHOD_STORED);
77 | }
78 | var method = ae.getMethod();
79 | if (method === METHOD_STORED) {
80 | ae.setSize(source.length);
81 | ae.setCompressedSize(source.length);
82 | ae.setCrc(crc32.buf(source) >>> 0);
83 | }
84 | this._writeLocalFileHeader(ae);
85 | if (method === METHOD_STORED) {
86 | this.write(source);
87 | this._afterAppend(ae);
88 | callback(null, ae);
89 | return;
90 | } else if (method === METHOD_DEFLATED) {
91 | this._smartStream(ae, callback).end(source);
92 | return;
93 | } else {
94 | callback(new Error("compression method " + method + " not implemented"));
95 | return;
96 | }
97 | }
98 |
99 | _appendStream(ae, source, callback) {
100 | ae.getGeneralPurposeBit().useDataDescriptor(true);
101 | ae.setVersionNeededToExtract(MIN_VERSION_DATA_DESCRIPTOR);
102 | this._writeLocalFileHeader(ae);
103 | var smart = this._smartStream(ae, callback);
104 | source.once("error", function (err) {
105 | smart.emit("error", err);
106 | smart.end();
107 | });
108 | source.pipe(smart);
109 | }
110 |
111 | _finish() {
112 | this._archive.centralOffset = this.offset;
113 | this._entries.forEach(
114 | function (ae) {
115 | this._writeCentralFileHeader(ae);
116 | }.bind(this),
117 | );
118 | this._archive.centralLength = this.offset - this._archive.centralOffset;
119 | if (this.isZip64()) {
120 | this._writeCentralDirectoryZip64();
121 | }
122 | this._writeCentralDirectoryEnd();
123 | this._archive.processing = false;
124 | this._archive.finish = true;
125 | this._archive.finished = true;
126 | this.end();
127 | }
128 |
129 | _normalizeEntry(ae) {
130 | if (ae.getMethod() === -1) {
131 | ae.setMethod(METHOD_DEFLATED);
132 | }
133 | if (ae.getMethod() === METHOD_DEFLATED) {
134 | ae.getGeneralPurposeBit().useDataDescriptor(true);
135 | ae.setVersionNeededToExtract(MIN_VERSION_DATA_DESCRIPTOR);
136 | }
137 | if (ae.getTime() === -1) {
138 | ae.setTime(new Date(), this._archive.forceLocalTime);
139 | }
140 | ae._offsets = {
141 | file: 0,
142 | data: 0,
143 | contents: 0,
144 | };
145 | }
146 |
147 | _smartStream(ae, callback) {
148 | var deflate = ae.getMethod() === METHOD_DEFLATED;
149 | var process = deflate
150 | ? new DeflateCRC32Stream(this.options.zlib)
151 | : new CRC32Stream();
152 | var error = null;
153 | function handleStuff() {
154 | var digest = process.digest().readUInt32BE(0);
155 | ae.setCrc(digest);
156 | ae.setSize(process.size());
157 | ae.setCompressedSize(process.size(true));
158 | this._afterAppend(ae);
159 | callback(error, ae);
160 | }
161 | process.once("end", handleStuff.bind(this));
162 | process.once("error", function (err) {
163 | error = err;
164 | });
165 | process.pipe(this, { end: false });
166 | return process;
167 | }
168 |
169 | _writeCentralDirectoryEnd() {
170 | var records = this._entries.length;
171 | var size = this._archive.centralLength;
172 | var offset = this._archive.centralOffset;
173 | if (this.isZip64()) {
174 | records = ZIP64_MAGIC_SHORT;
175 | size = ZIP64_MAGIC;
176 | offset = ZIP64_MAGIC;
177 | }
178 | // signature
179 | this.write(getLongBytes(SIG_EOCD));
180 | // disk numbers
181 | this.write(SHORT_ZERO);
182 | this.write(SHORT_ZERO);
183 | // number of entries
184 | this.write(getShortBytes(records));
185 | this.write(getShortBytes(records));
186 | // length and location of CD
187 | this.write(getLongBytes(size));
188 | this.write(getLongBytes(offset));
189 | // archive comment
190 | var comment = this.getComment();
191 | var commentLength = Buffer.byteLength(comment);
192 | this.write(getShortBytes(commentLength));
193 | this.write(comment);
194 | }
195 |
196 | _writeCentralDirectoryZip64() {
197 | // signature
198 | this.write(getLongBytes(SIG_ZIP64_EOCD));
199 | // size of the ZIP64 EOCD record
200 | this.write(getEightBytes(44));
201 | // version made by
202 | this.write(getShortBytes(MIN_VERSION_ZIP64));
203 | // version to extract
204 | this.write(getShortBytes(MIN_VERSION_ZIP64));
205 | // disk numbers
206 | this.write(LONG_ZERO);
207 | this.write(LONG_ZERO);
208 | // number of entries
209 | this.write(getEightBytes(this._entries.length));
210 | this.write(getEightBytes(this._entries.length));
211 | // length and location of CD
212 | this.write(getEightBytes(this._archive.centralLength));
213 | this.write(getEightBytes(this._archive.centralOffset));
214 | // extensible data sector
215 | // not implemented at this time
216 | // end of central directory locator
217 | this.write(getLongBytes(SIG_ZIP64_EOCD_LOC));
218 | // disk number holding the ZIP64 EOCD record
219 | this.write(LONG_ZERO);
220 | // relative offset of the ZIP64 EOCD record
221 | this.write(
222 | getEightBytes(this._archive.centralOffset + this._archive.centralLength),
223 | );
224 | // total number of disks
225 | this.write(getLongBytes(1));
226 | }
227 |
228 | _writeCentralFileHeader(ae) {
229 | var gpb = ae.getGeneralPurposeBit();
230 | var method = ae.getMethod();
231 | var fileOffset = ae._offsets.file;
232 | var size = ae.getSize();
233 | var compressedSize = ae.getCompressedSize();
234 | if (ae.isZip64() || fileOffset > ZIP64_MAGIC) {
235 | size = ZIP64_MAGIC;
236 | compressedSize = ZIP64_MAGIC;
237 | fileOffset = ZIP64_MAGIC;
238 | ae.setVersionNeededToExtract(MIN_VERSION_ZIP64);
239 | var extraBuf = Buffer.concat(
240 | [
241 | getShortBytes(ZIP64_EXTRA_ID),
242 | getShortBytes(24),
243 | getEightBytes(ae.getSize()),
244 | getEightBytes(ae.getCompressedSize()),
245 | getEightBytes(ae._offsets.file),
246 | ],
247 | 28,
248 | );
249 | ae.setExtra(extraBuf);
250 | }
251 | // signature
252 | this.write(getLongBytes(SIG_CFH));
253 | // version made by
254 | this.write(getShortBytes((ae.getPlatform() << 8) | VERSION_MADEBY));
255 | // version to extract and general bit flag
256 | this.write(getShortBytes(ae.getVersionNeededToExtract()));
257 | this.write(gpb.encode());
258 | // compression method
259 | this.write(getShortBytes(method));
260 | // datetime
261 | this.write(getLongBytes(ae.getTimeDos()));
262 | // crc32 checksum
263 | this.write(getLongBytes(ae.getCrc()));
264 | // sizes
265 | this.write(getLongBytes(compressedSize));
266 | this.write(getLongBytes(size));
267 | var name = ae.getName();
268 | var comment = ae.getComment();
269 | var extra = ae.getCentralDirectoryExtra();
270 | if (gpb.usesUTF8ForNames()) {
271 | name = Buffer.from(name);
272 | comment = Buffer.from(comment);
273 | }
274 | // name length
275 | this.write(getShortBytes(name.length));
276 | // extra length
277 | this.write(getShortBytes(extra.length));
278 | // comments length
279 | this.write(getShortBytes(comment.length));
280 | // disk number start
281 | this.write(SHORT_ZERO);
282 | // internal attributes
283 | this.write(getShortBytes(ae.getInternalAttributes()));
284 | // external attributes
285 | this.write(getLongBytes(ae.getExternalAttributes()));
286 | // relative offset of LFH
287 | this.write(getLongBytes(fileOffset));
288 | // name
289 | this.write(name);
290 | // extra
291 | this.write(extra);
292 | // comment
293 | this.write(comment);
294 | }
295 |
296 | _writeDataDescriptor(ae) {
297 | // signature
298 | this.write(getLongBytes(SIG_DD));
299 | // crc32 checksum
300 | this.write(getLongBytes(ae.getCrc()));
301 | // sizes
302 | if (ae.isZip64()) {
303 | this.write(getEightBytes(ae.getCompressedSize()));
304 | this.write(getEightBytes(ae.getSize()));
305 | } else {
306 | this.write(getLongBytes(ae.getCompressedSize()));
307 | this.write(getLongBytes(ae.getSize()));
308 | }
309 | }
310 |
311 | _writeLocalFileHeader(ae) {
312 | var gpb = ae.getGeneralPurposeBit();
313 | var method = ae.getMethod();
314 | var name = ae.getName();
315 | var extra = ae.getLocalFileDataExtra();
316 | if (ae.isZip64()) {
317 | gpb.useDataDescriptor(true);
318 | ae.setVersionNeededToExtract(MIN_VERSION_ZIP64);
319 | }
320 | if (gpb.usesUTF8ForNames()) {
321 | name = Buffer.from(name);
322 | }
323 | ae._offsets.file = this.offset;
324 | // signature
325 | this.write(getLongBytes(SIG_LFH));
326 | // version to extract and general bit flag
327 | this.write(getShortBytes(ae.getVersionNeededToExtract()));
328 | this.write(gpb.encode());
329 | // compression method
330 | this.write(getShortBytes(method));
331 | // datetime
332 | this.write(getLongBytes(ae.getTimeDos()));
333 | ae._offsets.data = this.offset;
334 | // crc32 checksum and sizes
335 | if (gpb.usesDataDescriptor()) {
336 | this.write(LONG_ZERO);
337 | this.write(LONG_ZERO);
338 | this.write(LONG_ZERO);
339 | } else {
340 | this.write(getLongBytes(ae.getCrc()));
341 | this.write(getLongBytes(ae.getCompressedSize()));
342 | this.write(getLongBytes(ae.getSize()));
343 | }
344 | // name length
345 | this.write(getShortBytes(name.length));
346 | // extra length
347 | this.write(getShortBytes(extra.length));
348 | // name
349 | this.write(name);
350 | // extra
351 | this.write(extra);
352 | ae._offsets.contents = this.offset;
353 | }
354 |
355 | getComment(comment) {
356 | return this._archive.comment !== null ? this._archive.comment : "";
357 | }
358 |
359 | isZip64() {
360 | return (
361 | this._archive.forceZip64 ||
362 | this._entries.length > ZIP64_MAGIC_SHORT ||
363 | this._archive.centralLength > ZIP64_MAGIC ||
364 | this._archive.centralOffset > ZIP64_MAGIC
365 | );
366 | }
367 |
368 | setComment(comment) {
369 | this._archive.comment = comment;
370 | }
371 | }
372 |
--------------------------------------------------------------------------------