From 52c7948526152269d898f1187bcda4d103bb4380 Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 23 Aug 2021 00:14:18 +0200 Subject: [PATCH 01/19] Create HTTPClientFactory and introduce ImageTest --- src/DI.php | 4 +- src/Factory/HTTPClientFactory.php | 87 ++++++++++ .../{HTTPRequest.php => HTTPClient.php} | 162 +++++------------- .../{IHTTPRequest.php => IHTTPClient.php} | 9 +- static/dependencies.config.php | 9 +- tests/datasets/curl/image.content | 81 +++++++++ tests/src/Network/HTTPRequestTest.php | 60 +++++++ 7 files changed, 278 insertions(+), 134 deletions(-) create mode 100644 src/Factory/HTTPClientFactory.php rename src/Network/{HTTPRequest.php => HTTPClient.php} (77%) rename src/Network/{IHTTPRequest.php => IHTTPClient.php} (96%) create mode 100644 tests/datasets/curl/image.content create mode 100644 tests/src/Network/HTTPRequestTest.php diff --git a/src/DI.php b/src/DI.php index cbc353161..5c56b45e1 100644 --- a/src/DI.php +++ b/src/DI.php @@ -407,11 +407,11 @@ abstract class DI // /** - * @return Network\IHTTPRequest + * @return Network\IHTTPClient */ public static function httpRequest() { - return self::$dice->create(Network\IHTTPRequest::class); + return self::$dice->create(Network\IHTTPClient::class); } // diff --git a/src/Factory/HTTPClientFactory.php b/src/Factory/HTTPClientFactory.php new file mode 100644 index 000000000..604b6fd62 --- /dev/null +++ b/src/Factory/HTTPClientFactory.php @@ -0,0 +1,87 @@ +config = $config; + $this->profiler = $profiler; + $this->baseUrl = $baseUrl; + } + + public function createClient(): IHTTPClient + { + $proxy = $this->config->get('system', 'proxy'); + + if (!empty($proxy)) { + $proxyuser = $this->config->get('system', 'proxyuser'); + + if (!empty($proxyuser)) { + $proxy = $proxyuser . '@' . $proxy; + } + } + + $logger = $this->logger; + + $onRedirect = function ( + RequestInterface $request, + ResponseInterface $response, + UriInterface $uri + ) use ($logger) { + $logger->notice('Curl redirect.', ['url' => $request->getUri(), 'to' => $uri]); + }; + + $guzzle = new Client([ + RequestOptions::ALLOW_REDIRECTS => [ + 'max' => 8, + 'on_redirect' => $onRedirect, + 'track_redirect' => true, + 'strict' => true, + 'referer' => true, + ], + RequestOptions::HTTP_ERRORS => false, + // Without this setting it seems as if some webservers send compressed content + // This seems to confuse curl so that it shows this uncompressed. + /// @todo We could possibly set this value to "gzip" or something similar + RequestOptions::DECODE_CONTENT => '', + RequestOptions::FORCE_IP_RESOLVE => ($this->config->get('system', 'ipv4_resolve') ? 'v4' : null), + RequestOptions::CONNECT_TIMEOUT => 10, + RequestOptions::TIMEOUT => $this->config->get('system', 'curl_timeout', 60), + // by default we will allow self-signed certs + // but you can override this + RequestOptions::VERIFY => (bool)$this->config->get('system', 'verifyssl'), + RequestOptions::PROXY => $proxy, + ]); + + $userAgent = FRIENDICA_PLATFORM . " '" . + FRIENDICA_CODENAME . "' " . + FRIENDICA_VERSION . '-' . + DB_UPDATE_VERSION . '; ' . + $this->baseUrl->get(); + + return new HTTPClient($logger, $this->profiler, $this->config, $userAgent, $guzzle); + } +} diff --git a/src/Network/HTTPRequest.php b/src/Network/HTTPClient.php similarity index 77% rename from src/Network/HTTPRequest.php rename to src/Network/HTTPClient.php index b08e91832..e20ddd3c9 100644 --- a/src/Network/HTTPRequest.php +++ b/src/Network/HTTPClient.php @@ -23,23 +23,22 @@ namespace Friendica\Network; use DOMDocument; use DomXPath; -use Friendica\App; use Friendica\Core\Config\IConfig; use Friendica\Core\System; use Friendica\Util\Network; use Friendica\Util\Profiler; use GuzzleHttp\Client; +use GuzzleHttp\Cookie\FileCookieJar; use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Exception\TransferException; -use Psr\Http\Message\RequestInterface; +use GuzzleHttp\RequestOptions; use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\UriInterface; use Psr\Log\LoggerInterface; /** * Performs HTTP requests to a given URL */ -class HTTPRequest implements IHTTPRequest +class HTTPClient implements IHTTPClient { /** @var LoggerInterface */ private $logger; @@ -48,31 +47,20 @@ class HTTPRequest implements IHTTPRequest /** @var IConfig */ private $config; /** @var string */ - private $baseUrl; + private $userAgent; + /** @var Client */ + private $client; - public function __construct(LoggerInterface $logger, Profiler $profiler, IConfig $config, App\BaseURL $baseUrl) + public function __construct(LoggerInterface $logger, Profiler $profiler, IConfig $config, string $userAgent, Client $client) { - $this->logger = $logger; - $this->profiler = $profiler; - $this->config = $config; - $this->baseUrl = $baseUrl->get(); + $this->logger = $logger; + $this->profiler = $profiler; + $this->config = $config; + $this->userAgent = $userAgent; + $this->client = $client; } - /** {@inheritDoc} - * - * @throws HTTPException\InternalServerErrorException - */ - public function head(string $url, array $opts = []) - { - $opts['nobody'] = true; - - return $this->get($url, $opts); - } - - /** - * {@inheritDoc} - */ - public function get(string $url, array $opts = []) + protected function request(string $method, string $url, array $opts = []) { $this->profiler->startRecording('network'); @@ -105,19 +93,13 @@ class HTTPRequest implements IHTTPRequest return CurlResult::createErrorCurl($url); } - $curlOptions = []; + $conf = []; if (!empty($opts['cookiejar'])) { - $curlOptions[CURLOPT_COOKIEJAR] = $opts["cookiejar"]; - $curlOptions[CURLOPT_COOKIEFILE] = $opts["cookiejar"]; + $jar = new FileCookieJar($opts['cookiejar']); + $conf[RequestOptions::COOKIES] = $jar; } - // These settings aren't needed. We're following the location already. - // $curlOptions[CURLOPT_FOLLOWLOCATION] =true; - // $curlOptions[CURLOPT_MAXREDIRS] = 5; - - $curlOptions[CURLOPT_HTTPHEADER] = []; - if (!empty($opts['accept_content'])) { array_push($curlOptions[CURLOPT_HTTPHEADER], 'Accept: ' . $opts['accept_content']); } @@ -126,74 +108,17 @@ class HTTPRequest implements IHTTPRequest $curlOptions[CURLOPT_HTTPHEADER] = array_merge($opts['header'], $curlOptions[CURLOPT_HTTPHEADER]); } - $curlOptions[CURLOPT_RETURNTRANSFER] = true; - $curlOptions[CURLOPT_USERAGENT] = $this->getUserAgent(); - - $range = intval($this->config->get('system', 'curl_range_bytes', 0)); - - if ($range > 0) { - $curlOptions[CURLOPT_RANGE] = '0-' . $range; - } - - // Without this setting it seems as if some webservers send compressed content - // This seems to confuse curl so that it shows this uncompressed. - /// @todo We could possibly set this value to "gzip" or something similar - $curlOptions[CURLOPT_ENCODING] = ''; + $curlOptions[CURLOPT_USERAGENT] = $this->userAgent; if (!empty($opts['headers'])) { $this->logger->notice('Wrong option \'headers\' used.'); $curlOptions[CURLOPT_HTTPHEADER] = array_merge($opts['headers'], $curlOptions[CURLOPT_HTTPHEADER]); } - if (!empty($opts['nobody'])) { - $curlOptions[CURLOPT_NOBODY] = $opts['nobody']; - } - - $curlOptions[CURLOPT_CONNECTTIMEOUT] = 10; - if (!empty($opts['timeout'])) { $curlOptions[CURLOPT_TIMEOUT] = $opts['timeout']; - } else { - $curl_time = $this->config->get('system', 'curl_timeout', 60); - $curlOptions[CURLOPT_TIMEOUT] = intval($curl_time); } - // by default we will allow self-signed certs - // but you can override this - - $check_cert = $this->config->get('system', 'verifyssl'); - $curlOptions[CURLOPT_SSL_VERIFYPEER] = ($check_cert) ? true : false; - - if ($check_cert) { - $curlOptions[CURLOPT_SSL_VERIFYHOST] = 2; - } - - $proxy = $this->config->get('system', 'proxy'); - - if (!empty($proxy)) { - $curlOptions[CURLOPT_HTTPPROXYTUNNEL] = 1; - $curlOptions[CURLOPT_PROXY] = $proxy; - $proxyuser = $this->config->get('system', 'proxyuser'); - - if (!empty($proxyuser)) { - $curlOptions[CURLOPT_PROXYUSERPWD] = $proxyuser; - } - } - - if ($this->config->get('system', 'ipv4_resolve', false)) { - $curlOptions[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4; - } - - $logger = $this->logger; - - $onRedirect = function( - RequestInterface $request, - ResponseInterface $response, - UriInterface $uri - ) use ($logger) { - $logger->notice('Curl redirect.', ['url' => $request->getUri(), 'to' => $uri]); - }; - $onHeaders = function (ResponseInterface $response) use ($opts) { if (!empty($opts['content_length']) && $response->getHeaderLine('Content-Length') > $opts['content_length']) { @@ -201,20 +126,11 @@ class HTTPRequest implements IHTTPRequest } }; - $client = new Client([ - 'allow_redirect' => [ - 'max' => 8, - 'on_redirect' => $onRedirect, - 'track_redirect' => true, - 'strict' => true, - 'referer' => true, - ], - 'on_headers' => $onHeaders, - 'curl' => $curlOptions - ]); - try { - $response = $client->get($url); + $response = $this->client->$method($url, [ + 'on_headers' => $onHeaders, + 'curl' => $curlOptions, + ]); return new GuzzleResponse($response, $url); } catch (TransferException $exception) { if ($exception instanceof RequestException && @@ -228,6 +144,23 @@ class HTTPRequest implements IHTTPRequest } } + /** {@inheritDoc} + * + * @throws HTTPException\InternalServerErrorException + */ + public function head(string $url, array $opts = []) + { + return $this->request('head', $url, $opts); + } + + /** + * {@inheritDoc} + */ + public function get(string $url, array $opts = []) + { + return $this->request('get', $url, $opts); + } + /** * {@inheritDoc} * @@ -262,7 +195,7 @@ class HTTPRequest implements IHTTPRequest curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $params); - curl_setopt($ch, CURLOPT_USERAGENT, $this->getUserAgent()); + curl_setopt($ch, CURLOPT_USERAGENT, $this->userAgent); if ($this->config->get('system', 'ipv4_resolve', false)) { curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); @@ -376,7 +309,7 @@ class HTTPRequest implements IHTTPRequest curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); curl_setopt($ch, CURLOPT_TIMEOUT, 10); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_USERAGENT, $this->getUserAgent()); + curl_setopt($ch, CURLOPT_USERAGENT, $this->userAgent); curl_exec($ch); $curl_info = @curl_getinfo($ch); @@ -421,7 +354,7 @@ class HTTPRequest implements IHTTPRequest curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); curl_setopt($ch, CURLOPT_TIMEOUT, 10); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_USERAGENT, $this->getUserAgent()); + curl_setopt($ch, CURLOPT_USERAGENT, $this->userAgent); $body = curl_exec($ch); curl_close($ch); @@ -485,17 +418,4 @@ class HTTPRequest implements IHTTPRequest ] ); } - - /** - * {@inheritDoc} - */ - public function getUserAgent() - { - return - FRIENDICA_PLATFORM . " '" . - FRIENDICA_CODENAME . "' " . - FRIENDICA_VERSION . '-' . - DB_UPDATE_VERSION . '; ' . - $this->baseUrl; - } } diff --git a/src/Network/IHTTPRequest.php b/src/Network/IHTTPClient.php similarity index 96% rename from src/Network/IHTTPRequest.php rename to src/Network/IHTTPClient.php index b496c7feb..8fa5285d2 100644 --- a/src/Network/IHTTPRequest.php +++ b/src/Network/IHTTPClient.php @@ -24,7 +24,7 @@ namespace Friendica\Network; /** * Interface for calling HTTP requests and returning their responses */ -interface IHTTPRequest +interface IHTTPClient { /** * Fetches the content of an URL @@ -114,11 +114,4 @@ interface IHTTPRequest * @todo Remove the $fetchbody parameter that generates an extraneous HEAD request */ public function finalUrl(string $url, int $depth = 1, bool $fetchbody = false); - - /** - * Returns the current UserAgent as a String - * - * @return string the UserAgent as a String - */ - public function getUserAgent(); } diff --git a/static/dependencies.config.php b/static/dependencies.config.php index f07a61807..2068b6b1a 100644 --- a/static/dependencies.config.php +++ b/static/dependencies.config.php @@ -220,8 +220,11 @@ return [ ['getBackend', [], Dice::CHAIN_CALL], ], ], - Network\IHTTPRequest::class => [ - 'instanceOf' => Network\HTTPRequest::class, + Network\IHTTPClient::class => [ + 'instanceOf' => Factory\HTTPClientFactory::class, + 'call' => [ + ['createClient', [], Dice::CHAIN_CALL], + ], ], Factory\Api\Mastodon\Error::class => [ 'constructParams' => [ @@ -232,5 +235,5 @@ return [ 'constructParams' => [ [Dice::INSTANCE => Util\ReversedFileReader::class], ] - ] + ], ]; diff --git a/tests/datasets/curl/image.content b/tests/datasets/curl/image.content new file mode 100644 index 000000000..eea1b7e46 --- /dev/null +++ b/tests/datasets/curl/image.content @@ -0,0 +1,81 @@ +�PNG + +���Bh[ �]\��|�;?�[����8�h �p&�&Xw^�v��ނ����<����W����oBk�%X<�W���M�z���g���������'OǦ�ȖʹK� ��.x�U +������O?�؎dA�,��ú����� �u� P�*8z�}y�W���ZfG��A h�`1pO)x��+�:1#1���>��'�����K�=�E�Ah��� �|��B��A �l�s�����o}����!�x�� �Q� n�z�u���kud@fd@Ȍ� ���=v�����i�U�#hc ��w��ڰ)dd&f@f0�D�]x~��7����o�,�# �x� ,e�_���7\;�ߥ#M��L�Ā����X�#c��_�����?�qDFa)�~��|���o�X��Ȭ����00 �A$� +��~����������’��� ?�S7"1X� + 0�` ƂE���͓w��/�!�%N� ,:B��t��VG���� �٤�e�����v}��}�_}�ɩZ����v�b��.���Vk!#C��ݟI� F' 3 |����t٘�c�`l�ݖ�0�X�L�f���XTG'f>�W���'��p�x�=:Ȣ��$�B�����M�J����0���'��1q$ˤ=���%� +�@?x�����}i���*Hk-A�3�$X ����wߡ р2�L�6��T�$�T�S �R �Af Td�G����?z��O�XX��W:M� �}�m׭�W��f�0���H��lb; 2���*���d�C��t��>��]�C��@c�?�g�� �W����#҆ �1ȆL\�$��jd=���rv��x�a ����3��'���G_y�� �M�`�3�\Љ� �o�m���1h�C�Li�GI�2��? >��<}��?}��w�X%D��� �N,�.�x��zH�� ����I|op�\h��>� +�����$� ��������}y���'��+K���a���T��_���cS"1R�(D���(!*P�� ��s��d)rÅ��6 Ǒ&�d�>��_�*�[��2�;�z����S�w���{�5�恠��p�U�M^,,@���o|�;O�,��l�{DM��D�\�� +����������}go13.\ A���@;�z�mj� b ز��+�~fsߵC�v_EA��,}�����¿��BWA[��P+�e�t'�1�.�=\'�[!S^�G�8[`xW9I*��)���Vu���^߽��o��ܐ?��Y �����|��^��@�Qd�!)�Y�����g�f!Ʃ � �<$b�X[�� "��^���=��������%��3��: BǑG�{?�������H��٦��!�#�6���5(��J�)��JT�f�ݓ�w�������{��Ҫ���5�, �����>�'��ӗY����2*elԼ刡s�8c+)�AJ�s�T�M{q��>� +4XT�%��DCEo������'���=��IJ���K�� X ��x߿z$(x�W�T����,"���Z�� + e��T�_���'�R!tiY���� �9�A!�"Ac-'W��=�5� +L��ZT33z^� /(BO!�g�� �>0y#���� ��!)J�灧Py蹧�y񂏁���G���J�� ��{T���9�Q,)�`�z�o<�����I�F��e9�!e�ALC$7!�fP +������Vt�v?t�HW�_=ҷq���K� ec_��8��Z�ku�׹V7�:�j�^3�:�j\��j�T+�R��\��j�T�P��j̈́!�!��& +8�$ĸ��>�/�kvK�e#!" 9EC�G����G�oR7 � *�X�B +*�T�b�J%*�T�R }� � + +Hn,Fw1�J72c�F+�nnL���_��Jc|��Y!�r�ZM)T +�� + �E��W��aO�Z��/��/����e��_����^���n�x>,1�u�D�����؃����+���׬����w_��u˯[7|���,�3��9 M�Ƨ���x4>��'�䤞��&��D4>��O�鲙��J�T�z� ���( D7�"��ζ�V��+y��ZF �3�� 0�T�R KEohп�2o� +�6o6n,\�1X��XF�s���Q1�ώ� X��D��G^���=�O�ӧM�€F@H˖a-�zġ�Z�k�[���Ѱ�{������"����i��  +�U*�[�^l�X�r��yC�fM�r����-�]%,��/X\�M~����;L���:3��� +�Y�'�.!�_� �x�����mO�������23�)֞bf��l@[5s��&�Ő�м0��`�Hsrd ���U��(\��p���k��������8ڬ�^~.!KB� ���������@��l<������ (6�� S\�� \׳jY�'� C �B� ֯+]{M�u�����q��_��'�x�Y +���_@���1`��54/6��J; +�� N��~�tA� s�O� +5�lX_�fK�u�u���`�o���E���R,d����?�?�y&6���x���>�I �k���3ل5��5G��NI��1�8 �J%���K7�X��ҵ�x#+Пs�!�� ����5�FS���~����L 3� P�<��#`�tj`��#� +=���Y���˵]�,Y��!���w��Lxp�M 5�F�)��� ������2q<�SͶ��FS��adE����{�=���\6k?Q|�K^� �z����=bN� 0I�s� +0�&�Yˋ���VE�6K�]��ZK�zhj55��{��}ok�V�nxIClj�^:a>D� To��9��1�)�}ÜN��h^ig^�Sԥ#�6{ ���g-�i�l W� P��������������Β� �,��|eߛ~�럁�2�>3W� �hV�u���-��/`X⾡�^3\�b1��e��s�oy0izgm�(�lYR�P�`td��7>k*Ӡ�����& +!�8 MqX�zdta��Ȅ���״�k�j�i���40.�Txct��Jݽ�i����}��ًܸg�XX ������u�۟�z��u�;���@ +jt;��C~�ߥ�zz�t�U#����=��v���j�*J���9 �?�* �Y�q[�4�e BN�ߜ��G���m�D�Y�f].���F�#��a� +d�" �5 (n������ߓ��?�{e0��7Btb�4�C$D����:Xߐk;@3S^�k����^�V�J���`��]P�����S�$�3q�P�7����v="r��D !0�B��;� F��R�y�����v͢Sd�S��MR�]7ލ��'���ǜ���y�l�1$F&D��� ��H�mf�5�Ev?#�A��_�e�=��.*"X� P��.6f��o��DB�[##X*�'������v&$D�Gqf�����6�����o]$G�`D��]3������S�F/��<�qGD�T)n���\� 0"�FDm �v� ����Q58��3?"X� ̌�]7܍�/?�� }��F����BDD D�^�ls � �Ǥ]&�M+]����~�ODX��`�Gb:@�����g;�[R�"2�ޢ5�00�:����f�J�" D����p�0"X��80�ūoF�8���5�۫�p!0���A{���k�"X#��S�P����+LJOM�jUI�b�"#��p� ���ba�ۏr�  ""�!��������!�tB�qN)0 rM�k���Ę>u*�m��D���_Zl��_�������^$B? �C�G/@��G�#�ߧ�W��A@������� +����JE�E)�j �������=�>a�#��@�� �Ⱥ��~����� }}=?�,���ꔏ�G������v����a��A���� c�Fl��=�k�P�U� \c|A�8�Kx�x���{�/��Σz�$d������l�9 `�v� "`HhP3"*���PCR���O��Q��OƗ@.��%:o�H�[���j#�� @�C�C��������5�|���x�{��� +<(��J +��E�����r�3 B�W�1|JCZ � + +�K�O��;@��������'#,rD��� �ozO��0�^zG߇�j����O6��$�#�����%’�] +K +K>����,��5�w_��� 1� *���咽�ʋ?`��I��K��Y.�0$F���� i7!zP��tN�����=Vm�nm��EX���uA`@��M=�<��G�a��^��C��Ń�=���������z +��;�6��~��s[_y�d���K�?��W�~�X�7� x*�f�D�����J��� +l%}��vxh��َ�}��sWa�Uū�tj�> �xժ��p��Ĵ��� +h <��W��_|��#?����F V�wM5d���!\2D�.vop��zs4~�OO�������"L�Ԇx�%7��.�Lv*jBL"���1D�q���ܽ�g߃��{\xÃ}_�֋���4h�K a�!��_}r�_<����}�+�u��C ֥��7�p��9�'��"� +]� +֯'Vn�ƫV����< p<-�-���Z�28��Wlx���/=�����w`|f���r� b��_�p��j?��[((F'��CzG@d�� qX�� tr�2N��l�&=XS�*�-�6&��u�?q�^ݹs�{�M~ v�lD�m�<��ן7Ơ�81�����Ɋ��S���t���G?�����8�VvuҖr�"��^0q`����ek��q.O�K.����\��餉�E��r��� �Y[�lYS˾i���0��������s�vE���ryo���Ƕ��H�) ��Q�fl�4�A!���=���|���O����5�%�ka�ٵ8��{�� h���c_��<�c8�����"�B�4GGGt���&�8�`c�f����i��1��#Ә�u��qQ�����x͖��\ ���ڟ?�ҾbwQ#E��F� + ��r�o\��Ϡ�<��������TY|M��4�&�Dq*� `��Mœ���qJ�&B@?��t̀4����W,���=�i�'���� VǑ� �7l��? +��I|��/��Ɍ&�"�ZH��ie� Q��I_����� ��u�74�u�͙�w �e��գ˾��IJ?Ty����8 +F�өdJDgmY��deˆ�b��9��Բ��v0�f�;�nkf�2��_m��iͮS���f�ቪ� ́��X9::��cըqJϤG�}��? � ��X,��� +mx�����Λc�ž�LdfB�͉�>>��3щJtlF���U�TM�#.�f&4H&�ĸ�ACv~"^ +�u��w�v�1R�iEaM�O-x�v�E��c*3�m?�l}ZO�!�a+=�����t��b�j�49^�8�˰���4�&��s*�k9�\ ֯����6o����R�̩���񩚞���>6��τ�+�DE���Su3Uӓu3]7u+Uk�{j�#U� 7�(ݻ�t���U� +\8{�j�9;�t94Su���S�CK�4�3c�\����z!<��T I�4�&b�`�����a��j�Kת�����n�Mw��l{�.�#�<��.5"XB>�Ǝ�v�T���>u��$0 �6&��L�*p��� hlL� +��N�a�D��uS��{ӻ���W��l�r'5�}j��wkm�+�ɣ� s�2�&��TP[Q��.O�������*���%�"XB�1&<�������N=} +d�K��k��-������:���EKX���᡽�}�£���cs!]�R��t��ʁw�S�7�93� M�` ��YŃ�<�]��Jt�)O�a@����:�z����¦k�}�BkD��%�����S߿#:�_��l� +�ud���]�� ;R�����h��L�t���������| +"X� �,Ar��5,Qr�'���,�8�3.����_��!R"|�C�����?��.���lE��w���#d"�� +2��9��L�deb�$S"7Y� +g[OMO�#X�BA�N�v��e-%`� :7̲����}�>m�E���b�ԧ�N��[�#�D ���[��\-�C����Eι�Y��e���/w�������˳��tEK��1���ZS��_�T� ��܊��W� ��Hζ�fd�+�hYB�s��g��摘Vv�>@��mJ;�f�ø�BR�ó�s�GH��gvrͥqM��I��L\��k�F�����܂��c[έ�lm�D�2��v��{�p�O��B9LJZ +,-��,�H�%�$�� 'tsa_b', + 'Content-Type' => 'image/png', + 'Cache-Control' => 'max-age=604800, must-revalidate', + 'Content-Length' => 24875, + ], file_get_contents(__DIR__ . '/../../datasets/curl/image.content')) + ]); + + $config = \Mockery::mock(IConfig::class); + $config->shouldReceive('get')->with('system', 'curl_range_bytes', 0)->once()->andReturn(null); + $config->shouldReceive('get')->with('system', 'verifyssl')->once(); + $config->shouldReceive('get')->with('system', 'proxy')->once(); + $config->shouldReceive('get')->with('system', 'ipv4_resolve', false)->once()->andReturnFalse(); + $config->shouldReceive('get')->with('system', 'blocklist', [])->once()->andReturn([]); + + $baseUrl = \Mockery::mock(BaseURL::class); + $baseUrl->shouldReceive('get')->andReturn('http://friendica.local'); + + $profiler = \Mockery::mock(Profiler::class); + $profiler->shouldReceive('startRecording')->andReturnTrue(); + $profiler->shouldReceive('stopRecording')->andReturnTrue(); + + $httpRequest = new HTTPRequest(new NullLogger(), $profiler, $config, $baseUrl); + + self::assertInstanceOf(IHTTPRequest::class, $httpRequest); + + $dice = \Mockery::mock(Dice::class); + $dice->shouldReceive('create')->with(IHTTPRequest::class)->andReturn($httpRequest)->once(); + $dice->shouldReceive('create')->with(BaseURL::class)->andReturn($baseUrl); + $dice->shouldReceive('create')->with(IConfig::class)->andReturn($config)->once(); + + DI::init($dice); + + print_r(Images::getInfoFromURL('https://pbs.twimg.com/profile_images/2365515285/9re7kx4xmc0eu9ppmado.png')); + } +} From 736277dcf05c676b1fb10812ee5cf6e2fe73bb61 Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 23 Aug 2021 13:44:46 +0200 Subject: [PATCH 02/19] Refactor HTTPClient::get() / ::head() --- src/Factory/HTTPClientFactory.php | 19 ++++++++++-------- src/Network/HTTPClient.php | 32 ++++++++++++++++--------------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/Factory/HTTPClientFactory.php b/src/Factory/HTTPClientFactory.php index 604b6fd62..b17e06532 100644 --- a/src/Factory/HTTPClientFactory.php +++ b/src/Factory/HTTPClientFactory.php @@ -54,6 +54,12 @@ class HTTPClientFactory extends BaseFactory $logger->notice('Curl redirect.', ['url' => $request->getUri(), 'to' => $uri]); }; + $userAgent = FRIENDICA_PLATFORM . " '" . + FRIENDICA_CODENAME . "' " . + FRIENDICA_VERSION . '-' . + DB_UPDATE_VERSION . '; ' . + $this->baseUrl->get(); + $guzzle = new Client([ RequestOptions::ALLOW_REDIRECTS => [ 'max' => 8, @@ -72,16 +78,13 @@ class HTTPClientFactory extends BaseFactory RequestOptions::TIMEOUT => $this->config->get('system', 'curl_timeout', 60), // by default we will allow self-signed certs // but you can override this - RequestOptions::VERIFY => (bool)$this->config->get('system', 'verifyssl'), - RequestOptions::PROXY => $proxy, + RequestOptions::VERIFY => (bool)$this->config->get('system', 'verifyssl'), + RequestOptions::PROXY => $proxy, + RequestOptions::HEADERS => [ + 'User-Agent' => $userAgent, + ], ]); - $userAgent = FRIENDICA_PLATFORM . " '" . - FRIENDICA_CODENAME . "' " . - FRIENDICA_VERSION . '-' . - DB_UPDATE_VERSION . '; ' . - $this->baseUrl->get(); - return new HTTPClient($logger, $this->profiler, $this->config, $userAgent, $guzzle); } } diff --git a/src/Network/HTTPClient.php b/src/Network/HTTPClient.php index e20ddd3c9..3011696b8 100644 --- a/src/Network/HTTPClient.php +++ b/src/Network/HTTPClient.php @@ -60,7 +60,10 @@ class HTTPClient implements IHTTPClient $this->client = $client; } - protected function request(string $method, string $url, array $opts = []) + /** + * @throws HTTPException\InternalServerErrorException + */ + protected function request(string $method, string $url, array $opts = []): IHTTPResult { $this->profiler->startRecording('network'); @@ -96,30 +99,32 @@ class HTTPClient implements IHTTPClient $conf = []; if (!empty($opts['cookiejar'])) { - $jar = new FileCookieJar($opts['cookiejar']); + $jar = new FileCookieJar($opts['cookiejar']); $conf[RequestOptions::COOKIES] = $jar; } + $header = []; + if (!empty($opts['accept_content'])) { - array_push($curlOptions[CURLOPT_HTTPHEADER], 'Accept: ' . $opts['accept_content']); + array_push($header, 'Accept: ' . $opts['accept_content']); } if (!empty($opts['header'])) { - $curlOptions[CURLOPT_HTTPHEADER] = array_merge($opts['header'], $curlOptions[CURLOPT_HTTPHEADER]); + $header = array_merge($opts['header'], $header); } - $curlOptions[CURLOPT_USERAGENT] = $this->userAgent; - if (!empty($opts['headers'])) { $this->logger->notice('Wrong option \'headers\' used.'); - $curlOptions[CURLOPT_HTTPHEADER] = array_merge($opts['headers'], $curlOptions[CURLOPT_HTTPHEADER]); + $header = array_merge($opts['headers'], $header); } + $conf[RequestOptions::HEADERS] = array_merge($this->client->getConfig(RequestOptions::HEADERS), $header); + if (!empty($opts['timeout'])) { - $curlOptions[CURLOPT_TIMEOUT] = $opts['timeout']; + $conf[RequestOptions::TIMEOUT] = $opts['timeout']; } - $onHeaders = function (ResponseInterface $response) use ($opts) { + $conf[RequestOptions::ON_HEADERS] = function (ResponseInterface $response) use ($opts) { if (!empty($opts['content_length']) && $response->getHeaderLine('Content-Length') > $opts['content_length']) { throw new TransferException('The file is too big!'); @@ -127,10 +132,7 @@ class HTTPClient implements IHTTPClient }; try { - $response = $this->client->$method($url, [ - 'on_headers' => $onHeaders, - 'curl' => $curlOptions, - ]); + $response = $this->client->$method($url, $conf); return new GuzzleResponse($response, $url); } catch (TransferException $exception) { if ($exception instanceof RequestException && @@ -148,7 +150,7 @@ class HTTPClient implements IHTTPClient * * @throws HTTPException\InternalServerErrorException */ - public function head(string $url, array $opts = []) + public function head(string $url, array $opts = []): IHTTPResult { return $this->request('head', $url, $opts); } @@ -156,7 +158,7 @@ class HTTPClient implements IHTTPClient /** * {@inheritDoc} */ - public function get(string $url, array $opts = []) + public function get(string $url, array $opts = []): IHTTPResult { return $this->request('get', $url, $opts); } From e576af218bc476b958a88314d4452b40014196d9 Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 23 Aug 2021 14:02:52 +0200 Subject: [PATCH 03/19] Use Guzzle for HTTPClient::post() --- src/Factory/HTTPClientFactory.php | 2 +- src/Network/HTTPClient.php | 119 +++++------------------------- 2 files changed, 20 insertions(+), 101 deletions(-) diff --git a/src/Factory/HTTPClientFactory.php b/src/Factory/HTTPClientFactory.php index b17e06532..636f8a46d 100644 --- a/src/Factory/HTTPClientFactory.php +++ b/src/Factory/HTTPClientFactory.php @@ -51,7 +51,7 @@ class HTTPClientFactory extends BaseFactory ResponseInterface $response, UriInterface $uri ) use ($logger) { - $logger->notice('Curl redirect.', ['url' => $request->getUri(), 'to' => $uri]); + $logger->notice('Curl redirect.', ['url' => $request->getUri(), 'to' => $uri, 'method' => $request->getMethod()]); }; $userAgent = FRIENDICA_PLATFORM . " '" . diff --git a/src/Network/HTTPClient.php b/src/Network/HTTPClient.php index 3011696b8..000d3c76a 100644 --- a/src/Network/HTTPClient.php +++ b/src/Network/HTTPClient.php @@ -66,6 +66,7 @@ class HTTPClient implements IHTTPClient protected 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)]); @@ -132,7 +133,16 @@ class HTTPClient implements IHTTPClient }; try { - $response = $this->client->$method($url, $conf); + switch ($method) { + case 'get': + $response = $this->client->get($url, $conf); + break; + case 'head': + $response = $this->client->head($url, $conf); + break; + default: + throw new TransferException('Invalid method'); + } return new GuzzleResponse($response, $url); } catch (TransferException $exception) { if ($exception instanceof RequestException && @@ -142,6 +152,7 @@ class HTTPClient implements IHTTPClient return new CurlResult($url, '', ['http_code' => $exception->getCode()], $exception->getCode(), ''); } } finally { + $this->logger->debug('Request stop.', ['url' => $url, 'method' => $method]); $this->profiler->stopRecording(); } } @@ -165,114 +176,22 @@ class HTTPClient implements IHTTPClient /** * {@inheritDoc} - * - * @param int $redirects The recursion counter for internal use - default 0 - * - * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - public function post(string $url, $params, array $headers = [], int $timeout = 0, &$redirects = 0) + public function post(string $url, $params, array $headers = [], int $timeout = 0): IHTTPResult { - $this->profiler->startRecording('network'); + $opts = []; - if (Network::isLocalLink($url)) { - $this->logger->info('Local link', ['url' => $url, 'callstack' => System::callstack(20)]); - } - - if (Network::isUrlBlocked($url)) { - $this->logger->info('Domain is blocked.' . ['url' => $url]); - $this->profiler->stopRecording(); - return CurlResult::createErrorCurl($url); - } - - $ch = curl_init($url); - - if (($redirects > 8) || (!$ch)) { - $this->profiler->stopRecording(); - return CurlResult::createErrorCurl($url); - } - - $this->logger->debug('Post_url: start.', ['url' => $url]); - - curl_setopt($ch, CURLOPT_HEADER, true); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, $params); - curl_setopt($ch, CURLOPT_USERAGENT, $this->userAgent); - - if ($this->config->get('system', 'ipv4_resolve', false)) { - curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); - } - - @curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); - - if (intval($timeout)) { - curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); - } else { - $curl_time = $this->config->get('system', 'curl_timeout', 60); - curl_setopt($ch, CURLOPT_TIMEOUT, intval($curl_time)); - } + $opts[RequestOptions::JSON] = $params; if (!empty($headers)) { - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + $opts['headers'] = $headers; } - $check_cert = $this->config->get('system', 'verifyssl'); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false)); - - if ($check_cert) { - @curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + if (!empty($timeout)) { + $opts[RequestOptions::TIMEOUT] = $timeout; } - $proxy = $this->config->get('system', 'proxy'); - - if (!empty($proxy)) { - curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1); - curl_setopt($ch, CURLOPT_PROXY, $proxy); - $proxyuser = $this->config->get('system', 'proxyuser'); - if (!empty($proxyuser)) { - curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxyuser); - } - } - - // don't let curl abort the entire application - // if it throws any errors. - - $s = @curl_exec($ch); - - $curl_info = curl_getinfo($ch); - - $curlResponse = new CurlResult($url, $s, $curl_info, curl_errno($ch), curl_error($ch)); - - if (!Network::isRedirectBlocked($url) && $curlResponse->isRedirectUrl()) { - $redirects++; - $this->logger->info('Post redirect.', ['url' => $url, 'to' => $curlResponse->getRedirectUrl()]); - curl_close($ch); - $this->profiler->stopRecording(); - return $this->post($curlResponse->getRedirectUrl(), $params, $headers, $redirects, $timeout); - } - - curl_close($ch); - - $this->profiler->stopRecording(); - - // Very old versions of Lighttpd don't like the "Expect" header, so we remove it when needed - if ($curlResponse->getReturnCode() == 417) { - $redirects++; - - if (empty($headers)) { - $headers = ['Expect:']; - } else { - if (!in_array('Expect:', $headers)) { - array_push($headers, 'Expect:'); - } - } - $this->logger->info('Server responds with 417, applying workaround', ['url' => $url]); - return $this->post($url, $params, $headers, $redirects, $timeout); - } - - $this->logger->debug('Post_url: End.', ['url' => $url]); - - return $curlResponse; + return $this->request('post', $url, $opts); } /** From 8385ee7a612b33c2e2af2533d922f4a8a21e6dd4 Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 23 Aug 2021 14:28:25 +0200 Subject: [PATCH 04/19] Use mattwright/urlresolver for HTTPClient::finalUrl() --- composer.json | 3 +- composer.lock | 48 ++++++++++- src/Factory/HTTPClientFactory.php | 10 ++- src/Network/HTTPClient.php | 131 ++++++------------------------ src/Network/IHTTPClient.php | 6 +- 5 files changed, 83 insertions(+), 115 deletions(-) diff --git a/composer.json b/composer.json index 2dd5dec7b..bf0559254 100644 --- a/composer.json +++ b/composer.json @@ -69,7 +69,8 @@ "npm-asset/perfect-scrollbar": "0.6.16", "npm-asset/textcomplete": "^0.18.2", "npm-asset/typeahead.js": "^0.11.1", - "minishlink/web-push": "^6.0" + "minishlink/web-push": "^6.0", + "mattwright/urlresolver": "^2.0" }, "repositories": [ { diff --git a/composer.lock b/composer.lock index 5e8f1a20a..906a681e4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7d6dee6e449da931e8fe209e61b2e78e", + "content-hash": "c9e0a9eacc23d884012042eeab01cc8b", "packages": [ { "name": "asika/simple-console", @@ -1133,6 +1133,52 @@ ], "time": "2017-07-19T15:11:19+00:00" }, + { + "name": "mattwright/urlresolver", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/mattwright/URLResolver.php.git", + "reference": "416039192cb6d9158bdacd68349bceff8739b857" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mattwright/URLResolver.php/zipball/416039192cb6d9158bdacd68349bceff8739b857", + "reference": "416039192cb6d9158bdacd68349bceff8739b857", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-mbstring": "*", + "php": ">=5.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "mattwright\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Wright", + "email": "mw@mattwright.com" + } + ], + "description": "PHP class that attempts to resolve URLs to a final, canonical link.", + "homepage": "https://github.com/mattwright/URLResolver.php", + "keywords": [ + "canonical", + "link", + "redirect", + "resolve", + "url" + ], + "time": "2019-01-18T00:59:34+00:00" + }, { "name": "michelf/php-markdown", "version": "1.9.0", diff --git a/src/Factory/HTTPClientFactory.php b/src/Factory/HTTPClientFactory.php index 636f8a46d..c1cb47541 100644 --- a/src/Factory/HTTPClientFactory.php +++ b/src/Factory/HTTPClientFactory.php @@ -10,6 +10,7 @@ use Friendica\Network\IHTTPClient; use Friendica\Util\Profiler; use GuzzleHttp\Client; use GuzzleHttp\RequestOptions; +use mattwright\URLResolver; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\UriInterface; @@ -85,6 +86,13 @@ class HTTPClientFactory extends BaseFactory ], ]); - return new HTTPClient($logger, $this->profiler, $this->config, $userAgent, $guzzle); + $resolver = new URLResolver(); + $resolver->setUserAgent($userAgent); + $resolver->setMaxRedirects(10); + $resolver->setRequestTimeout(10); + // if the file is too large then exit + $resolver->setMaxResponseDataSize(1000000); + + return new HTTPClient($logger, $this->profiler, $guzzle, $resolver); } } diff --git a/src/Network/HTTPClient.php b/src/Network/HTTPClient.php index 000d3c76a..d83b805df 100644 --- a/src/Network/HTTPClient.php +++ b/src/Network/HTTPClient.php @@ -21,9 +21,6 @@ namespace Friendica\Network; -use DOMDocument; -use DomXPath; -use Friendica\Core\Config\IConfig; use Friendica\Core\System; use Friendica\Util\Network; use Friendica\Util\Profiler; @@ -32,6 +29,7 @@ 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\LoggerInterface; @@ -44,20 +42,17 @@ class HTTPClient implements IHTTPClient private $logger; /** @var Profiler */ private $profiler; - /** @var IConfig */ - private $config; - /** @var string */ - private $userAgent; /** @var Client */ private $client; + /** @var URLResolver */ + private $resolver; - public function __construct(LoggerInterface $logger, Profiler $profiler, IConfig $config, string $userAgent, Client $client) + public function __construct(LoggerInterface $logger, Profiler $profiler, Client $client, URLResolver $resolver) { - $this->logger = $logger; - $this->profiler = $profiler; - $this->config = $config; - $this->userAgent = $userAgent; - $this->client = $client; + $this->logger = $logger; + $this->profiler = $profiler; + $this->client = $client; + $this->resolver = $resolver; } /** @@ -97,6 +92,11 @@ class HTTPClient implements IHTTPClient return CurlResult::createErrorCurl($url); } + if (Network::isRedirectBlocked($url)) { + $this->logger->info('Domain should not be redirected.', ['url' => $url]); + return CurlResult::createErrorCurl($url); + } + $conf = []; if (!empty($opts['cookiejar'])) { @@ -197,10 +197,12 @@ class HTTPClient implements IHTTPClient /** * {@inheritDoc} */ - public function finalUrl(string $url, int $depth = 1, bool $fetchbody = false) + public function finalUrl(string $url) { + $this->profiler->startRecording('network'); + if (Network::isLocalLink($url)) { - $this->logger->info('Local link', ['url' => $url, 'callstack' => System::callstack(20)]); + $this->logger->debug('Local link', ['url' => $url, 'callstack' => System::callstack(20)]); } if (Network::isUrlBlocked($url)) { @@ -215,104 +217,19 @@ class HTTPClient implements IHTTPClient $url = Network::stripTrackingQueryParams($url); - if ($depth > 10) { - return $url; - } - $url = trim($url, "'"); - $this->profiler->startRecording('network'); + // Designate a temporary file that will store cookies during the session. + // Some websites test the browser for cookie support, so this enhances results. + $this->resolver->setCookieJar(tempnam(get_temppath() , 'url_resolver-')); - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_HEADER, 1); - curl_setopt($ch, CURLOPT_NOBODY, 1); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); - curl_setopt($ch, CURLOPT_TIMEOUT, 10); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_USERAGENT, $this->userAgent); + $urlResult = $this->resolver->resolveURL($url); - curl_exec($ch); - $curl_info = @curl_getinfo($ch); - $http_code = $curl_info['http_code']; - curl_close($ch); - - $this->profiler->stopRecording(); - - if ($http_code == 0) { - return $url; + if ($urlResult->didErrorOccur()) { + throw new TransferException($urlResult->getErrorMessageString()); } - if (in_array($http_code, ['301', '302'])) { - if (!empty($curl_info['redirect_url'])) { - return $this->finalUrl($curl_info['redirect_url'], ++$depth, $fetchbody); - } elseif (!empty($curl_info['location'])) { - return $this->finalUrl($curl_info['location'], ++$depth, $fetchbody); - } - } - - // Check for redirects in the meta elements of the body if there are no redirects in the header. - if (!$fetchbody) { - return $this->finalUrl($url, ++$depth, true); - } - - // if the file is too large then exit - if ($curl_info["download_content_length"] > 1000000) { - return $url; - } - - // if it isn't a HTML file then exit - if (!empty($curl_info["content_type"]) && !strstr(strtolower($curl_info["content_type"]), "html")) { - return $url; - } - - $this->profiler->startRecording('network'); - - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_HEADER, 0); - curl_setopt($ch, CURLOPT_NOBODY, 0); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); - curl_setopt($ch, CURLOPT_TIMEOUT, 10); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_USERAGENT, $this->userAgent); - - $body = curl_exec($ch); - curl_close($ch); - - $this->profiler->stopRecording(); - - if (trim($body) == "") { - return $url; - } - - // Check for redirect in meta elements - $doc = new DOMDocument(); - @$doc->loadHTML($body); - - $xpath = new DomXPath($doc); - - $list = $xpath->query("//meta[@content]"); - foreach ($list as $node) { - $attr = []; - if ($node->attributes->length) { - foreach ($node->attributes as $attribute) { - $attr[$attribute->name] = $attribute->value; - } - } - - if (@$attr["http-equiv"] == 'refresh') { - $path = $attr["content"]; - $pathinfo = explode(";", $path); - foreach ($pathinfo as $value) { - if (substr(strtolower($value), 0, 4) == "url=") { - return $this->finalUrl(substr($value, 4), ++$depth); - } - } - } - } - - return $url; + return $urlResult->getURL(); } /** diff --git a/src/Network/IHTTPClient.php b/src/Network/IHTTPClient.php index 8fa5285d2..180908eed 100644 --- a/src/Network/IHTTPClient.php +++ b/src/Network/IHTTPClient.php @@ -104,14 +104,10 @@ interface IHTTPClient * through HTTP code or meta refresh tags. Stops after 10 redirections. * * @param string $url A user-submitted URL - * @param int $depth The current redirection recursion level (internal) - * @param bool $fetchbody Wether to fetch the body or not after the HEAD requests * * @return string A canonical URL * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @see ParseUrl::getSiteinfo - * - * @todo Remove the $fetchbody parameter that generates an extraneous HEAD request */ - public function finalUrl(string $url, int $depth = 1, bool $fetchbody = false); + public function finalUrl(string $url); } From 3eb2abdb2aec830d0d17d1e14695273dbcde5328 Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 23 Aug 2021 15:47:41 +0200 Subject: [PATCH 05/19] Adapt tests --- src/DI.php | 11 ++++ src/Network/HTTPClient.php | 5 -- src/Network/IHTTPClient.php | 1 - tests/datasets/curl/image.content | Bin 16946 -> 24875 bytes tests/src/Core/InstallerTest.php | 10 +-- tests/src/Core/StorageManagerTest.php | 6 +- tests/src/Network/HTTPRequestTest.php | 87 +++++++++++++++++--------- tests/src/Util/ImagesTest.php | 13 ++++ 8 files changed, 90 insertions(+), 43 deletions(-) create mode 100644 tests/src/Util/ImagesTest.php diff --git a/src/DI.php b/src/DI.php index 5c56b45e1..7f6f28f33 100644 --- a/src/DI.php +++ b/src/DI.php @@ -39,6 +39,17 @@ abstract class DI self::$dice = $dice; } + /** + * Returns a clone of the current dice instance + * This usefull for overloading the current instance with mocked methods during tests + * + * @return Dice + */ + public static function getDice() + { + return clone self::$dice; + } + // // common instances // diff --git a/src/Network/HTTPClient.php b/src/Network/HTTPClient.php index d83b805df..c90dbc896 100644 --- a/src/Network/HTTPClient.php +++ b/src/Network/HTTPClient.php @@ -92,11 +92,6 @@ class HTTPClient implements IHTTPClient return CurlResult::createErrorCurl($url); } - if (Network::isRedirectBlocked($url)) { - $this->logger->info('Domain should not be redirected.', ['url' => $url]); - return CurlResult::createErrorCurl($url); - } - $conf = []; if (!empty($opts['cookiejar'])) { diff --git a/src/Network/IHTTPClient.php b/src/Network/IHTTPClient.php index 180908eed..c8611e8f5 100644 --- a/src/Network/IHTTPClient.php +++ b/src/Network/IHTTPClient.php @@ -107,7 +107,6 @@ interface IHTTPClient * * @return string A canonical URL * @throws \Friendica\Network\HTTPException\InternalServerErrorException - * @see ParseUrl::getSiteinfo */ public function finalUrl(string $url); } diff --git a/tests/datasets/curl/image.content b/tests/datasets/curl/image.content index eea1b7e46350ecea68f98f1d84f0f8786fddf9aa..98285c3e11ad2ad3a7e0610ee356cea43f7fbb49 100644 GIT binary patch literal 24875 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'test Error'); // Mocking the CURL Request - $networkMock = Mockery::mock(IHTTPRequest::class); + $networkMock = Mockery::mock(IHTTPClient::class); $networkMock ->shouldReceive('fetchFull') ->with('https://test/install/testrewrite') @@ -342,7 +342,7 @@ class InstallerTest extends MockedTest ->andReturn($IHTTPResult); $this->dice->shouldReceive('create') - ->with(IHTTPRequest::class) + ->with(IHTTPClient::class) ->andReturn($networkMock); DI::init($this->dice); @@ -378,7 +378,7 @@ class InstallerTest extends MockedTest ->andReturn('204'); // Mocking the CURL Request - $networkMock = Mockery::mock(IHTTPRequest::class); + $networkMock = Mockery::mock(IHTTPClient::class); $networkMock ->shouldReceive('fetchFull') ->with('https://test/install/testrewrite') @@ -389,7 +389,7 @@ class InstallerTest extends MockedTest ->andReturn($IHTTPResultW); $this->dice->shouldReceive('create') - ->with(IHTTPRequest::class) + ->with(IHTTPClient::class) ->andReturn($networkMock); DI::init($this->dice); diff --git a/tests/src/Core/StorageManagerTest.php b/tests/src/Core/StorageManagerTest.php index 9e8e3aa2c..93fc0b664 100644 --- a/tests/src/Core/StorageManagerTest.php +++ b/tests/src/Core/StorageManagerTest.php @@ -34,7 +34,7 @@ use Friendica\Factory\ConfigFactory; use Friendica\Model\Config\Config; use Friendica\Model\Storage; use Friendica\Core\Session; -use Friendica\Network\HTTPRequest; +use Friendica\Network\HTTPClient; use Friendica\Test\DatabaseTest; use Friendica\Test\Util\Database\StaticDatabase; use Friendica\Test\Util\VFSTrait; @@ -55,7 +55,7 @@ class StorageManagerTest extends DatabaseTest private $logger; /** @var L10n */ private $l10n; - /** @var HTTPRequest */ + /** @var HTTPClient */ private $httpRequest; protected function setUp(): void @@ -84,7 +84,7 @@ class StorageManagerTest extends DatabaseTest $this->l10n = \Mockery::mock(L10n::class); - $this->httpRequest = \Mockery::mock(HTTPRequest::class); + $this->httpRequest = \Mockery::mock(HTTPClient::class); } /** diff --git a/tests/src/Network/HTTPRequestTest.php b/tests/src/Network/HTTPRequestTest.php index 4fd0ab179..230dc3eec 100644 --- a/tests/src/Network/HTTPRequestTest.php +++ b/tests/src/Network/HTTPRequestTest.php @@ -3,58 +3,87 @@ namespace Friendica\Test\src\Network; use Dice\Dice; -use Friendica\App\BaseURL; -use Friendica\Core\Config\IConfig; use Friendica\DI; -use Friendica\Network\HTTPRequest; -use Friendica\Network\IHTTPRequest; +use Friendica\Network\HTTPClient; +use Friendica\Network\IHTTPClient; use Friendica\Test\MockedTest; use Friendica\Util\Images; use Friendica\Util\Profiler; +use GuzzleHttp\Client; use GuzzleHttp\Handler\MockHandler; +use GuzzleHttp\HandlerStack; use GuzzleHttp\Psr7\Response; +use mattwright\URLResolver; use Psr\Log\NullLogger; require_once __DIR__ . '/../../../static/dbstructure.config.php'; class HTTPRequestTest extends MockedTest { - public function testImageFetch() + /** @var HandlerStack */ + protected $handler; + + protected function setUp(): void { - $mock = new MockHandler([ - new Response(200, [ - 'Server' => 'tsa_b', - 'Content-Type' => 'image/png', - 'Cache-Control' => 'max-age=604800, must-revalidate', - 'Content-Length' => 24875, - ], file_get_contents(__DIR__ . '/../../datasets/curl/image.content')) - ]); + parent::setUp(); - $config = \Mockery::mock(IConfig::class); - $config->shouldReceive('get')->with('system', 'curl_range_bytes', 0)->once()->andReturn(null); - $config->shouldReceive('get')->with('system', 'verifyssl')->once(); - $config->shouldReceive('get')->with('system', 'proxy')->once(); - $config->shouldReceive('get')->with('system', 'ipv4_resolve', false)->once()->andReturnFalse(); - $config->shouldReceive('get')->with('system', 'blocklist', [])->once()->andReturn([]); + $this->handler = HandlerStack::create(); - $baseUrl = \Mockery::mock(BaseURL::class); - $baseUrl->shouldReceive('get')->andReturn('http://friendica.local'); + $client = new Client(['handler' => $this->handler]); + + $resolver = \Mockery::mock(URLResolver::class); $profiler = \Mockery::mock(Profiler::class); $profiler->shouldReceive('startRecording')->andReturnTrue(); $profiler->shouldReceive('stopRecording')->andReturnTrue(); - $httpRequest = new HTTPRequest(new NullLogger(), $profiler, $config, $baseUrl); + $httpClient = new HTTPClient(new NullLogger(), $profiler, $client, $resolver); - self::assertInstanceOf(IHTTPRequest::class, $httpRequest); + $dice = DI::getDice(); + $newDice = \Mockery::mock($dice)->makePartial(); + $newDice->shouldReceive('create')->with(IHTTPClient::class)->andReturn($httpClient); + DI::init($newDice); + } - $dice = \Mockery::mock(Dice::class); - $dice->shouldReceive('create')->with(IHTTPRequest::class)->andReturn($httpRequest)->once(); - $dice->shouldReceive('create')->with(BaseURL::class)->andReturn($baseUrl); - $dice->shouldReceive('create')->with(IConfig::class)->andReturn($config)->once(); + public function dataImages() + { + return [ + 'image1' => [ + 'url' => 'https://pbs.twimg.com/profile_images/2365515285/9re7kx4xmc0eu9ppmado.png', + 'headers' => [ + 'Server' => 'tsa_b', + 'Content-Type' => 'image/png', + 'Cache-Control' => 'max-age=604800,must-revalidate', + 'Last-Modified' => 'Thu,04Nov201001:42:54GMT', + 'Content-Length' => '24875', + 'Access-Control-Allow-Origin' => '*', + 'Access-Control-Expose-Headers' => 'Content-Length', + 'Date' => 'Mon,23Aug202112:39:00GMT', + 'Connection' => 'keep-alive', + ], + 'data' => file_get_contents(__DIR__ . '/../../datasets/curl/image.content'), + 'assertion' => [ + '0' => '400', + '1' => '400', + '2' => '3', + '3' => 'width="400" height="400"', + 'bits' => '8', + 'mime' => 'image/png', + 'size' => '24875', + ] + ] + ]; + } - DI::init($dice); + /** + * @dataProvider dataImages + */ + public function testGetInfoFromURL(string $url, array $headers, string $data, array $assertion) + { + $this->handler->setHandler(new MockHandler([ + new Response(200, $headers, $data), + ])); - print_r(Images::getInfoFromURL('https://pbs.twimg.com/profile_images/2365515285/9re7kx4xmc0eu9ppmado.png')); + self::assertArraySubset($assertion, Images::getInfoFromURL($url)); } } diff --git a/tests/src/Util/ImagesTest.php b/tests/src/Util/ImagesTest.php new file mode 100644 index 000000000..d35f9c4ec --- /dev/null +++ b/tests/src/Util/ImagesTest.php @@ -0,0 +1,13 @@ + Date: Mon, 23 Aug 2021 20:09:37 +0200 Subject: [PATCH 06/19] Fixing https://github.com/friendica/friendica/issues/10473#issuecomment-903993836 --- src/Network/HTTPClient.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Network/HTTPClient.php b/src/Network/HTTPClient.php index c90dbc896..d2ea596f1 100644 --- a/src/Network/HTTPClient.php +++ b/src/Network/HTTPClient.php @@ -31,6 +31,7 @@ use GuzzleHttp\Exception\TransferException; use GuzzleHttp\RequestOptions; use mattwright\URLResolver; use Psr\Http\Message\ResponseInterface; +use Psr\Log\InvalidArgumentException; use Psr\Log\LoggerInterface; /** @@ -146,6 +147,9 @@ class HTTPClient implements IHTTPClient } else { return new CurlResult($url, '', ['http_code' => $exception->getCode()], $exception->getCode(), ''); } + } catch (InvalidArgumentException $argumentException) { + $this->logger->info('Invalid Argument for HTTP call.', ['url' => $url, 'method' => $method, 'exception' => $argumentException]); + return new CurlResult($url, '', ['http_code' => $argumentException->getCode()], $argumentException->getCode(), $argumentException->getMessage()); } finally { $this->logger->debug('Request stop.', ['url' => $url, 'method' => $method]); $this->profiler->stopRecording(); From c7f54d83ceb7ee119020254e81a18ebe9aca6ec0 Mon Sep 17 00:00:00 2001 From: Philipp Date: Tue, 24 Aug 2021 13:19:17 +0200 Subject: [PATCH 07/19] Introduce DiceTestTrait for partial mocking DI:: calls --- tests/DiceTestTrait.php | 70 +++++++++++++++++++++ tests/src/Network/HTTPRequestTest.php | 89 --------------------------- tests/src/Util/ImagesTest.php | 62 ++++++++++++++++++- 3 files changed, 130 insertions(+), 91 deletions(-) create mode 100644 tests/DiceTestTrait.php delete mode 100644 tests/src/Network/HTTPRequestTest.php diff --git a/tests/DiceTestTrait.php b/tests/DiceTestTrait.php new file mode 100644 index 000000000..2426c20dc --- /dev/null +++ b/tests/DiceTestTrait.php @@ -0,0 +1,70 @@ +. + * + */ + +namespace Friendica\Test; + +use Friendica\DI; +use Friendica\Network\HTTPClient; +use Friendica\Network\IHTTPClient; +use GuzzleHttp\Client; +use GuzzleHttp\HandlerStack; +use mattwright\URLResolver; + +/** + * This class mocks some DICE dependencies because they're not direct usable for test environments + * (Like fetching data from external endpoints) + */ +trait DiceTestTrait +{ + /** + * Handler for mocking requests anywhere for testing purpose + * + * @var HandlerStack + */ + protected static $httpRequestHandler; + + protected static function setUpDice(): void + { + if (!empty(self::$httpRequestHandler) && self::$httpRequestHandler instanceof HandlerStack) { + return; + } + + self::$httpRequestHandler = HandlerStack::create(); + + $client = new Client(['handler' => self::$httpRequestHandler]); + + $resolver = \Mockery::mock(URLResolver::class); + + $httpClient = new HTTPClient(DI::logger(), DI::profiler(), $client, $resolver); + + $dice = DI::getDice(); + $newDice = \Mockery::mock($dice)->makePartial(); + $newDice->shouldReceive('create')->with(IHTTPClient::class)->andReturn($httpClient); + DI::init($newDice); + } + + protected function tearDown() : void + { + \Mockery::close(); + + parent::tearDown(); + } +} diff --git a/tests/src/Network/HTTPRequestTest.php b/tests/src/Network/HTTPRequestTest.php deleted file mode 100644 index 230dc3eec..000000000 --- a/tests/src/Network/HTTPRequestTest.php +++ /dev/null @@ -1,89 +0,0 @@ -handler = HandlerStack::create(); - - $client = new Client(['handler' => $this->handler]); - - $resolver = \Mockery::mock(URLResolver::class); - - $profiler = \Mockery::mock(Profiler::class); - $profiler->shouldReceive('startRecording')->andReturnTrue(); - $profiler->shouldReceive('stopRecording')->andReturnTrue(); - - $httpClient = new HTTPClient(new NullLogger(), $profiler, $client, $resolver); - - $dice = DI::getDice(); - $newDice = \Mockery::mock($dice)->makePartial(); - $newDice->shouldReceive('create')->with(IHTTPClient::class)->andReturn($httpClient); - DI::init($newDice); - } - - public function dataImages() - { - return [ - 'image1' => [ - 'url' => 'https://pbs.twimg.com/profile_images/2365515285/9re7kx4xmc0eu9ppmado.png', - 'headers' => [ - 'Server' => 'tsa_b', - 'Content-Type' => 'image/png', - 'Cache-Control' => 'max-age=604800,must-revalidate', - 'Last-Modified' => 'Thu,04Nov201001:42:54GMT', - 'Content-Length' => '24875', - 'Access-Control-Allow-Origin' => '*', - 'Access-Control-Expose-Headers' => 'Content-Length', - 'Date' => 'Mon,23Aug202112:39:00GMT', - 'Connection' => 'keep-alive', - ], - 'data' => file_get_contents(__DIR__ . '/../../datasets/curl/image.content'), - 'assertion' => [ - '0' => '400', - '1' => '400', - '2' => '3', - '3' => 'width="400" height="400"', - 'bits' => '8', - 'mime' => 'image/png', - 'size' => '24875', - ] - ] - ]; - } - - /** - * @dataProvider dataImages - */ - public function testGetInfoFromURL(string $url, array $headers, string $data, array $assertion) - { - $this->handler->setHandler(new MockHandler([ - new Response(200, $headers, $data), - ])); - - self::assertArraySubset($assertion, Images::getInfoFromURL($url)); - } -} diff --git a/tests/src/Util/ImagesTest.php b/tests/src/Util/ImagesTest.php index d35f9c4ec..a7cc682f9 100644 --- a/tests/src/Util/ImagesTest.php +++ b/tests/src/Util/ImagesTest.php @@ -2,12 +2,70 @@ namespace Friendica\Test\src\Util; +use Friendica\Test\DiceTestTrait; use Friendica\Test\MockedTest; +use Friendica\Util\Images; +use GuzzleHttp\Handler\MockHandler; +use GuzzleHttp\Psr7\Response; class ImagesTest extends MockedTest { - public function testGetInfoFromURL() - { + use DiceTestTrait; + public static function setUpBeforeClass(): void + { + parent::setUpBeforeClass(); + + self::setUpDice(); + } + + public function dataImages() + { + return [ + 'image' => [ + 'url' => 'https://pbs.twimg.com/profile_images/2365515285/9re7kx4xmc0eu9ppmado.png', + 'headers' => [ + 'Server' => 'tsa_b', + 'Content-Type' => 'image/png', + 'Cache-Control' => 'max-age=604800,must-revalidate', + 'Last-Modified' => 'Thu,04Nov201001:42:54GMT', + 'Content-Length' => '24875', + 'Access-Control-Allow-Origin' => '*', + 'Access-Control-Expose-Headers' => 'Content-Length', + 'Date' => 'Mon,23Aug202112:39:00GMT', + 'Connection' => 'keep-alive', + ], + 'data' => file_get_contents(__DIR__ . '/../../datasets/curl/image.content'), + 'assertion' => [ + '0' => '400', + '1' => '400', + '2' => '3', + '3' => 'width="400" height="400"', + 'bits' => '8', + 'mime' => 'image/png', + 'size' => '24875', + ] + ], + 'emptyUrl' => [ + 'url' => '', + 'headers' => [], + 'data' => '', + 'assertion' => [], + ], + ]; + } + + /** + * Test the Images::getInfoFromURL() method + * + * @dataProvider dataImages + */ + public function testGetInfoFromURL(string $url, array $headers, string $data, array $assertion) + { + self::$httpRequestHandler->setHandler(new MockHandler([ + new Response(200, $headers, $data), + ])); + + self::assertArraySubset($assertion, Images::getInfoFromURL($url)); } } From 2356221abaa5d9ea2eb86174e91b0d43913f413c Mon Sep 17 00:00:00 2001 From: Philipp Date: Tue, 24 Aug 2021 14:17:42 +0200 Subject: [PATCH 08/19] Fixup HTTP headers for httpClient requests --- src/Core/Search.php | 2 +- src/Model/GServer.php | 3 +-- src/Model/Storage/ExternalResource.php | 2 +- src/Module/Proxy.php | 2 +- src/Network/HTTPClient.php | 2 +- src/Network/Probe.php | 6 +++--- src/Protocol/DFRN.php | 2 +- src/Protocol/Diaspora.php | 2 +- src/Protocol/OStatus.php | 4 ++-- src/Protocol/Salmon.php | 12 ++++++------ src/Util/HTTPSignature.php | 25 ++++++++++++++----------- src/Worker/PubSubPublish.php | 7 ++++--- 12 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/Core/Search.php b/src/Core/Search.php index a3588b3bd..efe8331b8 100644 --- a/src/Core/Search.php +++ b/src/Core/Search.php @@ -227,7 +227,7 @@ class Search $return = Contact::searchByName($search, $mode); } else { $p = $page > 1 ? 'p=' . $page : ''; - $curlResult = DI::httpRequest()->get(self::getGlobalDirectory() . '/search/people?' . $p . '&q=' . urlencode($search), ['accept_content' => 'application/json']); + $curlResult = DI::httpRequest()->get(self::getGlobalDirectory() . '/search/people?' . $p . '&q=' . urlencode($search), ['accept_content' => ['application/json']]); if ($curlResult->isSuccess()) { $searchResult = json_decode($curlResult->getBody(), true); if (!empty($searchResult['profiles'])) { diff --git a/src/Model/GServer.php b/src/Model/GServer.php index eb99b1bbc..15bf727e5 100644 --- a/src/Model/GServer.php +++ b/src/Model/GServer.php @@ -1728,8 +1728,7 @@ class GServer if (!empty($accesstoken)) { $api = 'https://instances.social/api/1.0/instances/list?count=0'; - $header = ['Authorization: Bearer '.$accesstoken]; - $curlResult = DI::httpRequest()->get($api, ['header' => $header]); + $curlResult = DI::httpRequest()->get($api, ['header' => ['Authorization' => ['Bearer ' . $accesstoken]]]); if ($curlResult->isSuccess()) { $servers = json_decode($curlResult->getBody(), true); diff --git a/src/Model/Storage/ExternalResource.php b/src/Model/Storage/ExternalResource.php index 918bcf8ac..413050c30 100644 --- a/src/Model/Storage/ExternalResource.php +++ b/src/Model/Storage/ExternalResource.php @@ -50,7 +50,7 @@ class ExternalResource implements IStorage } try { - $fetchResult = HTTPSignature::fetchRaw($data->url, $data->uid, ['accept_content' => '']); + $fetchResult = HTTPSignature::fetchRaw($data->url, $data->uid, ['accept_content' => []]); } catch (Exception $exception) { throw new ReferenceStorageException(sprintf('External resource failed to get %s', $reference), $exception->getCode(), $exception); } diff --git a/src/Module/Proxy.php b/src/Module/Proxy.php index 04fe00db1..cd11d9e3a 100644 --- a/src/Module/Proxy.php +++ b/src/Module/Proxy.php @@ -75,7 +75,7 @@ class Proxy extends BaseModule $request['url'] = str_replace(' ', '+', $request['url']); // Fetch the content with the local user - $fetchResult = HTTPSignature::fetchRaw($request['url'], local_user(), ['accept_content' => '', 'timeout' => 10]); + $fetchResult = HTTPSignature::fetchRaw($request['url'], local_user(), ['accept_content' => [], 'timeout' => 10]); $img_str = $fetchResult->getBody(); if (!$fetchResult->isSuccess() || empty($img_str)) { diff --git a/src/Network/HTTPClient.php b/src/Network/HTTPClient.php index d2ea596f1..cd1d9e46b 100644 --- a/src/Network/HTTPClient.php +++ b/src/Network/HTTPClient.php @@ -103,7 +103,7 @@ class HTTPClient implements IHTTPClient $header = []; if (!empty($opts['accept_content'])) { - array_push($header, 'Accept: ' . $opts['accept_content']); + $header['Accept'] = $opts['accept_content']; } if (!empty($opts['header'])) { diff --git a/src/Network/Probe.php b/src/Network/Probe.php index 4cacfedf4..bdc12aadc 100644 --- a/src/Network/Probe.php +++ b/src/Network/Probe.php @@ -170,7 +170,7 @@ class Probe Logger::info('Probing', ['host' => $host, 'ssl_url' => $ssl_url, 'url' => $url, 'callstack' => System::callstack(20)]); $xrd = null; - $curlResult = DI::httpRequest()->get($ssl_url, ['timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml']); + $curlResult = DI::httpRequest()->get($ssl_url, ['timeout' => $xrd_timeout, 'accept_content' => ['application/xrd+xml']]); $ssl_connection_error = ($curlResult->getErrorNumber() == CURLE_COULDNT_CONNECT) || ($curlResult->getReturnCode() == 0); if ($curlResult->isSuccess()) { $xml = $curlResult->getBody(); @@ -187,7 +187,7 @@ class Probe } if (!is_object($xrd) && !empty($url)) { - $curlResult = DI::httpRequest()->get($url, ['timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml']); + $curlResult = DI::httpRequest()->get($url, ['timeout' => $xrd_timeout, 'accept_content' => ['application/xrd+xml']]); $connection_error = ($curlResult->getErrorNumber() == CURLE_COULDNT_CONNECT) || ($curlResult->getReturnCode() == 0); if ($curlResult->isTimeout()) { Logger::info('Probing timeout', ['url' => $url]); @@ -940,7 +940,7 @@ class Probe { $xrd_timeout = DI::config()->get('system', 'xrd_timeout', 20); - $curlResult = DI::httpRequest()->get($url, ['timeout' => $xrd_timeout, 'accept_content' => $type]); + $curlResult = DI::httpRequest()->get($url, ['timeout' => $xrd_timeout, 'accept_content' => [$type]]); if ($curlResult->isTimeout()) { self::$istimeout = true; return []; diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index cd63291ee..141bce542 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -976,7 +976,7 @@ class DFRN $content_type = ($public_batch ? "application/magic-envelope+xml" : "application/json"); - $postResult = DI::httpRequest()->post($dest_url, $envelope, ["Content-Type: " . $content_type]); + $postResult = DI::httpRequest()->post($dest_url, $envelope, ['Content-Type' => $content_type]); $xml = $postResult->getBody(); $curl_stat = $postResult->getReturnCode(); diff --git a/src/Protocol/Diaspora.php b/src/Protocol/Diaspora.php index b38e0506b..32c0ce042 100644 --- a/src/Protocol/Diaspora.php +++ b/src/Protocol/Diaspora.php @@ -3022,7 +3022,7 @@ class Diaspora if (!intval(DI::config()->get("system", "diaspora_test"))) { $content_type = (($public_batch) ? "application/magic-envelope+xml" : "application/json"); - $postResult = DI::httpRequest()->post($dest_url . "/", $envelope, ["Content-Type: " . $content_type]); + $postResult = DI::httpRequest()->post($dest_url . "/", $envelope, ['Content-Type' => $content_type]); $return_code = $postResult->getReturnCode(); } else { Logger::log("test_mode"); diff --git a/src/Protocol/OStatus.php b/src/Protocol/OStatus.php index 215c08fa0..5a0135f9c 100644 --- a/src/Protocol/OStatus.php +++ b/src/Protocol/OStatus.php @@ -727,7 +727,7 @@ class OStatus self::$conv_list[$conversation] = true; - $curlResult = DI::httpRequest()->get($conversation, ['accept_content' => 'application/atom+xml, text/html']); + $curlResult = DI::httpRequest()->get($conversation, ['accept_content' => ['application/atom+xml', 'text/html']]); if (!$curlResult->isSuccess() || empty($curlResult->getBody())) { return; @@ -921,7 +921,7 @@ class OStatus } $stored = false; - $curlResult = DI::httpRequest()->get($related, ['accept_content' => 'application/atom+xml, text/html']); + $curlResult = DI::httpRequest()->get($related, ['accept_content' => ['application/atom+xml', 'text/html']]); if (!$curlResult->isSuccess() || empty($curlResult->getBody())) { return; diff --git a/src/Protocol/Salmon.php b/src/Protocol/Salmon.php index 53367f6d0..8d17f9678 100644 --- a/src/Protocol/Salmon.php +++ b/src/Protocol/Salmon.php @@ -156,8 +156,8 @@ class Salmon // slap them $postResult = DI::httpRequest()->post($url, $salmon, [ - 'Content-type: application/magic-envelope+xml', - 'Content-length: ' . strlen($salmon) + 'Content-type' => 'application/magic-envelope+xml', + 'Content-length' => strlen($salmon), ]); $return_code = $postResult->getReturnCode(); @@ -181,8 +181,8 @@ class Salmon // slap them $postResult = DI::httpRequest()->post($url, $salmon, [ - 'Content-type: application/magic-envelope+xml', - 'Content-length: ' . strlen($salmon) + 'Content-type' => 'application/magic-envelope+xml', + 'Content-length' => strlen($salmon), ]); $return_code = $postResult->getReturnCode(); } @@ -204,8 +204,8 @@ class Salmon // slap them $postResult = DI::httpRequest()->post($url, $salmon, [ - 'Content-type: application/magic-envelope+xml', - 'Content-length: ' . strlen($salmon)]); + 'Content-type' => 'application/magic-envelope+xml', + 'Content-length' => strlen($salmon)]); $return_code = $postResult->getReturnCode(); } diff --git a/src/Util/HTTPSignature.php b/src/Util/HTTPSignature.php index e2de810a6..cf3e1294f 100644 --- a/src/Util/HTTPSignature.php +++ b/src/Util/HTTPSignature.php @@ -29,6 +29,7 @@ use Friendica\Model\APContact; use Friendica\Model\Contact; use Friendica\Model\User; use Friendica\Network\CurlResult; +use Friendica\Network\IHTTPResult; /** * Implements HTTP Signatures per draft-cavage-http-signatures-07. @@ -290,15 +291,20 @@ class HTTPSignature $content_length = strlen($content); $date = DateTimeFormat::utcNow(DateTimeFormat::HTTP); - $headers = ['Date: ' . $date, 'Content-Length: ' . $content_length, 'Digest: ' . $digest, 'Host: ' . $host]; + $headers = [ + 'Date' => $date, + 'Content-Length' => $content_length, + 'Digest' => $digest, + 'Host' => $host + ]; $signed_data = "(request-target): post " . $path . "\ndate: ". $date . "\ncontent-length: " . $content_length . "\ndigest: " . $digest . "\nhost: " . $host; $signature = base64_encode(Crypto::rsaSign($signed_data, $owner['uprvkey'], 'sha256')); - $headers[] = 'Signature: keyId="' . $owner['url'] . '#main-key' . '",algorithm="rsa-sha256",headers="(request-target) date content-length digest host",signature="' . $signature . '"'; + $headers['Signature'] = 'keyId="' . $owner['url'] . '#main-key' . '",algorithm="rsa-sha256",headers="(request-target) date content-length digest host",signature="' . $signature . '"'; - $headers[] = 'Content-Type: application/activity+json'; + $headers['Content-Type'] = 'application/activity+json'; $postResult = DI::httpRequest()->post($target, $content, $headers); $return_code = $postResult->getReturnCode(); @@ -409,10 +415,10 @@ class HTTPSignature * 'nobody' => only return the header * 'cookiejar' => path to cookie jar file * - * @return CurlResult CurlResult + * @return IHTTPResult CurlResult * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - public static function fetchRaw($request, $uid = 0, $opts = ['accept_content' => 'application/activity+json, application/ld+json']) + public static function fetchRaw($request, $uid = 0, $opts = ['accept_content' => ['application/activity+json', 'application/ld+json']]) { $header = []; @@ -434,17 +440,14 @@ class HTTPSignature $path = parse_url($request, PHP_URL_PATH); $date = DateTimeFormat::utcNow(DateTimeFormat::HTTP); - $header = ['Date: ' . $date, 'Host: ' . $host]; + $header['Date'] = $date; + $header['Host'] = $host; $signed_data = "(request-target): get " . $path . "\ndate: ". $date . "\nhost: " . $host; $signature = base64_encode(Crypto::rsaSign($signed_data, $owner['uprvkey'], 'sha256')); - $header[] = 'Signature: keyId="' . $owner['url'] . '#main-key' . '",algorithm="rsa-sha256",headers="(request-target) date host",signature="' . $signature . '"'; - } - - if (!empty($opts['accept_content'])) { - $header[] = 'Accept: ' . $opts['accept_content']; + $header['Signature'] = 'keyId="' . $owner['url'] . '#main-key' . '",algorithm="rsa-sha256",headers="(request-target) date host",signature="' . $signature . '"'; } $curl_opts = $opts; diff --git a/src/Worker/PubSubPublish.php b/src/Worker/PubSubPublish.php index a5381c18d..a3e2ee4ad 100644 --- a/src/Worker/PubSubPublish.php +++ b/src/Worker/PubSubPublish.php @@ -59,11 +59,12 @@ class PubSubPublish $hmac_sig = hash_hmac("sha1", $params, $subscriber['secret']); - $headers = ["Content-type: application/atom+xml", - sprintf("Link: <%s>;rel=hub,<%s>;rel=self", + $headers = [ + 'Content-type' => 'application/atom+xml', + 'Link' => sprintf("<%s>;rel=hub,<%s>;rel=self", DI::baseUrl() . '/pubsubhubbub/' . $subscriber['nickname'], $subscriber['topic']), - "X-Hub-Signature: sha1=" . $hmac_sig]; + 'X-Hub-Signature' => 'sha1=' . $hmac_sig]; Logger::log('POST ' . print_r($headers, true) . "\n" . $params, Logger::DATA); From a3d04042900d31d377ccfbe457e174be797d8d89 Mon Sep 17 00:00:00 2001 From: Philipp Date: Tue, 24 Aug 2021 23:26:33 +0200 Subject: [PATCH 09/19] Fix curResult::getHeader() in GServer.php --- src/Model/GServer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Model/GServer.php b/src/Model/GServer.php index 15bf727e5..f81b2acc1 100644 --- a/src/Model/GServer.php +++ b/src/Model/GServer.php @@ -1619,11 +1619,11 @@ class GServer } elseif ($curlResult->inHeader('x-diaspora-version')) { $serverdata['platform'] = 'diaspora'; $serverdata['network'] = Protocol::DIASPORA; - $serverdata['version'] = $curlResult->getHeader('x-diaspora-version'); + $serverdata['version'] = $curlResult->getHeader('x-diaspora-version')[0] ?? ''; } elseif ($curlResult->inHeader('x-friendica-version')) { $serverdata['platform'] = 'friendica'; $serverdata['network'] = Protocol::DFRN; - $serverdata['version'] = $curlResult->getHeader('x-friendica-version'); + $serverdata['version'] = $curlResult->getHeader('x-friendica-version')[0] ?? ''; } else { return $serverdata; } From d4a233a1494e51e4a8cb2f6e3c4c96084b496485 Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 25 Aug 2021 00:13:50 +0200 Subject: [PATCH 10/19] Extend test capability for HTTP Requests --- src/Factory/HTTPClientFactory.php | 11 +- ...Trait.php => DiceHttpMockHandlerTrait.php} | 36 +++-- tests/src/Network/ProbeTest.php | 133 +++++++++++++++++- tests/src/Util/ImagesTest.php | 16 +-- 4 files changed, 166 insertions(+), 30 deletions(-) rename tests/{DiceTestTrait.php => DiceHttpMockHandlerTrait.php} (57%) diff --git a/src/Factory/HTTPClientFactory.php b/src/Factory/HTTPClientFactory.php index c1cb47541..040ad7514 100644 --- a/src/Factory/HTTPClientFactory.php +++ b/src/Factory/HTTPClientFactory.php @@ -9,6 +9,7 @@ use Friendica\Network\HTTPClient; use Friendica\Network\IHTTPClient; use Friendica\Util\Profiler; use GuzzleHttp\Client; +use GuzzleHttp\HandlerStack; use GuzzleHttp\RequestOptions; use mattwright\URLResolver; use Psr\Http\Message\RequestInterface; @@ -33,7 +34,14 @@ class HTTPClientFactory extends BaseFactory $this->baseUrl = $baseUrl; } - public function createClient(): IHTTPClient + /** + * 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'); @@ -84,6 +92,7 @@ class HTTPClientFactory extends BaseFactory RequestOptions::HEADERS => [ 'User-Agent' => $userAgent, ], + 'handler' => $handlerStack ?? HandlerStack::create(), ]); $resolver = new URLResolver(); diff --git a/tests/DiceTestTrait.php b/tests/DiceHttpMockHandlerTrait.php similarity index 57% rename from tests/DiceTestTrait.php rename to tests/DiceHttpMockHandlerTrait.php index 2426c20dc..969b76b5b 100644 --- a/tests/DiceTestTrait.php +++ b/tests/DiceHttpMockHandlerTrait.php @@ -21,47 +21,45 @@ namespace Friendica\Test; +use Dice\Dice; use Friendica\DI; -use Friendica\Network\HTTPClient; +use Friendica\Factory\HTTPClientFactory; use Friendica\Network\IHTTPClient; -use GuzzleHttp\Client; use GuzzleHttp\HandlerStack; -use mattwright\URLResolver; /** - * This class mocks some DICE dependencies because they're not direct usable for test environments - * (Like fetching data from external endpoints) + * This class injects a mockable handler into the IHTTPClient dependency per Dice */ -trait DiceTestTrait +trait DiceHttpMockHandlerTrait { /** * Handler for mocking requests anywhere for testing purpose * * @var HandlerStack */ - protected static $httpRequestHandler; + protected $httpRequestHandler; - protected static function setUpDice(): void + protected function setupHttpMockHandler(): void { - if (!empty(self::$httpRequestHandler) && self::$httpRequestHandler instanceof HandlerStack) { + if (!empty($this->httpRequestHandler) && $this->httpRequestHandler instanceof HandlerStack) { return; } - self::$httpRequestHandler = HandlerStack::create(); + $this->httpRequestHandler = HandlerStack::create(); - $client = new Client(['handler' => self::$httpRequestHandler]); + $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, + 'call' => [ + ['createClient', [$this->httpRequestHandler], Dice::CHAIN_CALL], + ], + ]); - $resolver = \Mockery::mock(URLResolver::class); - - $httpClient = new HTTPClient(DI::logger(), DI::profiler(), $client, $resolver); - - $dice = DI::getDice(); - $newDice = \Mockery::mock($dice)->makePartial(); - $newDice->shouldReceive('create')->with(IHTTPClient::class)->andReturn($httpClient); DI::init($newDice); } - protected function tearDown() : void + protected function tearDown(): void { \Mockery::close(); diff --git a/tests/src/Network/ProbeTest.php b/tests/src/Network/ProbeTest.php index 58711cb91..79c323adc 100644 --- a/tests/src/Network/ProbeTest.php +++ b/tests/src/Network/ProbeTest.php @@ -3,10 +3,21 @@ namespace Friendica\Test\src\Network; use Friendica\Network\Probe; -use PHPUnit\Framework\TestCase; +use Friendica\Test\DiceHttpMockHandlerTrait; +use Friendica\Test\FixtureTest; +use GuzzleHttp\Middleware; -class ProbeTest extends TestCase +class ProbeTest extends FixtureTest { + use DiceHttpMockHandlerTrait; + + protected function setUp(): void + { + parent::setUp(); + + $this->setupHttpMockHandler(); + } + const TEMPLATENOBASE = ' @@ -105,4 +116,122 @@ class ProbeTest extends TestCase } } } + + public function dataUri() + { + return [ + '@-first' => [ + 'uri' => '@Artists4Future_Muenchen@climatejustice.global', + 'assertUri' => 'Artists4Future_Muenchen@climatejustice.global', + 'assertInfos' => [ + 'name' => 'Artists4Future München', + 'nick' => 'Artists4Future_Muenchen', + 'url' => 'https://climatejustice.global/users/Artists4Future_Muenchen', + 'alias' => 'https://climatejustice.global/@Artists4Future_Muenchen', + 'photo' => 'https://cdn.masto.host/climatejusticeglobal/accounts/avatars/000/021/220/original/05ee9e827a5b47fc.jpg', + 'header' => 'https://cdn.masto.host/climatejusticeglobal/accounts/headers/000/021/220/original/9b98b75cf696cd11.jpg', + 'account-type' => 0, + 'about' => 'Wir sind Künstler oder einfach gerne kreativ tätig und setzen uns unabhängig von politischen Parteien für den Klimaschutz ein. Die Bedingungen zu schaffen, die die [url=https://climatejustice.global/tags/Klimakrise]#Klimakrise[/url] verhindern/eindämmen (gemäß den Forderungen der [url=https://climatejustice.global/tags/Fridays4Future]#Fridays4Future[/url]) ist Aufgabe der Politik, muss aber gesamtgesellschaftlich getragen werden. Mit unseren künstlerischen Aktionen wollen wir einen anderen Zugang anbieten für wissenschaftlich rationale Argumente, speziell zur Erderwärmung und ihre Konsequenzen.', + 'hide' => 0, + 'batch' => 'https://climatejustice.global/inbox', + 'notify' => 'https://climatejustice.global/users/Artists4Future_Muenchen/inbox', + 'poll' => 'https://climatejustice.global/users/Artists4Future_Muenchen/outbox', + 'subscribe' => 'https://climatejustice.global/authorize_interaction?uri={uri}', + 'following' => 'https://climatejustice.global/users/Artists4Future_Muenchen/following', + 'followers' => 'https://climatejustice.global/users/Artists4Future_Muenchen/followers', + 'inbox' => 'https://climatejustice.global/users/Artists4Future_Muenchen/inbox', + 'outbox' => 'https://climatejustice.global/users/Artists4Future_Muenchen/outbox', + 'sharedinbox' => 'https://climatejustice.global/inbox', + 'priority' => 0, + 'network' => 'apub', + 'pubkey' => '-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6pYKPuDKb+rmBB869uPV +uLYFPosGxMUfenWqfWmFKzEqJ87rAft0IQDAL6dCoYE55ov/lEDNROhasTZLirZf +M5b7/1JmwMrAfEiaciuYqDWT3/yDpnekOIdzP5iSClg4zt7e6HRFuClqo4+b6hIE +DTMV4ksItvq/92MIu62pZ2SZr5ADPPZ/914lJ86hIH5BanbE8ZFzDS9vJA7V74rt +Vvkr5c/OiUyuODNYApSl87Ez8cuj8Edt89YWkDCajQn3EkmXGeJY/VRjEDfcyk6r +AvdUa0ArjXud3y3NkakVFZ0d7tmB20Vn9s/CfYHU8FXzbI1kFkov2BX899VVP5Ay +xQIDAQAB +-----END PUBLIC KEY-----', + 'manually-approve' => 0, + 'baseurl' => 'https://climatejustice.global', + ] + ] + ]; + } + + /** + * @dataProvider dataUri + */ + public function testCleanUri(string $uri, string $assertUri, array $assertInfos) + { + self::markTestIncomplete('hard work due mocking 19 different http-requests'); + + /** + * Requests: + * + * GET : https://climatejustice.global/.well-known/webfinger?resource=acct:Artists4Future_Muenchen%40climatejustice.global + * 200 + * GET : http://localhost/.well-known/nodeinfo + * 200 + * GET : http://localhost/statistics.json + * 404 + * GET : http://localhost + * 200 + * GET : http://localhost/friendica/json + * 404 + * GET : http://localhost/friendika/json + * 404 + * GET : http://localhost/poco + * 403 + * GET : http://localhost/api/v1/directory?limit=1 + * 200 + * GET : http://localhost/.well-known/x-social-relay + * 200 + * GET : http://localhost/friendica + * 404 + * GET : https://climatejustice.global/users/Artists4Future_Muenchen + * 200 + * GET : https://climatejustice.global/users/Artists4Future_Muenchen/following + * 200 + * GET : https://climatejustice.global/users/Artists4Future_Muenchen/followers + * 200 + * GET : https://climatejustice.global/users/Artists4Future_Muenchen/outbox + * 200 + * GET : https://climatejustice.global/.well-known/nodeinfo + * 200 + * GET : https://climatejustice.global/nodeinfo/2.0 + * 200 + * GET : https://climatejustice.global/poco + * 404 + * GET : https://climatejustice.global/api/v1/directory?limit=1 + * 200 + * GET : https://climatejustice.global/.well-known/webfinger?resource=acct%3AArtists4Future_Muenchen%40climatejustice.global + * 200 + * + */ + + $container = []; + $history = Middleware::history($container); + + $this->httpRequestHandler->push($history); + + $cleaned = Probe::cleanURI($uri); + self::assertEquals($assertUri, $cleaned); + self::assertArraySubset($assertInfos, Probe::uri($cleaned, '', 0)); + + + // Iterate over the requests and responses + foreach ($container as $transaction) { + echo $transaction['request']->getMethod() . " : " . $transaction['request']->getUri() . PHP_EOL; + //> GET, HEAD + if ($transaction['response']) { + echo $transaction['response']->getStatusCode() . PHP_EOL; + //> 200, 200 + } elseif ($transaction['error']) { + echo $transaction['error']; + //> exception + } + } + } } diff --git a/tests/src/Util/ImagesTest.php b/tests/src/Util/ImagesTest.php index a7cc682f9..ddadf9547 100644 --- a/tests/src/Util/ImagesTest.php +++ b/tests/src/Util/ImagesTest.php @@ -2,7 +2,7 @@ namespace Friendica\Test\src\Util; -use Friendica\Test\DiceTestTrait; +use Friendica\Test\DiceHttpMockHandlerTrait; use Friendica\Test\MockedTest; use Friendica\Util\Images; use GuzzleHttp\Handler\MockHandler; @@ -10,13 +10,13 @@ use GuzzleHttp\Psr7\Response; class ImagesTest extends MockedTest { - use DiceTestTrait; + use DiceHttpMockHandlerTrait; - public static function setUpBeforeClass(): void + protected function setUp(): void { - parent::setUpBeforeClass(); + parent::setUp(); - self::setUpDice(); + $this->setupHttpMockHandler(); } public function dataImages() @@ -56,13 +56,13 @@ class ImagesTest extends MockedTest } /** - * Test the Images::getInfoFromURL() method + * Test the Images::getInfoFromURL() method (only remote images, not local/relative!) * * @dataProvider dataImages */ - public function testGetInfoFromURL(string $url, array $headers, string $data, array $assertion) + public function testGetInfoFromRemotURL(string $url, array $headers, string $data, array $assertion) { - self::$httpRequestHandler->setHandler(new MockHandler([ + $this->httpRequestHandler->setHandler(new MockHandler([ new Response(200, $headers, $data), ])); From f01d882e6cfaba26ae85e5d2789971f5393bee00 Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 25 Aug 2021 00:40:15 +0200 Subject: [PATCH 11/19] Fix HTTPClient --- src/Network/HTTPClient.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Network/HTTPClient.php b/src/Network/HTTPClient.php index cd1d9e46b..f4653628f 100644 --- a/src/Network/HTTPClient.php +++ b/src/Network/HTTPClient.php @@ -131,10 +131,9 @@ class HTTPClient implements IHTTPClient try { switch ($method) { case 'get': - $response = $this->client->get($url, $conf); - break; case 'head': - $response = $this->client->head($url, $conf); + case 'post': + $response = $this->client->$method($url, $conf); break; default: throw new TransferException('Invalid method'); From f2dcc2788d19cd2d3789a436583e7520722b5715 Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 25 Aug 2021 13:39:40 +0200 Subject: [PATCH 12/19] Add Test for HTTPSignature::createSig() --- tests/src/Util/HTTPSignatureTest.php | 122 ++++++++++++++++++++++++--- 1 file changed, 110 insertions(+), 12 deletions(-) diff --git a/tests/src/Util/HTTPSignatureTest.php b/tests/src/Util/HTTPSignatureTest.php index 440e1e8db..02f04ec01 100644 --- a/tests/src/Util/HTTPSignatureTest.php +++ b/tests/src/Util/HTTPSignatureTest.php @@ -29,9 +29,11 @@ use PHPUnit\Framework\TestCase; */ class HTTPSignatureTest extends TestCase { - public function testParseSigheader() + public function dataParseSigned() { - $header = 'keyId="test-key-a", algorithm="hs2019", + return [ + 'signed1' => [ + 'header' => 'keyId="test-key-a", algorithm="hs2019", created=1402170695, headers="(request-target) (created) host date content-type digest content-length", @@ -40,16 +42,112 @@ class HTTPSignatureTest extends TestCase XVsM9jy+Q1+RIlD9wfWoPHhqhoXt35ZkasuIDPF/AETuObs9QydlsqONwbK+T dQguDK/8Va1Pocl6wK1uLwqcXlxhPEb55EmdYB9pddDyHTADING7K4qMwof2m C3t8Pb0yoLZoZX5a4Or4FrCCKK/9BHAhq/RsVk0dTENMbTB4i7cHvKQu+o9xu - YWuxyvBa0Z6NdOb0di70cdrSDEsL5Gz7LBY5J2N9KdGg=="'; + YWuxyvBa0Z6NdOb0di70cdrSDEsL5Gz7LBY5J2N9KdGg=="', + 'assertion' => [ + 'keyId' => 'test-key-a', + 'algorithm' => 'hs2019', + 'created' => '1402170695', + 'expires' => null, + 'headers' => ['(request-target)', '(created)', 'host', 'date', 'content-type', 'digest', 'content-length'], + 'signature' => base64_decode('KXUj1H3ZOhv3Nk4xlRLTn4bOMlMOmFiud3VXrMa9MaLCxnVmrqOX5BulRvB65YW/wQp0oT/nNQpXgOYeY8ovmHlpkRyz5buNDqoOpRsCpLGxsIJ9cX8XVsM9jy+Q1+RIlD9wfWoPHhqhoXt35ZkasuIDPF/AETuObs9QydlsqONwbK+TdQguDK/8Va1Pocl6wK1uLwqcXlxhPEb55EmdYB9pddDyHTADING7K4qMwof2mC3t8Pb0yoLZoZX5a4Or4FrCCKK/9BHAhq/RsVk0dTENMbTB4i7cHvKQu+o9xuYWuxyvBa0Z6NdOb0di70cdrSDEsL5Gz7LBY5J2N9KdGg=='), + ], + ], + 'signed2' => [ + 'signature' => 'Signature keyId="acct:admin@friendica.local",algorithm="rsa-sha512",created=1402170695,headers="accept x-open-web-auth",signature="lZzgtUOtyko/ZUjTRq6kUye6VmLbF2Zrku9TMl+9LEEdt7rxXOd+jpRr8L0s0G0oYSrbMzArgnk5S2XAz1XLi6GhoEgpyKJNmpnYUc/GvD86k0Cmb12TQF7B4Zv8k6h5LLCHppMi5myNIqQ95mqzVeWewCTgUupZVaqJxUAve1Uyx12jlzfYo6HAprXHhZKbLSAVfg+qiS8ufVp4coCYguioSMtMd4QvCFtAO3rFGwZ5yizXmPiKjhLQqxoy15+1VifTeAy8KJ+nPy+XeakpiYTfbPuCvaDqAMqboFx+J5epKS7M2T581oIgpSmQpHpkfCU8AT/JJ99Kktyzfn+ctK3Q8bKSjZQOT9S66S1b04jvFMggDM7p8Zu+Nr/SaS7ro5Yr7Fc200CM7yoRvef7MKQ7o0HASP8sMQwtSB4k+NlNBLUu9Eo/6P16bzx27cg3yGyuh15qmU8dL9heHqBL+E/m96BTZKIB5D81TkO/m0MpvJIxR0NfS9qKFcCAoev8kfcGcnVxtcxuCys7M/iW4ykJoMhFJDnsfN7mygOledeTmug8ARm0WpxUhtoNqhQrDXjAGbuPnQ1B/fPNwKVrHdFa2i/va0kgwa5+yh2ACqyMfrKCSkv2Prni3iKTKs8W0o/bF6FFeSqPsCGxneSMoYx3FOaSy6ts2gyuAwEozSg="', + 'assertion' => [ + 'keyId' => 'acct:admin@friendica.local', + 'algorithm' => 'rsa-sha512', + 'created' => '1402170695', + 'expires' => null, + 'headers' => ['accept', 'x-open-web-auth'], + 'signature' => base64_decode('lZzgtUOtyko/ZUjTRq6kUye6VmLbF2Zrku9TMl+9LEEdt7rxXOd+jpRr8L0s0G0oYSrbMzArgnk5S2XAz1XLi6GhoEgpyKJNmpnYUc/GvD86k0Cmb12TQF7B4Zv8k6h5LLCHppMi5myNIqQ95mqzVeWewCTgUupZVaqJxUAve1Uyx12jlzfYo6HAprXHhZKbLSAVfg+qiS8ufVp4coCYguioSMtMd4QvCFtAO3rFGwZ5yizXmPiKjhLQqxoy15+1VifTeAy8KJ+nPy+XeakpiYTfbPuCvaDqAMqboFx+J5epKS7M2T581oIgpSmQpHpkfCU8AT/JJ99Kktyzfn+ctK3Q8bKSjZQOT9S66S1b04jvFMggDM7p8Zu+Nr/SaS7ro5Yr7Fc200CM7yoRvef7MKQ7o0HASP8sMQwtSB4k+NlNBLUu9Eo/6P16bzx27cg3yGyuh15qmU8dL9heHqBL+E/m96BTZKIB5D81TkO/m0MpvJIxR0NfS9qKFcCAoev8kfcGcnVxtcxuCys7M/iW4ykJoMhFJDnsfN7mygOledeTmug8ARm0WpxUhtoNqhQrDXjAGbuPnQ1B/fPNwKVrHdFa2i/va0kgwa5+yh2ACqyMfrKCSkv2Prni3iKTKs8W0o/bF6FFeSqPsCGxneSMoYx3FOaSy6ts2gyuAwEozSg='), + ] + ] + ]; + } - $headers = HTTPSignature::parseSigheader($header); - self::assertSame([ - 'keyId' => 'test-key-a', - 'algorithm' => 'hs2019', - 'created' => '1402170695', - 'expires' => null, - 'headers' => ['(request-target)', '(created)', 'host', 'date', 'content-type', 'digest', 'content-length'], - 'signature' => base64_decode('KXUj1H3ZOhv3Nk4xlRLTn4bOMlMOmFiud3VXrMa9MaLCxnVmrqOX5BulRvB65YW/wQp0oT/nNQpXgOYeY8ovmHlpkRyz5buNDqoOpRsCpLGxsIJ9cX8XVsM9jy+Q1+RIlD9wfWoPHhqhoXt35ZkasuIDPF/AETuObs9QydlsqONwbK+TdQguDK/8Va1Pocl6wK1uLwqcXlxhPEb55EmdYB9pddDyHTADING7K4qMwof2mC3t8Pb0yoLZoZX5a4Or4FrCCKK/9BHAhq/RsVk0dTENMbTB4i7cHvKQu+o9xuYWuxyvBa0Z6NdOb0di70cdrSDEsL5Gz7LBY5J2N9KdGg=='), - ], $headers); + public function dataHeader() + { + return [ + 'signed' => [ + 'privKey' => '-----BEGIN PRIVATE KEY----- +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 +G1vVmRgkLDqhc4+r3wDz3qy6JpV7tg== +-----END PRIVATE KEY-----', + 'keyId' => 'acct:admin@friendica.local', + 'header' => [ + 'Accept' => 'application/x-dfrn+json, application/x-zot+json', + 'X-Open-Web-Auth' => '1dde649b855fd1aae542a91c4edd8c3a7a4c59d8eaf3136cdee05dfc16a30bac', + ], + 'signature' => 'Signature keyId="acct:admin@friendica.local",algorithm="rsa-sha512",headers="accept x-open-web-auth",signature="cb09/wdmRdFhrQUczL0lR6LTkVh8qb/vYh70DFCW40QrzvuUYHzJ+GqqJW6tcCb2rXP4t+aobWKfZ4wFMBtVbejAiCgF01pzEBJfDevdyu7khlfKo+Gtw1CGk4/0so1QmqASeHdlG3ID3GnmuovqZn2v5f5D+1UAJ6Pu6mtAXrGRntoEM/oqYBAsRrMtDSDAE4tnOSDu2YfVJJLfyUX1ZWjUK0ejZXZ0YTDJwU8PqRcRX17NhBaDq82dEHlfhD7I/aOclwVbfKIi5Ud619XCxPq0sAAYso17MhUm40oBCJVze2x4pswJhv9IFYohtR5G/fKkz2eosu3xeRflvGicS4JdclhTRgCGWDy10DV76FiXiS5N2clLXreHItWEXlZKZx6m9zAZoEX92VRnc4BY4jDsRR89Pl88hELaxiNipviyHS7XYZTRnkLM+nvtOcxkHSCbEs7oGw+AX+pLHoU5otNOqy+ZbXQ1cUvFOBYZmYdX3DiWaLfBKraakPkslJuU3yJu95X1qYmQTpOZDR4Ma/yf5fmWJh5D9ywnXxxd6RaupoO6HTtIl6gmsfcsyZNi5hRbbgPI3BiQwGYVGF6qzJpEOMzEyHyAuFeanhicc8b+P+DCwXni5sjM7ntKwShbCBP80KHSdoumORin3/PYgHCmHZVv71N0HNlPZnyVzZw="', + ] + ]; + } + + /** + * @dataProvider dataParseSigned + */ + public function testParseSigheader(string $signature, array $assertion) + { + $headers = HTTPSignature::parseSigheader($signature); + self::assertEquals($assertion, $headers); + } + + /** + * @dataProvider dataHeader + */ + public function testSignHeader(string $privKey, string $keyId, array $header, string $signature) + { + $signed = HTTPSignature::createSig($header, $privKey, $keyId); + print_r($signed); + self::assertEquals($signature, substr($signed[2], strlen('Authorization: '))); } } From befd5c860d6af9c2ba7cdcade898e54b53d2060b Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 25 Aug 2021 13:45:00 +0200 Subject: [PATCH 13/19] Fix headers (string to array) and make sure the signature doesn't change --- src/Module/Magic.php | 7 ++++--- src/Util/HTTPSignature.php | 16 +++++++--------- tests/src/Util/HTTPSignatureTest.php | 7 +++---- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/Module/Magic.php b/src/Module/Magic.php index 45fde43f6..12747dca7 100644 --- a/src/Module/Magic.php +++ b/src/Module/Magic.php @@ -88,9 +88,10 @@ class Magic extends BaseModule $exp = explode('/profile/', $contact['url']); $basepath = $exp[0]; - $header = []; - $header['Accept'] = 'application/x-dfrn+json, application/x-zot+json'; - $header['X-Open-Web-Auth'] = Strings::getRandomHex(); + $header = [ + 'Accept' => ['application/x-dfrn+json', 'application/x-zot+json'], + 'X-Open-Web-Auth' => [Strings::getRandomHex()], + ]; // Create a header that is signed with the local users private key. $header = HTTPSignature::createSig( diff --git a/src/Util/HTTPSignature.php b/src/Util/HTTPSignature.php index cf3e1294f..eab778b82 100644 --- a/src/Util/HTTPSignature.php +++ b/src/Util/HTTPSignature.php @@ -140,6 +140,9 @@ class HTTPSignature public static function createSig($head, $prvkey, $keyid = 'Key') { $return_headers = []; + if (!empty($head)) { + $return_headers = $head; + } $alg = 'sha512'; $algorithm = 'rsa-sha512'; @@ -149,15 +152,7 @@ class HTTPSignature $headerval = 'keyId="' . $keyid . '",algorithm="' . $algorithm . '",headers="' . $x['headers'] . '",signature="' . $x['signature'] . '"'; - $sighead = 'Authorization: Signature ' . $headerval; - - if ($head) { - foreach ($head as $k => $v) { - $return_headers[] = $k . ': ' . $v; - } - } - - $return_headers[] = $sighead; + $return_headers['Authorization'] = ['Signature ' . $headerval]; return $return_headers; } @@ -176,6 +171,9 @@ class HTTPSignature $fields = ''; foreach ($head as $k => $v) { + if (is_array($v)) { + $v = implode(', ', $v); + } $headers .= strtolower($k) . ': ' . trim($v) . "\n"; if ($fields) { $fields .= ' '; diff --git a/tests/src/Util/HTTPSignatureTest.php b/tests/src/Util/HTTPSignatureTest.php index 02f04ec01..a2d138975 100644 --- a/tests/src/Util/HTTPSignatureTest.php +++ b/tests/src/Util/HTTPSignatureTest.php @@ -124,8 +124,8 @@ G1vVmRgkLDqhc4+r3wDz3qy6JpV7tg== -----END PRIVATE KEY-----', 'keyId' => 'acct:admin@friendica.local', 'header' => [ - 'Accept' => 'application/x-dfrn+json, application/x-zot+json', - 'X-Open-Web-Auth' => '1dde649b855fd1aae542a91c4edd8c3a7a4c59d8eaf3136cdee05dfc16a30bac', + 'Accept' => ['application/x-dfrn+json', 'application/x-zot+json'], + 'X-Open-Web-Auth' => ['1dde649b855fd1aae542a91c4edd8c3a7a4c59d8eaf3136cdee05dfc16a30bac'], ], 'signature' => 'Signature keyId="acct:admin@friendica.local",algorithm="rsa-sha512",headers="accept x-open-web-auth",signature="cb09/wdmRdFhrQUczL0lR6LTkVh8qb/vYh70DFCW40QrzvuUYHzJ+GqqJW6tcCb2rXP4t+aobWKfZ4wFMBtVbejAiCgF01pzEBJfDevdyu7khlfKo+Gtw1CGk4/0so1QmqASeHdlG3ID3GnmuovqZn2v5f5D+1UAJ6Pu6mtAXrGRntoEM/oqYBAsRrMtDSDAE4tnOSDu2YfVJJLfyUX1ZWjUK0ejZXZ0YTDJwU8PqRcRX17NhBaDq82dEHlfhD7I/aOclwVbfKIi5Ud619XCxPq0sAAYso17MhUm40oBCJVze2x4pswJhv9IFYohtR5G/fKkz2eosu3xeRflvGicS4JdclhTRgCGWDy10DV76FiXiS5N2clLXreHItWEXlZKZx6m9zAZoEX92VRnc4BY4jDsRR89Pl88hELaxiNipviyHS7XYZTRnkLM+nvtOcxkHSCbEs7oGw+AX+pLHoU5otNOqy+ZbXQ1cUvFOBYZmYdX3DiWaLfBKraakPkslJuU3yJu95X1qYmQTpOZDR4Ma/yf5fmWJh5D9ywnXxxd6RaupoO6HTtIl6gmsfcsyZNi5hRbbgPI3BiQwGYVGF6qzJpEOMzEyHyAuFeanhicc8b+P+DCwXni5sjM7ntKwShbCBP80KHSdoumORin3/PYgHCmHZVv71N0HNlPZnyVzZw="', ] @@ -147,7 +147,6 @@ G1vVmRgkLDqhc4+r3wDz3qy6JpV7tg== public function testSignHeader(string $privKey, string $keyId, array $header, string $signature) { $signed = HTTPSignature::createSig($header, $privKey, $keyId); - print_r($signed); - self::assertEquals($signature, substr($signed[2], strlen('Authorization: '))); + self::assertEquals($signature, $signed['Authorization'][0]); } } From 7d251f092ecd634b2db1ea136585f7f5bf914628 Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 25 Aug 2021 13:58:29 +0200 Subject: [PATCH 14/19] Add logpoint --- src/Network/HTTPClient.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Network/HTTPClient.php b/src/Network/HTTPClient.php index f4653628f..90f7775df 100644 --- a/src/Network/HTTPClient.php +++ b/src/Network/HTTPClient.php @@ -129,6 +129,8 @@ class HTTPClient implements IHTTPClient }; try { + $this->logger->debug('http request config.', ['url' => $url, 'method' => $method, 'options' => $conf]); + switch ($method) { case 'get': case 'head': From 12367648fae2aebd981b9cde83599a34682221be Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 25 Aug 2021 13:59:37 +0200 Subject: [PATCH 15/19] Add suggestions --- src/Factory/HTTPClientFactory.php | 2 +- src/Network/HTTPClient.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Factory/HTTPClientFactory.php b/src/Factory/HTTPClientFactory.php index 040ad7514..7da2718d7 100644 --- a/src/Factory/HTTPClientFactory.php +++ b/src/Factory/HTTPClientFactory.php @@ -86,7 +86,7 @@ class HTTPClientFactory extends BaseFactory RequestOptions::CONNECT_TIMEOUT => 10, RequestOptions::TIMEOUT => $this->config->get('system', 'curl_timeout', 60), // by default we will allow self-signed certs - // but you can override this + // but it can be overridden RequestOptions::VERIFY => (bool)$this->config->get('system', 'verifyssl'), RequestOptions::PROXY => $proxy, RequestOptions::HEADERS => [ diff --git a/src/Network/HTTPClient.php b/src/Network/HTTPClient.php index 90f7775df..72f11fa3e 100644 --- a/src/Network/HTTPClient.php +++ b/src/Network/HTTPClient.php @@ -135,6 +135,8 @@ class HTTPClient implements IHTTPClient case 'get': case 'head': case 'post': + case 'put': + case 'delete': $response = $this->client->$method($url, $conf); break; default: From a6258cfbfadd208474e20bda1bbeaa22cfab6f40 Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 25 Aug 2021 14:28:59 +0200 Subject: [PATCH 16/19] Adapt description for "accept_content" --- src/Network/IHTTPClient.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Network/IHTTPClient.php b/src/Network/IHTTPClient.php index c8611e8f5..d0220d2c9 100644 --- a/src/Network/IHTTPClient.php +++ b/src/Network/IHTTPClient.php @@ -61,7 +61,7 @@ interface IHTTPClient * * @param string $url URL to fetch * @param array $opts (optional parameters) assoziative array with: - * 'accept_content' => supply Accept: header with 'accept_content' as the value + * '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 @@ -75,7 +75,7 @@ interface IHTTPClient * * @param string $url URL to fetch * @param array $opts (optional parameters) assoziative array with: - * 'accept_content' => supply Accept: header with 'accept_content' as the value + * '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 From f00da9eccfda6b9b70160962b19c01b4b690d8d4 Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 25 Aug 2021 17:02:34 +0200 Subject: [PATCH 17/19] Update src/Network/IHTTPClient.php Co-authored-by: Hypolite Petovan --- src/Network/IHTTPClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Network/IHTTPClient.php b/src/Network/IHTTPClient.php index d0220d2c9..eadfb39fc 100644 --- a/src/Network/IHTTPClient.php +++ b/src/Network/IHTTPClient.php @@ -60,7 +60,7 @@ interface IHTTPClient * Send a HEAD to an URL. * * @param string $url URL to fetch - * @param array $opts (optional parameters) assoziative array with: + * @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 From f10de081661b53baec0463a605e2bc6db5e449c3 Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 25 Aug 2021 17:02:42 +0200 Subject: [PATCH 18/19] Update src/Network/IHTTPClient.php Co-authored-by: Hypolite Petovan --- src/Network/IHTTPClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Network/IHTTPClient.php b/src/Network/IHTTPClient.php index eadfb39fc..f7064e1c0 100644 --- a/src/Network/IHTTPClient.php +++ b/src/Network/IHTTPClient.php @@ -74,7 +74,7 @@ interface IHTTPClient * Send a GET to an URL. * * @param string $url URL to fetch - * @param array $opts (optional parameters) assoziative array with: + * @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 From 4ddaf49f537b6e79aac3f11f98440329aa3221c5 Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 25 Aug 2021 18:01:07 +0200 Subject: [PATCH 19/19] Revert setCookieJar() and add overwrite parameter fpr rare cases --- src/Factory/HTTPClientFactory.php | 3 +++ src/Network/HTTPClient.php | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Factory/HTTPClientFactory.php b/src/Factory/HTTPClientFactory.php index 7da2718d7..7f7d5e5d7 100644 --- a/src/Factory/HTTPClientFactory.php +++ b/src/Factory/HTTPClientFactory.php @@ -101,6 +101,9 @@ class HTTPClientFactory extends BaseFactory $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() . '/url_resolver.cookie', true); return new HTTPClient($logger, $this->profiler, $guzzle, $resolver); } diff --git a/src/Network/HTTPClient.php b/src/Network/HTTPClient.php index 72f11fa3e..f4293cd08 100644 --- a/src/Network/HTTPClient.php +++ b/src/Network/HTTPClient.php @@ -221,10 +221,6 @@ class HTTPClient implements IHTTPClient $url = trim($url, "'"); - // Designate a temporary file that will store cookies during the session. - // Some websites test the browser for cookie support, so this enhances results. - $this->resolver->setCookieJar(tempnam(get_temppath() , 'url_resolver-')); - $urlResult = $this->resolver->resolveURL($url); if ($urlResult->didErrorOccur()) {