diff --git a/cli/src/commands/serve_web.rs b/cli/src/commands/serve_web.rs
index fba927234260..12c0cdafec90 100644
--- a/cli/src/commands/serve_web.rs
+++ b/cli/src/commands/serve_web.rs
@@ -12,7 +12,6 @@ use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
-use const_format::concatcp;
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Response, Server};
use tokio::io::{AsyncBufReadExt, BufReader};
@@ -56,16 +55,9 @@ const RELEASE_CACHE_SECS: u64 = 60 * 60;
/// Number of bytes for the secret keys. See workbench.ts for their usage.
const SECRET_KEY_BYTES: usize = 32;
/// Path to mint the key combining server and client parts.
-const SECRET_KEY_MINT_PATH: &str = "/_vscode-cli/mint-key";
+const SECRET_KEY_MINT_PATH: &str = "_vscode-cli/mint-key";
/// Cookie set to the `SECRET_KEY_MINT_PATH`
const PATH_COOKIE_NAME: &str = "vscode-secret-key-path";
-/// Cookie set to the `SECRET_KEY_MINT_PATH`
-const PATH_COOKIE_VALUE: &str = concatcp!(
- PATH_COOKIE_NAME,
- "=",
- SECRET_KEY_MINT_PATH,
- "; SameSite=Strict; Path=/"
-);
/// HTTP-only cookie where the client's secret half is stored.
const SECRET_KEY_COOKIE_NAME: &str = "vscode-cli-secret-half";
@@ -158,17 +150,22 @@ struct HandleContext {
/// Handler function for an inbound request
async fn handle(ctx: HandleContext, req: Request
) -> Result, Infallible> {
let client_key_half = get_client_key_half(&req);
- let mut res = match req.uri().path() {
- SECRET_KEY_MINT_PATH => handle_secret_mint(ctx, req),
- _ => handle_proxied(ctx, req).await,
+ let path = req.uri().path();
+
+ let mut res = if path.starts_with(&ctx.cm.base_path)
+ && path.get(ctx.cm.base_path.len()..).unwrap_or_default() == SECRET_KEY_MINT_PATH
+ {
+ handle_secret_mint(&ctx, req)
+ } else {
+ handle_proxied(&ctx, req).await
};
- append_secret_headers(&mut res, &client_key_half);
+ append_secret_headers(&ctx.cm.base_path, &mut res, &client_key_half);
Ok(res)
}
-async fn handle_proxied(ctx: HandleContext, req: Request) -> Response {
+async fn handle_proxied(ctx: &HandleContext, req: Request) -> Response {
let release = if let Some((r, _)) = get_release_from_path(req.uri().path(), ctx.cm.platform) {
r
} else {
@@ -194,7 +191,7 @@ async fn handle_proxied(ctx: HandleContext, req: Request) -> Response) -> Response {
+fn handle_secret_mint(ctx: &HandleContext, req: Request) -> Response {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
@@ -208,11 +205,20 @@ fn handle_secret_mint(ctx: HandleContext, req: Request) -> Response
/// Appends headers to response to maintain the secret storage of the workbench:
/// sets the `PATH_COOKIE_VALUE` so workbench.ts knows about the 'mint' endpoint,
/// and maintains the http-only cookie the client will use for cookies.
-fn append_secret_headers(res: &mut Response, client_key_half: &SecretKeyPart) {
+fn append_secret_headers(
+ base_path: &str,
+ res: &mut Response,
+ client_key_half: &SecretKeyPart,
+) {
let headers = res.headers_mut();
headers.append(
hyper::header::SET_COOKIE,
- PATH_COOKIE_VALUE.parse().unwrap(),
+ format!(
+ "{}={}{}; SameSite=Strict; Path=/",
+ PATH_COOKIE_NAME, base_path, SECRET_KEY_MINT_PATH,
+ )
+ .parse()
+ .unwrap(),
);
headers.append(
hyper::header::SET_COOKIE,
@@ -496,6 +502,8 @@ struct ConnectionManager {
pub platform: Platform,
pub log: log::Logger,
args: ServeWebArgs,
+ /// Server base path, ending in `/`
+ base_path: String,
/// Cache where servers are stored
cache: DownloadCache,
/// Mapping of (Quality, Commit) to the state each server is in
@@ -510,11 +518,24 @@ fn key_for_release(release: &Release) -> (Quality, String) {
(release.quality, release.commit.clone())
}
+fn normalize_base_path(p: &str) -> String {
+ let p = p.trim_matches('/');
+
+ if p.is_empty() {
+ return "/".to_string();
+ }
+
+ format!("/{}/", p.trim_matches('/'))
+}
+
impl ConnectionManager {
pub fn new(ctx: &CommandContext, platform: Platform, args: ServeWebArgs) -> Arc {
+ let base_path = normalize_base_path(args.server_base_path.as_deref().unwrap_or_default());
+
Arc::new(Self {
platform,
args,
+ base_path,
log: ctx.log.clone(),
cache: DownloadCache::new(ctx.paths.web_server_storage()),
update_service: UpdateService::new(