mastercontroller 1.3.1 → 1.3.3

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/MasterRequest.js CHANGED
@@ -1,7 +1,6 @@
1
1
 
2
2
  // version 0.0.2
3
3
 
4
- var master = require('./MasterControl');
5
4
  var url = require('url');
6
5
  const StringDecoder = require('string_decoder').StringDecoder;
7
6
  var qs = require('qs');
@@ -20,15 +19,30 @@ class MasterRequest{
20
19
  this.options = {};
21
20
  this.options.disableFormidableMultipartFormData = options.disableFormidableMultipartFormData === null? false : options.disableFormidableMultipartFormData;
22
21
  this.options.formidable = options.formidable === null? {}: options.formidable;
22
+ // Body size limits (DoS protection)
23
+ this.options.maxBodySize = options.maxBodySize || 10 * 1024 * 1024; // 10MB default
24
+ this.options.maxJsonSize = options.maxJsonSize || 1 * 1024 * 1024; // 1MB default for JSON
25
+ this.options.maxTextSize = options.maxTextSize || 1 * 1024 * 1024; // 1MB default for text
23
26
  }
24
27
  }
25
28
 
26
- getRequestParam(request, res){
29
+ getRequestParam(requestOrContext, res){
27
30
  var $that = this;
28
31
  $that.response = res;
29
32
  try {
30
33
  return new Promise(function (resolve, reject) {
31
- var querydata = url.parse(request.requrl, true);
34
+ // BACKWARD COMPATIBILITY: Support both old and new patterns
35
+ // New pattern (v1.3.x pipeline): Pass context with requrl property
36
+ // Old pattern (pre-v1.3.x): Pass request with requrl property
37
+ const request = requestOrContext.request || requestOrContext;
38
+ let requrl = requestOrContext.requrl || request.requrl;
39
+
40
+ // Fallback: If requrl not set, parse from request.url
41
+ if (!requrl) {
42
+ requrl = url.parse(request.url, true);
43
+ }
44
+
45
+ var querydata = url.parse(requrl, true);
32
46
  $that.parsedURL.query = querydata.query;
33
47
  $that.form = new formidable.IncomingForm($that.options.formidable);
34
48
  if(request.headers['content-type'] || request.headers['transfer-encoding'] ){
@@ -43,16 +57,25 @@ class MasterRequest{
43
57
  case "multipart/form-data" :
44
58
  // Offer operturnity to add options. find a way to add dependecy injection. to request
45
59
  if(!$that.options.disableFormidableMultipartFormData){
46
-
60
+
47
61
  $that.parsedURL.formData = {
48
62
  files : {},
49
63
  fields : {}
50
64
  };
51
65
 
66
+ // Track uploaded files for cleanup on error
67
+ const uploadedFiles = [];
68
+ let uploadAborted = false;
69
+
52
70
  $that.form.on('field', function(field, value) {
53
71
  $that.parsedURL.formData.fields[field] = value;
54
72
  });
55
73
 
74
+ $that.form.on('fileBegin', function(formname, file) {
75
+ // Track file for potential cleanup
76
+ uploadedFiles.push(file);
77
+ });
78
+
56
79
  $that.form.on('file', function(field, file) {
57
80
  file.extension = file.name === undefined ? path.extname(file.originalFilename) : path.extname(file.name);
58
81
 
@@ -65,14 +88,55 @@ class MasterRequest{
65
88
  }
66
89
  });
67
90
 
91
+ $that.form.on('error', function(err) {
92
+ // CRITICAL: Handle upload errors
93
+ uploadAborted = true;
94
+ console.error('[MasterRequest] File upload error:', err.message);
95
+
96
+ // Cleanup temporary files
97
+ uploadedFiles.forEach(file => {
98
+ if (file.filepath) {
99
+ try {
100
+ $that.deleteFileBuffer(file.filepath);
101
+ } catch (cleanupErr) {
102
+ console.error('[MasterRequest] Failed to cleanup temp file:', cleanupErr.message);
103
+ }
104
+ }
105
+ });
106
+
107
+ reject(new Error(`File upload failed: ${err.message}`));
108
+ });
109
+
110
+ $that.form.on('aborted', function() {
111
+ // CRITICAL: Handle client abort (connection closed)
112
+ uploadAborted = true;
113
+ console.warn('[MasterRequest] File upload aborted by client');
114
+
115
+ // Cleanup temporary files
116
+ uploadedFiles.forEach(file => {
117
+ if (file.filepath) {
118
+ try {
119
+ $that.deleteFileBuffer(file.filepath);
120
+ } catch (cleanupErr) {
121
+ console.error('[MasterRequest] Failed to cleanup temp file:', cleanupErr.message);
122
+ }
123
+ }
124
+ });
125
+
126
+ reject(new Error('File upload aborted by client'));
127
+ });
128
+
68
129
  $that.form.on('end', function() {
69
- resolve($that.parsedURL);
130
+ // Only resolve if upload wasn't aborted
131
+ if (!uploadAborted) {
132
+ resolve($that.parsedURL);
133
+ }
70
134
  });
71
135
 
72
136
  $that.form.parse(request);
73
-
137
+
74
138
  }else{
75
-
139
+
76
140
  resolve($that.parsedURL);
77
141
  console.log("skipped multipart/form-data")
78
142
  }
@@ -124,38 +188,59 @@ class MasterRequest{
124
188
  let body = '';
125
189
  let receivedBytes = 0;
126
190
  const maxBytes = 1 * 1024 * 1024; // 1MB limit
127
-
128
-
191
+ let errorOccurred = false;
192
+
129
193
  try {
130
194
 
131
195
  request.on('data', (chunk) => {
196
+ if (errorOccurred) return;
197
+
132
198
  receivedBytes += chunk.length;
133
-
199
+
134
200
  // Prevent memory overload
135
201
  if (receivedBytes > maxBytes) {
136
- req.destroy(); // Close the connection
202
+ errorOccurred = true;
203
+ request.destroy(); // ✅ Fixed: was 'req', now 'request'
204
+ console.error(`Plain text payload too large: ${receivedBytes} bytes (max: ${maxBytes})`);
205
+ func({ error: 'Payload too large', maxSize: maxBytes });
137
206
  return;
138
207
  }
139
-
208
+
140
209
  // Append chunk to body
141
210
  body += chunk.toString('utf8');
142
211
  });
143
-
212
+
144
213
  request.on('end', () => {
214
+ if (errorOccurred) return;
215
+
145
216
  try {
146
217
  // Process the plain text data here
147
218
  const responseData = body;
148
- func(responseData );
219
+ func(responseData);
149
220
  } catch (err) {
150
-
151
221
  console.error('Processing error handling text/plain:', err);
152
- throw err;
222
+ func({ error: err.message });
153
223
  }
154
224
 
155
225
  });
226
+
227
+ request.on('error', (err) => {
228
+ if (errorOccurred) return;
229
+ errorOccurred = true;
230
+ console.error('[MasterRequest] Stream error in fetchData:', err.message);
231
+ func({ error: err.message });
232
+ });
233
+
234
+ request.on('aborted', () => {
235
+ if (errorOccurred) return;
236
+ errorOccurred = true;
237
+ console.warn('[MasterRequest] Request aborted in fetchData');
238
+ func({ error: 'Request aborted' });
239
+ });
240
+
156
241
  } catch (error) {
157
242
  console.error("Failed to fetch data:", error);
158
- throw error;
243
+ func({ error: error.message });
159
244
  }
160
245
  }
161
246
 
@@ -170,56 +255,148 @@ class MasterRequest{
170
255
 
171
256
  urlEncodeStream(request, func){
172
257
  const decoder = new StringDecoder('utf-8');
173
- //request.pipe(decoder);
174
258
  let buffer = '';
259
+ let receivedBytes = 0;
260
+ const maxBytes = this.options.maxBodySize || 10 * 1024 * 1024; // 10MB limit
261
+ let errorOccurred = false;
262
+
175
263
  request.on('data', (chunk) => {
264
+ if (errorOccurred) return;
265
+
266
+ receivedBytes += chunk.length;
267
+
268
+ // Prevent memory overload (DoS protection)
269
+ if (receivedBytes > maxBytes) {
270
+ errorOccurred = true;
271
+ request.destroy();
272
+ console.error(`Form data too large: ${receivedBytes} bytes (max: ${maxBytes})`);
273
+ func({ error: 'Payload too large', maxSize: maxBytes });
274
+ return;
275
+ }
276
+
176
277
  buffer += decoder.write(chunk);
177
278
  });
178
279
 
179
280
  request.on('end', () => {
281
+ if (errorOccurred) return;
282
+
180
283
  buffer += decoder.end();
181
284
  var buff = qs.parse(buffer);
182
285
  func(buff);
183
286
  });
184
287
 
185
- }
288
+ request.on('error', (err) => {
289
+ if (errorOccurred) return;
290
+ errorOccurred = true;
291
+ console.error('[MasterRequest] Stream error in urlEncodeStream:', err.message);
292
+ func({ error: err.message });
293
+ });
186
294
 
187
- stringToJson(request, func){
295
+ request.on('aborted', () => {
296
+ if (errorOccurred) return;
297
+ errorOccurred = true;
298
+ console.warn('[MasterRequest] Request aborted in urlEncodeStream');
299
+ func({ error: 'Request aborted' });
300
+ });
188
301
 
189
302
  }
190
-
303
+
191
304
  jsonStream(request, func){
192
- //request.pipe(decoder);
193
305
  let buffer = '';
306
+ let receivedBytes = 0;
307
+ const maxBytes = this.options.maxJsonSize || 1 * 1024 * 1024; // 1MB limit
308
+ let errorOccurred = false;
309
+
194
310
  request.on('data', (chunk) => {
311
+ if (errorOccurred) return;
312
+
313
+ receivedBytes += chunk.length;
314
+
315
+ // Prevent memory overload (DoS protection)
316
+ if (receivedBytes > maxBytes) {
317
+ errorOccurred = true;
318
+ request.destroy();
319
+ console.error(`JSON payload too large: ${receivedBytes} bytes (max: ${maxBytes})`);
320
+ func({ error: 'JSON payload too large', maxSize: maxBytes });
321
+ return;
322
+ }
323
+
195
324
  buffer += chunk;
196
325
  });
197
326
 
198
327
  request.on('end', () => {
328
+ if (errorOccurred) return;
329
+
199
330
  try {
200
331
  var buff = JSON.parse(buffer);
201
332
  func(buff);
202
333
  } catch (e) {
203
- var buff = qs.parse(buffer);
204
- func(buff);
334
+ // Security: Don't fallback to qs.parse to avoid prototype pollution
335
+ console.error('Invalid JSON payload:', e.message);
336
+ func({ error: 'Invalid JSON', details: e.message });
205
337
  }
206
338
  });
207
339
 
340
+ request.on('error', (err) => {
341
+ if (errorOccurred) return;
342
+ errorOccurred = true;
343
+ console.error('[MasterRequest] Stream error in jsonStream:', err.message);
344
+ func({ error: err.message });
345
+ });
346
+
347
+ request.on('aborted', () => {
348
+ if (errorOccurred) return;
349
+ errorOccurred = true;
350
+ console.warn('[MasterRequest] Request aborted in jsonStream');
351
+ func({ error: 'Request aborted' });
352
+ });
353
+
208
354
  }
209
355
 
210
356
  textStream(request, func){
211
357
  const decoder = new StringDecoder('utf-8');
212
- //request.pipe(decoder);
213
358
  let buffer = '';
359
+ let receivedBytes = 0;
360
+ const maxBytes = this.options.maxTextSize || 1 * 1024 * 1024; // 1MB limit
361
+ let errorOccurred = false;
362
+
214
363
  request.on('data', (chunk) => {
364
+ if (errorOccurred) return;
365
+
366
+ receivedBytes += chunk.length;
367
+
368
+ // Prevent memory overload (DoS protection)
369
+ if (receivedBytes > maxBytes) {
370
+ errorOccurred = true;
371
+ request.destroy();
372
+ console.error(`Text payload too large: ${receivedBytes} bytes (max: ${maxBytes})`);
373
+ func({ error: 'Text payload too large', maxSize: maxBytes });
374
+ return;
375
+ }
376
+
215
377
  buffer += decoder.write(chunk);
216
378
  });
217
379
 
218
380
  request.on('end', () => {
381
+ if (errorOccurred) return;
219
382
  buffer += decoder.end();
220
383
  func(buffer);
221
384
  });
222
385
 
386
+ request.on('error', (err) => {
387
+ if (errorOccurred) return;
388
+ errorOccurred = true;
389
+ console.error('[MasterRequest] Stream error in textStream:', err.message);
390
+ func({ error: err.message });
391
+ });
392
+
393
+ request.on('aborted', () => {
394
+ if (errorOccurred) return;
395
+ errorOccurred = true;
396
+ console.warn('[MasterRequest] Request aborted in textStream');
397
+ func({ error: 'Request aborted' });
398
+ });
399
+
223
400
  }
224
401
 
225
402
  // have a clear all object that you can run that will delete all rununing objects
@@ -229,4 +406,4 @@ class MasterRequest{
229
406
  }
230
407
  }
231
408
 
232
- master.extend("request", MasterRequest);
409
+ module.exports = { MasterRequest };
package/MasterRouter.js CHANGED
@@ -1,6 +1,5 @@
1
1
  // version 0.0.250
2
2
 
3
- var master = require('./MasterControl');
4
3
  var toolClass = require('./MasterTools');
5
4
  const EventEmitter = require("events");
6
5
  var path = require('path');
@@ -564,4 +563,4 @@ class MasterRouter {
564
563
 
565
564
  }
566
565
 
567
- master.extend("router", MasterRouter);
566
+ module.exports = { MasterRouter };
package/MasterSocket.js CHANGED
@@ -1,6 +1,5 @@
1
1
  // version 0.1.2
2
2
 
3
- var master = require('./MasterControl');
4
3
  const { Server } = require('socket.io');
5
4
  const fs = require('fs');
6
5
  const path = require('path');
@@ -26,7 +25,12 @@ class MasterSocket{
26
25
  // Prefer explicit server, fallback to master.server
27
26
  const httpServer = serverOrIo || master.server;
28
27
  if (!httpServer) {
29
- throw new Error('MasterSocket.init requires an HTTP server or a pre-created Socket.IO instance');
28
+ throw new Error(
29
+ 'MasterSocket.init requires an HTTP server. ' +
30
+ 'Either pass the server explicitly: master.socket.init(server) ' +
31
+ 'or call master.start(server) before socket.init(). ' +
32
+ 'Current initialization order issue: socket.init() called before master.start()'
33
+ );
30
34
  }
31
35
  this.io = new Server(httpServer, ioOptions);
32
36
  }
@@ -134,7 +138,7 @@ class MasterSocket{
134
138
  }
135
139
  }
136
140
 
137
- master.extend("socket", MasterSocket);
141
+ module.exports = { MasterSocket };
138
142
 
139
143
  // shallow+deep merge helper
140
144
  function isObject(item) {
package/MasterTemp.js CHANGED
@@ -1,5 +1,4 @@
1
1
  // version 0.0.3
2
- var master = require('./MasterControl');
3
2
 
4
3
  class MasterTemp{
5
4
 
@@ -27,4 +26,4 @@ class MasterTemp{
27
26
 
28
27
  }
29
28
 
30
- master.extend("tempdata", MasterTemp);
29
+ module.exports = { MasterTemp };
package/MasterTimeout.js CHANGED
@@ -13,7 +13,6 @@
13
13
  * @version 1.0.0
14
14
  */
15
15
 
16
- var master = require('./MasterControl');
17
16
  const { logger } = require('./error/MasterErrorLogger');
18
17
 
19
18
  class MasterTimeout {
@@ -327,6 +326,5 @@ class MasterTimeout {
327
326
  }
328
327
  }
329
328
 
330
- master.extend("timeout", MasterTimeout);
331
-
332
- module.exports = MasterTimeout;
329
+ // Export for MasterControl to register (prevents circular dependency)
330
+ module.exports = { MasterTimeout };