@actions/http-client 1.0.7 → 1.0.11
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/RELEASES.md +11 -1
- package/auth.js +5 -2
- package/index.d.ts +6 -1
- package/index.js +74 -38
- package/interfaces.d.ts +2 -3
- package/interfaces.js +0 -1
- package/package.json +6 -2
- package/proxy.d.ts +2 -4
- package/proxy.js +8 -8
package/RELEASES.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
## Releases
|
|
2
2
|
|
|
3
|
+
## 1.0.10
|
|
4
|
+
|
|
5
|
+
Contains a bug fix where proxy is defined without a user and password. see [PR here](https://github.com/actions/http-client/pull/42)
|
|
6
|
+
|
|
7
|
+
## 1.0.9
|
|
8
|
+
Throw HttpClientError instead of a generic Error from the \<verb>Json() helper methods when the server responds with a non-successful status code.
|
|
9
|
+
|
|
10
|
+
## 1.0.8
|
|
11
|
+
Fixed security issue where a redirect (e.g. 302) to another domain would pass headers. The fix was to strip the authorization header if the hostname was different. More [details in PR #27](https://github.com/actions/http-client/pull/27)
|
|
12
|
+
|
|
3
13
|
## 1.0.7
|
|
4
14
|
Update NPM dependencies and add 429 to the list of HttpCodes
|
|
5
15
|
|
|
@@ -13,4 +23,4 @@ Adds \<verb>Json() helper methods for json over http scenarios.
|
|
|
13
23
|
Started to add \<verb>Json() helper methods. Do not use this release for that. Use >= 1.0.5 since there was an issue with types.
|
|
14
24
|
|
|
15
25
|
## 1.0.1 to 1.0.3
|
|
16
|
-
Adds proxy support.
|
|
26
|
+
Adds proxy support.
|
package/auth.js
CHANGED
|
@@ -6,7 +6,9 @@ class BasicCredentialHandler {
|
|
|
6
6
|
this.password = password;
|
|
7
7
|
}
|
|
8
8
|
prepareRequest(options) {
|
|
9
|
-
options.headers['Authorization'] =
|
|
9
|
+
options.headers['Authorization'] =
|
|
10
|
+
'Basic ' +
|
|
11
|
+
Buffer.from(this.username + ':' + this.password).toString('base64');
|
|
10
12
|
}
|
|
11
13
|
// This handler cannot handle 401
|
|
12
14
|
canHandleAuthentication(response) {
|
|
@@ -42,7 +44,8 @@ class PersonalAccessTokenCredentialHandler {
|
|
|
42
44
|
// currently implements pre-authorization
|
|
43
45
|
// TODO: support preAuth = false where it hooks on 401
|
|
44
46
|
prepareRequest(options) {
|
|
45
|
-
options.headers['Authorization'] =
|
|
47
|
+
options.headers['Authorization'] =
|
|
48
|
+
'Basic ' + Buffer.from('PAT:' + this.token).toString('base64');
|
|
46
49
|
}
|
|
47
50
|
// This handler cannot handle 401
|
|
48
51
|
canHandleAuthentication(response) {
|
package/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import http = require(
|
|
2
|
+
import http = require('http');
|
|
3
3
|
import ifm = require('./interfaces');
|
|
4
4
|
export declare enum HttpCodes {
|
|
5
5
|
OK = 200,
|
|
@@ -42,6 +42,11 @@ export declare enum MediaTypes {
|
|
|
42
42
|
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
|
|
43
43
|
*/
|
|
44
44
|
export declare function getProxyUrl(serverUrl: string): string;
|
|
45
|
+
export declare class HttpClientError extends Error {
|
|
46
|
+
constructor(message: string, statusCode: number);
|
|
47
|
+
statusCode: number;
|
|
48
|
+
result?: any;
|
|
49
|
+
}
|
|
45
50
|
export declare class HttpClientResponse implements ifm.IHttpClientResponse {
|
|
46
51
|
constructor(message: http.IncomingMessage);
|
|
47
52
|
message: http.IncomingMessage;
|
package/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const url = require("url");
|
|
4
3
|
const http = require("http");
|
|
5
4
|
const https = require("https");
|
|
6
5
|
const pm = require("./proxy");
|
|
@@ -49,15 +48,34 @@ var MediaTypes;
|
|
|
49
48
|
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
|
|
50
49
|
*/
|
|
51
50
|
function getProxyUrl(serverUrl) {
|
|
52
|
-
let proxyUrl = pm.getProxyUrl(
|
|
51
|
+
let proxyUrl = pm.getProxyUrl(new URL(serverUrl));
|
|
53
52
|
return proxyUrl ? proxyUrl.href : '';
|
|
54
53
|
}
|
|
55
54
|
exports.getProxyUrl = getProxyUrl;
|
|
56
|
-
const HttpRedirectCodes = [
|
|
57
|
-
|
|
55
|
+
const HttpRedirectCodes = [
|
|
56
|
+
HttpCodes.MovedPermanently,
|
|
57
|
+
HttpCodes.ResourceMoved,
|
|
58
|
+
HttpCodes.SeeOther,
|
|
59
|
+
HttpCodes.TemporaryRedirect,
|
|
60
|
+
HttpCodes.PermanentRedirect
|
|
61
|
+
];
|
|
62
|
+
const HttpResponseRetryCodes = [
|
|
63
|
+
HttpCodes.BadGateway,
|
|
64
|
+
HttpCodes.ServiceUnavailable,
|
|
65
|
+
HttpCodes.GatewayTimeout
|
|
66
|
+
];
|
|
58
67
|
const RetryableHttpVerbs = ['OPTIONS', 'GET', 'DELETE', 'HEAD'];
|
|
59
68
|
const ExponentialBackoffCeiling = 10;
|
|
60
69
|
const ExponentialBackoffTimeSlice = 5;
|
|
70
|
+
class HttpClientError extends Error {
|
|
71
|
+
constructor(message, statusCode) {
|
|
72
|
+
super(message);
|
|
73
|
+
this.name = 'HttpClientError';
|
|
74
|
+
this.statusCode = statusCode;
|
|
75
|
+
Object.setPrototypeOf(this, HttpClientError.prototype);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.HttpClientError = HttpClientError;
|
|
61
79
|
class HttpClientResponse {
|
|
62
80
|
constructor(message) {
|
|
63
81
|
this.message = message;
|
|
@@ -76,7 +94,7 @@ class HttpClientResponse {
|
|
|
76
94
|
}
|
|
77
95
|
exports.HttpClientResponse = HttpClientResponse;
|
|
78
96
|
function isHttps(requestUrl) {
|
|
79
|
-
let parsedUrl =
|
|
97
|
+
let parsedUrl = new URL(requestUrl);
|
|
80
98
|
return parsedUrl.protocol === 'https:';
|
|
81
99
|
}
|
|
82
100
|
exports.isHttps = isHttps;
|
|
@@ -179,18 +197,22 @@ class HttpClient {
|
|
|
179
197
|
*/
|
|
180
198
|
async request(verb, requestUrl, data, headers) {
|
|
181
199
|
if (this._disposed) {
|
|
182
|
-
throw new Error(
|
|
200
|
+
throw new Error('Client has already been disposed.');
|
|
183
201
|
}
|
|
184
|
-
let parsedUrl =
|
|
202
|
+
let parsedUrl = new URL(requestUrl);
|
|
185
203
|
let info = this._prepareRequest(verb, parsedUrl, headers);
|
|
186
204
|
// Only perform retries on reads since writes may not be idempotent.
|
|
187
|
-
let maxTries =
|
|
205
|
+
let maxTries = this._allowRetries && RetryableHttpVerbs.indexOf(verb) != -1
|
|
206
|
+
? this._maxRetries + 1
|
|
207
|
+
: 1;
|
|
188
208
|
let numTries = 0;
|
|
189
209
|
let response;
|
|
190
210
|
while (numTries < maxTries) {
|
|
191
211
|
response = await this.requestRaw(info, data);
|
|
192
212
|
// Check if it's an authentication challenge
|
|
193
|
-
if (response &&
|
|
213
|
+
if (response &&
|
|
214
|
+
response.message &&
|
|
215
|
+
response.message.statusCode === HttpCodes.Unauthorized) {
|
|
194
216
|
let authenticationHandler;
|
|
195
217
|
for (let i = 0; i < this.handlers.length; i++) {
|
|
196
218
|
if (this.handlers[i].canHandleAuthentication(response)) {
|
|
@@ -208,21 +230,32 @@ class HttpClient {
|
|
|
208
230
|
}
|
|
209
231
|
}
|
|
210
232
|
let redirectsRemaining = this._maxRedirects;
|
|
211
|
-
while (HttpRedirectCodes.indexOf(response.message.statusCode) != -1
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const redirectUrl = response.message.headers[
|
|
233
|
+
while (HttpRedirectCodes.indexOf(response.message.statusCode) != -1 &&
|
|
234
|
+
this._allowRedirects &&
|
|
235
|
+
redirectsRemaining > 0) {
|
|
236
|
+
const redirectUrl = response.message.headers['location'];
|
|
215
237
|
if (!redirectUrl) {
|
|
216
238
|
// if there's no location to redirect to, we won't
|
|
217
239
|
break;
|
|
218
240
|
}
|
|
219
|
-
let parsedRedirectUrl =
|
|
220
|
-
if (parsedUrl.protocol == 'https:' &&
|
|
221
|
-
|
|
241
|
+
let parsedRedirectUrl = new URL(redirectUrl);
|
|
242
|
+
if (parsedUrl.protocol == 'https:' &&
|
|
243
|
+
parsedUrl.protocol != parsedRedirectUrl.protocol &&
|
|
244
|
+
!this._allowRedirectDowngrade) {
|
|
245
|
+
throw new Error('Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.');
|
|
222
246
|
}
|
|
223
247
|
// we need to finish reading the response before reassigning response
|
|
224
248
|
// which will leak the open socket.
|
|
225
249
|
await response.readBody();
|
|
250
|
+
// strip authorization header if redirected to a different hostname
|
|
251
|
+
if (parsedRedirectUrl.hostname !== parsedUrl.hostname) {
|
|
252
|
+
for (let header in headers) {
|
|
253
|
+
// header names are case insensitive
|
|
254
|
+
if (header.toLowerCase() === 'authorization') {
|
|
255
|
+
delete headers[header];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
226
259
|
// let's make the request with the new redirectUrl
|
|
227
260
|
info = this._prepareRequest(verb, parsedRedirectUrl, headers);
|
|
228
261
|
response = await this.requestRaw(info, data);
|
|
@@ -273,8 +306,8 @@ class HttpClient {
|
|
|
273
306
|
*/
|
|
274
307
|
requestRawWithCallback(info, data, onResult) {
|
|
275
308
|
let socket;
|
|
276
|
-
if (typeof
|
|
277
|
-
info.options.headers[
|
|
309
|
+
if (typeof data === 'string') {
|
|
310
|
+
info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8');
|
|
278
311
|
}
|
|
279
312
|
let callbackCalled = false;
|
|
280
313
|
let handleResult = (err, res) => {
|
|
@@ -287,7 +320,7 @@ class HttpClient {
|
|
|
287
320
|
let res = new HttpClientResponse(msg);
|
|
288
321
|
handleResult(null, res);
|
|
289
322
|
});
|
|
290
|
-
req.on('socket',
|
|
323
|
+
req.on('socket', sock => {
|
|
291
324
|
socket = sock;
|
|
292
325
|
});
|
|
293
326
|
// If we ever get disconnected, we want the socket to timeout eventually
|
|
@@ -302,10 +335,10 @@ class HttpClient {
|
|
|
302
335
|
// res should have headers
|
|
303
336
|
handleResult(err, null);
|
|
304
337
|
});
|
|
305
|
-
if (data && typeof
|
|
338
|
+
if (data && typeof data === 'string') {
|
|
306
339
|
req.write(data, 'utf8');
|
|
307
340
|
}
|
|
308
|
-
if (data && typeof
|
|
341
|
+
if (data && typeof data !== 'string') {
|
|
309
342
|
data.on('close', function () {
|
|
310
343
|
req.end();
|
|
311
344
|
});
|
|
@@ -321,7 +354,7 @@ class HttpClient {
|
|
|
321
354
|
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
|
|
322
355
|
*/
|
|
323
356
|
getAgent(serverUrl) {
|
|
324
|
-
let parsedUrl =
|
|
357
|
+
let parsedUrl = new URL(serverUrl);
|
|
325
358
|
return this._getAgent(parsedUrl);
|
|
326
359
|
}
|
|
327
360
|
_prepareRequest(method, requestUrl, headers) {
|
|
@@ -332,31 +365,34 @@ class HttpClient {
|
|
|
332
365
|
const defaultPort = usingSsl ? 443 : 80;
|
|
333
366
|
info.options = {};
|
|
334
367
|
info.options.host = info.parsedUrl.hostname;
|
|
335
|
-
info.options.port = info.parsedUrl.port
|
|
336
|
-
|
|
368
|
+
info.options.port = info.parsedUrl.port
|
|
369
|
+
? parseInt(info.parsedUrl.port)
|
|
370
|
+
: defaultPort;
|
|
371
|
+
info.options.path =
|
|
372
|
+
(info.parsedUrl.pathname || '') + (info.parsedUrl.search || '');
|
|
337
373
|
info.options.method = method;
|
|
338
374
|
info.options.headers = this._mergeHeaders(headers);
|
|
339
375
|
if (this.userAgent != null) {
|
|
340
|
-
info.options.headers[
|
|
376
|
+
info.options.headers['user-agent'] = this.userAgent;
|
|
341
377
|
}
|
|
342
378
|
info.options.agent = this._getAgent(info.parsedUrl);
|
|
343
379
|
// gives handlers an opportunity to participate
|
|
344
380
|
if (this.handlers) {
|
|
345
|
-
this.handlers.forEach(
|
|
381
|
+
this.handlers.forEach(handler => {
|
|
346
382
|
handler.prepareRequest(info.options);
|
|
347
383
|
});
|
|
348
384
|
}
|
|
349
385
|
return info;
|
|
350
386
|
}
|
|
351
387
|
_mergeHeaders(headers) {
|
|
352
|
-
const lowercaseKeys = obj => Object.keys(obj).reduce((c, k) => (c[k.toLowerCase()] = obj[k], c), {});
|
|
388
|
+
const lowercaseKeys = obj => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {});
|
|
353
389
|
if (this.requestOptions && this.requestOptions.headers) {
|
|
354
390
|
return Object.assign({}, lowercaseKeys(this.requestOptions.headers), lowercaseKeys(headers));
|
|
355
391
|
}
|
|
356
392
|
return lowercaseKeys(headers || {});
|
|
357
393
|
}
|
|
358
394
|
_getExistingOrDefaultHeader(additionalHeaders, header, _default) {
|
|
359
|
-
const lowercaseKeys = obj => Object.keys(obj).reduce((c, k) => (c[k.toLowerCase()] = obj[k], c), {});
|
|
395
|
+
const lowercaseKeys = obj => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {});
|
|
360
396
|
let clientHeader;
|
|
361
397
|
if (this.requestOptions && this.requestOptions.headers) {
|
|
362
398
|
clientHeader = lowercaseKeys(this.requestOptions.headers)[header];
|
|
@@ -391,10 +427,12 @@ class HttpClient {
|
|
|
391
427
|
maxSockets: maxSockets,
|
|
392
428
|
keepAlive: this._keepAlive,
|
|
393
429
|
proxy: {
|
|
394
|
-
|
|
430
|
+
...((proxyUrl.username || proxyUrl.password) && {
|
|
431
|
+
proxyAuth: `${proxyUrl.username}:${proxyUrl.password}`
|
|
432
|
+
}),
|
|
395
433
|
host: proxyUrl.hostname,
|
|
396
434
|
port: proxyUrl.port
|
|
397
|
-
}
|
|
435
|
+
}
|
|
398
436
|
};
|
|
399
437
|
let tunnelAgent;
|
|
400
438
|
const overHttps = proxyUrl.protocol === 'https:';
|
|
@@ -421,7 +459,9 @@ class HttpClient {
|
|
|
421
459
|
// we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process
|
|
422
460
|
// http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options
|
|
423
461
|
// we have to cast it to any and change it directly
|
|
424
|
-
agent.options = Object.assign(agent.options || {}, {
|
|
462
|
+
agent.options = Object.assign(agent.options || {}, {
|
|
463
|
+
rejectUnauthorized: false
|
|
464
|
+
});
|
|
425
465
|
}
|
|
426
466
|
return agent;
|
|
427
467
|
}
|
|
@@ -482,14 +522,10 @@ class HttpClient {
|
|
|
482
522
|
msg = contents;
|
|
483
523
|
}
|
|
484
524
|
else {
|
|
485
|
-
msg =
|
|
486
|
-
}
|
|
487
|
-
let err = new Error(msg);
|
|
488
|
-
// attach statusCode and body obj (if available) to the error object
|
|
489
|
-
err['statusCode'] = statusCode;
|
|
490
|
-
if (response.result) {
|
|
491
|
-
err['result'] = response.result;
|
|
525
|
+
msg = 'Failed request: (' + statusCode + ')';
|
|
492
526
|
}
|
|
527
|
+
let err = new HttpClientError(msg, statusCode);
|
|
528
|
+
err.result = response.result;
|
|
493
529
|
reject(err);
|
|
494
530
|
}
|
|
495
531
|
else {
|
package/interfaces.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import http = require(
|
|
3
|
-
import url = require("url");
|
|
2
|
+
import http = require('http');
|
|
4
3
|
export interface IHeaders {
|
|
5
4
|
[key: string]: any;
|
|
6
5
|
}
|
|
@@ -27,7 +26,7 @@ export interface IHttpClientResponse {
|
|
|
27
26
|
}
|
|
28
27
|
export interface IRequestInfo {
|
|
29
28
|
options: http.RequestOptions;
|
|
30
|
-
parsedUrl:
|
|
29
|
+
parsedUrl: URL;
|
|
31
30
|
httpModule: any;
|
|
32
31
|
}
|
|
33
32
|
export interface IRequestOptions {
|
package/interfaces.js
CHANGED
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actions/http-client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"description": "Actions Http Client",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "rm -Rf ./_out && tsc && cp package*.json ./_out && cp *.md ./_out && cp LICENSE ./_out && cp actions.png ./_out",
|
|
8
|
-
"test": "jest"
|
|
8
|
+
"test": "jest",
|
|
9
|
+
"format": "prettier --write *.ts && prettier --write **/*.ts",
|
|
10
|
+
"format-check": "prettier --check *.ts && prettier --check **/*.ts",
|
|
11
|
+
"audit-check": "npm audit --audit-level=moderate"
|
|
9
12
|
},
|
|
10
13
|
"repository": {
|
|
11
14
|
"type": "git",
|
|
@@ -25,6 +28,7 @@
|
|
|
25
28
|
"@types/jest": "^25.1.4",
|
|
26
29
|
"@types/node": "^12.12.31",
|
|
27
30
|
"jest": "^25.1.0",
|
|
31
|
+
"prettier": "^2.0.4",
|
|
28
32
|
"proxy": "^1.0.1",
|
|
29
33
|
"ts-jest": "^25.2.1",
|
|
30
34
|
"typescript": "^3.8.3"
|
package/proxy.d.ts
CHANGED
|
@@ -1,4 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export declare function getProxyUrl(reqUrl: url.Url): url.Url | undefined;
|
|
4
|
-
export declare function checkBypass(reqUrl: url.Url): boolean;
|
|
1
|
+
export declare function getProxyUrl(reqUrl: URL): URL | undefined;
|
|
2
|
+
export declare function checkBypass(reqUrl: URL): boolean;
|
package/proxy.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const url = require("url");
|
|
4
3
|
function getProxyUrl(reqUrl) {
|
|
5
4
|
let usingSsl = reqUrl.protocol === 'https:';
|
|
6
5
|
let proxyUrl;
|
|
@@ -9,15 +8,13 @@ function getProxyUrl(reqUrl) {
|
|
|
9
8
|
}
|
|
10
9
|
let proxyVar;
|
|
11
10
|
if (usingSsl) {
|
|
12
|
-
proxyVar = process.env[
|
|
13
|
-
process.env["HTTPS_PROXY"];
|
|
11
|
+
proxyVar = process.env['https_proxy'] || process.env['HTTPS_PROXY'];
|
|
14
12
|
}
|
|
15
13
|
else {
|
|
16
|
-
proxyVar = process.env[
|
|
17
|
-
process.env["HTTP_PROXY"];
|
|
14
|
+
proxyVar = process.env['http_proxy'] || process.env['HTTP_PROXY'];
|
|
18
15
|
}
|
|
19
16
|
if (proxyVar) {
|
|
20
|
-
proxyUrl =
|
|
17
|
+
proxyUrl = new URL(proxyVar);
|
|
21
18
|
}
|
|
22
19
|
return proxyUrl;
|
|
23
20
|
}
|
|
@@ -26,7 +23,7 @@ function checkBypass(reqUrl) {
|
|
|
26
23
|
if (!reqUrl.hostname) {
|
|
27
24
|
return false;
|
|
28
25
|
}
|
|
29
|
-
let noProxy = process.env[
|
|
26
|
+
let noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || '';
|
|
30
27
|
if (!noProxy) {
|
|
31
28
|
return false;
|
|
32
29
|
}
|
|
@@ -47,7 +44,10 @@ function checkBypass(reqUrl) {
|
|
|
47
44
|
upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`);
|
|
48
45
|
}
|
|
49
46
|
// Compare request host against noproxy
|
|
50
|
-
for (let upperNoProxyItem of noProxy
|
|
47
|
+
for (let upperNoProxyItem of noProxy
|
|
48
|
+
.split(',')
|
|
49
|
+
.map(x => x.trim().toUpperCase())
|
|
50
|
+
.filter(x => x)) {
|
|
51
51
|
if (upperReqHosts.some(x => x === upperNoProxyItem)) {
|
|
52
52
|
return true;
|
|
53
53
|
}
|