check-response.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. 'use strict'
  2. const config = require('./config.js')
  3. const errors = require('./errors.js')
  4. const LRU = require('lru-cache')
  5. module.exports = checkResponse
  6. function checkResponse (method, res, registry, startTime, opts) {
  7. opts = config(opts)
  8. if (res.headers.has('npm-notice') && !res.headers.has('x-local-cache')) {
  9. opts.log.notice('', res.headers.get('npm-notice'))
  10. }
  11. checkWarnings(res, registry, opts)
  12. if (res.status >= 400) {
  13. logRequest(method, res, startTime, opts)
  14. return checkErrors(method, res, startTime, opts)
  15. } else {
  16. res.body.on('end', () => logRequest(method, res, startTime, opts))
  17. if (opts.ignoreBody) {
  18. res.body.resume()
  19. res.body = null
  20. }
  21. return res
  22. }
  23. }
  24. function logRequest (method, res, startTime, opts) {
  25. const elapsedTime = Date.now() - startTime
  26. const attempt = res.headers.get('x-fetch-attempts')
  27. const attemptStr = attempt && attempt > 1 ? ` attempt #${attempt}` : ''
  28. const cacheStr = res.headers.get('x-local-cache') ? ' (from cache)' : ''
  29. opts.log.http(
  30. 'fetch',
  31. `${method.toUpperCase()} ${res.status} ${res.url} ${elapsedTime}ms${attemptStr}${cacheStr}`
  32. )
  33. }
  34. const WARNING_REGEXP = /^\s*(\d{3})\s+(\S+)\s+"(.*)"\s+"([^"]+)"/
  35. const BAD_HOSTS = new LRU({ max: 50 })
  36. function checkWarnings (res, registry, opts) {
  37. if (res.headers.has('warning') && !BAD_HOSTS.has(registry)) {
  38. const warnings = {}
  39. res.headers.raw()['warning'].forEach(w => {
  40. const match = w.match(WARNING_REGEXP)
  41. if (match) {
  42. warnings[match[1]] = {
  43. code: match[1],
  44. host: match[2],
  45. message: match[3],
  46. date: new Date(match[4])
  47. }
  48. }
  49. })
  50. BAD_HOSTS.set(registry, true)
  51. if (warnings['199']) {
  52. if (warnings['199'].message.match(/ENOTFOUND/)) {
  53. opts.log.warn('registry', `Using stale data from ${registry} because the host is inaccessible -- are you offline?`)
  54. } else {
  55. opts.log.warn('registry', `Unexpected warning for ${registry}: ${warnings['199'].message}`)
  56. }
  57. }
  58. if (warnings['111']) {
  59. // 111 Revalidation failed -- we're using stale data
  60. opts.log.warn(
  61. 'registry',
  62. `Using stale data from ${registry} due to a request error during revalidation.`
  63. )
  64. }
  65. }
  66. }
  67. function checkErrors (method, res, startTime, opts) {
  68. return res.buffer()
  69. .catch(() => null)
  70. .then(body => {
  71. let parsed = body
  72. try {
  73. parsed = JSON.parse(body.toString('utf8'))
  74. } catch (e) {}
  75. if (res.status === 401 && res.headers.get('www-authenticate')) {
  76. const auth = res.headers.get('www-authenticate')
  77. .split(/,\s*/)
  78. .map(s => s.toLowerCase())
  79. if (auth.indexOf('ipaddress') !== -1) {
  80. throw new errors.HttpErrorAuthIPAddress(
  81. method, res, parsed, opts.spec
  82. )
  83. } else if (auth.indexOf('otp') !== -1) {
  84. throw new errors.HttpErrorAuthOTP(
  85. method, res, parsed, opts.spec
  86. )
  87. } else {
  88. throw new errors.HttpErrorAuthUnknown(
  89. method, res, parsed, opts.spec
  90. )
  91. }
  92. } else if (res.status === 401 && body != null && /one-time pass/.test(body.toString('utf8'))) {
  93. // Heuristic for malformed OTP responses that don't include the www-authenticate header.
  94. throw new errors.HttpErrorAuthOTP(
  95. method, res, parsed, opts.spec
  96. )
  97. } else {
  98. throw new errors.HttpErrorGeneral(
  99. method, res, parsed, opts.spec
  100. )
  101. }
  102. })
  103. }