Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/next/lib/share/get-contents.ts
1450 views
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import pathToFiles from "./path-to-files";
7
import { promises as fs } from "fs";
8
import { join } from "path";
9
import { sortBy } from "lodash";
10
import { hasSpecialViewer } from "@cocalc/frontend/file-extensions";
11
import { getExtension } from "./util";
12
13
const MB: number = 1000000;
14
15
const LISTED_LIMITS = {
16
listing: 3000, // directory listing is truncated after this many files
17
ipynb: 7 * MB,
18
sagews: 5 * MB,
19
whiteboard: 3 * MB,
20
slides: 3 * MB,
21
other: 1 * MB,
22
html: 3 * MB,
23
// no special viewer
24
generic: 2 * MB,
25
};
26
27
const UNLISTED_LIMITS = {
28
...LISTED_LIMITS,
29
ipynb: 15 * MB,
30
sagews: 10 * MB,
31
whiteboard: 10 * MB,
32
slides: 10 * MB,
33
other: 5 * MB,
34
html: 40 * MB, // E.g., cambridge: https://cocalc.com/Cambridge/S002211202200903X/S002211202200903X-Figure-4/files/Figure4.html
35
36
// no special viewer
37
generic: 10 * MB,
38
};
39
40
// also used for proxied content -- see https://github.com/sagemathinc/cocalc/issues/8020
41
export function getSizeLimit(path: string, unlisted: boolean = false): number {
42
const LIMITS = unlisted ? UNLISTED_LIMITS : LISTED_LIMITS;
43
const ext = getExtension(path);
44
if (hasSpecialViewer(ext)) {
45
return LIMITS[ext] ?? LIMITS.other;
46
}
47
return LIMITS.generic;
48
}
49
50
export interface FileInfo {
51
name: string;
52
error?: Error;
53
isdir?: boolean;
54
size?: number;
55
mtime?: number;
56
url?: string; // if given and click on this file, goes here. Can be used to make path canonical and is used for navigating github repos (say).
57
}
58
59
export interface PathContents {
60
isdir?: boolean;
61
listing?: FileInfo[];
62
content?: string;
63
size?: number;
64
mtime?: number;
65
truncated?: string;
66
}
67
68
export default async function getContents(
69
project_id: string,
70
path: string,
71
unlisted?: boolean, // if true, higher size limits, since much less likely to be abused
72
): Promise<PathContents> {
73
const fsPath = pathToFiles(project_id, path);
74
const obj: PathContents = {};
75
76
// use lstat instead of stat so it works on symlinks too
77
const stats = await fs.lstat(fsPath);
78
obj.isdir = stats.isDirectory();
79
obj.mtime = stats.mtime.valueOf();
80
if (obj.isdir) {
81
// get listing
82
const { listing, truncated } = await getDirectoryListing(fsPath);
83
obj.listing = listing;
84
if (truncated) {
85
obj.truncated = truncated;
86
}
87
} else {
88
// get actual file content
89
if (stats.size >= getSizeLimit(fsPath, unlisted)) {
90
obj.truncated = "File too big to be displayed; download it instead.";
91
} else {
92
obj.content = (await fs.readFile(fsPath)).toString();
93
}
94
obj.size = stats.size;
95
}
96
return obj;
97
}
98
99
async function getDirectoryListing(
100
path: string,
101
): Promise<{ listing: FileInfo[]; truncated?: string }> {
102
const listing: FileInfo[] = [];
103
let truncated: string | undefined = undefined;
104
for (const name of await fs.readdir(path)) {
105
if (name.startsWith(".")) {
106
// We never grab hidden files. This is a public share server after all.
107
continue;
108
}
109
const obj: FileInfo = { name };
110
// use lstat instead of stat so it works on symlinks too
111
try {
112
const stats = await fs.lstat(join(path, name));
113
if (stats.isDirectory()) {
114
obj.isdir = true;
115
// For a directory, we define "size" to be the number of items
116
// in the directory.
117
obj.size = (await fs.readdir(join(path, name))).length;
118
} else {
119
obj.size = stats.size;
120
}
121
obj.mtime = stats.mtime.valueOf();
122
} catch (err) {
123
obj.error = err;
124
}
125
listing.push(obj);
126
if (listing.length >= LISTED_LIMITS.listing) {
127
truncated = `Too many files -- only showing ${LISTED_LIMITS.listing} of them.`;
128
break;
129
}
130
}
131
return { listing: sortBy(listing, ["name"]), truncated };
132
}
133
134