mirror of https://github.com/coder/code-server.git
TLS socket still doesn't work
This commit is contained in:
parent
a4f21fb0d4
commit
4c4a179bce
|
@ -17,7 +17,7 @@ function docker-build() {
|
|||
if [[ "${image}" == "codercom/nbin-alpine" ]] ; then
|
||||
docker exec "${containerId}" apk add libxkbfile-dev libsecret-dev
|
||||
else
|
||||
# TODO: at some point git existing but now it seems to have disappeared.
|
||||
# TODO: at some point git existed but it seems to have disappeared.
|
||||
docker exec "${containerId}" yum install -y libxkbfile-devel libsecret-devel git
|
||||
fi
|
||||
|
||||
|
|
|
@ -371,7 +371,7 @@ index 9f68b645b6..f0cae7111d 100644
|
|||
this.channel.call('setLevel', level);
|
||||
}
|
||||
diff --git a/src/vs/platform/remote/browser/browserWebSocketFactory.ts b/src/vs/platform/remote/browser/browserWebSocketFactory.ts
|
||||
index 6d9ecbcf5a..1ebd5a4b84 100644
|
||||
index 6d9ecbcf5a..1b3499dddf 100644
|
||||
--- a/src/vs/platform/remote/browser/browserWebSocketFactory.ts
|
||||
+++ b/src/vs/platform/remote/browser/browserWebSocketFactory.ts
|
||||
@@ -79,7 +79,7 @@ class BrowserSocket implements ISocket {
|
||||
|
@ -379,7 +379,7 @@ index 6d9ecbcf5a..1ebd5a4b84 100644
|
|||
connect(host: string, port: number, query: string, callback: IConnectCallback): void {
|
||||
const errorListener = (err: any) => callback(err, undefined);
|
||||
- const socket = new WebSocket(`ws://${host}:${port}/?${query}&skipWebSocketFrames=false`);
|
||||
+ const socket = new WebSocket(`ws://${host}:${port}${window.location.pathname.replace(/\/+$/, '')}/?${query}&skipWebSocketFrames=false`);
|
||||
+ const socket = new WebSocket(`${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${host}:${port}${window.location.pathname.replace(/\/+$/, '')}/?${query}&skipWebSocketFrames=false`);
|
||||
socket.onopen = function (event) {
|
||||
socket.removeEventListener('error', errorListener);
|
||||
callback(undefined, new BrowserSocket(socket));
|
||||
|
@ -1397,7 +1397,7 @@ index 306d58f915..58c603ad3d 100644
|
|||
if (definition.fontCharacter || definition.fontColor) {
|
||||
let body = '';
|
||||
diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts
|
||||
index c28adc0ad9..4517c308da 100644
|
||||
index c28adc0ad9..3d1adba3d9 100644
|
||||
--- a/src/vs/workbench/workbench.web.main.ts
|
||||
+++ b/src/vs/workbench/workbench.web.main.ts
|
||||
@@ -128,7 +128,7 @@ import 'vs/workbench/services/extensions/browser/extensionService';
|
||||
|
@ -1422,3 +1422,9 @@ index c28adc0ad9..4517c308da 100644
|
|||
|
||||
// Output Panel
|
||||
import 'vs/workbench/contrib/output/browser/output.contribution';
|
||||
@@ -356,3 +356,5 @@ import 'vs/workbench/contrib/outline/browser/outline.contribution';
|
||||
// import 'vs/workbench/contrib/issue/electron-browser/issue.contribution';
|
||||
|
||||
//#endregion
|
||||
+
|
||||
+import 'vs/server/src/client';
|
||||
|
|
|
@ -7,9 +7,9 @@ import { buildHelpMessage, buildVersionMessage, options } from "vs/platform/envi
|
|||
import pkg from "vs/platform/product/node/package";
|
||||
import product from "vs/platform/product/node/product";
|
||||
|
||||
import { AuthType, MainServer } from "vs/server/src/server";
|
||||
import { MainServer } from "vs/server/src/server";
|
||||
import "vs/server/src/tar";
|
||||
import { buildAllowedMessage, generateCertificate, generatePassword, open, unpackExecutables } from "vs/server/src/util";
|
||||
import { AuthType, buildAllowedMessage, generateCertificate, generatePassword, open, unpackExecutables } from "vs/server/src/util";
|
||||
|
||||
interface Args extends ParsedArgs {
|
||||
auth?: AuthType;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
import 'vs/css!./media/firefox';
|
|
@ -1,4 +1,5 @@
|
|||
import * as cp from "child_process";
|
||||
import * as tls from "tls";
|
||||
|
||||
import { getPathFromAmdModule } from "vs/base/common/amd";
|
||||
import { VSBuffer } from "vs/base/common/buffer";
|
||||
|
@ -88,7 +89,7 @@ export class ExtensionHostConnection extends Connection {
|
|||
type: "VSCODE_EXTHOST_IPC_SOCKET",
|
||||
initialDataChunk: (buffer.buffer as Buffer).toString("base64"),
|
||||
skipWebSocketFrames: this.protocol.getSocket() instanceof NodeSocket,
|
||||
}, socket);
|
||||
}, socket instanceof tls.TLSSocket ? (<any>socket)._parent : socket);
|
||||
}
|
||||
|
||||
private spawn(buffer: VSBuffer): cp.ChildProcess {
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
@supports (-moz-appearance:none) {
|
||||
/*
|
||||
.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-buttons-container .monaco-button.monaco-text-button {
|
||||
max-width: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.monaco-shell .screen-reader-detected-explanation .buttons a,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink,
|
||||
.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-buttons-container .monaco-button {
|
||||
max-width: -moz-fit-content;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit,
|
||||
.explorer-viewlet .panel-header .count,
|
||||
.extensions-viewlet > .extensions .extension > .details > .header-container > .header > .version,
|
||||
.debug-viewlet .debug-call-stack .stack-frame .label {
|
||||
min-width: -moz-fit-content;
|
||||
}
|
||||
*/
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
import * as crypto from "crypto";
|
||||
import * as net from "net";
|
||||
|
||||
import { VSBuffer } from "vs/base/common/buffer";
|
||||
|
@ -13,30 +12,12 @@ export interface SocketOptions {
|
|||
}
|
||||
|
||||
export class Protocol extends PersistentProtocol {
|
||||
public constructor(
|
||||
secWebsocketKey: string,
|
||||
socket: net.Socket,
|
||||
public readonly options: SocketOptions,
|
||||
) {
|
||||
public constructor(socket: net.Socket, public readonly options: SocketOptions) {
|
||||
super(
|
||||
options.skipWebSocketFrames
|
||||
? new NodeSocket(socket)
|
||||
: new WebSocketNodeSocket(new NodeSocket(socket)),
|
||||
);
|
||||
socket.on("error", () => socket.destroy());
|
||||
socket.on("end", () => socket.destroy());
|
||||
|
||||
// This magic value is specified by the websocket spec.
|
||||
const magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
const reply = crypto.createHash("sha1")
|
||||
.update(secWebsocketKey + magic)
|
||||
.digest("base64");
|
||||
socket.write([
|
||||
"HTTP/1.1 101 Switching Protocols",
|
||||
"Upgrade: websocket",
|
||||
"Connection: Upgrade",
|
||||
`Sec-WebSocket-Accept: ${reply}`,
|
||||
].join("\r\n") + "\r\n\r\n");
|
||||
}
|
||||
|
||||
public getUnderlyingSocket(): net.Socket {
|
||||
|
|
118
src/server.ts
118
src/server.ts
|
@ -1,3 +1,4 @@
|
|||
import * as crypto from "crypto";
|
||||
import * as fs from "fs";
|
||||
import * as http from "http";
|
||||
import * as https from "https";
|
||||
|
@ -55,7 +56,7 @@ import { Connection, ManagementConnection, ExtensionHostConnection } from "vs/se
|
|||
import { ExtensionEnvironmentChannel, FileProviderChannel , } from "vs/server/src/channel";
|
||||
import { TelemetryClient } from "vs/server/src/insights";
|
||||
import { Protocol } from "vs/server/src/protocol";
|
||||
import { getMediaMime, getUriTransformer } from "vs/server/src/util";
|
||||
import { AuthType, getMediaMime, getUriTransformer } from "vs/server/src/util";
|
||||
|
||||
export enum HttpCode {
|
||||
Ok = 200,
|
||||
|
@ -95,10 +96,6 @@ export class HttpError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
export enum AuthType {
|
||||
Password = "password",
|
||||
}
|
||||
|
||||
export interface ServerOptions {
|
||||
readonly auth?: AuthType;
|
||||
readonly basePath?: string;
|
||||
|
@ -140,6 +137,7 @@ export abstract class Server {
|
|||
if (!this.listenPromise) {
|
||||
this.listenPromise = new Promise((resolve, reject) => {
|
||||
this.server.on("error", reject);
|
||||
this.server.on("upgrade", this.onUpgrade);
|
||||
const onListen = () => resolve(this.address());
|
||||
if (this.options.socket) {
|
||||
this.server.listen(this.options.socket, onListen);
|
||||
|
@ -167,6 +165,11 @@ export abstract class Server {
|
|||
return `${this.protocol}://${endpoint}`;
|
||||
}
|
||||
|
||||
protected abstract handleWebSocket(
|
||||
socket: net.Socket,
|
||||
parsedUrl: url.UrlWithParsedQuery
|
||||
): Promise<void>;
|
||||
|
||||
protected abstract handleRequest(
|
||||
base: string,
|
||||
requestPath: string,
|
||||
|
@ -174,7 +177,8 @@ export abstract class Server {
|
|||
request: http.IncomingMessage,
|
||||
): Promise<Response>;
|
||||
|
||||
protected async getResource(filePath: string): Promise<Response> {
|
||||
protected async getResource(...parts: string[]): Promise<Response> {
|
||||
const filePath = path.join(...parts);
|
||||
return { content: await util.promisify(fs.readFile)(filePath), filePath };
|
||||
}
|
||||
|
||||
|
@ -205,7 +209,7 @@ export abstract class Server {
|
|||
return { redirect: request.url };
|
||||
}
|
||||
|
||||
const parsedUrl = request.url ? url.parse(request.url, true) : {} as url.UrlWithParsedQuery;
|
||||
const parsedUrl = request.url ? url.parse(request.url, true) : { query: {}};
|
||||
const fullPath = decodeURIComponent(parsedUrl.pathname || "/");
|
||||
const match = fullPath.match(/^(\/?[^/]*)(.*)$/);
|
||||
let [, base, requestPath] = match
|
||||
|
@ -218,15 +222,13 @@ export abstract class Server {
|
|||
base = "/";
|
||||
}
|
||||
base = path.normalize(base);
|
||||
if (requestPath !== "") { // "" will become "." with normalize.
|
||||
requestPath = path.normalize(requestPath);
|
||||
}
|
||||
requestPath = path.normalize(requestPath || "/index.html");
|
||||
|
||||
switch (base) {
|
||||
case "/":
|
||||
this.ensureGet(request);
|
||||
if (requestPath === "/favicon.ico") {
|
||||
return this.getResource(path.join(this.rootPath, "/out/vs/server/src/favicon", requestPath));
|
||||
return this.getResource(this.rootPath, "/out/vs/server/src/favicon", requestPath);
|
||||
} else if (!this.authenticate(request)) {
|
||||
return { redirect: "/login" };
|
||||
}
|
||||
|
@ -238,11 +240,11 @@ export abstract class Server {
|
|||
return this.tryLogin(request);
|
||||
}
|
||||
this.ensureGet(request);
|
||||
return this.getResource(path.join(this.rootPath, "/out/vs/server/src/login", requestPath));
|
||||
return this.getResource(this.rootPath, "/out/vs/server/src/login", requestPath);
|
||||
default:
|
||||
this.ensureGet(request);
|
||||
if (!this.authenticate(request)) {
|
||||
throw new HttpError(`Unauthorized`, HttpCode.Unauthorized);
|
||||
throw new HttpError("Unauthorized", HttpCode.Unauthorized);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -250,6 +252,41 @@ export abstract class Server {
|
|||
return this.handleRequest(base, requestPath, parsedUrl, request);
|
||||
}
|
||||
|
||||
private onUpgrade = async (request: http.IncomingMessage, socket: net.Socket): Promise<void> => {
|
||||
try {
|
||||
await this.preHandleWebSocket(request, socket);
|
||||
} catch (error) {
|
||||
socket.destroy();
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private preHandleWebSocket(request: http.IncomingMessage, socket: net.Socket): Promise<void> {
|
||||
socket.on("error", () => socket.destroy());
|
||||
socket.on("end", () => socket.destroy());
|
||||
|
||||
if (!this.authenticate(request)) {
|
||||
throw new HttpError("Unauthorized", HttpCode.Unauthorized);
|
||||
} else if (request.headers.upgrade !== "websocket") {
|
||||
throw new Error("HTTP/1.1 400 Bad Request");
|
||||
}
|
||||
|
||||
// This magic value is specified by the websocket spec.
|
||||
const magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
const reply = crypto.createHash("sha1")
|
||||
.update(<string>request.headers["sec-websocket-key"] + magic)
|
||||
.digest("base64");
|
||||
socket.write([
|
||||
"HTTP/1.1 101 Switching Protocols",
|
||||
"Upgrade: websocket",
|
||||
"Connection: Upgrade",
|
||||
`Sec-WebSocket-Accept: ${reply}`,
|
||||
].join("\r\n") + "\r\n\r\n");
|
||||
|
||||
const parsedUrl = request.url ? url.parse(request.url, true) : { query: {}};
|
||||
return this.handleWebSocket(socket, parsedUrl);
|
||||
}
|
||||
|
||||
private async tryLogin(request: http.IncomingMessage): Promise<Response> {
|
||||
if (this.authenticate(request)) {
|
||||
this.ensureGet(request);
|
||||
|
@ -305,10 +342,7 @@ export abstract class Server {
|
|||
const onData = (d: Buffer): void => {
|
||||
body += d;
|
||||
if (body.length > 1e6) {
|
||||
onError(new HttpError(
|
||||
"Payload is too large",
|
||||
HttpCode.LargePayload,
|
||||
));
|
||||
onError(new HttpError("Payload is too large", HttpCode.LargePayload));
|
||||
request.connection.destroy();
|
||||
}
|
||||
};
|
||||
|
@ -359,16 +393,6 @@ export class MainServer extends Server {
|
|||
|
||||
public constructor(options: ServerOptions, args: ParsedArgs) {
|
||||
super(options);
|
||||
this.server.on("upgrade", async (request, socket) => {
|
||||
const protocol = this.createProtocol(request, socket);
|
||||
try {
|
||||
await this.connect(await protocol.handshake(), protocol);
|
||||
} catch (error) {
|
||||
protocol.sendMessage({ type: "error", reason: error.message });
|
||||
protocol.dispose();
|
||||
protocol.getSocket().dispose();
|
||||
}
|
||||
});
|
||||
this.servicesPromise = this.initializeServices(args);
|
||||
}
|
||||
|
||||
|
@ -382,6 +406,21 @@ export class MainServer extends Server {
|
|||
return address;
|
||||
}
|
||||
|
||||
protected async handleWebSocket(socket: net.Socket, parsedUrl: url.UrlWithParsedQuery): Promise<void> {
|
||||
const protocol = new Protocol(socket, {
|
||||
reconnectionToken: <string>parsedUrl.query.reconnectionToken || "",
|
||||
reconnection: parsedUrl.query.reconnection === "true",
|
||||
skipWebSocketFrames: parsedUrl.query.skipWebSocketFrames === "true",
|
||||
});
|
||||
try {
|
||||
await this.connect(await protocol.handshake(), protocol);
|
||||
} catch (error) {
|
||||
protocol.sendMessage({ type: "error", reason: error.message });
|
||||
protocol.dispose();
|
||||
protocol.getSocket().dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected async handleRequest(
|
||||
base: string,
|
||||
requestPath: string,
|
||||
|
@ -390,14 +429,15 @@ export class MainServer extends Server {
|
|||
): Promise<Response> {
|
||||
switch (base) {
|
||||
case "/": return this.getRoot(request, parsedUrl);
|
||||
case "/node_modules":
|
||||
case "/out":
|
||||
return this.getResource(path.join(this.rootPath, base, requestPath));
|
||||
case "/resources": return this.getResource(requestPath);
|
||||
case "/webview":
|
||||
const webviewPath = path.join(this.rootPath, "out/vs/workbench/contrib/webview/browser/pre");
|
||||
return this.getResource(path.join(webviewPath, requestPath || "/index.html"));
|
||||
default: throw new HttpError("Not found", HttpCode.NotFound);
|
||||
return this.getResource(
|
||||
this.rootPath,
|
||||
"out/vs/workbench/contrib/webview/browser/pre",
|
||||
requestPath
|
||||
);
|
||||
default:
|
||||
return this.getResource(this.rootPath, base, requestPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -440,18 +480,6 @@ export class MainServer extends Server {
|
|||
return { content, filePath };
|
||||
}
|
||||
|
||||
private createProtocol(request: http.IncomingMessage, socket: net.Socket): Protocol {
|
||||
if (request.headers.upgrade !== "websocket") {
|
||||
throw new Error("HTTP/1.1 400 Bad Request");
|
||||
}
|
||||
const query = request.url ? url.parse(request.url, true).query : {};
|
||||
return new Protocol(<string>request.headers["sec-websocket-key"], socket, {
|
||||
reconnectionToken: <string>query.reconnectionToken || "",
|
||||
reconnection: query.reconnection === "true",
|
||||
skipWebSocketFrames: query.skipWebSocketFrames === "true",
|
||||
});
|
||||
}
|
||||
|
||||
private async connect(message: ConnectionTypeRequest, protocol: Protocol): Promise<void> {
|
||||
switch (message.desiredConnectionType) {
|
||||
case ConnectionType.ExtensionHost:
|
||||
|
|
|
@ -12,7 +12,9 @@ import { extname } from "vs/base/common/path";
|
|||
import { URITransformer, IRawURITransformer } from "vs/base/common/uriIpc";
|
||||
import { mkdirp } from "vs/base/node/pfs";
|
||||
|
||||
import { AuthType } from "vs/server/src/server";
|
||||
export enum AuthType {
|
||||
Password = "password",
|
||||
}
|
||||
|
||||
export const tmpdir = path.join(os.tmpdir(), "code-server");
|
||||
|
||||
|
|
Loading…
Reference in New Issue