Comparing version 2.12.0 to 2.14.0
200
main.js
@@ -22,2 +22,3 @@ // Copyright 2010-2012 Mikeal Rogers | ||
, qs = require('querystring') | ||
, crypto = require('crypto') | ||
, oauth = require('./oauth') | ||
@@ -52,2 +53,6 @@ , uuid = require('./uuid') | ||
function md5 (str) { | ||
return crypto.createHash('md5').update(str).digest('hex') | ||
} | ||
// Hacky fix for pre-0.4.4 https | ||
@@ -114,5 +119,5 @@ if (https && !https.Agent) { | ||
if (!options) options = {} | ||
if (process.env.NODE_DEBUG && /request/.test(process.env.NODE_DEBUG)) console.error('REQUEST', options) | ||
if (request.debug) console.error('REQUEST', options) | ||
if (!self.pool && self.pool !== false) self.pool = globalPool | ||
self.dests = [] | ||
self.dests = self.dests || [] | ||
self.__isRequestRequest = true | ||
@@ -144,2 +149,3 @@ | ||
} | ||
if (self.proxy) { | ||
@@ -155,3 +161,5 @@ if (typeof self.proxy == 'string') self.proxy = url.parse(self.proxy) | ||
, port: +self.proxy.port | ||
, proxyAuth: self.proxy.auth } | ||
, proxyAuth: self.proxy.auth | ||
, headers: { Host: self.uri.hostname + ':' + | ||
(self.uri.port || self.uri.protocol === 'https:' ? 443 : 80) }} | ||
, ca: this.ca } | ||
@@ -189,3 +197,3 @@ | ||
self.setHost = false | ||
if (!self.headers.host) { | ||
if (!(self.headers.host || self.headers.Host)) { | ||
self.headers.host = self.uri.hostname | ||
@@ -219,4 +227,3 @@ if (self.uri.port) { | ||
if (self.setHost) delete self.headers.host | ||
if (self.req._reusedSocket && error.code === 'ECONNRESET' | ||
if (self.req && self.req._reusedSocket && error.code === 'ECONNRESET' | ||
&& self.agent.addRequestNoreuse) { | ||
@@ -250,3 +257,14 @@ self.agent = { addRequest: self.agent.addRequestNoreuse.bind(self.agent) } | ||
} | ||
if (options.qs) self.qs(options.qs) | ||
if (self.uri.path) { | ||
self.path = self.uri.path | ||
} else { | ||
self.path = self.uri.pathname + (self.uri.search || "") | ||
} | ||
if (self.path.length === 0) self.path = '/' | ||
if (options.oauth) { | ||
@@ -260,4 +278,12 @@ self.oauth(options.oauth) | ||
if (options.auth) { | ||
self.auth( | ||
options.auth.user || options.auth.username, | ||
options.auth.pass || options.auth.password, | ||
options.auth.sendImmediately) | ||
} | ||
if (self.uri.auth && !self.headers.authorization) { | ||
self.headers.authorization = "Basic " + toBase64(self.uri.auth.split(':').map(function(item){ return qs.unescape(item)}).join(':')) | ||
var authPieces = self.uri.auth.split(':').map(function(item){ return qs.unescape(item) }) | ||
self.auth(authPieces[0], authPieces[1], true) | ||
} | ||
@@ -268,12 +294,3 @@ if (self.proxy && self.proxy.auth && !self.headers['proxy-authorization'] && !self.tunnel) { | ||
if (options.qs) self.qs(options.qs) | ||
if (self.uri.path) { | ||
self.path = self.uri.path | ||
} else { | ||
self.path = self.uri.pathname + (self.uri.search || "") | ||
} | ||
if (self.path.length === 0) self.path = '/' | ||
if (self.proxy && !self.tunnel) self.path = (self.uri.protocol + '//' + self.uri.host + self.path) | ||
@@ -416,3 +433,3 @@ | ||
var tunnelOptions = { proxy: { host: self.proxy.hostname | ||
, post: +self.proxy.port | ||
, port: +self.proxy.port | ||
, proxyAuth: self.proxy.auth } | ||
@@ -475,3 +492,10 @@ , ca: self.ca } | ||
if (this.ca) options.ca = this.ca | ||
if (typeof this.rejectUnauthorized !== 'undefined') | ||
options.rejectUnauthorized = this.rejectUnauthorized; | ||
if (this.cert && this.key) { | ||
options.key = this.key | ||
options.cert = this.cert | ||
} | ||
var poolKey = '' | ||
@@ -495,6 +519,16 @@ | ||
if (typeof proxy === 'string') proxy = url.parse(proxy) | ||
var caRelevant = (proxy && proxy.protocol === 'https:') || this.uri.protocol === 'https:' | ||
if (options.ca && caRelevant) { | ||
if (poolKey) poolKey += ':' | ||
poolKey += options.ca | ||
var isHttps = (proxy && proxy.protocol === 'https:') || this.uri.protocol === 'https:' | ||
if (isHttps) { | ||
if (options.ca) { | ||
if (poolKey) poolKey += ':' | ||
poolKey += options.ca | ||
} | ||
if (typeof options.rejectUnauthorized !== 'undefined') { | ||
if (poolKey) poolKey += ':' | ||
poolKey += options.rejectUnauthorized | ||
} | ||
if (options.cert) | ||
poolKey += options.cert.toString('ascii') + options.key.toString('ascii') | ||
} | ||
@@ -532,3 +566,9 @@ | ||
} | ||
self.req = self.httpModule.request(self, function (response) { | ||
// We have a method named auth, which is completely different from the http.request | ||
// auth option. If we don't remove it, we're gonna have a bad time. | ||
var reqOptions = copy(self) | ||
delete reqOptions.auth | ||
self.req = self.httpModule.request(reqOptions, function (response) { | ||
if (response.connection.listeners('error').indexOf(self._parserErrorHandler) === -1) { | ||
@@ -568,6 +608,70 @@ response.connection.once('error', self._parserErrorHandler) | ||
if (response.statusCode >= 300 && response.statusCode < 400 && | ||
(self.followAllRedirects || | ||
(self.followRedirect && (self.method !== 'PUT' && self.method !== 'POST' && self.method !== 'DELETE'))) && | ||
response.headers.location) { | ||
var redirectTo = null | ||
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) { | ||
if (self.followAllRedirects) { | ||
redirectTo = response.headers.location | ||
} else if (self.followRedirect) { | ||
switch (self.method) { | ||
case 'PUT': | ||
case 'POST': | ||
case 'DELETE': | ||
// Do not follow redirects | ||
break | ||
default: | ||
redirectTo = response.headers.location | ||
break | ||
} | ||
} | ||
} else if (response.statusCode == 401 && self._hasAuth && !self._sentAuth) { | ||
var authHeader = response.headers['www-authenticate'] | ||
var authVerb = authHeader && authHeader.split(' ')[0] | ||
switch (authVerb) { | ||
case 'Basic': | ||
self.auth(self._user, self._pass, true) | ||
redirectTo = self.uri | ||
break | ||
case 'Digest': | ||
// TODO: More complete implementation of RFC 2617. For reference: | ||
// http://tools.ietf.org/html/rfc2617#section-3 | ||
// https://github.com/bagder/curl/blob/master/lib/http_digest.c | ||
var matches = authHeader.match(/([a-z0-9_-]+)="([^"]+)"/gi) | ||
var challenge = {} | ||
for (var i = 0; i < matches.length; i++) { | ||
var eqPos = matches[i].indexOf('=') | ||
var key = matches[i].substring(0, eqPos) | ||
var quotedValue = matches[i].substring(eqPos + 1) | ||
challenge[key] = quotedValue.substring(1, quotedValue.length - 1) | ||
} | ||
var ha1 = md5(self._user + ':' + challenge.realm + ':' + self._pass) | ||
var ha2 = md5(self.method + ':' + self.uri.path) | ||
var digestResponse = md5(ha1 + ':' + challenge.nonce + ':1::auth:' + ha2) | ||
var authValues = { | ||
username: self._user, | ||
realm: challenge.realm, | ||
nonce: challenge.nonce, | ||
uri: self.uri.path, | ||
qop: challenge.qop, | ||
response: digestResponse, | ||
nc: 1, | ||
cnonce: '' | ||
} | ||
authHeader = [] | ||
for (var k in authValues) { | ||
authHeader.push(k + '="' + authValues[k] + '"') | ||
} | ||
authHeader = 'Digest ' + authHeader.join(', ') | ||
self.setHeader('authorization', authHeader) | ||
self._sentAuth = true | ||
redirectTo = self.uri | ||
break | ||
} | ||
} | ||
if (redirectTo) { | ||
if (self._redirectsFollowed >= self.maxRedirects) { | ||
@@ -579,8 +683,8 @@ self.emit('error', new Error("Exceeded maxRedirects. Probably stuck in a redirect loop "+self.uri.href)) | ||
if (!isUrl.test(response.headers.location)) { | ||
response.headers.location = url.resolve(self.uri.href, response.headers.location) | ||
if (!isUrl.test(redirectTo)) { | ||
redirectTo = url.resolve(self.uri.href, redirectTo) | ||
} | ||
var uriPrev = self.uri | ||
self.uri = url.parse(response.headers.location) | ||
self.uri = url.parse(redirectTo) | ||
@@ -594,6 +698,6 @@ // handle the case where we change protocol from https to http or vice versa | ||
{ statusCode : response.statusCode | ||
, redirectUri: response.headers.location | ||
, redirectUri: redirectTo | ||
} | ||
) | ||
if (self.followAllRedirects) self.method = 'GET' | ||
if (self.followAllRedirects && response.statusCode != 401) self.method = 'GET' | ||
// self.method = 'GET' // Force all redirects to use GET || commented out fixes #215 | ||
@@ -604,4 +708,6 @@ delete self.src | ||
delete self._started | ||
delete self.body | ||
delete self._form | ||
if (response.statusCode != 401) { | ||
delete self.body | ||
delete self._form | ||
} | ||
if (self.headers) { | ||
@@ -612,3 +718,3 @@ delete self.headers.host | ||
} | ||
if (log) log('Redirect to %uri', self) | ||
if (log) log('Redirect to %uri due to status %status', {uri: self.uri, status: response.statusCode}) | ||
self.init() | ||
@@ -839,2 +945,15 @@ return // Ignore the rest of the response | ||
} | ||
Request.prototype.auth = function (user, pass, sendImmediately) { | ||
if (typeof user !== 'string' || typeof pass !== 'string') { | ||
throw new Error('auth() received invalid user or password') | ||
} | ||
this._user = user | ||
this._pass = pass | ||
this._hasAuth = true | ||
if (sendImmediately || typeof sendImmediately == 'undefined') { | ||
this.setHeader('authorization', 'Basic ' + toBase64(user + ':' + pass)) | ||
this._sentAuth = true | ||
} | ||
return this | ||
} | ||
Request.prototype.aws = function (opts, now) { | ||
@@ -896,3 +1015,4 @@ if (!now) { | ||
delete oa.oauth_token_secret | ||
var timestamp = oa.oauth_timestamp | ||
var baseurl = this.uri.protocol + '//' + this.uri.host + this.uri.pathname | ||
@@ -910,3 +1030,4 @@ var signature = oauth.hmacsign(this.method, baseurl, oa, consumer_secret, token_secret) | ||
} | ||
this.headers.Authorization = | ||
oa.oauth_timestamp = timestamp | ||
this.headers.Authorization = | ||
'OAuth '+Object.keys(oa).sort().map(function (i) {return i+'="'+oauth.rfc3986(oa[i])+'"'}).join(',') | ||
@@ -1023,2 +1144,4 @@ this.headers.Authorization += ',oauth_signature="' + oauth.rfc3986(signature) + '"' | ||
request.debug = process.env.NODE_DEBUG && /request/.test(process.env.NODE_DEBUG) | ||
request.initParams = initParams | ||
@@ -1142,6 +1265,5 @@ | ||
function toJSON () { | ||
return getSafe(this, (((1+Math.random())*0x10000)|0).toString(16)) | ||
return getSafe(this, '__' + (((1+Math.random())*0x10000)|0).toString(16)) | ||
} | ||
Request.prototype.toJSON = toJSON | ||
@@ -23,3 +23,5 @@ var CombinedStream = require('combined-stream'); | ||
FormData.prototype.append = function(field, value) { | ||
FormData.prototype.append = function(field, value, options) { | ||
options = options || {}; | ||
var append = CombinedStream.prototype.append.bind(this); | ||
@@ -30,4 +32,4 @@ | ||
var header = this._multiPartHeader(field, value); | ||
var footer = this._multiPartFooter(field, value); | ||
var header = this._multiPartHeader(field, value, options); | ||
var footer = this._multiPartFooter(field, value, options); | ||
@@ -38,8 +40,16 @@ append(header); | ||
this._trackLength(header, value) | ||
// pass along options.knownLength | ||
this._trackLength(header, value, options); | ||
}; | ||
FormData.prototype._trackLength = function(header, value) { | ||
FormData.prototype._trackLength = function(header, value, options) { | ||
var valueLength = 0; | ||
if (Buffer.isBuffer(value)) { | ||
// used w/ trackLengthSync(), when length is known. | ||
// e.g. for streaming directly from a remote server, | ||
// w/ a known file a size, and not wanting to wait for | ||
// incoming file to finish to get its size. | ||
if (options.knownLength != null) { | ||
valueLength += +options.knownLength; | ||
} else if (Buffer.isBuffer(value)) { | ||
valueLength = value.length; | ||
@@ -51,2 +61,4 @@ } else if (typeof value === 'string') { | ||
this._valueLength += valueLength; | ||
// @check why add CRLF? does this account for custom/multiple CRLFs? | ||
this._overheadLength += | ||
@@ -56,3 +68,3 @@ Buffer.byteLength(header) + | ||
// empty or ethier doesn't have path or not an http response | ||
// empty or either doesn't have path or not an http response | ||
if (!value || ( !value.path && !(value.readable && value.hasOwnProperty('httpVersion')) )) { | ||
@@ -64,4 +76,9 @@ return; | ||
// do we already know the size? | ||
// 0 additional leaves value from getSyncLength() | ||
if (options.knownLength != null) { | ||
next(null, 0); | ||
// check if it's local file | ||
if (value.hasOwnProperty('fd')) { | ||
} else if (value.hasOwnProperty('fd')) { | ||
fs.stat(value.path, function(err, stat) { | ||
@@ -96,27 +113,36 @@ if (err) { | ||
FormData.prototype._multiPartHeader = function(field, value) { | ||
FormData.prototype._multiPartHeader = function(field, value, options) { | ||
var boundary = this.getBoundary(); | ||
var header = | ||
'--' + boundary + FormData.LINE_BREAK + | ||
'Content-Disposition: form-data; name="' + field + '"'; | ||
var header = ''; | ||
// fs- and request- streams have path property | ||
// TODO: Use request's response mime-type | ||
if (value.path) { | ||
header += | ||
'; filename="' + path.basename(value.path) + '"' + FormData.LINE_BREAK + | ||
'Content-Type: ' + mime.lookup(value.path); | ||
// custom header specified (as string)? | ||
// it becomes responsible for boundary | ||
// (e.g. to handle extra CRLFs on .NET servers) | ||
if (options.header != null) { | ||
header = options.header; | ||
} else { | ||
header += '--' + boundary + FormData.LINE_BREAK + | ||
'Content-Disposition: form-data; name="' + field + '"'; | ||
// http response has not | ||
} else if (value.readable && value.hasOwnProperty('httpVersion')) { | ||
header += | ||
'; filename="' + path.basename(value.client._httpMessage.path) + '"' + FormData.LINE_BREAK + | ||
'Content-Type: ' + value.headers['content-type']; | ||
// fs- and request- streams have path property | ||
// TODO: Use request's response mime-type | ||
if (value.path) { | ||
header += | ||
'; filename="' + path.basename(value.path) + '"' + FormData.LINE_BREAK + | ||
'Content-Type: ' + mime.lookup(value.path); | ||
// http response has not | ||
} else if (value.readable && value.hasOwnProperty('httpVersion')) { | ||
header += | ||
'; filename="' + path.basename(value.client._httpMessage.path) + '"' + FormData.LINE_BREAK + | ||
'Content-Type: ' + value.headers['content-type']; | ||
} | ||
header += FormData.LINE_BREAK + FormData.LINE_BREAK; | ||
} | ||
header += FormData.LINE_BREAK + FormData.LINE_BREAK; | ||
return header; | ||
}; | ||
FormData.prototype._multiPartFooter = function(field, value) { | ||
FormData.prototype._multiPartFooter = function(field, value, options) { | ||
return function(next) { | ||
@@ -216,17 +242,33 @@ var footer = FormData.LINE_BREAK; | ||
FormData.prototype.submit = function(url, cb) { | ||
FormData.prototype.submit = function(params, cb) { | ||
this.getLength(function(err, length) { | ||
var request | ||
, parsedUrl = parseUrl(url) | ||
, options = { | ||
method: 'post', | ||
port: parsedUrl.port || 80, | ||
path: parsedUrl.pathname, | ||
headers: this.getHeaders({'Content-Length': length}), | ||
host: parsedUrl.hostname | ||
}; | ||
, options | ||
, defaults = { | ||
method : 'post', | ||
port : 80, | ||
headers: this.getHeaders({'Content-Length': length}) | ||
}; | ||
if (parsedUrl.protocol == 'https:') { | ||
// parse provided url if it's string | ||
// or treat it as options object | ||
if (typeof params == 'string') { | ||
params = parseUrl(params); | ||
options = populate({ | ||
port: params.port, | ||
path: params.pathname, | ||
host: params.hostname | ||
}, defaults); | ||
} | ||
else // use custom params | ||
{ | ||
options = populate(params, defaults); | ||
} | ||
// https if specified, fallback to http in any other case | ||
if (params.protocol == 'https:') { | ||
// override default port | ||
if (!parsedUrl.port) options.port = 443; | ||
if (!params.port) options.port = 443; | ||
request = https.request(options); | ||
@@ -246,1 +288,13 @@ } else { | ||
}; | ||
/* | ||
* Santa's little helpers | ||
*/ | ||
// populates missing values | ||
function populate(dst, src) { | ||
for (var prop in src) { | ||
if (!dst[prop]) dst[prop] = src[prop]; | ||
} | ||
return dst; | ||
} |
@@ -67,14 +67,2 @@ /*global setTimeout: false, console: false */ | ||
var _indexOf = function (arr, item) { | ||
if (arr.indexOf) { | ||
return arr.indexOf(item); | ||
} | ||
for (var i = 0; i < arr.length; i += 1) { | ||
if (arr[i] === item) { | ||
return i; | ||
} | ||
} | ||
return -1; | ||
}; | ||
//// exported async module functions //// | ||
@@ -93,2 +81,3 @@ | ||
async.forEach = function (arr, iterator, callback) { | ||
callback = callback || function () {}; | ||
if (!arr.length) { | ||
@@ -107,3 +96,3 @@ return callback(); | ||
if (completed === arr.length) { | ||
callback(); | ||
callback(null); | ||
} | ||
@@ -116,2 +105,3 @@ } | ||
async.forEachSeries = function (arr, iterator, callback) { | ||
callback = callback || function () {}; | ||
if (!arr.length) { | ||
@@ -130,3 +120,3 @@ return callback(); | ||
if (completed === arr.length) { | ||
callback(); | ||
callback(null); | ||
} | ||
@@ -142,3 +132,40 @@ else { | ||
async.forEachLimit = function (arr, limit, iterator, callback) { | ||
callback = callback || function () {}; | ||
if (!arr.length || limit <= 0) { | ||
return callback(); | ||
} | ||
var completed = 0; | ||
var started = 0; | ||
var running = 0; | ||
(function replenish () { | ||
if (completed === arr.length) { | ||
return callback(); | ||
} | ||
while (running < limit && started < arr.length) { | ||
started += 1; | ||
running += 1; | ||
iterator(arr[started - 1], function (err) { | ||
if (err) { | ||
callback(err); | ||
callback = function () {}; | ||
} | ||
else { | ||
completed += 1; | ||
running -= 1; | ||
if (completed === arr.length) { | ||
callback(); | ||
} | ||
else { | ||
replenish(); | ||
} | ||
} | ||
}); | ||
} | ||
})(); | ||
}; | ||
var doParallel = function (fn) { | ||
@@ -256,2 +283,3 @@ return function () { | ||
main_callback(x); | ||
main_callback = function () {}; | ||
} | ||
@@ -334,3 +362,3 @@ else { | ||
var completed = []; | ||
var results = {}; | ||
@@ -350,3 +378,3 @@ var listeners = []; | ||
var taskComplete = function () { | ||
_forEach(listeners, function (fn) { | ||
_forEach(listeners.slice(0), function (fn) { | ||
fn(); | ||
@@ -357,4 +385,5 @@ }); | ||
addListener(function () { | ||
if (completed.length === keys.length) { | ||
callback(null); | ||
if (_keys(results).length === keys.length) { | ||
callback(null, results); | ||
callback = function () {}; | ||
} | ||
@@ -372,3 +401,7 @@ }); | ||
else { | ||
completed.push(k); | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
if (args.length <= 1) { | ||
args = args[0]; | ||
} | ||
results[k] = args; | ||
taskComplete(); | ||
@@ -380,7 +413,7 @@ } | ||
return _reduce(requires, function (a, x) { | ||
return (a && _indexOf(completed, x) !== -1); | ||
}, true); | ||
return (a && results.hasOwnProperty(x)); | ||
}, true) && !results.hasOwnProperty(k); | ||
}; | ||
if (ready()) { | ||
task[task.length - 1](taskCallback); | ||
task[task.length - 1](taskCallback, results); | ||
} | ||
@@ -391,3 +424,3 @@ else { | ||
removeListener(listener); | ||
task[task.length - 1](taskCallback); | ||
task[task.length - 1](taskCallback, results); | ||
} | ||
@@ -401,6 +434,6 @@ }; | ||
async.waterfall = function (tasks, callback) { | ||
callback = callback || function () {}; | ||
if (!tasks.length) { | ||
return callback(); | ||
} | ||
callback = callback || function () {}; | ||
var wrapIterator = function (iterator) { | ||
@@ -563,4 +596,4 @@ return function (err) { | ||
var workers = 0; | ||
var tasks = []; | ||
var q = { | ||
tasks: [], | ||
concurrency: concurrency, | ||
@@ -571,10 +604,20 @@ saturated: null, | ||
push: function (data, callback) { | ||
tasks.push({data: data, callback: callback}); | ||
if(q.saturated && tasks.length == concurrency) q.saturated(); | ||
async.nextTick(q.process); | ||
if(data.constructor !== Array) { | ||
data = [data]; | ||
} | ||
_forEach(data, function(task) { | ||
q.tasks.push({ | ||
data: task, | ||
callback: typeof callback === 'function' ? callback : null | ||
}); | ||
if (q.saturated && q.tasks.length == concurrency) { | ||
q.saturated(); | ||
} | ||
async.nextTick(q.process); | ||
}); | ||
}, | ||
process: function () { | ||
if (workers < q.concurrency && tasks.length) { | ||
var task = tasks.splice(0, 1)[0]; | ||
if(q.empty && tasks.length == 0) q.empty(); | ||
if (workers < q.concurrency && q.tasks.length) { | ||
var task = q.tasks.shift(); | ||
if(q.empty && q.tasks.length == 0) q.empty(); | ||
workers += 1; | ||
@@ -586,3 +629,3 @@ worker(task.data, function () { | ||
} | ||
if(q.drain && tasks.length + workers == 0) q.drain(); | ||
if(q.drain && q.tasks.length + workers == 0) q.drain(); | ||
q.process(); | ||
@@ -593,3 +636,3 @@ }); | ||
length: function () { | ||
return tasks.length; | ||
return q.tasks.length; | ||
}, | ||
@@ -631,6 +674,7 @@ running: function () { | ||
var memo = {}; | ||
var queues = {}; | ||
hasher = hasher || function (x) { | ||
return x; | ||
}; | ||
return function () { | ||
var memoized = function () { | ||
var args = Array.prototype.slice.call(arguments); | ||
@@ -642,11 +686,27 @@ var callback = args.pop(); | ||
} | ||
else if (key in queues) { | ||
queues[key].push(callback); | ||
} | ||
else { | ||
queues[key] = [callback]; | ||
fn.apply(null, args.concat([function () { | ||
memo[key] = arguments; | ||
callback.apply(null, arguments); | ||
var q = queues[key]; | ||
delete queues[key]; | ||
for (var i = 0, l = q.length; i < l; i++) { | ||
q[i].apply(null, arguments); | ||
} | ||
}])); | ||
} | ||
}; | ||
memoized.unmemoized = fn; | ||
return memoized; | ||
}; | ||
async.unmemoize = function (fn) { | ||
return function () { | ||
return (fn.unmemoized || fn).apply(null, arguments); | ||
}; | ||
}; | ||
}()); |
@@ -8,3 +8,3 @@ { | ||
}, | ||
"version": "0.1.9", | ||
"version": "0.1.22", | ||
"repository": { | ||
@@ -23,2 +23,7 @@ "type": "git", | ||
], | ||
"devDependencies": { | ||
"nodeunit": ">0.0.0", | ||
"uglify-js": "1.2.x", | ||
"nodelint": ">0.0.0" | ||
}, | ||
"_npmUser": { | ||
@@ -28,5 +33,4 @@ "name": "mikeal", | ||
}, | ||
"_id": "async@0.1.9", | ||
"_id": "async@0.1.22", | ||
"dependencies": {}, | ||
"devDependencies": {}, | ||
"optionalDependencies": {}, | ||
@@ -40,6 +44,3 @@ "engines": { | ||
"_defaultsLoaded": true, | ||
"dist": { | ||
"shasum": "fd9b6aca66495fd0f7e97f86e71c7706ca9ae754" | ||
}, | ||
"_from": "async@0.1.9" | ||
"_from": "async@~0.1.9" | ||
} |
@@ -10,3 +10,3 @@ # Async.js | ||
suspects (map, reduce, filter, forEach…) as well as some common patterns | ||
for asynchronous flow control (parallel, series, waterfall…). All these | ||
for asynchronous control flow (parallel, series, waterfall…). All these | ||
functions assume you follow the node.js convention of providing a single | ||
@@ -84,3 +84,3 @@ callback as the last argument of your async function. | ||
### Flow Control | ||
### Control Flow | ||
@@ -101,2 +101,3 @@ * [series](#series) | ||
* [memoize](#memoize) | ||
* [unmemoize](#unmemoize) | ||
* [log](#log) | ||
@@ -149,2 +150,28 @@ * [dir](#dir) | ||
<a name="forEachLimit" /> | ||
### forEachLimit(arr, limit, iterator, callback) | ||
The same as forEach only the iterator is applied to batches of items in the | ||
array, in series. The next batch of iterators is only called once the current | ||
one has completed processing. | ||
__Arguments__ | ||
* arr - An array to iterate over. | ||
* limit - How many items should be in each batch. | ||
* iterator(item, callback) - A function to apply to each item in the array. | ||
The iterator is passed a callback which must be called once it has completed. | ||
* callback(err) - A callback which is called after all the iterator functions | ||
have finished, or an error has occurred. | ||
__Example__ | ||
// Assume documents is an array of JSON objects and requestApi is a | ||
// function that interacts with a rate-limited REST api. | ||
async.forEachLimit(documents, 20, requestApi, function(err){ | ||
// if any of the saves produced an error, err would equal that error | ||
}); | ||
--------------------------------------- | ||
<a name="map" /> | ||
@@ -450,3 +477,3 @@ ### map(arr, iterator, callback) | ||
## Flow Control | ||
## Control Flow | ||
@@ -508,3 +535,3 @@ <a name="series" /> | ||
function(err, results) { | ||
// results is now equals to: {one: 1, two: 2} | ||
// results is now equal to: {one: 1, two: 2} | ||
}); | ||
@@ -554,5 +581,4 @@ | ||
function(err, results){ | ||
// in this case, the results array will equal ['two','one'] | ||
// because the functions were run in parallel and the second | ||
// function had a shorter timeout before calling the callback. | ||
// the results array will equal ['one','two'] even though | ||
// the second function had a shorter timeout. | ||
}); | ||
@@ -609,3 +635,3 @@ | ||
} | ||
}); | ||
); | ||
@@ -638,5 +664,7 @@ | ||
must call on completion. | ||
* callback(err) - An optional callback to run once all the functions have | ||
completed. This function gets passed any error that may have occurred. | ||
* callback(err, [results]) - An optional callback to run once all the functions | ||
have completed. This will be passed the results of the last task's callback. | ||
__Example__ | ||
@@ -655,3 +683,5 @@ | ||
} | ||
]); | ||
], function (err, result) { | ||
// result now equals 'done' | ||
}); | ||
@@ -687,2 +717,3 @@ | ||
once the worker has finished processing the task. | ||
instead of a single task, an array of tasks can be submitted. the respective callback is used for every task in the list. | ||
* saturated - a callback that is called when the queue length hits the concurrency and further tasks will be queued | ||
@@ -697,3 +728,3 @@ * empty - a callback that is called when the last item from the queue is given to a worker | ||
var q = async.queue(function (task, callback) { | ||
console.log('hello ' + task.name). | ||
console.log('hello ' + task.name); | ||
callback(); | ||
@@ -717,3 +748,9 @@ }, 2); | ||
// add some items to the queue (batch-wise) | ||
q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function (err) { | ||
console.log('finished processing bar'); | ||
}); | ||
--------------------------------------- | ||
@@ -727,5 +764,6 @@ | ||
and each function is run as soon as its requirements are satisfied. If any of | ||
the functions pass and error to their callback, that function will not complete | ||
the functions pass an error to their callback, that function will not complete | ||
(so any other functions depending on it will not run) and the main callback | ||
will be called immediately with the error. | ||
will be called immediately with the error. Functions also receive an object | ||
containing the results of functions which have completed so far. | ||
@@ -738,4 +776,6 @@ __Arguments__ | ||
syntax is easier to understand by looking at the example. | ||
* callback(err) - An optional callback which is called when all the tasks have | ||
been completed. The callback may receive an error as an argument. | ||
* callback(err, results) - An optional callback which is called when all the | ||
tasks have been completed. The callback will receive an error as an argument | ||
if any tasks pass an error to their callback. If all tasks complete | ||
successfully, it will receive an object containing their results. | ||
@@ -755,5 +795,7 @@ __Example__ | ||
// write the data to a file in the directory | ||
callback(null, filename); | ||
}], | ||
email_link: ['write_file', function(callback){ | ||
email_link: ['write_file', function(callback, results){ | ||
// once the file is written let's email a link to it... | ||
// results.write_file contains the filename returned by write_file. | ||
}] | ||
@@ -780,3 +822,3 @@ }); | ||
}, | ||
email_link: ['write_file', function(callback){ | ||
email_link: function(callback){ | ||
// once the file is written let's email a link to it... | ||
@@ -788,3 +830,3 @@ } | ||
For a complicated series of async tasks using the auto function makes adding | ||
new tasks much easier and makes the code more readable. | ||
new tasks much easier and makes the code more readable. | ||
@@ -834,3 +876,3 @@ | ||
Creates a continuation function with some arguments already applied, a useful | ||
shorthand when combined with other flow control functions. Any arguments | ||
shorthand when combined with other control flow functions. Any arguments | ||
passed to the returned function are added to the arguments originally passed | ||
@@ -929,3 +971,12 @@ to apply. | ||
<a name="unmemoize" /> | ||
### unmemoize(fn) | ||
Undoes a memoized function, reverting it to the original, unmemoized | ||
form. Comes handy in tests. | ||
__Arguments__ | ||
* fn - the memoized function | ||
<a name="log" /> | ||
@@ -932,0 +983,0 @@ ### log(function, arguments) |
@@ -33,2 +33,4 @@ var util = require('util'); | ||
&& (typeof stream !== 'string') | ||
&& (typeof stream !== 'boolean') | ||
&& (typeof stream !== 'number') | ||
&& (!Buffer.isBuffer(stream)); | ||
@@ -71,3 +73,3 @@ }; | ||
if (!stream) { | ||
if (typeof stream == 'undefined') { | ||
this.end(); | ||
@@ -74,0 +76,0 @@ return; |
@@ -9,3 +9,3 @@ { | ||
"description": "A stream that emits multiple other streams one after another.", | ||
"version": "0.0.3", | ||
"version": "0.0.4", | ||
"homepage": "https://github.com/felixge/node-combined-stream", | ||
@@ -30,3 +30,3 @@ "repository": { | ||
}, | ||
"_id": "combined-stream@0.0.3", | ||
"_id": "combined-stream@0.0.4", | ||
"optionalDependencies": {}, | ||
@@ -38,5 +38,5 @@ "_engineSupported": true, | ||
"dist": { | ||
"shasum": "c41c9899277b587901bb6ce4bf458b94693afafa" | ||
"shasum": "2d1a43347dbe9515a4a2796732e5b88473840b22" | ||
}, | ||
"_from": "combined-stream@0.0.3" | ||
"_from": "combined-stream@~0.0.4" | ||
} |
var common = module.exports; | ||
var path = require('path'); | ||
var fs = require('fs'); | ||
var root = path.join(__dirname, '..'); | ||
@@ -11,3 +12,13 @@ | ||
// Create tmp directory if it does not exist | ||
// Not using fs.exists so as to be node 0.6.x compatible | ||
try { | ||
fs.statSync(common.dir.tmp); | ||
} | ||
catch (e) { | ||
// Dir does not exist | ||
fs.mkdirSync(common.dir.tmp); | ||
} | ||
common.CombinedStream = require(root); | ||
common.assert = require('assert'); |
@@ -9,3 +9,3 @@ { | ||
"description": "A module to create readable `\"multipart/form-data\"` streams. Can be used to submit forms and file uploads to other web applications.", | ||
"version": "0.0.3", | ||
"version": "0.0.7", | ||
"repository": { | ||
@@ -20,3 +20,3 @@ "type": "git", | ||
"dependencies": { | ||
"combined-stream": "0.0.3", | ||
"combined-stream": "~0.0.4", | ||
"mime": "~1.2.2", | ||
@@ -35,3 +35,3 @@ "async": "~0.1.9" | ||
}, | ||
"_id": "form-data@0.0.3", | ||
"_id": "form-data@0.0.7", | ||
"optionalDependencies": {}, | ||
@@ -43,5 +43,5 @@ "_engineSupported": true, | ||
"dist": { | ||
"shasum": "6eea17b45790b42d779a1d581d1b3600fe0c7c0d" | ||
"shasum": "7211182a26a266ce39710dc8bc4a81b7040859be" | ||
}, | ||
"_from": "form-data" | ||
"_from": "form-data@~0.0.3" | ||
} |
@@ -86,2 +86,34 @@ # form-data | ||
To use custom headers and pre-known length in parts: | ||
``` javascript | ||
var CRLF = '\r\n'; | ||
var form = new FormData(); | ||
var options = { | ||
header: CRLF + '--' + form.getBoundary() + CRLF + 'X-Custom-Header: 123' + CRLF + CRLF, | ||
knownLength: 1 | ||
}; | ||
form.append('my_buffer', buffer, options); | ||
form.submit('http://example.com/', function(err, res) { | ||
if (err) throw err; | ||
console.log('Done'); | ||
}); | ||
``` | ||
For edge cases, like POST request to URL with query string or to pass HTTP auth creadentials, object can be passed to `form.submit()` as first parameter: | ||
``` javascript | ||
form.submit({ | ||
host: 'example.com', | ||
path: '/probably.php?extra=params', | ||
auth: 'username:password' | ||
}, function(err, res) { | ||
console.log(res.statusCode); | ||
}); | ||
``` | ||
[xhr2-fd]: http://dev.w3.org/2006/webapi/XMLHttpRequest-2/Overview.html#the-formdata-interface |
@@ -13,7 +13,10 @@ var common = require('../common'); | ||
// wrap non simple values into function | ||
// just to deal with ReadStream "autostart" | ||
// Can't wait for 0.10 | ||
var FIELDS = [ | ||
{name: 'my_field', value: 'my_value'}, | ||
{name: 'my_buffer', value: new Buffer([1, 2, 3])}, | ||
{name: 'my_file', value: fs.createReadStream(common.dir.fixture + '/unicycle.jpg')}, | ||
{name: 'remote_file', value: request(remoteFile) } | ||
{name: 'my_buffer', value: function(){ return new Buffer([1, 2, 3])} }, | ||
{name: 'my_file', value: function(){ return fs.createReadStream(common.dir.fixture + '/unicycle.jpg')} }, | ||
{name: 'remote_file', value: function(){ return request(remoteFile)} } | ||
]; | ||
@@ -23,3 +26,4 @@ | ||
// formidable is broken so let's do it manual way | ||
// formidable is fixed on github | ||
// but still 7 month old in npm | ||
// | ||
@@ -71,2 +75,3 @@ // var form = new IncomingForm(); | ||
assert.ok( data.indexOf('; filename="'+path.basename(field.value.path)+'"') != -1 ); | ||
// check for unicycle.jpg traces | ||
@@ -95,2 +100,6 @@ assert.ok( data.indexOf('2005:06:21 01:44:12') != -1 ); | ||
FIELDS.forEach(function(field) { | ||
// important to append ReadStreams within the same tick | ||
if ((typeof field.value == 'function')) { | ||
field.value = field.value(); | ||
} | ||
form.append(field.name, field.value); | ||
@@ -97,0 +106,0 @@ }); |
@@ -13,7 +13,10 @@ var common = require('../common'); | ||
// wrap non simple values into function | ||
// just to deal with ReadStream "autostart" | ||
// Can't wait for 0.10 | ||
var FIELDS = [ | ||
{name: 'my_field', value: 'my_value'}, | ||
{name: 'my_buffer', value: new Buffer([1, 2, 3])}, | ||
{name: 'my_file', value: fs.createReadStream(common.dir.fixture + '/unicycle.jpg') }, | ||
{name: 'remote_file', value: request(remoteFile) } | ||
{name: 'my_buffer', value: function(){ return new Buffer([1, 2, 3])} }, | ||
{name: 'my_file', value: function(){ return fs.createReadStream(common.dir.fixture + '/unicycle.jpg')} }, | ||
{name: 'remote_file', value: function(){ return request(remoteFile)} } | ||
]; | ||
@@ -23,3 +26,4 @@ | ||
// formidable is broken so let's do it manual way | ||
// formidable is fixed on github | ||
// but still 7 month old in npm | ||
// | ||
@@ -90,4 +94,10 @@ // var form = new IncomingForm(); | ||
server.listen(common.port, function() { | ||
var form = new FormData(); | ||
FIELDS.forEach(function(field) { | ||
// important to append ReadStreams within the same tick | ||
if ((typeof field.value == 'function')) { | ||
field.value = field.value(); | ||
} | ||
form.append(field.name, field.value); | ||
@@ -94,0 +104,0 @@ }); |
@@ -26,2 +26,7 @@ var path = require('path'); | ||
for (var i = 0; i < exts.length; i++) { | ||
if (process.env.DEBUG_MIME && this.types[exts]) { | ||
console.warn(this._loading.replace(/.*\//, ''), 'changes "' + exts[i] + '" extension type from ' + | ||
this.types[exts] + ' to ' + type); | ||
} | ||
this.types[exts[i]] = type; | ||
@@ -46,2 +51,4 @@ } | ||
Mime.prototype.load = function(file) { | ||
this._loading = file; | ||
// Read file and split into lines | ||
@@ -59,2 +66,4 @@ var map = {}, | ||
this.define(map); | ||
this._loading = null; | ||
}; | ||
@@ -105,4 +114,4 @@ | ||
} | ||
} | ||
}; | ||
module.exports = mime; |
@@ -27,3 +27,3 @@ { | ||
}, | ||
"version": "1.2.7", | ||
"version": "1.2.9", | ||
"_npmUser": { | ||
@@ -33,3 +33,3 @@ "name": "mikeal", | ||
}, | ||
"_id": "mime@1.2.7", | ||
"_id": "mime@1.2.9", | ||
"optionalDependencies": {}, | ||
@@ -43,3 +43,3 @@ "engines": { | ||
"_defaultsLoaded": true, | ||
"_from": "mime" | ||
"_from": "mime@~1.2.7" | ||
} |
{ "name" : "request" | ||
, "description" : "Simplified HTTP request client." | ||
, "tags" : ["http", "simple", "util", "utility"] | ||
, "version" : "2.12.0" | ||
, "version" : "2.14.0" | ||
, "author" : "Mikeal Rogers <mikeal.rogers@gmail.com>" | ||
@@ -6,0 +6,0 @@ , "repository" : |
@@ -118,2 +118,22 @@ # Request -- Simplified HTTP request method | ||
## HTTP Authentication | ||
```javascript | ||
request.auth('username', 'password', false).get('http://some.server.com/'); | ||
// or | ||
request.get('http://some.server.com/', { | ||
'auth': { | ||
'user': 'username', | ||
'pass': 'password', | ||
'sendImmediately': false | ||
} | ||
}); | ||
``` | ||
If passed as an option, `auth` should be a hash containing values `user` || `username`, `password` || `pass`, and `sendImmediately` (optional). The method form takes parameters `auth(username, password, sendImmediately)`. | ||
`sendImmediately` defaults to true, which will cause a basic authentication header to be sent. If `sendImmediately` is `false`, then `request` will retry with a proper authentication header after receiving a 401 response from the server (which must contain a `WWW-Authenticate` header indicating the required authentication method). | ||
Digest authentication is supported, but it only works with `sendImmediately` set to `false` (otherwise `request` will send basic authentication on the initial request, which will probably cause the request to fail). | ||
## OAuth Signing | ||
@@ -177,2 +197,3 @@ | ||
* `form` - when passed an object this will set `body` but to a querystring representation of value and adds `Content-type: application/x-www-form-urlencoded; charset=utf-8` header. When passed no option a FormData instance is returned that will be piped to request. | ||
* `auth` - A hash containing values `user` || `username`, `password` || `pass`, and `sendImmediately` (optional). See documentation above. | ||
* `json` - sets `body` but to JSON representation of value and adds `Content-type: application/json` header. Additionally, parses the response body as json. | ||
@@ -179,0 +200,0 @@ * `multipart` - (experimental) array of objects which contains their own headers and `body` attribute. Sends `multipart/related` request. See example below. |
var spawn = require('child_process').spawn | ||
, exitCode = 0 | ||
, timeout = 10000 | ||
; | ||
var tests = [ | ||
'test-body.js' | ||
'test-basic-auth.js' | ||
, 'test-body.js' | ||
, 'test-cookie.js' | ||
, 'test-cookiejar.js' | ||
, 'test-defaults.js' | ||
, 'test-digest-auth.js' | ||
, 'test-errors.js' | ||
@@ -38,9 +41,20 @@ , 'test-form.js' | ||
var proc = spawn('node', [ 'tests/' + file ]) | ||
var killed = false | ||
var t = setTimeout(function () { | ||
proc.kill() | ||
exitCode += 1 | ||
console.error(file + ' timeout') | ||
killed = true | ||
}, timeout) | ||
proc.stdout.pipe(process.stdout) | ||
proc.stderr.pipe(process.stderr) | ||
proc.on('exit', function (code) { | ||
exitCode += code || 0 | ||
next() | ||
if (code && !killed) console.error(file + ' failed') | ||
exitCode += code || 0 | ||
clearTimeout(t) | ||
next() | ||
}) | ||
} | ||
next() |
@@ -72,2 +72,3 @@ var server = require('./server') | ||
test.uri = s.url + '/' + i | ||
test.rejectUnauthorized = false | ||
request(test, function (err, resp, body) { | ||
@@ -74,0 +75,0 @@ if (err) throw err |
@@ -54,3 +54,4 @@ var server = require('./server') | ||
request({ url: (i % 2 ? sUrl : ssUrl) + '/a' | ||
, headers: { 'x-test-key': val } }) | ||
, headers: { 'x-test-key': val } | ||
, rejectUnauthorized: false }) | ||
} | ||
@@ -57,0 +58,0 @@ |
@@ -26,3 +26,3 @@ // test that we can tunnel a https request over an http proxy | ||
.join('\nSQUIDERR ')) | ||
ready = c.toString().match(/ready to serve requests/i) | ||
ready = c.toString().match(/ready to serve requests|Accepting HTTP Socket connections/i) | ||
}) | ||
@@ -29,0 +29,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
332
448426
123
5720
34
34