From 4cb198075dfe02efed34a492761566a815f0ccb4 Mon Sep 17 00:00:00 2001 From: Ame_x Edam Date: Wed, 15 Jan 2025 09:18:10 +0000 Subject: [PATCH 1/7] feat(etag): allow for custom hashing methods to be used to etag --- src/middleware/etag/digest.ts | 17 +++++++++------- src/middleware/etag/index.test.ts | 33 +++++++++++++++++++++++++++++++ src/middleware/etag/index.ts | 3 ++- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/middleware/etag/digest.ts b/src/middleware/etag/digest.ts index 5af9cacc9..2f44fdd00 100644 --- a/src/middleware/etag/digest.ts +++ b/src/middleware/etag/digest.ts @@ -9,13 +9,21 @@ const mergeBuffers = (buffer1: ArrayBuffer | undefined, buffer2: Uint8Array): Ui } export const generateDigest = async ( - stream: ReadableStream | null + stream: ReadableStream | null, + generateDigest?: (body: Uint8Array) => ArrayBuffer | Promise ): Promise => { if (!stream || !crypto || !crypto.subtle) { return null } let result: ArrayBuffer | undefined = undefined + generateDigest ||= (body: Uint8Array) => + crypto.subtle.digest( + { + name: 'SHA-1', + }, + body + ) const reader = stream.getReader() for (;;) { @@ -24,12 +32,7 @@ export const generateDigest = async ( break } - result = await crypto.subtle.digest( - { - name: 'SHA-1', - }, - mergeBuffers(result, value) - ) + result = await generateDigest(mergeBuffers(result, value)) } if (!result) { diff --git a/src/middleware/etag/index.test.ts b/src/middleware/etag/index.test.ts index a1c67c608..2795be087 100644 --- a/src/middleware/etag/index.test.ts +++ b/src/middleware/etag/index.test.ts @@ -20,6 +20,39 @@ describe('Etag Middleware', () => { expect(res.headers.get('ETag')).toBe('"4515561204e8269cb4468d5b39288d8f2482dcfe"') }) + it('Should return etag header with another algorithm', async () => { + const app = new Hono() + app.use( + '/etag/*', + etag({ + generateDigest: (body: Uint8Array) => + crypto.subtle.digest( + { + name: 'SHA-256', + }, + body + ), + }) + ) + app.get('/etag/abc', (c) => { + return c.text('Hono is cool') + }) + app.get('/etag/def', (c) => { + return c.json({ message: 'Hono is cool' }) + }) + let res = await app.request('http://localhost/etag/abc') + expect(res.headers.get('ETag')).not.toBeFalsy() + expect(res.headers.get('ETag')).toBe( + '"ee7e84f92c4f54fec768123ac23003a6eb8437db95bcfbfc35db477af1ccb49e"' + ) + + res = await app.request('http://localhost/etag/def') + expect(res.headers.get('ETag')).not.toBeFalsy() + expect(res.headers.get('ETag')).toBe( + '"6ae7438c67f07b60b2ab069dbce206b00d3528c690840a77e0222d37398a8547"' + ) + }) + it('Should return etag header - binary', async () => { const app = new Hono() app.use('/etag/*', etag()) diff --git a/src/middleware/etag/index.ts b/src/middleware/etag/index.ts index f8555583c..9d025cb1b 100644 --- a/src/middleware/etag/index.ts +++ b/src/middleware/etag/index.ts @@ -9,6 +9,7 @@ import { generateDigest } from './digest' type ETagOptions = { retainedHeaders?: string[] weak?: boolean + generateDigest?: (body: Uint8Array) => ArrayBuffer | Promise } /** @@ -63,7 +64,7 @@ export const etag = (options?: ETagOptions): MiddlewareHandler => { let etag = res.headers.get('ETag') if (!etag) { - const hash = await generateDigest(res.clone().body) + const hash = await generateDigest(res.clone().body, options?.generateDigest) if (hash === null) { return } From 3cce13571303837f99eebf646e1459756d4e2f6e Mon Sep 17 00:00:00 2001 From: Ame_x Edam Date: Wed, 15 Jan 2025 09:23:47 +0000 Subject: [PATCH 2/7] chore: add jsdoc --- src/middleware/etag/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/middleware/etag/index.ts b/src/middleware/etag/index.ts index 9d025cb1b..57b3d6fe5 100644 --- a/src/middleware/etag/index.ts +++ b/src/middleware/etag/index.ts @@ -39,6 +39,9 @@ function etagMatches(etag: string, ifNoneMatch: string | null) { * @param {ETagOptions} [options] - The options for the ETag middleware. * @param {boolean} [options.weak=false] - Define using or not using a weak validation. If true is set, then `W/` is added to the prefix of the value. * @param {string[]} [options.retainedHeaders=RETAINED_304_HEADERS] - The headers that you want to retain in the 304 Response. + * @param {function(Uint8Array): ArrayBuffer | Promise | undefined} [options.generateDigest] - + * A custom digest generation function. By default, it uses 'SHA-1' + * This function is called with the response body as a `Uint8Array` and should return a hash as an `ArrayBuffer` or a Promise of one. * @returns {MiddlewareHandler} The middleware handler function. * * @example From 929ae30562d5820135bd68cd3b72b7c995949bdf Mon Sep 17 00:00:00 2001 From: EdamAmex <121654029+EdamAme-x@users.noreply.github.com> Date: Thu, 16 Jan 2025 13:07:16 +0900 Subject: [PATCH 3/7] fix jsdoc --- src/middleware/etag/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/etag/index.ts b/src/middleware/etag/index.ts index 57b3d6fe5..c63994d56 100644 --- a/src/middleware/etag/index.ts +++ b/src/middleware/etag/index.ts @@ -39,7 +39,7 @@ function etagMatches(etag: string, ifNoneMatch: string | null) { * @param {ETagOptions} [options] - The options for the ETag middleware. * @param {boolean} [options.weak=false] - Define using or not using a weak validation. If true is set, then `W/` is added to the prefix of the value. * @param {string[]} [options.retainedHeaders=RETAINED_304_HEADERS] - The headers that you want to retain in the 304 Response. - * @param {function(Uint8Array): ArrayBuffer | Promise | undefined} [options.generateDigest] - + * @param {function(Uint8Array): ArrayBuffer | Promise} [options.generateDigest] - * A custom digest generation function. By default, it uses 'SHA-1' * This function is called with the response body as a `Uint8Array` and should return a hash as an `ArrayBuffer` or a Promise of one. * @returns {MiddlewareHandler} The middleware handler function. From a48d0541b741504adfe9d2494b4006f99eb297c4 Mon Sep 17 00:00:00 2001 From: Ame_x Edam Date: Sat, 25 Jan 2025 05:58:41 +0000 Subject: [PATCH 4/7] cleanup --- src/middleware/etag/digest.ts | 9 +-------- src/middleware/etag/index.ts | 11 ++++++++++- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/middleware/etag/digest.ts b/src/middleware/etag/digest.ts index 2f44fdd00..f454fb546 100644 --- a/src/middleware/etag/digest.ts +++ b/src/middleware/etag/digest.ts @@ -10,20 +10,13 @@ const mergeBuffers = (buffer1: ArrayBuffer | undefined, buffer2: Uint8Array): Ui export const generateDigest = async ( stream: ReadableStream | null, - generateDigest?: (body: Uint8Array) => ArrayBuffer | Promise + generateDigest: (body: Uint8Array) => ArrayBuffer | Promise ): Promise => { if (!stream || !crypto || !crypto.subtle) { return null } let result: ArrayBuffer | undefined = undefined - generateDigest ||= (body: Uint8Array) => - crypto.subtle.digest( - { - name: 'SHA-1', - }, - body - ) const reader = stream.getReader() for (;;) { diff --git a/src/middleware/etag/index.ts b/src/middleware/etag/index.ts index c63994d56..e5aff8d08 100644 --- a/src/middleware/etag/index.ts +++ b/src/middleware/etag/index.ts @@ -57,6 +57,15 @@ function etagMatches(etag: string, ifNoneMatch: string | null) { export const etag = (options?: ETagOptions): MiddlewareHandler => { const retainedHeaders = options?.retainedHeaders ?? RETAINED_304_HEADERS const weak = options?.weak ?? false + const _generateDigest = + options?.generateDigest ?? + ((body: Uint8Array) => + crypto.subtle.digest( + { + name: 'SHA-1', + }, + body + )) return async function etag(c, next) { const ifNoneMatch = c.req.header('If-None-Match') ?? null @@ -67,7 +76,7 @@ export const etag = (options?: ETagOptions): MiddlewareHandler => { let etag = res.headers.get('ETag') if (!etag) { - const hash = await generateDigest(res.clone().body, options?.generateDigest) + const hash = await generateDigest(res.clone().body, _generateDigest) if (hash === null) { return } From 33c4a6a4c5ab0fde58c597892a07b5e39e2f9d50 Mon Sep 17 00:00:00 2001 From: EdamAmex <121654029+EdamAme-x@users.noreply.github.com> Date: Sun, 2 Feb 2025 20:07:55 +0900 Subject: [PATCH 5/7] fix: remove unneed code --- src/middleware/etag/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/middleware/etag/index.ts b/src/middleware/etag/index.ts index e5aff8d08..29b97c1ef 100644 --- a/src/middleware/etag/index.ts +++ b/src/middleware/etag/index.ts @@ -84,7 +84,6 @@ export const etag = (options?: ETagOptions): MiddlewareHandler => { } if (etagMatches(etag, ifNoneMatch)) { - await c.res.blob() // Force using body c.res = new Response(null, { status: 304, statusText: 'Not Modified', From f1199184d864679f8d0a0f24527f29103a3bfe31 Mon Sep 17 00:00:00 2001 From: Ame_x Edam Date: Mon, 3 Feb 2025 11:05:28 +0000 Subject: [PATCH 6/7] fix arg name --- src/middleware/etag/digest.ts | 4 ++-- src/middleware/etag/index.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/middleware/etag/digest.ts b/src/middleware/etag/digest.ts index f454fb546..336590b8c 100644 --- a/src/middleware/etag/digest.ts +++ b/src/middleware/etag/digest.ts @@ -10,7 +10,7 @@ const mergeBuffers = (buffer1: ArrayBuffer | undefined, buffer2: Uint8Array): Ui export const generateDigest = async ( stream: ReadableStream | null, - generateDigest: (body: Uint8Array) => ArrayBuffer | Promise + generator: (body: Uint8Array) => ArrayBuffer | Promise ): Promise => { if (!stream || !crypto || !crypto.subtle) { return null @@ -25,7 +25,7 @@ export const generateDigest = async ( break } - result = await generateDigest(mergeBuffers(result, value)) + result = await generator(mergeBuffers(result, value)) } if (!result) { diff --git a/src/middleware/etag/index.ts b/src/middleware/etag/index.ts index 29b97c1ef..b8fb4ecaf 100644 --- a/src/middleware/etag/index.ts +++ b/src/middleware/etag/index.ts @@ -57,7 +57,7 @@ function etagMatches(etag: string, ifNoneMatch: string | null) { export const etag = (options?: ETagOptions): MiddlewareHandler => { const retainedHeaders = options?.retainedHeaders ?? RETAINED_304_HEADERS const weak = options?.weak ?? false - const _generateDigest = + const generator = options?.generateDigest ?? ((body: Uint8Array) => crypto.subtle.digest( @@ -76,7 +76,7 @@ export const etag = (options?: ETagOptions): MiddlewareHandler => { let etag = res.headers.get('ETag') if (!etag) { - const hash = await generateDigest(res.clone().body, _generateDigest) + const hash = await generateDigest(res.clone().body, generator) if (hash === null) { return } From 8fc6cfd20d261e44cb2adacfb0d2d9664f6c80b0 Mon Sep 17 00:00:00 2001 From: EdamAmex <121654029+EdamAme-x@users.noreply.github.com> Date: Tue, 4 Feb 2025 19:06:14 +0900 Subject: [PATCH 7/7] fix --- src/middleware/etag/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/etag/index.test.ts b/src/middleware/etag/index.test.ts index 2795be087..296c74bb9 100644 --- a/src/middleware/etag/index.test.ts +++ b/src/middleware/etag/index.test.ts @@ -25,7 +25,7 @@ describe('Etag Middleware', () => { app.use( '/etag/*', etag({ - generateDigest: (body: Uint8Array) => + generateDigest: (body) => crypto.subtle.digest( { name: 'SHA-256',