42 lines
1.5 KiB
JavaScript
42 lines
1.5 KiB
JavaScript
var dns = require('dns').promises;
|
|
var net = require('net');
|
|
|
|
function isPrivateIp(ip) {
|
|
if (!ip) return true;
|
|
if (net.isIPv4(ip)) {
|
|
var p = ip.split('.').map(Number);
|
|
if (p[0] === 10 || p[0] === 127 || p[0] === 0) return true;
|
|
if (p[0] === 100 && p[1] >= 64 && p[1] <= 127) return true;
|
|
if (p[0] === 169 && p[1] === 254) return true;
|
|
if (p[0] === 172 && p[1] >= 16 && p[1] <= 31) return true;
|
|
if (p[0] === 192 && p[1] === 168) return true;
|
|
if (p[0] >= 224) return true;
|
|
return false;
|
|
}
|
|
if (net.isIPv6(ip)) {
|
|
var low = ip.toLowerCase();
|
|
if (low === '::1' || low === '::') return true;
|
|
if (low.startsWith('fe80:') || low.startsWith('fc') || low.startsWith('fd')) return true;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
async function assertSafeHttpsUrl(urlStr, label) {
|
|
var u;
|
|
try { u = new URL(urlStr); }
|
|
catch (e) { throw new Error((label || 'URL') + ' is invalid'); }
|
|
if (u.protocol !== 'https:') throw new Error((label || 'URL') + ' must use https://');
|
|
if (u.username || u.password) throw new Error((label || 'URL') + ' must not include credentials');
|
|
var addrs = await dns.lookup(u.hostname, { all: true });
|
|
if (!addrs.length) throw new Error((label || 'URL') + ' did not resolve');
|
|
for (var i = 0; i < addrs.length; i++) {
|
|
if (isPrivateIp(addrs[i].address)) throw new Error((label || 'URL') + ' resolves to a private IP');
|
|
}
|
|
return u;
|
|
}
|
|
|
|
module.exports = {
|
|
assertSafeHttpsUrl: assertSafeHttpsUrl,
|
|
isPrivateIp: isPrivateIp
|
|
};
|