Skip to content

Commit 6a98bd3

Browse files
HCK-10586: add ipv6 escaping (#150)
* HCK-10586: add ipv6 escaping * HCK-10586: enchanced ipv6 escaping logic
1 parent 9b8d86b commit 6a98bd3

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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@
4848
"cassandra-driver": "4.7.2",
4949
"fast-xml-parser": "4.4.1",
5050
"jks-js": "1.1.3",
51-
"lodash": "4.17.21"
51+
"lodash": "4.17.21",
52+
"ip": "2.0.1"
5253
},
5354
"lint-staged": {
5455
"*.{js,json}": "prettier --write"

reverse_engineering/cassandraHelper.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const { getEntityLevelConfig } = require('../helpers/levelConfigHelper');
77
const CassandraRetryPolicy = require('./cassandraRetryPolicy');
88
const xmlParser = require('fast-xml-parser');
99
const filterComplexUdt = require('./helpers/filterComplexUdt');
10+
const { escapeV6IpForURL } = require('./helpers/escapeV6IPForURL');
1011

1112
const state = {
1213
client: null,
@@ -270,7 +271,7 @@ module.exports = () => {
270271
? { username: 'token', password: info.astraToken }
271272
: { username: info.user, password: info.password };
272273
const authProvider = new cassandra.auth.PlainTextAuthProvider(credentials.username, credentials.password);
273-
const contactPoints = info.hosts.map(item => `${item.host}:${item.port}`);
274+
const contactPoints = info.hosts.map(item => `${escapeV6IpForURL({ host: item.host })}:${item.port}`);
274275
const readTimeout = validateRequestTimeout(info.requestTimeout, info.queryRequestTimeout);
275276

276277
return getSslOptions(info, app, logger).then(sslOptions => {
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+
};

0 commit comments

Comments
 (0)