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