From: Philipp Date: Sun, 22 Aug 2021 22:14:18 +0000 (+0200) Subject: Create HTTPClientFactory and introduce ImageTest X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=52c7948526152269d898f1187bcda4d103bb4380;p=friendica.git Create HTTPClientFactory and introduce ImageTest --- diff --git a/src/DI.php b/src/DI.php index cbc353161c..5c56b45e13 100644 --- a/src/DI.php +++ b/src/DI.php @@ -407,11 +407,11 @@ abstract class DI // /** - * @return Network\IHTTPRequest + * @return Network\IHTTPClient */ public static function httpRequest() { - return self::$dice->create(Network\IHTTPRequest::class); + return self::$dice->create(Network\IHTTPClient::class); } // diff --git a/src/Factory/HTTPClientFactory.php b/src/Factory/HTTPClientFactory.php new file mode 100644 index 0000000000..604b6fd62c --- /dev/null +++ b/src/Factory/HTTPClientFactory.php @@ -0,0 +1,87 @@ +config = $config; + $this->profiler = $profiler; + $this->baseUrl = $baseUrl; + } + + public function createClient(): IHTTPClient + { + $proxy = $this->config->get('system', 'proxy'); + + if (!empty($proxy)) { + $proxyuser = $this->config->get('system', 'proxyuser'); + + if (!empty($proxyuser)) { + $proxy = $proxyuser . '@' . $proxy; + } + } + + $logger = $this->logger; + + $onRedirect = function ( + RequestInterface $request, + ResponseInterface $response, + UriInterface $uri + ) use ($logger) { + $logger->notice('Curl redirect.', ['url' => $request->getUri(), 'to' => $uri]); + }; + + $guzzle = new Client([ + RequestOptions::ALLOW_REDIRECTS => [ + 'max' => 8, + 'on_redirect' => $onRedirect, + 'track_redirect' => true, + 'strict' => true, + 'referer' => true, + ], + RequestOptions::HTTP_ERRORS => false, + // Without this setting it seems as if some webservers send compressed content + // This seems to confuse curl so that it shows this uncompressed. + /// @todo We could possibly set this value to "gzip" or something similar + RequestOptions::DECODE_CONTENT => '', + RequestOptions::FORCE_IP_RESOLVE => ($this->config->get('system', 'ipv4_resolve') ? 'v4' : null), + RequestOptions::CONNECT_TIMEOUT => 10, + RequestOptions::TIMEOUT => $this->config->get('system', 'curl_timeout', 60), + // by default we will allow self-signed certs + // but you can override this + RequestOptions::VERIFY => (bool)$this->config->get('system', 'verifyssl'), + RequestOptions::PROXY => $proxy, + ]); + + $userAgent = FRIENDICA_PLATFORM . " '" . + FRIENDICA_CODENAME . "' " . + FRIENDICA_VERSION . '-' . + DB_UPDATE_VERSION . '; ' . + $this->baseUrl->get(); + + return new HTTPClient($logger, $this->profiler, $this->config, $userAgent, $guzzle); + } +} diff --git a/src/Network/HTTPClient.php b/src/Network/HTTPClient.php new file mode 100644 index 0000000000..e20ddd3c9e --- /dev/null +++ b/src/Network/HTTPClient.php @@ -0,0 +1,421 @@ +. + * + */ + +namespace Friendica\Network; + +use DOMDocument; +use DomXPath; +use Friendica\Core\Config\IConfig; +use Friendica\Core\System; +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 Psr\Http\Message\ResponseInterface; +use Psr\Log\LoggerInterface; + +/** + * Performs HTTP requests to a given URL + */ +class HTTPClient implements IHTTPClient +{ + /** @var LoggerInterface */ + private $logger; + /** @var Profiler */ + private $profiler; + /** @var IConfig */ + private $config; + /** @var string */ + private $userAgent; + /** @var Client */ + private $client; + + public function __construct(LoggerInterface $logger, Profiler $profiler, IConfig $config, string $userAgent, Client $client) + { + $this->logger = $logger; + $this->profiler = $profiler; + $this->config = $config; + $this->userAgent = $userAgent; + $this->client = $client; + } + + protected function request(string $method, string $url, array $opts = []) + { + $this->profiler->startRecording('network'); + + 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['cookiejar'])) { + $jar = new FileCookieJar($opts['cookiejar']); + $conf[RequestOptions::COOKIES] = $jar; + } + + if (!empty($opts['accept_content'])) { + array_push($curlOptions[CURLOPT_HTTPHEADER], 'Accept: ' . $opts['accept_content']); + } + + if (!empty($opts['header'])) { + $curlOptions[CURLOPT_HTTPHEADER] = array_merge($opts['header'], $curlOptions[CURLOPT_HTTPHEADER]); + } + + $curlOptions[CURLOPT_USERAGENT] = $this->userAgent; + + if (!empty($opts['headers'])) { + $this->logger->notice('Wrong option \'headers\' used.'); + $curlOptions[CURLOPT_HTTPHEADER] = array_merge($opts['headers'], $curlOptions[CURLOPT_HTTPHEADER]); + } + + if (!empty($opts['timeout'])) { + $curlOptions[CURLOPT_TIMEOUT] = $opts['timeout']; + } + + $onHeaders = function (ResponseInterface $response) use ($opts) { + if (!empty($opts['content_length']) && + $response->getHeaderLine('Content-Length') > $opts['content_length']) { + throw new TransferException('The file is too big!'); + } + }; + + try { + $response = $this->client->$method($url, [ + 'on_headers' => $onHeaders, + 'curl' => $curlOptions, + ]); + 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' => $exception->getCode()], $exception->getCode(), ''); + } + } finally { + $this->profiler->stopRecording(); + } + } + + /** {@inheritDoc} + * + * @throws HTTPException\InternalServerErrorException + */ + public function head(string $url, array $opts = []) + { + return $this->request('head', $url, $opts); + } + + /** + * {@inheritDoc} + */ + public function get(string $url, array $opts = []) + { + return $this->request('get', $url, $opts); + } + + /** + * {@inheritDoc} + * + * @param int $redirects The recursion counter for internal use - default 0 + * + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public function post(string $url, $params, array $headers = [], int $timeout = 0, &$redirects = 0) + { + $this->profiler->startRecording('network'); + + if (Network::isLocalLink($url)) { + $this->logger->info('Local link', ['url' => $url, 'callstack' => System::callstack(20)]); + } + + if (Network::isUrlBlocked($url)) { + $this->logger->info('Domain is blocked.' . ['url' => $url]); + $this->profiler->stopRecording(); + return CurlResult::createErrorCurl($url); + } + + $ch = curl_init($url); + + if (($redirects > 8) || (!$ch)) { + $this->profiler->stopRecording(); + return CurlResult::createErrorCurl($url); + } + + $this->logger->debug('Post_url: start.', ['url' => $url]); + + curl_setopt($ch, CURLOPT_HEADER, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $params); + curl_setopt($ch, CURLOPT_USERAGENT, $this->userAgent); + + if ($this->config->get('system', 'ipv4_resolve', false)) { + curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + } + + @curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); + + if (intval($timeout)) { + curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); + } else { + $curl_time = $this->config->get('system', 'curl_timeout', 60); + curl_setopt($ch, CURLOPT_TIMEOUT, intval($curl_time)); + } + + if (!empty($headers)) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + } + + $check_cert = $this->config->get('system', 'verifyssl'); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false)); + + if ($check_cert) { + @curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + } + + $proxy = $this->config->get('system', 'proxy'); + + if (!empty($proxy)) { + curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1); + curl_setopt($ch, CURLOPT_PROXY, $proxy); + $proxyuser = $this->config->get('system', 'proxyuser'); + if (!empty($proxyuser)) { + curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxyuser); + } + } + + // don't let curl abort the entire application + // if it throws any errors. + + $s = @curl_exec($ch); + + $curl_info = curl_getinfo($ch); + + $curlResponse = new CurlResult($url, $s, $curl_info, curl_errno($ch), curl_error($ch)); + + if (!Network::isRedirectBlocked($url) && $curlResponse->isRedirectUrl()) { + $redirects++; + $this->logger->info('Post redirect.', ['url' => $url, 'to' => $curlResponse->getRedirectUrl()]); + curl_close($ch); + $this->profiler->stopRecording(); + return $this->post($curlResponse->getRedirectUrl(), $params, $headers, $redirects, $timeout); + } + + curl_close($ch); + + $this->profiler->stopRecording(); + + // Very old versions of Lighttpd don't like the "Expect" header, so we remove it when needed + if ($curlResponse->getReturnCode() == 417) { + $redirects++; + + if (empty($headers)) { + $headers = ['Expect:']; + } else { + if (!in_array('Expect:', $headers)) { + array_push($headers, 'Expect:'); + } + } + $this->logger->info('Server responds with 417, applying workaround', ['url' => $url]); + return $this->post($url, $params, $headers, $redirects, $timeout); + } + + $this->logger->debug('Post_url: End.', ['url' => $url]); + + return $curlResponse; + } + + /** + * {@inheritDoc} + */ + public function finalUrl(string $url, int $depth = 1, bool $fetchbody = false) + { + if (Network::isLocalLink($url)) { + $this->logger->info('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); + + if ($depth > 10) { + return $url; + } + + $url = trim($url, "'"); + + $this->profiler->startRecording('network'); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_NOBODY, 1); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_USERAGENT, $this->userAgent); + + curl_exec($ch); + $curl_info = @curl_getinfo($ch); + $http_code = $curl_info['http_code']; + curl_close($ch); + + $this->profiler->stopRecording(); + + if ($http_code == 0) { + return $url; + } + + if (in_array($http_code, ['301', '302'])) { + if (!empty($curl_info['redirect_url'])) { + return $this->finalUrl($curl_info['redirect_url'], ++$depth, $fetchbody); + } elseif (!empty($curl_info['location'])) { + return $this->finalUrl($curl_info['location'], ++$depth, $fetchbody); + } + } + + // Check for redirects in the meta elements of the body if there are no redirects in the header. + if (!$fetchbody) { + return $this->finalUrl($url, ++$depth, true); + } + + // if the file is too large then exit + if ($curl_info["download_content_length"] > 1000000) { + return $url; + } + + // if it isn't a HTML file then exit + if (!empty($curl_info["content_type"]) && !strstr(strtolower($curl_info["content_type"]), "html")) { + return $url; + } + + $this->profiler->startRecording('network'); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_NOBODY, 0); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_USERAGENT, $this->userAgent); + + $body = curl_exec($ch); + curl_close($ch); + + $this->profiler->stopRecording(); + + if (trim($body) == "") { + return $url; + } + + // Check for redirect in meta elements + $doc = new DOMDocument(); + @$doc->loadHTML($body); + + $xpath = new DomXPath($doc); + + $list = $xpath->query("//meta[@content]"); + foreach ($list as $node) { + $attr = []; + if ($node->attributes->length) { + foreach ($node->attributes as $attribute) { + $attr[$attribute->name] = $attribute->value; + } + } + + if (@$attr["http-equiv"] == 'refresh') { + $path = $attr["content"]; + $pathinfo = explode(";", $path); + foreach ($pathinfo as $value) { + if (substr(strtolower($value), 0, 4) == "url=") { + return $this->finalUrl(substr($value, 4), ++$depth); + } + } + } + } + + return $url; + } + + /** + * {@inheritDoc} + */ + public function fetch(string $url, int $timeout = 0, string $accept_content = '', string $cookiejar = '') + { + $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 = '') + { + return $this->get( + $url, + [ + 'timeout' => $timeout, + 'accept_content' => $accept_content, + 'cookiejar' => $cookiejar + ] + ); + } +} diff --git a/src/Network/HTTPRequest.php b/src/Network/HTTPRequest.php deleted file mode 100644 index b08e918326..0000000000 --- a/src/Network/HTTPRequest.php +++ /dev/null @@ -1,501 +0,0 @@ -. - * - */ - -namespace Friendica\Network; - -use DOMDocument; -use DomXPath; -use Friendica\App; -use Friendica\Core\Config\IConfig; -use Friendica\Core\System; -use Friendica\Util\Network; -use Friendica\Util\Profiler; -use GuzzleHttp\Client; -use GuzzleHttp\Exception\RequestException; -use GuzzleHttp\Exception\TransferException; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\UriInterface; -use Psr\Log\LoggerInterface; - -/** - * Performs HTTP requests to a given URL - */ -class HTTPRequest implements IHTTPRequest -{ - /** @var LoggerInterface */ - private $logger; - /** @var Profiler */ - private $profiler; - /** @var IConfig */ - private $config; - /** @var string */ - private $baseUrl; - - public function __construct(LoggerInterface $logger, Profiler $profiler, IConfig $config, App\BaseURL $baseUrl) - { - $this->logger = $logger; - $this->profiler = $profiler; - $this->config = $config; - $this->baseUrl = $baseUrl->get(); - } - - /** {@inheritDoc} - * - * @throws HTTPException\InternalServerErrorException - */ - public function head(string $url, array $opts = []) - { - $opts['nobody'] = true; - - return $this->get($url, $opts); - } - - /** - * {@inheritDoc} - */ - public function get(string $url, array $opts = []) - { - $this->profiler->startRecording('network'); - - 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); - } - - $curlOptions = []; - - if (!empty($opts['cookiejar'])) { - $curlOptions[CURLOPT_COOKIEJAR] = $opts["cookiejar"]; - $curlOptions[CURLOPT_COOKIEFILE] = $opts["cookiejar"]; - } - - // These settings aren't needed. We're following the location already. - // $curlOptions[CURLOPT_FOLLOWLOCATION] =true; - // $curlOptions[CURLOPT_MAXREDIRS] = 5; - - $curlOptions[CURLOPT_HTTPHEADER] = []; - - if (!empty($opts['accept_content'])) { - array_push($curlOptions[CURLOPT_HTTPHEADER], 'Accept: ' . $opts['accept_content']); - } - - if (!empty($opts['header'])) { - $curlOptions[CURLOPT_HTTPHEADER] = array_merge($opts['header'], $curlOptions[CURLOPT_HTTPHEADER]); - } - - $curlOptions[CURLOPT_RETURNTRANSFER] = true; - $curlOptions[CURLOPT_USERAGENT] = $this->getUserAgent(); - - $range = intval($this->config->get('system', 'curl_range_bytes', 0)); - - if ($range > 0) { - $curlOptions[CURLOPT_RANGE] = '0-' . $range; - } - - // Without this setting it seems as if some webservers send compressed content - // This seems to confuse curl so that it shows this uncompressed. - /// @todo We could possibly set this value to "gzip" or something similar - $curlOptions[CURLOPT_ENCODING] = ''; - - if (!empty($opts['headers'])) { - $this->logger->notice('Wrong option \'headers\' used.'); - $curlOptions[CURLOPT_HTTPHEADER] = array_merge($opts['headers'], $curlOptions[CURLOPT_HTTPHEADER]); - } - - if (!empty($opts['nobody'])) { - $curlOptions[CURLOPT_NOBODY] = $opts['nobody']; - } - - $curlOptions[CURLOPT_CONNECTTIMEOUT] = 10; - - if (!empty($opts['timeout'])) { - $curlOptions[CURLOPT_TIMEOUT] = $opts['timeout']; - } else { - $curl_time = $this->config->get('system', 'curl_timeout', 60); - $curlOptions[CURLOPT_TIMEOUT] = intval($curl_time); - } - - // by default we will allow self-signed certs - // but you can override this - - $check_cert = $this->config->get('system', 'verifyssl'); - $curlOptions[CURLOPT_SSL_VERIFYPEER] = ($check_cert) ? true : false; - - if ($check_cert) { - $curlOptions[CURLOPT_SSL_VERIFYHOST] = 2; - } - - $proxy = $this->config->get('system', 'proxy'); - - if (!empty($proxy)) { - $curlOptions[CURLOPT_HTTPPROXYTUNNEL] = 1; - $curlOptions[CURLOPT_PROXY] = $proxy; - $proxyuser = $this->config->get('system', 'proxyuser'); - - if (!empty($proxyuser)) { - $curlOptions[CURLOPT_PROXYUSERPWD] = $proxyuser; - } - } - - if ($this->config->get('system', 'ipv4_resolve', false)) { - $curlOptions[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4; - } - - $logger = $this->logger; - - $onRedirect = function( - RequestInterface $request, - ResponseInterface $response, - UriInterface $uri - ) use ($logger) { - $logger->notice('Curl redirect.', ['url' => $request->getUri(), 'to' => $uri]); - }; - - $onHeaders = function (ResponseInterface $response) use ($opts) { - if (!empty($opts['content_length']) && - $response->getHeaderLine('Content-Length') > $opts['content_length']) { - throw new TransferException('The file is too big!'); - } - }; - - $client = new Client([ - 'allow_redirect' => [ - 'max' => 8, - 'on_redirect' => $onRedirect, - 'track_redirect' => true, - 'strict' => true, - 'referer' => true, - ], - 'on_headers' => $onHeaders, - 'curl' => $curlOptions - ]); - - try { - $response = $client->get($url); - 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' => $exception->getCode()], $exception->getCode(), ''); - } - } finally { - $this->profiler->stopRecording(); - } - } - - /** - * {@inheritDoc} - * - * @param int $redirects The recursion counter for internal use - default 0 - * - * @throws \Friendica\Network\HTTPException\InternalServerErrorException - */ - public function post(string $url, $params, array $headers = [], int $timeout = 0, &$redirects = 0) - { - $this->profiler->startRecording('network'); - - if (Network::isLocalLink($url)) { - $this->logger->info('Local link', ['url' => $url, 'callstack' => System::callstack(20)]); - } - - if (Network::isUrlBlocked($url)) { - $this->logger->info('Domain is blocked.' . ['url' => $url]); - $this->profiler->stopRecording(); - return CurlResult::createErrorCurl($url); - } - - $ch = curl_init($url); - - if (($redirects > 8) || (!$ch)) { - $this->profiler->stopRecording(); - return CurlResult::createErrorCurl($url); - } - - $this->logger->debug('Post_url: start.', ['url' => $url]); - - curl_setopt($ch, CURLOPT_HEADER, true); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, $params); - curl_setopt($ch, CURLOPT_USERAGENT, $this->getUserAgent()); - - if ($this->config->get('system', 'ipv4_resolve', false)) { - curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); - } - - @curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); - - if (intval($timeout)) { - curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); - } else { - $curl_time = $this->config->get('system', 'curl_timeout', 60); - curl_setopt($ch, CURLOPT_TIMEOUT, intval($curl_time)); - } - - if (!empty($headers)) { - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - } - - $check_cert = $this->config->get('system', 'verifyssl'); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false)); - - if ($check_cert) { - @curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); - } - - $proxy = $this->config->get('system', 'proxy'); - - if (!empty($proxy)) { - curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1); - curl_setopt($ch, CURLOPT_PROXY, $proxy); - $proxyuser = $this->config->get('system', 'proxyuser'); - if (!empty($proxyuser)) { - curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxyuser); - } - } - - // don't let curl abort the entire application - // if it throws any errors. - - $s = @curl_exec($ch); - - $curl_info = curl_getinfo($ch); - - $curlResponse = new CurlResult($url, $s, $curl_info, curl_errno($ch), curl_error($ch)); - - if (!Network::isRedirectBlocked($url) && $curlResponse->isRedirectUrl()) { - $redirects++; - $this->logger->info('Post redirect.', ['url' => $url, 'to' => $curlResponse->getRedirectUrl()]); - curl_close($ch); - $this->profiler->stopRecording(); - return $this->post($curlResponse->getRedirectUrl(), $params, $headers, $redirects, $timeout); - } - - curl_close($ch); - - $this->profiler->stopRecording(); - - // Very old versions of Lighttpd don't like the "Expect" header, so we remove it when needed - if ($curlResponse->getReturnCode() == 417) { - $redirects++; - - if (empty($headers)) { - $headers = ['Expect:']; - } else { - if (!in_array('Expect:', $headers)) { - array_push($headers, 'Expect:'); - } - } - $this->logger->info('Server responds with 417, applying workaround', ['url' => $url]); - return $this->post($url, $params, $headers, $redirects, $timeout); - } - - $this->logger->debug('Post_url: End.', ['url' => $url]); - - return $curlResponse; - } - - /** - * {@inheritDoc} - */ - public function finalUrl(string $url, int $depth = 1, bool $fetchbody = false) - { - if (Network::isLocalLink($url)) { - $this->logger->info('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); - - if ($depth > 10) { - return $url; - } - - $url = trim($url, "'"); - - $this->profiler->startRecording('network'); - - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_HEADER, 1); - curl_setopt($ch, CURLOPT_NOBODY, 1); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); - curl_setopt($ch, CURLOPT_TIMEOUT, 10); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_USERAGENT, $this->getUserAgent()); - - curl_exec($ch); - $curl_info = @curl_getinfo($ch); - $http_code = $curl_info['http_code']; - curl_close($ch); - - $this->profiler->stopRecording(); - - if ($http_code == 0) { - return $url; - } - - if (in_array($http_code, ['301', '302'])) { - if (!empty($curl_info['redirect_url'])) { - return $this->finalUrl($curl_info['redirect_url'], ++$depth, $fetchbody); - } elseif (!empty($curl_info['location'])) { - return $this->finalUrl($curl_info['location'], ++$depth, $fetchbody); - } - } - - // Check for redirects in the meta elements of the body if there are no redirects in the header. - if (!$fetchbody) { - return $this->finalUrl($url, ++$depth, true); - } - - // if the file is too large then exit - if ($curl_info["download_content_length"] > 1000000) { - return $url; - } - - // if it isn't a HTML file then exit - if (!empty($curl_info["content_type"]) && !strstr(strtolower($curl_info["content_type"]), "html")) { - return $url; - } - - $this->profiler->startRecording('network'); - - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_HEADER, 0); - curl_setopt($ch, CURLOPT_NOBODY, 0); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); - curl_setopt($ch, CURLOPT_TIMEOUT, 10); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_USERAGENT, $this->getUserAgent()); - - $body = curl_exec($ch); - curl_close($ch); - - $this->profiler->stopRecording(); - - if (trim($body) == "") { - return $url; - } - - // Check for redirect in meta elements - $doc = new DOMDocument(); - @$doc->loadHTML($body); - - $xpath = new DomXPath($doc); - - $list = $xpath->query("//meta[@content]"); - foreach ($list as $node) { - $attr = []; - if ($node->attributes->length) { - foreach ($node->attributes as $attribute) { - $attr[$attribute->name] = $attribute->value; - } - } - - if (@$attr["http-equiv"] == 'refresh') { - $path = $attr["content"]; - $pathinfo = explode(";", $path); - foreach ($pathinfo as $value) { - if (substr(strtolower($value), 0, 4) == "url=") { - return $this->finalUrl(substr($value, 4), ++$depth); - } - } - } - } - - return $url; - } - - /** - * {@inheritDoc} - */ - public function fetch(string $url, int $timeout = 0, string $accept_content = '', string $cookiejar = '') - { - $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 = '') - { - return $this->get( - $url, - [ - 'timeout' => $timeout, - 'accept_content' => $accept_content, - 'cookiejar' => $cookiejar - ] - ); - } - - /** - * {@inheritDoc} - */ - public function getUserAgent() - { - return - FRIENDICA_PLATFORM . " '" . - FRIENDICA_CODENAME . "' " . - FRIENDICA_VERSION . '-' . - DB_UPDATE_VERSION . '; ' . - $this->baseUrl; - } -} diff --git a/src/Network/IHTTPClient.php b/src/Network/IHTTPClient.php new file mode 100644 index 0000000000..8fa5285d26 --- /dev/null +++ b/src/Network/IHTTPClient.php @@ -0,0 +1,117 @@ +. + * + */ + +namespace Friendica\Network; + +/** + * Interface for calling HTTP requests and returning their responses + */ +interface IHTTPClient +{ + /** + * 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 = ''); + + /** + * 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 IHTTPResult With all relevant information, 'body' contains the actual fetched content. + */ + public function fetchFull(string $url, int $timeout = 0, string $accept_content = '', string $cookiejar = ''); + + /** + * Send a HEAD to an URL. + * + * @param string $url URL to fetch + * @param array $opts (optional parameters) assoziative array with: + * 'accept_content' => 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 CurlResult + */ + public function head(string $url, array $opts = []); + + /** + * Send a GET to an URL. + * + * @param string $url URL to fetch + * @param array $opts (optional parameters) assoziative array with: + * 'accept_content' => 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 IHTTPResult + */ + public function get(string $url, array $opts = []); + + /** + * 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 IHTTPResult The content + */ + public function post(string $url, $params, array $headers = [], int $timeout = 0); + + /** + * 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 + * @param int $depth The current redirection recursion level (internal) + * @param bool $fetchbody Wether to fetch the body or not after the HEAD requests + * + * @return string A canonical URL + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @see ParseUrl::getSiteinfo + * + * @todo Remove the $fetchbody parameter that generates an extraneous HEAD request + */ + public function finalUrl(string $url, int $depth = 1, bool $fetchbody = false); +} diff --git a/src/Network/IHTTPRequest.php b/src/Network/IHTTPRequest.php deleted file mode 100644 index b496c7feba..0000000000 --- a/src/Network/IHTTPRequest.php +++ /dev/null @@ -1,124 +0,0 @@ -. - * - */ - -namespace Friendica\Network; - -/** - * Interface for calling HTTP requests and returning their responses - */ -interface IHTTPRequest -{ - /** - * 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 = ''); - - /** - * 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 IHTTPResult With all relevant information, 'body' contains the actual fetched content. - */ - public function fetchFull(string $url, int $timeout = 0, string $accept_content = '', string $cookiejar = ''); - - /** - * Send a HEAD to an URL. - * - * @param string $url URL to fetch - * @param array $opts (optional parameters) assoziative array with: - * 'accept_content' => 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 CurlResult - */ - public function head(string $url, array $opts = []); - - /** - * Send a GET to an URL. - * - * @param string $url URL to fetch - * @param array $opts (optional parameters) assoziative array with: - * 'accept_content' => 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 IHTTPResult - */ - public function get(string $url, array $opts = []); - - /** - * 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 IHTTPResult The content - */ - public function post(string $url, $params, array $headers = [], int $timeout = 0); - - /** - * 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 - * @param int $depth The current redirection recursion level (internal) - * @param bool $fetchbody Wether to fetch the body or not after the HEAD requests - * - * @return string A canonical URL - * @throws \Friendica\Network\HTTPException\InternalServerErrorException - * @see ParseUrl::getSiteinfo - * - * @todo Remove the $fetchbody parameter that generates an extraneous HEAD request - */ - public function finalUrl(string $url, int $depth = 1, bool $fetchbody = false); - - /** - * Returns the current UserAgent as a String - * - * @return string the UserAgent as a String - */ - public function getUserAgent(); -} diff --git a/static/dependencies.config.php b/static/dependencies.config.php index f07a61807e..2068b6b1ad 100644 --- a/static/dependencies.config.php +++ b/static/dependencies.config.php @@ -220,8 +220,11 @@ return [ ['getBackend', [], Dice::CHAIN_CALL], ], ], - Network\IHTTPRequest::class => [ - 'instanceOf' => Network\HTTPRequest::class, + Network\IHTTPClient::class => [ + 'instanceOf' => Factory\HTTPClientFactory::class, + 'call' => [ + ['createClient', [], Dice::CHAIN_CALL], + ], ], Factory\Api\Mastodon\Error::class => [ 'constructParams' => [ @@ -232,5 +235,5 @@ return [ 'constructParams' => [ [Dice::INSTANCE => Util\ReversedFileReader::class], ] - ] + ], ]; diff --git a/tests/datasets/curl/image.content b/tests/datasets/curl/image.content new file mode 100644 index 0000000000..eea1b7e463 --- /dev/null +++ b/tests/datasets/curl/image.content @@ -0,0 +1,81 @@ +�PNG + +���Bh[ �]\��|�;?�[����8�h �p&�&Xw^�v��ނ����<����W����oBk�%X<�W���M�z���g���������'OǦ�ȖʹK� ��.x�U +������O?�؎dA�,��ú����� �u� P�*8z�}y�W���ZfG��A h�`1pO)x��+�:1#1���>��'�����K�=�E�Ah��� �|��B��A �l�s�����o}����!�x�� �Q� n�z�u���kud@fd@Ȍ� ���=v�����i�U�#hc ��w��ڰ)dd&f@f0�D�]x~��7����o�,�# �x� ,e�_���7\;�ߥ#M��L�Ā����X�#c��_�����?�qDFa)�~��|���o�X��Ȭ����00 �A$� +��~����������’��� ?�S7"1X� + 0�` ƂE���͓w��/�!�%N� ,:B��t��VG���� �٤�e�����v}��}�_}�ɩZ����v�b��.���Vk!#C��ݟI� F' 3 |����t٘�c�`l�ݖ�0�X�L�f���XTG'f>�W���'��p�x�=:Ȣ��$�B�����M�J����0���'��1q$ˤ=���%� +�@?x�����}i���*Hk-A�3�$X ����wߡ р2�L�6��T�$�T�S �R �Af Td�G����?z��O�XX��W:M� �}�m׭�W��f�0���H��lb; 2���*���d�C��t��>��]�C��@c�?�g�� �W����#҆ �1ȆL\�$��jd=���rv��x�a ����3��'���G_y�� �M�`�3�\Љ� �o�m���1h�C�Li�GI�2��? >��<}��?}��w�X%D��� �N,�.�x��zH�� ����I|op�\h��>� +�����$� ��������}y���'��+K���a���T��_���cS"1R�(D���(!*P�� ��s��d)rÅ��6 Ǒ&�d�>��_�*�[��2�;�z����S�w���{�5�恠��p�U�M^,,@���o|�;O�,��l�{DM��D�\�� +����������}go13.\ A���@;�z�mj� b ز��+�~fsߵC�v_EA��,}�����¿��BWA[��P+�e�t'�1�.�=\'�[!S^�G�8[`xW9I*��)���Vu���^߽��o��ܐ?��Y �����|��^��@�Qd�!)�Y�����g�f!Ʃ � �<$b�X[�� "��^���=��������%��3��: BǑG�{?�������H��٦��!�#�6���5(��J�)��JT�f�ݓ�w�������{��Ҫ���5�, �����>�'��ӗY����2*elԼ刡s�8c+)�AJ�s�T�M{q��>� +4XT�%��DCEo������'���=��IJ���K�� X ��x߿z$(x�W�T����,"���Z�� + e��T�_���'�R!tiY���� �9�A!�"Ac-'W��=�5� +L��ZT33z^� /(BO!�g�� �>0y#���� ��!)J�灧Py蹧�y񂏁���G���J�� ��{T���9�Q,)�`�z�o<�����I�F��e9�!e�ALC$7!�fP +������Vt�v?t�HW�_=ҷq���K� ec_��8��Z�ku�׹V7�:�j�^3�:�j\��j�T+�R��\��j�T�P��j̈́!�!��& +8�$ĸ��>�/�kvK�e#!" 9EC�G����G�oR7 � *�X�B +*�T�b�J%*�T�R }� � + +Hn,Fw1�J72c�F+�nnL���_��Jc|��Y!�r�ZM)T +�� + �E��W��aO�Z��/��/����e��_����^���n�x>,1�u�D�����؃����+���׬����w_��u˯[7|���,�3��9 M�Ƨ���x4>��'�䤞��&��D4>��O�鲙��J�T�z� ���( D7�"��ζ�V��+y��ZF �3�� 0�T�R KEohп�2o� +�6o6n,\�1X��XF�s���Q1�ώ� X��D��G^���=�O�ӧM�€F@H˖a-�zġ�Z�k�[���Ѱ�{������"����i��  +�U*�[�^l�X�r��yC�fM�r����-�]%,��/X\�M~����;L���:3��� +�Y�'�.!�_� �x�����mO�������23�)֞bf��l@[5s��&�Ő�м0��`�Hsrd ���U��(\��p���k��������8ڬ�^~.!KB� ���������@��l<������ (6�� S\�� \׳jY�'� C �B� ֯+]{M�u�����q��_��'�x�Y +���_@���1`��54/6��J; +�� N��~�tA� s�O� +5�lX_�fK�u�u���`�o���E���R,d����?�?�y&6���x���>�I �k���3ل5��5G��NI��1�8 �J%���K7�X��ҵ�x#+Пs�!�� ����5�FS���~����L 3� P�<��#`�tj`��#� +=���Y���˵]�,Y��!���w��Lxp�M 5�F�)��� ������2q<�SͶ��FS��adE����{�=���\6k?Q|�K^� �z����=bN� 0I�s� +0�&�Yˋ���VE�6K�]��ZK�zhj55��{��}ok�V�nxIClj�^:a>D� To��9��1�)�}ÜN��h^ig^�Sԥ#�6{ ���g-�i�l W� P��������������Β� �,��|eߛ~�럁�2�>3W� �hV�u���-��/`X⾡�^3\�b1��e��s�oy0izgm�(�lYR�P�`td��7>k*Ӡ�����& +!�8 MqX�zdta��Ȅ���״�k�j�i���40.�Txct��Jݽ�i����}��ًܸg�XX ������u�۟�z��u�;���@ +jt;��C~�ߥ�zz�t�U#����=��v���j�*J���9 �?�* �Y�q[�4�e BN�ߜ��G���m�D�Y�f].���F�#��a� +d�" �5 (n������ߓ��?�{e0��7Btb�4�C$D����:Xߐk;@3S^�k����^�V�J���`��]P�����S�$�3q�P�7����v="r��D !0�B��;� F��R�y�����v͢Sd�S��MR�]7ލ��'���ǜ���y�l�1$F&D��� ��H�mf�5�Ev?#�A��_�e�=��.*"X� P��.6f��o��DB�[##X*�'������v&$D�Gqf�����6�����o]$G�`D��]3������S�F/��<�qGD�T)n���\� 0"�FDm �v� ����Q58��3?"X� ̌�]7܍�/?�� }��F����BDD D�^�ls � �Ǥ]&�M+]����~�ODX��`�Gb:@�����g;�[R�"2�ޢ5�00�:����f�J�" D����p�0"X��80�ūoF�8���5�۫�p!0���A{���k�"X#��S�P����+LJOM�jUI�b�"#��p� ���ba�ۏr�  ""�!��������!�tB�qN)0 rM�k���Ę>u*�m��D���_Zl��_�������^$B? �C�G/@��G�#�ߧ�W��A@������� +����JE�E)�j �������=�>a�#��@�� �Ⱥ��~����� }}=?�,���ꔏ�G������v����a��A���� c�Fl��=�k�P�U� \c|A�8�Kx�x���{�/��Σz�$d������l�9 `�v� "`HhP3"*���PCR���O��Q��OƗ@.��%:o�H�[���j#�� @�C�C��������5�|���x�{��� +<(��J +��E�����r�3 B�W�1|JCZ � + +�K�O��;@��������'#,rD��� �ozO��0�^zG߇�j����O6��$�#�����%’�] +K +K>����,��5�w_��� 1� *���咽�ʋ?`��I��K��Y.�0$F���� i7!zP��tN�����=Vm�nm��EX���uA`@��M=�<��G�a��^��C��Ń�=���������z +��;�6��~��s[_y�d���K�?��W�~�X�7� x*�f�D�����J��� +l%}��vxh��َ�}��sWa�Uū�tj�> �xժ��p��Ĵ��� +h <��W��_|��#?����F V�wM5d���!\2D�.vop��zs4~�OO�������"L�Ԇx�%7��.�Lv*jBL"���1D�q���ܽ�g߃��{\xÃ}_�֋���4h�K a�!��_}r�_<����}�+�u��C ֥��7�p��9�'��"� +]� +֯'Vn�ƫV����< p<-�-���Z�28��Wlx���/=�����w`|f���r� b��_�p��j?��[((F'��CzG@d�� qX�� tr�2N��l�&=XS�*�-�6&��u�?q�^ݹs�{�M~ v�lD�m�<��ן7Ơ�81�����Ɋ��S���t���G?�����8�VvuҖr�"��^0q`����ek��q.O�K.����\��餉�E��r��� �Y[�lYS˾i���0��������s�vE���ryo���Ƕ��H�) ��Q�fl�4�A!���=���|���O����5�%�ka�ٵ8��{�� h���c_��<�c8�����"�B�4GGGt���&�8�`c�f����i��1��#Ә�u��qQ�����x͖��\ ���ڟ?�ҾbwQ#E��F� + ��r�o\��Ϡ�<��������TY|M��4�&�Dq*� `��Mœ���qJ�&B@?��t̀4����W,���=�i�'���� VǑ� �7l��? +��I|��/��Ɍ&�"�ZH��ie� Q��I_����� ��u�74�u�͙�w �e��գ˾��IJ?Ty����8 +F�өdJDgmY��deˆ�b��9��Բ��v0�f�;�nkf�2��_m��iͮS���f�ቪ� ́��X9::��cըqJϤG�}��? � ��X,��� +mx�����Λc�ž�LdfB�͉�>>��3щJtlF���U�TM�#.�f&4H&�ĸ�ACv~"^ +�u��w�v�1R�iEaM�O-x�v�E��c*3�m?�l}ZO�!�a+=�����t��b�j�49^�8�˰���4�&��s*�k9�\ ֯����6o����R�̩���񩚞���>6��τ�+�DE���Su3Uӓu3]7u+Uk�{j�#U� 7�(ݻ�t���U� +\8{�j�9;�t94Su���S�CK�4�3c�\����z!<��T I�4�&b�`�����a��j�Kת�����n�Mw��l{�.�#�<��.5"XB>�Ǝ�v�T���>u��$0 �6&��L�*p��� hlL� +��N�a�D��uS��{ӻ���W��l�r'5�}j��wkm�+�ɣ� s�2�&��TP[Q��.O�������*���%�"XB�1&<�������N=} +d�K��k��-������:���EKX���᡽�}�£���cs!]�R��t��ʁw�S�7�93� M�` ��YŃ�<�]��Jt�)O�a@����:�z����¦k�}�BkD��%�����S߿#:�_��l� +�ud���]�� ;R�����h��L�t���������| +"X� �,Ar��5,Qr�'���,�8�3.����_��!R"|�C�����?��.���lE��w���#d"�� +2��9��L�deb�$S"7Y� +g[OMO�#X�BA�N�v��e-%`� :7̲����}�>m�E���b�ԧ�N��[�#�D ���[��\-�C����Eι�Y��e���/w�������˳��tEK��1���ZS��_�T� ��܊��W� ��Hζ�fd�+�hYB�s��g��摘Vv�>@��mJ;�f�ø�BR�ó�s�GH��gvrͥqM��I��L\��k�F�����܂��c[έ�lm�D�2��v��{�p�O��B9LJZ +,-��,�H�%�$�� 'tsa_b', + 'Content-Type' => 'image/png', + 'Cache-Control' => 'max-age=604800, must-revalidate', + 'Content-Length' => 24875, + ], file_get_contents(__DIR__ . '/../../datasets/curl/image.content')) + ]); + + $config = \Mockery::mock(IConfig::class); + $config->shouldReceive('get')->with('system', 'curl_range_bytes', 0)->once()->andReturn(null); + $config->shouldReceive('get')->with('system', 'verifyssl')->once(); + $config->shouldReceive('get')->with('system', 'proxy')->once(); + $config->shouldReceive('get')->with('system', 'ipv4_resolve', false)->once()->andReturnFalse(); + $config->shouldReceive('get')->with('system', 'blocklist', [])->once()->andReturn([]); + + $baseUrl = \Mockery::mock(BaseURL::class); + $baseUrl->shouldReceive('get')->andReturn('http://friendica.local'); + + $profiler = \Mockery::mock(Profiler::class); + $profiler->shouldReceive('startRecording')->andReturnTrue(); + $profiler->shouldReceive('stopRecording')->andReturnTrue(); + + $httpRequest = new HTTPRequest(new NullLogger(), $profiler, $config, $baseUrl); + + self::assertInstanceOf(IHTTPRequest::class, $httpRequest); + + $dice = \Mockery::mock(Dice::class); + $dice->shouldReceive('create')->with(IHTTPRequest::class)->andReturn($httpRequest)->once(); + $dice->shouldReceive('create')->with(BaseURL::class)->andReturn($baseUrl); + $dice->shouldReceive('create')->with(IConfig::class)->andReturn($config)->once(); + + DI::init($dice); + + print_r(Images::getInfoFromURL('https://pbs.twimg.com/profile_images/2365515285/9re7kx4xmc0eu9ppmado.png')); + } +}