Skip to content

Commit 1069f61

Browse files
authoredOct 29, 2021
fix: md4 support on Node.js v17 (#193)
1 parent d9f4e23 commit 1069f61

File tree

6 files changed

+329
-68
lines changed

6 files changed

+329
-68
lines changed
 

‎.github/workflows/nodejs.yml

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
name: loader-utils
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
- next
8+
pull_request:
9+
branches:
10+
- master
11+
- next
12+
13+
jobs:
14+
lint:
15+
name: Lint - ${{ matrix.os }} - Node v${{ matrix.node-version }}
16+
17+
env:
18+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
19+
20+
strategy:
21+
matrix:
22+
os: [ubuntu-latest]
23+
node-version: [12.x]
24+
25+
runs-on: ${{ matrix.os }}
26+
27+
steps:
28+
- uses: actions/checkout@v2
29+
with:
30+
fetch-depth: 0
31+
32+
- name: Use Node.js ${{ matrix.node-version }}
33+
uses: actions/setup-node@v2
34+
with:
35+
node-version: ${{ matrix.node-version }}
36+
cache: 'yarn'
37+
38+
- name: Install dependencies
39+
run: yarn
40+
41+
- name: Lint
42+
run: yarn lint
43+
44+
- name: Security audit
45+
run: yarn audit
46+
47+
- name: Check commit message
48+
uses: wagoid/commitlint-github-action@v4
49+
50+
test:
51+
name: Test - ${{ matrix.os }} - Node v${{ matrix.node-version }}
52+
53+
strategy:
54+
matrix:
55+
os: [ubuntu-latest, windows-latest, macos-latest]
56+
node-version: [8.x, 10.x, 12.x, 14.x, 16.x, 17.x]
57+
58+
runs-on: ${{ matrix.os }}
59+
60+
steps:
61+
- name: Setup Git
62+
if: matrix.os == 'windows-latest'
63+
run: git config --global core.autocrlf input
64+
65+
- uses: actions/checkout@v2
66+
67+
- name: Use Node.js ${{ matrix.node-version }}
68+
uses: actions/setup-node@v2
69+
with:
70+
node-version: ${{ matrix.node-version }}
71+
cache: 'yarn'
72+
73+
- name: Install dependencies
74+
run: yarn
75+
76+
- name: Run tests
77+
run: yarn test
78+
79+
- name: Submit coverage data to codecov
80+
uses: codecov/codecov-action@v2
81+
with:
82+
token: ${{ secrets.CODECOV_TOKEN }}

‎.travis.yml

-36
This file was deleted.

‎appveyor.yml

-31
This file was deleted.

‎lib/getHashDigest.js

+19-1
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,29 @@ function encodeBufferToBase(buffer, base) {
3939
return output;
4040
}
4141

42+
let createMd4 = undefined;
43+
4244
function getHashDigest(buffer, hashType, digestType, maxLength) {
4345
hashType = hashType || 'md4';
4446
maxLength = maxLength || 9999;
4547

46-
const hash = require('crypto').createHash(hashType);
48+
let hash;
49+
50+
try {
51+
hash = require('crypto').createHash(hashType);
52+
} catch (error) {
53+
if (error.code === 'ERR_OSSL_EVP_UNSUPPORTED' && hashType === 'md4') {
54+
if (createMd4 === undefined) {
55+
createMd4 = require('./hash/md4');
56+
}
57+
58+
hash = createMd4();
59+
}
60+
61+
if (!hash) {
62+
throw error;
63+
}
64+
}
4765

4866
hash.update(buffer);
4967

‎lib/hash/md4.js

+20
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎lib/hash/wasm-hash.js

+208
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
Author Tobias Koppers @sokra
4+
*/
5+
6+
'use strict';
7+
8+
// 65536 is the size of a wasm memory page
9+
// 64 is the maximum chunk size for every possible wasm hash implementation
10+
// 4 is the maximum number of bytes per char for string encoding (max is utf-8)
11+
// ~3 makes sure that it's always a block of 4 chars, so avoid partially encoded bytes for base64
12+
const MAX_SHORT_STRING = Math.floor((65536 - 64) / 4) & ~3;
13+
14+
class WasmHash {
15+
/**
16+
* @param {WebAssembly.Instance} instance wasm instance
17+
* @param {WebAssembly.Instance[]} instancesPool pool of instances
18+
* @param {number} chunkSize size of data chunks passed to wasm
19+
* @param {number} digestSize size of digest returned by wasm
20+
*/
21+
constructor(instance, instancesPool, chunkSize, digestSize) {
22+
const exports = /** @type {any} */ (instance.exports);
23+
24+
exports.init();
25+
26+
this.exports = exports;
27+
this.mem = Buffer.from(exports.memory.buffer, 0, 65536);
28+
this.buffered = 0;
29+
this.instancesPool = instancesPool;
30+
this.chunkSize = chunkSize;
31+
this.digestSize = digestSize;
32+
}
33+
34+
reset() {
35+
this.buffered = 0;
36+
this.exports.init();
37+
}
38+
39+
/**
40+
* @param {Buffer | string} data data
41+
* @param {BufferEncoding=} encoding encoding
42+
* @returns {this} itself
43+
*/
44+
update(data, encoding) {
45+
if (typeof data === 'string') {
46+
while (data.length > MAX_SHORT_STRING) {
47+
this._updateWithShortString(data.slice(0, MAX_SHORT_STRING), encoding);
48+
data = data.slice(MAX_SHORT_STRING);
49+
}
50+
51+
this._updateWithShortString(data, encoding);
52+
53+
return this;
54+
}
55+
56+
this._updateWithBuffer(data);
57+
58+
return this;
59+
}
60+
61+
/**
62+
* @param {string} data data
63+
* @param {BufferEncoding=} encoding encoding
64+
* @returns {void}
65+
*/
66+
_updateWithShortString(data, encoding) {
67+
const { exports, buffered, mem, chunkSize } = this;
68+
69+
let endPos;
70+
71+
if (data.length < 70) {
72+
if (!encoding || encoding === 'utf-8' || encoding === 'utf8') {
73+
endPos = buffered;
74+
for (let i = 0; i < data.length; i++) {
75+
const cc = data.charCodeAt(i);
76+
77+
if (cc < 0x80) {
78+
mem[endPos++] = cc;
79+
} else if (cc < 0x800) {
80+
mem[endPos] = (cc >> 6) | 0xc0;
81+
mem[endPos + 1] = (cc & 0x3f) | 0x80;
82+
endPos += 2;
83+
} else {
84+
// bail-out for weird chars
85+
endPos += mem.write(data.slice(endPos), endPos, encoding);
86+
break;
87+
}
88+
}
89+
} else if (encoding === 'latin1') {
90+
endPos = buffered;
91+
92+
for (let i = 0; i < data.length; i++) {
93+
const cc = data.charCodeAt(i);
94+
95+
mem[endPos++] = cc;
96+
}
97+
} else {
98+
endPos = buffered + mem.write(data, buffered, encoding);
99+
}
100+
} else {
101+
endPos = buffered + mem.write(data, buffered, encoding);
102+
}
103+
104+
if (endPos < chunkSize) {
105+
this.buffered = endPos;
106+
} else {
107+
const l = endPos & ~(this.chunkSize - 1);
108+
109+
exports.update(l);
110+
111+
const newBuffered = endPos - l;
112+
113+
this.buffered = newBuffered;
114+
115+
if (newBuffered > 0) {
116+
mem.copyWithin(0, l, endPos);
117+
}
118+
}
119+
}
120+
121+
/**
122+
* @param {Buffer} data data
123+
* @returns {void}
124+
*/
125+
_updateWithBuffer(data) {
126+
const { exports, buffered, mem } = this;
127+
const length = data.length;
128+
129+
if (buffered + length < this.chunkSize) {
130+
data.copy(mem, buffered, 0, length);
131+
132+
this.buffered += length;
133+
} else {
134+
const l = (buffered + length) & ~(this.chunkSize - 1);
135+
136+
if (l > 65536) {
137+
let i = 65536 - buffered;
138+
139+
data.copy(mem, buffered, 0, i);
140+
exports.update(65536);
141+
142+
const stop = l - buffered - 65536;
143+
144+
while (i < stop) {
145+
data.copy(mem, 0, i, i + 65536);
146+
exports.update(65536);
147+
i += 65536;
148+
}
149+
150+
data.copy(mem, 0, i, l - buffered);
151+
152+
exports.update(l - buffered - i);
153+
} else {
154+
data.copy(mem, buffered, 0, l - buffered);
155+
156+
exports.update(l);
157+
}
158+
159+
const newBuffered = length + buffered - l;
160+
161+
this.buffered = newBuffered;
162+
163+
if (newBuffered > 0) {
164+
data.copy(mem, 0, length - newBuffered, length);
165+
}
166+
}
167+
}
168+
169+
digest(type) {
170+
const { exports, buffered, mem, digestSize } = this;
171+
172+
exports.final(buffered);
173+
174+
this.instancesPool.push(this);
175+
176+
const hex = mem.toString('latin1', 0, digestSize);
177+
178+
if (type === 'hex') {
179+
return hex;
180+
}
181+
182+
if (type === 'binary' || !type) {
183+
return Buffer.from(hex, 'hex');
184+
}
185+
186+
return Buffer.from(hex, 'hex').toString(type);
187+
}
188+
}
189+
190+
const create = (wasmModule, instancesPool, chunkSize, digestSize) => {
191+
if (instancesPool.length > 0) {
192+
const old = instancesPool.pop();
193+
194+
old.reset();
195+
196+
return old;
197+
} else {
198+
return new WasmHash(
199+
new WebAssembly.Instance(wasmModule),
200+
instancesPool,
201+
chunkSize,
202+
digestSize
203+
);
204+
}
205+
};
206+
207+
module.exports = create;
208+
module.exports.MAX_SHORT_STRING = MAX_SHORT_STRING;

0 commit comments

Comments
 (0)
Please sign in to comment.