refactor: improve drag interaction and workspace identity normalization
This commit is contained in:
@@ -56,6 +56,12 @@ describe("workspace route parsing", () => {
|
||||
expect(decodeWorkspaceIdFromPathSegment("L3RtcC9yZXBv")).toBe("/tmp/repo");
|
||||
});
|
||||
|
||||
it("decodes non-canonical base64url workspace IDs used by older links", () => {
|
||||
expect(
|
||||
decodeWorkspaceIdFromPathSegment("L1VzZXJzL21vYm91ZHJhL2Rldi9wYXNlby")
|
||||
).toBe("/Users/moboudra/dev/paseo");
|
||||
});
|
||||
|
||||
it("encodes file paths as base64url (no padding)", () => {
|
||||
const encoded = encodeFilePathForPathSegment("src/index.ts");
|
||||
expect(encoded).toMatch(/^[A-Za-z0-9_-]+$/);
|
||||
|
||||
@@ -30,7 +30,7 @@ function toBase64UrlNoPad(input: string): string {
|
||||
.replace(/=+$/g, "");
|
||||
}
|
||||
|
||||
function tryDecodeBase64UrlNoPadUtf8(input: string): string | null {
|
||||
function decodeBase64UrlNoPadUtf8(input: string): string | null {
|
||||
const normalized = input.trim();
|
||||
if (normalized.length === 0) {
|
||||
return null;
|
||||
@@ -49,6 +49,16 @@ function tryDecodeBase64UrlNoPadUtf8(input: string): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
return decoded;
|
||||
}
|
||||
|
||||
function tryDecodeBase64UrlNoPadUtf8(input: string): string | null {
|
||||
const normalized = input.trim();
|
||||
const decoded = decodeBase64UrlNoPadUtf8(normalized);
|
||||
if (!decoded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Validate via round-trip to avoid false positives ("workspace-1" etc).
|
||||
if (toBase64UrlNoPad(decoded) !== normalized) {
|
||||
return null;
|
||||
@@ -57,6 +67,14 @@ function tryDecodeBase64UrlNoPadUtf8(input: string): string | null {
|
||||
return decoded;
|
||||
}
|
||||
|
||||
function isPathLikeWorkspaceIdentity(value: string): boolean {
|
||||
return (
|
||||
value.includes("/") ||
|
||||
value.includes("\\") ||
|
||||
/^[A-Za-z]:[\\/]/.test(value)
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeWorkspaceId(value: string): string {
|
||||
return value.trim().replace(/\\/g, "/").replace(/\/+$/, "");
|
||||
}
|
||||
@@ -106,6 +124,13 @@ export function decodeWorkspaceIdFromPathSegment(workspaceIdSegment: string): st
|
||||
return normalizeWorkspaceId(base64Decoded);
|
||||
}
|
||||
|
||||
// Some older links use non-canonical base64url (non-zero pad bits). Accept
|
||||
// decoded values only when they clearly represent filesystem paths.
|
||||
const relaxedBase64Decoded = decodeBase64UrlNoPadUtf8(decoded);
|
||||
if (relaxedBase64Decoded && isPathLikeWorkspaceIdentity(relaxedBase64Decoded)) {
|
||||
return normalizeWorkspaceId(relaxedBase64Decoded);
|
||||
}
|
||||
|
||||
return normalizeWorkspaceId(decoded);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
function trimNonEmpty(value: string | null | undefined): string | null {
|
||||
if (typeof value !== "string") {
|
||||
return null;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : null;
|
||||
}
|
||||
|
||||
export function normalizeWorkspaceIdentity(value: string | null | undefined): string | null {
|
||||
const trimmed = trimNonEmpty(value);
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
const withUnixSeparators = trimmed.replace(/\\/g, "/");
|
||||
if (withUnixSeparators === "/") {
|
||||
return withUnixSeparators;
|
||||
}
|
||||
const withoutTrailingSlash = withUnixSeparators.replace(/\/+$/, "");
|
||||
return withoutTrailingSlash.length > 0 ? withoutTrailingSlash : "/";
|
||||
}
|
||||
Reference in New Issue
Block a user