node-pkware 1.0.0-beta.4 → 1.0.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 CHANGED
@@ -64,7 +64,7 @@ Returns a function, that you can use as a [transform.\_transform](https://nodejs
64
64
 
65
65
  Takes an optional config object, which has the following properties:
66
66
 
67
- ```
67
+ ```js
68
68
  {
69
69
  debug: boolean, // whether the code should display debug messages on the console or not (default = false)
70
70
  inputBufferSize: int, // the starting size of the input buffer, may expand later as needed. Not having to expand may have performance impact (default 0)
@@ -78,7 +78,7 @@ Takes an optional config object, which has the following properties:
78
78
 
79
79
  Takes an optional config object, which has the following properties:
80
80
 
81
- ```
81
+ ```js
82
82
  {
83
83
  debug: boolean, // whether the code should display debug messages on the console or not (default = false)
84
84
  inputBufferSize: int, // the starting size of the input buffer, may expand later as needed. Not having to expand may have performance impact (default 0)
package/bin/explode.js CHANGED
@@ -1,45 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const fs = require('fs')
4
- const minimist = require('minimist')
4
+ const minimist = require('minimist-lite')
5
5
  const { getPackageVersion, parseNumberString, fileExists } = require('../src/helpers/functions.js')
6
6
  const { transformEmpty, transformIdentity, transformSplitBy, splitAt, through } = require('../src/helpers/stream.js')
7
7
  const { explode } = require('../src/explode.js')
8
- // const {
9
- // COMPRESSION_BINARY,
10
- // COMPRESSION_ASCII,
11
- // DICTIONARY_SIZE_SMALL,
12
- // DICTIONARY_SIZE_MEDIUM,
13
- // DICTIONARY_SIZE_LARGE
14
- // } = require('../src/constants.js')
15
8
 
16
9
  const args = minimist(process.argv.slice(2), {
17
10
  string: ['output', 'offset', 'input-buffer-size', 'output-buffer-size'],
18
- boolean: ['version', 'drop-before-offset', 'debug' /*, 'auto-detect' */],
11
+ boolean: ['version', 'drop-before-offset', 'debug'],
19
12
  alias: {
20
13
  v: 'version'
21
14
  }
22
15
  })
23
16
 
24
- const decompress = (input, output, offset, /* autoDetect, */ keepHeader, config) => {
17
+ const decompress = (input, output, offset, keepHeader, config) => {
25
18
  const leftHandler = keepHeader ? transformIdentity() : transformEmpty()
26
19
  const rightHandler = explode(config)
27
20
 
28
- let handler = rightHandler
29
-
30
- // if (autoDetect) {
31
- // const everyPkwareHeader = [
32
- // Buffer.from([COMPRESSION_BINARY, DICTIONARY_SIZE_SMALL]),
33
- // Buffer.from([COMPRESSION_BINARY, DICTIONARY_SIZE_MEDIUM]),
34
- // Buffer.from([COMPRESSION_BINARY, DICTIONARY_SIZE_LARGE]),
35
- // Buffer.from([COMPRESSION_ASCII, DICTIONARY_SIZE_SMALL]),
36
- // Buffer.from([COMPRESSION_ASCII, DICTIONARY_SIZE_MEDIUM]),
37
- // Buffer.from([COMPRESSION_ASCII, DICTIONARY_SIZE_LARGE])
38
- // ]
39
- // handler = transformSplitBy(splitAtMatch(everyPkwareHeader, offset, config.debug), leftHandler, rightHandler)
40
- // } else if (offset > 0) {
41
- handler = transformSplitBy(splitAt(offset), leftHandler, rightHandler)
42
- // }
21
+ const handler = transformSplitBy(splitAt(offset), leftHandler, rightHandler)
43
22
 
44
23
  return new Promise((resolve, reject) => {
45
24
  input.pipe(through(handler).on('error', reject)).pipe(output).on('finish', resolve).on('error', reject)
@@ -79,7 +58,6 @@ const decompress = (input, output, offset, /* autoDetect, */ keepHeader, config)
79
58
  }
80
59
 
81
60
  const offset = parseNumberString(args.offset, 0)
82
- // const autoDetect = args['auto-detect']
83
61
 
84
62
  const keepHeader = !args['drop-before-offset']
85
63
  const config = {
@@ -88,7 +66,7 @@ const decompress = (input, output, offset, /* autoDetect, */ keepHeader, config)
88
66
  outputBufferSize: parseNumberString(args['output-buffer-size'], 0x40000)
89
67
  }
90
68
 
91
- decompress(input, output, offset, /* autoDetect, */ keepHeader, config)
69
+ decompress(input, output, offset, keepHeader, config)
92
70
  .then(() => {
93
71
  process.exit(0)
94
72
  })
package/bin/implode.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const fs = require('fs')
4
- const minimist = require('minimist')
4
+ const minimist = require('minimist-lite')
5
5
  const {
6
6
  COMPRESSION_BINARY,
7
7
  COMPRESSION_ASCII,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-pkware",
3
- "version": "1.0.0-beta.4",
3
+ "version": "1.0.0",
4
4
  "description": "The nodejs implementation of StormLib's pkware compressor/de-compressor",
5
5
  "main": "src/index.js",
6
6
  "types": "types/index.d.ts",
@@ -28,28 +28,28 @@
28
28
  "author": "Lajos Meszaros <m_lajos@hotmail.com>",
29
29
  "license": "GPL-3.0-or-later",
30
30
  "dependencies": {
31
- "minimist": "^1.2.5",
31
+ "minimist-lite": "^2.0.0",
32
32
  "ramda": "^0.27.1",
33
- "ramda-adjunct": "^2.33.0"
33
+ "ramda-adjunct": "^2.35.0"
34
34
  },
35
35
  "devDependencies": {
36
- "arx-header-size": "^0.4.4",
36
+ "arx-header-size": "^0.6.1",
37
37
  "binary-comparator": "^0.5.0",
38
- "eslint": "^7.32.0",
38
+ "eslint": "^8.5.0",
39
39
  "eslint-config-prettier": "^8.3.0",
40
40
  "eslint-config-prettier-standard": "^4.0.1",
41
41
  "eslint-config-standard": "^16.0.3",
42
- "eslint-plugin-import": "^2.24.2",
42
+ "eslint-plugin-import": "^2.25.3",
43
43
  "eslint-plugin-node": "^11.1.0",
44
44
  "eslint-plugin-prettier": "^4.0.0",
45
- "eslint-plugin-promise": "^5.1.0",
45
+ "eslint-plugin-promise": "^6.0.0",
46
46
  "eslint-plugin-ramda": "^2.5.1",
47
47
  "eslint-plugin-standard": "^4.1.0",
48
- "lint-staged": "^11.1.2",
49
- "mocha": "^9.1.1",
50
- "nodemon": "^2.0.12",
48
+ "lint-staged": "^12.1.3",
49
+ "mocha": "^9.1.3",
50
+ "nodemon": "^2.0.15",
51
51
  "pre-commit": "^1.2.2",
52
- "prettier": "^2.3.2",
52
+ "prettier": "^2.5.1",
53
53
  "prettier-config-standard": "^4.0.0"
54
54
  },
55
55
  "pre-commit": [
package/src/explode.js CHANGED
@@ -128,9 +128,9 @@ const parseInitialData = (state, debug = false) => {
128
128
  }
129
129
 
130
130
  if (debug) {
131
- console.log(`compression type: ${state.compressionType === COMPRESSION_BINARY ? 'binary' : 'ascii'}`)
131
+ console.log(`explode: compression type: ${state.compressionType === COMPRESSION_BINARY ? 'binary' : 'ascii'}`)
132
132
  console.log(
133
- `compression level: ${
133
+ `explode: compression level: ${
134
134
  state.dictionarySizeBits === 4 ? 'small' : state.dictionarySizeBits === 5 ? 'medium' : 'large'
135
135
  }`
136
136
  )
@@ -331,7 +331,7 @@ const explode = (config = {}) => {
331
331
  }
332
332
 
333
333
  if (debug) {
334
- console.log(`reading ${toHex(chunk.length)} bytes from chunk #${state.stats.chunkCounter++}`)
334
+ console.log(`explode: reading ${toHex(chunk.length)} bytes from chunk #${state.stats.chunkCounter++}`)
335
335
  }
336
336
 
337
337
  processChunkData(state, debug)
@@ -369,9 +369,9 @@ const explode = (config = {}) => {
369
369
 
370
370
  if (debug) {
371
371
  console.log('---------------')
372
- console.log('total number of chunks read:', state.stats.chunkCounter)
373
- console.log('inputBuffer heap size', toHex(state.inputBuffer.heapSize()))
374
- console.log('outputBuffer heap size', toHex(state.outputBuffer.heapSize()))
372
+ console.log('explode: total number of chunks read:', state.stats.chunkCounter)
373
+ console.log('explode: inputBuffer heap size', toHex(state.inputBuffer.heapSize()))
374
+ console.log('explode: outputBuffer heap size', toHex(state.outputBuffer.heapSize()))
375
375
  }
376
376
 
377
377
  if (state.needMoreInput) {
@@ -44,6 +44,7 @@ class ExpandingBuffer {
44
44
  }
45
45
  }
46
46
 
47
+ // watch out! the buffer returned by Buffer.slice() will point to the same memory!
47
48
  read(offset, limit) {
48
49
  if (offset < 0 || limit < 1) {
49
50
  return Buffer.from([])
@@ -3,6 +3,8 @@ const { promisify } = require('util')
3
3
  const { isFunction } = require('ramda-adjunct')
4
4
  const ExpandingBuffer = require('./ExpandingBuffer.js')
5
5
 
6
+ const emptyBuffer = Buffer.from([])
7
+
6
8
  class QuasiTransform {
7
9
  constructor(handler) {
8
10
  this.handler = handler
@@ -33,12 +35,12 @@ const splitAt = index => {
33
35
 
34
36
  if (index <= cntr) {
35
37
  // index ..... cntr ..... chunk.length
36
- left = Buffer.from([])
38
+ left = emptyBuffer
37
39
  right = chunk
38
40
  } else if (index >= cntr + chunk.length) {
39
41
  // cntr ..... chunk.length ..... index
40
42
  left = chunk
41
- right = Buffer.from([])
43
+ right = emptyBuffer
42
44
  isLeftDone = index === cntr + chunk.length
43
45
  } else {
44
46
  // cntr ..... index ..... chunk.length
@@ -60,7 +62,7 @@ const transformIdentity = () => {
60
62
 
61
63
  const transformEmpty = () => {
62
64
  return function (chunk, encoding, callback) {
63
- callback(null, Buffer.from([]))
65
+ callback(null, emptyBuffer)
64
66
  }
65
67
  }
66
68
 
@@ -73,6 +75,8 @@ const through = handler => {
73
75
  const transformSplitBy = (predicate, leftHandler, rightHandler) => {
74
76
  let isFirstChunk = true
75
77
  let wasLeftFlushCalled = false
78
+ const damChunkSize = 0x10000
79
+ const dam = new ExpandingBuffer()
76
80
 
77
81
  const leftTransform = new QuasiTransform(leftHandler)
78
82
  const rightTransform = new QuasiTransform(rightHandler)
@@ -86,8 +90,12 @@ const transformSplitBy = (predicate, leftHandler, rightHandler) => {
86
90
  if (isFirstChunk) {
87
91
  isFirstChunk = false
88
92
  this._flush = flushCallback => {
89
- let leftFiller = Promise.resolve(Buffer.from([]))
90
- let rightFiller = Promise.resolve(Buffer.from([]))
93
+ if (!dam.isEmpty()) {
94
+ this.push(dam.read())
95
+ }
96
+
97
+ let leftFiller = Promise.resolve(emptyBuffer)
98
+ let rightFiller = Promise.resolve(emptyBuffer)
91
99
 
92
100
  if (!wasLeftFlushCalled && isFunction(leftTransform._flush)) {
93
101
  leftFiller = new Promise((resolve, reject) => {
@@ -123,7 +131,7 @@ const transformSplitBy = (predicate, leftHandler, rightHandler) => {
123
131
  }
124
132
  }
125
133
 
126
- let filler = Promise.resolve(Buffer.from([]))
134
+ let filler = Promise.resolve(emptyBuffer)
127
135
  if (isLeftDone && !wasLeftFlushCalled && isFunction(leftTransform._flush)) {
128
136
  wasLeftFlushCalled = true
129
137
  filler = new Promise((resolve, reject) => {
@@ -139,7 +147,18 @@ const transformSplitBy = (predicate, leftHandler, rightHandler) => {
139
147
 
140
148
  Promise.all([_left, filler, _right])
141
149
  .then(buffers => {
142
- callback(null, Buffer.concat(buffers))
150
+ dam.append(Buffer.concat(buffers))
151
+ if (dam.size() > damChunkSize) {
152
+ const chunks = Math.floor(dam.size() / damChunkSize)
153
+ const data = Buffer.from(dam.read(0, chunks * damChunkSize))
154
+ dam.flushStart(chunks * damChunkSize)
155
+ for (let i = 0; i < chunks - 1; i++) {
156
+ this.push(data.slice(i * damChunkSize, i * damChunkSize + damChunkSize))
157
+ }
158
+ callback(null, data.slice((chunks - 1) * damChunkSize))
159
+ } else {
160
+ callback(null, emptyBuffer)
161
+ }
143
162
  })
144
163
  .catch(err => {
145
164
  callback(err)
@@ -161,56 +180,11 @@ const streamToBuffer = done => {
161
180
  })
162
181
  }
163
182
 
164
- /*
165
- export const splitAtMatch = (matches, skipBytes = 0, debug = false) => {
166
- let alreadyMatched = false
167
- const empty = Buffer.from([])
168
-
169
- return (chunk, offset) => {
170
- if (alreadyMatched) {
171
- return {
172
- left: empty,
173
- right: chunk
174
- }
175
- }
176
-
177
- const idxs = matches
178
- .map(bytes => chunk.indexOf(bytes))
179
- .filter(idx => idx > -1)
180
- .sort(subtract)
181
- .filter(idx => idx + offset >= skipBytes)
182
-
183
- if (idxs.length === 0) {
184
- return {
185
- left: empty,
186
- right: chunk
187
- }
188
- }
189
-
190
- alreadyMatched = true
191
- if (debug) {
192
- console.log(`found pkware header ${dumpBytes(chunk.slice(idxs[0], idxs[0] + 2))} at ${toHex(idxs[0])}`)
193
- }
194
- return splitAtIndex(idxs[0])(chunk, offset)
195
- }
196
- }
197
- */
198
-
199
- const outputInChunks = (buffer, stream) => {
200
- const chunks = Math.ceil(buffer.length / 1000)
201
- for (let i = 0; i < chunks - 1; i++) {
202
- stream.write(buffer.slice(i * 1000, (i + 1) * 1000))
203
- }
204
- stream.write(buffer.slice((chunks - 1) * 1000))
205
- stream.end()
206
- }
207
-
208
183
  module.exports = {
209
184
  splitAt,
210
185
  transformIdentity,
211
186
  transformEmpty,
212
187
  through,
213
188
  transformSplitBy,
214
- streamToBuffer,
215
- outputInChunks
189
+ streamToBuffer
216
190
  }
@@ -54,6 +54,11 @@ const buffersShouldEqual = (expected, result, offset = 0, displayAsHex = false)
54
54
  if (!Buffer.isBuffer(expected)) {
55
55
  throw new Error('expected is not a Buffer')
56
56
  }
57
+
58
+ if (!Buffer.isBuffer(result)) {
59
+ throw new Error('result is not a Buffer')
60
+ }
61
+
57
62
  const diff = report(expected, result, compare(expected, result, offset), displayAsHex)
58
63
  assert.ok(expected.equals(result), diff)
59
64
  }
package/src/implode.js CHANGED
@@ -114,7 +114,9 @@ const getSizeOfMatching = (inputBytes, a, b) => {
114
114
  // us to store backward length in less amount of bits
115
115
  // currently the code goes from the furthest point
116
116
  const findRepetitions = (inputBytes, endOfLastMatch, cursor) => {
117
- if (endOfLastMatch === cursor || cursor - endOfLastMatch < 2) {
117
+ const notEnoughBytes = inputBytes.length - cursor < 2
118
+ const tooClose = cursor === endOfLastMatch || cursor - endOfLastMatch < 2
119
+ if (notEnoughBytes || tooClose) {
118
120
  return { size: 0, distance: 0 }
119
121
  }
120
122
 
@@ -191,41 +193,37 @@ const processChunkData = (state, debug = false) => {
191
193
 
192
194
  /* eslint-disable prefer-const */
193
195
 
194
- let endOfLastMatch = 0
196
+ let endOfLastMatch = 0 // used when searching for longer repetitions later
195
197
  while (state.startIndex < state.inputBuffer.size()) {
196
198
  let { size, distance } = findRepetitions(state.inputBuffer.read(endOfLastMatch), endOfLastMatch, state.startIndex)
197
199
 
198
- const isFlushable = isRepetitionFlushable(size, distance, state.startIndex, state.inputBuffer.size())
200
+ let isFlushable = isRepetitionFlushable(size, distance, state.startIndex, state.inputBuffer.size())
199
201
 
200
202
  if (isFlushable === false) {
201
203
  const byte = state.inputBuffer.read(state.startIndex, 1)
202
204
  outputBits(state, state.nChBits[byte], state.nChCodes[byte])
203
205
  state.startIndex += 1
204
206
  } else {
205
- /*
206
207
  if (isFlushable === null) {
208
+ /*
207
209
  // Try to find better repetition 1 byte later.
208
210
  // stormlib/implode.c L517
209
-
210
- // let cursor = state.startIndex
211
- // let newSize = size
212
- // let newDistance = distance
213
- // let currentSize
214
- // let currentDistance
215
- // while (newSize <= currentSize && isRepetitionFlushable(newSize, newDistance, state.startIndex, state.inputBuffer.size())) {
216
- // currentSize = newSize
217
- // currentDistance = newDistance
218
- // const reps = findRepetitions(state.inputBuffer.read(endOfLastMatch), endOfLastMatch, ++cursor)
219
- // newSize = reps.size
220
- // newDistance = reps.distance
221
- // }
222
- // size = newSize
223
- // distance = currentDistance
211
+ let cursor = state.startIndex
212
+ let newSize = size
213
+ let newDistance = distance
214
+ let currentSize
215
+ let currentDistance
216
+ while (newSize <= currentSize && isRepetitionFlushable(newSize, newDistance, state.startIndex, state.inputBuffer.size())) {
217
+ currentSize = newSize
218
+ currentDistance = newDistance
219
+ const reps = findRepetitions(state.inputBuffer.read(endOfLastMatch), endOfLastMatch, ++cursor)
220
+ newSize = reps.size
221
+ newDistance = reps.distance
222
+ }
223
+ size = newSize
224
+ distance = currentDistance
225
+ */
224
226
  }
225
- */
226
-
227
- /*
228
- endOfLastMatch = state.startIndex + size
229
227
 
230
228
  const byte = size + 0xfe
231
229
  outputBits(state, state.nChBits[byte], state.nChCodes[byte])
@@ -240,18 +238,24 @@ const processChunkData = (state, debug = false) => {
240
238
  }
241
239
 
242
240
  state.startIndex += size
243
- */
244
-
245
- // TODO: temporarily write out data byte-by-byte here too, because above block with minimal repetition
246
- // flushing breaks the compression self check tests
247
- const byte = state.inputBuffer.read(state.startIndex, 1)
248
- outputBits(state, state.nChBits[byte], state.nChCodes[byte])
249
- state.startIndex += 1
250
241
  }
251
242
 
243
+ /*
252
244
  state.inputBuffer.dropStart(endOfLastMatch)
253
245
  state.startIndex -= endOfLastMatch
254
246
  endOfLastMatch = 0
247
+ */
248
+
249
+ if (state.dictionarySizeBits === DICTIONARY_SIZE_SMALL && state.startIndex >= 0x400) {
250
+ state.inputBuffer.dropStart(0x400)
251
+ state.startIndex -= 0x400
252
+ } else if (state.dictionarySizeBits === DICTIONARY_SIZE_MEDIUM && state.startIndex >= 0x800) {
253
+ state.inputBuffer.dropStart(0x800)
254
+ state.startIndex -= 0x800
255
+ } else if (state.dictionarySizeBits === DICTIONARY_SIZE_LARGE && state.startIndex >= 0x1000) {
256
+ state.inputBuffer.dropStart(0x1000)
257
+ state.startIndex -= 0x1000
258
+ }
255
259
  }
256
260
 
257
261
  /* eslint-enable prefer-const */
@@ -286,7 +290,7 @@ const implode = (compressionType, dictionarySizeBits, config = {}) => {
286
290
  }
287
291
 
288
292
  if (debug) {
289
- console.log(`reading ${toHex(chunk.length)} bytes from chunk #${state.stats.chunkCounter++}`)
293
+ console.log(`implode: reading ${toHex(chunk.length)} bytes from chunk #${state.stats.chunkCounter++}`)
290
294
  }
291
295
 
292
296
  processChunkData(state, debug)
@@ -331,9 +335,9 @@ const implode = (compressionType, dictionarySizeBits, config = {}) => {
331
335
 
332
336
  if (debug) {
333
337
  console.log('---------------')
334
- console.log('total number of chunks read:', state.stats.chunkCounter)
335
- console.log('inputBuffer heap size', toHex(state.inputBuffer.heapSize()))
336
- console.log('outputBuffer heap size', toHex(state.outputBuffer.heapSize()))
338
+ console.log('implode: total number of chunks read:', state.stats.chunkCounter)
339
+ console.log('implode: inputBuffer heap size', toHex(state.inputBuffer.heapSize()))
340
+ console.log('implode: outputBuffer heap size', toHex(state.outputBuffer.heapSize()))
337
341
  }
338
342
 
339
343
  callback(null, state.outputBuffer.read())
@@ -13,7 +13,7 @@ declare class ExpandingBuffer {
13
13
  isEmpty(): boolean
14
14
  heapSize(): number
15
15
  append(buffer: Buffer): void
16
- read(offset: number, limit: number): number | Buffer
16
+ read(offset?: number, limit?: number): number | Buffer
17
17
  flushStart(numberOfBytes: number): void
18
18
  flushEnd(numberOfBytes: number): void
19
19
  dropStart(numberOfBytes: number): void