TLS socket still doesn't work

This commit is contained in:
Asher 2019-07-25 17:39:43 -05:00
parent a4f21fb0d4
commit 4c4a179bce
No known key found for this signature in database
GPG Key ID: D63C1EF81242354A
9 changed files with 113 additions and 73 deletions

View File

@ -17,7 +17,7 @@ function docker-build() {
if [[ "${image}" == "codercom/nbin-alpine" ]] ; then if [[ "${image}" == "codercom/nbin-alpine" ]] ; then
docker exec "${containerId}" apk add libxkbfile-dev libsecret-dev docker exec "${containerId}" apk add libxkbfile-dev libsecret-dev
else 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 docker exec "${containerId}" yum install -y libxkbfile-devel libsecret-devel git
fi fi

View File

@ -371,7 +371,7 @@ index 9f68b645b6..f0cae7111d 100644
this.channel.call('setLevel', level); this.channel.call('setLevel', level);
} }
diff --git a/src/vs/platform/remote/browser/browserWebSocketFactory.ts b/src/vs/platform/remote/browser/browserWebSocketFactory.ts 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 --- a/src/vs/platform/remote/browser/browserWebSocketFactory.ts
+++ b/src/vs/platform/remote/browser/browserWebSocketFactory.ts +++ b/src/vs/platform/remote/browser/browserWebSocketFactory.ts
@@ -79,7 +79,7 @@ class BrowserSocket implements ISocket { @@ -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 { connect(host: string, port: number, query: string, callback: IConnectCallback): void {
const errorListener = (err: any) => callback(err, undefined); const errorListener = (err: any) => callback(err, undefined);
- const socket = new WebSocket(`ws://${host}:${port}/?${query}&skipWebSocketFrames=false`); - 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.onopen = function (event) {
socket.removeEventListener('error', errorListener); socket.removeEventListener('error', errorListener);
callback(undefined, new BrowserSocket(socket)); callback(undefined, new BrowserSocket(socket));
@ -1397,7 +1397,7 @@ index 306d58f915..58c603ad3d 100644
if (definition.fontCharacter || definition.fontColor) { if (definition.fontCharacter || definition.fontColor) {
let body = ''; let body = '';
diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts 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 --- a/src/vs/workbench/workbench.web.main.ts
+++ b/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'; @@ -128,7 +128,7 @@ import 'vs/workbench/services/extensions/browser/extensionService';
@ -1422,3 +1422,9 @@ index c28adc0ad9..4517c308da 100644
// Output Panel // Output Panel
import 'vs/workbench/contrib/output/browser/output.contribution'; 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';

View File

@ -7,9 +7,9 @@ import { buildHelpMessage, buildVersionMessage, options } from "vs/platform/envi
import pkg from "vs/platform/product/node/package"; import pkg from "vs/platform/product/node/package";
import product from "vs/platform/product/node/product"; 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 "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 { interface Args extends ParsedArgs {
auth?: AuthType; auth?: AuthType;

1
src/client.ts Normal file
View File

@ -0,0 +1 @@
import 'vs/css!./media/firefox';

View File

@ -1,4 +1,5 @@
import * as cp from "child_process"; import * as cp from "child_process";
import * as tls from "tls";
import { getPathFromAmdModule } from "vs/base/common/amd"; import { getPathFromAmdModule } from "vs/base/common/amd";
import { VSBuffer } from "vs/base/common/buffer"; import { VSBuffer } from "vs/base/common/buffer";
@ -88,7 +89,7 @@ export class ExtensionHostConnection extends Connection {
type: "VSCODE_EXTHOST_IPC_SOCKET", type: "VSCODE_EXTHOST_IPC_SOCKET",
initialDataChunk: (buffer.buffer as Buffer).toString("base64"), initialDataChunk: (buffer.buffer as Buffer).toString("base64"),
skipWebSocketFrames: this.protocol.getSocket() instanceof NodeSocket, skipWebSocketFrames: this.protocol.getSocket() instanceof NodeSocket,
}, socket); }, socket instanceof tls.TLSSocket ? (<any>socket)._parent : socket);
} }
private spawn(buffer: VSBuffer): cp.ChildProcess { private spawn(buffer: VSBuffer): cp.ChildProcess {

21
src/media/firefox.css Normal file
View File

@ -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;
}
*/
}

View File

@ -1,4 +1,3 @@
import * as crypto from "crypto";
import * as net from "net"; import * as net from "net";
import { VSBuffer } from "vs/base/common/buffer"; import { VSBuffer } from "vs/base/common/buffer";
@ -13,30 +12,12 @@ export interface SocketOptions {
} }
export class Protocol extends PersistentProtocol { export class Protocol extends PersistentProtocol {
public constructor( public constructor(socket: net.Socket, public readonly options: SocketOptions) {
secWebsocketKey: string,
socket: net.Socket,
public readonly options: SocketOptions,
) {
super( super(
options.skipWebSocketFrames options.skipWebSocketFrames
? new NodeSocket(socket) ? new NodeSocket(socket)
: new WebSocketNodeSocket(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 { public getUnderlyingSocket(): net.Socket {

View File

@ -1,3 +1,4 @@
import * as crypto from "crypto";
import * as fs from "fs"; import * as fs from "fs";
import * as http from "http"; import * as http from "http";
import * as https from "https"; 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 { ExtensionEnvironmentChannel, FileProviderChannel , } from "vs/server/src/channel";
import { TelemetryClient } from "vs/server/src/insights"; import { TelemetryClient } from "vs/server/src/insights";
import { Protocol } from "vs/server/src/protocol"; 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 { export enum HttpCode {
Ok = 200, Ok = 200,
@ -95,10 +96,6 @@ export class HttpError extends Error {
} }
} }
export enum AuthType {
Password = "password",
}
export interface ServerOptions { export interface ServerOptions {
readonly auth?: AuthType; readonly auth?: AuthType;
readonly basePath?: string; readonly basePath?: string;
@ -140,6 +137,7 @@ export abstract class Server {
if (!this.listenPromise) { if (!this.listenPromise) {
this.listenPromise = new Promise((resolve, reject) => { this.listenPromise = new Promise((resolve, reject) => {
this.server.on("error", reject); this.server.on("error", reject);
this.server.on("upgrade", this.onUpgrade);
const onListen = () => resolve(this.address()); const onListen = () => resolve(this.address());
if (this.options.socket) { if (this.options.socket) {
this.server.listen(this.options.socket, onListen); this.server.listen(this.options.socket, onListen);
@ -167,6 +165,11 @@ export abstract class Server {
return `${this.protocol}://${endpoint}`; return `${this.protocol}://${endpoint}`;
} }
protected abstract handleWebSocket(
socket: net.Socket,
parsedUrl: url.UrlWithParsedQuery
): Promise<void>;
protected abstract handleRequest( protected abstract handleRequest(
base: string, base: string,
requestPath: string, requestPath: string,
@ -174,7 +177,8 @@ export abstract class Server {
request: http.IncomingMessage, request: http.IncomingMessage,
): Promise<Response>; ): 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 }; return { content: await util.promisify(fs.readFile)(filePath), filePath };
} }
@ -205,7 +209,7 @@ export abstract class Server {
return { redirect: request.url }; 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 fullPath = decodeURIComponent(parsedUrl.pathname || "/");
const match = fullPath.match(/^(\/?[^/]*)(.*)$/); const match = fullPath.match(/^(\/?[^/]*)(.*)$/);
let [, base, requestPath] = match let [, base, requestPath] = match
@ -218,15 +222,13 @@ export abstract class Server {
base = "/"; base = "/";
} }
base = path.normalize(base); base = path.normalize(base);
if (requestPath !== "") { // "" will become "." with normalize. requestPath = path.normalize(requestPath || "/index.html");
requestPath = path.normalize(requestPath);
}
switch (base) { switch (base) {
case "/": case "/":
this.ensureGet(request); this.ensureGet(request);
if (requestPath === "/favicon.ico") { 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)) { } else if (!this.authenticate(request)) {
return { redirect: "/login" }; return { redirect: "/login" };
} }
@ -238,11 +240,11 @@ export abstract class Server {
return this.tryLogin(request); return this.tryLogin(request);
} }
this.ensureGet(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: default:
this.ensureGet(request); this.ensureGet(request);
if (!this.authenticate(request)) { if (!this.authenticate(request)) {
throw new HttpError(`Unauthorized`, HttpCode.Unauthorized); throw new HttpError("Unauthorized", HttpCode.Unauthorized);
} }
break; break;
} }
@ -250,6 +252,41 @@ export abstract class Server {
return this.handleRequest(base, requestPath, parsedUrl, request); 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> { private async tryLogin(request: http.IncomingMessage): Promise<Response> {
if (this.authenticate(request)) { if (this.authenticate(request)) {
this.ensureGet(request); this.ensureGet(request);
@ -305,10 +342,7 @@ export abstract class Server {
const onData = (d: Buffer): void => { const onData = (d: Buffer): void => {
body += d; body += d;
if (body.length > 1e6) { if (body.length > 1e6) {
onError(new HttpError( onError(new HttpError("Payload is too large", HttpCode.LargePayload));
"Payload is too large",
HttpCode.LargePayload,
));
request.connection.destroy(); request.connection.destroy();
} }
}; };
@ -359,16 +393,6 @@ export class MainServer extends Server {
public constructor(options: ServerOptions, args: ParsedArgs) { public constructor(options: ServerOptions, args: ParsedArgs) {
super(options); 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); this.servicesPromise = this.initializeServices(args);
} }
@ -382,6 +406,21 @@ export class MainServer extends Server {
return address; 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( protected async handleRequest(
base: string, base: string,
requestPath: string, requestPath: string,
@ -390,14 +429,15 @@ export class MainServer extends Server {
): Promise<Response> { ): Promise<Response> {
switch (base) { switch (base) {
case "/": return this.getRoot(request, parsedUrl); 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 "/resources": return this.getResource(requestPath);
case "/webview": case "/webview":
const webviewPath = path.join(this.rootPath, "out/vs/workbench/contrib/webview/browser/pre"); return this.getResource(
return this.getResource(path.join(webviewPath, requestPath || "/index.html")); this.rootPath,
default: throw new HttpError("Not found", HttpCode.NotFound); "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 }; 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> { private async connect(message: ConnectionTypeRequest, protocol: Protocol): Promise<void> {
switch (message.desiredConnectionType) { switch (message.desiredConnectionType) {
case ConnectionType.ExtensionHost: case ConnectionType.ExtensionHost:

View File

@ -12,7 +12,9 @@ import { extname } from "vs/base/common/path";
import { URITransformer, IRawURITransformer } from "vs/base/common/uriIpc"; import { URITransformer, IRawURITransformer } from "vs/base/common/uriIpc";
import { mkdirp } from "vs/base/node/pfs"; 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"); export const tmpdir = path.join(os.tmpdir(), "code-server");