@nestjs/platform-fastify 11.2.3 → 11.2.5

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.
@@ -122,8 +122,24 @@ export declare class FastifyAdapter<TServer extends RawServerBase = RawServerDef
122
122
  private registerMiddie;
123
123
  private getRequestOriginalUrl;
124
124
  private injectRouteOptions;
125
+ /**
126
+ * Fastify still accepts the router options ("ignoreTrailingSlash",
127
+ * "caseSensitive", ...) at the top level, but "initialConfig.routerOptions"
128
+ * only reflects them when they are passed through "routerOptions". As the
129
+ * adapter always passes "routerOptions" (for the version constraint), the
130
+ * top-level values are folded in so that plugins relying on
131
+ * "initialConfig.routerOptions" (like @fastify/middie) normalize request
132
+ * paths exactly like the router does.
133
+ */
134
+ private getTopLevelRouterOptions;
125
135
  private sanitizeUrl;
126
136
  private removeDuplicateSlashes;
127
137
  private trimLastSlash;
138
+ /**
139
+ * Mirrors the absolute-form request target handling of "find-my-way".
140
+ * Returns the path of an absolute-form target ("http://host/path" -> "/path")
141
+ * and leaves any other request target untouched.
142
+ */
143
+ private getPathFromRequestTarget;
128
144
  }
129
145
  export {};
@@ -14,7 +14,6 @@ const path_to_regexp_1 = require("path-to-regexp");
14
14
  const fast_querystring_1 = require("fast-querystring");
15
15
  const url_sanitizer_1 = require("find-my-way/lib/url-sanitizer");
16
16
  const constants_1 = require("../constants");
17
- const fastify_middie_1 = require("./middie/fastify-middie");
18
17
  /**
19
18
  * @publicApi
20
19
  */
@@ -95,6 +94,7 @@ class FastifyAdapter extends http_adapter_1.AbstractHttpAdapter {
95
94
  : (0, fastify_1.fastify)({
96
95
  ...instanceOrOptions,
97
96
  routerOptions: {
97
+ ...this.getTopLevelRouterOptions(instanceOrOptions),
98
98
  ...instanceOrOptions?.routerOptions,
99
99
  constraints: {
100
100
  version: this.versionConstraint,
@@ -462,7 +462,7 @@ class FastifyAdapter extends http_adapter_1.AbstractHttpAdapter {
462
462
  }
463
463
  async registerMiddie() {
464
464
  this.isMiddieRegistered = true;
465
- await this.register(fastify_middie_1.default);
465
+ await this.register(Promise.resolve().then(() => require('@fastify/middie')));
466
466
  }
467
467
  getRequestOriginalUrl(rawRequest) {
468
468
  return rawRequest.originalUrl || rawRequest.url;
@@ -511,9 +511,39 @@ class FastifyAdapter extends http_adapter_1.AbstractHttpAdapter {
511
511
  }
512
512
  return this.instance.route(routeToInject);
513
513
  }
514
+ /**
515
+ * Fastify still accepts the router options ("ignoreTrailingSlash",
516
+ * "caseSensitive", ...) at the top level, but "initialConfig.routerOptions"
517
+ * only reflects them when they are passed through "routerOptions". As the
518
+ * adapter always passes "routerOptions" (for the version constraint), the
519
+ * top-level values are folded in so that plugins relying on
520
+ * "initialConfig.routerOptions" (like @fastify/middie) normalize request
521
+ * paths exactly like the router does.
522
+ */
523
+ getTopLevelRouterOptions(options) {
524
+ const routerOptions = {};
525
+ const routerOptionKeys = [
526
+ 'ignoreTrailingSlash',
527
+ 'ignoreDuplicateSlashes',
528
+ 'caseSensitive',
529
+ 'useSemicolonDelimiter',
530
+ 'maxParamLength',
531
+ 'allowUnsafeRegex',
532
+ ];
533
+ for (const key of routerOptionKeys) {
534
+ if (options?.[key] !== undefined) {
535
+ routerOptions[key] = options[key];
536
+ }
537
+ }
538
+ return routerOptions;
539
+ }
514
540
  sanitizeUrl(url) {
515
541
  const initialConfig = this.instance.initialConfig;
516
542
  const routerOptions = initialConfig.routerOptions;
543
+ // Absolute-form request targets ("GET http://host/path HTTP/1.1") must be
544
+ // resolved to their path before any other normalization, as the Fastify
545
+ // router does, so that middleware and routes always match the same path.
546
+ url = this.getPathFromRequestTarget(url);
517
547
  if (routerOptions.ignoreDuplicateSlashes ||
518
548
  initialConfig.ignoreDuplicateSlashes) {
519
549
  url = this.removeDuplicateSlashes(url);
@@ -541,5 +571,30 @@ class FastifyAdapter extends http_adapter_1.AbstractHttpAdapter {
541
571
  }
542
572
  return path;
543
573
  }
574
+ /**
575
+ * Mirrors the absolute-form request target handling of "find-my-way".
576
+ * Returns the path of an absolute-form target ("http://host/path" -> "/path")
577
+ * and leaves any other request target untouched.
578
+ */
579
+ getPathFromRequestTarget(url) {
580
+ if (url.charCodeAt(0) === 47 /* '/' */) {
581
+ return url;
582
+ }
583
+ const schemeEnd = url.indexOf('://');
584
+ if (schemeEnd === -1) {
585
+ return url;
586
+ }
587
+ const scheme = url.slice(0, schemeEnd).toLowerCase();
588
+ if (scheme !== 'http' && scheme !== 'https') {
589
+ return url;
590
+ }
591
+ const authorityStart = schemeEnd + 3;
592
+ const pathStart = url.indexOf('/', authorityStart);
593
+ if (pathStart === authorityStart || !URL.canParse(url)) {
594
+ // Malformed target: the router rejects it before any middleware runs
595
+ return url;
596
+ }
597
+ return pathStart === -1 ? '/' : url.slice(pathStart);
598
+ }
544
599
  }
545
600
  exports.FastifyAdapter = FastifyAdapter;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nestjs/platform-fastify",
3
- "version": "11.2.3",
3
+ "version": "11.2.5",
4
4
  "description": "Nest - modern, fast, powerful node.js web framework (@platform-fastify)",
5
5
  "author": "Kamil Mysliwiec",
6
6
  "license": "MIT",
@@ -20,13 +20,12 @@
20
20
  "dependencies": {
21
21
  "@fastify/cors": "11.3.0",
22
22
  "@fastify/formbody": "8.0.2",
23
+ "@fastify/middie": "9.3.4",
23
24
  "fast-querystring": "1.1.2",
24
25
  "fastify": "5.11.3",
25
- "fastify-plugin": "6.0.0",
26
26
  "find-my-way": "9.7.0",
27
27
  "light-my-request": "6.6.0",
28
28
  "path-to-regexp": "8.4.2",
29
- "reusify": "1.1.0",
30
29
  "tslib": "2.8.1"
31
30
  },
32
31
  "peerDependencies": {
@@ -43,5 +42,5 @@
43
42
  "optional": true
44
43
  }
45
44
  },
46
- "gitHead": "2b36ee5fea13dedcedfd9815a9c193b2d21130c1"
45
+ "gitHead": "e91618ceec8b2e8fb30ac6277bec07719fdf0f37"
47
46
  }
@@ -1,46 +0,0 @@
1
- import { FastifyInstance, FastifyPluginCallback } from 'fastify';
2
- import * as http from 'node:http';
3
- export type MiddlewareFn<Req extends {
4
- url: string;
5
- originalUrl?: string;
6
- }, Res extends {
7
- finished?: boolean;
8
- writableEnded?: boolean;
9
- }, Ctx = unknown> = (req: Req, res: Res, next: (err?: unknown) => void) => void;
10
- declare const supportedHooks: readonly ["onError", "onSend", "preParsing", "preSerialization", "onRequest", "onResponse", "onTimeout", "preHandler", "preValidation"];
11
- type SupportedHook = (typeof supportedHooks)[number];
12
- interface MiddieOptions {
13
- hook?: SupportedHook;
14
- }
15
- declare function fastifyMiddie(fastify: FastifyInstance, options: MiddieOptions, next: (err?: Error) => void): void;
16
- declare namespace fastifyMiddie {
17
- export interface FastifyMiddieOptions {
18
- hook?: 'onRequest' | 'preParsing' | 'preValidation' | 'preHandler' | 'preSerialization' | 'onSend' | 'onResponse' | 'onTimeout' | 'onError';
19
- }
20
- type FastifyMiddie = FastifyPluginCallback<fastifyMiddie.FastifyMiddieOptions>;
21
- export interface IncomingMessageExtended {
22
- body?: any;
23
- query?: any;
24
- }
25
- export type NextFunction = (err?: any) => void;
26
- export type SimpleHandleFunction = (req: http.IncomingMessage & IncomingMessageExtended, res: http.ServerResponse) => void;
27
- export type NextHandleFunction = (req: http.IncomingMessage & IncomingMessageExtended, res: http.ServerResponse, next: NextFunction) => void;
28
- export type Handler = SimpleHandleFunction | NextHandleFunction;
29
- export const fastifyMiddie: FastifyMiddie;
30
- export { fastifyMiddie as default };
31
- }
32
- declare module 'fastify' {
33
- interface FastifyInstance {
34
- use(fn: fastifyMiddie.Handler): this;
35
- use(route: string, fn: fastifyMiddie.Handler): this;
36
- use(routes: string[], fn: fastifyMiddie.Handler): this;
37
- }
38
- }
39
- /**
40
- * A clone of `@fastify/middie` engine https://github.com/fastify/middie
41
- * with an extra vulnerability fix. Path is now decoded before matching to
42
- * avoid bypassing middleware with encoded characters.
43
- */
44
- declare const _default: typeof fastifyMiddie;
45
- export default _default;
46
- export { fastifyMiddie };
@@ -1,252 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.fastifyMiddie = fastifyMiddie;
4
- const fastify_plugin_1 = require("fastify-plugin");
5
- const url_sanitizer_1 = require("find-my-way/lib/url-sanitizer");
6
- const path_to_regexp_1 = require("path-to-regexp");
7
- const reusify = require("reusify");
8
- function bindLast(fn, last) {
9
- return (...args) => fn(...args, last);
10
- }
11
- /**
12
- * A clone of `@fastify/middie` engine https://github.com/fastify/middie
13
- * with an extra vulnerability fix. Path is now decoded before matching to
14
- * avoid bypassing middleware with encoded characters.
15
- */
16
- function middie(complete, initialConfig) {
17
- const middlewares = [];
18
- const pool = reusify(Holder);
19
- return {
20
- use,
21
- run: bindLast(run, initialConfig),
22
- };
23
- function use(url, f) {
24
- if (f === undefined) {
25
- f = url;
26
- url = null;
27
- }
28
- let regexp;
29
- if (typeof url === 'string') {
30
- const pathRegExp = (0, path_to_regexp_1.pathToRegexp)(sanitizePrefixUrl(url), {
31
- end: false,
32
- });
33
- regexp = pathRegExp.regexp;
34
- }
35
- if (Array.isArray(f)) {
36
- for (const val of f) {
37
- middlewares.push({ regexp, fn: val });
38
- }
39
- }
40
- else {
41
- middlewares.push({ regexp, fn: f });
42
- }
43
- return this;
44
- }
45
- function run(req, res, ctx, initialConfig) {
46
- if (!middlewares.length) {
47
- complete(null, req, res, ctx);
48
- return;
49
- }
50
- req.originalUrl = req.url;
51
- const holder = pool.get();
52
- holder.req = req;
53
- holder.res = res;
54
- holder.url = sanitizeUrl(req.url);
55
- holder.context = ctx;
56
- holder.initialConfig = initialConfig;
57
- holder.done();
58
- }
59
- function Holder() {
60
- this.req = null;
61
- this.res = null;
62
- this.url = null;
63
- this.context = null;
64
- this.initialConfig = null;
65
- this.i = 0;
66
- const that = this;
67
- this.done = function (err) {
68
- const req = that.req;
69
- const res = that.res;
70
- const url = that.url;
71
- const context = that.context;
72
- const i = that.i++;
73
- req.url = req.originalUrl;
74
- if (res.finished === true || res.writableEnded === true) {
75
- cleanup();
76
- return;
77
- }
78
- if (err || middlewares.length === i) {
79
- complete(err, req, res, context);
80
- cleanup();
81
- }
82
- else {
83
- const { fn, regexp } = middlewares[i];
84
- if (regexp) {
85
- // Decode URL before matching to avoid bypassing middleware
86
- let sanitizedUrl = url;
87
- if (that.initialConfig.ignoreDuplicateSlashes ||
88
- that.initialConfig.routerOptions?.ignoreDuplicateSlashes) {
89
- sanitizedUrl = removeDuplicateSlashes(sanitizedUrl);
90
- }
91
- if (that.initialConfig.ignoreTrailingSlash ||
92
- that.initialConfig.routerOptions?.ignoreTrailingSlash) {
93
- sanitizedUrl = trimLastSlash(sanitizedUrl);
94
- }
95
- if (that.initialConfig.caseSensitive === false ||
96
- that.initialConfig.routerOptions?.caseSensitive === false) {
97
- sanitizedUrl = sanitizedUrl.toLowerCase();
98
- }
99
- const decodedUrl = (0, url_sanitizer_1.safeDecodeURI)(sanitizedUrl, that.initialConfig?.routerOptions?.useSemicolonDelimiter ||
100
- that.initialConfig?.useSemicolonDelimiter).path;
101
- const result = regexp.exec(decodedUrl);
102
- if (result) {
103
- req.url = req.url.replace(result[0], '');
104
- if (req.url[0] !== '/')
105
- req.url = '/' + req.url;
106
- fn(req, res, that.done);
107
- }
108
- else {
109
- that.done();
110
- }
111
- }
112
- else {
113
- fn(req, res, that.done);
114
- }
115
- }
116
- };
117
- function cleanup() {
118
- that.req = null;
119
- that.res = null;
120
- that.context = null;
121
- that.initialConfig = null;
122
- that.i = 0;
123
- pool.release(that);
124
- }
125
- }
126
- }
127
- function removeDuplicateSlashes(path) {
128
- const REMOVE_DUPLICATE_SLASHES_REGEXP = /\/\/+/g;
129
- return path.indexOf('//') !== -1
130
- ? path.replace(REMOVE_DUPLICATE_SLASHES_REGEXP, '/')
131
- : path;
132
- }
133
- function trimLastSlash(path) {
134
- if (path.length > 1 && path.charCodeAt(path.length - 1) === 47) {
135
- return path.slice(0, -1);
136
- }
137
- return path;
138
- }
139
- function sanitizeUrl(url) {
140
- for (let i = 0, len = url.length; i < len; i++) {
141
- const charCode = url.charCodeAt(i);
142
- if (charCode === 63 || charCode === 35) {
143
- return url.slice(0, i);
144
- }
145
- }
146
- return url;
147
- }
148
- function sanitizePrefixUrl(url) {
149
- if (url === '/')
150
- return '';
151
- if (url[url.length - 1] === '/')
152
- return url.slice(0, -1);
153
- return url;
154
- }
155
- const kMiddlewares = Symbol('fastify-middie-middlewares');
156
- const kMiddie = Symbol('fastify-middie-instance');
157
- const kMiddieHasMiddlewares = Symbol('fastify-middie-has-middlewares');
158
- const supportedHooksWithPayload = [
159
- 'onError',
160
- 'onSend',
161
- 'preParsing',
162
- 'preSerialization',
163
- ];
164
- const supportedHooksWithoutPayload = [
165
- 'onRequest',
166
- 'onResponse',
167
- 'onTimeout',
168
- 'preHandler',
169
- 'preValidation',
170
- ];
171
- const supportedHooks = [
172
- ...supportedHooksWithPayload,
173
- ...supportedHooksWithoutPayload,
174
- ];
175
- function fastifyMiddie(fastify, options, next) {
176
- fastify.decorate('use', use);
177
- fastify[kMiddlewares] = [];
178
- fastify[kMiddieHasMiddlewares] = false;
179
- fastify[kMiddie] = middie(onMiddieEnd, fastify.initialConfig);
180
- const hook = options.hook || 'onRequest';
181
- if (!supportedHooks.includes(hook)) {
182
- next(new Error(`The hook "${hook}" is not supported by fastify-middie`));
183
- return;
184
- }
185
- fastify
186
- .addHook(hook, supportedHooksWithPayload.includes(hook)
187
- ? runMiddieWithPayload
188
- : runMiddie)
189
- .addHook('onRegister', onRegister);
190
- function use(path, fn) {
191
- if (typeof path === 'string') {
192
- const prefix = this.prefix;
193
- path = prefix + (path === '/' && prefix.length > 0 ? '' : path);
194
- }
195
- this[kMiddlewares].push([path, fn]);
196
- if (fn == null) {
197
- this[kMiddie].use(path);
198
- }
199
- else {
200
- this[kMiddie].use(path, fn);
201
- }
202
- this[kMiddieHasMiddlewares] = true;
203
- return this;
204
- }
205
- function runMiddie(req, reply, next) {
206
- if (this[kMiddieHasMiddlewares]) {
207
- const raw = req.raw;
208
- raw.id = req.id;
209
- raw.hostname = req.hostname;
210
- raw.protocol = req.protocol;
211
- raw.ip = req.ip;
212
- raw.ips = req.ips;
213
- raw.log = req.log;
214
- req.raw.query = req.query;
215
- reply.raw.log = req.log;
216
- if (req.body !== undefined)
217
- req.raw.body = req.body;
218
- this[kMiddie].run(req.raw, reply.raw, next);
219
- }
220
- else {
221
- next();
222
- }
223
- }
224
- function runMiddieWithPayload(req, reply, _payload, next) {
225
- runMiddie.bind(this)(req, reply, next);
226
- }
227
- function onMiddieEnd(err, _req, _res, next) {
228
- next(err);
229
- }
230
- function onRegister(instance) {
231
- const middlewares = instance[kMiddlewares].slice();
232
- instance[kMiddlewares] = [];
233
- instance[kMiddie] = middie(onMiddieEnd, instance.initialConfig);
234
- instance[kMiddieHasMiddlewares] = false;
235
- instance.decorate('use', use);
236
- for (const middleware of middlewares) {
237
- instance[kMiddlewares].push(middleware);
238
- instance[kMiddie].use(...middleware);
239
- }
240
- instance[kMiddieHasMiddlewares] = middlewares.length > 0;
241
- }
242
- next();
243
- }
244
- /**
245
- * A clone of `@fastify/middie` engine https://github.com/fastify/middie
246
- * with an extra vulnerability fix. Path is now decoded before matching to
247
- * avoid bypassing middleware with encoded characters.
248
- */
249
- exports.default = (0, fastify_plugin_1.default)(fastifyMiddie, {
250
- fastify: '5.x',
251
- name: '@fastify/middie',
252
- });