Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/hub/proxy/file-download.ts
1710 views
1
import { readFile as readProjectFile } from "@cocalc/conat/files/read";
2
import { once } from "events";
3
import { path_split } from "@cocalc/util/misc";
4
import mime from "mime-types";
5
import getLogger from "../logger";
6
7
const DANGEROUS_CONTENT_TYPE = new Set(["image/svg+xml" /*, "text/html"*/]);
8
9
const logger = getLogger("hub:proxy:file-download");
10
11
// assumes request has already been authenticated!
12
13
export async function handleFileDownload(req, res, url, project_id) {
14
logger.debug("handling the request via conat file streaming", url);
15
const i = url.indexOf("files/");
16
const compute_server_id = req.query.id ?? 0;
17
let j = url.lastIndexOf("?");
18
if (j == -1) {
19
j = url.length;
20
}
21
const path = decodeURIComponent(url.slice(i + "files/".length, j));
22
logger.debug("conat: get file", { project_id, path, compute_server_id, url });
23
const fileName = path_split(path).tail;
24
const contentType = mime.lookup(fileName);
25
if (req.query.download != null || DANGEROUS_CONTENT_TYPE.has(contentType)) {
26
const fileNameEncoded = encodeURIComponent(fileName)
27
.replace(/['()]/g, escape)
28
.replace(/\*/g, "%2A");
29
res.setHeader(
30
"Content-disposition",
31
`attachment; filename*=UTF-8''${fileNameEncoded}`,
32
);
33
}
34
res.setHeader("Content-type", contentType);
35
36
let headersSent = false;
37
res.on("finish", () => {
38
headersSent = true;
39
});
40
try {
41
for await (const chunk of await readProjectFile({
42
project_id,
43
compute_server_id,
44
path,
45
// allow a long download time (1 hour), since files can be large and
46
// networks can be slow.
47
maxWait: 1000 * 60 * 60,
48
})) {
49
if (res.writableEnded || res.destroyed) {
50
break;
51
}
52
if (!res.write(chunk)) {
53
await once(res, "drain");
54
}
55
}
56
res.end();
57
} catch (err) {
58
logger.debug(
59
"ERROR streaming file",
60
{ project_id, compute_server_id, path },
61
err,
62
);
63
if (!headersSent) {
64
res.statusCode = 500;
65
res.end("Error reading file.");
66
} else {
67
// Data sent, forcibly kill the connection
68
res.destroy(err);
69
}
70
}
71
}
72
73