Skip to content

Commit 3145427

Browse files
authored
Merge pull request #181 from smckee-r7/fix-pac-file-support
Fix pac proxy agent support
2 parents 4f79ee5 + 52c6912 commit 3145427

File tree

2 files changed

+116
-4
lines changed

2 files changed

+116
-4
lines changed

src/rules/http-agents.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as https from 'https';
66
import * as LRU from 'lru-cache';
77

88
import getHttpsProxyAgent = require('https-proxy-agent');
9-
import getPacProxyAgent = require('pac-proxy-agent');
9+
import { PacProxyAgent } from 'pac-proxy-agent';
1010
import { SocksProxyAgent } from 'socks-proxy-agent';
1111
const getSocksProxyAgent = (opts: any) => new SocksProxyAgent(opts);
1212

@@ -28,8 +28,8 @@ const ProxyAgentFactoryMap = {
2828
'http:': getHttpsProxyAgent, // HTTPS here really means 'CONNECT-tunnelled' - it can do either
2929
'https:': getHttpsProxyAgent,
3030

31-
'pac+http:': getPacProxyAgent,
32-
'pac+https:': getPacProxyAgent,
31+
'pac+http:': (...args: any) => new PacProxyAgent(...args),
32+
'pac+https:': (...args: any) => new PacProxyAgent(...args),
3333

3434
'socks:': getSocksProxyAgent,
3535
'socks4:': getSocksProxyAgent,
@@ -76,7 +76,7 @@ export async function getAgent({
7676
});
7777

7878
if (!proxyAgentCache.has(cacheKey)) {
79-
const { protocol, auth, hostname, port } = url.parse(proxySetting.proxyUrl);
79+
const { href, protocol, auth, hostname, port } = url.parse(proxySetting.proxyUrl);
8080
const buildProxyAgent = ProxyAgentFactoryMap[protocol as keyof typeof ProxyAgentFactoryMap];
8181

8282
// If you specify trusted CAs, we override the CAs used for this connection, i.e. the trusted
@@ -88,6 +88,7 @@ export async function getAgent({
8888
);
8989

9090
proxyAgentCache.set(cacheKey, buildProxyAgent({
91+
href,
9192
protocol,
9293
auth,
9394
hostname,

test/integration/proxying/upstream-proxying.spec.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import _ = require("lodash");
22
import * as fs from 'fs/promises';
33
import request = require("request-promise-native");
4+
import url = require('url');
45

56
import { getLocal, Mockttp, MockedEndpoint, getAdminServer, getRemote } from "../../..";
67
import {
@@ -453,6 +454,116 @@ nodeOnly(() => {
453454

454455
});
455456

457+
describe("with a PAC file", () => {
458+
const https = {
459+
keyPath: './test/fixtures/test-ca.key',
460+
certPath: './test/fixtures/test-ca.pem'
461+
};
462+
463+
const intermediateProxy = getLocal({ https });
464+
465+
beforeEach(async () => {
466+
server = getLocal({ https });
467+
await server.start();
468+
469+
await intermediateProxy.start();
470+
471+
process.env = _.merge({}, process.env, server.proxyEnv);
472+
});
473+
474+
afterEach(async () => {
475+
await intermediateProxy.stop();
476+
});
477+
478+
it("should forward traffic to intermediateProxy using PAC file", async () => {
479+
const pacFile = `function FindProxyForURL(url, host) { return "PROXY ${url.parse(intermediateProxy.url).host}"; }`;
480+
await remoteServer.forGet('/proxy-all').thenReply(200, pacFile);
481+
482+
await server.forAnyRequest().thenPassThrough({
483+
ignoreHostHttpsErrors: true,
484+
proxyConfig: {
485+
proxyUrl: `pac+${remoteServer.url}/proxy-all`
486+
}
487+
});
488+
489+
await intermediateProxy.forAnyRequest().thenPassThrough({
490+
ignoreHostHttpsErrors: true,
491+
beforeRequest: (req) => {
492+
expect(req.url).to.equal('https://example.com/');
493+
return {
494+
response: {
495+
statusCode: 200,
496+
body: 'Proxied'
497+
}
498+
};
499+
}
500+
});
501+
502+
// make a request that hits the proxy based on PAC file
503+
expect(await request.get('https://example.com/')).to.equal('Proxied');
504+
});
505+
506+
it("should bypass intermediateProxy using PAC file", async () => {
507+
const pacFile = `function FindProxyForURL(url, host) { if (host.endsWith(".com")) { return "PROXY ${url.parse(intermediateProxy.url).host}"; } else { return "DIRECT"; } }`;
508+
await remoteServer.forGet('/proxy-bypass').thenReply(200, pacFile);
509+
await remoteServer.forGet('/remote-response').thenReply(200, 'Remote response');
510+
511+
await server.forAnyRequest().thenPassThrough({
512+
ignoreHostHttpsErrors: true,
513+
proxyConfig: {
514+
proxyUrl: `pac+${remoteServer.url}/proxy-bypass`
515+
}
516+
});
517+
518+
await intermediateProxy.forAnyRequest().thenPassThrough({
519+
ignoreHostHttpsErrors: true,
520+
beforeRequest: (req) => {
521+
expect(req.url).to.not.equal('https://example.org/');
522+
return {
523+
response: {
524+
statusCode: 200,
525+
body: 'Proxied'
526+
}
527+
};
528+
}
529+
});
530+
531+
// make a request that hits the proxy based on PAC file
532+
expect(await request.get('https://example.com/')).to.equal('Proxied');
533+
534+
// make a request that bypasses proxy based on PAC file
535+
expect(await request.get(remoteServer.urlFor('/remote-response'))).to.equal('Remote response');
536+
});
537+
538+
it("should fallback to intermediateProxy using PAC file", async () => {
539+
const pacFile = `function FindProxyForURL(url, host) { return "PROXY invalid-proxy:8080; PROXY ${url.parse(intermediateProxy.url).host};"; }`;
540+
await remoteServer.forGet('/proxy-fallback').thenReply(200, pacFile);
541+
542+
await server.forAnyRequest().thenPassThrough({
543+
ignoreHostHttpsErrors: true,
544+
proxyConfig: {
545+
proxyUrl: `pac+${remoteServer.url}/proxy-fallback`
546+
}
547+
});
548+
549+
await intermediateProxy.forAnyRequest().thenPassThrough({
550+
ignoreHostHttpsErrors: true,
551+
beforeRequest: (req) => {
552+
expect(req.url).to.equal('https://example.com/');
553+
return {
554+
response: {
555+
statusCode: 200,
556+
body: 'Proxied'
557+
}
558+
};
559+
}
560+
});
561+
562+
// make a request that hits the proxy based on PAC file
563+
expect(await request.get('https://example.com/')).to.equal('Proxied');
564+
});
565+
});
566+
456567
});
457568

458569
});

0 commit comments

Comments
 (0)