const MAX_FILE_SIZE_MB = 10000 ; import { Router } from "express" ; import { appendFile , mkdir , copyFile , rename , readFile , unlink } from "fs" ; import { join } from "path" ; import { IncomingForm } from "formidable" ; import { callback } from "awaiting" ; const { ensure_containing_directory_exists , } = require ( "@cocalc/backend/misc_node" ) ; import { getLogger } from "./logger" ; export default function init ( ) { const winston = getLogger ( "upload" ) ; winston . info ( "configuring the upload endpoint" ) ; const router = Router ( ) ; router . get ( "/.smc/upload" , function ( _ , res ) { winston . http ( "upload GET" ) ; return res . send ( "hello" ) ; } ) ; router . post ( "/.smc/upload" , async function ( req , res ) : Promise < void > { function dbg ( ... m ) { winston . http ( "upload POST " , ... m ) ; } dbg ( ) ; const { dest_dir } = req . query ; if ( typeof dest_dir != "string" ) { res . status ( 500 ) . send ( "query parm dest_dir must be a string" ) ; return ; } const { HOME } = process . env ; if ( ! HOME ) { throw Error ( "HOME env var must be set" ) ; } const uploadDir = join ( HOME , dest_dir ) ; const options = { uploadDir , keepExtensions : true , maxFileSize : MAX_FILE_SIZE_MB * 1024 * 1024 , } ; const form = new IncomingForm ( options ) ; form . uploadDir = uploadDir ; try { dbg ( "ensure target path exists... " , options . uploadDir ) ; await callback ( mkdir , options . uploadDir , { recursive : true } ) ; dbg ( "parsing form data..." ) ; const { fields , files } = await callback ( form_parse , form , req ) ; if ( files . file ?. path == null || files . file ?. name == null ) { dbg ( "error parsing form data" ) ; throw Error ( "files.file.[path | name] is null" ) ; } else { dbg ( `uploading '${ files . file . name }' -> '${ files . file . path }'` ) ; } dbg ( `'${ files . file . name }' -> '${ files . file . path }' worked; ${ JSON . stringify ( fields ) }` ) ; const dest = join ( HOME , dest_dir , fields . fullPath ?? files . file . name ) ; dbg ( `dest='${ dest }'` ) ; await callback ( ensure_containing_directory_exists , dest ) ; dbg ( "append the next chunk onto the destination file..." ) ; await handle_chunk_data ( parseInt ( fields . dzchunkindex ) , parseInt ( fields . dztotalchunkcount ) , files . file . path , dest ) ; res . send ( "received upload:\n\n" ) ; } catch ( err ) { dbg ( "upload failed " , err ) ; res . status ( 500 ) . send ( `upload failed -- ${ err }` ) ; } } ) ; return router ; } async function handle_chunk_data ( index , total , chunk , dest ) { const temp = dest + ".partial-upload" ; if ( index === 0 ) { await moveFile ( chunk , temp ) ; } else { const data = await callback ( readFile , chunk ) ; await callback ( appendFile , temp , data ) ; await callback ( unlink , chunk ) ; } if ( index === total - 1 ) { await moveFile ( temp , dest ) ; } } function form_parse ( form , req , cb ) { form . parse ( req , ( err , fields , files ) => { cb ( err , { fields , files } ) ; } ) ; } async function moveFile ( src , dest ) { try { await callback ( rename , src , dest ) ; } catch ( _ ) { await callback ( copyFile , src , dest ) ; await callback ( unlink , src ) ; } }