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