waveterm/frontend/util/waveutil.ts
Mike Sawka 93b7269304
Do not allow large/recursive file transfers (for now), remove s3 and wavefile fs implementations (#2808)
Big simplification. Remove the FileShare interface that abstracted
wsh://, s3://, and wavefile:// files.
It produced a lot of complexity for very little usage. We're just going
to focus on the wsh:// implementation since that's core to our remote
workflows.

* remove s3 implementation (and connections, and picker items for
preview)
* remove capabilities for FE
* remove wavefile backend impl as well
* simplify wsh file remote backend
* remove ability to copy/move/ls recursively
* limit file transfers to 32m

the longer term fix here is to use the new streaming RPC primitives.
they have full end-to-end flow-control built in and will not create
pipeline stalls, blocking other requests, and OOM issues.

these other impls had to be removed (or fixed) because transferring
large files could cause stalls or crashes with the new router
infrastructure.
2026-01-28 14:28:31 -08:00

95 lines
3.2 KiB
TypeScript

// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0s
import { getWebServerEndpoint } from "@/util/endpoints";
import { boundNumber, isBlank } from "@/util/util";
import { generate as generateCSS, parse as parseCSS, walk as walkCSS } from "css-tree";
function encodeFileURL(file: string) {
const webEndpoint = getWebServerEndpoint();
const fileUri = formatRemoteUri(file, "local");
const rtn = webEndpoint + `/wave/stream-file?path=${encodeURIComponent(fileUri)}&no404=1`;
return rtn;
}
export function processBackgroundUrls(cssText: string): string {
if (isBlank(cssText)) {
return null;
}
cssText = cssText.trim();
if (cssText.endsWith(";")) {
cssText = cssText.slice(0, -1);
}
const attrRe = /^background(-image)?\s*:\s*/i;
cssText = cssText.replace(attrRe, "");
const ast = parseCSS("background: " + cssText, {
context: "declaration",
});
let hasUnsafeUrl = false;
walkCSS(ast, {
visit: "Url",
enter(node) {
const originalUrl = node.value.trim();
if (
originalUrl.startsWith("http:") ||
originalUrl.startsWith("https:") ||
originalUrl.startsWith("data:")
) {
return;
}
// allow file:/// urls (if they are absolute)
if (originalUrl.startsWith("file://")) {
const path = originalUrl.slice(7);
if (!path.startsWith("/")) {
console.log(`Invalid background, contains a non-absolute file URL: ${originalUrl}`);
hasUnsafeUrl = true;
return;
}
const newUrl = encodeFileURL(path);
node.value = newUrl;
return;
}
// allow absolute paths
if (originalUrl.startsWith("/") || originalUrl.startsWith("~/") || /^[a-zA-Z]:(\/|\\)/.test(originalUrl)) {
const newUrl = encodeFileURL(originalUrl);
node.value = newUrl;
return;
}
hasUnsafeUrl = true;
console.log(`Invalid background, contains an unsafe URL scheme: ${originalUrl}`);
},
});
if (hasUnsafeUrl) {
return null;
}
const rtnStyle = generateCSS(ast);
if (rtnStyle == null) {
return null;
}
return rtnStyle.replace(/^background:\s*/, "");
}
export function computeBgStyleFromMeta(meta: MetaType, defaultOpacity: number = null): React.CSSProperties {
const bgAttr = meta?.["bg"];
if (isBlank(bgAttr)) {
return null;
}
try {
const processedBg = processBackgroundUrls(bgAttr);
const rtn: React.CSSProperties = {};
rtn.background = processedBg;
rtn.opacity = boundNumber(meta["bg:opacity"], 0, 1) ?? defaultOpacity;
if (!isBlank(meta?.["bg:blendmode"])) {
rtn.backgroundBlendMode = meta["bg:blendmode"];
}
return rtn;
} catch (e) {
console.error("error processing background", e);
return null;
}
}
export function formatRemoteUri(path: string, connection: string): string {
connection = connection ?? "local";
return `wsh://${connection}/${path}`;
}