Bearsampp
2026.9.23
Toggle main menu visibility
Loading...
Searching...
No Matches
class.symlinks.php
Go to the documentation of this file.
1
<?php
2
/*
3
*
4
* * Copyright (c) 2021-2026 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
14
class
Symlinks
15
{
16
const
APACHE_SYMLINK
=
'apache'
;
17
const
BRUNO_SYMLINK
=
'bruno'
;
18
const
COMPOSER_SYMLINK
=
'composer'
;
19
const
GHOSTSCRIPT_SYMLINK
=
'ghostscript'
;
20
const
GIT_SYMLINK
=
'git'
;
21
const
MAILPIT_SYMLINK
=
'mailpit'
;
22
const
MARIADB_SYMLINK
=
'mariadb'
;
23
const
MEMCACHED_SYMLINK
=
'memcached'
;
24
const
MYSQL_SYMLINK
=
'mysql'
;
25
const
NGROK_SYMLINK
=
'ngrok'
;
26
const
NODEJS_SYMLINK
=
'nodejs'
;
27
const
PERL_SYMLINK
=
'perl'
;
28
const
PHP_SYMLINK
=
'php'
;
29
const
PHPMYADMIN_SYMLINK
=
'phpmyadmin'
;
30
const
PHPPGADMIN_SYMLINK
=
'phppgadmin'
;
31
const
POSTGRESQL_SYMLINK
=
'postgresql'
;
32
const
POWERSHELL_SYMLINK
=
'powershell'
;
33
const
PYTHON_SYMLINK
=
'python'
;
34
const
RUBY_SYMLINK
=
'ruby'
;
35
const
XLIGHT_SYMLINK
=
'xlight'
;
36
40
private
static
$skipSymlinkCreation
=
false
;
41
45
private
static
$root
;
46
52
public
function
__construct
(
$root
)
53
{
54
self::$root =
$root
;
55
self::initializePaths
();
56
}
57
62
public
static
function
initializePaths
()
63
{
64
// Path initialization logic can be added here if needed
65
// For now, it's a placeholder as requested by the issue to restore it.
66
}
67
76
public
static
function
setSkipSymlinkCreation
(
bool
$skip): void
77
{
78
self::$skipSymlinkCreation = $skip;
79
}
80
86
public
static
function
isSkippingSymlinkCreation
(): bool
87
{
88
return
self::$skipSymlinkCreation;
89
}
90
97
public
static
function
createModuleSymlink
($module)
98
{
99
$src =
Path::formatWindowsPath
($module->currentPath);
100
$dest =
Path::formatWindowsPath
($module->symlinkPath);
101
102
if
(is_link($dest)) {
103
if
(readlink($dest) === $src) {
104
return
;
105
}
106
Batch::removeSymlink
($dest);
107
} elseif (file_exists($dest)) {
108
if
(is_dir($dest)) {
109
Log::error
(
'Cannot create symlink: a real directory exists at the destination: '
. $dest);
110
111
return
;
112
}
113
Log::warning
(
'Removing file at symlink location: '
. $dest);
114
if
(!@unlink($dest)) {
115
Log::error
(
'Failed to remove file at symlink location: '
. $dest);
116
117
return
;
118
}
119
}
120
121
Batch::createSymlink
($src, $dest);
122
}
123
132
private
static
function
isPathWithinAllowedBase
($path)
133
{
134
global
$bearsamppRoot
;
135
136
// Normalize paths for comparison
137
$normalizedPath = realpath($path);
138
if
($normalizedPath ===
false
) {
139
Log::error
(
'Failed to resolve path: '
. $path);
140
141
return
false
;
142
}
143
144
$allowedBases = [
145
realpath(
Path::getAppsPath
()),
146
realpath(
Path::getBinPath
()),
147
realpath(
Path::getToolsPath
())
148
];
149
150
foreach
($allowedBases as $base) {
151
if
($base ===
false
) {
152
continue
;
153
}
154
155
// Ensure path starts with allowed base (with directory separator to prevent substring matches)
156
if
(strpos($normalizedPath, $base . DIRECTORY_SEPARATOR) === 0 ||
157
$normalizedPath === $base) {
158
return
true
;
159
}
160
}
161
162
Log::error
(
'Path is outside allowed symlink directories: '
. $path);
163
164
return
false
;
165
}
166
175
private
static
function
isSymlink
($path)
176
{
177
return
is_link($path);
178
}
179
188
public
static
function
safeRemoveSymlink
($path)
189
{
190
// Validate path is within allowed directories
191
if
(!self::isPathWithinAllowedBase($path)) {
192
Log::error
(
'Symlink removal blocked - path not in allowed directories: '
. $path);
193
194
return
false
;
195
}
196
197
// Check if path exists
198
if
(!file_exists($path) && !self::isSymlink($path)) {
199
Log::trace
(
'Symlink or directory already deleted or missing: '
. $path);
200
201
return
false
;
202
}
203
204
// If it's a directory (including junctions/directory-symlinks), use rmdir
205
if
(is_dir($path)) {
206
// Attempt to remove as a symlink/junction first (non-recursive)
207
if
(@rmdir($path)) {
208
Log::debug
(
'Safely removed directory symlink/junction: '
. $path);
209
210
return
true
;
211
}
212
213
// If it failed and it's NOT a link, it's a real directory with content.
214
// We MUST NOT recursively delete it to avoid data loss.
215
if
(!self::isSymlink($path)) {
216
Log::error
(
'Symlink removal blocked - path is a real directory with content: '
. $path);
217
218
return
false
;
219
}
else
{
220
// If it's a link but rmdir failed, try unlink (e.g. file symlink)
221
if
(@unlink($path)) {
222
Log::debug
(
'Safely removed symlink via unlink: '
. $path);
223
224
return
true
;
225
}
226
}
227
}
228
229
// If it's a symlink but not a directory (e.g. file symlink), or if rmdir failed
230
if
(self::isSymlink($path)) {
231
if
(@unlink($path) || @rmdir($path)) {
232
Log::debug
(
'Safely removed symlink: '
. $path);
233
234
return
true
;
235
}
236
}
237
238
if
(self::isSymlink($path)) {
239
Log::error
(
'Failed to remove symlink: '
. $path);
240
241
return
false
;
242
}
243
244
// Regular files should not be deleted here
245
Log::warning
(
'Path is a regular file, not a symlink - refusing deletion: '
. $path);
246
247
return
false
;
248
}
249
264
public
static
function
deleteCurrentSymlinks
()
265
{
266
global
$bearsamppRoot
,
$bearsamppCore
;
267
268
// Check to see if purging is necessary
269
$appsPath =
Path::getAppsPath
();
270
$binPath =
Path::getBinPath
();
271
$toolsPath =
Path::getToolsPath
();
272
273
$array = [
274
self::APACHE_SYMLINK => $binPath .
'/apache/current'
,
275
self::BRUNO_SYMLINK => $toolsPath .
'/bruno/current'
,
276
self::COMPOSER_SYMLINK => $toolsPath .
'/composer/current'
,
277
self::GHOSTSCRIPT_SYMLINK => $toolsPath .
'/ghostscript/current'
,
278
self::GIT_SYMLINK => $toolsPath .
'/git/current'
,
279
self::MAILPIT_SYMLINK => $binPath .
'/mailpit/current'
,
280
self::MARIADB_SYMLINK => $binPath .
'/mariadb/current'
,
281
self::MEMCACHED_SYMLINK => $binPath .
'/memcached/current'
,
282
self::MYSQL_SYMLINK => $binPath .
'/mysql/current'
,
283
self::NGROK_SYMLINK => $toolsPath .
'/ngrok/current'
,
284
self::NODEJS_SYMLINK => $binPath .
'/nodejs/current'
,
285
self::PERL_SYMLINK => $toolsPath .
'/perl/current'
,
286
self::PHP_SYMLINK => $binPath .
'/php/current'
,
287
self::PHPMYADMIN_SYMLINK => $appsPath .
'/phpmyadmin/current'
,
288
self::PHPPGADMIN_SYMLINK => $appsPath .
'/phppgadmin/current'
,
289
self::POSTGRESQL_SYMLINK => $binPath .
'/postgresql/current'
,
290
self::POWERSHELL_SYMLINK => $toolsPath .
'/powershell/current'
,
291
self::PYTHON_SYMLINK => $toolsPath .
'/python/current'
,
292
self::RUBY_SYMLINK => $toolsPath .
'/ruby/current'
,
293
self::XLIGHT_SYMLINK => $binPath .
'/xlight/current'
,
294
];
295
296
// Fix for PHP 8.2: Add null checks before accessing array elements
297
if
(!is_array($array) || empty($array)) {
298
Log::error
(
'Current symlinks array is not initialized or empty.'
);
299
300
return
;
301
}
302
303
// Purge "current" symlinks with safety checks
304
foreach
($array as $name => $path) {
305
// Skip if path is null
306
if
(empty($path)) {
307
continue
;
308
}
309
310
if
(!file_exists($path) && !is_link($path)) {
311
// Log that the symlink was already missing
312
Log::trace
(
'Symlink already deleted or missing: '
. $path);
313
continue
;
314
}
315
316
// Use safe removal method with path validation and non-recursive guarantees
317
self::safeRemoveSymlink
($path);
318
}
319
}
320
}
$bearsamppRoot
global $bearsamppRoot
Definition
ajax.apache.php:16
$bearsamppCore
global $bearsamppCore
Definition
ajax.latestversion.php:24
Batch\removeSymlink
static removeSymlink($link)
Definition
class.batch.php:292
Batch\createSymlink
static createSymlink($src, $dest)
Definition
class.batch.php:277
Log\debug
static debug($data, $file=null)
Definition
class.log.php:726
Log\warning
static warning($data, $file=null)
Definition
class.log.php:748
Log\trace
static trace($data, $file=null)
Definition
class.log.php:715
Log\error
static error($data, $file=null)
Definition
class.log.php:760
Path\getToolsPath
static getToolsPath($aetrayPath=false)
Definition
class.path.php:1122
Path\formatWindowsPath
static formatWindowsPath($path)
Definition
class.path.php:127
Path\getBinPath
static getBinPath($aetrayPath=false)
Definition
class.path.php:384
Path\getAppsPath
static getAppsPath($aetrayPath=false)
Definition
class.path.php:360
Symlinks
Definition
class.symlinks.php:15
Symlinks\APACHE_SYMLINK
const APACHE_SYMLINK
Definition
class.symlinks.php:16
Symlinks\MARIADB_SYMLINK
const MARIADB_SYMLINK
Definition
class.symlinks.php:22
Symlinks\MYSQL_SYMLINK
const MYSQL_SYMLINK
Definition
class.symlinks.php:24
Symlinks\safeRemoveSymlink
static safeRemoveSymlink($path)
Definition
class.symlinks.php:188
Symlinks\deleteCurrentSymlinks
static deleteCurrentSymlinks()
Definition
class.symlinks.php:264
Symlinks\PHPPGADMIN_SYMLINK
const PHPPGADMIN_SYMLINK
Definition
class.symlinks.php:30
Symlinks\PHP_SYMLINK
const PHP_SYMLINK
Definition
class.symlinks.php:28
Symlinks\isPathWithinAllowedBase
static isPathWithinAllowedBase($path)
Definition
class.symlinks.php:132
Symlinks\POWERSHELL_SYMLINK
const POWERSHELL_SYMLINK
Definition
class.symlinks.php:32
Symlinks\createModuleSymlink
static createModuleSymlink($module)
Definition
class.symlinks.php:97
Symlinks\$skipSymlinkCreation
static $skipSymlinkCreation
Definition
class.symlinks.php:40
Symlinks\XLIGHT_SYMLINK
const XLIGHT_SYMLINK
Definition
class.symlinks.php:35
Symlinks\GHOSTSCRIPT_SYMLINK
const GHOSTSCRIPT_SYMLINK
Definition
class.symlinks.php:19
Symlinks\POSTGRESQL_SYMLINK
const POSTGRESQL_SYMLINK
Definition
class.symlinks.php:31
Symlinks\isSkippingSymlinkCreation
static isSkippingSymlinkCreation()
Definition
class.symlinks.php:86
Symlinks\__construct
__construct($root)
Definition
class.symlinks.php:52
Symlinks\NODEJS_SYMLINK
const NODEJS_SYMLINK
Definition
class.symlinks.php:26
Symlinks\COMPOSER_SYMLINK
const COMPOSER_SYMLINK
Definition
class.symlinks.php:18
Symlinks\GIT_SYMLINK
const GIT_SYMLINK
Definition
class.symlinks.php:20
Symlinks\PYTHON_SYMLINK
const PYTHON_SYMLINK
Definition
class.symlinks.php:33
Symlinks\initializePaths
static initializePaths()
Definition
class.symlinks.php:62
Symlinks\BRUNO_SYMLINK
const BRUNO_SYMLINK
Definition
class.symlinks.php:17
Symlinks\MAILPIT_SYMLINK
const MAILPIT_SYMLINK
Definition
class.symlinks.php:21
Symlinks\$root
static $root
Definition
class.symlinks.php:45
Symlinks\MEMCACHED_SYMLINK
const MEMCACHED_SYMLINK
Definition
class.symlinks.php:23
Symlinks\PHPMYADMIN_SYMLINK
const PHPMYADMIN_SYMLINK
Definition
class.symlinks.php:29
Symlinks\NGROK_SYMLINK
const NGROK_SYMLINK
Definition
class.symlinks.php:25
Symlinks\setSkipSymlinkCreation
static setSkipSymlinkCreation(bool $skip)
Definition
class.symlinks.php:76
Symlinks\RUBY_SYMLINK
const RUBY_SYMLINK
Definition
class.symlinks.php:34
Symlinks\PERL_SYMLINK
const PERL_SYMLINK
Definition
class.symlinks.php:27
Symlinks\isSymlink
static isSymlink($path)
Definition
class.symlinks.php:175
sandbox
core
classes
class.symlinks.php
Generated by
1.17.0