Path: blob/master/src/packages/next/lib/share/get-contents.ts
1450 views
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import pathToFiles from "./path-to-files";6import { promises as fs } from "fs";7import { join } from "path";8import { sortBy } from "lodash";9import { hasSpecialViewer } from "@cocalc/frontend/file-extensions";10import { getExtension } from "./util";1112const MB: number = 1000000;1314const LISTED_LIMITS = {15listing: 3000, // directory listing is truncated after this many files16ipynb: 7 * MB,17sagews: 5 * MB,18whiteboard: 3 * MB,19slides: 3 * MB,20other: 1 * MB,21html: 3 * MB,22// no special viewer23generic: 2 * MB,24};2526const UNLISTED_LIMITS = {27...LISTED_LIMITS,28ipynb: 15 * MB,29sagews: 10 * MB,30whiteboard: 10 * MB,31slides: 10 * MB,32other: 5 * MB,33html: 40 * MB, // E.g., cambridge: https://cocalc.com/Cambridge/S002211202200903X/S002211202200903X-Figure-4/files/Figure4.html3435// no special viewer36generic: 10 * MB,37};3839// also used for proxied content -- see https://github.com/sagemathinc/cocalc/issues/802040export function getSizeLimit(path: string, unlisted: boolean = false): number {41const LIMITS = unlisted ? UNLISTED_LIMITS : LISTED_LIMITS;42const ext = getExtension(path);43if (hasSpecialViewer(ext)) {44return LIMITS[ext] ?? LIMITS.other;45}46return LIMITS.generic;47}4849export interface FileInfo {50name: string;51error?: Error;52isdir?: boolean;53size?: number;54mtime?: number;55url?: 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).56}5758export interface PathContents {59isdir?: boolean;60listing?: FileInfo[];61content?: string;62size?: number;63mtime?: number;64truncated?: string;65}6667export default async function getContents(68project_id: string,69path: string,70unlisted?: boolean, // if true, higher size limits, since much less likely to be abused71): Promise<PathContents> {72const fsPath = pathToFiles(project_id, path);73const obj: PathContents = {};7475// use lstat instead of stat so it works on symlinks too76const stats = await fs.lstat(fsPath);77obj.isdir = stats.isDirectory();78obj.mtime = stats.mtime.valueOf();79if (obj.isdir) {80// get listing81const { listing, truncated } = await getDirectoryListing(fsPath);82obj.listing = listing;83if (truncated) {84obj.truncated = truncated;85}86} else {87// get actual file content88if (stats.size >= getSizeLimit(fsPath, unlisted)) {89obj.truncated = "File too big to be displayed; download it instead.";90} else {91obj.content = (await fs.readFile(fsPath)).toString();92}93obj.size = stats.size;94}95return obj;96}9798async function getDirectoryListing(99path: string,100): Promise<{ listing: FileInfo[]; truncated?: string }> {101const listing: FileInfo[] = [];102let truncated: string | undefined = undefined;103for (const name of await fs.readdir(path)) {104if (name.startsWith(".")) {105// We never grab hidden files. This is a public share server after all.106continue;107}108const obj: FileInfo = { name };109// use lstat instead of stat so it works on symlinks too110try {111const stats = await fs.lstat(join(path, name));112if (stats.isDirectory()) {113obj.isdir = true;114// For a directory, we define "size" to be the number of items115// in the directory.116obj.size = (await fs.readdir(join(path, name))).length;117} else {118obj.size = stats.size;119}120obj.mtime = stats.mtime.valueOf();121} catch (err) {122obj.error = err;123}124listing.push(obj);125if (listing.length >= LISTED_LIMITS.listing) {126truncated = `Too many files -- only showing ${LISTED_LIMITS.listing} of them.`;127break;128}129}130return { listing: sortBy(listing, ["name"]), truncated };131}132133134