diff --git a/CHANGELOG.md b/CHANGELOG.md index c345669..f69d8e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 This update introduces minor bug fixes. +### Added + - Added host resolving in network calls to improve the handling of invalid or unreachable URLs + ### Changed - Update progress bar text to display basename only - Updated exception handling in PackageReader diff --git a/src/ncc/Classes/GiteaExtension/GiteaRepository.php b/src/ncc/Classes/GiteaExtension/GiteaRepository.php index 612527b..b1de575 100644 --- a/src/ncc/Classes/GiteaExtension/GiteaRepository.php +++ b/src/ncc/Classes/GiteaExtension/GiteaRepository.php @@ -39,6 +39,7 @@ use ncc\Objects\Vault\Password\AccessToken; use ncc\Objects\Vault\Password\UsernamePassword; use ncc\Utilities\Console; + use ncc\Utilities\Resolver; use ncc\Utilities\RuntimeCache; use RuntimeException; @@ -97,12 +98,18 @@ 'User-Agent: ncc' ]; - if($authentication !== null) { $headers = self::injectAuthentication($authentication, $curl, $headers); } + $resolved_host = Resolver::getResolveOption($endpoint); + + if($resolved_host !== null) + { + curl_setopt($curl, CURLOPT_RESOLVE, [$resolved_host]); + } + curl_setopt_array($curl, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => HttpRequestType::GET, @@ -186,6 +193,13 @@ $headers = self::injectAuthentication($authentication, $curl, $headers); } + $resolved_host = Resolver::getResolveOption($endpoint); + + if($resolved_host !== null) + { + curl_setopt($curl, CURLOPT_RESOLVE, [$resolved_host]); + } + curl_setopt_array($curl, [ CURLOPT_URL => $endpoint, CURLOPT_RETURNTRANSFER => true, @@ -246,6 +260,13 @@ $headers = self::injectAuthentication($authentication, $curl, $headers); } + $resolved_host = Resolver::getResolveOption($endpoint); + + if($resolved_host !== null) + { + curl_setopt($curl, CURLOPT_RESOLVE, [$resolved_host]); + } + curl_setopt_array($curl, [ CURLOPT_URL => $endpoint, CURLOPT_RETURNTRANSFER => true, @@ -337,6 +358,13 @@ $headers = self::injectAuthentication($authentication, $curl, $headers); } + $resolved_host = Resolver::getResolveOption($endpoint); + + if($resolved_host !== null) + { + curl_setopt($curl, CURLOPT_RESOLVE, [$resolved_host]); + } + curl_setopt_array($curl, [ CURLOPT_URL => $endpoint, CURLOPT_RETURNTRANSFER => true, @@ -433,6 +461,13 @@ $headers = self::injectAuthentication($authentication, $curl, $headers); } + $resolved_host = Resolver::getResolveOption($endpoint); + + if($resolved_host !== null) + { + curl_setopt($curl, CURLOPT_RESOLVE, [$resolved_host]); + } + curl_setopt_array($curl, [ CURLOPT_URL => $endpoint, CURLOPT_RETURNTRANSFER => true, diff --git a/src/ncc/Classes/GithubExtension/GithubRepository.php b/src/ncc/Classes/GithubExtension/GithubRepository.php index dfa2ce8..574adf5 100644 --- a/src/ncc/Classes/GithubExtension/GithubRepository.php +++ b/src/ncc/Classes/GithubExtension/GithubRepository.php @@ -39,6 +39,7 @@ use ncc\Objects\Vault\Password\AccessToken; use ncc\Objects\Vault\Password\UsernamePassword; use ncc\Utilities\Console; + use ncc\Utilities\Resolver; use ncc\Utilities\RuntimeCache; use RuntimeException; @@ -102,6 +103,13 @@ $headers = self::injectAuthentication($authentication, $curl, $headers); } + $resolved_host = Resolver::getResolveOption($endpoint); + + if($resolved_host !== null) + { + curl_setopt($curl, CURLOPT_RESOLVE, [$resolved_host]); + } + curl_setopt_array($curl, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => HttpRequestType::GET, @@ -183,6 +191,13 @@ $headers = self::injectAuthentication($authentication, $curl, $headers); } + $resolved_host = Resolver::getResolveOption($endpoint); + + if($resolved_host !== null) + { + curl_setopt($curl, CURLOPT_RESOLVE, [$resolved_host]); + } + curl_setopt_array($curl, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_NOBODY => true, @@ -247,6 +262,13 @@ $headers = self::injectAuthentication($authentication, $curl, $headers); } + $resolved_host = Resolver::getResolveOption($endpoint); + + if($resolved_host !== null) + { + curl_setopt($curl, CURLOPT_RESOLVE, [$resolved_host]); + } + curl_setopt_array($curl, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => HttpRequestType::GET, @@ -331,7 +353,18 @@ $headers = self::injectAuthentication($authentication, $curl, $headers); } - curl_setopt_array($curl, [CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => HttpRequestType::GET, CURLOPT_HTTPHEADER => $headers]); + $resolved_host = Resolver::getResolveOption($endpoint); + + if($resolved_host !== null) + { + curl_setopt($curl, CURLOPT_RESOLVE, [$resolved_host]); + } + + curl_setopt_array($curl, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_CUSTOMREQUEST => HttpRequestType::GET, + CURLOPT_HTTPHEADER => $headers + ]); Console::outDebug(sprintf('Fetching release package for %s/%s/%s from %s', $group, $project, $release, $endpoint)); $response = self::processHttpResponse($curl, $group, $project); @@ -413,6 +446,13 @@ $headers = self::injectAuthentication($authentication, $curl, $headers); } + $resolved_host = Resolver::getResolveOption($endpoint); + + if($resolved_host !== null) + { + curl_setopt($curl, CURLOPT_RESOLVE, [$resolved_host]); + } + curl_setopt_array($curl, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => HttpRequestType::GET, diff --git a/src/ncc/Classes/GitlabExtension/GitlabRepository.php b/src/ncc/Classes/GitlabExtension/GitlabRepository.php index b231efe..2fded3e 100644 --- a/src/ncc/Classes/GitlabExtension/GitlabRepository.php +++ b/src/ncc/Classes/GitlabExtension/GitlabRepository.php @@ -39,6 +39,7 @@ use ncc\Objects\Vault\Password\AccessToken; use ncc\Objects\Vault\Password\UsernamePassword; use ncc\Utilities\Console; + use ncc\Utilities\Resolver; use ncc\Utilities\RuntimeCache; use RuntimeException; @@ -103,6 +104,13 @@ $headers = self::injectAuthentication($authentication, $curl, $headers); } + $resolved_host = Resolver::getResolveOption($endpoint); + + if($resolved_host !== null) + { + curl_setopt($curl, CURLOPT_RESOLVE, [$resolved_host]); + } + curl_setopt_array($curl, [ CURLOPT_URL => $endpoint, CURLOPT_RETURNTRANSFER => true, @@ -187,6 +195,13 @@ $headers = self::injectAuthentication($authentication, $curl, $headers); } + $resolved_host = Resolver::getResolveOption($endpoint); + + if($resolved_host !== null) + { + curl_setopt($curl, CURLOPT_RESOLVE, [$resolved_host]); + } + curl_setopt_array($curl, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_NOBODY => true, @@ -250,6 +265,13 @@ $headers = self::injectAuthentication($authentication, $curl, $headers); } + $resolved_host = Resolver::getResolveOption($endpoint); + + if($resolved_host !== null) + { + curl_setopt($curl, CURLOPT_RESOLVE, [$resolved_host]); + } + curl_setopt_array($curl, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => HttpRequestType::GET, @@ -333,6 +355,13 @@ $headers = self::injectAuthentication($authentication, $curl, $headers); } + $resolved_host = Resolver::getResolveOption($endpoint); + + if($resolved_host !== null) + { + curl_setopt($curl, CURLOPT_RESOLVE, [$resolved_host]); + } + curl_setopt_array($curl, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => HttpRequestType::GET, @@ -418,6 +447,13 @@ $headers = self::injectAuthentication($authentication, $curl, $headers); } + $resolved_host = Resolver::getResolveOption($endpoint); + + if($resolved_host !== null) + { + curl_setopt($curl, CURLOPT_RESOLVE, [$resolved_host]); + } + curl_setopt_array($curl, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => HttpRequestType::GET, diff --git a/src/ncc/Classes/PackagistExtension/PackagistRepository.php b/src/ncc/Classes/PackagistExtension/PackagistRepository.php index 98ef09b..6261856 100644 --- a/src/ncc/Classes/PackagistExtension/PackagistRepository.php +++ b/src/ncc/Classes/PackagistExtension/PackagistRepository.php @@ -38,6 +38,7 @@ use ncc\ThirdParty\composer\Semver\Comparator; use ncc\ThirdParty\composer\Semver\Semver; use ncc\Utilities\Console; + use ncc\Utilities\Resolver; use ncc\Utilities\RuntimeCache; use RuntimeException; @@ -70,6 +71,13 @@ 'User-Agent: ncc' ]; + $resolved_host = Resolver::getResolveOption($endpoint); + + if($resolved_host !== null) + { + curl_setopt($curl, CURLOPT_RESOLVE, [$resolved_host]); + } + curl_setopt_array($curl, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => HttpRequestType::GET, @@ -130,6 +138,13 @@ 'User-Agent: ncc' ]; + $resolved_host = Resolver::getResolveOption($endpoint); + + if($resolved_host !== null) + { + curl_setopt($curl, CURLOPT_RESOLVE, [$resolved_host]); + } + curl_setopt_array($curl, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => HttpRequestType::GET, diff --git a/src/ncc/Managers/PackageManager.php b/src/ncc/Managers/PackageManager.php index c67047a..1148291 100644 --- a/src/ncc/Managers/PackageManager.php +++ b/src/ncc/Managers/PackageManager.php @@ -886,6 +886,12 @@ $file_handle = fopen($file_path, 'wb'); $end = false; $progress_bar = new ConsoleProgressBar(sprintf('Downloading %s', $url), 100); + $resolved_host = Resolver::getResolveOption($url); + + if($resolved_host !== null) + { + curl_setopt($curl, CURLOPT_RESOLVE, [$resolved_host]); + } curl_setopt($curl, CURLOPT_RETURNTRANSFER, false); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); diff --git a/src/ncc/Utilities/Resolver.php b/src/ncc/Utilities/Resolver.php index f58f0e9..1e049c9 100644 --- a/src/ncc/Utilities/Resolver.php +++ b/src/ncc/Utilities/Resolver.php @@ -41,6 +41,11 @@ */ private static $user_id_cache; + /** + * @var array + */ + private static $resolved_hosts = []; + /** * Returns the current scope of the application * @@ -323,4 +328,54 @@ return explode(':', $component_path, 2)[1]; } + + /** + * Resolve host and cache the result + * + * @param string $host The host to resolve + * @return string The resolved host IP address + */ + public static function resolveHost(string $host): string + { + $resolved_host = self::$resolved_hosts[$host] ?? null; + + if($resolved_host !== null) + { + Console::outDebug(sprintf('Resolved host "%s" to "%s" from cache', $host, $resolved_host)); + return $resolved_host; + } + + $resolved_host = gethostbyname($host); + + if($resolved_host === $host) + { + Console::outDebug(sprintf('Unable to resolve host "%s"', $host)); + return $host; + } + + Console::outDebug(sprintf('Resolved host "%s" to "%s"', $host, $resolved_host)); + self::$resolved_hosts[$host] = $resolved_host; + return $resolved_host; + } + + /** + * Get resolved option for the given URL + * + * @param string $url The input URL + * @return string|null Resolved option in the format "{host}:{port}:{ip_address}", or null if $ip_address is same as $host + */ + public static function getResolveOption(string $url): ?string + { + $parsed_url = parse_url($url); + $host = $parsed_url['host']; + $port = str_starts_with($url, 'https') ? 443 : 80; + $ip_address = self::resolveHost($host); + + if($ip_address === $host) + { + return null; + } + + return "{$host}:{$port}:{$ip_address}"; + } } \ No newline at end of file