fix(canvas): normalize legacy attachment content URLs #1845
@@ -113,6 +113,31 @@ describe("resolveAttachmentHref — platform-pending: scheme (poll-mode uploads)
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveAttachmentHref — legacy platform content URLs", () => {
|
||||
const chatWs = "chat-ws-aaaaaaaa";
|
||||
const sourceWs = "d76977b1-d620-4f42-a57e-111111111111";
|
||||
const fileID = "e2dfaf2e-1111-4abc-9999-222222222222";
|
||||
|
||||
it("rewrites /workspaces/<ws>/content/<file>/content to the authenticated pending-upload endpoint", () => {
|
||||
const url = resolveAttachmentHref(
|
||||
chatWs,
|
||||
`/workspaces/${sourceWs}/content/${fileID}/content`,
|
||||
);
|
||||
expect(url).toContain(`/workspaces/${sourceWs}/pending-uploads/${fileID}/content`);
|
||||
expect(url).not.toContain(`/workspaces/${chatWs}/`);
|
||||
});
|
||||
|
||||
it("treats legacy content URLs as platform attachments so previews fetch with auth headers", () => {
|
||||
expect(isPlatformAttachment(`/workspaces/${sourceWs}/content/${fileID}/content`)).toBe(true);
|
||||
});
|
||||
|
||||
it("passes malformed legacy content URLs through unchanged", () => {
|
||||
const malformed = `/workspaces/${sourceWs}/content//content`;
|
||||
expect(resolveAttachmentHref(chatWs, malformed)).toBe(malformed);
|
||||
expect(isPlatformAttachment(malformed)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isPlatformAttachment", () => {
|
||||
it("returns true for platform-pending: URIs", () => {
|
||||
expect(isPlatformAttachment("platform-pending:abc/file")).toBe(true);
|
||||
|
||||
@@ -125,6 +125,8 @@ export async function uploadChatFiles(
|
||||
* - `/workspace/...` (bare absolute path inside the container)
|
||||
* - `platform-pending:<wsid>/<file_id>` (poll-mode upload, staged
|
||||
* on platform side; resolves to /pending-uploads/<file_id>/content)
|
||||
* - `/workspaces/<wsid>/content/<file_id>/content` (legacy platform
|
||||
* content URL; normalizes to the same pending-upload endpoint)
|
||||
* Everything that looks like an allowed-root container path is
|
||||
* rewritten to the authenticated /chat/download endpoint. HTTP(S)
|
||||
* URIs pass through unchanged so we can also render links to
|
||||
@@ -163,6 +165,11 @@ export function resolveAttachmentHref(
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
const legacy = parseLegacyPlatformContentUri(uri);
|
||||
if (legacy) {
|
||||
const [wsid, fileID] = legacy;
|
||||
return `${PLATFORM_URL}/workspaces/${encodeURIComponent(wsid)}/pending-uploads/${encodeURIComponent(fileID)}/content`;
|
||||
}
|
||||
const containerPath = normalizeWorkspaceUri(uri);
|
||||
if (containerPath) {
|
||||
return `${PLATFORM_URL}/workspaces/${workspaceId}/chat/download?path=${encodeURIComponent(containerPath)}`;
|
||||
@@ -175,6 +182,7 @@ export function resolveAttachmentHref(
|
||||
* downloadChatFile rather than letting the browser navigate. */
|
||||
export function isPlatformAttachment(uri: string): boolean {
|
||||
if (uri.startsWith("platform-pending:")) return true;
|
||||
if (parseLegacyPlatformContentUri(uri)) return true;
|
||||
return normalizeWorkspaceUri(uri) !== null;
|
||||
}
|
||||
|
||||
@@ -183,6 +191,12 @@ export function isPlatformAttachment(uri: string): boolean {
|
||||
* mirror the server's `allowedRoots` allowlist. */
|
||||
const ALLOWED_CONTAINER_ROOTS = ["/configs", "/workspace", "/home", "/plugins"];
|
||||
|
||||
function parseLegacyPlatformContentUri(uri: string): [string, string] | null {
|
||||
const m = uri.match(/^\/workspaces\/([^/]+)\/content\/([^/]+)\/content(?:[?#].*)?$/);
|
||||
if (!m || !m[1] || !m[2]) return null;
|
||||
return [m[1], m[2]];
|
||||
}
|
||||
|
||||
function normalizeWorkspaceUri(uri: string): string | null {
|
||||
let path: string | null = null;
|
||||
if (uri.startsWith("workspace:")) {
|
||||
|
||||
Reference in New Issue
Block a user