UNPKG

49.4 kBJavaScriptView Raw
1'use strict'
2
3var http = require('http')
4 , https = require('https')
5 , url = require('url')
6 , util = require('util')
7 , stream = require('stream')
8 , qs = require('qs')
9 , querystring = require('querystring')
10 , zlib = require('zlib')
11 , helpers = require('./lib/helpers')
12 , bl = require('bl')
13 , oauth = require('oauth-sign')
14 , hawk = require('hawk')
15 , aws = require('aws-sign2')
16 , httpSignature = require('http-signature')
17 , uuid = require('node-uuid')
18 , mime = require('mime-types')
19 , tunnel = require('tunnel-agent')
20 , stringstream = require('stringstream')
21 , caseless = require('caseless')
22 , ForeverAgent = require('forever-agent')
23 , FormData = require('form-data')
24 , cookies = require('./lib/cookies')
25 , copy = require('./lib/copy')
26 , debug = require('./lib/debug')
27 , net = require('net')
28
29var safeStringify = helpers.safeStringify
30 , md5 = helpers.md5
31 , isReadStream = helpers.isReadStream
32 , toBase64 = helpers.toBase64
33 , defer = helpers.defer
34 , globalCookieJar = cookies.jar()
35
36
37var globalPool = {}
38 , isUrl = /^https?:/
39
40var defaultProxyHeaderWhiteList = [
41 'accept',
42 'accept-charset',
43 'accept-encoding',
44 'accept-language',
45 'accept-ranges',
46 'cache-control',
47 'content-encoding',
48 'content-language',
49 'content-length',
50 'content-location',
51 'content-md5',
52 'content-range',
53 'content-type',
54 'connection',
55 'date',
56 'expect',
57 'max-forwards',
58 'pragma',
59 'proxy-authorization',
60 'referer',
61 'te',
62 'transfer-encoding',
63 'user-agent',
64 'via'
65]
66
67function filterForNonReserved(reserved, options) {
68 // Filter out properties that are not reserved.
69 // Reserved values are passed in at call site.
70
71 var object = {}
72 for (var i in options) {
73 var notReserved = (reserved.indexOf(i) === -1)
74 if (notReserved) {
75 object[i] = options[i]
76 }
77 }
78 return object
79}
80
81function filterOutReservedFunctions(reserved, options) {
82 // Filter out properties that are functions and are reserved.
83 // Reserved values are passed in at call site.
84
85 var object = {}
86 for (var i in options) {
87 var isReserved = !(reserved.indexOf(i) === -1)
88 var isFunction = (typeof options[i] === 'function')
89 if (!(isReserved && isFunction)) {
90 object[i] = options[i]
91 }
92 }
93 return object
94
95}
96
97function constructProxyHost(uriObject) {
98 var port = uriObject.portA
99 , protocol = uriObject.protocol
100 , proxyHost = uriObject.hostname + ':'
101
102 if (port) {
103 proxyHost += port
104 } else if (protocol === 'https:') {
105 proxyHost += '443'
106 } else {
107 proxyHost += '80'
108 }
109
110 return proxyHost
111}
112
113function constructProxyHeaderWhiteList(headers, proxyHeaderWhiteList) {
114 return Object.keys(headers)
115 .filter(function (header) {
116 return proxyHeaderWhiteList.indexOf(header.toLowerCase()) !== -1
117 })
118 .reduce(function (set, header) {
119 set[header] = headers[header]
120 return set
121 }, {})
122}
123
124function construcTunnelOptions(request) {
125 var proxy = request.proxy
126 var proxyHeaders = request.proxyHeaders
127 var proxyAuth
128
129 if (proxy.auth) {
130 proxyAuth = proxy.auth
131 }
132
133 if (!proxy.auth && request.proxyAuthorization) {
134 proxyHeaders['Proxy-Authorization'] = request.proxyAuthorization
135 }
136
137 var tunnelOptions = {
138 proxy: {
139 host: proxy.hostname,
140 port: +proxy.port,
141 proxyAuth: proxyAuth,
142 headers: proxyHeaders
143 },
144 rejectUnauthorized: request.rejectUnauthorized,
145 headers: request.headers,
146 ca: request.ca,
147 cert: request.cert,
148 key: request.key
149 }
150
151 return tunnelOptions
152}
153
154function constructTunnelFnName(uri, proxy) {
155 var uriProtocol = (uri.protocol === 'https:' ? 'https' : 'http')
156 var proxyProtocol = (proxy.protocol === 'https:' ? 'Https' : 'Http')
157 return [uriProtocol, proxyProtocol].join('Over')
158}
159
160function getTunnelFn(request) {
161 var uri = request.uri
162 var proxy = request.proxy
163 var tunnelFnName = constructTunnelFnName(uri, proxy)
164 return tunnel[tunnelFnName]
165}
166
167// Decide the proper request proxy to use based on the request URI object and the
168// environmental variables (NO_PROXY, HTTP_PROXY, etc.)
169function getProxyFromURI(uri) {
170 // respect NO_PROXY environment variables (see: http://lynx.isc.org/current/breakout/lynx_help/keystrokes/environments.html)
171 var noProxy = process.env.NO_PROXY || process.env.no_proxy || null
172
173 // easy case first - if NO_PROXY is '*'
174 if (noProxy === '*') {
175 return null
176 }
177
178 // otherwise, parse the noProxy value to see if it applies to the URL
179 if (noProxy !== null) {
180 var noProxyItem, hostname, port, noProxyItemParts, noProxyHost, noProxyPort, noProxyList
181
182 // canonicalize the hostname, so that 'oogle.com' won't match 'google.com'
183 hostname = uri.hostname.replace(/^\.*/, '.').toLowerCase()
184 noProxyList = noProxy.split(',')
185
186 for (var i = 0, len = noProxyList.length; i < len; i++) {
187 noProxyItem = noProxyList[i].trim().toLowerCase()
188
189 // no_proxy can be granular at the port level, which complicates things a bit.
190 if (noProxyItem.indexOf(':') > -1) {
191 noProxyItemParts = noProxyItem.split(':', 2)
192 noProxyHost = noProxyItemParts[0].replace(/^\.*/, '.')
193 noProxyPort = noProxyItemParts[1]
194 port = uri.port || (uri.protocol === 'https:' ? '443' : '80')
195
196 // we've found a match - ports are same and host ends with no_proxy entry.
197 if (port === noProxyPort && hostname.indexOf(noProxyHost) === hostname.length - noProxyHost.length) {
198 return null
199 }
200 } else {
201 noProxyItem = noProxyItem.replace(/^\.*/, '.')
202 if (hostname.indexOf(noProxyItem) === hostname.length - noProxyItem.length) {
203 return null
204 }
205 }
206 }
207 }
208
209 // check for HTTP(S)_PROXY environment variables
210 if (uri.protocol === 'http:') {
211 return process.env.HTTP_PROXY || process.env.http_proxy || null
212 } else if (uri.protocol === 'https:') {
213 return process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy || null
214 }
215
216 // return null if all else fails (What uri protocol are you using then?)
217 return null
218}
219
220// Function for properly handling a connection error
221function connectionErrorHandler(error) {
222 var socket = this
223 if (socket.res) {
224 if (socket.res.request) {
225 socket.res.request.emit('error', error)
226 } else {
227 socket.res.emit('error', error)
228 }
229 } else {
230 socket._httpMessage.emit('error', error)
231 }
232}
233
234// Return a simpler request object to allow serialization
235function requestToJSON() {
236 var self = this
237 return {
238 uri: self.uri,
239 method: self.method,
240 headers: self.headers
241 }
242}
243
244// Return a simpler response object to allow serialization
245function responseToJSON() {
246 var self = this
247 return {
248 statusCode: self.statusCode,
249 body: self.body,
250 headers: self.headers,
251 request: requestToJSON.call(self.request)
252 }
253}
254
255function Request (options) {
256 // if tunnel property of options was not given default to false
257 // if given the method property in options, set property explicitMethod to true
258
259 // extend the Request instance with any non-reserved properties
260 // remove any reserved functions from the options object
261 // set Request instance to be readable and writable
262 // call init
263
264 var self = this
265 stream.Stream.call(self)
266 var reserved = Object.keys(Request.prototype)
267 var nonReserved = filterForNonReserved(reserved, options)
268
269 stream.Stream.call(self)
270 util._extend(self, nonReserved)
271 options = filterOutReservedFunctions(reserved, options)
272
273 self.readable = true
274 self.writable = true
275 if (typeof options.tunnel === 'undefined') {
276 options.tunnel = false
277 }
278 if (options.method) {
279 self.explicitMethod = true
280 }
281 self.canTunnel = options.tunnel !== false && tunnel
282 self.init(options)
283}
284
285util.inherits(Request, stream.Stream)
286
287Request.prototype.setupTunnel = function () {
288 // Set up the tunneling agent if necessary
289 // Only send the proxy whitelisted header names.
290 // Turn on tunneling for the rest of request.
291
292 var self = this
293
294 if (typeof self.proxy === 'string') {
295 self.proxy = url.parse(self.proxy)
296 }
297
298 if (!self.proxy) {
299 return false
300 }
301
302 if (!self.tunnel && self.uri.protocol !== 'https:') {
303 return false
304 }
305
306 if (!self.proxyHeaderWhiteList) {
307 self.proxyHeaderWhiteList = defaultProxyHeaderWhiteList
308 }
309
310 var proxyHost = constructProxyHost(self.uri)
311 self.proxyHeaders = constructProxyHeaderWhiteList(self.headers, self.proxyHeaderWhiteList)
312 self.proxyHeaders.host = proxyHost
313
314 var tunnelFn = getTunnelFn(self)
315 var tunnelOptions = construcTunnelOptions(self)
316
317 self.agent = tunnelFn(tunnelOptions)
318 self.tunnel = true
319 return true
320}
321
322Request.prototype.init = function (options) {
323 // init() contains all the code to setup the request object.
324 // the actual outgoing request is not started until start() is called
325 // this function is called from both the constructor and on redirect.
326 var self = this
327 if (!options) {
328 options = {}
329 }
330 self.headers = self.headers ? copy(self.headers) : {}
331
332 caseless.httpify(self, self.headers)
333
334 // Never send proxy-auth to the endpoint!
335 if (self.hasHeader('proxy-authorization')) {
336 self.proxyAuthorization = self.getHeader('proxy-authorization')
337 self.removeHeader('proxy-authorization')
338 }
339
340 if (!self.method) {
341 self.method = options.method || 'GET'
342 }
343 self.localAddress = options.localAddress
344
345 if (!self.qsLib) {
346 self.qsLib = (options.useQuerystring ? querystring : qs)
347 }
348
349 debug(options)
350 if (!self.pool && self.pool !== false) {
351 self.pool = globalPool
352 }
353 self.dests = self.dests || []
354 self.__isRequestRequest = true
355
356 // Protect against double callback
357 if (!self._callback && self.callback) {
358 self._callback = self.callback
359 self.callback = function () {
360 if (self._callbackCalled) {
361 return // Print a warning maybe?
362 }
363 self._callbackCalled = true
364 self._callback.apply(self, arguments)
365 }
366 self.on('error', self.callback.bind())
367 self.on('complete', self.callback.bind(self, null))
368 }
369
370 // People use this property instead all the time, so support it
371 if (!self.uri && self.url) {
372 self.uri = self.url
373 delete self.url
374 }
375
376 // A URI is needed by this point, throw if we haven't been able to get one
377 if (!self.uri) {
378 return self.emit('error', new Error('options.uri is a required argument'))
379 }
380
381 // If a string URI/URL was given, parse it into a URL object
382 if(typeof self.uri === 'string') {
383 self.uri = url.parse(self.uri)
384 }
385
386 // DEPRECATED: Warning for users of the old Unix Sockets URL Scheme
387 if (self.uri.protocol === 'unix:') {
388 return self.emit('error', new Error('`unix://` URL scheme is no longer supported. Please use the format `http://unix:SOCKET:PATH`'))
389 }
390
391 // Support Unix Sockets
392 if(self.uri.host === 'unix') {
393 // Get the socket & request paths from the URL
394 var unixParts = self.uri.path.split(':')
395 , host = unixParts[0]
396 , path = unixParts[1]
397 // Apply unix properties to request
398 self.socketPath = host
399 self.uri.pathname = path
400 self.uri.path = path
401 self.uri.host = host
402 self.uri.hostname = host
403 self.uri.isUnix = true
404 }
405
406 if (self.strictSSL === false) {
407 self.rejectUnauthorized = false
408 }
409
410 if(!self.hasOwnProperty('proxy')) {
411 self.proxy = getProxyFromURI(self.uri)
412 }
413
414 // Pass in `tunnel:true` to *always* tunnel through proxies
415 self.tunnel = !!options.tunnel
416 if (self.proxy) {
417 self.setupTunnel()
418 }
419
420 if (!self.uri.pathname) {self.uri.pathname = '/'}
421
422 if (!(self.uri.host || (self.uri.hostname && self.uri.port)) && !self.uri.isUnix) {
423 // Invalid URI: it may generate lot of bad errors, like 'TypeError: Cannot call method `indexOf` of undefined' in CookieJar
424 // Detect and reject it as soon as possible
425 var faultyUri = url.format(self.uri)
426 var message = 'Invalid URI "' + faultyUri + '"'
427 if (Object.keys(options).length === 0) {
428 // No option ? This can be the sign of a redirect
429 // As this is a case where the user cannot do anything (they didn't call request directly with this URL)
430 // they should be warned that it can be caused by a redirection (can save some hair)
431 message += '. This can be caused by a crappy redirection.'
432 }
433 // This error was fatal
434 return self.emit('error', new Error(message))
435 }
436
437 self._redirectsFollowed = self._redirectsFollowed || 0
438 self.maxRedirects = (self.maxRedirects !== undefined) ? self.maxRedirects : 10
439 self.allowRedirect = (typeof self.followRedirect === 'function') ? self.followRedirect : function(response) {
440 return true
441 }
442 self.followRedirects = (self.followRedirect !== undefined) ? !!self.followRedirect : true
443 self.followAllRedirects = (self.followAllRedirects !== undefined) ? self.followAllRedirects : false
444 if (self.followRedirects || self.followAllRedirects) {
445 self.redirects = self.redirects || []
446 }
447
448 self.setHost = false
449 if (!self.hasHeader('host')) {
450 self.setHeader('host', self.uri.hostname)
451 if (self.uri.port) {
452 if ( !(self.uri.port === 80 && self.uri.protocol === 'http:') &&
453 !(self.uri.port === 443 && self.uri.protocol === 'https:') ) {
454 self.setHeader('host', self.getHeader('host') + (':' + self.uri.port) )
455 }
456 }
457 self.setHost = true
458 }
459
460 self.jar(self._jar || options.jar)
461
462 if (!self.uri.port) {
463 if (self.uri.protocol === 'http:') {self.uri.port = 80}
464 else if (self.uri.protocol === 'https:') {self.uri.port = 443}
465 }
466
467 if (self.proxy && !self.tunnel) {
468 self.port = self.proxy.port
469 self.host = self.proxy.hostname
470 } else {
471 self.port = self.uri.port
472 self.host = self.uri.hostname
473 }
474
475 if (options.form) {
476 self.form(options.form)
477 }
478
479 if (options.formData) {
480 var formData = options.formData
481 var requestForm = self.form()
482 var appendFormValue = function (key, value) {
483 if (value.hasOwnProperty('value') && value.hasOwnProperty('options')) {
484 requestForm.append(key, value.value, value.options)
485 } else {
486 requestForm.append(key, value)
487 }
488 }
489 for (var formKey in formData) {
490 if (formData.hasOwnProperty(formKey)) {
491 var formValue = formData[formKey]
492 if (formValue instanceof Array) {
493 for (var j = 0; j < formValue.length; j++) {
494 appendFormValue(formKey, formValue[j])
495 }
496 } else {
497 appendFormValue(formKey, formValue)
498 }
499 }
500 }
501 }
502
503 if (options.qs) {
504 self.qs(options.qs)
505 }
506
507 if (self.uri.path) {
508 self.path = self.uri.path
509 } else {
510 self.path = self.uri.pathname + (self.uri.search || '')
511 }
512
513 if (self.path.length === 0) {
514 self.path = '/'
515 }
516
517 // Auth must happen last in case signing is dependent on other headers
518 if (options.oauth) {
519 self.oauth(options.oauth)
520 }
521
522 if (options.aws) {
523 self.aws(options.aws)
524 }
525
526 if (options.hawk) {
527 self.hawk(options.hawk)
528 }
529
530 if (options.httpSignature) {
531 self.httpSignature(options.httpSignature)
532 }
533
534 if (options.auth) {
535 if (Object.prototype.hasOwnProperty.call(options.auth, 'username')) {
536 options.auth.user = options.auth.username
537 }
538 if (Object.prototype.hasOwnProperty.call(options.auth, 'password')) {
539 options.auth.pass = options.auth.password
540 }
541
542 self.auth(
543 options.auth.user,
544 options.auth.pass,
545 options.auth.sendImmediately,
546 options.auth.bearer
547 )
548 }
549
550 if (self.gzip && !self.hasHeader('accept-encoding')) {
551 self.setHeader('accept-encoding', 'gzip')
552 }
553
554 if (self.uri.auth && !self.hasHeader('authorization')) {
555 var uriAuthPieces = self.uri.auth.split(':').map(function(item){ return querystring.unescape(item) })
556 self.auth(uriAuthPieces[0], uriAuthPieces.slice(1).join(':'), true)
557 }
558
559 if (self.proxy && !self.tunnel) {
560 if (self.proxy.auth && !self.proxyAuthorization) {
561 var proxyAuthPieces = self.proxy.auth.split(':').map(function(item){
562 return querystring.unescape(item)
563 })
564 var authHeader = 'Basic ' + toBase64(proxyAuthPieces.join(':'))
565 self.proxyAuthorization = authHeader
566 }
567 if (self.proxyAuthorization) {
568 self.setHeader('proxy-authorization', self.proxyAuthorization)
569 }
570 }
571
572 if (self.proxy && !self.tunnel) {
573 self.path = (self.uri.protocol + '//' + self.uri.host + self.path)
574 }
575
576 if (options.json) {
577 self.json(options.json)
578 } else if (options.multipart) {
579 self.boundary = uuid()
580 self.multipart(options.multipart)
581 }
582
583 if (self.body) {
584 var length = 0
585 if (!Buffer.isBuffer(self.body)) {
586 if (Array.isArray(self.body)) {
587 for (var i = 0; i < self.body.length; i++) {
588 length += self.body[i].length
589 }
590 } else {
591 self.body = new Buffer(self.body)
592 length = self.body.length
593 }
594 } else {
595 length = self.body.length
596 }
597 if (length) {
598 if (!self.hasHeader('content-length')) {
599 self.setHeader('content-length', length)
600 }
601 } else {
602 throw new Error('Argument error, options.body.')
603 }
604 }
605
606 var protocol = self.proxy && !self.tunnel ? self.proxy.protocol : self.uri.protocol
607 , defaultModules = {'http:':http, 'https:':https}
608 , httpModules = self.httpModules || {}
609
610 self.httpModule = httpModules[protocol] || defaultModules[protocol]
611
612 if (!self.httpModule) {
613 return self.emit('error', new Error('Invalid protocol: ' + protocol))
614 }
615
616 if (options.ca) {
617 self.ca = options.ca
618 }
619
620 if (!self.agent) {
621 if (options.agentOptions) {
622 self.agentOptions = options.agentOptions
623 }
624
625 if (options.agentClass) {
626 self.agentClass = options.agentClass
627 } else if (options.forever) {
628 self.agentClass = protocol === 'http:' ? ForeverAgent : ForeverAgent.SSL
629 } else {
630 self.agentClass = self.httpModule.Agent
631 }
632 }
633
634 if (self.pool === false) {
635 self.agent = false
636 } else {
637 self.agent = self.agent || self.getAgent()
638 if (self.maxSockets) {
639 // Don't use our pooling if node has the refactored client
640 self.agent.maxSockets = self.maxSockets
641 }
642 if (self.pool.maxSockets) {
643 // Don't use our pooling if node has the refactored client
644 self.agent.maxSockets = self.pool.maxSockets
645 }
646 }
647
648 self.on('pipe', function (src) {
649 if (self.ntick && self._started) {
650 throw new Error('You cannot pipe to this stream after the outbound request has started.')
651 }
652 self.src = src
653 if (isReadStream(src)) {
654 if (!self.hasHeader('content-type')) {
655 self.setHeader('content-type', mime.lookup(src.path))
656 }
657 } else {
658 if (src.headers) {
659 for (var i in src.headers) {
660 if (!self.hasHeader(i)) {
661 self.setHeader(i, src.headers[i])
662 }
663 }
664 }
665 if (self._json && !self.hasHeader('content-type')) {
666 self.setHeader('content-type', 'application/json')
667 }
668 if (src.method && !self.explicitMethod) {
669 self.method = src.method
670 }
671 }
672
673 // self.on('pipe', function () {
674 // console.error('You have already piped to this stream. Pipeing twice is likely to break the request.')
675 // })
676 })
677
678 defer(function () {
679 if (self._aborted) {
680 return
681 }
682
683 var end = function () {
684 if (self._form) {
685 self._form.pipe(self)
686 }
687 if (self.body) {
688 if (Array.isArray(self.body)) {
689 self.body.forEach(function (part) {
690 self.write(part)
691 })
692 } else {
693 self.write(self.body)
694 }
695 self.end()
696 } else if (self.requestBodyStream) {
697 console.warn('options.requestBodyStream is deprecated, please pass the request object to stream.pipe.')
698 self.requestBodyStream.pipe(self)
699 } else if (!self.src) {
700 if (self.method !== 'GET' && typeof self.method !== 'undefined') {
701 self.setHeader('content-length', 0)
702 }
703 self.end()
704 }
705 }
706
707 if (self._form && !self.hasHeader('content-length')) {
708 // Before ending the request, we had to compute the length of the whole form, asyncly
709 self.setHeader(self._form.getHeaders())
710 self._form.getLength(function (err, length) {
711 if (!err) {
712 self.setHeader('content-length', length)
713 }
714 end()
715 })
716 } else {
717 end()
718 }
719
720 self.ntick = true
721 })
722
723}
724
725// Must call this when following a redirect from https to http or vice versa
726// Attempts to keep everything as identical as possible, but update the
727// httpModule, Tunneling agent, and/or Forever Agent in use.
728Request.prototype._updateProtocol = function () {
729 var self = this
730 var protocol = self.uri.protocol
731
732 if (protocol === 'https:' || self.tunnel) {
733 // previously was doing http, now doing https
734 // if it's https, then we might need to tunnel now.
735 if (self.proxy) {
736 if (self.setupTunnel()) {
737 return
738 }
739 }
740
741 self.httpModule = https
742 switch (self.agentClass) {
743 case ForeverAgent:
744 self.agentClass = ForeverAgent.SSL
745 break
746 case http.Agent:
747 self.agentClass = https.Agent
748 break
749 default:
750 // nothing we can do. Just hope for the best.
751 return
752 }
753
754 // if there's an agent, we need to get a new one.
755 if (self.agent) {
756 self.agent = self.getAgent()
757 }
758
759 } else {
760 // previously was doing https, now doing http
761 self.httpModule = http
762 switch (self.agentClass) {
763 case ForeverAgent.SSL:
764 self.agentClass = ForeverAgent
765 break
766 case https.Agent:
767 self.agentClass = http.Agent
768 break
769 default:
770 // nothing we can do. just hope for the best
771 return
772 }
773
774 // if there's an agent, then get a new one.
775 if (self.agent) {
776 self.agent = null
777 self.agent = self.getAgent()
778 }
779 }
780}
781
782Request.prototype.getAgent = function () {
783 var self = this
784 var Agent = self.agentClass
785 var options = {}
786 if (self.agentOptions) {
787 for (var i in self.agentOptions) {
788 options[i] = self.agentOptions[i]
789 }
790 }
791 if (self.ca) {
792 options.ca = self.ca
793 }
794 if (self.ciphers) {
795 options.ciphers = self.ciphers
796 }
797 if (self.secureProtocol) {
798 options.secureProtocol = self.secureProtocol
799 }
800 if (self.secureOptions) {
801 options.secureOptions = self.secureOptions
802 }
803 if (typeof self.rejectUnauthorized !== 'undefined') {
804 options.rejectUnauthorized = self.rejectUnauthorized
805 }
806
807 if (self.cert && self.key) {
808 options.key = self.key
809 options.cert = self.cert
810 }
811
812 var poolKey = ''
813
814 // different types of agents are in different pools
815 if (Agent !== self.httpModule.Agent) {
816 poolKey += Agent.name
817 }
818
819 if (!self.httpModule.globalAgent) {
820 // node 0.4.x
821 options.host = self.host
822 options.port = self.port
823 if (poolKey) {
824 poolKey += ':'
825 }
826 poolKey += self.host + ':' + self.port
827 }
828
829 // ca option is only relevant if proxy or destination are https
830 var proxy = self.proxy
831 if (typeof proxy === 'string') {
832 proxy = url.parse(proxy)
833 }
834 var isHttps = (proxy && proxy.protocol === 'https:') || this.uri.protocol === 'https:'
835
836 if (isHttps) {
837 if (options.ca) {
838 if (poolKey) {
839 poolKey += ':'
840 }
841 poolKey += options.ca
842 }
843
844 if (typeof options.rejectUnauthorized !== 'undefined') {
845 if (poolKey) {
846 poolKey += ':'
847 }
848 poolKey += options.rejectUnauthorized
849 }
850
851 if (options.cert) {
852 poolKey += options.cert.toString('ascii') + options.key.toString('ascii')
853 }
854
855 if (options.ciphers) {
856 if (poolKey) {
857 poolKey += ':'
858 }
859 poolKey += options.ciphers
860 }
861
862 if (options.secureProtocol) {
863 if (poolKey) {
864 poolKey += ':'
865 }
866 poolKey += options.secureProtocol
867 }
868
869 if (options.secureOptions) {
870 if (poolKey) {
871 poolKey += ':'
872 }
873 poolKey += options.secureOptions
874 }
875 }
876
877 if (self.pool === globalPool && !poolKey && Object.keys(options).length === 0 && self.httpModule.globalAgent) {
878 // not doing anything special. Use the globalAgent
879 return self.httpModule.globalAgent
880 }
881
882 // we're using a stored agent. Make sure it's protocol-specific
883 poolKey = self.uri.protocol + poolKey
884
885 // generate a new agent for this setting if none yet exists
886 if (!self.pool[poolKey]) {
887 self.pool[poolKey] = new Agent(options)
888 }
889
890 return self.pool[poolKey]
891}
892
893Request.prototype.start = function () {
894 // start() is called once we are ready to send the outgoing HTTP request.
895 // this is usually called on the first write(), end() or on nextTick()
896 var self = this
897
898 if (self._aborted) {
899 return
900 }
901
902 self._started = true
903 self.method = self.method || 'GET'
904 self.href = self.uri.href
905
906 if (self.src && self.src.stat && self.src.stat.size && !self.hasHeader('content-length')) {
907 self.setHeader('content-length', self.src.stat.size)
908 }
909 if (self._aws) {
910 self.aws(self._aws, true)
911 }
912
913 // We have a method named auth, which is completely different from the http.request
914 // auth option. If we don't remove it, we're gonna have a bad time.
915 var reqOptions = copy(self)
916 delete reqOptions.auth
917
918 debug('make request', self.uri.href)
919 self.req = self.httpModule.request(reqOptions)
920
921 if (self.timeout && !self.timeoutTimer) {
922 self.timeoutTimer = setTimeout(function () {
923 self.abort()
924 var e = new Error('ETIMEDOUT')
925 e.code = 'ETIMEDOUT'
926 self.emit('error', e)
927 }, self.timeout)
928
929 // Set additional timeout on socket - in case if remote
930 // server freeze after sending headers
931 if (self.req.setTimeout) { // only works on node 0.6+
932 self.req.setTimeout(self.timeout, function () {
933 if (self.req) {
934 self.req.abort()
935 var e = new Error('ESOCKETTIMEDOUT')
936 e.code = 'ESOCKETTIMEDOUT'
937 self.emit('error', e)
938 }
939 })
940 }
941 }
942
943 self.req.on('response', self.onRequestResponse.bind(self))
944 self.req.on('error', self.onRequestError.bind(self))
945 self.req.on('drain', function() {
946 self.emit('drain')
947 })
948 self.req.on('socket', function(socket) {
949 self.emit('socket', socket)
950 })
951
952 self.on('end', function() {
953 if ( self.req.connection ) {
954 self.req.connection.removeListener('error', connectionErrorHandler)
955 }
956 })
957 self.emit('request', self.req)
958}
959
960Request.prototype.onRequestError = function (error) {
961 var self = this
962 if (self._aborted) {
963 return
964 }
965 if (self.req && self.req._reusedSocket && error.code === 'ECONNRESET'
966 && self.agent.addRequestNoreuse) {
967 self.agent = { addRequest: self.agent.addRequestNoreuse.bind(self.agent) }
968 self.start()
969 self.req.end()
970 return
971 }
972 if (self.timeout && self.timeoutTimer) {
973 clearTimeout(self.timeoutTimer)
974 self.timeoutTimer = null
975 }
976 self.emit('error', error)
977}
978
979Request.prototype.onRequestResponse = function (response) {
980 var self = this
981 debug('onRequestResponse', self.uri.href, response.statusCode, response.headers)
982 response.on('end', function() {
983 debug('response end', self.uri.href, response.statusCode, response.headers)
984 })
985
986 // The check on response.connection is a workaround for browserify.
987 if (response.connection && response.connection.listeners('error').indexOf(connectionErrorHandler) === -1) {
988 response.connection.setMaxListeners(0)
989 response.connection.once('error', connectionErrorHandler)
990 }
991 if (self._aborted) {
992 debug('aborted', self.uri.href)
993 response.resume()
994 return
995 }
996 if (self._paused) {
997 response.pause()
998 } else if (response.resume) {
999 // response.resume should be defined, but check anyway before calling. Workaround for browserify.
1000 response.resume()
1001 }
1002
1003 self.response = response
1004 response.request = self
1005 response.toJSON = responseToJSON
1006
1007 // XXX This is different on 0.10, because SSL is strict by default
1008 if (self.httpModule === https &&
1009 self.strictSSL && (!response.hasOwnProperty('client') ||
1010 !response.client.authorized)) {
1011 debug('strict ssl error', self.uri.href)
1012 var sslErr = response.hasOwnProperty('client') ? response.client.authorizationError : self.uri.href + ' does not support SSL'
1013 self.emit('error', new Error('SSL Error: ' + sslErr))
1014 return
1015 }
1016
1017 // Save the original host before any redirect (if it changes, we need to
1018 // remove any authorization headers)
1019 self.originalHost = self.headers.host
1020 if (self.setHost) {
1021 self.removeHeader('host')
1022 }
1023 if (self.timeout && self.timeoutTimer) {
1024 clearTimeout(self.timeoutTimer)
1025 self.timeoutTimer = null
1026 }
1027
1028 var targetCookieJar = (self._jar && self._jar.setCookie) ? self._jar : globalCookieJar
1029 var addCookie = function (cookie) {
1030 //set the cookie if it's domain in the href's domain.
1031 try {
1032 targetCookieJar.setCookie(cookie, self.uri.href, {ignoreError: true})
1033 } catch (e) {
1034 self.emit('error', e)
1035 }
1036 }
1037
1038 response.caseless = caseless(response.headers)
1039
1040 if (response.caseless.has('set-cookie') && (!self._disableCookies)) {
1041 var headerName = response.caseless.has('set-cookie')
1042 if (Array.isArray(response.headers[headerName])) {
1043 response.headers[headerName].forEach(addCookie)
1044 } else {
1045 addCookie(response.headers[headerName])
1046 }
1047 }
1048
1049 var redirectTo = null
1050 if (response.statusCode >= 300 && response.statusCode < 400 && response.caseless.has('location')) {
1051 var location = response.caseless.get('location')
1052 debug('redirect', location)
1053
1054 if (self.followAllRedirects) {
1055 redirectTo = location
1056 } else if (self.followRedirects) {
1057 switch (self.method) {
1058 case 'PATCH':
1059 case 'PUT':
1060 case 'POST':
1061 case 'DELETE':
1062 // Do not follow redirects
1063 break
1064 default:
1065 redirectTo = location
1066 break
1067 }
1068 }
1069 } else if (response.statusCode === 401 && self._hasAuth && !self._sentAuth) {
1070 var authHeader = response.caseless.get('www-authenticate')
1071 var authVerb = authHeader && authHeader.split(' ')[0].toLowerCase()
1072 debug('reauth', authVerb)
1073
1074 switch (authVerb) {
1075 case 'basic':
1076 self.auth(self._user, self._pass, true)
1077 redirectTo = self.uri
1078 break
1079
1080 case 'bearer':
1081 self.auth(null, null, true, self._bearer)
1082 redirectTo = self.uri
1083 break
1084
1085 case 'digest':
1086 // TODO: More complete implementation of RFC 2617.
1087 // - check challenge.algorithm
1088 // - support algorithm="MD5-sess"
1089 // - handle challenge.domain
1090 // - support qop="auth-int" only
1091 // - handle Authentication-Info (not necessarily?)
1092 // - check challenge.stale (not necessarily?)
1093 // - increase nc (not necessarily?)
1094 // For reference:
1095 // http://tools.ietf.org/html/rfc2617#section-3
1096 // https://github.com/bagder/curl/blob/master/lib/http_digest.c
1097
1098 var challenge = {}
1099 var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
1100 for (;;) {
1101 var match = re.exec(authHeader)
1102 if (!match) {
1103 break
1104 }
1105 challenge[match[1]] = match[2] || match[3]
1106 }
1107
1108 var ha1 = md5(self._user + ':' + challenge.realm + ':' + self._pass)
1109 var ha2 = md5(self.method + ':' + self.uri.path)
1110 var qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop) && 'auth'
1111 var nc = qop && '00000001'
1112 var cnonce = qop && uuid().replace(/-/g, '')
1113 var digestResponse = qop ? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2) : md5(ha1 + ':' + challenge.nonce + ':' + ha2)
1114 var authValues = {
1115 username: self._user,
1116 realm: challenge.realm,
1117 nonce: challenge.nonce,
1118 uri: self.uri.path,
1119 qop: qop,
1120 response: digestResponse,
1121 nc: nc,
1122 cnonce: cnonce,
1123 algorithm: challenge.algorithm,
1124 opaque: challenge.opaque
1125 }
1126
1127 authHeader = []
1128 for (var k in authValues) {
1129 if (authValues[k]) {
1130 if (k === 'qop' || k === 'nc' || k === 'algorithm') {
1131 authHeader.push(k + '=' + authValues[k])
1132 } else {
1133 authHeader.push(k + '="' + authValues[k] + '"')
1134 }
1135 }
1136 }
1137 authHeader = 'Digest ' + authHeader.join(', ')
1138 self.setHeader('authorization', authHeader)
1139 self._sentAuth = true
1140
1141 redirectTo = self.uri
1142 break
1143 }
1144 }
1145
1146 if (redirectTo && self.allowRedirect.call(self, response)) {
1147 debug('redirect to', redirectTo)
1148
1149 // ignore any potential response body. it cannot possibly be useful
1150 // to us at this point.
1151 if (self._paused) {
1152 response.resume()
1153 }
1154
1155 if (self._redirectsFollowed >= self.maxRedirects) {
1156 self.emit('error', new Error('Exceeded maxRedirects. Probably stuck in a redirect loop ' + self.uri.href))
1157 return
1158 }
1159 self._redirectsFollowed += 1
1160
1161 if (!isUrl.test(redirectTo)) {
1162 redirectTo = url.resolve(self.uri.href, redirectTo)
1163 }
1164
1165 var uriPrev = self.uri
1166 self.uri = url.parse(redirectTo)
1167
1168 // handle the case where we change protocol from https to http or vice versa
1169 if (self.uri.protocol !== uriPrev.protocol) {
1170 self._updateProtocol()
1171 }
1172
1173 self.redirects.push(
1174 { statusCode : response.statusCode
1175 , redirectUri: redirectTo
1176 }
1177 )
1178 if (self.followAllRedirects && response.statusCode !== 401 && response.statusCode !== 307) {
1179 self.method = 'GET'
1180 }
1181 // self.method = 'GET' // Force all redirects to use GET || commented out fixes #215
1182 delete self.src
1183 delete self.req
1184 delete self.agent
1185 delete self._started
1186 if (response.statusCode !== 401 && response.statusCode !== 307) {
1187 // Remove parameters from the previous response, unless this is the second request
1188 // for a server that requires digest authentication.
1189 delete self.body
1190 delete self._form
1191 if (self.headers) {
1192 self.removeHeader('host')
1193 self.removeHeader('content-type')
1194 self.removeHeader('content-length')
1195 if (self.uri.hostname !== self.originalHost.split(':')[0]) {
1196 // Remove authorization if changing hostnames (but not if just
1197 // changing ports or protocols). This matches the behavior of curl:
1198 // https://github.com/bagder/curl/blob/6beb0eee/lib/http.c#L710
1199 self.removeHeader('authorization')
1200 }
1201 }
1202 }
1203
1204 self.emit('redirect')
1205
1206 self.init()
1207 return // Ignore the rest of the response
1208 } else {
1209 self._redirectsFollowed = self._redirectsFollowed || 0
1210 // Be a good stream and emit end when the response is finished.
1211 // Hack to emit end on close because of a core bug that never fires end
1212 response.on('close', function () {
1213 if (!self._ended) {
1214 self.response.emit('end')
1215 }
1216 })
1217
1218 response.on('end', function () {
1219 self._ended = true
1220 })
1221
1222 var dataStream
1223 if (self.gzip) {
1224 var contentEncoding = response.headers['content-encoding'] || 'identity'
1225 contentEncoding = contentEncoding.trim().toLowerCase()
1226
1227 if (contentEncoding === 'gzip') {
1228 dataStream = zlib.createGunzip()
1229 response.pipe(dataStream)
1230 } else {
1231 // Since previous versions didn't check for Content-Encoding header,
1232 // ignore any invalid values to preserve backwards-compatibility
1233 if (contentEncoding !== 'identity') {
1234 debug('ignoring unrecognized Content-Encoding ' + contentEncoding)
1235 }
1236 dataStream = response
1237 }
1238 } else {
1239 dataStream = response
1240 }
1241
1242 if (self.encoding) {
1243 if (self.dests.length !== 0) {
1244 console.error('Ignoring encoding parameter as this stream is being piped to another stream which makes the encoding option invalid.')
1245 } else if (dataStream.setEncoding) {
1246 dataStream.setEncoding(self.encoding)
1247 } else {
1248 // Should only occur on node pre-v0.9.4 (joyent/node@9b5abe5) with
1249 // zlib streams.
1250 // If/When support for 0.9.4 is dropped, this should be unnecessary.
1251 dataStream = dataStream.pipe(stringstream(self.encoding))
1252 }
1253 }
1254
1255 self.emit('response', response)
1256
1257 self.dests.forEach(function (dest) {
1258 self.pipeDest(dest)
1259 })
1260
1261 dataStream.on('data', function (chunk) {
1262 self._destdata = true
1263 self.emit('data', chunk)
1264 })
1265 dataStream.on('end', function (chunk) {
1266 self.emit('end', chunk)
1267 })
1268 dataStream.on('error', function (error) {
1269 self.emit('error', error)
1270 })
1271 dataStream.on('close', function () {self.emit('close')})
1272
1273 if (self.callback) {
1274 var buffer = bl()
1275 , strings = []
1276
1277 self.on('data', function (chunk) {
1278 if (Buffer.isBuffer(chunk)) {
1279 buffer.append(chunk)
1280 } else {
1281 strings.push(chunk)
1282 }
1283 })
1284 self.on('end', function () {
1285 debug('end event', self.uri.href)
1286 if (self._aborted) {
1287 debug('aborted', self.uri.href)
1288 return
1289 }
1290
1291 if (buffer.length) {
1292 debug('has body', self.uri.href, buffer.length)
1293 if (self.encoding === null) {
1294 // response.body = buffer
1295 // can't move to this until https://github.com/rvagg/bl/issues/13
1296 response.body = buffer.slice()
1297 } else {
1298 response.body = buffer.toString(self.encoding)
1299 }
1300 } else if (strings.length) {
1301 // The UTF8 BOM [0xEF,0xBB,0xBF] is converted to [0xFE,0xFF] in the JS UTC16/UCS2 representation.
1302 // Strip this value out when the encoding is set to 'utf8', as upstream consumers won't expect it and it breaks JSON.parse().
1303 if (self.encoding === 'utf8' && strings[0].length > 0 && strings[0][0] === '\uFEFF') {
1304 strings[0] = strings[0].substring(1)
1305 }
1306 response.body = strings.join('')
1307 }
1308
1309 if (self._json) {
1310 try {
1311 response.body = JSON.parse(response.body)
1312 } catch (e) {}
1313 }
1314 debug('emitting complete', self.uri.href)
1315 if(typeof response.body === 'undefined' && !self._json) {
1316 response.body = ''
1317 }
1318 self.emit('complete', response, response.body)
1319 })
1320 }
1321 //if no callback
1322 else{
1323 self.on('end', function () {
1324 if (self._aborted) {
1325 debug('aborted', self.uri.href)
1326 return
1327 }
1328 self.emit('complete', response)
1329 })
1330 }
1331 }
1332 debug('finish init function', self.uri.href)
1333}
1334
1335Request.prototype.abort = function () {
1336 var self = this
1337 self._aborted = true
1338
1339 if (self.req) {
1340 self.req.abort()
1341 }
1342 else if (self.response) {
1343 self.response.abort()
1344 }
1345
1346 self.emit('abort')
1347}
1348
1349Request.prototype.pipeDest = function (dest) {
1350 var self = this
1351 var response = self.response
1352 // Called after the response is received
1353 if (dest.headers && !dest.headersSent) {
1354 if (response.caseless.has('content-type')) {
1355 var ctname = response.caseless.has('content-type')
1356 if (dest.setHeader) {
1357 dest.setHeader(ctname, response.headers[ctname])
1358 }
1359 else {
1360 dest.headers[ctname] = response.headers[ctname]
1361 }
1362 }
1363
1364 if (response.caseless.has('content-length')) {
1365 var clname = response.caseless.has('content-length')
1366 if (dest.setHeader) {
1367 dest.setHeader(clname, response.headers[clname])
1368 } else {
1369 dest.headers[clname] = response.headers[clname]
1370 }
1371 }
1372 }
1373 if (dest.setHeader && !dest.headersSent) {
1374 for (var i in response.headers) {
1375 // If the response content is being decoded, the Content-Encoding header
1376 // of the response doesn't represent the piped content, so don't pass it.
1377 if (!self.gzip || i !== 'content-encoding') {
1378 dest.setHeader(i, response.headers[i])
1379 }
1380 }
1381 dest.statusCode = response.statusCode
1382 }
1383 if (self.pipefilter) {
1384 self.pipefilter(response, dest)
1385 }
1386}
1387
1388Request.prototype.qs = function (q, clobber) {
1389 var self = this
1390 var base
1391 if (!clobber && self.uri.query) {
1392 base = self.qsLib.parse(self.uri.query)
1393 } else {
1394 base = {}
1395 }
1396
1397 for (var i in q) {
1398 base[i] = q[i]
1399 }
1400
1401 if (self.qsLib.stringify(base) === ''){
1402 return self
1403 }
1404
1405 self.uri = url.parse(self.uri.href.split('?')[0] + '?' + self.qsLib.stringify(base))
1406 self.url = self.uri
1407 self.path = self.uri.path
1408
1409 return self
1410}
1411Request.prototype.form = function (form) {
1412 var self = this
1413 if (form) {
1414 self.setHeader('content-type', 'application/x-www-form-urlencoded')
1415 self.body = (typeof form === 'string') ? form.toString('utf8') : self.qsLib.stringify(form).toString('utf8')
1416 return self
1417 }
1418 // create form-data object
1419 self._form = new FormData()
1420 return self._form
1421}
1422Request.prototype.multipart = function (multipart) {
1423 var self = this
1424 self.body = []
1425
1426 if (!self.hasHeader('content-type')) {
1427 self.setHeader('content-type', 'multipart/related; boundary=' + self.boundary)
1428 } else {
1429 var headerName = self.hasHeader('content-type')
1430 self.setHeader(headerName, self.headers[headerName].split(';')[0] + '; boundary=' + self.boundary)
1431 }
1432
1433 if (!multipart.forEach) {
1434 throw new Error('Argument error, options.multipart.')
1435 }
1436
1437 if (self.preambleCRLF) {
1438 self.body.push(new Buffer('\r\n'))
1439 }
1440
1441 multipart.forEach(function (part) {
1442 var body = part.body
1443 if(typeof body === 'undefined') {
1444 throw new Error('Body attribute missing in multipart.')
1445 }
1446 delete part.body
1447 var preamble = '--' + self.boundary + '\r\n'
1448 Object.keys(part).forEach(function (key) {
1449 preamble += key + ': ' + part[key] + '\r\n'
1450 })
1451 preamble += '\r\n'
1452 self.body.push(new Buffer(preamble))
1453 self.body.push(new Buffer(body))
1454 self.body.push(new Buffer('\r\n'))
1455 })
1456 self.body.push(new Buffer('--' + self.boundary + '--'))
1457
1458 if (self.postambleCRLF) {
1459 self.body.push(new Buffer('\r\n'))
1460 }
1461
1462 return self
1463}
1464Request.prototype.json = function (val) {
1465 var self = this
1466
1467 if (!self.hasHeader('accept')) {
1468 self.setHeader('accept', 'application/json')
1469 }
1470
1471 self._json = true
1472 if (typeof val === 'boolean') {
1473 if (typeof self.body === 'object') {
1474 self.body = safeStringify(self.body)
1475 if (!self.hasHeader('content-type')) {
1476 self.setHeader('content-type', 'application/json')
1477 }
1478 }
1479 } else {
1480 self.body = safeStringify(val)
1481 if (!self.hasHeader('content-type')) {
1482 self.setHeader('content-type', 'application/json')
1483 }
1484 }
1485
1486 return self
1487}
1488Request.prototype.getHeader = function (name, headers) {
1489 var self = this
1490 var result, re, match
1491 if (!headers) {
1492 headers = self.headers
1493 }
1494 Object.keys(headers).forEach(function (key) {
1495 if (key.length !== name.length) {
1496 return
1497 }
1498 re = new RegExp(name, 'i')
1499 match = key.match(re)
1500 if (match) {
1501 result = headers[key]
1502 }
1503 })
1504 return result
1505}
1506var getHeader = Request.prototype.getHeader
1507
1508Request.prototype.auth = function (user, pass, sendImmediately, bearer) {
1509 var self = this
1510 if (bearer !== undefined) {
1511 self._bearer = bearer
1512 self._hasAuth = true
1513 if (sendImmediately || typeof sendImmediately === 'undefined') {
1514 if (typeof bearer === 'function') {
1515 bearer = bearer()
1516 }
1517 self.setHeader('authorization', 'Bearer ' + bearer)
1518 self._sentAuth = true
1519 }
1520 return self
1521 }
1522 if (typeof user !== 'string' || (pass !== undefined && typeof pass !== 'string')) {
1523 throw new Error('auth() received invalid user or password')
1524 }
1525 self._user = user
1526 self._pass = pass
1527 self._hasAuth = true
1528 var header = typeof pass !== 'undefined' ? user + ':' + pass : user
1529 if (sendImmediately || typeof sendImmediately === 'undefined') {
1530 self.setHeader('authorization', 'Basic ' + toBase64(header))
1531 self._sentAuth = true
1532 }
1533 return self
1534}
1535
1536Request.prototype.aws = function (opts, now) {
1537 var self = this
1538
1539 if (!now) {
1540 self._aws = opts
1541 return self
1542 }
1543 var date = new Date()
1544 self.setHeader('date', date.toUTCString())
1545 var auth =
1546 { key: opts.key
1547 , secret: opts.secret
1548 , verb: self.method.toUpperCase()
1549 , date: date
1550 , contentType: self.getHeader('content-type') || ''
1551 , md5: self.getHeader('content-md5') || ''
1552 , amazonHeaders: aws.canonicalizeHeaders(self.headers)
1553 }
1554 var path = self.uri.path
1555 if (opts.bucket && path) {
1556 auth.resource = '/' + opts.bucket + path
1557 } else if (opts.bucket && !path) {
1558 auth.resource = '/' + opts.bucket
1559 } else if (!opts.bucket && path) {
1560 auth.resource = path
1561 } else if (!opts.bucket && !path) {
1562 auth.resource = '/'
1563 }
1564 auth.resource = aws.canonicalizeResource(auth.resource)
1565 self.setHeader('authorization', aws.authorization(auth))
1566
1567 return self
1568}
1569Request.prototype.httpSignature = function (opts) {
1570 var self = this
1571 httpSignature.signRequest({
1572 getHeader: function(header) {
1573 return getHeader(header, self.headers)
1574 },
1575 setHeader: function(header, value) {
1576 self.setHeader(header, value)
1577 },
1578 method: self.method,
1579 path: self.path
1580 }, opts)
1581 debug('httpSignature authorization', self.getHeader('authorization'))
1582
1583 return self
1584}
1585
1586Request.prototype.hawk = function (opts) {
1587 var self = this
1588 self.setHeader('Authorization', hawk.client.header(self.uri, self.method, opts).field)
1589}
1590
1591Request.prototype.oauth = function (_oauth) {
1592 var self = this
1593 var form, query
1594 if (self.hasHeader('content-type') &&
1595 self.getHeader('content-type').slice(0, 'application/x-www-form-urlencoded'.length) ===
1596 'application/x-www-form-urlencoded'
1597 ) {
1598 form = self.body
1599 }
1600 if (self.uri.query) {
1601 query = self.uri.query
1602 }
1603
1604 var oa = {}
1605 for (var i in _oauth) {
1606 oa['oauth_' + i] = _oauth[i]
1607 }
1608 if ('oauth_realm' in oa) {
1609 delete oa.oauth_realm
1610 }
1611 if (!oa.oauth_version) {
1612 oa.oauth_version = '1.0'
1613 }
1614 if (!oa.oauth_timestamp) {
1615 oa.oauth_timestamp = Math.floor( Date.now() / 1000 ).toString()
1616 }
1617 if (!oa.oauth_nonce) {
1618 oa.oauth_nonce = uuid().replace(/-/g, '')
1619 }
1620
1621 oa.oauth_signature_method = 'HMAC-SHA1'
1622
1623 var consumer_secret = oa.oauth_consumer_secret
1624 delete oa.oauth_consumer_secret
1625 var token_secret = oa.oauth_token_secret
1626 delete oa.oauth_token_secret
1627
1628 var baseurl = self.uri.protocol + '//' + self.uri.host + self.uri.pathname
1629 var params = self.qsLib.parse([].concat(query, form, self.qsLib.stringify(oa)).join('&'))
1630 var signature = oauth.hmacsign(self.method, baseurl, params, consumer_secret, token_secret)
1631
1632 var realm = _oauth.realm ? 'realm="' + _oauth.realm + '",' : ''
1633 var authHeader = 'OAuth ' + realm +
1634 Object.keys(oa).sort().map(function (i) {return i + '="' + oauth.rfc3986(oa[i]) + '"'}).join(',')
1635 authHeader += ',oauth_signature="' + oauth.rfc3986(signature) + '"'
1636 self.setHeader('Authorization', authHeader)
1637 return self
1638}
1639Request.prototype.jar = function (jar) {
1640 var self = this
1641 var cookies
1642
1643 if (self._redirectsFollowed === 0) {
1644 self.originalCookieHeader = self.getHeader('cookie')
1645 }
1646
1647 if (!jar) {
1648 // disable cookies
1649 cookies = false
1650 self._disableCookies = true
1651 } else {
1652 var targetCookieJar = (jar && jar.getCookieString) ? jar : globalCookieJar
1653 var urihref = self.uri.href
1654 //fetch cookie in the Specified host
1655 if (targetCookieJar) {
1656 cookies = targetCookieJar.getCookieString(urihref)
1657 }
1658 }
1659
1660 //if need cookie and cookie is not empty
1661 if (cookies && cookies.length) {
1662 if (self.originalCookieHeader) {
1663 // Don't overwrite existing Cookie header
1664 self.setHeader('cookie', self.originalCookieHeader + '; ' + cookies)
1665 } else {
1666 self.setHeader('cookie', cookies)
1667 }
1668 }
1669 self._jar = jar
1670 return self
1671}
1672
1673
1674// Stream API
1675Request.prototype.pipe = function (dest, opts) {
1676 var self = this
1677
1678 if (self.response) {
1679 if (self._destdata) {
1680 throw new Error('You cannot pipe after data has been emitted from the response.')
1681 } else if (self._ended) {
1682 throw new Error('You cannot pipe after the response has been ended.')
1683 } else {
1684 stream.Stream.prototype.pipe.call(self, dest, opts)
1685 self.pipeDest(dest)
1686 return dest
1687 }
1688 } else {
1689 self.dests.push(dest)
1690 stream.Stream.prototype.pipe.call(self, dest, opts)
1691 return dest
1692 }
1693}
1694Request.prototype.write = function () {
1695 var self = this
1696 if (!self._started) {
1697 self.start()
1698 }
1699 return self.req.write.apply(self.req, arguments)
1700}
1701Request.prototype.end = function (chunk) {
1702 var self = this
1703 if (chunk) {
1704 self.write(chunk)
1705 }
1706 if (!self._started) {
1707 self.start()
1708 }
1709 self.req.end()
1710}
1711Request.prototype.pause = function () {
1712 var self = this
1713 if (!self.response) {
1714 self._paused = true
1715 } else {
1716 self.response.pause.apply(self.response, arguments)
1717 }
1718}
1719Request.prototype.resume = function () {
1720 var self = this
1721 if (!self.response) {
1722 self._paused = false
1723 } else {
1724 self.response.resume.apply(self.response, arguments)
1725 }
1726}
1727Request.prototype.destroy = function () {
1728 var self = this
1729 if (!self._ended) {
1730 self.end()
1731 } else if (self.response) {
1732 self.response.destroy()
1733 }
1734}
1735
1736Request.defaultProxyHeaderWhiteList =
1737 defaultProxyHeaderWhiteList.slice()
1738
1739// Exports
1740
1741Request.prototype.toJSON = requestToJSON
1742module.exports = Request