diff --git a/src/composer.json b/src/composer.json index 6f2b7754..d88ad12b 100644 --- a/src/composer.json +++ b/src/composer.json @@ -26,7 +26,8 @@ "symfony/process": "^5", "symfony/filesystem": "^5", "lucatume/di52": "^3", - "stecman/symfony-console-completion": "^0.11.0" + "stecman/symfony-console-completion": "^0.11.0", + "composer/ca-bundle": "^1.4" }, "require-dev": { "phpunit/phpunit": "^8", diff --git a/src/composer.lock b/src/composer.lock index 82f67bb5..2bd507c8 100644 --- a/src/composer.lock +++ b/src/composer.lock @@ -6,6 +6,82 @@ ], "content-hash": "ed84f6c3e05a35fd3f0bf19609c0d984", "packages": [ + { + "name": "composer/ca-bundle", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "3ce240142f6d59b808dd65c1f52f7a1c252e6cfd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/3ce240142f6d59b808dd65c1f52f7a1c252e6cfd", + "reference": "3ce240142f6d59b808dd65c1f52f7a1c252e6cfd", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.55", + "psr/log": "^1.0", + "symfony/phpunit-bridge": "^4.2 || ^5", + "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/ca-bundle/issues", + "source": "https://github.com/composer/ca-bundle/tree/1.4.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-02-23T10:16:52+00:00" + }, { "name": "lucatume/di52", "version": "3.3.5", @@ -1291,20 +1367,21 @@ }, { "name": "phar-io/manifest", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -1345,9 +1422,15 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2021-07-20T11:28:43+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", @@ -1526,16 +1609,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "7.0.15", + "version": "7.0.17", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "819f92bba8b001d4363065928088de22f25a3a48" + "reference": "40a4ed114a4aea5afd6df8d0f0c9cd3033097f66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/819f92bba8b001d4363065928088de22f25a3a48", - "reference": "819f92bba8b001d4363065928088de22f25a3a48", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/40a4ed114a4aea5afd6df8d0f0c9cd3033097f66", + "reference": "40a4ed114a4aea5afd6df8d0f0c9cd3033097f66", "shasum": "" }, "require": { @@ -1587,7 +1670,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.15" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.17" }, "funding": [ { @@ -1595,20 +1678,20 @@ "type": "github" } ], - "time": "2021-07-26T12:20:09+00:00" + "time": "2024-03-02T06:09:37+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "2.0.5", + "version": "2.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5" + "reference": "69deeb8664f611f156a924154985fbd4911eb36b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", - "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/69deeb8664f611f156a924154985fbd4911eb36b", + "reference": "69deeb8664f611f156a924154985fbd4911eb36b", "shasum": "" }, "require": { @@ -1647,7 +1730,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.5" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.6" }, "funding": [ { @@ -1655,7 +1738,7 @@ "type": "github" } ], - "time": "2021-12-02T12:42:26+00:00" + "time": "2024-03-01T13:39:50+00:00" }, { "name": "phpunit/php-text-template", @@ -1704,16 +1787,16 @@ }, { "name": "phpunit/php-timer", - "version": "2.1.3", + "version": "2.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662" + "reference": "a691211e94ff39a34811abd521c31bd5b305b0bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662", - "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/a691211e94ff39a34811abd521c31bd5b305b0bb", + "reference": "a691211e94ff39a34811abd521c31bd5b305b0bb", "shasum": "" }, "require": { @@ -1751,7 +1834,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.3" + "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.4" }, "funding": [ { @@ -1759,7 +1842,7 @@ "type": "github" } ], - "time": "2020-11-30T08:20:02+00:00" + "time": "2024-03-01T13:42:41+00:00" }, { "name": "phpunit/php-token-stream", @@ -1921,16 +2004,16 @@ }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" + "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", + "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", "shasum": "" }, "require": { @@ -1964,7 +2047,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.3" }, "funding": [ { @@ -1972,7 +2055,7 @@ "type": "github" } ], - "time": "2020-11-30T08:15:22+00:00" + "time": "2024-03-01T13:45:45+00:00" }, { "name": "sebastian/comparator", @@ -2116,16 +2199,16 @@ }, { "name": "sebastian/environment", - "version": "4.2.4", + "version": "4.2.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0" + "reference": "56932f6049a0482853056ffd617c91ffcc754205" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", - "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/56932f6049a0482853056ffd617c91ffcc754205", + "reference": "56932f6049a0482853056ffd617c91ffcc754205", "shasum": "" }, "require": { @@ -2167,7 +2250,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/4.2.4" + "source": "https://github.com/sebastianbergmann/environment/tree/4.2.5" }, "funding": [ { @@ -2175,24 +2258,24 @@ "type": "github" } ], - "time": "2020-11-30T07:53:42+00:00" + "time": "2024-03-01T13:49:59+00:00" }, { "name": "sebastian/exporter", - "version": "3.1.5", + "version": "3.1.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "73a9676f2833b9a7c36968f9d882589cd75511e6" + "reference": "1939bc8fd1d39adcfa88c5b35335910869214c56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/73a9676f2833b9a7c36968f9d882589cd75511e6", - "reference": "73a9676f2833b9a7c36968f9d882589cd75511e6", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/1939bc8fd1d39adcfa88c5b35335910869214c56", + "reference": "1939bc8fd1d39adcfa88c5b35335910869214c56", "shasum": "" }, "require": { - "php": ">=7.0", + "php": ">=7.2", "sebastian/recursion-context": "^3.0" }, "require-dev": { @@ -2244,7 +2327,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.5" + "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.6" }, "funding": [ { @@ -2252,7 +2335,7 @@ "type": "github" } ], - "time": "2022-09-14T06:00:17+00:00" + "time": "2024-03-02T06:21:38+00:00" }, { "name": "sebastian/global-state", @@ -2320,16 +2403,16 @@ }, { "name": "sebastian/object-enumerator", - "version": "3.0.4", + "version": "3.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2" + "reference": "ac5b293dba925751b808e02923399fb44ff0d541" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", - "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/ac5b293dba925751b808e02923399fb44ff0d541", + "reference": "ac5b293dba925751b808e02923399fb44ff0d541", "shasum": "" }, "require": { @@ -2365,7 +2448,7 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.5" }, "funding": [ { @@ -2373,20 +2456,20 @@ "type": "github" } ], - "time": "2020-11-30T07:40:27+00:00" + "time": "2024-03-01T13:54:02+00:00" }, { "name": "sebastian/object-reflector", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d" + "reference": "1d439c229e61f244ff1f211e5c99737f90c67def" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", - "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/1d439c229e61f244ff1f211e5c99737f90c67def", + "reference": "1d439c229e61f244ff1f211e5c99737f90c67def", "shasum": "" }, "require": { @@ -2420,7 +2503,7 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2" + "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.3" }, "funding": [ { @@ -2428,20 +2511,20 @@ "type": "github" } ], - "time": "2020-11-30T07:37:18+00:00" + "time": "2024-03-01T13:56:04+00:00" }, { "name": "sebastian/recursion-context", - "version": "3.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb" + "reference": "9bfd3c6f1f08c026f542032dfb42813544f7d64c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb", - "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/9bfd3c6f1f08c026f542032dfb42813544f7d64c", + "reference": "9bfd3c6f1f08c026f542032dfb42813544f7d64c", "shasum": "" }, "require": { @@ -2483,7 +2566,7 @@ "homepage": "http://www.github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.2" }, "funding": [ { @@ -2491,20 +2574,20 @@ "type": "github" } ], - "time": "2020-11-30T07:34:24+00:00" + "time": "2024-03-01T14:07:30+00:00" }, { "name": "sebastian/resource-operations", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3" + "reference": "72a7f7674d053d548003b16ff5a106e7e0e06eee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3", - "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/72a7f7674d053d548003b16ff5a106e7e0e06eee", + "reference": "72a7f7674d053d548003b16ff5a106e7e0e06eee", "shasum": "" }, "require": { @@ -2534,8 +2617,7 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.3" }, "funding": [ { @@ -2543,20 +2625,20 @@ "type": "github" } ], - "time": "2020-11-30T07:30:19+00:00" + "time": "2024-03-01T13:59:09+00:00" }, { "name": "sebastian/type", - "version": "1.1.4", + "version": "1.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4" + "reference": "18f071c3a29892b037d35e6b20ddf3ea39b42874" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/0150cfbc4495ed2df3872fb31b26781e4e077eb4", - "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/18f071c3a29892b037d35e6b20ddf3ea39b42874", + "reference": "18f071c3a29892b037d35e6b20ddf3ea39b42874", "shasum": "" }, "require": { @@ -2591,7 +2673,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/1.1.4" + "source": "https://github.com/sebastianbergmann/type/tree/1.1.5" }, "funding": [ { @@ -2599,7 +2681,7 @@ "type": "github" } ], - "time": "2020-11-30T07:25:11+00:00" + "time": "2024-03-01T14:04:07+00:00" }, { "name": "sebastian/version", diff --git a/src/src/RequestBuilder.php b/src/src/RequestBuilder.php index 6ed8e9a9..e503cba4 100644 --- a/src/src/RequestBuilder.php +++ b/src/src/RequestBuilder.php @@ -2,12 +2,10 @@ namespace QIT_CLI; +use Composer\CaBundle\CaBundle; use QIT_CLI\Exceptions\DoingAutocompleteException; use QIT_CLI\Exceptions\NetworkErrorException; -use QIT_CLI\IO\Input; use QIT_CLI\IO\Output; -use Symfony\Component\Console\Helper\QuestionHelper; -use Symfony\Component\Console\Question\ConfirmationQuestion; class RequestBuilder { /** @var string $url */ @@ -37,11 +35,6 @@ class RequestBuilder { /** @var int */ protected $timeout_in_seconds = 15; - /** - * @var bool Whether we asked about CA file on this request. - */ - protected static $asked_ca_file_override = false; - public function __construct( string $url = '' ) { $this->url = $url; } @@ -168,7 +161,19 @@ public function request(): string { CURLOPT_HEADER => 1, ]; - $this->maybe_set_certificate_authority_file( $curl_parameters ); + try { + $ca_path_or_file = CaBundle::getSystemCaRootBundlePath(); + + if ( is_dir( $ca_path_or_file ) ) { + $curl_parameters[ CURLOPT_CAPATH ] = $ca_path_or_file; + } else { + $curl_parameters[ CURLOPT_CAINFO ] = $ca_path_or_file; + } + } catch ( \Exception $e ) { + if ( App::make( Output::class )->isVerbose() ) { + App::make( Output::class )->writeln( 'Could not set CAINFO for cURL: ' . $e->getMessage() . '' ); + } + } if ( App::make( Output::class )->isVeryVerbose() ) { $curl_parameters[ CURLOPT_VERBOSE ] = true; @@ -269,22 +274,13 @@ public function request(): string { if ( $response_status_code === 429 ) { if ( $this->retry_429 > 0 ) { $this->retry_429 --; - App::make( Output::class )->writeln( 'Request failed... Retrying (429 Too many Requests)' ); + $sleep_seconds = $this->wait_after_429( $headers ); + App::make( Output::class )->writeln( sprintf( 'Request failed... Waiting %d seconds and retrying (429 Too many Requests)', $sleep_seconds ) ); - sleep( $this->wait_after_429( $headers ) ); + sleep( $sleep_seconds ); goto retry_request; // phpcs:ignore Generic.PHP.DiscourageGoto.Found } } else { - // Is it an SSL error? - foreach ( [ 'ssl', 'certificate', 'issuer' ] as $keyword ) { - if ( stripos( $error_message, $keyword ) !== false ) { - $downloaded = $this->maybe_download_certificate_authority_file(); - if ( $downloaded ) { - goto retry_request; // phpcs:ignore Generic.PHP.DiscourageGoto.Found - } - break; - } - } if ( $this->retry > 0 ) { $this->retry --; App::make( Output::class )->writeln( sprintf( 'Request failed... Retrying (HTTP Status Code %s)', $response_status_code ) ); @@ -309,113 +305,6 @@ public function request(): string { return $body; } - /** - * @param array $curl_parameters - * - * @return void - */ - protected function maybe_set_certificate_authority_file( array &$curl_parameters ) { - // Early bail: We only do this for Windows. - if ( ! is_windows() ) { - return; - } - - $cached_ca_filepath = App::make( Cache::class )->get( 'ca_filepath' ); - - // Cache hit. - if ( $cached_ca_filepath !== null && file_exists( $cached_ca_filepath ) ) { - $curl_parameters[ CURLOPT_CAINFO ] = $cached_ca_filepath; - } - } - - /** - * @return bool Whether it downloaded the CA file or not. - */ - protected function maybe_download_certificate_authority_file(): bool { - $output = App::make( Output::class ); - // Early bail: We only do this for Windows. - if ( ! is_windows() ) { - if ( $output->isVerbose() ) { - $output->writeln( 'Skipping certificate authority file check. Not running on Windows.' ); - } - - return false; - } - - if ( $output->isVerbose() ) { - $output->writeln( 'Checking if we need to download the certificate authority file...' ); - } - - $cached_ca_filepath = App::make( Cache::class )->get( 'ca_filepath' ); - - // Cache hit. - if ( $cached_ca_filepath !== null && file_exists( $cached_ca_filepath ) ) { - return false; - } - - if ( $output->isVerbose() ) { - $output->writeln( 'No cached certificate authority file found.' ); - } - - if ( self::$asked_ca_file_override ) { - if ( $output->isVerbose() ) { - $output->writeln( 'Skipping certificate authority file check. Already asked.' ); - } - - return false; - } - - self::$asked_ca_file_override = true; - - // Ask the user if he wants us to solve it for them. - $input = App::make( Input::class ); - - $helper = App::make( QuestionHelper::class ); - $question = new ConfirmationQuestion( "A QIT network request failed due to an SSL certificate issue on Windows. Would you like to download a CA file, used exclusively for QIT requests, to potentially fix this?\n Please answer [y/n]: ", false ); - - if ( getenv( 'QIT_WINDOWS_DOWNLOAD_CA' ) !== 'yes' && ( ! $input->isInteractive() || ! $helper->ask( $input, $output, $question ) ) ) { - if ( $output->isVerbose() ) { - $output->writeln( 'Skipping certificate authority file download.' ); - } - - return false; - } - - if ( $output->isVerbose() ) { - $output->writeln( 'Downloading certificate authority file...' ); - } - - // Download it to QIT Config Dir and save it in the cache. - $local_ca_file = Config::get_qit_dir() . 'cacert.pem'; - - if ( ! file_exists( $local_ca_file ) ) { - $remote_ca_file_contents = @file_get_contents( 'http://curl.se/ca/cacert.pem' ); - - if ( empty( $remote_ca_file_contents ) ) { - $output->writeln( "Could not download the certificate authority file. Please download it manually from http://curl.se/ca/cacert.pem and place it in $local_ca_file" ); - - return false; - } - - if ( ! file_put_contents( $local_ca_file, $remote_ca_file_contents ) ) { - $output->writeln( "Could not write the certificate authority file. Please download it manually from http://curl.se/ca/cacert.pem and place it in $local_ca_file" ); - - return false; - } - clearstatcache( true, $local_ca_file ); - } - - if ( $output->isVerbose() ) { - $output->writeln( 'Certificate authority file downloaded and saved.' ); - } - - $year_in_seconds = 60 * 60 * 24 * 365; - - App::make( Cache::class )->set( 'ca_filepath', $local_ca_file, $year_in_seconds ); - - return true; - } - protected function wait_after_429( string $headers, int $max_wait = 60 ): int { $retry_after = null; @@ -473,6 +362,8 @@ protected function wait_after_429( string $headers, int $max_wait = 60 ): int { // And no longer than 60 seconds. $retry_after = min( $max_wait, $retry_after ); + $retry_after += rand( 0, 5 ); // Add a random number of seconds to avoid all clients retrying at the same time. + return $retry_after; } diff --git a/src/tests/RequestBuilderTest.php b/src/tests/RequestBuilderTest.php index 9473cc8c..0c98a74b 100644 --- a/src/tests/RequestBuilderTest.php +++ b/src/tests/RequestBuilderTest.php @@ -2,6 +2,7 @@ namespace QIT_CLI_Tests; +use PHPUnit\Framework\AssertionFailedError; use QIT_CLI\RequestBuilder; use PHPUnit\Framework\TestCase; @@ -20,10 +21,21 @@ public function wait_after_429( string $headers, int $max_wait = 60 ): int { }; } + protected function assertRetryDelayWithinRange( $expected, $actual, $delta ) { + if ( $actual < $expected ) { + $this->fail( "Expected value is $expected, actual value is $actual, which is less than expected." ); + } elseif ( $actual > $expected + $delta ) { + $this->fail( "Expected value is $expected, actual value is $actual, which is greater than expected + delta ($delta)." ); + } else { + // If the actual value is within the acceptable range, explicitly assert true. + $this->assertTrue( true ); + } + } + public function test_retry_after_seconds() { $headers = "Retry-After: 59\r\nOther-Header: value"; - $this->assertEquals( 59, $this->sut->wait_after_429( $headers ) ); + $this->assertRetryDelayWithinRange( 59, $this->sut->wait_after_429( $headers ), 5 ); } public function test_retry_after_http_date() { @@ -34,35 +46,56 @@ public function test_retry_after_http_date() { // Since time will pass between the creation of the date and this calculation, // allow a small margin in the assertion $expected_delay = $dateTime->getTimestamp() - time(); - $this->assertEqualsWithDelta( $expected_delay, $this->sut->wait_after_429( $headers, 130 ), 5 ); + $this->assertRetryDelayWithinRange( $expected_delay, $this->sut->wait_after_429( $headers, 130 ), 5 ); } public function test_no_retry_after_header() { $headers = "Other-Header: value"; - $this->assertEquals( 5, $this->sut->wait_after_429( $headers ) ); + $this->assertRetryDelayWithinRange( 5, $this->sut->wait_after_429( $headers ), 5 ); } public function test_invalid_retry_after_header() { $headers = "Retry-After: invalid\r\nOther-Header: value"; - $this->assertEquals( 5, $this->sut->wait_after_429( $headers ) ); + $this->assertRetryDelayWithinRange( 5, $this->sut->wait_after_429( $headers ), 5 ); } public function test_exponential_backoff() { $headers = "Retry-After: invalid\r\nOther-Header: value"; - $this->assertEquals( 5, $this->sut->wait_after_429( $headers ) ); + $this->assertRetryDelayWithinRange( 5, $this->sut->wait_after_429( $headers ), 5 ); // Mimick integration-level test. $this->sut->retry_429 --; - $this->assertEquals( 10, $this->sut->wait_after_429( $headers ) ); + $this->assertRetryDelayWithinRange( 10, $this->sut->wait_after_429( $headers ), 5 ); $this->sut->retry_429 --; - $this->assertEquals( 20, $this->sut->wait_after_429( $headers ) ); + $this->assertRetryDelayWithinRange( 20, $this->sut->wait_after_429( $headers ), 5 ); $this->sut->retry_429 --; - $this->assertEquals( 40, $this->sut->wait_after_429( $headers ) ); + $this->assertRetryDelayWithinRange( 40, $this->sut->wait_after_429( $headers ), 5 ); $this->sut->retry_429 --; - $this->assertEquals( 80, $this->sut->wait_after_429( $headers, 300 ) ); + $this->assertRetryDelayWithinRange( 80, $this->sut->wait_after_429( $headers, 200 ), 5 ); $this->sut->retry_429 --; - $this->assertEquals( 160, $this->sut->wait_after_429( $headers, 300 ) ); + $this->assertRetryDelayWithinRange( 160, $this->sut->wait_after_429( $headers, 200 ), 5 ); + } + + // + // Some tests for our custom assertion. + // + public function testExactValue() { + $this->assertRetryDelayWithinRange( 10, 10, 1 ); // Delta of 1 + } + + public function testWithinDelta() { + $this->assertRetryDelayWithinRange( 10, 11, 1 ); // Within delta of 1 + } + + public function testExceedsDelta() { + $this->expectException( AssertionFailedError::class ); + $this->assertRetryDelayWithinRange( 10, 12, 1 ); // Exceeds delta of 1 + } + + public function testBelowExpected() { + $this->expectException( AssertionFailedError::class ); + $this->assertRetryDelayWithinRange( 10, 9, 1 ); // Below expected } }