100 curl_setopt($ch, CURLOPT_RETURNTRANSFER,
true);
101 curl_setopt($ch, CURLOPT_VERBOSE,
true);
102 curl_setopt($ch, CURLOPT_HEADER,
true);
103 curl_setopt($ch, CURLOPT_URL, $url);
114 $responseHeaders = explode(
"\r\n\r\n",
$response, 2);
115 if (!isset($responseHeaders[0]) || empty($responseHeaders[0])) {
119 return explode(
"\n", $responseHeaders[0]);
187 if (empty($user) || !is_string($user)) {
192 $user = rawurlencode($user);
196 return "https://github.com/{$user}";
199 if (empty($repo) || !is_string($repo)) {
202 $repo = rawurlencode($repo);
204 return "https://github.com/{$user}/{$repo}";
207 if (empty($repo) || !is_string($repo)) {
210 $repo = rawurlencode($repo);
212 return "https://github.com/{$user}/{$repo}/issues";
215 if (empty($repo) || empty($branch) || empty($path) || !is_string($repo) || !is_string($branch) || !is_string($path)) {
218 $repo = rawurlencode($repo);
219 $branch = rawurlencode($branch);
221 $path = ltrim($path,
'/');
222 $segments = array_map(
'rawurlencode', explode(
'/', $path));
223 $pathEncoded = implode(
'/', $segments);
225 return "https://raw.githubusercontent.com/{$user}/{$repo}/{$branch}/{$pathEncoded}";
259 $context = stream_context_create(array(
261 'verify_peer' =>
false,
262 'verify_peer_name' =>
false,
263 'allow_self_signed' =>
true,
267 $fp = @stream_socket_client(($ssl ?
'ssl://' :
'') . $host .
':' .
$port, $errno, $errstr, 5, STREAM_CLIENT_CONNECT, $context);
270 $result = explode(PHP_EOL, $out);
275 $rebuildResult = array();
279 $rebuildResult[] = $row;
305 public static function fetchGet($url, $verify =
true)
308 curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
309 curl_setopt($ch, CURLOPT_RETURNTRANSFER,
true);
310 curl_setopt($ch, CURLOPT_VERBOSE,
true);
311 curl_setopt($ch, CURLOPT_URL, $url);
312 curl_setopt($ch, CURLOPT_HTTPHEADER, array(
314 'Accept: application/json, text/plain, */*',
317 $data = curl_exec($ch);
318 if (curl_errno($ch)) {
322 return trim((
string)$data);
411 if ($url ===
'' || strpos($url,
'?') ===
false) {
415 $sensitiveParams = [
'api_key',
'download_id',
'token',
'access_token',
'auth',
'password',
'passwd',
'key',
'secret'];
417 [$base, $query] = explode(
'?', $url, 2);
418 parse_str($query, $params);
420 foreach ($params as $name => &$value) {
421 foreach ($sensitiveParams as $sensitive) {
422 if (stripos($name, $sensitive) !==
false) {
429 return $base .
'?' . http_build_query($params);
499 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $verify);
500 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $verify ? 2 : 0);
504 if (is_file($cacert)) {
505 curl_setopt($ch, CURLOPT_CAINFO, $cacert);
528 public static function proxyFetch($url, $method =
'GET', $verify =
true)
530 if (!self::isGithubHost($url)) {
531 Log::error(
'[PROXY] proxyFetch() blocked non-GitHub URL: ' . self::safeUrlForLog($url));
536 $method = strtoupper($method);
537 Log::trace(
'[PROXY] proxyFetch() ' . $method .
' target: ' . self::safeUrlForLog($url));
540 'url' => (
string)$url,
546 curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
547 curl_setopt($ch, CURLOPT_RETURNTRANSFER,
true);
548 curl_setopt($ch, CURLOPT_HEADER,
true);
549 curl_setopt($ch, CURLOPT_POST,
true);
550 curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
551 curl_setopt($ch, CURLOPT_HTTPHEADER, array(
552 'Content-Type: application/json',
560 Log::error(
'Proxy request failed: ' . curl_error($ch));
561 Log::trace(
'[PROXY] proxyFetch() FAILED - target: ' . self::safeUrlForLog($url) .
' - ' . curl_error($ch));
566 $status = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
567 $headerSize = (int)curl_getinfo($ch, CURLINFO_HEADER_SIZE);
574 if ($headerSize > 0 && strlen(
$response) >= $headerSize) {
579 $headerData = substr(
$response, 0, $headerSize);
583 $blocks = preg_split(
'/\r?\n\r?\n/', trim($headerData));
584 $headerBlock = end($blocks);
586 foreach (explode(
"\r\n", $headerBlock) as $line) {
587 if (strpos($line,
':') ===
false) {
590 list($name, $value) = explode(
':', $line, 2);
594 $name = strtolower(trim($name));
595 $value = trim($value);
596 if ($name !==
'' && !isset($headers[$name])) {
597 $headers[$name] = $value;
603 $separatorPos = strpos(
$response,
"\r\n\r\n");
605 if ($separatorPos !==
false) {
606 foreach (explode(
"\r\n", substr(
$response, 0, $separatorPos)) as $line) {
607 if (strpos($line,
':') ===
false) {
610 list($name, $value) = explode(
':', $line, 2);
611 $name = strtolower(trim($name));
612 $value = trim($value);
613 if ($name !==
'' && !isset($headers[$name])) {
614 $headers[$name] = $value;
620 Log::trace(
'[PROXY] proxyFetch() END - status ' . $status .
', body length: ' . strlen($body));
622 return array(
'status' => $status,
'headers' => $headers,
'body' => $body);
641 public static function proxyDownload($url, $filePath, $progressBar =
false, $verify =
true)
643 if (!self::isGithubHost($url)) {
644 Log::error(
'[PROXY] proxyDownload() blocked non-GitHub URL: ' . self::safeUrlForLog($url));
649 Log::trace(
'[PROXY] proxyDownload() START target: ' . self::safeUrlForLog($url) .
' -> ' . $filePath);
651 $outputStream = @fopen($filePath,
'wb');
652 if ($outputStream ===
false) {
653 Log::trace(
'[PROXY] proxyDownload() FAILED - cannot open output file: ' . $filePath);
663 curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
664 curl_setopt($ch, CURLOPT_RETURNTRANSFER,
false);
665 curl_setopt($ch, CURLOPT_POST,
true);
666 curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(array(
667 'url' => (
string)$url,
670 curl_setopt($ch, CURLOPT_HTTPHEADER, array(
671 'Content-Type: application/json',
677 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, self::PROXY_CONNECT_TIMEOUT);
678 curl_setopt($ch, CURLOPT_LOW_SPEED_LIMIT, self::PROXY_LOW_SPEED_LIMIT);
679 curl_setopt($ch, CURLOPT_LOW_SPEED_TIME, self::PROXY_LOW_SPEED_TIME);
682 curl_setopt($ch, CURLOPT_HEADERFUNCTION,
function ($ch, $line) use (&$status) {
683 if (preg_match(
'#^HTTP/\S+\s+(\d{3})#', $line, $match) === 1) {
684 $status = (int)$match[1];
687 return strlen($line);
692 curl_setopt($ch, CURLOPT_WRITEFUNCTION,
function ($ch, $data) use (&$outputStream, &$chunksRead, $progressBar) {
693 if ($outputStream ===
false) {
697 $len = fwrite($outputStream, $data);
698 if ($len ===
false) {
704 echo json_encode(array(
'progress' => $chunksRead)) . PHP_EOL;
706 if (ob_get_length() !==
false) {
715 $success = (curl_exec($ch) !==
false);
716 $error = curl_error($ch);
723 $curlStatus = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
724 if ($curlStatus > 0) {
725 $status = $curlStatus;
728 fclose($outputStream);
731 Log::error(
'Proxy download failed: ' . $error);
732 Log::trace(
'[PROXY] proxyDownload() FAILED - status ' . $status .
', target: ' . self::safeUrlForLog($url) .
' - ' . $error);
734 Log::trace(
'[PROXY] proxyDownload() END - status ' . $status .
', chunks ' . $chunksRead .
', target: ' . self::safeUrlForLog($url));
737 return $success && $status >= 200 && $status < 300;
756 public static function downloadFile(
string $url,
string $filePath, $progressBar =
false, $verify =
true)
761 if (self::isGithubHost($url)) {
762 Log::trace(
'downloadFile() downloading via GitHub proxy: ' . $url);
765 Log::error(
'Error fetching content from URL: ' . $url);
767 return [
'error' =>
'Error fetching module'];
770 return [
'success' =>
true];
773 Log::trace(
'downloadFile() downloading directly (non-GitHub): ' . $url);
777 $inputStream = @fopen($url,
'rb',
false, self::getSslStreamContext(
true, $url));
778 if ($inputStream ===
false) {
779 Log::error(
'Error fetching content from URL: ' . $url);
781 return [
'error' =>
'Error fetching module'];
785 $outputStream = @fopen($filePath,
'wb');
786 if ($outputStream ===
false) {
787 Log::error(
'Error opening file for writing: ' . $filePath);
788 fclose($inputStream);
790 return [
'error' =>
'Error saving module'];
797 while (!feof($inputStream)) {
798 $buffer = fread($inputStream, $bufferSize);
799 fwrite($outputStream, $buffer);
804 $progress = $chunksRead;
805 echo json_encode([
'progress' => $progress]) . PHP_EOL;
808 if (ob_get_length() !==
false) {
815 fclose($inputStream);
816 fclose($outputStream);
818 return [
'success' =>
true];