From b8ddf28b5b1ca9682355d40da5f0280b91d2a141 Mon Sep 17 00:00:00 2001 From: TheNorthMemory Date: Tue, 24 Aug 2021 19:10:06 +0800 Subject: [PATCH] better `Hash` & `Multipart`, ref #34 --- lib/hash.js | 45 +++++++++++++++++++++++++++++----------- lib/multipart.js | 54 +++++++++++++++++++++++++++--------------------- 2 files changed, 63 insertions(+), 36 deletions(-) diff --git a/lib/hash.js b/lib/hash.js index d9534db..fcf5977 100644 --- a/lib/hash.js +++ b/lib/hash.js @@ -1,6 +1,19 @@ const { createHash, createHmac, timingSafeEqual } = require('crypto'); -const Formatter = require('./formatter'); +const { queryStringLike, ksort } = require('./formatter'); + +/** @constant 'md5' */ +const md5 = 'md5'; +/** @constant 'hex' */ +const hex = 'hex'; +/** @constant 'sha256' */ +const sha256 = 'sha256'; +/** @constant 'sha1' */ +const sha1 = 'sha1'; +/** @constant 'MD5' */ +const ALGO_MD5 = 'MD5'; +/** @constant 'HMAC-SHA256' */ +const ALGO_HMAC_SHA256 = 'HMAC-SHA256'; /** * Crypto hash functions utils. @@ -20,7 +33,11 @@ class Hash { * @return {string} - data signature */ static md5(thing, key = '', agency = false) { - return createHash('md5').update(thing).update(`${key ? `&${agency ? 'secret' : 'key'}=${key}` : ''}`).digest('hex'); + return createHash(md5) + .update(thing) + .update(key ? `&${agency} ? 'secret=' : '&key='` : '') + .update(key) + .digest(hex); } /** @@ -30,12 +47,16 @@ class Hash { * @param {string} [algorithm = sha256] - The algorithm string, default is `sha256`. * @return {string} - data signature */ - static hmac(thing, key, algorithm = 'sha256') { - return createHmac(algorithm, key).update(thing).update(`&key=${key}`).digest('hex'); + static hmac(thing, key, algorithm = sha256) { + return createHmac(algorithm, key) + .update(thing) + .update('&key=') + .update(key) + .digest(hex); } /** - * @deprecated {@since v0.5.5}, instead of by `hmac` + * @deprecated v0.5.5, use the `Hash.hmac` method instead * * Calculate the input string with a secret `key` in HMAC-SHA256 * @param {string|Buffer} thing - The input string. @@ -43,7 +64,7 @@ class Hash { * @return {string} - data signature */ static hmacSha256(thing, key) { - return this.hmac(thing, key, 'sha256'); + return this.hmac(thing, key, sha256); } /** @@ -52,7 +73,7 @@ class Hash { * @return {string} - data signature */ static sha1(thing) { - return createHash('sha1').update(thing).digest('hex'); + return createHash(sha1).update(thing).digest(hex); } /** @@ -61,7 +82,7 @@ class Hash { * @return {string} - data signature */ static sha256(thing) { - return createHash('sha256').update(thing).digest('hex'); + return createHash(sha256).update(thing).digest(hex); } /** @@ -73,7 +94,7 @@ class Hash { static equals(known, user) { const a = Buffer.from(known); const b = (user === undefined || user === null) ? undefined : Buffer.from(user); - return undefined !== b && a.length === b.length && timingSafeEqual(a, b); + return Buffer.isBuffer(b) && a.length === b.length && timingSafeEqual(a, b); } /** @@ -85,11 +106,11 @@ class Hash { */ static sign(type, data, key) { const alias = { - MD5: this.md5, - 'HMAC-SHA256': this.hmac, + [ALGO_MD5]: this.md5, + [ALGO_HMAC_SHA256]: this.hmac, }; - return alias[type](Formatter.queryStringLike(Formatter.ksort(data)), key).toUpperCase(); + return alias[type](queryStringLike(ksort(data)), key).toUpperCase(); } } diff --git a/lib/multipart.js b/lib/multipart.js index 7f258d9..9d598fa 100644 --- a/lib/multipart.js +++ b/lib/multipart.js @@ -2,7 +2,9 @@ const { extname } = require('path'); const { Readable } = require('stream'); const { ReadStream } = require('fs'); -const { iterator, toStringTag } = Symbol; +const HYPHENS = Buffer.from('--'); +const EMPTY = Buffer.from([]); +const CRLF = Buffer.from('\r\n'); /** * Simple and lite of `multipart/form-data` implementation, most similar to `form-data`. @@ -57,9 +59,10 @@ class Multipart extends Readable { * @readonly * @memberof Multipart# * @prop {Buffer} dashDash - Double `dash` buffer + * @deprecated v1.0.0 - Only for compatible, use the literal `Buffer.from('--')` Buffer instead */ dashDash: { - value: Buffer.from('--'), + value: HYPHENS, configurable: false, enumerable: false, writable: false, @@ -82,9 +85,10 @@ class Multipart extends Readable { * @readonly * @memberof Multipart# * @prop {Buffer} EMPTY - An empty buffer + * @deprecated v1.0.0 - Only for compatible, use the literal `Buffer.from([])` Buffer instead */ EMPTY: { - value: Buffer.from([]), + value: EMPTY, configurable: false, enumerable: false, writable: false, @@ -93,10 +97,11 @@ class Multipart extends Readable { /** * @readonly * @memberof Multipart# - * @prop {Buffer} CRLF - Double `dash` buffer + * @prop {Buffer} CRLF - The `CRLF` characters buffer + * @deprecated v1.0.0 - Only for compatible, use the literal `Buffer.from('\r\n')` Buffer instead */ CRLF: { - value: Buffer.from('\r\n'), + value: CRLF, configurable: false, enumerable: false, writable: false, @@ -183,13 +188,11 @@ class Multipart extends Readable { * @returns {this} - The `Multipart` class instance self */ append(name, value, filename = '') { - const { - data, dashDash, boundary, CRLF, indices, - } = this; + const { data, boundary, indices } = this; - data.splice(...(data.length ? [-2, 1] : [0, 0, dashDash, boundary, CRLF])); + data.splice(...(data.length ? [-2, 1] : [0, 0, HYPHENS, boundary, CRLF])); indices.push([name, data.push(...this.formed(name, value, filename)) - 1]); - data.push(CRLF, dashDash, boundary, dashDash, CRLF); + data.push(CRLF, HYPHENS, boundary, HYPHENS, CRLF); return this; } @@ -204,7 +207,7 @@ class Multipart extends Readable { * @returns {Array} - The part of data */ formed(name, value, filename = '') { - const { mimeTypes, CRLF, EMPTY } = this; + const { mimeTypes } = this; const isBufferOrStream = Buffer.isBuffer(value) || (value instanceof ReadStream); return [ Buffer.from(`Content-Disposition: form-data; name="${name}"${filename && isBufferOrStream ? `; filename="${filename}"` : ''}`), @@ -307,21 +310,21 @@ class Multipart extends Readable { * * @return {Iterator>>} - An Array Iterator key/value pairs. */ - entries() { return this.indices.map(([name, index]) => [name, this.data[index]])[iterator](); } + entries() { return this.indices.map(([name, index]) => [name, this.data[index]])[Symbol.iterator](); } /** * To go through all keys contained in {@link Multipart#data} instance * * @return {Iterator} - An Array Iterator key pairs. */ - keys() { return this.indices.map(([name]) => name)[iterator](); } + keys() { return this.indices.map(([name]) => name)[Symbol.iterator](); } /** * To go through all values contained in {@link Multipart#data} instance * * @return {Iterator} - An Array Iterator value pairs. */ - values() { return this.indices.map(([, index]) => this.data[index])[iterator](); } + values() { return this.indices.map(([, index]) => this.data[index])[Symbol.iterator](); } /** * The WeChatPay APIv3' specific, the `meta` JSON @@ -334,22 +337,22 @@ class Multipart extends Readable { * alias of {@link Multipart#entries} * @return {Iterator>>} - An Array Iterator key/value pairs. */ - [iterator]() { return this.entries(); } + [Symbol.iterator]() { return this.entries(); } /** * @returns {string} - FormData string */ - static get [toStringTag]() { return 'FormData'; } + static get [Symbol.toStringTag]() { return 'FormData'; } /** * @returns {string} - FormData string */ - get [toStringTag]() { return this.constructor[toStringTag]; } + get [Symbol.toStringTag]() { return this.constructor[Symbol.toStringTag]; } /** * @returns {string} - FormData string */ - toString() { return `[object ${this[toStringTag]}]`; } + toString() { return `[object ${this[Symbol.toStringTag]}]`; } /* eslint-disable-next-line class-methods-use-this, no-underscore-dangle */ _read() {} @@ -357,9 +360,11 @@ class Multipart extends Readable { /** * Pushing {@link Multipart#data} into the readable BufferList * + * @param {boolean} [end = true] - End the writer when the reader ends. Default: `true`. Available {@since v1.0.0} + * * @returns {Promise} - The Multipart instance */ - async flowing() { + async flowing(end = true) { /* eslint-disable-next-line no-restricted-syntax */ for (const value of this.data) { if (value instanceof ReadStream) { @@ -371,7 +376,7 @@ class Multipart extends Readable { this.push(value); } } - this.push(null); + if (end) { this.push(null); } return this; } @@ -385,8 +390,9 @@ class Multipart extends Readable { * @returns {stream.Writable} - The destination, allowing for a chain of pipes */ pipe(destination, options = { end: true }) { + const { end = true } = options; super.pipe(destination, options); - this.flowing(); + this.flowing(end); return destination; } } @@ -396,9 +402,9 @@ try { /* eslint-disable-next-line global-require, import/no-unresolved, import/no-extraneous-dependencies */ FormData = require('form-data'); /* @see [issue#396 `Object.prototype.toString.call(form)`]{@link https://github.com/form-data/form-data/issues/396} */ - if (!Reflect.has(FormData, toStringTag)) { - Reflect.set(FormData.prototype, toStringTag, 'FormData'); - Reflect.set(FormData, toStringTag, FormData.prototype[toStringTag]); + if (!Reflect.has(FormData, Symbol.toStringTag)) { + Reflect.set(FormData.prototype, Symbol.toStringTag, 'FormData'); + Reflect.set(FormData, Symbol.toStringTag, FormData.prototype[Symbol.toStringTag]); } } catch (e) { /* eslint max-classes-per-file: ["error", 2] */