@flusys/nestjs-storage 7.0.2 → 8.0.0-beta.1

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 (202) hide show
  1. package/config/index.d.ts +3 -3
  2. package/controllers/file-manager.controller.d.ts +4 -4
  3. package/controllers/folder.controller.d.ts +2 -2
  4. package/controllers/index.d.ts +4 -4
  5. package/controllers/storage-config.controller.d.ts +2 -2
  6. package/controllers/upload.controller.d.ts +3 -3
  7. package/docs/index.d.ts +1 -1
  8. package/docs/storage-swagger.config.d.ts +2 -2
  9. package/dtos/index.d.ts +4 -4
  10. package/entities/file-manager-with-company.entity.d.ts +2 -2
  11. package/entities/file-manager.entity.d.ts +1 -1
  12. package/entities/folder-with-company.entity.d.ts +1 -1
  13. package/entities/index.d.ts +15 -15
  14. package/entities/storage-config-with-company.entity.d.ts +1 -1
  15. package/enums/index.d.ts +1 -1
  16. package/fesm/493.js +2768 -0
  17. package/fesm/537.js +1157 -0
  18. package/fesm/562.js +786 -0
  19. package/fesm/{providers/azure-provider.optional.js → chunks/190.js} +33 -30
  20. package/fesm/chunks/252.js +12 -0
  21. package/fesm/chunks/327.js +12 -0
  22. package/fesm/chunks/385.js +12 -0
  23. package/fesm/{providers/s3-provider.optional.js → chunks/591.js} +37 -34
  24. package/fesm/chunks/746.js +12 -0
  25. package/fesm/chunks/798.js +12 -0
  26. package/fesm/{providers/sftp-provider.optional.js → chunks/918.js} +35 -31
  27. package/fesm/config/index.js +119 -3
  28. package/fesm/controllers/index.js +16 -4
  29. package/fesm/docs/index.js +206 -1
  30. package/fesm/dtos/index.js +879 -4
  31. package/fesm/entities/index.js +463 -18
  32. package/fesm/enums/index.js +36 -1
  33. package/fesm/index.js +398 -12
  34. package/fesm/interfaces/index.js +19 -5
  35. package/fesm/modules/index.js +16 -1
  36. package/fesm/providers/index.js +364 -3
  37. package/fesm/runtime.js +252 -0
  38. package/fesm/services/index.js +16 -6
  39. package/fesm/utils/index.js +149 -1
  40. package/index.d.ts +11 -11
  41. package/interfaces/file-manager.interface.d.ts +1 -1
  42. package/interfaces/folder.interface.d.ts +1 -1
  43. package/interfaces/index.d.ts +5 -5
  44. package/interfaces/storage-config.interface.d.ts +1 -1
  45. package/interfaces/storage-module-options.interface.d.ts +3 -3
  46. package/interfaces/storage-provider.interface.d.ts +1 -1
  47. package/middlewares/file-serve.middleware.d.ts +4 -3
  48. package/middlewares/index.d.ts +1 -1
  49. package/modules/index.d.ts +1 -1
  50. package/modules/storage.module.d.ts +2 -2
  51. package/package.json +30 -41
  52. package/providers/azure-provider.optional.d.ts +2 -2
  53. package/providers/index.d.ts +3 -3
  54. package/providers/local-provider.d.ts +2 -2
  55. package/providers/s3-provider.optional.d.ts +2 -2
  56. package/providers/sftp-provider.optional.d.ts +2 -2
  57. package/providers/storage-factory.service.d.ts +3 -3
  58. package/providers/storage-provider.registry.d.ts +2 -2
  59. package/services/file-manager.service.d.ts +9 -8
  60. package/services/folder.service.d.ts +6 -6
  61. package/services/index.d.ts +6 -6
  62. package/services/storage-config.service.d.ts +3 -3
  63. package/services/storage-datasource.provider.d.ts +3 -3
  64. package/services/storage-provider-config.service.d.ts +7 -7
  65. package/services/upload.service.d.ts +6 -6
  66. package/utils/image-compressor.util.d.ts +1 -1
  67. package/utils/index.d.ts +1 -1
  68. package/cjs/config/event-actions.js +0 -34
  69. package/cjs/config/index.js +0 -20
  70. package/cjs/config/message-keys.js +0 -46
  71. package/cjs/config/storage.constants.js +0 -28
  72. package/cjs/controllers/file-manager.controller.js +0 -152
  73. package/cjs/controllers/file-manager.controller.spec.js +0 -135
  74. package/cjs/controllers/folder.controller.js +0 -102
  75. package/cjs/controllers/folder.controller.spec.js +0 -86
  76. package/cjs/controllers/index.js +0 -21
  77. package/cjs/controllers/storage-config.controller.js +0 -102
  78. package/cjs/controllers/storage-config.controller.spec.js +0 -85
  79. package/cjs/controllers/upload.controller.js +0 -234
  80. package/cjs/controllers/upload.controller.spec.js +0 -132
  81. package/cjs/docs/index.js +0 -11
  82. package/cjs/docs/storage-swagger.config.js +0 -188
  83. package/cjs/docs/storage-swagger.config.spec.js +0 -72
  84. package/cjs/dtos/file-manager.dto.js +0 -327
  85. package/cjs/dtos/folder.dto.js +0 -105
  86. package/cjs/dtos/index.js +0 -21
  87. package/cjs/dtos/storage-config.dto.js +0 -170
  88. package/cjs/dtos/upload.dto.js +0 -272
  89. package/cjs/entities/file-manager-with-company.entity.js +0 -61
  90. package/cjs/entities/file-manager.entity.js +0 -122
  91. package/cjs/entities/folder-with-company.entity.js +0 -55
  92. package/cjs/entities/folder.entity.js +0 -64
  93. package/cjs/entities/index.js +0 -63
  94. package/cjs/entities/index.spec.js +0 -18
  95. package/cjs/entities/storage-config-with-company.entity.js +0 -51
  96. package/cjs/entities/storage-config.entity.js +0 -96
  97. package/cjs/enums/file-location.enum.js +0 -17
  98. package/cjs/enums/index.js +0 -18
  99. package/cjs/index.js +0 -29
  100. package/cjs/interfaces/file-manager.interface.js +0 -4
  101. package/cjs/interfaces/folder.interface.js +0 -4
  102. package/cjs/interfaces/index.js +0 -22
  103. package/cjs/interfaces/storage-config.interface.js +0 -4
  104. package/cjs/interfaces/storage-module-options.interface.js +0 -4
  105. package/cjs/interfaces/storage-provider.interface.js +0 -4
  106. package/cjs/middlewares/file-serve.middleware.js +0 -241
  107. package/cjs/middlewares/file-serve.middleware.spec.js +0 -211
  108. package/cjs/middlewares/index.js +0 -18
  109. package/cjs/modules/index.js +0 -18
  110. package/cjs/modules/storage.module.js +0 -165
  111. package/cjs/modules/storage.module.spec.js +0 -184
  112. package/cjs/providers/azure-provider.optional.js +0 -155
  113. package/cjs/providers/azure-provider.optional.spec.js +0 -228
  114. package/cjs/providers/index.js +0 -26
  115. package/cjs/providers/local-provider.js +0 -230
  116. package/cjs/providers/local-provider.spec.js +0 -239
  117. package/cjs/providers/s3-provider.optional.js +0 -175
  118. package/cjs/providers/s3-provider.optional.spec.js +0 -243
  119. package/cjs/providers/sftp-provider.optional.js +0 -203
  120. package/cjs/providers/sftp-provider.optional.spec.js +0 -217
  121. package/cjs/providers/storage-factory.service.js +0 -175
  122. package/cjs/providers/storage-factory.service.spec.js +0 -253
  123. package/cjs/providers/storage-provider.registry.js +0 -41
  124. package/cjs/providers/storage-provider.registry.spec.js +0 -67
  125. package/cjs/services/file-manager.service.js +0 -387
  126. package/cjs/services/file-manager.service.spec.js +0 -580
  127. package/cjs/services/folder.service.js +0 -101
  128. package/cjs/services/folder.service.spec.js +0 -167
  129. package/cjs/services/index.js +0 -23
  130. package/cjs/services/storage-config.service.js +0 -129
  131. package/cjs/services/storage-config.service.spec.js +0 -257
  132. package/cjs/services/storage-datasource.provider.js +0 -137
  133. package/cjs/services/storage-datasource.provider.spec.js +0 -217
  134. package/cjs/services/storage-provider-config.service.js +0 -171
  135. package/cjs/services/storage-provider-config.service.spec.js +0 -265
  136. package/cjs/services/upload.service.js +0 -376
  137. package/cjs/services/upload.service.spec.js +0 -426
  138. package/cjs/utils/file-validator.util.js +0 -440
  139. package/cjs/utils/file-validator.util.spec.js +0 -259
  140. package/cjs/utils/image-compressor.util.js +0 -162
  141. package/cjs/utils/image-compressor.util.spec.js +0 -166
  142. package/cjs/utils/index.js +0 -18
  143. package/fesm/config/event-actions.js +0 -13
  144. package/fesm/config/message-keys.js +0 -22
  145. package/fesm/config/storage.constants.js +0 -7
  146. package/fesm/controllers/file-manager.controller.js +0 -142
  147. package/fesm/controllers/file-manager.controller.spec.js +0 -131
  148. package/fesm/controllers/folder.controller.js +0 -92
  149. package/fesm/controllers/folder.controller.spec.js +0 -82
  150. package/fesm/controllers/storage-config.controller.js +0 -92
  151. package/fesm/controllers/storage-config.controller.spec.js +0 -81
  152. package/fesm/controllers/upload.controller.js +0 -224
  153. package/fesm/controllers/upload.controller.spec.js +0 -128
  154. package/fesm/docs/storage-swagger.config.js +0 -181
  155. package/fesm/docs/storage-swagger.config.spec.js +0 -68
  156. package/fesm/dtos/file-manager.dto.js +0 -300
  157. package/fesm/dtos/folder.dto.js +0 -84
  158. package/fesm/dtos/storage-config.dto.js +0 -149
  159. package/fesm/dtos/upload.dto.js +0 -245
  160. package/fesm/entities/file-manager-with-company.entity.js +0 -51
  161. package/fesm/entities/file-manager.entity.js +0 -112
  162. package/fesm/entities/folder-with-company.entity.js +0 -45
  163. package/fesm/entities/folder.entity.js +0 -54
  164. package/fesm/entities/index.spec.js +0 -14
  165. package/fesm/entities/storage-config-with-company.entity.js +0 -41
  166. package/fesm/entities/storage-config.entity.js +0 -86
  167. package/fesm/enums/file-location.enum.js +0 -7
  168. package/fesm/interfaces/file-manager.interface.js +0 -1
  169. package/fesm/interfaces/folder.interface.js +0 -1
  170. package/fesm/interfaces/storage-config.interface.js +0 -1
  171. package/fesm/interfaces/storage-module-options.interface.js +0 -3
  172. package/fesm/interfaces/storage-provider.interface.js +0 -4
  173. package/fesm/middlewares/file-serve.middleware.js +0 -190
  174. package/fesm/middlewares/file-serve.middleware.spec.js +0 -207
  175. package/fesm/middlewares/index.js +0 -1
  176. package/fesm/modules/storage.module.js +0 -156
  177. package/fesm/modules/storage.module.spec.js +0 -180
  178. package/fesm/providers/azure-provider.optional.spec.js +0 -224
  179. package/fesm/providers/local-provider.js +0 -182
  180. package/fesm/providers/local-provider.spec.js +0 -194
  181. package/fesm/providers/s3-provider.optional.spec.js +0 -239
  182. package/fesm/providers/sftp-provider.optional.spec.js +0 -213
  183. package/fesm/providers/storage-factory.service.js +0 -124
  184. package/fesm/providers/storage-factory.service.spec.js +0 -249
  185. package/fesm/providers/storage-provider.registry.js +0 -34
  186. package/fesm/providers/storage-provider.registry.spec.js +0 -63
  187. package/fesm/services/file-manager.service.js +0 -336
  188. package/fesm/services/file-manager.service.spec.js +0 -576
  189. package/fesm/services/folder.service.js +0 -91
  190. package/fesm/services/folder.service.spec.js +0 -163
  191. package/fesm/services/storage-config.service.js +0 -119
  192. package/fesm/services/storage-config.service.spec.js +0 -253
  193. package/fesm/services/storage-datasource.provider.js +0 -86
  194. package/fesm/services/storage-datasource.provider.spec.js +0 -172
  195. package/fesm/services/storage-provider-config.service.js +0 -161
  196. package/fesm/services/storage-provider-config.service.spec.js +0 -261
  197. package/fesm/services/upload.service.js +0 -366
  198. package/fesm/services/upload.service.spec.js +0 -422
  199. package/fesm/utils/file-validator.util.js +0 -433
  200. package/fesm/utils/file-validator.util.spec.js +0 -255
  201. package/fesm/utils/image-compressor.util.js +0 -114
  202. package/fesm/utils/image-compressor.util.spec.js +0 -162
@@ -1,440 +0,0 @@
1
- /**
2
- * Result of file content validation.
3
- */ "use strict";
4
- Object.defineProperty(exports, "__esModule", {
5
- value: true
6
- });
7
- Object.defineProperty(exports, "FileValidator", {
8
- enumerable: true,
9
- get: function() {
10
- return FileValidator;
11
- }
12
- });
13
- /**
14
- * Magic byte signatures for common file types.
15
- * Each entry maps a hex signature pattern to its MIME type.
16
- */ const MAGIC_BYTES = [
17
- // Images
18
- {
19
- signature: [
20
- 0xff,
21
- 0xd8,
22
- 0xff
23
- ],
24
- offset: 0,
25
- mimeType: 'image/jpeg'
26
- },
27
- {
28
- signature: [
29
- 0x89,
30
- 0x50,
31
- 0x4e,
32
- 0x47,
33
- 0x0d,
34
- 0x0a,
35
- 0x1a,
36
- 0x0a
37
- ],
38
- offset: 0,
39
- mimeType: 'image/png'
40
- },
41
- {
42
- signature: [
43
- 0x47,
44
- 0x49,
45
- 0x46,
46
- 0x38,
47
- 0x37,
48
- 0x61
49
- ],
50
- offset: 0,
51
- mimeType: 'image/gif'
52
- },
53
- {
54
- signature: [
55
- 0x47,
56
- 0x49,
57
- 0x46,
58
- 0x38,
59
- 0x39,
60
- 0x61
61
- ],
62
- offset: 0,
63
- mimeType: 'image/gif'
64
- },
65
- {
66
- signature: [
67
- 0x42,
68
- 0x4d
69
- ],
70
- offset: 0,
71
- mimeType: 'image/bmp'
72
- },
73
- {
74
- signature: [
75
- 0x52,
76
- 0x49,
77
- 0x46,
78
- 0x46
79
- ],
80
- offset: 0,
81
- mimeType: 'image/webp'
82
- },
83
- {
84
- signature: [
85
- 0x00,
86
- 0x00,
87
- 0x01,
88
- 0x00
89
- ],
90
- offset: 0,
91
- mimeType: 'image/x-icon'
92
- },
93
- {
94
- signature: [
95
- 0x00,
96
- 0x00,
97
- 0x02,
98
- 0x00
99
- ],
100
- offset: 0,
101
- mimeType: 'image/x-icon'
102
- },
103
- // Documents
104
- {
105
- signature: [
106
- 0x25,
107
- 0x50,
108
- 0x44,
109
- 0x46
110
- ],
111
- offset: 0,
112
- mimeType: 'application/pdf'
113
- },
114
- {
115
- signature: [
116
- 0x50,
117
- 0x4b,
118
- 0x03,
119
- 0x04
120
- ],
121
- offset: 0,
122
- mimeType: 'application/zip'
123
- },
124
- // Audio
125
- {
126
- signature: [
127
- 0x49,
128
- 0x44,
129
- 0x33
130
- ],
131
- offset: 0,
132
- mimeType: 'audio/mpeg'
133
- },
134
- {
135
- signature: [
136
- 0xff,
137
- 0xfb
138
- ],
139
- offset: 0,
140
- mimeType: 'audio/mpeg'
141
- },
142
- {
143
- signature: [
144
- 0xff,
145
- 0xfa
146
- ],
147
- offset: 0,
148
- mimeType: 'audio/mpeg'
149
- },
150
- {
151
- signature: [
152
- 0x4f,
153
- 0x67,
154
- 0x67,
155
- 0x53
156
- ],
157
- offset: 0,
158
- mimeType: 'audio/ogg'
159
- },
160
- {
161
- signature: [
162
- 0x66,
163
- 0x4c,
164
- 0x61,
165
- 0x43
166
- ],
167
- offset: 0,
168
- mimeType: 'audio/flac'
169
- },
170
- // Video
171
- {
172
- signature: [
173
- 0x00,
174
- 0x00,
175
- 0x00,
176
- 0x1c,
177
- 0x66,
178
- 0x74,
179
- 0x79,
180
- 0x70
181
- ],
182
- offset: 0,
183
- mimeType: 'video/mp4'
184
- },
185
- {
186
- signature: [
187
- 0x00,
188
- 0x00,
189
- 0x00,
190
- 0x20,
191
- 0x66,
192
- 0x74,
193
- 0x79,
194
- 0x70
195
- ],
196
- offset: 0,
197
- mimeType: 'video/mp4'
198
- },
199
- {
200
- signature: [
201
- 0x1a,
202
- 0x45,
203
- 0xdf,
204
- 0xa3
205
- ],
206
- offset: 0,
207
- mimeType: 'video/webm'
208
- },
209
- {
210
- signature: [
211
- 0x52,
212
- 0x49,
213
- 0x46,
214
- 0x46
215
- ],
216
- offset: 0,
217
- mimeType: 'video/avi'
218
- },
219
- // Archives
220
- {
221
- signature: [
222
- 0x1f,
223
- 0x8b
224
- ],
225
- offset: 0,
226
- mimeType: 'application/gzip'
227
- },
228
- {
229
- signature: [
230
- 0x37,
231
- 0x7a,
232
- 0xbc,
233
- 0xaf,
234
- 0x27,
235
- 0x1c
236
- ],
237
- offset: 0,
238
- mimeType: 'application/x-7z-compressed'
239
- },
240
- {
241
- signature: [
242
- 0x52,
243
- 0x61,
244
- 0x72,
245
- 0x21,
246
- 0x1a,
247
- 0x07
248
- ],
249
- offset: 0,
250
- mimeType: 'application/x-rar-compressed'
251
- }
252
- ];
253
- /**
254
- * MIME type aliases - types that are equivalent.
255
- */ const MIME_ALIASES = {
256
- 'image/jpeg': [
257
- 'image/jpg'
258
- ],
259
- 'image/jpg': [
260
- 'image/jpeg'
261
- ],
262
- 'video/mp4': [
263
- 'video/quicktime'
264
- ],
265
- 'application/zip': [
266
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
267
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
268
- 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
269
- ]
270
- };
271
- /**
272
- * File types that are text-based and don't have magic bytes.
273
- * SECURITY NOTE: Dangerous types (HTML, JS, SVG) are excluded as they can contain scripts.
274
- * These types require explicit allowlisting and additional content scanning.
275
- */ const TEXT_BASED_TYPES = [
276
- 'text/plain',
277
- 'text/csv',
278
- 'text/markdown',
279
- 'application/json',
280
- 'application/xml',
281
- 'application/typescript',
282
- 'text/css'
283
- ];
284
- /**
285
- * Dangerous text-based types that can execute scripts.
286
- * These bypass magic-bytes validation but require explicit allowlisting.
287
- */ const DANGEROUS_TEXT_TYPES = [
288
- 'text/html',
289
- 'application/javascript',
290
- 'text/javascript',
291
- 'image/svg+xml',
292
- 'application/xhtml+xml'
293
- ];
294
- /**
295
- * ZIP-based format prefixes that are valid when detected as application/zip.
296
- */ const ZIP_VARIANT_PREFIXES = [
297
- 'application/vnd.openxmlformats-officedocument',
298
- 'application/x-zip',
299
- 'application/x-compressed'
300
- ];
301
- let FileValidator = class FileValidator {
302
- // Result Helpers
303
- static failureResult(message, detectedType, declaredType) {
304
- return {
305
- valid: false,
306
- detectedType,
307
- declaredType,
308
- message
309
- };
310
- }
311
- static successResult(detectedType, declaredType) {
312
- return {
313
- valid: true,
314
- detectedType,
315
- declaredType
316
- };
317
- }
318
- /**
319
- * Detect file type from buffer using magic bytes.
320
- * @param buffer - File buffer to analyze
321
- * @returns Detected MIME type or null if unknown
322
- */ static detectFileType(buffer) {
323
- for (const { signature, offset, mimeType } of MAGIC_BYTES){
324
- if (buffer.length < offset + signature.length) continue;
325
- let matches = true;
326
- for(let i = 0; i < signature.length; i++){
327
- if (buffer[offset + i] !== signature[i]) {
328
- matches = false;
329
- break;
330
- }
331
- }
332
- if (matches) {
333
- return mimeType;
334
- }
335
- }
336
- return null;
337
- }
338
- /**
339
- * Check if a MIME type is text-based (doesn't have magic bytes).
340
- */ static isTextBasedType(mimeType) {
341
- return TEXT_BASED_TYPES.some((t)=>mimeType.startsWith(t) || mimeType === t);
342
- }
343
- /**
344
- * Check if a MIME type is a dangerous text type that can execute scripts.
345
- */ static isDangerousTextType(mimeType) {
346
- return DANGEROUS_TEXT_TYPES.some((t)=>mimeType === t);
347
- }
348
- /**
349
- * Check if two MIME types are compatible (exact match or aliases).
350
- */ static mimeTypesMatch(detected, declared) {
351
- // Exact match
352
- if (detected === declared) return true;
353
- // Check aliases
354
- const aliases = MIME_ALIASES[detected];
355
- if (aliases?.includes(declared)) return true;
356
- // Check reverse aliases
357
- const reverseAliases = MIME_ALIASES[declared];
358
- if (reverseAliases?.includes(detected)) return true;
359
- // Check if both are in same category (e.g., both images)
360
- const detectedCategory = detected.split('/')[0];
361
- const declaredCategory = declared.split('/')[0];
362
- // For ZIP-based formats, allow any ZIP-detected file if declared is a ZIP variant
363
- if (detected === 'application/zip' && ZIP_VARIANT_PREFIXES.some((v)=>declared.startsWith(v))) {
364
- return true;
365
- }
366
- return detectedCategory === declaredCategory;
367
- }
368
- /**
369
- * Check if a MIME type is in the allowed list.
370
- */ static isTypeAllowed(mimeType, allowedTypes) {
371
- // Wildcard allows all
372
- if (allowedTypes.includes('*/*')) return true;
373
- return allowedTypes.some((allowed)=>{
374
- // Category wildcard (e.g., "image/*")
375
- if (allowed.endsWith('/*')) {
376
- const category = allowed.slice(0, -2);
377
- return mimeType.startsWith(category);
378
- }
379
- return allowed === mimeType;
380
- });
381
- }
382
- /**
383
- * Validate file content matches declared MIME type using magic bytes.
384
- * @param buffer - File buffer
385
- * @param declaredMimeType - MIME type declared by client
386
- * @param allowedTypes - List of allowed MIME types/patterns
387
- * @returns Validation result
388
- */ static validateFileContent(buffer, declaredMimeType, allowedTypes = [
389
- '*/*'
390
- ]) {
391
- try {
392
- const detectedType = this.detectFileType(buffer);
393
- // No magic bytes detected - handle text-based types
394
- if (!detectedType) {
395
- return this.validateUndetectedType(declaredMimeType, allowedTypes);
396
- }
397
- // Verify detected type matches declared type
398
- if (!this.mimeTypesMatch(detectedType, declaredMimeType)) {
399
- return this.failureResult(`File content does not match declared type. Detected: ${detectedType}, Declared: ${declaredMimeType}`, detectedType, declaredMimeType);
400
- }
401
- // Verify type is in allowed list
402
- if (!this.isTypeAllowed(detectedType, allowedTypes)) {
403
- return this.failureResult(`File type "${detectedType}" is not allowed`, detectedType, declaredMimeType);
404
- }
405
- return this.successResult(detectedType, declaredMimeType);
406
- } catch {
407
- return this.failureResult('File validation failed');
408
- }
409
- }
410
- /**
411
- * Handle validation for files without detectable magic bytes.
412
- */ static validateUndetectedType(declaredMimeType, allowedTypes) {
413
- // Check for dangerous text types first (HTML, JS, SVG)
414
- if (this.isDangerousTextType(declaredMimeType)) {
415
- const explicitlyAllowed = allowedTypes.some((t)=>t === declaredMimeType && t !== '*/*' && !t.endsWith('/*'));
416
- if (!explicitlyAllowed) {
417
- return this.failureResult(`File type "${declaredMimeType}" is potentially dangerous and not explicitly allowed`, declaredMimeType, declaredMimeType);
418
- }
419
- }
420
- // Safe text-based files don't have magic bytes, trust declared type
421
- if (this.isTextBasedType(declaredMimeType)) {
422
- const isAllowed = this.isTypeAllowed(declaredMimeType, allowedTypes);
423
- return isAllowed ? this.successResult(declaredMimeType, declaredMimeType) : this.failureResult(`File type "${declaredMimeType}" is not allowed`, declaredMimeType, declaredMimeType);
424
- }
425
- // Binary files without recognized signatures - be cautious
426
- return this.failureResult('Unable to verify file type. File may be corrupted or unsupported.', undefined, declaredMimeType);
427
- }
428
- /**
429
- * Sanitize filename to prevent path traversal and special character issues.
430
- * @param filename - Original filename
431
- * @returns Sanitized filename
432
- */ static sanitizeFilename(filename) {
433
- return filename// Remove path components (prevent traversal)
434
- .replace(/^.*[\\\/]/, '')// Remove null bytes
435
- .replace(/\0/g, '')// Replace multiple dots with single
436
- .replace(/\.{2,}/g, '.')// Remove special characters except allowed ones
437
- .replace(/[^a-zA-Z0-9._-]/g, '_')// Limit length
438
- .substring(0, 255);
439
- }
440
- };
@@ -1,259 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", {
3
- value: true
4
- });
5
- const _filevalidatorutil = require("./file-validator.util");
6
- const JPEG_BYTES = Buffer.from([
7
- 0xff,
8
- 0xd8,
9
- 0xff,
10
- 0xe0,
11
- 0x00,
12
- 0x10
13
- ]);
14
- const PNG_BYTES = Buffer.from([
15
- 0x89,
16
- 0x50,
17
- 0x4e,
18
- 0x47,
19
- 0x0d,
20
- 0x0a,
21
- 0x1a,
22
- 0x0a,
23
- 0x00,
24
- 0x00
25
- ]);
26
- const PDF_BYTES = Buffer.from([
27
- 0x25,
28
- 0x50,
29
- 0x44,
30
- 0x46,
31
- 0x2d,
32
- 0x31,
33
- 0x2e,
34
- 0x34
35
- ]);
36
- const ZIP_BYTES = Buffer.from([
37
- 0x50,
38
- 0x4b,
39
- 0x03,
40
- 0x04,
41
- 0x00,
42
- 0x00
43
- ]);
44
- const GIF87_BYTES = Buffer.from([
45
- 0x47,
46
- 0x49,
47
- 0x46,
48
- 0x38,
49
- 0x37,
50
- 0x61
51
- ]);
52
- const UNKNOWN_BYTES = Buffer.from([
53
- 0x01,
54
- 0x02,
55
- 0x03,
56
- 0x04
57
- ]);
58
- describe('FileValidator', ()=>{
59
- describe('detectFileType', ()=>{
60
- it('detects JPEG from magic bytes', ()=>{
61
- expect(_filevalidatorutil.FileValidator.detectFileType(JPEG_BYTES)).toBe('image/jpeg');
62
- });
63
- it('detects PNG from magic bytes', ()=>{
64
- expect(_filevalidatorutil.FileValidator.detectFileType(PNG_BYTES)).toBe('image/png');
65
- });
66
- it('detects PDF from magic bytes', ()=>{
67
- expect(_filevalidatorutil.FileValidator.detectFileType(PDF_BYTES)).toBe('application/pdf');
68
- });
69
- it('detects ZIP-based formats (docx/xlsx/pptx share this signature)', ()=>{
70
- expect(_filevalidatorutil.FileValidator.detectFileType(ZIP_BYTES)).toBe('application/zip');
71
- });
72
- it('detects legacy GIF87a signature', ()=>{
73
- expect(_filevalidatorutil.FileValidator.detectFileType(GIF87_BYTES)).toBe('image/gif');
74
- });
75
- it('returns null for unknown/unrecognized signatures', ()=>{
76
- expect(_filevalidatorutil.FileValidator.detectFileType(UNKNOWN_BYTES)).toBeNull();
77
- });
78
- it('returns null for a buffer shorter than any known signature', ()=>{
79
- expect(_filevalidatorutil.FileValidator.detectFileType(Buffer.from([
80
- 0xff
81
- ]))).toBeNull();
82
- });
83
- it('returns null for an empty buffer', ()=>{
84
- expect(_filevalidatorutil.FileValidator.detectFileType(Buffer.alloc(0))).toBeNull();
85
- });
86
- });
87
- describe('isTextBasedType', ()=>{
88
- it('recognizes plain text and structured text types', ()=>{
89
- expect(_filevalidatorutil.FileValidator.isTextBasedType('text/plain')).toBe(true);
90
- expect(_filevalidatorutil.FileValidator.isTextBasedType('application/json')).toBe(true);
91
- });
92
- it('rejects a non text-based type', ()=>{
93
- expect(_filevalidatorutil.FileValidator.isTextBasedType('image/png')).toBe(false);
94
- });
95
- });
96
- describe('isDangerousTextType', ()=>{
97
- it('flags text/html as dangerous', ()=>{
98
- expect(_filevalidatorutil.FileValidator.isDangerousTextType('text/html')).toBe(true);
99
- });
100
- it('flags image/svg+xml as dangerous (can embed scripts)', ()=>{
101
- expect(_filevalidatorutil.FileValidator.isDangerousTextType('image/svg+xml')).toBe(true);
102
- });
103
- it('does not flag a safe type', ()=>{
104
- expect(_filevalidatorutil.FileValidator.isDangerousTextType('image/png')).toBe(false);
105
- });
106
- });
107
- describe('mimeTypesMatch', ()=>{
108
- it('matches identical types', ()=>{
109
- expect(_filevalidatorutil.FileValidator.mimeTypesMatch('image/png', 'image/png')).toBe(true);
110
- });
111
- it('matches known aliases (jpeg <-> jpg)', ()=>{
112
- expect(_filevalidatorutil.FileValidator.mimeTypesMatch('image/jpeg', 'image/jpg')).toBe(true);
113
- expect(_filevalidatorutil.FileValidator.mimeTypesMatch('image/jpg', 'image/jpeg')).toBe(true);
114
- });
115
- it('matches same broad category (image/* vs image/*)', ()=>{
116
- expect(_filevalidatorutil.FileValidator.mimeTypesMatch('image/png', 'image/webp')).toBe(true);
117
- });
118
- it('matches ZIP-detected content against an office document declared type', ()=>{
119
- expect(_filevalidatorutil.FileValidator.mimeTypesMatch('application/zip', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document')).toBe(true);
120
- });
121
- it('does not match unrelated categories', ()=>{
122
- expect(_filevalidatorutil.FileValidator.mimeTypesMatch('image/png', 'application/pdf')).toBe(false);
123
- });
124
- });
125
- describe('isTypeAllowed', ()=>{
126
- it('allows anything when wildcard */* is present', ()=>{
127
- expect(_filevalidatorutil.FileValidator.isTypeAllowed('application/pdf', [
128
- '*/*'
129
- ])).toBe(true);
130
- });
131
- it('allows category wildcard image/*', ()=>{
132
- expect(_filevalidatorutil.FileValidator.isTypeAllowed('image/png', [
133
- 'image/*'
134
- ])).toBe(true);
135
- });
136
- it('rejects a type outside the category wildcard', ()=>{
137
- expect(_filevalidatorutil.FileValidator.isTypeAllowed('application/pdf', [
138
- 'image/*'
139
- ])).toBe(false);
140
- });
141
- it('allows exact match', ()=>{
142
- expect(_filevalidatorutil.FileValidator.isTypeAllowed('application/pdf', [
143
- 'application/pdf'
144
- ])).toBe(true);
145
- });
146
- it('rejects when not in the explicit allow-list', ()=>{
147
- expect(_filevalidatorutil.FileValidator.isTypeAllowed('application/pdf', [
148
- 'image/png',
149
- 'image/jpeg'
150
- ])).toBe(false);
151
- });
152
- });
153
- describe('validateFileContent', ()=>{
154
- it('passes when magic bytes match declared type and type is allowed', ()=>{
155
- const result = _filevalidatorutil.FileValidator.validateFileContent(JPEG_BYTES, 'image/jpeg', [
156
- 'image/*'
157
- ]);
158
- expect(result.valid).toBe(true);
159
- expect(result.detectedType).toBe('image/jpeg');
160
- });
161
- it('fails when declared type does not match detected content (MIME spoofing)', ()=>{
162
- const result = _filevalidatorutil.FileValidator.validateFileContent(PNG_BYTES, 'application/pdf', [
163
- '*/*'
164
- ]);
165
- expect(result.valid).toBe(false);
166
- expect(result.message).toContain('does not match declared type');
167
- });
168
- it('fails when detected type is not in the allow-list', ()=>{
169
- const result = _filevalidatorutil.FileValidator.validateFileContent(PDF_BYTES, 'application/pdf', [
170
- 'image/*'
171
- ]);
172
- expect(result.valid).toBe(false);
173
- expect(result.message).toContain('not allowed');
174
- });
175
- it('trusts declared type for safe text-based content without magic bytes', ()=>{
176
- const result = _filevalidatorutil.FileValidator.validateFileContent(Buffer.from('name,age\nJohn,30'), 'text/csv', [
177
- 'text/*'
178
- ]);
179
- expect(result.valid).toBe(true);
180
- expect(result.detectedType).toBe('text/csv');
181
- });
182
- it('rejects dangerous text/html content unless explicitly allow-listed', ()=>{
183
- const result = _filevalidatorutil.FileValidator.validateFileContent(Buffer.from('<html><body>hi</body></html>'), 'text/html', [
184
- '*/*'
185
- ]);
186
- expect(result.valid).toBe(false);
187
- expect(result.message).toContain('potentially dangerous');
188
- });
189
- it('rejects image/svg+xml by default (can contain embedded scripts)', ()=>{
190
- const result = _filevalidatorutil.FileValidator.validateFileContent(Buffer.from('<svg onload="alert(1)"></svg>'), 'image/svg+xml', [
191
- '*/*'
192
- ]);
193
- expect(result.valid).toBe(false);
194
- });
195
- it('still fails text/html even when explicitly allow-listed, since it is not in TEXT_BASED_TYPES', ()=>{
196
- // NOTE: validateUndetectedType() skips the "dangerous type" rejection when the type is
197
- // explicitly allow-listed, but text/html is absent from TEXT_BASED_TYPES so it falls through
198
- // to the generic "Unable to verify file type" failure. Documenting actual behavior here;
199
- // see final report for this being flagged as a potential production logic quirk.
200
- const result = _filevalidatorutil.FileValidator.validateFileContent(Buffer.from('<html></html>'), 'text/html', [
201
- 'text/html'
202
- ]);
203
- expect(result.valid).toBe(false);
204
- expect(result.message).toContain('Unable to verify file type');
205
- });
206
- it('fails for binary content with no recognizable signature and no text fallback', ()=>{
207
- const result = _filevalidatorutil.FileValidator.validateFileContent(UNKNOWN_BYTES, 'application/octet-stream', [
208
- '*/*'
209
- ]);
210
- expect(result.valid).toBe(false);
211
- expect(result.message).toContain('Unable to verify file type');
212
- });
213
- it('defaults allowedTypes to */* when not provided', ()=>{
214
- const result = _filevalidatorutil.FileValidator.validateFileContent(JPEG_BYTES, 'image/jpeg');
215
- expect(result.valid).toBe(true);
216
- });
217
- it('returns a failure result instead of throwing on unexpected internal errors', ()=>{
218
- // Passing a non-buffer-like value to force detectFileType internals to misbehave gracefully
219
- const brokenBuffer = {
220
- length: 10
221
- };
222
- const result = _filevalidatorutil.FileValidator.validateFileContent(brokenBuffer, 'image/jpeg', [
223
- '*/*'
224
- ]);
225
- expect(result.valid).toBe(false);
226
- });
227
- });
228
- describe('sanitizeFilename', ()=>{
229
- it('strips directory components to prevent path traversal', ()=>{
230
- expect(_filevalidatorutil.FileValidator.sanitizeFilename('../../etc/passwd')).toBe('passwd');
231
- });
232
- it('strips Windows-style directory components', ()=>{
233
- expect(_filevalidatorutil.FileValidator.sanitizeFilename('..\\..\\windows\\system32\\evil.exe')).toBe('evil.exe');
234
- });
235
- it('removes null bytes', ()=>{
236
- expect(_filevalidatorutil.FileValidator.sanitizeFilename('file\0.txt')).toBe('file.txt');
237
- });
238
- it('collapses multiple consecutive dots into one', ()=>{
239
- expect(_filevalidatorutil.FileValidator.sanitizeFilename('archive....tar.gz')).toBe('archive.tar.gz');
240
- });
241
- it('replaces special/unsafe characters with underscores', ()=>{
242
- expect(_filevalidatorutil.FileValidator.sanitizeFilename('my file (final)!.txt')).toBe('my_file__final__.txt');
243
- });
244
- it('truncates filenames longer than 255 characters', ()=>{
245
- const longName = `${'a'.repeat(300)}.txt`;
246
- const sanitized = _filevalidatorutil.FileValidator.sanitizeFilename(longName);
247
- expect(sanitized.length).toBeLessThanOrEqual(255);
248
- });
249
- it('preserves a simple safe filename unchanged', ()=>{
250
- expect(_filevalidatorutil.FileValidator.sanitizeFilename('report-2024_final.pdf')).toBe('report-2024_final.pdf');
251
- });
252
- it('neutralizes an absolute-path traversal attempt embedded mid-string', ()=>{
253
- const malicious = '/var/www/../../etc/passwd';
254
- const sanitized = _filevalidatorutil.FileValidator.sanitizeFilename(malicious);
255
- expect(sanitized).not.toContain('/');
256
- expect(sanitized).not.toContain('..');
257
- });
258
- });
259
- });