hostile
Version:
Simple /etc/hosts manipulation
182 lines (160 loc) • 4.65 kB
JavaScript
/*! hostile. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
var fs = require('fs')
var once = require('once')
var split = require('split')
var through = require('@ljharb/through')
var net = require('net')
var WINDOWS = process.platform === 'win32'
var EOL = WINDOWS
? '\r\n'
: '\n'
exports.HOSTS = WINDOWS
? 'C:/Windows/System32/drivers/etc/hosts'
: '/etc/hosts'
/**
* Get a list of the lines that make up the filePath. If the
* `preserveFormatting` parameter is true, then include comments, blank lines
* and other non-host entries in the result.
*
* @param {boolean} preserveFormatting
* @param {function(err, lines)=} cb
*/
exports.getFile = function (filePath, preserveFormatting, cb) {
var lines = []
if (typeof cb !== 'function') {
fs.readFileSync(filePath, { encoding: 'utf8' }).split(/\r?\n/).forEach(online)
return lines
}
cb = once(cb)
fs.createReadStream(filePath, { encoding: 'utf8' })
.pipe(split())
.pipe(through(online))
.on('close', function () {
cb(null, lines)
})
.on('error', cb)
function online (line) {
// Remove all comment text from the line
var lineSansComments = line.replace(/#.*/, '')
var matches = /^\s*?(.+?)\s+(.+?)\s*$/.exec(lineSansComments)
if (matches && matches.length === 3) {
// Found a hosts entry
var ip = matches[1]
var host = matches[2]
lines.push([ip, host])
} else {
// Found a comment, blank line, or something else
if (preserveFormatting) {
lines.push(line)
}
}
}
}
/**
* Wrapper of `getFile` for getting a list of lines in the Host file
*
* @param {boolean} preserveFormatting
* @param {function(err, lines)=} cb
*/
exports.get = function (preserveFormatting, cb) {
return exports.getFile(exports.HOSTS, preserveFormatting, cb)
}
/**
* Add a rule to /etc/hosts. If the rule already exists, then this does nothing.
*
* @param {string} ip
* @param {string} host
* @param {function(Error)=} cb
*/
exports.set = function (ip, host, cb) {
var didUpdate = false
if (typeof cb !== 'function') {
return _set(exports.get(true))
}
exports.get(true, function (err, lines) {
if (err) return cb(err)
_set(lines)
})
function _set (lines) {
// Try to update entry, if host already exists in file
lines = lines.map(mapFunc)
// If entry did not exist, let's add it
if (!didUpdate) {
// If the last line is empty, or just whitespace, then insert the new entry
// right before it
var lastLine = lines[lines.length - 1]
if (typeof lastLine === 'string' && /\s*/.test(lastLine)) {
lines.splice(lines.length - 1, 0, [ip, host])
} else {
lines.push([ip, host])
}
}
exports.writeFile(lines, cb)
}
function mapFunc (line) {
// replace a line if both hostname and ip version of the address matches
if (Array.isArray(line) && line[1] === host && net.isIP(line[0]) === net.isIP(ip)) {
line[0] = ip
didUpdate = true
}
return line
}
}
/**
* Remove a rule from /etc/hosts. If the rule does not exist, then this does
* nothing.
*
* @param {string} ip
* @param {string} host
* @param {function(Error)=} cb
*/
exports.remove = function (ip, host, cb) {
if (typeof cb !== 'function') {
return _remove(exports.get(true))
}
exports.get(true, function (err, lines) {
if (err) return cb(err)
_remove(lines)
})
function _remove (lines) {
// Try to remove entry, if it exists
lines = lines.filter(filterFunc)
return exports.writeFile(lines, cb)
}
function filterFunc (line) {
return !(Array.isArray(line) && line[0] === ip && line[1] === host)
}
}
/**
* Write out an array of lines to the host file. Assumes that they're in the
* format that `get` returns.
*
* @param {Array.<string|Array.<string>>} lines
* @param {function(Error)=} cb
*/
exports.writeFile = function (lines, cb) {
lines = lines.map(function (line, lineNum) {
if (Array.isArray(line)) {
line = line[0] + ' ' + line[1]
}
return line + (lineNum === lines.length - 1 ? '' : EOL)
})
if (typeof cb !== 'function') {
var stat = fs.statSync(exports.HOSTS)
fs.writeFileSync(exports.HOSTS, lines.join(''), { mode: stat.mode })
return true
}
cb = once(cb)
fs.stat(exports.HOSTS, function (err, stat) {
if (err) {
return cb(err)
}
var s = fs.createWriteStream(exports.HOSTS, { mode: stat.mode })
s.on('close', cb)
s.on('error', cb)
lines.forEach(function (data) {
s.write(data)
})
s.end()
})
}