fix: address PR #149 review comments

This commit is contained in:
Abhimanyu Saharan
2026-02-23 02:00:07 +05:30
parent c13915bbdf
commit 2e36630df4
2 changed files with 61 additions and 7 deletions

View File

@@ -37,6 +37,22 @@ describe("validateGatewayUrl", () => {
expect(validateGatewayUrl(" wss://host:443 ")).toBeNull(); 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", () => { it("rejects empty string", () => {
expect(validateGatewayUrl("")).toBe("Gateway URL is required."); expect(validateGatewayUrl("")).toBe("Gateway URL is required.");
}); });
@@ -71,6 +87,18 @@ describe("validateGatewayUrl", () => {
); );
}); });
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", () => { it("rejects URL with only whitespace", () => {
expect(validateGatewayUrl(" ")).toBe("Gateway URL is required."); expect(validateGatewayUrl(" ")).toBe("Gateway URL is required.");
}); });

View File

@@ -13,16 +13,42 @@ export type GatewayCheckStatus = "idle" | "checking" | "success" | "error";
*/ */
function hasExplicitPort(urlString: string): boolean { function hasExplicitPort(urlString: string): boolean {
try { try {
const { hostname } = new URL(urlString);
// Extract the authority portion (between // and the first / ? or #) // Extract the authority portion (between // and the first / ? or #)
const withoutScheme = urlString.slice(urlString.indexOf("//") + 2); const withoutScheme = urlString.slice(urlString.indexOf("//") + 2);
const authority = withoutScheme.split(/[/?#]/)[0]; const authority = withoutScheme.split(/[/?#]/)[0];
// authority is either "host", "host:port", or "[ipv6]:port" if (!authority) {
// Remove a leading IPv6 bracket group before checking for ":" return false;
const withoutIPv6 = authority.startsWith("[") }
? authority.slice(authority.indexOf("]") + 1)
: authority.slice(hostname.length); // authority may be:
return withoutIPv6.startsWith(":") && /^:\d+$/.test(withoutIPv6); // - 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 { } catch {
return false; return false;
} }