@cocalc/project
Version:
CoCalc: project daemon
125 lines • 5.32 kB
JavaScript
;
/*
* 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