mirror of
https://github.com/zhigang1992/npm.git
synced 2026-06-17 04:09:14 +08:00
228 lines
7.3 KiB
JavaScript
228 lines
7.3 KiB
JavaScript
|
|
module.exports = request
|
|
request.GET = GET
|
|
request.PUT = PUT
|
|
request.reg = reg
|
|
request.upload = upload
|
|
|
|
var npm = require("../../../npm")
|
|
, http = require("http")
|
|
, url = require("url")
|
|
, log = require("../log")
|
|
, ini = require("../ini")
|
|
, Buffer = require("buffer").Buffer
|
|
, fs = require("../graceful-fs")
|
|
|
|
function request (method, where, what, etag, nofollow, cb) {
|
|
log.verbose(where||"/", method)
|
|
|
|
if (where.match(/^\/?favicon.ico/)) {
|
|
return cb(new Error("favicon.ico isn't a package, it's a picture."))
|
|
}
|
|
if (typeof cb !== "function") cb = nofollow, nofollow = false
|
|
if (typeof cb !== "function") cb = etag, etag = null
|
|
if (typeof cb !== "function") cb = what, what = null
|
|
var registry = reg()
|
|
if (registry instanceof Error) return cb(registry)
|
|
|
|
var adduserChange = /^\/?adduser\/org\.couchdb\.user:([^\/]+)\/-rev/
|
|
, authRequired = what && !where.match(/^\/?adduser\/org\.couchdb\.user:/)
|
|
|| where.match(adduserChange)
|
|
|| method === "DELETE"
|
|
if (!where.match(/^https?:\/\//)) {
|
|
where = where.split("/").map(function (p) {
|
|
p = p.trim()
|
|
if (p.match(/^org.couchdb.user/)) {
|
|
return p.replace(/\//g, encodeURIComponent("/"))
|
|
}
|
|
return encodeURIComponent(p)
|
|
}).join("/")
|
|
where = url.resolve(registry, where)
|
|
}
|
|
var u = url.parse(where)
|
|
, proxyConfig = npm.config.get("proxy")
|
|
if (proxyConfig) {
|
|
if (!proxyConfig.match(/^https?:\/\//)) {
|
|
proxyConfig = u.protocol+"//"+proxyConfig
|
|
}
|
|
var proxy = (proxyConfig) ? url.parse(proxyConfig) : null
|
|
if (!proxy.host) return cb(new Error("Bad proxy config: "+proxyConfig))
|
|
}
|
|
var https = (proxy || u).protocol === "https:"
|
|
, port = (proxy || u).port || (https ? 443 : 80)
|
|
, hostname = (proxy || u).hostname
|
|
, client = http.createClient(port, hostname, https)
|
|
, auth = authRequired && npm.config.get("_auth")
|
|
, path = proxy ? u.href : u.pathname
|
|
|
|
if (authRequired && !auth) {
|
|
return cb(new Error(
|
|
"Cannot insert data into the registry without authorization\n"
|
|
+ "See: npm-adduser(1)"))
|
|
}
|
|
if (auth && !https) {
|
|
log.warn("Sending authorization over insecure channel.")
|
|
}
|
|
var headers = { "host" : u.host
|
|
, "accept" : "application/json"
|
|
}
|
|
if (auth) headers.authorization = "Basic " + auth
|
|
if (proxy && proxy.auth) {
|
|
headers["proxy-authorization"] =
|
|
"Basic " + (new Buffer(proxy.auth).toString("base64"))
|
|
}
|
|
if (what) {
|
|
if (what instanceof File) {
|
|
log.verbose(what.name, "uploading")
|
|
headers["content-type"] = "application/octet-stream"
|
|
} else {
|
|
delete what._etag
|
|
log.silly(what,"writing json")
|
|
what = new Buffer(JSON.stringify(what))
|
|
headers["content-type"] = "application/json"
|
|
}
|
|
headers["content-length"] = what.length
|
|
} else {
|
|
headers["content-length"] = 0
|
|
}
|
|
if (etag) {
|
|
log.verbose(etag, "etag")
|
|
headers[method === "GET" ? "if-none-match" : "if-match"] = etag
|
|
}
|
|
log.silly(headers, "headers")
|
|
|
|
var req = client.request(method, path, headers)
|
|
client.on("error", log.er(cb, "Error connecting to server "+where))
|
|
req.on("error", log.er(cb, "Request error to "+where))
|
|
req.on("response", function (response) {
|
|
// if (response.statusCode !== 200) return cb(new Error(
|
|
// "Status code " + response.statusCode + " from PUT "+where))
|
|
var data = ""
|
|
response.on("error", log.er(cb, "response error from "+where))
|
|
response.on("data", function (chunk) {
|
|
log.silly(chunk+"", "chunk")
|
|
data += chunk
|
|
})
|
|
response.on("end", function () {
|
|
if (!nofollow
|
|
&& (response.statusCode === 301 || response.statusCode === 302)) {
|
|
// relative redirects SHOULD be disallowed, but alas...
|
|
var newLoc = response.headers.location
|
|
newLoc = newLoc && url.resolve(where, newLoc)
|
|
if (!newLoc) return cb(new Error(
|
|
response.statusCode + " status code with no location"))
|
|
return request(method, newLoc, what, etag,
|
|
log.er(cb, "Failed to fetch "+newLoc))
|
|
}
|
|
var parsed
|
|
if (response.statusCode !== 304) {
|
|
try {
|
|
parsed = JSON.parse(data)
|
|
} catch (ex) {
|
|
ex.message += "\n" + data
|
|
log.error("error parsing json", "registry")
|
|
return cb(ex, null, data, response)
|
|
}
|
|
}
|
|
var er = null
|
|
if (parsed && response.headers.etag) {
|
|
parsed._etag = response.headers.etag
|
|
}
|
|
if (parsed && parsed.error) {
|
|
var w = url.parse(where).pathname.substr(1)
|
|
, name = w.split("/")[0]
|
|
if (parsed.error === "not_found") {
|
|
er = new Error("404 Not Found: "+name)
|
|
er.errno = npm.E404
|
|
er.pkgid = name
|
|
} else {
|
|
er = new Error(
|
|
parsed.error + " " + (parsed.reason || "") + ": " + w)
|
|
}
|
|
}
|
|
return cb(er, parsed, data, response)
|
|
})
|
|
})
|
|
if (what instanceof File) {
|
|
var size = Math.min(what.length, 1024*1024*1024)
|
|
, remaining = what.length
|
|
log.verbose(what.length, "bytes")
|
|
;(function W () {
|
|
var b = new Buffer(size)
|
|
try {
|
|
var bytesRead = fs.readSync(what.fd, b, 0, b.length, null)
|
|
} catch (er) {
|
|
return log.er(cb, "Failure to read tarball")(er)
|
|
}
|
|
remaining -= bytesRead
|
|
if (bytesRead) {
|
|
log(bytesRead, "read")
|
|
log(remaining, "remain")
|
|
return (
|
|
req.write(bytesRead === b.length ? b : b.slice(0, bytesRead))
|
|
) ? W()
|
|
: req.on("drain", function DRAIN () {
|
|
log.silly(remaining, "drain")
|
|
req.removeListener("drain", DRAIN)
|
|
W()
|
|
})
|
|
}
|
|
if (!remaining) {
|
|
req.end()
|
|
log.verbose(what.name, "written to uploading stream")
|
|
log.verbose("Not done yet! If it hangs/quits now, it didn't work.", "upload")
|
|
return
|
|
}
|
|
// wtf!? No bytes read, but also bytes remaining.
|
|
return cb(new Error("Some kind of weirdness reading the file"))
|
|
})()
|
|
return
|
|
} else if (typeof what === "string" || Buffer.isBuffer(what)) {
|
|
// just a json blob
|
|
req.write(what)
|
|
}
|
|
req.end()
|
|
}
|
|
function GET (where, etag, nofollow, cb) {
|
|
request("GET", where, null, etag, nofollow, cb)
|
|
}
|
|
function PUT (where, what, etag, nofollow, cb) {
|
|
request("PUT", where, what, etag, nofollow, cb)
|
|
}
|
|
|
|
function upload (where, filename, etag, nofollow, cb) {
|
|
if (typeof etag === "function") cb = etag, etag = null
|
|
new File(filename, function (er, f) {
|
|
if (er) return log.er(cb, "Couldn't open "+filename)(er)
|
|
PUT(where, f, etag, nofollow, function (er) {
|
|
log.info("done with upload", "publish")
|
|
cb(er)
|
|
})
|
|
})
|
|
}
|
|
function File (name, cb) {
|
|
var f = this
|
|
f.name = name
|
|
if (f.loaded) return cb(null, f)
|
|
fs.stat(f.name, function (er, stat) {
|
|
if (er) return log.er(cb, "doesn't exist "+f.name)(er)
|
|
log.silly(stat, "stat "+name)
|
|
f.length = stat.size
|
|
fs.open(f.name, "r", 0666, function (er, fd) {
|
|
if (er) return log.er(cb, "Error opening "+f.name)(er)
|
|
f.fd = fd
|
|
cb(null, f)
|
|
})
|
|
})
|
|
}
|
|
|
|
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
|
|
}
|