Skip to content

Commit fe1d609

Browse files
committed
Make it possible to request absolute paths
1 parent a20fa4a commit fe1d609

1 file changed

Lines changed: 99 additions & 63 deletions

File tree

server.ts

Lines changed: 99 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ export interface Options {
6161
CONNECTION_AUTH_TOKEN: string;
6262
}
6363

64+
export interface Response {
65+
content?: string | Buffer;
66+
code?: number;
67+
headers: http.OutgoingHttpHeaders;
68+
}
69+
6470
export class HttpError extends Error {
6571
public constructor(message: string, public readonly code: number) {
6672
super(message);
@@ -87,16 +93,26 @@ export abstract class Server {
8793
}
8894

8995
const parsedUrl = url.parse(request.url || "", true);
90-
const requestPath = parsedUrl.pathname || "/";
9196

92-
const [content, headers] = await this.handleRequest(request, parsedUrl, requestPath);
93-
response.writeHead(HttpCode.Ok, {
97+
const fullPath = decodeURIComponent(parsedUrl.pathname || "/");
98+
const match = fullPath.match(/^(\/?[^/]*)(.*)$/);
99+
const [, base, requestPath] = match
100+
? match.map((p) => p !== "/" ? p.replace(/\/$/, "") : p)
101+
: ["", "", ""];
102+
103+
const { content, headers, code } = await this.handleRequest(
104+
request, parsedUrl, base, requestPath,
105+
);
106+
response.writeHead(code || HttpCode.Ok, {
94107
"Cache-Control": "max-age=86400",
95108
// TODO: ETag?
96109
...headers,
97110
});
98111
response.end(content);
99112
} catch (error) {
113+
if (error.code === "ENOENT" || error.code === "EISDIR") {
114+
error = new HttpError("Not found", HttpCode.NotFound);
115+
}
100116
response.writeHead(typeof error.code === "number" ? error.code : 500);
101117
response.end(error.message);
102118
}
@@ -106,8 +122,9 @@ export abstract class Server {
106122
protected abstract handleRequest(
107123
request: http.IncomingMessage,
108124
parsedUrl: url.UrlWithParsedQuery,
125+
base: string,
109126
requestPath: string,
110-
): Promise<[string | Buffer, http.OutgoingHttpHeaders]>;
127+
): Promise<Response>;
111128

112129
public listen(): Promise<string> {
113130
if (!this.listenPromise) {
@@ -146,7 +163,11 @@ export class MainServer extends Server {
146163

147164
private readonly services = new ServiceCollection();
148165

149-
public constructor(port: number, private readonly webviewServer: WebviewServer, args: ParsedArgs) {
166+
public constructor(
167+
port: number,
168+
private readonly webviewServer: WebviewServer,
169+
args: ParsedArgs,
170+
) {
150171
super(port);
151172

152173
this.server.on("upgrade", async (request, socket) => {
@@ -163,7 +184,6 @@ export class MainServer extends Server {
163184
this.ipc.registerChannel("loglevel", new LogLevelSetterChannel(logService));
164185

165186
const router = new StaticRouter((context: any) => {
166-
console.log("static router", context);
167187
return context.clientId === "renderer";
168188
});
169189

@@ -194,72 +214,88 @@ export class MainServer extends Server {
194214
protected async handleRequest(
195215
request: http.IncomingMessage,
196216
parsedUrl: url.UrlWithParsedQuery,
217+
base: string,
197218
requestPath: string,
198-
): Promise<[string | Buffer, http.OutgoingHttpHeaders]> {
199-
if (requestPath === "/") {
200-
const htmlPath = path.join(
201-
this.rootPath,
202-
'out/vs/code/browser/workbench/workbench.html',
203-
);
204-
205-
let html = await util.promisify(fs.readFile)(htmlPath, "utf8");
206-
207-
const remoteAuthority = request.headers.host as string;
208-
const transformer = getUriTransformer(remoteAuthority);
209-
210-
const webviewEndpoint = await this.webviewServer.listen();
211-
212-
const cwd = process.env.VSCODE_CWD || process.cwd();
213-
const workspacePath = parsedUrl.query.workspace as string | undefined;
214-
const folderPath = !workspacePath ? parsedUrl.query.folder as string | undefined || cwd: undefined;
215-
216-
const options: Options = {
217-
WORKBENCH_WEB_CONGIGURATION: {
218-
workspaceUri: workspacePath
219-
? transformer.transformOutgoing(URI.file(sanitizeFilePath(workspacePath, cwd)))
220-
: undefined,
221-
folderUri: folderPath
222-
? transformer.transformOutgoing(URI.file(sanitizeFilePath(folderPath, cwd)))
223-
: undefined,
224-
remoteAuthority,
225-
webviewEndpoint,
226-
},
227-
REMOTE_USER_DATA_URI: transformer.transformOutgoing(
228-
(this.services.get(IEnvironmentService) as EnvironmentService).webUserDataHome,
229-
),
230-
PRODUCT_CONFIGURATION: product,
231-
CONNECTION_AUTH_TOKEN: "",
232-
};
233-
234-
Object.keys(options).forEach((key) => {
235-
html = html.replace(`"{{${key}}}"`, `'${JSON.stringify(options[key])}'`);
236-
});
219+
): Promise<Response> {
220+
switch (base) {
221+
case "/":
222+
return this.getRoot(request, parsedUrl);
223+
case "/node_modules":
224+
case "/out":
225+
return this.getResource(path.join(this.rootPath, base, requestPath));
226+
// TODO: this setup means you can't request anything from the root if it
227+
// starts with /node_modules or /out, although that's probably low risk.
228+
// There doesn't seem to be a really good way to solve this since some
229+
// resources are requested by the browser (like the extension icon) and
230+
// some by the file provider (like the extension README). Maybe add a
231+
// /resource prefix and a file provider that strips that prefix?
232+
default:
233+
return this.getResource(path.join(base, requestPath));
234+
}
235+
}
237236

238-
html = html.replace('{{WEBVIEW_ENDPOINT}}', webviewEndpoint);
237+
private async getRoot(request: http.IncomingMessage, parsedUrl: url.UrlWithParsedQuery): Promise<Response> {
238+
const htmlPath = path.join(
239+
this.rootPath,
240+
'out/vs/code/browser/workbench/workbench.html',
241+
);
239242

240-
return [html, {
243+
let content = await util.promisify(fs.readFile)(htmlPath, "utf8");
244+
245+
const remoteAuthority = request.headers.host as string;
246+
const transformer = getUriTransformer(remoteAuthority);
247+
248+
const webviewEndpoint = await this.webviewServer.listen();
249+
250+
const cwd = process.env.VSCODE_CWD || process.cwd();
251+
const workspacePath = parsedUrl.query.workspace as string | undefined;
252+
const folderPath = !workspacePath ? parsedUrl.query.folder as string | undefined || cwd: undefined;
253+
254+
const options: Options = {
255+
WORKBENCH_WEB_CONGIGURATION: {
256+
workspaceUri: workspacePath
257+
? transformer.transformOutgoing(URI.file(sanitizeFilePath(workspacePath, cwd)))
258+
: undefined,
259+
folderUri: folderPath
260+
? transformer.transformOutgoing(URI.file(sanitizeFilePath(folderPath, cwd)))
261+
: undefined,
262+
remoteAuthority,
263+
webviewEndpoint,
264+
},
265+
REMOTE_USER_DATA_URI: transformer.transformOutgoing(
266+
(this.services.get(IEnvironmentService) as EnvironmentService).webUserDataHome,
267+
),
268+
PRODUCT_CONFIGURATION: product,
269+
CONNECTION_AUTH_TOKEN: "",
270+
};
271+
272+
Object.keys(options).forEach((key) => {
273+
content = content.replace(`"{{${key}}}"`, `'${JSON.stringify(options[key])}'`);
274+
});
275+
276+
content = content.replace('{{WEBVIEW_ENDPOINT}}', webviewEndpoint);
277+
278+
return {
279+
content,
280+
headers: {
241281
"Content-Type": "text/html",
242-
}];
282+
},
243283
}
284+
}
244285

245-
try {
246-
const content = await util.promisify(fs.readFile)(
247-
path.join(this.rootPath, requestPath),
248-
);
249-
return [content, {
250-
"Content-Type": getMediaMime(requestPath) || {
286+
private async getResource(filePath: string): Promise<Response> {
287+
const content = await util.promisify(fs.readFile)(filePath);
288+
return {
289+
content,
290+
headers: {
291+
"Content-Type": getMediaMime(filePath) || {
251292
".css": "text/css",
252293
".html": "text/html",
253294
".js": "text/javascript",
254295
".json": "application/json",
255-
}[extname(requestPath)] || "text/plain",
256-
}];
257-
} catch (error) {
258-
if (error.code === "ENOENT" || error.code === "EISDIR") {
259-
throw new HttpError("Not found", HttpCode.NotFound);
260-
}
261-
throw error;
262-
}
296+
}[extname(filePath)] || "text/plain",
297+
},
298+
};
263299
}
264300

265301
private createProtocol(request: http.IncomingMessage, socket: net.Socket): Protocol {
@@ -356,7 +392,7 @@ export class MainServer extends Server {
356392
}
357393

358394
export class WebviewServer extends Server {
359-
protected async handleRequest(): Promise<[string | Buffer, http.OutgoingHttpHeaders]> {
395+
protected async handleRequest(): Promise<Response> {
360396
throw new Error("not implemented");
361397
}
362398
}

0 commit comments

Comments
 (0)