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