diff --git a/package-lock.json b/package-lock.json index f824acca92..b8b37cd44e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5073,6 +5073,11 @@ "node": ">= 0.10" } }, + "node_modules/ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + }, "node_modules/ip-regex": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", @@ -7818,6 +7823,15 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -7960,6 +7974,32 @@ "dev": true, "optional": true }, + "node_modules/socks": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", + "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==", + "dependencies": { + "ip": "^1.1.5", + "smart-buffer": "^4.1.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.0.tgz", + "integrity": "sha512-57e7lwCN4Tzt3mXz25VxOErJKXlPfXmkMLnk310v/jwW20jWRVcgsOit+xNkN3eIEdB47GwnfAEBLacZ/wVIKg==", + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.1", + "socks": "^2.6.1" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/socksv5": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/socksv5/-/socksv5-0.0.6.tgz", @@ -9304,6 +9344,7 @@ "proper-lockfile": "^4.1.1", "proxy-from-env": "^1.1.0", "rimraf": "^3.0.2", + "socks-proxy-agent": "^6.1.0", "stack-utils": "^2.0.3", "ws": "^7.4.6", "yauzl": "^2.10.0", @@ -13257,6 +13298,11 @@ "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", "dev": true }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + }, "ip-regex": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", @@ -14687,6 +14733,7 @@ "proper-lockfile": "^4.1.1", "proxy-from-env": "^1.1.0", "rimraf": "^3.0.2", + "socks-proxy-agent": "^6.1.0", "stack-utils": "^2.0.3", "ws": "^7.4.6", "yauzl": "^2.10.0", @@ -15384,6 +15431,11 @@ } } }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -15505,6 +15557,25 @@ "kind-of": "^3.2.0" } }, + "socks": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", + "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==", + "requires": { + "ip": "^1.1.5", + "smart-buffer": "^4.1.0" + } + }, + "socks-proxy-agent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.0.tgz", + "integrity": "sha512-57e7lwCN4Tzt3mXz25VxOErJKXlPfXmkMLnk310v/jwW20jWRVcgsOit+xNkN3eIEdB47GwnfAEBLacZ/wVIKg==", + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.1", + "socks": "^2.6.1" + } + }, "socksv5": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/socksv5/-/socksv5-0.0.6.tgz", diff --git a/packages/playwright-core/package.json b/packages/playwright-core/package.json index e1a15856d2..3d99c83811 100644 --- a/packages/playwright-core/package.json +++ b/packages/playwright-core/package.json @@ -38,6 +38,7 @@ "proper-lockfile": "^4.1.1", "proxy-from-env": "^1.1.0", "rimraf": "^3.0.2", + "socks-proxy-agent": "^6.1.0", "stack-utils": "^2.0.3", "ws": "^7.4.6", "yauzl": "^2.10.0", diff --git a/packages/playwright-core/src/server/fetch.ts b/packages/playwright-core/src/server/fetch.ts index e8525077c6..942dd1eac0 100644 --- a/packages/playwright-core/src/server/fetch.ts +++ b/packages/playwright-core/src/server/fetch.ts @@ -17,6 +17,7 @@ import * as http from 'http'; import * as https from 'https'; import { HttpsProxyAgent } from 'https-proxy-agent'; +import { SocksProxyAgent } from 'socks-proxy-agent'; import { pipeline, Readable, Transform } from 'stream'; import url from 'url'; import zlib from 'zlib'; @@ -108,9 +109,16 @@ export abstract class FetchRequest extends SdkObject { if (proxy) { // TODO: support bypass proxy const proxyOpts = url.parse(proxy.server); - if (proxy.username) - proxyOpts.auth = `${proxy.username}:${proxy.password || ''}`; - agent = new HttpsProxyAgent(proxyOpts); + if (proxyOpts.protocol?.startsWith('socks')) { + agent = new SocksProxyAgent({ + host: proxyOpts.hostname, + port: proxyOpts.port || undefined, + }); + } else { + if (proxy.username) + proxyOpts.auth = `${proxy.username}:${proxy.password || ''}`; + agent = new HttpsProxyAgent(proxyOpts); + } } const timeout = defaults.timeoutSettings.timeout(params); diff --git a/tests/global-fetch.spec.ts b/tests/global-fetch.spec.ts index f6b6ec89af..f32eb9bc98 100644 --- a/tests/global-fetch.spec.ts +++ b/tests/global-fetch.spec.ts @@ -115,6 +115,14 @@ it('should return error with wrong credentials', async ({ playwright, server }) expect(response2.status()).toBe(401); }); +it('should use socks proxy', async ({ playwright, server, socksPort }) => { + const request = await playwright.request.newContext({ proxy: { + server: `socks5://localhost:${socksPort}`, + } }); + const response = await request.get(server.EMPTY_PAGE); + expect(await response.text()).toContain('Served by the SOCKS proxy'); +}); + it('should pass proxy credentials', async ({ playwright, server, proxyServer }) => { proxyServer.forwardTo(server.PORT); let auth;