@nifrajs/image 3.1.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.d.ts CHANGED
@@ -41,8 +41,11 @@ export interface ImageHandlerOptions {
41
41
  readonly sourceConcurrency?: number;
42
42
  /** Maximum queued image requests beyond the admission width. Default `concurrency * 16`. */
43
43
  readonly maxQueue?: number;
44
- /** `Cache-Control: public, max-age=<n>, immutable` seconds. Default 1 year. */
44
+ /** `Cache-Control: public, max-age=<n>` seconds. Default 1 hour. */
45
45
  readonly cacheMaxAge?: number;
46
+ /** Add `immutable` to Cache-Control. Off by default: enable only when every source URL changes with
47
+ * its content, otherwise browsers are allowed to retain a changed image until the full max-age. */
48
+ readonly immutable?: boolean;
46
49
  /** Quality used when `?q` is absent. Default 75. */
47
50
  readonly defaultQuality?: number;
48
51
  /** Remote-fetch timeout (ms). Default 10 000. */
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAEL,KAAK,YAAY,EAGlB,MAAM,cAAc,CAAA;AAOrB,cAAc,cAAc,CAAA;AAE5B,MAAM,WAAW,mBAAmB;IAClC,kFAAkF;IAClF,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,CAAA;IAC/B;sEACkE;IAClE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB;+FAC2F;IAC3F,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3C,2EAA2E;IAC3E,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC,gGAAgG;IAChG,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC,kFAAkF;IAClF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,oGAAoG;IACpG,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B;;;;;;;;OAQG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IACnC,4FAA4F;IAC5F,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,+EAA+E;IAC/E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,oDAAoD;IACpD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC,iDAAiD;IACjD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC;sCACkC;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;IAC7B;;0FAEsF;IACtF,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAC/C;AAkCD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,mBAAwB,GAChC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAGrC"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,EAEL,KAAK,YAAY,EAGlB,MAAM,cAAc,CAAA;AAOrB,cAAc,cAAc,CAAA;AAE5B,MAAM,WAAW,mBAAmB;IAClC,kFAAkF;IAClF,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,CAAA;IAC/B;sEACkE;IAClE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB;+FAC2F;IAC3F,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3C,2EAA2E;IAC3E,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC,gGAAgG;IAChG,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC,kFAAkF;IAClF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,oGAAoG;IACpG,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B;;;;;;;;OAQG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IACnC,4FAA4F;IAC5F,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,oEAAoE;IACpE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B;uGACmG;IACnG,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;IAC5B,oDAAoD;IACpD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC,iDAAiD;IACjD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC;sCACkC;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;IAC7B;;0FAEsF;IACtF,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAC/C;AAmCD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,mBAAwB,GAChC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAGrC"}
package/dist/server.js CHANGED
@@ -10,7 +10,8 @@
10
10
  * edge. On Workers / Vercel-Edge / Deno-Deploy there is no native codec - use the CDN `cloudflareLoader`
11
11
  * from `@nifrajs/image` instead. The dependency-free core (`@nifrajs/image`) never imports this module.
12
12
  */
13
- import { readFile, realpath, stat } from "node:fs/promises";
13
+ import { constants as FS } from "node:fs";
14
+ import { open, realpath } from "node:fs/promises";
14
15
  import { resolve as resolvePath, sep } from "node:path";
15
16
  import { bunImageBackend, ImageProcessingError, } from "./backend.js";
16
17
  import { verifyImageParams } from "./sign.js";
@@ -47,11 +48,24 @@ function resolveConfig(options) {
47
48
  const concurrency = options.concurrency ?? 4;
48
49
  const sourceConcurrency = options.sourceConcurrency ?? concurrency * 2;
49
50
  const maxQueue = options.maxQueue ?? concurrency * 16;
51
+ const maxWidth = options.maxWidth ?? 3840;
52
+ const cacheMaxAge = options.cacheMaxAge ?? 3_600;
53
+ const defaultQuality = options.defaultQuality ?? 75;
54
+ const fetchTimeoutMs = options.fetchTimeoutMs ?? 10_000;
55
+ const immutable = options.immutable ?? false;
50
56
  assertNonNegativeSafeInteger(maxSourceBytes, "maxSourceBytes");
51
57
  assertPositiveSafeInteger(maxSourcePixels, "maxSourcePixels");
52
58
  assertPositiveSafeInteger(concurrency, "concurrency");
53
59
  assertPositiveSafeInteger(sourceConcurrency, "sourceConcurrency");
54
60
  assertNonNegativeSafeInteger(maxQueue, "maxQueue");
61
+ assertPositiveSafeInteger(maxWidth, "maxWidth");
62
+ assertNonNegativeSafeInteger(cacheMaxAge, "cacheMaxAge");
63
+ assertPositiveSafeInteger(fetchTimeoutMs, "fetchTimeoutMs");
64
+ if (!Number.isSafeInteger(defaultQuality) || defaultQuality < 1 || defaultQuality > 100) {
65
+ throw new RangeError("image: defaultQuality must be a safe integer from 1 to 100");
66
+ }
67
+ if (typeof immutable !== "boolean")
68
+ throw new TypeError("image: immutable must be a boolean");
55
69
  if (sourceConcurrency < concurrency) {
56
70
  // Otherwise the codec lane can never fill, and `concurrency` silently means something narrower.
57
71
  throw new RangeError("image: sourceConcurrency must be >= concurrency");
@@ -62,10 +76,11 @@ function resolveConfig(options) {
62
76
  allowedOrigins: new Set(options.allowedOrigins ?? []),
63
77
  maxSourceBytes,
64
78
  maxSourcePixels,
65
- maxWidth: options.maxWidth ?? 3840,
66
- cacheMaxAge: options.cacheMaxAge ?? 31_536_000,
67
- defaultQuality: options.defaultQuality ?? 75,
68
- fetchTimeoutMs: options.fetchTimeoutMs ?? 10_000,
79
+ maxWidth,
80
+ cacheMaxAge,
81
+ immutable,
82
+ defaultQuality,
83
+ fetchTimeoutMs,
69
84
  fetchImpl: options.fetch ?? globalThis.fetch,
70
85
  signing: options.signing ?? null,
71
86
  admit: createSemaphore(sourceConcurrency, maxQueue),
@@ -113,30 +128,25 @@ async function handle(req, cfg) {
113
128
  if (!ok)
114
129
  return errorResponse(403, "invalid_signature");
115
130
  }
116
- // 3. Strong validator from the request-deterministic inputs, computed BEFORE any fetch/decode so a
117
- // conditional request short-circuits all expensive work. The clamped width is a pure function of
118
- // (src, requestedWidth), so (src, requestedWidth, quality, wantsWebp) fully keys the response.
119
- const etag = `"${fnv1a(`${src}|${requestedWidth}|${quality}|${wantsWebp ? 1 : 0}`)}"`;
120
- const cacheHeaders = {
121
- "Cache-Control": `public, max-age=${cfg.cacheMaxAge}, immutable`,
122
- ETag: etag,
123
- Vary: "Accept",
124
- };
125
- if (ifNoneMatch(req, etag)) {
126
- return new Response(null, { status: 304, headers: cacheHeaders });
127
- }
128
- // 4/5. Admission precedes source reads so queued requests do not retain large buffers. It is a
131
+ // 3/4. Admission precedes source reads so queued requests do not retain large buffers. It is a
129
132
  // separate, wider lane from the codec one below: a remote source is a network wait, and letting
130
133
  // it hold a codec slot would let `concurrency` slow origins stall every transform on the box.
131
- if (!(await cfg.admit.acquire()))
134
+ const admission = await cfg.admit.acquire(req.signal);
135
+ if (admission === "full")
132
136
  return errorResponse(503, "image_queue_full");
137
+ if (admission === "aborted")
138
+ return errorResponse(499, "request_cancelled");
133
139
  try {
134
- const source = await readSource(src, cfg);
140
+ const source = await readSource(src, cfg, req.signal);
135
141
  if (!source.ok)
136
142
  return errorResponse(source.status, source.code);
137
143
  // The bytes are in hand; now queue for CPU. This semaphore never refuses - its waiter bound is the
138
144
  // admission width - so a wait here is a wait, not a 503.
139
- await cfg.codec.acquire();
145
+ const codecAdmission = await cfg.codec.acquire(req.signal);
146
+ if (codecAdmission === "full")
147
+ return errorResponse(503, "image_queue_full");
148
+ if (codecAdmission === "aborted")
149
+ return errorResponse(499, "request_cancelled");
140
150
  let out;
141
151
  try {
142
152
  // Probe → enforce the portable pixel cap → clamp to intrinsic (never upscale) → transform.
@@ -144,6 +154,8 @@ async function handle(req, cfg) {
144
154
  if (probe.width * probe.height > cfg.maxSourcePixels) {
145
155
  return errorResponse(413, "source_too_large");
146
156
  }
157
+ if (req.signal.aborted)
158
+ return errorResponse(499, "request_cancelled");
147
159
  const targetWidth = Math.max(1, Math.min(width, probe.width));
148
160
  const format = negotiateFormat(probe.format, wantsWebp);
149
161
  out = await cfg.backend.transform({
@@ -152,14 +164,25 @@ async function handle(req, cfg) {
152
164
  quality,
153
165
  format,
154
166
  });
167
+ if (req.signal.aborted)
168
+ return errorResponse(499, "request_cancelled");
155
169
  }
156
170
  finally {
157
171
  cfg.codec.release();
158
172
  }
159
- const headers = new Headers(cacheHeaders);
173
+ // A strong validator describes the bytes actually emitted. Hashing request parameters here would
174
+ // falsely return 304 when a mutable local/remote source changes at the same URL.
175
+ const etag = `"${await sha256(out.bytes)}"`;
176
+ const headers = new Headers({
177
+ "Cache-Control": `public, max-age=${cfg.cacheMaxAge}${cfg.immutable ? ", immutable" : ""}`,
178
+ ETag: etag,
179
+ Vary: "Accept",
180
+ });
160
181
  headers.set("Content-Type", out.contentType);
161
182
  headers.set("Content-Length", String(out.bytes.byteLength));
162
183
  headers.set("X-Content-Type-Options", "nosniff"); // never let a client sniff the re-encoded bytes
184
+ if (ifNoneMatch(req, etag))
185
+ return new Response(null, { status: 304, headers });
163
186
  // HEAD: identical headers, no body.
164
187
  if (req.method === "HEAD")
165
188
  return new Response(null, { status: 200, headers });
@@ -183,7 +206,9 @@ async function handle(req, cfg) {
183
206
  * must be `http(s)` **and** on the `allowedOrigins` allowlist; anything else is a local path resolved
184
207
  * under `root` with traversal + symlink containment checks. Both branches fail closed.
185
208
  */
186
- async function readSource(src, cfg) {
209
+ async function readSource(src, cfg, signal) {
210
+ if (signal.aborted)
211
+ return { ok: false, status: 499, code: "request_cancelled" };
187
212
  const asUrl = tryParseUrl(src);
188
213
  if (asUrl !== null) {
189
214
  if (asUrl.protocol !== "http:" && asUrl.protocol !== "https:") {
@@ -192,33 +217,52 @@ async function readSource(src, cfg) {
192
217
  if (!cfg.allowedOrigins.has(asUrl.origin)) {
193
218
  return { ok: false, status: 403, code: "source_not_allowed" };
194
219
  }
195
- return fetchRemote(asUrl, cfg);
220
+ return fetchRemote(asUrl, cfg, signal);
196
221
  }
197
- return readLocal(src, cfg);
222
+ return readLocal(src, cfg, signal);
198
223
  }
199
- async function fetchRemote(url, cfg) {
200
- let res;
224
+ async function fetchRemote(url, cfg, requestSignal) {
225
+ const controller = new AbortController();
226
+ let timedOut = false;
227
+ const onRequestAbort = () => controller.abort(requestSignal.reason);
228
+ requestSignal.addEventListener("abort", onRequestAbort, { once: true });
229
+ // Close the add/check race: a signal that aborted immediately before listener registration still
230
+ // cancels the fetch rather than running an abandoned request to completion.
231
+ if (requestSignal.aborted)
232
+ onRequestAbort();
233
+ const timer = setTimeout(() => {
234
+ timedOut = true;
235
+ controller.abort();
236
+ }, cfg.fetchTimeoutMs);
201
237
  try {
202
- res = await cfg.fetchImpl(url, {
238
+ const res = await cfg.fetchImpl(url, {
203
239
  redirect: "error", // a redirect could bounce to a disallowed origin - refuse it.
204
- signal: AbortSignal.timeout(cfg.fetchTimeoutMs),
240
+ signal: controller.signal,
205
241
  headers: { Accept: "image/*" },
206
242
  });
243
+ if (!res.ok)
244
+ return { ok: false, status: 502, code: "upstream_error" };
245
+ const bytes = await readBoundedBytes(res, cfg.maxSourceBytes);
246
+ if (bytes === null)
247
+ return { ok: false, status: 413, code: "source_too_large" };
248
+ if (requestSignal.aborted)
249
+ return { ok: false, status: 499, code: "request_cancelled" };
250
+ return { ok: true, bytes };
207
251
  }
208
252
  catch (err) {
209
253
  const name = err instanceof Error ? err.name : "";
210
- return name === "TimeoutError"
254
+ if (requestSignal.aborted)
255
+ return { ok: false, status: 499, code: "request_cancelled" };
256
+ return timedOut || name === "TimeoutError"
211
257
  ? { ok: false, status: 504, code: "upstream_timeout" }
212
258
  : { ok: false, status: 502, code: "upstream_unreachable" };
213
259
  }
214
- if (!res.ok)
215
- return { ok: false, status: 502, code: "upstream_error" };
216
- const bytes = await readBoundedBytes(res, cfg.maxSourceBytes);
217
- if (bytes === null)
218
- return { ok: false, status: 413, code: "source_too_large" };
219
- return { ok: true, bytes };
260
+ finally {
261
+ clearTimeout(timer);
262
+ requestSignal.removeEventListener("abort", onRequestAbort);
263
+ }
220
264
  }
221
- async function readLocal(src, cfg) {
265
+ async function readLocal(src, cfg, signal) {
222
266
  if (cfg.root === null)
223
267
  return { ok: false, status: 403, code: "source_not_allowed" };
224
268
  // `src` is already percent-decoded by URLSearchParams - do NOT decode again (double-decode bypass).
@@ -231,29 +275,57 @@ async function readLocal(src, cfg) {
231
275
  if (resolved !== cfg.root && !resolved.startsWith(cfg.root + sep)) {
232
276
  return { ok: false, status: 403, code: "source_not_allowed" };
233
277
  }
234
- let info;
278
+ // Resolve containment, then open that verified name exactly once. Reopening `resolved` after these
279
+ // checks would let a writable source tree swap a symlink between verification and read.
280
+ let real;
235
281
  try {
236
- info = await stat(resolved);
282
+ real = await realpath(resolved);
283
+ const realRoot = await realpath(cfg.root);
284
+ if (real !== realRoot && !real.startsWith(realRoot + sep)) {
285
+ return { ok: false, status: 403, code: "source_not_allowed" };
286
+ }
237
287
  }
238
288
  catch {
239
289
  return { ok: false, status: 404, code: "source_not_found" };
240
290
  }
241
- if (!info.isFile())
242
- return { ok: false, status: 404, code: "source_not_found" };
243
- if (info.size > cfg.maxSourceBytes)
244
- return { ok: false, status: 413, code: "source_too_large" };
245
- // Defense-in-depth: a symlink inside root could point outside it - re-check the real path.
291
+ let handle;
246
292
  try {
247
- const real = await realpath(resolved);
248
- const realRoot = await realpath(cfg.root);
249
- if (real !== realRoot && !real.startsWith(realRoot + sep)) {
250
- return { ok: false, status: 403, code: "source_not_allowed" };
293
+ handle = await open(real, FS.O_RDONLY | FS.O_NOFOLLOW);
294
+ const info = await handle.stat();
295
+ if (!info.isFile())
296
+ return { ok: false, status: 404, code: "source_not_found" };
297
+ if (info.size > cfg.maxSourceBytes) {
298
+ return { ok: false, status: 413, code: "source_too_large" };
299
+ }
300
+ // Read no more than the descriptor size that passed the cap. If the file grows concurrently, the
301
+ // appended bytes are deliberately ignored; if it shrinks, return only the bytes actually read.
302
+ const bytes = new Uint8Array(info.size);
303
+ let offset = 0;
304
+ while (offset < bytes.byteLength) {
305
+ if (signal.aborted)
306
+ return { ok: false, status: 499, code: "request_cancelled" };
307
+ const { bytesRead } = await handle.read(bytes, offset, bytes.byteLength - offset, offset);
308
+ if (bytesRead === 0)
309
+ break;
310
+ offset += bytesRead;
251
311
  }
312
+ if (signal.aborted)
313
+ return { ok: false, status: 499, code: "request_cancelled" };
314
+ return { ok: true, bytes: offset === bytes.byteLength ? bytes : bytes.slice(0, offset) };
252
315
  }
253
316
  catch {
254
317
  return { ok: false, status: 404, code: "source_not_found" };
255
318
  }
256
- return { ok: true, bytes: new Uint8Array(await readFile(resolved)) };
319
+ finally {
320
+ if (handle !== undefined) {
321
+ try {
322
+ await handle.close();
323
+ }
324
+ catch {
325
+ // The response has already been decided; descriptor-close failure cannot change it.
326
+ }
327
+ }
328
+ }
257
329
  }
258
330
  /** Read a response body, aborting (→ null) once `limit` bytes are exceeded. Trusts neither the
259
331
  * `Content-Length` header (checked as a fast reject) nor an absent one - the running total is the gate. */
@@ -321,14 +393,13 @@ function ifNoneMatch(req, etag) {
321
393
  return true;
322
394
  return header.split(",").some((t) => t.trim() === etag);
323
395
  }
324
- /** 32-bit FNV-1a over a short key string → hex. Used only as a cache validator (ETag), not security. */
325
- function fnv1a(input) {
326
- let h = 0x811c9dc5;
327
- for (let i = 0; i < input.length; i++) {
328
- h ^= input.charCodeAt(i);
329
- h = Math.imul(h, 0x01000193);
330
- }
331
- return (h >>> 0).toString(16);
396
+ /** Collision-resistant content digest for a strong representation validator. */
397
+ async function sha256(bytes) {
398
+ // Copy so a backend-owned mutable buffer cannot change while Web Crypto is reading it.
399
+ const stable = new Uint8Array(bytes.byteLength);
400
+ stable.set(bytes);
401
+ const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", stable));
402
+ return [...digest].map((byte) => byte.toString(16).padStart(2, "0")).join("");
332
403
  }
333
404
  function errorResponse(status, code, extraHeaders = {}) {
334
405
  return new Response(JSON.stringify({ error: code }), {
@@ -339,20 +410,41 @@ function errorResponse(status, code, extraHeaders = {}) {
339
410
  function createSemaphore(max, maxWaiters) {
340
411
  let active = 0;
341
412
  const waiters = [];
342
- const acquire = async () => {
413
+ const acquire = async (signal) => {
414
+ if (signal?.aborted === true)
415
+ return "aborted";
343
416
  if (active < max) {
344
417
  active++;
345
- return true;
418
+ return "acquired";
346
419
  }
347
420
  if (waiters.length >= maxWaiters)
348
- return false;
349
- await new Promise((res) => waiters.push(res)); // slot handed over by release(), active unchanged
350
- return true;
421
+ return "full";
422
+ return new Promise((resolve) => {
423
+ const cleanup = () => signal?.removeEventListener("abort", onAbort);
424
+ const waiter = {
425
+ grant: () => {
426
+ cleanup();
427
+ resolve("acquired"); // slot handed over by release(); active remains unchanged
428
+ },
429
+ };
430
+ const onAbort = () => {
431
+ const index = waiters.indexOf(waiter);
432
+ if (index === -1)
433
+ return; // already granted synchronously by release()
434
+ waiters.splice(index, 1);
435
+ cleanup();
436
+ resolve("aborted");
437
+ };
438
+ waiters.push(waiter);
439
+ signal?.addEventListener("abort", onAbort, { once: true });
440
+ if (signal?.aborted === true)
441
+ onAbort(); // close the add/check race
442
+ });
351
443
  };
352
444
  const release = () => {
353
445
  const next = waiters.shift();
354
446
  if (next !== undefined) {
355
- next(); // hand the slot to the waiter without touching `active`
447
+ next.grant(); // hand the slot to the waiter without touching `active`
356
448
  }
357
449
  else {
358
450
  active--;
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nifrajs/image",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "description": "Image optimization for nifra - a CLS-safe responsive <Image> + pluggable loaders (CDN or self-hosted) + pure-JS dimension reading. Zero-dependency core; optional Bun.Image-backed resize endpoint at @nifrajs/image/server.",
5
5
  "keywords": [
6
6
  "bun",
package/src/server.ts CHANGED
@@ -11,7 +11,8 @@
11
11
  * from `@nifrajs/image` instead. The dependency-free core (`@nifrajs/image`) never imports this module.
12
12
  */
13
13
 
14
- import { readFile, realpath, stat } from "node:fs/promises"
14
+ import { constants as FS } from "node:fs"
15
+ import { open, realpath } from "node:fs/promises"
15
16
  import { resolve as resolvePath, sep } from "node:path"
16
17
 
17
18
  import {
@@ -57,8 +58,11 @@ export interface ImageHandlerOptions {
57
58
  readonly sourceConcurrency?: number
58
59
  /** Maximum queued image requests beyond the admission width. Default `concurrency * 16`. */
59
60
  readonly maxQueue?: number
60
- /** `Cache-Control: public, max-age=<n>, immutable` seconds. Default 1 year. */
61
+ /** `Cache-Control: public, max-age=<n>` seconds. Default 1 hour. */
61
62
  readonly cacheMaxAge?: number
63
+ /** Add `immutable` to Cache-Control. Off by default: enable only when every source URL changes with
64
+ * its content, otherwise browsers are allowed to retain a changed image until the full max-age. */
65
+ readonly immutable?: boolean
62
66
  /** Quality used when `?q` is absent. Default 75. */
63
67
  readonly defaultQuality?: number
64
68
  /** Remote-fetch timeout (ms). Default 10 000. */
@@ -94,6 +98,7 @@ interface ResolvedConfig {
94
98
  readonly maxSourcePixels: number
95
99
  readonly maxWidth: number
96
100
  readonly cacheMaxAge: number
101
+ readonly immutable: boolean
97
102
  readonly defaultQuality: number
98
103
  readonly fetchTimeoutMs: number
99
104
  readonly fetchImpl: typeof fetch
@@ -125,11 +130,23 @@ function resolveConfig(options: ImageHandlerOptions): ResolvedConfig {
125
130
  const concurrency = options.concurrency ?? 4
126
131
  const sourceConcurrency = options.sourceConcurrency ?? concurrency * 2
127
132
  const maxQueue = options.maxQueue ?? concurrency * 16
133
+ const maxWidth = options.maxWidth ?? 3840
134
+ const cacheMaxAge = options.cacheMaxAge ?? 3_600
135
+ const defaultQuality = options.defaultQuality ?? 75
136
+ const fetchTimeoutMs = options.fetchTimeoutMs ?? 10_000
137
+ const immutable = options.immutable ?? false
128
138
  assertNonNegativeSafeInteger(maxSourceBytes, "maxSourceBytes")
129
139
  assertPositiveSafeInteger(maxSourcePixels, "maxSourcePixels")
130
140
  assertPositiveSafeInteger(concurrency, "concurrency")
131
141
  assertPositiveSafeInteger(sourceConcurrency, "sourceConcurrency")
132
142
  assertNonNegativeSafeInteger(maxQueue, "maxQueue")
143
+ assertPositiveSafeInteger(maxWidth, "maxWidth")
144
+ assertNonNegativeSafeInteger(cacheMaxAge, "cacheMaxAge")
145
+ assertPositiveSafeInteger(fetchTimeoutMs, "fetchTimeoutMs")
146
+ if (!Number.isSafeInteger(defaultQuality) || defaultQuality < 1 || defaultQuality > 100) {
147
+ throw new RangeError("image: defaultQuality must be a safe integer from 1 to 100")
148
+ }
149
+ if (typeof immutable !== "boolean") throw new TypeError("image: immutable must be a boolean")
133
150
  if (sourceConcurrency < concurrency) {
134
151
  // Otherwise the codec lane can never fill, and `concurrency` silently means something narrower.
135
152
  throw new RangeError("image: sourceConcurrency must be >= concurrency")
@@ -140,10 +157,11 @@ function resolveConfig(options: ImageHandlerOptions): ResolvedConfig {
140
157
  allowedOrigins: new Set(options.allowedOrigins ?? []),
141
158
  maxSourceBytes,
142
159
  maxSourcePixels,
143
- maxWidth: options.maxWidth ?? 3840,
144
- cacheMaxAge: options.cacheMaxAge ?? 31_536_000,
145
- defaultQuality: options.defaultQuality ?? 75,
146
- fetchTimeoutMs: options.fetchTimeoutMs ?? 10_000,
160
+ maxWidth,
161
+ cacheMaxAge,
162
+ immutable,
163
+ defaultQuality,
164
+ fetchTimeoutMs,
147
165
  fetchImpl: options.fetch ?? globalThis.fetch,
148
166
  signing: options.signing ?? null,
149
167
  admit: createSemaphore(sourceConcurrency, maxQueue),
@@ -198,29 +216,20 @@ async function handle(req: Request, cfg: ResolvedConfig): Promise<Response> {
198
216
  if (!ok) return errorResponse(403, "invalid_signature")
199
217
  }
200
218
 
201
- // 3. Strong validator from the request-deterministic inputs, computed BEFORE any fetch/decode so a
202
- // conditional request short-circuits all expensive work. The clamped width is a pure function of
203
- // (src, requestedWidth), so (src, requestedWidth, quality, wantsWebp) fully keys the response.
204
- const etag = `"${fnv1a(`${src}|${requestedWidth}|${quality}|${wantsWebp ? 1 : 0}`)}"`
205
- const cacheHeaders: Record<string, string> = {
206
- "Cache-Control": `public, max-age=${cfg.cacheMaxAge}, immutable`,
207
- ETag: etag,
208
- Vary: "Accept",
209
- }
210
- if (ifNoneMatch(req, etag)) {
211
- return new Response(null, { status: 304, headers: cacheHeaders })
212
- }
213
-
214
- // 4/5. Admission precedes source reads so queued requests do not retain large buffers. It is a
219
+ // 3/4. Admission precedes source reads so queued requests do not retain large buffers. It is a
215
220
  // separate, wider lane from the codec one below: a remote source is a network wait, and letting
216
221
  // it hold a codec slot would let `concurrency` slow origins stall every transform on the box.
217
- if (!(await cfg.admit.acquire())) return errorResponse(503, "image_queue_full")
222
+ const admission = await cfg.admit.acquire(req.signal)
223
+ if (admission === "full") return errorResponse(503, "image_queue_full")
224
+ if (admission === "aborted") return errorResponse(499, "request_cancelled")
218
225
  try {
219
- const source = await readSource(src, cfg)
226
+ const source = await readSource(src, cfg, req.signal)
220
227
  if (!source.ok) return errorResponse(source.status, source.code)
221
228
  // The bytes are in hand; now queue for CPU. This semaphore never refuses - its waiter bound is the
222
229
  // admission width - so a wait here is a wait, not a 503.
223
- await cfg.codec.acquire()
230
+ const codecAdmission = await cfg.codec.acquire(req.signal)
231
+ if (codecAdmission === "full") return errorResponse(503, "image_queue_full")
232
+ if (codecAdmission === "aborted") return errorResponse(499, "request_cancelled")
224
233
  let out: Awaited<ReturnType<ImageBackend["transform"]>>
225
234
  try {
226
235
  // Probe → enforce the portable pixel cap → clamp to intrinsic (never upscale) → transform.
@@ -228,6 +237,7 @@ async function handle(req: Request, cfg: ResolvedConfig): Promise<Response> {
228
237
  if (probe.width * probe.height > cfg.maxSourcePixels) {
229
238
  return errorResponse(413, "source_too_large")
230
239
  }
240
+ if (req.signal.aborted) return errorResponse(499, "request_cancelled")
231
241
  const targetWidth = Math.max(1, Math.min(width, probe.width))
232
242
  const format = negotiateFormat(probe.format, wantsWebp)
233
243
  out = await cfg.backend.transform({
@@ -236,14 +246,23 @@ async function handle(req: Request, cfg: ResolvedConfig): Promise<Response> {
236
246
  quality,
237
247
  format,
238
248
  })
249
+ if (req.signal.aborted) return errorResponse(499, "request_cancelled")
239
250
  } finally {
240
251
  cfg.codec.release()
241
252
  }
242
253
 
243
- const headers = new Headers(cacheHeaders)
254
+ // A strong validator describes the bytes actually emitted. Hashing request parameters here would
255
+ // falsely return 304 when a mutable local/remote source changes at the same URL.
256
+ const etag = `"${await sha256(out.bytes)}"`
257
+ const headers = new Headers({
258
+ "Cache-Control": `public, max-age=${cfg.cacheMaxAge}${cfg.immutable ? ", immutable" : ""}`,
259
+ ETag: etag,
260
+ Vary: "Accept",
261
+ })
244
262
  headers.set("Content-Type", out.contentType)
245
263
  headers.set("Content-Length", String(out.bytes.byteLength))
246
264
  headers.set("X-Content-Type-Options", "nosniff") // never let a client sniff the re-encoded bytes
265
+ if (ifNoneMatch(req, etag)) return new Response(null, { status: 304, headers })
247
266
  // HEAD: identical headers, no body.
248
267
  if (req.method === "HEAD") return new Response(null, { status: 200, headers })
249
268
  return new Response(out.bytes, { status: 200, headers })
@@ -268,7 +287,12 @@ type SourceResult =
268
287
  * must be `http(s)` **and** on the `allowedOrigins` allowlist; anything else is a local path resolved
269
288
  * under `root` with traversal + symlink containment checks. Both branches fail closed.
270
289
  */
271
- async function readSource(src: string, cfg: ResolvedConfig): Promise<SourceResult> {
290
+ async function readSource(
291
+ src: string,
292
+ cfg: ResolvedConfig,
293
+ signal: AbortSignal,
294
+ ): Promise<SourceResult> {
295
+ if (signal.aborted) return { ok: false, status: 499, code: "request_cancelled" }
272
296
  const asUrl = tryParseUrl(src)
273
297
  if (asUrl !== null) {
274
298
  if (asUrl.protocol !== "http:" && asUrl.protocol !== "https:") {
@@ -277,32 +301,55 @@ async function readSource(src: string, cfg: ResolvedConfig): Promise<SourceResul
277
301
  if (!cfg.allowedOrigins.has(asUrl.origin)) {
278
302
  return { ok: false, status: 403, code: "source_not_allowed" }
279
303
  }
280
- return fetchRemote(asUrl, cfg)
304
+ return fetchRemote(asUrl, cfg, signal)
281
305
  }
282
- return readLocal(src, cfg)
306
+ return readLocal(src, cfg, signal)
283
307
  }
284
308
 
285
- async function fetchRemote(url: URL, cfg: ResolvedConfig): Promise<SourceResult> {
286
- let res: Response
309
+ async function fetchRemote(
310
+ url: URL,
311
+ cfg: ResolvedConfig,
312
+ requestSignal: AbortSignal,
313
+ ): Promise<SourceResult> {
314
+ const controller = new AbortController()
315
+ let timedOut = false
316
+ const onRequestAbort = (): void => controller.abort(requestSignal.reason)
317
+ requestSignal.addEventListener("abort", onRequestAbort, { once: true })
318
+ // Close the add/check race: a signal that aborted immediately before listener registration still
319
+ // cancels the fetch rather than running an abandoned request to completion.
320
+ if (requestSignal.aborted) onRequestAbort()
321
+ const timer = setTimeout(() => {
322
+ timedOut = true
323
+ controller.abort()
324
+ }, cfg.fetchTimeoutMs)
287
325
  try {
288
- res = await cfg.fetchImpl(url, {
326
+ const res = await cfg.fetchImpl(url, {
289
327
  redirect: "error", // a redirect could bounce to a disallowed origin - refuse it.
290
- signal: AbortSignal.timeout(cfg.fetchTimeoutMs),
328
+ signal: controller.signal,
291
329
  headers: { Accept: "image/*" },
292
330
  })
331
+ if (!res.ok) return { ok: false, status: 502, code: "upstream_error" }
332
+ const bytes = await readBoundedBytes(res, cfg.maxSourceBytes)
333
+ if (bytes === null) return { ok: false, status: 413, code: "source_too_large" }
334
+ if (requestSignal.aborted) return { ok: false, status: 499, code: "request_cancelled" }
335
+ return { ok: true, bytes }
293
336
  } catch (err) {
294
337
  const name = err instanceof Error ? err.name : ""
295
- return name === "TimeoutError"
338
+ if (requestSignal.aborted) return { ok: false, status: 499, code: "request_cancelled" }
339
+ return timedOut || name === "TimeoutError"
296
340
  ? { ok: false, status: 504, code: "upstream_timeout" }
297
341
  : { ok: false, status: 502, code: "upstream_unreachable" }
342
+ } finally {
343
+ clearTimeout(timer)
344
+ requestSignal.removeEventListener("abort", onRequestAbort)
298
345
  }
299
- if (!res.ok) return { ok: false, status: 502, code: "upstream_error" }
300
- const bytes = await readBoundedBytes(res, cfg.maxSourceBytes)
301
- if (bytes === null) return { ok: false, status: 413, code: "source_too_large" }
302
- return { ok: true, bytes }
303
346
  }
304
347
 
305
- async function readLocal(src: string, cfg: ResolvedConfig): Promise<SourceResult> {
348
+ async function readLocal(
349
+ src: string,
350
+ cfg: ResolvedConfig,
351
+ signal: AbortSignal,
352
+ ): Promise<SourceResult> {
306
353
  if (cfg.root === null) return { ok: false, status: 403, code: "source_not_allowed" }
307
354
  // `src` is already percent-decoded by URLSearchParams - do NOT decode again (double-decode bypass).
308
355
  if (src.includes("\0")) return { ok: false, status: 400, code: "invalid_src" }
@@ -313,25 +360,49 @@ async function readLocal(src: string, cfg: ResolvedConfig): Promise<SourceResult
313
360
  if (resolved !== cfg.root && !resolved.startsWith(cfg.root + sep)) {
314
361
  return { ok: false, status: 403, code: "source_not_allowed" }
315
362
  }
316
- let info: Awaited<ReturnType<typeof stat>>
363
+ // Resolve containment, then open that verified name exactly once. Reopening `resolved` after these
364
+ // checks would let a writable source tree swap a symlink between verification and read.
365
+ let real: string
317
366
  try {
318
- info = await stat(resolved)
367
+ real = await realpath(resolved)
368
+ const realRoot = await realpath(cfg.root)
369
+ if (real !== realRoot && !real.startsWith(realRoot + sep)) {
370
+ return { ok: false, status: 403, code: "source_not_allowed" }
371
+ }
319
372
  } catch {
320
373
  return { ok: false, status: 404, code: "source_not_found" }
321
374
  }
322
- if (!info.isFile()) return { ok: false, status: 404, code: "source_not_found" }
323
- if (info.size > cfg.maxSourceBytes) return { ok: false, status: 413, code: "source_too_large" }
324
- // Defense-in-depth: a symlink inside root could point outside it - re-check the real path.
375
+ let handle: Awaited<ReturnType<typeof open>> | undefined
325
376
  try {
326
- const real = await realpath(resolved)
327
- const realRoot = await realpath(cfg.root)
328
- if (real !== realRoot && !real.startsWith(realRoot + sep)) {
329
- return { ok: false, status: 403, code: "source_not_allowed" }
377
+ handle = await open(real, FS.O_RDONLY | FS.O_NOFOLLOW)
378
+ const info = await handle.stat()
379
+ if (!info.isFile()) return { ok: false, status: 404, code: "source_not_found" }
380
+ if (info.size > cfg.maxSourceBytes) {
381
+ return { ok: false, status: 413, code: "source_too_large" }
382
+ }
383
+ // Read no more than the descriptor size that passed the cap. If the file grows concurrently, the
384
+ // appended bytes are deliberately ignored; if it shrinks, return only the bytes actually read.
385
+ const bytes = new Uint8Array(info.size)
386
+ let offset = 0
387
+ while (offset < bytes.byteLength) {
388
+ if (signal.aborted) return { ok: false, status: 499, code: "request_cancelled" }
389
+ const { bytesRead } = await handle.read(bytes, offset, bytes.byteLength - offset, offset)
390
+ if (bytesRead === 0) break
391
+ offset += bytesRead
330
392
  }
393
+ if (signal.aborted) return { ok: false, status: 499, code: "request_cancelled" }
394
+ return { ok: true, bytes: offset === bytes.byteLength ? bytes : bytes.slice(0, offset) }
331
395
  } catch {
332
396
  return { ok: false, status: 404, code: "source_not_found" }
397
+ } finally {
398
+ if (handle !== undefined) {
399
+ try {
400
+ await handle.close()
401
+ } catch {
402
+ // The response has already been decided; descriptor-close failure cannot change it.
403
+ }
404
+ }
333
405
  }
334
- return { ok: true, bytes: new Uint8Array(await readFile(resolved)) }
335
406
  }
336
407
 
337
408
  /** Read a response body, aborting (→ null) once `limit` bytes are exceeded. Trusts neither the
@@ -397,14 +468,13 @@ function ifNoneMatch(req: Request, etag: string): boolean {
397
468
  return header.split(",").some((t) => t.trim() === etag)
398
469
  }
399
470
 
400
- /** 32-bit FNV-1a over a short key string → hex. Used only as a cache validator (ETag), not security. */
401
- function fnv1a(input: string): string {
402
- let h = 0x811c9dc5
403
- for (let i = 0; i < input.length; i++) {
404
- h ^= input.charCodeAt(i)
405
- h = Math.imul(h, 0x01000193)
406
- }
407
- return (h >>> 0).toString(16)
471
+ /** Collision-resistant content digest for a strong representation validator. */
472
+ async function sha256(bytes: Uint8Array): Promise<string> {
473
+ // Copy so a backend-owned mutable buffer cannot change while Web Crypto is reading it.
474
+ const stable = new Uint8Array(bytes.byteLength)
475
+ stable.set(bytes)
476
+ const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", stable))
477
+ return [...digest].map((byte) => byte.toString(16).padStart(2, "0")).join("")
408
478
  }
409
479
 
410
480
  function errorResponse(
@@ -424,26 +494,47 @@ function errorResponse(
424
494
  * past the limit in the microtask gap. Bounds concurrent codec work.
425
495
  */
426
496
  interface Semaphore {
427
- acquire(): Promise<boolean>
497
+ acquire(signal?: AbortSignal): Promise<"acquired" | "full" | "aborted">
428
498
  release(): void
429
499
  }
430
500
 
431
501
  function createSemaphore(max: number, maxWaiters: number): Semaphore {
432
502
  let active = 0
433
- const waiters: Array<() => void> = []
434
- const acquire = async (): Promise<boolean> => {
503
+ interface Waiter {
504
+ readonly grant: () => void
505
+ }
506
+ const waiters: Waiter[] = []
507
+ const acquire = async (signal?: AbortSignal): Promise<"acquired" | "full" | "aborted"> => {
508
+ if (signal?.aborted === true) return "aborted"
435
509
  if (active < max) {
436
510
  active++
437
- return true
511
+ return "acquired"
438
512
  }
439
- if (waiters.length >= maxWaiters) return false
440
- await new Promise<void>((res) => waiters.push(res)) // slot handed over by release(), active unchanged
441
- return true
513
+ if (waiters.length >= maxWaiters) return "full"
514
+ return new Promise((resolve) => {
515
+ const cleanup = (): void => signal?.removeEventListener("abort", onAbort)
516
+ const waiter: Waiter = {
517
+ grant: () => {
518
+ cleanup()
519
+ resolve("acquired") // slot handed over by release(); active remains unchanged
520
+ },
521
+ }
522
+ const onAbort = (): void => {
523
+ const index = waiters.indexOf(waiter)
524
+ if (index === -1) return // already granted synchronously by release()
525
+ waiters.splice(index, 1)
526
+ cleanup()
527
+ resolve("aborted")
528
+ }
529
+ waiters.push(waiter)
530
+ signal?.addEventListener("abort", onAbort, { once: true })
531
+ if (signal?.aborted === true) onAbort() // close the add/check race
532
+ })
442
533
  }
443
534
  const release = (): void => {
444
535
  const next = waiters.shift()
445
536
  if (next !== undefined) {
446
- next() // hand the slot to the waiter without touching `active`
537
+ next.grant() // hand the slot to the waiter without touching `active`
447
538
  } else {
448
539
  active--
449
540
  }