diff options
author | Forrest L Norvell <forrest@npmjs.com> | 2014-08-08 04:56:53 +0400 |
---|---|---|
committer | Forrest L Norvell <forrest@npmjs.com> | 2014-08-08 04:56:53 +0400 |
commit | d987707442409f75ab0fa7f346a036ea5787754a (patch) | |
tree | a3046d074805f8df0cc70939c84a68d100db75c6 | |
parent | 9b318e21559546cebd6715f9366a6b058ab2d5b3 (diff) |
move fetch into npm-registry-client
The primary purpose is so that everything doing network requests
includes auth, if so required. Sends bearer token by default, will send
HTTP Basic auth with `always-auth` set in the config.
-rw-r--r-- | lib/cache/add-remote-tarball.js | 51 | ||||
-rw-r--r-- | lib/utils/fetch.js | 106 | ||||
-rw-r--r-- | node_modules/npm-registry-client/LICENSE | 36 | ||||
-rw-r--r-- | node_modules/npm-registry-client/lib/attempt.js | 22 | ||||
-rw-r--r-- | node_modules/npm-registry-client/lib/authify.js | 25 | ||||
-rw-r--r-- | node_modules/npm-registry-client/lib/fetch.js | 81 | ||||
-rw-r--r-- | node_modules/npm-registry-client/lib/initialize.js | 41 | ||||
-rw-r--r-- | node_modules/npm-registry-client/lib/request.js | 174 | ||||
-rw-r--r-- | node_modules/npm-registry-client/lib/unpublish.js | 11 | ||||
-rw-r--r-- | node_modules/npm-registry-client/package.json | 27 | ||||
-rw-r--r-- | node_modules/npm-registry-client/test/fetch-basic.js | 44 | ||||
-rw-r--r-- | node_modules/npm-registry-client/test/fetch-retries.js | 52 | ||||
-rw-r--r-- | node_modules/npm-registry-client/test/request-gzip-content.js | 8 | ||||
-rw-r--r-- | node_modules/npm-registry-client/test/unpublish-scoped.js | 59 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | test/tap/cache-shasum.js | 3 | ||||
-rw-r--r-- | test/tap/url-dependencies.js | 20 |
17 files changed, 470 insertions, 292 deletions
diff --git a/lib/cache/add-remote-tarball.js b/lib/cache/add-remote-tarball.js index db9a05d82..2dbc366e4 100644 --- a/lib/cache/add-remote-tarball.js +++ b/lib/cache/add-remote-tarball.js @@ -4,8 +4,9 @@ var mkdir = require("mkdirp") , path = require("path") , sha = require("sha") , retry = require("retry") + , createWriteStream = require("graceful-fs").createWriteStream , npm = require("../npm.js") - , fetch = require("../utils/fetch.js") + , registry = npm.registry , inflight = require("inflight") , locker = require("../utils/locker.js") , lock = locker.lock @@ -80,27 +81,47 @@ function addRemoteTarball_(u, tmp, shasum, cb) { } function fetchAndShaCheck (u, tmp, shasum, cb) { - fetch(u, tmp, function (er, response) { + registry.fetch(u, null, function (er, response) { if (er) { log.error("fetch failed", u) return cb(er, response) } - if (!shasum) { - // Well, we weren't given a shasum, so at least sha what we have - // in case we want to compare it to something else later - return sha.get(tmp, function (er, shasum) { - cb(er, response, shasum) + // mkdir takes a little while, so don't drop chunks! + response.pause() + + mkdir(path.dirname(tmp), function (er) { + if (er) return cb(er) + + var tarball = createWriteStream(tmp, { mode : npm.modes.file }) + tarball.on("error", function (er) { + cb(er) + tarball.destroy() }) - } - // validate that the url we just downloaded matches the expected shasum. - sha.check(tmp, shasum, function (er) { - if (er && er.message) { - // add original filename for better debuggability - er.message = er.message + '\n' + 'From: ' + u - } - return cb(er, response, shasum) + tarball.on("finish", function () { + if (!shasum) { + // Well, we weren't given a shasum, so at least sha what we have + // in case we want to compare it to something else later + return sha.get(tmp, function (er, shasum) { + log.silly("fetchAndShaCheck", "shasum", shasum) + cb(er, response, shasum) + }) + } + + // validate that the url we just downloaded matches the expected shasum. + log.silly("fetchAndShaCheck", "shasum", shasum) + sha.check(tmp, shasum, function (er) { + if (er && er.message) { + // add original filename for better debuggability + er.message = er.message + "\n" + "From: " + u + } + return cb(er, response, shasum) + }) + }) + + response.pipe(tarball) + response.resume() }) }) } diff --git a/lib/utils/fetch.js b/lib/utils/fetch.js deleted file mode 100644 index f6e5166ff..000000000 --- a/lib/utils/fetch.js +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Fetch an HTTP url to a local file. - **/ - -var request = require("request") - , fs = require("graceful-fs") - , npm = require("../npm.js") - , url = require("url") - , log = require("npmlog") - , path = require("path") - , mkdir = require("mkdirp") - , chownr = require("chownr") - , regHost - , once = require("once") - , crypto = require("crypto") - -module.exports = fetch - -function fetch (remote, local, headers, cb) { - if (typeof cb !== "function") cb = headers, headers = {} - cb = once(cb) - log.verbose("fetch", "to=", local) - mkdir(path.dirname(local), function (er, made) { - if (er) return cb(er) - fetch_(remote, local, headers, cb) - }) -} - -function fetch_ (remote, local, headers, cb) { - var fstr = fs.createWriteStream(local, { mode : npm.modes.file }) - var response = null - - fstr.on("error", function (er) { - cb(er) - fstr.destroy() - }) - - var req = makeRequest(remote, fstr, headers) - req.on("response", function (res) { - log.http(res.statusCode, remote) - response = res - response.resume() - // Work around bug in node v0.10.0 where the CryptoStream - // gets stuck and never starts reading again. - if (process.version === "v0.10.0") { - response.resume = function (orig) { return function() { - var ret = orig.apply(response, arguments) - if (response.socket.encrypted) - response.socket.encrypted.read(0) - return ret - }}(response.resume) - } - }) - - fstr.on("close", function () { - var er - if (response && response.statusCode && response.statusCode >= 400) { - er = new Error(response.statusCode + " " - + require("http").STATUS_CODES[response.statusCode]) - } - cb(er, response) - }) -} - -function makeRequest (remote, fstr, headers) { - remote = url.parse(remote) - log.http("GET", remote.href) - regHost = regHost || url.parse(npm.config.get("registry")).host - - if (remote.host === regHost && npm.config.get("always-auth")) { - remote.auth = new Buffer( npm.config.get("_auth") - , "base64" ).toString("utf8") - if (!remote.auth) return fstr.emit("error", new Error( - "Auth required and none provided. Please run 'npm adduser'")) - } - - var proxy - if (remote.protocol !== "https:" || !(proxy = npm.config.get("https-proxy"))) { - proxy = npm.config.get("proxy") - } - - var sessionToken = npm.registry.sessionToken - if (!sessionToken) { - sessionToken = crypto.randomBytes(8).toString("hex") - npm.registry.sessionToken = sessionToken - } - - var ca = remote.host === regHost ? npm.config.get("ca") : undefined - var opts = { url: remote - , proxy: proxy - , strictSSL: npm.config.get("strict-ssl") - , rejectUnauthorized: npm.config.get("strict-ssl") - , ca: ca - , headers: - { "user-agent": npm.config.get("user-agent") - , "npm-session": sessionToken - , referer: npm.registry.refer - } - } - var req = request(opts) - req.on("error", function (er) { - fstr.emit("error", er) - }) - req.pipe(fstr) - return req -} diff --git a/node_modules/npm-registry-client/LICENSE b/node_modules/npm-registry-client/LICENSE index 0c44ae716..19129e315 100644 --- a/node_modules/npm-registry-client/LICENSE +++ b/node_modules/npm-registry-client/LICENSE @@ -1,27 +1,15 @@ -Copyright (c) Isaac Z. Schlueter ("Author") -All rights reserved. +The ISC License -The BSD License +Copyright (c) Isaac Z. Schlueter and Contributors -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR -BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN -IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/node_modules/npm-registry-client/lib/attempt.js b/node_modules/npm-registry-client/lib/attempt.js new file mode 100644 index 000000000..9a0c81876 --- /dev/null +++ b/node_modules/npm-registry-client/lib/attempt.js @@ -0,0 +1,22 @@ +var retry = require("retry") + +module.exports = attempt + +function attempt(cb) { + // Tuned to spread 3 attempts over about a minute. + // See formula at <https://github.com/tim-kos/node-retry>. + var operation = retry.operation({ + retries : this.conf.get("fetch-retries") || 2, + factor : this.conf.get("fetch-retry-factor"), + minTimeout : this.conf.get("fetch-retry-mintimeout") || 10000, + maxTimeout : this.conf.get("fetch-retry-maxtimeout") || 60000 + }) + + var client = this + operation.attempt(function (currentAttempt) { + client.log.info("retrier", "registry request attempt "+currentAttempt+ + " at "+(new Date()).toLocaleTimeString()) + + cb(operation) + }) +} diff --git a/node_modules/npm-registry-client/lib/authify.js b/node_modules/npm-registry-client/lib/authify.js new file mode 100644 index 000000000..a4290fd5e --- /dev/null +++ b/node_modules/npm-registry-client/lib/authify.js @@ -0,0 +1,25 @@ +var url = require("url") + +module.exports = authify + +function authify (authed, parsed, headers) { + var c = this.conf.getCredentialsByURI(url.format(parsed)) + + if (c && c.token) { + this.log.verbose("request", "using bearer token for auth") + headers.authorization = "Bearer " + c.token + + return null + } + + if (authed) { + if (c && c.username && c.password) { + parsed.auth = c.username + ":" + c.password + } + else { + return new Error( + "This request requires auth credentials. Run `npm login` and repeat the request." + ) + } + } +} diff --git a/node_modules/npm-registry-client/lib/fetch.js b/node_modules/npm-registry-client/lib/fetch.js new file mode 100644 index 000000000..826155863 --- /dev/null +++ b/node_modules/npm-registry-client/lib/fetch.js @@ -0,0 +1,81 @@ +var assert = require("assert") + , url = require("url") + +var request = require("request") + , once = require("once") + +module.exports = fetch + +function fetch (uri, headers, cb) { + assert(uri, "must pass resource to fetch") + assert(cb, "must pass callback") + + if (!headers) headers = {} + + cb = once(cb) + + var client = this + this.attempt(function (operation) { + makeRequest.call(client, uri, headers, function (er, req) { + if (er) return cb(er) + + req.on("error", function (err) { + if (operation.retry(err)) { + client.log.info("retry", "will retry, error on last attempt: " + err) + } + }) + + req.on("response", function (res) { + client.log.http("fetch", res.statusCode, uri) + + // Only retry on 408, 5xx or no `response`. + var statusCode = res && res.statusCode + + var timeout = statusCode === 408 + if (timeout) er = new Error("request timed out") + + var serverError = statusCode >= 500 + if (serverError) er = new Error("server error " + statusCode) + + if (er && operation.retry(er)) { + client.log.info("retry", "will retry, error on last attempt: " + er) + return + } + + // Work around bug in node v0.10.0 where the CryptoStream + // gets stuck and never starts reading again. + res.resume() + if (process.version === "v0.10.0") unstick(res) + + cb(null, res) + }) + }) + }) +} + +function unstick(response) { + response.resume = function (orig) { return function() { + var ret = orig.apply(response, arguments) + if (response.socket.encrypted) response.socket.encrypted.read(0) + return ret + }}(response.resume) +} + +function makeRequest (remote, headers, cb) { + var parsed = url.parse(remote) + this.log.http("fetch", "GET", parsed.href) + + var er = this.authify(this.conf.get("always-auth"), parsed, headers) + if (er) return cb(er) + + var opts = this.initialize( + parsed, + "GET", + "application/x-tar", + headers + ) + // always want to follow redirects for fetch + opts.followRedirect = true + + cb(null, request(opts)) +} diff --git a/node_modules/npm-registry-client/lib/initialize.js b/node_modules/npm-registry-client/lib/initialize.js new file mode 100644 index 000000000..b6e89ffe9 --- /dev/null +++ b/node_modules/npm-registry-client/lib/initialize.js @@ -0,0 +1,41 @@ +var crypto = require("crypto") + +var pkg = require("../package.json") + +module.exports = initialize + +function initialize (uri, method, accept, headers) { + if (!this.sessionToken) { + this.sessionToken = crypto.randomBytes(8).toString("hex") + this.log.verbose("request id", this.sessionToken) + } + + var strict = this.conf.get("strict-ssl") + if (strict === undefined) strict = true + + var p = this.conf.get("proxy") + var sp = this.conf.get("https-proxy") || p + + var opts = { + url : uri, + method : method, + headers : headers, + proxy : uri.protocol === "https:" ? sp : p, + localAddress : this.conf.get("local-address"), + strictSSL : strict, + cert : this.conf.get("cert"), + key : this.conf.get("key"), + ca : this.conf.get("ca") + } + + headers.version = this.version || pkg.version + headers.accept = accept + + if (this.refer) headers.referer = this.refer + + headers["npm-session"] = this.sessionToken + headers["user-agent"] = this.conf.get("user-agent") || + "node/" + process.version + + return opts +} diff --git a/node_modules/npm-registry-client/lib/request.js b/node_modules/npm-registry-client/lib/request.js index a67a83390..b59a60f00 100644 --- a/node_modules/npm-registry-client/lib/request.js +++ b/node_modules/npm-registry-client/lib/request.js @@ -1,15 +1,13 @@ -module.exports = regRequest - -var url = require("url") +var assert = require("assert") + , url = require("url") , zlib = require("zlib") - , assert = require("assert") - , rm = require("rimraf") , Stream = require("stream").Stream + +var rm = require("rimraf") , request = require("request") - , retry = require("retry") - , crypto = require("crypto") + , once = require("once") -var pkg = require("../package.json") +module.exports = regRequest // npm: means // 1. https @@ -20,11 +18,12 @@ function regRequest (method, uri, options, cb_) { assert(cb_, "must pass callback") options = options || {} - var nofollow = (typeof options.follow === 'boolean' ? !options.follow : false) - var etag = options.etag - var what = options.body var parsed = url.parse(uri) + var where = parsed.pathname + var what = options.body + var follow = (typeof options.follow === "boolean" ? options.follow : true) + this.log.verbose("request", "on initialization, where is", where) var authThis = false if (parsed.protocol === "npm") { @@ -32,42 +31,28 @@ function regRequest (method, uri, options, cb_) { authThis = true } - var where = parsed.pathname if (parsed.search) { where = where + parsed.search parsed.search = "" } parsed.pathname = "/" - this.log.verbose("request", "where is", where) - - var registry = url.format(parsed) - this.log.verbose("request", "registry", registry) - - if (!this.sessionToken) { - this.sessionToken = crypto.randomBytes(8).toString("hex") - this.log.verbose("request id", this.sessionToken) - } + this.log.verbose("request", "after pass 1, where is", where) // 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) - } + var cb = once(cb_) if (where.match(/^\/?favicon.ico/)) { return cb(new Error("favicon.ico isn't a package, it's a picture.")) } var adduserChange = /^\/?-\/user\/org\.couchdb\.user:([^\/]+)\/-rev/ - , adduserNew = /^\/?-\/user\/org\.couchdb\.user:([^\/]+)/ - , nu = where.match(adduserNew) - , uc = where.match(adduserChange) - , alwaysAuth = this.conf.get("always-auth") - , isDel = method === "DELETE" - , isWrite = what || isDel + , uc = where.match(adduserChange) + , adduserNew = /^\/?-\/user\/org\.couchdb\.user:([^\/]+)/ + , nu = where.match(adduserNew) + , alwaysAuth = this.conf.get("always-auth") + , isDel = method === "DELETE" + , isWrite = what || isDel // resolve to a full url on the registry if (!where.match(/^https?:\/\//)) { @@ -86,56 +71,20 @@ function regRequest (method, uri, options, cb_) { return p }).join("/") if (q) where += "?" + q - this.log.verbose("request", "url resolving", [registry, where]) + + var registry = url.format(parsed) + this.log.verbose("request", "resolving registry", [registry, where]) + where = url.resolve(registry, where) - this.log.verbose("request", "url resolved", where) - } - this.log.verbose("request", "where is", where) - - var remote = url.parse(where) - if ((authThis || alwaysAuth || isWrite) && !nu || uc || isDel) { - this.log.verbose("request", "setting basic auth") - - var c = this.conf.getCredentialsByURI(where) - if (c) { - if (!c.token) { - if (c.username && c.password) { - remote.auth = c.username + ":" + c.password - } - else { - return cb(new Error( - "This request requires auth credentials. Run `npm login` and repeat the request." - )) - } - } - else { - this.log.verbose("request", "using bearer token for auth") - } - } - else { - return cb(new Error( - "This request requires auth credentials. Run `npm login` and repeat the request." - )) - } - } - else { - this.log.verbose("request", "no basic auth needed") + this.log.verbose("request", "after pass 2, where is", where) } - // Tuned to spread 3 attempts over about a minute. - // See formula at <https://github.com/tim-kos/node-retry>. - var operation = retry.operation({ - retries: this.conf.get("fetch-retries") || 2, - factor: this.conf.get("fetch-retry-factor"), - minTimeout: this.conf.get("fetch-retry-mintimeout") || 10000, - maxTimeout: this.conf.get("fetch-retry-maxtimeout") || 60000 - }) + var authed = (authThis || alwaysAuth || isWrite) && !nu || uc || isDel + if (!authed) this.log.verbose("request", "no auth needed") var self = this - operation.attempt(function (currentAttempt) { - self.log.info("trying", "registry request attempt " + currentAttempt - + " at " + (new Date()).toLocaleTimeString()) - makeRequest.call(self, method, remote, where, what, etag, nofollow + this.attempt(function (operation) { + makeRequest.call(self, method, where, what, options.etag, follow, authed , function (er, parsed, raw, response) { if (!er || (er.message && er.message.match(/^SSL Error/))) { if (er) @@ -154,55 +103,38 @@ function regRequest (method, uri, options, cb_) { return undefined } if (response) - this.log.verbose("headers", response.headers) + self.log.verbose("headers", response.headers) cb.apply(null, arguments) - }.bind(this)) - }.bind(this)) + }) + }) } -function makeRequest (method, remote, where, what, etag, nofollow, cb_) { - var cbCalled = false - function cb () { - if (cbCalled) return - cbCalled = true - cb_.apply(null, arguments) - } - - var strict = this.conf.get("strict-ssl") - if (strict === undefined) strict = true - var opts = { url: remote - , method: method - , encoding: null // tell request let body be Buffer instance - , ca: this.conf.get("ca") - , localAddress: this.conf.get("local-address") - , cert: this.conf.get("cert") - , key: this.conf.get("key") - , strictSSL: strict } - , headers = opts.headers = {} - if (etag) { - this.log.verbose("etag", etag) - headers[method === "GET" ? "if-none-match" : "if-match"] = etag - } +function makeRequest (method, where, what, etag, follow, authed, cb_) { + var cb = once(cb_) - headers["npm-session"] = this.sessionToken - headers.version = this.version || pkg.version + var parsed = url.parse(where) + var headers = {} - if (this.refer) { - headers.referer = this.refer - } - - headers.accept = "application/json" + // metadata should be compressed headers["accept-encoding"] = "gzip" - headers["user-agent"] = this.conf.get("user-agent") || - "node/" + process.version + var er = this.authify(authed, parsed, headers) + if (er) return cb_(er) - var c = this.conf.getCredentialsByURI(url.format(remote)) - if (c.token) headers.authorization = "Bearer " + c.token + var opts = this.initialize( + parsed, + method, + "application/json", + headers + ) - var p = this.conf.get("proxy") - var sp = this.conf.get("https-proxy") || p - opts.proxy = remote.protocol === "https:" ? sp : p + opts.followRedirect = follow + opts.encoding = null // tell request let body be Buffer instance + + if (etag) { + this.log.verbose("etag", etag) + headers[method === "GET" ? "if-none-match" : "if-match"] = etag + } // figure out wth "what" is if (what) { @@ -219,11 +151,7 @@ function makeRequest (method, remote, where, what, etag, nofollow, cb_) { } } - if (nofollow) { - opts.followRedirect = false - } - - this.log.http(method, remote.href || "/") + this.log.http("request", method, parsed.href || "/") var done = requestDone.call(this, method, where, cb) var req = request(opts, decodeResponseBody(done)) diff --git a/node_modules/npm-registry-client/lib/unpublish.js b/node_modules/npm-registry-client/lib/unpublish.js index 6a4ac8a19..346d537fe 100644 --- a/node_modules/npm-registry-client/lib/unpublish.js +++ b/node_modules/npm-registry-client/lib/unpublish.js @@ -22,7 +22,7 @@ function unpublish (uri, ver, cb) { // remove all if no version specified if (!ver) { this.log.info("unpublish", "No version specified, removing all") - return this.request("DELETE", uri+'/-rev/'+data._rev, null, cb) + return this.request("DELETE", uri+"/-rev/"+data._rev, null, cb) } var versions = data.versions || {} @@ -72,7 +72,7 @@ function unpublish (uri, ver, cb) { function detacher (uri, data, dist, cb) { return function (er) { if (er) return cb(er) - this.get(url.resolve(uri, data.name), null, function (er, data) { + this.get(escape(uri, data.name), null, function (er, data) { if (er) return cb(er) var tb = url.parse(dist.tarball) @@ -96,10 +96,15 @@ function detach (uri, data, path, rev, cb) { this.log.info("detach", path) return this.request("DELETE", url.resolve(uri, path), null, cb) } - this.get(url.resolve(uri, data.name), null, function (er, data) { + this.get(escape(uri, data.name), null, function (er, data) { rev = data._rev if (!rev) return cb(new Error( "No _rev found in "+data._id)) detach.call(this, data, path, rev, cb) }.bind(this)) } + +function escape (base, name) { + var escaped = name.replace(/\//, "%2f") + return url.resolve(base, escaped) +} diff --git a/node_modules/npm-registry-client/package.json b/node_modules/npm-registry-client/package.json index 6acb40410..ec65c225b 100644 --- a/node_modules/npm-registry-client/package.json +++ b/node_modules/npm-registry-client/package.json @@ -1,8 +1,12 @@ { - "author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)", + "author": { + "name": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/" + }, "name": "npm-registry-client", "description": "Client for the npm registry", - "version": "3.0.5", + "version": "3.1.1", "repository": { "url": "git://github.com/isaacs/npm-registry-client" }, @@ -13,20 +17,33 @@ "dependencies": { "chownr": "0", "graceful-fs": "^3.0.0", - "mkdirp": "0.5", + "mkdirp": "^0.5.0", "normalize-package-data": "0.4", "npm-cache-filename": "^1.0.0", + "once": "^1.3.0", "request": "2 >=2.25.0", "retry": "0.6.0", "rimraf": "2", "semver": "2 >=2.2.1 || 3.x", - "slide": "^1.1.3" + "slide": "^1.1.3", + "npmlog": "" }, "devDependencies": { + "concat-stream": "^1.4.6", "tap": "" }, "optionalDependencies": { "npmlog": "" }, - "license": "BSD" + "license": "ISC", + "readme": "# npm-registry-client\n\nThe code that npm uses to talk to the registry.\n\nIt handles all the caching and HTTP calls.\n\n## Usage\n\n```javascript\nvar RegClient = require('npm-registry-client')\nvar client = new RegClient(config)\nvar uri = \"npm://registry.npmjs.org/npm\"\nvar options = {timeout: 1000}\n\nclient.get(uri, options, function (error, data, raw, res) {\n // error is an error if there was a problem.\n // data is the parsed data object\n // raw is the json string\n // res is the response from couch\n})\n```\n\n# Registry URLs\n\nThe registry calls take either a full URL pointing to a resource in the\nregistry, or a base URL for the registry as a whole (for the base URL, any path\nwill be ignored). In addition to `http` and `https`, `npm` URLs are allowed.\n`npm` URLs are `https` URLs with the additional restrictions that they will\nalways include authorization credentials, and the response is always registry\nmetadata (and not tarballs or other attachments).\n\n# Configuration\n\nThis program is designed to work with\n[npmconf](https://npmjs.org/package/npmconf), but you can also pass in\na plain-jane object with the appropriate configs, and it'll shim it\nfor you. Any configuration thingie that has get/set/del methods will\nalso be accepted.\n\n* `cache` **Required** {String} Path to the cache folder\n* `always-auth` {Boolean} Auth even for GET requests.\n* `auth` {String} A base64-encoded `username:password`\n* `email` {String} User's email address\n* `tag` {String} The default tag to use when publishing new packages.\n Default = `\"latest\"`\n* `ca` {String} Cerficate signing authority certificates to trust.\n* `cert` {String} Client certificate (PEM encoded). Enable access\n to servers that require client certificates\n* `key` {String} Private key (PEM encoded) for client certificate 'cert'\n* `strict-ssl` {Boolean} Whether or not to be strict with SSL\n certificates. Default = `true`\n* `user-agent` {String} User agent header to send. Default =\n `\"node/{process.version} {process.platform} {process.arch}\"`\n* `log` {Object} The logger to use. Defaults to `require(\"npmlog\")` if\n that works, otherwise logs are disabled.\n* `fetch-retries` {Number} Number of times to retry on GET failures.\n Default=2\n* `fetch-retry-factor` {Number} `factor` setting for `node-retry`. Default=10\n* `fetch-retry-mintimeout` {Number} `minTimeout` setting for `node-retry`.\n Default=10000 (10 seconds)\n* `fetch-retry-maxtimeout` {Number} `maxTimeout` setting for `node-retry`.\n Default=60000 (60 seconds)\n* `proxy` {URL} The url to proxy requests through.\n* `https-proxy` {URL} The url to proxy https requests through.\n Defaults to be the same as `proxy` if unset.\n* `_auth` {String} The base64-encoded authorization header.\n* `username` `_password` {String} Username/password to use to generate\n `_auth` if not supplied.\n* `_token` {Object} A token for use with\n [couch-login](https://npmjs.org/package/couch-login)\n\n# client.request(method, uri, options, cb)\n\n* `method` {String} HTTP method\n* `uri` {String} URI pointing to the resource to request\n* `options` {Object} Object containing optional per-request properties.\n * `what` {Stream | Buffer | String | Object} The request body. Objects\n that are not Buffers or Streams are encoded as JSON.\n * `etag` {String} The cached ETag\n * `follow` {Boolean} Follow 302/301 responses (defaults to true)\n* `cb` {Function}\n * `error` {Error | null}\n * `data` {Object} the parsed data object\n * `raw` {String} the json\n * `res` {Response Object} response from couch\n\nMake a request to the registry. All the other methods are wrappers around\n`request`.\n\n# client.adduser(base, username, password, email, cb)\n\n* `base` {String} Base registry URL\n* `username` {String}\n* `password` {String}\n* `email` {String}\n* `cb` {Function}\n\nAdd a user account to the registry, or verify the credentials.\n\n# client.deprecate(uri, version, message, cb)\n\n* `uri` {String} Full registry URI for the deprecated package\n* `version` {String} Semver version range\n* `message` {String} The message to use as a deprecation warning\n* `cb` {Function}\n\nDeprecate a version of a package in the registry.\n\n# client.bugs(uri, cb)\n\n* `uri` {String} Full registry URI for the package\n* `cb` {Function}\n\nGet the url for bugs of a package\n\n# client.get(uri, options, cb)\n\n* `uri` {String} The complete registry URI to fetch\n* `options` {Object} Object containing optional per-request properties.\n * `timeout` {Number} Duration before the request times out.\n * `follow` {Boolean} Follow 302/301 responses (defaults to true)\n * `staleOk` {Boolean} If there's cached data available, then return that\n to the callback quickly, and update the cache the background.\n\nFetches data from the registry via a GET request, saving it in the cache folder\nwith the ETag.\n\n# client.publish(uri, data, tarball, cb)\n\n* `uri` {String} The registry URI to publish to\n* `data` {Object} Package data\n* `tarball` {String | Stream} Filename or stream of the package tarball\n* `cb` {Function}\n\nPublish a package to the registry.\n\nNote that this does not create the tarball from a folder. However, it can\naccept a gzipped tar stream or a filename to a tarball.\n\n# client.star(uri, starred, cb)\n\n* `uri` {String} The complete registry URI to star\n* `starred` {Boolean} True to star the package, false to unstar it.\n* `cb` {Function}\n\nStar or unstar a package.\n\nNote that the user does not have to be the package owner to star or unstar a\npackage, though other writes do require that the user be the package owner.\n\n# client.stars(base, username, cb)\n\n* `base` {String} The base URL for the registry\n* `username` {String} Name of user to fetch starred packages for.\n* `cb` {Function}\n\nView your own or another user's starred packages.\n\n# client.tag(uri, version, tag, cb)\n\n* `uri` {String} The complete registry URI to tag\n* `version` {String} Version to tag\n* `tag` {String} Tag name to apply\n* `cb` {Function}\n\nMark a version in the `dist-tags` hash, so that `pkg@tag` will fetch the\nspecified version.\n\n# client.unpublish(uri, [ver], cb)\n\n* `uri` {String} The complete registry URI to unpublish\n* `ver` {String} version to unpublish. Leave blank to unpublish all\n versions.\n* `cb` {Function}\n\nRemove a version of a package (or all versions) from the registry. When the\nlast version us unpublished, the entire document is removed from the database.\n\n# client.upload(uri, file, [etag], [nofollow], cb)\n\n* `uri` {String} The complete registry URI to upload to\n* `file` {String | Stream} Either the filename or a readable stream\n* `etag` {String} Cache ETag\n* `nofollow` {Boolean} Do not follow 301/302 responses\n* `cb` {Function}\n\nUpload an attachment. Mostly used by `client.publish()`.\n", + "readmeFilename": "README.md", + "gitHead": "e11fcc97220fb240d859018e268edc8b69e94fcb", + "bugs": { + "url": "https://github.com/isaacs/npm-registry-client/issues" + }, + "homepage": "https://github.com/isaacs/npm-registry-client", + "_id": "npm-registry-client@3.1.1", + "_shasum": "197d43a342df0b07792fdcee5b0a46c64d17c7cb", + "_from": "npm-registry-client@3.1.1" } diff --git a/node_modules/npm-registry-client/test/fetch-basic.js b/node_modules/npm-registry-client/test/fetch-basic.js new file mode 100644 index 000000000..2ce3b212b --- /dev/null +++ b/node_modules/npm-registry-client/test/fetch-basic.js @@ -0,0 +1,44 @@ +var resolve = require("path").resolve +var createReadStream = require("graceful-fs").createReadStream +var readFileSync = require("graceful-fs").readFileSync + +var tap = require("tap") +var cat = require("concat-stream") + +var server = require("./lib/server.js") +var common = require("./lib/common.js") + +var tgz = resolve(__dirname, "./fixtures/underscore/1.3.3/package.tgz") + +tap.test("basic fetch", function (t) { + server.expect("/underscore/-/underscore-1.3.3.tgz", function (req, res) { + t.equal(req.method, "GET", "got expected method") + + res.writeHead(200, { + "content-type" : "application/x-tar", + "content-encoding" : "gzip" + }) + + createReadStream(tgz).pipe(res) + }) + + var client = common.freshClient() + client.fetch( + "http://localhost:1337/underscore/-/underscore-1.3.3.tgz", + null, + function (er, res) { + t.ifError(er, "loaded successfully") + + var sink = cat(function (data) { + t.deepEqual(data, readFileSync(tgz)) + t.end() + }) + + res.on("error", function (error) { + t.ifError(error, "no errors on stream") + }) + + res.pipe(sink) + } + ) +}) diff --git a/node_modules/npm-registry-client/test/fetch-retries.js b/node_modules/npm-registry-client/test/fetch-retries.js new file mode 100644 index 000000000..e3e89a6ea --- /dev/null +++ b/node_modules/npm-registry-client/test/fetch-retries.js @@ -0,0 +1,52 @@ +var resolve = require("path").resolve +var createReadStream = require("graceful-fs").createReadStream +var readFileSync = require("graceful-fs").readFileSync + +var tap = require("tap") +var cat = require("concat-stream") + +var server = require("./lib/server.js") +var common = require("./lib/common.js") + +var tgz = resolve(__dirname, "./fixtures/underscore/1.3.3/package.tgz") + +tap.test("fetch with timeout", function (t) { + server.expect("/underscore/-/underscore-1.3.3.tgz", function (req, res) { + t.equal(req.method, "GET", "got expected method") + + res.writeHead(408) + res.end() + }) + + server.expect("/underscore/-/underscore-1.3.3.tgz", function (req, res) { + t.equal(req.method, "GET", "got expected method") + + res.writeHead(200, { + "content-type" : "application/x-tar", + "content-encoding" : "gzip" + }) + + createReadStream(tgz).pipe(res) + }) + + var client = common.freshClient() + client.conf.set("fetch-retry-mintimeout", 100) + client.fetch( + "http://localhost:1337/underscore/-/underscore-1.3.3.tgz", + {}, + function (er, res) { + t.ifError(er, "loaded successfully") + + var sink = cat(function (data) { + t.deepEqual(data, readFileSync(tgz)) + t.end() + }) + + res.on("error", function (error) { + t.ifError(error, "no errors on stream") + }) + + res.pipe(sink) + } + ) +}) diff --git a/node_modules/npm-registry-client/test/request-gzip-content.js b/node_modules/npm-registry-client/test/request-gzip-content.js index 79c2e8dc0..1085bfaca 100644 --- a/node_modules/npm-registry-client/test/request-gzip-content.js +++ b/node_modules/npm-registry-client/test/request-gzip-content.js @@ -19,10 +19,12 @@ var pkg = { zlib.gzip(JSON.stringify(pkg), function (err, pkgGzip) { tap.test("request gzip package content", function (t) { + t.ifError(err, "example package compressed") + server.expect("GET", "/some-package-gzip/1.2.3", function (req, res) { res.statusCode = 200 - res.setHeader("Content-Encoding", "gzip"); - res.setHeader("Content-Type", "application/json"); + res.setHeader("Content-Encoding", "gzip") + res.setHeader("Content-Type", "application/json") res.end(pkgGzip) }) @@ -46,4 +48,4 @@ zlib.gzip(JSON.stringify(pkg), function (err, pkgGzip) { t.end() }) }) -}); +}) diff --git a/node_modules/npm-registry-client/test/unpublish-scoped.js b/node_modules/npm-registry-client/test/unpublish-scoped.js new file mode 100644 index 000000000..0e5cb8606 --- /dev/null +++ b/node_modules/npm-registry-client/test/unpublish-scoped.js @@ -0,0 +1,59 @@ +var tap = require("tap") + +var server = require("./lib/server.js") +var common = require("./lib/common.js") + +var nerfed = "//localhost:" + server.port + "/:" + +var configuration = {} +configuration[nerfed + "_authToken"] = "of-glad-tidings" + +var client = common.freshClient(configuration) + +var cache = require("./fixtures/@npm/npm-registry-client/cache.json") + +var REV = "/-rev/213-0a1049cf56172b7d9a1184742c6477b9" +var VERSION = "3.0.6" + +tap.test("unpublish a package", function (t) { + server.expect("GET", "/@npm%2fnpm-registry-client?write=true", function (req, res) { + t.equal(req.method, "GET") + + res.json(cache) + }) + + server.expect("PUT", "/@npm%2fnpm-registry-client" + REV, function (req, res) { + t.equal(req.method, "PUT") + + var b = "" + req.setEncoding("utf-8") + req.on("data", function (d) { + b += d + }) + + req.on("end", function () { + var updated = JSON.parse(b) + t.notOk(updated.versions[VERSION]) + }) + + res.json(cache) + }) + + server.expect("GET", "/@npm%2fnpm-registry-client", function (req, res) { + t.equal(req.method, "GET") + + res.json(cache) + }) + + server.expect("DELETE", "/@npm%2fnpm-registry-client/-/@npm%2fnpm-registry-client-" + VERSION + ".tgz" + REV, function (req, res) { + t.equal(req.method, "DELETE") + + res.json({unpublished:true}) + }) + + client.unpublish("http://localhost:1337/@npm%2fnpm-registry-client", VERSION, function (error) { + t.ifError(error, "no errors") + + t.end() + }) +}) diff --git a/package.json b/package.json index be0ade9d9..dcc493689 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "npm-cache-filename": "~1.0.1", "npm-install-checks": "~1.0.2", "npm-package-arg": "~2.0.2", - "npm-registry-client": "~3.0.2", + "npm-registry-client": "~3.1.0", "npm-user-validate": "~0.1.0", "npmconf": "~2.0.4", "npmlog": "~0.1.1", diff --git a/test/tap/cache-shasum.js b/test/tap/cache-shasum.js index 2139d8fb7..7450d3e60 100644 --- a/test/tap/cache-shasum.js +++ b/test/tap/cache-shasum.js @@ -1,7 +1,6 @@ var npm = require.resolve("../../") var test = require("tap").test var path = require("path") -var fs = require("fs") var rimraf = require("rimraf") var mkdirp = require("mkdirp") var mr = require("npm-registry-mock") @@ -44,7 +43,7 @@ test("compare", function(t) { var d = path.resolve(__dirname, "cache-shasum/request") var p = path.resolve(d, "2.27.0/package.tgz") var r = require("./cache-shasum/localhost_1337/request/.cache.json") - var rshasum = r.versions['2.27.0'].dist.shasum + var rshasum = r.versions["2.27.0"].dist.shasum sha.get(p, function (er, pshasum) { if (er) throw er diff --git a/test/tap/url-dependencies.js b/test/tap/url-dependencies.js index 7f8cc7864..34a77f4bc 100644 --- a/test/tap/url-dependencies.js +++ b/test/tap/url-dependencies.js @@ -7,7 +7,7 @@ var spawn = require("child_process").spawn var npm = require.resolve("../../bin/npm-cli.js") var node = process.execPath var pkg = path.resolve(__dirname, "url-dependencies") -var common = require('../common-tap') +var common = require("../common-tap") var mockRoutes = { "get": { @@ -19,9 +19,9 @@ test("url-dependencies: download first time", function(t) { cleanup() performInstall(function(output){ - if(!tarballWasFetched(output)){ + if (!tarballWasFetched(output)){ t.fail("Tarball was not fetched") - }else{ + } else { t.pass("Tarball was fetched") } t.end() @@ -33,9 +33,9 @@ test("url-dependencies: do not download subsequent times", function(t) { performInstall(function(){ performInstall(function(output){ - if(tarballWasFetched(output)){ + if (tarballWasFetched(output)){ t.fail("Tarball was fetched second time around") - }else{ + } else { t.pass("Tarball was not fetched") } t.end() @@ -44,7 +44,7 @@ test("url-dependencies: do not download subsequent times", function(t) { }) function tarballWasFetched(output){ - return output.indexOf("http GET " + common.registry + "/underscore/-/underscore-1.3.1.tgz") > -1 + return output.indexOf("http fetch GET " + common.registry + "/underscore/-/underscore-1.3.1.tgz") > -1 } function performInstall (cb) { @@ -53,10 +53,10 @@ function performInstall (cb) { , child = spawn(node, [npm, "install"], { cwd: pkg, env: { - npm_config_registry: common.registry, - npm_config_cache_lock_stale: 1000, - npm_config_cache_lock_wait: 1000, - npm_config_loglevel: "http", + "npm_config_registry": common.registry, + "npm_config_cache_lock_stale": 1000, + "npm_config_cache_lock_wait": 1000, + "npm_config_loglevel": "http", HOME: process.env.HOME, Path: process.env.PATH, PATH: process.env.PATH |