structured-fw 1.4.0 → 1.6.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.
Files changed (32) hide show
  1. package/README.md +64 -23
  2. package/build/system/EventEmitter.js +4 -6
  3. package/build/system/StructuredError.d.ts +8 -0
  4. package/build/system/StructuredError.js +40 -0
  5. package/build/system/bin/structured.js +1 -2
  6. package/build/system/client/ClientApplication.js +3 -2
  7. package/build/system/client/ClientComponent.js +21 -13
  8. package/build/system/client/DataStore.js +2 -4
  9. package/build/system/client/DataStoreView.js +3 -1
  10. package/build/system/client/NetRequest.js +7 -2
  11. package/build/system/server/Application.d.ts +3 -2
  12. package/build/system/server/Application.js +19 -7
  13. package/build/system/server/Component.js +15 -7
  14. package/build/system/server/Components.js +3 -2
  15. package/build/system/server/Document.d.ts +1 -1
  16. package/build/system/server/Document.js +10 -7
  17. package/build/system/server/DocumentHead.js +9 -8
  18. package/build/system/server/FormValidation.js +121 -123
  19. package/build/system/server/Handlebars.js +2 -4
  20. package/build/system/server/Layout.d.ts +2 -2
  21. package/build/system/server/Layout.js +4 -0
  22. package/build/system/server/Request.d.ts +1 -11
  23. package/build/system/server/Request.js +12 -269
  24. package/build/system/server/RequestContext.d.ts +43 -0
  25. package/build/system/server/RequestContext.js +322 -0
  26. package/build/system/server/Session.js +3 -2
  27. package/build/system/server/dom/DOMNode.js +11 -7
  28. package/build/system/server/dom/HTMLParser.js +10 -8
  29. package/build/system/types/application.types.d.ts +1 -1
  30. package/build/system/types/component.types.d.ts +1 -1
  31. package/build/system/types/request.types.d.ts +1 -19
  32. package/package.json +2 -1
@@ -1,132 +1,130 @@
1
1
  export class FormValidation {
2
- constructor() {
3
- this.fieldRules = [];
4
- this.singleError = false;
5
- this.customValidators = [];
6
- this.validators = {
7
- 'required': async (data, field) => {
8
- if (!(field in data)) {
9
- return false;
10
- }
11
- const value = data[field];
12
- if (value === null ||
13
- value === undefined ||
14
- (typeof value === 'string' && value.trim().length === 0)) {
15
- return false;
16
- }
2
+ fieldRules = [];
3
+ singleError = false;
4
+ customValidators = [];
5
+ validators = {
6
+ 'required': async (data, field) => {
7
+ if (!(field in data)) {
8
+ return false;
9
+ }
10
+ const value = data[field];
11
+ if (value === null ||
12
+ value === undefined ||
13
+ (typeof value === 'string' && value.trim().length === 0)) {
14
+ return false;
15
+ }
16
+ return true;
17
+ },
18
+ 'number': async (data, field) => {
19
+ const value = data[field];
20
+ if (typeof value === 'number') {
17
21
  return true;
18
- },
19
- 'number': async (data, field) => {
20
- const value = data[field];
21
- if (typeof value === 'number') {
22
- return true;
23
- }
24
- if (typeof value !== 'string') {
25
- return false;
26
- }
27
- return /^-?\d+$/.test(value);
28
- },
29
- 'float': async (data, field) => {
30
- const value = data[field];
31
- if (typeof value === 'number') {
32
- return true;
33
- }
34
- if (typeof value !== 'string') {
35
- return false;
36
- }
37
- return /^-?\d+\.\d+$/.test(value);
38
- },
39
- 'numeric': async (data, field, arg, rules) => {
40
- return await this.validators['number'](data, field, arg, rules) || await this.validators['float'](data, field, arg, rules);
41
- },
42
- 'min': async (data, field, arg, rules) => {
43
- const value = data[field];
44
- if (typeof value === 'number') {
45
- return value >= arg;
46
- }
47
- if (typeof value !== 'string') {
48
- return false;
49
- }
50
- if (await this.validators['numeric'](data, field, arg, rules)) {
51
- return parseFloat(value) >= arg;
52
- }
22
+ }
23
+ if (typeof value !== 'string') {
53
24
  return false;
54
- },
55
- 'max': async (data, field, arg, rules) => {
56
- const value = data[field];
57
- if (typeof value === 'number') {
58
- return value <= arg;
59
- }
60
- if (typeof value !== 'string') {
61
- return false;
62
- }
63
- if (await this.validators['numeric'](data, field, arg, rules)) {
64
- return parseFloat(value) <= arg;
65
- }
25
+ }
26
+ return /^-?\d+$/.test(value);
27
+ },
28
+ 'float': async (data, field) => {
29
+ const value = data[field];
30
+ if (typeof value === 'number') {
31
+ return true;
32
+ }
33
+ if (typeof value !== 'string') {
66
34
  return false;
67
- },
68
- 'minLength': async (data, field, arg) => {
69
- const value = data[field];
70
- if (typeof value !== 'string') {
71
- return false;
72
- }
73
- return value.length >= arg;
74
- },
75
- 'maxLength': async (data, field, arg) => {
76
- const value = data[field];
77
- if (typeof value !== 'string') {
78
- return false;
79
- }
80
- return value.length <= arg;
81
- },
82
- 'alphanumeric': async (data, field) => {
83
- const value = data[field];
84
- if (typeof value !== 'string') {
85
- return false;
86
- }
87
- return /^[a-zA-Z0-9]+$/.test(value);
88
- },
89
- 'validEmail': async (data, field) => {
90
- const value = data[field];
91
- if (typeof value !== 'string') {
92
- return false;
93
- }
94
- return /^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/.test(value);
95
35
  }
96
- };
97
- this.decorators = {
98
- 'required': (fieldHuman) => {
99
- return `${fieldHuman} is required`;
100
- },
101
- 'number': (fieldHuman) => {
102
- return `${fieldHuman} has to be a whole number`;
103
- },
104
- 'float': (fieldHuman) => {
105
- return `${fieldHuman} has to be a decimal number`;
106
- },
107
- 'numeric': (fieldHuman) => {
108
- return `${fieldHuman} has to contain a numeric value`;
109
- },
110
- 'min': (fieldHuman, data, field, arg) => {
111
- return `${fieldHuman} has to be a value greater than ${arg}`;
112
- },
113
- 'max': (fieldHuman, data, field, arg) => {
114
- return `${fieldHuman} has to be a value lower than ${arg}`;
115
- },
116
- 'minLength': (fieldHuman, data, field, arg) => {
117
- return `${fieldHuman} has to contain at least ${arg} characters`;
118
- },
119
- 'maxLength': (fieldHuman, data, field, arg) => {
120
- return `${fieldHuman} has to contain no more than ${arg} characters`;
121
- },
122
- 'alphanumeric': (fieldHuman) => {
123
- return `${fieldHuman} can contain only letter and numbers`;
124
- },
125
- 'validEmail': () => {
126
- return `Please enter a valid email address`;
36
+ return /^-?\d+\.\d+$/.test(value);
37
+ },
38
+ 'numeric': async (data, field, arg, rules) => {
39
+ return await this.validators['number'](data, field, arg, rules) || await this.validators['float'](data, field, arg, rules);
40
+ },
41
+ 'min': async (data, field, arg, rules) => {
42
+ const value = data[field];
43
+ if (typeof value === 'number') {
44
+ return value >= arg;
127
45
  }
128
- };
129
- }
46
+ if (typeof value !== 'string') {
47
+ return false;
48
+ }
49
+ if (await this.validators['numeric'](data, field, arg, rules)) {
50
+ return parseFloat(value) >= arg;
51
+ }
52
+ return false;
53
+ },
54
+ 'max': async (data, field, arg, rules) => {
55
+ const value = data[field];
56
+ if (typeof value === 'number') {
57
+ return value <= arg;
58
+ }
59
+ if (typeof value !== 'string') {
60
+ return false;
61
+ }
62
+ if (await this.validators['numeric'](data, field, arg, rules)) {
63
+ return parseFloat(value) <= arg;
64
+ }
65
+ return false;
66
+ },
67
+ 'minLength': async (data, field, arg) => {
68
+ const value = data[field];
69
+ if (typeof value !== 'string') {
70
+ return false;
71
+ }
72
+ return value.length >= arg;
73
+ },
74
+ 'maxLength': async (data, field, arg) => {
75
+ const value = data[field];
76
+ if (typeof value !== 'string') {
77
+ return false;
78
+ }
79
+ return value.length <= arg;
80
+ },
81
+ 'alphanumeric': async (data, field) => {
82
+ const value = data[field];
83
+ if (typeof value !== 'string') {
84
+ return false;
85
+ }
86
+ return /^[a-zA-Z0-9]+$/.test(value);
87
+ },
88
+ 'validEmail': async (data, field) => {
89
+ const value = data[field];
90
+ if (typeof value !== 'string') {
91
+ return false;
92
+ }
93
+ return /^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/.test(value);
94
+ }
95
+ };
96
+ decorators = {
97
+ 'required': (fieldHuman) => {
98
+ return `${fieldHuman} is required`;
99
+ },
100
+ 'number': (fieldHuman) => {
101
+ return `${fieldHuman} has to be a whole number`;
102
+ },
103
+ 'float': (fieldHuman) => {
104
+ return `${fieldHuman} has to be a decimal number`;
105
+ },
106
+ 'numeric': (fieldHuman) => {
107
+ return `${fieldHuman} has to contain a numeric value`;
108
+ },
109
+ 'min': (fieldHuman, data, field, arg) => {
110
+ return `${fieldHuman} has to be a value greater than ${arg}`;
111
+ },
112
+ 'max': (fieldHuman, data, field, arg) => {
113
+ return `${fieldHuman} has to be a value lower than ${arg}`;
114
+ },
115
+ 'minLength': (fieldHuman, data, field, arg) => {
116
+ return `${fieldHuman} has to contain at least ${arg} characters`;
117
+ },
118
+ 'maxLength': (fieldHuman, data, field, arg) => {
119
+ return `${fieldHuman} has to contain no more than ${arg} characters`;
120
+ },
121
+ 'alphanumeric': (fieldHuman) => {
122
+ return `${fieldHuman} can contain only letter and numbers`;
123
+ },
124
+ 'validEmail': () => {
125
+ return `Please enter a valid email address`;
126
+ }
127
+ };
130
128
  addRule(fieldName, nameHumanReadable, rules) {
131
129
  const rule = {
132
130
  field: [fieldName, nameHumanReadable],
@@ -1,9 +1,7 @@
1
1
  import { default as HandlebarsInstance } from 'handlebars';
2
2
  export class Handlebars {
3
- constructor() {
4
- this.instance = HandlebarsInstance;
5
- this.helpers = {};
6
- }
3
+ instance = HandlebarsInstance;
4
+ helpers = {};
7
5
  register(name, helper) {
8
6
  this.helpers[name] = helper;
9
7
  this.instance.registerHelper(name, helper);
@@ -1,7 +1,7 @@
1
1
  import { LooseObject } from '../types/general.types.js';
2
- import { RequestContext } from "../types/request.types.js";
3
2
  import { Application } from "./Application.js";
4
3
  import { Document } from "./Document.js";
4
+ import { RequestContext } from './RequestContext.js';
5
5
  export declare class Layout {
6
6
  layoutComponent: string;
7
7
  app: Application;
@@ -9,5 +9,5 @@ export declare class Layout {
9
9
  attributes: Record<string, string>;
10
10
  constructor(app: Application, layoutComponent: string, language?: string, attributes?: Record<string, string>);
11
11
  private layoutComponentExists;
12
- document(ctx: RequestContext, title: string, componentName: string, data?: LooseObject, attributes?: Record<string, string>): Promise<Document>;
12
+ document(ctx: RequestContext<any>, title: string, componentName: string, data?: LooseObject, attributes?: Record<string, string>): Promise<Document>;
13
13
  }
@@ -1,6 +1,10 @@
1
1
  import { Component } from "./Component.js";
2
2
  import { Document } from "./Document.js";
3
3
  export class Layout {
4
+ layoutComponent;
5
+ app;
6
+ language;
7
+ attributes;
4
8
  constructor(app, layoutComponent, language = 'en', attributes) {
5
9
  this.app = app;
6
10
  this.layoutComponent = layoutComponent;
@@ -1,24 +1,14 @@
1
1
  import { IncomingMessage, ServerResponse } from "node:http";
2
2
  import { LooseObject } from '../types/general.types.js';
3
- import { RequestMethod, PostedDataDecoded, RequestBodyFile, RequestCallback } from "../types/request.types.js";
3
+ import { RequestMethod, RequestCallback } from "../types/request.types.js";
4
4
  import { Application } from "./Application.js";
5
- import { Document } from "./Document.js";
6
5
  export declare class Request {
7
6
  private app;
8
7
  constructor(app: Application);
9
- pageNotFoundCallback: RequestCallback<void | Document, LooseObject>;
10
8
  private readonly handlers;
11
9
  on<R extends any, Body extends LooseObject | undefined = LooseObject>(methods: RequestMethod | Array<RequestMethod>, pattern: string | RegExp | Array<string | RegExp>, callback: RequestCallback<R, Body>, scope?: any, isStaticAsset?: boolean): void;
12
10
  private getHandler;
13
11
  handle(request: IncomingMessage, response: ServerResponse): Promise<void>;
14
- private extractURIArguments;
15
12
  private patternToSegments;
16
- private parseBody;
17
- private dataRaw;
18
- redirect(response: ServerResponse, to: string, statusCode?: number): void;
19
13
  loadHandlers(basePath?: string): Promise<void>;
20
- static queryStringDecode(queryString: string, initialValue?: PostedDataDecoded, trimValues?: boolean): PostedDataDecoded;
21
- static parseBodyMultipart(bodyRaw: string, boundary: string): PostedDataDecoded;
22
- static multipartBodyFiles(bodyRaw: string, boundary: string): Record<string, RequestBodyFile>;
23
- private sendResponse;
24
14
  }
@@ -1,19 +1,13 @@
1
- import { mergeDeep, queryStringDecode, queryStringDecodedSetValue } from "../Util.js";
2
- import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
1
+ import { existsSync, readdirSync, statSync } from "node:fs";
3
2
  import path from "node:path";
4
- import { Buffer } from "node:buffer";
5
- import { Document } from "./Document.js";
6
- import zlib from "node:zlib";
3
+ import { RequestContext } from "./RequestContext.js";
4
+ import { StructuredError } from "../StructuredError.js";
7
5
  export class Request {
6
+ app;
8
7
  constructor(app) {
9
- this.pageNotFoundCallback = async ({ response }) => {
10
- response.statusCode = 404;
11
- response.write('Page not found');
12
- response.end();
13
- };
14
- this.handlers = [];
15
8
  this.app = app;
16
9
  }
10
+ handlers = [];
17
11
  on(methods, pattern, callback, scope, isStaticAsset = false) {
18
12
  if (!(methods instanceof Array)) {
19
13
  methods = [methods];
@@ -90,128 +84,23 @@ export class Request {
90
84
  if (this.app.config.url.removeTrailingSlash && uri.length > 1 && uri.endsWith('/')) {
91
85
  uri = uri.substring(0, uri.length - 1);
92
86
  }
93
- let getArgs = {};
94
87
  if (uri.indexOf('?') > -1) {
95
88
  const uriParts = uri.split('?');
96
89
  uri = uriParts[0];
97
- getArgs = queryStringDecode(uriParts[1]);
98
90
  }
99
91
  const handler = this.getHandler(uri, requestMethod);
100
- const context = {
101
- request,
102
- response,
103
- handler,
104
- args: {},
105
- data: {},
106
- body: undefined,
107
- getArgs,
108
- cookies: this.app.cookies.parse(request),
109
- timeStart: new Date().getTime(),
110
- isAjax: request.headers['x-requested-with'] == 'xmlhttprequest',
111
- respondWith: async (data) => {
112
- if (typeof data === 'string' || Buffer.isBuffer(data)) {
113
- this.sendResponse(request, response, data, 'text/plain; charset=utf-8');
114
- }
115
- else if (typeof data === 'number') {
116
- this.sendResponse(request, response, data.toString(), 'text/plain; charset=utf-8');
117
- }
118
- else if (data instanceof Document) {
119
- this.sendResponse(request, response, await data.toString(), 'text/html; charset=utf-8');
120
- }
121
- else if (data === undefined || data === null) {
122
- this.sendResponse(request, response, '', 'text/plain; charset=utf-8');
123
- }
124
- else {
125
- this.sendResponse(request, response, JSON.stringify(data, null, 4), 'application/json; charset=utf-8');
126
- }
127
- },
128
- redirect: (to, statusCode = 302) => {
129
- this.redirect(response, to, statusCode);
130
- },
131
- show404: async () => {
132
- this.app.emit('pageNotFound', context);
133
- response.statusCode = 404;
134
- const res = await this.pageNotFoundCallback.apply(this.app, [context]);
135
- if (res instanceof Document) {
136
- await context.respondWith(res);
137
- }
138
- }
139
- };
140
- if (handler !== null) {
141
- if (!handler.staticAsset) {
142
- const results = await this.app.emit('beforeRequestHandler', context);
143
- if (results.includes(false)) {
144
- context.response.end();
145
- return;
146
- }
147
- try {
148
- await this.parseBody(context);
149
- }
150
- catch (e) {
151
- console.error(`Error parsing request body: ${e.message}`);
152
- }
153
- const URIArgs = this.extractURIArguments(uri, handler.match);
154
- context.args = URIArgs;
155
- }
156
- try {
157
- const response = await handler.callback.apply(handler.scope, [context]);
158
- if (!context.response.headersSent) {
159
- await context.respondWith(response);
160
- }
161
- }
162
- catch (e) {
163
- console.log('Error executing request handler ', e, handler.callback.toString());
164
- }
165
- if (!handler.staticAsset) {
166
- await this.app.emit('afterRequestHandler', context);
167
- }
168
- }
169
- else {
170
- let staticAsset = false;
171
- if (this.app.config.url.isAsset(context.request.url || '')) {
172
- const basePath = context.request.url?.startsWith('/assets/ts/') ? './' : '../';
173
- const assetPath = path.resolve(basePath + context.request.url);
174
- if (existsSync(assetPath)) {
175
- await this.app.emit('beforeAssetAccess', context);
176
- const extension = (context.request.url || '').split('.').pop();
177
- let contentType = 'application/javascript';
178
- if (extension) {
179
- const typeByExtension = this.app.contentType(extension);
180
- if (typeByExtension) {
181
- contentType = typeByExtension;
182
- }
183
- }
184
- this.sendResponse(request, response, readFileSync(assetPath), contentType);
185
- staticAsset = true;
186
- await this.app.emit('afterAssetAccess', context);
187
- }
188
- }
189
- if (!staticAsset) {
190
- await context.show404();
191
- }
92
+ try {
93
+ const ctx = new RequestContext(this.app, request, response, handler);
94
+ await ctx.exec();
192
95
  }
193
- response.end();
194
- }
195
- extractURIArguments(uri, match) {
196
- if (match instanceof RegExp) {
197
- const matches = match.exec(uri);
198
- if (matches) {
199
- return {
200
- matches
201
- };
96
+ catch (e) {
97
+ if (e instanceof StructuredError) {
98
+ e.log();
202
99
  }
203
100
  else {
204
- return {};
101
+ throw e;
205
102
  }
206
103
  }
207
- const uriArgs = {};
208
- const segments = uri.split('/');
209
- match.forEach((segmentPattern, i) => {
210
- if (segmentPattern.name) {
211
- uriArgs[segmentPattern.name] = segmentPattern.type === 'number' ? parseInt(segments[i]) : segments[i];
212
- }
213
- });
214
- return uriArgs;
215
104
  }
216
105
  patternToSegments(pattern) {
217
106
  const segments = [];
@@ -244,81 +133,6 @@ export class Request {
244
133
  });
245
134
  return segments;
246
135
  }
247
- async parseBody(ctx) {
248
- if (ctx.request.headers['content-type']) {
249
- ctx.bodyRaw = await this.dataRaw(ctx.request);
250
- if (ctx.request.headers['content-type'].indexOf('urlencoded') > -1) {
251
- const bodyRaw = ctx.bodyRaw.toString('utf-8');
252
- try {
253
- ctx.body = queryStringDecode(bodyRaw);
254
- }
255
- catch (e) {
256
- console.error(`Error parsing urlencoded request body for request to ${ctx.request.url}, (${e.message}).
257
- Raw data:
258
- ${bodyRaw}
259
-
260
- `);
261
- }
262
- }
263
- else if (ctx.request.headers['content-type'].indexOf('multipart/form-data') > -1) {
264
- let boundary = /^multipart\/form-data; boundary=(.+)$/.exec(ctx.request.headers['content-type']);
265
- if (boundary) {
266
- boundary = `--${boundary[1]}`;
267
- try {
268
- ctx.body = Request.parseBodyMultipart(ctx.bodyRaw.toString('utf-8'), boundary);
269
- }
270
- catch (e) {
271
- console.error(`Error parsing multipart request body for request to ${ctx.request.url}, (${e.message}).
272
- Raw data:
273
- ${ctx.bodyRaw.toString('utf-8')}
274
-
275
- `);
276
- }
277
- try {
278
- ctx.files = Request.multipartBodyFiles(ctx.bodyRaw.toString('binary'), boundary);
279
- }
280
- catch (e) {
281
- console.error(`Error parsing multipart request body files for request to ${ctx.request.url}, (${e.message}).
282
- Raw data:
283
- ${ctx.bodyRaw.toString('utf-8')}
284
-
285
- `);
286
- }
287
- }
288
- }
289
- else if (ctx.request.headers['content-type'].indexOf('application/json') > -1) {
290
- try {
291
- ctx.body = JSON.parse(ctx.bodyRaw.toString());
292
- }
293
- catch (e) {
294
- ctx.body = undefined;
295
- }
296
- }
297
- }
298
- return;
299
- }
300
- dataRaw(request) {
301
- const chunks = [];
302
- return new Promise((resolve, reject) => {
303
- request.on('data', (chunk) => {
304
- chunks.push(chunk);
305
- });
306
- request.on('close', () => {
307
- const size = chunks.reduce((prev, curr) => {
308
- return prev + curr.length;
309
- }, 0);
310
- const data = Buffer.concat(chunks, size);
311
- resolve(data);
312
- });
313
- request.on('error', (e) => {
314
- reject(e);
315
- });
316
- });
317
- }
318
- redirect(response, to, statusCode = 302) {
319
- response.setHeader('Location', to);
320
- response.writeHead(statusCode);
321
- }
322
136
  async loadHandlers(basePath) {
323
137
  let routesPath;
324
138
  if (basePath) {
@@ -348,76 +162,5 @@ export class Request {
348
162
  }
349
163
  }
350
164
  }
351
- return;
352
- }
353
- static queryStringDecode(queryString, initialValue = {}, trimValues = true) {
354
- return queryStringDecode(queryString, initialValue, trimValues);
355
- }
356
- static parseBodyMultipart(bodyRaw, boundary) {
357
- const pairsRaw = bodyRaw.split(boundary);
358
- const pairs = pairsRaw.map((pair) => {
359
- const parts = pair.split(/\r?\n\r?\n/, 2).filter((part) => { return part.length > 0; });
360
- if (parts.length > 0) {
361
- const header = parts[0];
362
- const data = typeof parts[1] === 'string' ? parts[1].trim() : '';
363
- const headerParts = /Content-Disposition: form-data; name="([^\r\n"]+)"/m.exec(header);
364
- if (headerParts) {
365
- return {
366
- key: headerParts[1],
367
- value: data
368
- };
369
- }
370
- }
371
- return null;
372
- });
373
- const urlEncoded = pairs.reduce((prev, curr) => {
374
- if (curr !== null) {
375
- prev.push(`${curr.key}=${encodeURIComponent(curr.value.replaceAll('&', '%26'))}`);
376
- }
377
- return prev;
378
- }, []).join('&');
379
- return queryStringDecode(urlEncoded);
380
- }
381
- static multipartBodyFiles(bodyRaw, boundary) {
382
- let files = {};
383
- const pairsRaw = bodyRaw.split(boundary);
384
- pairsRaw.map((pair) => {
385
- const parts = /Content-Disposition: form-data; name="(.+?)"; filename="(.+?)"\r\nContent-Type: (.*)\r\n\r\n([\s\S]+)$/m.exec(pair);
386
- if (parts) {
387
- const file = {
388
- data: Buffer.from(parts[4].substring(0, parts[4].length - 2).trim(), 'binary'),
389
- fileName: parts[2],
390
- type: parts[3]
391
- };
392
- files = mergeDeep(files, queryStringDecodedSetValue(parts[1], file));
393
- }
394
- return null;
395
- });
396
- return files;
397
- }
398
- sendResponse(request, response, buffer, contentType) {
399
- const mimeType = contentType.split(';')[0];
400
- const gzipResponse = this.app.config.gzip.enabled &&
401
- this.app.config.gzip.types.includes(mimeType) &&
402
- request.headers['accept-encoding']?.includes('gzip') &&
403
- buffer.length >= this.app.config.gzip.minSize;
404
- if (!response.hasHeader('Content-Type')) {
405
- response.setHeader('Content-Type', contentType);
406
- }
407
- if (typeof buffer === 'string') {
408
- buffer = Buffer.from(buffer, 'utf-8');
409
- }
410
- if (gzipResponse) {
411
- response.setHeader('Content-Encoding', 'gzip');
412
- const compressed = zlib.gzipSync(buffer, {
413
- level: this.app.config.gzip.compressionLevel
414
- });
415
- response.setHeader('Content-Length', compressed.length);
416
- response.write(compressed);
417
- }
418
- else {
419
- response.setHeader('Content-Length', buffer.length);
420
- response.write(buffer);
421
- }
422
165
  }
423
166
  }