Skip to content

Commit

Permalink
better Hash & Multipart, ref #34
Browse files Browse the repository at this point in the history
  • Loading branch information
TheNorthMemory committed Aug 24, 2021
1 parent c1deda1 commit b8ddf28
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 36 deletions.
45 changes: 33 additions & 12 deletions lib/hash.js
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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);
}

/**
Expand All @@ -30,20 +47,24 @@ 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.
* @param {string} key - The secret key string.
* @return {string} - data signature
*/
static hmacSha256(thing, key) {
return this.hmac(thing, key, 'sha256');
return this.hmac(thing, key, sha256);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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();
}
}

Expand Down
54 changes: 30 additions & 24 deletions lib/multipart.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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;
}
Expand All @@ -204,7 +207,7 @@ class Multipart extends Readable {
* @returns {Array<Buffer|ReadStream>} - 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}"` : ''}`),
Expand Down Expand Up @@ -307,21 +310,21 @@ class Multipart extends Readable {
*
* @return {Iterator<Array<EntryTuple<string|undefined, Buffer|ReadStream>>>} - 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<string|undefined>} - 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<Buffer|ReadStream>} - 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
Expand All @@ -334,32 +337,34 @@ class Multipart extends Readable {
* alias of {@link Multipart#entries}
* @return {Iterator<Array<EntryTuple<string|undefined, Buffer|ReadStream>>>} - 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() {}

/**
* 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<this>} - 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) {
Expand All @@ -371,7 +376,7 @@ class Multipart extends Readable {
this.push(value);
}
}
this.push(null);
if (end) { this.push(null); }

return this;
}
Expand All @@ -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;
}
}
Expand All @@ -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] */
Expand Down

0 comments on commit b8ddf28

Please sign in to comment.