Files
npm/lib/utils/ini.js
isaacs 3f65d82fe6 Handle : in passwords
1. Make this work properly in the config handling logic.
2. Flat out disallow them in the adduer logic.

<https://issues.apache.org/jira/browse/COUCHDB-969>
2010-11-28 20:30:12 -08:00

293 lines
8.5 KiB
JavaScript

// Create a chain of config objects, in this priority order:
//
// CLI - the --foo things in the command line.
// ENV - all the things starting with npm_config_ in the environment
// USER - $HOME/.npmrc
// GLOBAL - $PREFIX/etc/npmrc
//
// If the CLI or ENV specify a userconfig, then that file is used
// as the USER config.
//
// If the CLI or ENV specify a globalconfig, then that file is used
// as the GLOBAL config.
//
// export npm_config_userconfig=/some/other/file
// export npm_config_globalconfig=global
//
// For implementation reasons, "_" in env vars is turned into "-". So,
// export npm_config_auto_activate
exports.resolveConfigs = resolveConfigs
exports.save = save
exports.del = del
exports.get = get
exports.set = set
exports.unParseField = unParseField
Object.defineProperty(exports, "keys",
{ get : function () { return configList.keys }})
var fs = require("./graceful-fs")
, path = require("path")
, http = require("http")
, sys = require("./sys")
, crypto = require("crypto")
, privateKey = null
, mkdir = require("./mkdir-p")
, chain = require("./chain")
, log = require("./log")
, ini = require("./ini-parser")
, base64 = require("./base64")
, ProtoList = require("./proto-list")
, defaultConfig = require("./default-config")
, configList = new ProtoList()
, TRANS =
{ "default" : 4
, "global" : 3
, "user" : 2
, "env" : 1
, "cli" : 0
}
exports.configList = configList
configList.push(defaultConfig)
function resolveConfigs (cli, cb) {
var cl = configList
, dc = cl.pop()
if (!cb) cb = cli, cli = {}
cl.list.length = 0
Object.keys(cli).forEach(function (k) {
cli[k] = parseField(cli[k], k)
})
cl.push(cli)
cl.push(parseEnv(process.env))
parseFile(cl.get("userconfig") || dc.userconfig, function (er, conf) {
if (er) return cb(er)
cl.push(conf)
parseFile(cl.get("globalconfig") || dc.globalconfig, function (er, conf) {
if (er) return cb(er)
cl.push(conf)
cl.push(dc)
// make sure that the root folder exists
// This is a bit of a kludge, but until we can abstract out
// file read/write in a clean way, this is the only single
// gateway where we can ensure that these folders exist.
var npmdir = path.join(cl.get("root"), ".npm")
chain
( cl.get("binroot") && [mkdir, cl.get("binroot")]
, [mkdir, path.join(npmdir, ".tmp")]
, [mkdir, path.join(npmdir, ".cache")]
, function (er) {
exports.resolved = true
cb(er)
}
)
})
})
}
function parseEnv (env) {
var conf = {}
Object.keys(env)
.filter(function (k) { return k.match(/^npm_config_[^_]/i) })
.forEach(function (k) {
conf[k.replace(/^npm_config_/i, "")
.toLowerCase()
.replace(/_/g, "-")] = parseField(env[k], k)
})
return conf
}
function unParseField (f, k) {
var isPath = k.match(/root$/i)
if (isPath) {
if (process.env.HOME.substr(-1) === "/") {
process.env.HOME = process.env.HOME(0, process.env.HOME.length-1)
}
if (f.indexOf(process.env.HOME) === 0) {
f = "~"+f.substr(process.env.HOME.length)
}
}
return f
}
function parseField (f, k) {
var isPath = k.match(/root$/i)
f = (""+f).trim()
if (f === "") f = true
if (isPath) {
if (f.substr(0, 2) === "~/" && process.env.HOME) {
f = path.join(process.env.HOME, f.substr(2))
}
if (f.charAt(0) !== "/") {
f = path.join(process.cwd(), f.substr(2))
}
}
switch (f) {
case "true": f = true; break
case "false": f = false; break
case "null": f = null; break
case "undefined": f = undefined; break
}
return f
}
function parseFile (file, cb) {
if (!file) return cb(null, {})
log.verbose(file, "config file")
fs.readFile(file, function (er, data) {
if (er) return cb(null, {})
var d = ini.parse(""+data)["-"]
, f = {}
Object.keys(d).forEach(function (k) {
f[k] = parseField(d[k], k)
})
decryptAuth(f, cb)
})
}
function decryptAuth (config, cb) {
// todo: remove the _ kludgery in 1.0.0
var proto = config.__proto__
config.__proto__ = {}
if (config.password) config._password = config.password
if (config.authCrypt) config._authCrypt = config.authCrypt
delete config.password
delete config.authCrypt
if (!config._authCrypt || !crypto.Decipher) {
config.__proto__ = proto
return cb(null, config)
}
getKey(function (er, key) {
if (er) return log(er, "error getting key to decrypt auth", cb)
if (!key) return log("Could not get key", "error decrypting auth", cb)
var c = (new crypto.Decipher).init("aes192", key)
config._auth = c.update(config._authCrypt, "hex", "utf8")
config._auth += c.final("utf8")
delete config._authCrypt
var unpw = base64.decode(config._auth).split(":")
, un = unpw.shift()
, pw = unpw.join(":")
config.username = un = (config.username || un)
config._password = pw = (config._password || pw)
config._auth = base64.encode(un + ":" + pw)
config.__proto__ = proto
cb(null, config)
})
}
function encryptAuth (config, cb) {
var proto = config.__proto__
config.__proto__ = {}
// todo: remove the _ kludgery in 1.0.0
if (config.password) config._password = config.password
if (config.auth) config._auth = config.authCrypt
delete config.password
delete config.auth
var auth = config._auth ? base64.decode(config._auth) : null
if (config.username && config._password && !auth) {
auth = config.username+":"+config._password
}
if (!auth || !crypto.Cipher) {
config.__proto__ = proto
return cb(null, config)
}
var unpw = auth.split(":")
, un = unpw.shift()
, pw = unpw.join(":")
config.username = un = config.username || un
config._password = pw = config._password || pw
auth = un + ":" + pw
config._auth = base64.encode(auth)
getKey(function (er, key) {
if (er) return log(er, "error getting key to encrypt auth", cb)
if (!key) return log("Could not get key", "error encrypting auth", cb)
var c = (new crypto.Cipher).init("aes192", key)
config._authCrypt = c.update(config._auth, "utf8", "hex")
config._authCrypt += c.final("hex")
delete config._auth
delete config.username
delete config._password
config.__proto__ = proto
cb(null, config)
})
}
function getKey (cb) {
if (privateKey) return cb(null, privateKey)
var ssh = path.join(process.env.HOME, ".ssh")
, keys = [ path.join(ssh, "id_dsa")
, path.join(ssh, "id_rsa")
, path.join(ssh, "identity")
]
;(function K (k) {
if (!k) return cb(null, false)
fs.readFile(k, function (er, data) {
if (er) return K(keys.shift())
return cb(null, privateKey = data+"")
})
})(keys.shift())
}
function save (cb) {
return saveConfig("global", function (er) { saveConfig("user", cb) })
}
function saveConfig (which, cb) {
if (which !== "global") which = "user"
saveConfigfile
( configList.get(which + "config")
, configList.list[TRANS[which]]
, cb
)
}
function saveConfigfile (file, config, cb) {
encryptAuth(config, function () { // ignore errors
var data = {}
Object.keys(config).forEach(function (k) {
data[k] = unParseField(config[k], k)
})
data = ini.stringify({"-":data}).trim()
return (data) ? writeConfigfile(file, data+"\n", cb)
: rmConfigfile(file, cb)
})
}
function writeConfigfile (configfile, data, cb) {
fs.writeFile
( configfile, data, "utf8"
, function (er) {
if (er) log(er, "Failed saving "+configfile)
cb()
}
)
}
function rmConfigfile (configfile, cb) {
fs.stat(configfile, function (e) {
if (e) return cb()
fs.unlink(configfile, function (er) {
if (er) log(er, "Couldn't remove "+configfile)
cb()
})
})
}
function snapshot (which) {
return (!which) ? configList.snapshot
: configList.list[TRANS[which]] ? configList.list[TRANS[which]]
: undefined
}
function get (key, which) {
return (!key) ? snapshot(which)
: (!which) ? configList.get(key) // resolved
: configList.list[TRANS[which]] ? configList.list[TRANS[which]][key]
: undefined
}
function del (key, which) {
if (!which) configList.list.forEach(function (l) {
delete l[key]
})
else if (configList.list[TRANS[which]]) {
delete configList.list[TRANS[which]]
}
}
function set (key, value, which) {
which = which || "cli"
if (configList.length === 1) {
return new Error("trying to set before loading")
}
return configList.list[TRANS[which]][key] = value
}