PluginProbe ʕ •ᴥ•ʔ
JetBackup – Backup, Restore & Migrate / 3.1.9.2
JetBackup – Backup, Restore & Migrate v3.1.9.2
3.1.22.3 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.8.1 1.4.9 1.5.0 1.5.1 1.5.1.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.10 1.6.11 1.6.12 1.6.13 1.6.15 1.6.5.1 1.6.8.8 1.6.9 1.6.9.1 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7.5 2.0.8.7 2.0.9.11 2.0.9.14 2.0.9.15 2.0.9.6 2.0.9.7 2.0.9.9 3.1.10.7 3.1.11.1 3.1.12.3 3.1.13.4 3.1.14.17 3.1.15.4 3.1.16.1 3.1.17.5 3.1.18.10 3.1.18.8 3.1.18.9 3.1.19.8 3.1.20.3 3.1.21.3 3.1.7.9 3.1.9.2 trunk 1.1.90 1.1.91 1.2.0 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2
backup / src / JetBackup / Web / JetHttp.php
backup / src / JetBackup / Web Last commit date
File 1 year ago .htaccess 1 year ago JetHttp.php 1 year ago JetHttpResponse.php 1 year ago JetHttpResponseHeaders.php 1 year ago index.html 1 year ago web.config 1 year ago
JetHttp.php
501 lines
1 <?php
2 /*
3 *
4 * JetBackup @ package
5 * Created By Idan Ben-Ezra
6 *
7 * Copyrights @ JetApps
8 * https://www.jetapps.com
9 *
10 **/
11 namespace JetBackup\Web;
12
13 use JetBackup\Exception\HttpRequestException;
14 use JetBackup\Web\File\FileStream;
15 use JetBackup\Web\File\FileChunk;
16 use JetBackup\Web\File\FileDownload;
17
18 defined("__JETBACKUP__") or die("Restricted Access.");
19
20 class JetHttp {
21
22 const METHOD_HEAD = 1;
23 const METHOD_PUT = 2;
24 const METHOD_DELETE = 3;
25 const METHOD_GET = 4;
26 const METHOD_POST = 5;
27
28 const LINE_BREAK = "\r\n";
29
30 private $_curl;
31 private JetHttpResponse $_response;
32 private bool $_debug;
33 private array $_headers;
34 private array $_options;
35 private int $_method;
36 private $_body;
37 private bool $_is_ftp=false;
38
39 /**
40 * @param bool $debug
41 */
42 public function __construct(bool $debug=false) {
43 $this->reset();
44
45 $this->_debug = !!$debug;
46 if($this->_debug) $this->setVerbose();
47
48 $this->renew();
49 }
50
51 /**
52 * @param bool $debug
53 *
54 * @return JetHttp
55 */
56 public static function request(bool $debug=false):JetHttp {
57 return new JetHttp($debug);
58 }
59
60 /**
61 * @return void
62 */
63 public function reset():void {
64 $this->_headers = [];
65 $this->_options = [];
66 $this->_method = 0;
67 $this->_body = false;
68 $this->_response = new JetHttpResponse();
69
70 if($this->_curl) curl_reset($this->_curl);
71 }
72
73 /**
74 * @param string $key
75 * @param string $value
76 *
77 * @return JetHttp
78 */
79 public function addHeader(string $key, string $value):JetHttp {
80 $headers = $this->getHeaders();
81 $headers[$key] = $value;
82 $this->setHeaders($headers);
83 return $this;
84 }
85
86 /**
87 * @return array
88 */
89 public function getHeaders(): array {
90 return $this->_headers;
91 }
92
93 /**
94 * @param array $headers
95 *
96 * @return JetHttp
97 */
98 public function setHeaders(array $headers):JetHttp {
99 $this->_headers = $headers;
100 return $this;
101 }
102
103 /**
104 * @param int $option
105 * @param mixed $value
106 *
107 * @return JetHttp
108 */
109 public function addOption(int $option, $value):JetHttp {
110 $this->_options[$option] = $value;
111 return $this;
112 }
113
114 /**
115 * @return array
116 */
117 public function getOption(int $option) {
118 return $this->_options[$option] ?? null;
119 }
120
121 /**
122 * @return array
123 */
124 public function getOptions(): array {
125 return $this->_options;
126 }
127
128 /**
129 * @param array $options
130 *
131 * @return JetHttp
132 */
133 public function setOptions(array $options):JetHttp {
134 $this->_options = $options;
135 return $this;
136 }
137
138 /**
139 * @param int $option
140 *
141 * @return bool
142 */
143 public function optionExists(int $option):bool {
144 $options = $this->getOptions();
145 return isset($options[$option]);
146 }
147
148 /**
149 * @return int
150 */
151 public function getMethod(): int {
152 return $this->_method;
153 }
154
155 /**
156 * @param int $method
157 *
158 * @return JetHttp
159 */
160 public function setMethod(int $method):JetHttp {
161 $this->_method = $method;
162 return $this;
163 }
164
165 /**
166 * @return string|array|false
167 */
168 public function getBody() {
169 return $this->_body;
170 }
171
172 /**
173 * @param string|array|false $body
174 *
175 * @return JetHttp
176 */
177 public function setBody($body):JetHttp {
178 $this->_body = $body;
179 return $this;
180 }
181
182 /**
183 * @param null $stderr
184 *
185 * @return JetHttp
186 */
187 public function setVerbose($stderr=null):JetHttp {
188 $this->addOption(CURLOPT_VERBOSE, true);
189 if($stderr !== null) $this->addOption(CURLOPT_STDERR, $stderr);
190 return $this;
191 }
192
193 /**
194 * @param int|null $limit
195 * @param int|null $time
196 *
197 * @return JetHttp
198 */
199 public function setLowSpeed(?int $limit=null, ?int $time=null):JetHttp {
200 if($limit !== null) $this->addOption(CURLOPT_LOW_SPEED_LIMIT, $limit);
201 if($time !== null) $this->addOption(CURLOPT_LOW_SPEED_TIME, $time);
202 return $this;
203 }
204
205 /**
206 * @param int|null $peer
207 * @param int|null $host
208 *
209 * @return JetHttp
210 */
211 public function setSSLVerify(?int $peer=null, ?int $host=null):JetHttp {
212 if($peer !== null) $this->addOption(CURLOPT_SSL_VERIFYPEER, $peer);
213 if($host !== null) $this->addOption(CURLOPT_SSL_VERIFYHOST, $host);
214 return $this;
215 }
216
217 public function setAuth(?string $username=null, ?string $password=null, ?int $type=null):JetHttp {
218 if($username !== null) $this->addOption(CURLOPT_USERNAME, $username);
219 if($password !== null) $this->addOption(CURLOPT_USERPWD, $password);
220 if($type !== null) $this->addOption(CURLOPT_HTTPAUTH, $type);
221 return $this;
222 }
223
224 /**
225 * @param int $port
226 *
227 * @return $this
228 */
229 public function setPort(int $port):JetHttp {
230 $this->addOption(CURLOPT_PORT, $port);
231 return $this;
232 }
233
234 /**
235 * @param int $timeout
236 *
237 * @return JetHttp
238 */
239 public function setTimeout(int $timeout):JetHttp {
240 $this->addOption(CURLOPT_TIMEOUT, $timeout);
241 return $this;
242 }
243
244 /**
245 * @param int $timeout
246 *
247 * @return JetHttp
248 */
249 public function setConnectionTimeout(int $timeout):JetHttp {
250 $this->addOption(CURLOPT_CONNECTTIMEOUT, $timeout);
251 return $this;
252 }
253
254 /**
255 * @return JetHttp
256 */
257 public function setFollowLocation():JetHttp {
258 $this->addOption(CURLOPT_FOLLOWLOCATION, 1);
259 return $this;
260 }
261
262 public function setReturnTransfer():JetHttp {
263 $this->addOption(CURLOPT_RETURNTRANSFER, 1);
264 return $this;
265 }
266
267 /**
268 * @param string $url
269 * @param FileChunk $chunk
270 * @param bool $self_method
271 *
272 * @return JetHttpResponse
273 * @throws HttpRequestException
274 */
275 public function uploadChunk(string $url, FileChunk $chunk, $self_method=false):JetHttpResponse {
276
277 if(!$self_method) {
278 $this->setMethod(0);
279 $this->addOption(CURLOPT_PUT, 1);
280 }
281
282 $this->addOption(CURLOPT_INFILE, $chunk->getFile()->getDescriptor());
283 $this->addOption(CURLOPT_INFILESIZE, $chunk->getSize());
284 $this->addOption(CURLOPT_READFUNCTION, function ($ch, $fd, $length) use ($chunk) {
285 return $chunk->readPiece($length);
286 });
287
288 return $this->exec($url);
289 }
290
291 /**
292 * @param string $url
293 * @param FileStream $file
294 *
295 * @return JetHttpResponse
296 * @throws HttpRequestException
297 */
298 public function upload(string $url, FileStream $file):JetHttpResponse {
299 $this->setMethod(0);
300 $this->addOption(CURLOPT_PUT, 1);
301 $this->addOption(CURLOPT_INFILE, $file->getDescriptor());
302 $this->addOption(CURLOPT_INFILESIZE, $file->getSize());
303 $this->addOption(CURLOPT_READFUNCTION, function ($ch, $fd, $length) use ($file) {
304 return $file->read($length);
305 });
306
307 return $this->exec($url);
308 }
309
310 /**
311 * @param string $url
312 * @param FileStream $stream
313 * @param string $details
314 *
315 * @return JetHttpResponse
316 * @throws HttpRequestException
317 */
318 public function uploadString(string $url, FileStream $stream, string $details):JetHttpResponse {
319
320 $boundary = uniqid();
321 $delimiter = '-------------' . $boundary;
322
323 $body = "--$delimiter" . self::LINE_BREAK;
324 $body .= "Content-Type: application/json" . self::LINE_BREAK . self::LINE_BREAK;
325 $body .= $details . self::LINE_BREAK;
326 $body .= "--$delimiter" . self::LINE_BREAK;
327 $body .= "Content-Type: " . $stream->getMimeType() . self::LINE_BREAK . self::LINE_BREAK;
328 $body .= ($stream->getSize() > 0 ? $stream->read() : '') . self::LINE_BREAK;
329 $body .= "--$delimiter--" . self::LINE_BREAK;
330
331 $this->addHeader('Content-Type', 'multipart/related; boundary=' . $delimiter);
332 $this->addHeader('Content-Length', strlen($body));
333 $this->setMethod(self::METHOD_POST);
334 $this->setBody($body);
335
336 return $this->exec($url);
337 }
338
339 /**
340 * @param string $url
341 * @param FileDownload $fileDownload
342 *
343 * @return JetHttpResponse
344 * @throws HttpRequestException
345 */
346 public function download(string $url, FileDownload $fileDownload):JetHttpResponse {
347
348 $this->addOption(CURLOPT_WRITEFUNCTION, function($ch, $str) use ($fileDownload) {
349
350 $response = $this->_response->getHeaders();
351
352 if(!$this->_is_ftp && (!$response || $response->getCode() < 200 || $response->getCode() > 299)) {
353 $fileDownload->deleteFile();
354 $this->_response->appendBody($str);
355 } else {
356 $fileDownload->writeFile($str);
357 }
358
359 return strlen($str);
360
361 });
362
363 $this->_prepare($url);
364 curl_exec($this->_curl);
365 return $this->_finalize();
366 }
367
368 /**
369 * @param string $url
370 *
371 * @return JetHttpResponse
372 * @throws HttpRequestException
373 */
374 public function exec(string $url):JetHttpResponse {
375 $this->_prepare($url);
376 $this->_response->setBody(curl_exec($this->_curl));
377 return $this->_finalize();
378 }
379
380 /**
381 * @return JetHttpResponse
382 * @throws HttpRequestException
383 */
384 private function _finalize(): JetHttpResponse {
385 $error_code = curl_errno($this->_curl);
386 $error_message = curl_error($this->_curl);
387
388 /**
389 * Detect known transient SSL/network errors (case-insensitive)
390 * By default the error code will be 0, so we return custom error
391 * code 499 so our wrapper clients will trigger retry
392 */
393 $retryableSSL = (
394 stripos($error_message, 'SSL_ERROR_SYSCALL') !== false ||
395 stripos($error_message, 'Connection reset') !== false ||
396 stripos($error_message, 'Connection aborted') !== false ||
397 stripos($error_message, 'timeout') !== false ||
398 stripos($error_message, 'timed out') !== false ||
399 stripos($error_message, 'could not resolve host') !== false || // DNS issue
400 stripos($error_message, 'Failed to connect to') !== false || // TCP connection issue
401 stripos($error_message, 'Transfer closed') !== false // abrupt remote closure
402 );
403
404 if ($error_code || $retryableSSL) {
405 $code = $retryableSSL ? 499 : $error_code;
406 throw new HttpRequestException($error_message, $code);
407 }
408
409 return $this->_response;
410 }
411
412
413 /**
414 * @param string $url
415 *
416 * @return void
417 */
418 private function _prepare(string $url):void {
419
420 $this->_is_ftp = str_starts_with($url, 'ftp:') || str_starts_with($url, 'ftps:');
421 $this->_response->isFTP($this->_is_ftp);
422
423 $this->addOption(CURLOPT_URL, $url);
424 $this->addOption(CURLOPT_HEADER, 0);
425 $this->addOption(CURLOPT_HEADERFUNCTION, function($ch, $str) {
426 $this->_response->appendHeadersBuffer($str);
427 return strlen($str);
428 });
429
430 switch($this->getMethod()) {
431 case self::METHOD_HEAD: $this->addOption(CURLOPT_NOBODY, 1); break;
432 case self::METHOD_PUT: $this->addOption(CURLOPT_CUSTOMREQUEST, 'PUT'); break;
433 case self::METHOD_DELETE: $this->addOption(CURLOPT_CUSTOMREQUEST, 'DELETE'); break;
434 case self::METHOD_GET: $this->addOption(CURLOPT_CUSTOMREQUEST, 'GET'); break;
435 case self::METHOD_POST: $this->addOption(CURLOPT_POST, 1); break;
436 }
437
438 if($this->getBody() !== false) $this->addOption(CURLOPT_POSTFIELDS, $this->getBody());
439
440 $headers = [];
441 foreach($this->getHeaders() as $key => $value) $headers[] = "$key:$value";
442 if($headers) $this->addOption(CURLOPT_HTTPHEADER, $headers);
443
444 curl_setopt_array($this->_curl, $this->getOptions());
445 //$this->_logRequestDetails($url, $headers);
446 }
447
448 // Use for heavy debugs
449 private function _logRequestDetails(string $url, array $headers): void {
450 $options = $this->getOptions();
451 $method = $this->getMethod();
452 $body = $this->getBody();
453
454 $methodMap = [
455 self::METHOD_HEAD => 'HEAD',
456 self::METHOD_PUT => 'PUT',
457 self::METHOD_DELETE => 'DELETE',
458 self::METHOD_GET => 'GET',
459 self::METHOD_POST => 'POST',
460 ];
461
462 $logMessage = "[JetHttp Request]\n";
463 $logMessage .= "URL: $url\n";
464 $logMessage .= "Method: " . $methodMap[$method] ?? 'UNKNOWN' . "\n";
465 $logMessage .= "Headers: " . print_r($headers, true) . "\n";
466 $logMessage .= "Options: " . print_r($options, true) . "\n";
467 if ($body) {
468 $logMessage .= "Body: " . (is_string($body) ? $body : json_encode($body, JSON_PRETTY_PRINT)) . "\n";
469 }
470
471 // Choose logging option
472 //error_log($logMessage);
473 //echo $logMessage . "\n";
474 // file_put_contents('/path/to/log.txt', $logMessage, FILE_APPEND);
475
476 //foreach($this->getOptions() as $option => $value) echo "OPTION: $option -> " . (is_callable($value) ? "FUNC" : print_r($value, true)) . "\n";
477 //echo "\n\n";
478 }
479
480
481 /**
482 * @return void
483 */
484 public function close():void {
485 $this->_curl = false;
486 }
487
488 /**
489 * @return void
490 */
491 public function renew():void {
492 $this->_curl = curl_init();
493 }
494
495 /**
496 *
497 */
498 public function __destruct() {
499 $this->close();
500 }
501 }