documonster 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README_zh.md +1 -1
- package/dist/esm/modules/archive/compression/compress.base.js +20 -6
- package/dist/esm/modules/archive/compression/compress.browser.js +45 -32
- package/dist/esm/modules/archive/compression/compress.js +74 -18
- package/dist/esm/modules/archive/compression/deflate-fallback.js +14 -3
- package/dist/esm/modules/archive/compression/worker-pool/pool.browser.js +9 -3
- package/dist/esm/modules/archive/compression/worker-pool/worker-script.js +35 -7
- package/dist/esm/modules/archive/core/errors.js +26 -4
- package/dist/esm/modules/archive/index.base.js +1 -1
- package/dist/esm/modules/archive/unzip/zip-extract-core.js +74 -34
- package/dist/esm/modules/archive/unzip/zip-parser.js +55 -9
- package/dist/esm/modules/archive/zip-spec/zip-parser-core.js +14 -2
- package/dist/esm/modules/csv/parse/scanner/scanner.js +3 -0
- package/dist/esm/modules/csv/parse/sync.js +3 -2
- package/dist/esm/modules/csv/stream/parser.js +40 -4
- package/dist/esm/modules/csv/worker/worker-script.generated.js +1 -1
- package/dist/esm/modules/excel/core/cell.js +22 -2
- package/dist/esm/modules/excel/core/table.js +39 -2
- package/dist/esm/modules/excel/core/worksheet-core.js +39 -0
- package/dist/esm/modules/excel/core/worksheet-load.js +3 -1
- package/dist/esm/modules/excel/stream/workbook-reader.browser.js +34 -11
- package/dist/esm/modules/excel/surface/cell.js +64 -23
- package/dist/esm/modules/excel/utils/cell-format.js +3 -2
- package/dist/esm/modules/excel/xlsb/read/package.js +3 -1
- package/dist/esm/modules/excel/xlsx/read/package.js +17 -1
- package/dist/esm/modules/pdf/builder/document-builder.js +3 -1
- package/dist/esm/modules/pdf/builder/pdf-editor.js +218 -58
- package/dist/esm/modules/pdf/core/pdf-kdf.browser.js +9 -1
- package/dist/esm/modules/pdf/core/pdf-kdf.js +8 -0
- package/dist/esm/modules/pdf/core/pdf-writer.js +159 -149
- package/dist/esm/modules/pdf/core/pdfa.js +7 -3
- package/dist/esm/modules/pdf/errors.js +25 -0
- package/dist/esm/modules/pdf/index.js +1 -1
- package/dist/esm/modules/pdf/reader/pdf-decrypt.js +136 -74
- package/dist/esm/modules/pdf/reader/pdf-document.js +314 -105
- package/dist/esm/modules/pdf/reader/pdf-parser.js +29 -11
- package/dist/esm/modules/pdf/reader/pdf-reader.js +8 -1
- package/dist/esm/modules/pdf/reader/stream-filters.js +139 -77
- package/dist/esm/modules/pdf/render/pdf-exporter.js +3 -1
- package/dist/esm/modules/pdf/word-bridge.js +22 -48
- package/dist/esm/modules/word/advanced/field-engine.js +75 -56
- package/dist/esm/modules/word/builder/document-handle.js +15 -1
- package/dist/esm/modules/word/convert/docx-to-semantic.js +84 -83
- package/dist/esm/modules/word/convert/html/html-renderer.js +107 -78
- package/dist/esm/modules/word/convert/markdown/markdown-import.js +43 -10
- package/dist/esm/modules/word/convert/markdown/markdown-renderer.js +98 -130
- package/dist/esm/modules/word/convert/odt/odt.js +149 -55
- package/dist/esm/modules/word/core/text-utils.js +9 -0
- package/dist/esm/modules/word/errors.js +1 -1
- package/dist/esm/modules/word/layout/layout-constants.js +20 -23
- package/dist/esm/modules/word/layout/layout-full.js +259 -280
- package/dist/esm/modules/word/layout/layout.js +140 -197
- package/dist/esm/modules/word/query/final-view.js +49 -0
- package/dist/esm/modules/word/query/heading.js +81 -0
- package/dist/esm/modules/word/query/merge.js +39 -22
- package/dist/esm/modules/word/query/numbering-counter.js +108 -0
- package/dist/esm/modules/word/query/page-field.js +212 -0
- package/dist/esm/modules/word/query/search.js +15 -14
- package/dist/esm/modules/word/query/split.js +15 -25
- package/dist/esm/modules/word/query/style-resolve.js +196 -53
- package/dist/esm/modules/word/query/style-resolver.js +24 -0
- package/dist/esm/modules/word/reader/chart-parser.js +17 -7
- package/dist/esm/modules/word/reader/comments-parser.js +4 -4
- package/dist/esm/modules/word/reader/doc-props-parsers.js +10 -9
- package/dist/esm/modules/word/reader/docx-reader.js +46 -32
- package/dist/esm/modules/word/reader/metadata-parsers.js +5 -5
- package/dist/esm/modules/word/reader/numbering-parser.js +4 -3
- package/dist/esm/modules/word/reader/part-xml.js +26 -0
- package/dist/esm/modules/word/reader/reader-context.js +3 -3
- package/dist/esm/modules/word/reader/settings-parser.js +3 -3
- package/dist/esm/modules/word/reader/styles-parser.js +3 -3
- package/dist/esm/modules/word/reader/theme-parser.js +4 -3
- package/dist/esm/modules/word/surface/query.js +1 -1
- package/dist/esm/modules/word/writer/docx-packager.js +4 -1
- package/dist/esm/modules/word/writer/streaming-writer.js +11 -2
- package/dist/esm/modules/xml/dom.js +4 -8
- package/dist/esm/modules/xml/errors.js +1 -3
- package/dist/esm/modules/xml/sax.js +8 -7
- package/dist/esm/modules/xml/to-object.js +4 -8
- package/dist/esm/utils/crypto.browser.js +147 -0
- package/dist/esm/utils/crypto.js +14 -0
- package/dist/esm/utils/limits.js +19 -0
- package/dist/iife/documonster.archive.iife.min.js +39 -11
- package/dist/iife/documonster.csv.iife.min.js +17 -17
- package/dist/iife/documonster.draw.iife.min.js +1 -1
- package/dist/iife/documonster.excel.iife.min.js +88 -60
- package/dist/iife/documonster.formula.iife.min.js +1 -1
- package/dist/iife/documonster.markdown.iife.min.js +1 -1
- package/dist/iife/documonster.mermaid.iife.min.js +1 -1
- package/dist/iife/documonster.pdf.iife.min.js +44 -42
- package/dist/iife/documonster.stream.iife.min.js +1 -1
- package/dist/iife/documonster.word.iife.min.js +95 -67
- package/dist/iife/documonster.xml.iife.min.js +6 -6
- package/dist/types/modules/archive/compression/compress.base.d.ts +12 -3
- package/dist/types/modules/archive/compression/compress.browser.d.ts +7 -4
- package/dist/types/modules/archive/compression/compress.d.ts +9 -4
- package/dist/types/modules/archive/compression/deflate-fallback.d.ts +3 -1
- package/dist/types/modules/archive/compression/worker-pool/pool.browser.d.ts +4 -1
- package/dist/types/modules/archive/compression/worker-pool/types.d.ts +4 -0
- package/dist/types/modules/archive/core/errors.d.ts +31 -2
- package/dist/types/modules/archive/index.base.d.ts +1 -1
- package/dist/types/modules/archive/unzip/zip-extract-core.d.ts +9 -2
- package/dist/types/modules/archive/unzip/zip-parser.d.ts +33 -0
- package/dist/types/modules/archive/zip-spec/zip-parser-core.d.ts +6 -0
- package/dist/types/modules/csv/parse/scanner/types.d.ts +5 -0
- package/dist/types/modules/csv/stream/parser.d.ts +16 -0
- package/dist/types/modules/csv/worker/worker-script.generated.d.ts +1 -1
- package/dist/types/modules/excel/core/table.d.ts +15 -0
- package/dist/types/modules/excel/core/worksheet-core.d.ts +26 -0
- package/dist/types/modules/excel/stream/workbook-reader.browser.d.ts +11 -0
- package/dist/types/modules/excel/surface/cell.d.ts +8 -8
- package/dist/types/modules/excel/xlsx/types.d.ts +16 -0
- package/dist/types/modules/pdf/builder/document-builder.d.ts +7 -0
- package/dist/types/modules/pdf/builder/pdf-editor.d.ts +49 -5
- package/dist/types/modules/pdf/core/pdf-kdf.browser.d.ts +4 -0
- package/dist/types/modules/pdf/core/pdf-kdf.d.ts +4 -0
- package/dist/types/modules/pdf/core/pdf-writer.d.ts +49 -10
- package/dist/types/modules/pdf/core/pdfa.d.ts +2 -0
- package/dist/types/modules/pdf/errors.d.ts +19 -0
- package/dist/types/modules/pdf/index.d.ts +2 -2
- package/dist/types/modules/pdf/reader/pdf-document.d.ts +83 -5
- package/dist/types/modules/pdf/reader/pdf-parser.d.ts +2 -2
- package/dist/types/modules/pdf/reader/pdf-reader.d.ts +7 -0
- package/dist/types/modules/pdf/reader/stream-filters.d.ts +5 -1
- package/dist/types/modules/pdf/types.d.ts +7 -0
- package/dist/types/modules/word/convert/conversion-ir.d.ts +2 -0
- package/dist/types/modules/word/core/text-utils.d.ts +7 -1
- package/dist/types/modules/word/errors.d.ts +3 -3
- package/dist/types/modules/word/index.base.d.ts +1 -1
- package/dist/types/modules/word/layout/layout-constants.d.ts +13 -9
- package/dist/types/modules/word/query/final-view.d.ts +26 -0
- package/dist/types/modules/word/query/heading.d.ts +30 -0
- package/dist/types/modules/word/query/numbering-counter.d.ts +47 -0
- package/dist/types/modules/word/query/page-field.d.ts +79 -0
- package/dist/types/modules/word/query/search.d.ts +8 -2
- package/dist/types/modules/word/query/split.d.ts +4 -1
- package/dist/types/modules/word/query/style-resolve.d.ts +58 -11
- package/dist/types/modules/word/query/style-resolver.d.ts +29 -0
- package/dist/types/modules/word/reader/chart-parser.d.ts +2 -2
- package/dist/types/modules/word/reader/comments-parser.d.ts +1 -1
- package/dist/types/modules/word/reader/doc-props-parsers.d.ts +4 -4
- package/dist/types/modules/word/reader/metadata-parsers.d.ts +2 -2
- package/dist/types/modules/word/reader/numbering-parser.d.ts +1 -1
- package/dist/types/modules/word/reader/part-xml.d.ts +14 -0
- package/dist/types/modules/word/reader/reader-context.d.ts +1 -1
- package/dist/types/modules/word/reader/settings-parser.d.ts +1 -1
- package/dist/types/modules/word/reader/styles-parser.d.ts +1 -1
- package/dist/types/modules/word/reader/theme-parser.d.ts +1 -1
- package/dist/types/modules/word/surface/query.d.ts +2 -1
- package/dist/types/modules/word/types.d.ts +5 -1
- package/dist/types/modules/word/writer/docx-packager.d.ts +7 -0
- package/dist/types/modules/word/writer/streaming-writer.d.ts +7 -0
- package/dist/types/modules/xml/errors.d.ts +9 -0
- package/dist/types/modules/xml/index.d.ts +1 -0
- package/dist/types/modules/xml/sax.d.ts +2 -1
- package/dist/types/utils/crypto.browser.d.ts +10 -0
- package/dist/types/utils/crypto.d.ts +10 -0
- package/dist/types/utils/limits.d.ts +12 -0
- package/package.json +1 -1
|
@@ -68,10 +68,17 @@ export class Crc32MismatchError extends ArchiveError {
|
|
|
68
68
|
* This is a security feature to detect ZIP bombs and corrupted archives.
|
|
69
69
|
*/
|
|
70
70
|
export class EntrySizeMismatchError extends ArchiveError {
|
|
71
|
-
constructor(path, expected,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
constructor(path, expected,
|
|
72
|
+
/**
|
|
73
|
+
* Bytes produced. `undefined` when the inflater was stopped at the
|
|
74
|
+
* declared size, so no count beyond it exists.
|
|
75
|
+
*/
|
|
76
|
+
actual, reason, options) {
|
|
77
|
+
const msg = reason === "too-few-bytes"
|
|
78
|
+
? `Entry "${path}" produced fewer bytes than declared: expected ${expected}, got ${actual}`
|
|
79
|
+
: actual === undefined
|
|
80
|
+
? `Entry "${path}" exceeds its declared ${expected} bytes`
|
|
81
|
+
: `Entry "${path}" produced more bytes than declared: expected ${expected}, got at least ${actual}`;
|
|
75
82
|
super(msg, options);
|
|
76
83
|
this.path = path;
|
|
77
84
|
this.expected = expected;
|
|
@@ -92,6 +99,21 @@ export class EntrySizeMismatchError extends ArchiveError {
|
|
|
92
99
|
return this.reason === "too-few-bytes";
|
|
93
100
|
}
|
|
94
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Error thrown when decompressing or extracting would exceed a configured limit.
|
|
104
|
+
*/
|
|
105
|
+
export class ArchiveLimitError extends ArchiveError {
|
|
106
|
+
constructor(limit, allowed, options) {
|
|
107
|
+
super(limit === "maxOutputLength"
|
|
108
|
+
? `Decompressed output exceeds maxOutputLength (${allowed} bytes)`
|
|
109
|
+
: limit === "maxEntries"
|
|
110
|
+
? `Archive entry count exceeds maxEntries (${allowed})`
|
|
111
|
+
: `Archive uncompressed size exceeds maxTotalUncompressedSize (${allowed} bytes)`, options);
|
|
112
|
+
this.limit = limit;
|
|
113
|
+
this.allowed = allowed;
|
|
114
|
+
this.name = "ArchiveLimitError";
|
|
115
|
+
}
|
|
116
|
+
}
|
|
95
117
|
// -----------------------------------------------------------------------------
|
|
96
118
|
// Encryption Errors
|
|
97
119
|
// -----------------------------------------------------------------------------
|
|
@@ -37,4 +37,4 @@ export {
|
|
|
37
37
|
// Abort error class
|
|
38
38
|
AbortError,
|
|
39
39
|
// Error classes
|
|
40
|
-
ArchiveError, ZipParseError, InvalidZipSignatureError, EocdNotFoundError, Crc32MismatchError, DecryptionError, PasswordRequiredError, RangeNotSupportedError, HttpRangeError, FileTooLargeError, UnsupportedCompressionError, EntrySizeMismatchError } from "./core/errors.js";
|
|
40
|
+
ArchiveError, ZipParseError, InvalidZipSignatureError, EocdNotFoundError, Crc32MismatchError, DecryptionError, PasswordRequiredError, RangeNotSupportedError, HttpRangeError, FileTooLargeError, UnsupportedCompressionError, EntrySizeMismatchError, ArchiveLimitError } from "./core/errors.js";
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { decompress, decompressSync } from "#platform/modules/archive/compression/compress";
|
|
13
13
|
import { crc32, crc32Finalize, crc32Update } from "#platform/modules/archive/compression/crc32";
|
|
14
14
|
import { createInflateStream } from "#platform/modules/archive/compression/streaming-compress";
|
|
15
|
-
import { ArchiveError, Crc32MismatchError, DecryptionError, EntrySizeMismatchError, FileTooLargeError, PasswordRequiredError, UnsupportedCompressionError, throwIfAborted, toError } from "../core/errors.js";
|
|
15
|
+
import { ArchiveError, ArchiveLimitError, Crc32MismatchError, DecryptionError, EntrySizeMismatchError, FileTooLargeError, PasswordRequiredError, UnsupportedCompressionError, throwIfAborted, toError } from "../core/errors.js";
|
|
16
16
|
import { ZIP_CRYPTO_HEADER_SIZE, aesDecrypt, zipCryptoDecrypt, zipCryptoDecryptByte, zipCryptoInitKeys } from "../crypto/index.js";
|
|
17
17
|
import { collect } from "../io/archive-sink.js";
|
|
18
18
|
import { BinaryReader } from "../zip-spec/binary.js";
|
|
@@ -25,11 +25,45 @@ import { concatUint8Arrays } from "../../../utils/binary.js";
|
|
|
25
25
|
export const LOCAL_HEADER_FIXED_SIZE = 30;
|
|
26
26
|
/**
|
|
27
27
|
* Maximum allowed uncompressed entry size for non-streaming extraction (512 MB).
|
|
28
|
-
*
|
|
29
|
-
*
|
|
28
|
+
*
|
|
29
|
+
* Enforced twice: against the *declared* size before anything is inflated, and
|
|
30
|
+
* as the inflater's output bound, so an entry that lies about its size is
|
|
31
|
+
* stopped as soon as it overruns rather than after it has been materialised.
|
|
30
32
|
* The streaming extraction path (processEntryDataStream) has its own byte-level checks.
|
|
31
33
|
*/
|
|
32
34
|
const DEFAULT_MAX_ENTRY_SIZE = 512 * 1024 * 1024;
|
|
35
|
+
/**
|
|
36
|
+
* Reject an entry whose declared size exceeds `maxEntrySize`, and fix the
|
|
37
|
+
* output bound to inflate it under.
|
|
38
|
+
*
|
|
39
|
+
* With size validation on, the bound is the declared size exactly: any byte
|
|
40
|
+
* past it already makes the entry invalid, so there is no reason to produce
|
|
41
|
+
* it. With validation off the declared size is not trusted for anything, and
|
|
42
|
+
* `maxEntrySize` alone bounds the in-memory result.
|
|
43
|
+
*/
|
|
44
|
+
function planEntryInflation(entry, validateEntrySizes, maxEntrySize) {
|
|
45
|
+
if (validateEntrySizes && entry.uncompressedSize > maxEntrySize) {
|
|
46
|
+
throw new FileTooLargeError(entry.path, `declares uncompressed size of ${entry.uncompressedSize} bytes, ` +
|
|
47
|
+
`which exceeds the maximum allowed size of ${maxEntrySize} bytes. ` +
|
|
48
|
+
"Use the streaming API for large entries.");
|
|
49
|
+
}
|
|
50
|
+
const bound = validateEntrySizes ? entry.uncompressedSize : maxEntrySize;
|
|
51
|
+
return { entry, validateEntrySizes, bound };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Translate an inflater limit hit into the error that names the entry.
|
|
55
|
+
*/
|
|
56
|
+
function translateOutputLimit(err, plan) {
|
|
57
|
+
if (!(err instanceof ArchiveLimitError)) {
|
|
58
|
+
return err;
|
|
59
|
+
}
|
|
60
|
+
const { entry, bound } = plan;
|
|
61
|
+
return plan.validateEntrySizes
|
|
62
|
+
? new EntrySizeMismatchError(entry.path, entry.uncompressedSize, undefined, "too-many-bytes", {
|
|
63
|
+
cause: err
|
|
64
|
+
})
|
|
65
|
+
: new FileTooLargeError(entry.path, `inflates beyond ${bound} bytes`, { cause: err });
|
|
66
|
+
}
|
|
33
67
|
/**
|
|
34
68
|
* Process compressed (and possibly encrypted) entry data to get the final content.
|
|
35
69
|
*
|
|
@@ -40,19 +74,14 @@ const DEFAULT_MAX_ENTRY_SIZE = 512 * 1024 * 1024;
|
|
|
40
74
|
* @param password - Optional password for decryption
|
|
41
75
|
* @param checkCrc32 - Whether to validate CRC32 checksum (default: false)
|
|
42
76
|
* @param validateEntrySizes - Whether to validate decompressed size matches declared size (default: true)
|
|
77
|
+
* @param maxEntrySize - Largest entry that may be inflated into memory (default: 512 MiB)
|
|
43
78
|
* @returns Decompressed entry content
|
|
44
79
|
*/
|
|
45
|
-
export async function processEntryData(entry, compressedData, password, checkCrc32 = false, validateEntrySizes = true) {
|
|
80
|
+
export async function processEntryData(entry, compressedData, password, checkCrc32 = false, validateEntrySizes = true, maxEntrySize = DEFAULT_MAX_ENTRY_SIZE) {
|
|
46
81
|
let result;
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
// the streaming path processEntryDataStream which validates actual output bytes).
|
|
51
|
-
if (validateEntrySizes && entry.uncompressedSize > DEFAULT_MAX_ENTRY_SIZE) {
|
|
52
|
-
throw new FileTooLargeError(entry.path, `declares uncompressed size of ${entry.uncompressedSize} bytes, ` +
|
|
53
|
-
`which exceeds the maximum allowed size of ${DEFAULT_MAX_ENTRY_SIZE} bytes. ` +
|
|
54
|
-
"Use the streaming API for large entries.");
|
|
55
|
-
}
|
|
82
|
+
// Rejects an over-large declared size up front, and bounds the inflater so an
|
|
83
|
+
// entry that lies about its size stops as soon as it overruns.
|
|
84
|
+
const plan = planEntryInflation(entry, validateEntrySizes, maxEntrySize);
|
|
56
85
|
// Handle encrypted entries
|
|
57
86
|
if (entry.isEncrypted) {
|
|
58
87
|
if (!password) {
|
|
@@ -65,7 +94,7 @@ export async function processEntryData(entry, compressedData, password, checkCrc
|
|
|
65
94
|
throw new DecryptionError(entry.path);
|
|
66
95
|
}
|
|
67
96
|
// Decompress if needed (use original compression method)
|
|
68
|
-
result = await decompressData(decrypted, entry.originalCompressionMethod ?? COMPRESSION_STORE,
|
|
97
|
+
result = await decompressData(decrypted, entry.originalCompressionMethod ?? COMPRESSION_STORE, plan);
|
|
69
98
|
}
|
|
70
99
|
else if (entry.encryptionMethod === "zipcrypto") {
|
|
71
100
|
// ZipCrypto decryption
|
|
@@ -73,7 +102,7 @@ export async function processEntryData(entry, compressedData, password, checkCrc
|
|
|
73
102
|
if (!decrypted) {
|
|
74
103
|
throw new DecryptionError(entry.path);
|
|
75
104
|
}
|
|
76
|
-
result = await decompressData(decrypted, entry.compressionMethod,
|
|
105
|
+
result = await decompressData(decrypted, entry.compressionMethod, plan);
|
|
77
106
|
}
|
|
78
107
|
else {
|
|
79
108
|
throw new DecryptionError(entry.path, "Unsupported encryption method");
|
|
@@ -81,7 +110,7 @@ export async function processEntryData(entry, compressedData, password, checkCrc
|
|
|
81
110
|
}
|
|
82
111
|
else {
|
|
83
112
|
// Non-encrypted entry
|
|
84
|
-
result = await decompressData(compressedData, entry.compressionMethod,
|
|
113
|
+
result = await decompressData(compressedData, entry.compressionMethod, plan);
|
|
85
114
|
}
|
|
86
115
|
// Validate entry size (ZIP bomb protection)
|
|
87
116
|
if (validateEntrySizes && result.length !== entry.uncompressedSize) {
|
|
@@ -111,17 +140,14 @@ export async function processEntryData(entry, compressedData, password, checkCrc
|
|
|
111
140
|
* @param compressedData - Raw compressed (and possibly encrypted) data from the ZIP
|
|
112
141
|
* @param password - Optional password for decryption
|
|
113
142
|
* @param validateEntrySizes - Whether to validate decompressed size matches declared size (default: true)
|
|
143
|
+
* @param maxEntrySize - Largest entry that may be inflated into memory (default: 512 MiB)
|
|
114
144
|
* @returns Decompressed entry content
|
|
115
145
|
* @throws Error if the entry uses AES encryption
|
|
116
146
|
*/
|
|
117
|
-
export function processEntryDataSync(entry, compressedData, password, validateEntrySizes = true) {
|
|
147
|
+
export function processEntryDataSync(entry, compressedData, password, validateEntrySizes = true, maxEntrySize = DEFAULT_MAX_ENTRY_SIZE) {
|
|
118
148
|
let result;
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
throw new FileTooLargeError(entry.path, `declares uncompressed size of ${entry.uncompressedSize} bytes, ` +
|
|
122
|
-
`which exceeds the maximum allowed size of ${DEFAULT_MAX_ENTRY_SIZE} bytes. ` +
|
|
123
|
-
"Use the streaming API for large entries.");
|
|
124
|
-
}
|
|
149
|
+
// Same up-front check and inflater bound as the async version.
|
|
150
|
+
const plan = planEntryInflation(entry, validateEntrySizes, maxEntrySize);
|
|
125
151
|
// Handle encrypted entries
|
|
126
152
|
if (entry.isEncrypted) {
|
|
127
153
|
if (!password) {
|
|
@@ -137,7 +163,7 @@ export function processEntryDataSync(entry, compressedData, password, validateEn
|
|
|
137
163
|
if (!decrypted) {
|
|
138
164
|
throw new DecryptionError(entry.path);
|
|
139
165
|
}
|
|
140
|
-
result = decompressDataSync(decrypted, entry.compressionMethod,
|
|
166
|
+
result = decompressDataSync(decrypted, entry.compressionMethod, plan);
|
|
141
167
|
// Always verify CRC32 for ZipCrypto because header verification only checks 1 byte
|
|
142
168
|
// (1/256 false positive rate with wrong password per ZIP spec)
|
|
143
169
|
const actualCrc = crc32(result);
|
|
@@ -151,7 +177,7 @@ export function processEntryDataSync(entry, compressedData, password, validateEn
|
|
|
151
177
|
}
|
|
152
178
|
else {
|
|
153
179
|
// Non-encrypted entry
|
|
154
|
-
result = decompressDataSync(compressedData, entry.compressionMethod,
|
|
180
|
+
result = decompressDataSync(compressedData, entry.compressionMethod, plan);
|
|
155
181
|
}
|
|
156
182
|
// Validate entry size (ZIP bomb protection)
|
|
157
183
|
if (validateEntrySizes && result.length !== entry.uncompressedSize) {
|
|
@@ -161,14 +187,20 @@ export function processEntryDataSync(entry, compressedData, password, validateEn
|
|
|
161
187
|
return result;
|
|
162
188
|
}
|
|
163
189
|
/**
|
|
164
|
-
* Decompress data based on compression method (async)
|
|
190
|
+
* Decompress an entry's data based on compression method (async), under the
|
|
191
|
+
* entry's output bound.
|
|
165
192
|
*/
|
|
166
|
-
async function decompressData(data, compressionMethod,
|
|
193
|
+
async function decompressData(data, compressionMethod, plan) {
|
|
167
194
|
if (compressionMethod === COMPRESSION_STORE) {
|
|
168
195
|
return data;
|
|
169
196
|
}
|
|
170
197
|
if (compressionMethod === COMPRESSION_DEFLATE) {
|
|
171
|
-
|
|
198
|
+
try {
|
|
199
|
+
return await decompress(data, { maxOutputLength: plan.bound });
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
throw translateOutputLimit(err, plan);
|
|
203
|
+
}
|
|
172
204
|
}
|
|
173
205
|
throw new UnsupportedCompressionError(compressionMethod);
|
|
174
206
|
}
|
|
@@ -429,7 +461,7 @@ function applyValidationStreams(entry, source, options) {
|
|
|
429
461
|
* requires the full ciphertext; this function falls back to buffering for AES.
|
|
430
462
|
*/
|
|
431
463
|
export function processEntryDataStream(entry, compressedData, options = {}) {
|
|
432
|
-
const { password, checkCrc32 = false, validateEntrySizes = true, signal } = options;
|
|
464
|
+
const { password, checkCrc32 = false, validateEntrySizes = true, maxEntrySize = DEFAULT_MAX_ENTRY_SIZE, signal } = options;
|
|
433
465
|
async function* run() {
|
|
434
466
|
throwIfAborted(signal);
|
|
435
467
|
if (entry.type === "directory") {
|
|
@@ -441,11 +473,13 @@ export function processEntryDataStream(entry, compressedData, options = {}) {
|
|
|
441
473
|
throw new PasswordRequiredError(entry.path);
|
|
442
474
|
}
|
|
443
475
|
if (entry.encryptionMethod === "aes" && entry.aesKeyStrength) {
|
|
476
|
+
// Reject an over-large declared size before buffering the ciphertext.
|
|
477
|
+
const plan = planEntryInflation(entry, validateEntrySizes, maxEntrySize);
|
|
444
478
|
// AES requires full ciphertext to verify HMAC.
|
|
445
479
|
const encrypted = await collect(compressedData);
|
|
446
480
|
const decrypted = await aesDecrypt(encrypted, password, entry.aesKeyStrength);
|
|
447
481
|
const method = entry.originalCompressionMethod ?? COMPRESSION_STORE;
|
|
448
|
-
const out = await decompressData(decrypted, method,
|
|
482
|
+
const out = await decompressData(decrypted, method, plan);
|
|
449
483
|
// Validate size for AES entries (already fully buffered)
|
|
450
484
|
if (validateEntrySizes && out.length !== entry.uncompressedSize) {
|
|
451
485
|
const reason = out.length > entry.uncompressedSize ? "too-many-bytes" : "too-few-bytes";
|
|
@@ -483,7 +517,7 @@ export function processEntryDataStream(entry, compressedData, options = {}) {
|
|
|
483
517
|
// Fallback for other encryption methods.
|
|
484
518
|
const encrypted = await collect(compressedData);
|
|
485
519
|
// processEntryData already handles size validation internally
|
|
486
|
-
const out = await processEntryData(entry, encrypted, password, checkCrc32, validateEntrySizes);
|
|
520
|
+
const out = await processEntryData(entry, encrypted, password, checkCrc32, validateEntrySizes, maxEntrySize);
|
|
487
521
|
if (out.length) {
|
|
488
522
|
yield out;
|
|
489
523
|
}
|
|
@@ -513,14 +547,20 @@ export function processEntryDataStream(entry, compressedData, options = {}) {
|
|
|
513
547
|
return run();
|
|
514
548
|
}
|
|
515
549
|
/**
|
|
516
|
-
* Decompress data based on compression method (sync)
|
|
550
|
+
* Decompress an entry's data based on compression method (sync), under the
|
|
551
|
+
* entry's output bound.
|
|
517
552
|
*/
|
|
518
|
-
function decompressDataSync(data, compressionMethod,
|
|
553
|
+
function decompressDataSync(data, compressionMethod, plan) {
|
|
519
554
|
if (compressionMethod === COMPRESSION_STORE) {
|
|
520
555
|
return data;
|
|
521
556
|
}
|
|
522
557
|
if (compressionMethod === COMPRESSION_DEFLATE) {
|
|
523
|
-
|
|
558
|
+
try {
|
|
559
|
+
return decompressSync(data, { maxOutputLength: plan.bound });
|
|
560
|
+
}
|
|
561
|
+
catch (err) {
|
|
562
|
+
throw translateOutputLimit(err, plan);
|
|
563
|
+
}
|
|
524
564
|
}
|
|
525
565
|
throw new UnsupportedCompressionError(compressionMethod);
|
|
526
566
|
}
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* No dependency on Node.js stream module
|
|
5
5
|
*/
|
|
6
6
|
import { EMPTY_UINT8ARRAY } from "../core/bytes.js";
|
|
7
|
-
import { FileTooLargeError } from "../core/errors.js";
|
|
7
|
+
import { ArchiveLimitError, FileTooLargeError } from "../core/errors.js";
|
|
8
8
|
import { processEntryData, processEntryDataSync, readEntryCompressedData } from "./zip-extract-core.js";
|
|
9
9
|
import { parseZipArchiveFromBuffer } from "../zip-spec/zip-parser-core.js";
|
|
10
|
+
import { assertLimitOption } from "../../../utils/limits.js";
|
|
10
11
|
const MAX_SAFE_INTEGER_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
|
|
11
12
|
function assertEntryExtractableInMemory(entry) {
|
|
12
13
|
// This parser extracts into memory. If ZIP64 values exceed JS safe integers,
|
|
@@ -29,7 +30,8 @@ function assertEntryExtractableInMemory(entry) {
|
|
|
29
30
|
function parseZipArchive(data, options = {}) {
|
|
30
31
|
return parseZipArchiveFromBuffer(data, {
|
|
31
32
|
decodeStrings: options.decodeStrings,
|
|
32
|
-
encoding: options.encoding
|
|
33
|
+
encoding: options.encoding,
|
|
34
|
+
maxEntries: options.maxEntries
|
|
33
35
|
});
|
|
34
36
|
}
|
|
35
37
|
/**
|
|
@@ -41,7 +43,7 @@ async function extractEntryData(data, entry, options = {}) {
|
|
|
41
43
|
}
|
|
42
44
|
assertEntryExtractableInMemory(entry);
|
|
43
45
|
const compressedData = readEntryCompressedData(data, entry);
|
|
44
|
-
return processEntryData(entry, compressedData, options.password);
|
|
46
|
+
return processEntryData(entry, compressedData, options.password, false, true, options.maxEntrySize);
|
|
45
47
|
}
|
|
46
48
|
/**
|
|
47
49
|
* Extract file data synchronously (only supports ZipCrypto, not AES)
|
|
@@ -52,19 +54,39 @@ function extractEntryDataSync(data, entry, options = {}) {
|
|
|
52
54
|
}
|
|
53
55
|
assertEntryExtractableInMemory(entry);
|
|
54
56
|
const compressedData = readEntryCompressedData(data, entry);
|
|
55
|
-
return processEntryDataSync(entry, compressedData, options.password);
|
|
57
|
+
return processEntryDataSync(entry, compressedData, options.password, true, options.maxEntrySize);
|
|
56
58
|
}
|
|
57
59
|
/**
|
|
58
60
|
* High-level ZIP parser class
|
|
59
61
|
*/
|
|
60
62
|
export class ZipParser {
|
|
61
63
|
constructor(data, options = {}) {
|
|
64
|
+
assertLimitOption("maxEntrySize", options.maxEntrySize);
|
|
65
|
+
assertLimitOption("maxTotalUncompressedSize", options.maxTotalUncompressedSize);
|
|
62
66
|
this.data = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
63
67
|
const result = parseZipArchive(this.data, options);
|
|
64
68
|
this.entries = result.entries;
|
|
65
69
|
this.archiveComment = result.comment;
|
|
66
70
|
this.entryMap = new Map(this.entries.map(e => [e.path, e]));
|
|
67
71
|
this.password = options.password;
|
|
72
|
+
this.maxEntrySize = options.maxEntrySize;
|
|
73
|
+
this.maxTotalUncompressedSize = options.maxTotalUncompressedSize ?? Infinity;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Reject a bulk extraction whose declared sizes exceed the total limit.
|
|
77
|
+
* Each entry is inflated under a bound of its declared size, so the declared
|
|
78
|
+
* sum is an upper bound on what extraction can actually produce.
|
|
79
|
+
*/
|
|
80
|
+
assertTotalWithinLimit() {
|
|
81
|
+
let total = 0;
|
|
82
|
+
for (const entry of this.entries) {
|
|
83
|
+
if (entry.type !== "directory") {
|
|
84
|
+
total += entry.uncompressedSize;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (total > this.maxTotalUncompressedSize) {
|
|
88
|
+
throw new ArchiveLimitError("maxTotalUncompressedSize", this.maxTotalUncompressedSize);
|
|
89
|
+
}
|
|
68
90
|
}
|
|
69
91
|
/**
|
|
70
92
|
* Set the password for encrypted entries.
|
|
@@ -179,7 +201,10 @@ export class ZipParser {
|
|
|
179
201
|
if (!entry) {
|
|
180
202
|
return null;
|
|
181
203
|
}
|
|
182
|
-
return extractEntryData(this.data, entry, {
|
|
204
|
+
return extractEntryData(this.data, entry, {
|
|
205
|
+
password: password ?? this.password,
|
|
206
|
+
maxEntrySize: this.maxEntrySize
|
|
207
|
+
});
|
|
183
208
|
}
|
|
184
209
|
/**
|
|
185
210
|
* Extract a single file (sync)
|
|
@@ -195,7 +220,10 @@ export class ZipParser {
|
|
|
195
220
|
if (!entry) {
|
|
196
221
|
return null;
|
|
197
222
|
}
|
|
198
|
-
return extractEntryDataSync(this.data, entry, {
|
|
223
|
+
return extractEntryDataSync(this.data, entry, {
|
|
224
|
+
password: password ?? this.password,
|
|
225
|
+
maxEntrySize: this.maxEntrySize
|
|
226
|
+
});
|
|
199
227
|
}
|
|
200
228
|
/**
|
|
201
229
|
* Extract all files (async)
|
|
@@ -204,8 +232,12 @@ export class ZipParser {
|
|
|
204
232
|
async extractAll(password) {
|
|
205
233
|
const result = new Map();
|
|
206
234
|
const pw = password ?? this.password;
|
|
235
|
+
this.assertTotalWithinLimit();
|
|
207
236
|
for (const entry of this.entries) {
|
|
208
|
-
const data = await extractEntryData(this.data, entry, {
|
|
237
|
+
const data = await extractEntryData(this.data, entry, {
|
|
238
|
+
password: pw,
|
|
239
|
+
maxEntrySize: this.maxEntrySize
|
|
240
|
+
});
|
|
209
241
|
result.set(entry.path, data);
|
|
210
242
|
}
|
|
211
243
|
return result;
|
|
@@ -222,8 +254,12 @@ export class ZipParser {
|
|
|
222
254
|
extractAllSync(password) {
|
|
223
255
|
const result = {};
|
|
224
256
|
const pw = password ?? this.password;
|
|
257
|
+
this.assertTotalWithinLimit();
|
|
225
258
|
for (const entry of this.entries) {
|
|
226
|
-
result[entry.path] = extractEntryDataSync(this.data, entry, {
|
|
259
|
+
result[entry.path] = extractEntryDataSync(this.data, entry, {
|
|
260
|
+
password: pw,
|
|
261
|
+
maxEntrySize: this.maxEntrySize
|
|
262
|
+
});
|
|
227
263
|
}
|
|
228
264
|
return result;
|
|
229
265
|
}
|
|
@@ -234,11 +270,21 @@ export class ZipParser {
|
|
|
234
270
|
*/
|
|
235
271
|
async forEach(callback, password) {
|
|
236
272
|
const pw = password ?? this.password;
|
|
273
|
+
let produced = 0;
|
|
237
274
|
for (const entry of this.entries) {
|
|
238
275
|
let dataPromise = null;
|
|
239
276
|
const getData = () => {
|
|
240
277
|
if (!dataPromise) {
|
|
241
|
-
dataPromise = extractEntryData(this.data, entry, {
|
|
278
|
+
dataPromise = extractEntryData(this.data, entry, {
|
|
279
|
+
password: pw,
|
|
280
|
+
maxEntrySize: this.maxEntrySize
|
|
281
|
+
}).then(bytes => {
|
|
282
|
+
produced += bytes.length;
|
|
283
|
+
if (produced > this.maxTotalUncompressedSize) {
|
|
284
|
+
throw new ArchiveLimitError("maxTotalUncompressedSize", this.maxTotalUncompressedSize);
|
|
285
|
+
}
|
|
286
|
+
return bytes;
|
|
287
|
+
});
|
|
242
288
|
}
|
|
243
289
|
return dataPromise;
|
|
244
290
|
};
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
* This module contains pure functions for parsing EOCD, ZIP64 EOCD, and Central Directory.
|
|
5
5
|
* Used by both `ZipParser` (in-memory) and `RemoteZipReader` (random-access).
|
|
6
6
|
*/
|
|
7
|
-
import { ArchiveError, EocdNotFoundError } from "../core/errors.js";
|
|
7
|
+
import { ArchiveError, ArchiveLimitError, EocdNotFoundError } from "../core/errors.js";
|
|
8
8
|
import { decodeZipPath, decodeZipComment, resolveZipStringCodec } from "../core/text.js";
|
|
9
9
|
import { BinaryReader } from "./binary.js";
|
|
10
10
|
import { resolveZipLastModifiedDateFromUnixSeconds } from "./timestamps.js";
|
|
11
11
|
import { parseZipExtraFields } from "./zip-extra-fields.js";
|
|
12
|
-
import { CENTRAL_DIR_HEADER_SIG, COMPRESSION_AES, UINT16_MAX, UINT32_MAX, ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG, ZIP64_END_OF_CENTRAL_DIR_SIG, ZIP_OS_MSDOS, getUnixModeFromExternalAttributes, isSymlinkMode, isDirectoryMode } from "./zip-records.js";
|
|
12
|
+
import { CENTRAL_DIR_HEADER_SIG, COMPRESSION_AES, ZIP_CENTRAL_DIR_HEADER_FIXED_SIZE, UINT16_MAX, UINT32_MAX, ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG, ZIP64_END_OF_CENTRAL_DIR_SIG, ZIP_OS_MSDOS, getUnixModeFromExternalAttributes, isSymlinkMode, isDirectoryMode } from "./zip-records.js";
|
|
13
13
|
import { uint8ArrayToString as decodeUtf8 } from "../../../utils/binary.js";
|
|
14
14
|
// -----------------------------------------------------------------------------
|
|
15
15
|
// Constants
|
|
@@ -323,6 +323,18 @@ export function parseCentralDirectoryAt(data, offset, totalEntries, options = {}
|
|
|
323
323
|
if (totalEntries === 0) {
|
|
324
324
|
return [];
|
|
325
325
|
}
|
|
326
|
+
// Every record is at least 46 bytes, so a count the buffer cannot hold is a
|
|
327
|
+
// lie — reject it before sizing an array from it.
|
|
328
|
+
if (totalEntries * ZIP_CENTRAL_DIR_HEADER_FIXED_SIZE > data.length - offset) {
|
|
329
|
+
throw new ArchiveError(`Central Directory declares ${totalEntries} entries, more than its ${data.length - offset} bytes can hold`);
|
|
330
|
+
}
|
|
331
|
+
// The 46-byte check only proves the count is *possible*; a genuine central
|
|
332
|
+
// directory of many tiny entries still costs one JS object (plus path
|
|
333
|
+
// strings and extra-field maps) per record, so the count needs its own bound.
|
|
334
|
+
const maxEntries = options.maxEntries ?? Infinity;
|
|
335
|
+
if (totalEntries > maxEntries) {
|
|
336
|
+
throw new ArchiveLimitError("maxEntries", maxEntries);
|
|
337
|
+
}
|
|
326
338
|
const entries = new Array(totalEntries);
|
|
327
339
|
const reader = new BinaryReader(data, offset);
|
|
328
340
|
for (let i = 0; i < totalEntries; i++) {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Provides parseCsv function and low-level parsing generators.
|
|
6
6
|
*/
|
|
7
7
|
import { getUtf8ByteLength } from "../constants.js";
|
|
8
|
+
import { CsvError } from "../errors.js";
|
|
8
9
|
import { resolveParseConfig, toScannerConfig } from "./config.js";
|
|
9
10
|
import { filterValidHeaders } from "./helpers.js";
|
|
10
11
|
import { splitLinesWithEndings } from "./lines.js";
|
|
@@ -95,7 +96,7 @@ export function* parseFastMode(input, config, state, errors) {
|
|
|
95
96
|
if (config.maxRowBytes !== undefined) {
|
|
96
97
|
const lineBytes = getUtf8ByteLength(line);
|
|
97
98
|
if (lineBytes > config.maxRowBytes) {
|
|
98
|
-
throw new
|
|
99
|
+
throw new CsvError(`Row exceeds the maximum size of ${config.maxRowBytes} bytes`);
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
if (config.infoOption) {
|
|
@@ -203,7 +204,7 @@ export function* parseWithScanner(input, config, state, errors) {
|
|
|
203
204
|
const rawRow = input.slice(scanResult.rawStart, rawEndPos);
|
|
204
205
|
const rowBytes = getUtf8ByteLength(rawRow);
|
|
205
206
|
if (rowBytes > config.maxRowBytes) {
|
|
206
|
-
throw new
|
|
207
|
+
throw new CsvError(`Row exceeds the maximum size of ${config.maxRowBytes} bytes`);
|
|
207
208
|
}
|
|
208
209
|
}
|
|
209
210
|
// Skip comment lines
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Works identically in both Node.js and Browser environments.
|
|
6
6
|
*/
|
|
7
7
|
import { DEFAULT_LINEBREAK_REGEX, getUtf8ByteLength } from "../constants.js";
|
|
8
|
+
import { CsvError } from "../errors.js";
|
|
8
9
|
import { createParseConfig, toScannerConfig } from "../parse/config.js";
|
|
9
10
|
import { createDelimiterDetector } from "../parse/delimiter-detector.js";
|
|
10
11
|
import { convertRowToObject, filterValidHeaders } from "../parse/helpers.js";
|
|
@@ -444,6 +445,36 @@ export class CsvParserStream extends Transform {
|
|
|
444
445
|
callback();
|
|
445
446
|
}
|
|
446
447
|
}
|
|
448
|
+
/**
|
|
449
|
+
* Fail early when the unfinished row already exceeds `maxRowBytes`.
|
|
450
|
+
*
|
|
451
|
+
* The completed-row check alone cannot bound memory: an unclosed quote (or a line that never
|
|
452
|
+
* ends) is never completed, so it would be buffered forever. Every UTF-16 code unit encodes
|
|
453
|
+
* to at least one UTF-8 byte, so a pending length above the limit proves the row is too
|
|
454
|
+
* large without measuring it — reading `.length` does not flatten the appended rope. Two
|
|
455
|
+
* code units of slack cover a line ending that has only partly arrived.
|
|
456
|
+
*/
|
|
457
|
+
pendingRowLimitError(pendingLength) {
|
|
458
|
+
const limit = this.parseConfig.maxRowBytes;
|
|
459
|
+
if (limit === undefined || pendingLength <= limit + 2) {
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
return new CsvError(`Row exceeds the maximum size of ${limit} bytes`);
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Emit the rows this chunk completed, then fail if the unfinished row left behind is
|
|
466
|
+
* already over the limit. The completed rows go out first: they are valid, and the
|
|
467
|
+
* error belongs to the row after them. `callback` is called exactly once.
|
|
468
|
+
*/
|
|
469
|
+
finishChunk(rows, pendingLength, callback) {
|
|
470
|
+
const limitError = this.pendingRowLimitError(pendingLength);
|
|
471
|
+
if (!limitError) {
|
|
472
|
+
this.processPendingRows(rows, callback);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
// A consumer that aborted from its chunk callback asked for no more rows, not an error.
|
|
476
|
+
this.processPendingRows(rows, err => callback(err ?? (this.chunkAborted ? null : limitError)));
|
|
477
|
+
}
|
|
447
478
|
/**
|
|
448
479
|
* Reset info state for next row (used when skipping rows or after processing)
|
|
449
480
|
*/
|
|
@@ -497,7 +528,7 @@ export class CsvParserStream extends Transform {
|
|
|
497
528
|
}
|
|
498
529
|
// Scanner internally tracks unconsumed data - no need to reset
|
|
499
530
|
// It will continue from where it left off on the next feed()
|
|
500
|
-
this.
|
|
531
|
+
this.finishChunk(pendingRows, this.scanner.pendingLength(), callback);
|
|
501
532
|
}
|
|
502
533
|
/**
|
|
503
534
|
* Run the hook and BOM strip on the first text, exactly as `Csv.parse` does.
|
|
@@ -712,12 +743,17 @@ export class CsvParserStream extends Transform {
|
|
|
712
743
|
// Nothing has arrived that could end a line, so the buffer is left alone entirely —
|
|
713
744
|
// touching it would flatten the rope built by appending. See fastModeLineEndPending.
|
|
714
745
|
if (!this.fastModeLineEndPending) {
|
|
715
|
-
callback();
|
|
746
|
+
callback(this.pendingRowLimitError(this.buffer.length));
|
|
716
747
|
return;
|
|
717
748
|
}
|
|
718
749
|
const completeEnd = this.getFastModeCompleteDataEnd(this.buffer);
|
|
719
750
|
// If no complete line, wait for more data
|
|
720
751
|
if (completeEnd === -1) {
|
|
752
|
+
const limitError = this.pendingRowLimitError(this.buffer.length);
|
|
753
|
+
if (limitError) {
|
|
754
|
+
callback(limitError);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
721
757
|
// The buffer stays, so record that it holds no line ending and spare the next
|
|
722
758
|
// chunk from searching it again. Backing off by one character short of the
|
|
723
759
|
// longest line ending keeps a line ending that straddles the mark reachable —
|
|
@@ -768,7 +804,7 @@ export class CsvParserStream extends Transform {
|
|
|
768
804
|
return;
|
|
769
805
|
}
|
|
770
806
|
}
|
|
771
|
-
this.
|
|
807
|
+
this.finishChunk(pendingRows, this.buffer.length, callback);
|
|
772
808
|
}
|
|
773
809
|
buildRow(rawRow, info) {
|
|
774
810
|
const { dynamicTyping, castDate, groupColumnsByName = false } = this.options;
|
|
@@ -879,7 +915,7 @@ export class CsvParserStream extends Transform {
|
|
|
879
915
|
if (raw !== undefined && this.parseConfig.maxRowBytes !== undefined) {
|
|
880
916
|
const rawBytes = getUtf8ByteLength(raw);
|
|
881
917
|
if (rawBytes > this.parseConfig.maxRowBytes) {
|
|
882
|
-
callback(new
|
|
918
|
+
callback(new CsvError(`Row exceeds the maximum size of ${this.parseConfig.maxRowBytes} bytes`));
|
|
883
919
|
return "error";
|
|
884
920
|
}
|
|
885
921
|
}
|