ipx 0.7.2 → 0.9.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.
- package/README.md +2 -2
- package/bin/ipx.mjs +2 -0
- package/dist/{cli.js → chunks/middleware.cjs} +31 -48
- package/dist/chunks/middleware.mjs +527 -0
- package/dist/cli.cjs +33 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +27 -0
- package/dist/index.cjs +19 -543
- package/dist/index.mjs +13 -528
- package/package.json +15 -15
- package/CHANGELOG.md +0 -211
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
High performance, secure and easy to use image proxy based on [sharp](https://github.com/lovell/sharp) and [libvips](https://github.com/libvips/libvips).
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## Usage
|
|
10
10
|
|
|
11
11
|
### Quick Start
|
|
12
12
|
|
|
@@ -85,6 +85,6 @@ Config can be customized using `IPX_*` environment variables.
|
|
|
85
85
|
- `IPX_DOMAINS`
|
|
86
86
|
- Default: `[]`
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
## License
|
|
89
89
|
|
|
90
90
|
MIT
|
package/bin/ipx.mjs
ADDED
|
@@ -1,38 +1,29 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
1
|
'use strict';
|
|
4
2
|
|
|
5
|
-
const consola = require('consola');
|
|
6
|
-
const listhen = require('listhen');
|
|
7
|
-
const Sharp = require('sharp');
|
|
8
3
|
const defu = require('defu');
|
|
9
4
|
const imageMeta = require('image-meta');
|
|
10
5
|
const ufo = require('ufo');
|
|
11
|
-
const
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const pathe = require('pathe');
|
|
12
8
|
const isValidPath = require('is-valid-path');
|
|
13
|
-
const fsExtra = require('fs-extra');
|
|
14
|
-
const destr = require('destr');
|
|
15
9
|
const http = require('http');
|
|
16
10
|
const https = require('https');
|
|
17
|
-
const
|
|
11
|
+
const ohmyfetch = require('ohmyfetch');
|
|
12
|
+
const destr = require('destr');
|
|
18
13
|
const getEtag = require('etag');
|
|
19
14
|
const xss = require('xss');
|
|
20
15
|
|
|
21
|
-
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e :
|
|
16
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
|
|
22
17
|
|
|
23
|
-
const consola__default = /*#__PURE__*/_interopDefaultLegacy(consola);
|
|
24
|
-
const Sharp__default = /*#__PURE__*/_interopDefaultLegacy(Sharp);
|
|
25
18
|
const defu__default = /*#__PURE__*/_interopDefaultLegacy(defu);
|
|
26
|
-
const imageMeta__default = /*#__PURE__*/_interopDefaultLegacy(imageMeta);
|
|
27
19
|
const isValidPath__default = /*#__PURE__*/_interopDefaultLegacy(isValidPath);
|
|
28
|
-
const destr__default = /*#__PURE__*/_interopDefaultLegacy(destr);
|
|
29
20
|
const http__default = /*#__PURE__*/_interopDefaultLegacy(http);
|
|
30
21
|
const https__default = /*#__PURE__*/_interopDefaultLegacy(https);
|
|
31
|
-
const
|
|
22
|
+
const destr__default = /*#__PURE__*/_interopDefaultLegacy(destr);
|
|
32
23
|
const getEtag__default = /*#__PURE__*/_interopDefaultLegacy(getEtag);
|
|
33
24
|
const xss__default = /*#__PURE__*/_interopDefaultLegacy(xss);
|
|
34
25
|
|
|
35
|
-
const Handlers =
|
|
26
|
+
const Handlers = {
|
|
36
27
|
__proto__: null,
|
|
37
28
|
get quality () { return quality; },
|
|
38
29
|
get fit () { return fit; },
|
|
@@ -64,11 +55,10 @@ const Handlers = /*#__PURE__*/Object.freeze({
|
|
|
64
55
|
get w () { return w; },
|
|
65
56
|
get h () { return h; },
|
|
66
57
|
get s () { return s; }
|
|
67
|
-
}
|
|
58
|
+
};
|
|
68
59
|
|
|
69
60
|
function getEnv(name, defaultValue) {
|
|
70
|
-
|
|
71
|
-
return (_a = destr__default['default'](process.env[name])) != null ? _a : defaultValue;
|
|
61
|
+
return destr__default(process.env[name]) ?? defaultValue;
|
|
72
62
|
}
|
|
73
63
|
function cachedPromise(fn) {
|
|
74
64
|
let p;
|
|
@@ -90,15 +80,15 @@ function createError(message, statusCode) {
|
|
|
90
80
|
}
|
|
91
81
|
|
|
92
82
|
const createFilesystemSource = (options) => {
|
|
93
|
-
const rootDir =
|
|
83
|
+
const rootDir = pathe.resolve(options.dir);
|
|
94
84
|
return async (id) => {
|
|
95
|
-
const fsPath =
|
|
96
|
-
if (!isValidPath__default
|
|
85
|
+
const fsPath = pathe.resolve(pathe.join(rootDir, id));
|
|
86
|
+
if (!isValidPath__default(id) || id.includes("..") || !fsPath.startsWith(rootDir)) {
|
|
97
87
|
throw createError("Forbidden path:" + id, 403);
|
|
98
88
|
}
|
|
99
89
|
let stats;
|
|
100
90
|
try {
|
|
101
|
-
stats = await
|
|
91
|
+
stats = await fs.promises.stat(fsPath);
|
|
102
92
|
} catch (err) {
|
|
103
93
|
if (err.code === "ENOENT") {
|
|
104
94
|
throw createError("File not found: " + fsPath, 404);
|
|
@@ -112,28 +102,28 @@ const createFilesystemSource = (options) => {
|
|
|
112
102
|
return {
|
|
113
103
|
mtime: stats.mtime,
|
|
114
104
|
maxAge: options.maxAge || 300,
|
|
115
|
-
getData: cachedPromise(() =>
|
|
105
|
+
getData: cachedPromise(() => fs.promises.readFile(fsPath))
|
|
116
106
|
};
|
|
117
107
|
};
|
|
118
108
|
};
|
|
119
109
|
|
|
120
110
|
const createHTTPSource = (options) => {
|
|
121
|
-
const httpsAgent = new https__default
|
|
122
|
-
const httpAgent = new http__default
|
|
111
|
+
const httpsAgent = new https__default.Agent({ keepAlive: true });
|
|
112
|
+
const httpAgent = new http__default.Agent({ keepAlive: true });
|
|
123
113
|
let domains = options.domains || [];
|
|
124
114
|
if (typeof domains === "string") {
|
|
125
115
|
domains = domains.split(",").map((s) => s.trim());
|
|
126
116
|
}
|
|
127
117
|
const hosts = domains.map((domain) => ufo.parseURL(domain, "https://").host);
|
|
128
118
|
return async (id, reqOptions) => {
|
|
129
|
-
const
|
|
130
|
-
if (!
|
|
119
|
+
const url = new URL(id);
|
|
120
|
+
if (!url.hostname) {
|
|
131
121
|
throw createError("Hostname is missing: " + id, 403);
|
|
132
122
|
}
|
|
133
|
-
if (!
|
|
134
|
-
throw createError("Forbidden host: " +
|
|
123
|
+
if (!reqOptions?.bypassDomain && !hosts.find((host) => url.hostname === host)) {
|
|
124
|
+
throw createError("Forbidden host: " + url.hostname, 403);
|
|
135
125
|
}
|
|
136
|
-
const response = await
|
|
126
|
+
const response = await ohmyfetch.fetch(id, {
|
|
137
127
|
agent: id.startsWith("https") ? httpsAgent : httpAgent
|
|
138
128
|
});
|
|
139
129
|
if (!response.ok) {
|
|
@@ -161,7 +151,7 @@ const createHTTPSource = (options) => {
|
|
|
161
151
|
};
|
|
162
152
|
|
|
163
153
|
function VArg(arg) {
|
|
164
|
-
return destr__default
|
|
154
|
+
return destr__default(arg);
|
|
165
155
|
}
|
|
166
156
|
function parseArgs(args, mappers) {
|
|
167
157
|
const vargs = args.split("_");
|
|
@@ -385,7 +375,7 @@ function createIPX(userOptions) {
|
|
|
385
375
|
alias: getEnv("IPX_ALIAS", {}),
|
|
386
376
|
sharp: {}
|
|
387
377
|
};
|
|
388
|
-
const options = defu__default
|
|
378
|
+
const options = defu__default(userOptions, defaults);
|
|
389
379
|
options.alias = Object.fromEntries(Object.entries(options.alias).map((e) => [ufo.withLeadingSlash(e[0]), e[1]]));
|
|
390
380
|
const ctx = {
|
|
391
381
|
sources: {}
|
|
@@ -420,7 +410,7 @@ function createIPX(userOptions) {
|
|
|
420
410
|
const getData = cachedPromise(async () => {
|
|
421
411
|
const src = await getSrc();
|
|
422
412
|
const data = await src.getData();
|
|
423
|
-
const meta =
|
|
413
|
+
const meta = imageMeta.imageMeta(data);
|
|
424
414
|
const mFormat = modifiers.f || modifiers.format;
|
|
425
415
|
let format = mFormat || meta.type;
|
|
426
416
|
if (format === "jpg") {
|
|
@@ -437,7 +427,8 @@ function createIPX(userOptions) {
|
|
|
437
427
|
if (animated) {
|
|
438
428
|
format = "webp";
|
|
439
429
|
}
|
|
440
|
-
|
|
430
|
+
const Sharp = await import('sharp').then((r) => r.default || r);
|
|
431
|
+
let sharp = Sharp(data, { animated });
|
|
441
432
|
Object.assign(sharp.options, options.sharp);
|
|
442
433
|
const handlers = Object.entries(modifiers).map(([name, args]) => ({ handler: getHandler(name), name, args })).filter((h) => h.handler).sort((a, b) => {
|
|
443
434
|
const aKey = (a.handler.order || a.name || "").toString();
|
|
@@ -505,7 +496,7 @@ async function _handleRequest(req, ipx) {
|
|
|
505
496
|
res.headers["Cache-Control"] = `max-age=${+src.maxAge}, public, s-maxage=${+src.maxAge}`;
|
|
506
497
|
}
|
|
507
498
|
const { data, format } = await img.data();
|
|
508
|
-
const etag = getEtag__default
|
|
499
|
+
const etag = getEtag__default(data);
|
|
509
500
|
res.headers.ETag = etag;
|
|
510
501
|
if (etag && req.headers["if-none-match"] === etag) {
|
|
511
502
|
res.statusCode = 304;
|
|
@@ -520,7 +511,7 @@ async function _handleRequest(req, ipx) {
|
|
|
520
511
|
function handleRequest(req, ipx) {
|
|
521
512
|
return _handleRequest(req, ipx).catch((err) => {
|
|
522
513
|
const statusCode = parseInt(err.statusCode) || 500;
|
|
523
|
-
const statusMessage = err.statusMessage ? xss__default
|
|
514
|
+
const statusMessage = err.statusMessage ? xss__default(err.statusMessage) : `IPX Error (${statusCode})`;
|
|
524
515
|
if (process.env.NODE_ENV !== "production" && statusCode === 500) {
|
|
525
516
|
console.error(err);
|
|
526
517
|
}
|
|
@@ -545,14 +536,6 @@ function createIPXMiddleware(ipx) {
|
|
|
545
536
|
};
|
|
546
537
|
}
|
|
547
538
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
await listhen.listen(middleware, {
|
|
552
|
-
clipboard: false
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
main().catch((err) => {
|
|
556
|
-
consola__default['default'].error(err);
|
|
557
|
-
process.exit(1);
|
|
558
|
-
});
|
|
539
|
+
exports.createIPX = createIPX;
|
|
540
|
+
exports.createIPXMiddleware = createIPXMiddleware;
|
|
541
|
+
exports.handleRequest = handleRequest;
|