Skip to content

Commit f3b64af

Browse files
HCK-10586: add ipv6 escaping (#60)
* HCK-10586: add ipv6 escaping * HCK-10586: remove template * HCK-10586: enchanced ipv6 escaping logic
1 parent 3a9cd21 commit f3b64af

File tree

4 files changed

+75
-2
lines changed

4 files changed

+75
-2
lines changed

package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"disabled": false,
5151
"dependencies": {
5252
"async": "3.2.6",
53+
"ip": "2.0.1",
5354
"lodash": "4.17.21",
5455
"neo4j-driver": "4.4.11"
5556
},
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
const ip = require('ip');
2+
3+
/**
4+
* @see https://en.wikipedia.org/wiki/IPv6_address
5+
* Literal IPv6 addresses in resources (URLs):
6+
------------------------------------------------
7+
* Colon (:) characters in IPv6 addresses may conflict with the established syntax of resource identifiers,
8+
* such as URIs and URLs. The colon is conventionally used to terminate the host path before a port number.[10]
9+
* To alleviate this conflict, literal IPv6 addresses are enclosed in square brackets in such resource identifiers;
10+
* When the URL doesn't conatoin the port the notation is http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]/
11+
* When the URL also contains a port number the notation is: https://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443/
12+
*
13+
* @param {{
14+
* host: string
15+
* }} param
16+
* @returns {string}
17+
*/
18+
function escapeV6IpForURL({ host }) {
19+
/**
20+
* If the host is already URL compatible then the ip lib will return false > ip.isV6Format('[::1]') false
21+
* If the host is a proper ipv6 ip then the `new URL(host)` will fail with Uncaught TypeError: Invalid URL code: 'ERR_INVALID_URL',
22+
* !ip.isV4Format(host) check required because isV6Format returns true for ipv4 address because of backward compatibility
23+
*/
24+
if (ip.isV6Format(host) && !ip.isV4Format(host)) {
25+
return `[${host}]`;
26+
}
27+
28+
const isUrlValid = isValidURL(host);
29+
if (isUrlValid) {
30+
return host;
31+
}
32+
33+
const urlWithIpV6HostRegExp = new RegExp(/^http(s)?:\/\/(?<unescapedIpWithPort>([a-z0-9]{0,4}:?)+)/gim);
34+
const { unescapedIpWithPort } = urlWithIpV6HostRegExp.exec(host)?.groups ?? {};
35+
36+
if (!unescapedIpWithPort) {
37+
return host;
38+
}
39+
40+
const separatedIpPortionsAndPort = unescapedIpWithPort.split(':');
41+
const ipPortions = separatedIpPortionsAndPort.slice(0, separatedIpPortionsAndPort.length - 1);
42+
const port = separatedIpPortionsAndPort.at(-1);
43+
const escapedIpWithPort = `[${ipPortions.join(':')}]:${port}`;
44+
45+
return host.replace(unescapedIpWithPort, escapedIpWithPort);
46+
}
47+
48+
/**
49+
* @param {string} url
50+
* @returns {boolean}
51+
*/
52+
function isValidURL(url) {
53+
try {
54+
new URL(url);
55+
56+
return true;
57+
} catch {
58+
return false;
59+
}
60+
}
61+
62+
module.exports = {
63+
escapeV6IpForURL,
64+
};

reverse_engineering/neo4jHelper.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const neo4j = require('neo4j-driver');
22
const fs = require('fs');
33
const _ = require('lodash');
4+
const { escapeV6IpForURL } = require('./escapeV6IPForURL');
45

56
let driver;
67
let isSshTunnel = false;
@@ -430,10 +431,10 @@ const getConnectionURI = info => {
430431
if (neo4jProtocolRegex.test(info.host)) {
431432
host = info.host;
432433
} else {
433-
host = `bolt://${info.host}`;
434+
host = `bolt://${escapeV6IpForURL({ host: info.host })}`;
434435
}
435436
if (info.port) {
436-
host = `${host}:${info.port}`;
437+
host = `${escapeV6IpForURL({ host: info.host })}:${info.port}`;
437438
}
438439

439440
return host;

0 commit comments

Comments
 (0)