mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-20 00:17:44 +00:00
@@ -65,7 +65,7 @@ import { buildMqKafkaConnectionExtra, mqKafkaConnectionTarget, resolveMqKafkaCon
|
||||
import { assertCompleteDatabaseCategories, databaseSelectionForCategory } from "@/lib/connection/databaseCategoryOptions";
|
||||
import { loadConnectionPickerView, saveConnectionPickerView, type DbPickerView } from "@/lib/connection/connectionPickerViewPreference";
|
||||
import { normalizeRocketmqNamesrvAddr } from "@/lib/connection/rocketmqNamesrv";
|
||||
import { normalizeRabbitmqAddresses } from "@/lib/connection/rabbitmqAddresses";
|
||||
import { normalizeRabbitmqAddresses, parseRabbitmqAddress } from "@/lib/connection/rabbitmqAddresses";
|
||||
import { detectMqUiAuthKind, isMqAuthKindAllowedForSystem, type MqUiAuthKind } from "@/lib/connection/mqAuth";
|
||||
import { driverInstallProgressChannel, driverInstallProgressPercent, isDriverInstallProgressForOperation, requestAgentInstallCancellation, resolveAgentInstallOutcome, type DriverInstallProgress } from "@/lib/connection/driverInstallProgressUi";
|
||||
import { requiresSqlServerLegacyCompatibilityComponent, setSqlServerLegacyCompatibilityConfig, sqlServerUsesLegacyCompatibility, SQLSERVER_LEGACY_COMPATIBILITY_DRIVER_KEY } from "@/lib/connection/sqlServerLegacyCompatibility";
|
||||
@@ -1744,7 +1744,12 @@ function buildMqAdminConfig(): MqAdminConfig {
|
||||
}
|
||||
|
||||
if (systemKind === "rabbitmq") {
|
||||
const addresses = normalizeRabbitmqAddresses(mqRabbitmqAddresses.value);
|
||||
let addresses: string;
|
||||
try {
|
||||
addresses = normalizeRabbitmqAddresses(mqRabbitmqAddresses.value);
|
||||
} catch {
|
||||
throw new Error(t(mqRabbitmqAddresses.value.trim() ? "connection.mqRabbitmqAddressesInvalid" : "connection.mqRabbitmqAddressesRequired"));
|
||||
}
|
||||
const extra: Record<string, unknown> = {
|
||||
addresses,
|
||||
virtualHost: mqRabbitmqVirtualHost.value.trim() || "/",
|
||||
@@ -2287,14 +2292,9 @@ function applyMqKafkaConnectionTarget(config: LegacyConnectionConfig, extra: Rec
|
||||
function applyMqRabbitmqAddresses(config: LegacyConnectionConfig, addresses: string) {
|
||||
const first = normalizeRabbitmqAddresses(addresses).split(",")[0];
|
||||
if (!first) throw new Error(t("connection.mqRabbitmqAddressesRequired"));
|
||||
let parsed: URL;
|
||||
try {
|
||||
parsed = new URL(`amqp://${first}`);
|
||||
} catch {
|
||||
throw new Error(t("connection.mqRabbitmqAddressesInvalid"));
|
||||
}
|
||||
config.host = parsed.hostname;
|
||||
config.port = Number(parsed.port) || 5672;
|
||||
const parsed = parseRabbitmqAddress(first);
|
||||
config.host = parsed.host;
|
||||
config.port = parsed.port;
|
||||
config.ssl = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -740,7 +740,7 @@ export default {
|
||||
mqRabbitmqAddresses: "Addresses",
|
||||
mqRabbitmqAddressesPlaceholder: "127.0.0.1:5672",
|
||||
mqRabbitmqAddressesRequired: "RabbitMQ addresses are required",
|
||||
mqRabbitmqAddressesInvalid: "RabbitMQ addresses are invalid",
|
||||
mqRabbitmqAddressesInvalid: "Enter RabbitMQ addresses as host:port (for example, 127.0.0.1:5672), without amqp://",
|
||||
mqVirtualHost: "Virtual Host",
|
||||
mqVirtualHostPlaceholder: "/",
|
||||
mqRabbitmqAdminUrl: "Management URL",
|
||||
|
||||
@@ -938,7 +938,7 @@ export default withEnglishFallback({
|
||||
mqRabbitmqAddresses: "Direcciones",
|
||||
mqRabbitmqAddressesPlaceholder: "127.0.0.1:5672",
|
||||
mqRabbitmqAddressesRequired: "Las direcciones de RabbitMQ son obligatorias",
|
||||
mqRabbitmqAddressesInvalid: "Las direcciones de RabbitMQ no son válidas",
|
||||
mqRabbitmqAddressesInvalid: "Introduce las direcciones de RabbitMQ como host:puerto (por ejemplo, 127.0.0.1:5672), sin amqp://",
|
||||
mqVirtualHost: "Host virtual",
|
||||
mqVirtualHostPlaceholder: "/",
|
||||
mqRabbitmqAdminUrl: "Management URL",
|
||||
|
||||
@@ -936,7 +936,7 @@ export default withEnglishFallback({
|
||||
mqRabbitmqAddresses: "Indirizzi",
|
||||
mqRabbitmqAddressesPlaceholder: "127.0.0.1:5672",
|
||||
mqRabbitmqAddressesRequired: "Gli indirizzi RabbitMQ sono obbligatori",
|
||||
mqRabbitmqAddressesInvalid: "Gli indirizzi RabbitMQ non sono validi",
|
||||
mqRabbitmqAddressesInvalid: "Inserisci gli indirizzi RabbitMQ come host:porta (ad esempio 127.0.0.1:5672), senza amqp://",
|
||||
mqVirtualHost: "Host virtuale",
|
||||
mqVirtualHostPlaceholder: "/",
|
||||
mqRabbitmqAdminUrl: "Management URL",
|
||||
|
||||
@@ -936,7 +936,7 @@ export default withEnglishFallback({
|
||||
mqRabbitmqAddresses: "アドレス",
|
||||
mqRabbitmqAddressesPlaceholder: "127.0.0.1:5672",
|
||||
mqRabbitmqAddressesRequired: "RabbitMQ アドレスは必須です",
|
||||
mqRabbitmqAddressesInvalid: "RabbitMQ アドレスが無効です",
|
||||
mqRabbitmqAddressesInvalid: "RabbitMQ アドレスは host:port 形式(例: 127.0.0.1:5672)で入力し、amqp:// は含めないでください",
|
||||
mqVirtualHost: "仮想ホスト",
|
||||
mqVirtualHostPlaceholder: "/",
|
||||
mqRabbitmqAdminUrl: "Management URL",
|
||||
|
||||
@@ -730,7 +730,7 @@ export default withEnglishFallback({
|
||||
mqRabbitmqAddresses: "주소",
|
||||
mqRabbitmqAddressesPlaceholder: "127.0.0.1:5672",
|
||||
mqRabbitmqAddressesRequired: "RabbitMQ 주소는 필수입니다",
|
||||
mqRabbitmqAddressesInvalid: "RabbitMQ 주소가 잘못되었습니다",
|
||||
mqRabbitmqAddressesInvalid: "RabbitMQ 주소를 host:port 형식(예: 127.0.0.1:5672)으로 입력하고 amqp://는 포함하지 마세요",
|
||||
mqVirtualHost: "가상 호스트",
|
||||
mqVirtualHostPlaceholder: "/",
|
||||
mqRabbitmqAdminUrl: "관리 URL",
|
||||
|
||||
@@ -937,7 +937,7 @@ export default withEnglishFallback({
|
||||
mqRabbitmqAddresses: "Endereços",
|
||||
mqRabbitmqAddressesPlaceholder: "127.0.0.1:5672",
|
||||
mqRabbitmqAddressesRequired: "Os endereços do RabbitMQ são obrigatórios",
|
||||
mqRabbitmqAddressesInvalid: "Os endereços do RabbitMQ são inválidos",
|
||||
mqRabbitmqAddressesInvalid: "Informe os endereços do RabbitMQ como host:porta (por exemplo, 127.0.0.1:5672), sem amqp://",
|
||||
mqVirtualHost: "Host virtual",
|
||||
mqVirtualHostPlaceholder: "/",
|
||||
mqRabbitmqAdminUrl: "Management URL",
|
||||
|
||||
@@ -664,7 +664,7 @@ export default withEnglishFallback({
|
||||
mqRabbitmqAddresses: "地址",
|
||||
mqRabbitmqAddressesPlaceholder: "127.0.0.1:5672",
|
||||
mqRabbitmqAddressesRequired: "RabbitMQ 地址不能为空",
|
||||
mqRabbitmqAddressesInvalid: "RabbitMQ 地址无效",
|
||||
mqRabbitmqAddressesInvalid: "RabbitMQ 地址无效,请填写 host:port(例如 127.0.0.1:5672),不要包含 amqp://",
|
||||
mqVirtualHost: "虚拟主机",
|
||||
mqVirtualHostPlaceholder: "/",
|
||||
mqRabbitmqAdminUrl: "Management URL",
|
||||
|
||||
@@ -936,7 +936,7 @@ export default withEnglishFallback({
|
||||
mqRabbitmqAddresses: "位址",
|
||||
mqRabbitmqAddressesPlaceholder: "127.0.0.1:5672",
|
||||
mqRabbitmqAddressesRequired: "RabbitMQ 位址不能為空",
|
||||
mqRabbitmqAddressesInvalid: "RabbitMQ 位址無效",
|
||||
mqRabbitmqAddressesInvalid: "RabbitMQ 位址無效,請填寫 host:port(例如 127.0.0.1:5672),不要包含 amqp://",
|
||||
mqVirtualHost: "虛擬主機",
|
||||
mqVirtualHostPlaceholder: "/",
|
||||
mqRabbitmqAdminUrl: "Management URL",
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { normalizeRabbitmqAddresses } from "@/lib/connection/rabbitmqAddresses";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { normalizeRabbitmqAddresses, parseRabbitmqAddress } from "@/lib/connection/rabbitmqAddresses";
|
||||
|
||||
describe("RabbitMQ addresses", () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("keeps comma-separated addresses", () => {
|
||||
expect(normalizeRabbitmqAddresses("node1:5672, node2:5672")).toBe("node1:5672,node2:5672");
|
||||
});
|
||||
@@ -11,12 +15,36 @@ describe("RabbitMQ addresses", () => {
|
||||
expect(normalizeRabbitmqAddresses("node1, node2:5673")).toBe("node1:5672,node2:5673");
|
||||
});
|
||||
|
||||
it("keeps IPv4 addresses with custom ports", () => {
|
||||
expect(normalizeRabbitmqAddresses("192.0.2.176:51016")).toBe("192.0.2.176:51016");
|
||||
expect(parseRabbitmqAddress("192.0.2.176:51016")).toEqual({ host: "192.0.2.176", port: 51016 });
|
||||
});
|
||||
|
||||
it("parses IPv4 addresses without the browser URL implementation", () => {
|
||||
vi.stubGlobal(
|
||||
"URL",
|
||||
class {
|
||||
constructor() {
|
||||
throw new Error("custom schemes are unsupported");
|
||||
}
|
||||
},
|
||||
);
|
||||
expect(normalizeRabbitmqAddresses("192.0.2.176:51016")).toBe("192.0.2.176:51016");
|
||||
expect(parseRabbitmqAddress("192.0.2.176:51016")).toEqual({ host: "192.0.2.176", port: 51016 });
|
||||
});
|
||||
|
||||
it("normalizes common address separators to commas", () => {
|
||||
expect(normalizeRabbitmqAddresses("node1:5672;node2:5672,node3:5672\nnode4:5672 node5:5672")).toBe("node1:5672,node2:5672,node3:5672,node4:5672,node5:5672");
|
||||
});
|
||||
|
||||
it("keeps IPv6 addresses", () => {
|
||||
expect(normalizeRabbitmqAddresses("[::1]:5672;[2001:db8::1]:5672")).toBe("[::1]:5672,[2001:db8::1]:5672");
|
||||
expect(parseRabbitmqAddress("[::1]:5672")).toEqual({ host: "::1", port: 5672 });
|
||||
});
|
||||
|
||||
it("appends the default port to IPv6 addresses without a port", () => {
|
||||
expect(normalizeRabbitmqAddresses("[::1]")).toBe("[::1]:5672");
|
||||
expect(parseRabbitmqAddress("[::1]")).toEqual({ host: "::1", port: 5672 });
|
||||
});
|
||||
|
||||
it("rejects empty addresses", () => {
|
||||
@@ -30,4 +58,10 @@ describe("RabbitMQ addresses", () => {
|
||||
it("rejects invalid address values", () => {
|
||||
expect(() => normalizeRabbitmqAddresses("node1:5672/path,node2:5672")).toThrow("RabbitMQ addresses are invalid");
|
||||
});
|
||||
|
||||
it("rejects invalid ports", () => {
|
||||
expect(() => normalizeRabbitmqAddresses("node1:")).toThrow("RabbitMQ addresses are invalid");
|
||||
expect(() => normalizeRabbitmqAddresses("node1:0")).toThrow("RabbitMQ addresses are invalid");
|
||||
expect(() => normalizeRabbitmqAddresses("node1:65536")).toThrow("RabbitMQ addresses are invalid");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
const RABBITMQ_ADDRESS_SEPARATOR = /[\s,;,;]+/u;
|
||||
const RABBITMQ_DEFAULT_PORT = "5672";
|
||||
const RABBITMQ_PORT_PATTERN = /^\d+$/u;
|
||||
const RABBITMQ_INVALID_HOST_CHARACTER = /[\s/?#@%\\\u200b-\u200d\ufeff]/u;
|
||||
|
||||
export interface RabbitmqAddress {
|
||||
host: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
function requireRabbitmqAddresses(value: string): string {
|
||||
const trimmed = value.trim();
|
||||
@@ -7,20 +14,66 @@ function requireRabbitmqAddresses(value: string): string {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function normalizeRabbitmqAddress(address: string): string {
|
||||
function invalidRabbitmqAddresses(): never {
|
||||
throw new Error("RabbitMQ addresses are invalid");
|
||||
}
|
||||
|
||||
function validateRabbitmqPort(port: string): void {
|
||||
if (!RABBITMQ_PORT_PATTERN.test(port)) invalidRabbitmqAddresses();
|
||||
const parsed = Number(port);
|
||||
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) invalidRabbitmqAddresses();
|
||||
}
|
||||
|
||||
function validateRabbitmqHostname(host: string): void {
|
||||
if (!host || RABBITMQ_INVALID_HOST_CHARACTER.test(host)) invalidRabbitmqAddresses();
|
||||
}
|
||||
|
||||
function validateRabbitmqIpv6(host: string): void {
|
||||
try {
|
||||
const parsed = new URL(`http://[${host}]`);
|
||||
if (!parsed.hostname || parsed.username || parsed.password || parsed.port || parsed.search || parsed.hash || parsed.pathname !== "/") {
|
||||
invalidRabbitmqAddresses();
|
||||
}
|
||||
} catch {
|
||||
invalidRabbitmqAddresses();
|
||||
}
|
||||
}
|
||||
|
||||
export function parseRabbitmqAddress(address: string): RabbitmqAddress {
|
||||
if (address.includes("://")) {
|
||||
throw new Error("RabbitMQ addresses must be host:port values without a URL scheme");
|
||||
}
|
||||
let parsed: URL;
|
||||
try {
|
||||
parsed = new URL(`amqp://${address}`);
|
||||
} catch {
|
||||
throw new Error("RabbitMQ addresses are invalid");
|
||||
|
||||
if (address.startsWith("[")) {
|
||||
const match = address.match(/^\[([^\]]+)\](?::([^:]+))?$/u);
|
||||
if (!match) invalidRabbitmqAddresses();
|
||||
const [, host, port] = match;
|
||||
validateRabbitmqIpv6(host);
|
||||
if (port) {
|
||||
validateRabbitmqPort(port);
|
||||
return { host, port: Number(port) };
|
||||
}
|
||||
return { host, port: Number(RABBITMQ_DEFAULT_PORT) };
|
||||
}
|
||||
if (!parsed.hostname || parsed.username || parsed.password || parsed.search || parsed.hash || (parsed.pathname && parsed.pathname !== "/")) {
|
||||
throw new Error("RabbitMQ addresses are invalid");
|
||||
|
||||
if (address.includes("[") || address.includes("]")) invalidRabbitmqAddresses();
|
||||
const separator = address.indexOf(":");
|
||||
if (separator !== address.lastIndexOf(":")) invalidRabbitmqAddresses();
|
||||
const host = separator >= 0 ? address.slice(0, separator) : address;
|
||||
const port = separator >= 0 ? address.slice(separator + 1) : "";
|
||||
if (!host || (separator >= 0 && !port)) invalidRabbitmqAddresses();
|
||||
validateRabbitmqHostname(host);
|
||||
if (port) {
|
||||
validateRabbitmqPort(port);
|
||||
return { host, port: Number(port) };
|
||||
}
|
||||
return parsed.port ? address : `${address}:${RABBITMQ_DEFAULT_PORT}`;
|
||||
return { host, port: Number(RABBITMQ_DEFAULT_PORT) };
|
||||
}
|
||||
|
||||
function normalizeRabbitmqAddress(address: string): string {
|
||||
const parsed = parseRabbitmqAddress(address);
|
||||
const hasExplicitPort = address.startsWith("[") ? address.includes("]:") : address.includes(":");
|
||||
return hasExplicitPort ? address : `${address}:${parsed.port}`;
|
||||
}
|
||||
|
||||
export function normalizeRabbitmqAddresses(value: string): string {
|
||||
|
||||
Reference in New Issue
Block a user