ipx 0.9.10 → 1.0.0-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/LICENSE +1 -1
- package/dist/cli.cjs +6 -10
- package/dist/cli.mjs +6 -6
- package/dist/index.cjs +4 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.mjs +4 -4
- package/dist/{chunks/middleware.mjs → shared/ipx.d659cb77.mjs} +79 -82
- package/dist/{chunks/middleware.cjs → shared/ipx.dd1c1144.cjs} +87 -99
- package/package.json +36 -33
package/LICENSE
CHANGED
package/dist/cli.cjs
CHANGED
|
@@ -2,23 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
const consola = require('consola');
|
|
4
4
|
const listhen = require('listhen');
|
|
5
|
-
const middleware = require('./
|
|
5
|
+
const middleware = require('./shared/ipx.dd1c1144.cjs');
|
|
6
6
|
require('defu');
|
|
7
7
|
require('image-meta');
|
|
8
8
|
require('ufo');
|
|
9
|
-
require('fs');
|
|
9
|
+
require('node:fs');
|
|
10
10
|
require('pathe');
|
|
11
|
-
require('http');
|
|
12
|
-
require('https');
|
|
11
|
+
require('node:http');
|
|
12
|
+
require('node:https');
|
|
13
13
|
require('ohmyfetch');
|
|
14
14
|
require('destr');
|
|
15
15
|
require('etag');
|
|
16
16
|
require('xss');
|
|
17
17
|
|
|
18
|
-
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
|
|
19
|
-
|
|
20
|
-
const consola__default = /*#__PURE__*/_interopDefaultLegacy(consola);
|
|
21
|
-
|
|
22
18
|
async function main() {
|
|
23
19
|
const ipx = middleware.createIPX({});
|
|
24
20
|
const middleware$1 = middleware.createIPXMiddleware(ipx);
|
|
@@ -26,7 +22,7 @@ async function main() {
|
|
|
26
22
|
clipboard: false
|
|
27
23
|
});
|
|
28
24
|
}
|
|
29
|
-
main().catch((
|
|
30
|
-
|
|
25
|
+
main().catch((error) => {
|
|
26
|
+
consola.error(error);
|
|
31
27
|
process.exit(1);
|
|
32
28
|
});
|
package/dist/cli.mjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import consola from 'consola';
|
|
2
2
|
import { listen } from 'listhen';
|
|
3
|
-
import { c as createIPX, a as createIPXMiddleware } from './
|
|
3
|
+
import { c as createIPX, a as createIPXMiddleware } from './shared/ipx.d659cb77.mjs';
|
|
4
4
|
import 'defu';
|
|
5
5
|
import 'image-meta';
|
|
6
6
|
import 'ufo';
|
|
7
|
-
import 'fs';
|
|
7
|
+
import 'node:fs';
|
|
8
8
|
import 'pathe';
|
|
9
|
-
import 'http';
|
|
10
|
-
import 'https';
|
|
9
|
+
import 'node:http';
|
|
10
|
+
import 'node:https';
|
|
11
11
|
import 'ohmyfetch';
|
|
12
12
|
import 'destr';
|
|
13
13
|
import 'etag';
|
|
@@ -20,7 +20,7 @@ async function main() {
|
|
|
20
20
|
clipboard: false
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
|
-
main().catch((
|
|
24
|
-
consola.error(
|
|
23
|
+
main().catch((error) => {
|
|
24
|
+
consola.error(error);
|
|
25
25
|
process.exit(1);
|
|
26
26
|
});
|
package/dist/index.cjs
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const middleware = require('./chunks/middleware.cjs');
|
|
3
|
+
const middleware = require('./shared/ipx.dd1c1144.cjs');
|
|
6
4
|
require('defu');
|
|
7
5
|
require('image-meta');
|
|
8
6
|
require('ufo');
|
|
9
|
-
require('fs');
|
|
7
|
+
require('node:fs');
|
|
10
8
|
require('pathe');
|
|
11
|
-
require('http');
|
|
12
|
-
require('https');
|
|
9
|
+
require('node:http');
|
|
10
|
+
require('node:https');
|
|
13
11
|
require('ohmyfetch');
|
|
14
12
|
require('destr');
|
|
15
13
|
require('etag');
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { IncomingMessage, ServerResponse } from 'http';
|
|
1
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
2
|
|
|
3
3
|
interface SourceData {
|
|
4
4
|
mtime?: Date;
|
|
5
5
|
maxAge?: number;
|
|
6
6
|
getData: () => Promise<Buffer>;
|
|
7
7
|
}
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
type Source = (source: string, requestOptions?: any) => Promise<SourceData>;
|
|
9
|
+
type SourceFactory<T = Record<string, any>> = (options: T) => Source;
|
|
10
10
|
|
|
11
11
|
interface ImageMeta {
|
|
12
12
|
width: number;
|
|
@@ -17,7 +17,7 @@ interface ImageMeta {
|
|
|
17
17
|
interface IPXCTX {
|
|
18
18
|
sources: Record<string, Source>;
|
|
19
19
|
}
|
|
20
|
-
|
|
20
|
+
type IPX = (id: string, modifiers?: Record<string, string>, requestOptions?: any) => {
|
|
21
21
|
src: () => Promise<SourceData>;
|
|
22
22
|
data: () => Promise<{
|
|
23
23
|
data: Buffer;
|
|
@@ -48,7 +48,7 @@ interface IPXHResponse {
|
|
|
48
48
|
headers: Record<string, string>;
|
|
49
49
|
body: any;
|
|
50
50
|
}
|
|
51
|
-
declare function handleRequest(
|
|
52
|
-
declare function createIPXMiddleware(ipx: IPX): (
|
|
51
|
+
declare function handleRequest(request: IPXHRequest, ipx: IPX): Promise<IPXHResponse>;
|
|
52
|
+
declare function createIPXMiddleware(ipx: IPX): (request: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
53
53
|
|
|
54
54
|
export { IPX, IPXCTX, IPXHRequest, IPXHResponse, IPXOptions, ImageMeta, Source, SourceData, SourceFactory, createIPX, createIPXMiddleware, handleRequest };
|
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
export { c as createIPX, a as createIPXMiddleware, h as handleRequest } from './
|
|
1
|
+
export { c as createIPX, a as createIPXMiddleware, h as handleRequest } from './shared/ipx.d659cb77.mjs';
|
|
2
2
|
import 'defu';
|
|
3
3
|
import 'image-meta';
|
|
4
4
|
import 'ufo';
|
|
5
|
-
import 'fs';
|
|
5
|
+
import 'node:fs';
|
|
6
6
|
import 'pathe';
|
|
7
|
-
import 'http';
|
|
8
|
-
import 'https';
|
|
7
|
+
import 'node:http';
|
|
8
|
+
import 'node:https';
|
|
9
9
|
import 'ohmyfetch';
|
|
10
10
|
import 'destr';
|
|
11
11
|
import 'etag';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import defu from 'defu';
|
|
2
2
|
import { imageMeta } from 'image-meta';
|
|
3
3
|
import { withLeadingSlash, hasProtocol, joinURL, decode } from 'ufo';
|
|
4
|
-
import { promises } from 'fs';
|
|
4
|
+
import { promises } from 'node:fs';
|
|
5
5
|
import { resolve, join, parse } from 'pathe';
|
|
6
|
-
import http from 'http';
|
|
7
|
-
import https from 'https';
|
|
6
|
+
import http from 'node:http';
|
|
7
|
+
import https from 'node:https';
|
|
8
8
|
import { fetch } from 'ohmyfetch';
|
|
9
9
|
import destr from 'destr';
|
|
10
10
|
import getEtag from 'etag';
|
|
@@ -49,23 +49,23 @@ const Handlers = {
|
|
|
49
49
|
function getEnv(name, defaultValue) {
|
|
50
50
|
return destr(process.env[name]) ?? defaultValue;
|
|
51
51
|
}
|
|
52
|
-
function cachedPromise(
|
|
52
|
+
function cachedPromise(function_) {
|
|
53
53
|
let p;
|
|
54
|
-
return (...
|
|
54
|
+
return (...arguments_) => {
|
|
55
55
|
if (p) {
|
|
56
56
|
return p;
|
|
57
57
|
}
|
|
58
|
-
p = Promise.resolve(
|
|
58
|
+
p = Promise.resolve(function_(...arguments_));
|
|
59
59
|
return p;
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
62
|
class IPXError extends Error {
|
|
63
63
|
}
|
|
64
64
|
function createError(statusMessage, statusCode, trace) {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return
|
|
65
|
+
const error = new IPXError(statusMessage + (trace ? ` (${trace})` : ""));
|
|
66
|
+
error.statusMessage = "IPX: " + statusMessage;
|
|
67
|
+
error.statusCode = statusCode;
|
|
68
|
+
return error;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
const createFilesystemSource = (options) => {
|
|
@@ -78,12 +78,9 @@ const createFilesystemSource = (options) => {
|
|
|
78
78
|
let stats;
|
|
79
79
|
try {
|
|
80
80
|
stats = await promises.stat(fsPath);
|
|
81
|
-
} catch (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
} else {
|
|
85
|
-
throw createError("File access error " + err.code, 403, fsPath);
|
|
86
|
-
}
|
|
81
|
+
} catch (error_) {
|
|
82
|
+
const error = error_.code === "ENOENT" ? createError("File not found", 404, fsPath) : createError("File access error " + error_.code, 403, fsPath);
|
|
83
|
+
throw error;
|
|
87
84
|
}
|
|
88
85
|
if (!stats.isFile()) {
|
|
89
86
|
throw createError("Path should be a file", 400, fsPath);
|
|
@@ -100,12 +97,13 @@ function isValidPath(fp) {
|
|
|
100
97
|
if (isWindows) {
|
|
101
98
|
fp = fp.slice(parse(fp).root.length);
|
|
102
99
|
}
|
|
103
|
-
if (/[
|
|
100
|
+
if (/["*:<>?|]/.test(fp)) {
|
|
104
101
|
return false;
|
|
105
102
|
}
|
|
106
103
|
return true;
|
|
107
104
|
}
|
|
108
105
|
|
|
106
|
+
const HTTP_RE = /^https?:\/\//;
|
|
109
107
|
const createHTTPSource = (options) => {
|
|
110
108
|
const httpsAgent = new https.Agent({ keepAlive: true });
|
|
111
109
|
const httpAgent = new http.Agent({ keepAlive: true });
|
|
@@ -113,18 +111,18 @@ const createHTTPSource = (options) => {
|
|
|
113
111
|
if (typeof _domains === "string") {
|
|
114
112
|
_domains = _domains.split(",").map((s) => s.trim());
|
|
115
113
|
}
|
|
116
|
-
const domains = _domains.map((d) => {
|
|
117
|
-
if (!
|
|
114
|
+
const domains = new Set(_domains.map((d) => {
|
|
115
|
+
if (!HTTP_RE.test(d)) {
|
|
118
116
|
d = "http://" + d;
|
|
119
117
|
}
|
|
120
118
|
return new URL(d).hostname;
|
|
121
|
-
}).filter(Boolean);
|
|
122
|
-
return async (id,
|
|
119
|
+
}).filter(Boolean));
|
|
120
|
+
return async (id, requestOptions) => {
|
|
123
121
|
const hostname = new URL(id).hostname;
|
|
124
122
|
if (!hostname) {
|
|
125
123
|
throw createError("Hostname is missing", 403, id);
|
|
126
124
|
}
|
|
127
|
-
if (!
|
|
125
|
+
if (!requestOptions?.bypassDomain && !domains.has(hostname)) {
|
|
128
126
|
throw createError("Forbidden host", 403, hostname);
|
|
129
127
|
}
|
|
130
128
|
const response = await fetch(id, {
|
|
@@ -139,7 +137,7 @@ const createHTTPSource = (options) => {
|
|
|
139
137
|
if (_cacheControl) {
|
|
140
138
|
const m = _cacheControl.match(/max-age=(\d+)/);
|
|
141
139
|
if (m && m[1]) {
|
|
142
|
-
maxAge = parseInt(m[1]);
|
|
140
|
+
maxAge = Number.parseInt(m[1]);
|
|
143
141
|
}
|
|
144
142
|
}
|
|
145
143
|
let mtime;
|
|
@@ -155,19 +153,19 @@ const createHTTPSource = (options) => {
|
|
|
155
153
|
};
|
|
156
154
|
};
|
|
157
155
|
|
|
158
|
-
function VArg(
|
|
159
|
-
return destr(
|
|
156
|
+
function VArg(argument) {
|
|
157
|
+
return destr(argument);
|
|
160
158
|
}
|
|
161
|
-
function parseArgs(
|
|
162
|
-
const vargs =
|
|
163
|
-
return mappers.map((v,
|
|
159
|
+
function parseArgs(arguments_, mappers) {
|
|
160
|
+
const vargs = arguments_.split("_");
|
|
161
|
+
return mappers.map((v, index) => v(vargs[index]));
|
|
164
162
|
}
|
|
165
163
|
function getHandler(key) {
|
|
166
164
|
return Handlers[key];
|
|
167
165
|
}
|
|
168
|
-
function applyHandler(
|
|
169
|
-
const
|
|
170
|
-
return handler.apply(
|
|
166
|
+
function applyHandler(context, pipe, handler, argumentsString) {
|
|
167
|
+
const arguments_ = handler.args ? parseArgs(argumentsString, handler.args) : [];
|
|
168
|
+
return handler.apply(context, pipe, ...arguments_);
|
|
171
169
|
}
|
|
172
170
|
function clampDimensionsPreservingAspectRatio(sourceDimensions, desiredDimensions) {
|
|
173
171
|
const desiredAspectRatio = desiredDimensions.width / desiredDimensions.height;
|
|
@@ -204,8 +202,8 @@ const position = {
|
|
|
204
202
|
context.position = position2;
|
|
205
203
|
}
|
|
206
204
|
};
|
|
207
|
-
const HEX_RE = /^([
|
|
208
|
-
const SHORTHEX_RE = /^([
|
|
205
|
+
const HEX_RE = /^([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i;
|
|
206
|
+
const SHORTHEX_RE = /^([\da-f])([\da-f])([\da-f])$/i;
|
|
209
207
|
const background = {
|
|
210
208
|
args: [VArg],
|
|
211
209
|
order: -1,
|
|
@@ -226,19 +224,19 @@ const enlarge = {
|
|
|
226
224
|
const width = {
|
|
227
225
|
args: [VArg],
|
|
228
226
|
apply: (context, pipe, width2) => {
|
|
229
|
-
return pipe.resize(width2,
|
|
227
|
+
return pipe.resize(width2, void 0, { withoutEnlargement: !context.enlarge });
|
|
230
228
|
}
|
|
231
229
|
};
|
|
232
230
|
const height = {
|
|
233
231
|
args: [VArg],
|
|
234
232
|
apply: (context, pipe, height2) => {
|
|
235
|
-
return pipe.resize(
|
|
233
|
+
return pipe.resize(void 0, height2, { withoutEnlargement: !context.enlarge });
|
|
236
234
|
}
|
|
237
235
|
};
|
|
238
236
|
const resize = {
|
|
239
237
|
args: [VArg, VArg, VArg],
|
|
240
238
|
apply: (context, pipe, size) => {
|
|
241
|
-
let [width2, height2] = String(size).split("x").map(
|
|
239
|
+
let [width2, height2] = String(size).split("x").map(Number);
|
|
242
240
|
if (!width2) {
|
|
243
241
|
return;
|
|
244
242
|
}
|
|
@@ -387,7 +385,7 @@ const h = height;
|
|
|
387
385
|
const s = resize;
|
|
388
386
|
const pos = position;
|
|
389
387
|
|
|
390
|
-
const SUPPORTED_FORMATS = ["jpeg", "png", "webp", "avif", "tiff", "gif"];
|
|
388
|
+
const SUPPORTED_FORMATS = /* @__PURE__ */ new Set(["jpeg", "png", "webp", "avif", "tiff", "gif"]);
|
|
391
389
|
function createIPX(userOptions) {
|
|
392
390
|
const defaults = {
|
|
393
391
|
dir: getEnv("IPX_DIR", "."),
|
|
@@ -399,42 +397,42 @@ function createIPX(userOptions) {
|
|
|
399
397
|
};
|
|
400
398
|
const options = defu(userOptions, defaults);
|
|
401
399
|
options.alias = Object.fromEntries(Object.entries(options.alias).map((e) => [withLeadingSlash(e[0]), e[1]]));
|
|
402
|
-
const
|
|
400
|
+
const context = {
|
|
403
401
|
sources: {}
|
|
404
402
|
};
|
|
405
403
|
if (options.dir) {
|
|
406
|
-
|
|
404
|
+
context.sources.filesystem = createFilesystemSource({
|
|
407
405
|
dir: options.dir,
|
|
408
406
|
maxAge: options.maxAge
|
|
409
407
|
});
|
|
410
408
|
}
|
|
411
409
|
if (options.domains) {
|
|
412
|
-
|
|
410
|
+
context.sources.http = createHTTPSource({
|
|
413
411
|
domains: options.domains,
|
|
414
412
|
fetchOptions: options.fetchOptions,
|
|
415
413
|
maxAge: options.maxAge
|
|
416
414
|
});
|
|
417
415
|
}
|
|
418
|
-
return function ipx(id, modifiers = {},
|
|
416
|
+
return function ipx(id, modifiers = {}, requestOptions = {}) {
|
|
419
417
|
if (!id) {
|
|
420
418
|
throw createError("resource id is missing", 400);
|
|
421
419
|
}
|
|
422
420
|
id = hasProtocol(id) ? id : withLeadingSlash(id);
|
|
423
421
|
for (const base in options.alias) {
|
|
424
422
|
if (id.startsWith(base)) {
|
|
425
|
-
id = joinURL(options.alias[base], id.
|
|
423
|
+
id = joinURL(options.alias[base], id.slice(base.length));
|
|
426
424
|
}
|
|
427
425
|
}
|
|
428
|
-
const
|
|
426
|
+
const getSource = cachedPromise(() => {
|
|
429
427
|
const source = hasProtocol(id) ? "http" : "filesystem";
|
|
430
|
-
if (!
|
|
428
|
+
if (!context.sources[source]) {
|
|
431
429
|
throw createError("Unknown source", 400, source);
|
|
432
430
|
}
|
|
433
|
-
return
|
|
431
|
+
return context.sources[source](id, requestOptions);
|
|
434
432
|
});
|
|
435
433
|
const getData = cachedPromise(async () => {
|
|
436
|
-
const
|
|
437
|
-
const data = await
|
|
434
|
+
const source = await getSource();
|
|
435
|
+
const data = await source.getData();
|
|
438
436
|
const meta = imageMeta(data);
|
|
439
437
|
const mFormat = modifiers.f || modifiers.format;
|
|
440
438
|
let format = mFormat || meta.type;
|
|
@@ -452,18 +450,18 @@ function createIPX(userOptions) {
|
|
|
452
450
|
const Sharp = await import('sharp').then((r) => r.default || r);
|
|
453
451
|
let sharp = Sharp(data, { animated });
|
|
454
452
|
Object.assign(sharp.options, options.sharp);
|
|
455
|
-
const handlers = Object.entries(modifiers).map(([name,
|
|
453
|
+
const handlers = Object.entries(modifiers).map(([name, arguments_]) => ({ handler: getHandler(name), name, args: arguments_ })).filter((h) => h.handler).sort((a, b) => {
|
|
456
454
|
const aKey = (a.handler.order || a.name || "").toString();
|
|
457
455
|
const bKey = (b.handler.order || b.name || "").toString();
|
|
458
456
|
return aKey.localeCompare(bKey);
|
|
459
457
|
});
|
|
460
|
-
const
|
|
458
|
+
const handlerContext = { meta };
|
|
461
459
|
for (const h of handlers) {
|
|
462
|
-
sharp = applyHandler(
|
|
460
|
+
sharp = applyHandler(handlerContext, sharp, h.handler, h.args) || sharp;
|
|
463
461
|
}
|
|
464
|
-
if (SUPPORTED_FORMATS.
|
|
462
|
+
if (SUPPORTED_FORMATS.has(format)) {
|
|
465
463
|
sharp = sharp.toFormat(format, {
|
|
466
|
-
quality:
|
|
464
|
+
quality: handlerContext.quality,
|
|
467
465
|
progressive: format === "jpeg"
|
|
468
466
|
});
|
|
469
467
|
}
|
|
@@ -475,81 +473,80 @@ function createIPX(userOptions) {
|
|
|
475
473
|
};
|
|
476
474
|
});
|
|
477
475
|
return {
|
|
478
|
-
src:
|
|
476
|
+
src: getSource,
|
|
479
477
|
data: getData
|
|
480
478
|
};
|
|
481
479
|
};
|
|
482
480
|
}
|
|
483
481
|
|
|
484
|
-
const MODIFIER_SEP = /[
|
|
485
|
-
const MODIFIER_VAL_SEP = /[_
|
|
486
|
-
async function _handleRequest(
|
|
482
|
+
const MODIFIER_SEP = /[&,]/g;
|
|
483
|
+
const MODIFIER_VAL_SEP = /[:=_]/g;
|
|
484
|
+
async function _handleRequest(request, ipx) {
|
|
487
485
|
const res = {
|
|
488
486
|
statusCode: 200,
|
|
489
487
|
statusMessage: "",
|
|
490
488
|
headers: {},
|
|
491
489
|
body: ""
|
|
492
490
|
};
|
|
493
|
-
const [
|
|
491
|
+
const [modifiersString = "", ...idSegments] = request.url.slice(1).split("/");
|
|
494
492
|
const id = safeString(decode(idSegments.join("/")));
|
|
495
|
-
if (!
|
|
496
|
-
throw createError("Modifiers are missing", 400,
|
|
493
|
+
if (!modifiersString) {
|
|
494
|
+
throw createError("Modifiers are missing", 400, request.url);
|
|
497
495
|
}
|
|
498
496
|
if (!id || id === "/") {
|
|
499
|
-
throw createError("Resource id is missing", 400,
|
|
497
|
+
throw createError("Resource id is missing", 400, request.url);
|
|
500
498
|
}
|
|
501
499
|
const modifiers = /* @__PURE__ */ Object.create(null);
|
|
502
|
-
if (
|
|
503
|
-
for (const p of
|
|
500
|
+
if (modifiersString !== "_") {
|
|
501
|
+
for (const p of modifiersString.split(MODIFIER_SEP)) {
|
|
504
502
|
const [key, value = ""] = p.split(MODIFIER_VAL_SEP);
|
|
505
503
|
modifiers[safeString(key)] = safeString(decode(value));
|
|
506
504
|
}
|
|
507
505
|
}
|
|
508
|
-
const img = ipx(id, modifiers,
|
|
509
|
-
const
|
|
510
|
-
if (
|
|
511
|
-
if (
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
return res;
|
|
515
|
-
}
|
|
506
|
+
const img = ipx(id, modifiers, request.options);
|
|
507
|
+
const source = await img.src();
|
|
508
|
+
if (source.mtime) {
|
|
509
|
+
if (request.headers["if-modified-since"] && new Date(request.headers["if-modified-since"]) >= source.mtime) {
|
|
510
|
+
res.statusCode = 304;
|
|
511
|
+
return res;
|
|
516
512
|
}
|
|
517
|
-
res.headers["Last-Modified"] =
|
|
513
|
+
res.headers["Last-Modified"] = source.mtime.toUTCString();
|
|
518
514
|
}
|
|
519
|
-
if (typeof
|
|
520
|
-
res.headers["Cache-Control"] = `max-age=${+
|
|
515
|
+
if (typeof source.maxAge === "number") {
|
|
516
|
+
res.headers["Cache-Control"] = `max-age=${+source.maxAge}, public, s-maxage=${+source.maxAge}`;
|
|
521
517
|
}
|
|
522
518
|
const { data, format } = await img.data();
|
|
523
519
|
const etag = getEtag(data);
|
|
524
520
|
res.headers.ETag = etag;
|
|
525
|
-
if (etag &&
|
|
521
|
+
if (etag && request.headers["if-none-match"] === etag) {
|
|
526
522
|
res.statusCode = 304;
|
|
527
523
|
return res;
|
|
528
524
|
}
|
|
529
525
|
if (format) {
|
|
530
526
|
res.headers["Content-Type"] = `image/${format}`;
|
|
531
527
|
}
|
|
528
|
+
res.headers["Content-Security-Policy"] = "default-src 'none'";
|
|
532
529
|
res.body = data;
|
|
533
530
|
return sanetizeReponse(res);
|
|
534
531
|
}
|
|
535
|
-
function handleRequest(
|
|
536
|
-
return _handleRequest(
|
|
537
|
-
const statusCode = parseInt(
|
|
538
|
-
const statusMessage =
|
|
532
|
+
function handleRequest(request, ipx) {
|
|
533
|
+
return _handleRequest(request, ipx).catch((error) => {
|
|
534
|
+
const statusCode = Number.parseInt(error.statusCode) || 500;
|
|
535
|
+
const statusMessage = error.statusMessage ? error.statusMessage : `IPX Error (${statusCode})`;
|
|
539
536
|
if (process.env.NODE_ENV !== "production" && statusCode === 500) {
|
|
540
|
-
console.error(
|
|
537
|
+
console.error(error);
|
|
541
538
|
}
|
|
542
539
|
return sanetizeReponse({
|
|
543
540
|
statusCode,
|
|
544
541
|
statusMessage,
|
|
545
|
-
body: "IPX Error: " +
|
|
542
|
+
body: "IPX Error: " + error,
|
|
546
543
|
headers: {}
|
|
547
544
|
});
|
|
548
545
|
});
|
|
549
546
|
}
|
|
550
547
|
function createIPXMiddleware(ipx) {
|
|
551
|
-
return function IPXMiddleware(
|
|
552
|
-
return handleRequest({ url:
|
|
548
|
+
return function IPXMiddleware(request, res) {
|
|
549
|
+
return handleRequest({ url: request.url, headers: request.headers }, ipx).then((_res) => {
|
|
553
550
|
res.statusCode = _res.statusCode;
|
|
554
551
|
res.statusMessage = _res.statusMessage;
|
|
555
552
|
for (const name in _res.headers) {
|
|
@@ -3,24 +3,15 @@
|
|
|
3
3
|
const defu = require('defu');
|
|
4
4
|
const imageMeta = require('image-meta');
|
|
5
5
|
const ufo = require('ufo');
|
|
6
|
-
const
|
|
6
|
+
const node_fs = require('node:fs');
|
|
7
7
|
const pathe = require('pathe');
|
|
8
|
-
const http = require('http');
|
|
9
|
-
const https = require('https');
|
|
8
|
+
const http = require('node:http');
|
|
9
|
+
const https = require('node:https');
|
|
10
10
|
const ohmyfetch = require('ohmyfetch');
|
|
11
11
|
const destr = require('destr');
|
|
12
12
|
const getEtag = require('etag');
|
|
13
13
|
const xss = require('xss');
|
|
14
14
|
|
|
15
|
-
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
|
|
16
|
-
|
|
17
|
-
const defu__default = /*#__PURE__*/_interopDefaultLegacy(defu);
|
|
18
|
-
const http__default = /*#__PURE__*/_interopDefaultLegacy(http);
|
|
19
|
-
const https__default = /*#__PURE__*/_interopDefaultLegacy(https);
|
|
20
|
-
const destr__default = /*#__PURE__*/_interopDefaultLegacy(destr);
|
|
21
|
-
const getEtag__default = /*#__PURE__*/_interopDefaultLegacy(getEtag);
|
|
22
|
-
const xss__default = /*#__PURE__*/_interopDefaultLegacy(xss);
|
|
23
|
-
|
|
24
15
|
const Handlers = {
|
|
25
16
|
__proto__: null,
|
|
26
17
|
get quality () { return quality; },
|
|
@@ -58,25 +49,25 @@ const Handlers = {
|
|
|
58
49
|
};
|
|
59
50
|
|
|
60
51
|
function getEnv(name, defaultValue) {
|
|
61
|
-
return
|
|
52
|
+
return destr(process.env[name]) ?? defaultValue;
|
|
62
53
|
}
|
|
63
|
-
function cachedPromise(
|
|
54
|
+
function cachedPromise(function_) {
|
|
64
55
|
let p;
|
|
65
|
-
return (...
|
|
56
|
+
return (...arguments_) => {
|
|
66
57
|
if (p) {
|
|
67
58
|
return p;
|
|
68
59
|
}
|
|
69
|
-
p = Promise.resolve(
|
|
60
|
+
p = Promise.resolve(function_(...arguments_));
|
|
70
61
|
return p;
|
|
71
62
|
};
|
|
72
63
|
}
|
|
73
64
|
class IPXError extends Error {
|
|
74
65
|
}
|
|
75
66
|
function createError(statusMessage, statusCode, trace) {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return
|
|
67
|
+
const error = new IPXError(statusMessage + (trace ? ` (${trace})` : ""));
|
|
68
|
+
error.statusMessage = "IPX: " + statusMessage;
|
|
69
|
+
error.statusCode = statusCode;
|
|
70
|
+
return error;
|
|
80
71
|
}
|
|
81
72
|
|
|
82
73
|
const createFilesystemSource = (options) => {
|
|
@@ -88,13 +79,10 @@ const createFilesystemSource = (options) => {
|
|
|
88
79
|
}
|
|
89
80
|
let stats;
|
|
90
81
|
try {
|
|
91
|
-
stats = await
|
|
92
|
-
} catch (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
} else {
|
|
96
|
-
throw createError("File access error " + err.code, 403, fsPath);
|
|
97
|
-
}
|
|
82
|
+
stats = await node_fs.promises.stat(fsPath);
|
|
83
|
+
} catch (error_) {
|
|
84
|
+
const error = error_.code === "ENOENT" ? createError("File not found", 404, fsPath) : createError("File access error " + error_.code, 403, fsPath);
|
|
85
|
+
throw error;
|
|
98
86
|
}
|
|
99
87
|
if (!stats.isFile()) {
|
|
100
88
|
throw createError("Path should be a file", 400, fsPath);
|
|
@@ -102,7 +90,7 @@ const createFilesystemSource = (options) => {
|
|
|
102
90
|
return {
|
|
103
91
|
mtime: stats.mtime,
|
|
104
92
|
maxAge: options.maxAge,
|
|
105
|
-
getData: cachedPromise(() =>
|
|
93
|
+
getData: cachedPromise(() => node_fs.promises.readFile(fsPath))
|
|
106
94
|
};
|
|
107
95
|
};
|
|
108
96
|
};
|
|
@@ -111,31 +99,32 @@ function isValidPath(fp) {
|
|
|
111
99
|
if (isWindows) {
|
|
112
100
|
fp = fp.slice(pathe.parse(fp).root.length);
|
|
113
101
|
}
|
|
114
|
-
if (/[
|
|
102
|
+
if (/["*:<>?|]/.test(fp)) {
|
|
115
103
|
return false;
|
|
116
104
|
}
|
|
117
105
|
return true;
|
|
118
106
|
}
|
|
119
107
|
|
|
108
|
+
const HTTP_RE = /^https?:\/\//;
|
|
120
109
|
const createHTTPSource = (options) => {
|
|
121
|
-
const httpsAgent = new
|
|
122
|
-
const httpAgent = new
|
|
110
|
+
const httpsAgent = new https.Agent({ keepAlive: true });
|
|
111
|
+
const httpAgent = new http.Agent({ keepAlive: true });
|
|
123
112
|
let _domains = options.domains || [];
|
|
124
113
|
if (typeof _domains === "string") {
|
|
125
114
|
_domains = _domains.split(",").map((s) => s.trim());
|
|
126
115
|
}
|
|
127
|
-
const domains = _domains.map((d) => {
|
|
128
|
-
if (!
|
|
116
|
+
const domains = new Set(_domains.map((d) => {
|
|
117
|
+
if (!HTTP_RE.test(d)) {
|
|
129
118
|
d = "http://" + d;
|
|
130
119
|
}
|
|
131
120
|
return new URL(d).hostname;
|
|
132
|
-
}).filter(Boolean);
|
|
133
|
-
return async (id,
|
|
121
|
+
}).filter(Boolean));
|
|
122
|
+
return async (id, requestOptions) => {
|
|
134
123
|
const hostname = new URL(id).hostname;
|
|
135
124
|
if (!hostname) {
|
|
136
125
|
throw createError("Hostname is missing", 403, id);
|
|
137
126
|
}
|
|
138
|
-
if (!
|
|
127
|
+
if (!requestOptions?.bypassDomain && !domains.has(hostname)) {
|
|
139
128
|
throw createError("Forbidden host", 403, hostname);
|
|
140
129
|
}
|
|
141
130
|
const response = await ohmyfetch.fetch(id, {
|
|
@@ -150,7 +139,7 @@ const createHTTPSource = (options) => {
|
|
|
150
139
|
if (_cacheControl) {
|
|
151
140
|
const m = _cacheControl.match(/max-age=(\d+)/);
|
|
152
141
|
if (m && m[1]) {
|
|
153
|
-
maxAge = parseInt(m[1]);
|
|
142
|
+
maxAge = Number.parseInt(m[1]);
|
|
154
143
|
}
|
|
155
144
|
}
|
|
156
145
|
let mtime;
|
|
@@ -166,19 +155,19 @@ const createHTTPSource = (options) => {
|
|
|
166
155
|
};
|
|
167
156
|
};
|
|
168
157
|
|
|
169
|
-
function VArg(
|
|
170
|
-
return
|
|
158
|
+
function VArg(argument) {
|
|
159
|
+
return destr(argument);
|
|
171
160
|
}
|
|
172
|
-
function parseArgs(
|
|
173
|
-
const vargs =
|
|
174
|
-
return mappers.map((v,
|
|
161
|
+
function parseArgs(arguments_, mappers) {
|
|
162
|
+
const vargs = arguments_.split("_");
|
|
163
|
+
return mappers.map((v, index) => v(vargs[index]));
|
|
175
164
|
}
|
|
176
165
|
function getHandler(key) {
|
|
177
166
|
return Handlers[key];
|
|
178
167
|
}
|
|
179
|
-
function applyHandler(
|
|
180
|
-
const
|
|
181
|
-
return handler.apply(
|
|
168
|
+
function applyHandler(context, pipe, handler, argumentsString) {
|
|
169
|
+
const arguments_ = handler.args ? parseArgs(argumentsString, handler.args) : [];
|
|
170
|
+
return handler.apply(context, pipe, ...arguments_);
|
|
182
171
|
}
|
|
183
172
|
function clampDimensionsPreservingAspectRatio(sourceDimensions, desiredDimensions) {
|
|
184
173
|
const desiredAspectRatio = desiredDimensions.width / desiredDimensions.height;
|
|
@@ -215,8 +204,8 @@ const position = {
|
|
|
215
204
|
context.position = position2;
|
|
216
205
|
}
|
|
217
206
|
};
|
|
218
|
-
const HEX_RE = /^([
|
|
219
|
-
const SHORTHEX_RE = /^([
|
|
207
|
+
const HEX_RE = /^([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i;
|
|
208
|
+
const SHORTHEX_RE = /^([\da-f])([\da-f])([\da-f])$/i;
|
|
220
209
|
const background = {
|
|
221
210
|
args: [VArg],
|
|
222
211
|
order: -1,
|
|
@@ -237,19 +226,19 @@ const enlarge = {
|
|
|
237
226
|
const width = {
|
|
238
227
|
args: [VArg],
|
|
239
228
|
apply: (context, pipe, width2) => {
|
|
240
|
-
return pipe.resize(width2,
|
|
229
|
+
return pipe.resize(width2, void 0, { withoutEnlargement: !context.enlarge });
|
|
241
230
|
}
|
|
242
231
|
};
|
|
243
232
|
const height = {
|
|
244
233
|
args: [VArg],
|
|
245
234
|
apply: (context, pipe, height2) => {
|
|
246
|
-
return pipe.resize(
|
|
235
|
+
return pipe.resize(void 0, height2, { withoutEnlargement: !context.enlarge });
|
|
247
236
|
}
|
|
248
237
|
};
|
|
249
238
|
const resize = {
|
|
250
239
|
args: [VArg, VArg, VArg],
|
|
251
240
|
apply: (context, pipe, size) => {
|
|
252
|
-
let [width2, height2] = String(size).split("x").map(
|
|
241
|
+
let [width2, height2] = String(size).split("x").map(Number);
|
|
253
242
|
if (!width2) {
|
|
254
243
|
return;
|
|
255
244
|
}
|
|
@@ -398,7 +387,7 @@ const h = height;
|
|
|
398
387
|
const s = resize;
|
|
399
388
|
const pos = position;
|
|
400
389
|
|
|
401
|
-
const SUPPORTED_FORMATS = ["jpeg", "png", "webp", "avif", "tiff", "gif"];
|
|
390
|
+
const SUPPORTED_FORMATS = /* @__PURE__ */ new Set(["jpeg", "png", "webp", "avif", "tiff", "gif"]);
|
|
402
391
|
function createIPX(userOptions) {
|
|
403
392
|
const defaults = {
|
|
404
393
|
dir: getEnv("IPX_DIR", "."),
|
|
@@ -408,44 +397,44 @@ function createIPX(userOptions) {
|
|
|
408
397
|
maxAge: getEnv("IPX_MAX_AGE", 300),
|
|
409
398
|
sharp: {}
|
|
410
399
|
};
|
|
411
|
-
const options =
|
|
400
|
+
const options = defu(userOptions, defaults);
|
|
412
401
|
options.alias = Object.fromEntries(Object.entries(options.alias).map((e) => [ufo.withLeadingSlash(e[0]), e[1]]));
|
|
413
|
-
const
|
|
402
|
+
const context = {
|
|
414
403
|
sources: {}
|
|
415
404
|
};
|
|
416
405
|
if (options.dir) {
|
|
417
|
-
|
|
406
|
+
context.sources.filesystem = createFilesystemSource({
|
|
418
407
|
dir: options.dir,
|
|
419
408
|
maxAge: options.maxAge
|
|
420
409
|
});
|
|
421
410
|
}
|
|
422
411
|
if (options.domains) {
|
|
423
|
-
|
|
412
|
+
context.sources.http = createHTTPSource({
|
|
424
413
|
domains: options.domains,
|
|
425
414
|
fetchOptions: options.fetchOptions,
|
|
426
415
|
maxAge: options.maxAge
|
|
427
416
|
});
|
|
428
417
|
}
|
|
429
|
-
return function ipx(id, modifiers = {},
|
|
418
|
+
return function ipx(id, modifiers = {}, requestOptions = {}) {
|
|
430
419
|
if (!id) {
|
|
431
420
|
throw createError("resource id is missing", 400);
|
|
432
421
|
}
|
|
433
422
|
id = ufo.hasProtocol(id) ? id : ufo.withLeadingSlash(id);
|
|
434
423
|
for (const base in options.alias) {
|
|
435
424
|
if (id.startsWith(base)) {
|
|
436
|
-
id = ufo.joinURL(options.alias[base], id.
|
|
425
|
+
id = ufo.joinURL(options.alias[base], id.slice(base.length));
|
|
437
426
|
}
|
|
438
427
|
}
|
|
439
|
-
const
|
|
428
|
+
const getSource = cachedPromise(() => {
|
|
440
429
|
const source = ufo.hasProtocol(id) ? "http" : "filesystem";
|
|
441
|
-
if (!
|
|
430
|
+
if (!context.sources[source]) {
|
|
442
431
|
throw createError("Unknown source", 400, source);
|
|
443
432
|
}
|
|
444
|
-
return
|
|
433
|
+
return context.sources[source](id, requestOptions);
|
|
445
434
|
});
|
|
446
435
|
const getData = cachedPromise(async () => {
|
|
447
|
-
const
|
|
448
|
-
const data = await
|
|
436
|
+
const source = await getSource();
|
|
437
|
+
const data = await source.getData();
|
|
449
438
|
const meta = imageMeta.imageMeta(data);
|
|
450
439
|
const mFormat = modifiers.f || modifiers.format;
|
|
451
440
|
let format = mFormat || meta.type;
|
|
@@ -463,18 +452,18 @@ function createIPX(userOptions) {
|
|
|
463
452
|
const Sharp = await import('sharp').then((r) => r.default || r);
|
|
464
453
|
let sharp = Sharp(data, { animated });
|
|
465
454
|
Object.assign(sharp.options, options.sharp);
|
|
466
|
-
const handlers = Object.entries(modifiers).map(([name,
|
|
455
|
+
const handlers = Object.entries(modifiers).map(([name, arguments_]) => ({ handler: getHandler(name), name, args: arguments_ })).filter((h) => h.handler).sort((a, b) => {
|
|
467
456
|
const aKey = (a.handler.order || a.name || "").toString();
|
|
468
457
|
const bKey = (b.handler.order || b.name || "").toString();
|
|
469
458
|
return aKey.localeCompare(bKey);
|
|
470
459
|
});
|
|
471
|
-
const
|
|
460
|
+
const handlerContext = { meta };
|
|
472
461
|
for (const h of handlers) {
|
|
473
|
-
sharp = applyHandler(
|
|
462
|
+
sharp = applyHandler(handlerContext, sharp, h.handler, h.args) || sharp;
|
|
474
463
|
}
|
|
475
|
-
if (SUPPORTED_FORMATS.
|
|
464
|
+
if (SUPPORTED_FORMATS.has(format)) {
|
|
476
465
|
sharp = sharp.toFormat(format, {
|
|
477
|
-
quality:
|
|
466
|
+
quality: handlerContext.quality,
|
|
478
467
|
progressive: format === "jpeg"
|
|
479
468
|
});
|
|
480
469
|
}
|
|
@@ -486,81 +475,80 @@ function createIPX(userOptions) {
|
|
|
486
475
|
};
|
|
487
476
|
});
|
|
488
477
|
return {
|
|
489
|
-
src:
|
|
478
|
+
src: getSource,
|
|
490
479
|
data: getData
|
|
491
480
|
};
|
|
492
481
|
};
|
|
493
482
|
}
|
|
494
483
|
|
|
495
|
-
const MODIFIER_SEP = /[
|
|
496
|
-
const MODIFIER_VAL_SEP = /[_
|
|
497
|
-
async function _handleRequest(
|
|
484
|
+
const MODIFIER_SEP = /[&,]/g;
|
|
485
|
+
const MODIFIER_VAL_SEP = /[:=_]/g;
|
|
486
|
+
async function _handleRequest(request, ipx) {
|
|
498
487
|
const res = {
|
|
499
488
|
statusCode: 200,
|
|
500
489
|
statusMessage: "",
|
|
501
490
|
headers: {},
|
|
502
491
|
body: ""
|
|
503
492
|
};
|
|
504
|
-
const [
|
|
493
|
+
const [modifiersString = "", ...idSegments] = request.url.slice(1).split("/");
|
|
505
494
|
const id = safeString(ufo.decode(idSegments.join("/")));
|
|
506
|
-
if (!
|
|
507
|
-
throw createError("Modifiers are missing", 400,
|
|
495
|
+
if (!modifiersString) {
|
|
496
|
+
throw createError("Modifiers are missing", 400, request.url);
|
|
508
497
|
}
|
|
509
498
|
if (!id || id === "/") {
|
|
510
|
-
throw createError("Resource id is missing", 400,
|
|
499
|
+
throw createError("Resource id is missing", 400, request.url);
|
|
511
500
|
}
|
|
512
501
|
const modifiers = /* @__PURE__ */ Object.create(null);
|
|
513
|
-
if (
|
|
514
|
-
for (const p of
|
|
502
|
+
if (modifiersString !== "_") {
|
|
503
|
+
for (const p of modifiersString.split(MODIFIER_SEP)) {
|
|
515
504
|
const [key, value = ""] = p.split(MODIFIER_VAL_SEP);
|
|
516
505
|
modifiers[safeString(key)] = safeString(ufo.decode(value));
|
|
517
506
|
}
|
|
518
507
|
}
|
|
519
|
-
const img = ipx(id, modifiers,
|
|
520
|
-
const
|
|
521
|
-
if (
|
|
522
|
-
if (
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
return res;
|
|
526
|
-
}
|
|
508
|
+
const img = ipx(id, modifiers, request.options);
|
|
509
|
+
const source = await img.src();
|
|
510
|
+
if (source.mtime) {
|
|
511
|
+
if (request.headers["if-modified-since"] && new Date(request.headers["if-modified-since"]) >= source.mtime) {
|
|
512
|
+
res.statusCode = 304;
|
|
513
|
+
return res;
|
|
527
514
|
}
|
|
528
|
-
res.headers["Last-Modified"] =
|
|
515
|
+
res.headers["Last-Modified"] = source.mtime.toUTCString();
|
|
529
516
|
}
|
|
530
|
-
if (typeof
|
|
531
|
-
res.headers["Cache-Control"] = `max-age=${+
|
|
517
|
+
if (typeof source.maxAge === "number") {
|
|
518
|
+
res.headers["Cache-Control"] = `max-age=${+source.maxAge}, public, s-maxage=${+source.maxAge}`;
|
|
532
519
|
}
|
|
533
520
|
const { data, format } = await img.data();
|
|
534
|
-
const etag =
|
|
521
|
+
const etag = getEtag(data);
|
|
535
522
|
res.headers.ETag = etag;
|
|
536
|
-
if (etag &&
|
|
523
|
+
if (etag && request.headers["if-none-match"] === etag) {
|
|
537
524
|
res.statusCode = 304;
|
|
538
525
|
return res;
|
|
539
526
|
}
|
|
540
527
|
if (format) {
|
|
541
528
|
res.headers["Content-Type"] = `image/${format}`;
|
|
542
529
|
}
|
|
530
|
+
res.headers["Content-Security-Policy"] = "default-src 'none'";
|
|
543
531
|
res.body = data;
|
|
544
532
|
return sanetizeReponse(res);
|
|
545
533
|
}
|
|
546
|
-
function handleRequest(
|
|
547
|
-
return _handleRequest(
|
|
548
|
-
const statusCode = parseInt(
|
|
549
|
-
const statusMessage =
|
|
534
|
+
function handleRequest(request, ipx) {
|
|
535
|
+
return _handleRequest(request, ipx).catch((error) => {
|
|
536
|
+
const statusCode = Number.parseInt(error.statusCode) || 500;
|
|
537
|
+
const statusMessage = error.statusMessage ? error.statusMessage : `IPX Error (${statusCode})`;
|
|
550
538
|
if (process.env.NODE_ENV !== "production" && statusCode === 500) {
|
|
551
|
-
console.error(
|
|
539
|
+
console.error(error);
|
|
552
540
|
}
|
|
553
541
|
return sanetizeReponse({
|
|
554
542
|
statusCode,
|
|
555
543
|
statusMessage,
|
|
556
|
-
body: "IPX Error: " +
|
|
544
|
+
body: "IPX Error: " + error,
|
|
557
545
|
headers: {}
|
|
558
546
|
});
|
|
559
547
|
});
|
|
560
548
|
}
|
|
561
549
|
function createIPXMiddleware(ipx) {
|
|
562
|
-
return function IPXMiddleware(
|
|
563
|
-
return handleRequest({ url:
|
|
550
|
+
return function IPXMiddleware(request, res) {
|
|
551
|
+
return handleRequest({ url: request.url, headers: request.headers }, ipx).then((_res) => {
|
|
564
552
|
res.statusCode = _res.statusCode;
|
|
565
553
|
res.statusMessage = _res.statusMessage;
|
|
566
554
|
for (const name in _res.headers) {
|
|
@@ -575,7 +563,7 @@ function sanetizeReponse(res) {
|
|
|
575
563
|
statusCode: res.statusCode || 200,
|
|
576
564
|
statusMessage: res.statusMessage ? safeString(res.statusMessage) : "OK",
|
|
577
565
|
headers: safeStringObject(res.headers || {}),
|
|
578
|
-
body: typeof res.body === "string" ?
|
|
566
|
+
body: typeof res.body === "string" ? xss(safeString(res.body)) : res.body || ""
|
|
579
567
|
};
|
|
580
568
|
}
|
|
581
569
|
function safeString(input) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ipx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-0",
|
|
4
4
|
"repository": "unjs/ipx",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"exports": {
|
|
@@ -17,42 +17,45 @@
|
|
|
17
17
|
"dist",
|
|
18
18
|
"bin"
|
|
19
19
|
],
|
|
20
|
-
"dependencies": {
|
|
21
|
-
"consola": "^2.15.3",
|
|
22
|
-
"defu": "^6.0.0",
|
|
23
|
-
"destr": "^1.1.1",
|
|
24
|
-
"etag": "^1.8.1",
|
|
25
|
-
"image-meta": "^0.1.1",
|
|
26
|
-
"listhen": "^0.2.13",
|
|
27
|
-
"ohmyfetch": "^0.4.18",
|
|
28
|
-
"pathe": "^0.3.2",
|
|
29
|
-
"sharp": "^0.30.7",
|
|
30
|
-
"ufo": "^0.8.5",
|
|
31
|
-
"xss": "^1.0.13"
|
|
32
|
-
},
|
|
33
|
-
"devDependencies": {
|
|
34
|
-
"@nuxtjs/eslint-config-typescript": "latest",
|
|
35
|
-
"@types/etag": "latest",
|
|
36
|
-
"@types/is-valid-path": "latest",
|
|
37
|
-
"@types/node-fetch": "latest",
|
|
38
|
-
"@types/sharp": "latest",
|
|
39
|
-
"c8": "latest",
|
|
40
|
-
"eslint": "latest",
|
|
41
|
-
"jiti": "latest",
|
|
42
|
-
"nodemon": "latest",
|
|
43
|
-
"serve-handler": "^6.1.3",
|
|
44
|
-
"standard-version": "latest",
|
|
45
|
-
"typescript": "latest",
|
|
46
|
-
"unbuild": "latest",
|
|
47
|
-
"vitest": "latest"
|
|
48
|
-
},
|
|
49
|
-
"packageManager": "pnpm@7.5.0",
|
|
50
20
|
"scripts": {
|
|
51
21
|
"build": "unbuild",
|
|
52
22
|
"dev": "nodemon",
|
|
53
23
|
"lint": "eslint --ext .ts .",
|
|
24
|
+
"prepack": "pnpm build",
|
|
54
25
|
"release": "pnpm test && standard-version && git push --follow-tags && pnpm publish",
|
|
55
26
|
"start": "node bin/ipx.js",
|
|
56
27
|
"test": "pnpm lint && vitest run --coverage"
|
|
57
|
-
}
|
|
58
|
-
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"consola": "^2.15.3",
|
|
31
|
+
"defu": "^6.1.1",
|
|
32
|
+
"destr": "^1.2.1",
|
|
33
|
+
"etag": "^1.8.1",
|
|
34
|
+
"image-meta": "^0.1.1",
|
|
35
|
+
"listhen": "^1.0.0",
|
|
36
|
+
"ohmyfetch": "^0.4.21",
|
|
37
|
+
"pathe": "^1.0.0",
|
|
38
|
+
"sharp": "^0.31.2",
|
|
39
|
+
"ufo": "^1.0.0",
|
|
40
|
+
"xss": "^1.0.14"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@nuxtjs/eslint-config-typescript": "^12.0.0",
|
|
44
|
+
"@types/etag": "^1.8.1",
|
|
45
|
+
"@types/is-valid-path": "^0.1.0",
|
|
46
|
+
"@types/node-fetch": "^2.6.2",
|
|
47
|
+
"@types/sharp": "^0.31.0",
|
|
48
|
+
"@vitest/coverage-c8": "^0.25.3",
|
|
49
|
+
"changelogen": "^0.4.0",
|
|
50
|
+
"eslint": "^8.28.0",
|
|
51
|
+
"eslint-config-unjs": "^0.0.2",
|
|
52
|
+
"jiti": "^1.16.0",
|
|
53
|
+
"nodemon": "^2.0.20",
|
|
54
|
+
"serve-handler": "^6.1.5",
|
|
55
|
+
"standard-version": "^9.5.0",
|
|
56
|
+
"typescript": "^4.9.3",
|
|
57
|
+
"unbuild": "^1.0.1",
|
|
58
|
+
"vitest": "^0.25.3"
|
|
59
|
+
},
|
|
60
|
+
"packageManager": "pnpm@7.17.0"
|
|
61
|
+
}
|