Skip to content

Commit 9fba5e1

Browse files
jasnelltargos
authored andcommitted
net: add SocketAddress.parse
Adds a new `net.SocketAddress.parse(...)` API. ```js const addr = SocketAddress.parse('123.123.123.123:1234'); console.log(addr.address); // 123.123.123.123 console.log(addr.port); 1234 ``` PR-URL: #56076 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 565b04a commit 9fba5e1

File tree

3 files changed

+182
-100
lines changed

3 files changed

+182
-100
lines changed

doc/api/net.md

+11
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,17 @@ added:
244244

245245
* Type {number}
246246

247+
### `SocketAddress.parse(input)`
248+
249+
<!-- YAML
250+
added: REPLACEME
251+
-->
252+
253+
* `input` {string} An input string containing an IP address and optional port,
254+
e.g. `123.1.2.3:1234` or `[1::1]:1234`.
255+
* Returns: {net.SocketAddress} Returns a `SocketAddress` if parsing was successful.
256+
Otherwise returns `undefined`.
257+
247258
## Class: `net.Server`
248259

249260
<!-- YAML

lib/internal/socketaddress.js

+33-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ const {
3737
kDeserialize,
3838
} = require('internal/worker/js_transferable');
3939

40+
const { URL } = require('internal/url');
41+
4042
const kHandle = Symbol('kHandle');
4143
const kDetail = Symbol('kDetail');
4244

@@ -74,7 +76,7 @@ class SocketAddress {
7476
validatePort(port, 'options.port');
7577
validateUint32(flowlabel, 'options.flowlabel', false);
7678

77-
this[kHandle] = new _SocketAddress(address, port, type, flowlabel);
79+
this[kHandle] = new _SocketAddress(address, port | 0, type, flowlabel | 0);
7880
this[kDetail] = this[kHandle].detail({
7981
address: undefined,
8082
port: undefined,
@@ -138,6 +140,36 @@ class SocketAddress {
138140
flowlabel: this.flowlabel,
139141
};
140142
}
143+
144+
/**
145+
* Parse an "${ip}:${port}" formatted string into a SocketAddress.
146+
* Returns undefined if the input cannot be successfully parsed.
147+
* @param {string} input
148+
* @returns {SocketAddress|undefined}
149+
*/
150+
static parse(input) {
151+
validateString(input, 'input');
152+
// While URL.parse is not expected to throw, there are several
153+
// other pieces here that do... the destucturing, the SocketAddress
154+
// constructor, etc. So we wrap this in a try/catch to be safe.
155+
try {
156+
const {
157+
hostname: address,
158+
port,
159+
} = URL.parse(`http://${input}`);
160+
if (address.startsWith('[') && address.endsWith(']')) {
161+
return new SocketAddress({
162+
address: address.slice(1, -1),
163+
port: port | 0,
164+
family: 'ipv6',
165+
});
166+
}
167+
return new SocketAddress({ address, port: port | 0 });
168+
} catch {
169+
// Ignore errors here. Return undefined if the input cannot
170+
// be successfully parsed or is not a proper socket address.
171+
}
172+
}
141173
}
142174

143175
class InternalSocketAddress {

test/parallel/test-socketaddress.js

+138-99
Original file line numberDiff line numberDiff line change
@@ -17,121 +17,160 @@ const {
1717
const { internalBinding } = require('internal/test/binding');
1818
const {
1919
SocketAddress: _SocketAddress,
20-
AF_INET
20+
AF_INET,
2121
} = internalBinding('block_list');
2222

23-
{
24-
const sa = new SocketAddress();
25-
strictEqual(sa.address, '127.0.0.1');
26-
strictEqual(sa.port, 0);
27-
strictEqual(sa.family, 'ipv4');
28-
strictEqual(sa.flowlabel, 0);
23+
const { describe, it } = require('node:test');
2924

30-
const mc = new MessageChannel();
31-
mc.port1.onmessage = common.mustCall(({ data }) => {
32-
ok(SocketAddress.isSocketAddress(data));
25+
describe('net.SocketAddress...', () => {
3326

34-
strictEqual(data.address, '127.0.0.1');
35-
strictEqual(data.port, 0);
36-
strictEqual(data.family, 'ipv4');
37-
strictEqual(data.flowlabel, 0);
27+
it('is cloneable', () => {
28+
const sa = new SocketAddress();
29+
strictEqual(sa.address, '127.0.0.1');
30+
strictEqual(sa.port, 0);
31+
strictEqual(sa.family, 'ipv4');
32+
strictEqual(sa.flowlabel, 0);
3833

39-
mc.port1.close();
34+
const mc = new MessageChannel();
35+
mc.port1.onmessage = common.mustCall(({ data }) => {
36+
ok(SocketAddress.isSocketAddress(data));
37+
38+
strictEqual(data.address, '127.0.0.1');
39+
strictEqual(data.port, 0);
40+
strictEqual(data.family, 'ipv4');
41+
strictEqual(data.flowlabel, 0);
42+
43+
mc.port1.close();
44+
});
45+
mc.port2.postMessage(sa);
4046
});
41-
mc.port2.postMessage(sa);
42-
}
43-
44-
{
45-
const sa = new SocketAddress({});
46-
strictEqual(sa.address, '127.0.0.1');
47-
strictEqual(sa.port, 0);
48-
strictEqual(sa.family, 'ipv4');
49-
strictEqual(sa.flowlabel, 0);
50-
}
51-
52-
{
53-
const sa = new SocketAddress({
54-
address: '123.123.123.123',
47+
48+
it('has reasonable defaults', () => {
49+
const sa = new SocketAddress({});
50+
strictEqual(sa.address, '127.0.0.1');
51+
strictEqual(sa.port, 0);
52+
strictEqual(sa.family, 'ipv4');
53+
strictEqual(sa.flowlabel, 0);
5554
});
56-
strictEqual(sa.address, '123.123.123.123');
57-
strictEqual(sa.port, 0);
58-
strictEqual(sa.family, 'ipv4');
59-
strictEqual(sa.flowlabel, 0);
60-
}
61-
62-
{
63-
const sa = new SocketAddress({
64-
address: '123.123.123.123',
65-
port: 80
55+
56+
it('interprets simple ipv4 correctly', () => {
57+
const sa = new SocketAddress({
58+
address: '123.123.123.123',
59+
});
60+
strictEqual(sa.address, '123.123.123.123');
61+
strictEqual(sa.port, 0);
62+
strictEqual(sa.family, 'ipv4');
63+
strictEqual(sa.flowlabel, 0);
6664
});
67-
strictEqual(sa.address, '123.123.123.123');
68-
strictEqual(sa.port, 80);
69-
strictEqual(sa.family, 'ipv4');
70-
strictEqual(sa.flowlabel, 0);
71-
}
72-
73-
{
74-
const sa = new SocketAddress({
75-
family: 'ipv6'
65+
66+
it('sets the port correctly', () => {
67+
const sa = new SocketAddress({
68+
address: '123.123.123.123',
69+
port: 80
70+
});
71+
strictEqual(sa.address, '123.123.123.123');
72+
strictEqual(sa.port, 80);
73+
strictEqual(sa.family, 'ipv4');
74+
strictEqual(sa.flowlabel, 0);
7675
});
77-
strictEqual(sa.address, '::');
78-
strictEqual(sa.port, 0);
79-
strictEqual(sa.family, 'ipv6');
80-
strictEqual(sa.flowlabel, 0);
81-
}
82-
83-
{
84-
const sa = new SocketAddress({
85-
family: 'ipv6',
86-
flowlabel: 1,
76+
77+
it('interprets simple ipv6 correctly', () => {
78+
const sa = new SocketAddress({
79+
family: 'ipv6'
80+
});
81+
strictEqual(sa.address, '::');
82+
strictEqual(sa.port, 0);
83+
strictEqual(sa.family, 'ipv6');
84+
strictEqual(sa.flowlabel, 0);
8785
});
88-
strictEqual(sa.address, '::');
89-
strictEqual(sa.port, 0);
90-
strictEqual(sa.family, 'ipv6');
91-
strictEqual(sa.flowlabel, 1);
92-
}
93-
94-
[1, false, 'hello'].forEach((i) => {
95-
throws(() => new SocketAddress(i), {
96-
code: 'ERR_INVALID_ARG_TYPE'
86+
87+
it('uses the flowlabel correctly', () => {
88+
const sa = new SocketAddress({
89+
family: 'ipv6',
90+
flowlabel: 1,
91+
});
92+
strictEqual(sa.address, '::');
93+
strictEqual(sa.port, 0);
94+
strictEqual(sa.family, 'ipv6');
95+
strictEqual(sa.flowlabel, 1);
9796
});
98-
});
9997

100-
[1, false, {}, [], 'test'].forEach((family) => {
101-
throws(() => new SocketAddress({ family }), {
102-
code: 'ERR_INVALID_ARG_VALUE'
98+
it('validates input correctly', () => {
99+
[1, false, 'hello'].forEach((i) => {
100+
throws(() => new SocketAddress(i), {
101+
code: 'ERR_INVALID_ARG_TYPE'
102+
});
103+
});
104+
105+
[1, false, {}, [], 'test'].forEach((family) => {
106+
throws(() => new SocketAddress({ family }), {
107+
code: 'ERR_INVALID_ARG_VALUE'
108+
});
109+
});
110+
111+
[1, false, {}, []].forEach((address) => {
112+
throws(() => new SocketAddress({ address }), {
113+
code: 'ERR_INVALID_ARG_TYPE'
114+
});
115+
});
116+
117+
[-1, false, {}, []].forEach((port) => {
118+
throws(() => new SocketAddress({ port }), {
119+
code: 'ERR_SOCKET_BAD_PORT'
120+
});
121+
});
122+
123+
throws(() => new SocketAddress({ flowlabel: -1 }), {
124+
code: 'ERR_OUT_OF_RANGE'
125+
});
103126
});
104-
});
105127

106-
[1, false, {}, []].forEach((address) => {
107-
throws(() => new SocketAddress({ address }), {
108-
code: 'ERR_INVALID_ARG_TYPE'
128+
it('InternalSocketAddress correctly inherits from SocketAddress', () => {
129+
// Test that the internal helper class InternalSocketAddress correctly
130+
// inherits from SocketAddress and that it does not throw when its properties
131+
// are accessed.
132+
133+
const address = '127.0.0.1';
134+
const port = 8080;
135+
const flowlabel = 0;
136+
const handle = new _SocketAddress(address, port, AF_INET, flowlabel);
137+
const addr = new InternalSocketAddress(handle);
138+
ok(addr instanceof SocketAddress);
139+
strictEqual(addr.address, address);
140+
strictEqual(addr.port, port);
141+
strictEqual(addr.family, 'ipv4');
142+
strictEqual(addr.flowlabel, flowlabel);
109143
});
110-
});
111144

112-
[-1, false, {}, []].forEach((port) => {
113-
throws(() => new SocketAddress({ port }), {
114-
code: 'ERR_SOCKET_BAD_PORT'
145+
it('SocketAddress.parse() works as expected', () => {
146+
const good = [
147+
{ input: '1.2.3.4', address: '1.2.3.4', port: 0, family: 'ipv4' },
148+
{ input: '192.168.257:1', address: '192.168.1.1', port: 1, family: 'ipv4' },
149+
{ input: '256', address: '0.0.1.0', port: 0, family: 'ipv4' },
150+
{ input: '999999999:12', address: '59.154.201.255', port: 12, family: 'ipv4' },
151+
{ input: '0xffffffff', address: '255.255.255.255', port: 0, family: 'ipv4' },
152+
{ input: '0x.0x.0', address: '0.0.0.0', port: 0, family: 'ipv4' },
153+
{ input: '[1:0::]', address: '1::', port: 0, family: 'ipv6' },
154+
{ input: '[1::8]:123', address: '1::8', port: 123, family: 'ipv6' },
155+
];
156+
157+
good.forEach((i) => {
158+
const addr = SocketAddress.parse(i.input);
159+
strictEqual(addr.address, i.address);
160+
strictEqual(addr.port, i.port);
161+
strictEqual(addr.family, i.family);
162+
});
163+
164+
const bad = [
165+
'not an ip',
166+
'abc.123',
167+
'259.1.1.1',
168+
'12:12:12',
169+
];
170+
171+
bad.forEach((i) => {
172+
strictEqual(SocketAddress.parse(i), undefined);
173+
});
115174
});
116-
});
117175

118-
throws(() => new SocketAddress({ flowlabel: -1 }), {
119-
code: 'ERR_OUT_OF_RANGE'
120176
});
121-
122-
{
123-
// Test that the internal helper class InternalSocketAddress correctly
124-
// inherits from SocketAddress and that it does not throw when its properties
125-
// are accessed.
126-
127-
const address = '127.0.0.1';
128-
const port = 8080;
129-
const flowlabel = 0;
130-
const handle = new _SocketAddress(address, port, AF_INET, flowlabel);
131-
const addr = new InternalSocketAddress(handle);
132-
ok(addr instanceof SocketAddress);
133-
strictEqual(addr.address, address);
134-
strictEqual(addr.port, port);
135-
strictEqual(addr.family, 'ipv4');
136-
strictEqual(addr.flowlabel, flowlabel);
137-
}

0 commit comments

Comments
 (0)