Archived
1
0

Target a recent commit for VS Code

This is so we can try out the web worker extension host.
This commit is contained in:
Asher
2019-08-29 19:05:48 -05:00
parent 624a4c08b9
commit b901043bfc
12 changed files with 443 additions and 580 deletions

View File

@ -6,7 +6,7 @@ import { OS } from "vs/base/common/platform";
import { URI, UriComponents } from "vs/base/common/uri";
import { transformOutgoingURIs } from "vs/base/common/uriIpc";
import { IServerChannel } from "vs/base/parts/ipc/common/ipc";
import { IDiagnosticInfo } from "vs/platform/diagnostics/common/diagnosticsService";
import { IDiagnosticInfo } from "vs/platform/diagnostics/common/diagnostics";
import { IEnvironmentService } from "vs/platform/environment/common/environment";
import { ExtensionIdentifier, IExtensionDescription } from "vs/platform/extensions/common/extensions";
import { FileDeleteOptions, FileOpenOptions, FileOverwriteOptions, FileType, IStat, IWatchOptions } from "vs/platform/files/common/files";
@ -163,11 +163,10 @@ export class FileProviderChannel implements IServerChannel, IDisposable {
}
private transform(resource: UriComponents): URI {
// HACK: for now assume /out is relative to the build (used for the
// walkthrough content).
if (resource.path.indexOf("/out") === 0) {
return URI.file(this.environmentService.appRoot + resource.path);
// This is used by the webview service worker to load resources.
// Used for walkthrough content.
if (resource.path.indexOf("/static") === 0) {
return URI.file(this.environmentService.appRoot + resource.path.replace(/^\/static/, ""));
// Used by the webview service worker to load resources.
} else if (resource.path === "/vscode-resource" && resource.query) {
try {
const query = JSON.parse(resource.query);
@ -185,6 +184,7 @@ export class ExtensionEnvironmentChannel implements IServerChannel {
private readonly environment: IEnvironmentService,
private readonly log: ILogService,
private readonly telemetry: ITelemetryService,
private readonly connectionToken: string,
) {}
public listen(_: unknown, event: string): Event<any> {
@ -207,6 +207,7 @@ export class ExtensionEnvironmentChannel implements IServerChannel {
private async getEnvironmentData(locale: string): Promise<IRemoteAgentEnvironment> {
return {
pid: process.pid,
connectionToken: this.connectionToken,
appRoot: URI.file(this.environment.appRoot),
appSettingsHome: this.environment.appSettingsHome,
settingsPath: this.environment.machineSettingsHome,

View File

@ -7,6 +7,7 @@ import { LocalizationsService } from "vs/platform/localizations/electron-browser
import { IUpdateService } from "vs/platform/update/common/update";
import { UpdateService } from "vs/platform/update/electron-browser/updateService";
import { TelemetryChannelClient } from "vs/server/src/telemetry";
import { IUploadService, UploadService } from 'vs/server/src/upload';
import { IRemoteAgentService } from "vs/workbench/services/remote/common/remoteAgentService";
class TelemetryService extends TelemetryChannelClient {
@ -18,12 +19,13 @@ class TelemetryService extends TelemetryChannelClient {
}
registerSingleton(ILocalizationsService, LocalizationsService);
registerSingleton(IUpdateService, UpdateService);
registerSingleton(ITelemetryService, TelemetryService);
registerSingleton(IUpdateService, UpdateService);
registerSingleton(IUploadService, UploadService, true);
import "vs/workbench/contrib/update/electron-browser/update.contribution";
import 'vs/workbench/contrib/localizations/browser/localizations.contribution';
import "vs/css!./media/firefox";
import { coderApi, vscodeApi } from "vs/server/src/api";
/**

View File

@ -1,104 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'self' 'unsafe-inline'; script-src 'unsafe-inline'; manifest-src 'self'; img-src 'self';">
<title>Authenticate: code-server</title>
<style>
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
html, body {
background-color: #FFFFFF;
height: 100%;
min-height: 100%;
}
body {
align-items: center;
display: flex;
font-family: "monospace";
justify-content: center;
margin: 0;
padding: 10px;
}
.login-form {
border-radius: 5px;
box-shadow: 0 18px 80px 10px rgba(69, 65, 78, 0.08);
color: #575962;
margin-top: -10%;
max-width: 328px;
padding: 40px;
position: relative;
width: 100%;
}
.login-form > .title {
text-align: center;
text-transform: uppercase;
font-size: 12px;
font-weight: 500;
letter-spacing: 1.5px;
line-height: 15px;
margin-bottom: 0px;
margin-bottom: 5px;
margin-top: 0px;
}
.login-form > .subtitle {
font-size: 19px;
font-weight: bold;
line-height: 25px;
margin-bottom: 45px;
margin: 0;
text-align: center;
}
.login-form > .field {
text-align: left;
font-size: 12px;
color: #797E84;
margin: 16px 0;
}
.login-form > .field > .input {
background: none !important;
border: 1px solid #ccc;
border-radius: 2px;
padding: 5px;
width: 100%;
}
.login-form > .button {
border: none;
border-radius: 24px;
box-shadow: 0 12px 17px 2px rgba(171,173,163,0.14), 0 5px 22px 4px rgba(171,173,163,0.12), 0 7px 8px -4px rgba(171,173,163,0.2);
cursor: pointer;
display: block;
padding: 15px 5px;
width: 100%;
}
.login-form > .button:hover {
background-color: rgb(0, 122, 204);
color: #fff;
}
.error-display {
box-sizing: border-box;
color: #bb2d0f;
font-size: 14px;
font-weight: 400;
line-height: 12px;
padding: 20px 8px 0;
text-align: center;
}
</style>
<link rel="icon" href="./favicon.ico" type="image/x-icon" />
<link rel="manifest" href="./manifest.json">
<link href="./static/out/vs/server/src/media/login.css" rel="stylesheet">
</head>
<body>
<form class="login-form" method="post">

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -1,14 +0,0 @@
@supports (-moz-appearance:none) {
/* Firefox doesn't support -webkit-margin-{before/after} so use margin. */
/* These are the collapsible section headings in a sidebar panel. */
.monaco-panel-view .panel > .panel-header h3.title {
margin-bottom: 0;
margin-top: 0;
}
/* Firefox doesn't seem to support fit-content. */
/* These are the file tabs. */
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit {
min-width: -moz-fit-content;
}
}

94
src/media/login.css Normal file
View File

@ -0,0 +1,94 @@
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
html, body {
background-color: #FFFFFF;
height: 100%;
min-height: 100%;
}
body {
align-items: center;
display: flex;
font-family: "monospace";
justify-content: center;
margin: 0;
padding: 10px;
}
.login-form {
border-radius: 5px;
box-shadow: 0 18px 80px 10px rgba(69, 65, 78, 0.08);
color: #575962;
margin-top: -10%;
max-width: 328px;
padding: 40px;
position: relative;
width: 100%;
}
.login-form > .title {
text-align: center;
text-transform: uppercase;
font-size: 12px;
font-weight: 500;
letter-spacing: 1.5px;
line-height: 15px;
margin-bottom: 0px;
margin-bottom: 5px;
margin-top: 0px;
}
.login-form > .subtitle {
font-size: 19px;
font-weight: bold;
line-height: 25px;
margin-bottom: 45px;
margin: 0;
text-align: center;
}
.login-form > .field {
text-align: left;
font-size: 12px;
color: #797E84;
margin: 16px 0;
}
.login-form > .field > .input {
background: none !important;
border: 1px solid #ccc;
border-radius: 2px;
padding: 5px;
width: 100%;
}
.login-form > .button {
border: none;
border-radius: 24px;
box-shadow: 0 12px 17px 2px rgba(171,173,163,0.14), 0 5px 22px 4px rgba(171,173,163,0.12), 0 7px 8px -4px rgba(171,173,163,0.2);
cursor: pointer;
display: block;
padding: 15px 5px;
width: 100%;
}
.login-form > .button:hover {
background-color: rgb(0, 122, 204);
color: #fff;
}
.error-display {
box-sizing: border-box;
color: #bb2d0f;
font-size: 14px;
font-weight: 400;
line-height: 12px;
padding: 20px 8px 0;
text-align: center;
}

13
src/media/manifest.json Normal file
View File

@ -0,0 +1,13 @@
{
"name": "code-server",
"short_name": "code-server",
"start_url": ".",
"display": "standalone",
"background-color": "#fff",
"description": "Run VS Code on a remote server.",
"icons": [{
"src": "static/code-server.png",
"sizes": "128x128",
"type": "image/png"
}]
}

View File

@ -41,10 +41,9 @@ import { LocalizationsChannel } from "vs/platform/localizations/node/localizatio
import { getLogLevel, ILogService } from "vs/platform/log/common/log";
import { LogLevelSetterChannel } from "vs/platform/log/common/logIpc";
import { SpdLogService } from "vs/platform/log/node/spdlogService";
import { IProductConfiguration, IProductService } from "vs/platform/product/common/product";
import { IProductService } from "vs/platform/product/common/product";
import pkg from "vs/platform/product/node/package";
import product from "vs/platform/product/node/product";
import { ProductService } from "vs/platform/product/node/productService";
import { ConnectionType, ConnectionTypeRequest } from "vs/platform/remote/common/remoteAgentConnection";
import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from "vs/platform/remote/common/remoteAgentFileSystemChannel";
import { IRequestService } from "vs/platform/request/common/request";
@ -81,8 +80,6 @@ export enum HttpCode {
export interface Options {
WORKBENCH_WEB_CONGIGURATION: IWorkbenchConstructionOptions;
REMOTE_USER_DATA_URI: UriComponents | URI;
PRODUCT_CONFIGURATION: IProductConfiguration | null;
CONNECTION_AUTH_TOKEN: string;
NLS_CONFIGURATION: NLSConfiguration;
}
@ -110,6 +107,7 @@ export class HttpError extends Error {
export interface ServerOptions {
readonly auth?: AuthType;
readonly basePath?: string;
readonly connectionToken?: string;
readonly cert?: string;
readonly certKey?: string;
readonly folderUri?: string;
@ -122,6 +120,8 @@ export interface ServerOptions {
export abstract class Server {
protected readonly server: http.Server | https.Server;
protected rootPath = path.resolve(__dirname, "../../../..");
protected serverRoot = path.join(this.rootPath, "/out/vs/server/src");
protected readonly allowedRequestPaths: string[] = [this.rootPath];
private listenPromise: Promise<string> | undefined;
public readonly protocol: "http" | "https";
public readonly options: ServerOptions;
@ -185,6 +185,9 @@ export abstract class Server {
protected async getResource(...parts: string[]): Promise<Response> {
const filePath = path.join(...parts);
if (!this.isAllowedRequestPath(filePath)) {
throw new HttpError("Unauthorized", HttpCode.Unauthorized);
}
return { content: await util.promisify(fs.readFile)(filePath), filePath };
}
@ -193,10 +196,20 @@ export abstract class Server {
return `${this.protocol}://${request.headers.host}${this.options.basePath}${path}${split.length === 2 ? `?${split[1]}` : ""}`;
}
private isAllowedRequestPath(path: string): boolean {
for (let i = 0; i < this.allowedRequestPaths.length; ++i) {
if (path.indexOf(this.allowedRequestPaths[i]) === 0) {
return true;
}
}
return false;
}
private onRequest = async (request: http.IncomingMessage, response: http.ServerResponse): Promise<void> => {
try {
const payload = await this.preHandleRequest(request);
response.writeHead(payload.redirect ? HttpCode.Redirect : payload.code || HttpCode.Ok, {
// "Cache-Control": "public, max-age=31536000",
"Content-Type": getMediaMime(payload.filePath),
...(payload.redirect ? { Location: this.withBase(request, payload.redirect) } : {}),
...(request.headers["service-worker"] ? { "Service-Worker-Allowed": this.options.basePath || "/" } : {}),
@ -233,25 +246,29 @@ export abstract class Server {
base = path.normalize(base);
requestPath = path.normalize(requestPath || "/index.html");
if (base !== "/login" || !this.options.auth || requestPath !== "/index.html") {
this.ensureGet(request);
}
switch (base) {
case "/":
this.ensureGet(request);
if (requestPath === "/favicon.ico") {
return this.getResource(this.rootPath, "/out/vs/server/src/favicon", requestPath);
} else if (!this.authenticate(request)) {
switch (requestPath) {
case "/favicon.ico":
case "/manifest.json":
return this.getResource(this.serverRoot, "media", requestPath);
}
if (!this.authenticate(request)) {
return { redirect: "/login" };
}
break;
case "/static":
return this.getResource(this.rootPath, requestPath);
case "/login":
if (!this.options.auth) {
if (!this.options.auth || requestPath !== "/index.html") {
throw new HttpError("Not found", HttpCode.NotFound);
} else if (requestPath === "/index.html") {
return this.tryLogin(request);
}
this.ensureGet(request);
return this.getResource(this.rootPath, "/out/vs/server/src/login", requestPath);
return this.tryLogin(request);
default:
this.ensureGet(request);
if (!this.authenticate(request)) {
throw new HttpError("Unauthorized", HttpCode.Unauthorized);
}
@ -274,6 +291,7 @@ export abstract class Server {
socket.on("error", () => socket.destroy());
socket.on("end", () => socket.destroy());
this.ensureGet(request);
if (!this.authenticate(request)) {
throw new HttpError("Unauthorized", HttpCode.Unauthorized);
} else if (request.headers.upgrade !== "websocket") {
@ -297,8 +315,7 @@ export abstract class Server {
}
private async tryLogin(request: http.IncomingMessage): Promise<Response> {
if (this.authenticate(request)) {
this.ensureGet(request);
if (this.authenticate(request) && (request.method === "GET" || request.method === "POST")) {
return { redirect: "/" };
}
if (request.method === "POST") {
@ -322,7 +339,7 @@ export abstract class Server {
}
private async getLogin(error: string = "", payload?: LoginPayload): Promise<Response> {
const filePath = path.join(this.rootPath, "out/vs/server/src/login/index.html");
const filePath = path.join(this.serverRoot, "login/index.html");
const content = (await util.promisify(fs.readFile)(filePath, "utf8"))
.replace("{{ERROR}}", error)
.replace("display:none", error ? "display:block" : "display:none")
@ -446,24 +463,12 @@ export class MainServer extends Server {
): Promise<Response> {
switch (base) {
case "/": return this.getRoot(request, parsedUrl);
case "/resources":
case "/vscode-resources":
if (requestPath === "/fetch") {
if (typeof parsedUrl.query.u === "string") {
return this.getResource(JSON.parse(parsedUrl.query.u).path);
}
// For some reason VS Code encodes the = so the query doesn't parse
// correctly. We'll look through what's available and try to find it.
for (let value in parsedUrl.query) {
if (value && typeof value === "string") {
const query = querystring.parse(value);
if (typeof query.u === "string") {
return this.getResource(JSON.parse(query.u).path);
}
}
}
case "/resource":
case "/vscode-remote-resource":
if (typeof parsedUrl.query.path === "string") {
return this.getResource(parsedUrl.query.path);
}
throw new HttpError("Not found", HttpCode.NotFound);
break;
case "/webview":
if (requestPath.indexOf("/vscode-resource") === 0) {
return this.getResource(requestPath.replace(/^\/vscode-resource/, ""));
@ -473,9 +478,8 @@ export class MainServer extends Server {
"out/vs/workbench/contrib/webview/browser/pre",
requestPath
);
default:
return this.getResource(this.rootPath, base, requestPath);
}
throw new HttpError("Not found", HttpCode.NotFound);
}
private async getRoot(request: http.IncomingMessage, parsedUrl: url.UrlWithParsedQuery): Promise<Response> {
@ -502,16 +506,11 @@ export class MainServer extends Server {
? transformer.transformOutgoing(URI.file(sanitizeFilePath(folderPath, cwd)))
: undefined,
remoteAuthority,
productConfiguration: product,
},
REMOTE_USER_DATA_URI: transformer.transformOutgoing(
(this.services.get(IEnvironmentService) as EnvironmentService).webUserDataHome,
),
PRODUCT_CONFIGURATION: {
...product,
// @ts-ignore workaround for getting the VS Code version to the browser.
version: pkg.version,
},
CONNECTION_AUTH_TOKEN: "",
NLS_CONFIGURATION: await getNlsConfiguration(locale, environment.userDataPath),
};
@ -586,6 +585,14 @@ export class MainServer extends Server {
const fileService = new FileService(logService);
fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(logService));
this.allowedRequestPaths.push(
path.join(environmentService.userDataPath, "clp"), // Language packs.
environmentService.extensionsPath,
environmentService.builtinExtensionsPath,
...environmentService.extraExtensionPaths,
...environmentService.extraBuiltinExtensionPaths,
);
this.ipc.registerChannel("loglevel", new LogLevelSetterChannel(logService));
this.ipc.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ExtensionHostDebugBroadcastChannel());
@ -595,7 +602,7 @@ export class MainServer extends Server {
this.services.set(IConfigurationService, new SyncDescriptor(ConfigurationService, [environmentService.machineSettingsResource]));
this.services.set(IRequestService, new SyncDescriptor(RequestService));
this.services.set(IFileService, fileService);
this.services.set(IProductService, new SyncDescriptor(ProductService));
this.services.set(IProductService, { _serviceBrand: undefined, ...product });
this.services.set(IDialogService, new DialogChannelClient(this.ipc.getChannel("dialog", router)));
this.services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
this.services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
@ -608,14 +615,9 @@ export class MainServer extends Server {
),
commonProperties: resolveCommonProperties(
product.commit, pkg.codeServerVersion, await getMachineId(),
environmentService.installSourcePath, "code-server",
[], environmentService.installSourcePath, "code-server",
),
piiPaths: [
environmentService.appRoot,
environmentService.extensionsPath,
...environmentService.extraExtensionPaths,
...environmentService.extraBuiltinExtensionPaths,
],
piiPaths: this.allowedRequestPaths,
} as ITelemetryServiceConfig]));
} else {
this.services.set(ITelemetryService, NullTelemetryService);
@ -633,7 +635,7 @@ export class MainServer extends Server {
const telemetryService = this.services.get(ITelemetryService) as ITelemetryService;
const extensionsChannel = new ExtensionManagementChannel(extensionsService, (context) => getUriTransformer(context.remoteAuthority));
const extensionsEnvironmentChannel = new ExtensionEnvironmentChannel(environmentService, logService, telemetryService);
const extensionsEnvironmentChannel = new ExtensionEnvironmentChannel(environmentService, logService, telemetryService, this.options.connectionToken || "");
const fileChannel = new FileProviderChannel(environmentService, logService);
const requestChannel = new RequestChannel(this.services.get(IRequestService) as IRequestService);
const telemetryChannel = new TelemetryChannel(telemetryService);