Skip to content

Commit 566f0a1

Browse files
jasnellruyadorno
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 ed7eab1 commit 566f0a1

File tree

3 files changed

+182
-100
lines changed

3 files changed

+182
-100
lines changed

doc/api/net.md

Lines changed: 11 additions & 0 deletions
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

Lines changed: 33 additions & 1 deletion
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

Lines changed: 138 additions & 99 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy