Bearsampp 2026.5.5
Loading...
Searching...
No Matches
class.httpclient.php
Go to the documentation of this file.
1<?php
2/*
3 * Copyright (c) 2022 - 2024 Bearsampp
4 * License: GNU General Public License version 3 or later; see LICENSE.txt
5 * Website: https://bearsampp.com
6 * Github: https://github.com/Bearsampp
7 */
12{
17 private static $verbose = false;
18
25 public static function setVerbose($verbose)
26 {
27 self::$verbose = (bool) $verbose;
28 }
29
35 public static function getVerbose()
36 {
37 return self::$verbose;
38 }
39
47 public static function get($url, $headers = array())
48 {
49 $ch = curl_init();
50
51 // Parse URL to check if it's localhost
52 $parsedUrl = parse_url($url);
53 $isLocalhost = isset($parsedUrl['host']) && (
54 $parsedUrl['host'] === 'localhost' ||
55 $parsedUrl['host'] === '127.0.0.1' ||
56 $parsedUrl['host'] === '::1'
57 );
58
59 // Set basic cURL options
60 curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
61 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
62 curl_setopt($ch, CURLOPT_VERBOSE, self::$verbose);
63 curl_setopt($ch, CURLOPT_URL, $url);
64
65 // Configure SSL verification - allow self-signed for localhost
66 if ($isLocalhost) {
67 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
68 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
69 } else {
70 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
71 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
72
73 // Configure CA bundle for better compatibility
74 $caBundle = self::getCABundlePath();
75 if ($caBundle && file_exists($caBundle)) {
76 curl_setopt($ch, CURLOPT_CAINFO, $caBundle);
77 }
78 }
79
80 // Merge with default headers if any
81 $defaultHeaders = self::getDefaultHeaders();
82 $allHeaders = array_merge($defaultHeaders, $headers);
83 if (!empty($allHeaders)) {
84 curl_setopt($ch, CURLOPT_HTTPHEADER, $allHeaders);
85 }
86
87 $data = curl_exec($ch);
88
89 $error = null;
90 $errorCode = curl_errno($ch);
91 if ($errorCode) {
92 $error = curl_error($ch);
93 $errorType = self::categorizeCurlError($errorCode, $error, $isLocalhost);
94 Log::error('CURL Error (' . $errorType . '): ' . $error . ' (URL: ' . $url . ')');
95 }
96
97 // curl_close() is deprecated in PHP 8.0+ as it has no effect since PHP 8.0
98 // The resource is automatically closed when it goes out of scope
99 if (PHP_VERSION_ID < 80000) {
100 curl_close($ch);
101 }
102
103 // Always return array with consistent structure
104 if ($error !== null) {
105 return array('data' => trim($data), 'error' => $error, 'error_type' => self::categorizeCurlError($errorCode, $error, $isLocalhost));
106 }
107
108 return array('data' => trim($data));
109 }
110
120 public static function getApiJson($url, $headers = array())
121 {
122 $apiHeaders = self::getApiHeaders();
123 $allHeaders = array_merge($apiHeaders, $headers);
124 return self::get($url, $allHeaders);
125 }
126
134 public static function getCurlHeaders($url, $headers = array())
135 {
136 $ch = curl_init();
137
138 // Parse URL to check if it's localhost
139 $parsedUrl = parse_url($url);
140 $isLocalhost = isset($parsedUrl['host']) && (
141 $parsedUrl['host'] === 'localhost' ||
142 $parsedUrl['host'] === '127.0.0.1' ||
143 $parsedUrl['host'] === '::1'
144 );
145
146 // Set basic cURL options
147 curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
148 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
149 curl_setopt($ch, CURLOPT_VERBOSE, self::$verbose);
150 curl_setopt($ch, CURLOPT_HEADER, true);
151 curl_setopt($ch, CURLOPT_NOBODY, true); // HEAD request to get headers only
152 curl_setopt($ch, CURLOPT_URL, $url);
153
154 // Configure SSL verification - allow self-signed for localhost
155 if ($isLocalhost) {
156 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
157 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
158 } else {
159 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
160 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
161
162 // Configure CA bundle for better compatibility
163 $caBundle = self::getCABundlePath();
164 if ($caBundle && file_exists($caBundle)) {
165 curl_setopt($ch, CURLOPT_CAINFO, $caBundle);
166 }
167 }
168
169 // Merge with default headers if any
170 $defaultHeaders = self::getDefaultHeaders();
171 $allHeaders = array_merge($defaultHeaders, $headers);
172 if (!empty($allHeaders)) {
173 curl_setopt($ch, CURLOPT_HTTPHEADER, $allHeaders);
174 }
175
176 $response = curl_exec($ch);
177
178 $error = null;
179 $errorCode = curl_errno($ch);
180 if ($errorCode) {
181 $error = curl_error($ch);
182 $errorType = self::categorizeCurlError($errorCode, $error, $isLocalhost);
183 Log::error('CURL Error getting headers (' . $errorType . '): ' . $error . ' (URL: ' . $url . ')');
184 }
185
186 // curl_close() is deprecated in PHP 8.0+ as it has no effect since PHP 8.0
187 // The resource is automatically closed when it goes out of scope
188 if (PHP_VERSION_ID < 80000) {
189 curl_close($ch);
190 }
191
192 // Always return array with consistent structure
193 if ($error !== null) {
194 return array('headers' => array(), 'error' => $error, 'error_type' => self::categorizeCurlError($errorCode, $error, $isLocalhost));
195 }
196
197 // Parse headers from response
198 $parsedHeaders = array();
199 if (!empty($response)) {
200 $responseLines = explode("\n", $response);
201 foreach ($responseLines as $line) {
202 $line = trim($line);
203 if (!empty($line)) {
204 $parsedHeaders[] = $line;
205 }
206 }
207 }
208
209 return array('headers' => $parsedHeaders);
210 }
211
217 private static function getCABundlePath()
218 {
219 // Try common CA bundle locations
220 $possiblePaths = array(
221 // Windows CA store
222 'C:/Windows/System32/certs/cacert.pem',
223 // Common locations
224 dirname(__DIR__, 2) . '/ssl/cacert.pem',
225 // PHP's default location
226 ini_get('curl.cainfo'),
227 // Environment variable
228 getenv('CURL_CA_BUNDLE'),
229 );
230
231 foreach ($possiblePaths as $path) {
232 if ($path && file_exists($path)) {
233 return $path;
234 }
235 }
236
237 return null;
238 }
239
245 private static function getDefaultHeaders()
246 {
247 return array();
248 }
249
255 private static function getApiHeaders()
256 {
257 return array(
258 'User-Agent: ' . APP_GITHUB_USERAGENT,
259 'Accept: application/vnd.github.v3+json'
260 );
261 }
262
271 private static function categorizeCurlError($errorCode, $errorMessage, $isLocalhost)
272 {
273 // Connection errors (e.g., DNS resolution, connection refused, timeouts)
274 if (in_array($errorCode, array(
275 CURLE_COULDNT_RESOLVE_HOST, // 6
276 CURLE_COULDNT_CONNECT, // 7
277 CURLE_OPERATION_TIMEOUTED, // 28
278 CURLE_RECV_ERROR, // 56
279 CURLE_SEND_ERROR // 55
280 ))) {
281 return 'connection';
282 }
283
284 // SSL/TLS certificate and verification errors
285 if (in_array($errorCode, array(
286 CURLE_SSL_CACERT, // 60 - CA certificate verification failed
287 CURLE_SSL_PEER_CERTIFICATE, // 60 - Peer certificate cannot be authenticated
288 CURLE_SSL_CERTPROBLEM, // 58 - Certificate problem
289 CURLE_SSL_CIPHER, // 59 - Failed to use specified cipher
290 CURLE_SSL_CONNECT_ERROR, // 35 - SSL connection error
291 CURLE_SSL_ENGINE_NOTFOUND, // 53 - SSL engine not found
292 CURLE_SSL_ENGINE_SETFAILED // 54 - Cannot set SSL crypto engine as default
293 ))) {
294 // For localhost, treat SSL errors as self-signed certificate issues
295 return $isLocalhost ? 'self-signed' : 'ssl';
296 }
297
298 // Other errors
299 return 'other';
300 }
301
309 public static function getHttpHeaders($pingUrl)
310 {
311 if (function_exists('curl_version')) {
313 } else {
315 }
316
317 if (!empty($result)) {
318 $rebuildResult = array();
319 foreach ($result as $row) {
320 $row = trim($row);
321 if (!empty($row)) {
322 $rebuildResult[] = $row;
323 }
324 }
325 $result = $rebuildResult;
326
327 Log::debug('getHttpHeaders:');
328 foreach ($result as $header) {
329 Log::debug('-> ' . $header);
330 }
331 }
332
333 return $result;
334 }
335
346 public static function getFopenHttpHeaders($url)
347 {
348 $result = array();
349
350 $context = stream_context_create(array(
351 'ssl' => array(
352 'verify_peer' => true,
353 'verify_peer_name' => true,
354 'allow_self_signed' => false,
355 )
356 ));
357
358 $fp = @fopen($url, 'r', false, $context);
359 if ($fp) {
360 $meta = stream_get_meta_data($fp);
361 $result = isset($meta['wrapper_data']) ? $meta['wrapper_data'] : $result;
362 fclose($fp);
363 }
364
365 return $result;
366 }
367
377 public static function getCurlHttpHeaders($url)
378 {
380
381 // Handle error response from HttpClient
382 if (is_array($result) && isset($result['error'])) {
383 Log::error('Failed to get headers via HttpClient::getCurlHeaders: ' . $result['error']);
384 return array();
385 }
386
387 // Extract headers array from consistent HttpClient response structure
388 return isset($result['headers']) ? $result['headers'] : array();
389 }
390
391
400 public static function checkInternetState()
401 {
402 $connected = @fsockopen('www.google.com', 80);
403 if ($connected) {
404 fclose($connected);
405
406 return true; // Internet connection is active
407 } else {
408 return false; // Internet connection is not active
409 }
410 }
411}
$result
static setVerbose($verbose)
static getCurlHeaders($url, $headers=array())
static getDefaultHeaders()
static getCABundlePath()
static getHttpHeaders($pingUrl)
static getApiHeaders()
static getApiJson($url, $headers=array())
static getVerbose()
static get($url, $headers=array())
static getFopenHttpHeaders($url)
static categorizeCurlError($errorCode, $errorMessage, $isLocalhost)
static getCurlHttpHeaders($url)
static checkInternetState()
static debug($data, $file=null)
static error($data, $file=null)
const APP_GITHUB_USERAGENT
Definition root.php:18