@datanimbus/postman-request 3.0.1

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,2098 @@
1
+ 'use strict'
2
+
3
+ var tls = require('tls')
4
+ var http = require('http')
5
+ var https = require('https')
6
+ var fsPromise = require('fs/promises')
7
+ var events = require('events')
8
+ var http2 = require('./lib/http2')
9
+ var autohttp2 = require('./lib/autohttp')
10
+ var url = require('url')
11
+ var util = require('util')
12
+ var stream = require('stream')
13
+ var zlib = require('zlib')
14
+ var aws2 = require('aws-sign2')
15
+ var aws4 = require('aws4')
16
+ var uuid = require('uuid').v4
17
+ var httpSignature = require('http-signature')
18
+ var mime = require('mime-types')
19
+ var caseless = require('caseless')
20
+ var ForeverAgent = require('forever-agent')
21
+ var FormData = require('@postman/form-data')
22
+ var extend = require('extend')
23
+ var isstream = require('isstream')
24
+ var streamLength = require('stream-length')
25
+ var isTypedArray = require('is-typedarray').strict
26
+ var helpers = require('./lib/helpers')
27
+ var cookies = require('./lib/cookies')
28
+ var getProxyFromURI = require('./lib/getProxyFromURI')
29
+ var Querystring = require('./lib/querystring').Querystring
30
+ var Har = require('./lib/har').Har
31
+ var Auth = require('./lib/auth').Auth
32
+ var OAuth = require('./lib/oauth').OAuth
33
+ var hawk = require('./lib/hawk')
34
+ var Multipart = require('./lib/multipart').Multipart
35
+ var Redirect = require('./lib/redirect').Redirect
36
+ var Tunnel = require('./lib/tunnel').Tunnel
37
+ var SocksProxy = require('./lib/socks').SocksProxy
38
+ var Buffer = require('safe-buffer').Buffer
39
+ var inflate = require('./lib/inflate')
40
+ var urlParse = require('./lib/url-parse')
41
+ var safeStringify = helpers.safeStringify
42
+ var isReadStream = helpers.isReadStream
43
+ var toBase64 = helpers.toBase64
44
+ var defer = helpers.defer
45
+ var copy = helpers.copy
46
+ var version = helpers.version
47
+ var now = helpers.now
48
+ var SizeTrackerStream = helpers.SizeTrackerStream
49
+ var globalCookieJar = cookies.jar()
50
+
51
+ var globalPool = {}
52
+
53
+ function filterForNonReserved (reserved, options) {
54
+ // Filter out properties that are not reserved.
55
+ // Reserved values are passed in at call site.
56
+
57
+ var object = {}
58
+ for (var i in options) {
59
+ var notReserved = (reserved.indexOf(i) === -1)
60
+ if (notReserved) {
61
+ object[i] = options[i]
62
+ }
63
+ }
64
+ return object
65
+ }
66
+
67
+ function filterOutReservedFunctions (reserved, options) {
68
+ // Filter out properties that are functions and are reserved.
69
+ // Reserved values are passed in at call site.
70
+
71
+ var object = {}
72
+ for (var i in options) {
73
+ var isReserved = !(reserved.indexOf(i) === -1)
74
+ var isFunction = (typeof options[i] === 'function')
75
+ if (!(isReserved && isFunction)) {
76
+ object[i] = options[i]
77
+ }
78
+ }
79
+ return object
80
+ }
81
+
82
+ function transformFormData (formData) {
83
+ // Transform the object representation of form-data fields to array representation.
84
+ // This might not preserve the order of form fields defined in object representation.
85
+ // But, this transformation is required to support backward compatibility.
86
+ //
87
+ // Form-Data should be stored as an array to respect the fields order.
88
+ // RFC 7578#section-5.2 Ordered Fields and Duplicated Field Names
89
+ // https://tools.ietf.org/html/rfc7578#section-5.2
90
+
91
+ var transformedFormData = []
92
+ var appendFormParam = function (key, param) {
93
+ transformedFormData.push({
94
+ key: key,
95
+ value: param && param.hasOwnProperty('value') ? param.value : param,
96
+ options: param && param.hasOwnProperty('options') ? param.options : undefined
97
+ })
98
+ }
99
+ for (var formKey in formData) {
100
+ if (formData.hasOwnProperty(formKey)) {
101
+ var formValue = formData[formKey]
102
+ if (Array.isArray(formValue)) {
103
+ for (var j = 0; j < formValue.length; j++) {
104
+ appendFormParam(formKey, formValue[j])
105
+ }
106
+ } else {
107
+ appendFormParam(formKey, formValue)
108
+ }
109
+ }
110
+ }
111
+ return transformedFormData
112
+ }
113
+
114
+ // Return a simpler request object to allow serialization
115
+ function requestToJSON () {
116
+ var self = this
117
+ return {
118
+ uri: self.uri,
119
+ method: self.method,
120
+ headers: self.headers
121
+ }
122
+ }
123
+
124
+ // Return a simpler response object to allow serialization
125
+ function responseToJSON () {
126
+ var self = this
127
+ return {
128
+ statusCode: self.statusCode,
129
+ body: self.body,
130
+ headers: self.headers,
131
+ request: requestToJSON.call(self.request)
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Return request headers in [{key: headerName, value: headerValue}] form
137
+ * @param {String} [headerString] - headers string created by Node stored in ClientRequest._header
138
+ *
139
+ * */
140
+ function parseRequestHeaders (headerString) {
141
+ var arr = headerString.split('\r\n')
142
+ var acc = []
143
+
144
+ // first element of accumulator is not a header
145
+ // last two elements are empty strings
146
+ for (var i = 1; i < arr.length - 2; i++) {
147
+ // HTTP/2 specific headers beging with :, so we find the index of the first colon skipping the first character
148
+ var splitIndex = arr[i].indexOf(':', 1)
149
+
150
+ acc.push({
151
+ key: arr[i].slice(0, splitIndex),
152
+ value: arr[i].slice(splitIndex + 2)
153
+ })
154
+ }
155
+
156
+ return acc
157
+ }
158
+
159
+ /**
160
+ * Return response headers in [{key: headerName, value: headerValue}] form
161
+ * @param {Array} [rawHeaders] - https://nodejs.org/api/http.html#http_message_rawheaders
162
+ *
163
+ * */
164
+ function parseResponseHeaders (rawHeaders) {
165
+ var acc = []
166
+
167
+ for (var i = 0; i < rawHeaders.length; i = i + 2) {
168
+ acc.push({
169
+ key: rawHeaders[i],
170
+ value: rawHeaders[i + 1]
171
+ })
172
+ }
173
+
174
+ return acc
175
+ }
176
+
177
+ function Request (options) {
178
+ // if given the method property in options, set property explicitMethod to true
179
+
180
+ // extend the Request instance with any non-reserved properties
181
+ // remove any reserved functions from the options object
182
+ // set Request instance to be readable and writable
183
+ // call init
184
+
185
+ var self = this
186
+
187
+ // start with HAR, then override with additional options
188
+ if (options.har) {
189
+ self._har = new Har(self)
190
+ options = self._har.options(options)
191
+ }
192
+
193
+ // transform `formData` for backward compatibility
194
+ // don't check for explicit object type to support legacy shenanigans
195
+ if (options.formData && !Array.isArray(options.formData)) {
196
+ options.formData = transformFormData(options.formData)
197
+ }
198
+
199
+ // use custom URL parser if provided, fallback to url.parse and url.resolve
200
+ if (!(
201
+ options.urlParser &&
202
+ typeof options.urlParser.parse === 'function' &&
203
+ typeof options.urlParser.resolve === 'function'
204
+ )) {
205
+ options.urlParser = {
206
+ parse: url.parse,
207
+ resolve: url.resolve
208
+ }
209
+ }
210
+
211
+ stream.Stream.call(self)
212
+ var reserved = Object.keys(Request.prototype)
213
+ var nonReserved = filterForNonReserved(reserved, options)
214
+
215
+ extend(self, nonReserved)
216
+ options = filterOutReservedFunctions(reserved, options)
217
+
218
+ self.readable = true
219
+ self.writable = true
220
+ self._debug = []
221
+ if (options.method) {
222
+ self.explicitMethod = true
223
+ }
224
+ self._qs = new Querystring(self)
225
+ self._auth = new Auth(self)
226
+ self._oauth = new OAuth(self)
227
+ self._multipart = new Multipart(self)
228
+ self._redirect = new Redirect(self)
229
+ self._tunnel = new Tunnel(self)
230
+ self._socks = new SocksProxy(self)
231
+ self.init(options)
232
+ }
233
+
234
+ util.inherits(Request, stream.Stream)
235
+
236
+ // Debugging
237
+ Request.debug = process.env.NODE_DEBUG && /\brequest\b/.test(process.env.NODE_DEBUG)
238
+
239
+ function debug () {
240
+ if (Request.debug) {
241
+ console.error('REQUEST %s', util.format.apply(util, arguments))
242
+ }
243
+ }
244
+
245
+ Request.prototype.debug = debug
246
+
247
+ Request.prototype.init = function (options) {
248
+ // init() contains all the code to setup the request object.
249
+ // the actual outgoing request is not started until start() is called
250
+ // this function is called from both the constructor and on redirect.
251
+ var self = this
252
+ if (!options) {
253
+ options = {}
254
+ }
255
+ self.headers = self.headers ? copy(self.headers) : {}
256
+
257
+ // for this request (or redirect) store its debug logs in `_reqResInfo` and
258
+ // store its reference in `_debug` which holds debug logs of every request
259
+ self._reqResInfo = {}
260
+ self._debug.push(self._reqResInfo)
261
+
262
+ // additional postman feature starts
263
+ // bind default events sent via options
264
+ if (options.bindOn) {
265
+ Object.keys(options.bindOn).forEach(function (eventName) {
266
+ !Array.isArray(options.bindOn[eventName]) && (options.bindOn[eventName] = [options.bindOn[eventName]])
267
+ options.bindOn[eventName].forEach(function (listener) {
268
+ self.on(eventName, listener)
269
+ })
270
+ })
271
+ }
272
+ if (options.once) {
273
+ Object.keys(options.once).forEach(function (eventName) {
274
+ !Array.isArray(options.bindOnce[eventName]) && (options.bindOnce[eventName] = [options.bindOnce[eventName]])
275
+ options.bindOnce[eventName].forEach(function (listener) {
276
+ self.once(eventName, listener)
277
+ })
278
+ })
279
+ }
280
+ // additional postman feature ends
281
+
282
+ // Delete headers with value undefined or HTTP/2 specific pseudoheaders since they break
283
+ // ClientRequest.OutgoingMessage.setHeader in node 0.12
284
+ for (var headerName in self.headers) {
285
+ if (typeof self.headers[headerName] === 'undefined' || headerName.startsWith(':')) {
286
+ delete self.headers[headerName]
287
+ }
288
+ }
289
+
290
+ caseless.httpify(self, self.headers)
291
+
292
+ if (!self.method) {
293
+ self.method = options.method || 'GET'
294
+ }
295
+ if (!self.localAddress) {
296
+ self.localAddress = options.localAddress
297
+ }
298
+
299
+ self._qs.init(options)
300
+
301
+ debug(options)
302
+ if (!self.pool && self.pool !== false) {
303
+ self.pool = globalPool
304
+ }
305
+ self.dests = self.dests || []
306
+ self.__isRequestRequest = true
307
+
308
+ // Protect against double callback
309
+ if (!self._callback && self.callback) {
310
+ self._callback = self.callback
311
+ self.callback = function (error, response, body) {
312
+ if (self._callbackCalled) {
313
+ return // Print a warning maybe?
314
+ }
315
+ self._callbackCalled = true
316
+ self._callback(error, response, body, self._debug)
317
+ }
318
+ self.on('error', self.callback.bind())
319
+ self.on('complete', self.callback.bind(self, null))
320
+ }
321
+
322
+ // People use this property instead all the time, so support it
323
+ if (!self.uri && self.url) {
324
+ self.uri = self.url
325
+ delete self.url
326
+ }
327
+
328
+ // If there's a baseUrl, then use it as the base URL (i.e. uri must be
329
+ // specified as a relative path and is appended to baseUrl).
330
+ if (self.baseUrl) {
331
+ if (typeof self.baseUrl !== 'string') {
332
+ return self.emit('error', new Error('options.baseUrl must be a string'))
333
+ }
334
+
335
+ if (typeof self.uri !== 'string') {
336
+ return self.emit('error', new Error('options.uri must be a string when using options.baseUrl'))
337
+ }
338
+
339
+ if (self.uri.indexOf('//') === 0 || self.uri.indexOf('://') !== -1) {
340
+ return self.emit('error', new Error('options.uri must be a path when using options.baseUrl'))
341
+ }
342
+
343
+ // Handle all cases to make sure that there's only one slash between
344
+ // baseUrl and uri.
345
+ var baseUrlEndsWithSlash = self.baseUrl.lastIndexOf('/') === self.baseUrl.length - 1
346
+ var uriStartsWithSlash = self.uri.indexOf('/') === 0
347
+
348
+ if (baseUrlEndsWithSlash && uriStartsWithSlash) {
349
+ self.uri = self.baseUrl + self.uri.slice(1)
350
+ } else if (baseUrlEndsWithSlash || uriStartsWithSlash) {
351
+ self.uri = self.baseUrl + self.uri
352
+ } else if (self.uri === '') {
353
+ self.uri = self.baseUrl
354
+ } else {
355
+ self.uri = self.baseUrl + '/' + self.uri
356
+ }
357
+ delete self.baseUrl
358
+ }
359
+
360
+ // A URI is needed by this point, emit error if we haven't been able to get one
361
+ if (!self.uri) {
362
+ return self.emit('error', new Error('options.uri is a required argument'))
363
+ }
364
+
365
+ // If a string URI/URL was given, parse it into a URL object
366
+ if (typeof self.uri === 'string') {
367
+ self.uri = self.urlParser.parse(self.uri)
368
+ }
369
+
370
+ // Some URL objects are not from a URL parsed string and need href added
371
+ if (!self.uri.href) {
372
+ self.uri.href = url.format(self.uri)
373
+ }
374
+
375
+ // DEPRECATED: Warning for users of the old Unix Sockets URL Scheme
376
+ if (self.uri.protocol === 'unix:') {
377
+ return self.emit('error', new Error('`unix://` URL scheme is no longer supported. Please use the format `http://unix:SOCKET:PATH`'))
378
+ }
379
+
380
+ // Support Unix Sockets
381
+ if (self.uri.host === 'unix' || self.uri.host === 'unix:') {
382
+ self.enableUnixSocket()
383
+ }
384
+
385
+ if (self.strictSSL === false) {
386
+ self.rejectUnauthorized = false
387
+ }
388
+
389
+ if (!self.uri.pathname) { self.uri.pathname = '/' }
390
+
391
+ if (!(self.uri.host || (self.uri.hostname && self.uri.port)) && !self.uri.isUnix) {
392
+ // Invalid URI: it may generate lot of bad errors, like 'TypeError: Cannot call method `indexOf` of undefined' in CookieJar
393
+ // Detect and reject it as soon as possible
394
+ var faultyUri = url.format(self.uri)
395
+ var message = 'Invalid URI "' + faultyUri + '"'
396
+ if (Object.keys(options).length === 0) {
397
+ // No option ? This can be the sign of a redirect
398
+ // As this is a case where the user cannot do anything (they didn't call request directly with this URL)
399
+ // they should be warned that it can be caused by a redirection (can save some hair)
400
+ message += '. This can be caused by a crappy redirection.'
401
+ }
402
+ // This error was fatal
403
+ self.abort()
404
+ return self.emit('error', new Error(message))
405
+ }
406
+
407
+ if (!self.hasOwnProperty('proxy')) {
408
+ self.proxy = getProxyFromURI(self.uri)
409
+ }
410
+
411
+ if (typeof self.proxy === 'string') {
412
+ self.proxy = self.urlParser.parse(self.proxy)
413
+
414
+ if (self.proxy.auth) {
415
+ self.proxy.auth = self._qs.unescape(self.proxy.auth)
416
+ }
417
+ }
418
+
419
+ self.tunnel = self._tunnel.isEnabled()
420
+ self.socks = self._socks.isEnabled()
421
+
422
+ if (self.proxy) {
423
+ if (self.socks) {
424
+ self._socks.setup()
425
+ } else {
426
+ self._tunnel.setup(options)
427
+ }
428
+ }
429
+
430
+ self._redirect.onRequest(options)
431
+
432
+ // Add `Host` header if not defined already
433
+ self.setHost = (self.setHost === undefined || Boolean(self.setHost))
434
+ if (!self.hasHeader('host') && self.setHost) {
435
+ var hostHeaderName = self.originalHostHeaderName || 'Host'
436
+ self.setHeader(hostHeaderName, self.uri.host)
437
+ // Drop :port suffix from Host header if known protocol.
438
+ if (self.uri.port) {
439
+ if ((self.uri.port === '80' && self.uri.protocol === 'http:') ||
440
+ (self.uri.port === '443' && self.uri.protocol === 'https:')) {
441
+ self.setHeader(hostHeaderName, self.uri.hostname)
442
+ }
443
+ }
444
+ }
445
+
446
+ if (!self.uri.port) {
447
+ if (self.uri.protocol === 'http:') { self.uri.port = 80 } else if (self.uri.protocol === 'https:') { self.uri.port = 443 }
448
+ }
449
+
450
+ if (self.proxy && !self.tunnel && !self.socks) {
451
+ self.port = self.proxy.port
452
+ self.host = self.proxy.hostname
453
+ } else {
454
+ self.port = self.uri.port
455
+ self.host = self.uri.hostname
456
+ }
457
+
458
+ if (options.form) {
459
+ self.form(options.form)
460
+ }
461
+
462
+ if (options.formData) {
463
+ var formData = options.formData
464
+ var requestForm = self.form()
465
+ for (var i = 0, ii = formData.length; i < ii; i++) {
466
+ var formParam = formData[i]
467
+ if (!formParam) { continue }
468
+ if (formParam.options) {
469
+ requestForm.append(formParam.key, formParam.value, formParam.options)
470
+ } else {
471
+ requestForm.append(formParam.key, formParam.value)
472
+ }
473
+ }
474
+ }
475
+
476
+ if (options.qs) {
477
+ self.qs(options.qs)
478
+ }
479
+
480
+ if (self.uri.path) {
481
+ self.path = self.uri.path
482
+ } else {
483
+ self.path = self.uri.pathname + (self.uri.search || '')
484
+ }
485
+
486
+ if (self.path.length === 0) {
487
+ self.path = '/'
488
+ }
489
+
490
+ // Auth must happen last in case signing is dependent on other headers
491
+ if (options.aws) {
492
+ self.aws(options.aws)
493
+ }
494
+
495
+ if (options.hawk) {
496
+ self.hawk(options.hawk)
497
+ }
498
+
499
+ if (options.httpSignature) {
500
+ self.httpSignature(options.httpSignature)
501
+ }
502
+
503
+ if (options.auth) {
504
+ if (Object.prototype.hasOwnProperty.call(options.auth, 'username')) {
505
+ options.auth.user = options.auth.username
506
+ }
507
+ if (Object.prototype.hasOwnProperty.call(options.auth, 'password')) {
508
+ options.auth.pass = options.auth.password
509
+ }
510
+
511
+ self.auth(
512
+ options.auth.user,
513
+ options.auth.pass,
514
+ options.auth.sendImmediately,
515
+ options.auth.bearer
516
+ )
517
+ }
518
+
519
+ if (!self.hasHeader('accept-encoding')) {
520
+ var acceptEncoding = ''
521
+
522
+ self.gzip && (acceptEncoding += 'gzip, deflate')
523
+
524
+ if (self.brotli) {
525
+ acceptEncoding && (acceptEncoding += ', ')
526
+ acceptEncoding += 'br'
527
+ }
528
+
529
+ acceptEncoding && self.setHeader('Accept-Encoding', acceptEncoding)
530
+ }
531
+
532
+ if (self.uri.auth && !self.hasHeader('authorization')) {
533
+ var uriAuthPieces = self.uri.auth.split(':').map(function (item) { return self._qs.unescape(item) })
534
+ self.auth(uriAuthPieces[0], uriAuthPieces.slice(1).join(':'), true)
535
+ }
536
+
537
+ if (!self.tunnel && !self.socks && self.proxy && self.proxy.auth && !self.hasHeader('proxy-authorization')) {
538
+ self.setHeader('Proxy-Authorization', 'Basic ' + toBase64(self.proxy.auth))
539
+ }
540
+
541
+ if (self.proxy && !self.tunnel && !self.socks) {
542
+ self.path = (self.uri.protocol + '//' + self.uri.host + self.path)
543
+ }
544
+
545
+ if (options.json) {
546
+ self.json(options.json)
547
+ }
548
+ if (options.multipart) {
549
+ self.multipart(options.multipart)
550
+ }
551
+
552
+ // enable timings if verbose is true
553
+ if (options.time || options.verbose) {
554
+ self.timing = true
555
+
556
+ // NOTE: elapsedTime is deprecated in favor of .timings
557
+ self.elapsedTime = self.elapsedTime || 0
558
+ }
559
+
560
+ if (options.verbose) {
561
+ self.verbose = true
562
+ }
563
+
564
+ if (typeof options.maxResponseSize === 'number') {
565
+ self.maxResponseSize = options.maxResponseSize
566
+ }
567
+
568
+ function setContentLength () {
569
+ if (isTypedArray(self.body)) {
570
+ self.body = Buffer.from(self.body)
571
+ }
572
+
573
+ if (!self.hasHeader('content-length')) {
574
+ var length
575
+ if (typeof self.body === 'string') {
576
+ length = Buffer.byteLength(self.body)
577
+ } else if (Array.isArray(self.body)) {
578
+ length = self.body.reduce(function (a, b) { return a + b.length }, 0)
579
+ } else {
580
+ length = self.body.length
581
+ }
582
+
583
+ if (length) {
584
+ self.setHeader('Content-Length', length)
585
+ } else {
586
+ self.emit('error', new Error('Argument error, options.body.'))
587
+ }
588
+ }
589
+ }
590
+
591
+ if (self.body && !isstream(self.body)) {
592
+ setContentLength()
593
+ }
594
+
595
+ if (options.oauth) {
596
+ self.oauth(options.oauth)
597
+ } else if (self._oauth.params && self.hasHeader('authorization')) {
598
+ self.oauth(self._oauth.params)
599
+ }
600
+
601
+ var protocol = (self.proxy && !self.tunnel && !self.socks) ? self.proxy.protocol : self.uri.protocol
602
+ var defaultModules = {'http:': { http2: http, http1: http, auto: http }, 'https:': { http1: https, http2: http2, auto: autohttp2 }}
603
+ var httpModules = self.httpModules || {}
604
+
605
+ // If user defines httpModules, respect if they have different httpModules for different http versions, else use the tls specific http module
606
+ // If the user defines nothing, revert to default modules
607
+ self.httpModule = (httpModules[protocol] && httpModules[protocol][self.protocolVersion]) || httpModules[protocol] || (defaultModules[protocol] && defaultModules[protocol][self.protocolVersion])
608
+
609
+ if (httpModules[protocol] && !(httpModules[protocol][options.protocolVersion])) {
610
+ // If the user is only specifying https/http modules, revert to http1
611
+ self.protocolVersion = 'http1'
612
+ }
613
+
614
+ if (!self.httpModule) {
615
+ return self.emit('error', new Error('Invalid protocol: ' + protocol))
616
+ }
617
+
618
+ if (options.ca) {
619
+ self.ca = options.ca
620
+ }
621
+
622
+ // prefer common self.agent if exists
623
+ if (self.agents && !self.agent) {
624
+ var agent = protocol === 'http:' ? self.agents.http : self.agents.https
625
+ if (agent) {
626
+ if (agent.agentClass || agent.agentOptions) {
627
+ options.agentClass = agent.agentClass || options.agentClass
628
+ options.agentOptions = agent.agentOptions || options.agentOptions
629
+ } else {
630
+ self.agent = agent
631
+ }
632
+ }
633
+ }
634
+
635
+ if (!self.agent) {
636
+ if (options.agentOptions) {
637
+ self.agentOptions = options.agentOptions
638
+ }
639
+
640
+ if (options.agentClass) {
641
+ self.agentClass = options.agentClass
642
+ } else if (options.forever) {
643
+ var v = version()
644
+ // use ForeverAgent in node 0.10- only
645
+ if (v.major === 0 && v.minor <= 10) {
646
+ self.agentClass = protocol === 'http:' ? ForeverAgent : ForeverAgent.SSL
647
+ } else {
648
+ self.agentClass = self.httpModule.Agent
649
+ self.agentOptions = self.agentOptions || {}
650
+ self.agentOptions.keepAlive = true
651
+ }
652
+ } else {
653
+ self.agentClass = self.httpModule.Agent
654
+ }
655
+ }
656
+
657
+ if (self.pool === false) {
658
+ self.agent = false
659
+ } else {
660
+ try {
661
+ self.agent = self.agent || self.getNewAgent({agentIdleTimeout: options.agentIdleTimeout})
662
+ } catch (error) {
663
+ // tls.createSecureContext() throws on bad options
664
+ return self.emit('error', error)
665
+ }
666
+ }
667
+
668
+ self.on('pipe', function (src) {
669
+ if (self.ntick && self._started) {
670
+ self.emit('error', new Error('You cannot pipe to this stream after the outbound request has started.'))
671
+ }
672
+ self.src = src
673
+ if (isReadStream(src)) {
674
+ if (!self.hasHeader('content-type')) {
675
+ // @note fallback to 'application/octet-stream' if mime.lookup returns `false`
676
+ self.setHeader('Content-Type', mime.lookup(src.path) || 'application/octet-stream')
677
+ }
678
+ } else {
679
+ if (src.headers) {
680
+ for (var i in src.headers) {
681
+ if (!self.hasHeader(i)) {
682
+ self.setHeader(i, src.headers[i])
683
+ }
684
+ }
685
+ }
686
+ if (self._json && !self.hasHeader('content-type')) {
687
+ self.setHeader('Content-Type', 'application/json')
688
+ }
689
+ if (src.method && !self.explicitMethod) {
690
+ self.method = src.method
691
+ }
692
+ }
693
+
694
+ // self.on('pipe', function () {
695
+ // console.error('You have already piped to this stream. Pipeing twice is likely to break the request.')
696
+ // })
697
+ })
698
+
699
+ defer(function () {
700
+ if (self._aborted) {
701
+ return
702
+ }
703
+
704
+ var end = function () {
705
+ if (self._form) {
706
+ if (!self._auth.hasAuth || (self._auth.hasAuth && self._auth.sentAuth)) {
707
+ try {
708
+ self._form.pipe(self)
709
+ } catch (err) {
710
+ self.abort()
711
+ options.callback && options.callback(err)
712
+ return
713
+ }
714
+ }
715
+ }
716
+ if (self._multipart && self._multipart.chunked) {
717
+ self._multipart.body.pipe(self)
718
+ }
719
+ if (self.body) {
720
+ if (isstream(self.body)) {
721
+ if (self.hasHeader('content-length')) {
722
+ self.body.pipe(self)
723
+ } else { // certain servers require content-length to function. we try to pre-detect if possible
724
+ streamLength(self.body, {}, function (err, len) {
725
+ if (!(err || self._started || self.hasHeader('content-length') || len === null || len < 0)) {
726
+ self.setHeader('Content-Length', len)
727
+ }
728
+ self.body.pipe(self)
729
+ })
730
+ }
731
+ } else {
732
+ setContentLength()
733
+ if (Array.isArray(self.body)) {
734
+ self.body.forEach(function (part) {
735
+ self.write(part)
736
+ })
737
+ } else {
738
+ self.write(self.body)
739
+ }
740
+ self.end()
741
+ }
742
+ } else if (self.requestBodyStream) {
743
+ console.warn('options.requestBodyStream is deprecated, please pass the request object to stream.pipe.')
744
+ self.requestBodyStream.pipe(self)
745
+ } else if (!self.src) {
746
+ if ((self._auth.hasAuth && !self._auth.sentAuth) || self.hasHeader('content-length')) {
747
+ self.end()
748
+ return
749
+ }
750
+ switch (self.method) {
751
+ case 'GET':
752
+ case 'HEAD':
753
+ case 'TRACE':
754
+ case 'DELETE':
755
+ case 'CONNECT':
756
+ case 'OPTIONS':
757
+ case undefined:
758
+ // @note this behavior is same as Node.js
759
+ break
760
+ default:
761
+ self.setHeader('Content-Length', 0)
762
+ break
763
+ }
764
+ self.end()
765
+ }
766
+ }
767
+
768
+ self.jar(self._jar || options.jar, function () {
769
+ if (self._form && !self.hasHeader('content-length')) {
770
+ // Before ending the request, we had to compute the length of the whole form, asyncly
771
+ self._form.getLength(function (err, length) {
772
+ if (!err && !isNaN(length)) {
773
+ self.setHeader('Content-Length', length)
774
+ }
775
+ end()
776
+ })
777
+ } else {
778
+ end()
779
+ }
780
+ })
781
+
782
+ self.ntick = true
783
+ })
784
+ }
785
+
786
+ Request.prototype.getNewAgent = function ({agentIdleTimeout}) {
787
+ var self = this
788
+ var Agent = self.agentClass
789
+ var options = {}
790
+ if (self.agentOptions) {
791
+ for (var i in self.agentOptions) {
792
+ options[i] = self.agentOptions[i]
793
+ }
794
+ }
795
+ if (self.ca) {
796
+ options.ca = self.ca
797
+ }
798
+ if (self.extraCA) {
799
+ options.extraCA = self.extraCA
800
+ }
801
+ if (self.ciphers) {
802
+ options.ciphers = self.ciphers
803
+ }
804
+ if (self.secureProtocol) {
805
+ options.secureProtocol = self.secureProtocol
806
+ }
807
+ if (self.secureOptions) {
808
+ options.secureOptions = self.secureOptions
809
+ }
810
+ if (self.sslKeyLogFile) {
811
+ options.sslKeyLogFile = self.sslKeyLogFile
812
+ }
813
+ if (typeof self.rejectUnauthorized !== 'undefined') {
814
+ options.rejectUnauthorized = self.rejectUnauthorized
815
+ }
816
+
817
+ if (self.cert && self.key) {
818
+ options.key = self.key
819
+ options.cert = self.cert
820
+ }
821
+
822
+ if (self.pfx) {
823
+ options.pfx = self.pfx
824
+ }
825
+
826
+ if (self.passphrase) {
827
+ options.passphrase = self.passphrase
828
+ }
829
+
830
+ var poolKey = ''
831
+
832
+ // different types of agents are in different pools
833
+ if (Agent !== self.httpModule.Agent) {
834
+ poolKey += Agent.name
835
+ }
836
+
837
+ // ca option is only relevant if proxy or destination are https
838
+ var proxy = self.proxy
839
+ if (typeof proxy === 'string') {
840
+ proxy = self.urlParser.parse(proxy)
841
+ }
842
+ var isHttps = (proxy && proxy.protocol === 'https:') || this.uri.protocol === 'https:'
843
+
844
+ if (isHttps) {
845
+ if (options.ca) {
846
+ if (poolKey) {
847
+ poolKey += ':'
848
+ }
849
+ poolKey += options.ca
850
+ }
851
+
852
+ // only add when NodeExtraCACerts is enabled
853
+ if (options.extraCA) {
854
+ if (poolKey) {
855
+ poolKey += ':'
856
+ }
857
+ poolKey += options.extraCA
858
+
859
+ // Create a new secure context to add the extra CA
860
+ var secureContext = tls.createSecureContext(options)
861
+ secureContext.context.addCACert(options.extraCA)
862
+ options.secureContext = secureContext
863
+ }
864
+
865
+ if (typeof options.rejectUnauthorized !== 'undefined') {
866
+ if (poolKey) {
867
+ poolKey += ':'
868
+ }
869
+ poolKey += options.rejectUnauthorized
870
+ }
871
+
872
+ if (options.cert) {
873
+ if (poolKey) {
874
+ poolKey += ':'
875
+ }
876
+ poolKey += options.cert.toString('ascii') + options.key.toString('ascii')
877
+ }
878
+
879
+ if (options.pfx) {
880
+ if (poolKey) {
881
+ poolKey += ':'
882
+ }
883
+ poolKey += options.pfx.toString('ascii')
884
+ }
885
+
886
+ if (options.passphrase) {
887
+ if (poolKey) {
888
+ poolKey += ':'
889
+ }
890
+ poolKey += options.passphrase
891
+ }
892
+
893
+ if (options.ciphers) {
894
+ if (poolKey) {
895
+ poolKey += ':'
896
+ }
897
+ poolKey += options.ciphers
898
+ }
899
+
900
+ if (options.secureProtocol) {
901
+ if (poolKey) {
902
+ poolKey += ':'
903
+ }
904
+ poolKey += options.secureProtocol
905
+ }
906
+
907
+ if (options.secureOptions) {
908
+ if (poolKey) {
909
+ poolKey += ':'
910
+ }
911
+ poolKey += options.secureOptions
912
+ }
913
+
914
+ if (options.sslKeyLogFile) {
915
+ if (poolKey) {
916
+ poolKey += ':'
917
+ }
918
+ poolKey += options.sslKeyLogFile
919
+ }
920
+ }
921
+
922
+ if (self.pool === globalPool && !poolKey && Object.keys(options).length === 0 && self.httpModule.globalAgent && typeof agentIdleTimeout !== 'number') {
923
+ // not doing anything special. Use the globalAgent
924
+ return self.httpModule.globalAgent
925
+ }
926
+
927
+ // we're using a stored agent. Make sure it's protocol-specific
928
+ poolKey = self.protocolVersion + ':' + self.uri.protocol + poolKey
929
+
930
+ let agent = self.pool[poolKey]
931
+
932
+ // generate a new agent for this setting if none yet exists
933
+ if (!agent || (typeof agentIdleTimeout === 'number' && (agent.lastUsedAt || 0) + agentIdleTimeout < Date.now())) {
934
+ agent = self.pool[poolKey] = new Agent(options)
935
+ // properly set maxSockets on new agents
936
+ if (self.pool.maxSockets) {
937
+ self.pool[poolKey].maxSockets = self.pool.maxSockets
938
+ }
939
+ }
940
+
941
+ agent.lastUsedAt = Date.now()
942
+ return agent
943
+ }
944
+
945
+ Request.prototype.start = function () {
946
+ // start() is called once we are ready to send the outgoing HTTP request.
947
+ // this is usually called on the first write(), end() or on nextTick()
948
+ var self = this
949
+
950
+ if (self.timing) {
951
+ // All timings will be relative to this request's startTime. In order to do this,
952
+ // we need to capture the wall-clock start time (via Date), immediately followed
953
+ // by the high-resolution timer (via now()). While these two won't be set
954
+ // at the _exact_ same time, they should be close enough to be able to calculate
955
+ // high-resolution, monotonically non-decreasing timestamps relative to startTime.
956
+ var startTime = new Date().getTime()
957
+ var startTimeNow = now()
958
+ }
959
+
960
+ if (self._aborted) {
961
+ return
962
+ }
963
+
964
+ // postman: emit start event
965
+ self.emit('start')
966
+
967
+ self._started = true
968
+ self.method = self.method || 'GET'
969
+ self.href = self.uri.href
970
+
971
+ if (self.src && self.src.stat && self.src.stat.size && !self.hasHeader('content-length')) {
972
+ self.setHeader('Content-Length', self.src.stat.size)
973
+ }
974
+ if (self._aws) {
975
+ self.aws(self._aws, true)
976
+ }
977
+
978
+ self._reqResInfo.request = {
979
+ method: self.method,
980
+ href: self.uri.href,
981
+ headers: [],
982
+ proxy: (self.proxy && { href: self.proxy.href }) || undefined,
983
+ httpVersion: '1.1'
984
+ }
985
+
986
+ // We have a method named auth, which is completely different from the http.request
987
+ // auth option. If we don't remove it, we're gonna have a bad time.
988
+ var reqOptions = copy(self)
989
+ delete reqOptions.auth
990
+
991
+ // Workaround for a bug in Node: https://github.com/nodejs/node/issues/8321
992
+ if (!(self.disableUrlEncoding || self.proxy || self.uri.isUnix)) {
993
+ try {
994
+ extend(reqOptions, urlParse(self.uri.href))
995
+ } catch (e) { } // nothing to do if urlParse fails, "extend" never throws an error.
996
+ }
997
+
998
+ debug('make request', self.uri.href)
999
+
1000
+ // node v6.8.0 now supports a `timeout` value in `http.request()`, but we
1001
+ // should delete it for now since we handle timeouts manually for better
1002
+ // consistency with node versions before v6.8.0
1003
+ delete reqOptions.timeout
1004
+
1005
+ try {
1006
+ self.req = self.httpModule.request(reqOptions)
1007
+
1008
+ // Remove blacklisted headers from the request instance.
1009
+ // @note don't check for `hasHeader` because headers like `connection`,
1010
+ // 'content-length', 'transfer-encoding' etc. are added at the very end
1011
+ // and `removeHeader` updates the Node.js internal state which makes sure
1012
+ // these headers are not added.
1013
+ if (Array.isArray(self.blacklistHeaders) && self.blacklistHeaders.length) {
1014
+ self.blacklistHeaders.forEach(function (header) {
1015
+ self.req.removeHeader(header)
1016
+ // also remove from the `self` for the consistency
1017
+ self.removeHeader(header)
1018
+ })
1019
+ }
1020
+ } catch (err) {
1021
+ self.emit('error', err)
1022
+ return
1023
+ }
1024
+
1025
+ if (self.timing) {
1026
+ self.startTime = startTime
1027
+ self.startTimeNow = startTimeNow
1028
+
1029
+ // Timing values will all be relative to startTime (by comparing to startTimeNow
1030
+ // so we have an accurate clock)
1031
+ self.timings = {}
1032
+ }
1033
+
1034
+ var timeout
1035
+ if (self.timeout && !self.timeoutTimer) {
1036
+ if (self.timeout < 0) {
1037
+ timeout = 0
1038
+ } else if (typeof self.timeout === 'number' && isFinite(self.timeout)) {
1039
+ timeout = self.timeout
1040
+ }
1041
+ }
1042
+
1043
+ self.req.on('response', self.onRequestResponse.bind(self))
1044
+ self.req.on('error', self.onRequestError.bind(self))
1045
+ self.req.on('drain', function () {
1046
+ self.emit('drain')
1047
+ })
1048
+
1049
+ self.req.on('socket', function (socket) {
1050
+ if (self.verbose) {
1051
+ // The reused socket holds all the session data which was injected in
1052
+ // during the first connection. This is done because events like
1053
+ // `lookup`, `connect` & `secureConnect` will not be triggered for a
1054
+ // reused socket and debug information will be lost for that request.
1055
+ var reusedSocket = Boolean(socket.__SESSION_ID && socket.__SESSION_DATA)
1056
+
1057
+ if (!reusedSocket) {
1058
+ socket.__SESSION_ID = uuid()
1059
+ socket.__SESSION_DATA = {}
1060
+ }
1061
+
1062
+ // @note make sure you don't serialize this object to avoid memory leak
1063
+ self._reqResInfo.session = {
1064
+ id: socket.__SESSION_ID,
1065
+ reused: reusedSocket,
1066
+ data: socket.__SESSION_DATA
1067
+ }
1068
+ }
1069
+
1070
+ // Attach event only once on the socket, so that data is not written multiple times
1071
+ // Since the agent key also includes keyLog, we are sure that a socket which is not supposed to be logging the
1072
+ // ssl content will not have a keylog listener set inadvertently, so we don't need to care about removing this listener
1073
+ if (self.sslKeyLogFile && !events.getEventListeners(socket, 'keylog').length) {
1074
+ socket.on('keylog', (line) => {
1075
+ fsPromise.appendFile(self.sslKeyLogFile, line)
1076
+ .catch(() => {
1077
+ debug('Failed to append keylog to file: ' + self.sslKeyLogFile)
1078
+ })
1079
+ })
1080
+ }
1081
+ // `._connecting` was the old property which was made public in node v6.1.0
1082
+ var isConnecting = socket._connecting || socket.connecting
1083
+ if (self.timing) {
1084
+ self.timings.socket = now() - self.startTimeNow
1085
+
1086
+ if (isConnecting) {
1087
+ var onLookupTiming = function () {
1088
+ self.timings.lookup = now() - self.startTimeNow
1089
+ }
1090
+
1091
+ var onConnectTiming = function () {
1092
+ self.timings.connect = now() - self.startTimeNow
1093
+
1094
+ if (self.verbose) {
1095
+ socket.__SESSION_DATA.addresses = {
1096
+ // local address
1097
+ // @note there's no `socket.localFamily` but `.address` method
1098
+ // returns same output as of remote.
1099
+ local: (typeof socket.address === 'function') && socket.address(),
1100
+
1101
+ // remote address
1102
+ remote: {
1103
+ address: socket.remoteAddress,
1104
+ family: socket.remoteFamily,
1105
+ port: socket.remotePort
1106
+ }
1107
+ }
1108
+ }
1109
+ }
1110
+
1111
+ var onSecureConnectTiming = function () {
1112
+ self.timings.secureConnect = now() - self.startTimeNow
1113
+
1114
+ if (self.verbose) {
1115
+ socket.__SESSION_DATA.tls = {
1116
+ // true if the session was reused
1117
+ reused: (typeof socket.isSessionReused === 'function') && socket.isSessionReused(),
1118
+
1119
+ // true if the peer certificate was signed by one of the CAs specified
1120
+ authorized: socket.authorized,
1121
+
1122
+ // reason why the peer's certificate was not been verified
1123
+ authorizationError: socket.authorizationError,
1124
+
1125
+ // negotiated cipher name
1126
+ cipher: (typeof socket.getCipher === 'function') && socket.getCipher(),
1127
+
1128
+ // negotiated SSL/TLS protocol version
1129
+ // @note Node >= v5.7.0
1130
+ protocol: (typeof socket.getProtocol === 'function') && socket.getProtocol(),
1131
+
1132
+ // type, name, and size of parameter of an ephemeral key exchange
1133
+ // @note Node >= v5.0.0
1134
+ ephemeralKeyInfo: (typeof socket.getEphemeralKeyInfo === 'function') && socket.getEphemeralKeyInfo()
1135
+ }
1136
+
1137
+ // peer certificate information
1138
+ // @note if session is reused, all certificate information is
1139
+ // stripped from the socket (returns {}).
1140
+ // Refer: https://github.com/nodejs/node/issues/3940
1141
+ var peerCert = (typeof socket.getPeerCertificate === 'function') && (socket.getPeerCertificate() || {})
1142
+
1143
+ socket.__SESSION_DATA.tls.peerCertificate = {
1144
+ subject: peerCert.subject && {
1145
+ country: peerCert.subject.C,
1146
+ stateOrProvince: peerCert.subject.ST,
1147
+ locality: peerCert.subject.L,
1148
+ organization: peerCert.subject.O,
1149
+ organizationalUnit: peerCert.subject.OU,
1150
+ commonName: peerCert.subject.CN,
1151
+ alternativeNames: peerCert.subjectaltname
1152
+ },
1153
+ issuer: peerCert.issuer && {
1154
+ country: peerCert.issuer.C,
1155
+ stateOrProvince: peerCert.issuer.ST,
1156
+ locality: peerCert.issuer.L,
1157
+ organization: peerCert.issuer.O,
1158
+ organizationalUnit: peerCert.issuer.OU,
1159
+ commonName: peerCert.issuer.CN
1160
+ },
1161
+ validFrom: peerCert.valid_from,
1162
+ validTo: peerCert.valid_to,
1163
+ fingerprint: peerCert.fingerprint,
1164
+ serialNumber: peerCert.serialNumber
1165
+ }
1166
+ }
1167
+ }
1168
+
1169
+ socket.once('lookup', onLookupTiming)
1170
+ socket.once('connect', onConnectTiming)
1171
+ socket.once('secureConnect', onSecureConnectTiming)
1172
+
1173
+ // clean up timing event listeners if needed on error
1174
+ self.req.once('error', function () {
1175
+ // Swallow ERR_HTTP2_SOCKET_UNBOUND error when removing listeners in case of error.
1176
+ // This needs to be done since http2 ClientSession disassociates the underlying socket from the session before emitting the error event
1177
+ try {
1178
+ socket.removeListener('lookup', onLookupTiming)
1179
+ socket.removeListener('connect', onConnectTiming)
1180
+ } catch (err) {
1181
+ if (err.code !== 'ERR_HTTP2_SOCKET_UNBOUND') {
1182
+ throw err
1183
+ }
1184
+ }
1185
+ })
1186
+ }
1187
+ }
1188
+
1189
+ var setReqTimeout = function () {
1190
+ // This timeout sets the amount of time to wait *between* bytes sent
1191
+ // from the server once connected.
1192
+ //
1193
+ // In particular, it's useful for erroring if the server fails to send
1194
+ // data halfway through streaming a response.
1195
+ self.req.setTimeout(timeout, function () {
1196
+ if (self.req) {
1197
+ self.abort()
1198
+ var e = new Error('ESOCKETTIMEDOUT')
1199
+ e.code = 'ESOCKETTIMEDOUT'
1200
+ e.connect = false
1201
+ self.emit('error', e)
1202
+ }
1203
+ })
1204
+ }
1205
+ if (timeout !== undefined) {
1206
+ // Only start the connection timer if we're actually connecting a new
1207
+ // socket, otherwise if we're already connected (because this is a
1208
+ // keep-alive connection) do not bother. This is important since we won't
1209
+ // get a 'connect' event for an already connected socket.
1210
+ if (isConnecting) {
1211
+ var onReqSockConnect = function () {
1212
+ socket.removeListener('connect', onReqSockConnect)
1213
+ self.clearTimeout()
1214
+ setReqTimeout()
1215
+ }
1216
+
1217
+ socket.on('connect', onReqSockConnect)
1218
+
1219
+ self.req.on('error', function (err) { // eslint-disable-line handle-callback-err
1220
+ // Swallow ERR_HTTP2_SOCKET_UNBOUND error when removing listeners in case of error.
1221
+ // This needs to be done since http2 ClientSession disassociates the underlying socket from the session before emitting the error event
1222
+ try {
1223
+ socket.removeListener('connect', onReqSockConnect)
1224
+ } catch (err) {
1225
+ if (err.code !== 'ERR_HTTP2_SOCKET_UNBOUND') {
1226
+ throw err
1227
+ }
1228
+ }
1229
+ })
1230
+
1231
+ // Set a timeout in memory - this block will throw if the server takes more
1232
+ // than `timeout` to write the HTTP status and headers (corresponding to
1233
+ // the on('response') event on the client). NB: this measures wall-clock
1234
+ // time, not the time between bytes sent by the server.
1235
+ self.timeoutTimer = setTimeout(function () {
1236
+ socket.removeListener('connect', onReqSockConnect)
1237
+ self.abort()
1238
+ var e = new Error('ETIMEDOUT')
1239
+ e.code = 'ETIMEDOUT'
1240
+ e.connect = true
1241
+ self.emit('error', e)
1242
+ }, timeout)
1243
+ } else {
1244
+ // We're already connected
1245
+ setReqTimeout()
1246
+ }
1247
+ }
1248
+ self.emit('socket', socket)
1249
+ })
1250
+
1251
+ self.emit('request', self.req)
1252
+ }
1253
+
1254
+ Request.prototype.onRequestError = function (error) {
1255
+ var self = this
1256
+ if (self._aborted) {
1257
+ return
1258
+ }
1259
+ if (self.req && self.req._reusedSocket && error.code === 'ECONNRESET' &&
1260
+ self.agent.addRequestNoreuse) {
1261
+ self.agent = {addRequest: self.agent.addRequestNoreuse.bind(self.agent)}
1262
+ self.start()
1263
+ self.req.end()
1264
+ return
1265
+ }
1266
+ if (error.res && error.res.statusCode && error.res.statusMessage) {
1267
+ // Handle the response to populate all the right properties for the consumer to pick up
1268
+ self.onRequestResponse(error.res)
1269
+ }
1270
+ self.clearTimeout()
1271
+ self.emit('error', error)
1272
+ }
1273
+
1274
+ Request.prototype.onRequestResponse = function (response) {
1275
+ var self = this
1276
+ // De-referencing self.startTimeNow to prevent race condition during redirects
1277
+ // Race-condition:
1278
+ // 30x-url: Request start (self.startTimeNow initialized (self.start()))
1279
+ // Redirect header to 200 request received
1280
+ // 200-url: Request start (self.startTimeNow re-initialized, old value overwritten (redirect.js -> request.init() -> self.start()))
1281
+ // 30x-url: end event received, timing calculated using new self.startTimeNow (incorrect)
1282
+ //
1283
+ // This must've been happening with http/1.1 as well when using keep-alive, but there were no tests to catch this.
1284
+ // Was highlighted with http/2 where connections are reused by default
1285
+ // Does not show up in http/1.x tests due to delays involving socket establishment
1286
+ //
1287
+ // New flow
1288
+ // 30x-url: Request start (self.startTimeNow initialized)
1289
+ // Redirect header to 200 request received
1290
+ // 200-url: Request start (self.startTimeNow re-initialized, old value overwritten)
1291
+ // 30x-url: end event received, timing calculated using requestSegmentStartTime (correct)
1292
+ const requestSegmentStartTime = self.startTimeNow
1293
+
1294
+ if (self.timing) {
1295
+ self.timings.response = now() - requestSegmentStartTime
1296
+ }
1297
+
1298
+ debug('onRequestResponse', self.uri.href, response.statusCode, response.headers)
1299
+ response.on('end', function () {
1300
+ if (self.timing) {
1301
+ self.timings.end = now() - requestSegmentStartTime
1302
+ response.timingStart = self.startTime
1303
+ response.timingStartTimer = requestSegmentStartTime
1304
+
1305
+ // fill in the blanks for any periods that didn't trigger, such as
1306
+ // no lookup or connect due to keep alive
1307
+ if (!self.timings.socket) {
1308
+ self.timings.socket = 0
1309
+ }
1310
+ if (!self.timings.lookup) {
1311
+ self.timings.lookup = self.timings.socket
1312
+ }
1313
+ if (!self.timings.connect) {
1314
+ self.timings.connect = self.timings.lookup
1315
+ }
1316
+ if (!self.timings.secureConnect && self.uri.protocol === 'https:') {
1317
+ self.timings.secureConnect = self.timings.connect
1318
+ }
1319
+ if (!self.timings.response) {
1320
+ self.timings.response = self.timings.connect
1321
+ }
1322
+
1323
+ debug('elapsed time', self.timings.end)
1324
+
1325
+ // elapsedTime includes all redirects
1326
+ self.elapsedTime += Math.round(self.timings.end)
1327
+
1328
+ // NOTE: elapsedTime is deprecated in favor of .timings
1329
+ response.elapsedTime = self.elapsedTime
1330
+
1331
+ // timings is just for the final fetch
1332
+ response.timings = self.timings
1333
+
1334
+ // pre-calculate phase timings as well
1335
+ response.timingPhases = {
1336
+ wait: self.timings.socket,
1337
+ dns: self.timings.lookup - self.timings.socket,
1338
+ tcp: self.timings.connect - self.timings.lookup,
1339
+ firstByte: self.timings.response - self.timings.connect,
1340
+ download: self.timings.end - self.timings.response,
1341
+ total: self.timings.end
1342
+ }
1343
+
1344
+ // if secureConnect is present, add secureHandshake and update firstByte
1345
+ if (self.timings.secureConnect) {
1346
+ response.timingPhases.secureHandshake = self.timings.secureConnect - self.timings.connect
1347
+ response.timingPhases.firstByte = self.timings.response - self.timings.secureConnect
1348
+ }
1349
+ }
1350
+
1351
+ debug('response end', self.uri.href, response.statusCode, response.headers)
1352
+ })
1353
+
1354
+ if (self._aborted) {
1355
+ debug('aborted', self.uri.href)
1356
+ response.resume()
1357
+ return
1358
+ }
1359
+
1360
+ self._reqResInfo.response = {
1361
+ statusCode: response.statusCode,
1362
+ headers: parseResponseHeaders(response.rawHeaders),
1363
+ httpVersion: response.httpVersion
1364
+ }
1365
+
1366
+ // Setting this again since the actual request version that was used is found only after ALPN negotiation in case of protocolVersion: auto
1367
+ self._reqResInfo.request.httpVersion = response.httpVersion
1368
+
1369
+ if (self.timing) {
1370
+ self._reqResInfo.timingStart = self.startTime
1371
+ self._reqResInfo.timingStartTimer = self.startTimeNow
1372
+ self._reqResInfo.timings = self.timings
1373
+ }
1374
+
1375
+ self.response = response
1376
+ response.request = self
1377
+ response.toJSON = responseToJSON
1378
+
1379
+ // XXX This is different on 0.10, because SSL is strict by default
1380
+ if (self.uri.protocol === 'https:' &&
1381
+ self.strictSSL && (!response.hasOwnProperty('socket') ||
1382
+ !response.socket.authorized)) {
1383
+ debug('strict ssl error', self.uri.href)
1384
+ var sslErr = response.hasOwnProperty('socket') ? response.socket.authorizationError : self.uri.href + ' does not support SSL'
1385
+ self.emit('error', new Error('SSL Error: ' + sslErr))
1386
+ return
1387
+ }
1388
+
1389
+ // Save the original host before any redirect (if it changes, we need to
1390
+ // remove any authorization headers). Also remember the case of the header
1391
+ // name because lots of broken servers expect Host instead of host and we
1392
+ // want the caller to be able to specify this.
1393
+ self.originalHost = self.getHeader('host')
1394
+ if (!self.originalHostHeaderName) {
1395
+ self.originalHostHeaderName = self.hasHeader('host')
1396
+ }
1397
+ if (self.setHost) {
1398
+ self.removeHeader('host')
1399
+ }
1400
+ self.clearTimeout()
1401
+
1402
+ function responseHandler () {
1403
+ if (self._redirect.onResponse(response)) {
1404
+ return // Ignore the rest of the response
1405
+ }
1406
+
1407
+ // Be a good stream and emit end when the response is finished.
1408
+ // Hack to emit end on close because of a core bug that never fires end
1409
+ response.once('close', function () {
1410
+ if (!self._ended) {
1411
+ self.response.emit('end')
1412
+ }
1413
+ })
1414
+
1415
+ response.once('end', function () {
1416
+ self._ended = true
1417
+ })
1418
+
1419
+ var noBody = function (code) {
1420
+ return (
1421
+ self.method === 'HEAD' ||
1422
+ // Informational
1423
+ (code >= 100 && code < 200) ||
1424
+ // No Content
1425
+ code === 204 ||
1426
+ // Not Modified
1427
+ code === 304
1428
+ )
1429
+ }
1430
+
1431
+ var responseContent
1432
+ var downloadSizeTracker = new SizeTrackerStream()
1433
+
1434
+ if ((self.gzip || self.brotli) && !noBody(response.statusCode)) {
1435
+ var contentEncoding = response.headers['content-encoding'] || 'identity'
1436
+ contentEncoding = contentEncoding.trim().toLowerCase()
1437
+
1438
+ // Be more lenient with decoding compressed responses, since (very rarely)
1439
+ // servers send slightly invalid gzip responses that are still accepted
1440
+ // by common browsers.
1441
+ // Always using Z_SYNC_FLUSH is what cURL does.
1442
+ var zlibOptions = {
1443
+ flush: zlib.Z_SYNC_FLUSH,
1444
+ finishFlush: zlib.Z_SYNC_FLUSH
1445
+ }
1446
+
1447
+ if (self.gzip && contentEncoding === 'gzip') {
1448
+ responseContent = zlib.createGunzip(zlibOptions)
1449
+ response.pipe(downloadSizeTracker).pipe(responseContent)
1450
+ } else if (self.gzip && contentEncoding === 'deflate') {
1451
+ responseContent = inflate.createInflate(zlibOptions)
1452
+ response.pipe(downloadSizeTracker).pipe(responseContent)
1453
+ } else if (self.brotli && contentEncoding === 'br') {
1454
+ responseContent = zlib.createBrotliDecompress()
1455
+ response.pipe(downloadSizeTracker).pipe(responseContent)
1456
+ } else {
1457
+ // Since previous versions didn't check for Content-Encoding header,
1458
+ // ignore any invalid values to preserve backwards-compatibility
1459
+ if (contentEncoding !== 'identity') {
1460
+ debug('ignoring unrecognized Content-Encoding ' + contentEncoding)
1461
+ }
1462
+ responseContent = response.pipe(downloadSizeTracker)
1463
+ }
1464
+ } else {
1465
+ responseContent = response.pipe(downloadSizeTracker)
1466
+ }
1467
+
1468
+ if (self.encoding) {
1469
+ if (self.dests.length !== 0) {
1470
+ console.error('Ignoring encoding parameter as this stream is being piped to another stream which makes the encoding option invalid.')
1471
+ } else {
1472
+ responseContent.setEncoding(self.encoding)
1473
+ }
1474
+ }
1475
+
1476
+ // Node by default returns the status message with `latin1` character encoding,
1477
+ // which results in characters lying outside the range of `U+0000 to U+00FF` getting truncated
1478
+ // so that they can be mapped in the given range.
1479
+ // Refer: https://nodejs.org/docs/latest-v12.x/api/buffer.html#buffer_buffers_and_character_encodings
1480
+ //
1481
+ // Exposing `statusMessageEncoding` option to make encoding type configurable.
1482
+ // This would help in correctly representing status messages belonging to range outside of `latin1`
1483
+ //
1484
+ // @note: The Regex `[^\w\s-']` is tested to prevent unnecessary computation of creating a Buffer and
1485
+ // then decoding it when the status message consists of common characters,
1486
+ // specifically belonging to the following set: [a-z, A-Z, 0-9, -, _ ', whitespace]
1487
+ // As in that case, no matter what the encoding type is used for decoding the buffer, the result would remain the same.
1488
+ //
1489
+ // @note: Providing a value in this option will result in force re-encoding of the status message
1490
+ // which may not always be intended by the server - specifically in cases where
1491
+ // server returns a status message which when encoded again with a different character encoding
1492
+ // results in some other characters.
1493
+ // For example: If the server intentionally responds with `ð\x9F\x98\x8A` as status message
1494
+ // but if the statusMessageEncoding option is set to `utf8`, then it would get converted to '😊'.
1495
+ var statusMessage = String(response.statusMessage)
1496
+ if (self.statusMessageEncoding && /[^\w\s-']/.test(statusMessage)) {
1497
+ response.statusMessage = Buffer.from(statusMessage, 'latin1').toString(self.statusMessageEncoding)
1498
+ }
1499
+
1500
+ if (self._paused) {
1501
+ responseContent.pause()
1502
+ }
1503
+
1504
+ self.responseContent = responseContent
1505
+
1506
+ self.emit('response', response)
1507
+
1508
+ self.dests.forEach(function (dest) {
1509
+ self.pipeDest(dest)
1510
+ })
1511
+
1512
+ var responseThresholdEnabled = false
1513
+ var responseBytesLeft
1514
+
1515
+ if (typeof self.maxResponseSize === 'number') {
1516
+ responseThresholdEnabled = true
1517
+ responseBytesLeft = self.maxResponseSize
1518
+ }
1519
+
1520
+ responseContent.on('data', function (chunk) {
1521
+ if (self.timing && !self.responseStarted) {
1522
+ self.responseStartTime = (new Date()).getTime()
1523
+
1524
+ // NOTE: responseStartTime is deprecated in favor of .timings
1525
+ response.responseStartTime = self.responseStartTime
1526
+ }
1527
+ // if response threshold is set, update the response bytes left to hit
1528
+ // threshold. If exceeds, abort the request.
1529
+ if (responseThresholdEnabled) {
1530
+ responseBytesLeft -= chunk.length
1531
+ if (responseBytesLeft < 0) {
1532
+ self.emit('error', new Error('Maximum response size reached'))
1533
+ self.destroy()
1534
+ self.abort()
1535
+ return
1536
+ }
1537
+ }
1538
+ self._destdata = true
1539
+ self.emit('data', chunk)
1540
+ })
1541
+ responseContent.once('end', function (chunk) {
1542
+ self._reqResInfo.response.downloadedBytes = downloadSizeTracker.size
1543
+ self.emit('end', chunk)
1544
+ })
1545
+ responseContent.on('error', function (error) {
1546
+ self.emit('error', error)
1547
+ })
1548
+ responseContent.on('close', function () { self.emit('close') })
1549
+
1550
+ if (self.callback) {
1551
+ self.readResponseBody(response)
1552
+ } else { // if no callback
1553
+ self.on('end', function () {
1554
+ if (self._aborted) {
1555
+ debug('aborted', self.uri.href)
1556
+ return
1557
+ }
1558
+ self.emit('complete', response)
1559
+ })
1560
+ }
1561
+ }
1562
+
1563
+ function forEachAsync (items, fn, cb) {
1564
+ !cb && (cb = function () { /* (ಠ_ಠ) */ })
1565
+
1566
+ if (!(Array.isArray(items) && fn)) { return cb() }
1567
+
1568
+ var index = 0
1569
+ var totalItems = items.length
1570
+ function next (err) {
1571
+ if (err || index >= totalItems) {
1572
+ return cb(err)
1573
+ }
1574
+
1575
+ try {
1576
+ fn.call(items, items[index++], next)
1577
+ } catch (error) {
1578
+ return cb(error)
1579
+ }
1580
+ }
1581
+
1582
+ if (!totalItems) { return cb() }
1583
+
1584
+ next()
1585
+ }
1586
+
1587
+ var targetCookieJar = (self._jar && self._jar.setCookie) ? self._jar : globalCookieJar
1588
+ var addCookie = function (cookie, cb) {
1589
+ // set the cookie if it's domain in the URI's domain.
1590
+ targetCookieJar.setCookie(cookie, self.uri, {ignoreError: true}, function () {
1591
+ // swallow the error, don't fail the request because of cookie jar failure
1592
+ cb()
1593
+ })
1594
+ }
1595
+
1596
+ response.caseless = caseless(response.headers)
1597
+
1598
+ if (response.caseless.has('set-cookie') && (!self._disableCookies)) {
1599
+ var headerName = response.caseless.has('set-cookie')
1600
+ if (Array.isArray(response.headers[headerName])) {
1601
+ forEachAsync(response.headers[headerName], addCookie, function (err) {
1602
+ if (err) { return self.emit('error', err) }
1603
+
1604
+ responseHandler()
1605
+ })
1606
+ } else {
1607
+ addCookie(response.headers[headerName], responseHandler)
1608
+ }
1609
+ } else {
1610
+ responseHandler()
1611
+ }
1612
+
1613
+ debug('finish init function', self.uri.href)
1614
+ }
1615
+
1616
+ Request.prototype.readResponseBody = function (response) {
1617
+ var self = this
1618
+ debug('reading response\'s body')
1619
+ var buffers = []
1620
+ var bufferLength = 0
1621
+ var strings = []
1622
+
1623
+ self.on('data', function (chunk) {
1624
+ if (!Buffer.isBuffer(chunk)) {
1625
+ strings.push(chunk)
1626
+ } else if (chunk.length) {
1627
+ bufferLength += chunk.length
1628
+ buffers.push(chunk)
1629
+ }
1630
+ })
1631
+ self.on('end', function () {
1632
+ debug('end event', self.uri.href)
1633
+ if (self._aborted) {
1634
+ debug('aborted', self.uri.href)
1635
+ // `buffer` is defined in the parent scope and used in a closure it exists for the life of the request.
1636
+ // This can lead to leaky behavior if the user retains a reference to the request object.
1637
+ buffers = []
1638
+ bufferLength = 0
1639
+ return
1640
+ }
1641
+
1642
+ if (bufferLength) {
1643
+ debug('has body', self.uri.href, bufferLength)
1644
+ response.body = Buffer.concat(buffers, bufferLength)
1645
+ if (self.encoding !== null) {
1646
+ response.body = response.body.toString(self.encoding)
1647
+ }
1648
+ // `buffer` is defined in the parent scope and used in a closure it exists for the life of the Request.
1649
+ // This can lead to leaky behavior if the user retains a reference to the request object.
1650
+ buffers = []
1651
+ bufferLength = 0
1652
+ } else if (strings.length) {
1653
+ // The UTF8 BOM [0xEF,0xBB,0xBF] is converted to [0xFE,0xFF] in the JS UTC16/UCS2 representation.
1654
+ // Strip this value out when the encoding is set to 'utf8', as upstream consumers won't expect it and it breaks JSON.parse().
1655
+ if (self.encoding === 'utf8' && strings[0].length > 0 && strings[0][0] === '\uFEFF') {
1656
+ strings[0] = strings[0].substring(1)
1657
+ }
1658
+ response.body = strings.join('')
1659
+ }
1660
+
1661
+ if (self._json) {
1662
+ try {
1663
+ response.body = JSON.parse(response.body, self._jsonReviver)
1664
+ } catch (e) {
1665
+ debug('invalid JSON received', self.uri.href)
1666
+ }
1667
+ }
1668
+ debug('emitting complete', self.uri.href)
1669
+ if (typeof response.body === 'undefined' && !self._json) {
1670
+ response.body = self.encoding === null ? Buffer.alloc(0) : ''
1671
+ }
1672
+ self.emit('complete', response, response.body)
1673
+ })
1674
+ }
1675
+
1676
+ Request.prototype.abort = function () {
1677
+ var self = this
1678
+ self._aborted = true
1679
+
1680
+ if (self.req) {
1681
+ self.req.abort()
1682
+ } else if (self.response) {
1683
+ self.response.destroy()
1684
+ }
1685
+
1686
+ self.clearTimeout()
1687
+ self.emit('abort')
1688
+ }
1689
+
1690
+ Request.prototype.pipeDest = function (dest) {
1691
+ var self = this
1692
+ var response = self.response
1693
+ // Called after the response is received
1694
+ if (dest.headers && !dest.headersSent) {
1695
+ if (response.caseless.has('content-type')) {
1696
+ var ctname = response.caseless.has('content-type')
1697
+ if (dest.setHeader) {
1698
+ dest.setHeader(ctname, response.headers[ctname])
1699
+ } else {
1700
+ dest.headers[ctname] = response.headers[ctname]
1701
+ }
1702
+ }
1703
+
1704
+ if (response.caseless.has('content-length')) {
1705
+ var clname = response.caseless.has('content-length')
1706
+ if (dest.setHeader) {
1707
+ dest.setHeader(clname, response.headers[clname])
1708
+ } else {
1709
+ dest.headers[clname] = response.headers[clname]
1710
+ }
1711
+ }
1712
+ }
1713
+ if (dest.setHeader && !dest.headersSent) {
1714
+ for (var i in response.headers) {
1715
+ if (i.startsWith(':')) {
1716
+ // Don't set HTTP/2 pseudoheaders
1717
+ continue
1718
+ }
1719
+ // If the response content is being decoded, the Content-Encoding header
1720
+ // of the response doesn't represent the piped content, so don't pass it.
1721
+ if (!self.gzip || i !== 'content-encoding') {
1722
+ dest.setHeader(i, response.headers[i])
1723
+ }
1724
+ }
1725
+ dest.statusCode = response.statusCode
1726
+ }
1727
+ if (self.pipefilter) {
1728
+ self.pipefilter(response, dest)
1729
+ }
1730
+ }
1731
+
1732
+ Request.prototype.qs = function (q, clobber) {
1733
+ var self = this
1734
+ var base
1735
+ if (!clobber && self.uri.query) {
1736
+ base = self._qs.parse(self.uri.query)
1737
+ } else {
1738
+ base = {}
1739
+ }
1740
+
1741
+ for (var i in q) {
1742
+ base[i] = q[i]
1743
+ }
1744
+
1745
+ var qs = self._qs.stringify(base)
1746
+
1747
+ if (qs === '') {
1748
+ return self
1749
+ }
1750
+
1751
+ self.uri = self.urlParser.parse(self.uri.href.split('?')[0] + '?' + qs)
1752
+ self.url = self.uri
1753
+ self.path = self.uri.path
1754
+
1755
+ if (self.uri.host === 'unix') {
1756
+ self.enableUnixSocket()
1757
+ }
1758
+
1759
+ return self
1760
+ }
1761
+ Request.prototype.form = function (form) {
1762
+ var self = this
1763
+ var contentType = self.getHeader('content-type')
1764
+ var overrideInvalidContentType = contentType ? !self.allowContentTypeOverride : true
1765
+ if (form) {
1766
+ if (overrideInvalidContentType && !/^application\/x-www-form-urlencoded\b/.test(contentType)) {
1767
+ self.setHeader('Content-Type', 'application/x-www-form-urlencoded')
1768
+ }
1769
+ self.body = (typeof form === 'string')
1770
+ ? self._qs.rfc3986(form.toString('utf8'))
1771
+ : self._qs.stringify(form).toString('utf8')
1772
+ return self
1773
+ }
1774
+ // form-data
1775
+ var contentTypeMatch = contentType && contentType.match &&
1776
+ contentType.match(/^multipart\/form-data;.*boundary=(?:"([^"]+)"|([^;]+))/)
1777
+ var boundary = contentTypeMatch && (contentTypeMatch[1] || contentTypeMatch[2])
1778
+ // create form-data object
1779
+ // set custom boundary if present in content-type else auto-generate
1780
+ self._form = new FormData({ _boundary: boundary })
1781
+ self._form.on('error', function (err) {
1782
+ err.message = 'form-data: ' + err.message
1783
+ self.emit('error', err)
1784
+ self.abort()
1785
+ })
1786
+ if (overrideInvalidContentType && !contentTypeMatch) {
1787
+ // overrides invalid or missing content-type
1788
+ self.setHeader('Content-Type', 'multipart/form-data; boundary=' + self._form.getBoundary())
1789
+ }
1790
+ return self._form
1791
+ }
1792
+ Request.prototype.multipart = function (multipart) {
1793
+ var self = this
1794
+
1795
+ self._multipart.onRequest(multipart)
1796
+
1797
+ if (!self._multipart.chunked) {
1798
+ self.body = self._multipart.body
1799
+ }
1800
+
1801
+ return self
1802
+ }
1803
+ Request.prototype.json = function (val) {
1804
+ var self = this
1805
+
1806
+ if (!self.hasHeader('accept')) {
1807
+ self.setHeader('Accept', 'application/json')
1808
+ }
1809
+
1810
+ if (typeof self.jsonReplacer === 'function') {
1811
+ self._jsonReplacer = self.jsonReplacer
1812
+ }
1813
+
1814
+ self._json = true
1815
+ if (typeof val === 'boolean') {
1816
+ if (self.body !== undefined) {
1817
+ if (!/^application\/x-www-form-urlencoded\b/.test(self.getHeader('content-type'))) {
1818
+ self.body = safeStringify(self.body, self._jsonReplacer)
1819
+ } else {
1820
+ self.body = self._qs.rfc3986(self.body)
1821
+ }
1822
+ if (!self.hasHeader('content-type')) {
1823
+ self.setHeader('Content-Type', 'application/json')
1824
+ }
1825
+ }
1826
+ } else {
1827
+ self.body = safeStringify(val, self._jsonReplacer)
1828
+ if (!self.hasHeader('content-type')) {
1829
+ self.setHeader('Content-Type', 'application/json')
1830
+ }
1831
+ }
1832
+
1833
+ if (typeof self.jsonReviver === 'function') {
1834
+ self._jsonReviver = self.jsonReviver
1835
+ }
1836
+
1837
+ return self
1838
+ }
1839
+ Request.prototype.getHeader = function (name, headers) {
1840
+ var self = this
1841
+ var result, re, match
1842
+ if (!headers) {
1843
+ headers = self.headers
1844
+ }
1845
+ Object.keys(headers).forEach(function (key) {
1846
+ if (key.length !== name.length) {
1847
+ return
1848
+ }
1849
+ re = new RegExp(name, 'i')
1850
+ match = key.match(re)
1851
+ if (match) {
1852
+ result = headers[key]
1853
+ }
1854
+ })
1855
+ return result
1856
+ }
1857
+ Request.prototype.enableUnixSocket = function () {
1858
+ // Get the socket & request paths from the URL
1859
+ var unixParts = this.uri.path.split(':')
1860
+ var host = unixParts[0]
1861
+ var path = unixParts[1]
1862
+ // Apply unix properties to request
1863
+ this.socketPath = host
1864
+ this.uri.pathname = path
1865
+ this.uri.path = path
1866
+ this.uri.host = host
1867
+ this.uri.hostname = host
1868
+ this.uri.isUnix = true
1869
+ }
1870
+
1871
+ Request.prototype.auth = function (user, pass, sendImmediately, bearer) {
1872
+ var self = this
1873
+
1874
+ self._auth.onRequest(user, pass, sendImmediately, bearer)
1875
+
1876
+ return self
1877
+ }
1878
+ Request.prototype.aws = function (opts, now) {
1879
+ var self = this
1880
+
1881
+ if (!now) {
1882
+ self._aws = opts
1883
+ return self
1884
+ }
1885
+
1886
+ if (opts.sign_version === 4 || opts.sign_version === '4') {
1887
+ // use aws4
1888
+ var options = {
1889
+ host: self.uri.host,
1890
+ path: self.uri.path,
1891
+ method: self.method,
1892
+ headers: self.headers,
1893
+ body: self.body
1894
+ }
1895
+ if (opts.service) {
1896
+ options.service = opts.service
1897
+ }
1898
+ var signRes = aws4.sign(options, {
1899
+ accessKeyId: opts.key,
1900
+ secretAccessKey: opts.secret,
1901
+ sessionToken: opts.session
1902
+ })
1903
+ self.setHeader('Authorization', signRes.headers.Authorization)
1904
+ self.setHeader('X-Amz-Date', signRes.headers['X-Amz-Date'])
1905
+ if (signRes.headers['X-Amz-Security-Token']) {
1906
+ self.setHeader('X-Amz-Security-Token', signRes.headers['X-Amz-Security-Token'])
1907
+ }
1908
+ } else {
1909
+ // default: use aws-sign2
1910
+ var date = new Date()
1911
+ self.setHeader('Date', date.toUTCString())
1912
+ var auth = {
1913
+ key: opts.key,
1914
+ secret: opts.secret,
1915
+ verb: self.method.toUpperCase(),
1916
+ date: date,
1917
+ contentType: self.getHeader('content-type') || '',
1918
+ md5: self.getHeader('content-md5') || '',
1919
+ amazonHeaders: aws2.canonicalizeHeaders(self.headers)
1920
+ }
1921
+ var path = self.uri.path
1922
+ if (opts.bucket && path) {
1923
+ auth.resource = '/' + opts.bucket + path
1924
+ } else if (opts.bucket && !path) {
1925
+ auth.resource = '/' + opts.bucket
1926
+ } else if (!opts.bucket && path) {
1927
+ auth.resource = path
1928
+ } else if (!opts.bucket && !path) {
1929
+ auth.resource = '/'
1930
+ }
1931
+ auth.resource = aws2.canonicalizeResource(auth.resource)
1932
+ self.setHeader('Authorization', aws2.authorization(auth))
1933
+ }
1934
+
1935
+ return self
1936
+ }
1937
+ Request.prototype.httpSignature = function (opts) {
1938
+ var self = this
1939
+ httpSignature.signRequest({
1940
+ getHeader: function (header) {
1941
+ return self.getHeader(header, self.headers)
1942
+ },
1943
+ setHeader: function (header, value) {
1944
+ self.setHeader(header, value)
1945
+ },
1946
+ method: self.method,
1947
+ path: self.path
1948
+ }, opts)
1949
+ debug('httpSignature authorization', self.getHeader('authorization'))
1950
+
1951
+ return self
1952
+ }
1953
+ Request.prototype.hawk = function (opts) {
1954
+ var self = this
1955
+ self.setHeader('Authorization', hawk.header(self.uri, self.method, opts))
1956
+ }
1957
+ Request.prototype.oauth = function (_oauth) {
1958
+ var self = this
1959
+
1960
+ self._oauth.onRequest(_oauth)
1961
+
1962
+ return self
1963
+ }
1964
+
1965
+ Request.prototype.jar = function (jar, cb) {
1966
+ var self = this
1967
+ self._jar = jar
1968
+
1969
+ if (!jar) {
1970
+ // disable cookies
1971
+ self._disableCookies = true
1972
+ return cb()
1973
+ }
1974
+
1975
+ if (self._redirect.redirectsFollowed === 0) {
1976
+ self.originalCookieHeader = self.getHeader('cookie')
1977
+ }
1978
+
1979
+ var targetCookieJar = jar.getCookieString ? jar : globalCookieJar
1980
+ // fetch cookie in the Specified host
1981
+ targetCookieJar.getCookieString(self.uri, function (err, cookies) {
1982
+ if (err) { return cb() }
1983
+
1984
+ // if need cookie and cookie is not empty
1985
+ if (cookies && cookies.length) {
1986
+ if (self.originalCookieHeader) {
1987
+ if (Array.isArray(self.originalCookieHeader)) {
1988
+ self.originalCookieHeader = self.originalCookieHeader.join('; ')
1989
+ }
1990
+ // Don't overwrite existing Cookie header
1991
+ self.setHeader('Cookie', self.originalCookieHeader + '; ' + cookies)
1992
+ } else {
1993
+ self.setHeader('Cookie', cookies)
1994
+ }
1995
+ }
1996
+
1997
+ cb()
1998
+ })
1999
+ }
2000
+
2001
+ // Stream API
2002
+ Request.prototype.pipe = function (dest, opts) {
2003
+ var self = this
2004
+
2005
+ if (self.response) {
2006
+ if (self._destdata) {
2007
+ self.emit('error', new Error('You cannot pipe after data has been emitted from the response.'))
2008
+ } else if (self._ended) {
2009
+ self.emit('error', new Error('You cannot pipe after the response has been ended.'))
2010
+ } else {
2011
+ stream.Stream.prototype.pipe.call(self, dest, opts)
2012
+ self.pipeDest(dest)
2013
+ return dest
2014
+ }
2015
+ } else {
2016
+ self.dests.push(dest)
2017
+ stream.Stream.prototype.pipe.call(self, dest, opts)
2018
+ return dest
2019
+ }
2020
+ }
2021
+ Request.prototype.write = function () {
2022
+ var self = this
2023
+ if (self._aborted) { return }
2024
+
2025
+ if (!self._started) {
2026
+ self.start()
2027
+ }
2028
+ if (self.req) {
2029
+ return self.req.write.apply(self.req, arguments)
2030
+ }
2031
+ }
2032
+ Request.prototype.end = function (chunk) {
2033
+ var self = this
2034
+ if (self._aborted) { return }
2035
+
2036
+ if (chunk) {
2037
+ self.write(chunk)
2038
+ }
2039
+ if (!self._started) {
2040
+ self.start()
2041
+ }
2042
+ if (self.req) {
2043
+ self.req.end()
2044
+
2045
+ // Reference to request, so if _reqResInfo is updated (in case of redirects), we still can update the headers
2046
+ const request = self._reqResInfo.request
2047
+ Promise.resolve(self.req._header).then(function (header) {
2048
+ if (!header) {
2049
+ request.headers = []
2050
+ return
2051
+ }
2052
+ request.headers = parseRequestHeaders(header)
2053
+ })
2054
+ }
2055
+ }
2056
+ Request.prototype.pause = function () {
2057
+ var self = this
2058
+ if (!self.responseContent) {
2059
+ self._paused = true
2060
+ } else {
2061
+ self.responseContent.pause.apply(self.responseContent, arguments)
2062
+ }
2063
+ }
2064
+ Request.prototype.resume = function () {
2065
+ var self = this
2066
+ if (!self.responseContent) {
2067
+ self._paused = false
2068
+ } else {
2069
+ self.responseContent.resume.apply(self.responseContent, arguments)
2070
+ }
2071
+ }
2072
+ Request.prototype.destroy = function () {
2073
+ var self = this
2074
+ this.clearTimeout()
2075
+ if (!self._ended) {
2076
+ self.end()
2077
+ } else if (self.response) {
2078
+ self.response.destroy()
2079
+ }
2080
+ }
2081
+
2082
+ Request.prototype.clearTimeout = function () {
2083
+ if (this.timeoutTimer) {
2084
+ clearTimeout(this.timeoutTimer)
2085
+ this.timeoutTimer = null
2086
+ }
2087
+ }
2088
+
2089
+ Request.defaultProxyHeaderWhiteList =
2090
+ Tunnel.defaultProxyHeaderWhiteList.slice()
2091
+
2092
+ Request.defaultProxyHeaderExclusiveList =
2093
+ Tunnel.defaultProxyHeaderExclusiveList.slice()
2094
+
2095
+ // Exports
2096
+
2097
+ Request.prototype.toJSON = requestToJSON
2098
+ module.exports = Request