Skip to content
This repository was archived by the owner on May 3, 2022. It is now read-only.

Commit e949b05

Browse files
authored
Merge branch 'main' into dhadka/typed-error
2 parents 55850f1 + 544584c commit e949b05

File tree

7 files changed

+95
-41
lines changed

7 files changed

+95
-41
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: http-tests
22
on:
33
push:
44
branches:
5-
- master
5+
- main
66
paths-ignore:
77
- '**.md'
88
pull_request:

RELEASES.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
## 1.0.9
44
Throw HttpClientError instead of a generic Error from the \<verb>Json() helper methods when the server responds with a non-successful status code.
55

6+
## 1.0.8
7+
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)
8+
69
## 1.0.7
710
Update NPM dependencies and add 429 to the list of HttpCodes
811

@@ -16,4 +19,4 @@ Adds \<verb>Json() helper methods for json over http scenarios.
1619
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.
1720

1821
## 1.0.1 to 1.0.3
19-
Adds proxy support.
22+
Adds proxy support.

__tests__/basics.test.ts

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ describe('basics', () => {
128128
})
129129
})
130130

131-
it('does basic get request with redirects', async done => {
131+
it.skip('does basic get request with redirects', async done => {
132132
let res: httpm.HttpClientResponse = await _http.get(
133133
'https://httpbin.org/redirect-to?url=' +
134134
encodeURIComponent('https://httpbin.org/get')
@@ -140,7 +140,7 @@ describe('basics', () => {
140140
done()
141141
})
142142

143-
it('does basic get request with redirects (303)', async done => {
143+
it.skip('does basic get request with redirects (303)', async done => {
144144
let res: httpm.HttpClientResponse = await _http.get(
145145
'https://httpbin.org/redirect-to?url=' +
146146
encodeURIComponent('https://httpbin.org/get') +
@@ -164,7 +164,7 @@ describe('basics', () => {
164164
done()
165165
})
166166

167-
it('does not follow redirects if disabled', async done => {
167+
it.skip('does not follow redirects if disabled', async done => {
168168
let http: httpm.HttpClient = new httpm.HttpClient(
169169
'typed-test-client-tests',
170170
null,
@@ -179,6 +179,52 @@ describe('basics', () => {
179179
done()
180180
})
181181

182+
it.skip('does not pass auth with diff hostname redirects', async done => {
183+
let headers = {
184+
accept: 'application/json',
185+
authorization: 'shhh'
186+
}
187+
let res: httpm.HttpClientResponse = await _http.get(
188+
'https://httpbin.org/redirect-to?url=' +
189+
encodeURIComponent('https://www.httpbin.org/get'),
190+
headers
191+
)
192+
193+
expect(res.message.statusCode).toBe(200)
194+
let body: string = await res.readBody()
195+
let obj: any = JSON.parse(body)
196+
// httpbin "fixes" the casing
197+
expect(obj.headers['Accept']).toBe('application/json')
198+
expect(obj.headers['Authorization']).toBeUndefined()
199+
expect(obj.headers['authorization']).toBeUndefined()
200+
expect(obj.url).toBe('https://www.httpbin.org/get')
201+
202+
done()
203+
})
204+
205+
it.skip('does not pass Auth with diff hostname redirects', async done => {
206+
let headers = {
207+
Accept: 'application/json',
208+
Authorization: 'shhh'
209+
}
210+
let res: httpm.HttpClientResponse = await _http.get(
211+
'https://httpbin.org/redirect-to?url=' +
212+
encodeURIComponent('https://www.httpbin.org/get'),
213+
headers
214+
)
215+
216+
expect(res.message.statusCode).toBe(200)
217+
let body: string = await res.readBody()
218+
let obj: any = JSON.parse(body)
219+
// httpbin "fixes" the casing
220+
expect(obj.headers['Accept']).toBe('application/json')
221+
expect(obj.headers['Authorization']).toBeUndefined()
222+
expect(obj.headers['authorization']).toBeUndefined()
223+
expect(obj.url).toBe('https://www.httpbin.org/get')
224+
225+
done()
226+
})
227+
182228
it('does basic head request', async done => {
183229
let res: httpm.HttpClientResponse = await _http.head(
184230
'http://httpbin.org/get'

__tests__/proxy.test.ts

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import * as http from 'http'
22
import * as httpm from '../_out'
33
import * as pm from '../_out/proxy'
44
import * as proxy from 'proxy'
5-
import * as url from 'url'
65

76
let _proxyConnects: string[]
87
let _proxyServer: http.Server
@@ -39,107 +38,107 @@ describe('proxy', () => {
3938
})
4039

4140
it('getProxyUrl does not return proxyUrl if variables not set', () => {
42-
let proxyUrl = pm.getProxyUrl(url.parse('https://github.com'))
41+
let proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
4342
expect(proxyUrl).toBeUndefined()
4443
})
4544

4645
it('getProxyUrl returns proxyUrl if https_proxy set for https url', () => {
4746
process.env['https_proxy'] = 'https://myproxysvr'
48-
let proxyUrl = pm.getProxyUrl(url.parse('https://github.com'))
47+
let proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
4948
expect(proxyUrl).toBeDefined()
5049
})
5150

5251
it('getProxyUrl does not return proxyUrl if http_proxy set for https url', () => {
5352
process.env['http_proxy'] = 'https://myproxysvr'
54-
let proxyUrl = pm.getProxyUrl(url.parse('https://github.com'))
53+
let proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
5554
expect(proxyUrl).toBeUndefined()
5655
})
5756

5857
it('getProxyUrl returns proxyUrl if http_proxy set for http url', () => {
5958
process.env['http_proxy'] = 'http://myproxysvr'
60-
let proxyUrl = pm.getProxyUrl(url.parse('http://github.com'))
59+
let proxyUrl = pm.getProxyUrl(new URL('http://github.com'))
6160
expect(proxyUrl).toBeDefined()
6261
})
6362

6463
it('getProxyUrl does not return proxyUrl if https_proxy set and in no_proxy list', () => {
6564
process.env['https_proxy'] = 'https://myproxysvr'
6665
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
67-
let proxyUrl = pm.getProxyUrl(url.parse('https://myserver'))
66+
let proxyUrl = pm.getProxyUrl(new URL('https://myserver'))
6867
expect(proxyUrl).toBeUndefined()
6968
})
7069

7170
it('getProxyUrl returns proxyUrl if https_proxy set and not in no_proxy list', () => {
7271
process.env['https_proxy'] = 'https://myproxysvr'
7372
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
74-
let proxyUrl = pm.getProxyUrl(url.parse('https://github.com'))
73+
let proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
7574
expect(proxyUrl).toBeDefined()
7675
})
7776

7877
it('getProxyUrl does not return proxyUrl if http_proxy set and in no_proxy list', () => {
7978
process.env['http_proxy'] = 'http://myproxysvr'
8079
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
81-
let proxyUrl = pm.getProxyUrl(url.parse('http://myserver'))
80+
let proxyUrl = pm.getProxyUrl(new URL('http://myserver'))
8281
expect(proxyUrl).toBeUndefined()
8382
})
8483

8584
it('getProxyUrl returns proxyUrl if http_proxy set and not in no_proxy list', () => {
8685
process.env['http_proxy'] = 'http://myproxysvr'
8786
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
88-
let proxyUrl = pm.getProxyUrl(url.parse('http://github.com'))
87+
let proxyUrl = pm.getProxyUrl(new URL('http://github.com'))
8988
expect(proxyUrl).toBeDefined()
9089
})
9190

9291
it('checkBypass returns true if host as no_proxy list', () => {
9392
process.env['no_proxy'] = 'myserver'
94-
let bypass = pm.checkBypass(url.parse('https://myserver'))
93+
let bypass = pm.checkBypass(new URL('https://myserver'))
9594
expect(bypass).toBeTruthy()
9695
})
9796

9897
it('checkBypass returns true if host in no_proxy list', () => {
9998
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
100-
let bypass = pm.checkBypass(url.parse('https://myserver'))
99+
let bypass = pm.checkBypass(new URL('https://myserver'))
101100
expect(bypass).toBeTruthy()
102101
})
103102

104103
it('checkBypass returns true if host in no_proxy list with spaces', () => {
105104
process.env['no_proxy'] = 'otherserver, myserver ,anotherserver:8080'
106-
let bypass = pm.checkBypass(url.parse('https://myserver'))
105+
let bypass = pm.checkBypass(new URL('https://myserver'))
107106
expect(bypass).toBeTruthy()
108107
})
109108

110109
it('checkBypass returns true if host in no_proxy list with port', () => {
111110
process.env['no_proxy'] = 'otherserver, myserver:8080 ,anotherserver'
112-
let bypass = pm.checkBypass(url.parse('https://myserver:8080'))
111+
let bypass = pm.checkBypass(new URL('https://myserver:8080'))
113112
expect(bypass).toBeTruthy()
114113
})
115114

116115
it('checkBypass returns true if host with port in no_proxy list without port', () => {
117116
process.env['no_proxy'] = 'otherserver, myserver ,anotherserver'
118-
let bypass = pm.checkBypass(url.parse('https://myserver:8080'))
117+
let bypass = pm.checkBypass(new URL('https://myserver:8080'))
119118
expect(bypass).toBeTruthy()
120119
})
121120

122121
it('checkBypass returns true if host in no_proxy list with default https port', () => {
123122
process.env['no_proxy'] = 'otherserver, myserver:443 ,anotherserver'
124-
let bypass = pm.checkBypass(url.parse('https://myserver'))
123+
let bypass = pm.checkBypass(new URL('https://myserver'))
125124
expect(bypass).toBeTruthy()
126125
})
127126

128127
it('checkBypass returns true if host in no_proxy list with default http port', () => {
129128
process.env['no_proxy'] = 'otherserver, myserver:80 ,anotherserver'
130-
let bypass = pm.checkBypass(url.parse('http://myserver'))
129+
let bypass = pm.checkBypass(new URL('http://myserver'))
131130
expect(bypass).toBeTruthy()
132131
})
133132

134133
it('checkBypass returns false if host not in no_proxy list', () => {
135134
process.env['no_proxy'] = 'otherserver, myserver ,anotherserver:8080'
136-
let bypass = pm.checkBypass(url.parse('https://github.com'))
135+
let bypass = pm.checkBypass(new URL('https://github.com'))
137136
expect(bypass).toBeFalsy()
138137
})
139138

140139
it('checkBypass returns false if empty no_proxy', () => {
141140
process.env['no_proxy'] = ''
142-
let bypass = pm.checkBypass(url.parse('https://github.com'))
141+
let bypass = pm.checkBypass(new URL('https://github.com'))
143142
expect(bypass).toBeFalsy()
144143
})
145144

index.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import url = require('url')
21
import http = require('http')
32
import https = require('https')
43
import ifm = require('./interfaces')
@@ -50,7 +49,7 @@ export enum MediaTypes {
5049
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
5150
*/
5251
export function getProxyUrl(serverUrl: string): string {
53-
let proxyUrl = pm.getProxyUrl(url.parse(serverUrl))
52+
let proxyUrl = pm.getProxyUrl(new URL(serverUrl))
5453
return proxyUrl ? proxyUrl.href : ''
5554
}
5655

@@ -104,7 +103,7 @@ export class HttpClientResponse implements ifm.IHttpClientResponse {
104103
}
105104

106105
export function isHttps(requestUrl: string) {
107-
let parsedUrl: url.Url = url.parse(requestUrl)
106+
let parsedUrl: URL = new URL(requestUrl)
108107
return parsedUrl.protocol === 'https:'
109108
}
110109

@@ -334,7 +333,7 @@ export class HttpClient {
334333
throw new Error('Client has already been disposed.')
335334
}
336335

337-
let parsedUrl = url.parse(requestUrl)
336+
let parsedUrl = new URL(requestUrl)
338337
let info: ifm.IRequestInfo = this._prepareRequest(verb, parsedUrl, headers)
339338

340339
// Only perform retries on reads since writes may not be idempotent.
@@ -383,7 +382,7 @@ export class HttpClient {
383382
// if there's no location to redirect to, we won't
384383
break
385384
}
386-
let parsedRedirectUrl = url.parse(redirectUrl)
385+
let parsedRedirectUrl = new URL(redirectUrl)
387386
if (
388387
parsedUrl.protocol == 'https:' &&
389388
parsedUrl.protocol != parsedRedirectUrl.protocol &&
@@ -398,6 +397,16 @@ export class HttpClient {
398397
// which will leak the open socket.
399398
await response.readBody()
400399

400+
// strip authorization header if redirected to a different hostname
401+
if (parsedRedirectUrl.hostname !== parsedUrl.hostname) {
402+
for (let header in headers) {
403+
// header names are case insensitive
404+
if (header.toLowerCase() === 'authorization') {
405+
delete headers[header]
406+
}
407+
}
408+
}
409+
401410
// let's make the request with the new redirectUrl
402411
info = this._prepareRequest(verb, parsedRedirectUrl, headers)
403412
response = await this.requestRaw(info, data)
@@ -528,13 +537,13 @@ export class HttpClient {
528537
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
529538
*/
530539
public getAgent(serverUrl: string): http.Agent {
531-
let parsedUrl = url.parse(serverUrl)
540+
let parsedUrl = new URL(serverUrl)
532541
return this._getAgent(parsedUrl)
533542
}
534543

535544
private _prepareRequest(
536545
method: string,
537-
requestUrl: url.Url,
546+
requestUrl: URL,
538547
headers: ifm.IHeaders
539548
): ifm.IRequestInfo {
540549
const info: ifm.IRequestInfo = <ifm.IRequestInfo>{}
@@ -599,9 +608,9 @@ export class HttpClient {
599608
return additionalHeaders[header] || clientHeader || _default
600609
}
601610

602-
private _getAgent(parsedUrl: url.Url): http.Agent {
611+
private _getAgent(parsedUrl: URL): http.Agent {
603612
let agent
604-
let proxyUrl: url.Url = pm.getProxyUrl(parsedUrl)
613+
let proxyUrl: URL = pm.getProxyUrl(parsedUrl)
605614
let useProxy = proxyUrl && proxyUrl.hostname
606615

607616
if (this._keepAlive && useProxy) {
@@ -633,7 +642,7 @@ export class HttpClient {
633642
maxSockets: maxSockets,
634643
keepAlive: this._keepAlive,
635644
proxy: {
636-
proxyAuth: proxyUrl.auth,
645+
proxyAuth: `${proxyUrl.username}:${proxyUrl.password}`,
637646
host: proxyUrl.hostname,
638647
port: proxyUrl.port
639648
}

interfaces.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import http = require('http')
2-
import url = require('url')
32

43
export interface IHeaders {
54
[key: string]: any
@@ -73,7 +72,7 @@ export interface IHttpClientResponse {
7372

7473
export interface IRequestInfo {
7574
options: http.RequestOptions
76-
parsedUrl: url.Url
75+
parsedUrl: URL
7776
httpModule: any
7877
}
7978

proxy.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import * as url from 'url'
2-
3-
export function getProxyUrl(reqUrl: url.Url): url.Url | undefined {
1+
export function getProxyUrl(reqUrl: URL): URL | undefined {
42
let usingSsl = reqUrl.protocol === 'https:'
53

6-
let proxyUrl: url.Url
4+
let proxyUrl: URL
75
if (checkBypass(reqUrl)) {
86
return proxyUrl
97
}
@@ -16,13 +14,13 @@ export function getProxyUrl(reqUrl: url.Url): url.Url | undefined {
1614
}
1715

1816
if (proxyVar) {
19-
proxyUrl = url.parse(proxyVar)
17+
proxyUrl = new URL(proxyVar)
2018
}
2119

2220
return proxyUrl
2321
}
2422

25-
export function checkBypass(reqUrl: url.Url): boolean {
23+
export function checkBypass(reqUrl: URL): boolean {
2624
if (!reqUrl.hostname) {
2725
return false
2826
}

0 commit comments

Comments
 (0)