use Friendica\Model\Photo;
use Friendica\Model\Post;
use Friendica\Model\Tag;
-use Friendica\Network\HTTPClientOptions;
+use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Object\Image;
use Friendica\Protocol\Activity;
use Friendica\Util\Images;
$text = DI::cache()->get($cache_key);
if (is_null($text)) {
- $curlResult = DI::httpClient()->head($match[1], [HTTPClientOptions::TIMEOUT => DI::config()->get('system', 'xrd_timeout')]);
+ $curlResult = DI::httpClient()->head($match[1], [HttpClientOptions::TIMEOUT => DI::config()->get('system', 'xrd_timeout')]);
if ($curlResult->isSuccess()) {
$mimetype = $curlResult->getHeader('Content-Type')[0] ?? '';
} else {
return $text;
}
- $curlResult = DI::httpClient()->head($match[1], [HTTPClientOptions::TIMEOUT => DI::config()->get('system', 'xrd_timeout')]);
+ $curlResult = DI::httpClient()->head($match[1], [HttpClientOptions::TIMEOUT => DI::config()->get('system', 'xrd_timeout')]);
if ($curlResult->isSuccess()) {
$mimetype = $curlResult->getHeader('Content-Type')[0] ?? '';
} else {
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Network\HTTPException;
-use Friendica\Network\HTTPClientOptions;
+use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Object\Search\ContactResult;
use Friendica\Object\Search\ResultList;
use Friendica\Util\Network;
$return = Contact::searchByName($search, $mode);
} else {
$p = $page > 1 ? 'p=' . $page : '';
- $curlResult = DI::httpClient()->get(self::getGlobalDirectory() . '/search/people?' . $p . '&q=' . urlencode($search), [HTTPClientOptions::ACCEPT_CONTENT => ['application/json']]);
+ $curlResult = DI::httpClient()->get(self::getGlobalDirectory() . '/search/people?' . $p . '&q=' . urlencode($search), [HttpClientOptions::ACCEPT_CONTENT => ['application/json']]);
if ($curlResult->isSuccess()) {
$searchResult = json_decode($curlResult->getBody(), true);
if (!empty($searchResult['profiles'])) {
//
/**
- * @return Network\IHTTPClient
+ * @return Network\HTTPClient\Capability\ICanRequestPerHttp
*/
public static function httpClient()
{
- return self::$dice->create(Network\IHTTPClient::class);
+ return self::$dice->create(Network\HTTPClient\Capability\ICanRequestPerHttp::class);
}
//
+++ /dev/null
-<?php
-
-namespace Friendica\Factory;
-
-use Friendica\App;
-use Friendica\BaseFactory;
-use Friendica\Core\Config\Capability\IManageConfigValues;
-use Friendica\Network\HTTPClient;
-use Friendica\Network\IHTTPClient;
-use Friendica\Util\Profiler;
-use Friendica\Util\Strings;
-use GuzzleHttp\Client;
-use GuzzleHttp\HandlerStack;
-use GuzzleHttp\RequestOptions;
-use mattwright\URLResolver;
-use Psr\Http\Message\RequestInterface;
-use Psr\Http\Message\ResponseInterface;
-use Psr\Http\Message\UriInterface;
-use Psr\Log\LoggerInterface;
-
-require_once __DIR__ . '/../../static/dbstructure.config.php';
-
-class HTTPClientFactory extends BaseFactory
-{
- /** @var IManageConfigValues */
- private $config;
- /** @var Profiler */
- private $profiler;
- /** @var App\BaseURL */
- private $baseUrl;
-
- public function __construct(LoggerInterface $logger, IManageConfigValues $config, Profiler $profiler, App\BaseURL $baseUrl)
- {
- parent::__construct($logger);
- $this->config = $config;
- $this->profiler = $profiler;
- $this->baseUrl = $baseUrl;
- }
-
- /**
- * Creates a IHTTPClient for communications with HTTP endpoints
- *
- * @param HandlerStack|null $handlerStack (optional) A handler replacement (just usefull at test environments)
- *
- * @return IHTTPClient
- */
- public function createClient(HandlerStack $handlerStack = null): 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, 'method' => $request->getMethod()]);
- };
-
- $userAgent = FRIENDICA_PLATFORM . " '" .
- FRIENDICA_CODENAME . "' " .
- FRIENDICA_VERSION . '-' .
- DB_UPDATE_VERSION . '; ' .
- $this->baseUrl->get();
-
- $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 it can be overridden
- RequestOptions::VERIFY => (bool)$this->config->get('system', 'verifyssl'),
- RequestOptions::PROXY => $proxy,
- RequestOptions::HEADERS => [
- 'User-Agent' => $userAgent,
- ],
- 'handler' => $handlerStack ?? HandlerStack::create(),
- ]);
-
- $resolver = new URLResolver();
- $resolver->setUserAgent($userAgent);
- $resolver->setMaxRedirects(10);
- $resolver->setRequestTimeout(10);
- // if the file is too large then exit
- $resolver->setMaxResponseDataSize(1000000);
- // Designate a temporary file that will store cookies during the session.
- // Some websites test the browser for cookie support, so this enhances results.
- $resolver->setCookieJar(get_temppath() .'/resolver-cookie-' . Strings::getRandomName(10));
-
- return new HTTPClient($logger, $this->profiler, $guzzle, $resolver);
- }
-}
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Module\Register;
-use Friendica\Network\HTTPClientOptions;
-use Friendica\Network\IHTTPResult;
+use Friendica\Network\HTTPClient\Client\HttpClientOptions;
+use Friendica\Network\HTTPClient\Capability\ICanHandleHttpResponses;
use Friendica\Protocol\Relay;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network;
// When a nodeinfo is present, we don't need to dig further
$xrd_timeout = DI::config()->get('system', 'xrd_timeout');
- $curlResult = DI::httpClient()->get($url . '/.well-known/nodeinfo', [HTTPClientOptions::TIMEOUT => $xrd_timeout]);
+ $curlResult = DI::httpClient()->get($url . '/.well-known/nodeinfo', [HttpClientOptions::TIMEOUT => $xrd_timeout]);
if ($curlResult->isTimeout()) {
self::setFailure($url);
return false;
// On a redirect follow the new host but mark the old one as failure
if ($curlResult->isSuccess() && (parse_url($url, PHP_URL_HOST) != parse_url($curlResult->getRedirectUrl(), PHP_URL_HOST))) {
- $curlResult = DI::httpClient()->get($url, [HTTPClientOptions::TIMEOUT => $xrd_timeout]);
+ $curlResult = DI::httpClient()->get($url, [HttpClientOptions::TIMEOUT => $xrd_timeout]);
if (parse_url($url, PHP_URL_HOST) != parse_url($curlResult->getRedirectUrl(), PHP_URL_HOST)) {
Logger::info('Found redirect. Mark old entry as failure', ['old' => $url, 'new' => $curlResult->getRedirectUrl()]);
self::setFailure($url);
$basedata = ['detection-method' => self::DETECT_MANUAL];
}
- $curlResult = DI::httpClient()->get($baseurl, [HTTPClientOptions::TIMEOUT => $xrd_timeout]);
+ $curlResult = DI::httpClient()->get($baseurl, [HttpClientOptions::TIMEOUT => $xrd_timeout]);
if ($curlResult->isSuccess()) {
if ((parse_url($baseurl, PHP_URL_HOST) != parse_url($curlResult->getRedirectUrl(), PHP_URL_HOST))) {
Logger::info('Found redirect. Mark old entry as failure', ['old' => $url, 'new' => $curlResult->getRedirectUrl()]);
// When the base path doesn't seem to contain a social network we try the complete path.
// Most detectable system have to be installed in the root directory.
// We checked the base to avoid false positives.
- $curlResult = DI::httpClient()->get($url, [HTTPClientOptions::TIMEOUT => $xrd_timeout]);
+ $curlResult = DI::httpClient()->get($url, [HttpClientOptions::TIMEOUT => $xrd_timeout]);
if ($curlResult->isSuccess()) {
$urldata = self::analyseRootHeader($curlResult, $serverdata);
$urldata = self::analyseRootBody($curlResult, $urldata, $url);
/**
* Detect server type by using the nodeinfo data
*
- * @param string $url address of the server
- * @param IHTTPResult $httpResult
+ * @param string $url address of the server
+ * @param ICanHandleHttpResponses $httpResult
*
* @return array Server data
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
- private static function fetchNodeinfo(string $url, IHTTPResult $httpResult)
+ private static function fetchNodeinfo(string $url, ICanHandleHttpResponses $httpResult)
{
if (!$httpResult->isSuccess()) {
return [];
private static function validHostMeta(string $url)
{
$xrd_timeout = DI::config()->get('system', 'xrd_timeout');
- $curlResult = DI::httpClient()->get($url . '/.well-known/host-meta', [HTTPClientOptions::TIMEOUT => $xrd_timeout]);
+ $curlResult = DI::httpClient()->get($url . '/.well-known/host-meta', [HttpClientOptions::TIMEOUT => $xrd_timeout]);
if (!$curlResult->isSuccess()) {
return false;
}
if (!empty($accesstoken)) {
$api = 'https://instances.social/api/1.0/instances/list?count=0';
- $curlResult = DI::httpClient()->get($api, [HTTPClientOptions::HEADERS => ['Authorization' => ['Bearer ' . $accesstoken]]]);
+ $curlResult = DI::httpClient()->get($api, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . $accesstoken]]]);
if ($curlResult->isSuccess()) {
$servers = json_decode($curlResult->getBody(), true);
use Friendica\Database\Database;
use Friendica\Database\DBA;
use Friendica\DI;
-use Friendica\Network\HTTPClientOptions;
+use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Util\Proxy;
/**
{
$timeout = DI::config()->get('system', 'xrd_timeout');
- $curlResult = DI::httpClient()->head($url, [HTTPClientOptions::TIMEOUT => $timeout]);
+ $curlResult = DI::httpClient()->head($url, [HttpClientOptions::TIMEOUT => $timeout]);
if ($curlResult->isSuccess()) {
if (empty($media['mimetype'])) {
return $curlResult->getHeader('Content-Type')[0] ?? '';
use Friendica\Model\Item;
use Friendica\Model\Photo;
use Friendica\Model\Post;
-use Friendica\Network\HTTPClientOptions;
+use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Util\Images;
use Friendica\Util\Network;
use Friendica\Util\ParseUrl;
// Fetch the mimetype or size if missing.
if (empty($media['mimetype']) || empty($media['size'])) {
$timeout = DI::config()->get('system', 'xrd_timeout');
- $curlResult = DI::httpClient()->head($media['url'], [HTTPClientOptions::TIMEOUT => $timeout]);
+ $curlResult = DI::httpClient()->head($media['url'], [HttpClientOptions::TIMEOUT => $timeout]);
if ($curlResult->isSuccess()) {
if (empty($media['mimetype'])) {
$media['mimetype'] = $curlResult->getHeader('Content-Type')[0] ?? '';
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\User;
-use Friendica\Network\HTTPClientOptions;
+use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Util\HTTPSignature;
use Friendica\Util\Strings;
);
// Try to get an authentication token from the other instance.
- $curlResult = DI::httpClient()->get($basepath . '/owa', [HTTPClientOptions::HEADERS => $header]);
+ $curlResult = DI::httpClient()->get($basepath . '/owa', [HttpClientOptions::HEADERS => $header]);
if ($curlResult->isSuccess()) {
$j = json_decode($curlResult->getBody(), true);
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Network;
-
-use Friendica\Core\Logger;
-use Friendica\Core\System;
-use Friendica\Network\HTTPException\InternalServerErrorException;
-use Friendica\Util\Network;
-
-/**
- * A content class for Curl call results
- */
-class CurlResult implements IHTTPResult
-{
- /**
- * @var int HTTP return code or 0 if timeout or failure
- */
- private $returnCode;
-
- /**
- * @var string the content type of the Curl call
- */
- private $contentType;
-
- /**
- * @var string the HTTP headers of the Curl call
- */
- private $header;
-
- /**
- * @var array the HTTP headers of the Curl call
- */
- private $header_fields;
-
- /**
- * @var boolean true (if HTTP 2xx result) or false
- */
- private $isSuccess;
-
- /**
- * @var string the URL which was called
- */
- private $url;
-
- /**
- * @var string in case of redirect, content was finally retrieved from this URL
- */
- private $redirectUrl;
-
- /**
- * @var string fetched content
- */
- private $body;
-
- /**
- * @var array some informations about the fetched data
- */
- private $info;
-
- /**
- * @var boolean true if the URL has a redirect
- */
- private $isRedirectUrl;
-
- /**
- * @var boolean true if the curl request timed out
- */
- private $isTimeout;
-
- /**
- * @var int the error number or 0 (zero) if no error
- */
- private $errorNumber;
-
- /**
- * @var string the error message or '' (the empty string) if no
- */
- private $error;
-
- /**
- * Creates an errored CURL response
- *
- * @param string $url optional URL
- *
- * @return IHTTPResult a CURL with error response
- * @throws InternalServerErrorException
- */
- public static function createErrorCurl($url = '')
- {
- return new CurlResult($url, '', ['http_code' => 0]);
- }
-
- /**
- * Curl constructor.
- * @param string $url the URL which was called
- * @param string $result the result of the curl execution
- * @param array $info an additional info array
- * @param int $errorNumber the error number or 0 (zero) if no error
- * @param string $error the error message or '' (the empty string) if no
- *
- * @throws InternalServerErrorException when HTTP code of the CURL response is missing
- */
- public function __construct($url, $result, $info, $errorNumber = 0, $error = '')
- {
- if (!array_key_exists('http_code', $info)) {
- throw new InternalServerErrorException('CURL response doesn\'t contains a response HTTP code');
- }
-
- $this->returnCode = $info['http_code'];
- $this->url = $url;
- $this->info = $info;
- $this->errorNumber = $errorNumber;
- $this->error = $error;
-
- Logger::debug('construct', ['url' => $url, 'returncode' => $this->returnCode, 'result' => $result]);
-
- $this->parseBodyHeader($result);
- $this->checkSuccess();
- $this->checkRedirect();
- $this->checkInfo();
- }
-
- private function parseBodyHeader($result)
- {
- // Pull out multiple headers, e.g. proxy and continuation headers
- // allow for HTTP/2.x without fixing code
-
- $header = '';
- $base = $result;
- while (preg_match('/^HTTP\/.+? \d+/', $base)) {
- $chunk = substr($base, 0, strpos($base, "\r\n\r\n") + 4);
- $header .= $chunk;
- $base = substr($base, strlen($chunk));
- }
-
- $this->body = substr($result, strlen($header));
- $this->header = $header;
- $this->header_fields = []; // Is filled on demand
- }
-
- private function checkSuccess()
- {
- $this->isSuccess = ($this->returnCode >= 200 && $this->returnCode <= 299) || $this->errorNumber == 0;
-
- // Everything higher or equal 400 is not a success
- if ($this->returnCode >= 400) {
- $this->isSuccess = false;
- }
-
- if (!$this->isSuccess) {
- Logger::debug('debug', ['info' => $this->info]);
- }
-
- if (!$this->isSuccess && $this->errorNumber == CURLE_OPERATION_TIMEDOUT) {
- $this->isTimeout = true;
- } else {
- $this->isTimeout = false;
- }
- }
-
- private function checkRedirect()
- {
- if (!array_key_exists('url', $this->info)) {
- $this->redirectUrl = '';
- } else {
- $this->redirectUrl = $this->info['url'];
- }
-
- if ($this->returnCode == 301 || $this->returnCode == 302 || $this->returnCode == 303 || $this->returnCode== 307) {
- $redirect_parts = parse_url($this->info['redirect_url'] ?? '');
- if (empty($redirect_parts)) {
- $redirect_parts = [];
- }
-
- if (preg_match('/(Location:|URI:)(.*?)\n/i', $this->header, $matches)) {
- $redirect_parts2 = parse_url(trim(array_pop($matches)));
- if (!empty($redirect_parts2)) {
- $redirect_parts = array_merge($redirect_parts, $redirect_parts2);
- }
- }
-
- $parts = parse_url($this->info['url'] ?? '');
- if (empty($parts)) {
- $parts = [];
- }
-
- /// @todo Checking the corresponding RFC which parts of a redirect can be ommitted.
- $components = ['scheme', 'host', 'path', 'query', 'fragment'];
- foreach ($components as $component) {
- if (empty($redirect_parts[$component]) && !empty($parts[$component])) {
- $redirect_parts[$component] = $parts[$component];
- }
- }
-
- $this->redirectUrl = Network::unparseURL($redirect_parts);
-
- $this->isRedirectUrl = true;
- } else {
- $this->isRedirectUrl = false;
- }
- }
-
- private function checkInfo()
- {
- if (isset($this->info['content_type'])) {
- $this->contentType = $this->info['content_type'];
- } else {
- $this->contentType = '';
- }
- }
-
- /** {@inheritDoc} */
- public function getReturnCode()
- {
- return $this->returnCode;
- }
-
- /** {@inheritDoc} */
- public function getContentType()
- {
- return $this->contentType;
- }
-
- /** {@inheritDoc} */
- public function getHeader($header)
- {
- if (empty($header)) {
- return [];
- }
-
- $header = strtolower(trim($header));
-
- $headers = $this->getHeaderArray();
-
- if (isset($headers[$header])) {
- return $headers[$header];
- }
-
- return [];
- }
-
- /** {@inheritDoc} */
- public function getHeaders()
- {
- return $this->getHeaderArray();
- }
-
- /** {@inheritDoc} */
- public function inHeader(string $field)
- {
- $field = strtolower(trim($field));
-
- $headers = $this->getHeaderArray();
-
- return array_key_exists($field, $headers);
- }
-
- /** {@inheritDoc} */
- public function getHeaderArray()
- {
- if (!empty($this->header_fields)) {
- return $this->header_fields;
- }
-
- $this->header_fields = [];
-
- $lines = explode("\n", trim($this->header));
- foreach ($lines as $line) {
- $parts = explode(':', $line);
- $headerfield = strtolower(trim(array_shift($parts)));
- $headerdata = trim(implode(':', $parts));
- if (empty($this->header_fields[$headerfield])) {
- $this->header_fields[$headerfield] = [$headerdata];
- } elseif (!in_array($headerdata, $this->header_fields[$headerfield])) {
- $this->header_fields[$headerfield][] = $headerdata;
- }
- }
-
- return $this->header_fields;
- }
-
- /** {@inheritDoc} */
- public function isSuccess()
- {
- return $this->isSuccess;
- }
-
- /** {@inheritDoc} */
- public function getUrl()
- {
- return $this->url;
- }
-
- /** {@inheritDoc} */
- public function getRedirectUrl()
- {
- return $this->redirectUrl;
- }
-
- /** {@inheritDoc} */
- public function getBody()
- {
- return $this->body;
- }
-
- /** {@inheritDoc} */
- public function isRedirectUrl()
- {
- return $this->isRedirectUrl;
- }
-
- /** {@inheritDoc} */
- public function getErrorNumber()
- {
- return $this->errorNumber;
- }
-
- /** {@inheritDoc} */
- public function getError()
- {
- return $this->error;
- }
-
- /** {@inheritDoc} */
- public function isTimeout()
- {
- return $this->isTimeout;
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2020, Friendica
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Network;
-
-use Friendica\Core\Logger;
-use Friendica\Core\System;
-use Friendica\Network\HTTPException\NotImplementedException;
-use GuzzleHttp\Psr7\Response;
-use Psr\Http\Message\ResponseInterface;
-
-/**
- * A content wrapper class for Guzzle call results
- */
-class GuzzleResponse extends Response implements IHTTPResult, ResponseInterface
-{
- /** @var string The URL */
- private $url;
- /** @var boolean */
- private $isTimeout;
- /** @var boolean */
- private $isSuccess;
- /**
- * @var int the error number or 0 (zero) if no error
- */
- private $errorNumber;
-
- /**
- * @var string the error message or '' (the empty string) if no
- */
- private $error;
-
- public function __construct(ResponseInterface $response, string $url, $errorNumber = 0, $error = '')
- {
- parent::__construct($response->getStatusCode(), $response->getHeaders(), $response->getBody(), $response->getProtocolVersion(), $response->getReasonPhrase());
- $this->url = $url;
- $this->error = $error;
- $this->errorNumber = $errorNumber;
-
- $this->checkSuccess();
- }
-
- private function checkSuccess()
- {
- $this->isSuccess = ($this->getStatusCode() >= 200 && $this->getStatusCode() <= 299) || $this->errorNumber == 0;
-
- // Everything higher or equal 400 is not a success
- if ($this->getReturnCode() >= 400) {
- $this->isSuccess = false;
- }
-
- if (!$this->isSuccess) {
- Logger::debug('debug', ['info' => $this->getHeaders()]);
- }
-
- if (!$this->isSuccess && $this->errorNumber == CURLE_OPERATION_TIMEDOUT) {
- $this->isTimeout = true;
- } else {
- $this->isTimeout = false;
- }
- }
-
- /** {@inheritDoc} */
- public function getReturnCode()
- {
- return $this->getStatusCode();
- }
-
- /** {@inheritDoc} */
- public function getContentType()
- {
- $contentTypes = $this->getHeader('Content-Type') ?? [];
- return array_pop($contentTypes) ?? '';
- }
-
- /** {@inheritDoc} */
- public function inHeader(string $field)
- {
- return $this->hasHeader($field);
- }
-
- /** {@inheritDoc} */
- public function getHeaderArray()
- {
- return $this->getHeaders();
- }
-
- /** {@inheritDoc} */
- public function isSuccess()
- {
- return $this->isSuccess;
- }
-
- /** {@inheritDoc} */
- public function getUrl()
- {
- return $this->url;
- }
-
- /** {@inheritDoc} */
- public function getRedirectUrl()
- {
- return $this->url;
- }
-
- /** {@inheritDoc} */
- public function isRedirectUrl()
- {
- throw new NotImplementedException();
- }
-
- /** {@inheritDoc} */
- public function getErrorNumber()
- {
- return $this->errorNumber;
- }
-
- /** {@inheritDoc} */
- public function getError()
- {
- return $this->error;
- }
-
- /** {@inheritDoc} */
- public function isTimeout()
- {
- return $this->isTimeout;
- }
-
- /// @todo - fix mismatching use of "getBody()" as string here and parent "getBody()" as streaminterface
- public function getBody(): string
- {
- return (string) parent::getBody();
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU APGL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Network;
-
-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 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 IHTTPClient
-{
- /** @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 = []): IHTTPResult
- {
- $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 = []): IHTTPResult
- {
- return $this->request('head', $url, $opts);
- }
-
- /**
- * {@inheritDoc}
- */
- public function get(string $url, array $opts = []): IHTTPResult
- {
- return $this->request('get', $url, $opts);
- }
-
- /**
- * {@inheritDoc}
- */
- public function post(string $url, $params, array $headers = [], int $timeout = 0): IHTTPResult
- {
- $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)
- {
- $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 = '')
- {
- $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
- ]
- );
- }
-}
--- /dev/null
+<?php
+
+namespace Friendica\Network\HTTPClient\Capability;
+
+use Psr\Http\Message\MessageInterface;
+
+/**
+ * Temporary class to map Friendica used variables based on PSR-7 HTTPResponse
+ */
+interface ICanHandleHttpResponses
+{
+ /**
+ * Gets the Return Code
+ *
+ * @return string The Return Code
+ */
+ public function getReturnCode();
+
+ /**
+ * Returns the Content Type
+ *
+ * @return string the Content Type
+ */
+ public function getContentType();
+
+ /**
+ * Returns the headers
+ *
+ * @param string $header optional header field. Return all fields if empty
+ *
+ * @return string[] the headers or the specified content of the header variable
+ *@see MessageInterface::getHeader()
+ *
+ */
+ public function getHeader(string $header);
+
+ /**
+ * Returns all headers
+ * @see MessageInterface::getHeaders()
+ *
+ * @return string[][]
+ */
+ public function getHeaders();
+
+ /**
+ * Check if a specified header exists
+ * @see MessageInterface::hasHeader()
+ *
+ * @param string $field header field
+ *
+ * @return boolean "true" if header exists
+ */
+ public function inHeader(string $field);
+
+ /**
+ * Returns the headers as an associated array
+ * @see MessageInterface::getHeaders()
+ * @deprecated
+ *
+ * @return string[][] associated header array
+ */
+ public function getHeaderArray();
+
+ /**
+ * @return bool
+ */
+ public function isSuccess();
+
+ /**
+ * @return string
+ */
+ public function getUrl();
+
+ /**
+ * @return string
+ */
+ public function getRedirectUrl();
+
+ /**
+ * @see MessageInterface::getBody()
+ *
+ * @return string
+ */
+ public function getBody();
+
+ /**
+ * @return boolean
+ */
+ public function isRedirectUrl();
+
+ /**
+ * @return integer
+ */
+ public function getErrorNumber();
+
+ /**
+ * @return string
+ */
+ public function getError();
+
+ /**
+ * @return boolean
+ */
+ public function isTimeout();
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU APGL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+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;
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU APGL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+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
+ ]
+ );
+ }
+}
--- /dev/null
+<?php
+
+namespace Friendica\Network\HTTPClient\Client;
+
+use GuzzleHttp\RequestOptions;
+
+/**
+ * This class contains a list of possible HTTPClient request options.
+ */
+class HttpClientOptions
+{
+ /**
+ * accept_content: (array) supply Accept: header with 'accept_content' as the value
+ */
+ const ACCEPT_CONTENT = 'accept_content';
+ /**
+ * timeout: (int) out in seconds, default system config value or 60 seconds
+ */
+ const TIMEOUT = RequestOptions::TIMEOUT;
+ /**
+ * cookiejar: (string) path to cookie jar file
+ */
+ const COOKIEJAR = 'cookiejar';
+ /**
+ * headers: (array) header array
+ */
+ const HEADERS = RequestOptions::HEADERS;
+ /**
+ * header: (array) header array (legacy version)
+ */
+ const LEGACY_HEADER = 'header';
+ /**
+ * content_length: (int) maximum File content length
+ */
+ const CONTENT_LENGTH = 'content_length';
+ /**
+ * body: (mixed) Setting the body for sending data
+ */
+ const BODY = RequestOptions::BODY;
+ /**
+ * auth: (array) Authentication settings for specific requests
+ */
+ const AUTH = RequestOptions::AUTH;
+}
--- /dev/null
+<?php
+
+namespace Friendica\Network\HTTPClient\Factory;
+
+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\Util\Profiler;
+use Friendica\Util\Strings;
+use GuzzleHttp;
+use GuzzleHttp\HandlerStack;
+use GuzzleHttp\RequestOptions;
+use mattwright\URLResolver;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\UriInterface;
+use Psr\Log\LoggerInterface;
+
+require_once __DIR__ . '/../../../../static/dbstructure.config.php';
+
+class HttpClient extends BaseFactory
+{
+ /** @var IManageConfigValues */
+ private $config;
+ /** @var Profiler */
+ private $profiler;
+ /** @var App\BaseURL */
+ private $baseUrl;
+
+ public function __construct(LoggerInterface $logger, IManageConfigValues $config, Profiler $profiler, App\BaseURL $baseUrl)
+ {
+ parent::__construct($logger);
+ $this->config = $config;
+ $this->profiler = $profiler;
+ $this->baseUrl = $baseUrl;
+ }
+
+ /**
+ * Creates a IHTTPClient for communications with HTTP endpoints
+ *
+ * @param HandlerStack|null $handlerStack (optional) A handler replacement (just usefull at test environments)
+ *
+ * @return ICanRequestPerHttp
+ */
+ public function createClient(HandlerStack $handlerStack = null): ICanRequestPerHttp
+ {
+ $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, 'method' => $request->getMethod()]);
+ };
+
+ $userAgent = FRIENDICA_PLATFORM . " '" .
+ FRIENDICA_CODENAME . "' " .
+ FRIENDICA_VERSION . '-' .
+ DB_UPDATE_VERSION . '; ' .
+ $this->baseUrl->get();
+
+ $guzzle = new GuzzleHttp\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 it can be overridden
+ RequestOptions::VERIFY => (bool)$this->config->get('system', 'verifyssl'),
+ RequestOptions::PROXY => $proxy,
+ RequestOptions::HEADERS => [
+ 'User-Agent' => $userAgent,
+ ],
+ 'handler' => $handlerStack ?? HandlerStack::create(),
+ ]);
+
+ $resolver = new URLResolver();
+ $resolver->setUserAgent($userAgent);
+ $resolver->setMaxRedirects(10);
+ $resolver->setRequestTimeout(10);
+ // if the file is too large then exit
+ $resolver->setMaxResponseDataSize(1000000);
+ // Designate a temporary file that will store cookies during the session.
+ // 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);
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Network\HTTPClient\Response;
+
+use Friendica\Core\Logger;
+use Friendica\Network\HTTPClient\Capability\ICanHandleHttpResponses;
+use Friendica\Network\HTTPException\InternalServerErrorException;
+use Friendica\Util\Network;
+
+/**
+ * A content class for Curl call results
+ */
+class CurlResult implements ICanHandleHttpResponses
+{
+ /**
+ * @var int HTTP return code or 0 if timeout or failure
+ */
+ private $returnCode;
+
+ /**
+ * @var string the content type of the Curl call
+ */
+ private $contentType;
+
+ /**
+ * @var string the HTTP headers of the Curl call
+ */
+ private $header;
+
+ /**
+ * @var array the HTTP headers of the Curl call
+ */
+ private $header_fields;
+
+ /**
+ * @var boolean true (if HTTP 2xx result) or false
+ */
+ private $isSuccess;
+
+ /**
+ * @var string the URL which was called
+ */
+ private $url;
+
+ /**
+ * @var string in case of redirect, content was finally retrieved from this URL
+ */
+ private $redirectUrl;
+
+ /**
+ * @var string fetched content
+ */
+ private $body;
+
+ /**
+ * @var array some informations about the fetched data
+ */
+ private $info;
+
+ /**
+ * @var boolean true if the URL has a redirect
+ */
+ private $isRedirectUrl;
+
+ /**
+ * @var boolean true if the curl request timed out
+ */
+ private $isTimeout;
+
+ /**
+ * @var int the error number or 0 (zero) if no error
+ */
+ private $errorNumber;
+
+ /**
+ * @var string the error message or '' (the empty string) if no
+ */
+ private $error;
+
+ /**
+ * Creates an errored CURL response
+ *
+ * @param string $url optional URL
+ *
+ * @return ICanHandleHttpResponses a CURL with error response
+ * @throws InternalServerErrorException
+ */
+ public static function createErrorCurl(string $url = '')
+ {
+ return new CurlResult($url, '', ['http_code' => 0]);
+ }
+
+ /**
+ * Curl constructor.
+ *
+ * @param string $url the URL which was called
+ * @param string $result the result of the curl execution
+ * @param array $info an additional info array
+ * @param int $errorNumber the error number or 0 (zero) if no error
+ * @param string $error the error message or '' (the empty string) if no
+ *
+ * @throws InternalServerErrorException when HTTP code of the CURL response is missing
+ */
+ public function __construct(string $url, string $result, array $info, int $errorNumber = 0, string $error = '')
+ {
+ if (!array_key_exists('http_code', $info)) {
+ throw new InternalServerErrorException('CURL response doesn\'t contains a response HTTP code');
+ }
+
+ $this->returnCode = $info['http_code'];
+ $this->url = $url;
+ $this->info = $info;
+ $this->errorNumber = $errorNumber;
+ $this->error = $error;
+
+ Logger::debug('construct', ['url' => $url, 'returncode' => $this->returnCode, 'result' => $result]);
+
+ $this->parseBodyHeader($result);
+ $this->checkSuccess();
+ $this->checkRedirect();
+ $this->checkInfo();
+ }
+
+ private function parseBodyHeader($result)
+ {
+ // Pull out multiple headers, e.g. proxy and continuation headers
+ // allow for HTTP/2.x without fixing code
+
+ $header = '';
+ $base = $result;
+ while (preg_match('/^HTTP\/.+? \d+/', $base)) {
+ $chunk = substr($base, 0, strpos($base, "\r\n\r\n") + 4);
+ $header .= $chunk;
+ $base = substr($base, strlen($chunk));
+ }
+
+ $this->body = substr($result, strlen($header));
+ $this->header = $header;
+ $this->header_fields = []; // Is filled on demand
+ }
+
+ private function checkSuccess()
+ {
+ $this->isSuccess = ($this->returnCode >= 200 && $this->returnCode <= 299) || $this->errorNumber == 0;
+
+ // Everything higher or equal 400 is not a success
+ if ($this->returnCode >= 400) {
+ $this->isSuccess = false;
+ }
+
+ if (!$this->isSuccess) {
+ Logger::debug('debug', ['info' => $this->info]);
+ }
+
+ if (!$this->isSuccess && $this->errorNumber == CURLE_OPERATION_TIMEDOUT) {
+ $this->isTimeout = true;
+ } else {
+ $this->isTimeout = false;
+ }
+ }
+
+ private function checkRedirect()
+ {
+ if (!array_key_exists('url', $this->info)) {
+ $this->redirectUrl = '';
+ } else {
+ $this->redirectUrl = $this->info['url'];
+ }
+
+ if ($this->returnCode == 301 || $this->returnCode == 302 || $this->returnCode == 303 || $this->returnCode == 307) {
+ $redirect_parts = parse_url($this->info['redirect_url'] ?? '');
+ if (empty($redirect_parts)) {
+ $redirect_parts = [];
+ }
+
+ if (preg_match('/(Location:|URI:)(.*?)\n/i', $this->header, $matches)) {
+ $redirect_parts2 = parse_url(trim(array_pop($matches)));
+ if (!empty($redirect_parts2)) {
+ $redirect_parts = array_merge($redirect_parts, $redirect_parts2);
+ }
+ }
+
+ $parts = parse_url($this->info['url'] ?? '');
+ if (empty($parts)) {
+ $parts = [];
+ }
+
+ /// @todo Checking the corresponding RFC which parts of a redirect can be ommitted.
+ $components = ['scheme', 'host', 'path', 'query', 'fragment'];
+ foreach ($components as $component) {
+ if (empty($redirect_parts[$component]) && !empty($parts[$component])) {
+ $redirect_parts[$component] = $parts[$component];
+ }
+ }
+
+ $this->redirectUrl = Network::unparseURL($redirect_parts);
+
+ $this->isRedirectUrl = true;
+ } else {
+ $this->isRedirectUrl = false;
+ }
+ }
+
+ private function checkInfo()
+ {
+ if (isset($this->info['content_type'])) {
+ $this->contentType = $this->info['content_type'];
+ } else {
+ $this->contentType = '';
+ }
+ }
+
+ /** {@inheritDoc} */
+ public function getReturnCode(): string
+ {
+ return $this->returnCode;
+ }
+
+ /** {@inheritDoc} */
+ public function getContentType(): string
+ {
+ return $this->contentType;
+ }
+
+ /** {@inheritDoc} */
+ public function getHeader(string $header): array
+ {
+ if (empty($header)) {
+ return [];
+ }
+
+ $header = strtolower(trim($header));
+
+ $headers = $this->getHeaderArray();
+
+ if (isset($headers[$header])) {
+ return $headers[$header];
+ }
+
+ return [];
+ }
+
+ /** {@inheritDoc} */
+ public function getHeaders(): array
+ {
+ return $this->getHeaderArray();
+ }
+
+ /** {@inheritDoc} */
+ public function inHeader(string $field): bool
+ {
+ $field = strtolower(trim($field));
+
+ $headers = $this->getHeaderArray();
+
+ return array_key_exists($field, $headers);
+ }
+
+ /** {@inheritDoc} */
+ public function getHeaderArray(): array
+ {
+ if (!empty($this->header_fields)) {
+ return $this->header_fields;
+ }
+
+ $this->header_fields = [];
+
+ $lines = explode("\n", trim($this->header));
+ foreach ($lines as $line) {
+ $parts = explode(':', $line);
+ $headerfield = strtolower(trim(array_shift($parts)));
+ $headerdata = trim(implode(':', $parts));
+ if (empty($this->header_fields[$headerfield])) {
+ $this->header_fields[$headerfield] = [$headerdata];
+ } elseif (!in_array($headerdata, $this->header_fields[$headerfield])) {
+ $this->header_fields[$headerfield][] = $headerdata;
+ }
+ }
+
+ return $this->header_fields;
+ }
+
+ /** {@inheritDoc} */
+ public function isSuccess(): bool
+ {
+ return $this->isSuccess;
+ }
+
+ /** {@inheritDoc} */
+ public function getUrl(): string
+ {
+ return $this->url;
+ }
+
+ /** {@inheritDoc} */
+ public function getRedirectUrl(): string
+ {
+ return $this->redirectUrl;
+ }
+
+ /** {@inheritDoc} */
+ public function getBody(): string
+ {
+ return $this->body;
+ }
+
+ /** {@inheritDoc} */
+ public function isRedirectUrl(): bool
+ {
+ return $this->isRedirectUrl;
+ }
+
+ /** {@inheritDoc} */
+ public function getErrorNumber(): int
+ {
+ return $this->errorNumber;
+ }
+
+ /** {@inheritDoc} */
+ public function getError(): string
+ {
+ return $this->error;
+ }
+
+ /** {@inheritDoc} */
+ public function isTimeout(): bool
+ {
+ return $this->isTimeout;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2020, Friendica
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Network\HTTPClient\Response;
+
+use Friendica\Core\Logger;
+use Friendica\Network\HTTPClient\Capability\ICanHandleHttpResponses;
+use Friendica\Network\HTTPException\NotImplementedException;
+use GuzzleHttp\Psr7\Response;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * A content wrapper class for Guzzle call results
+ */
+class GuzzleResponse extends Response implements ICanHandleHttpResponses, ResponseInterface
+{
+ /** @var string The URL */
+ private $url;
+ /** @var boolean */
+ private $isTimeout;
+ /** @var boolean */
+ private $isSuccess;
+ /**
+ * @var int the error number or 0 (zero) if no error
+ */
+ private $errorNumber;
+
+ /**
+ * @var string the error message or '' (the empty string) if no
+ */
+ private $error;
+
+ public function __construct(ResponseInterface $response, string $url, $errorNumber = 0, $error = '')
+ {
+ parent::__construct($response->getStatusCode(), $response->getHeaders(), $response->getBody(), $response->getProtocolVersion(), $response->getReasonPhrase());
+ $this->url = $url;
+ $this->error = $error;
+ $this->errorNumber = $errorNumber;
+
+ $this->checkSuccess();
+ }
+
+ private function checkSuccess()
+ {
+ $this->isSuccess = ($this->getStatusCode() >= 200 && $this->getStatusCode() <= 299) || $this->errorNumber == 0;
+
+ // Everything higher or equal 400 is not a success
+ if ($this->getReturnCode() >= 400) {
+ $this->isSuccess = false;
+ }
+
+ if (!$this->isSuccess) {
+ Logger::debug('debug', ['info' => $this->getHeaders()]);
+ }
+
+ if (!$this->isSuccess && $this->errorNumber == CURLE_OPERATION_TIMEDOUT) {
+ $this->isTimeout = true;
+ } else {
+ $this->isTimeout = false;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public function getReturnCode(): string
+ {
+ return $this->getStatusCode();
+ }
+
+ /** {@inheritDoc} */
+ public function getContentType(): string
+ {
+ $contentTypes = $this->getHeader('Content-Type') ?? [];
+
+ return array_pop($contentTypes) ?? '';
+ }
+
+ /** {@inheritDoc} */
+ public function inHeader(string $field): bool
+ {
+ return $this->hasHeader($field);
+ }
+
+ /** {@inheritDoc} */
+ public function getHeaderArray(): array
+ {
+ return $this->getHeaders();
+ }
+
+ /** {@inheritDoc} */
+ public function isSuccess(): bool
+ {
+ return $this->isSuccess;
+ }
+
+ /** {@inheritDoc} */
+ public function getUrl(): string
+ {
+ return $this->url;
+ }
+
+ /** {@inheritDoc} */
+ public function getRedirectUrl(): string
+ {
+ return $this->url;
+ }
+
+ /** {@inheritDoc}
+ *
+ * @throws NotImplementedException
+ */
+ public function isRedirectUrl(): bool
+ {
+ throw new NotImplementedException();
+ }
+
+ /** {@inheritDoc} */
+ public function getErrorNumber(): int
+ {
+ return $this->errorNumber;
+ }
+
+ /** {@inheritDoc} */
+ public function getError(): string
+ {
+ return $this->error;
+ }
+
+ /** {@inheritDoc} */
+ public function isTimeout(): bool
+ {
+ return $this->isTimeout;
+ }
+
+ /// @todo - fix mismatching use of "getBody()" as string here and parent "getBody()" as streaminterface
+ public function getBody(): string
+ {
+ return (string) parent::getBody();
+ }
+}
+++ /dev/null
-<?php
-
-namespace Friendica\Network;
-
-use GuzzleHttp\RequestOptions;
-
-/**
- * This class contains a list of possible HTTPClient request options.
- */
-class HTTPClientOptions
-{
- /**
- * accept_content: (array) supply Accept: header with 'accept_content' as the value
- */
- const ACCEPT_CONTENT = 'accept_content';
- /**
- * timeout: (int) out in seconds, default system config value or 60 seconds
- */
- const TIMEOUT = RequestOptions::TIMEOUT;
- /**
- * cookiejar: (string) path to cookie jar file
- */
- const COOKIEJAR = 'cookiejar';
- /**
- * headers: (array) header array
- */
- const HEADERS = RequestOptions::HEADERS;
- /**
- * header: (array) header array (legacy version)
- */
- const LEGACY_HEADER = 'header';
- /**
- * content_length: (int) maximum File content length
- */
- const CONTENT_LENGTH = 'content_length';
- /**
- * body: (mixed) Setting the body for sending data
- */
- const BODY = RequestOptions::BODY;
- /**
- * auth: (array) Authentication settings for specific requests
- */
- const AUTH = RequestOptions::AUTH;
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU APGL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Network;
-
-use GuzzleHttp\Exception\TransferException;
-
-/**
- * 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) 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 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) 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 IHTTPResult
- */
- public function get(string $url, array $opts = []);
-
- /**
- * 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 IHTTPResult
- */
- public function request(string $method, 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
- *
- * @return string A canonical URL
- *
- * @throws TransferException In case there's an error during the resolving
- */
- public function finalUrl(string $url);
-}
+++ /dev/null
-<?php
-
-namespace Friendica\Network;
-
-use Psr\Http\Message\MessageInterface;
-
-/**
- * Temporary class to map Friendica used variables based on PSR-7 HTTPResponse
- */
-interface IHTTPResult
-{
- /**
- * Gets the Return Code
- *
- * @return string The Return Code
- */
- public function getReturnCode();
-
- /**
- * Returns the Content Type
- *
- * @return string the Content Type
- */
- public function getContentType();
-
- /**
- * Returns the headers
- * @see MessageInterface::getHeader()
- *
- * @param string $header optional header field. Return all fields if empty
- *
- * @return string[] the headers or the specified content of the header variable
- */
- public function getHeader($header);
-
- /**
- * Returns all headers
- * @see MessageInterface::getHeaders()
- *
- * @return string[][]
- */
- public function getHeaders();
-
- /**
- * Check if a specified header exists
- * @see MessageInterface::hasHeader()
- *
- * @param string $field header field
- *
- * @return boolean "true" if header exists
- */
- public function inHeader(string $field);
-
- /**
- * Returns the headers as an associated array
- * @see MessageInterface::getHeaders()
- * @deprecated
- *
- * @return string[][] associated header array
- */
- public function getHeaderArray();
-
- /**
- * @return bool
- */
- public function isSuccess();
-
- /**
- * @return string
- */
- public function getUrl();
-
- /**
- * @return string
- */
- public function getRedirectUrl();
-
- /**
- * @see MessageInterface::getBody()
- *
- * @return string
- */
- public function getBody();
-
- /**
- * @return boolean
- */
- public function isRedirectUrl();
-
- /**
- * @return integer
- */
- public function getErrorNumber();
-
- /**
- * @return string
- */
- public function getError();
-
- /**
- * @return boolean
- */
- public function isTimeout();
-}
use Friendica\Model\GServer;
use Friendica\Model\Profile;
use Friendica\Model\User;
+use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Protocol\ActivityNamespace;
use Friendica\Protocol\ActivityPub;
use Friendica\Protocol\Email;
Logger::info('Probing', ['host' => $host, 'ssl_url' => $ssl_url, 'url' => $url, 'callstack' => System::callstack(20)]);
$xrd = null;
- $curlResult = DI::httpClient()->get($ssl_url, [HTTPClientOptions::TIMEOUT => $xrd_timeout, HTTPClientOptions::ACCEPT_CONTENT => ['application/xrd+xml']]);
+ $curlResult = DI::httpClient()->get($ssl_url, [HttpClientOptions::TIMEOUT => $xrd_timeout, HttpClientOptions::ACCEPT_CONTENT => ['application/xrd+xml']]);
$ssl_connection_error = ($curlResult->getErrorNumber() == CURLE_COULDNT_CONNECT) || ($curlResult->getReturnCode() == 0);
if ($curlResult->isSuccess()) {
$xml = $curlResult->getBody();
}
if (!is_object($xrd) && !empty($url)) {
- $curlResult = DI::httpClient()->get($url, [HTTPClientOptions::TIMEOUT => $xrd_timeout, HTTPClientOptions::ACCEPT_CONTENT => ['application/xrd+xml']]);
+ $curlResult = DI::httpClient()->get($url, [HttpClientOptions::TIMEOUT => $xrd_timeout, HttpClientOptions::ACCEPT_CONTENT => ['application/xrd+xml']]);
$connection_error = ($curlResult->getErrorNumber() == CURLE_COULDNT_CONNECT) || ($curlResult->getReturnCode() == 0);
if ($curlResult->isTimeout()) {
Logger::info('Probing timeout', ['url' => $url]);
*/
private static function getHideStatus($url)
{
- $curlResult = DI::httpClient()->get($url, [HTTPClientOptions::CONTENT_LENGTH => 1000000]);
+ $curlResult = DI::httpClient()->get($url, [HttpClientOptions::CONTENT_LENGTH => 1000000]);
if (!$curlResult->isSuccess()) {
return false;
}
{
$xrd_timeout = DI::config()->get('system', 'xrd_timeout', 20);
- $curlResult = DI::httpClient()->get($url, [HTTPClientOptions::TIMEOUT => $xrd_timeout, HTTPClientOptions::ACCEPT_CONTENT => [$type]]);
+ $curlResult = DI::httpClient()->get($url, [HttpClientOptions::TIMEOUT => $xrd_timeout, HttpClientOptions::ACCEPT_CONTENT => [$type]]);
if ($curlResult->isTimeout()) {
self::$istimeout = true;
return [];
use Friendica\Model\Post;
use Friendica\Model\Tag;
use Friendica\Model\User;
-use Friendica\Network\HTTPClientOptions;
+use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Network\Probe;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Images;
self::$conv_list[$conversation] = true;
- $curlResult = DI::httpClient()->get($conversation, [HTTPClientOptions::ACCEPT_CONTENT => ['application/atom+xml', 'text/html']]);
+ $curlResult = DI::httpClient()->get($conversation, [HttpClientOptions::ACCEPT_CONTENT => ['application/atom+xml', 'text/html']]);
if (!$curlResult->isSuccess() || empty($curlResult->getBody())) {
return;
}
$stored = false;
- $curlResult = DI::httpClient()->get($related, [HTTPClientOptions::ACCEPT_CONTENT => ['application/atom+xml', 'text/html']]);
+ $curlResult = DI::httpClient()->get($related, [HttpClientOptions::ACCEPT_CONTENT => ['application/atom+xml', 'text/html']]);
if (!$curlResult->isSuccess() || empty($curlResult->getBody())) {
return;
use Friendica\Model\APContact;
use Friendica\Model\Contact;
use Friendica\Model\User;
-use Friendica\Network\CurlResult;
-use Friendica\Network\HTTPClientOptions;
-use Friendica\Network\IHTTPResult;
+use Friendica\Network\HTTPClient\Response\CurlResult;
+use Friendica\Network\HTTPClient\Client\HttpClientOptions;
+use Friendica\Network\HTTPClient\Capability\ICanHandleHttpResponses;
/**
* Implements HTTP Signatures per draft-cavage-http-signatures-07.
* 'nobody' => only return the header
* 'cookiejar' => path to cookie jar file
*
- * @return IHTTPResult CurlResult
+ * @return \Friendica\Network\HTTPClient\Capability\ICanHandleHttpResponses CurlResult
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function fetchRaw($request, $uid = 0, $opts = ['accept_content' => ['application/activity+json', 'application/ld+json']])
}
$curl_opts = $opts;
- $curl_opts[HTTPClientOptions::HEADERS] = $header;
+ $curl_opts[HttpClientOptions::HEADERS] = $header;
if (!empty($opts['nobody'])) {
$curlResult = DI::httpClient()->head($request, $curl_opts);
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Network\HTTPException;
-use Friendica\Network\HTTPClientOptions;
+use Friendica\Network\HTTPClient\Client\HttpClientOptions;
/**
* Get information about a given URL
return $siteinfo;
}
- $curlResult = DI::httpClient()->get($url, [HTTPClientOptions::CONTENT_LENGTH => 1000000]);
+ $curlResult = DI::httpClient()->get($url, [HttpClientOptions::CONTENT_LENGTH => 1000000]);
if (!$curlResult->isSuccess() || empty($curlResult->getBody())) {
return $siteinfo;
}
use Friendica\Model\Item;
use Friendica\Model\Post;
use Friendica\Model\User;
-use Friendica\Network\HTTPClientOptions;
+use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Protocol\Activity;
use Friendica\Protocol\ActivityPub;
use Friendica\Protocol\Email;
}
$cookiejar = tempnam(get_temppath(), 'cookiejar-onepoll-');
- $curlResult = DI::httpClient()->get($contact['poll'], [HTTPClientOptions::COOKIEJAR => $cookiejar]);
+ $curlResult = DI::httpClient()->get($contact['poll'], [HttpClientOptions::COOKIEJAR => $cookiejar]);
unlink($cookiejar);
if ($curlResult->isTimeout()) {
['getBackend', [], Dice::CHAIN_CALL],
],
],
- Network\IHTTPClient::class => [
- 'instanceOf' => Factory\HTTPClientFactory::class,
+ Network\HTTPClient\Capability\ICanRequestPerHttp::class => [
+ 'instanceOf' => Network\HTTPClient\Factory\HttpClient::class,
'call' => [
['createClient', [], Dice::CHAIN_CALL],
],
use Dice\Dice;
use Friendica\DI;
-use Friendica\Factory\HTTPClientFactory;
-use Friendica\Network\IHTTPClient;
+use Friendica\Network\HTTPClient\Factory\HttpClient;
+use Friendica\Network\HTTPClient\Capability\ICanRequestPerHttp;
use GuzzleHttp\HandlerStack;
/**
$dice = DI::getDice();
// addRule() clones the current instance and returns a new one, so no concurrency problems :-)
- $newDice = $dice->addRule(IHTTPClient::class, [
- 'instanceOf' => HTTPClientFactory::class,
+ $newDice = $dice->addRule(ICanRequestPerHttp::class, [
+ 'instanceOf' => HttpClient::class,
'call' => [
['createClient', [$this->httpRequestHandler], Dice::CHAIN_CALL],
],
use Dice\Dice;
use Friendica\Core\Config\ValueObject\Cache;
use Friendica\DI;
-use Friendica\Network\IHTTPResult;
-use Friendica\Network\IHTTPClient;
+use Friendica\Network\HTTPClient\Capability\ICanHandleHttpResponses;
+use Friendica\Network\HTTPClient\Capability\ICanRequestPerHttp;
use Friendica\Test\MockedTest;
use Friendica\Test\Util\VFSTrait;
use Mockery;
$this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
// Mocking the CURL Response
- $IHTTPResult = Mockery::mock(IHTTPResult::class);
+ $IHTTPResult = Mockery::mock(ICanHandleHttpResponses::class);
$IHTTPResult
->shouldReceive('getReturnCode')
->andReturn('404');
->andReturn('test Error');
// Mocking the CURL Request
- $networkMock = Mockery::mock(IHTTPClient::class);
+ $networkMock = Mockery::mock(ICanRequestPerHttp::class);
$networkMock
->shouldReceive('fetchFull')
->with('https://test/install/testrewrite')
->andReturn($IHTTPResult);
$this->dice->shouldReceive('create')
- ->with(IHTTPClient::class)
+ ->with(ICanRequestPerHttp::class)
->andReturn($networkMock);
DI::init($this->dice);
$this->l10nMock->shouldReceive('t')->andReturnUsing(function ($args) { return $args; });
// Mocking the failed CURL Response
- $IHTTPResultF = Mockery::mock(IHTTPResult::class);
+ $IHTTPResultF = Mockery::mock(ICanHandleHttpResponses::class);
$IHTTPResultF
->shouldReceive('getReturnCode')
->andReturn('404');
// Mocking the working CURL Response
- $IHTTPResultW = Mockery::mock(IHTTPResult::class);
+ $IHTTPResultW = Mockery::mock(ICanHandleHttpResponses::class);
$IHTTPResultW
->shouldReceive('getReturnCode')
->andReturn('204');
// Mocking the CURL Request
- $networkMock = Mockery::mock(IHTTPClient::class);
+ $networkMock = Mockery::mock(ICanRequestPerHttp::class);
$networkMock
->shouldReceive('fetchFull')
->with('https://test/install/testrewrite')
->andReturn($IHTTPResultW);
$this->dice->shouldReceive('create')
- ->with(IHTTPClient::class)
+ ->with(ICanRequestPerHttp::class)
->andReturn($networkMock);
DI::init($this->dice);
use Friendica\Core\Config\Factory\Config;
use Friendica\Core\Config\Repository;
use Friendica\Core\Storage\Type;
-use Friendica\Network\HTTPClient;
+use Friendica\Network\HTTPClient\Client\HttpClientCan;
use Friendica\Test\DatabaseTest;
use Friendica\Test\Util\Database\StaticDatabase;
use Friendica\Test\Util\VFSTrait;
private $logger;
/** @var L10n */
private $l10n;
- /** @var HTTPClient */
+ /** @var HttpClientCan */
private $httpRequest;
protected function setUp(): void
$this->l10n = \Mockery::mock(L10n::class);
- $this->httpRequest = \Mockery::mock(HTTPClient::class);
+ $this->httpRequest = \Mockery::mock(HttpClientCan::class);
}
protected function tearDown(): void
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Test\src\Network;
-
-use Dice\Dice;
-use Friendica\DI;
-use Friendica\Network\CurlResult;
-use Mockery\MockInterface;
-use PHPUnit\Framework\TestCase;
-use Psr\Log\LoggerInterface;
-use Psr\Log\NullLogger;
-
-class CurlResultTest extends TestCase
-{
- protected function setUp(): void
- {
- parent::setUp();
-
- /** @var Dice|MockInterface $dice */
- $dice = \Mockery::mock(Dice::class)->makePartial();
- $dice = $dice->addRules(include __DIR__ . '/../../../static/dependencies.config.php');
-
- $logger = new NullLogger();
- $dice->shouldReceive('create')
- ->with(LoggerInterface::class)
- ->andReturn($logger);
-
- DI::init($dice);
- }
-
- /**
- * @small
- */
- public function testNormal()
- {
- $header = file_get_contents(__DIR__ . '/../../datasets/curl/about.head');
- $headerArray = include(__DIR__ . '/../../datasets/curl/about.head.php');
- $body = file_get_contents(__DIR__ . '/../../datasets/curl/about.body');
-
-
- $curlResult = new CurlResult('https://test.local', $header . $body, [
- 'http_code' => 200,
- 'content_type' => 'text/html; charset=utf-8',
- 'url' => 'https://test.local'
- ]);
-
- self::assertTrue($curlResult->isSuccess());
- self::assertFalse($curlResult->isTimeout());
- self::assertFalse($curlResult->isRedirectUrl());
- self::assertSame($headerArray, $curlResult->getHeaders());
- self::assertSame($body, $curlResult->getBody());
- self::assertSame('text/html; charset=utf-8', $curlResult->getContentType());
- self::assertSame('https://test.local', $curlResult->getUrl());
- self::assertSame('https://test.local', $curlResult->getRedirectUrl());
- }
-
- /**
- * @small
- * @runInSeparateProcess
- * @preserveGlobalState disabled
- */
- public function testRedirect()
- {
- $header = file_get_contents(__DIR__ . '/../../datasets/curl/about.head');
- $headerArray = include(__DIR__ . '/../../datasets/curl/about.head.php');
- $body = file_get_contents(__DIR__ . '/../../datasets/curl/about.body');
-
-
- $curlResult = new CurlResult('https://test.local/test/it', $header . $body, [
- 'http_code' => 301,
- 'content_type' => 'text/html; charset=utf-8',
- 'url' => 'https://test.local/test/it',
- 'redirect_url' => 'https://test.other'
- ]);
-
- self::assertTrue($curlResult->isSuccess());
- self::assertFalse($curlResult->isTimeout());
- self::assertTrue($curlResult->isRedirectUrl());
- self::assertSame($headerArray, $curlResult->getHeaders());
- self::assertSame($body, $curlResult->getBody());
- self::assertSame('text/html; charset=utf-8', $curlResult->getContentType());
- self::assertSame('https://test.local/test/it', $curlResult->getUrl());
- self::assertSame('https://test.other/test/it', $curlResult->getRedirectUrl());
- }
-
- /**
- * @small
- */
- public function testTimeout()
- {
- $header = file_get_contents(__DIR__ . '/../../datasets/curl/about.head');
- $headerArray = include(__DIR__ . '/../../datasets/curl/about.head.php');
- $body = file_get_contents(__DIR__ . '/../../datasets/curl/about.body');
-
-
- $curlResult = new CurlResult('https://test.local/test/it', $header . $body, [
- 'http_code' => 500,
- 'content_type' => 'text/html; charset=utf-8',
- 'url' => 'https://test.local/test/it',
- 'redirect_url' => 'https://test.other'
- ], CURLE_OPERATION_TIMEDOUT, 'Tested error');
-
- self::assertFalse($curlResult->isSuccess());
- self::assertTrue($curlResult->isTimeout());
- self::assertFalse($curlResult->isRedirectUrl());
- self::assertSame($headerArray, $curlResult->getHeaders());
- self::assertSame($body, $curlResult->getBody());
- self::assertSame('text/html; charset=utf-8', $curlResult->getContentType());
- self::assertSame('https://test.local/test/it', $curlResult->getRedirectUrl());
- self::assertSame('Tested error', $curlResult->getError());
- }
-
- /**
- * @small
- * @runInSeparateProcess
- * @preserveGlobalState disabled
- */
- public function testRedirectHeader()
- {
- $header = file_get_contents(__DIR__ . '/../../datasets/curl/about.redirect');
- $headerArray = include(__DIR__ . '/../../datasets/curl/about.redirect.php');
- $body = file_get_contents(__DIR__ . '/../../datasets/curl/about.body');
-
-
- $curlResult = new CurlResult('https://test.local/test/it?key=value', $header . $body, [
- 'http_code' => 301,
- 'content_type' => 'text/html; charset=utf-8',
- 'url' => 'https://test.local/test/it?key=value',
- ]);
-
- self::assertTrue($curlResult->isSuccess());
- self::assertFalse($curlResult->isTimeout());
- self::assertTrue($curlResult->isRedirectUrl());
- self::assertSame($headerArray, $curlResult->getHeaders());
- self::assertSame($body, $curlResult->getBody());
- self::assertSame('text/html; charset=utf-8', $curlResult->getContentType());
- self::assertSame('https://test.local/test/it?key=value', $curlResult->getUrl());
- self::assertSame('https://test.other/some/?key=value', $curlResult->getRedirectUrl());
- }
-
- /**
- * @small
- */
- public function testInHeader()
- {
- $header = file_get_contents(__DIR__ . '/../../datasets/curl/about.head');
- $body = file_get_contents(__DIR__ . '/../../datasets/curl/about.body');
-
- $curlResult = new CurlResult('https://test.local', $header . $body, [
- 'http_code' => 200,
- 'content_type' => 'text/html; charset=utf-8',
- 'url' => 'https://test.local'
- ]);
- self::assertTrue($curlResult->inHeader('vary'));
- self::assertFalse($curlResult->inHeader('wrongHeader'));
- }
-
- /**
- * @small
- */
- public function testGetHeaderArray()
- {
- $header = file_get_contents(__DIR__ . '/../../datasets/curl/about.head');
- $body = file_get_contents(__DIR__ . '/../../datasets/curl/about.body');
-
- $curlResult = new CurlResult('https://test.local', $header . $body, [
- 'http_code' => 200,
- 'content_type' => 'text/html; charset=utf-8',
- 'url' => 'https://test.local'
- ]);
-
- $headers = $curlResult->getHeaderArray();
-
- self::assertNotEmpty($headers);
- self::assertArrayHasKey('vary', $headers);
- }
-
- /**
- * @small
- */
- public function testGetHeaderWithParam()
- {
- $header = file_get_contents(__DIR__ . '/../../datasets/curl/about.head');
- $body = file_get_contents(__DIR__ . '/../../datasets/curl/about.body');
-
- $curlResult = new CurlResult('https://test.local', $header . $body, [
- 'http_code' => 200,
- 'content_type' => 'text/html; charset=utf-8',
- 'url' => 'https://test.local'
- ]);
-
- self::assertNotEmpty($curlResult->getHeaders());
- self::assertEmpty($curlResult->getHeader('wrongHeader'));
- }
-}
--- /dev/null
+<?php
+
+namespace Friendica\Test\src\Network\HTTPClient\Client;
+
+use Friendica\DI;
+use Friendica\Test\DiceHttpMockHandlerTrait;
+use Friendica\Test\MockedTest;
+use GuzzleHttp\Handler\MockHandler;
+use GuzzleHttp\Psr7\Response;
+
+class HTTPClientTest extends MockedTest
+{
+ use DiceHttpMockHandlerTrait;
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ $this->setupHttpMockHandler();
+ }
+
+ /**
+ * Test for issue https://github.com/friendica/friendica/issues/10473#issuecomment-907749093
+ */
+ public function testInvalidURI()
+ {
+ $this->httpRequestHandler->setHandler(new MockHandler([
+ new Response(301, ['Location' => 'https:///']),
+ ]));
+
+ self::assertFalse(DI::httpClient()->get('https://friendica.local')->isSuccess());
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Test\src\Network\HTTPClient\Response;
+
+use Dice\Dice;
+use Friendica\DI;
+use Friendica\Network\HTTPClient\Response\CurlResult;
+use Mockery\MockInterface;
+use PHPUnit\Framework\TestCase;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+
+class CurlResultTest extends TestCase
+{
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ /** @var Dice|MockInterface $dice */
+ $dice = \Mockery::mock(Dice::class)->makePartial();
+ $dice = $dice->addRules(include __DIR__ . '/../../../../../static/dependencies.config.php');
+
+ $logger = new NullLogger();
+ $dice->shouldReceive('create')
+ ->with(LoggerInterface::class)
+ ->andReturn($logger);
+
+ DI::init($dice);
+ }
+
+ /**
+ * @small
+ */
+ public function testNormal()
+ {
+ $header = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.head');
+ $headerArray = include(__DIR__ . '/../../../../datasets/curl/about.head.php');
+ $body = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.body');
+
+
+ $curlResult = new \Friendica\Network\HTTPClient\Response\CurlResult('https://test.local', $header . $body, [
+ 'http_code' => 200,
+ 'content_type' => 'text/html; charset=utf-8',
+ 'url' => 'https://test.local'
+ ]);
+
+ self::assertTrue($curlResult->isSuccess());
+ self::assertFalse($curlResult->isTimeout());
+ self::assertFalse($curlResult->isRedirectUrl());
+ self::assertSame($headerArray, $curlResult->getHeaders());
+ self::assertSame($body, $curlResult->getBody());
+ self::assertSame('text/html; charset=utf-8', $curlResult->getContentType());
+ self::assertSame('https://test.local', $curlResult->getUrl());
+ self::assertSame('https://test.local', $curlResult->getRedirectUrl());
+ }
+
+ /**
+ * @small
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
+ */
+ public function testRedirect()
+ {
+ $header = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.head');
+ $headerArray = include(__DIR__ . '/../../../../datasets/curl/about.head.php');
+ $body = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.body');
+
+
+ $curlResult = new \Friendica\Network\HTTPClient\Response\CurlResult('https://test.local/test/it', $header . $body, [
+ 'http_code' => 301,
+ 'content_type' => 'text/html; charset=utf-8',
+ 'url' => 'https://test.local/test/it',
+ 'redirect_url' => 'https://test.other'
+ ]);
+
+ self::assertTrue($curlResult->isSuccess());
+ self::assertFalse($curlResult->isTimeout());
+ self::assertTrue($curlResult->isRedirectUrl());
+ self::assertSame($headerArray, $curlResult->getHeaders());
+ self::assertSame($body, $curlResult->getBody());
+ self::assertSame('text/html; charset=utf-8', $curlResult->getContentType());
+ self::assertSame('https://test.local/test/it', $curlResult->getUrl());
+ self::assertSame('https://test.other/test/it', $curlResult->getRedirectUrl());
+ }
+
+ /**
+ * @small
+ */
+ public function testTimeout()
+ {
+ $header = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.head');
+ $headerArray = include(__DIR__ . '/../../../../datasets/curl/about.head.php');
+ $body = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.body');
+
+
+ $curlResult = new \Friendica\Network\HTTPClient\Response\CurlResult('https://test.local/test/it', $header . $body, [
+ 'http_code' => 500,
+ 'content_type' => 'text/html; charset=utf-8',
+ 'url' => 'https://test.local/test/it',
+ 'redirect_url' => 'https://test.other'
+ ], CURLE_OPERATION_TIMEDOUT, 'Tested error');
+
+ self::assertFalse($curlResult->isSuccess());
+ self::assertTrue($curlResult->isTimeout());
+ self::assertFalse($curlResult->isRedirectUrl());
+ self::assertSame($headerArray, $curlResult->getHeaders());
+ self::assertSame($body, $curlResult->getBody());
+ self::assertSame('text/html; charset=utf-8', $curlResult->getContentType());
+ self::assertSame('https://test.local/test/it', $curlResult->getRedirectUrl());
+ self::assertSame('Tested error', $curlResult->getError());
+ }
+
+ /**
+ * @small
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
+ */
+ public function testRedirectHeader()
+ {
+ $header = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.redirect');
+ $headerArray = include(__DIR__ . '/../../../../datasets/curl/about.redirect.php');
+ $body = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.body');
+
+
+ $curlResult = new CurlResult('https://test.local/test/it?key=value', $header . $body, [
+ 'http_code' => 301,
+ 'content_type' => 'text/html; charset=utf-8',
+ 'url' => 'https://test.local/test/it?key=value',
+ ]);
+
+ self::assertTrue($curlResult->isSuccess());
+ self::assertFalse($curlResult->isTimeout());
+ self::assertTrue($curlResult->isRedirectUrl());
+ self::assertSame($headerArray, $curlResult->getHeaders());
+ self::assertSame($body, $curlResult->getBody());
+ self::assertSame('text/html; charset=utf-8', $curlResult->getContentType());
+ self::assertSame('https://test.local/test/it?key=value', $curlResult->getUrl());
+ self::assertSame('https://test.other/some/?key=value', $curlResult->getRedirectUrl());
+ }
+
+ /**
+ * @small
+ */
+ public function testInHeader()
+ {
+ $header = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.head');
+ $body = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.body');
+
+ $curlResult = new \Friendica\Network\HTTPClient\Response\CurlResult('https://test.local', $header . $body, [
+ 'http_code' => 200,
+ 'content_type' => 'text/html; charset=utf-8',
+ 'url' => 'https://test.local'
+ ]);
+ self::assertTrue($curlResult->inHeader('vary'));
+ self::assertFalse($curlResult->inHeader('wrongHeader'));
+ }
+
+ /**
+ * @small
+ */
+ public function testGetHeaderArray()
+ {
+ $header = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.head');
+ $body = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.body');
+
+ $curlResult = new \Friendica\Network\HTTPClient\Response\CurlResult('https://test.local', $header . $body, [
+ 'http_code' => 200,
+ 'content_type' => 'text/html; charset=utf-8',
+ 'url' => 'https://test.local'
+ ]);
+
+ $headers = $curlResult->getHeaderArray();
+
+ self::assertNotEmpty($headers);
+ self::assertArrayHasKey('vary', $headers);
+ }
+
+ /**
+ * @small
+ */
+ public function testGetHeaderWithParam()
+ {
+ $header = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.head');
+ $body = file_get_contents(__DIR__ . '/../../../../datasets/curl/about.body');
+
+ $curlResult = new CurlResult('https://test.local', $header . $body, [
+ 'http_code' => 200,
+ 'content_type' => 'text/html; charset=utf-8',
+ 'url' => 'https://test.local'
+ ]);
+
+ self::assertNotEmpty($curlResult->getHeaders());
+ self::assertEmpty($curlResult->getHeader('wrongHeader'));
+ }
+}
+++ /dev/null
-<?php
-
-namespace Friendica\Test\src\Network;
-
-use Friendica\DI;
-use Friendica\Test\DiceHttpMockHandlerTrait;
-use Friendica\Test\MockedTest;
-use GuzzleHttp\Handler\MockHandler;
-use GuzzleHttp\Psr7\Response;
-
-class HTTPClientTest extends MockedTest
-{
- use DiceHttpMockHandlerTrait;
-
- protected function setUp(): void
- {
- parent::setUp();
-
- $this->setupHttpMockHandler();
- }
-
- /**
- * Test for issue https://github.com/friendica/friendica/issues/10473#issuecomment-907749093
- */
- public function testInvalidURI()
- {
- $this->httpRequestHandler->setHandler(new MockHandler([
- new Response(301, ['Location' => 'https:///']),
- ]));
-
- self::assertFalse(DI::httpClient()->get('https://friendica.local')->isSuccess());
- }
-}