@open-condo/miniapp-utils
Version:
A set of helper functions / components / hooks used to build new condo apps fast
394 lines (389 loc) • 13.3 kB
JavaScript
// src/helpers/ip/ranges.ts
var ranges_default = {
/** localhost IP ranges */
localhost: {
/** the localhost address ranges for IPv4 */
ipv4: ["127.0.0.0/8"],
/** the localhost address ranges for IPv6 */
ipv6: ["::1/128"]
},
/** private IP ranges */
private: {
/** private address ranges for IPv4 */
ipv4: [
"10.0.0.0/8",
// RFC 1918
"172.16.0.0/12",
// RFC 1918
"192.168.0.0/16"
// RFC 1918
],
/** private address ranges for IPv6 */
ipv6: [
"fe80::/10",
// link-local address
"fc00::/7"
// unique local address (ULA)
]
},
/** reserved IP ranges */
reserved: {
/** reserved address ranges for IPv4 */
ipv4: [
"0.0.0.0/8",
// broadcast "this"
"100.64.0.0/10",
// carrier-grade NAT
"169.254.0.0/16",
// DHCP fallback
"192.0.0.0/24",
// IANA Special Purpose Address Registry
"192.0.2.0/24",
// TEST-NET-1 for documentation examples
"192.88.99.0/24",
// deprecated 6to4 anycast relays
"198.18.0.0/15",
// for testing inter-network comms between two subnets
"198.51.100.0/24",
// TEST-NET-2 for documentation examples
"203.0.113.0/24",
// TEST-NET-3 for documentation examples
"224.0.0.0/4",
// multicast
"240.0.0.0/4",
// reserved unspecified
"255.255.255.255/32"
// limited broadcast address
],
/** reserved address ranges for IPv6 */
ipv6: [
"::/128",
// unspecified address
"64:ff9b::/96",
// IPv4/IPv6 translation
"100::/64",
// discard prefix
"2001::/32",
// Teredo tunneling
"2001:10::/28",
// deprecated
"2001:20::/28",
// ORCHIDv2
"2001:db8::/32",
// for documentation and examples
"2002::/16",
// 6to4
"ff00::/8"
// multicast
]
}
};
// src/helpers/ip/utils.ts
var v4Seg = "(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])";
var v4Str = `(${v4Seg}[.]){3}${v4Seg}`;
var IPv4Reg = new RegExp(`^${v4Str}$`);
var v6Seg = "(?:[0-9a-fA-F]{1,4})";
var IPv6Reg = new RegExp(
`^((?:${v6Seg}:){7}(?:${v6Seg}|:)|(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|(?:${v6Seg}:){5}(?::${v4Str}|(:${v6Seg}){1,2}|:)|(?:${v6Seg}:){4}(?:(:${v6Seg}){0,1}:${v4Str}|(:${v6Seg}){1,3}|:)|(?:${v6Seg}:){3}(?:(:${v6Seg}){0,2}:${v4Str}|(:${v6Seg}){1,4}|:)|(?:${v6Seg}:){2}(?:(:${v6Seg}){0,3}:${v4Str}|(:${v6Seg}){1,5}|:)|(?:${v6Seg}:){1}(?:(:${v6Seg}){0,4}:${v4Str}|(:${v6Seg}){1,6}|:)|(?::((?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:)))(%[0-9a-zA-Z]{1,})?$`
);
function isIPv4(s) {
return IPv4Reg.test(s);
}
function isIPv6(s) {
return IPv6Reg.test(s);
}
function isIP(s) {
if (isIPv4(s)) return 4;
if (isIPv6(s)) return 6;
return 0;
}
// src/helpers/ip/ipv4.ts
function ipv4ToLong(ip) {
if (!isIPv4(ip)) {
throw new Error(`not a valid IPv4 address: ${ip}`);
}
const octets = ip.split(".");
return (parseInt(octets[0], 10) << 24) + (parseInt(octets[1], 10) << 16) + (parseInt(octets[2], 10) << 8) + parseInt(octets[3], 10) >>> 0;
}
function createLongChecker(subnet) {
const [subnetAddress, prefixLengthString] = subnet.split("/");
const prefixLength = parseInt(prefixLengthString, 10);
if (!subnetAddress || !Number.isInteger(prefixLength)) {
throw new Error(`not a valid IPv4 subnet: ${subnet}`);
}
if (prefixLength < 0 || prefixLength > 32) {
throw new Error(`not a valid IPv4 prefix length: ${prefixLength} (from ${subnet})`);
}
const subnetLong = ipv4ToLong(subnetAddress);
return (addressLong) => {
if (prefixLength === 0) {
return true;
}
const subnetPrefix = subnetLong >> 32 - prefixLength;
const addressPrefix = addressLong >> 32 - prefixLength;
return subnetPrefix === addressPrefix;
};
}
function createChecker(subnetOrSubnets) {
if (Array.isArray(subnetOrSubnets)) {
const checks = subnetOrSubnets.map((subnet) => createLongChecker(subnet));
return (address) => {
const addressLong = ipv4ToLong(address);
return checks.some((check2) => check2(addressLong));
};
}
const check = createLongChecker(subnetOrSubnets);
return (address) => {
const addressLong = ipv4ToLong(address);
return check(addressLong);
};
}
var specialNetsCache = {};
function isLocalhost(address) {
if (!("localhost" in specialNetsCache)) {
specialNetsCache["localhost"] = createChecker(ranges_default.localhost.ipv4);
}
return specialNetsCache["localhost"](address);
}
function isSpecial(address) {
if (!("special" in specialNetsCache)) {
specialNetsCache["special"] = createChecker([
...ranges_default.private.ipv4,
...ranges_default.localhost.ipv4,
...ranges_default.reserved.ipv4
]);
}
return specialNetsCache["special"](address);
}
// src/helpers/ip/ipv6.ts
var dot = /\./;
var mappedIpv4 = /^(.+:ffff:)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:%.+)?$/;
var colon = /:/;
var doubleColon = /::/;
function mappedIpv4ToIpv6(ip) {
const matches = ip.match(mappedIpv4);
if (!matches || !isIPv4(matches[2])) {
throw new Error(`not a mapped IPv4 address: ${ip}`);
}
const prefix = matches[1];
const ipv4 = matches[2];
const parts = ipv4.split(dot).map((x) => parseInt(x, 10));
const segment7 = ((parts[0] << 8) + parts[1]).toString(16);
const segment8 = ((parts[2] << 8) + parts[3]).toString(16);
return `${prefix}${segment7}:${segment8}`;
}
function extractMappedIpv4(ip) {
const matches = ip.match(mappedIpv4);
if (!matches || !isIPv4(matches[2])) {
throw new Error(`not a mapped IPv4 address: ${ip}`);
}
return matches[2];
}
function getIpv6Segments(ip) {
if (!isIPv6(ip)) {
throw new Error(`not a valid IPv6 address: ${ip}`);
}
if (dot.test(ip)) {
return getIpv6Segments(mappedIpv4ToIpv6(ip));
}
const [beforeChunk, afterChunk] = ip.split(doubleColon);
const beforeParts = beforeChunk && beforeChunk.split(colon) || [];
const afterParts = afterChunk && afterChunk.split(colon) || [];
const missingSegments = new Array(8 - (beforeParts.length + afterParts.length));
return beforeParts.concat(missingSegments, afterParts);
}
function createChecker2(subnetOrSubnets) {
if (Array.isArray(subnetOrSubnets)) {
const checks = subnetOrSubnets.map((subnet) => createSegmentChecker(subnet));
return (address) => {
const segments = getIpv6Segments(address);
return checks.some((check2) => check2(segments));
};
}
const check = createSegmentChecker(subnetOrSubnets);
return (address) => {
const segments = getIpv6Segments(address);
return check(segments);
};
}
function createSegmentChecker(subnet) {
const [subnetAddress, prefixLengthString] = subnet.split("/");
const prefixLength = parseInt(prefixLengthString, 10);
if (!subnetAddress || !Number.isInteger(prefixLength)) {
throw new Error(`not a valid IPv6 CIDR subnet: ${subnet}`);
}
if (prefixLength < 0 || prefixLength > 128) {
throw new Error(`not a valid IPv6 prefix length: ${prefixLength} (from ${subnet})`);
}
const subnetSegments = getIpv6Segments(subnetAddress);
return (addressSegments) => {
for (let i = 0; i < 8; ++i) {
const bitCount = Math.min(prefixLength - i * 16, 16);
if (bitCount <= 0) {
break;
}
const subnetPrefix = (subnetSegments[i] && parseInt(subnetSegments[i], 16) || 0) >> 16 - bitCount;
const addressPrefix = (addressSegments[i] && parseInt(addressSegments[i], 16) || 0) >> 16 - bitCount;
if (subnetPrefix !== addressPrefix) {
return false;
}
}
return true;
};
}
var specialNetsCache2 = {};
function isLocalhost2(address) {
if (!("localhost" in specialNetsCache2)) {
specialNetsCache2["localhost"] = createChecker2(ranges_default.localhost.ipv6);
}
return specialNetsCache2["localhost"](address);
}
function isIPv4MappedAddress(address) {
if (!("mapped" in specialNetsCache2)) {
specialNetsCache2["mapped"] = createChecker2("::ffff:0:0/96");
}
if (specialNetsCache2["mapped"](address)) {
const matches = address.match(mappedIpv4);
return Boolean(matches && isIPv4(matches[2]));
}
return false;
}
function isSpecial2(address) {
if (!("special" in specialNetsCache2)) {
specialNetsCache2["special"] = createChecker2([
...ranges_default.private.ipv6,
...ranges_default.localhost.ipv6,
...ranges_default.reserved.ipv6
]);
}
return specialNetsCache2["special"](address);
}
// src/helpers/ip/index.ts
function isLocalhost3(address) {
if (isIPv6(address)) {
if (isIPv4MappedAddress(address)) {
return isLocalhost(extractMappedIpv4(address));
}
return isLocalhost2(address);
} else {
return isLocalhost(address);
}
}
function isSpecial3(address) {
if (isIPv6(address)) {
if (isIPv4MappedAddress(address)) {
return isSpecial(extractMappedIpv4(address));
}
return isSpecial2(address);
} else {
return isSpecial(address);
}
}
// src/helpers/urls.ts
var REGEXP_ESCAPE_CHARS = /[\\^$.*+?()[\]{}|]/g;
var WILDCARD_REGEXP_PART = "([a-zA-Z0-9-]{1,63})";
var WILDCARD_REGEXP_PART_ESCAPED = _escapeRegexp(WILDCARD_REGEXP_PART);
function isSafeUrl(url) {
if (!url || typeof url !== "string") return false;
let decodedUrl;
try {
decodedUrl = decodeURI(url);
} catch (error) {
return false;
}
const normalizedUrl = decodedUrl.replace(/[\u0000-\u001F\s]/g, "").toLowerCase();
return !normalizedUrl.includes("javascript:");
}
function replaceDomainPrefix(originalUrl, prefix) {
const url = new URL(originalUrl);
const originalHostnameParts = url.hostname.split(".");
const suffixParts = originalHostnameParts.length > 2 ? originalHostnameParts.slice(1) : originalHostnameParts;
const suffix = suffixParts.join(".");
url.hostname = `${prefix}.${suffix}`;
return url.toString();
}
function _escapeRegexp(source) {
return source.replace(REGEXP_ESCAPE_CHARS, "\\$&");
}
function _getUrl(strUrl, cache) {
if (!cache) return new URL(strUrl);
let parsed = cache.get(strUrl);
if (!parsed) {
parsed = new URL(strUrl);
cache.set(strUrl, parsed);
}
return parsed;
}
function _getCachedRegexp(pattern, cache) {
if (!cache) return new RegExp(pattern, "g");
let re = cache.get(pattern);
if (!re) {
re = new RegExp(pattern, "g");
cache.set(pattern, re);
}
re.lastIndex = 0;
return re;
}
function getWildcardDomain(domain, cache) {
const url = _getUrl(domain, cache);
const [prefix, ...rest] = url.hostname.split(".");
const port = url.port ? `:${url.port}` : "";
return {
wildcardDomain: `${url.protocol}//*.${rest.join(".")}${port}`,
prefix
};
}
function replaceDomain(source, from, to, options = {}) {
const { encoded = false, urlsCache, regexpsCache } = options;
const fromUrl = _getUrl(from, urlsCache);
const decodedHostname = decodeURIComponent(fromUrl.hostname);
if (!decodedHostname.startsWith("*.")) {
const escapedFrom2 = _escapeRegexp(from);
const fromWithBoundary2 = `${escapedFrom2}(?!\\.${WILDCARD_REGEXP_PART}|${WILDCARD_REGEXP_PART})`;
const fromSearch2 = _getCachedRegexp(fromWithBoundary2, regexpsCache);
let replaced2 = source.replace(fromSearch2, to);
if (encoded) {
const fromEncoded = encodeURIComponent(from);
const escapedFromEncoded = _escapeRegexp(fromEncoded);
const fromEncodedWithBoundary = `${escapedFromEncoded}(?!%2E${WILDCARD_REGEXP_PART}|${WILDCARD_REGEXP_PART})`;
const fromEncodedSearch = _getCachedRegexp(fromEncodedWithBoundary, regexpsCache);
replaced2 = replaced2.replace(fromEncodedSearch, encodeURIComponent(to));
}
return replaced2;
}
const fromPattern = from.replace("*", WILDCARD_REGEXP_PART);
const toPattern = to.replace("*", "$1");
const escapedFrom = _escapeRegexp(fromPattern).replace(WILDCARD_REGEXP_PART_ESCAPED, WILDCARD_REGEXP_PART);
const fromWithBoundary = `${escapedFrom}(?!\\.${WILDCARD_REGEXP_PART}|${WILDCARD_REGEXP_PART})`;
const fromSearch = _getCachedRegexp(fromWithBoundary, regexpsCache);
let replaced = source.replace(fromSearch, toPattern);
if (encoded) {
const fromEncoded = encodeURIComponent(from).replace("*", WILDCARD_REGEXP_PART);
const toEncoded = encodeURIComponent(to).replace("*", "$1");
const escapedFromEncoded = _escapeRegexp(fromEncoded).replace(WILDCARD_REGEXP_PART_ESCAPED, WILDCARD_REGEXP_PART);
const fromEncodedWithBoundary = `${escapedFromEncoded}(?!%2E${WILDCARD_REGEXP_PART}|${WILDCARD_REGEXP_PART})`;
const fromEncodedSearch = _getCachedRegexp(fromEncodedWithBoundary, regexpsCache);
replaced = replaced.replace(fromEncodedSearch, toEncoded);
}
return replaced;
}
function getUrlMeta(url) {
try {
const u = new URL(url);
const hostnameUnWrapped = u.hostname.startsWith("[") && u.hostname.endsWith("]") ? u.hostname.slice(1, -1) : u.hostname;
u.isIP = isIP(hostnameUnWrapped) > 0;
u.isSpecialIP = u.isIP && isSpecial3(hostnameUnWrapped);
u.isLocalhost = u.hostname === "localhost" || u.isIP && isLocalhost3(hostnameUnWrapped);
return u;
} catch {
return null;
}
}
export {
getUrlMeta,
getWildcardDomain,
isSafeUrl,
replaceDomain,
replaceDomainPrefix
};
//# sourceMappingURL=urls.mjs.map