@@ -140,11 +140,109 @@ function populateProcessEnv(url: URL, env: CloudflareEnv) {
140
140
process . env . __NEXT_PRIVATE_ORIGIN = url . origin ;
141
141
}
142
142
143
+ export type RemotePattern = {
144
+ protocol ?: "http" | "https" ;
145
+ hostname : string ;
146
+ port ?: string ;
147
+ pathname : string ;
148
+ search ?: string ;
149
+ } ;
150
+
151
+ const imgRemotePatterns = __IMAGES_REMOTE_PATTERNS__ ;
152
+
153
+ /**
154
+ * Fetches an images.
155
+ *
156
+ * Local images (starting with a '/' as fetched using the passed fetcher).
157
+ * Remote images should match the configured remote patterns or a 404 response is returned.
158
+ */
159
+ export function fetchImage ( fetcher : Fetcher | undefined , url : string ) {
160
+ // https://github.com/vercel/next.js/blob/d76f0b1/packages/next/src/server/image-optimizer.ts#L208
161
+ if ( ! url || url . length > 3072 || url . startsWith ( "//" ) ) {
162
+ return new Response ( "Not Found" , { status : 404 } ) ;
163
+ }
164
+
165
+ // Local
166
+ if ( url . startsWith ( "/" ) ) {
167
+ if ( / \/ _ n e x t \/ i m a g e ( $ | \/ ) / . test ( decodeURIComponent ( parseUrl ( url ) ?. pathname ?? "" ) ) ) {
168
+ return new Response ( "Not Found" , { status : 404 } ) ;
169
+ }
170
+
171
+ return fetcher ?. fetch ( `http://assets.local${ url } ` ) ;
172
+ }
173
+
174
+ // Remote
175
+ let hrefParsed : URL ;
176
+ try {
177
+ hrefParsed = new URL ( url ) ;
178
+ } catch {
179
+ return new Response ( "Not Found" , { status : 404 } ) ;
180
+ }
181
+
182
+ if ( ! [ "http:" , "https:" ] . includes ( hrefParsed . protocol ) ) {
183
+ return new Response ( "Not Found" , { status : 404 } ) ;
184
+ }
185
+
186
+ if ( ! imgRemotePatterns . some ( ( p : RemotePattern ) => matchRemotePattern ( p , hrefParsed ) ) ) {
187
+ return new Response ( "Not Found" , { status : 404 } ) ;
188
+ }
189
+
190
+ return fetch ( url , { cf : { cacheEverything : true } } ) ;
191
+ }
192
+
193
+ export function matchRemotePattern ( pattern : RemotePattern , url : URL ) : boolean {
194
+ // https://github.com/vercel/next.js/blob/d76f0b1/packages/next/src/shared/lib/match-remote-pattern.ts
195
+ if ( pattern . protocol !== undefined ) {
196
+ if ( pattern . protocol . replace ( / : $ / , "" ) !== url . protocol . replace ( / : $ / , "" ) ) {
197
+ return false ;
198
+ }
199
+ }
200
+ if ( pattern . port !== undefined ) {
201
+ if ( pattern . port !== url . port ) {
202
+ return false ;
203
+ }
204
+ }
205
+
206
+ if ( pattern . hostname === undefined ) {
207
+ throw new Error ( `Pattern should define hostname but found\n${ JSON . stringify ( pattern ) } ` ) ;
208
+ } else {
209
+ if ( ! new RegExp ( pattern . hostname ) . test ( url . hostname ) ) {
210
+ return false ;
211
+ }
212
+ }
213
+
214
+ if ( pattern . search !== undefined ) {
215
+ if ( pattern . search !== url . search ) {
216
+ return false ;
217
+ }
218
+ }
219
+
220
+ // Should be the same as writeImagesManifest()
221
+ if ( ! new RegExp ( pattern . pathname ) . test ( url . pathname ) ) {
222
+ return false ;
223
+ }
224
+
225
+ return true ;
226
+ }
227
+
228
+ function parseUrl ( url : string ) : URL | undefined {
229
+ let parsed : URL | undefined = undefined ;
230
+ try {
231
+ parsed = new URL ( url , "http://n" ) ;
232
+ } catch {
233
+ // empty
234
+ }
235
+ return parsed ;
236
+ }
237
+
143
238
/* eslint-disable no-var */
144
239
declare global {
145
240
// Build timestamp
146
241
var __BUILD_TIMESTAMP_MS__ : number ;
147
242
// Next basePath
148
243
var __NEXT_BASE_PATH__ : string ;
244
+ // Images patterns
245
+ var __IMAGES_REMOTE_PATTERNS__ : RemotePattern [ ] ;
246
+ var __IMAGES_LOCAL_PATTERNS__ : unknown [ ] ;
149
247
}
150
248
/* eslint-enable no-var */
0 commit comments