Skip to content

Commit

Permalink
Merge pull request #181 from smckee-r7/fix-pac-file-support
Browse files Browse the repository at this point in the history
Fix pac proxy agent support
  • Loading branch information
pimterry authored Sep 25, 2024
2 parents 4f79ee5 + 52c6912 commit 3145427
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 4 deletions.
9 changes: 5 additions & 4 deletions src/rules/http-agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as https from 'https';
import * as LRU from 'lru-cache';

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

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

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

'socks:': getSocksProxyAgent,
'socks4:': getSocksProxyAgent,
Expand Down Expand Up @@ -76,7 +76,7 @@ export async function getAgent({
});

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

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

proxyAgentCache.set(cacheKey, buildProxyAgent({
href,
protocol,
auth,
hostname,
Expand Down
111 changes: 111 additions & 0 deletions test/integration/proxying/upstream-proxying.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import _ = require("lodash");
import * as fs from 'fs/promises';
import request = require("request-promise-native");
import url = require('url');

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

});

describe("with a PAC file", () => {
const https = {
keyPath: './test/fixtures/test-ca.key',
certPath: './test/fixtures/test-ca.pem'
};

const intermediateProxy = getLocal({ https });

beforeEach(async () => {
server = getLocal({ https });
await server.start();

await intermediateProxy.start();

process.env = _.merge({}, process.env, server.proxyEnv);
});

afterEach(async () => {
await intermediateProxy.stop();
});

it("should forward traffic to intermediateProxy using PAC file", async () => {
const pacFile = `function FindProxyForURL(url, host) { return "PROXY ${url.parse(intermediateProxy.url).host}"; }`;
await remoteServer.forGet('/proxy-all').thenReply(200, pacFile);

await server.forAnyRequest().thenPassThrough({
ignoreHostHttpsErrors: true,
proxyConfig: {
proxyUrl: `pac+${remoteServer.url}/proxy-all`
}
});

await intermediateProxy.forAnyRequest().thenPassThrough({
ignoreHostHttpsErrors: true,
beforeRequest: (req) => {
expect(req.url).to.equal('https://example.com/');
return {
response: {
statusCode: 200,
body: 'Proxied'
}
};
}
});

// make a request that hits the proxy based on PAC file
expect(await request.get('https://example.com/')).to.equal('Proxied');
});

it("should bypass intermediateProxy using PAC file", async () => {
const pacFile = `function FindProxyForURL(url, host) { if (host.endsWith(".com")) { return "PROXY ${url.parse(intermediateProxy.url).host}"; } else { return "DIRECT"; } }`;
await remoteServer.forGet('/proxy-bypass').thenReply(200, pacFile);
await remoteServer.forGet('/remote-response').thenReply(200, 'Remote response');

await server.forAnyRequest().thenPassThrough({
ignoreHostHttpsErrors: true,
proxyConfig: {
proxyUrl: `pac+${remoteServer.url}/proxy-bypass`
}
});

await intermediateProxy.forAnyRequest().thenPassThrough({
ignoreHostHttpsErrors: true,
beforeRequest: (req) => {
expect(req.url).to.not.equal('https://example.org/');
return {
response: {
statusCode: 200,
body: 'Proxied'
}
};
}
});

// make a request that hits the proxy based on PAC file
expect(await request.get('https://example.com/')).to.equal('Proxied');

// make a request that bypasses proxy based on PAC file
expect(await request.get(remoteServer.urlFor('/remote-response'))).to.equal('Remote response');
});

it("should fallback to intermediateProxy using PAC file", async () => {
const pacFile = `function FindProxyForURL(url, host) { return "PROXY invalid-proxy:8080; PROXY ${url.parse(intermediateProxy.url).host};"; }`;
await remoteServer.forGet('/proxy-fallback').thenReply(200, pacFile);

await server.forAnyRequest().thenPassThrough({
ignoreHostHttpsErrors: true,
proxyConfig: {
proxyUrl: `pac+${remoteServer.url}/proxy-fallback`
}
});

await intermediateProxy.forAnyRequest().thenPassThrough({
ignoreHostHttpsErrors: true,
beforeRequest: (req) => {
expect(req.url).to.equal('https://example.com/');
return {
response: {
statusCode: 200,
body: 'Proxied'
}
};
}
});

// make a request that hits the proxy based on PAC file
expect(await request.get('https://example.com/')).to.equal('Proxied');
});
});

});

});

0 comments on commit 3145427

Please sign in to comment.