diff --git a/package.json b/package.json index d260cf071..44bca30e2 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@coder/nbin": "^1.2.0", "@types/pem": "^1.9.5", "@types/safe-compare": "^1.1.0", + "@types/tar-fs": "^1.16.1", "@types/tar-stream": "^1.6.1", "nodemon": "^1.19.1" }, @@ -29,6 +30,7 @@ "httpolyglot": "^0.1.2", "pem": "^1.14.2", "safe-compare": "^1.1.4", + "tar-fs": "^2.0.0", "tar-stream": "^2.1.0" } } diff --git a/src/server.ts b/src/server.ts index f07e41f85..705a1a597 100644 --- a/src/server.ts +++ b/src/server.ts @@ -5,6 +5,7 @@ import * as https from "https"; import * as net from "net"; import * as path from "path"; import * as querystring from "querystring"; +import { Readable } from "stream"; import * as tls from "tls"; import * as url from "url"; import * as util from "util"; @@ -67,6 +68,8 @@ import { AuthType, getMediaMime, getUriTransformer, localRequire, tmpdir } from import { RemoteExtensionLogFileName } from "vs/workbench/services/remote/common/remoteAgentService"; import { IWorkbenchConstructionOptions } from "vs/workbench/workbench.web.api"; +const tarFs = localRequire("tar-fs/index"); + export enum HttpCode { Ok = 200, Redirect = 302, @@ -89,7 +92,9 @@ export interface Response { content?: string | Buffer; filePath?: string; headers?: http.OutgoingHttpHeaders; + mime?: string; redirect?: string; + stream?: Readable; } export interface LoginPayload { @@ -185,11 +190,21 @@ export abstract class Server { ): Promise; protected async getResource(...parts: string[]): Promise { + const filePath = this.ensureAuthorizedFilePath(...parts); + return { content: await util.promisify(fs.readFile)(filePath), filePath }; + } + + protected async getTarredResource(...parts: string[]): Promise { + const filePath = this.ensureAuthorizedFilePath(...parts); + return { stream: tarFs.pack(filePath), filePath, mime: "application/tar" }; + } + + protected ensureAuthorizedFilePath(...parts: string[]): string { const filePath = path.join(...parts); if (!this.isAllowedRequestPath(filePath)) { throw new HttpError("Unauthorized", HttpCode.Unauthorized); } - return { content: await util.promisify(fs.readFile)(filePath), filePath }; + return filePath; } protected withBase(request: http.IncomingMessage, path: string): string { @@ -211,13 +226,21 @@ export abstract class Server { const parsedUrl = request.url ? url.parse(request.url, true) : { query: {}}; const payload = await this.preHandleRequest(request, parsedUrl); response.writeHead(payload.redirect ? HttpCode.Redirect : payload.code || HttpCode.Ok, { - "Content-Type": getMediaMime(payload.filePath), + "Content-Type": payload.mime || getMediaMime(payload.filePath), ...(payload.redirect ? { Location: this.withBase(request, payload.redirect) } : {}), ...(request.headers["service-worker"] ? { "Service-Worker-Allowed": this.options.basePath || "/" } : {}), ...(payload.cache ? { "Cache-Control": "public, max-age=31536000" } : {}), ...payload.headers, }); - response.end(payload.content); + if (payload.stream) { + payload.stream.on("error", (error: NodeJS.ErrnoException) => { + response.writeHead(error.code === "ENOENT" ? HttpCode.NotFound : HttpCode.ServerError); + response.end(error.message); + }); + payload.stream.pipe(response); + } else { + response.end(payload.content); + } } catch (error) { if (error.code === "ENOENT" || error.code === "EISDIR") { error = new HttpError("Not found", HttpCode.NotFound); @@ -484,6 +507,11 @@ export class MainServer extends Server { return this.getResource(parsedUrl.query.path); } break; + case "/tar": + if (typeof parsedUrl.query.path === "string") { + return this.getTarredResource(parsedUrl.query.path); + } + break; case "/webview": if (requestPath.indexOf("/vscode-resource") === 0) { return this.getResource(requestPath.replace(/^\/vscode-resource/, "")); diff --git a/yarn.lock b/yarn.lock index 85e3ba5d1..49fcfcd5c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35,6 +35,13 @@ resolved "https://registry.yarnpkg.com/@types/safe-compare/-/safe-compare-1.1.0.tgz#47ed9b9ca51a3a791b431cd59b28f47fa9bf1224" integrity sha512-1ri+LJhh0gRxIa37IpGytdaW7yDEHeJniBSMD1BmitS07R1j63brcYCzry+l0WJvGdEKQNQ7DYXO2epgborWPw== +"@types/tar-fs@^1.16.1": + version "1.16.1" + resolved "https://registry.yarnpkg.com/@types/tar-fs/-/tar-fs-1.16.1.tgz#6e3fba276c173e365ae91e55f7b797a0e64298e5" + integrity sha512-uQQIaa8ukcKf/1yy2kzfP1PF+7jEZghFDKpDvgtsYo/mbqM1g4Qza1Y5oAw6kJMa7eLA/HkmxUsDqb2sWKVF9g== + dependencies: + "@types/node" "*" + "@types/tar-stream@^1.6.1": version "1.6.1" resolved "https://registry.yarnpkg.com/@types/tar-stream/-/tar-stream-1.6.1.tgz#67d759068ff781d976cad978893bb7a334ec8809" @@ -480,7 +487,7 @@ duplexer3@^0.1.4: resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= -end-of-stream@^1.4.1: +end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== @@ -1293,7 +1300,7 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" -once@^1.3.0, once@^1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= @@ -1417,6 +1424,14 @@ pstree.remy@^1.1.6: resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.7.tgz#c76963a28047ed61542dc361aa26ee55a7fa15f3" integrity sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A== +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" @@ -1744,7 +1759,17 @@ supports-color@^5.2.0, supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -tar-stream@^2.1.0: +tar-fs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.0.tgz#677700fc0c8b337a78bee3623fdc235f21d7afad" + integrity sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA== + dependencies: + chownr "^1.1.1" + mkdirp "^0.5.1" + pump "^3.0.0" + tar-stream "^2.0.0" + +tar-stream@^2.0.0, tar-stream@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3" integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw==