Merge pull request #161 from abhi1693/chore/pr-149-cherry-pick-fixes
fix: handle default ports in gateway URL validation
This commit is contained in:
@@ -13,14 +13,94 @@ const mockedGatewaysStatusApiV1GatewaysStatusGet = vi.mocked(
|
|||||||
);
|
);
|
||||||
|
|
||||||
describe("validateGatewayUrl", () => {
|
describe("validateGatewayUrl", () => {
|
||||||
it("requires ws/wss with an explicit port", () => {
|
it("accepts ws:// with explicit non-default port", () => {
|
||||||
expect(validateGatewayUrl("https://gateway.example")).toBe(
|
expect(validateGatewayUrl("ws://localhost:18789")).toBeNull();
|
||||||
"Gateway URL must start with ws:// or wss://.",
|
});
|
||||||
);
|
|
||||||
expect(validateGatewayUrl("ws://gateway.example")).toBe(
|
it("accepts wss:// with explicit non-default port", () => {
|
||||||
|
expect(validateGatewayUrl("wss://gateway.example.com:8443")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accepts wss:// with explicit default port 443", () => {
|
||||||
|
expect(validateGatewayUrl("wss://devbot.tailcc2080.ts.net:443")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accepts ws:// with explicit default port 80", () => {
|
||||||
|
expect(validateGatewayUrl("ws://localhost:80")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accepts URLs with a path after the port", () => {
|
||||||
|
expect(validateGatewayUrl("wss://host.example.com:443/gateway")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("trims surrounding whitespace before validating", () => {
|
||||||
|
expect(validateGatewayUrl(" wss://host:443 ")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accepts IPv6 URLs with explicit non-default port", () => {
|
||||||
|
expect(validateGatewayUrl("wss://[::1]:8080")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accepts IPv6 URLs with explicit default port", () => {
|
||||||
|
expect(validateGatewayUrl("wss://[2001:db8::1]:443")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accepts userinfo URLs with explicit port", () => {
|
||||||
|
expect(validateGatewayUrl("ws://user:pass@gateway.example.com:8080")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accepts userinfo URLs with IPv6 host and explicit port", () => {
|
||||||
|
expect(validateGatewayUrl("wss://user@[::1]:443")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects empty string", () => {
|
||||||
|
expect(validateGatewayUrl("")).toBe("Gateway URL is required.");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects wss:// with no port at all", () => {
|
||||||
|
expect(validateGatewayUrl("wss://gateway.example.com")).toBe(
|
||||||
"Gateway URL must include an explicit port.",
|
"Gateway URL must include an explicit port.",
|
||||||
);
|
);
|
||||||
expect(validateGatewayUrl("ws://gateway.example:18789")).toBeNull();
|
});
|
||||||
|
|
||||||
|
it("rejects ws:// with no port at all", () => {
|
||||||
|
expect(validateGatewayUrl("ws://localhost")).toBe(
|
||||||
|
"Gateway URL must include an explicit port.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects https:// scheme", () => {
|
||||||
|
expect(validateGatewayUrl("https://gateway.example.com:443")).toBe(
|
||||||
|
"Gateway URL must start with ws:// or wss://.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects http:// scheme", () => {
|
||||||
|
expect(validateGatewayUrl("http://localhost:8080")).toBe(
|
||||||
|
"Gateway URL must start with ws:// or wss://.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects completely invalid URL", () => {
|
||||||
|
expect(validateGatewayUrl("not-a-url")).toBe(
|
||||||
|
"Enter a valid gateway URL including port.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects out-of-range ports", () => {
|
||||||
|
expect(validateGatewayUrl("wss://gateway.example.com:65536")).toBe(
|
||||||
|
"Enter a valid gateway URL including port.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects userinfo URLs with no explicit port", () => {
|
||||||
|
expect(validateGatewayUrl("ws://user:pass@gateway.example.com")).toBe(
|
||||||
|
"Gateway URL must include an explicit port.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects URL with only whitespace", () => {
|
||||||
|
expect(validateGatewayUrl(" ")).toBe("Gateway URL is required.");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,56 @@ export const DEFAULT_WORKSPACE_ROOT = "~/.openclaw";
|
|||||||
|
|
||||||
export type GatewayCheckStatus = "idle" | "checking" | "success" | "error";
|
export type GatewayCheckStatus = "idle" | "checking" | "success" | "error";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true only when the URL string contains an explicit ":port" segment.
|
||||||
|
*
|
||||||
|
* JavaScript's URL API sets `.port` to "" for *both* an omitted port and a
|
||||||
|
* port that equals the scheme's default (e.g. 443 for wss:). We therefore
|
||||||
|
* inspect the raw host+port token from the URL string instead.
|
||||||
|
*/
|
||||||
|
function hasExplicitPort(urlString: string): boolean {
|
||||||
|
try {
|
||||||
|
// Extract the authority portion (between // and the first / ? or #)
|
||||||
|
const withoutScheme = urlString.slice(urlString.indexOf("//") + 2);
|
||||||
|
const authority = withoutScheme.split(/[/?#]/)[0];
|
||||||
|
if (!authority) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// authority may be:
|
||||||
|
// - host[:port]
|
||||||
|
// - [ipv6][:port]
|
||||||
|
// - userinfo@host[:port]
|
||||||
|
// - userinfo@[ipv6][:port]
|
||||||
|
const atIndex = authority.lastIndexOf("@");
|
||||||
|
const hostPort = atIndex === -1 ? authority : authority.slice(atIndex + 1);
|
||||||
|
|
||||||
|
let portSegment = "";
|
||||||
|
if (hostPort.startsWith("[")) {
|
||||||
|
const closingBracketIndex = hostPort.indexOf("]");
|
||||||
|
if (closingBracketIndex === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
portSegment = hostPort.slice(closingBracketIndex + 1);
|
||||||
|
} else {
|
||||||
|
const lastColonIndex = hostPort.lastIndexOf(":");
|
||||||
|
if (lastColonIndex === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
portSegment = hostPort.slice(lastColonIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!portSegment.startsWith(":") || !/^:\d+$/.test(portSegment)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const port = Number.parseInt(portSegment.slice(1), 10);
|
||||||
|
return Number.isInteger(port) && port >= 0 && port <= 65535;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const validateGatewayUrl = (value: string) => {
|
export const validateGatewayUrl = (value: string) => {
|
||||||
const trimmed = value.trim();
|
const trimmed = value.trim();
|
||||||
if (!trimmed) return "Gateway URL is required.";
|
if (!trimmed) return "Gateway URL is required.";
|
||||||
@@ -12,7 +62,7 @@ export const validateGatewayUrl = (value: string) => {
|
|||||||
if (url.protocol !== "ws:" && url.protocol !== "wss:") {
|
if (url.protocol !== "ws:" && url.protocol !== "wss:") {
|
||||||
return "Gateway URL must start with ws:// or wss://.";
|
return "Gateway URL must start with ws:// or wss://.";
|
||||||
}
|
}
|
||||||
if (!url.port) {
|
if (!hasExplicitPort(trimmed)) {
|
||||||
return "Gateway URL must include an explicit port.";
|
return "Gateway URL must include an explicit port.";
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user