UNPKG

hosts-etc

Version:

Manages your system host file in a more productive way!

273 lines (236 loc) 8.66 kB
const fs = require('fs'); const HOSTS_FILE = process.platform === "win32" ? "C:/Windows/System32/drivers/etc/hosts" : "/etc/hosts"; let hostsCache = null; let cache = true; class Host { constructor(host, address, opts) { opts = opts || {}; this.host = host; this.address = address; this.region = opts.region || ""; this.comment = opts.comment || ""; } makeHostLine() { return this.address + "\t" + this.host + (this.comment !== "" ? "\t# " + this.comment : ""); } makeRegionLine() { if(this.region == "") return ""; return "# region " + this.region; } } module.exports.useCache = function (c) { if(typeof c === "undefined") c = true; cache = c; return module.exports; }; module.exports.HOSTS = HOSTS_FILE; module.exports.Host = Host; module.exports.hostifyData = function (hosts) { let out = "# Host file generated by TheBrenny/hosts-etc\n\n"; for(let r of Object.keys(hosts).sort()) { if(hosts[r].length === 0) continue; out += hosts[r][0].makeRegionLine() + "\n"; for(let h of hosts[r]) out += h.makeHostLine() + "\n"; out += (hosts[r][0].region != "" ? "# end region" : "") + "\n"; } return out; }; module.exports.get = function (query) { if(typeof query === 'undefined') query = ""; query = (query || "").toString(); let lines = getFileContents().replace(/\r\n/g, "\n").split("\n"); let regions = getAllHosts(lines); let out = {}; let rx = /((\d{1,3}|x)\.(\d{1,3}|x)\.(\d{1,3}|x)\.(\d{1,3}|x)|::1)/; if(query == "") { // get all out = regions; } else if(query.startsWith("#")) { // get region only let region = query.slice(1).trim(); out[region] = regions[region] || []; } else if(query.startsWith("c#")) { // get comment equals let comment = query.slice(2).trim(); for(let r in regions) { // regions[r] = region object == array of hosts for(let h of regions[r]) { // h = host object if(h.comment === comment) { out[r] = out[r] || []; out[r].push(h); } } } } else if(rx.test(query)) { // get IPs let ips = query.match(rx)[0]; rx = new RegExp(ips.replace(/x/g, "\\d{1,3}")); for(let r in regions) { // r = region object == array of hosts for(let h of regions[r]) { // h = host object if(rx.test(h.address)) { out[r] = out[r] || []; out[r].push(h); } } } } else { // Matching hostname for(let r in regions) { // regions[r] = region object == array of hosts for(let h of regions[r]) { // h = host object if(h.host.includes(query)) { out[r] = out[r] || []; out[r].push(h); } } } } // {"region": [Host, ...]} return out; // Gets all hosts into a JSON *object*! function getAllHosts(lines) { if(hostsCache && cache) return hostsCache; let regionStart = /^[ \t]*#[ \t]*region +(.+?)[ \t]*$/gm; let regionEnd = /^[ \t]*#[ \t]*end region[ \t]*$/gm; let hostsRx = /^[ \t]*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|::1)[ \t]+?(\S+?)[ \t]*(#.*)?$/gm; let region = []; let out = {}; for(let line of lines) { if(line === "") continue; // Region End if(regionEnd.test(line)) { region.pop(); continue; } // Region Start let r = [...line.matchAll(regionStart)]; if(r.length > 0) { region.push(r[0][1]); continue; } // Host r = [...line.matchAll(hostsRx)]; if(r.length > 0) { r = r[0]; let h = new Host(r[2], r[1], { region: region.join("."), comment: (r[3] && r[3].substr(1).trim()) || "" }); out[region] = out[region] || []; out[region].push(h); continue; } } hostsCache = out; return out; } }; module.exports.set = function (host) { if(host.constructor !== "Host") host = new Host(host.host, host.address, host); let updated = 0; let hosts = this.get(); // find if host exists, if yes delete it let hostsAsArray = []; for(let r in hosts) hostsAsArray = hostsAsArray.concat(hosts[r]); let hostIndex = hostsAsArray.findIndex((el) => hostsMatch(el, host)); if(hostIndex !== -1) { let r = hostsAsArray[hostIndex].region; hostIndex = hosts[r].findIndex((el) => hostsMatch(el, host)); // reuse to save space hosts[r].splice(hostIndex, 1); } // push new host hosts[host.region] = hosts[host.region] || []; hosts[host.region].push(host); updated++; // overwrite system hosts file setFileContents(null, this.hostifyData(hosts)); // return updated return updated; function hostsMatch(a, b) { return a.host === b.host && a.address === b.address; } }; const filters = { comment: (comment, host) => host.comment !== comment, ip: (rx, host) => !rx.test(host.address), host: (query, host) => host.host !== query }; module.exports.remove = function (query) { let updated = 0; let hosts = this.get(); let out = Object.assign({}, hosts); let rx = /((\d{1,3}|x)\.(\d{1,3}|x)\.(\d{1,3}|x)\.(\d{1,3}|x)|::1)/; if(query.startsWith("#")) { // remove region let region = query.slice(1).trim(); updated += out[region].length; delete out[region]; } else if(query.startsWith("c#")) { // remove comment equals const comment = query.slice(2).trim(); const filter = filters.comment.bind(null, comment); for(let r in hosts) { out[r] = out[r].filter(filter); updated += (hosts[r].length - out[r].length); if(out[r].length === 0) delete out[r]; } } else if(rx.test(query)) { // remove IPs let ips = query.match(rx)[0]; rx = new RegExp(ips.replace(/x/g, "\\d{1,3}")); const filter = filters.ip.bind(null, rx); for(let r in hosts) { out[r] = out[r].filter(filter); updated += (hosts[r].length - out[r].length); if(out[r].length === 0) delete out[r]; } } else { // Matching hostname const filter = filters.host.bind(null, query); for(let r in hosts) { out[r] = out[r].filter(filter); updated += (hosts[r].length - out[r].length); if(out[r].length === 0) delete out[r]; } } setFileContents(null, this.hostifyData(out)); return updated; }; function getFileContents(file) { return fs.readFileSync(file || HOSTS_FILE).toString(); } function setFileContents(file, data) { file = file || HOSTS_FILE; if(!fs.existsSync(file + ".hosts-etc.bkp")) fs.copyFileSync(file, file + ".hosts-etc.bkp"); return fs.writeFileSync(file, data); } module.exports.promise = {}; module.exports.promise.HOSTS = HOSTS_FILE; // Deprecated! Use HOSTS_FILE module.exports.promise.HOSTS_FILE = HOSTS_FILE; module.exports.promise.Host = Host; module.exports.promise.hostifyData = async function (hosts) { return Promise.resolve(module.exports.hostifyData(hosts)); }; // TODO: Actually convert these to use promises, not just a promise wrapper around sync methods! module.exports.promise.get = async function (query) { return new Promise((res, rej) => { try { res(module.exports.get(query)); } catch(e) { rej(e); } }); }; module.exports.promise.set = async function (hostData) { return new Promise((res, rej) => { try { res(module.exports.set(hostData)); } catch(e) { rej(e); } }); }; module.exports.promise.remove = async function (query) { return new Promise((res, rej) => { try { res(module.exports.remove(query)); } catch(e) { rej(e); } }); };