diff --git a/CHANGELOG.md b/CHANGELOG.md index 9513f2c..f582fcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [Unreleased] + +### Added + +- `Innmind\HttpTransport\Curl::proxy()` + ## 8.0.0 - 2025-05-10 ### Changed diff --git a/src/Curl.php b/src/Curl.php index 1339ef6..a484ee8 100644 --- a/src/Curl.php +++ b/src/Curl.php @@ -11,6 +11,7 @@ Request, Factory\Header\Factory, }; +use Innmind\Url\Url; use Innmind\TimeContinuum\{ Clock, Period, @@ -33,6 +34,7 @@ private function __construct( private Period $timeout, private \Closure $heartbeat, private bool $disableSSLVerification, + private ?Url $proxy, ) { } @@ -44,6 +46,7 @@ public function __invoke(Request $request): Either $this->io, $request, $this->disableSSLVerification, + $this->proxy, ); $this->concurrency->add($scheduled); @@ -67,6 +70,7 @@ public static function of( Period::second(1), static fn() => null, false, + null, ); } @@ -84,6 +88,7 @@ public function maxConcurrency(int $max): self $this->timeout, $this->heartbeat, $this->disableSSLVerification, + $this->proxy, ); } @@ -105,6 +110,7 @@ public function heartbeat(Period $timeout, ?callable $heartbeat = null): self default => \Closure::fromCallable($heartbeat), }, $this->disableSSLVerification, + $this->proxy, ); } @@ -123,6 +129,23 @@ public function disableSSLVerification(): self $this->timeout, $this->heartbeat, true, + $this->proxy, + ); + } + + /** + * @psalm-mutation-free + */ + public function proxy(Url $proxy): self + { + return new self( + $this->headerFactory, + $this->io, + $this->concurrency, + $this->timeout, + $this->heartbeat, + $this->disableSSLVerification, + $proxy, ); } } diff --git a/src/Curl/Scheduled.php b/src/Curl/Scheduled.php index edb0b0c..3a9008e 100644 --- a/src/Curl/Scheduled.php +++ b/src/Curl/Scheduled.php @@ -15,7 +15,11 @@ Headers, Factory\Header\Factory, }; -use Innmind\Url\Authority\UserInformation\User; +use Innmind\Url\{ + Url, + Authority\UserInformation, + Authority\UserInformation\User, +}; use Innmind\IO\{ IO, Files\Temporary, @@ -36,6 +40,7 @@ private function __construct( private IO $io, private Request $request, private bool $disableSSLVerification, + private ?Url $proxy, ) { } @@ -44,12 +49,14 @@ public static function of( IO $io, Request $request, bool $disableSSLVerification, + ?Url $proxy, ): self { return new self( $headerFactory, $io, $request, $disableSSLVerification, + $proxy, ); } @@ -143,6 +150,42 @@ private function options(): Either $options[] = [\CURLOPT_SSL_VERIFYPEER, false]; } + if ($this->proxy) { + $options[] = [ + \CURLOPT_PROXY, + $this + ->proxy + ->withAuthority( + $this->proxy->authority()->withoutUserInformation(), + ) + ->withoutPath() + ->withoutQuery() + ->withoutFragment() + ->toString(), + ]; + + if (!$this->proxy->authority()->userInformation()->equals(UserInformation::none())) { + $options[] = [ + \CURLOPT_PROXYUSERNAME, + $this + ->proxy + ->authority() + ->userInformation() + ->user() + ->toString(), + ]; + $options[] = [ + \CURLOPT_PROXYPASSWORD, + $this + ->proxy + ->authority() + ->userInformation() + ->password() + ->toString(), + ]; + } + } + $header = match ($this->request->method()) { Method::head => [\CURLOPT_NOBODY, true], Method::get => [\CURLOPT_HTTPGET, true], diff --git a/tests/CurlTest.php b/tests/CurlTest.php index 61b8a10..8f9bc42 100644 --- a/tests/CurlTest.php +++ b/tests/CurlTest.php @@ -542,4 +542,7 @@ public function testTimeout() } // Don't know how to test MalformedResponse, ConnectionFailed, Information and ServerError + + // Proxies are not tested due to unreliable results on free proxies such as + // https://github.com/proxifly/free-proxy-list?tab=readme-ov-file }