From 7befd995e7607f0f3ee8933fc4a5ad8523f7d9f2 Mon Sep 17 00:00:00 2001 From: Netkas Date: Wed, 25 Oct 2023 15:08:58 -0400 Subject: [PATCH] Added host resolving in network calls to improve the handling of invalid or unreachable URLs The 'Resolver' utility was utilized to resolve the host and cache the result, enhancing the robustness and efficiency of the network calling mechanism across different modules including PackageManager, GiteaRepository, GithubRepository, and others. --- CHANGELOG.md | 3 + .../GiteaExtension/GiteaRepository.php | 37 ++++++++++++- .../GithubExtension/GithubRepository.php | 42 +++++++++++++- .../GitlabExtension/GitlabRepository.php | 36 ++++++++++++ .../PackagistRepository.php | 15 +++++ src/ncc/Managers/PackageManager.php | 6 ++ src/ncc/Utilities/Resolver.php | 55 +++++++++++++++++++ 7 files changed, 192 insertions(+), 2 deletions(-) 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