@js-eyes/protocol 2.8.1 → 2.8.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/zip-extract.js CHANGED
@@ -1,208 +1,208 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const zlib = require('zlib');
6
-
7
- const EOCD_SIGNATURE = 0x06054b50;
8
- const ZIP64_EOCD_LOCATOR = 0x07064b50;
9
- const ZIP64_EOCD = 0x06064b50;
10
- const CENTRAL_DIR_HEADER = 0x02014b50;
11
- const LOCAL_HEADER = 0x04034b50;
12
-
13
- const DEFAULT_MAX_TOTAL_SIZE = 100 * 1024 * 1024;
14
- const DEFAULT_MAX_FILE_SIZE = 20 * 1024 * 1024;
15
- const DEFAULT_MAX_ENTRIES = 5000;
16
-
17
- function readUInt16(buf, off) { return buf.readUInt16LE(off); }
18
- function readUInt32(buf, off) { return buf.readUInt32LE(off); }
19
-
20
- function findEndOfCentralDirectory(buf) {
21
- const minLen = 22;
22
- if (buf.length < minLen) {
23
- throw new Error('zip: file too small');
24
- }
25
- const maxBack = Math.min(buf.length, 65536 + minLen);
26
- for (let i = buf.length - minLen; i >= buf.length - maxBack; i--) {
27
- if (i < 0) break;
28
- if (readUInt32(buf, i) === EOCD_SIGNATURE) {
29
- return i;
30
- }
31
- }
32
- throw new Error('zip: end-of-central-directory not found');
33
- }
34
-
35
- function parseEntries(buf) {
36
- const eocdOffset = findEndOfCentralDirectory(buf);
37
- let cdSize = readUInt32(buf, eocdOffset + 12);
38
- let cdOffset = readUInt32(buf, eocdOffset + 16);
39
- let entryCount = readUInt16(buf, eocdOffset + 10);
40
-
41
- if (cdOffset === 0xffffffff || cdSize === 0xffffffff || entryCount === 0xffff) {
42
- const locatorOffset = eocdOffset - 20;
43
- if (locatorOffset >= 0 && readUInt32(buf, locatorOffset) === ZIP64_EOCD_LOCATOR) {
44
- const zip64Offset = Number(buf.readBigUInt64LE(locatorOffset + 8));
45
- if (zip64Offset >= 0 && readUInt32(buf, zip64Offset) === ZIP64_EOCD) {
46
- entryCount = Number(buf.readBigUInt64LE(zip64Offset + 32));
47
- cdSize = Number(buf.readBigUInt64LE(zip64Offset + 40));
48
- cdOffset = Number(buf.readBigUInt64LE(zip64Offset + 48));
49
- }
50
- }
51
- }
52
-
53
- const entries = [];
54
- let offset = cdOffset;
55
-
56
- for (let i = 0; i < entryCount; i++) {
57
- if (readUInt32(buf, offset) !== CENTRAL_DIR_HEADER) {
58
- throw new Error(`zip: bad central directory at entry ${i}`);
59
- }
60
- const versionMadeBy = readUInt16(buf, offset + 4);
61
- const generalFlag = readUInt16(buf, offset + 8);
62
- const method = readUInt16(buf, offset + 10);
63
- let compressedSize = readUInt32(buf, offset + 20);
64
- let uncompressedSize = readUInt32(buf, offset + 24);
65
- const fileNameLength = readUInt16(buf, offset + 28);
66
- const extraLength = readUInt16(buf, offset + 30);
67
- const commentLength = readUInt16(buf, offset + 32);
68
- const externalAttrs = readUInt32(buf, offset + 38);
69
- let localHeaderOffset = readUInt32(buf, offset + 42);
70
- const fileName = buf.slice(offset + 46, offset + 46 + fileNameLength).toString('utf8');
71
-
72
- let extraOffset = offset + 46 + fileNameLength;
73
- const extraEnd = extraOffset + extraLength;
74
- while (extraOffset + 4 <= extraEnd) {
75
- const id = readUInt16(buf, extraOffset);
76
- const size = readUInt16(buf, extraOffset + 2);
77
- if (id === 0x0001) {
78
- let p = extraOffset + 4;
79
- if (uncompressedSize === 0xffffffff && p + 8 <= extraEnd) {
80
- uncompressedSize = Number(buf.readBigUInt64LE(p)); p += 8;
81
- }
82
- if (compressedSize === 0xffffffff && p + 8 <= extraEnd) {
83
- compressedSize = Number(buf.readBigUInt64LE(p)); p += 8;
84
- }
85
- if (localHeaderOffset === 0xffffffff && p + 8 <= extraEnd) {
86
- localHeaderOffset = Number(buf.readBigUInt64LE(p)); p += 8;
87
- }
88
- }
89
- extraOffset += 4 + size;
90
- }
91
-
92
- const isUnix = (versionMadeBy >>> 8) === 3;
93
- const unixMode = isUnix ? (externalAttrs >>> 16) & 0xffff : 0;
94
- const isSymlink = isUnix && (unixMode & 0xf000) === 0xa000;
95
-
96
- entries.push({
97
- fileName,
98
- method,
99
- compressedSize,
100
- uncompressedSize,
101
- localHeaderOffset,
102
- generalFlag,
103
- isSymlink,
104
- isDirectory: fileName.endsWith('/') || (isUnix && (unixMode & 0xf000) === 0x4000),
105
- unixMode,
106
- });
107
-
108
- offset += 46 + fileNameLength + extraLength + commentLength;
109
- }
110
-
111
- return entries;
112
- }
113
-
114
- function readEntryData(buf, entry) {
115
- const lhOffset = entry.localHeaderOffset;
116
- if (readUInt32(buf, lhOffset) !== LOCAL_HEADER) {
117
- throw new Error(`zip: bad local header for ${entry.fileName}`);
118
- }
119
- const fileNameLen = readUInt16(buf, lhOffset + 26);
120
- const extraLen = readUInt16(buf, lhOffset + 28);
121
- const dataStart = lhOffset + 30 + fileNameLen + extraLen;
122
- const compressed = buf.slice(dataStart, dataStart + entry.compressedSize);
123
-
124
- if (entry.method === 0) {
125
- return compressed;
126
- }
127
- if (entry.method === 8) {
128
- return zlib.inflateRawSync(compressed);
129
- }
130
- throw new Error(`zip: unsupported compression method ${entry.method} for ${entry.fileName}`);
131
- }
132
-
133
- function isPathInside(parent, child) {
134
- const rel = path.relative(parent, child);
135
- return !rel.startsWith('..') && !path.isAbsolute(rel);
136
- }
137
-
138
- function safeJoin(targetDir, fileName) {
139
- const normalized = fileName.replace(/\\/g, '/').replace(/^\/+/, '');
140
- if (normalized.split('/').some((segment) => segment === '..')) {
141
- throw new Error(`zip: refusing path traversal entry ${fileName}`);
142
- }
143
- const dest = path.resolve(targetDir, normalized);
144
- if (!isPathInside(path.resolve(targetDir), dest)) {
145
- throw new Error(`zip: entry escapes target dir: ${fileName}`);
146
- }
147
- return dest;
148
- }
149
-
150
- function extractZipBuffer(buffer, targetDir, options = {}) {
151
- const maxTotal = options.maxTotalSize || DEFAULT_MAX_TOTAL_SIZE;
152
- const maxFile = options.maxFileSize || DEFAULT_MAX_FILE_SIZE;
153
- const maxEntries = options.maxEntries || DEFAULT_MAX_ENTRIES;
154
-
155
- const entries = parseEntries(buffer);
156
- if (entries.length > maxEntries) {
157
- throw new Error(`zip: too many entries (${entries.length} > ${maxEntries})`);
158
- }
159
-
160
- let totalSize = 0;
161
- for (const entry of entries) {
162
- if (entry.uncompressedSize > maxFile) {
163
- throw new Error(`zip: entry ${entry.fileName} exceeds max file size (${entry.uncompressedSize})`);
164
- }
165
- totalSize += entry.uncompressedSize;
166
- if (totalSize > maxTotal) {
167
- throw new Error(`zip: total uncompressed size exceeds limit (${totalSize} > ${maxTotal})`);
168
- }
169
- }
170
-
171
- fs.mkdirSync(targetDir, { recursive: true });
172
-
173
- const written = [];
174
- for (const entry of entries) {
175
- if (entry.isSymlink) {
176
- throw new Error(`zip: symlinks are not allowed (${entry.fileName})`);
177
- }
178
- if (entry.isDirectory) {
179
- const dest = safeJoin(targetDir, entry.fileName);
180
- fs.mkdirSync(dest, { recursive: true });
181
- continue;
182
- }
183
- const dest = safeJoin(targetDir, entry.fileName);
184
- fs.mkdirSync(path.dirname(dest), { recursive: true });
185
- const data = readEntryData(buffer, entry);
186
- if (data.length > maxFile) {
187
- throw new Error(`zip: decompressed entry exceeds limit (${entry.fileName})`);
188
- }
189
- fs.writeFileSync(dest, data);
190
- written.push(path.relative(targetDir, dest));
191
- }
192
-
193
- return { entries: entries.length, written };
194
- }
195
-
196
- function extractZipFile(zipPath, targetDir, options = {}) {
197
- const buffer = fs.readFileSync(zipPath);
198
- return extractZipBuffer(buffer, targetDir, options);
199
- }
200
-
201
- module.exports = {
202
- DEFAULT_MAX_FILE_SIZE,
203
- DEFAULT_MAX_TOTAL_SIZE,
204
- DEFAULT_MAX_ENTRIES,
205
- extractZipBuffer,
206
- extractZipFile,
207
- parseEntries,
208
- };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const zlib = require('zlib');
6
+
7
+ const EOCD_SIGNATURE = 0x06054b50;
8
+ const ZIP64_EOCD_LOCATOR = 0x07064b50;
9
+ const ZIP64_EOCD = 0x06064b50;
10
+ const CENTRAL_DIR_HEADER = 0x02014b50;
11
+ const LOCAL_HEADER = 0x04034b50;
12
+
13
+ const DEFAULT_MAX_TOTAL_SIZE = 100 * 1024 * 1024;
14
+ const DEFAULT_MAX_FILE_SIZE = 20 * 1024 * 1024;
15
+ const DEFAULT_MAX_ENTRIES = 5000;
16
+
17
+ function readUInt16(buf, off) { return buf.readUInt16LE(off); }
18
+ function readUInt32(buf, off) { return buf.readUInt32LE(off); }
19
+
20
+ function findEndOfCentralDirectory(buf) {
21
+ const minLen = 22;
22
+ if (buf.length < minLen) {
23
+ throw new Error('zip: file too small');
24
+ }
25
+ const maxBack = Math.min(buf.length, 65536 + minLen);
26
+ for (let i = buf.length - minLen; i >= buf.length - maxBack; i--) {
27
+ if (i < 0) break;
28
+ if (readUInt32(buf, i) === EOCD_SIGNATURE) {
29
+ return i;
30
+ }
31
+ }
32
+ throw new Error('zip: end-of-central-directory not found');
33
+ }
34
+
35
+ function parseEntries(buf) {
36
+ const eocdOffset = findEndOfCentralDirectory(buf);
37
+ let cdSize = readUInt32(buf, eocdOffset + 12);
38
+ let cdOffset = readUInt32(buf, eocdOffset + 16);
39
+ let entryCount = readUInt16(buf, eocdOffset + 10);
40
+
41
+ if (cdOffset === 0xffffffff || cdSize === 0xffffffff || entryCount === 0xffff) {
42
+ const locatorOffset = eocdOffset - 20;
43
+ if (locatorOffset >= 0 && readUInt32(buf, locatorOffset) === ZIP64_EOCD_LOCATOR) {
44
+ const zip64Offset = Number(buf.readBigUInt64LE(locatorOffset + 8));
45
+ if (zip64Offset >= 0 && readUInt32(buf, zip64Offset) === ZIP64_EOCD) {
46
+ entryCount = Number(buf.readBigUInt64LE(zip64Offset + 32));
47
+ cdSize = Number(buf.readBigUInt64LE(zip64Offset + 40));
48
+ cdOffset = Number(buf.readBigUInt64LE(zip64Offset + 48));
49
+ }
50
+ }
51
+ }
52
+
53
+ const entries = [];
54
+ let offset = cdOffset;
55
+
56
+ for (let i = 0; i < entryCount; i++) {
57
+ if (readUInt32(buf, offset) !== CENTRAL_DIR_HEADER) {
58
+ throw new Error(`zip: bad central directory at entry ${i}`);
59
+ }
60
+ const versionMadeBy = readUInt16(buf, offset + 4);
61
+ const generalFlag = readUInt16(buf, offset + 8);
62
+ const method = readUInt16(buf, offset + 10);
63
+ let compressedSize = readUInt32(buf, offset + 20);
64
+ let uncompressedSize = readUInt32(buf, offset + 24);
65
+ const fileNameLength = readUInt16(buf, offset + 28);
66
+ const extraLength = readUInt16(buf, offset + 30);
67
+ const commentLength = readUInt16(buf, offset + 32);
68
+ const externalAttrs = readUInt32(buf, offset + 38);
69
+ let localHeaderOffset = readUInt32(buf, offset + 42);
70
+ const fileName = buf.slice(offset + 46, offset + 46 + fileNameLength).toString('utf8');
71
+
72
+ let extraOffset = offset + 46 + fileNameLength;
73
+ const extraEnd = extraOffset + extraLength;
74
+ while (extraOffset + 4 <= extraEnd) {
75
+ const id = readUInt16(buf, extraOffset);
76
+ const size = readUInt16(buf, extraOffset + 2);
77
+ if (id === 0x0001) {
78
+ let p = extraOffset + 4;
79
+ if (uncompressedSize === 0xffffffff && p + 8 <= extraEnd) {
80
+ uncompressedSize = Number(buf.readBigUInt64LE(p)); p += 8;
81
+ }
82
+ if (compressedSize === 0xffffffff && p + 8 <= extraEnd) {
83
+ compressedSize = Number(buf.readBigUInt64LE(p)); p += 8;
84
+ }
85
+ if (localHeaderOffset === 0xffffffff && p + 8 <= extraEnd) {
86
+ localHeaderOffset = Number(buf.readBigUInt64LE(p)); p += 8;
87
+ }
88
+ }
89
+ extraOffset += 4 + size;
90
+ }
91
+
92
+ const isUnix = (versionMadeBy >>> 8) === 3;
93
+ const unixMode = isUnix ? (externalAttrs >>> 16) & 0xffff : 0;
94
+ const isSymlink = isUnix && (unixMode & 0xf000) === 0xa000;
95
+
96
+ entries.push({
97
+ fileName,
98
+ method,
99
+ compressedSize,
100
+ uncompressedSize,
101
+ localHeaderOffset,
102
+ generalFlag,
103
+ isSymlink,
104
+ isDirectory: fileName.endsWith('/') || (isUnix && (unixMode & 0xf000) === 0x4000),
105
+ unixMode,
106
+ });
107
+
108
+ offset += 46 + fileNameLength + extraLength + commentLength;
109
+ }
110
+
111
+ return entries;
112
+ }
113
+
114
+ function readEntryData(buf, entry) {
115
+ const lhOffset = entry.localHeaderOffset;
116
+ if (readUInt32(buf, lhOffset) !== LOCAL_HEADER) {
117
+ throw new Error(`zip: bad local header for ${entry.fileName}`);
118
+ }
119
+ const fileNameLen = readUInt16(buf, lhOffset + 26);
120
+ const extraLen = readUInt16(buf, lhOffset + 28);
121
+ const dataStart = lhOffset + 30 + fileNameLen + extraLen;
122
+ const compressed = buf.slice(dataStart, dataStart + entry.compressedSize);
123
+
124
+ if (entry.method === 0) {
125
+ return compressed;
126
+ }
127
+ if (entry.method === 8) {
128
+ return zlib.inflateRawSync(compressed);
129
+ }
130
+ throw new Error(`zip: unsupported compression method ${entry.method} for ${entry.fileName}`);
131
+ }
132
+
133
+ function isPathInside(parent, child) {
134
+ const rel = path.relative(parent, child);
135
+ return !rel.startsWith('..') && !path.isAbsolute(rel);
136
+ }
137
+
138
+ function safeJoin(targetDir, fileName) {
139
+ const normalized = fileName.replace(/\\/g, '/').replace(/^\/+/, '');
140
+ if (normalized.split('/').some((segment) => segment === '..')) {
141
+ throw new Error(`zip: refusing path traversal entry ${fileName}`);
142
+ }
143
+ const dest = path.resolve(targetDir, normalized);
144
+ if (!isPathInside(path.resolve(targetDir), dest)) {
145
+ throw new Error(`zip: entry escapes target dir: ${fileName}`);
146
+ }
147
+ return dest;
148
+ }
149
+
150
+ function extractZipBuffer(buffer, targetDir, options = {}) {
151
+ const maxTotal = options.maxTotalSize || DEFAULT_MAX_TOTAL_SIZE;
152
+ const maxFile = options.maxFileSize || DEFAULT_MAX_FILE_SIZE;
153
+ const maxEntries = options.maxEntries || DEFAULT_MAX_ENTRIES;
154
+
155
+ const entries = parseEntries(buffer);
156
+ if (entries.length > maxEntries) {
157
+ throw new Error(`zip: too many entries (${entries.length} > ${maxEntries})`);
158
+ }
159
+
160
+ let totalSize = 0;
161
+ for (const entry of entries) {
162
+ if (entry.uncompressedSize > maxFile) {
163
+ throw new Error(`zip: entry ${entry.fileName} exceeds max file size (${entry.uncompressedSize})`);
164
+ }
165
+ totalSize += entry.uncompressedSize;
166
+ if (totalSize > maxTotal) {
167
+ throw new Error(`zip: total uncompressed size exceeds limit (${totalSize} > ${maxTotal})`);
168
+ }
169
+ }
170
+
171
+ fs.mkdirSync(targetDir, { recursive: true });
172
+
173
+ const written = [];
174
+ for (const entry of entries) {
175
+ if (entry.isSymlink) {
176
+ throw new Error(`zip: symlinks are not allowed (${entry.fileName})`);
177
+ }
178
+ if (entry.isDirectory) {
179
+ const dest = safeJoin(targetDir, entry.fileName);
180
+ fs.mkdirSync(dest, { recursive: true });
181
+ continue;
182
+ }
183
+ const dest = safeJoin(targetDir, entry.fileName);
184
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
185
+ const data = readEntryData(buffer, entry);
186
+ if (data.length > maxFile) {
187
+ throw new Error(`zip: decompressed entry exceeds limit (${entry.fileName})`);
188
+ }
189
+ fs.writeFileSync(dest, data);
190
+ written.push(path.relative(targetDir, dest));
191
+ }
192
+
193
+ return { entries: entries.length, written };
194
+ }
195
+
196
+ function extractZipFile(zipPath, targetDir, options = {}) {
197
+ const buffer = fs.readFileSync(zipPath);
198
+ return extractZipBuffer(buffer, targetDir, options);
199
+ }
200
+
201
+ module.exports = {
202
+ DEFAULT_MAX_FILE_SIZE,
203
+ DEFAULT_MAX_TOTAL_SIZE,
204
+ DEFAULT_MAX_ENTRIES,
205
+ extractZipBuffer,
206
+ extractZipFile,
207
+ parseEntries,
208
+ };