diff --git a/packages/server/package.json b/packages/server/package.json index e4594bfd..ab014af8 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -17,6 +17,7 @@ "@oclif/errors": "^1.2.2", "@oclif/plugin-help": "^2.1.4", "express": "^4.16.4", + "mime-types": "^2.1.21", "nexe": "^2.0.0-rc.34", "node-pty": "^0.8.1", "spdlog": "^0.7.2", @@ -24,6 +25,7 @@ }, "devDependencies": { "@types/express": "^4.16.0", + "@types/mime-types": "^2.1.0", "@types/ws": "^6.0.1", "string-replace-webpack-plugin": "^0.1.3", "ts-node": "^7.0.1", diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index 7fb04ffe..29271ce5 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -4,8 +4,11 @@ import { Server, ServerOptions } from "@coder/protocol/src/node/server"; import { NewSessionMessage } from '@coder/protocol/src/proto'; import { ChildProcess } from "child_process"; import * as express from "express"; +import * as fs from "fs"; import * as http from "http"; +import * as mime from "mime-types"; import * as path from "path"; +import * as util from "util"; import * as ws from "ws"; import { forkModule } from "./vscode/bootstrapFork"; @@ -63,6 +66,43 @@ export const createApp = (registerMiddleware?: (app: express.Application) => voi app.use(express.static(path.join(__dirname, "../build/web"))); + app.get("/resource/:url(*)", async (req, res) => { + try { + const fullPath = `/${req.params.url}`; + const relative = path.relative(options!.dataDirectory, fullPath); + if (relative.startsWith("..")) { + return res.status(403).end(); + } + const exists = await util.promisify(fs.exists)(fullPath); + if (!exists) { + res.status(404).end(); + return; + } + const stat = await util.promisify(fs.stat)(fullPath); + if (!stat.isFile()) { + res.write("Resource must be a file."); + res.status(422); + res.end(); + + return; + } + let mimeType = mime.lookup(fullPath); + if (mimeType === false) { + mimeType = "application/octet-stream"; + } + const content = await util.promisify(fs.readFile)(fullPath); + + res.header("Content-Type", mimeType as string); + res.write(content); + res.status(200); + res.end(); + } catch (ex) { + res.write(ex.toString()); + res.status(500); + res.end(); + } + }); + return { express: app, server, diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index 88cf64ee..4825a8be 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -107,6 +107,11 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/mime-types@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.0.tgz#9ca52cda363f699c69466c2a6ccdaad913ea7a73" + integrity sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM= + "@types/mime@*": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.0.tgz#5a7306e367c539b9f6543499de8dd519fac37a8b" @@ -2224,7 +2229,7 @@ mime-db@^1.28.0, mime-db@~1.37.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg== -mime-types@^2.1.12, mime-types@~2.1.18, mime-types@~2.1.19: +mime-types@^2.1.12, mime-types@^2.1.21, mime-types@~2.1.18, mime-types@~2.1.19: version "2.1.21" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==