redirect.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. 'use strict'
  2. var url = require('url')
  3. var isUrl = /^https?:/
  4. function Redirect (request) {
  5. this.request = request
  6. this.followRedirect = true
  7. this.followRedirects = true
  8. this.followAllRedirects = false
  9. this.followOriginalHttpMethod = false
  10. this.allowRedirect = function () { return true }
  11. this.maxRedirects = 10
  12. this.redirects = []
  13. this.redirectsFollowed = 0
  14. this.removeRefererHeader = false
  15. }
  16. Redirect.prototype.onRequest = function (options) {
  17. var self = this
  18. if (options.maxRedirects !== undefined) {
  19. self.maxRedirects = options.maxRedirects
  20. }
  21. if (typeof options.followRedirect === 'function') {
  22. self.allowRedirect = options.followRedirect
  23. }
  24. if (options.followRedirect !== undefined) {
  25. self.followRedirects = !!options.followRedirect
  26. }
  27. if (options.followAllRedirects !== undefined) {
  28. self.followAllRedirects = options.followAllRedirects
  29. }
  30. if (self.followRedirects || self.followAllRedirects) {
  31. self.redirects = self.redirects || []
  32. }
  33. if (options.removeRefererHeader !== undefined) {
  34. self.removeRefererHeader = options.removeRefererHeader
  35. }
  36. if (options.followOriginalHttpMethod !== undefined) {
  37. self.followOriginalHttpMethod = options.followOriginalHttpMethod
  38. }
  39. }
  40. Redirect.prototype.redirectTo = function (response) {
  41. var self = this
  42. var request = self.request
  43. var redirectTo = null
  44. if (response.statusCode >= 300 && response.statusCode < 400 && response.caseless.has('location')) {
  45. var location = response.caseless.get('location')
  46. request.debug('redirect', location)
  47. if (self.followAllRedirects) {
  48. redirectTo = location
  49. } else if (self.followRedirects) {
  50. switch (request.method) {
  51. case 'PATCH':
  52. case 'PUT':
  53. case 'POST':
  54. case 'DELETE':
  55. // Do not follow redirects
  56. break
  57. default:
  58. redirectTo = location
  59. break
  60. }
  61. }
  62. } else if (response.statusCode === 401) {
  63. var authHeader = request._auth.onResponse(response)
  64. if (authHeader) {
  65. request.setHeader('authorization', authHeader)
  66. redirectTo = request.uri
  67. }
  68. }
  69. return redirectTo
  70. }
  71. Redirect.prototype.onResponse = function (response) {
  72. var self = this
  73. var request = self.request
  74. var redirectTo = self.redirectTo(response)
  75. if (!redirectTo || !self.allowRedirect.call(request, response)) {
  76. return false
  77. }
  78. request.debug('redirect to', redirectTo)
  79. // ignore any potential response body. it cannot possibly be useful
  80. // to us at this point.
  81. // response.resume should be defined, but check anyway before calling. Workaround for browserify.
  82. if (response.resume) {
  83. response.resume()
  84. }
  85. if (self.redirectsFollowed >= self.maxRedirects) {
  86. request.emit('error', new Error('Exceeded maxRedirects. Probably stuck in a redirect loop ' + request.uri.href))
  87. return false
  88. }
  89. self.redirectsFollowed += 1
  90. if (!isUrl.test(redirectTo)) {
  91. redirectTo = url.resolve(request.uri.href, redirectTo)
  92. }
  93. var uriPrev = request.uri
  94. request.uri = url.parse(redirectTo)
  95. // handle the case where we change protocol from https to http or vice versa
  96. if (request.uri.protocol !== uriPrev.protocol) {
  97. delete request.agent
  98. }
  99. self.redirects.push({ statusCode: response.statusCode, redirectUri: redirectTo })
  100. if (self.followAllRedirects && request.method !== 'HEAD' &&
  101. response.statusCode !== 401 && response.statusCode !== 307) {
  102. request.method = self.followOriginalHttpMethod ? request.method : 'GET'
  103. }
  104. // request.method = 'GET' // Force all redirects to use GET || commented out fixes #215
  105. delete request.src
  106. delete request.req
  107. delete request._started
  108. if (response.statusCode !== 401 && response.statusCode !== 307) {
  109. // Remove parameters from the previous response, unless this is the second request
  110. // for a server that requires digest authentication.
  111. delete request.body
  112. delete request._form
  113. if (request.headers) {
  114. request.removeHeader('host')
  115. request.removeHeader('content-type')
  116. request.removeHeader('content-length')
  117. if (request.uri.hostname !== request.originalHost.split(':')[0]) {
  118. // Remove authorization if changing hostnames (but not if just
  119. // changing ports or protocols). This matches the behavior of curl:
  120. // https://github.com/bagder/curl/blob/6beb0eee/lib/http.c#L710
  121. request.removeHeader('authorization')
  122. }
  123. }
  124. }
  125. if (!self.removeRefererHeader) {
  126. request.setHeader('referer', uriPrev.href)
  127. }
  128. request.emit('redirect')
  129. request.init()
  130. return true
  131. }
  132. exports.Redirect = Redirect