@verdaccio/proxy 6.0.0-6-next.20 → 6.0.0-6-next.21
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/CHANGELOG.md +37 -0
- package/build/agent.d.ts +17 -0
- package/build/agent.js +63 -0
- package/build/agent.js.map +1 -0
- package/build/index.d.ts +1 -1
- package/build/index.js +4 -4
- package/build/index.js.map +1 -1
- package/build/proxy-utils.js.map +1 -1
- package/build/{up-storage.d.ts → proxy.d.ts} +28 -54
- package/build/proxy.js +565 -0
- package/build/proxy.js.map +1 -0
- package/jest.config.js +4 -5
- package/package.json +66 -67
- package/src/agent.ts +54 -0
- package/src/index.ts +1 -1
- package/src/{up-storage.ts → proxy.ts} +248 -352
- package/test/headers.auth.spec.ts +15 -18
- package/test/noProxy.spec.ts +215 -76
- package/test/partials/ssl-cert-snakeoil.key +15 -0
- package/test/partials/ssl-cert-snakeoil.pem +12 -0
- package/test/proxy.error.___.ts +126 -0
- package/test/proxy.metadata.spec.ts +354 -0
- package/test/proxy.search.spec.ts +1 -7
- package/test/proxy.tarball.spec.ts +142 -204
- package/tsconfig.json +0 -6
- package/build/up-storage.js +0 -691
- package/build/up-storage.js.map +0 -1
- package/test/proxy.error.spec.ts +0 -126
- package/test/proxy.metadata.ts +0 -133
package/build/up-storage.js
DELETED
|
@@ -1,691 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.ProxyStorage = void 0;
|
|
7
|
-
|
|
8
|
-
var _JSONStream = _interopRequireDefault(require("JSONStream"));
|
|
9
|
-
|
|
10
|
-
var _debug = _interopRequireDefault(require("debug"));
|
|
11
|
-
|
|
12
|
-
var _lodash = _interopRequireDefault(require("lodash"));
|
|
13
|
-
|
|
14
|
-
var _request = _interopRequireDefault(require("request"));
|
|
15
|
-
|
|
16
|
-
var _stream = _interopRequireWildcard(require("stream"));
|
|
17
|
-
|
|
18
|
-
var _undici = require("undici");
|
|
19
|
-
|
|
20
|
-
var _url = require("url");
|
|
21
|
-
|
|
22
|
-
var _core = require("@verdaccio/core");
|
|
23
|
-
|
|
24
|
-
var _streams = require("@verdaccio/streams");
|
|
25
|
-
|
|
26
|
-
var _utils = require("@verdaccio/utils");
|
|
27
|
-
|
|
28
|
-
var _proxyUtils = require("./proxy-utils");
|
|
29
|
-
|
|
30
|
-
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
31
|
-
|
|
32
|
-
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
33
|
-
|
|
34
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
35
|
-
|
|
36
|
-
/* global AbortController */
|
|
37
|
-
const LoggerApi = require('@verdaccio/logger');
|
|
38
|
-
|
|
39
|
-
const debug = (0, _debug.default)('verdaccio:proxy');
|
|
40
|
-
|
|
41
|
-
const encode = function (thing) {
|
|
42
|
-
return encodeURIComponent(thing).replace(/^%40/, '@');
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const jsonContentType = _core.HEADERS.JSON;
|
|
46
|
-
const contentTypeAccept = `${jsonContentType};`;
|
|
47
|
-
/**
|
|
48
|
-
* Just a helper (`config[key] || default` doesn't work because of zeroes)
|
|
49
|
-
*/
|
|
50
|
-
|
|
51
|
-
const setConfig = (config, key, def) => {
|
|
52
|
-
return _lodash.default.isNil(config[key]) === false ? config[key] : def;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Implements Storage interface
|
|
57
|
-
* (same for storage.js, local-storage.js, up-storage.js)
|
|
58
|
-
*/
|
|
59
|
-
class ProxyStorage {
|
|
60
|
-
// FIXME: upname is assigned to each instance
|
|
61
|
-
// @ts-ignore
|
|
62
|
-
// FIXME: proxy can be boolean or object, something smells here
|
|
63
|
-
// @ts-ignore
|
|
64
|
-
// @ts-ignore
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Constructor
|
|
68
|
-
* @param {*} config
|
|
69
|
-
* @param {*} mainConfig
|
|
70
|
-
*/
|
|
71
|
-
constructor(config, mainConfig) {
|
|
72
|
-
this.config = config;
|
|
73
|
-
this.failed_requests = 0;
|
|
74
|
-
this.userAgent = mainConfig.user_agent;
|
|
75
|
-
this.ca = config.ca;
|
|
76
|
-
this.logger = LoggerApi.logger.child({
|
|
77
|
-
sub: 'out'
|
|
78
|
-
});
|
|
79
|
-
this.server_id = mainConfig.server_id;
|
|
80
|
-
this.url = new _url.URL(this.config.url);
|
|
81
|
-
|
|
82
|
-
this._setupProxy(this.url.hostname, config, mainConfig, this.url.protocol === 'https:');
|
|
83
|
-
|
|
84
|
-
this.config.url = this.config.url.replace(/\/$/, '');
|
|
85
|
-
|
|
86
|
-
if (this.config.timeout && Number(this.config.timeout) >= 1000) {
|
|
87
|
-
this.logger.warn(['Too big timeout value: ' + this.config.timeout, 'We changed time format to nginx-like one', '(see http://nginx.org/en/docs/syntax.html)', 'so please update your config accordingly'].join('\n'));
|
|
88
|
-
} // a bunch of different configurable timers
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
this.maxage = (0, _proxyUtils.parseInterval)(setConfig(this.config, 'maxage', '2m'));
|
|
92
|
-
this.timeout = (0, _proxyUtils.parseInterval)(setConfig(this.config, 'timeout', '30s'));
|
|
93
|
-
this.max_fails = Number(setConfig(this.config, 'max_fails', 2));
|
|
94
|
-
this.fail_timeout = (0, _proxyUtils.parseInterval)(setConfig(this.config, 'fail_timeout', '5m'));
|
|
95
|
-
this.strict_ssl = Boolean(setConfig(this.config, 'strict_ssl', true));
|
|
96
|
-
this.agent_options = setConfig(this.config, 'agent_options', {
|
|
97
|
-
keepAlive: true,
|
|
98
|
-
maxSockets: 40,
|
|
99
|
-
maxFreeSockets: 10
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Fetch an asset.
|
|
104
|
-
* @param {*} options
|
|
105
|
-
* @param {*} cb
|
|
106
|
-
* @return {Request}
|
|
107
|
-
*/
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
request(options, cb) {
|
|
111
|
-
let json;
|
|
112
|
-
|
|
113
|
-
if (this._statusCheck() === false) {
|
|
114
|
-
const streamRead = new _stream.default.Readable();
|
|
115
|
-
process.nextTick(function () {
|
|
116
|
-
if (cb) {
|
|
117
|
-
cb(_core.errorUtils.getInternalError(_core.errorUtils.API_ERROR.UPLINK_OFFLINE));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
streamRead.emit('error', _core.errorUtils.getInternalError(_core.errorUtils.API_ERROR.UPLINK_OFFLINE));
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
streamRead._read = function () {}; // preventing 'Uncaught, unspecified "error" event'
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
streamRead.on('error', function () {});
|
|
127
|
-
return streamRead;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const self = this;
|
|
131
|
-
|
|
132
|
-
const headers = this._setHeaders(options);
|
|
133
|
-
|
|
134
|
-
this._addProxyHeaders(options.req, headers);
|
|
135
|
-
|
|
136
|
-
this._overrideWithUpLinkConfLocaligHeaders(headers);
|
|
137
|
-
|
|
138
|
-
const method = options.method || 'GET';
|
|
139
|
-
const uri = options.uri_full || this.config.url + options.uri;
|
|
140
|
-
self.logger.info({
|
|
141
|
-
method: method,
|
|
142
|
-
headers: headers,
|
|
143
|
-
uri: uri
|
|
144
|
-
}, "making request: '@{method} @{uri}'");
|
|
145
|
-
|
|
146
|
-
if (_core.validatioUtils.isObject(options.json)) {
|
|
147
|
-
json = JSON.stringify(options.json);
|
|
148
|
-
headers['Content-Type'] = headers['Content-Type'] || _core.HEADERS.JSON;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const requestCallback = cb ? function (err, res, body) {
|
|
152
|
-
let error;
|
|
153
|
-
const responseLength = err ? 0 : body.length; // $FlowFixMe
|
|
154
|
-
|
|
155
|
-
processBody();
|
|
156
|
-
logActivity(); // $FlowFixMe
|
|
157
|
-
|
|
158
|
-
cb(err, res, body);
|
|
159
|
-
/**
|
|
160
|
-
* Perform a decode.
|
|
161
|
-
*/
|
|
162
|
-
|
|
163
|
-
function processBody() {
|
|
164
|
-
if (err) {
|
|
165
|
-
error = err.message;
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (options.json && res.statusCode < 300) {
|
|
170
|
-
try {
|
|
171
|
-
// $FlowFixMe
|
|
172
|
-
body = JSON.parse(body.toString(_core.CHARACTER_ENCODING.UTF8));
|
|
173
|
-
} catch (_err) {
|
|
174
|
-
body = {};
|
|
175
|
-
err = _err;
|
|
176
|
-
error = err.message;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (!err && _core.validatioUtils.isObject(body)) {
|
|
181
|
-
if (_lodash.default.isString(body.error)) {
|
|
182
|
-
error = body.error;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
/**
|
|
187
|
-
* Perform a log.
|
|
188
|
-
*/
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
function logActivity() {
|
|
192
|
-
let message = "@{!status}, req: '@{request.method} @{request.url}'"; // FIXME: use LOG_VERDACCIO_BYTES
|
|
193
|
-
|
|
194
|
-
message += error ? ', error: @{!error}' : ', bytes: @{bytes.in}/@{bytes.out}';
|
|
195
|
-
self.logger.http({
|
|
196
|
-
// if error is null/false change this to undefined so it wont log
|
|
197
|
-
err: err || undefined,
|
|
198
|
-
request: {
|
|
199
|
-
method: method,
|
|
200
|
-
url: uri
|
|
201
|
-
},
|
|
202
|
-
status: res != null ? res.statusCode : 'ERR',
|
|
203
|
-
error: error,
|
|
204
|
-
bytes: {
|
|
205
|
-
in: json ? json.length : 0,
|
|
206
|
-
out: responseLength || 0
|
|
207
|
-
}
|
|
208
|
-
}, message);
|
|
209
|
-
}
|
|
210
|
-
} : undefined;
|
|
211
|
-
let requestOptions = {
|
|
212
|
-
url: uri,
|
|
213
|
-
method: method,
|
|
214
|
-
headers: headers,
|
|
215
|
-
body: json,
|
|
216
|
-
proxy: this.proxy,
|
|
217
|
-
encoding: null,
|
|
218
|
-
gzip: true,
|
|
219
|
-
timeout: this.timeout,
|
|
220
|
-
strictSSL: this.strict_ssl,
|
|
221
|
-
agentOptions: this.agent_options
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
if (this.ca) {
|
|
225
|
-
requestOptions = Object.assign({}, requestOptions, {
|
|
226
|
-
ca: this.ca
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const req = (0, _request.default)(requestOptions, requestCallback);
|
|
231
|
-
let statusCalled = false;
|
|
232
|
-
req.on('response', function (res) {
|
|
233
|
-
// FIXME: _verdaccio_aborted seems not used
|
|
234
|
-
// @ts-ignore
|
|
235
|
-
if (!req._verdaccio_aborted && !statusCalled) {
|
|
236
|
-
statusCalled = true;
|
|
237
|
-
|
|
238
|
-
self._statusCheck(true);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (_lodash.default.isNil(requestCallback) === false) {
|
|
242
|
-
(function do_log() {
|
|
243
|
-
const message = "@{!status}, req: '@{request.method} @{request.url}' (streaming)";
|
|
244
|
-
self.logger.http({
|
|
245
|
-
request: {
|
|
246
|
-
method: method,
|
|
247
|
-
url: uri
|
|
248
|
-
},
|
|
249
|
-
status: _lodash.default.isNull(res) === false ? res.statusCode : 'ERR'
|
|
250
|
-
}, message);
|
|
251
|
-
})();
|
|
252
|
-
}
|
|
253
|
-
}); // eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
254
|
-
|
|
255
|
-
req.on('error', function (_err) {
|
|
256
|
-
// FIXME: _verdaccio_aborted seems not used
|
|
257
|
-
// @ts-ignore
|
|
258
|
-
if (!req._verdaccio_aborted && !statusCalled) {
|
|
259
|
-
statusCalled = true;
|
|
260
|
-
|
|
261
|
-
self._statusCheck(false);
|
|
262
|
-
}
|
|
263
|
-
}); // @ts-ignore
|
|
264
|
-
|
|
265
|
-
return req;
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Set default headers.
|
|
269
|
-
* @param {Object} options
|
|
270
|
-
* @return {Object}
|
|
271
|
-
* @private
|
|
272
|
-
*/
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
_setHeaders(options) {
|
|
276
|
-
const headers = options.headers || {};
|
|
277
|
-
const accept = _core.HEADERS.ACCEPT;
|
|
278
|
-
const acceptEncoding = _core.HEADERS.ACCEPT_ENCODING;
|
|
279
|
-
const userAgent = _core.HEADERS.USER_AGENT;
|
|
280
|
-
headers[accept] = headers[accept] || contentTypeAccept;
|
|
281
|
-
headers[acceptEncoding] = headers[acceptEncoding] || 'gzip'; // registry.npmjs.org will only return search result if user-agent include string 'npm'
|
|
282
|
-
|
|
283
|
-
headers[userAgent] = headers[userAgent] || `npm (${this.userAgent})`;
|
|
284
|
-
return this._setAuth(headers);
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* Validate configuration auth and assign Header authorization
|
|
288
|
-
* @param {Object} headers
|
|
289
|
-
* @return {Object}
|
|
290
|
-
* @private
|
|
291
|
-
*/
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
_setAuth(headers) {
|
|
295
|
-
const {
|
|
296
|
-
auth
|
|
297
|
-
} = this.config;
|
|
298
|
-
|
|
299
|
-
if (_lodash.default.isNil(auth) || headers[_core.HEADERS.AUTHORIZATION]) {
|
|
300
|
-
return headers;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (_lodash.default.isObject(auth) === false && _lodash.default.isObject(auth.token) === false) {
|
|
304
|
-
this._throwErrorAuth('Auth invalid');
|
|
305
|
-
} // get NPM_TOKEN http://blog.npmjs.org/post/118393368555/deploying-with-npm-private-modules
|
|
306
|
-
// or get other variable export in env
|
|
307
|
-
// https://github.com/verdaccio/verdaccio/releases/tag/v2.5.0
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
let token;
|
|
311
|
-
const tokenConf = auth;
|
|
312
|
-
|
|
313
|
-
if (_lodash.default.isNil(tokenConf.token) === false && _lodash.default.isString(tokenConf.token)) {
|
|
314
|
-
token = tokenConf.token;
|
|
315
|
-
} else if (_lodash.default.isNil(tokenConf.token_env) === false) {
|
|
316
|
-
if (_lodash.default.isString(tokenConf.token_env)) {
|
|
317
|
-
token = process.env[tokenConf.token_env];
|
|
318
|
-
} else if (_lodash.default.isBoolean(tokenConf.token_env) && tokenConf.token_env) {
|
|
319
|
-
token = process.env.NPM_TOKEN;
|
|
320
|
-
} else {
|
|
321
|
-
this.logger.error(_core.constants.ERROR_CODE.token_required);
|
|
322
|
-
|
|
323
|
-
this._throwErrorAuth(_core.constants.ERROR_CODE.token_required);
|
|
324
|
-
}
|
|
325
|
-
} else {
|
|
326
|
-
token = process.env.NPM_TOKEN;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (_lodash.default.isNil(token)) {
|
|
330
|
-
this._throwErrorAuth(_core.constants.ERROR_CODE.token_required);
|
|
331
|
-
} // define type Auth allow basic and bearer
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const type = tokenConf.type || _core.TOKEN_BASIC;
|
|
335
|
-
|
|
336
|
-
this._setHeaderAuthorization(headers, type, token);
|
|
337
|
-
|
|
338
|
-
return headers;
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* @param {string} message
|
|
342
|
-
* @throws {Error}
|
|
343
|
-
* @private
|
|
344
|
-
*/
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
_throwErrorAuth(message) {
|
|
348
|
-
this.logger.error(message);
|
|
349
|
-
throw new Error(message);
|
|
350
|
-
}
|
|
351
|
-
/**
|
|
352
|
-
* Assign Header authorization with type authentication
|
|
353
|
-
* @param {Object} headers
|
|
354
|
-
* @param {string} type
|
|
355
|
-
* @param {string} token
|
|
356
|
-
* @private
|
|
357
|
-
*/
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
_setHeaderAuthorization(headers, type, token) {
|
|
361
|
-
const _type = type.toLowerCase();
|
|
362
|
-
|
|
363
|
-
if (_type !== _core.TOKEN_BEARER.toLowerCase() && _type !== _core.TOKEN_BASIC.toLowerCase()) {
|
|
364
|
-
this._throwErrorAuth(`Auth type '${_type}' not allowed`);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
type = _lodash.default.upperFirst(type);
|
|
368
|
-
headers[_core.HEADERS.AUTHORIZATION] = (0, _utils.buildToken)(type, token);
|
|
369
|
-
}
|
|
370
|
-
/**
|
|
371
|
-
* It will add or override specified headers from config file.
|
|
372
|
-
*
|
|
373
|
-
* Eg:
|
|
374
|
-
*
|
|
375
|
-
* uplinks:
|
|
376
|
-
npmjs:
|
|
377
|
-
url: https://registry.npmjs.org/
|
|
378
|
-
headers:
|
|
379
|
-
Accept: "application/vnd.npm.install-v2+json; q=1.0"
|
|
380
|
-
verdaccio-staging:
|
|
381
|
-
url: https://mycompany.com/npm
|
|
382
|
-
headers:
|
|
383
|
-
Accept: "application/json"
|
|
384
|
-
authorization: "Basic YourBase64EncodedCredentials=="
|
|
385
|
-
* @param {Object} headers
|
|
386
|
-
* @private
|
|
387
|
-
*/
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
_overrideWithUpLinkConfLocaligHeaders(headers) {
|
|
391
|
-
if (!this.config.headers) {
|
|
392
|
-
return headers;
|
|
393
|
-
} // add/override headers specified in the config
|
|
394
|
-
|
|
395
|
-
/* eslint guard-for-in: 0 */
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
for (const key in this.config.headers) {
|
|
399
|
-
headers[key] = this.config.headers[key];
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
/**
|
|
403
|
-
* Get a remote package metadata
|
|
404
|
-
* @param {*} name package name
|
|
405
|
-
* @param {*} options request options, eg: eTag.
|
|
406
|
-
* @param {*} callback
|
|
407
|
-
*/
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
getRemoteMetadata(name, options, callback) {
|
|
411
|
-
const headers = {};
|
|
412
|
-
|
|
413
|
-
if (_lodash.default.isNil(options.etag) === false) {
|
|
414
|
-
headers['If-None-Match'] = options.etag;
|
|
415
|
-
headers[_core.HEADERS.ACCEPT] = contentTypeAccept;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
this.request({
|
|
419
|
-
uri: `/${encode(name)}`,
|
|
420
|
-
json: true,
|
|
421
|
-
headers: headers,
|
|
422
|
-
req: options.req
|
|
423
|
-
}, (err, res, body) => {
|
|
424
|
-
if (err) {
|
|
425
|
-
return callback(err);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
if (res.statusCode === _core.HTTP_STATUS.NOT_FOUND) {
|
|
429
|
-
return callback(_core.errorUtils.getNotFound(_core.errorUtils.API_ERROR.NOT_PACKAGE_UPLINK));
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
if (!(res.statusCode >= _core.HTTP_STATUS.OK && res.statusCode < _core.HTTP_STATUS.MULTIPLE_CHOICES)) {
|
|
433
|
-
const error = _core.errorUtils.getInternalError(`${_core.errorUtils.API_ERROR.BAD_STATUS_CODE}: ${res.statusCode}`);
|
|
434
|
-
|
|
435
|
-
error.remoteStatus = res.statusCode;
|
|
436
|
-
return callback(error);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
callback(null, body, res.headers.etag);
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
/**
|
|
443
|
-
* Fetch a tarball from the uplink.
|
|
444
|
-
* @param {String} url
|
|
445
|
-
* @return {Stream}
|
|
446
|
-
*/
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
fetchTarball(url) {
|
|
450
|
-
const stream = new _streams.ReadTarball({});
|
|
451
|
-
let current_length = 0;
|
|
452
|
-
let expected_length;
|
|
453
|
-
|
|
454
|
-
stream.abort = () => {};
|
|
455
|
-
|
|
456
|
-
const readStream = this.request({
|
|
457
|
-
uri_full: url,
|
|
458
|
-
encoding: null,
|
|
459
|
-
headers: {
|
|
460
|
-
Accept: contentTypeAccept
|
|
461
|
-
}
|
|
462
|
-
});
|
|
463
|
-
readStream.on('response', function (res) {
|
|
464
|
-
if (res.statusCode === _core.HTTP_STATUS.NOT_FOUND) {
|
|
465
|
-
return stream.emit('error', _core.errorUtils.getNotFound(_core.errorUtils.API_ERROR.NOT_FILE_UPLINK));
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
if (!(res.statusCode >= _core.HTTP_STATUS.OK && res.statusCode < _core.HTTP_STATUS.MULTIPLE_CHOICES)) {
|
|
469
|
-
return stream.emit('error', _core.errorUtils.getInternalError(`bad uplink status code: ${res.statusCode}`));
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
if (res.headers[_core.HEADER_TYPE.CONTENT_LENGTH]) {
|
|
473
|
-
expected_length = res.headers[_core.HEADER_TYPE.CONTENT_LENGTH];
|
|
474
|
-
stream.emit(_core.HEADER_TYPE.CONTENT_LENGTH, res.headers[_core.HEADER_TYPE.CONTENT_LENGTH]);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
readStream.pipe(stream);
|
|
478
|
-
});
|
|
479
|
-
readStream.on('error', function (err) {
|
|
480
|
-
stream.emit('error', err);
|
|
481
|
-
});
|
|
482
|
-
readStream.on('data', function (data) {
|
|
483
|
-
current_length += data.length;
|
|
484
|
-
});
|
|
485
|
-
readStream.on('end', function (data) {
|
|
486
|
-
if (data) {
|
|
487
|
-
current_length += data.length;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
if (expected_length && current_length != expected_length) {
|
|
491
|
-
stream.emit('error', _core.errorUtils.getInternalError(_core.errorUtils.API_ERROR.CONTENT_MISMATCH));
|
|
492
|
-
}
|
|
493
|
-
});
|
|
494
|
-
return stream;
|
|
495
|
-
}
|
|
496
|
-
/**
|
|
497
|
-
* Perform a stream search.
|
|
498
|
-
* @param {*} options request options
|
|
499
|
-
* @return {Stream}
|
|
500
|
-
*/
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
async search({
|
|
504
|
-
url,
|
|
505
|
-
abort
|
|
506
|
-
}) {
|
|
507
|
-
debug('search url %o', url);
|
|
508
|
-
let response;
|
|
509
|
-
|
|
510
|
-
try {
|
|
511
|
-
const fullURL = new _url.URL(`${this.url}${url}`); // FIXME: a better way to remove duplicate slashes?
|
|
512
|
-
|
|
513
|
-
const uri = fullURL.href.replace(/([^:]\/)\/+/g, '$1');
|
|
514
|
-
this.logger.http({
|
|
515
|
-
uri,
|
|
516
|
-
uplink: this.upname
|
|
517
|
-
}, 'search request to uplink @{uplink} - @{uri}');
|
|
518
|
-
response = await (0, _undici.fetch)(uri, {
|
|
519
|
-
method: 'GET',
|
|
520
|
-
// FUTURE: whitelist domains what we are sending not need it headers, security check
|
|
521
|
-
// headers: new Headers({
|
|
522
|
-
// ...headers,
|
|
523
|
-
// connection: 'keep-alive',
|
|
524
|
-
// }),
|
|
525
|
-
signal: abort === null || abort === void 0 ? void 0 : abort.signal
|
|
526
|
-
});
|
|
527
|
-
debug('response.status %o', response.status);
|
|
528
|
-
|
|
529
|
-
if (response.status >= _core.HTTP_STATUS.BAD_REQUEST) {
|
|
530
|
-
throw _core.errorUtils.getInternalError(`bad status code ${response.status} from uplink`);
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
const streamSearch = new _stream.PassThrough({
|
|
534
|
-
objectMode: true
|
|
535
|
-
});
|
|
536
|
-
const res = await response.text();
|
|
537
|
-
|
|
538
|
-
const streamResponse = _stream.Readable.from(res); // objects is one of the properties on the body, it ignores date and total
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
streamResponse.pipe(_JSONStream.default.parse('objects')).pipe(streamSearch, {
|
|
542
|
-
end: true
|
|
543
|
-
});
|
|
544
|
-
return streamSearch;
|
|
545
|
-
} catch (err) {
|
|
546
|
-
this.logger.error({
|
|
547
|
-
errorMessage: err === null || err === void 0 ? void 0 : err.message,
|
|
548
|
-
name: this.upname
|
|
549
|
-
}, 'proxy uplink @{name} search error: @{errorMessage}');
|
|
550
|
-
throw err;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
/**
|
|
554
|
-
* Add proxy headers.
|
|
555
|
-
* FIXME: object mutations, it should return an new object
|
|
556
|
-
* @param {*} req the http request
|
|
557
|
-
* @param {*} headers the request headers
|
|
558
|
-
*/
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
_addProxyHeaders(req, headers) {
|
|
562
|
-
if (req) {
|
|
563
|
-
// Only submit X-Forwarded-For field if we don't have a proxy selected
|
|
564
|
-
// in the config file.
|
|
565
|
-
//
|
|
566
|
-
// Otherwise misconfigured proxy could return 407:
|
|
567
|
-
// https://github.com/rlidwka/sinopia/issues/254
|
|
568
|
-
// @ts-ignore
|
|
569
|
-
if (!this.proxy) {
|
|
570
|
-
headers[_core.HEADERS.FORWARDED_FOR] = (req.headers['x-forwarded-for'] ? req.headers['x-forwarded-for'] + ', ' : '') + req.connection.remoteAddress;
|
|
571
|
-
}
|
|
572
|
-
} // always attach Via header to avoid loops, even if we're not proxying
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
headers['Via'] = req !== null && req !== void 0 && req.headers['via'] ? req.headers['via'] + ', ' : '';
|
|
576
|
-
headers['Via'] += '1.1 ' + this.server_id + ' (Verdaccio)';
|
|
577
|
-
}
|
|
578
|
-
/**
|
|
579
|
-
* Check whether the remote host is available.
|
|
580
|
-
* @param {*} alive
|
|
581
|
-
* @return {Boolean}
|
|
582
|
-
*/
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
_statusCheck(alive) {
|
|
586
|
-
if (arguments.length === 0) {
|
|
587
|
-
return this._ifRequestFailure() === false;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
if (alive) {
|
|
591
|
-
if (this.failed_requests >= this.max_fails) {
|
|
592
|
-
this.logger.warn({
|
|
593
|
-
host: this.url.host
|
|
594
|
-
}, 'host @{host} is back online');
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
this.failed_requests = 0;
|
|
598
|
-
} else {
|
|
599
|
-
this.failed_requests++;
|
|
600
|
-
|
|
601
|
-
if (this.failed_requests === this.max_fails) {
|
|
602
|
-
this.logger.warn({
|
|
603
|
-
host: this.url.host
|
|
604
|
-
}, 'host @{host} is now offline');
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
this.last_request_time = Date.now();
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* If the request failure.
|
|
612
|
-
* @return {boolean}
|
|
613
|
-
* @private
|
|
614
|
-
*/
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
_ifRequestFailure() {
|
|
618
|
-
return this.failed_requests >= this.max_fails && Math.abs(Date.now() - this.last_request_time) < this.fail_timeout;
|
|
619
|
-
}
|
|
620
|
-
/**
|
|
621
|
-
* Set up a proxy.
|
|
622
|
-
* @param {*} hostname
|
|
623
|
-
* @param {*} config
|
|
624
|
-
* @param {*} mainconfig
|
|
625
|
-
* @param {*} isHTTPS
|
|
626
|
-
*/
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
_setupProxy(hostname, config, mainconfig, isHTTPS) {
|
|
630
|
-
let noProxyList;
|
|
631
|
-
const proxy_key = isHTTPS ? 'https_proxy' : 'http_proxy'; // get http_proxy and no_proxy configs
|
|
632
|
-
|
|
633
|
-
if (proxy_key in config) {
|
|
634
|
-
this.proxy = config[proxy_key];
|
|
635
|
-
} else if (proxy_key in mainconfig) {
|
|
636
|
-
this.proxy = mainconfig[proxy_key];
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
if ('no_proxy' in config) {
|
|
640
|
-
noProxyList = config.no_proxy;
|
|
641
|
-
} else if ('no_proxy' in mainconfig) {
|
|
642
|
-
noProxyList = mainconfig.no_proxy;
|
|
643
|
-
} // use wget-like algorithm to determine if proxy shouldn't be used
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
if (hostname[0] !== '.') {
|
|
647
|
-
hostname = '.' + hostname;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
if (_lodash.default.isString(noProxyList) && noProxyList.length) {
|
|
651
|
-
noProxyList = noProxyList.split(',');
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
if (_lodash.default.isArray(noProxyList)) {
|
|
655
|
-
for (let i = 0; i < noProxyList.length; i++) {
|
|
656
|
-
let noProxyItem = noProxyList[i];
|
|
657
|
-
|
|
658
|
-
if (noProxyItem[0] !== '.') {
|
|
659
|
-
noProxyItem = '.' + noProxyItem;
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
if (hostname.lastIndexOf(noProxyItem) === hostname.length - noProxyItem.length) {
|
|
663
|
-
if (this.proxy) {
|
|
664
|
-
this.logger.debug({
|
|
665
|
-
url: this.url.href,
|
|
666
|
-
rule: noProxyItem
|
|
667
|
-
}, 'not using proxy for @{url}, excluded by @{rule} rule'); // @ts-ignore
|
|
668
|
-
|
|
669
|
-
this.proxy = false;
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
break;
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
} // if it's non-string (i.e. "false"), don't use it
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
if (_lodash.default.isString(this.proxy) === false) {
|
|
679
|
-
delete this.proxy;
|
|
680
|
-
} else {
|
|
681
|
-
this.logger.debug({
|
|
682
|
-
url: this.url.href,
|
|
683
|
-
proxy: this.proxy
|
|
684
|
-
}, 'using proxy @{proxy} for @{url}');
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
exports.ProxyStorage = ProxyStorage;
|
|
691
|
-
//# sourceMappingURL=up-storage.js.map
|