diff options
Diffstat (limited to 'deps/npm/lib/utils/npm-registry-client/request.js')
-rw-r--r-- | deps/npm/lib/utils/npm-registry-client/request.js | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/deps/npm/lib/utils/npm-registry-client/request.js b/deps/npm/lib/utils/npm-registry-client/request.js new file mode 100644 index 00000000000..d19e3ac31fb --- /dev/null +++ b/deps/npm/lib/utils/npm-registry-client/request.js @@ -0,0 +1,242 @@ +module.exports = regRequest + +regRequest.GET = GET +regRequest.PUT = PUT +regRequest.reg = reg +regRequest.upload = upload + +var npm = require("../../npm.js") + , url = require("url") + , log = require("../log.js") + , fs = require("graceful-fs") + , rm = require("rimraf") + , asyncMap = require("slide").asyncMap + , warnedAuth = false + , newloctimeout = 0 + , stream = require("stream") + , Stream = stream.Stream + , request = require("request") + , getAgent = require("../get-agent.js") + +function regRequest (method, where, what, etag, nofollow, cb_) { + if (typeof cb_ !== "function") cb_ = nofollow, nofollow = false + if (typeof cb_ !== "function") cb_ = etag, etag = null + if (typeof cb_ !== "function") cb_ = what, what = null + + log.verbose(where||"/", method) + + // Since there are multiple places where an error could occur, + // don't let the cb be called more than once. + var errState = null + function cb (er) { + if (errState) return + if (er) errState = er + cb_.apply(null, arguments) + } + + if (where.match(/^\/?favicon.ico/)) { + return cb(new Error("favicon.ico isn't a package, it's a picture.")) + } + + var registry = reg() + if (registry instanceof Error) return cb(registry) + + var adduserChange = /^\/?-\/user\/org\.couchdb\.user:([^\/]+)\/-rev/ + , adduserNew = /^\/?-\/user\/org\.couchdb\.user:([^\/]+)/ + , authRequired = (what || npm.config.get("always-auth")) + && !where.match(adduserNew) + || where.match(adduserChange) + || method === "DELETE" + + // resolve to a full url on the registry + if (!where.match(/^https?:\/\//)) { + log.verbose(where, "raw, before any munging") + + var q = where.split("?") + where = q.shift() + q = q.join("?") + + if (where.charAt(0) !== "/") where = "/" + where + where = "." + where.split("/").map(function (p) { + p = p.trim() + if (p.match(/^org.couchdb.user/)) { + return p.replace(/\//g, encodeURIComponent("/")) + } + return encodeURIComponent(p) + }).join("/") + if (q) where += "?" + q + log.verbose([registry, where], "url resolving") + where = url.resolve(registry, where) + log.verbose(where, "url resolved") + } + + var remote = url.parse(where) + , auth = authRequired && npm.config.get("_auth") + + if (authRequired && !auth) { + return cb(new Error( + "Cannot insert data into the registry without authorization\n" + + "See: npm-adduser(1)")) + } + + if (auth) remote.auth = new Buffer(auth, "base64").toString("utf8") + + makeRequest(method, remote, where, what, etag, nofollow, cb) +} + +function makeRequest (method, remote, where, what, etag, nofollow, cb) { + var opts = { url: remote + , method: method + , agent: getAgent(remote) + , strictSSL: npm.config.get("strict-ssl") } + , headers = opts.headers = {} + if (etag) { + log.verbose(etag, "etag") + headers[method === "GET" ? "if-none-match" : "if-match"] = etag + } + + headers.accept = "application/json" + + opts.proxy = npm.config.get( remote.protocol === "https:" + ? "https-proxy" : "proxy" ) + + // figure out wth 'what' is + if (what) { + if (Buffer.isBuffer(what) || typeof what === "string") { + opts.body = what + headers["content-type"] = "application/json" + headers["content-length"] = Buffer.byteLength(what) + } else if (what instanceof Stream) { + headers["content-type"] = "application/octet-stream" + if (what.size) headers["content-length"] = what.size + } else { + delete what._etag + opts.json = what + } + } + + if (nofollow) { + opts.followRedirect = false + } + + var req = request(opts, requestDone(method, where, cb)) + var r = npm.config.get("registry") + if (!r) { + return new Error("Must define registry URL before accessing registry.") + } + + req.on("error", cb) + + if (what && (what instanceof Stream)) { + what.pipe(req) + } +} + +// cb(er, parsed, raw, response) +function requestDone (method, where, cb) { return function (er, response, data) { + if (er) return cb(er) + + var parsed + + if (Buffer.isBuffer(data)) { + data = data.toString() + } + + if (data && typeof data === "string" && response.statusCode !== 304) { + try { + parsed = JSON.parse(data) + } catch (ex) { + ex.message += "\n" + data + log.verbose(data, "bad json") + log.error("error parsing json", "registry") + return cb(ex, null, data, response) + } + } else if (data) { + parsed = data + data = JSON.stringify(parsed) + } + + // expect data with any error codes + if (!data && response.statusCode >= 400) { + return cb( response.statusCode + " " + + require("http").STATUS_CODES[response.statusCode] + , null, data, response ) + } + + var er = null + if (parsed && response.headers.etag) { + parsed._etag = response.headers.etag + } + + if (parsed && parsed.error && response.statusCode >= 400) { + var w = url.parse(where).pathname.substr(1) + if (!w.match(/^-/) && parsed.error === "not_found") { + w = w.split("/") + name = w[w.indexOf("_rewrite") + 1] + er = new Error("404 Not Found: "+name) + er.errno = npm.E404 + er.pkgid = name + } else { + er = new Error( + parsed.error + " " + (parsed.reason || "") + ": " + w) + } + } else if (method !== "HEAD" && method !== "GET") { + // invalidate cache + // This is irrelevant for commands that do etag caching, but + // ls and view also have a timed cache, so this keeps the user + // from thinking that it didn't work when it did. + // Note that failure is an acceptable option here, since the + // only result will be a stale cache for some helper commands. + var path = require("path") + , p = url.parse(where).pathname.split("/") + , _ = "/" + , caches = p.map(function (part) { + return _ = path.join(_, part) + }).map(function (cache) { + return path.join(npm.cache, cache, ".cache.json") + }) + + // if the method is DELETE, then also remove the thing itself. + // Note that the search index is probably invalid. Whatever. + // That's what you get for deleting stuff. Don't do that. + if (method === "DELETE") { + p = p.slice(0, p.indexOf("-rev")) + caches.push(path.join(npm.cache, p.join("/"))) + } + + asyncMap(caches, rm, function () {}) + } + return cb(er, parsed, data, response) +}} + +function GET (where, etag, nofollow, cb) { + regRequest("GET", where, null, etag, nofollow, cb) +} + +function PUT (where, what, etag, nofollow, cb) { + regRequest("PUT", where, what, etag, nofollow, cb) +} + +function upload (where, filename, etag, nofollow, cb) { + if (typeof nofollow === "function") cb = nofollow, nofollow = false + if (typeof etag === "function") cb = etag, etag = null + + fs.stat(filename, function (er, stat) { + if (er) return cb(er) + var s = fs.createReadStream(filename) + s.size = stat.size + s.on("error", cb) + + PUT(where, s, etag, nofollow, cb) + }) +} + +function reg () { + var r = npm.config.get("registry") + if (!r) { + return new Error("Must define registry URL before accessing registry.") + } + if (r.substr(-1) !== "/") r += "/" + npm.config.set("registry", r) + return r +} |