@@ -3,8 +3,11 @@ import EventEmitter from 'node:events'
33import type { ServerOptions } from '../types'
44import type { DataStore , CancellationContext } from '../models'
55import type http from 'node:http'
6- import stream from 'node:stream '
6+ import { Upload } from '../models '
77import { ERRORS } from '../constants'
8+ import stream from 'node:stream/promises'
9+ import { addAbortSignal , PassThrough } from 'stream'
10+ import { StreamLimiter } from '../models/StreamLimiter'
811
912const reExtractFileID = / ( [ ^ / ] + ) \/ ? $ /
1013const reForwardedHost = / h o s t = " ? ( [ ^ " ; ] + ) /
@@ -132,24 +135,24 @@ export class BaseHandler extends EventEmitter {
132135 req : http . IncomingMessage ,
133136 id : string ,
134137 offset : number ,
138+ maxFileSize : number ,
135139 context : CancellationContext
136140 ) {
137141 return new Promise < number > ( async ( resolve , reject ) => {
142+ // Abort early if the operation has been cancelled.
138143 if ( context . signal . aborted ) {
139144 reject ( ERRORS . ABORTED )
140145 return
141146 }
142147
143- const proxy = new stream . PassThrough ( )
144- stream . addAbortSignal ( context . signal , proxy )
148+ // Create a PassThrough stream as a proxy to manage the request stream.
149+ // This allows for aborting the write process without affecting the incoming request stream.
150+ const proxy = new PassThrough ( )
151+ addAbortSignal ( context . signal , proxy )
145152
146153 proxy . on ( 'error' , ( err ) => {
147154 req . unpipe ( proxy )
148- if ( err . name === 'AbortError' ) {
149- reject ( ERRORS . ABORTED )
150- } else {
151- reject ( err )
152- }
155+ reject ( err . name === 'AbortError' ? ERRORS . ABORTED : err )
153156 } )
154157
155158 req . on ( 'error' , ( err ) => {
@@ -158,7 +161,71 @@ export class BaseHandler extends EventEmitter {
158161 }
159162 } )
160163
161- this . store . write ( req . pipe ( proxy ) , id , offset ) . then ( resolve ) . catch ( reject )
164+ // Pipe the request stream through the proxy. We use the proxy instead of the request stream directly
165+ // to ensure that errors in the pipeline do not cause the request stream to be destroyed,
166+ // which would result in a socket hangup error for the client.
167+ stream
168+ . pipeline ( req . pipe ( proxy ) , new StreamLimiter ( maxFileSize ) , async ( stream ) => {
169+ return this . store . write ( stream as StreamLimiter , id , offset )
170+ } )
171+ . then ( resolve )
172+ . catch ( reject )
162173 } )
163174 }
175+
176+ getConfiguredMaxSize ( req : http . IncomingMessage , id : string | null ) {
177+ if ( typeof this . options . maxSize === 'function' ) {
178+ return this . options . maxSize ( req , id )
179+ }
180+ return this . options . maxSize ?? 0
181+ }
182+
183+ /**
184+ * Calculates the maximum allowed size for the body of an upload request.
185+ * This function considers both the server's configured maximum size and
186+ * the specifics of the upload, such as whether the size is deferred or fixed.
187+ */
188+ async calculateMaxBodySize (
189+ req : http . IncomingMessage ,
190+ file : Upload ,
191+ configuredMaxSize ?: number
192+ ) {
193+ // Use the server-configured maximum size if it's not explicitly provided.
194+ configuredMaxSize ??= await this . getConfiguredMaxSize ( req , file . id )
195+
196+ // Parse the Content-Length header from the request (default to 0 if not set).
197+ const length = parseInt ( req . headers [ 'content-length' ] || '0' , 10 )
198+ const offset = file . offset
199+
200+ const hasContentLengthSet = req . headers [ 'content-length' ] !== undefined
201+ const hasConfiguredMaxSizeSet = configuredMaxSize > 0
202+
203+ if ( file . sizeIsDeferred ) {
204+ // For deferred size uploads, if it's not a chunked transfer, check against the configured maximum size.
205+ if (
206+ hasContentLengthSet &&
207+ hasConfiguredMaxSizeSet &&
208+ offset + length > configuredMaxSize
209+ ) {
210+ throw ERRORS . ERR_SIZE_EXCEEDED
211+ }
212+
213+ if ( hasConfiguredMaxSizeSet ) {
214+ return configuredMaxSize - offset
215+ } else {
216+ return Number . MAX_SAFE_INTEGER
217+ }
218+ }
219+
220+ // Check if the upload fits into the file's size when the size is not deferred.
221+ if ( offset + length > ( file . size || 0 ) ) {
222+ throw ERRORS . ERR_SIZE_EXCEEDED
223+ }
224+
225+ if ( hasContentLengthSet ) {
226+ return length
227+ }
228+
229+ return ( file . size || 0 ) - offset
230+ }
164231}
0 commit comments