Skip to content

Commit e46c0b0

Browse files
spike77453Nerivec
andauthored
fix: Allow literal (RFC2732) IPv6 addresses in TCP URI (#1601)
Co-authored-by: Nerivec <[email protected]>
1 parent 824028c commit e46c0b0

File tree

2 files changed

+98
-6
lines changed

2 files changed

+98
-6
lines changed

src/adapter/utils.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
1+
import assert from "node:assert";
12
import {existsSync, readFileSync} from "node:fs";
3+
import {urlToHttpOptions} from "node:url";
24
import type * as Models from "../models";
35

46
export function isTcpPath(path: string): boolean {
5-
// tcp path must be:
6-
// tcp://<host>:<port>
7-
const regex = /^(?:tcp:\/\/)[\w.-]+[:][\d]+$/gm;
8-
return regex.test(path);
7+
try {
8+
// validation as side-effect
9+
new URL(path);
10+
11+
return true;
12+
} catch {
13+
return false;
14+
}
915
}
1016

1117
export function parseTcpPath(path: string): {host: string; port: number} {
12-
// built-in extra validation
13-
const info = new URL(path);
18+
const info = urlToHttpOptions(new URL(path));
19+
20+
// urlToHttpOptions has a weird return type, extra validation doesn't hurt
21+
assert(info.hostname && info.port);
1422

1523
return {
1624
host: info.hostname,

test/adapter/adapter.test.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {DeconzAdapter} from "../../src/adapter/deconz/adapter/deconzAdapter";
88
import {EmberAdapter} from "../../src/adapter/ember/adapter/emberAdapter";
99
import {EZSPAdapter} from "../../src/adapter/ezsp/adapter/ezspAdapter";
1010
import {SerialPort} from "../../src/adapter/serialPort";
11+
import {parseTcpPath} from "../../src/adapter/utils";
1112
import {ZStackAdapter} from "../../src/adapter/z-stack/adapter/zStackAdapter";
1213
import {ZBOSSAdapter} from "../../src/adapter/zboss/adapter/zbossAdapter";
1314
import {ZiGateAdapter} from "../../src/adapter/zigate/adapter/zigateAdapter";
@@ -369,6 +370,89 @@ describe("Adapter", () => {
369370
}),
370371
).rejects.toThrow(`Cannot discover TCP adapters at this time. Specify valid 'adapter' and 'port' in your configuration.`);
371372
});
373+
374+
it("handles IPv4", async () => {
375+
const adapter = await Adapter.create(
376+
{panID: 0x1a62, channelList: [11]},
377+
{path: "tcp://192.168.12.34:6638", adapter: "zstack"},
378+
"test.db.backup",
379+
{disableLED: false},
380+
);
381+
382+
// @ts-expect-error protected
383+
const adapterOptions = adapter.serialPortOptions;
384+
385+
expect(adapterOptions).toStrictEqual({
386+
path: "tcp://192.168.12.34:6638",
387+
adapter: "zstack",
388+
});
389+
390+
const tcpPath = parseTcpPath(adapterOptions.path!);
391+
392+
expect(tcpPath).toStrictEqual({
393+
host: "192.168.12.34",
394+
port: 6638,
395+
});
396+
});
397+
398+
it("handles IPv6", async () => {
399+
const adapter = await Adapter.create(
400+
{panID: 0x1a62, channelList: [11]},
401+
{path: "tcp://[fd73:5e46:b9f0:0:80b5:4eff:dead:beef]:6638", adapter: "zstack"},
402+
"test.db.backup",
403+
{disableLED: false},
404+
);
405+
406+
// @ts-expect-error protected
407+
const adapterOptions = adapter.serialPortOptions;
408+
409+
expect(adapterOptions).toStrictEqual({
410+
path: "tcp://[fd73:5e46:b9f0:0:80b5:4eff:dead:beef]:6638",
411+
adapter: "zstack",
412+
});
413+
414+
const tcpPath = parseTcpPath(adapterOptions.path!);
415+
416+
expect(tcpPath).toStrictEqual({
417+
host: "fd73:5e46:b9f0:0:80b5:4eff:dead:beef",
418+
port: 6638,
419+
});
420+
});
421+
422+
it("handles domain name", async () => {
423+
const adapter = await Adapter.create(
424+
{panID: 0x1a62, channelList: [11]},
425+
{path: "tcp://myadapter.local:6638", adapter: "zstack"},
426+
"test.db.backup",
427+
{disableLED: false},
428+
);
429+
430+
// @ts-expect-error protected
431+
const adapterOptions = adapter.serialPortOptions;
432+
433+
expect(adapterOptions).toStrictEqual({
434+
path: "tcp://myadapter.local:6638",
435+
adapter: "zstack",
436+
});
437+
438+
const tcpPath = parseTcpPath(adapterOptions.path!);
439+
440+
expect(tcpPath).toStrictEqual({
441+
host: "myadapter.local",
442+
port: 6638,
443+
});
444+
});
445+
446+
it("handles invalid IPv6", async () => {
447+
await expect(
448+
Adapter.create(
449+
{panID: 0x1a62, channelList: [11]},
450+
{path: "tcp://fd73:5e46:b9f0:0:80b5:4eff:dead:beef:6638", adapter: "zstack"},
451+
"test.db.backup",
452+
{disableLED: false},
453+
),
454+
).rejects.toThrow("Invalid TCP path, expected format: tcp://<host>:<port>");
455+
});
372456
});
373457

374458
describe("USB discovery", () => {

0 commit comments

Comments
 (0)