@wiajs/request 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/request.js ADDED
@@ -0,0 +1,1553 @@
1
+ 'use strict'
2
+
3
+ var http = require('http')
4
+ var https = require('https')
5
+ var url = require('url')
6
+ var util = require('util')
7
+ var stream = require('stream')
8
+ var zlib = require('zlib')
9
+ var aws2 = require('aws-sign2')
10
+ var aws4 = require('aws4')
11
+ var httpSignature = require('http-signature')
12
+ var mime = require('mime-types')
13
+ var caseless = require('caseless')
14
+ var ForeverAgent = require('forever-agent')
15
+ var FormData = require('form-data')
16
+ var extend = require('extend')
17
+ var isstream = require('isstream')
18
+ var isTypedArray = require('is-typedarray').strict
19
+ var helpers = require('./lib/helpers')
20
+ var cookies = require('./lib/cookies')
21
+ var getProxyFromURI = require('./lib/getProxyFromURI')
22
+ var Querystring = require('./lib/querystring').Querystring
23
+ var Har = require('./lib/har').Har
24
+ var Auth = require('./lib/auth').Auth
25
+ var OAuth = require('./lib/oauth').OAuth
26
+ var hawk = require('./lib/hawk')
27
+ var Multipart = require('./lib/multipart').Multipart
28
+ var Redirect = require('./lib/redirect').Redirect
29
+ var Tunnel = require('./lib/tunnel').Tunnel
30
+ var now = require('performance-now')
31
+ var Buffer = require('safe-buffer').Buffer
32
+
33
+ var safeStringify = helpers.safeStringify
34
+ var isReadStream = helpers.isReadStream
35
+ var toBase64 = helpers.toBase64
36
+ var defer = helpers.defer
37
+ var copy = helpers.copy
38
+ var version = helpers.version
39
+ var globalCookieJar = cookies.jar()
40
+
41
+ var globalPool = {}
42
+
43
+ function filterForNonReserved (reserved, options) {
44
+ // Filter out properties that are not reserved.
45
+ // Reserved values are passed in at call site.
46
+
47
+ var object = {}
48
+ for (var i in options) {
49
+ var notReserved = (reserved.indexOf(i) === -1)
50
+ if (notReserved) {
51
+ object[i] = options[i]
52
+ }
53
+ }
54
+ return object
55
+ }
56
+
57
+ function filterOutReservedFunctions (reserved, options) {
58
+ // Filter out properties that are functions and are reserved.
59
+ // Reserved values are passed in at call site.
60
+
61
+ var object = {}
62
+ for (var i in options) {
63
+ var isReserved = !(reserved.indexOf(i) === -1)
64
+ var isFunction = (typeof options[i] === 'function')
65
+ if (!(isReserved && isFunction)) {
66
+ object[i] = options[i]
67
+ }
68
+ }
69
+ return object
70
+ }
71
+
72
+ // Return a simpler request object to allow serialization
73
+ function requestToJSON () {
74
+ var self = this
75
+ return {
76
+ uri: self.uri,
77
+ method: self.method,
78
+ headers: self.headers
79
+ }
80
+ }
81
+
82
+ // Return a simpler response object to allow serialization
83
+ function responseToJSON () {
84
+ var self = this
85
+ return {
86
+ statusCode: self.statusCode,
87
+ body: self.body,
88
+ headers: self.headers,
89
+ request: requestToJSON.call(self.request)
90
+ }
91
+ }
92
+
93
+ function Request (options) {
94
+ // if given the method property in options, set property explicitMethod to true
95
+
96
+ // extend the Request instance with any non-reserved properties
97
+ // remove any reserved functions from the options object
98
+ // set Request instance to be readable and writable
99
+ // call init
100
+
101
+ var self = this
102
+
103
+ // start with HAR, then override with additional options
104
+ if (options.har) {
105
+ self._har = new Har(self)
106
+ options = self._har.options(options)
107
+ }
108
+
109
+ stream.Stream.call(self)
110
+ var reserved = Object.keys(Request.prototype)
111
+ var nonReserved = filterForNonReserved(reserved, options)
112
+
113
+ extend(self, nonReserved)
114
+ options = filterOutReservedFunctions(reserved, options)
115
+
116
+ self.readable = true
117
+ self.writable = true
118
+ if (options.method) {
119
+ self.explicitMethod = true
120
+ }
121
+ self._qs = new Querystring(self)
122
+ self._auth = new Auth(self)
123
+ self._oauth = new OAuth(self)
124
+ self._multipart = new Multipart(self)
125
+ self._redirect = new Redirect(self)
126
+ self._tunnel = new Tunnel(self)
127
+ self.init(options)
128
+ }
129
+
130
+ util.inherits(Request, stream.Stream)
131
+
132
+ // Debugging
133
+ Request.debug = process.env.NODE_DEBUG && /\brequest\b/.test(process.env.NODE_DEBUG)
134
+ function debug () {
135
+ if (Request.debug) {
136
+ console.error('REQUEST %s', util.format.apply(util, arguments))
137
+ }
138
+ }
139
+ Request.prototype.debug = debug
140
+
141
+ Request.prototype.init = function (options) {
142
+ // init() contains all the code to setup the request object.
143
+ // the actual outgoing request is not started until start() is called
144
+ // this function is called from both the constructor and on redirect.
145
+ var self = this
146
+ if (!options) {
147
+ options = {}
148
+ }
149
+ self.headers = self.headers ? copy(self.headers) : {}
150
+
151
+ // Delete headers with value undefined since they break
152
+ // ClientRequest.OutgoingMessage.setHeader in node 0.12
153
+ for (var headerName in self.headers) {
154
+ if (typeof self.headers[headerName] === 'undefined') {
155
+ delete self.headers[headerName]
156
+ }
157
+ }
158
+
159
+ caseless.httpify(self, self.headers)
160
+
161
+ if (!self.method) {
162
+ self.method = options.method || 'GET'
163
+ }
164
+ if (!self.localAddress) {
165
+ self.localAddress = options.localAddress
166
+ }
167
+
168
+ self._qs.init(options)
169
+
170
+ debug(options)
171
+ if (!self.pool && self.pool !== false) {
172
+ self.pool = globalPool
173
+ }
174
+ self.dests = self.dests || []
175
+ self.__isRequestRequest = true
176
+
177
+ // Protect against double callback
178
+ if (!self._callback && self.callback) {
179
+ self._callback = self.callback
180
+ self.callback = function () {
181
+ if (self._callbackCalled) {
182
+ return // Print a warning maybe?
183
+ }
184
+ self._callbackCalled = true
185
+ self._callback.apply(self, arguments)
186
+ }
187
+ self.on('error', self.callback.bind())
188
+ self.on('complete', self.callback.bind(self, null))
189
+ }
190
+
191
+ // People use this property instead all the time, so support it
192
+ if (!self.uri && self.url) {
193
+ self.uri = self.url
194
+ delete self.url
195
+ }
196
+
197
+ // If there's a baseUrl, then use it as the base URL (i.e. uri must be
198
+ // specified as a relative path and is appended to baseUrl).
199
+ if (self.baseUrl) {
200
+ if (typeof self.baseUrl !== 'string') {
201
+ return self.emit('error', new Error('options.baseUrl must be a string'))
202
+ }
203
+
204
+ if (typeof self.uri !== 'string') {
205
+ return self.emit('error', new Error('options.uri must be a string when using options.baseUrl'))
206
+ }
207
+
208
+ if (self.uri.indexOf('//') === 0 || self.uri.indexOf('://') !== -1) {
209
+ return self.emit('error', new Error('options.uri must be a path when using options.baseUrl'))
210
+ }
211
+
212
+ // Handle all cases to make sure that there's only one slash between
213
+ // baseUrl and uri.
214
+ var baseUrlEndsWithSlash = self.baseUrl.lastIndexOf('/') === self.baseUrl.length - 1
215
+ var uriStartsWithSlash = self.uri.indexOf('/') === 0
216
+
217
+ if (baseUrlEndsWithSlash && uriStartsWithSlash) {
218
+ self.uri = self.baseUrl + self.uri.slice(1)
219
+ } else if (baseUrlEndsWithSlash || uriStartsWithSlash) {
220
+ self.uri = self.baseUrl + self.uri
221
+ } else if (self.uri === '') {
222
+ self.uri = self.baseUrl
223
+ } else {
224
+ self.uri = self.baseUrl + '/' + self.uri
225
+ }
226
+ delete self.baseUrl
227
+ }
228
+
229
+ // A URI is needed by this point, emit error if we haven't been able to get one
230
+ if (!self.uri) {
231
+ return self.emit('error', new Error('options.uri is a required argument'))
232
+ }
233
+
234
+ // If a string URI/URL was given, parse it into a URL object
235
+ if (typeof self.uri === 'string') {
236
+ self.uri = url.parse(self.uri)
237
+ }
238
+
239
+ // Some URL objects are not from a URL parsed string and need href added
240
+ if (!self.uri.href) {
241
+ self.uri.href = url.format(self.uri)
242
+ }
243
+
244
+ // DEPRECATED: Warning for users of the old Unix Sockets URL Scheme
245
+ if (self.uri.protocol === 'unix:') {
246
+ return self.emit('error', new Error('`unix://` URL scheme is no longer supported. Please use the format `http://unix:SOCKET:PATH`'))
247
+ }
248
+
249
+ // Support Unix Sockets
250
+ if (self.uri.host === 'unix') {
251
+ self.enableUnixSocket()
252
+ }
253
+
254
+ if (self.strictSSL === false) {
255
+ self.rejectUnauthorized = false
256
+ }
257
+
258
+ if (!self.uri.pathname) { self.uri.pathname = '/' }
259
+
260
+ if (!(self.uri.host || (self.uri.hostname && self.uri.port)) && !self.uri.isUnix) {
261
+ // Invalid URI: it may generate lot of bad errors, like 'TypeError: Cannot call method `indexOf` of undefined' in CookieJar
262
+ // Detect and reject it as soon as possible
263
+ var faultyUri = url.format(self.uri)
264
+ var message = 'Invalid URI "' + faultyUri + '"'
265
+ if (Object.keys(options).length === 0) {
266
+ // No option ? This can be the sign of a redirect
267
+ // As this is a case where the user cannot do anything (they didn't call request directly with this URL)
268
+ // they should be warned that it can be caused by a redirection (can save some hair)
269
+ message += '. This can be caused by a crappy redirection.'
270
+ }
271
+ // This error was fatal
272
+ self.abort()
273
+ return self.emit('error', new Error(message))
274
+ }
275
+
276
+ if (!self.hasOwnProperty('proxy')) {
277
+ self.proxy = getProxyFromURI(self.uri)
278
+ }
279
+
280
+ self.tunnel = self._tunnel.isEnabled()
281
+ if (self.proxy) {
282
+ self._tunnel.setup(options)
283
+ }
284
+
285
+ self._redirect.onRequest(options)
286
+
287
+ self.setHost = false
288
+ if (!self.hasHeader('host')) {
289
+ var hostHeaderName = self.originalHostHeaderName || 'host'
290
+ self.setHeader(hostHeaderName, self.uri.host)
291
+ // Drop :port suffix from Host header if known protocol.
292
+ if (self.uri.port) {
293
+ if ((self.uri.port === '80' && self.uri.protocol === 'http:') ||
294
+ (self.uri.port === '443' && self.uri.protocol === 'https:')) {
295
+ self.setHeader(hostHeaderName, self.uri.hostname)
296
+ }
297
+ }
298
+ self.setHost = true
299
+ }
300
+
301
+ self.jar(self._jar || options.jar)
302
+
303
+ if (!self.uri.port) {
304
+ if (self.uri.protocol === 'http:') { self.uri.port = 80 } else if (self.uri.protocol === 'https:') { self.uri.port = 443 }
305
+ }
306
+
307
+ if (self.proxy && !self.tunnel) {
308
+ self.port = self.proxy.port
309
+ self.host = self.proxy.hostname
310
+ } else {
311
+ self.port = self.uri.port
312
+ self.host = self.uri.hostname
313
+ }
314
+
315
+ if (options.form) {
316
+ self.form(options.form)
317
+ }
318
+
319
+ if (options.formData) {
320
+ var formData = options.formData
321
+ var requestForm = self.form()
322
+ var appendFormValue = function (key, value) {
323
+ if (value && value.hasOwnProperty('value') && value.hasOwnProperty('options')) {
324
+ requestForm.append(key, value.value, value.options)
325
+ } else {
326
+ requestForm.append(key, value)
327
+ }
328
+ }
329
+ for (var formKey in formData) {
330
+ if (formData.hasOwnProperty(formKey)) {
331
+ var formValue = formData[formKey]
332
+ if (formValue instanceof Array) {
333
+ for (var j = 0; j < formValue.length; j++) {
334
+ appendFormValue(formKey, formValue[j])
335
+ }
336
+ } else {
337
+ appendFormValue(formKey, formValue)
338
+ }
339
+ }
340
+ }
341
+ }
342
+
343
+ if (options.qs) {
344
+ self.qs(options.qs)
345
+ }
346
+
347
+ if (self.uri.path) {
348
+ self.path = self.uri.path
349
+ } else {
350
+ self.path = self.uri.pathname + (self.uri.search || '')
351
+ }
352
+
353
+ if (self.path.length === 0) {
354
+ self.path = '/'
355
+ }
356
+
357
+ // Auth must happen last in case signing is dependent on other headers
358
+ if (options.aws) {
359
+ self.aws(options.aws)
360
+ }
361
+
362
+ if (options.hawk) {
363
+ self.hawk(options.hawk)
364
+ }
365
+
366
+ if (options.httpSignature) {
367
+ self.httpSignature(options.httpSignature)
368
+ }
369
+
370
+ if (options.auth) {
371
+ if (Object.prototype.hasOwnProperty.call(options.auth, 'username')) {
372
+ options.auth.user = options.auth.username
373
+ }
374
+ if (Object.prototype.hasOwnProperty.call(options.auth, 'password')) {
375
+ options.auth.pass = options.auth.password
376
+ }
377
+
378
+ self.auth(
379
+ options.auth.user,
380
+ options.auth.pass,
381
+ options.auth.sendImmediately,
382
+ options.auth.bearer
383
+ )
384
+ }
385
+
386
+ if (self.gzip && !self.hasHeader('accept-encoding')) {
387
+ self.setHeader('accept-encoding', 'gzip, deflate')
388
+ }
389
+
390
+ if (self.uri.auth && !self.hasHeader('authorization')) {
391
+ var uriAuthPieces = self.uri.auth.split(':').map(function (item) { return self._qs.unescape(item) })
392
+ self.auth(uriAuthPieces[0], uriAuthPieces.slice(1).join(':'), true)
393
+ }
394
+
395
+ if (!self.tunnel && self.proxy && self.proxy.auth && !self.hasHeader('proxy-authorization')) {
396
+ var proxyAuthPieces = self.proxy.auth.split(':').map(function (item) { return self._qs.unescape(item) })
397
+ var authHeader = 'Basic ' + toBase64(proxyAuthPieces.join(':'))
398
+ self.setHeader('proxy-authorization', authHeader)
399
+ }
400
+
401
+ if (self.proxy && !self.tunnel) {
402
+ self.path = (self.uri.protocol + '//' + self.uri.host + self.path)
403
+ }
404
+
405
+ if (options.json) {
406
+ self.json(options.json)
407
+ }
408
+ if (options.multipart) {
409
+ self.multipart(options.multipart)
410
+ }
411
+
412
+ if (options.time) {
413
+ self.timing = true
414
+
415
+ // NOTE: elapsedTime is deprecated in favor of .timings
416
+ self.elapsedTime = self.elapsedTime || 0
417
+ }
418
+
419
+ function setContentLength () {
420
+ if (isTypedArray(self.body)) {
421
+ self.body = Buffer.from(self.body)
422
+ }
423
+
424
+ if (!self.hasHeader('content-length')) {
425
+ var length
426
+ if (typeof self.body === 'string') {
427
+ length = Buffer.byteLength(self.body)
428
+ } else if (Array.isArray(self.body)) {
429
+ length = self.body.reduce(function (a, b) { return a + b.length }, 0)
430
+ } else {
431
+ length = self.body.length
432
+ }
433
+
434
+ if (length) {
435
+ self.setHeader('content-length', length)
436
+ } else {
437
+ self.emit('error', new Error('Argument error, options.body.'))
438
+ }
439
+ }
440
+ }
441
+ if (self.body && !isstream(self.body)) {
442
+ setContentLength()
443
+ }
444
+
445
+ if (options.oauth) {
446
+ self.oauth(options.oauth)
447
+ } else if (self._oauth.params && self.hasHeader('authorization')) {
448
+ self.oauth(self._oauth.params)
449
+ }
450
+
451
+ var protocol = self.proxy && !self.tunnel ? self.proxy.protocol : self.uri.protocol
452
+ var defaultModules = {'http:': http, 'https:': https}
453
+ var httpModules = self.httpModules || {}
454
+
455
+ self.httpModule = httpModules[protocol] || defaultModules[protocol]
456
+
457
+ if (!self.httpModule) {
458
+ return self.emit('error', new Error('Invalid protocol: ' + protocol))
459
+ }
460
+
461
+ if (options.ca) {
462
+ self.ca = options.ca
463
+ }
464
+
465
+ if (!self.agent) {
466
+ if (options.agentOptions) {
467
+ self.agentOptions = options.agentOptions
468
+ }
469
+
470
+ if (options.agentClass) {
471
+ self.agentClass = options.agentClass
472
+ } else if (options.forever) {
473
+ var v = version()
474
+ // use ForeverAgent in node 0.10- only
475
+ if (v.major === 0 && v.minor <= 10) {
476
+ self.agentClass = protocol === 'http:' ? ForeverAgent : ForeverAgent.SSL
477
+ } else {
478
+ self.agentClass = self.httpModule.Agent
479
+ self.agentOptions = self.agentOptions || {}
480
+ self.agentOptions.keepAlive = true
481
+ }
482
+ } else {
483
+ self.agentClass = self.httpModule.Agent
484
+ }
485
+ }
486
+
487
+ if (self.pool === false) {
488
+ self.agent = false
489
+ } else {
490
+ self.agent = self.agent || self.getNewAgent()
491
+ }
492
+
493
+ self.on('pipe', function (src) {
494
+ if (self.ntick && self._started) {
495
+ self.emit('error', new Error('You cannot pipe to this stream after the outbound request has started.'))
496
+ }
497
+ self.src = src
498
+ if (isReadStream(src)) {
499
+ if (!self.hasHeader('content-type')) {
500
+ self.setHeader('content-type', mime.lookup(src.path))
501
+ }
502
+ } else {
503
+ if (src.headers) {
504
+ for (var i in src.headers) {
505
+ if (!self.hasHeader(i)) {
506
+ self.setHeader(i, src.headers[i])
507
+ }
508
+ }
509
+ }
510
+ if (self._json && !self.hasHeader('content-type')) {
511
+ self.setHeader('content-type', 'application/json')
512
+ }
513
+ if (src.method && !self.explicitMethod) {
514
+ self.method = src.method
515
+ }
516
+ }
517
+
518
+ // self.on('pipe', function () {
519
+ // console.error('You have already piped to this stream. Pipeing twice is likely to break the request.')
520
+ // })
521
+ })
522
+
523
+ defer(function () {
524
+ if (self._aborted) {
525
+ return
526
+ }
527
+
528
+ var end = function () {
529
+ if (self._form) {
530
+ if (!self._auth.hasAuth) {
531
+ self._form.pipe(self)
532
+ } else if (self._auth.hasAuth && self._auth.sentAuth) {
533
+ self._form.pipe(self)
534
+ }
535
+ }
536
+ if (self._multipart && self._multipart.chunked) {
537
+ self._multipart.body.pipe(self)
538
+ }
539
+ if (self.body) {
540
+ if (isstream(self.body)) {
541
+ self.body.pipe(self)
542
+ } else {
543
+ setContentLength()
544
+ if (Array.isArray(self.body)) {
545
+ self.body.forEach(function (part) {
546
+ self.write(part)
547
+ })
548
+ } else {
549
+ self.write(self.body)
550
+ }
551
+ self.end()
552
+ }
553
+ } else if (self.requestBodyStream) {
554
+ console.warn('options.requestBodyStream is deprecated, please pass the request object to stream.pipe.')
555
+ self.requestBodyStream.pipe(self)
556
+ } else if (!self.src) {
557
+ if (self._auth.hasAuth && !self._auth.sentAuth) {
558
+ self.end()
559
+ return
560
+ }
561
+ if (self.method !== 'GET' && typeof self.method !== 'undefined') {
562
+ self.setHeader('content-length', 0)
563
+ }
564
+ self.end()
565
+ }
566
+ }
567
+
568
+ if (self._form && !self.hasHeader('content-length')) {
569
+ // Before ending the request, we had to compute the length of the whole form, asyncly
570
+ self.setHeader(self._form.getHeaders(), true)
571
+ self._form.getLength(function (err, length) {
572
+ if (!err && !isNaN(length)) {
573
+ self.setHeader('content-length', length)
574
+ }
575
+ end()
576
+ })
577
+ } else {
578
+ end()
579
+ }
580
+
581
+ self.ntick = true
582
+ })
583
+ }
584
+
585
+ Request.prototype.getNewAgent = function () {
586
+ var self = this
587
+ var Agent = self.agentClass
588
+ var options = {}
589
+ if (self.agentOptions) {
590
+ for (var i in self.agentOptions) {
591
+ options[i] = self.agentOptions[i]
592
+ }
593
+ }
594
+ if (self.ca) {
595
+ options.ca = self.ca
596
+ }
597
+ if (self.ciphers) {
598
+ options.ciphers = self.ciphers
599
+ }
600
+ if (self.secureProtocol) {
601
+ options.secureProtocol = self.secureProtocol
602
+ }
603
+ if (self.secureOptions) {
604
+ options.secureOptions = self.secureOptions
605
+ }
606
+ if (typeof self.rejectUnauthorized !== 'undefined') {
607
+ options.rejectUnauthorized = self.rejectUnauthorized
608
+ }
609
+
610
+ if (self.cert && self.key) {
611
+ options.key = self.key
612
+ options.cert = self.cert
613
+ }
614
+
615
+ if (self.pfx) {
616
+ options.pfx = self.pfx
617
+ }
618
+
619
+ if (self.passphrase) {
620
+ options.passphrase = self.passphrase
621
+ }
622
+
623
+ var poolKey = ''
624
+
625
+ // different types of agents are in different pools
626
+ if (Agent !== self.httpModule.Agent) {
627
+ poolKey += Agent.name
628
+ }
629
+
630
+ // ca option is only relevant if proxy or destination are https
631
+ var proxy = self.proxy
632
+ if (typeof proxy === 'string') {
633
+ proxy = url.parse(proxy)
634
+ }
635
+ var isHttps = (proxy && proxy.protocol === 'https:') || this.uri.protocol === 'https:'
636
+
637
+ if (isHttps) {
638
+ if (options.ca) {
639
+ if (poolKey) {
640
+ poolKey += ':'
641
+ }
642
+ poolKey += options.ca
643
+ }
644
+
645
+ if (typeof options.rejectUnauthorized !== 'undefined') {
646
+ if (poolKey) {
647
+ poolKey += ':'
648
+ }
649
+ poolKey += options.rejectUnauthorized
650
+ }
651
+
652
+ if (options.cert) {
653
+ if (poolKey) {
654
+ poolKey += ':'
655
+ }
656
+ poolKey += options.cert.toString('ascii') + options.key.toString('ascii')
657
+ }
658
+
659
+ if (options.pfx) {
660
+ if (poolKey) {
661
+ poolKey += ':'
662
+ }
663
+ poolKey += options.pfx.toString('ascii')
664
+ }
665
+
666
+ if (options.ciphers) {
667
+ if (poolKey) {
668
+ poolKey += ':'
669
+ }
670
+ poolKey += options.ciphers
671
+ }
672
+
673
+ if (options.secureProtocol) {
674
+ if (poolKey) {
675
+ poolKey += ':'
676
+ }
677
+ poolKey += options.secureProtocol
678
+ }
679
+
680
+ if (options.secureOptions) {
681
+ if (poolKey) {
682
+ poolKey += ':'
683
+ }
684
+ poolKey += options.secureOptions
685
+ }
686
+ }
687
+
688
+ if (self.pool === globalPool && !poolKey && Object.keys(options).length === 0 && self.httpModule.globalAgent) {
689
+ // not doing anything special. Use the globalAgent
690
+ return self.httpModule.globalAgent
691
+ }
692
+
693
+ // we're using a stored agent. Make sure it's protocol-specific
694
+ poolKey = self.uri.protocol + poolKey
695
+
696
+ // generate a new agent for this setting if none yet exists
697
+ if (!self.pool[poolKey]) {
698
+ self.pool[poolKey] = new Agent(options)
699
+ // properly set maxSockets on new agents
700
+ if (self.pool.maxSockets) {
701
+ self.pool[poolKey].maxSockets = self.pool.maxSockets
702
+ }
703
+ }
704
+
705
+ return self.pool[poolKey]
706
+ }
707
+
708
+ Request.prototype.start = function () {
709
+ // start() is called once we are ready to send the outgoing HTTP request.
710
+ // this is usually called on the first write(), end() or on nextTick()
711
+ var self = this
712
+
713
+ if (self.timing) {
714
+ // All timings will be relative to this request's startTime. In order to do this,
715
+ // we need to capture the wall-clock start time (via Date), immediately followed
716
+ // by the high-resolution timer (via now()). While these two won't be set
717
+ // at the _exact_ same time, they should be close enough to be able to calculate
718
+ // high-resolution, monotonically non-decreasing timestamps relative to startTime.
719
+ var startTime = new Date().getTime()
720
+ var startTimeNow = now()
721
+ }
722
+
723
+ if (self._aborted) {
724
+ return
725
+ }
726
+
727
+ self._started = true
728
+ self.method = self.method || 'GET'
729
+ self.href = self.uri.href
730
+
731
+ if (self.src && self.src.stat && self.src.stat.size && !self.hasHeader('content-length')) {
732
+ self.setHeader('content-length', self.src.stat.size)
733
+ }
734
+ if (self._aws) {
735
+ self.aws(self._aws, true)
736
+ }
737
+
738
+ // We have a method named auth, which is completely different from the http.request
739
+ // auth option. If we don't remove it, we're gonna have a bad time.
740
+ var reqOptions = copy(self)
741
+ delete reqOptions.auth
742
+
743
+ debug('make request', self.uri.href)
744
+
745
+ // node v6.8.0 now supports a `timeout` value in `http.request()`, but we
746
+ // should delete it for now since we handle timeouts manually for better
747
+ // consistency with node versions before v6.8.0
748
+ delete reqOptions.timeout
749
+
750
+ try {
751
+ self.req = self.httpModule.request(reqOptions)
752
+ } catch (err) {
753
+ self.emit('error', err)
754
+ return
755
+ }
756
+
757
+ if (self.timing) {
758
+ self.startTime = startTime
759
+ self.startTimeNow = startTimeNow
760
+
761
+ // Timing values will all be relative to startTime (by comparing to startTimeNow
762
+ // so we have an accurate clock)
763
+ self.timings = {}
764
+ }
765
+
766
+ var timeout
767
+ if (self.timeout && !self.timeoutTimer) {
768
+ if (self.timeout < 0) {
769
+ timeout = 0
770
+ } else if (typeof self.timeout === 'number' && isFinite(self.timeout)) {
771
+ timeout = self.timeout
772
+ }
773
+ }
774
+
775
+ self.req.on('response', self.onRequestResponse.bind(self))
776
+ self.req.on('error', self.onRequestError.bind(self))
777
+ self.req.on('drain', function () {
778
+ self.emit('drain')
779
+ })
780
+
781
+ self.req.on('socket', function (socket) {
782
+ // `._connecting` was the old property which was made public in node v6.1.0
783
+ var isConnecting = socket._connecting || socket.connecting
784
+ if (self.timing) {
785
+ self.timings.socket = now() - self.startTimeNow
786
+
787
+ if (isConnecting) {
788
+ var onLookupTiming = function () {
789
+ self.timings.lookup = now() - self.startTimeNow
790
+ }
791
+
792
+ var onConnectTiming = function () {
793
+ self.timings.connect = now() - self.startTimeNow
794
+ }
795
+
796
+ socket.once('lookup', onLookupTiming)
797
+ socket.once('connect', onConnectTiming)
798
+
799
+ // clean up timing event listeners if needed on error
800
+ self.req.once('error', function () {
801
+ socket.removeListener('lookup', onLookupTiming)
802
+ socket.removeListener('connect', onConnectTiming)
803
+ })
804
+ }
805
+ }
806
+
807
+ var setReqTimeout = function () {
808
+ // This timeout sets the amount of time to wait *between* bytes sent
809
+ // from the server once connected.
810
+ //
811
+ // In particular, it's useful for erroring if the server fails to send
812
+ // data halfway through streaming a response.
813
+ self.req.setTimeout(timeout, function () {
814
+ if (self.req) {
815
+ self.abort()
816
+ var e = new Error('ESOCKETTIMEDOUT')
817
+ e.code = 'ESOCKETTIMEDOUT'
818
+ e.connect = false
819
+ self.emit('error', e)
820
+ }
821
+ })
822
+ }
823
+ if (timeout !== undefined) {
824
+ // Only start the connection timer if we're actually connecting a new
825
+ // socket, otherwise if we're already connected (because this is a
826
+ // keep-alive connection) do not bother. This is important since we won't
827
+ // get a 'connect' event for an already connected socket.
828
+ if (isConnecting) {
829
+ var onReqSockConnect = function () {
830
+ socket.removeListener('connect', onReqSockConnect)
831
+ self.clearTimeout()
832
+ setReqTimeout()
833
+ }
834
+
835
+ socket.on('connect', onReqSockConnect)
836
+
837
+ self.req.on('error', function (err) { // eslint-disable-line handle-callback-err
838
+ socket.removeListener('connect', onReqSockConnect)
839
+ })
840
+
841
+ // Set a timeout in memory - this block will throw if the server takes more
842
+ // than `timeout` to write the HTTP status and headers (corresponding to
843
+ // the on('response') event on the client). NB: this measures wall-clock
844
+ // time, not the time between bytes sent by the server.
845
+ self.timeoutTimer = setTimeout(function () {
846
+ socket.removeListener('connect', onReqSockConnect)
847
+ self.abort()
848
+ var e = new Error('ETIMEDOUT')
849
+ e.code = 'ETIMEDOUT'
850
+ e.connect = true
851
+ self.emit('error', e)
852
+ }, timeout)
853
+ } else {
854
+ // We're already connected
855
+ setReqTimeout()
856
+ }
857
+ }
858
+ self.emit('socket', socket)
859
+ })
860
+
861
+ self.emit('request', self.req)
862
+ }
863
+
864
+ Request.prototype.onRequestError = function (error) {
865
+ var self = this
866
+ if (self._aborted) {
867
+ return
868
+ }
869
+ if (self.req && self.req._reusedSocket && error.code === 'ECONNRESET' &&
870
+ self.agent.addRequestNoreuse) {
871
+ self.agent = { addRequest: self.agent.addRequestNoreuse.bind(self.agent) }
872
+ self.start()
873
+ self.req.end()
874
+ return
875
+ }
876
+ self.clearTimeout()
877
+ self.emit('error', error)
878
+ }
879
+
880
+ Request.prototype.onRequestResponse = function (response) {
881
+ var self = this
882
+
883
+ if (self.timing) {
884
+ self.timings.response = now() - self.startTimeNow
885
+ }
886
+
887
+ debug('onRequestResponse', self.uri.href, response.statusCode, response.headers)
888
+ response.on('end', function () {
889
+ if (self.timing) {
890
+ self.timings.end = now() - self.startTimeNow
891
+ response.timingStart = self.startTime
892
+
893
+ // fill in the blanks for any periods that didn't trigger, such as
894
+ // no lookup or connect due to keep alive
895
+ if (!self.timings.socket) {
896
+ self.timings.socket = 0
897
+ }
898
+ if (!self.timings.lookup) {
899
+ self.timings.lookup = self.timings.socket
900
+ }
901
+ if (!self.timings.connect) {
902
+ self.timings.connect = self.timings.lookup
903
+ }
904
+ if (!self.timings.response) {
905
+ self.timings.response = self.timings.connect
906
+ }
907
+
908
+ debug('elapsed time', self.timings.end)
909
+
910
+ // elapsedTime includes all redirects
911
+ self.elapsedTime += Math.round(self.timings.end)
912
+
913
+ // NOTE: elapsedTime is deprecated in favor of .timings
914
+ response.elapsedTime = self.elapsedTime
915
+
916
+ // timings is just for the final fetch
917
+ response.timings = self.timings
918
+
919
+ // pre-calculate phase timings as well
920
+ response.timingPhases = {
921
+ wait: self.timings.socket,
922
+ dns: self.timings.lookup - self.timings.socket,
923
+ tcp: self.timings.connect - self.timings.lookup,
924
+ firstByte: self.timings.response - self.timings.connect,
925
+ download: self.timings.end - self.timings.response,
926
+ total: self.timings.end
927
+ }
928
+ }
929
+ debug('response end', self.uri.href, response.statusCode, response.headers)
930
+ })
931
+
932
+ if (self._aborted) {
933
+ debug('aborted', self.uri.href)
934
+ response.resume()
935
+ return
936
+ }
937
+
938
+ self.response = response
939
+ response.request = self
940
+ response.toJSON = responseToJSON
941
+
942
+ // XXX This is different on 0.10, because SSL is strict by default
943
+ if (self.httpModule === https &&
944
+ self.strictSSL && (!response.hasOwnProperty('socket') ||
945
+ !response.socket.authorized)) {
946
+ debug('strict ssl error', self.uri.href)
947
+ var sslErr = response.hasOwnProperty('socket') ? response.socket.authorizationError : self.uri.href + ' does not support SSL'
948
+ self.emit('error', new Error('SSL Error: ' + sslErr))
949
+ return
950
+ }
951
+
952
+ // Save the original host before any redirect (if it changes, we need to
953
+ // remove any authorization headers). Also remember the case of the header
954
+ // name because lots of broken servers expect Host instead of host and we
955
+ // want the caller to be able to specify this.
956
+ self.originalHost = self.getHeader('host')
957
+ if (!self.originalHostHeaderName) {
958
+ self.originalHostHeaderName = self.hasHeader('host')
959
+ }
960
+ if (self.setHost) {
961
+ self.removeHeader('host')
962
+ }
963
+ self.clearTimeout()
964
+
965
+ var targetCookieJar = (self._jar && self._jar.setCookie) ? self._jar : globalCookieJar
966
+ var addCookie = function (cookie) {
967
+ // set the cookie if it's domain in the href's domain.
968
+ try {
969
+ targetCookieJar.setCookie(cookie, self.uri.href, {ignoreError: true})
970
+ } catch (e) {
971
+ self.emit('error', e)
972
+ }
973
+ }
974
+
975
+ response.caseless = caseless(response.headers)
976
+
977
+ if (response.caseless.has('set-cookie') && (!self._disableCookies)) {
978
+ var headerName = response.caseless.has('set-cookie')
979
+ if (Array.isArray(response.headers[headerName])) {
980
+ response.headers[headerName].forEach(addCookie)
981
+ } else {
982
+ addCookie(response.headers[headerName])
983
+ }
984
+ }
985
+
986
+ if (self._redirect.onResponse(response)) {
987
+ return // Ignore the rest of the response
988
+ } else {
989
+ // Be a good stream and emit end when the response is finished.
990
+ // Hack to emit end on close because of a core bug that never fires end
991
+ response.on('close', function () {
992
+ if (!self._ended) {
993
+ self.response.emit('end')
994
+ }
995
+ })
996
+
997
+ response.once('end', function () {
998
+ self._ended = true
999
+ })
1000
+
1001
+ var noBody = function (code) {
1002
+ return (
1003
+ self.method === 'HEAD' ||
1004
+ // Informational
1005
+ (code >= 100 && code < 200) ||
1006
+ // No Content
1007
+ code === 204 ||
1008
+ // Not Modified
1009
+ code === 304
1010
+ )
1011
+ }
1012
+
1013
+ var responseContent
1014
+ if (self.gzip && !noBody(response.statusCode)) {
1015
+ var contentEncoding = response.headers['content-encoding'] || 'identity'
1016
+ contentEncoding = contentEncoding.trim().toLowerCase()
1017
+
1018
+ // Be more lenient with decoding compressed responses, since (very rarely)
1019
+ // servers send slightly invalid gzip responses that are still accepted
1020
+ // by common browsers.
1021
+ // Always using Z_SYNC_FLUSH is what cURL does.
1022
+ var zlibOptions = {
1023
+ flush: zlib.Z_SYNC_FLUSH,
1024
+ finishFlush: zlib.Z_SYNC_FLUSH
1025
+ }
1026
+
1027
+ if (contentEncoding === 'gzip') {
1028
+ responseContent = zlib.createGunzip(zlibOptions)
1029
+ response.pipe(responseContent)
1030
+ } else if (contentEncoding === 'deflate') {
1031
+ responseContent = zlib.createInflate(zlibOptions)
1032
+ response.pipe(responseContent)
1033
+ } else {
1034
+ // Since previous versions didn't check for Content-Encoding header,
1035
+ // ignore any invalid values to preserve backwards-compatibility
1036
+ if (contentEncoding !== 'identity') {
1037
+ debug('ignoring unrecognized Content-Encoding ' + contentEncoding)
1038
+ }
1039
+ responseContent = response
1040
+ }
1041
+ } else {
1042
+ responseContent = response
1043
+ }
1044
+
1045
+ if (self.encoding) {
1046
+ if (self.dests.length !== 0) {
1047
+ console.error('Ignoring encoding parameter as this stream is being piped to another stream which makes the encoding option invalid.')
1048
+ } else {
1049
+ responseContent.setEncoding(self.encoding)
1050
+ }
1051
+ }
1052
+
1053
+ if (self._paused) {
1054
+ responseContent.pause()
1055
+ }
1056
+
1057
+ self.responseContent = responseContent
1058
+
1059
+ self.emit('response', response)
1060
+
1061
+ self.dests.forEach(function (dest) {
1062
+ self.pipeDest(dest)
1063
+ })
1064
+
1065
+ responseContent.on('data', function (chunk) {
1066
+ if (self.timing && !self.responseStarted) {
1067
+ self.responseStartTime = (new Date()).getTime()
1068
+
1069
+ // NOTE: responseStartTime is deprecated in favor of .timings
1070
+ response.responseStartTime = self.responseStartTime
1071
+ }
1072
+ self._destdata = true
1073
+ self.emit('data', chunk)
1074
+ })
1075
+ responseContent.once('end', function (chunk) {
1076
+ self.emit('end', chunk)
1077
+ })
1078
+ responseContent.on('error', function (error) {
1079
+ self.emit('error', error)
1080
+ })
1081
+ responseContent.on('close', function () { self.emit('close') })
1082
+
1083
+ if (self.callback) {
1084
+ self.readResponseBody(response)
1085
+ } else { // if no callback
1086
+ self.on('end', function () {
1087
+ if (self._aborted) {
1088
+ debug('aborted', self.uri.href)
1089
+ return
1090
+ }
1091
+ self.emit('complete', response)
1092
+ })
1093
+ }
1094
+ }
1095
+ debug('finish init function', self.uri.href)
1096
+ }
1097
+
1098
+ Request.prototype.readResponseBody = function (response) {
1099
+ var self = this
1100
+ debug("reading response's body")
1101
+ var buffers = []
1102
+ var bufferLength = 0
1103
+ var strings = []
1104
+
1105
+ self.on('data', function (chunk) {
1106
+ if (!Buffer.isBuffer(chunk)) {
1107
+ strings.push(chunk)
1108
+ } else if (chunk.length) {
1109
+ bufferLength += chunk.length
1110
+ buffers.push(chunk)
1111
+ }
1112
+ })
1113
+ self.on('end', function () {
1114
+ debug('end event', self.uri.href)
1115
+ if (self._aborted) {
1116
+ debug('aborted', self.uri.href)
1117
+ // `buffer` is defined in the parent scope and used in a closure it exists for the life of the request.
1118
+ // This can lead to leaky behavior if the user retains a reference to the request object.
1119
+ buffers = []
1120
+ bufferLength = 0
1121
+ return
1122
+ }
1123
+
1124
+ if (bufferLength) {
1125
+ debug('has body', self.uri.href, bufferLength)
1126
+ response.body = Buffer.concat(buffers, bufferLength)
1127
+ if (self.encoding !== null) {
1128
+ response.body = response.body.toString(self.encoding)
1129
+ }
1130
+ // `buffer` is defined in the parent scope and used in a closure it exists for the life of the Request.
1131
+ // This can lead to leaky behavior if the user retains a reference to the request object.
1132
+ buffers = []
1133
+ bufferLength = 0
1134
+ } else if (strings.length) {
1135
+ // The UTF8 BOM [0xEF,0xBB,0xBF] is converted to [0xFE,0xFF] in the JS UTC16/UCS2 representation.
1136
+ // Strip this value out when the encoding is set to 'utf8', as upstream consumers won't expect it and it breaks JSON.parse().
1137
+ if (self.encoding === 'utf8' && strings[0].length > 0 && strings[0][0] === '\uFEFF') {
1138
+ strings[0] = strings[0].substring(1)
1139
+ }
1140
+ response.body = strings.join('')
1141
+ }
1142
+
1143
+ if (self._json) {
1144
+ try {
1145
+ response.body = JSON.parse(response.body, self._jsonReviver)
1146
+ } catch (e) {
1147
+ debug('invalid JSON received', self.uri.href)
1148
+ }
1149
+ }
1150
+ debug('emitting complete', self.uri.href)
1151
+ if (typeof response.body === 'undefined' && !self._json) {
1152
+ response.body = self.encoding === null ? Buffer.alloc(0) : ''
1153
+ }
1154
+ self.emit('complete', response, response.body)
1155
+ })
1156
+ }
1157
+
1158
+ Request.prototype.abort = function () {
1159
+ var self = this
1160
+ self._aborted = true
1161
+
1162
+ if (self.req) {
1163
+ self.req.abort()
1164
+ } else if (self.response) {
1165
+ self.response.destroy()
1166
+ }
1167
+
1168
+ self.clearTimeout()
1169
+ self.emit('abort')
1170
+ }
1171
+
1172
+ Request.prototype.pipeDest = function (dest) {
1173
+ var self = this
1174
+ var response = self.response
1175
+ // Called after the response is received
1176
+ if (dest.headers && !dest.headersSent) {
1177
+ if (response.caseless.has('content-type')) {
1178
+ var ctname = response.caseless.has('content-type')
1179
+ if (dest.setHeader) {
1180
+ dest.setHeader(ctname, response.headers[ctname])
1181
+ } else {
1182
+ dest.headers[ctname] = response.headers[ctname]
1183
+ }
1184
+ }
1185
+
1186
+ if (response.caseless.has('content-length')) {
1187
+ var clname = response.caseless.has('content-length')
1188
+ if (dest.setHeader) {
1189
+ dest.setHeader(clname, response.headers[clname])
1190
+ } else {
1191
+ dest.headers[clname] = response.headers[clname]
1192
+ }
1193
+ }
1194
+ }
1195
+ if (dest.setHeader && !dest.headersSent) {
1196
+ for (var i in response.headers) {
1197
+ // If the response content is being decoded, the Content-Encoding header
1198
+ // of the response doesn't represent the piped content, so don't pass it.
1199
+ if (!self.gzip || i !== 'content-encoding') {
1200
+ dest.setHeader(i, response.headers[i])
1201
+ }
1202
+ }
1203
+ dest.statusCode = response.statusCode
1204
+ }
1205
+ if (self.pipefilter) {
1206
+ self.pipefilter(response, dest)
1207
+ }
1208
+ }
1209
+
1210
+ Request.prototype.qs = function (q, clobber) {
1211
+ var self = this
1212
+ var base
1213
+ if (!clobber && self.uri.query) {
1214
+ base = self._qs.parse(self.uri.query)
1215
+ } else {
1216
+ base = {}
1217
+ }
1218
+
1219
+ for (var i in q) {
1220
+ base[i] = q[i]
1221
+ }
1222
+
1223
+ var qs = self._qs.stringify(base)
1224
+
1225
+ if (qs === '') {
1226
+ return self
1227
+ }
1228
+
1229
+ self.uri = url.parse(self.uri.href.split('?')[0] + '?' + qs)
1230
+ self.url = self.uri
1231
+ self.path = self.uri.path
1232
+
1233
+ if (self.uri.host === 'unix') {
1234
+ self.enableUnixSocket()
1235
+ }
1236
+
1237
+ return self
1238
+ }
1239
+ Request.prototype.form = function (form) {
1240
+ var self = this
1241
+ if (form) {
1242
+ if (!/^application\/x-www-form-urlencoded\b/.test(self.getHeader('content-type'))) {
1243
+ self.setHeader('content-type', 'application/x-www-form-urlencoded')
1244
+ }
1245
+ self.body = (typeof form === 'string')
1246
+ ? self._qs.rfc3986(form.toString('utf8'))
1247
+ : self._qs.stringify(form).toString('utf8')
1248
+ return self
1249
+ }
1250
+ // create form-data object
1251
+ self._form = new FormData()
1252
+ self._form.on('error', function (err) {
1253
+ err.message = 'form-data: ' + err.message
1254
+ self.emit('error', err)
1255
+ self.abort()
1256
+ })
1257
+ return self._form
1258
+ }
1259
+ Request.prototype.multipart = function (multipart) {
1260
+ var self = this
1261
+
1262
+ self._multipart.onRequest(multipart)
1263
+
1264
+ if (!self._multipart.chunked) {
1265
+ self.body = self._multipart.body
1266
+ }
1267
+
1268
+ return self
1269
+ }
1270
+ Request.prototype.json = function (val) {
1271
+ var self = this
1272
+
1273
+ if (!self.hasHeader('accept')) {
1274
+ self.setHeader('accept', 'application/json')
1275
+ }
1276
+
1277
+ if (typeof self.jsonReplacer === 'function') {
1278
+ self._jsonReplacer = self.jsonReplacer
1279
+ }
1280
+
1281
+ self._json = true
1282
+ if (typeof val === 'boolean') {
1283
+ if (self.body !== undefined) {
1284
+ if (!/^application\/x-www-form-urlencoded\b/.test(self.getHeader('content-type'))) {
1285
+ self.body = safeStringify(self.body, self._jsonReplacer)
1286
+ } else {
1287
+ self.body = self._qs.rfc3986(self.body)
1288
+ }
1289
+ if (!self.hasHeader('content-type')) {
1290
+ self.setHeader('content-type', 'application/json')
1291
+ }
1292
+ }
1293
+ } else {
1294
+ self.body = safeStringify(val, self._jsonReplacer)
1295
+ if (!self.hasHeader('content-type')) {
1296
+ self.setHeader('content-type', 'application/json')
1297
+ }
1298
+ }
1299
+
1300
+ if (typeof self.jsonReviver === 'function') {
1301
+ self._jsonReviver = self.jsonReviver
1302
+ }
1303
+
1304
+ return self
1305
+ }
1306
+ Request.prototype.getHeader = function (name, headers) {
1307
+ var self = this
1308
+ var result, re, match
1309
+ if (!headers) {
1310
+ headers = self.headers
1311
+ }
1312
+ Object.keys(headers).forEach(function (key) {
1313
+ if (key.length !== name.length) {
1314
+ return
1315
+ }
1316
+ re = new RegExp(name, 'i')
1317
+ match = key.match(re)
1318
+ if (match) {
1319
+ result = headers[key]
1320
+ }
1321
+ })
1322
+ return result
1323
+ }
1324
+ Request.prototype.enableUnixSocket = function () {
1325
+ // Get the socket & request paths from the URL
1326
+ var unixParts = this.uri.path.split(':')
1327
+ var host = unixParts[0]
1328
+ var path = unixParts[1]
1329
+ // Apply unix properties to request
1330
+ this.socketPath = host
1331
+ this.uri.pathname = path
1332
+ this.uri.path = path
1333
+ this.uri.host = host
1334
+ this.uri.hostname = host
1335
+ this.uri.isUnix = true
1336
+ }
1337
+
1338
+ Request.prototype.auth = function (user, pass, sendImmediately, bearer) {
1339
+ var self = this
1340
+
1341
+ self._auth.onRequest(user, pass, sendImmediately, bearer)
1342
+
1343
+ return self
1344
+ }
1345
+ Request.prototype.aws = function (opts, now) {
1346
+ var self = this
1347
+
1348
+ if (!now) {
1349
+ self._aws = opts
1350
+ return self
1351
+ }
1352
+
1353
+ if (opts.sign_version === 4 || opts.sign_version === '4') {
1354
+ // use aws4
1355
+ var options = {
1356
+ host: self.uri.host,
1357
+ path: self.uri.path,
1358
+ method: self.method,
1359
+ headers: self.headers,
1360
+ body: self.body
1361
+ }
1362
+ if (opts.service) {
1363
+ options.service = opts.service
1364
+ }
1365
+ var signRes = aws4.sign(options, {
1366
+ accessKeyId: opts.key,
1367
+ secretAccessKey: opts.secret,
1368
+ sessionToken: opts.session
1369
+ })
1370
+ self.setHeader('authorization', signRes.headers.Authorization)
1371
+ self.setHeader('x-amz-date', signRes.headers['X-Amz-Date'])
1372
+ if (signRes.headers['X-Amz-Security-Token']) {
1373
+ self.setHeader('x-amz-security-token', signRes.headers['X-Amz-Security-Token'])
1374
+ }
1375
+ } else {
1376
+ // default: use aws-sign2
1377
+ var date = new Date()
1378
+ self.setHeader('date', date.toUTCString())
1379
+ var auth = {
1380
+ key: opts.key,
1381
+ secret: opts.secret,
1382
+ verb: self.method.toUpperCase(),
1383
+ date: date,
1384
+ contentType: self.getHeader('content-type') || '',
1385
+ md5: self.getHeader('content-md5') || '',
1386
+ amazonHeaders: aws2.canonicalizeHeaders(self.headers)
1387
+ }
1388
+ var path = self.uri.path
1389
+ if (opts.bucket && path) {
1390
+ auth.resource = '/' + opts.bucket + path
1391
+ } else if (opts.bucket && !path) {
1392
+ auth.resource = '/' + opts.bucket
1393
+ } else if (!opts.bucket && path) {
1394
+ auth.resource = path
1395
+ } else if (!opts.bucket && !path) {
1396
+ auth.resource = '/'
1397
+ }
1398
+ auth.resource = aws2.canonicalizeResource(auth.resource)
1399
+ self.setHeader('authorization', aws2.authorization(auth))
1400
+ }
1401
+
1402
+ return self
1403
+ }
1404
+ Request.prototype.httpSignature = function (opts) {
1405
+ var self = this
1406
+ httpSignature.signRequest({
1407
+ getHeader: function (header) {
1408
+ return self.getHeader(header, self.headers)
1409
+ },
1410
+ setHeader: function (header, value) {
1411
+ self.setHeader(header, value)
1412
+ },
1413
+ method: self.method,
1414
+ path: self.path
1415
+ }, opts)
1416
+ debug('httpSignature authorization', self.getHeader('authorization'))
1417
+
1418
+ return self
1419
+ }
1420
+ Request.prototype.hawk = function (opts) {
1421
+ var self = this
1422
+ self.setHeader('Authorization', hawk.header(self.uri, self.method, opts))
1423
+ }
1424
+ Request.prototype.oauth = function (_oauth) {
1425
+ var self = this
1426
+
1427
+ self._oauth.onRequest(_oauth)
1428
+
1429
+ return self
1430
+ }
1431
+
1432
+ Request.prototype.jar = function (jar) {
1433
+ var self = this
1434
+ var cookies
1435
+
1436
+ if (self._redirect.redirectsFollowed === 0) {
1437
+ self.originalCookieHeader = self.getHeader('cookie')
1438
+ }
1439
+
1440
+ if (!jar) {
1441
+ // disable cookies
1442
+ cookies = false
1443
+ self._disableCookies = true
1444
+ } else {
1445
+ var targetCookieJar = jar.getCookieString ? jar : globalCookieJar
1446
+ var urihref = self.uri.href
1447
+ // fetch cookie in the Specified host
1448
+ if (targetCookieJar) {
1449
+ cookies = targetCookieJar.getCookieString(urihref)
1450
+ }
1451
+ }
1452
+
1453
+ // if need cookie and cookie is not empty
1454
+ if (cookies && cookies.length) {
1455
+ if (self.originalCookieHeader) {
1456
+ // Don't overwrite existing Cookie header
1457
+ self.setHeader('cookie', self.originalCookieHeader + '; ' + cookies)
1458
+ } else {
1459
+ self.setHeader('cookie', cookies)
1460
+ }
1461
+ }
1462
+ self._jar = jar
1463
+ return self
1464
+ }
1465
+
1466
+ // Stream API
1467
+ Request.prototype.pipe = function (dest, opts) {
1468
+ var self = this
1469
+
1470
+ if (self.response) {
1471
+ if (self._destdata) {
1472
+ self.emit('error', new Error('You cannot pipe after data has been emitted from the response.'))
1473
+ } else if (self._ended) {
1474
+ self.emit('error', new Error('You cannot pipe after the response has been ended.'))
1475
+ } else {
1476
+ stream.Stream.prototype.pipe.call(self, dest, opts)
1477
+ self.pipeDest(dest)
1478
+ return dest
1479
+ }
1480
+ } else {
1481
+ self.dests.push(dest)
1482
+ stream.Stream.prototype.pipe.call(self, dest, opts)
1483
+ return dest
1484
+ }
1485
+ }
1486
+ Request.prototype.write = function () {
1487
+ var self = this
1488
+ if (self._aborted) { return }
1489
+
1490
+ if (!self._started) {
1491
+ self.start()
1492
+ }
1493
+ if (self.req) {
1494
+ return self.req.write.apply(self.req, arguments)
1495
+ }
1496
+ }
1497
+ Request.prototype.end = function (chunk) {
1498
+ var self = this
1499
+ if (self._aborted) { return }
1500
+
1501
+ if (chunk) {
1502
+ self.write(chunk)
1503
+ }
1504
+ if (!self._started) {
1505
+ self.start()
1506
+ }
1507
+ if (self.req) {
1508
+ self.req.end()
1509
+ }
1510
+ }
1511
+ Request.prototype.pause = function () {
1512
+ var self = this
1513
+ if (!self.responseContent) {
1514
+ self._paused = true
1515
+ } else {
1516
+ self.responseContent.pause.apply(self.responseContent, arguments)
1517
+ }
1518
+ }
1519
+ Request.prototype.resume = function () {
1520
+ var self = this
1521
+ if (!self.responseContent) {
1522
+ self._paused = false
1523
+ } else {
1524
+ self.responseContent.resume.apply(self.responseContent, arguments)
1525
+ }
1526
+ }
1527
+ Request.prototype.destroy = function () {
1528
+ var self = this
1529
+ this.clearTimeout()
1530
+ if (!self._ended) {
1531
+ self.end()
1532
+ } else if (self.response) {
1533
+ self.response.destroy()
1534
+ }
1535
+ }
1536
+
1537
+ Request.prototype.clearTimeout = function () {
1538
+ if (this.timeoutTimer) {
1539
+ clearTimeout(this.timeoutTimer)
1540
+ this.timeoutTimer = null
1541
+ }
1542
+ }
1543
+
1544
+ Request.defaultProxyHeaderWhiteList =
1545
+ Tunnel.defaultProxyHeaderWhiteList.slice()
1546
+
1547
+ Request.defaultProxyHeaderExclusiveList =
1548
+ Tunnel.defaultProxyHeaderExclusiveList.slice()
1549
+
1550
+ // Exports
1551
+
1552
+ Request.prototype.toJSON = requestToJSON
1553
+ module.exports = Request