Bearsampp 2026.3.26
API documentation
Loading...
Searching...
No Matches
class.util.php
Go to the documentation of this file.
1<?php
2/*
3 *
4 * * Copyright (c) 2022-2025 Bearsampp
5 * * License: GNU General Public License version 3 or later; see LICENSE.txt
6 * * Website: https://bearsampp.com
7 * * Github: https://github.com/Bearsampp
8 *
9 */
10
34class Util
35{
39 const LOG_ERROR = 'ERROR';
40 const LOG_WARNING = 'WARNING';
41 const LOG_INFO = 'INFO';
42 const LOG_DEBUG = 'DEBUG';
43 const LOG_TRACE = 'TRACE';
44
49 private static $logBuffer = [];
50
55 private static $logBufferSize = 50;
56
61 private static $shutdownRegistered = false;
62
67 private static $logStats = [
68 'buffered' => 0,
69 'flushed' => 0,
70 'writes' => 0
71 ];
72
77 private static $fileScanCache = null;
78
83 private static $fileScanCacheDuration = 3600;
84
89 private static $fileScanStats = [
90 'hits' => 0,
91 'misses' => 0,
92 'invalidations' => 0
93 ];
94
100 private static $cacheIntegrityKey = null;
101
106 private static $pathFormatCache = [];
107
112 private static $pathFormatCacheMaxSize = 500;
113
118 private static $pathFormatStats = [
119 'unix_hits' => 0,
120 'unix_misses' => 0,
121 'windows_hits' => 0,
122 'windows_misses' => 0
123 ];
124
133 public static function cleanArgv($name, $type = 'text')
134 {
135 if (isset($_SERVER['argv'])) {
136 if ($type == 'text') {
137 return (isset($_SERVER['argv'][$name]) && !empty($_SERVER['argv'][$name])) ? trim($_SERVER['argv'][$name]) : '';
138 } elseif ($type == 'numeric') {
139 return (isset($_SERVER['argv'][$name]) && is_numeric($_SERVER['argv'][$name])) ? intval($_SERVER['argv'][$name]) : '';
140 } elseif ($type == 'boolean') {
141 return (isset($_SERVER['argv'][$name])) ? true : false;
142 } elseif ($type == 'array') {
143 return (isset($_SERVER['argv'][$name]) && is_array($_SERVER['argv'][$name])) ? $_SERVER['argv'][$name] : array();
144 }
145 }
146
147 return false;
148 }
149
158 public static function cleanGetVar($name, $type = 'text')
159 {
160 if (is_string($name)) {
161 if ($type == 'text') {
162 $value = (isset($_GET[$name]) && !empty($_GET[$name])) ? stripslashes($_GET[$name]) : '';
163 // Additional sanitization: remove null bytes and control characters
164 return filter_var($value, FILTER_SANITIZE_FULL_SPECIAL_CHARS);
165 } elseif ($type == 'numeric') {
166 return (isset($_GET[$name]) && is_numeric($_GET[$name])) ? intval($_GET[$name]) : '';
167 } elseif ($type == 'boolean') {
168 return (isset($_GET[$name])) ? true : false;
169 } elseif ($type == 'array') {
170 return (isset($_GET[$name]) && is_array($_GET[$name])) ? $_GET[$name] : array();
171 }
172 }
173
174 return false;
175 }
176
185 public static function cleanPostVar($name, $type = 'text')
186 {
187 if (is_string($name)) {
188 if ($type == 'text') {
189 return (isset($_POST[$name]) && !empty($_POST[$name])) ? stripslashes(trim($_POST[$name])) : '';
190 } elseif ($type == 'number') {
191 return (isset($_POST[$name]) && is_numeric($_POST[$name])) ? intval($_POST[$name]) : '';
192 } elseif ($type == 'float') {
193 return (isset($_POST[$name]) && is_numeric($_POST[$name])) ? floatval($_POST[$name]) : '';
194 } elseif ($type == 'boolean') {
195 return (isset($_POST[$name])) ? true : false;
196 } elseif ($type == 'array') {
197 return (isset($_POST[$name]) && is_array($_POST[$name])) ? $_POST[$name] : array();
198 } elseif ($type == 'content') {
199 return (isset($_POST[$name]) && !empty($_POST[$name])) ? trim($_POST[$name]) : '';
200 }
201 }
202
203 return false;
204 }
205
213 public static function sanitizePID($pid)
214 {
215 // Remove all non-numeric characters
216 $sanitized = preg_replace('/[^0-9]/', '', (string)$pid);
217
218 if (empty($sanitized)) {
219 self::logWarning('Invalid PID provided: ' . var_export($pid, true));
220 return false;
221 }
222
223 $pidInt = (int)$sanitized;
224
225 // Validate range (PIDs are positive integers)
226 if ($pidInt <= 0 || $pidInt > 2147483647) {
227 self::logWarning('PID out of valid range: ' . $pidInt);
228 return false;
229 }
230
231 return $pidInt;
232 }
233
241 public static function sanitizePort($port)
242 {
243 $portStr = trim((string)$port);
244
245 // Require strictly digits to avoid silently changing meaning
246 if ($portStr === '' || !preg_match('/^\d+$/', $portStr)) {
247 self::logWarning('Invalid port provided: ' . var_export($port, true));
248 return false;
249 }
250
251 $portInt = (int)$portStr;
252
253 // Validate range (1-65535)
254 if ($portInt < 1 || $portInt > 65535) {
255 self::logWarning('Port out of valid range: ' . $portInt);
256 return false;
257 }
258
259 return $portInt;
260 }
261
269 public static function sanitizeServiceName($serviceName)
270 {
271 if (!is_string($serviceName) || empty($serviceName)) {
272 self::logWarning('Invalid service name: not a string or empty');
273 return false;
274 }
275
276 // Remove all characters except alphanumeric, underscore, and hyphen
277 $sanitized = preg_replace('/[^a-zA-Z0-9_-]/', '', $serviceName);
278
279 if (empty($sanitized)) {
280 self::logWarning('Service name became empty after sanitization: ' . $serviceName);
281 return false;
282 }
283
284 // Limit length to 256 characters (Windows service name limit)
285 if (strlen($sanitized) > 256) {
286 $sanitized = substr($sanitized, 0, 256);
287 }
288
289 return $sanitized;
290 }
291
299 public static function sanitizePath($path)
300 {
301 if (!is_string($path) || empty($path)) {
302 return false;
303 }
304
305 // Remove null bytes
306 $sanitized = str_replace("\0", '', $path);
307
308 // Check for path traversal attempts (but allow environment variables)
309 $pathWithoutEnvVars = preg_replace('/%[^%]+%/', '', $sanitized);
310 if (strpos($pathWithoutEnvVars, '..') !== false) {
311 self::logWarning('Path traversal attempt detected: ' . $path);
312 return false;
313 }
314
315 // Remove dangerous characters that could be used for command injection
316 // But preserve valid path characters including : for drive letters and ; for PATH
317 $sanitized = preg_replace('/[<>"|?*\x00-\x1F]/', '', $sanitized);
318
319 return $sanitized;
320 }
321
329 public static function sanitizeOutput($output)
330 {
331 if (!is_string($output)) {
332 return '';
333 }
334
335 // Remove null bytes
336 $output = str_replace("\0", '', $output);
337
338 // Escape HTML special characters
339 return htmlspecialchars($output, ENT_QUOTES | ENT_HTML5, 'UTF-8');
340 }
341
350 public static function contains($string, $search)
351 {
352 if (!empty($string) && !empty($search)) {
353 $result = stripos($string, $search);
354 if ($result !== false) {
355 return true;
356 } else {
357 return false;
358 }
359 } else {
360 return false;
361 }
362 }
363
372 public static function startWith($string, $search)
373 {
374 // Return false if string is NULL or empty
375 if ($string === null || $string === '') {
376 return false;
377 }
378
379 $length = strlen($search);
380
381 return (substr($string, 0, $length) === $search);
382 }
383
395 public static function endWith($string, $search)
396 {
397 $length = strlen($search);
398 $start = $length * -1;
399
400 return (substr($string, $start) === $search);
401 }
402
412 public static function random($length = 32, $withNumeric = true)
413 {
414 $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
415 if ($withNumeric) {
416 $characters .= '0123456789';
417 }
418
419 $charactersLength = strlen($characters);
420 $randomString = '';
421
422 try {
423 for ($i = 0; $i < $length; $i++) {
424 // Use cryptographically secure random_int() instead of rand()
425 $randomIndex = random_int(0, $charactersLength - 1);
426 $randomString .= $characters[$randomIndex];
427 }
428 } catch (Exception $e) {
429 self::logError('Failed to generate cryptographically secure random string: ' . $e->getMessage());
430 throw $e;
431 }
432
433 return $randomString;
434 }
435
445 public static function generateSecureToken($length = 32)
446 {
447 try {
448 return bin2hex(random_bytes($length));
449 } catch (Exception $e) {
450 self::logError('Failed to generate secure token: ' . $e->getMessage());
451 throw $e;
452 }
453 }
454
464 public static function generateSecureBytes($length = 32)
465 {
466 try {
467 return random_bytes($length);
468 } catch (Exception $e) {
469 self::logError('Failed to generate secure bytes: ' . $e->getMessage());
470 throw $e;
471 }
472 }
473
482 public static function clearFolders($paths, $exclude = array())
483 {
484 $result = array();
485 foreach ($paths as $path) {
486 $result[$path] = self::clearFolder($path, $exclude);
487 }
488
489 return $result;
490 }
491
500 public static function clearFolder($path, $exclude = array())
501 {
502 $result = array();
503 $result['return'] = true;
504 $result['nb_files'] = 0;
505
506 $handle = @opendir($path);
507 if (!$handle) {
508 return null;
509 }
510
511 while (false !== ($file = readdir($handle))) {
512 if ($file == '.' || $file == '..' || in_array($file, $exclude)) {
513 continue;
514 }
515 if (is_dir($path . '/' . $file)) {
516 $r = self::clearFolder($path . '/' . $file);
517 if (!$r) {
518 $result['return'] = false;
519
520 return $result;
521 }
522 } else {
523 $r = @unlink($path . '/' . $file);
524 if ($r) {
525 $result['nb_files']++;
526 } else {
527 $result['return'] = false;
528
529 return $result;
530 }
531 }
532 }
533
534 closedir($handle);
535
536 return $result;
537 }
538
544 public static function deleteFolder($path)
545 {
546 if (is_dir($path)) {
547 if (substr($path, strlen($path) - 1, 1) != '/') {
548 $path .= '/';
549 }
550 $files = glob($path . '*', GLOB_MARK);
551 foreach ($files as $file) {
552 if (is_dir($file)) {
553 self::deleteFolder($file);
554 } else {
555 unlink($file);
556 }
557 }
558 rmdir($path);
559 }
560 }
561
570 private static function findFile($startPath, $findFile)
571 {
572 $result = false;
573
574 $handle = @opendir($startPath);
575 if (!$handle) {
576 return false;
577 }
578
579 while (false !== ($file = readdir($handle))) {
580 if ($file == '.' || $file == '..') {
581 continue;
582 }
583 if (is_dir($startPath . '/' . $file)) {
584 $result = self::findFile($startPath . '/' . $file, $findFile);
585 if ($result !== false) {
586 break;
587 }
588 } elseif ($file == $findFile) {
589 $result = self::formatUnixPath($startPath . '/' . $file);
590 break;
591 }
592 }
593
594 closedir($handle);
595
596 return $result;
597 }
598
606 public static function isValidIp($ip)
607 {
608 return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)
609 || filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
610 }
611
619 public static function isValidPort($port)
620 {
621 return is_numeric($port) && ($port > 0 && $port <= 65535);
622 }
623
631 public static function replaceDefine($path, $var, $value)
632 {
633 self::replaceInFile($path, array(
634 '/^define\‍((.*?)' . $var . '(.*?),/' => 'define(\'' . $var . '\', ' . (is_int($value) ? $value : '\'' . $value . '\'') . ');'
635 ));
636 }
637
644 public static function replaceInFile($path, $replaceList)
645 {
646 if (file_exists($path)) {
647 $lines = file($path);
648 $fp = fopen($path, 'w');
649 foreach ($lines as $nb => $line) {
650 $replaceDone = false;
651 foreach ($replaceList as $regex => $replace) {
652 if (preg_match($regex, $line, $matches)) {
653 $countParams = preg_match_all('/{{(\d+)}}/', $replace, $paramsMatches);
654 if ($countParams > 0 && $countParams <= count($matches)) {
655 foreach ($paramsMatches[1] as $paramsMatch) {
656 $replace = str_replace('{{' . $paramsMatch . '}}', $matches[$paramsMatch], $replace);
657 }
658 }
659 self::logTrace('Replace in file ' . $path . ' :');
660 self::logTrace('## line_num: ' . trim($nb));
661 self::logTrace('## old: ' . trim($line));
662 self::logTrace('## new: ' . trim($replace));
663 fwrite($fp, $replace . PHP_EOL);
664
665 $replaceDone = true;
666 break;
667 }
668 }
669 if (!$replaceDone) {
670 fwrite($fp, $line);
671 }
672 }
673 fclose($fp);
674 }
675 }
676
684 public static function getVersionList($path)
685 {
686 $result = array();
687
688 $handle = @opendir($path);
689 if (!$handle) {
690 return false;
691 }
692
693 while (false !== ($file = readdir($handle))) {
694 $filePath = $path . '/' . $file;
695 if ($file != '.' && $file != '..' && is_dir($filePath) && $file != 'current') {
696 $result[] = str_replace(basename($path), '', $file);
697 }
698 }
699
700 closedir($handle);
701 natcasesort($result);
702
703 return $result;
704 }
705
711 public static function getMicrotime()
712 {
713 list($usec, $sec) = explode(' ', microtime());
714
715 return ((float)$usec + (float)$sec);
716 }
717
718 public static function getAppBinsRegKey($fromRegistry = true)
719 {
720 global $bearsamppRegistry;
721
722 if ($fromRegistry) {
723 $value = $bearsamppRegistry->getValue(
727 );
728 self::logDebug('App reg key from registry: ' . $value);
729 } else {
730 global $bearsamppBins, $bearsamppTools;
731 $value = '';
732 if ($bearsamppBins->getApache()->isEnable()) {
733 $value .= $bearsamppBins->getApache()->getSymlinkPath() . '/bin;';
734 }
735 if ($bearsamppBins->getPhp()->isEnable()) {
736 $value .= $bearsamppBins->getPhp()->getSymlinkPath() . ';';
737 $value .= $bearsamppBins->getPhp()->getSymlinkPath() . '/pear;';
738 $value .= $bearsamppBins->getPhp()->getSymlinkPath() . '/deps;';
739 $value .= $bearsamppBins->getPhp()->getSymlinkPath() . '/imagick;';
740 }
741 if ($bearsamppBins->getNodejs()->isEnable()) {
742 $value .= $bearsamppBins->getNodejs()->getSymlinkPath() . ';';
743 }
744 if ($bearsamppTools->getComposer()->isEnable()) {
745 $value .= $bearsamppTools->getComposer()->getSymlinkPath() . ';';
746 $value .= $bearsamppTools->getComposer()->getSymlinkPath() . '/vendor/bin;';
747 }
748 if ($bearsamppTools->getGhostscript()->isEnable()) {
749 $value .= $bearsamppTools->getGhostscript()->getSymlinkPath() . '/bin;';
750 }
751 if ($bearsamppTools->getGit()->isEnable()) {
752 $value .= $bearsamppTools->getGit()->getSymlinkPath() . '/bin;';
753 }
754 if ($bearsamppTools->getNgrok()->isEnable()) {
755 $value .= $bearsamppTools->getNgrok()->getSymlinkPath() . ';';
756 }
757 if ($bearsamppTools->getPerl()->isEnable()) {
758 $value .= $bearsamppTools->getPerl()->getSymlinkPath() . '/perl/site/bin;';
759 $value .= $bearsamppTools->getPerl()->getSymlinkPath() . '/perl/bin;';
760 $value .= $bearsamppTools->getPerl()->getSymlinkPath() . '/c/bin;';
761 }
762 if ($bearsamppTools->getPython()->isEnable()) {
763 $value .= $bearsamppTools->getPython()->getSymlinkPath() . '/bin;';
764 }
765 if ($bearsamppTools->getRuby()->isEnable()) {
766 $value .= $bearsamppTools->getRuby()->getSymlinkPath() . '/bin;';
767 }
768 $value = self::formatWindowsPath($value);
769 self::logDebug('Generated app bins reg key: ' . $value);
770 }
771
772 return $value;
773 }
774
782 public static function setAppBinsRegKey($value)
783 {
784 global $bearsamppRegistry;
785
786 return $bearsamppRegistry->setStringValue(
790 $value
791 );
792 }
793
799 public static function getAppPathRegKey()
800 {
801 global $bearsamppRegistry;
802
803 return $bearsamppRegistry->getValue(
807 );
808 }
809
817 public static function setAppPathRegKey($value)
818 {
819 global $bearsamppRegistry;
820
821 return $bearsamppRegistry->setStringValue(
825 $value
826 );
827 }
828
834 public static function getSysPathRegKey()
835 {
836 global $bearsamppRegistry;
837
838 return $bearsamppRegistry->getValue(
842 );
843 }
844
852 public static function setSysPathRegKey($value)
853 {
854 global $bearsamppRegistry;
855
856 return $bearsamppRegistry->setExpandStringValue(
860 $value
861 );
862 }
863
869 public static function getProcessorRegKey()
870 {
871 global $bearsamppRegistry;
872
873 return $bearsamppRegistry->getValue(
877 );
878 }
879
885 public static function getStartupLnkPath()
886 {
887 return Vbs::getStartupPath(APP_TITLE . '.lnk');
888 }
889
895 public static function isLaunchStartup()
896 {
897 return file_exists(self::getStartupLnkPath());
898 }
899
905 public static function enableLaunchStartup()
906 {
907 return Vbs::createShortcut(self::getStartupLnkPath());
908 }
909
915 public static function disableLaunchStartup()
916 {
917 $startupLnkPath = self::getStartupLnkPath();
918
919 // Check if file exists before attempting to delete
920 if (file_exists($startupLnkPath)) {
921 return @unlink($startupLnkPath);
922 }
923
924 // Return true if the file doesn't exist (already disabled)
925 return true;
926 }
927
936 private static function log($data, $type, $file = null)
937 {
939
940 // Safety check: if globals aren't initialized, use error_log as fallback
941 if (!isset($bearsamppRoot) || !isset($bearsamppCore) || !isset($bearsamppConfig)) {
942 error_log('[' . $type . '] ' . $data);
943 return;
944 }
945
946 // Register shutdown handler on first log call
947 if (!self::$shutdownRegistered) {
948 register_shutdown_function([__CLASS__, 'flushLogBuffer']);
949 self::$shutdownRegistered = true;
950 }
951
952 $file = $file == null ? ($type == self::LOG_ERROR ? $bearsamppRoot->getErrorLogFilePath() : $bearsamppRoot->getLogFilePath()) : $file;
953 if (!$bearsamppRoot->isRoot()) {
954 $file = $bearsamppRoot->getHomepageLogFilePath();
955 }
956
957 $verbose = array();
958 $verbose[Config::VERBOSE_SIMPLE] = $type == self::LOG_ERROR || $type == self::LOG_WARNING;
959 $verbose[Config::VERBOSE_REPORT] = $verbose[Config::VERBOSE_SIMPLE] || $type == self::LOG_INFO;
960 $verbose[Config::VERBOSE_DEBUG] = $verbose[Config::VERBOSE_REPORT] || $type == self::LOG_DEBUG;
961 $verbose[Config::VERBOSE_TRACE] = $verbose[Config::VERBOSE_DEBUG] || $type == self::LOG_TRACE;
962
963 $writeLog = false;
964 if ($bearsamppConfig->getLogsVerbose() == Config::VERBOSE_SIMPLE && $verbose[Config::VERBOSE_SIMPLE]) {
965 $writeLog = true;
966 } elseif ($bearsamppConfig->getLogsVerbose() == Config::VERBOSE_REPORT && $verbose[Config::VERBOSE_REPORT]) {
967 $writeLog = true;
968 } elseif ($bearsamppConfig->getLogsVerbose() == Config::VERBOSE_DEBUG && $verbose[Config::VERBOSE_DEBUG]) {
969 $writeLog = true;
970 } elseif ($bearsamppConfig->getLogsVerbose() == Config::VERBOSE_TRACE && $verbose[Config::VERBOSE_TRACE]) {
971 $writeLog = true;
972 }
973
974 if ($writeLog) {
975 // Add to buffer instead of writing immediately
976 self::$logBuffer[] = [
977 'file' => $file,
978 'data' => $data,
979 'type' => $type,
980 'time' => time()
981 ];
982 self::$logStats['buffered']++;
983
984 // Flush if buffer is full or if it's an error (errors should be written immediately)
985 if (count(self::$logBuffer) >= self::$logBufferSize || $type == self::LOG_ERROR) {
987 }
988 }
989 }
990
997 public static function flushLogBuffer()
998 {
999 if (empty(self::$logBuffer)) {
1000 return;
1001 }
1002
1003 global $bearsamppCore;
1004
1005 // Group logs by file
1006 $logsByFile = [];
1007 foreach (self::$logBuffer as $log) {
1008 if (!isset($logsByFile[$log['file']])) {
1009 $logsByFile[$log['file']] = [];
1010 }
1011 $logsByFile[$log['file']][] = $log;
1012 }
1013
1014 // Write all logs at once per file
1015 foreach ($logsByFile as $file => $logs) {
1016 $content = '';
1017 foreach ($logs as $log) {
1018 $content .= '[' . date('Y-m-d H:i:s', $log['time']) . '] # ' .
1019 APP_TITLE . ' ' . $bearsamppCore->getAppVersion() . ' # ' .
1020 $log['type'] . ': ' . $log['data'] . PHP_EOL;
1021 }
1022
1023 // Use LOCK_EX to prevent race conditions
1024 @file_put_contents($file, $content, FILE_APPEND | LOCK_EX);
1025 self::$logStats['writes']++;
1026 }
1027
1028 self::$logStats['flushed'] += count(self::$logBuffer);
1029 self::$logBuffer = [];
1030 }
1031
1038 public static function getLogStats()
1039 {
1040 return self::$logStats;
1041 }
1042
1050 public static function setLogBufferSize($size)
1051 {
1052 if ($size > 0 && $size <= 1000) {
1053 self::$logBufferSize = $size;
1054 }
1055 }
1056
1062 public static function getLogBufferSize()
1063 {
1064 return self::$logBufferSize;
1065 }
1066
1073 public static function logSeparator()
1074 {
1075 global $bearsamppRoot;
1076
1077 $logs = array(
1078 $bearsamppRoot->getLogFilePath(),
1079 $bearsamppRoot->getErrorLogFilePath(),
1080 $bearsamppRoot->getServicesLogFilePath(),
1081 $bearsamppRoot->getRegistryLogFilePath(),
1082 $bearsamppRoot->getStartupLogFilePath(),
1083 $bearsamppRoot->getBatchLogFilePath(),
1084 $bearsamppRoot->getVbsLogFilePath(),
1085 $bearsamppRoot->getWinbinderLogFilePath(),
1086 );
1087
1088 $separator = '========================================================================================' . PHP_EOL;
1089 foreach ($logs as $log) {
1090 if (!file_exists($log)) {
1091 continue; // Skip to the next iteration if the file does not exist
1092 }
1093 $logContent = @file_get_contents($log);
1094 if ($logContent !== false && !self::endWith($logContent, $separator)) {
1095 file_put_contents($log, $separator, FILE_APPEND);
1096 }
1097 }
1098 }
1099
1107 public static function logTrace($data, $file = null)
1108 {
1109 self::log($data, self::LOG_TRACE, $file);
1110 }
1111
1119 public static function logDebug($data, $file = null)
1120 {
1121 self::log($data, self::LOG_DEBUG, $file);
1122 }
1123
1131 public static function logInfo($data, $file = null)
1132 {
1133 self::log($data, self::LOG_INFO, $file);
1134 }
1135
1143 public static function logWarning($data, $file = null)
1144 {
1145 self::log($data, self::LOG_WARNING, $file);
1146 }
1147
1155 public static function logError($data, $file = null)
1156 {
1157 self::log($data, self::LOG_ERROR, $file);
1158 }
1159
1165 public static function logInitClass($classInstance)
1166 {
1167 self::logTrace('Init ' . get_class($classInstance));
1168 }
1169
1175 public static function logReloadClass($classInstance)
1176 {
1177 self::logTrace('Reload ' . get_class($classInstance));
1178 }
1179
1185 public static function getPowerShellPath()
1186 {
1187 if (is_dir('C:\Windows\System32\WindowsPowerShell')) {
1188 return self::findFile('C:\Windows\System32\WindowsPowerShell', 'powershell.exe');
1189 }
1190
1191 return false;
1192 }
1193
1204 public static function findRepos($initPath, $startPath, $checkFile, $maxDepth = 1)
1205 {
1206 $depth = substr_count(str_replace($initPath, '', $startPath), '/');
1207 $result = array();
1208
1209 $handle = @opendir($startPath);
1210 if (!$handle) {
1211 return $result;
1212 }
1213
1214 while (false !== ($file = readdir($handle))) {
1215 if ($file == '.' || $file == '..') {
1216 continue;
1217 }
1218 if (is_dir($startPath . '/' . $file) && ($initPath == $startPath || $depth <= $maxDepth)) {
1219 $tmpResults = self::findRepos($initPath, $startPath . '/' . $file, $checkFile, $maxDepth);
1220 foreach ($tmpResults as $tmpResult) {
1221 $result[] = $tmpResult;
1222 }
1223 } elseif (is_file($startPath . '/' . $checkFile) && !in_array($startPath, $result)) {
1224 $result[] = self::formatUnixPath($startPath);
1225 }
1226 }
1227
1228 closedir($handle);
1229
1230 return $result;
1231 }
1232
1244 public static function formatWindowsPath($path)
1245 {
1246 // Return early for empty strings
1247 if (empty($path)) {
1248 return $path;
1249 }
1250
1251 // Check cache first
1252 $cacheKey = 'w_' . $path;
1253 if (isset(self::$pathFormatCache[$cacheKey])) {
1254 self::$pathFormatStats['windows_hits']++;
1255 return self::$pathFormatCache[$cacheKey];
1256 }
1257
1258 self::$pathFormatStats['windows_misses']++;
1259
1260 // Perform the conversion
1261 $result = str_replace('/', '\\', $path);
1262
1263 // Store in cache if under size limit
1264 if (count(self::$pathFormatCache) < self::$pathFormatCacheMaxSize) {
1265 self::$pathFormatCache[$cacheKey] = $result;
1266 } else {
1267 // Cache is full - implement LRU by removing first 10% of entries
1268 $removeCount = (int)(self::$pathFormatCacheMaxSize * 0.1);
1269 self::$pathFormatCache = array_slice(self::$pathFormatCache, $removeCount, null, true);
1270 self::$pathFormatCache[$cacheKey] = $result;
1271 }
1272
1273 return $result;
1274 }
1275
1287 public static function formatUnixPath($path)
1288 {
1289 // Return early for empty strings
1290 if (empty($path)) {
1291 return $path;
1292 }
1293
1294 // Check cache first
1295 $cacheKey = 'u_' . $path;
1296 if (isset(self::$pathFormatCache[$cacheKey])) {
1297 self::$pathFormatStats['unix_hits']++;
1298 return self::$pathFormatCache[$cacheKey];
1299 }
1300
1301 self::$pathFormatStats['unix_misses']++;
1302
1303 // Perform the conversion
1304 $result = str_replace('\\', '/', $path);
1305
1306 // Store in cache if under size limit
1307 if (count(self::$pathFormatCache) < self::$pathFormatCacheMaxSize) {
1308 self::$pathFormatCache[$cacheKey] = $result;
1309 } else {
1310 // Cache is full - implement LRU by removing first 10% of entries
1311 $removeCount = (int)(self::$pathFormatCacheMaxSize * 0.1);
1312 self::$pathFormatCache = array_slice(self::$pathFormatCache, $removeCount, null, true);
1313 self::$pathFormatCache[$cacheKey] = $result;
1314 }
1315
1316 return $result;
1317 }
1318
1325 public static function getPathFormatStats()
1326 {
1327 return self::$pathFormatStats;
1328 }
1329
1336 public static function clearPathFormatCache()
1337 {
1338 self::$pathFormatCache = [];
1339 self::$pathFormatStats = [
1340 'unix_hits' => 0,
1341 'unix_misses' => 0,
1342 'windows_hits' => 0,
1343 'windows_misses' => 0
1344 ];
1345 }
1346
1352 public static function getPathFormatCacheSize()
1353 {
1354 return count(self::$pathFormatCache);
1355 }
1356
1364 public static function imgToBase64($path)
1365 {
1366 $type = pathinfo($path, PATHINFO_EXTENSION);
1367 $data = file_get_contents($path);
1368
1369 return 'data:image/' . $type . ';base64,' . base64_encode($data);
1370 }
1371
1379 public static function utf8ToCp1252($data)
1380 {
1381 return iconv('UTF-8', 'WINDOWS-1252//IGNORE', $data);
1382 }
1383
1391 public static function cp1252ToUtf8($data)
1392 {
1393 return iconv('WINDOWS-1252', 'UTF-8//IGNORE', $data);
1394 }
1395
1399 public static function startLoading()
1400 {
1401 global $bearsamppCore, $bearsamppWinbinder;
1402
1403 self::logTrace('startLoading() called');
1404 self::logTrace('PHP executable: ' . $bearsamppCore->getPhpExe());
1405 self::logTrace('Root file: ' . Core::isRoot_FILE);
1406 self::logTrace('Action: ' . Action::LOADING);
1407
1408 $command = Core::isRoot_FILE . ' ' . Action::LOADING;
1409 self::logTrace('Executing command: ' . $bearsamppCore->getPhpExe() . ' ' . $command);
1410
1411 $result = $bearsamppWinbinder->exec($bearsamppCore->getPhpExe(), $command);
1412 self::logTrace('exec() returned: ' . var_export($result, true));
1413
1414 self::logTrace('startLoading() completed');
1415 }
1416
1420 public static function stopLoading()
1421 {
1422 global $bearsamppCore;
1423 if (file_exists($bearsamppCore->getLoadingPid())) {
1424 $pids = file($bearsamppCore->getLoadingPid());
1425 foreach ($pids as $pid) {
1426 Win32Ps::kill($pid);
1427 }
1428 @unlink($bearsamppCore->getLoadingPid());
1429 }
1430
1431 // Clean up status file
1433 }
1434
1441 public static function updateLoadingText($text)
1442 {
1443 global $bearsamppCore;
1444
1445 $statusFile = $bearsamppCore->getTmpPath() . '/loading_status.txt';
1446 file_put_contents($statusFile, json_encode(['text' => $text]));
1447 }
1448
1452 public static function clearLoadingText()
1453 {
1454 global $bearsamppCore;
1455
1456 $statusFile = $bearsamppCore->getTmpPath() . '/loading_status.txt';
1457 if (file_exists($statusFile)) {
1458 @unlink($statusFile);
1459 }
1460 }
1461
1472 public static function getFilesToScan($path = null, $useCache = true, $forceRefresh = false)
1473 {
1474 // Generate cache key based on path parameter
1475 $cacheKey = md5(serialize($path));
1476
1477 // Try to get from cache if enabled and not forcing refresh
1478 if ($useCache && !$forceRefresh) {
1479 $cachedResult = self::getFileScanCache($cacheKey);
1480 if ($cachedResult !== false) {
1481 self::$fileScanStats['hits']++;
1482 self::logDebug('File scan cache HIT (saved expensive scan operation)');
1483 return $cachedResult;
1484 }
1485 }
1486
1487 self::$fileScanStats['misses']++;
1488 self::logDebug('File scan cache MISS (performing full scan)');
1489
1490 // Perform the actual scan
1491 $startTime = self::getMicrotime();
1492 $result = array();
1493 $pathsToScan = !empty($path) ? $path : self::getPathsToScan();
1494
1495 foreach ($pathsToScan as $pathToScan) {
1496 $pathStartTime = self::getMicrotime();
1497 $findFiles = self::findFiles($pathToScan['path'], $pathToScan['includes'], $pathToScan['recursive']);
1498 foreach ($findFiles as $findFile) {
1499 $result[] = $findFile;
1500 }
1501 self::logDebug($pathToScan['path'] . ' scanned in ' . round(self::getMicrotime() - $pathStartTime, 3) . 's');
1502 }
1503
1504 $totalTime = round(self::getMicrotime() - $startTime, 3);
1505 self::logInfo('Full file scan completed in ' . $totalTime . 's (' . count($result) . ' files found)');
1506
1507 // Store in cache if enabled
1508 if ($useCache) {
1509 self::setFileScanCache($cacheKey, $result);
1510 }
1511
1512 return $result;
1513 }
1514
1523 private static function getFileScanCache($cacheKey)
1524 {
1525 global $bearsamppRoot;
1526
1527 // Check if we have in-memory cache first
1528 if (self::$fileScanCache !== null && isset(self::$fileScanCache[$cacheKey])) {
1529 $cache = self::$fileScanCache[$cacheKey];
1530
1531 // Check if cache is still valid
1532 if (time() - $cache['timestamp'] < self::$fileScanCacheDuration) {
1533 return $cache['data'];
1534 } else {
1535 self::$fileScanStats['invalidations']++;
1536 unset(self::$fileScanCache[$cacheKey]);
1537 }
1538 }
1539
1540 // Try to load from file cache
1541 if (!isset($bearsamppRoot)) {
1542 return false;
1543 }
1544
1545 $cacheFile = $bearsamppRoot->getTmpPath() . '/filescan_cache_' . $cacheKey . '.dat';
1546
1547 if (file_exists($cacheFile)) {
1548 $fileContents = @file_get_contents($cacheFile);
1549
1550 if ($fileContents === false) {
1551 return false;
1552 }
1553
1554 // Verify file integrity before unserializing
1555 if (!self::verifyCacheIntegrity($fileContents, $cacheKey)) {
1556 self::logWarning('File scan cache integrity check failed for key: ' . $cacheKey . '. Possible tampering detected.');
1557 @unlink($cacheFile);
1558 return false;
1559 }
1560
1561 $cacheData = @unserialize($fileContents);
1562
1563 if ($cacheData !== false && isset($cacheData['timestamp']) && isset($cacheData['data']) && isset($cacheData['hmac'])) {
1564 // Check if file cache is still valid
1565 if (time() - $cacheData['timestamp'] < self::$fileScanCacheDuration) {
1566 // Store in memory cache for faster subsequent access
1567 if (self::$fileScanCache === null) {
1568 self::$fileScanCache = [];
1569 }
1570 self::$fileScanCache[$cacheKey] = $cacheData;
1571
1572 return $cacheData['data'];
1573 } else {
1574 // Cache expired, delete file
1575 self::$fileScanStats['invalidations']++;
1576 @unlink($cacheFile);
1577 }
1578 } else {
1579 // Invalid cache structure, delete file
1580 self::logWarning('Invalid cache structure detected for key: ' . $cacheKey);
1581 @unlink($cacheFile);
1582 }
1583 }
1584
1585 return false;
1586 }
1587
1596 private static function setFileScanCache($cacheKey, $data)
1597 {
1598 global $bearsamppRoot;
1599
1600 // Generate HMAC for integrity verification
1601 $hmac = self::generateCacheHMAC($data, $cacheKey);
1602
1603 $cacheData = [
1604 'timestamp' => time(),
1605 'data' => $data,
1606 'hmac' => $hmac
1607 ];
1608
1609 // Store in memory cache
1610 if (self::$fileScanCache === null) {
1611 self::$fileScanCache = [];
1612 }
1613 self::$fileScanCache[$cacheKey] = $cacheData;
1614
1615 // Store in file cache
1616 if (isset($bearsamppRoot)) {
1617 $cacheFile = $bearsamppRoot->getTmpPath() . '/filescan_cache_' . $cacheKey . '.dat';
1618 @file_put_contents($cacheFile, serialize($cacheData), LOCK_EX);
1619 self::logDebug('File scan results cached to: ' . $cacheFile);
1620 }
1621 }
1622
1629 private static function getCacheIntegrityKey()
1630 {
1631 if (self::$cacheIntegrityKey === null) {
1632 global $bearsamppRoot;
1633
1634 // Try to load existing key from session file
1635 if (isset($bearsamppRoot)) {
1636 $keyFile = $bearsamppRoot->getTmpPath() . '/cache_integrity.key';
1637
1638 if (file_exists($keyFile)) {
1639 $key = @file_get_contents($keyFile);
1640 if ($key !== false && strlen($key) === 64) {
1641 self::$cacheIntegrityKey = $key;
1642 return self::$cacheIntegrityKey;
1643 }
1644 }
1645
1646 // Generate new key if none exists or invalid
1647 try {
1648 self::$cacheIntegrityKey = bin2hex(random_bytes(32));
1649 @file_put_contents($keyFile, self::$cacheIntegrityKey, LOCK_EX);
1650 } catch (Exception $e) {
1651 self::logError('Failed to generate cache integrity key: ' . $e->getMessage());
1652 // Fallback to a less secure but functional key
1653 self::$cacheIntegrityKey = hash('sha256', uniqid('bearsampp_cache_', true));
1654 }
1655 } else {
1656 // Fallback if bearsamppRoot not available
1657 try {
1658 self::$cacheIntegrityKey = bin2hex(random_bytes(32));
1659 } catch (Exception $e) {
1660 self::$cacheIntegrityKey = hash('sha256', uniqid('bearsampp_cache_', true));
1661 }
1662 }
1663 }
1664
1665 return self::$cacheIntegrityKey;
1666 }
1667
1676 private static function generateCacheHMAC($data, $cacheKey)
1677 {
1679 $message = serialize($data) . $cacheKey;
1680 return hash_hmac('sha256', $message, $key);
1681 }
1682
1691 private static function verifyCacheIntegrity($fileContents, $cacheKey)
1692 {
1693 $cacheData = @unserialize($fileContents);
1694
1695 if ($cacheData === false || !isset($cacheData['hmac']) || !isset($cacheData['data'])) {
1696 return false;
1697 }
1698
1699 $expectedHmac = self::generateCacheHMAC($cacheData['data'], $cacheKey);
1700
1701 // Use hash_equals to prevent timing attacks
1702 return hash_equals($expectedHmac, $cacheData['hmac']);
1703 }
1704
1710 public static function clearFileScanCache()
1711 {
1712 global $bearsamppRoot;
1713
1714 // Clear memory cache
1715 self::$fileScanCache = null;
1716
1717 // Clear file caches
1718 if (isset($bearsamppRoot)) {
1719 $tmpPath = $bearsamppRoot->getTmpPath();
1720 $cacheFiles = glob($tmpPath . '/filescan_cache_*.dat');
1721
1722 if ($cacheFiles !== false) {
1723 foreach ($cacheFiles as $cacheFile) {
1724 @unlink($cacheFile);
1725 }
1726 self::logInfo('Cleared ' . count($cacheFiles) . ' file scan cache files');
1727 }
1728 }
1729
1730 // Reset stats
1731 self::$fileScanStats = [
1732 'hits' => 0,
1733 'misses' => 0,
1734 'invalidations' => 0
1735 ];
1736 }
1737
1743 public static function getFileScanStats()
1744 {
1745 return self::$fileScanStats;
1746 }
1747
1755 public static function setFileScanCacheDuration($seconds)
1756 {
1757 if ($seconds > 0 && $seconds <= 86400) { // Max 24 hours
1758 self::$fileScanCacheDuration = $seconds;
1759 self::logDebug('File scan cache duration set to ' . $seconds . ' seconds');
1760 }
1761 }
1762
1768 public static function getFileScanCacheDuration()
1769 {
1770 return self::$fileScanCacheDuration;
1771 }
1772
1797 private static function getPathsToScan()
1798 {
1799 global $bearsamppRoot, $bearsamppCore, $bearsamppBins, $bearsamppApps, $bearsamppTools;
1800 $paths = array();
1801
1802 // Alias
1803 $paths[] = array(
1804 'path' => $bearsamppRoot->getAliasPath(),
1805 'includes' => array(''),
1806 'recursive' => false
1807 );
1808
1809 // Vhosts
1810 $paths[] = array(
1811 'path' => $bearsamppRoot->getVhostsPath(),
1812 'includes' => array(''),
1813 'recursive' => false
1814 );
1815
1816 // OpenSSL
1817 $paths[] = array(
1818 'path' => $bearsamppCore->getOpenSslPath(),
1819 'includes' => array('openssl.cfg'),
1820 'recursive' => false
1821 );
1822
1823 // Homepage
1824 $paths[] = array(
1825 'path' => $bearsamppCore->getResourcesPath() . '/homepage',
1826 'includes' => array('alias.conf'),
1827 'recursive' => false
1828 );
1829
1830 // Apache
1831 $folderList = self::getFolderList($bearsamppBins->getApache()->getRootPath());
1832 foreach ($folderList as $folder) {
1833 $paths[] = array(
1834 'path' => $bearsamppBins->getApache()->getRootPath() . '/' . $folder,
1835 'includes' => array('.ini', '.conf'),
1836 'recursive' => true
1837 );
1838 }
1839
1840 // PHP
1841 $folderList = self::getFolderList($bearsamppBins->getPhp()->getRootPath());
1842 foreach ($folderList as $folder) {
1843 $paths[] = array(
1844 'path' => $bearsamppBins->getPhp()->getRootPath() . '/' . $folder,
1845 'includes' => array('.php', '.bat', '.ini', '.reg', '.inc'),
1846 'recursive' => true
1847 );
1848 }
1849
1850 // MySQL
1851 $folderList = self::getFolderList($bearsamppBins->getMysql()->getRootPath());
1852 foreach ($folderList as $folder) {
1853 $paths[] = array(
1854 'path' => $bearsamppBins->getMysql()->getRootPath() . '/' . $folder,
1855 'includes' => array('my.ini'),
1856 'recursive' => false
1857 );
1858 }
1859
1860 // MariaDB
1861 $folderList = self::getFolderList($bearsamppBins->getMariadb()->getRootPath());
1862 foreach ($folderList as $folder) {
1863 $paths[] = array(
1864 'path' => $bearsamppBins->getMariadb()->getRootPath() . '/' . $folder,
1865 'includes' => array('my.ini'),
1866 'recursive' => false
1867 );
1868 }
1869
1870 // PostgreSQL
1871 $folderList = self::getFolderList($bearsamppBins->getPostgresql()->getRootPath());
1872 foreach ($folderList as $folder) {
1873 $paths[] = array(
1874 'path' => $bearsamppBins->getPostgresql()->getRootPath() . '/' . $folder,
1875 'includes' => array( '.conf', '.bat', '.ber'),
1876 'recursive' => true
1877 );
1878 }
1879
1880 // Node.js
1881 $folderList = self::getFolderList($bearsamppBins->getNodejs()->getRootPath());
1882 foreach ($folderList as $folder) {
1883 $paths[] = array(
1884 'path' => $bearsamppBins->getNodejs()->getRootPath() . '/' . $folder . '/etc',
1885 'includes' => array('npmrc'),
1886 'recursive' => true
1887 );
1888 $paths[] = array(
1889 'path' => $bearsamppBins->getNodejs()->getRootPath() . '/' . $folder . '/node_modules/npm',
1890 'includes' => array('npmrc'),
1891 'recursive' => false
1892 );
1893 }
1894
1895 // Composer
1896 $folderList = self::getFolderList($bearsamppTools->getComposer()->getRootPath());
1897 foreach ($folderList as $folder) {
1898 $paths[] = array(
1899 'path' => $bearsamppTools->getComposer()->getRootPath() . '/' . $folder,
1900 'includes' => array('giscus.json'),
1901 'recursive' => false
1902 );
1903 }
1904
1905 // PowerShell
1906 $folderList = self::getFolderList($bearsamppTools->getPowerShell()->getRootPath());
1907 foreach ($folderList as $folder) {
1908 $paths[] = array(
1909 'path' => $bearsamppTools->getPowerShell()->getRootPath() . '/' . $folder,
1910 'includes' => array('console.xml', '.ini', '.btm'),
1911 'recursive' => true
1912 );
1913 }
1914
1915 // Python
1916 $folderList = self::getFolderList($bearsamppTools->getPython()->getRootPath());
1917 foreach ($folderList as $folder) {
1918 $paths[] = array(
1919 'path' => $bearsamppTools->getPython()->getRootPath() . '/' . $folder . '/bin',
1920 'includes' => array('.bat'),
1921 'recursive' => false
1922 );
1923 $paths[] = array(
1924 'path' => $bearsamppTools->getPython()->getRootPath() . '/' . $folder . '/settings',
1925 'includes' => array('winpython.ini'),
1926 'recursive' => false
1927 );
1928 }
1929
1930 // Ruby
1931 $folderList = self::getFolderList($bearsamppTools->getRuby()->getRootPath());
1932 foreach ($folderList as $folder) {
1933 $paths[] = array(
1934 'path' => $bearsamppTools->getRuby()->getRootPath() . '/' . $folder . '/bin',
1935 'includes' => array('!.dll', '!.exe'),
1936 'recursive' => false
1937 );
1938 }
1939
1940 return $paths;
1941 }
1942
1952 private static function findFiles($startPath, $includes = array(''), $recursive = true)
1953 {
1954 $result = array();
1955
1956 $handle = @opendir($startPath);
1957 if (!$handle) {
1958 return $result;
1959 }
1960
1961 while (false !== ($file = readdir($handle))) {
1962 if ($file == '.' || $file == '..') {
1963 continue;
1964 }
1965 if (is_dir($startPath . '/' . $file) && $recursive) {
1966 $tmpResults = self::findFiles($startPath . '/' . $file, $includes);
1967 foreach ($tmpResults as $tmpResult) {
1968 $result[] = $tmpResult;
1969 }
1970 } elseif (is_file($startPath . '/' . $file)) {
1971 foreach ($includes as $include) {
1972 if (self::startWith($include, '!')) {
1973 $include = ltrim($include, '!');
1974 if (self::startWith($file, '.') && !self::endWith($file, $include)) {
1975 $result[] = self::formatUnixPath($startPath . '/' . $file);
1976 } elseif ($file != $include) {
1977 $result[] = self::formatUnixPath($startPath . '/' . $file);
1978 }
1979 } elseif (self::endWith($file, $include) || $file == $include || empty($include)) {
1980 $result[] = self::formatUnixPath($startPath . '/' . $file);
1981 }
1982 }
1983 }
1984 }
1985
1986 closedir($handle);
1987
1988 return $result;
1989 }
1990
1999 public static function changePath($filesToScan, $rootPath = null)
2000 {
2002
2003 $result = array(
2004 'countChangedOcc' => 0,
2005 'countChangedFiles' => 0
2006 );
2007
2008 $rootPath = $rootPath != null ? $rootPath : $bearsamppRoot->getRootPath();
2009 $unixOldPath = self::formatUnixPath($bearsamppCore->getLastPathContent());
2010 $windowsOldPath = self::formatWindowsPath($bearsamppCore->getLastPathContent());
2011 $unixCurrentPath = self::formatUnixPath($rootPath);
2012 $windowsCurrentPath = self::formatWindowsPath($rootPath);
2013
2014 foreach ($filesToScan as $fileToScan) {
2015 $tmpCountChangedOcc = 0;
2016 $fileContentOr = file_get_contents($fileToScan);
2017 $fileContent = $fileContentOr;
2018
2019 // old path
2020 preg_match('#' . $unixOldPath . '#i', $fileContent, $unixMatches);
2021 if (!empty($unixMatches)) {
2022 $fileContent = str_replace($unixOldPath, $unixCurrentPath, $fileContent, $countChanged);
2023 $tmpCountChangedOcc += $countChanged;
2024 }
2025 preg_match('#' . str_replace('\\', '\\\\', $windowsOldPath) . '#i', $fileContent, $windowsMatches);
2026 if (!empty($windowsMatches)) {
2027 $fileContent = str_replace($windowsOldPath, $windowsCurrentPath, $fileContent, $countChanged);
2028 $tmpCountChangedOcc += $countChanged;
2029 }
2030
2031 // placeholders
2032 preg_match('#' . Core::PATH_LIN_PLACEHOLDER . '#i', $fileContent, $unixMatches);
2033 if (!empty($unixMatches)) {
2034 $fileContent = str_replace(Core::PATH_LIN_PLACEHOLDER, $unixCurrentPath, $fileContent, $countChanged);
2035 $tmpCountChangedOcc += $countChanged;
2036 }
2037 preg_match('#' . Core::PATH_WIN_PLACEHOLDER . '#i', $fileContent, $windowsMatches);
2038 if (!empty($windowsMatches)) {
2039 $fileContent = str_replace(Core::PATH_WIN_PLACEHOLDER, $windowsCurrentPath, $fileContent, $countChanged);
2040 $tmpCountChangedOcc += $countChanged;
2041 }
2042
2043 if ($fileContentOr != $fileContent) {
2044 $result['countChangedOcc'] += $tmpCountChangedOcc;
2045 $result['countChangedFiles'] += 1;
2046 file_put_contents($fileToScan, $fileContent);
2047 }
2048 }
2049
2050 self::logDebug('changePath() completed: ' . $result['countChangedFiles'] . ' files changed, ' . $result['countChangedOcc'] . ' total occurrences');
2051
2052 return $result;
2053 }
2054
2062 public static function getLatestVersion($url)
2063 {
2064 $result = self::getApiJson($url);
2065 if (empty($result)) {
2066 self::logError('Cannot retrieve latest github info for: ' . $result . ' RESULT');
2067
2068 return null;
2069 }
2070
2071 $resultArray = json_decode($result, true);
2072 if (isset($resultArray['tag_name']) && isset($resultArray['assets'][0]['browser_download_url'])) {
2073 $tagName = $resultArray['tag_name'];
2074 $downloadUrl = $resultArray['assets'][0]['browser_download_url'];
2075 $name = $resultArray['name'];
2076 self::logDebug('Latest version tag name: ' . $tagName);
2077 self::logDebug('Download URL: ' . $downloadUrl);
2078 self::logDebug('Name: ' . $name);
2079
2080 return ['version' => $tagName, 'html_url' => $downloadUrl, 'name' => $name];
2081 } else {
2082 self::logError('Tag name, download URL, or name not found in the response: ' . $result);
2083
2084 return null;
2085 }
2086 }
2087
2096 public static function getWebsiteUrlNoUtm($path = '', $fragment = '')
2097 {
2098 return self::getWebsiteUrl($path, $fragment, false);
2099 }
2100
2110 public static function getWebsiteUrl($path = '', $fragment = '', $utmSource = true)
2111 {
2112 global $bearsamppCore;
2113
2114 $url = APP_WEBSITE;
2115 if (!empty($path)) {
2116 $url .= '/' . ltrim($path, '/');
2117 }
2118 if ($utmSource) {
2119 $url = rtrim($url, '/') . '/?utm_source=bearsampp-' . $bearsamppCore->getAppVersion();
2120 }
2121 if (!empty($fragment)) {
2122 $url .= $fragment;
2123 }
2124
2125 return $url;
2126 }
2127
2135 public static function getChangelogUrl($utmSource = true)
2136 {
2137 return self::getWebsiteUrl('doc/changelog', null, $utmSource);
2138 }
2139
2148 public static function getRemoteFilesize($url, $humanFileSize = true)
2149 {
2150 $size = 0;
2151
2152 $data = get_headers($url, true);
2153 if (isset($data['Content-Length'])) {
2154 $size = intval($data['Content-Length']);
2155 }
2156
2157 return $humanFileSize ? self::humanFileSize($size) : $size;
2158 }
2159
2168 public static function humanFileSize($size, $unit = '')
2169 {
2170 if ((!$unit && $size >= 1 << 30) || $unit == 'GB') {
2171 return number_format($size / (1 << 30), 2) . 'GB';
2172 }
2173 if ((!$unit && $size >= 1 << 20) || $unit == 'MB') {
2174 return number_format($size / (1 << 20), 2) . 'MB';
2175 }
2176 if ((!$unit && $size >= 1 << 10) || $unit == 'KB') {
2177 return number_format($size / (1 << 10), 2) . 'KB';
2178 }
2179
2180 return number_format($size) . ' bytes';
2181 }
2182
2188 public static function is32BitsOs()
2189 {
2190 $processor = self::getProcessorRegKey();
2191
2192 return self::contains($processor, 'x86');
2193 }
2194
2202 public static function getHttpHeaders($pingUrl)
2203 {
2204 if (function_exists('curl_version')) {
2206 } else {
2208 }
2209
2210 if (!empty($result)) {
2211 $rebuildResult = array();
2212 foreach ($result as $row) {
2213 $row = trim($row);
2214 if (!empty($row)) {
2215 $rebuildResult[] = $row;
2216 }
2217 }
2218 $result = $rebuildResult;
2219
2220 self::logDebug('getHttpHeaders:');
2221 foreach ($result as $header) {
2222 self::logDebug('-> ' . $header);
2223 }
2224 }
2225
2226 return $result;
2227 }
2228
2240 public static function getFopenHttpHeaders($url)
2241 {
2242 $result = array();
2243
2244 $context = stream_context_create(array(
2245 'ssl' => array(
2246 'verify_peer' => false,
2247 'verify_peer_name' => false,
2248 'allow_self_signed' => true,
2249 )
2250 ));
2251
2252 $fp = @fopen($url, 'r', false, $context);
2253 if ($fp) {
2254 $meta = stream_get_meta_data($fp);
2255 $result = isset($meta['wrapper_data']) ? $meta['wrapper_data'] : $result;
2256 fclose($fp);
2257 }
2258
2259 return $result;
2260 }
2261
2273 public static function getCurlHttpHeaders($url)
2274 {
2275 $result = array();
2276
2277 $ch = curl_init();
2278 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
2279 curl_setopt($ch, CURLOPT_VERBOSE, true);
2280 curl_setopt($ch, CURLOPT_HEADER, true);
2281 curl_setopt($ch, CURLOPT_URL, $url);
2282 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
2283
2284 $response = @curl_exec($ch);
2285 if (empty($response)) {
2286 return $result;
2287 }
2288
2289 self::logTrace('getCurlHttpHeaders:' . $response);
2290 $responseHeaders = explode("\r\n\r\n", $response, 2);
2291 if (!isset($responseHeaders[0]) || empty($responseHeaders[0])) {
2292 return $result;
2293 }
2294
2295 return explode("\n", $responseHeaders[0]);
2296 }
2297
2311 public static function getHeaders($host, $port, $ssl = false)
2312 {
2313 $result = array();
2314 $context = stream_context_create(array(
2315 'ssl' => array(
2316 'verify_peer' => false,
2317 'verify_peer_name' => false,
2318 'allow_self_signed' => true,
2319 )
2320 ));
2321
2322 $fp = @stream_socket_client(($ssl ? 'ssl://' : '') . $host . ':' . $port, $errno, $errstr, 5, STREAM_CLIENT_CONNECT, $context);
2323 if ($fp) {
2324 $out = fgets($fp);
2325 $result = explode(PHP_EOL, $out);
2326 @fclose($fp);
2327 }
2328
2329 if (!empty($result)) {
2330 $rebuildResult = array();
2331 foreach ($result as $row) {
2332 $row = trim($row);
2333 if (!empty($row)) {
2334 $rebuildResult[] = $row;
2335 }
2336 }
2337 $result = $rebuildResult;
2338
2339 self::logDebug('getHeaders:');
2340 foreach ($result as $header) {
2341 self::logDebug('-> ' . $header);
2342 }
2343 }
2344
2345 return $result;
2346 }
2347
2355 public static function getApiJson($url)
2356 {
2358
2359 $ch = curl_init();
2360 curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
2361 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
2362 curl_setopt($ch, CURLOPT_VERBOSE, true);
2363 curl_setopt($ch, CURLOPT_URL, $url);
2364 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
2365 curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
2366 $data = curl_exec($ch);
2367 if (curl_errno($ch)) {
2368 Util::logError('CURL Error: ' . curl_error($ch));
2369 }
2370
2371 // curl_close() is deprecated in PHP 8.5+ as it has no effect since PHP 8.0
2372 // The resource is automatically closed when it goes out of scope
2373 if (PHP_VERSION_ID < 80500) {
2374 curl_close($ch);
2375 }
2376
2377 return trim($data);
2378 }
2379
2387 public static function isPortInUse($port)
2388 {
2389 // Set localIP statically
2390 $localIP = '127.0.0.1';
2391
2392 // Save current error reporting level
2393 $errorReporting = error_reporting();
2394
2395 // Disable error reporting temporarily
2396 error_reporting(0);
2397
2398 $connection = @fsockopen($localIP, $port);
2399
2400 // Restore original error reporting level
2401 error_reporting($errorReporting);
2402
2403 if (is_resource($connection)) {
2404 fclose($connection);
2406
2407 return $process != null ? $process : 'N/A';
2408 }
2409
2410 return false;
2411 }
2412
2420 public static function isValidDomainName($domainName)
2421 {
2422 return preg_match('/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i', $domainName)
2423 && preg_match('/^.{1,253}$/', $domainName)
2424 && preg_match('/^[^\.]{1,63}(\.[^\.]{1,63})*$/', $domainName);
2425 }
2426
2434 public static function isAlphanumeric($string)
2435 {
2436 return ctype_alnum($string);
2437 }
2438
2449 public static function installService($bin, $port, $syntaxCheckCmd, $showWindow = false)
2450 {
2451 global $bearsamppLang, $bearsamppWinbinder;
2452
2453 if (method_exists($bin, 'initData')) {
2454 $bin->initData();
2455 }
2456
2457 $name = $bin->getName();
2458 $service = $bin->getService();
2459 $boxTitle = sprintf($bearsamppLang->getValue(Lang::INSTALL_SERVICE_TITLE), $name);
2460
2461 $isPortInUse = self::isPortInUse($port);
2462 if ($isPortInUse === false) {
2463 if (!$service->isInstalled()) {
2464 $service->create();
2465 if ($service->start()) {
2466 self::logInfo(sprintf('%s service successfully installed. (name: %s ; port: %s)', $name, $service->getName(), $port));
2467 if ($showWindow) {
2468 $bearsamppWinbinder->messageBoxInfo(
2469 sprintf($bearsamppLang->getValue(Lang::SERVICE_INSTALLED), $name, $service->getName(), $port),
2470 $boxTitle
2471 );
2472 }
2473
2474 return true;
2475 } else {
2476 $serviceError = sprintf($bearsamppLang->getValue(Lang::SERVICE_INSTALL_ERROR), $name);
2477 $serviceErrorLog = sprintf('Error during the installation of %s service', $name);
2478 if (!empty($syntaxCheckCmd)) {
2479 $cmdSyntaxCheck = $bin->getCmdLineOutput($syntaxCheckCmd);
2480 if (!$cmdSyntaxCheck['syntaxOk']) {
2481 $serviceError .= PHP_EOL . sprintf($bearsamppLang->getValue(Lang::STARTUP_SERVICE_SYNTAX_ERROR), $cmdSyntaxCheck['content']);
2482 $serviceErrorLog .= sprintf(' (conf errors detected : %s)', $cmdSyntaxCheck['content']);
2483 }
2484 }
2485 self::logError($serviceErrorLog);
2486 if ($showWindow) {
2487 $bearsamppWinbinder->messageBoxError($serviceError, $boxTitle);
2488 }
2489 }
2490 } else {
2491 self::logWarning(sprintf('%s service already installed', $name));
2492 if ($showWindow) {
2493 $bearsamppWinbinder->messageBoxWarning(
2494 sprintf($bearsamppLang->getValue(Lang::SERVICE_ALREADY_INSTALLED), $name),
2495 $boxTitle
2496 );
2497 }
2498
2499 return true;
2500 }
2501 } elseif ($service->isRunning()) {
2502 self::logWarning(sprintf('%s service already installed and running', $name));
2503 if ($showWindow) {
2504 $bearsamppWinbinder->messageBoxWarning(
2505 sprintf($bearsamppLang->getValue(Lang::SERVICE_ALREADY_INSTALLED), $name),
2506 $boxTitle
2507 );
2508 }
2509
2510 return true;
2511 } else {
2512 self::logError(sprintf('Port %s is used by an other application : %s', $port, $isPortInUse));
2513 if ($showWindow) {
2514 $bearsamppWinbinder->messageBoxError(
2515 sprintf($bearsamppLang->getValue(Lang::PORT_NOT_USED_BY), $port, $isPortInUse),
2516 $boxTitle
2517 );
2518 }
2519 }
2520
2521 return false;
2522 }
2523
2532 public static function removeService($service, $name)
2533 {
2534 if (!($service instanceof Win32Service)) {
2535 self::logError('$service not an instance of Win32Service');
2536
2537 return false;
2538 }
2539
2540 if ($service->isInstalled()) {
2541 if ($service->delete()) {
2542 self::logInfo(sprintf('%s service successfully removed', $name));
2543
2544 return true;
2545 } else {
2546 self::logError(sprintf('Error during the uninstallation of %s service', $name));
2547
2548 return false;
2549 }
2550 } else {
2551 self::logWarning(sprintf('%s service does not exist', $name));
2552 }
2553
2554 return true;
2555 }
2556
2566 public static function startService($bin, $syntaxCheckCmd, $showWindow = false)
2567 {
2568 global $bearsamppLang, $bearsamppWinbinder;
2569
2570 if (method_exists($bin, 'initData')) {
2571 $bin->initData();
2572 }
2573
2574 $name = $bin->getName();
2575 $service = $bin->getService();
2576 $boxTitle = sprintf($bearsamppLang->getValue(Lang::START_SERVICE_TITLE), $name);
2577
2578 if (!$service->start()) {
2579 $serviceError = sprintf($bearsamppLang->getValue(Lang::START_SERVICE_ERROR), $name);
2580 $serviceErrorLog = sprintf('Error while starting the %s service', $name);
2581 if (!empty($syntaxCheckCmd)) {
2582 $cmdSyntaxCheck = $bin->getCmdLineOutput($syntaxCheckCmd);
2583 if (!$cmdSyntaxCheck['syntaxOk']) {
2584 $serviceError .= PHP_EOL . sprintf($bearsamppLang->getValue(Lang::STARTUP_SERVICE_SYNTAX_ERROR), $cmdSyntaxCheck['content']);
2585 $serviceErrorLog .= sprintf(' (conf errors detected : %s)', $cmdSyntaxCheck['content']);
2586 }
2587 }
2588 self::logError($serviceErrorLog);
2589 if ($showWindow) {
2590 $bearsamppWinbinder->messageBoxError($serviceError, $boxTitle);
2591 }
2592
2593 return false;
2594 }
2595
2596 return true;
2597 }
2598
2606 public static function getGithubUserUrl($part = null)
2607 {
2608 $part = !empty($part) ? '/' . $part : null;
2609
2610 return 'https://github.com/' . APP_GITHUB_USER . $part;
2611 }
2612
2620 public static function getGithubUrl($part = null)
2621 {
2622 $part = !empty($part) ? '/' . $part : null;
2623
2625 }
2626
2634 public static function getGithubRawUrl($file)
2635 {
2636 $file = !empty($file) ? '/' . $file : null;
2637
2638 return 'https://raw.githubusercontent.com/' . APP_GITHUB_USER . '/' . APP_GITHUB_REPO . '/main' . $file;
2639 }
2640
2648 public static function getFolderList($path)
2649 {
2650 $result = array();
2651
2652 $handle = @opendir($path);
2653 if (!$handle) {
2654 return false;
2655 }
2656
2657 while (false !== ($file = readdir($handle))) {
2658 $filePath = $path . '/' . $file;
2659 if ($file != '.' && $file != '..' && is_dir($filePath) && $file != 'current') {
2660 $result[] = $file;
2661 }
2662 }
2663
2664 closedir($handle);
2665
2666 return $result;
2667 }
2668
2677 public static function getNssmEnvPaths()
2678 {
2679 global $bearsamppRoot;
2680
2681 $result = '';
2682 $nssmEnvPathsFile = $bearsamppRoot->getRootPath() . '/nssmEnvPaths.dat';
2683
2684 if (is_file($nssmEnvPathsFile)) {
2685 $paths = explode(PHP_EOL, file_get_contents($nssmEnvPathsFile));
2686 foreach ($paths as $path) {
2687 $path = trim($path);
2688 if (stripos($path, ':') === false) {
2689 $path = $bearsamppRoot->getRootPath() . '/' . $path;
2690 }
2691 if (is_dir($path)) {
2692 $result .= self::formatUnixPath($path) . ';';
2693 } else {
2694 self::logWarning('Path not found in nssmEnvPaths.dat: ' . $path);
2695 }
2696 }
2697 }
2698
2699 return $result;
2700 }
2701
2713 public static function openFileContent($caption, $content)
2714 {
2715 global $bearsamppRoot, $bearsamppConfig, $bearsamppWinbinder;
2716
2717 $folderPath = $bearsamppRoot->getTmpPath() . '/openFileContent-' . self::random();
2718 if (!is_dir($folderPath)) {
2719 mkdir($folderPath, 0777, true);
2720 }
2721
2722 $filepath = self::formatWindowsPath($folderPath . '/' . $caption);
2723 file_put_contents($filepath, $content);
2724
2725 $bearsamppWinbinder->exec($bearsamppConfig->getNotepad(), '"' . $filepath . '"');
2726 }
2727
2733 public static function decryptFile()
2734 {
2736
2737 $stringfile = $bearsamppCore->getResourcesPath() . '/string.dat';
2738 $encryptedFile = $bearsamppCore->getResourcesPath() . '/github.dat';
2739 $method = 'AES-256-CBC'; // The same encryption method used
2740
2741 // Get key string
2742 $stringPhrase = @file_get_contents($stringfile);
2743 if ($stringPhrase === false) {
2744 Util::logDebug("Failed to read the file at path: {$stringfile}");
2745
2746 return false;
2747 }
2748
2749 $stringKey = convert_uudecode($stringPhrase);
2750
2751 // Read the encrypted data from the file
2752 $encryptedData = file_get_contents($encryptedFile);
2753 if ($encryptedData === false) {
2754 Util::logDebug("Failed to read the file at path: {$encryptedFile}");
2755
2756 return false;
2757 }
2758
2759 // Decode the base64 encoded data
2760 $data = base64_decode($encryptedData);
2761 if ($data === false) {
2762 Util::logDebug("Failed to decode the data from path: {$encryptedFile}");
2763
2764 return false;
2765 }
2766
2767 // Extract the IV which was prepended to the encrypted data
2768 $ivLength = openssl_cipher_iv_length($method);
2769 $iv = substr($data, 0, $ivLength);
2770 $encrypted = substr($data, $ivLength);
2771
2772 // Decrypt the data
2773 $decrypted = openssl_decrypt($encrypted, $method, $stringKey, 0, $iv);
2774 if ($decrypted === false) {
2775 Util::logDebug("Decryption failed for data from path: {$encryptedFile}");
2776
2777 return false;
2778 }
2779
2780 return $decrypted;
2781 }
2782
2788 public static function setupCurlHeaderWithToken()
2789 {
2790 // Usage
2792 $Token = self::decryptFile();
2793
2794 return [
2795 'Accept: application/vnd.github+json',
2796 'Authorization: Token ' . $Token,
2797 'User-Agent: ' . APP_GITHUB_USERAGENT,
2798 'X-GitHub-Api-Version: 2022-11-28'
2799 ];
2800 }
2801
2810 public static function checkInternetState()
2811 {
2812 $connected = @fsockopen('www.google.com', 80);
2813 if ($connected) {
2814 fclose($connected);
2815
2816 return true; // Internet connection is active
2817 } else {
2818 return false; // Internet connection is not active
2819 }
2820 }
2821}
$result
global $bearsamppBins
global $bearsamppLang
global $bearsamppRoot
$port
global $bearsamppCore
const LOADING
static getProcessUsingPort($port)
const VERBOSE_REPORT
const VERBOSE_SIMPLE
const VERBOSE_TRACE
const VERBOSE_DEBUG
const isRoot_FILE
const PATH_LIN_PLACEHOLDER
const PATH_WIN_PLACEHOLDER
const START_SERVICE_ERROR
const START_SERVICE_TITLE
const SERVICE_INSTALLED
const INSTALL_SERVICE_TITLE
const STARTUP_SERVICE_SYNTAX_ERROR
const SERVICE_INSTALL_ERROR
const SERVICE_ALREADY_INSTALLED
const PORT_NOT_USED_BY
const SYSPATH_REG_ENTRY
const PROCESSOR_REG_SUBKEY
const HKEY_LOCAL_MACHINE
const PROCESSOR_REG_ENTRY
const APP_PATH_REG_ENTRY
const APP_BINS_REG_ENTRY
static logError($data, $file=null)
static findRepos($initPath, $startPath, $checkFile, $maxDepth=1)
static isLaunchStartup()
static setLogBufferSize($size)
static getPathFormatCacheSize()
static installService($bin, $port, $syntaxCheckCmd, $showWindow=false)
static getWebsiteUrlNoUtm($path='', $fragment='')
static $logStats
static clearPathFormatCache()
static $fileScanStats
static disableLaunchStartup()
static getRemoteFilesize($url, $humanFileSize=true)
static getMicrotime()
static getGithubUrl($part=null)
static deleteFolder($path)
static logInitClass($classInstance)
static getHeaders($host, $port, $ssl=false)
static removeService($service, $name)
static setFileScanCacheDuration($seconds)
static logTrace($data, $file=null)
static sanitizeServiceName($serviceName)
static isValidPort($port)
static cleanArgv($name, $type='text')
static cp1252ToUtf8($data)
static setAppPathRegKey($value)
static logInfo($data, $file=null)
static imgToBase64($path)
static contains($string, $search)
const LOG_ERROR
static getVersionList($path)
static getHttpHeaders($pingUrl)
static utf8ToCp1252($data)
static isValidDomainName($domainName)
static getLatestVersion($url)
static getFolderList($path)
static getAppBinsRegKey($fromRegistry=true)
static getAppPathRegKey()
static getGithubRawUrl($file)
static getChangelogUrl($utmSource=true)
static generateSecureToken($length=32)
static openFileContent($caption, $content)
static logDebug($data, $file=null)
static $logBuffer
static $cacheIntegrityKey
static isAlphanumeric($string)
static logReloadClass($classInstance)
static getCacheIntegrityKey()
static logSeparator()
static getFileScanStats()
static getFileScanCacheDuration()
static flushLogBuffer()
static startWith($string, $search)
static getLogBufferSize()
static $fileScanCache
static $logBufferSize
static getGithubUserUrl($part=null)
static humanFileSize($size, $unit='')
static clearLoadingText()
static $fileScanCacheDuration
static clearFileScanCache()
static getPathsToScan()
const LOG_TRACE
static random($length=32, $withNumeric=true)
static startLoading()
static endWith($string, $search)
static getNssmEnvPaths()
static formatWindowsPath($path)
static changePath($filesToScan, $rootPath=null)
const LOG_INFO
static sanitizePID($pid)
static getWebsiteUrl($path='', $fragment='', $utmSource=true)
static formatUnixPath($path)
static isPortInUse($port)
static cleanGetVar($name, $type='text')
static $pathFormatCacheMaxSize
static getApiJson($url)
static decryptFile()
const LOG_DEBUG
static generateCacheHMAC($data, $cacheKey)
static $shutdownRegistered
static findFiles($startPath, $includes=array(''), $recursive=true)
static getFilesToScan($path=null, $useCache=true, $forceRefresh=false)
static $pathFormatStats
static replaceDefine($path, $var, $value)
static clearFolder($path, $exclude=array())
static sanitizeOutput($output)
static isValidIp($ip)
static setupCurlHeaderWithToken()
static stopLoading()
static getPathFormatStats()
static is32BitsOs()
static setFileScanCache($cacheKey, $data)
static generateSecureBytes($length=32)
static clearFolders($paths, $exclude=array())
static getLogStats()
static getFopenHttpHeaders($url)
static startService($bin, $syntaxCheckCmd, $showWindow=false)
const LOG_WARNING
static sanitizePort($port)
static sanitizePath($path)
static cleanPostVar($name, $type='text')
static findFile($startPath, $findFile)
static getCurlHttpHeaders($url)
static getFileScanCache($cacheKey)
static log($data, $type, $file=null)
static updateLoadingText($text)
static enableLaunchStartup()
static getStartupLnkPath()
static setSysPathRegKey($value)
static replaceInFile($path, $replaceList)
static getSysPathRegKey()
static getPowerShellPath()
static $pathFormatCache
static getProcessorRegKey()
static checkInternetState()
static setAppBinsRegKey($value)
static logWarning($data, $file=null)
static verifyCacheIntegrity($fileContents, $cacheKey)
static createShortcut($savePath)
static getStartupPath($file=null)
static kill($pid)
global $bearsamppConfig
Definition homepage.php:41
const APP_GITHUB_USERAGENT
Definition root.php:18
const APP_WEBSITE
Definition root.php:14
const APP_GITHUB_USER
Definition root.php:16
const APP_GITHUB_REPO
Definition root.php:17
const APP_TITLE
Definition root.php:13