11import 'server-only' ;
22
3+ import fnv1a from '@sindresorhus/fnv1a' ;
4+
35import { noCacheFetchOptions } from '@/lib/cache/http' ;
46
57import { rootUrl } from './links' ;
@@ -73,7 +75,7 @@ export async function getResizedImageURL(
7375 return null ;
7476 }
7577
76- const signature = await generateSignature ( input ) ;
78+ const signature = await generateSignatureV1 ( input ) ;
7779
7880 return ( options ) => {
7981 const url = new URL ( '/~gitbook/image' , rootUrl ( ) ) ;
@@ -93,6 +95,7 @@ export async function getResizedImageURL(
9395 }
9496
9597 url . searchParams . set ( 'sign' , signature ) ;
98+ url . searchParams . set ( 'sv' , '1' ) ;
9699
97100 return url . toString ( ) ;
98101 } ;
@@ -101,8 +104,12 @@ export async function getResizedImageURL(
101104/**
102105 * Verify a signature of an image URL
103106 */
104- export async function verifyImageSignature ( input : string , signature : string ) : Promise < boolean > {
105- const expectedSignature = await generateSignature ( input ) ;
107+ export async function verifyImageSignature (
108+ input : string ,
109+ { signature, version } : { signature : string ; version : '1' | '0' } ,
110+ ) : Promise < boolean > {
111+ const expectedSignature =
112+ version === '1' ? await generateSignatureV1 ( input ) : await generateSignatureV0 ( input ) ;
106113 return expectedSignature === signature ;
107114}
108115
@@ -201,7 +208,25 @@ function stringifyOptions(options: CloudflareImageOptions): string {
201208 } , '' ) ;
202209}
203210
204- async function generateSignature ( input : string ) : Promise < string > {
211+ // Reused buffer for FNV-1a hashing
212+ const fnv1aUtf8Buffer = new Uint8Array ( 512 ) ;
213+
214+ /**
215+ * New and faster algorithm to generate a signature for an image.
216+ * When setting it in a URL, we use version '1' for the 'sv' querystring parameneter
217+ * to know that it was the algorithm that was used.
218+ */
219+ async function generateSignatureV1 ( input : string ) : Promise < string > {
220+ const all = [ input , process . env . GITBOOK_IMAGE_RESIZE_SIGNING_KEY ] . filter ( Boolean ) . join ( ':' ) ;
221+ return fnv1a ( all , { utf8Buffer : fnv1aUtf8Buffer } ) . toString ( 16 ) ;
222+ }
223+
224+ /**
225+ * Initial algorithm used to generate a signature for an image. It didn't use any versioning in the URL.
226+ * We still need it to validate older signatures that were generated without versioning
227+ * but still exist in previously generated and cached content.
228+ */
229+ async function generateSignatureV0 ( input : string ) : Promise < string > {
205230 const all = [ input , process . env . GITBOOK_IMAGE_RESIZE_SIGNING_KEY ] . filter ( Boolean ) . join ( ':' ) ;
206231 const hash = await crypto . subtle . digest ( 'SHA-256' , new TextEncoder ( ) . encode ( all ) ) ;
207232
0 commit comments