diff --git a/package.json b/package.json index 8a9af319..1dbf15a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "proxy-chain", - "version": "2.5.1", + "version": "2.5.2", "description": "Node.js implementation of a proxy server (think Squid) with support for SSL, authentication, upstream proxy chaining, and protocol tunneling.", "main": "dist/index.js", "keywords": [ diff --git a/src/anonymize_proxy.ts b/src/anonymize_proxy.ts index 9771d211..0f5deff1 100644 --- a/src/anonymize_proxy.ts +++ b/src/anonymize_proxy.ts @@ -2,7 +2,7 @@ import net from 'net'; import http from 'http'; import { Buffer } from 'buffer'; import { URL } from 'url'; -import { Server } from './server'; +import { Server, SOCKS_PROTOCOLS } from './server'; import { nodeify } from './utils/nodeify'; // Dictionary, key is value returned from anonymizeProxy(), value is Server instance. @@ -38,10 +38,9 @@ export const anonymizeProxy = ( } const parsedProxyUrl = new URL(proxyUrl); - if (parsedProxyUrl.protocol !== 'http:') { - throw new Error( - 'Invalid "proxyUrl" option: only HTTP proxies are currently supported.', - ); + if (!['http:', ...SOCKS_PROTOCOLS].includes(parsedProxyUrl.protocol)) { + // eslint-disable-next-line max-len + throw new Error(`Invalid "proxyUrl" provided: URL must have one of the following protocols: "http", ${SOCKS_PROTOCOLS.map((p) => `"${p.replace(':', '')}"`).join(', ')} (was "${parsedProxyUrl}")`); } // If upstream proxy requires no password, return it directly diff --git a/src/server.ts b/src/server.ts index ddea7e8b..2590cd74 100644 --- a/src/server.ts +++ b/src/server.ts @@ -21,7 +21,7 @@ import { customConnect } from './custom_connect'; import { forwardSocks } from './forward_socks'; import { chainSocks } from './chain_socks'; -const SOCKS_PROTOCOLS = ['socks:', 'socks4:', 'socks4a:', 'socks5:', 'socks5h:']; +export const SOCKS_PROTOCOLS = ['socks:', 'socks4:', 'socks4a:', 'socks5:', 'socks5h:']; // TODO: // - Implement this requirement from rfc7230 diff --git a/test/anonymize_proxy.js b/test/anonymize_proxy.js index 16ebabc2..46d2e24f 100644 --- a/test/anonymize_proxy.js +++ b/test/anonymize_proxy.js @@ -114,19 +114,11 @@ describe('utils.anonymizeProxy', function () { assert.throws(() => { closeAnonymizedProxy(null); }, Error); }); - it('throws for unsupported proxy protocols', () => { - assert.throws(() => { anonymizeProxy('socks://whatever.com'); }, Error); + it('throws for unsupported https: protocol', () => { assert.throws(() => { anonymizeProxy('https://whatever.com'); }, Error); - assert.throws(() => { anonymizeProxy('socks5://whatever.com'); }, Error); - assert.throws(() => { - anonymizeProxy({ url: 'socks://whatever.com' }); - }, Error); assert.throws(() => { anonymizeProxy({ url: 'https://whatever.com' }); }, Error); - assert.throws(() => { - anonymizeProxy({ url: 'socks5://whatever.com' }); - }, Error); }); it('throws for invalid ports', () => { @@ -144,16 +136,12 @@ describe('utils.anonymizeProxy', function () { it('throws for invalid URLs', () => { assert.throws(() => { anonymizeProxy('://whatever.com'); }, Error); assert.throws(() => { anonymizeProxy('https://whatever.com'); }, Error); - assert.throws(() => { anonymizeProxy('socks5://whatever.com'); }, Error); assert.throws(() => { anonymizeProxy({ url: '://whatever.com' }); }, Error); assert.throws(() => { anonymizeProxy({ url: 'https://whatever.com' }); }, Error); - assert.throws(() => { - anonymizeProxy({ url: 'socks5://whatever.com' }); - }, Error); }); it('keeps already anonymous proxies (both with callbacks and promises)', () => { diff --git a/test/socks.js b/test/socks.js index f2ef988c..26ed1049 100644 --- a/test/socks.js +++ b/test/socks.js @@ -7,10 +7,12 @@ const ProxyChain = require('../src/index'); describe('SOCKS protocol', () => { let socksServer; let proxyServer; + let anonymizeProxyUrl; afterEach(() => { if (socksServer) socksServer.close(); if (proxyServer) proxyServer.close(); + if (anonymizeProxyUrl) ProxyChain.closeAnonymizedProxy(anonymizeProxyUrl, true); }); it('works without auth', (done) => { @@ -70,4 +72,27 @@ describe('SOCKS protocol', () => { .catch(done); }); }).timeout(5 * 1000); + + it('works with anonymizeProxy', (done) => { + portastic.find({ min: 50500, max: 50750 }).then((ports) => { + const [socksPort, proxyPort] = ports; + socksServer = socksv5.createServer((info, accept) => { + accept(); + }); + socksServer.listen(socksPort, 'localhost'); + socksServer.useAuth(socksv5.auth.UserPassword((user, password, cb) => { + cb(user === 'proxy-chain' && password === 'rules!'); + })); + + ProxyChain.anonymizeProxy({ port: proxyPort, url: `socks://proxy-chain:rules!@localhost:${socksPort}` }).then((anonymizedProxyUrl) => { + anonymizeProxyUrl = anonymizedProxyUrl; + gotScraping.get({ url: 'https://example.com', proxyUrl: anonymizedProxyUrl }) + .then((response) => { + expect(response.body).to.contain('Example Domain'); + done(); + }) + .catch(done); + }); + }); + }).timeout(5 * 1000); });