From: Philipp Date: Fri, 29 Oct 2021 06:03:59 +0000 (+0200) Subject: Add Feedback :-) X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=f4ea74447e8a487bdd7236bdc6789a5d53fa2226;p=friendica.git Add Feedback :-) --- diff --git a/src/Core/Logger/Exception/LogLevelException.php b/src/Core/Logger/Exception/LogLevelException.php new file mode 100644 index 0000000000..96abd2a9a7 --- /dev/null +++ b/src/Core/Logger/Exception/LogLevelException.php @@ -0,0 +1,13 @@ +get('system', 'debugging', false))) { $logger = new NullLogger(); @@ -84,8 +85,8 @@ class Logger } $introspection = new Introspection(self::$ignoreClassList); - $level = $config->get('system', 'loglevel'); - $loglevel = self::mapLegacyConfigDebugLevel((string)$level); + $minLevel = $minLevel ?? $config->get('system', 'loglevel'); + $loglevel = self::mapLegacyConfigDebugLevel((string)$minLevel); switch ($config->get('system', 'logger_config', 'stream')) { case 'monolog': @@ -106,8 +107,12 @@ class Logger static::addStreamHandler($logger, $stream, $loglevel); } catch (\Throwable $e) { // No Logger .. - /// @todo isn't it possible to give the admin any hint about this wrong configuration? - $logger = new NullLogger(); + try { + $logger = new SyslogLogger($this->channel, $introspection, $loglevel); + } catch (\Throwable $e) { + // No logger ... + $logger = new NullLogger(); + } } } break; @@ -115,9 +120,12 @@ class Logger case 'syslog': try { $logger = new SyslogLogger($this->channel, $introspection, $loglevel); + } catch (LogLevelException $exception) { + // If there's a wrong config value for loglevel, try again with standard + $logger = $this->create($database, $config, $profiler, $fileSystem, LogLevel::NOTICE); + $logger->warning('Invalid loglevel set in config.', ['loglevel' => $loglevel]); } catch (\Throwable $e) { // No logger ... - /// @todo isn't it possible to give the admin any hint about this wrong configuration? $logger = new NullLogger(); } break; @@ -129,14 +137,25 @@ class Logger if (!is_file($stream) || is_writable($stream)) { try { $logger = new StreamLogger($this->channel, $stream, $introspection, $fileSystem, $loglevel); + } catch (LogLevelException $exception) { + // If there's a wrong config value for loglevel, try again with standard + $logger = $this->create($database, $config, $profiler, $fileSystem, LogLevel::NOTICE); + $logger->warning('Invalid loglevel set in config.', ['loglevel' => $loglevel]); } catch (\Throwable $t) { // No logger ... - /// @todo isn't it possible to give the admin any hint about this wrong configuration? $logger = new NullLogger(); } } else { - /// @todo isn't it possible to give the admin any hint about this wrong configuration? - $logger = new NullLogger(); + try { + $logger = new SyslogLogger($this->channel, $introspection, $loglevel); + } catch (LogLevelException $exception) { + // If there's a wrong config value for loglevel, try again with standard + $logger = $this->create($database, $config, $profiler, $fileSystem, LogLevel::NOTICE); + $logger->warning('Invalid loglevel set in config.', ['loglevel' => $loglevel]); + } catch (\Throwable $e) { + // No logger ... + $logger = new NullLogger(); + } } break; } diff --git a/src/Core/Logger/Type/StreamLogger.php b/src/Core/Logger/Type/StreamLogger.php index be0283d0ec..f67aef9a49 100644 --- a/src/Core/Logger/Type/StreamLogger.php +++ b/src/Core/Logger/Type/StreamLogger.php @@ -23,6 +23,7 @@ namespace Friendica\Core\Logger\Type; use Friendica\Core\Logger\Exception\LoggerArgumentException; use Friendica\Core\Logger\Exception\LoggerException; +use Friendica\Core\Logger\Exception\LogLevelException; use Friendica\Util\DateTimeFormat; use Friendica\Util\FileSystem; use Friendica\Util\Introspection; @@ -83,6 +84,7 @@ class StreamLogger extends AbstractLogger * @param string $level The minimum loglevel at which this logger will be triggered * * @throws LoggerArgumentException + * @throws LogLevelException */ public function __construct($channel, $stream, Introspection $introspection, FileSystem $fileSystem, string $level = LogLevel::DEBUG) { @@ -102,7 +104,7 @@ class StreamLogger extends AbstractLogger if (array_key_exists($level, $this->levelToInt)) { $this->logLevel = $this->levelToInt[$level]; } else { - throw new LoggerArgumentException(sprintf('The level "%s" is not valid.', $level)); + throw new LogLevelException(sprintf('The level "%s" is not valid.', $level)); } $this->checkStream(); @@ -127,12 +129,12 @@ class StreamLogger extends AbstractLogger * @return void * * @throws LoggerException - * @throws LoggerArgumentException + * @throws LogLevelException */ protected function addEntry($level, string $message, array $context = []) { if (!array_key_exists($level, $this->levelToInt)) { - throw new LoggerArgumentException(sprintf('The level "%s" is not valid.', $level)); + throw new LogLevelException(sprintf('The level "%s" is not valid.', $level)); } $logLevel = $this->levelToInt[$level]; diff --git a/src/Core/Logger/Type/SyslogLogger.php b/src/Core/Logger/Type/SyslogLogger.php index 667b44ccc7..024d47b312 100644 --- a/src/Core/Logger/Type/SyslogLogger.php +++ b/src/Core/Logger/Type/SyslogLogger.php @@ -21,10 +21,9 @@ namespace Friendica\Core\Logger\Type; -use Friendica\Core\Logger\Exception\LoggerArgumentException; use Friendica\Core\Logger\Exception\LoggerException; +use Friendica\Core\Logger\Exception\LogLevelException; use Friendica\Util\Introspection; -use Psr\Log\InvalidArgumentException; use Psr\Log\LogLevel; /** @@ -99,7 +98,8 @@ class SyslogLogger extends AbstractLogger * @param int $logOpts Indicates what logging options will be used when generating a log message * @param int $logFacility Used to specify what type of program is logging the message * - * @throws LoggerArgumentException + * @throws LogLevelException + * @throws LoggerException */ public function __construct($channel, Introspection $introspection, string $level = LogLevel::NOTICE, int $logOpts = LOG_PID, int $logFacility = LOG_USER) { @@ -117,7 +117,7 @@ class SyslogLogger extends AbstractLogger * @param string $message * @param array $context * - * @throws LoggerArgumentException in case the level isn't valid + * @throws LogLevelException in case the level isn't valid * @throws LoggerException In case the syslog cannot be opened for writing */ protected function addEntry($level, string $message, array $context = []) @@ -139,12 +139,12 @@ class SyslogLogger extends AbstractLogger * * @return int The SysLog priority * - * @throws LoggerArgumentException If the loglevel isn't valid + * @throws LogLevelException If the loglevel isn't valid */ public function mapLevelToPriority(string $level): int { if (!array_key_exists($level, $this->logLevels)) { - throw new LoggerArgumentException(sprintf('The level "%s" is not valid.', $level)); + throw new LogLevelException(sprintf('The level "%s" is not valid.', $level)); } return $this->logLevels[$level]; diff --git a/src/DI.php b/src/DI.php index ecc65bbc98..8236447844 100644 --- a/src/DI.php +++ b/src/DI.php @@ -415,11 +415,11 @@ abstract class DI // /** - * @return Network\HTTPClient\Capability\ICanRequestPerHttp + * @return Network\HTTPClient\Capability\ICanSendHttpRequests */ public static function httpClient() { - return self::$dice->create(Network\HTTPClient\Capability\ICanRequestPerHttp::class); + return self::$dice->create(Network\HTTPClient\Capability\ICanSendHttpRequests::class); } // diff --git a/src/Network/HTTPClient/Capability/ICanRequestPerHttp.php b/src/Network/HTTPClient/Capability/ICanRequestPerHttp.php deleted file mode 100644 index b7ddcb254a..0000000000 --- a/src/Network/HTTPClient/Capability/ICanRequestPerHttp.php +++ /dev/null @@ -1,133 +0,0 @@ -. - * - */ - -namespace Friendica\Network\HTTPClient\Capability; - -use GuzzleHttp\Exception\TransferException; - -/** - * Interface for calling HTTP requests and returning their responses - */ -interface ICanRequestPerHttp -{ - /** - * Fetches the content of an URL - * - * Set the cookiejar argument to a string (e.g. "/tmp/friendica-cookies.txt") - * to preserve cookies from one request to the next. - * - * @param string $url URL to fetch - * @param int $timeout Timeout in seconds, default system config value or 60 seconds - * @param string $accept_content supply Accept: header with 'accept_content' as the value - * @param string $cookiejar Path to cookie jar file - * - * @return string The fetched content - */ - public function fetch(string $url, int $timeout = 0, string $accept_content = '', string $cookiejar = ''): string; - - /** - * Fetches the whole response of an URL. - * - * Inner workings and parameters are the same as @ref fetchUrl but returns an array with - * all the information collected during the fetch. - * - * @param string $url URL to fetch - * @param int $timeout Timeout in seconds, default system config value or 60 seconds - * @param string $accept_content supply Accept: header with 'accept_content' as the value - * @param string $cookiejar Path to cookie jar file - * - * @return ICanHandleHttpResponses With all relevant information, 'body' contains the actual fetched content. - */ - public function fetchFull(string $url, int $timeout = 0, string $accept_content = '', string $cookiejar = ''): ICanHandleHttpResponses; - - /** - * Send a HEAD to a URL. - * - * @param string $url URL to fetch - * @param array $opts (optional parameters) associative array with: - * 'accept_content' => (string array) supply Accept: header with 'accept_content' as the value - * 'timeout' => int Timeout in seconds, default system config value or 60 seconds - * 'cookiejar' => path to cookie jar file - * 'header' => header array - * - * @return ICanHandleHttpResponses - */ - public function head(string $url, array $opts = []): ICanHandleHttpResponses; - - /** - * Send a GET to an URL. - * - * @param string $url URL to fetch - * @param array $opts (optional parameters) associative array with: - * 'accept_content' => (string array) supply Accept: header with 'accept_content' as the value - * 'timeout' => int Timeout in seconds, default system config value or 60 seconds - * 'cookiejar' => path to cookie jar file - * 'header' => header array - * 'content_length' => int maximum File content length - * - * @return ICanHandleHttpResponses - */ - public function get(string $url, array $opts = []): ICanHandleHttpResponses; - - /** - * Sends a HTTP request to a given url - * - * @param string $method A HTTP request - * @param string $url Url to send to - * @param array $opts (optional parameters) associative array with: - * 'body' => (mixed) setting the body for sending data - * 'accept_content' => (string array) supply Accept: header with 'accept_content' as the value - * 'timeout' => int Timeout in seconds, default system config value or 60 seconds - * 'cookiejar' => path to cookie jar file - * 'header' => header array - * 'content_length' => int maximum File content length - * 'auth' => array authentication settings - * - * @return ICanHandleHttpResponses - */ - public function request(string $method, string $url, array $opts = []): ICanHandleHttpResponses; - - /** - * Send POST request to an URL - * - * @param string $url URL to post - * @param mixed $params array of POST variables - * @param array $headers HTTP headers - * @param int $timeout The timeout in seconds, default system config value or 60 seconds - * - * @return ICanHandleHttpResponses The content - */ - public function post(string $url, $params, array $headers = [], int $timeout = 0): ICanHandleHttpResponses; - - /** - * Returns the original URL of the provided URL - * - * This function strips tracking query params and follows redirections, either - * through HTTP code or meta refresh tags. Stops after 10 redirections. - * - * @param string $url A user-submitted URL - * - * @return string A canonical URL - * - * @throws TransferException In case there's an error during the resolving - */ - public function finalUrl(string $url): string; -} diff --git a/src/Network/HTTPClient/Capability/ICanSendHttpRequests.php b/src/Network/HTTPClient/Capability/ICanSendHttpRequests.php new file mode 100644 index 0000000000..24eb6dc50e --- /dev/null +++ b/src/Network/HTTPClient/Capability/ICanSendHttpRequests.php @@ -0,0 +1,133 @@ +. + * + */ + +namespace Friendica\Network\HTTPClient\Capability; + +use GuzzleHttp\Exception\TransferException; + +/** + * Interface for calling HTTP requests and returning their responses + */ +interface ICanSendHttpRequests +{ + /** + * Fetches the content of an URL + * + * Set the cookiejar argument to a string (e.g. "/tmp/friendica-cookies.txt") + * to preserve cookies from one request to the next. + * + * @param string $url URL to fetch + * @param int $timeout Timeout in seconds, default system config value or 60 seconds + * @param string $accept_content supply Accept: header with 'accept_content' as the value + * @param string $cookiejar Path to cookie jar file + * + * @return string The fetched content + */ + public function fetch(string $url, int $timeout = 0, string $accept_content = '', string $cookiejar = ''): string; + + /** + * Fetches the whole response of an URL. + * + * Inner workings and parameters are the same as @ref fetchUrl but returns an array with + * all the information collected during the fetch. + * + * @param string $url URL to fetch + * @param int $timeout Timeout in seconds, default system config value or 60 seconds + * @param string $accept_content supply Accept: header with 'accept_content' as the value + * @param string $cookiejar Path to cookie jar file + * + * @return ICanHandleHttpResponses With all relevant information, 'body' contains the actual fetched content. + */ + public function fetchFull(string $url, int $timeout = 0, string $accept_content = '', string $cookiejar = ''): ICanHandleHttpResponses; + + /** + * Send a HEAD to a URL. + * + * @param string $url URL to fetch + * @param array $opts (optional parameters) associative array with: + * 'accept_content' => (string array) supply Accept: header with 'accept_content' as the value + * 'timeout' => int Timeout in seconds, default system config value or 60 seconds + * 'cookiejar' => path to cookie jar file + * 'header' => header array + * + * @return ICanHandleHttpResponses + */ + public function head(string $url, array $opts = []): ICanHandleHttpResponses; + + /** + * Send a GET to an URL. + * + * @param string $url URL to fetch + * @param array $opts (optional parameters) associative array with: + * 'accept_content' => (string array) supply Accept: header with 'accept_content' as the value + * 'timeout' => int Timeout in seconds, default system config value or 60 seconds + * 'cookiejar' => path to cookie jar file + * 'header' => header array + * 'content_length' => int maximum File content length + * + * @return ICanHandleHttpResponses + */ + public function get(string $url, array $opts = []): ICanHandleHttpResponses; + + /** + * Sends a HTTP request to a given url + * + * @param string $method A HTTP request + * @param string $url Url to send to + * @param array $opts (optional parameters) associative array with: + * 'body' => (mixed) setting the body for sending data + * 'accept_content' => (string array) supply Accept: header with 'accept_content' as the value + * 'timeout' => int Timeout in seconds, default system config value or 60 seconds + * 'cookiejar' => path to cookie jar file + * 'header' => header array + * 'content_length' => int maximum File content length + * 'auth' => array authentication settings + * + * @return ICanHandleHttpResponses + */ + public function request(string $method, string $url, array $opts = []): ICanHandleHttpResponses; + + /** + * Send POST request to an URL + * + * @param string $url URL to post + * @param mixed $params array of POST variables + * @param array $headers HTTP headers + * @param int $timeout The timeout in seconds, default system config value or 60 seconds + * + * @return ICanHandleHttpResponses The content + */ + public function post(string $url, $params, array $headers = [], int $timeout = 0): ICanHandleHttpResponses; + + /** + * Returns the original URL of the provided URL + * + * This function strips tracking query params and follows redirections, either + * through HTTP code or meta refresh tags. Stops after 10 redirections. + * + * @param string $url A user-submitted URL + * + * @return string A canonical URL + * + * @throws TransferException In case there's an error during the resolving + */ + public function finalUrl(string $url): string; +} diff --git a/src/Network/HTTPClient/Client/HttpClient.php b/src/Network/HTTPClient/Client/HttpClient.php new file mode 100644 index 0000000000..33bb8357e7 --- /dev/null +++ b/src/Network/HTTPClient/Client/HttpClient.php @@ -0,0 +1,258 @@ +. + * + */ + +namespace Friendica\Network\HTTPClient\Client; + +use Friendica\Core\System; +use Friendica\Network\HTTPClient\Response\CurlResult; +use Friendica\Network\HTTPClient\Response\GuzzleResponse; +use Friendica\Network\HTTPClient\Capability\ICanSendHttpRequests; +use Friendica\Network\HTTPClient\Capability\ICanHandleHttpResponses; +use Friendica\Network\HTTPException\InternalServerErrorException; +use Friendica\Util\Network; +use Friendica\Util\Profiler; +use GuzzleHttp\Client; +use GuzzleHttp\Cookie\FileCookieJar; +use GuzzleHttp\Exception\RequestException; +use GuzzleHttp\Exception\TransferException; +use GuzzleHttp\RequestOptions; +use mattwright\URLResolver; +use Psr\Http\Message\ResponseInterface; +use Psr\Log\InvalidArgumentException; +use Psr\Log\LoggerInterface; + +/** + * Performs HTTP requests to a given URL + */ +class HttpClient implements ICanSendHttpRequests +{ + /** @var LoggerInterface */ + private $logger; + /** @var Profiler */ + private $profiler; + /** @var Client */ + private $client; + /** @var URLResolver */ + private $resolver; + + public function __construct(LoggerInterface $logger, Profiler $profiler, Client $client, URLResolver $resolver) + { + $this->logger = $logger; + $this->profiler = $profiler; + $this->client = $client; + $this->resolver = $resolver; + } + + /** + * {@inheritDoc} + */ + public function request(string $method, string $url, array $opts = []): ICanHandleHttpResponses + { + $this->profiler->startRecording('network'); + $this->logger->debug('Request start.', ['url' => $url, 'method' => $method]); + + if (Network::isLocalLink($url)) { + $this->logger->info('Local link', ['url' => $url, 'callstack' => System::callstack(20)]); + } + + if (strlen($url) > 1000) { + $this->logger->debug('URL is longer than 1000 characters.', ['url' => $url, 'callstack' => System::callstack(20)]); + $this->profiler->stopRecording(); + return CurlResult::createErrorCurl(substr($url, 0, 200)); + } + + $parts2 = []; + $parts = parse_url($url); + $path_parts = explode('/', $parts['path'] ?? ''); + foreach ($path_parts as $part) { + if (strlen($part) <> mb_strlen($part)) { + $parts2[] = rawurlencode($part); + } else { + $parts2[] = $part; + } + } + $parts['path'] = implode('/', $parts2); + $url = Network::unparseURL($parts); + + if (Network::isUrlBlocked($url)) { + $this->logger->info('Domain is blocked.', ['url' => $url]); + $this->profiler->stopRecording(); + return CurlResult::createErrorCurl($url); + } + + $conf = []; + + if (!empty($opts[HttpClientOptions::COOKIEJAR])) { + $jar = new FileCookieJar($opts[HttpClientOptions::COOKIEJAR]); + $conf[RequestOptions::COOKIES] = $jar; + } + + $headers = []; + + if (!empty($opts[HttpClientOptions::ACCEPT_CONTENT])) { + $headers['Accept'] = $opts[HttpClientOptions::ACCEPT_CONTENT]; + } + + if (!empty($opts[HttpClientOptions::LEGACY_HEADER])) { + $this->logger->notice('Wrong option \'headers\' used.'); + $headers = array_merge($opts[HttpClientOptions::LEGACY_HEADER], $headers); + } + + if (!empty($opts[HttpClientOptions::HEADERS])) { + $headers = array_merge($opts[HttpClientOptions::HEADERS], $headers); + } + + $conf[RequestOptions::HEADERS] = array_merge($this->client->getConfig(RequestOptions::HEADERS), $headers); + + if (!empty($opts[HttpClientOptions::TIMEOUT])) { + $conf[RequestOptions::TIMEOUT] = $opts[HttpClientOptions::TIMEOUT]; + } + + if (!empty($opts[HttpClientOptions::BODY])) { + $conf[RequestOptions::BODY] = $opts[HttpClientOptions::BODY]; + } + + if (!empty($opts[HttpClientOptions::AUTH])) { + $conf[RequestOptions::AUTH] = $opts[HttpClientOptions::AUTH]; + } + + $conf[RequestOptions::ON_HEADERS] = function (ResponseInterface $response) use ($opts) { + if (!empty($opts[HttpClientOptions::CONTENT_LENGTH]) && + (int)$response->getHeaderLine('Content-Length') > $opts[HttpClientOptions::CONTENT_LENGTH]) { + throw new TransferException('The file is too big!'); + } + }; + + try { + $this->logger->debug('http request config.', ['url' => $url, 'method' => $method, 'options' => $conf]); + + $response = $this->client->request($method, $url, $conf); + return new GuzzleResponse($response, $url); + } catch (TransferException $exception) { + if ($exception instanceof RequestException && + $exception->hasResponse()) { + return new GuzzleResponse($exception->getResponse(), $url, $exception->getCode(), ''); + } else { + return new CurlResult($url, '', ['http_code' => 500], $exception->getCode(), ''); + } + } catch (InvalidArgumentException | \InvalidArgumentException $argumentException) { + $this->logger->info('Invalid Argument for HTTP call.', ['url' => $url, 'method' => $method, 'exception' => $argumentException]); + return new CurlResult($url, '', ['http_code' => 500], $argumentException->getCode(), $argumentException->getMessage()); + } finally { + $this->logger->debug('Request stop.', ['url' => $url, 'method' => $method]); + $this->profiler->stopRecording(); + } + } + + /** {@inheritDoc} + */ + public function head(string $url, array $opts = []): ICanHandleHttpResponses + { + return $this->request('head', $url, $opts); + } + + /** + * {@inheritDoc} + */ + public function get(string $url, array $opts = []): ICanHandleHttpResponses + { + return $this->request('get', $url, $opts); + } + + /** + * {@inheritDoc} + */ + public function post(string $url, $params, array $headers = [], int $timeout = 0): ICanHandleHttpResponses + { + $opts = []; + + $opts[HttpClientOptions::BODY] = $params; + + if (!empty($headers)) { + $opts[HttpClientOptions::HEADERS] = $headers; + } + + if (!empty($timeout)) { + $opts[HttpClientOptions::TIMEOUT] = $timeout; + } + + return $this->request('post', $url, $opts); + } + + /** + * {@inheritDoc} + */ + public function finalUrl(string $url): string + { + $this->profiler->startRecording('network'); + + if (Network::isLocalLink($url)) { + $this->logger->debug('Local link', ['url' => $url, 'callstack' => System::callstack(20)]); + } + + if (Network::isUrlBlocked($url)) { + $this->logger->info('Domain is blocked.', ['url' => $url]); + return $url; + } + + if (Network::isRedirectBlocked($url)) { + $this->logger->info('Domain should not be redirected.', ['url' => $url]); + return $url; + } + + $url = Network::stripTrackingQueryParams($url); + + $url = trim($url, "'"); + + $urlResult = $this->resolver->resolveURL($url); + + if ($urlResult->didErrorOccur()) { + throw new TransferException($urlResult->getErrorMessageString(), $urlResult->getHTTPStatusCode()); + } + + return $urlResult->getURL(); + } + + /** + * {@inheritDoc} + */ + public function fetch(string $url, int $timeout = 0, string $accept_content = '', string $cookiejar = ''): string + { + $ret = $this->fetchFull($url, $timeout, $accept_content, $cookiejar); + + return $ret->getBody(); + } + + /** + * {@inheritDoc} + */ + public function fetchFull(string $url, int $timeout = 0, string $accept_content = '', string $cookiejar = ''): ICanHandleHttpResponses + { + return $this->get( + $url, + [ + 'timeout' => $timeout, + 'accept_content' => $accept_content, + 'cookiejar' => $cookiejar + ] + ); + } +} diff --git a/src/Network/HTTPClient/Client/HttpClientCan.php b/src/Network/HTTPClient/Client/HttpClientCan.php deleted file mode 100644 index ea07a5c83d..0000000000 --- a/src/Network/HTTPClient/Client/HttpClientCan.php +++ /dev/null @@ -1,258 +0,0 @@ -. - * - */ - -namespace Friendica\Network\HTTPClient\Client; - -use Friendica\Core\System; -use Friendica\Network\HTTPClient\Response\CurlResult; -use Friendica\Network\HTTPClient\Response\GuzzleResponse; -use Friendica\Network\HTTPClient\Capability\ICanRequestPerHttp; -use Friendica\Network\HTTPClient\Capability\ICanHandleHttpResponses; -use Friendica\Network\HTTPException\InternalServerErrorException; -use Friendica\Util\Network; -use Friendica\Util\Profiler; -use GuzzleHttp\Client; -use GuzzleHttp\Cookie\FileCookieJar; -use GuzzleHttp\Exception\RequestException; -use GuzzleHttp\Exception\TransferException; -use GuzzleHttp\RequestOptions; -use mattwright\URLResolver; -use Psr\Http\Message\ResponseInterface; -use Psr\Log\InvalidArgumentException; -use Psr\Log\LoggerInterface; - -/** - * Performs HTTP requests to a given URL - */ -class HttpClientCan implements ICanRequestPerHttp -{ - /** @var LoggerInterface */ - private $logger; - /** @var Profiler */ - private $profiler; - /** @var Client */ - private $client; - /** @var URLResolver */ - private $resolver; - - public function __construct(LoggerInterface $logger, Profiler $profiler, Client $client, URLResolver $resolver) - { - $this->logger = $logger; - $this->profiler = $profiler; - $this->client = $client; - $this->resolver = $resolver; - } - - /** - * {@inheritDoc} - */ - public function request(string $method, string $url, array $opts = []): ICanHandleHttpResponses - { - $this->profiler->startRecording('network'); - $this->logger->debug('Request start.', ['url' => $url, 'method' => $method]); - - if (Network::isLocalLink($url)) { - $this->logger->info('Local link', ['url' => $url, 'callstack' => System::callstack(20)]); - } - - if (strlen($url) > 1000) { - $this->logger->debug('URL is longer than 1000 characters.', ['url' => $url, 'callstack' => System::callstack(20)]); - $this->profiler->stopRecording(); - return CurlResult::createErrorCurl(substr($url, 0, 200)); - } - - $parts2 = []; - $parts = parse_url($url); - $path_parts = explode('/', $parts['path'] ?? ''); - foreach ($path_parts as $part) { - if (strlen($part) <> mb_strlen($part)) { - $parts2[] = rawurlencode($part); - } else { - $parts2[] = $part; - } - } - $parts['path'] = implode('/', $parts2); - $url = Network::unparseURL($parts); - - if (Network::isUrlBlocked($url)) { - $this->logger->info('Domain is blocked.', ['url' => $url]); - $this->profiler->stopRecording(); - return CurlResult::createErrorCurl($url); - } - - $conf = []; - - if (!empty($opts[HttpClientOptions::COOKIEJAR])) { - $jar = new FileCookieJar($opts[HttpClientOptions::COOKIEJAR]); - $conf[RequestOptions::COOKIES] = $jar; - } - - $headers = []; - - if (!empty($opts[HttpClientOptions::ACCEPT_CONTENT])) { - $headers['Accept'] = $opts[HttpClientOptions::ACCEPT_CONTENT]; - } - - if (!empty($opts[HttpClientOptions::LEGACY_HEADER])) { - $this->logger->notice('Wrong option \'headers\' used.'); - $headers = array_merge($opts[HttpClientOptions::LEGACY_HEADER], $headers); - } - - if (!empty($opts[HttpClientOptions::HEADERS])) { - $headers = array_merge($opts[HttpClientOptions::HEADERS], $headers); - } - - $conf[RequestOptions::HEADERS] = array_merge($this->client->getConfig(RequestOptions::HEADERS), $headers); - - if (!empty($opts[HttpClientOptions::TIMEOUT])) { - $conf[RequestOptions::TIMEOUT] = $opts[HttpClientOptions::TIMEOUT]; - } - - if (!empty($opts[HttpClientOptions::BODY])) { - $conf[RequestOptions::BODY] = $opts[HttpClientOptions::BODY]; - } - - if (!empty($opts[HttpClientOptions::AUTH])) { - $conf[RequestOptions::AUTH] = $opts[HttpClientOptions::AUTH]; - } - - $conf[RequestOptions::ON_HEADERS] = function (ResponseInterface $response) use ($opts) { - if (!empty($opts[HttpClientOptions::CONTENT_LENGTH]) && - (int)$response->getHeaderLine('Content-Length') > $opts[HttpClientOptions::CONTENT_LENGTH]) { - throw new TransferException('The file is too big!'); - } - }; - - try { - $this->logger->debug('http request config.', ['url' => $url, 'method' => $method, 'options' => $conf]); - - $response = $this->client->request($method, $url, $conf); - return new GuzzleResponse($response, $url); - } catch (TransferException $exception) { - if ($exception instanceof RequestException && - $exception->hasResponse()) { - return new GuzzleResponse($exception->getResponse(), $url, $exception->getCode(), ''); - } else { - return new CurlResult($url, '', ['http_code' => 500], $exception->getCode(), ''); - } - } catch (InvalidArgumentException | \InvalidArgumentException $argumentException) { - $this->logger->info('Invalid Argument for HTTP call.', ['url' => $url, 'method' => $method, 'exception' => $argumentException]); - return new CurlResult($url, '', ['http_code' => 500], $argumentException->getCode(), $argumentException->getMessage()); - } finally { - $this->logger->debug('Request stop.', ['url' => $url, 'method' => $method]); - $this->profiler->stopRecording(); - } - } - - /** {@inheritDoc} - */ - public function head(string $url, array $opts = []): ICanHandleHttpResponses - { - return $this->request('head', $url, $opts); - } - - /** - * {@inheritDoc} - */ - public function get(string $url, array $opts = []): ICanHandleHttpResponses - { - return $this->request('get', $url, $opts); - } - - /** - * {@inheritDoc} - */ - public function post(string $url, $params, array $headers = [], int $timeout = 0): ICanHandleHttpResponses - { - $opts = []; - - $opts[HttpClientOptions::BODY] = $params; - - if (!empty($headers)) { - $opts[HttpClientOptions::HEADERS] = $headers; - } - - if (!empty($timeout)) { - $opts[HttpClientOptions::TIMEOUT] = $timeout; - } - - return $this->request('post', $url, $opts); - } - - /** - * {@inheritDoc} - */ - public function finalUrl(string $url): string - { - $this->profiler->startRecording('network'); - - if (Network::isLocalLink($url)) { - $this->logger->debug('Local link', ['url' => $url, 'callstack' => System::callstack(20)]); - } - - if (Network::isUrlBlocked($url)) { - $this->logger->info('Domain is blocked.', ['url' => $url]); - return $url; - } - - if (Network::isRedirectBlocked($url)) { - $this->logger->info('Domain should not be redirected.', ['url' => $url]); - return $url; - } - - $url = Network::stripTrackingQueryParams($url); - - $url = trim($url, "'"); - - $urlResult = $this->resolver->resolveURL($url); - - if ($urlResult->didErrorOccur()) { - throw new TransferException($urlResult->getErrorMessageString(), $urlResult->getHTTPStatusCode()); - } - - return $urlResult->getURL(); - } - - /** - * {@inheritDoc} - */ - public function fetch(string $url, int $timeout = 0, string $accept_content = '', string $cookiejar = ''): string - { - $ret = $this->fetchFull($url, $timeout, $accept_content, $cookiejar); - - return $ret->getBody(); - } - - /** - * {@inheritDoc} - */ - public function fetchFull(string $url, int $timeout = 0, string $accept_content = '', string $cookiejar = ''): ICanHandleHttpResponses - { - return $this->get( - $url, - [ - 'timeout' => $timeout, - 'accept_content' => $accept_content, - 'cookiejar' => $cookiejar - ] - ); - } -} diff --git a/src/Network/HTTPClient/Factory/HttpClient.php b/src/Network/HTTPClient/Factory/HttpClient.php index 732f01b34c..c7ee68a926 100644 --- a/src/Network/HTTPClient/Factory/HttpClient.php +++ b/src/Network/HTTPClient/Factory/HttpClient.php @@ -6,7 +6,7 @@ use Friendica\App; use Friendica\BaseFactory; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Network\HTTPClient\Client; -use Friendica\Network\HTTPClient\Capability\ICanRequestPerHttp; +use Friendica\Network\HTTPClient\Capability\ICanSendHttpRequests; use Friendica\Util\Profiler; use Friendica\Util\Strings; use GuzzleHttp; @@ -42,9 +42,9 @@ class HttpClient extends BaseFactory * * @param HandlerStack|null $handlerStack (optional) A handler replacement (just usefull at test environments) * - * @return ICanRequestPerHttp + * @return ICanSendHttpRequests */ - public function createClient(HandlerStack $handlerStack = null): ICanRequestPerHttp + public function createClient(HandlerStack $handlerStack = null): ICanSendHttpRequests { $proxy = $this->config->get('system', 'proxy'); @@ -108,6 +108,6 @@ class HttpClient extends BaseFactory // Some websites test the browser for cookie support, so this enhances results. $resolver->setCookieJar(get_temppath() .'/resolver-cookie-' . Strings::getRandomName(10)); - return new Client\HttpClientCan($logger, $this->profiler, $guzzle, $resolver); + return new Client\HttpClient($logger, $this->profiler, $guzzle, $resolver); } } diff --git a/static/dependencies.config.php b/static/dependencies.config.php index dad374d8da..a43fb368af 100644 --- a/static/dependencies.config.php +++ b/static/dependencies.config.php @@ -146,7 +146,7 @@ return [ 'index', ], 'call' => [ - ['create', ['index'], Dice::CHAIN_CALL], + ['create', [], Dice::CHAIN_CALL], ], ], '$devLogger' => [ @@ -224,7 +224,7 @@ return [ ['getBackend', [], Dice::CHAIN_CALL], ], ], - Network\HTTPClient\Capability\ICanRequestPerHttp::class => [ + Network\HTTPClient\Capability\ICanSendHttpRequests::class => [ 'instanceOf' => Network\HTTPClient\Factory\HttpClient::class, 'call' => [ ['createClient', [], Dice::CHAIN_CALL], diff --git a/tests/DiceHttpMockHandlerTrait.php b/tests/DiceHttpMockHandlerTrait.php index 7f77d7e4fe..3381a3219f 100644 --- a/tests/DiceHttpMockHandlerTrait.php +++ b/tests/DiceHttpMockHandlerTrait.php @@ -24,7 +24,7 @@ namespace Friendica\Test; use Dice\Dice; use Friendica\DI; use Friendica\Network\HTTPClient\Factory\HttpClient; -use Friendica\Network\HTTPClient\Capability\ICanRequestPerHttp; +use Friendica\Network\HTTPClient\Capability\ICanSendHttpRequests; use GuzzleHttp\HandlerStack; /** @@ -49,7 +49,7 @@ trait DiceHttpMockHandlerTrait $dice = DI::getDice(); // addRule() clones the current instance and returns a new one, so no concurrency problems :-) - $newDice = $dice->addRule(ICanRequestPerHttp::class, [ + $newDice = $dice->addRule(ICanSendHttpRequests::class, [ 'instanceOf' => HttpClient::class, 'call' => [ ['createClient', [$this->httpRequestHandler], Dice::CHAIN_CALL], diff --git a/tests/src/Core/InstallerTest.php b/tests/src/Core/InstallerTest.php index 8db5a7d5c0..0e44f19a42 100644 --- a/tests/src/Core/InstallerTest.php +++ b/tests/src/Core/InstallerTest.php @@ -26,7 +26,7 @@ use Dice\Dice; use Friendica\Core\Config\ValueObject\Cache; use Friendica\DI; use Friendica\Network\HTTPClient\Capability\ICanHandleHttpResponses; -use Friendica\Network\HTTPClient\Capability\ICanRequestPerHttp; +use Friendica\Network\HTTPClient\Capability\ICanSendHttpRequests; use Friendica\Test\MockedTest; use Friendica\Test\Util\VFSTrait; use Mockery; @@ -331,7 +331,7 @@ class InstallerTest extends MockedTest ->andReturn('test Error'); // Mocking the CURL Request - $networkMock = Mockery::mock(ICanRequestPerHttp::class); + $networkMock = Mockery::mock(ICanSendHttpRequests::class); $networkMock ->shouldReceive('fetchFull') ->with('https://test/install/testrewrite') @@ -342,7 +342,7 @@ class InstallerTest extends MockedTest ->andReturn($IHTTPResult); $this->dice->shouldReceive('create') - ->with(ICanRequestPerHttp::class) + ->with(ICanSendHttpRequests::class) ->andReturn($networkMock); DI::init($this->dice); @@ -378,7 +378,7 @@ class InstallerTest extends MockedTest ->andReturn('204'); // Mocking the CURL Request - $networkMock = Mockery::mock(ICanRequestPerHttp::class); + $networkMock = Mockery::mock(ICanSendHttpRequests::class); $networkMock ->shouldReceive('fetchFull') ->with('https://test/install/testrewrite') @@ -389,7 +389,7 @@ class InstallerTest extends MockedTest ->andReturn($IHTTPResultW); $this->dice->shouldReceive('create') - ->with(ICanRequestPerHttp::class) + ->with(ICanSendHttpRequests::class) ->andReturn($networkMock); DI::init($this->dice); diff --git a/tests/src/Core/Logger/StreamLoggerTest.php b/tests/src/Core/Logger/StreamLoggerTest.php index 65ef76ea3d..16c8786beb 100644 --- a/tests/src/Core/Logger/StreamLoggerTest.php +++ b/tests/src/Core/Logger/StreamLoggerTest.php @@ -23,6 +23,7 @@ namespace Friendica\Test\src\Core\Logger; use Friendica\Core\Logger\Exception\LoggerArgumentException; use Friendica\Core\Logger\Exception\LoggerException; +use Friendica\Core\Logger\Exception\LogLevelException; use Friendica\Util\FileSystem; use Friendica\Test\Util\VFSTrait; use Friendica\Core\Logger\Type\StreamLogger; @@ -160,7 +161,7 @@ class StreamLoggerTest extends AbstractLoggerTest */ public function testWrongMinimumLevel() { - $this->expectException(LoggerArgumentException::class); + $this->expectException(LogLevelException::class); $this->expectExceptionMessageMatches("/The level \".*\" is not valid./"); $logger = new StreamLogger('test', 'file.text', $this->introspection, $this->fileSystem, 'NOPE'); @@ -171,7 +172,7 @@ class StreamLoggerTest extends AbstractLoggerTest */ public function testWrongLogLevel() { - $this->expectException(LoggerArgumentException::class); + $this->expectException(LogLevelException::class); $this->expectExceptionMessageMatches("/The level \".*\" is not valid./"); $logfile = vfsStream::newFile('friendica.log') diff --git a/tests/src/Core/Logger/SyslogLoggerTest.php b/tests/src/Core/Logger/SyslogLoggerTest.php index 8ba2ebc084..fc3525e0bc 100644 --- a/tests/src/Core/Logger/SyslogLoggerTest.php +++ b/tests/src/Core/Logger/SyslogLoggerTest.php @@ -23,6 +23,7 @@ namespace Friendica\Test\src\Core\Logger; use Friendica\Core\Logger\Exception\LoggerArgumentException; use Friendica\Core\Logger\Exception\LoggerException; +use Friendica\Core\Logger\Exception\LogLevelException; use Friendica\Core\Logger\Type\SyslogLogger; use Psr\Log\LogLevel; @@ -64,7 +65,7 @@ class SyslogLoggerTest extends AbstractLoggerTest */ public function testWrongMinimumLevel() { - $this->expectException(LoggerArgumentException::class); + $this->expectException(LogLevelException::class); $this->expectExceptionMessageMatches("/The level \".*\" is not valid./"); $logger = new SyslogLoggerWrapper('test', $this->introspection, 'NOPE'); @@ -75,7 +76,7 @@ class SyslogLoggerTest extends AbstractLoggerTest */ public function testWrongLogLevel() { - $this->expectException(LoggerArgumentException::class); + $this->expectException(LogLevelException::class); $this->expectExceptionMessageMatches("/The level \".*\" is not valid./"); $logger = new SyslogLoggerWrapper('test', $this->introspection); diff --git a/tests/src/Core/Storage/Repository/StorageManagerTest.php b/tests/src/Core/Storage/Repository/StorageManagerTest.php index c090c26963..608e48f074 100644 --- a/tests/src/Core/Storage/Repository/StorageManagerTest.php +++ b/tests/src/Core/Storage/Repository/StorageManagerTest.php @@ -40,7 +40,7 @@ use Friendica\DI; use Friendica\Core\Config\Factory\Config; use Friendica\Core\Config\Repository; use Friendica\Core\Storage\Type; -use Friendica\Network\HTTPClient\Client\HttpClientCan; +use Friendica\Network\HTTPClient\Client\HttpClient; use Friendica\Test\DatabaseTest; use Friendica\Test\Util\Database\StaticDatabase; use Friendica\Test\Util\VFSTrait; @@ -61,7 +61,7 @@ class StorageManagerTest extends DatabaseTest private $logger; /** @var L10n */ private $l10n; - /** @var HttpClientCan */ + /** @var HttpClient */ private $httpRequest; protected function setUp(): void @@ -93,7 +93,7 @@ class StorageManagerTest extends DatabaseTest $this->l10n = \Mockery::mock(L10n::class); - $this->httpRequest = \Mockery::mock(HttpClientCan::class); + $this->httpRequest = \Mockery::mock(HttpClient::class); } protected function tearDown(): void