Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/next/lib/share/handle-raw.ts
Views: 687
/*1This handles request to share/raw/[sha1]/[relative path].23It confirms that the request is valid (so the content is4actually currently publicly shared) then sends the result.5*/67import type { Request, Response } from "express";8import { static as ExpressStatic } from "express";9import LRU from "lru-cache";10import ms from "ms";11import { join } from "path";12import DirectoryListing from "serve-index";1314import { pathFromID } from "./path-to-files";15import { getExtension, isSha1Hash } from "./util";1617const MAX_AGE = Math.round(ms("15 minutes") / 1000);1819interface Options {20id: string;21path: string;22res: Response;23req: Request;24download?: boolean; // if true, cause download25next: (value?) => void;26}2728export default async function handle(options: Options): Promise<void> {29try {30await handleRequest(options);31} catch (err) {32// some other error33options.res.send(`Error: ${err}`);34}35}3637async function handleRequest(opts: Options): Promise<void> {38const {39id, // id of a public_path40path: pathEncoded, // full path in the project to requested file or directory41req,42res,43download,44next,45} = opts;46res.setHeader("Cache-Control", `public, max-age=${MAX_AGE}`);4748if (!isSha1Hash(id)) {49throw Error(`id=${id} is not a sha1 hash`);50}5152// store the URI decoded string from pathEncoded in path53// BUGFIX: https://github.com/sagemathinc/cocalc/issues/592854// This does not work with file names containing a percent sign, because next.js itself does decode the path as well.55const path = decodeURIComponent(pathEncoded);5657// the above must come before this check (since dots could be somehow encoded)58if (path.includes("..")) {59throw Error(`path (="${path}") must not include ".."`);60}6162let { fsPath, projectPath } = await pathFromID(id);6364if (!path.startsWith(projectPath)) {65// The projectPath absolutely must be an initial segment of the requested path.66// We do NOT just use a relative path *inside* the share, because the share might be a file itself67// and then the MIME type wouldn't be a function of the URL.68throw Error(`path (="${path}") must start with "${projectPath}"`);69}7071let url = path.slice(projectPath.length);72const target = join(fsPath, url);7374const ext = getExtension(target);75if (download || ext == "html" || ext == "svg") {76// NOTE: We *always* download .html, since it is far too dangerous to render77// an arbitrary html file from our domain.78res.download(target, next);79return;80}8182if (!url) {83const i = fsPath.lastIndexOf("/");84if (i == -1) {85// This can't actually happen, since fsPath is an absolute filesystem path, hence starts with /86throw Error(`invalid fsPath=${fsPath}`);87}88url = fsPath.slice(i);89fsPath = fsPath.slice(0, i);90}9192req.url = url;93staticHandler(fsPath, req, res, next);94}9596export function staticHandler(97fsPath: string,98req: Request,99res: Response,100next: Function,101) {102// console.log("staticHandler", { fsPath, url: req.url });103const handler = getStaticFileHandler(fsPath);104// @ts-ignore -- TODO105handler(req, res, () => {106// Static handler didn't work, so try the directory listing handler.107//console.log("directoryHandler", { fsPath, url: req.url });108const handler = getDirectoryHandler(fsPath);109try {110handler(req, res, next);111} catch (err) {112// I noticed in logs that if reeq.url is malformed then this directory listing handler --113// which is just some old middleware not updated in 6+ years -- can throw an exception114// which is not caught. So we catch it here and respond with some sort of generic115// server error, but without crashing the server.116// Respond with a 500 Internal Server Error status code.117if (!res.headersSent) {118res119.status(500)120.send(121`Something went wrong on the server, please try again later. -- ${err}`,122);123} else {124// In case headers were already sent, end the response without sending any data.125res.end();126}127}128});129}130131const staticFileCache = new LRU<string, ReturnType<typeof ExpressStatic>>({132max: 200,133});134function getStaticFileHandler(path: string): ReturnType<typeof ExpressStatic> {135const sfh = staticFileCache.get(path);136if (sfh) {137return sfh;138}139const handler = ExpressStatic(path);140staticFileCache.set(path, handler);141return handler;142}143144const directoryCache = new LRU<string, ReturnType<typeof DirectoryListing>>({145max: 200,146});147function getDirectoryHandler(148path: string,149): ReturnType<typeof DirectoryListing> {150const dh = directoryCache.get(path);151if (dh) {152return dh;153}154const handler = DirectoryListing(path, { icons: true, view: "details" });155directoryCache.set(path, handler);156return handler;157}158159160