ipx 2.0.1 → 2.1.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/cli.cjs +2 -2
- package/dist/cli.mjs +2 -2
- package/dist/index.cjs +23 -6
- package/dist/index.d.cts +6 -3
- package/dist/index.d.mts +6 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.mjs +23 -6
- package/dist/shared/{ipx.ebaf2d0c.cjs → ipx.7601f01b.cjs} +68 -51
- package/dist/shared/{ipx.4d79c6c9.mjs → ipx.b027cc1c.mjs} +68 -51
- package/package.json +17 -17
- package/dist/chunks/svgo-xss.cjs +0 -116
- package/dist/chunks/svgo-xss.mjs +0 -114
package/dist/cli.cjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const listhen = require('listhen');
|
|
4
4
|
const citty = require('citty');
|
|
5
5
|
const cli = require('listhen/cli');
|
|
6
|
-
const nodeFs = require('./shared/ipx.
|
|
6
|
+
const nodeFs = require('./shared/ipx.7601f01b.cjs');
|
|
7
7
|
require('defu');
|
|
8
8
|
require('ufo');
|
|
9
9
|
require('h3');
|
|
@@ -15,7 +15,7 @@ require('ofetch');
|
|
|
15
15
|
require('pathe');
|
|
16
16
|
|
|
17
17
|
const name = "ipx";
|
|
18
|
-
const version = "2.0
|
|
18
|
+
const version = "2.1.0";
|
|
19
19
|
const description = "High performance, secure and easy-to-use image optimizer.";
|
|
20
20
|
|
|
21
21
|
const serve = citty.defineCommand({
|
package/dist/cli.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { listen } from 'listhen';
|
|
2
2
|
import { defineCommand, runMain } from 'citty';
|
|
3
3
|
import { getArgs, parseArgs } from 'listhen/cli';
|
|
4
|
-
import { c as createIPX, g as ipxFSStorage, i as ipxHttpStorage, e as createIPXNodeServer } from './shared/ipx.
|
|
4
|
+
import { c as createIPX, g as ipxFSStorage, i as ipxHttpStorage, e as createIPXNodeServer } from './shared/ipx.b027cc1c.mjs';
|
|
5
5
|
import 'defu';
|
|
6
6
|
import 'ufo';
|
|
7
7
|
import 'h3';
|
|
@@ -13,7 +13,7 @@ import 'ofetch';
|
|
|
13
13
|
import 'pathe';
|
|
14
14
|
|
|
15
15
|
const name = "ipx";
|
|
16
|
-
const version = "2.0
|
|
16
|
+
const version = "2.1.0";
|
|
17
17
|
const description = "High performance, secure and easy-to-use image optimizer.";
|
|
18
18
|
|
|
19
19
|
const serve = defineCommand({
|
package/dist/index.cjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const nodeFs = require('./shared/ipx.
|
|
3
|
+
const nodeFs = require('./shared/ipx.7601f01b.cjs');
|
|
4
|
+
const h3 = require('h3');
|
|
4
5
|
require('defu');
|
|
5
6
|
require('ufo');
|
|
6
|
-
require('h3');
|
|
7
7
|
require('image-meta');
|
|
8
8
|
require('destr');
|
|
9
9
|
require('@fastify/accept-negotiator');
|
|
@@ -11,8 +11,9 @@ require('etag');
|
|
|
11
11
|
require('ofetch');
|
|
12
12
|
require('pathe');
|
|
13
13
|
|
|
14
|
-
function unstorageToIPXStorage(storage,
|
|
15
|
-
const
|
|
14
|
+
function unstorageToIPXStorage(storage, _options = {}) {
|
|
15
|
+
const options = typeof _options === "string" ? { prefix: _options } : _options;
|
|
16
|
+
const resolveKey = (id) => options.prefix ? `${options.prefix}:${id}` : id;
|
|
16
17
|
return {
|
|
17
18
|
name: "ipx:" + (storage.name || "unstorage"),
|
|
18
19
|
async getMeta(id, opts = {}) {
|
|
@@ -28,8 +29,24 @@ function unstorageToIPXStorage(storage, prefix) {
|
|
|
28
29
|
return;
|
|
29
30
|
}
|
|
30
31
|
const storageKey = resolveKey(id);
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
let data = await storage.getItemRaw(storageKey, opts);
|
|
33
|
+
if (!data) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (data instanceof Blob) {
|
|
37
|
+
data = await data.arrayBuffer();
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
return Buffer.from(data);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw h3.createError({
|
|
43
|
+
statusCode: 500,
|
|
44
|
+
statusText: `IPX_STORAGE_ERROR`,
|
|
45
|
+
message: `Failed to parse storage data to Buffer:
|
|
46
|
+
${error.message}`,
|
|
47
|
+
cause: error
|
|
48
|
+
});
|
|
49
|
+
}
|
|
33
50
|
}
|
|
34
51
|
};
|
|
35
52
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -145,11 +145,14 @@ type HTTPStorageOptions = {
|
|
|
145
145
|
declare function ipxHttpStorage(_options?: HTTPStorageOptions): IPXStorage;
|
|
146
146
|
|
|
147
147
|
type NodeFSSOptions = {
|
|
148
|
-
dir?: string;
|
|
148
|
+
dir?: string | string[];
|
|
149
149
|
maxAge?: number;
|
|
150
150
|
};
|
|
151
151
|
declare function ipxFSStorage(_options?: NodeFSSOptions): IPXStorage;
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
type UnstorageIPXStorageOptions = {
|
|
154
|
+
prefix?: string;
|
|
155
|
+
};
|
|
156
|
+
declare function unstorageToIPXStorage(storage: Storage | Driver, _options?: UnstorageIPXStorageOptions | string): IPXStorage;
|
|
154
157
|
|
|
155
|
-
export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type NodeFSSOptions, createIPX, createIPXH3App, createIPXH3Handler, createIPXNodeServer, createIPXPlainServer, createIPXWebServer, ipxFSStorage, ipxHttpStorage, unstorageToIPXStorage };
|
|
158
|
+
export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type NodeFSSOptions, type UnstorageIPXStorageOptions, createIPX, createIPXH3App, createIPXH3Handler, createIPXNodeServer, createIPXPlainServer, createIPXWebServer, ipxFSStorage, ipxHttpStorage, unstorageToIPXStorage };
|
package/dist/index.d.mts
CHANGED
|
@@ -145,11 +145,14 @@ type HTTPStorageOptions = {
|
|
|
145
145
|
declare function ipxHttpStorage(_options?: HTTPStorageOptions): IPXStorage;
|
|
146
146
|
|
|
147
147
|
type NodeFSSOptions = {
|
|
148
|
-
dir?: string;
|
|
148
|
+
dir?: string | string[];
|
|
149
149
|
maxAge?: number;
|
|
150
150
|
};
|
|
151
151
|
declare function ipxFSStorage(_options?: NodeFSSOptions): IPXStorage;
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
type UnstorageIPXStorageOptions = {
|
|
154
|
+
prefix?: string;
|
|
155
|
+
};
|
|
156
|
+
declare function unstorageToIPXStorage(storage: Storage | Driver, _options?: UnstorageIPXStorageOptions | string): IPXStorage;
|
|
154
157
|
|
|
155
|
-
export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type NodeFSSOptions, createIPX, createIPXH3App, createIPXH3Handler, createIPXNodeServer, createIPXPlainServer, createIPXWebServer, ipxFSStorage, ipxHttpStorage, unstorageToIPXStorage };
|
|
158
|
+
export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type NodeFSSOptions, type UnstorageIPXStorageOptions, createIPX, createIPXH3App, createIPXH3Handler, createIPXNodeServer, createIPXPlainServer, createIPXWebServer, ipxFSStorage, ipxHttpStorage, unstorageToIPXStorage };
|
package/dist/index.d.ts
CHANGED
|
@@ -145,11 +145,14 @@ type HTTPStorageOptions = {
|
|
|
145
145
|
declare function ipxHttpStorage(_options?: HTTPStorageOptions): IPXStorage;
|
|
146
146
|
|
|
147
147
|
type NodeFSSOptions = {
|
|
148
|
-
dir?: string;
|
|
148
|
+
dir?: string | string[];
|
|
149
149
|
maxAge?: number;
|
|
150
150
|
};
|
|
151
151
|
declare function ipxFSStorage(_options?: NodeFSSOptions): IPXStorage;
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
type UnstorageIPXStorageOptions = {
|
|
154
|
+
prefix?: string;
|
|
155
|
+
};
|
|
156
|
+
declare function unstorageToIPXStorage(storage: Storage | Driver, _options?: UnstorageIPXStorageOptions | string): IPXStorage;
|
|
154
157
|
|
|
155
|
-
export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type NodeFSSOptions, createIPX, createIPXH3App, createIPXH3Handler, createIPXNodeServer, createIPXPlainServer, createIPXWebServer, ipxFSStorage, ipxHttpStorage, unstorageToIPXStorage };
|
|
158
|
+
export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type NodeFSSOptions, type UnstorageIPXStorageOptions, createIPX, createIPXH3App, createIPXH3Handler, createIPXNodeServer, createIPXPlainServer, createIPXWebServer, ipxFSStorage, ipxHttpStorage, unstorageToIPXStorage };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { c as createIPX, b as createIPXH3App, a as createIPXH3Handler, e as createIPXNodeServer, f as createIPXPlainServer, d as createIPXWebServer, g as ipxFSStorage, i as ipxHttpStorage } from './shared/ipx.
|
|
1
|
+
export { c as createIPX, b as createIPXH3App, a as createIPXH3Handler, e as createIPXNodeServer, f as createIPXPlainServer, d as createIPXWebServer, g as ipxFSStorage, i as ipxHttpStorage } from './shared/ipx.b027cc1c.mjs';
|
|
2
|
+
import { createError } from 'h3';
|
|
2
3
|
import 'defu';
|
|
3
4
|
import 'ufo';
|
|
4
|
-
import 'h3';
|
|
5
5
|
import 'image-meta';
|
|
6
6
|
import 'destr';
|
|
7
7
|
import '@fastify/accept-negotiator';
|
|
@@ -9,8 +9,9 @@ import 'etag';
|
|
|
9
9
|
import 'ofetch';
|
|
10
10
|
import 'pathe';
|
|
11
11
|
|
|
12
|
-
function unstorageToIPXStorage(storage,
|
|
13
|
-
const
|
|
12
|
+
function unstorageToIPXStorage(storage, _options = {}) {
|
|
13
|
+
const options = typeof _options === "string" ? { prefix: _options } : _options;
|
|
14
|
+
const resolveKey = (id) => options.prefix ? `${options.prefix}:${id}` : id;
|
|
14
15
|
return {
|
|
15
16
|
name: "ipx:" + (storage.name || "unstorage"),
|
|
16
17
|
async getMeta(id, opts = {}) {
|
|
@@ -26,8 +27,24 @@ function unstorageToIPXStorage(storage, prefix) {
|
|
|
26
27
|
return;
|
|
27
28
|
}
|
|
28
29
|
const storageKey = resolveKey(id);
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
let data = await storage.getItemRaw(storageKey, opts);
|
|
31
|
+
if (!data) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (data instanceof Blob) {
|
|
35
|
+
data = await data.arrayBuffer();
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
return Buffer.from(data);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw createError({
|
|
41
|
+
statusCode: 500,
|
|
42
|
+
statusText: `IPX_STORAGE_ERROR`,
|
|
43
|
+
message: `Failed to parse storage data to Buffer:
|
|
44
|
+
${error.message}`,
|
|
45
|
+
cause: error
|
|
46
|
+
});
|
|
47
|
+
}
|
|
31
48
|
}
|
|
32
49
|
};
|
|
33
50
|
}
|
|
@@ -325,7 +325,9 @@ function createIPX(userOptions) {
|
|
|
325
325
|
const options = defu.defu(userOptions, {
|
|
326
326
|
alias: getEnv("IPX_ALIAS") || {},
|
|
327
327
|
maxAge: getEnv("IPX_MAX_AGE") ?? 60,
|
|
328
|
-
sharpOptions: {
|
|
328
|
+
sharpOptions: {
|
|
329
|
+
jpegProgressive: true
|
|
330
|
+
}
|
|
329
331
|
});
|
|
330
332
|
options.alias = Object.fromEntries(
|
|
331
333
|
Object.entries(options.alias || {}).map((e) => [
|
|
@@ -340,8 +342,7 @@ function createIPX(userOptions) {
|
|
|
340
342
|
});
|
|
341
343
|
const getSVGO = cachedPromise(async () => {
|
|
342
344
|
const { optimize } = await import('svgo');
|
|
343
|
-
|
|
344
|
-
return { optimize, xss };
|
|
345
|
+
return { optimize };
|
|
345
346
|
});
|
|
346
347
|
return function ipx(id, modifiers = {}, opts = {}) {
|
|
347
348
|
if (!id) {
|
|
@@ -416,10 +417,10 @@ function createIPX(userOptions) {
|
|
|
416
417
|
meta: imageMeta$1
|
|
417
418
|
};
|
|
418
419
|
} else {
|
|
419
|
-
const { optimize
|
|
420
|
+
const { optimize } = await getSVGO();
|
|
420
421
|
const svg = optimize(sourceData.toString("utf8"), {
|
|
421
422
|
...options.svgo,
|
|
422
|
-
plugins: [
|
|
423
|
+
plugins: ["removeScriptElement", ...options.svgo?.plugins || []]
|
|
423
424
|
}).data;
|
|
424
425
|
return {
|
|
425
426
|
data: svg,
|
|
@@ -450,8 +451,7 @@ function createIPX(userOptions) {
|
|
|
450
451
|
}
|
|
451
452
|
if (SUPPORTED_FORMATS.has(format || "")) {
|
|
452
453
|
sharp = sharp.toFormat(format, {
|
|
453
|
-
quality: handlerContext.quality
|
|
454
|
-
progressive: format === "jpeg"
|
|
454
|
+
quality: handlerContext.quality
|
|
455
455
|
});
|
|
456
456
|
}
|
|
457
457
|
const processedImage = await sharp.toBuffer();
|
|
@@ -519,13 +519,6 @@ function createIPXH3Handler(ipx) {
|
|
|
519
519
|
"content-security-policy",
|
|
520
520
|
"default-src 'none'"
|
|
521
521
|
);
|
|
522
|
-
if (typeof sourceMeta.maxAge === "number") {
|
|
523
|
-
sendResponseHeaderIfNotSet(
|
|
524
|
-
event,
|
|
525
|
-
"cache-control",
|
|
526
|
-
`max-age=${+sourceMeta.maxAge}, public, s-maxage=${+sourceMeta.maxAge}`
|
|
527
|
-
);
|
|
528
|
-
}
|
|
529
522
|
if (sourceMeta.mtime) {
|
|
530
523
|
sendResponseHeaderIfNotSet(
|
|
531
524
|
event,
|
|
@@ -539,6 +532,13 @@ function createIPXH3Handler(ipx) {
|
|
|
539
532
|
}
|
|
540
533
|
}
|
|
541
534
|
const { data, format } = await img.process();
|
|
535
|
+
if (typeof sourceMeta.maxAge === "number") {
|
|
536
|
+
sendResponseHeaderIfNotSet(
|
|
537
|
+
event,
|
|
538
|
+
"cache-control",
|
|
539
|
+
`max-age=${+sourceMeta.maxAge}, public, s-maxage=${+sourceMeta.maxAge}`
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
542
|
const etag = getEtag__default(data);
|
|
543
543
|
sendResponseHeaderIfNotSet(event, "etag", etag);
|
|
544
544
|
if (etag && h3.getRequestHeader(event, "if-none-match") === etag) {
|
|
@@ -605,9 +605,9 @@ function safeString(input) {
|
|
|
605
605
|
|
|
606
606
|
const HTTP_RE = /^https?:\/\//;
|
|
607
607
|
function ipxHttpStorage(_options = {}) {
|
|
608
|
-
const allowAllDomains = getEnv("IPX_HTTP_ALLOW_ALL_DOMAINS") ?? false;
|
|
608
|
+
const allowAllDomains = _options.allowAllDomains ?? getEnv("IPX_HTTP_ALLOW_ALL_DOMAINS") ?? false;
|
|
609
609
|
let _domains = _options.domains || getEnv("IPX_HTTP_DOMAINS") || [];
|
|
610
|
-
const defaultMaxAge = _options.maxAge || getEnv("IPX_HTTP_MAX_AGE");
|
|
610
|
+
const defaultMaxAge = _options.maxAge || getEnv("IPX_HTTP_MAX_AGE") || 300;
|
|
611
611
|
const fetchOptions = _options.fetchOptions || getEnv("IPX_HTTP_FETCH_OPTIONS") || {};
|
|
612
612
|
if (typeof _domains === "string") {
|
|
613
613
|
_domains = _domains.split(",").map((s) => s.trim());
|
|
@@ -684,56 +684,66 @@ function ipxHttpStorage(_options = {}) {
|
|
|
684
684
|
}
|
|
685
685
|
|
|
686
686
|
function ipxFSStorage(_options = {}) {
|
|
687
|
-
const
|
|
687
|
+
const dirs = resolveDirs(_options.dir);
|
|
688
688
|
const maxAge = _options.maxAge || getEnv("IPX_FS_MAX_AGE");
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
if (!isValidPath(resolved) || !resolved.startsWith(rootDir)) {
|
|
689
|
+
const _getFS = cachedPromise(
|
|
690
|
+
() => import('node:fs/promises').catch(() => {
|
|
692
691
|
throw h3.createError({
|
|
693
|
-
statusCode:
|
|
694
|
-
statusText: `
|
|
695
|
-
message: `
|
|
692
|
+
statusCode: 500,
|
|
693
|
+
statusText: `IPX_FILESYSTEM_ERROR`,
|
|
694
|
+
message: `Failed to resolve filesystem module`
|
|
696
695
|
});
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
let stats;
|
|
706
|
-
try {
|
|
707
|
-
const fs = await _getFS();
|
|
708
|
-
stats = await fs.stat(fsPath);
|
|
709
|
-
} catch (error) {
|
|
710
|
-
throw error.code === "ENOENT" ? h3.createError({
|
|
711
|
-
statusCode: 404,
|
|
712
|
-
statusText: `IPX_FILE_NOT_FOUND`,
|
|
713
|
-
message: `File not found: ${id}`
|
|
714
|
-
}) : h3.createError({
|
|
696
|
+
})
|
|
697
|
+
);
|
|
698
|
+
const resolveFile = async (id) => {
|
|
699
|
+
const fs = await _getFS();
|
|
700
|
+
for (const dir of dirs) {
|
|
701
|
+
const filePath = pathe.join(dir, id);
|
|
702
|
+
if (!isValidPath(filePath) || !filePath.startsWith(dir)) {
|
|
703
|
+
throw h3.createError({
|
|
715
704
|
statusCode: 403,
|
|
716
|
-
statusText: `
|
|
717
|
-
message: `
|
|
705
|
+
statusText: `IPX_FORBIDDEN_PATH`,
|
|
706
|
+
message: `Forbidden path: ${id}`
|
|
718
707
|
});
|
|
719
708
|
}
|
|
720
|
-
|
|
709
|
+
try {
|
|
710
|
+
const stats = await fs.stat(filePath);
|
|
711
|
+
if (!stats.isFile()) {
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
return {
|
|
715
|
+
stats,
|
|
716
|
+
read: () => fs.readFile(filePath)
|
|
717
|
+
};
|
|
718
|
+
} catch (error) {
|
|
719
|
+
if (error.code === "ENOENT") {
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
721
722
|
throw h3.createError({
|
|
722
|
-
statusCode:
|
|
723
|
-
statusText: `
|
|
724
|
-
message: `
|
|
723
|
+
statusCode: 403,
|
|
724
|
+
statusText: `IPX_FORBIDDEN_FILE`,
|
|
725
|
+
message: `Cannot access file: ${id}`
|
|
725
726
|
});
|
|
726
727
|
}
|
|
728
|
+
}
|
|
729
|
+
throw h3.createError({
|
|
730
|
+
statusCode: 404,
|
|
731
|
+
statusText: `IPX_FILE_NOT_FOUND`,
|
|
732
|
+
message: `File not found: ${id}`
|
|
733
|
+
});
|
|
734
|
+
};
|
|
735
|
+
return {
|
|
736
|
+
name: "ipx:node-fs",
|
|
737
|
+
async getMeta(id) {
|
|
738
|
+
const { stats } = await resolveFile(id);
|
|
727
739
|
return {
|
|
728
740
|
mtime: stats.mtime,
|
|
729
741
|
maxAge
|
|
730
742
|
};
|
|
731
743
|
},
|
|
732
744
|
async getData(id) {
|
|
733
|
-
const
|
|
734
|
-
|
|
735
|
-
const contents = await fs.readFile(fsPath);
|
|
736
|
-
return contents;
|
|
745
|
+
const { read } = await resolveFile(id);
|
|
746
|
+
return read();
|
|
737
747
|
}
|
|
738
748
|
};
|
|
739
749
|
}
|
|
@@ -747,6 +757,13 @@ function isValidPath(fp) {
|
|
|
747
757
|
}
|
|
748
758
|
return true;
|
|
749
759
|
}
|
|
760
|
+
function resolveDirs(dirs) {
|
|
761
|
+
if (!dirs || !Array.isArray(dirs)) {
|
|
762
|
+
const dir = pathe.resolve(dirs || getEnv("IPX_FS_DIR") || ".");
|
|
763
|
+
return [dir];
|
|
764
|
+
}
|
|
765
|
+
return dirs.map((dirs2) => pathe.resolve(dirs2));
|
|
766
|
+
}
|
|
750
767
|
|
|
751
768
|
exports.createIPX = createIPX;
|
|
752
769
|
exports.createIPXH3App = createIPXH3App;
|
|
@@ -318,7 +318,9 @@ function createIPX(userOptions) {
|
|
|
318
318
|
const options = defu(userOptions, {
|
|
319
319
|
alias: getEnv("IPX_ALIAS") || {},
|
|
320
320
|
maxAge: getEnv("IPX_MAX_AGE") ?? 60,
|
|
321
|
-
sharpOptions: {
|
|
321
|
+
sharpOptions: {
|
|
322
|
+
jpegProgressive: true
|
|
323
|
+
}
|
|
322
324
|
});
|
|
323
325
|
options.alias = Object.fromEntries(
|
|
324
326
|
Object.entries(options.alias || {}).map((e) => [
|
|
@@ -333,8 +335,7 @@ function createIPX(userOptions) {
|
|
|
333
335
|
});
|
|
334
336
|
const getSVGO = cachedPromise(async () => {
|
|
335
337
|
const { optimize } = await import('svgo');
|
|
336
|
-
|
|
337
|
-
return { optimize, xss };
|
|
338
|
+
return { optimize };
|
|
338
339
|
});
|
|
339
340
|
return function ipx(id, modifiers = {}, opts = {}) {
|
|
340
341
|
if (!id) {
|
|
@@ -409,10 +410,10 @@ function createIPX(userOptions) {
|
|
|
409
410
|
meta: imageMeta$1
|
|
410
411
|
};
|
|
411
412
|
} else {
|
|
412
|
-
const { optimize
|
|
413
|
+
const { optimize } = await getSVGO();
|
|
413
414
|
const svg = optimize(sourceData.toString("utf8"), {
|
|
414
415
|
...options.svgo,
|
|
415
|
-
plugins: [
|
|
416
|
+
plugins: ["removeScriptElement", ...options.svgo?.plugins || []]
|
|
416
417
|
}).data;
|
|
417
418
|
return {
|
|
418
419
|
data: svg,
|
|
@@ -443,8 +444,7 @@ function createIPX(userOptions) {
|
|
|
443
444
|
}
|
|
444
445
|
if (SUPPORTED_FORMATS.has(format || "")) {
|
|
445
446
|
sharp = sharp.toFormat(format, {
|
|
446
|
-
quality: handlerContext.quality
|
|
447
|
-
progressive: format === "jpeg"
|
|
447
|
+
quality: handlerContext.quality
|
|
448
448
|
});
|
|
449
449
|
}
|
|
450
450
|
const processedImage = await sharp.toBuffer();
|
|
@@ -512,13 +512,6 @@ function createIPXH3Handler(ipx) {
|
|
|
512
512
|
"content-security-policy",
|
|
513
513
|
"default-src 'none'"
|
|
514
514
|
);
|
|
515
|
-
if (typeof sourceMeta.maxAge === "number") {
|
|
516
|
-
sendResponseHeaderIfNotSet(
|
|
517
|
-
event,
|
|
518
|
-
"cache-control",
|
|
519
|
-
`max-age=${+sourceMeta.maxAge}, public, s-maxage=${+sourceMeta.maxAge}`
|
|
520
|
-
);
|
|
521
|
-
}
|
|
522
515
|
if (sourceMeta.mtime) {
|
|
523
516
|
sendResponseHeaderIfNotSet(
|
|
524
517
|
event,
|
|
@@ -532,6 +525,13 @@ function createIPXH3Handler(ipx) {
|
|
|
532
525
|
}
|
|
533
526
|
}
|
|
534
527
|
const { data, format } = await img.process();
|
|
528
|
+
if (typeof sourceMeta.maxAge === "number") {
|
|
529
|
+
sendResponseHeaderIfNotSet(
|
|
530
|
+
event,
|
|
531
|
+
"cache-control",
|
|
532
|
+
`max-age=${+sourceMeta.maxAge}, public, s-maxage=${+sourceMeta.maxAge}`
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
535
|
const etag = getEtag(data);
|
|
536
536
|
sendResponseHeaderIfNotSet(event, "etag", etag);
|
|
537
537
|
if (etag && getRequestHeader(event, "if-none-match") === etag) {
|
|
@@ -598,9 +598,9 @@ function safeString(input) {
|
|
|
598
598
|
|
|
599
599
|
const HTTP_RE = /^https?:\/\//;
|
|
600
600
|
function ipxHttpStorage(_options = {}) {
|
|
601
|
-
const allowAllDomains = getEnv("IPX_HTTP_ALLOW_ALL_DOMAINS") ?? false;
|
|
601
|
+
const allowAllDomains = _options.allowAllDomains ?? getEnv("IPX_HTTP_ALLOW_ALL_DOMAINS") ?? false;
|
|
602
602
|
let _domains = _options.domains || getEnv("IPX_HTTP_DOMAINS") || [];
|
|
603
|
-
const defaultMaxAge = _options.maxAge || getEnv("IPX_HTTP_MAX_AGE");
|
|
603
|
+
const defaultMaxAge = _options.maxAge || getEnv("IPX_HTTP_MAX_AGE") || 300;
|
|
604
604
|
const fetchOptions = _options.fetchOptions || getEnv("IPX_HTTP_FETCH_OPTIONS") || {};
|
|
605
605
|
if (typeof _domains === "string") {
|
|
606
606
|
_domains = _domains.split(",").map((s) => s.trim());
|
|
@@ -677,56 +677,66 @@ function ipxHttpStorage(_options = {}) {
|
|
|
677
677
|
}
|
|
678
678
|
|
|
679
679
|
function ipxFSStorage(_options = {}) {
|
|
680
|
-
const
|
|
680
|
+
const dirs = resolveDirs(_options.dir);
|
|
681
681
|
const maxAge = _options.maxAge || getEnv("IPX_FS_MAX_AGE");
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
if (!isValidPath(resolved) || !resolved.startsWith(rootDir)) {
|
|
682
|
+
const _getFS = cachedPromise(
|
|
683
|
+
() => import('node:fs/promises').catch(() => {
|
|
685
684
|
throw createError({
|
|
686
|
-
statusCode:
|
|
687
|
-
statusText: `
|
|
688
|
-
message: `
|
|
685
|
+
statusCode: 500,
|
|
686
|
+
statusText: `IPX_FILESYSTEM_ERROR`,
|
|
687
|
+
message: `Failed to resolve filesystem module`
|
|
689
688
|
});
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
let stats;
|
|
699
|
-
try {
|
|
700
|
-
const fs = await _getFS();
|
|
701
|
-
stats = await fs.stat(fsPath);
|
|
702
|
-
} catch (error) {
|
|
703
|
-
throw error.code === "ENOENT" ? createError({
|
|
704
|
-
statusCode: 404,
|
|
705
|
-
statusText: `IPX_FILE_NOT_FOUND`,
|
|
706
|
-
message: `File not found: ${id}`
|
|
707
|
-
}) : createError({
|
|
689
|
+
})
|
|
690
|
+
);
|
|
691
|
+
const resolveFile = async (id) => {
|
|
692
|
+
const fs = await _getFS();
|
|
693
|
+
for (const dir of dirs) {
|
|
694
|
+
const filePath = join(dir, id);
|
|
695
|
+
if (!isValidPath(filePath) || !filePath.startsWith(dir)) {
|
|
696
|
+
throw createError({
|
|
708
697
|
statusCode: 403,
|
|
709
|
-
statusText: `
|
|
710
|
-
message: `
|
|
698
|
+
statusText: `IPX_FORBIDDEN_PATH`,
|
|
699
|
+
message: `Forbidden path: ${id}`
|
|
711
700
|
});
|
|
712
701
|
}
|
|
713
|
-
|
|
702
|
+
try {
|
|
703
|
+
const stats = await fs.stat(filePath);
|
|
704
|
+
if (!stats.isFile()) {
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
return {
|
|
708
|
+
stats,
|
|
709
|
+
read: () => fs.readFile(filePath)
|
|
710
|
+
};
|
|
711
|
+
} catch (error) {
|
|
712
|
+
if (error.code === "ENOENT") {
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
714
715
|
throw createError({
|
|
715
|
-
statusCode:
|
|
716
|
-
statusText: `
|
|
717
|
-
message: `
|
|
716
|
+
statusCode: 403,
|
|
717
|
+
statusText: `IPX_FORBIDDEN_FILE`,
|
|
718
|
+
message: `Cannot access file: ${id}`
|
|
718
719
|
});
|
|
719
720
|
}
|
|
721
|
+
}
|
|
722
|
+
throw createError({
|
|
723
|
+
statusCode: 404,
|
|
724
|
+
statusText: `IPX_FILE_NOT_FOUND`,
|
|
725
|
+
message: `File not found: ${id}`
|
|
726
|
+
});
|
|
727
|
+
};
|
|
728
|
+
return {
|
|
729
|
+
name: "ipx:node-fs",
|
|
730
|
+
async getMeta(id) {
|
|
731
|
+
const { stats } = await resolveFile(id);
|
|
720
732
|
return {
|
|
721
733
|
mtime: stats.mtime,
|
|
722
734
|
maxAge
|
|
723
735
|
};
|
|
724
736
|
},
|
|
725
737
|
async getData(id) {
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
const contents = await fs.readFile(fsPath);
|
|
729
|
-
return contents;
|
|
738
|
+
const { read } = await resolveFile(id);
|
|
739
|
+
return read();
|
|
730
740
|
}
|
|
731
741
|
};
|
|
732
742
|
}
|
|
@@ -740,5 +750,12 @@ function isValidPath(fp) {
|
|
|
740
750
|
}
|
|
741
751
|
return true;
|
|
742
752
|
}
|
|
753
|
+
function resolveDirs(dirs) {
|
|
754
|
+
if (!dirs || !Array.isArray(dirs)) {
|
|
755
|
+
const dir = resolve(dirs || getEnv("IPX_FS_DIR") || ".");
|
|
756
|
+
return [dir];
|
|
757
|
+
}
|
|
758
|
+
return dirs.map((dirs2) => resolve(dirs2));
|
|
759
|
+
}
|
|
743
760
|
|
|
744
761
|
export { createIPXH3Handler as a, createIPXH3App as b, createIPX as c, createIPXWebServer as d, createIPXNodeServer as e, createIPXPlainServer as f, ipxFSStorage as g, ipxHttpStorage as i };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ipx",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"repository": "unjs/ipx",
|
|
5
5
|
"description": "High performance, secure and easy-to-use image optimizer.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,35 +38,35 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@fastify/accept-negotiator": "^1.1.0",
|
|
41
|
-
"citty": "^0.1.
|
|
41
|
+
"citty": "^0.1.5",
|
|
42
42
|
"consola": "^3.2.3",
|
|
43
|
-
"defu": "^6.1.
|
|
43
|
+
"defu": "^6.1.4",
|
|
44
44
|
"destr": "^2.0.2",
|
|
45
45
|
"etag": "^1.8.1",
|
|
46
|
-
"h3": "^1.
|
|
46
|
+
"h3": "^1.10.0",
|
|
47
47
|
"image-meta": "^0.2.0",
|
|
48
|
-
"listhen": "^1.5.
|
|
48
|
+
"listhen": "^1.5.6",
|
|
49
49
|
"ofetch": "^1.3.3",
|
|
50
|
-
"pathe": "^1.1.
|
|
50
|
+
"pathe": "^1.1.2",
|
|
51
51
|
"sharp": "^0.32.6",
|
|
52
|
-
"svgo": "^3.0
|
|
53
|
-
"ufo": "^1.3.
|
|
54
|
-
"unstorage": "^1.
|
|
52
|
+
"svgo": "^3.2.0",
|
|
53
|
+
"ufo": "^1.3.2",
|
|
54
|
+
"unstorage": "^1.10.1",
|
|
55
55
|
"xss": "^1.0.14"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@types/etag": "^1.8.
|
|
59
|
-
"@types/is-valid-path": "^0.1.
|
|
60
|
-
"@vitest/coverage-v8": "^
|
|
58
|
+
"@types/etag": "^1.8.3",
|
|
59
|
+
"@types/is-valid-path": "^0.1.2",
|
|
60
|
+
"@vitest/coverage-v8": "^1.1.3",
|
|
61
61
|
"changelogen": "^0.5.5",
|
|
62
|
-
"eslint": "^8.
|
|
62
|
+
"eslint": "^8.56.0",
|
|
63
63
|
"eslint-config-unjs": "^0.2.1",
|
|
64
64
|
"jiti": "^1.21.0",
|
|
65
|
-
"prettier": "^3.
|
|
65
|
+
"prettier": "^3.1.1",
|
|
66
66
|
"serve-handler": "^6.1.5",
|
|
67
|
-
"typescript": "^5.
|
|
67
|
+
"typescript": "^5.3.3",
|
|
68
68
|
"unbuild": "^2.0.0",
|
|
69
|
-
"vitest": "^
|
|
69
|
+
"vitest": "^1.1.3"
|
|
70
70
|
},
|
|
71
|
-
"packageManager": "pnpm@8.10.
|
|
71
|
+
"packageManager": "pnpm@8.10.5"
|
|
72
72
|
}
|
package/dist/chunks/svgo-xss.cjs
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const xss = {
|
|
4
|
-
name: "removeXSS",
|
|
5
|
-
fn() {
|
|
6
|
-
return {
|
|
7
|
-
element: {
|
|
8
|
-
enter: (node, parentNode) => {
|
|
9
|
-
if (node.name === "script") {
|
|
10
|
-
parentNode.children = parentNode.children.filter(
|
|
11
|
-
(child) => child !== node
|
|
12
|
-
);
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
for (const event of ALL_EVENTS) {
|
|
16
|
-
if (node.attributes[event] != null) {
|
|
17
|
-
delete node.attributes[event];
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
exit: (node, parentNode) => {
|
|
22
|
-
if (node.name !== "a") {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
for (const attr of Object.keys(node.attributes)) {
|
|
26
|
-
if (attr === "href" || attr.endsWith(":href")) {
|
|
27
|
-
if (node.attributes[attr] == null || !node.attributes[attr].trimStart().startsWith("javascript:")) {
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
const index = parentNode.children.indexOf(node);
|
|
31
|
-
parentNode.children.splice(index, 1, ...node.children);
|
|
32
|
-
for (const child of node.children) {
|
|
33
|
-
Object.defineProperty(child, "parentNode", {
|
|
34
|
-
writable: true,
|
|
35
|
-
value: parentNode
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
const ALL_EVENTS = [
|
|
46
|
-
"onabort",
|
|
47
|
-
"onactivate",
|
|
48
|
-
"onbegin",
|
|
49
|
-
"oncancel",
|
|
50
|
-
"oncanplay",
|
|
51
|
-
"oncanplaythrough",
|
|
52
|
-
"onchange",
|
|
53
|
-
"onclick",
|
|
54
|
-
"onclose",
|
|
55
|
-
"oncopy",
|
|
56
|
-
"oncuechange",
|
|
57
|
-
"oncut",
|
|
58
|
-
"ondblclick",
|
|
59
|
-
"ondrag",
|
|
60
|
-
"ondragend",
|
|
61
|
-
"ondragenter",
|
|
62
|
-
"ondragleave",
|
|
63
|
-
"ondragover",
|
|
64
|
-
"ondragstart",
|
|
65
|
-
"ondrop",
|
|
66
|
-
"ondurationchange",
|
|
67
|
-
"onemptied",
|
|
68
|
-
"onend",
|
|
69
|
-
"onended",
|
|
70
|
-
"onerror",
|
|
71
|
-
"onfocus",
|
|
72
|
-
"onfocusin",
|
|
73
|
-
"onfocusout",
|
|
74
|
-
"oninput",
|
|
75
|
-
"oninvalid",
|
|
76
|
-
"onkeydown",
|
|
77
|
-
"onkeypress",
|
|
78
|
-
"onkeyup",
|
|
79
|
-
"onload",
|
|
80
|
-
"onloadeddata",
|
|
81
|
-
"onloadedmetadata",
|
|
82
|
-
"onloadstart",
|
|
83
|
-
"onmousedown",
|
|
84
|
-
"onmouseenter",
|
|
85
|
-
"onmouseleave",
|
|
86
|
-
"onmousemove",
|
|
87
|
-
"onmouseout",
|
|
88
|
-
"onmouseover",
|
|
89
|
-
"onmouseup",
|
|
90
|
-
"onmousewheel",
|
|
91
|
-
"onpaste",
|
|
92
|
-
"onpause",
|
|
93
|
-
"onplay",
|
|
94
|
-
"onplaying",
|
|
95
|
-
"onprogress",
|
|
96
|
-
"onratechange",
|
|
97
|
-
"onrepeat",
|
|
98
|
-
"onreset",
|
|
99
|
-
"onresize",
|
|
100
|
-
"onscroll",
|
|
101
|
-
"onseeked",
|
|
102
|
-
"onseeking",
|
|
103
|
-
"onselect",
|
|
104
|
-
"onshow",
|
|
105
|
-
"onstalled",
|
|
106
|
-
"onsubmit",
|
|
107
|
-
"onsuspend",
|
|
108
|
-
"ontimeupdate",
|
|
109
|
-
"ontoggle",
|
|
110
|
-
"onunload",
|
|
111
|
-
"onvolumechange",
|
|
112
|
-
"onwaiting",
|
|
113
|
-
"onzoom"
|
|
114
|
-
];
|
|
115
|
-
|
|
116
|
-
exports.xss = xss;
|
package/dist/chunks/svgo-xss.mjs
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
const xss = {
|
|
2
|
-
name: "removeXSS",
|
|
3
|
-
fn() {
|
|
4
|
-
return {
|
|
5
|
-
element: {
|
|
6
|
-
enter: (node, parentNode) => {
|
|
7
|
-
if (node.name === "script") {
|
|
8
|
-
parentNode.children = parentNode.children.filter(
|
|
9
|
-
(child) => child !== node
|
|
10
|
-
);
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
for (const event of ALL_EVENTS) {
|
|
14
|
-
if (node.attributes[event] != null) {
|
|
15
|
-
delete node.attributes[event];
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
exit: (node, parentNode) => {
|
|
20
|
-
if (node.name !== "a") {
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
for (const attr of Object.keys(node.attributes)) {
|
|
24
|
-
if (attr === "href" || attr.endsWith(":href")) {
|
|
25
|
-
if (node.attributes[attr] == null || !node.attributes[attr].trimStart().startsWith("javascript:")) {
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
28
|
-
const index = parentNode.children.indexOf(node);
|
|
29
|
-
parentNode.children.splice(index, 1, ...node.children);
|
|
30
|
-
for (const child of node.children) {
|
|
31
|
-
Object.defineProperty(child, "parentNode", {
|
|
32
|
-
writable: true,
|
|
33
|
-
value: parentNode
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
const ALL_EVENTS = [
|
|
44
|
-
"onabort",
|
|
45
|
-
"onactivate",
|
|
46
|
-
"onbegin",
|
|
47
|
-
"oncancel",
|
|
48
|
-
"oncanplay",
|
|
49
|
-
"oncanplaythrough",
|
|
50
|
-
"onchange",
|
|
51
|
-
"onclick",
|
|
52
|
-
"onclose",
|
|
53
|
-
"oncopy",
|
|
54
|
-
"oncuechange",
|
|
55
|
-
"oncut",
|
|
56
|
-
"ondblclick",
|
|
57
|
-
"ondrag",
|
|
58
|
-
"ondragend",
|
|
59
|
-
"ondragenter",
|
|
60
|
-
"ondragleave",
|
|
61
|
-
"ondragover",
|
|
62
|
-
"ondragstart",
|
|
63
|
-
"ondrop",
|
|
64
|
-
"ondurationchange",
|
|
65
|
-
"onemptied",
|
|
66
|
-
"onend",
|
|
67
|
-
"onended",
|
|
68
|
-
"onerror",
|
|
69
|
-
"onfocus",
|
|
70
|
-
"onfocusin",
|
|
71
|
-
"onfocusout",
|
|
72
|
-
"oninput",
|
|
73
|
-
"oninvalid",
|
|
74
|
-
"onkeydown",
|
|
75
|
-
"onkeypress",
|
|
76
|
-
"onkeyup",
|
|
77
|
-
"onload",
|
|
78
|
-
"onloadeddata",
|
|
79
|
-
"onloadedmetadata",
|
|
80
|
-
"onloadstart",
|
|
81
|
-
"onmousedown",
|
|
82
|
-
"onmouseenter",
|
|
83
|
-
"onmouseleave",
|
|
84
|
-
"onmousemove",
|
|
85
|
-
"onmouseout",
|
|
86
|
-
"onmouseover",
|
|
87
|
-
"onmouseup",
|
|
88
|
-
"onmousewheel",
|
|
89
|
-
"onpaste",
|
|
90
|
-
"onpause",
|
|
91
|
-
"onplay",
|
|
92
|
-
"onplaying",
|
|
93
|
-
"onprogress",
|
|
94
|
-
"onratechange",
|
|
95
|
-
"onrepeat",
|
|
96
|
-
"onreset",
|
|
97
|
-
"onresize",
|
|
98
|
-
"onscroll",
|
|
99
|
-
"onseeked",
|
|
100
|
-
"onseeking",
|
|
101
|
-
"onselect",
|
|
102
|
-
"onshow",
|
|
103
|
-
"onstalled",
|
|
104
|
-
"onsubmit",
|
|
105
|
-
"onsuspend",
|
|
106
|
-
"ontimeupdate",
|
|
107
|
-
"ontoggle",
|
|
108
|
-
"onunload",
|
|
109
|
-
"onvolumechange",
|
|
110
|
-
"onwaiting",
|
|
111
|
-
"onzoom"
|
|
112
|
-
];
|
|
113
|
-
|
|
114
|
-
export { xss };
|