UNPKG

@cocalc/project

Version:
125 lines 5.32 kB
"use strict"; /* * This file is part of CoCalc: Copyright © 2020 Sagemath, Inc. * License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); /* Upload form handler */ // This is a limit on the size of each *chunk* that the frontend sends, // not the total size of the file... const MAX_FILE_SIZE_MB = 10000; const express_1 = require("express"); const fs_1 = require("fs"); const path_1 = require("path"); const formidable_1 = require("formidable"); const awaiting_1 = require("awaiting"); const ensure_containing_directory_exists_1 = __importDefault(require("@cocalc/backend/misc/ensure-containing-directory-exists")); const logger_1 = require("./logger"); function init() { const winston = (0, logger_1.getLogger)("upload"); winston.info("configuring the upload endpoint"); const router = (0, express_1.Router)(); router.get("/.smc/upload", function (_, res) { winston.http("upload GET"); return res.send("hello"); }); router.post("/.smc/upload", async function (req, res) { function dbg(...m) { winston.http("upload POST ", ...m); } // See https://github.com/felixge/node-formidable; user uploaded a file dbg(); // See http://stackoverflow.com/questions/14022353/how-to-change-upload-path-when-use-formidable-with-express-in-node-js // Important to set maxFileSize, since the default is 200MB! // See https://stackoverflow.com/questions/13374238/how-to-limit-upload-file-size-in-express-js 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 = (0, path_1.join)(HOME, dest_dir); const options = { uploadDir, keepExtensions: true, maxFileSize: MAX_FILE_SIZE_MB * 1024 * 1024, }; const form = new formidable_1.IncomingForm(options); // Using the uploadDir option to options is broken. formidable is a mess. form.uploadDir = uploadDir; try { // ensure target path existsJS dbg("ensure target path exists... ", options.uploadDir); await (0, awaiting_1.callback)(fs_1.mkdir, options.uploadDir, { recursive: true }); dbg("parsing form data..."); const { fields, files } = await (0, awaiting_1.callback)(form_parse, form, req); // dbg(`finished parsing form data. ${JSON.stringify({ fields, files })}`); 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 = (0, path_1.join)(HOME, dest_dir, fields.fullPath ?? files.file.name); dbg(`dest='${dest}'`); await (0, ensure_containing_directory_exists_1.default)(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; } exports.default = init; async function handle_chunk_data(index, total, chunk, dest) { const temp = dest + ".partial-upload"; if (index === 0) { // move chunk to the temp file await moveFile(chunk, temp); } else { // append chunk to the temp file const data = await (0, awaiting_1.callback)(fs_1.readFile, chunk); await (0, awaiting_1.callback)(fs_1.appendFile, temp, data); await (0, awaiting_1.callback)(fs_1.unlink, chunk); } // if it's the last chunk, move temp to actual file. if (index === total - 1) { await moveFile(temp, dest); } } // Get around that form.parse returns two extra args in its callback function form_parse(form, req, cb) { form.parse(req, (err, fields, files) => { cb(err, { fields, files }); }); } async function moveFile(src, dest) { try { await (0, awaiting_1.callback)(fs_1.rename, src, dest); } catch (_) { // in some cases, e.g., cocalc-docker, this happens: // "EXDEV: cross-device link not permitted" // so we just try again the slower way. This is slightly // inefficient, maybe, but more robust than trying to determine // if we are doing a cross device rename. await (0, awaiting_1.callback)(fs_1.copyFile, src, dest); await (0, awaiting_1.callback)(fs_1.unlink, src); } } //# sourceMappingURL=upload.js.map