]> git.mxchange.org Git - friendica.git/blob - src/Network/CurlResult.php
Merge pull request #8277 from MrPetovan/task/8251-use-about-for-pdesc
[friendica.git] / src / Network / CurlResult.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2020, Friendica
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\Network;
23
24 use Friendica\Core\Logger;
25 use Friendica\Network\HTTPException\InternalServerErrorException;
26 use Friendica\Util\Network;
27
28 /**
29  * A content class for Curl call results
30  */
31 class CurlResult
32 {
33         /**
34          * @var int HTTP return code or 0 if timeout or failure
35          */
36         private $returnCode;
37
38         /**
39          * @var string the content type of the Curl call
40          */
41         private $contentType;
42
43         /**
44          * @var string the HTTP headers of the Curl call
45          */
46         private $header;
47
48         /**
49          * @var array the HTTP headers of the Curl call
50          */
51         private $header_fields;
52
53         /**
54          * @var boolean true (if HTTP 2xx result) or false
55          */
56         private $isSuccess;
57
58         /**
59          * @var string the URL which was called
60          */
61         private $url;
62
63         /**
64          * @var string in case of redirect, content was finally retrieved from this URL
65          */
66         private $redirectUrl;
67
68         /**
69          * @var string fetched content
70          */
71         private $body;
72
73         /**
74          * @var array some informations about the fetched data
75          */
76         private $info;
77
78         /**
79          * @var boolean true if the URL has a redirect
80          */
81         private $isRedirectUrl;
82
83         /**
84          * @var boolean true if the curl request timed out
85          */
86         private $isTimeout;
87
88         /**
89          * @var int the error number or 0 (zero) if no error
90          */
91         private $errorNumber;
92
93         /**
94          * @var string the error message or '' (the empty string) if no
95          */
96         private $error;
97
98         /**
99          * Creates an errored CURL response
100          *
101          * @param string $url optional URL
102          *
103          * @return CurlResult a CURL with error response
104          * @throws InternalServerErrorException
105          */
106         public static function createErrorCurl($url = '')
107         {
108                 return new CurlResult($url, '', ['http_code' => 0]);
109         }
110
111         /**
112          * Curl constructor.
113          * @param string $url the URL which was called
114          * @param string $result the result of the curl execution
115          * @param array $info an additional info array
116          * @param int $errorNumber the error number or 0 (zero) if no error
117          * @param string $error the error message or '' (the empty string) if no
118          *
119          * @throws InternalServerErrorException when HTTP code of the CURL response is missing
120          */
121         public function __construct($url, $result, $info, $errorNumber = 0, $error = '')
122         {
123                 if (!array_key_exists('http_code', $info)) {
124                         throw new InternalServerErrorException('CURL response doesn\'t contains a response HTTP code');
125                 }
126
127                 $this->returnCode = $info['http_code'];
128                 $this->url = $url;
129                 $this->info = $info;
130                 $this->errorNumber = $errorNumber;
131                 $this->error = $error;
132
133                 Logger::log($url . ': ' . $this->returnCode . " " . $result, Logger::DATA);
134
135                 $this->parseBodyHeader($result);
136                 $this->checkSuccess();
137                 $this->checkRedirect();
138                 $this->checkInfo();
139         }
140
141         private function parseBodyHeader($result)
142         {
143                 // Pull out multiple headers, e.g. proxy and continuation headers
144                 // allow for HTTP/2.x without fixing code
145
146                 $header = '';
147                 $base = $result;
148                 while (preg_match('/^HTTP\/.+? \d+/', $base)) {
149                         $chunk = substr($base, 0, strpos($base, "\r\n\r\n") + 4);
150                         $header .= $chunk;
151                         $base = substr($base, strlen($chunk));
152                 }
153
154                 $this->body = substr($result, strlen($header));
155                 $this->header = $header;
156                 $this->header_fields = []; // Is filled on demand
157         }
158
159         private function checkSuccess()
160         {
161                 $this->isSuccess = ($this->returnCode >= 200 && $this->returnCode <= 299) || $this->errorNumber == 0;
162
163                 // Everything higher or equal 400 is not a success
164                 if ($this->returnCode >= 400) {
165                         $this->isSuccess = false;
166                 }
167
168                 if (!$this->isSuccess) {
169                         Logger::log('error: ' . $this->url . ': ' . $this->returnCode . ' - ' . $this->error, Logger::INFO);
170                         Logger::log('debug: ' . print_r($this->info, true), Logger::DATA);
171                 }
172
173                 if (!$this->isSuccess && $this->errorNumber == CURLE_OPERATION_TIMEDOUT) {
174                         $this->isTimeout = true;
175                 } else {
176                         $this->isTimeout = false;
177                 }
178         }
179
180         private function checkRedirect()
181         {
182                 if (!array_key_exists('url', $this->info)) {
183                         $this->redirectUrl = '';
184                 } else {
185                         $this->redirectUrl = $this->info['url'];
186                 }
187
188                 if ($this->returnCode == 301 || $this->returnCode == 302 || $this->returnCode == 303 || $this->returnCode== 307) {
189                         $redirect_parts = parse_url($this->info['redirect_url'] ?? '');
190                         if (empty($redirect_parts)) {
191                                 $redirect_parts = [];
192                         }
193
194                         if (preg_match('/(Location:|URI:)(.*?)\n/i', $this->header, $matches)) {
195                                 $redirect_parts2 = parse_url(trim(array_pop($matches)));
196                                 if (!empty($redirect_parts2)) {
197                                         $redirect_parts = array_merge($redirect_parts, $redirect_parts2);
198                                 }
199                         }
200
201                         $parts = parse_url($this->info['url'] ?? '');
202                         if (empty($parts)) {
203                                 $parts = [];
204                         }
205
206                         /// @todo Checking the corresponding RFC which parts of a redirect can be ommitted.
207                         $components = ['scheme', 'host', 'path', 'query', 'fragment'];
208                         foreach ($components as $component) {
209                                 if (empty($redirect_parts[$component]) && !empty($parts[$component])) {
210                                         $redirect_parts[$component] = $parts[$component];
211                                 }
212                         }
213
214                         $this->redirectUrl = Network::unparseURL($redirect_parts);
215
216                         $this->isRedirectUrl = true;
217                 } else {
218                         $this->isRedirectUrl = false;
219                 }
220         }
221
222         private function checkInfo()
223         {
224                 if (isset($this->info['content_type'])) {
225                         $this->contentType = $this->info['content_type'];
226                 } else {
227                         $this->contentType = '';
228                 }
229         }
230
231         /**
232          * Gets the Curl Code
233          *
234          * @return string The Curl Code
235          */
236         public function getReturnCode()
237         {
238                 return $this->returnCode;
239         }
240
241         /**
242          * Returns the Curl Content Type
243          *
244          * @return string the Curl Content Type
245          */
246         public function getContentType()
247         {
248                 return $this->contentType;
249         }
250
251         /**
252          * Returns the Curl headers
253          *
254          * @param string $field optional header field. Return all fields if empty
255          *
256          * @return string the Curl headers or the specified content of the header variable
257          */
258         public function getHeader(string $field = '')
259         {
260                 if (empty($field)) {
261                         return $this->header;
262                 }
263
264                 $field = strtolower(trim($field));
265
266                 $headers = $this->getHeaderArray();
267
268                 if (isset($headers[$field])) {
269                         return $headers[$field];
270                 }
271
272                 return '';
273         }
274
275         /**
276          * Check if a specified header exists
277          *
278          * @param string $field header field
279          *
280          * @return boolean "true" if header exists
281          */
282         public function inHeader(string $field)
283         {
284                 $field = strtolower(trim($field));
285
286                 $headers = $this->getHeaderArray();
287
288                 return array_key_exists($field, $headers);
289         }
290
291         /**
292          * Returns the Curl headers as an associated array
293          *
294          * @return array associated header array
295          */
296         public function getHeaderArray()
297         {
298                 if (!empty($this->header_fields)) {
299                         return $this->header_fields;
300                 }
301
302                 $this->header_fields = [];
303
304                 $lines = explode("\n", trim($this->header));
305                 foreach ($lines as $line) {
306                         $parts = explode(':', $line);
307                         $headerfield = strtolower(trim(array_shift($parts)));
308                         $headerdata = trim(implode(':', $parts));
309                         $this->header_fields[$headerfield] = $headerdata;
310                 }
311
312                 return $this->header_fields;
313         }
314
315         /**
316          * @return bool
317          */
318         public function isSuccess()
319         {
320                 return $this->isSuccess;
321         }
322
323         /**
324          * @return string
325          */
326         public function getUrl()
327         {
328                 return $this->url;
329         }
330
331         /**
332          * @return string
333          */
334         public function getRedirectUrl()
335         {
336                 return $this->redirectUrl;
337         }
338
339         /**
340          * @return string
341          */
342         public function getBody()
343         {
344                 return $this->body;
345         }
346
347         /**
348          * @return array
349          */
350         public function getInfo()
351         {
352                 return $this->info;
353         }
354
355         /**
356          * @return bool
357          */
358         public function isRedirectUrl()
359         {
360                 return $this->isRedirectUrl;
361         }
362
363         /**
364          * @return int
365          */
366         public function getErrorNumber()
367         {
368                 return $this->errorNumber;
369         }
370
371         /**
372          * @return string
373          */
374         public function getError()
375         {
376                 return $this->error;
377         }
378
379         /**
380          * @return bool
381          */
382         public function isTimeout()
383         {
384                 return $this->isTimeout;
385         }
386 }