1
1
import 'server-only' ;
2
2
3
+ import fnv1a from '@sindresorhus/fnv1a' ;
4
+
3
5
import { noCacheFetchOptions } from '@/lib/cache/http' ;
4
6
5
7
import { rootUrl } from './links' ;
@@ -73,7 +75,7 @@ export async function getResizedImageURL(
73
75
return null ;
74
76
}
75
77
76
- const signature = await generateSignature ( input ) ;
78
+ const signature = await generateSignatureV1 ( input ) ;
77
79
78
80
return ( options ) => {
79
81
const url = new URL ( '/~gitbook/image' , rootUrl ( ) ) ;
@@ -93,6 +95,7 @@ export async function getResizedImageURL(
93
95
}
94
96
95
97
url . searchParams . set ( 'sign' , signature ) ;
98
+ url . searchParams . set ( 'sv' , '1' ) ;
96
99
97
100
return url . toString ( ) ;
98
101
} ;
@@ -101,8 +104,12 @@ export async function getResizedImageURL(
101
104
/**
102
105
* Verify a signature of an image URL
103
106
*/
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 ) ;
106
113
return expectedSignature === signature ;
107
114
}
108
115
@@ -201,7 +208,25 @@ function stringifyOptions(options: CloudflareImageOptions): string {
201
208
} , '' ) ;
202
209
}
203
210
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 > {
205
230
const all = [ input , process . env . GITBOOK_IMAGE_RESIZE_SIGNING_KEY ] . filter ( Boolean ) . join ( ':' ) ;
206
231
const hash = await crypto . subtle . digest ( 'SHA-256' , new TextEncoder ( ) . encode ( all ) ) ;
207
232
0 commit comments