ipx 3.1.1 → 4.0.0-alpha.2

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.
@@ -1,778 +0,0 @@
1
- 'use strict';
2
-
3
- const defu = require('defu');
4
- const ufo = require('ufo');
5
- const h3 = require('h3');
6
- const imageMeta = require('image-meta');
7
- const destr = require('destr');
8
- const acceptNegotiator = require('@fastify/accept-negotiator');
9
- const getEtag = require('etag');
10
- const ofetch = require('ofetch');
11
- const pathe = require('pathe');
12
-
13
- function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
14
-
15
- const destr__default = /*#__PURE__*/_interopDefaultCompat(destr);
16
- const getEtag__default = /*#__PURE__*/_interopDefaultCompat(getEtag);
17
-
18
- const Handlers = {
19
- __proto__: null,
20
- get b () { return b; },
21
- get background () { return background; },
22
- get blur () { return blur; },
23
- get crop () { return crop; },
24
- get enlarge () { return enlarge; },
25
- get extend () { return extend; },
26
- get extract () { return extract; },
27
- get fit () { return fit; },
28
- get flatten () { return flatten; },
29
- get flip () { return flip; },
30
- get flop () { return flop; },
31
- get gamma () { return gamma; },
32
- get grayscale () { return grayscale; },
33
- get h () { return h; },
34
- get height () { return height; },
35
- get kernel () { return kernel; },
36
- get median () { return median; },
37
- get modulate () { return modulate; },
38
- get negate () { return negate; },
39
- get normalize () { return normalize; },
40
- get pos () { return pos; },
41
- get position () { return position; },
42
- get q () { return q; },
43
- get quality () { return quality; },
44
- get resize () { return resize; },
45
- get rotate () { return rotate; },
46
- get s () { return s; },
47
- get sharpen () { return sharpen; },
48
- get threshold () { return threshold; },
49
- get tint () { return tint; },
50
- get trim () { return trim; },
51
- get w () { return w; },
52
- get width () { return width; }
53
- };
54
-
55
- function VArg(argument) {
56
- return destr__default(argument);
57
- }
58
- function parseArgs(arguments_, mappers) {
59
- const vargs = arguments_.split("_");
60
- return mappers.map((v, index) => v(vargs[index]));
61
- }
62
- function getHandler(key) {
63
- return Handlers[key];
64
- }
65
- function applyHandler(context, pipe, handler, argumentsString) {
66
- const arguments_ = handler.args ? parseArgs(argumentsString, handler.args) : [];
67
- return handler.apply(context, pipe, ...arguments_);
68
- }
69
- function clampDimensionsPreservingAspectRatio(sourceDimensions, desiredDimensions) {
70
- const desiredAspectRatio = desiredDimensions.width / desiredDimensions.height;
71
- let { width, height } = desiredDimensions;
72
- if (sourceDimensions.width && width > sourceDimensions.width) {
73
- width = sourceDimensions.width;
74
- height = Math.round(sourceDimensions.width / desiredAspectRatio);
75
- }
76
- if (sourceDimensions.height && height > sourceDimensions.height) {
77
- height = sourceDimensions.height;
78
- width = Math.round(sourceDimensions.height * desiredAspectRatio);
79
- }
80
- return { width, height };
81
- }
82
-
83
- const quality = {
84
- args: [VArg],
85
- order: -1,
86
- apply: (context, _pipe, quality2) => {
87
- context.quality = quality2;
88
- }
89
- };
90
- const fit = {
91
- args: [VArg],
92
- order: -1,
93
- apply: (context, _pipe, fit2) => {
94
- context.fit = fit2;
95
- }
96
- };
97
- const position = {
98
- args: [VArg],
99
- order: -1,
100
- apply: (context, _pipe, position2) => {
101
- context.position = position2;
102
- }
103
- };
104
- const HEX_RE = /^([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i;
105
- const SHORTHEX_RE = /^([\da-f])([\da-f])([\da-f])$/i;
106
- const background = {
107
- args: [VArg],
108
- order: -1,
109
- apply: (context, _pipe, background2) => {
110
- background2 = String(background2);
111
- if (!background2.startsWith("#") && (HEX_RE.test(background2) || SHORTHEX_RE.test(background2))) {
112
- background2 = "#" + background2;
113
- }
114
- context.background = background2;
115
- }
116
- };
117
- const enlarge = {
118
- args: [],
119
- apply: (context) => {
120
- context.enlarge = true;
121
- }
122
- };
123
- const kernel = {
124
- args: [VArg],
125
- apply: (context, _pipe, kernel2) => {
126
- context.kernel = kernel2;
127
- }
128
- };
129
- const width = {
130
- args: [VArg],
131
- apply: (context, pipe, width2) => {
132
- return pipe.resize(width2, void 0, {
133
- withoutEnlargement: !context.enlarge
134
- });
135
- }
136
- };
137
- const height = {
138
- args: [VArg],
139
- apply: (context, pipe, height2) => {
140
- return pipe.resize(void 0, height2, {
141
- withoutEnlargement: !context.enlarge
142
- });
143
- }
144
- };
145
- const resize = {
146
- args: [VArg, VArg, VArg],
147
- apply: (context, pipe, size) => {
148
- let [width2, height2] = String(size).split("x").map(Number);
149
- if (!width2) {
150
- return;
151
- }
152
- if (!height2) {
153
- height2 = width2;
154
- }
155
- if (!context.enlarge) {
156
- const clamped = clampDimensionsPreservingAspectRatio(context.meta, {
157
- width: width2,
158
- height: height2
159
- });
160
- width2 = clamped.width;
161
- height2 = clamped.height;
162
- }
163
- return pipe.resize(width2, height2, {
164
- fit: context.fit,
165
- position: context.position,
166
- background: context.background,
167
- kernel: context.kernel
168
- });
169
- }
170
- };
171
- const trim = {
172
- args: [VArg],
173
- apply: (_context, pipe, threshold2) => {
174
- return pipe.trim(threshold2);
175
- }
176
- };
177
- const extend = {
178
- args: [VArg, VArg, VArg, VArg],
179
- apply: (context, pipe, top, right, bottom, left) => {
180
- return pipe.extend({
181
- top,
182
- left,
183
- bottom,
184
- right,
185
- background: context.background
186
- });
187
- }
188
- };
189
- const extract = {
190
- args: [VArg, VArg, VArg, VArg],
191
- apply: (_context, pipe, left, top, width2, height2) => {
192
- return pipe.extract({
193
- left,
194
- top,
195
- width: width2,
196
- height: height2
197
- });
198
- }
199
- };
200
- const rotate = {
201
- args: [VArg],
202
- apply: (context, pipe, angel) => {
203
- return pipe.rotate(angel, {
204
- background: context.background
205
- });
206
- }
207
- };
208
- const flip = {
209
- args: [],
210
- apply: (_context, pipe) => {
211
- return pipe.flip();
212
- }
213
- };
214
- const flop = {
215
- args: [],
216
- apply: (_context, pipe) => {
217
- return pipe.flop();
218
- }
219
- };
220
- const sharpen = {
221
- args: [VArg, VArg, VArg],
222
- apply: (_context, pipe, sigma, flat, jagged) => {
223
- return pipe.sharpen(sigma, flat, jagged);
224
- }
225
- };
226
- const median = {
227
- args: [VArg, VArg, VArg],
228
- apply: (_context, pipe, size) => {
229
- return pipe.median(size);
230
- }
231
- };
232
- const blur = {
233
- args: [VArg, VArg, VArg],
234
- apply: (_context, pipe, sigma) => {
235
- return pipe.blur(sigma);
236
- }
237
- };
238
- const flatten = {
239
- args: [VArg, VArg, VArg],
240
- apply: (context, pipe) => {
241
- return pipe.flatten({
242
- background: context.background
243
- });
244
- }
245
- };
246
- const gamma = {
247
- args: [VArg, VArg, VArg],
248
- apply: (_context, pipe, gamma2, gammaOut) => {
249
- return pipe.gamma(gamma2, gammaOut);
250
- }
251
- };
252
- const negate = {
253
- args: [VArg, VArg, VArg],
254
- apply: (_context, pipe) => {
255
- return pipe.negate();
256
- }
257
- };
258
- const normalize = {
259
- args: [VArg, VArg, VArg],
260
- apply: (_context, pipe) => {
261
- return pipe.normalize();
262
- }
263
- };
264
- const threshold = {
265
- args: [VArg],
266
- apply: (_context, pipe, threshold2) => {
267
- return pipe.threshold(threshold2);
268
- }
269
- };
270
- const modulate = {
271
- args: [VArg],
272
- apply: (_context, pipe, brightness, saturation, hue) => {
273
- return pipe.modulate({
274
- brightness,
275
- saturation,
276
- hue
277
- });
278
- }
279
- };
280
- const tint = {
281
- args: [VArg],
282
- apply: (_context, pipe, rgb) => {
283
- return pipe.tint(rgb);
284
- }
285
- };
286
- const grayscale = {
287
- args: [VArg],
288
- apply: (_context, pipe) => {
289
- return pipe.grayscale();
290
- }
291
- };
292
- const crop = extract;
293
- const q = quality;
294
- const b = background;
295
- const w = width;
296
- const h = height;
297
- const s = resize;
298
- const pos = position;
299
-
300
- function getEnv(name) {
301
- return name in process.env ? destr__default(process.env[name]) : void 0;
302
- }
303
- function cachedPromise(function_) {
304
- let p;
305
- return (...arguments_) => {
306
- if (p) {
307
- return p;
308
- }
309
- p = Promise.resolve(function_(...arguments_));
310
- return p;
311
- };
312
- }
313
-
314
- const SUPPORTED_FORMATS = /* @__PURE__ */ new Set([
315
- "jpeg",
316
- "png",
317
- "webp",
318
- "avif",
319
- "tiff",
320
- "heif",
321
- "gif",
322
- "heic"
323
- ]);
324
- function createIPX(userOptions) {
325
- const options = defu.defu(userOptions, {
326
- alias: getEnv("IPX_ALIAS") || {},
327
- maxAge: getEnv("IPX_MAX_AGE") ?? 60,
328
- sharpOptions: {
329
- jpegProgressive: true
330
- }
331
- });
332
- options.alias = Object.fromEntries(
333
- Object.entries(options.alias || {}).map((e) => [
334
- ufo.withLeadingSlash(e[0]),
335
- e[1]
336
- ])
337
- );
338
- const getSharp = cachedPromise(async () => {
339
- return await import('sharp').then(
340
- (r) => r.default || r
341
- );
342
- });
343
- const getSVGO = cachedPromise(async () => {
344
- const { optimize } = await import('svgo');
345
- return { optimize };
346
- });
347
- return function ipx(id, modifiers = {}, opts = {}) {
348
- if (!id) {
349
- throw h3.createError({
350
- statusCode: 400,
351
- statusText: `IPX_MISSING_ID`,
352
- message: `Resource id is missing`
353
- });
354
- }
355
- id = ufo.hasProtocol(id) ? id : ufo.withLeadingSlash(id);
356
- for (const base in options.alias) {
357
- if (id.startsWith(base)) {
358
- id = ufo.joinURL(options.alias[base], id.slice(base.length));
359
- }
360
- }
361
- const storage = ufo.hasProtocol(id) ? options.httpStorage || options.storage : options.storage || options.httpStorage;
362
- if (!storage) {
363
- throw h3.createError({
364
- statusCode: 500,
365
- statusText: `IPX_NO_STORAGE`,
366
- message: "No storage configured!"
367
- });
368
- }
369
- const getSourceMeta = cachedPromise(async () => {
370
- const sourceMeta = await storage.getMeta(id, opts);
371
- if (!sourceMeta) {
372
- throw h3.createError({
373
- statusCode: 404,
374
- statusText: `IPX_RESOURCE_NOT_FOUND`,
375
- message: `Resource not found: ${id}`
376
- });
377
- }
378
- const _maxAge = sourceMeta.maxAge ?? options.maxAge;
379
- return {
380
- maxAge: typeof _maxAge === "string" ? Number.parseInt(_maxAge) : _maxAge,
381
- mtime: sourceMeta.mtime ? new Date(sourceMeta.mtime) : void 0
382
- };
383
- });
384
- const getSourceData = cachedPromise(async () => {
385
- const sourceData = await storage.getData(id, opts);
386
- if (!sourceData) {
387
- throw h3.createError({
388
- statusCode: 404,
389
- statusText: `IPX_RESOURCE_NOT_FOUND`,
390
- message: `Resource not found: ${id}`
391
- });
392
- }
393
- return Buffer.from(sourceData);
394
- });
395
- const process = cachedPromise(async () => {
396
- const sourceData = await getSourceData();
397
- let imageMeta$1;
398
- try {
399
- imageMeta$1 = imageMeta.imageMeta(sourceData);
400
- } catch {
401
- throw h3.createError({
402
- statusCode: 400,
403
- statusText: `IPX_INVALID_IMAGE`,
404
- message: `Cannot parse image metadata: ${id}`
405
- });
406
- }
407
- let mFormat = modifiers.f || modifiers.format;
408
- if (mFormat === "jpg") {
409
- mFormat = "jpeg";
410
- }
411
- const format = mFormat && SUPPORTED_FORMATS.has(mFormat) ? mFormat : SUPPORTED_FORMATS.has(imageMeta$1.type || "") ? imageMeta$1.type : "jpeg";
412
- if (imageMeta$1.type === "svg" && !mFormat) {
413
- if (options.svgo === false) {
414
- return {
415
- data: sourceData,
416
- format: "svg+xml",
417
- meta: imageMeta$1
418
- };
419
- } else {
420
- const { optimize } = await getSVGO();
421
- const svg = optimize(sourceData.toString("utf8"), {
422
- ...options.svgo,
423
- plugins: ["removeScripts", ...options.svgo?.plugins || []]
424
- }).data;
425
- return {
426
- data: svg,
427
- format: "svg+xml",
428
- meta: imageMeta$1
429
- };
430
- }
431
- }
432
- const animated = modifiers.animated !== void 0 || modifiers.a !== void 0 || format === "gif";
433
- const Sharp = await getSharp();
434
- let sharp = Sharp(sourceData, { animated, ...options.sharpOptions });
435
- Object.assign(
436
- sharp.options,
437
- options.sharpOptions
438
- );
439
- const handlers = Object.entries(modifiers).map(([name, arguments_]) => ({
440
- handler: getHandler(name),
441
- name,
442
- args: arguments_
443
- })).filter((h) => h.handler).sort((a, b) => {
444
- const aKey = (a.handler.order || a.name || "").toString();
445
- const bKey = (b.handler.order || b.name || "").toString();
446
- return aKey.localeCompare(bKey);
447
- });
448
- const handlerContext = { meta: imageMeta$1 };
449
- for (const h of handlers) {
450
- sharp = applyHandler(handlerContext, sharp, h.handler, h.args) || sharp;
451
- }
452
- if (SUPPORTED_FORMATS.has(format || "")) {
453
- sharp = sharp.toFormat(format, {
454
- quality: handlerContext.quality
455
- });
456
- }
457
- const processedImage = await sharp.toBuffer();
458
- return {
459
- data: processedImage,
460
- format,
461
- meta: imageMeta$1
462
- };
463
- });
464
- return {
465
- getSourceMeta,
466
- process
467
- };
468
- };
469
- }
470
-
471
- const MODIFIER_SEP = /[&,]/g;
472
- const MODIFIER_VAL_SEP = /[:=_]/;
473
- function createIPXH3Handler(ipx) {
474
- const _handler = async (event) => {
475
- const [modifiersString = "", ...idSegments] = event.path.slice(
476
- 1
477
- /* leading slash */
478
- ).split("/");
479
- const id = safeString(ufo.decode(idSegments.join("/")));
480
- if (!modifiersString) {
481
- throw h3.createError({
482
- statusCode: 400,
483
- statusText: `IPX_MISSING_MODIFIERS`,
484
- message: `Modifiers are missing: ${id}`
485
- });
486
- }
487
- if (!id || id === "/") {
488
- throw h3.createError({
489
- statusCode: 400,
490
- statusText: `IPX_MISSING_ID`,
491
- message: `Resource id is missing: ${event.path}`
492
- });
493
- }
494
- const modifiers = /* @__PURE__ */ Object.create(null);
495
- if (modifiersString !== "_") {
496
- for (const p of modifiersString.split(MODIFIER_SEP)) {
497
- const [key, ...values] = p.split(MODIFIER_VAL_SEP);
498
- modifiers[safeString(key)] = values.map((v) => safeString(ufo.decode(v))).join("_");
499
- }
500
- }
501
- const mFormat = modifiers.f || modifiers.format;
502
- if (mFormat === "auto") {
503
- const acceptHeader = h3.getRequestHeader(event, "accept") || "";
504
- const animated = modifiers.animated ?? modifiers.a;
505
- const autoFormat = autoDetectFormat(
506
- acceptHeader,
507
- // #234 "animated" param adds {animated: ''} to the modifiers
508
- // TODO: fix modifiers to normalized to boolean
509
- !!animated || animated === ""
510
- );
511
- delete modifiers.f;
512
- delete modifiers.format;
513
- if (autoFormat) {
514
- modifiers.format = autoFormat;
515
- h3.appendResponseHeader(event, "vary", "Accept");
516
- }
517
- }
518
- const img = ipx(id, modifiers);
519
- const sourceMeta = await img.getSourceMeta();
520
- sendResponseHeaderIfNotSet(
521
- event,
522
- "content-security-policy",
523
- "default-src 'none'"
524
- );
525
- if (sourceMeta.mtime) {
526
- sendResponseHeaderIfNotSet(
527
- event,
528
- "last-modified",
529
- sourceMeta.mtime.toUTCString()
530
- );
531
- const _ifModifiedSince = h3.getRequestHeader(event, "if-modified-since");
532
- if (_ifModifiedSince && new Date(_ifModifiedSince) >= sourceMeta.mtime) {
533
- h3.setResponseStatus(event, 304);
534
- return h3.send(event);
535
- }
536
- }
537
- const { data, format } = await img.process();
538
- if (typeof sourceMeta.maxAge === "number") {
539
- sendResponseHeaderIfNotSet(
540
- event,
541
- "cache-control",
542
- `max-age=${+sourceMeta.maxAge}, public, s-maxage=${+sourceMeta.maxAge}`
543
- );
544
- }
545
- const etag = getEtag__default(data);
546
- sendResponseHeaderIfNotSet(event, "etag", etag);
547
- if (etag && h3.getRequestHeader(event, "if-none-match") === etag) {
548
- h3.setResponseStatus(event, 304);
549
- return h3.send(event);
550
- }
551
- if (format) {
552
- sendResponseHeaderIfNotSet(event, "content-type", `image/${format}`);
553
- }
554
- return data;
555
- };
556
- return h3.defineEventHandler(async (event) => {
557
- try {
558
- return await _handler(event);
559
- } catch (_error) {
560
- const error = h3.createError(_error);
561
- h3.setResponseStatus(event, error.statusCode, error.statusMessage);
562
- return {
563
- error: {
564
- message: `[${error.statusCode}] [${error.statusMessage || "IPX_ERROR"}] ${error.message}`
565
- }
566
- };
567
- }
568
- });
569
- }
570
- function createIPXH3App(ipx) {
571
- const app = h3.createApp({ debug: true });
572
- app.use(createIPXH3Handler(ipx));
573
- return app;
574
- }
575
- function createIPXWebServer(ipx) {
576
- return h3.toWebHandler(createIPXH3App(ipx));
577
- }
578
- function createIPXNodeServer(ipx) {
579
- return h3.toNodeListener(createIPXH3App(ipx));
580
- }
581
- function createIPXPlainServer(ipx) {
582
- return h3.toPlainHandler(createIPXH3App(ipx));
583
- }
584
- function sendResponseHeaderIfNotSet(event, name, value) {
585
- if (!h3.getResponseHeader(event, name)) {
586
- h3.setResponseHeader(event, name, value);
587
- }
588
- }
589
- function autoDetectFormat(acceptHeader, animated) {
590
- if (animated) {
591
- const acceptMime2 = acceptNegotiator.negotiate(acceptHeader, ["image/webp", "image/gif"]);
592
- return acceptMime2?.split("/")[1] || "gif";
593
- }
594
- const acceptMime = acceptNegotiator.negotiate(acceptHeader, [
595
- "image/avif",
596
- "image/webp",
597
- "image/jpeg",
598
- "image/png",
599
- "image/tiff",
600
- "image/heif",
601
- "image/gif"
602
- ]);
603
- return acceptMime?.split("/")[1] || "jpeg";
604
- }
605
- function safeString(input) {
606
- return JSON.stringify(input).replace(/^"|"$/g, "").replace(/\\+/g, "\\").replace(/\\"/g, '"');
607
- }
608
-
609
- const HTTP_RE = /^https?:\/\//;
610
- function ipxHttpStorage(_options = {}) {
611
- const allowAllDomains = _options.allowAllDomains ?? getEnv("IPX_HTTP_ALLOW_ALL_DOMAINS") ?? false;
612
- let _domains = _options.domains || getEnv("IPX_HTTP_DOMAINS") || [];
613
- const defaultMaxAge = _options.maxAge || getEnv("IPX_HTTP_MAX_AGE") || 300;
614
- const fetchOptions = _options.fetchOptions || getEnv("IPX_HTTP_FETCH_OPTIONS") || {};
615
- if (typeof _domains === "string") {
616
- _domains = _domains.split(",").map((s) => s.trim());
617
- }
618
- const domains = new Set(
619
- _domains.map((d) => {
620
- if (!HTTP_RE.test(d)) {
621
- d = "http://" + d;
622
- }
623
- return new URL(d).hostname;
624
- }).filter(Boolean)
625
- );
626
- function validateId(id) {
627
- const url = new URL(decodeURIComponent(id));
628
- if (!url.hostname) {
629
- throw h3.createError({
630
- statusCode: 403,
631
- statusText: `IPX_MISSING_HOSTNAME`,
632
- message: `Hostname is missing: ${id}`
633
- });
634
- }
635
- if (!allowAllDomains && !domains.has(url.hostname)) {
636
- throw h3.createError({
637
- statusCode: 403,
638
- statusText: `IPX_FORBIDDEN_HOST`,
639
- message: `Forbidden host: ${url.hostname}`
640
- });
641
- }
642
- return url.toString();
643
- }
644
- function parseResponse(response) {
645
- let maxAge = defaultMaxAge;
646
- if (_options.ignoreCacheControl !== true) {
647
- const _cacheControl = response.headers.get("cache-control");
648
- if (_cacheControl) {
649
- const m = _cacheControl.match(/max-age=(\d+)/);
650
- if (m && m[1]) {
651
- maxAge = Number.parseInt(m[1]);
652
- }
653
- }
654
- }
655
- let mtime;
656
- const _lastModified = response.headers.get("last-modified");
657
- if (_lastModified) {
658
- mtime = new Date(_lastModified);
659
- }
660
- return { maxAge, mtime };
661
- }
662
- return {
663
- name: "ipx:http",
664
- async getMeta(id) {
665
- const url = validateId(id);
666
- try {
667
- const response = await ofetch.ofetch.raw(url, {
668
- ...fetchOptions,
669
- method: "HEAD"
670
- });
671
- const { maxAge, mtime } = parseResponse(response);
672
- return { mtime, maxAge };
673
- } catch {
674
- return {};
675
- }
676
- },
677
- async getData(id) {
678
- const url = validateId(id);
679
- const response = await ofetch.ofetch(url, {
680
- ...fetchOptions,
681
- method: "GET",
682
- responseType: "arrayBuffer"
683
- });
684
- return response;
685
- }
686
- };
687
- }
688
-
689
- function ipxFSStorage(_options = {}) {
690
- const dirs = resolveDirs(_options.dir);
691
- const maxAge = _options.maxAge || getEnv("IPX_FS_MAX_AGE");
692
- const _getFS = cachedPromise(
693
- () => import('node:fs/promises').catch(() => {
694
- throw h3.createError({
695
- statusCode: 500,
696
- statusText: `IPX_FILESYSTEM_ERROR`,
697
- message: `Failed to resolve filesystem module`
698
- });
699
- })
700
- );
701
- const resolveFile = async (id) => {
702
- const fs = await _getFS();
703
- for (const dir of dirs) {
704
- const filePath = pathe.join(dir, id);
705
- if (!isValidPath(filePath) || !filePath.startsWith(dir + "/")) {
706
- throw h3.createError({
707
- statusCode: 403,
708
- statusText: `IPX_FORBIDDEN_PATH`,
709
- message: `Forbidden path: ${id}`
710
- });
711
- }
712
- try {
713
- const stats = await fs.stat(filePath);
714
- if (!stats.isFile()) {
715
- continue;
716
- }
717
- return {
718
- stats,
719
- read: () => fs.readFile(filePath)
720
- };
721
- } catch (error) {
722
- if (error.code === "ENOENT") {
723
- continue;
724
- }
725
- throw h3.createError({
726
- statusCode: 403,
727
- statusText: `IPX_FORBIDDEN_FILE`,
728
- message: `Cannot access file: ${id}`
729
- });
730
- }
731
- }
732
- throw h3.createError({
733
- statusCode: 404,
734
- statusText: `IPX_FILE_NOT_FOUND`,
735
- message: `File not found: ${id}`
736
- });
737
- };
738
- return {
739
- name: "ipx:node-fs",
740
- async getMeta(id) {
741
- const { stats } = await resolveFile(id);
742
- return {
743
- mtime: stats.mtime,
744
- maxAge
745
- };
746
- },
747
- async getData(id) {
748
- const { read } = await resolveFile(id);
749
- return read();
750
- }
751
- };
752
- }
753
- const isWindows = process.platform === "win32";
754
- function isValidPath(fp) {
755
- if (isWindows) {
756
- fp = fp.slice(pathe.parse(fp).root.length);
757
- }
758
- if (/["*:<>?|]/.test(fp)) {
759
- return false;
760
- }
761
- return true;
762
- }
763
- function resolveDirs(dirs) {
764
- if (!dirs || !Array.isArray(dirs)) {
765
- const dir = pathe.resolve(dirs || getEnv("IPX_FS_DIR") || ".");
766
- return [dir];
767
- }
768
- return dirs.map((dirs2) => pathe.resolve(dirs2));
769
- }
770
-
771
- exports.createIPX = createIPX;
772
- exports.createIPXH3App = createIPXH3App;
773
- exports.createIPXH3Handler = createIPXH3Handler;
774
- exports.createIPXNodeServer = createIPXNodeServer;
775
- exports.createIPXPlainServer = createIPXPlainServer;
776
- exports.createIPXWebServer = createIPXWebServer;
777
- exports.ipxFSStorage = ipxFSStorage;
778
- exports.ipxHttpStorage = ipxHttpStorage;