UNPKG

6.93 kBJavaScriptView Raw
1"use strict";
2/*
3 * @adonisjs/bodyparser
4 *
5 * (c) Harminder Virk <virk@adonisjs.com>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10Object.defineProperty(exports, "__esModule", { value: true });
11exports.PartHandler = void 0;
12/// <reference path="../../adonis-typings/index.ts" />
13const path_1 = require("path");
14const utils_1 = require("@poppinss/utils");
15const File_1 = require("./File");
16const utils_2 = require("../utils");
17/**
18 * Part handler handles the progress of a stream and also internally validates
19 * it's size and extension.
20 *
21 * This class offloads the task of validating a file stream, regardless of how
22 * the stream is consumed. For example:
23 *
24 * In classic scanerio, we will process the file stream and write files to the
25 * tmp directory and in more advanced cases, the end user can handle the
26 * stream by themselves and report each chunk to this class.
27 */
28class PartHandler {
29 constructor(part, options, drive) {
30 this.part = part;
31 this.options = options;
32 this.drive = drive;
33 /**
34 * A boolean to know if we can use the magic number to detect the file type. This is how it
35 * works.
36 *
37 * - We begin by extracting the file extension from the file name
38 * - If the extension is something we support via magic numbers, then we ignore the extension
39 * and inspect the buffer
40 * - Otherwise, we have no other option than to trust the extension
41 *
42 * Think of this as using the optimal way for validating the file type
43 */
44 this.canFileTypeBeDetected = utils_2.supportMagicFileTypes.includes((0, path_1.extname)(this.part.filename).replace(/^\./, ''));
45 /**
46 * Creating a new file object for each part inside the multipart
47 * form data
48 */
49 this.file = new File_1.File({
50 clientName: this.part.filename,
51 fieldName: this.part.name,
52 headers: this.part.headers,
53 }, {
54 size: this.options.size,
55 extnames: this.options.extnames,
56 }, this.drive);
57 /**
58 * A boolean to know, if we have emitted the error event after one or
59 * more validation errors. We need this flag, since the race conditions
60 * between `data` and `error` events will trigger multiple `error`
61 * emit.
62 */
63 this.emittedValidationError = false;
64 }
65 /**
66 * Detects the file type and extension and also validates it when validations
67 * are not deferred.
68 */
69 async detectFileTypeAndExtension(force) {
70 if (!this.buff) {
71 return;
72 }
73 const fileType = await (0, utils_2.getFileType)(this.buff, this.file.clientName, this.file.headers, force);
74 if (fileType) {
75 this.file.extname = fileType.ext;
76 this.file.type = fileType.type;
77 this.file.subtype = fileType.subtype;
78 }
79 }
80 /**
81 * Skip the stream or end it forcefully. This is invoked when the
82 * streaming consumer reports an error
83 */
84 skipEndStream() {
85 this.part.emit('close');
86 }
87 /**
88 * Finish the process of listening for any more events and mark the
89 * file state as consumed.
90 */
91 finish() {
92 this.file.state = 'consumed';
93 if (!this.options.deferValidations) {
94 this.file.validate();
95 }
96 }
97 /**
98 * Start the process the updating the file state
99 * to streaming mode.
100 */
101 begin() {
102 this.file.state = 'streaming';
103 }
104 /**
105 * Handles the file upload progress by validating the file size and
106 * extension.
107 */
108 async reportProgress(line, bufferLength) {
109 /**
110 * Do not consume stream data when file state is not `streaming`. Stream
111 * events race conditions may emit the `data` event after the `error`
112 * event in some cases, so we have to restrict it here.
113 */
114 if (this.file.state !== 'streaming') {
115 return;
116 }
117 /**
118 * Detect the file type and extension when extname is null, otherwise
119 * empty out the buffer. We only need the buffer to find the
120 * file extension from it's content.
121 */
122 if (this.file.extname === undefined) {
123 this.buff = this.buff ? Buffer.concat([this.buff, line]) : line;
124 await this.detectFileTypeAndExtension(false);
125 }
126 else {
127 this.buff = undefined;
128 }
129 /**
130 * The length of stream buffer
131 */
132 this.file.size = this.file.size + bufferLength;
133 /**
134 * Validate the file on every chunk, unless validations have been deferred.
135 */
136 if (this.options.deferValidations) {
137 return;
138 }
139 /**
140 * Attempt to validate the file after every chunk and report error
141 * when it has one or more failures. After this the consumer must
142 * call `reportError`.
143 */
144 this.file.validate();
145 if (!this.file.isValid && !this.emittedValidationError) {
146 this.emittedValidationError = true;
147 this.part.emit('error', new utils_1.Exception('one or more validations failed', 400, 'E_STREAM_VALIDATION_FAILURE'));
148 }
149 }
150 /**
151 * Report errors encountered while processing the stream. These can be errors
152 * apart from the one reported by this class. For example: The `s3` failure
153 * due to some bad credentails.
154 */
155 async reportError(error) {
156 if (this.file.state !== 'streaming') {
157 return;
158 }
159 this.skipEndStream();
160 this.finish();
161 if (error.code === 'E_STREAM_VALIDATION_FAILURE') {
162 return;
163 }
164 /**
165 * Push to the array of file errors
166 */
167 this.file.errors.push({
168 fieldName: this.file.fieldName,
169 clientName: this.file.clientName,
170 type: 'fatal',
171 message: error.message,
172 });
173 }
174 /**
175 * Report success data about the file.
176 */
177 async reportSuccess(data) {
178 if (this.file.state !== 'streaming') {
179 return;
180 }
181 /**
182 * Re-attempt to detect the file extension after we are done
183 * consuming the stream
184 */
185 if (this.file.extname === undefined) {
186 await this.detectFileTypeAndExtension(this.canFileTypeBeDetected ? false : true);
187 }
188 if (data) {
189 const { filePath, tmpPath, ...meta } = data;
190 if (filePath) {
191 this.file.filePath = filePath;
192 }
193 if (tmpPath) {
194 this.file.tmpPath = tmpPath;
195 }
196 this.file.meta = meta || {};
197 }
198 this.finish();
199 }
200}
201exports.PartHandler = PartHandler;