Skip to content

Commit 3661fa6

Browse files
committed
Normalize data to Uint8Array to avoid extra copies.
1 parent b21a8a4 commit 3661fa6

2 files changed

Lines changed: 35 additions & 66 deletions

File tree

lib/httpDigest.js

Lines changed: 22 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,20 @@ import crypto from './crypto.js';
99
* Creates a value suitable for the HTTP `Digest` header.
1010
*
1111
* @param {object} options - The options to use.
12-
* @param {string|object|Blob|Uint8Array} [options.data] - Input body to be
13-
* hashed (typically a request body).
12+
* @param {string|object|Blob|Uint8Array} [options.data] - The data to be
13+
* hashed (a request or response body).
1414
* @param {string} [options.algorithm] - Hash algorithm to use.
1515
* (e.g. 'sha256').
16-
* @param {boolean} [options.useMultihash] - Whether to encode via multihash.
17-
* If false, the hash will be base64 encoded (non-url).
16+
* @param {boolean} [options.useMultihash=true] - Whether to encode via
17+
* multihash; if false, the hash will be base64-encoded (non-url).
1818
*
1919
* @returns {Promise<string>} Resolves to `Digest` header value.
2020
*/
2121
export async function createHeaderValue({
2222
data, algorithm = 'sha256', useMultihash = true
2323
} = {}) {
24-
const body = _normalizeData(data);
2524
const {key, encodedDigest} = await _createHeaderValueComponents({
26-
body, algorithm, useMultihash
25+
data, algorithm, useMultihash
2726
});
2827
return `${key}=${encodedDigest}`;
2928
}
@@ -32,7 +31,8 @@ export async function createHeaderValue({
3231
* Verifies the HTTP `Digest` header value against the given HTTP body `data`.
3332
*
3433
* @param {object} options - The options to use.
35-
* @param {string|object|Blob} options.data - The data to be verified.
34+
* @param {string|object|Blob|Uint8Array} options.data - The data to be
35+
* verified (a request or response body).
3636
* @param {string} options.headerValue - The digest header value to verify
3737
* the data against.
3838
*
@@ -42,35 +42,21 @@ export async function createHeaderValue({
4242
export async function verifyHeaderValue({data, headerValue}) {
4343
try {
4444
const {key, algorithm, encodedDigest} = _parseHeaderValue(headerValue);
45-
const body = _normalizeData(data);
46-
const {encodedDigest: expectedDigest} = await _createHeaderValueComponents(
47-
{body, algorithm, useMultihash: key === 'mh'});
45+
const {encodedDigest: expectedDigest} = await _createHeaderValueComponents({
46+
data, algorithm, useMultihash: key === 'mh'
47+
});
4848
return {verified: encodedDigest === expectedDigest};
4949
} catch(error) {
5050
return {verified: false, error};
5151
}
5252
}
5353

54-
/**
55-
* Creates the digest header components.
56-
*
57-
* @param {object} options - The options to use.
58-
* @param {Blob} options.body - The request body.
59-
* @param {string} [options.algorithm] - The hash algorithm to use.
60-
* @param {boolean} [options.useMultihash=true] - Whether to serialize digest
61-
* using multihash or not.
62-
*
63-
* @returns {Promise<object>} - The header key and encoded digest in an object.
64-
*/
6554
async function _createHeaderValueComponents({
66-
body, algorithm = 'sha256', useMultihash = true
55+
data, algorithm = 'sha256', useMultihash = true
6756
}) {
6857
if(algorithm !== 'sha256') {
6958
throw new Error(`Algorithm "${algorithm}" is not supported.`);
7059
}
71-
// `Blob.bytes()` is only available in node.js 22+;
72-
// fallback to `Blob.arrayBuffer()`
73-
const data = await (body?.bytes?.() ?? body.arrayBuffer());
7460
const digest = await _getDigest({data, algorithm});
7561
if(useMultihash) {
7662
return {key: 'mh', encodedDigest: _createMultihash({digest})};
@@ -112,25 +98,25 @@ function _parseHeaderValue(headerValue) {
11298
}
11399

114100
async function _getDigest({data, algorithm}) {
101+
data = await _normalizeData(data);
115102
if(algorithm === 'sha256') {
116103
return new Uint8Array(await crypto.subtle.digest({name: 'SHA-256'}, data));
117104
}
118105
throw new Error(`Algorithm "${algorithm}" is not unsupported.`);
119106
}
120107

121-
// normalize all inputs to a Uint8Array for hashing
122-
function _normalizeData(data) {
108+
// normalize all inputs to a `Uint8Array` for hashing
109+
async function _normalizeData(data) {
123110
if(data instanceof Uint8Array) {
124-
return new Blob([/** @type {ArrayBuffer} */ (data.buffer)]);
125-
}
126-
if(data instanceof ArrayBuffer) {
127-
return new Blob([data]);
111+
return data;
128112
}
129-
if(typeof data === 'string') {
130-
return new Blob([data], {type: 'application/json'});
113+
if(data instanceof Blob) {
114+
// `Blob.bytes()` is only available in node.js 22+;
115+
// fallback to `Blob.arrayBuffer()`
116+
return data?.bytes?.() ?? data.arrayBuffer();
131117
}
132-
if(typeof data?.arrayBuffer === 'function') {
133-
return data;
118+
if(typeof data !== 'string') {
119+
data = JSON.stringify(data);
134120
}
135-
return new Blob([JSON.stringify(data)], {type: 'application/json'});
121+
return (new TextEncoder()).encode(data);
136122
}

test/unit/httpDigest.spec.js

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*!
2-
* Copyright (c) 2019-2020 Digital Bazaar, Inc. All rights reserved.
2+
* Copyright (c) 2019-2025 Digital Bazaar, Inc. All rights reserved.
33
*/
44
import {createHeaderValue, verifyHeaderValue} from '../../lib/index.js';
55

@@ -61,48 +61,31 @@ describe('http-signature-digest', () => {
6161
});
6262

6363
it('should create a digest of a JSON blob', async () => {
64-
const object = { hello: 'world' };
65-
const data = new Blob([JSON.stringify(object)], { type: 'application/json' })
64+
const object = {hello: 'world'};
65+
const data = new Blob(
66+
[JSON.stringify(object)], {type: 'application/json'});
6667
const objDigest = await createHeaderValue(
67-
{ data, algorithm: 'sha256', useMultihash: false }
68-
);
68+
{data, algorithm: 'sha256', useMultihash: false});
6969
objDigest.should
7070
.equal('SHA-256=k6I5cakU5erL8KjSUVTNownDwccvu5kU1Hxg88toFYg=');
7171
});
7272

7373
it('should create a digest of a text/plain blob', async () => {
74-
const object = { hello: 'world' };
75-
const data = new Blob([JSON.stringify(object)], { type: 'text/plain' })
74+
const object = {hello: 'world'};
75+
const data = new Blob([JSON.stringify(object)], {type: 'text/plain'});
7676
const objDigest = await createHeaderValue(
77-
{ data, algorithm: 'sha256', useMultihash: false }
78-
);
79-
objDigest.should
80-
.equal('SHA-256=k6I5cakU5erL8KjSUVTNownDwccvu5kU1Hxg88toFYg=');
81-
});
82-
83-
it('should create a digest of an ArrayBuffer', async () => {
84-
const object = { hello: 'world' };
85-
const text = JSON.stringify(object)
86-
const bytes = new TextEncoder().encode(text)
87-
const data = bytes.buffer
88-
should.equal(data instanceof ArrayBuffer, true, `data is a ArrayBuffer`)
89-
should.equal(data instanceof Uint8Array, false, `data is not a Uint8Array`)
90-
const objDigest = await createHeaderValue(
91-
{ data, algorithm: 'sha256', useMultihash: false }
92-
);
77+
{data, algorithm: 'sha256', useMultihash: false});
9378
objDigest.should
9479
.equal('SHA-256=k6I5cakU5erL8KjSUVTNownDwccvu5kU1Hxg88toFYg=');
9580
});
9681

9782
it('should create a digest of a Uint8Array', async () => {
98-
const object = { hello: 'world' };
99-
const text = JSON.stringify(object)
100-
const bytes = new TextEncoder().encode(text)
101-
const data = bytes
102-
should.equal(data instanceof Uint8Array, true, `data is a Uint8Array`)
83+
const object = {hello: 'world'};
84+
const text = JSON.stringify(object);
85+
const data = new TextEncoder().encode(text);
86+
should.equal(data instanceof Uint8Array, true, `data is a Uint8Array`);
10387
const objDigest = await createHeaderValue(
104-
{ data, algorithm: 'sha256', useMultihash: false }
105-
);
88+
{data, algorithm: 'sha256', useMultihash: false});
10689
objDigest.should
10790
.equal('SHA-256=k6I5cakU5erL8KjSUVTNownDwccvu5kU1Hxg88toFYg=');
10891
});

0 commit comments

Comments
 (0)