@@ -327,6 +327,9 @@ export function getActionContext(context: APIContext): AstroActionContext {
327327 try {
328328 input = await parseRequestBody ( context . request ) ;
329329 } catch ( e ) {
330+ if ( e instanceof ActionError ) {
331+ return { data : undefined , error : e } ;
332+ }
330333 if ( e instanceof TypeError ) {
331334 return { data : undefined , error : new ActionError ( { code : 'UNSUPPORTED_MEDIA_TYPE' } ) } ;
332335 }
@@ -378,16 +381,75 @@ function getCallerInfo(ctx: APIContext) {
378381 return undefined ;
379382}
380383
384+ const DEFAULT_ACTION_BODY_SIZE_LIMIT = 1024 * 1024 ;
385+
381386async function parseRequestBody ( request : Request ) {
382387 const contentType = request . headers . get ( 'content-type' ) ;
383- const contentLength = request . headers . get ( 'Content-Length' ) ;
388+ const contentLengthHeader = request . headers . get ( 'content-length' ) ;
389+ const contentLength = contentLengthHeader ? Number . parseInt ( contentLengthHeader , 10 ) : undefined ;
390+ const hasContentLength = typeof contentLength === 'number' && Number . isFinite ( contentLength ) ;
384391
385392 if ( ! contentType ) return undefined ;
393+ if ( hasContentLength && contentLength > DEFAULT_ACTION_BODY_SIZE_LIMIT ) {
394+ throw new ActionError ( {
395+ code : 'CONTENT_TOO_LARGE' ,
396+ message : `Request body exceeds ${ DEFAULT_ACTION_BODY_SIZE_LIMIT } bytes` ,
397+ } ) ;
398+ }
386399 if ( hasContentType ( contentType , formContentTypes ) ) {
400+ if ( ! hasContentLength ) {
401+ const body = await readRequestBodyWithLimit ( request . clone ( ) , DEFAULT_ACTION_BODY_SIZE_LIMIT ) ;
402+ const formRequest = new Request ( request . url , {
403+ method : request . method ,
404+ headers : request . headers ,
405+ body : toArrayBuffer ( body ) ,
406+ } ) ;
407+ return await formRequest . formData ( ) ;
408+ }
387409 return await request . clone ( ) . formData ( ) ;
388410 }
389411 if ( hasContentType ( contentType , [ 'application/json' ] ) ) {
390- return contentLength === '0' ? undefined : await request . clone ( ) . json ( ) ;
412+ if ( contentLength === 0 ) return undefined ;
413+ if ( ! hasContentLength ) {
414+ const body = await readRequestBodyWithLimit ( request . clone ( ) , DEFAULT_ACTION_BODY_SIZE_LIMIT ) ;
415+ if ( body . byteLength === 0 ) return undefined ;
416+ return JSON . parse ( new TextDecoder ( ) . decode ( body ) ) ;
417+ }
418+ return await request . clone ( ) . json ( ) ;
391419 }
392420 throw new TypeError ( 'Unsupported content type' ) ;
393421}
422+
423+ async function readRequestBodyWithLimit ( request : Request , limit : number ) : Promise < Uint8Array > {
424+ if ( ! request . body ) return new Uint8Array ( ) ;
425+ const reader = request . body . getReader ( ) ;
426+ const chunks : Uint8Array [ ] = [ ] ;
427+ let received = 0 ;
428+ while ( true ) {
429+ const { done, value } = await reader . read ( ) ;
430+ if ( done ) break ;
431+ if ( value ) {
432+ received += value . byteLength ;
433+ if ( received > limit ) {
434+ throw new ActionError ( {
435+ code : 'CONTENT_TOO_LARGE' ,
436+ message : `Request body exceeds ${ limit } bytes` ,
437+ } ) ;
438+ }
439+ chunks . push ( value ) ;
440+ }
441+ }
442+ const buffer = new Uint8Array ( received ) ;
443+ let offset = 0 ;
444+ for ( const chunk of chunks ) {
445+ buffer . set ( chunk , offset ) ;
446+ offset += chunk . byteLength ;
447+ }
448+ return buffer ;
449+ }
450+
451+ function toArrayBuffer ( buffer : Uint8Array ) : ArrayBuffer {
452+ const copy = new Uint8Array ( buffer . byteLength ) ;
453+ copy . set ( buffer ) ;
454+ return copy . buffer ;
455+ }
0 commit comments