Bearsampp
2026.7.11
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-2024 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
75
public
static
function
setSkipSymlinkCreation
(
bool
$skip): void
76
{
77
self::$skipSymlinkCreation = $skip;
78
}
79
85
public
static
function
isSkippingSymlinkCreation
(): bool
86
{
87
return
self::$skipSymlinkCreation;
88
}
89
96
public
static
function
createModuleSymlink
($module)
97
{
98
$src =
Path::formatWindowsPath
($module->currentPath);
99
$dest =
Path::formatWindowsPath
($module->symlinkPath);
100
101
if
(is_link($dest)) {
102
if
(readlink($dest) === $src) {
103
return
;
104
}
105
Batch::removeSymlink
($dest);
106
} elseif (file_exists($dest)) {
107
if
(is_dir($dest)) {
108
Log::error
(
'Cannot create symlink: a real directory exists at the destination: '
. $dest);
109
return
;
110
}
111
Log::warning
(
'Removing file at symlink location: '
. $dest);
112
if
(!@unlink($dest)) {
113
Log::error
(
'Failed to remove file at symlink location: '
. $dest);
114
return
;
115
}
116
}
117
118
Batch::createSymlink
($src, $dest);
119
}
120
128
private
static
function
isPathWithinAllowedBase
($path)
129
{
130
global
$bearsamppRoot
;
131
132
// Normalize paths for comparison
133
$normalizedPath = realpath($path);
134
if
($normalizedPath ===
false
) {
135
Log::error
(
'Failed to resolve path: '
. $path);
136
return
false
;
137
}
138
139
$allowedBases = [
140
realpath(
Path::getAppsPath
()),
141
realpath(
Path::getBinPath
()),
142
realpath(
Path::getToolsPath
())
143
];
144
145
foreach
($allowedBases as $base) {
146
if
($base ===
false
) {
147
continue
;
148
}
149
150
// Ensure path starts with allowed base (with directory separator to prevent substring matches)
151
if
(strpos($normalizedPath, $base . DIRECTORY_SEPARATOR) === 0 ||
152
$normalizedPath === $base) {
153
return
true
;
154
}
155
}
156
157
Log::error
(
'Path is outside allowed symlink directories: '
. $path);
158
return
false
;
159
}
160
168
private
static
function
isSymlink
($path)
169
{
170
return
is_link($path);
171
}
172
180
public
static
function
safeRemoveSymlink
($path)
181
{
182
// Validate path is within allowed directories
183
if
(!self::isPathWithinAllowedBase($path)) {
184
Log::error
(
'Symlink removal blocked - path not in allowed directories: '
. $path);
185
return
false
;
186
}
187
188
// Check if path exists
189
if
(!file_exists($path) && !is_link($path)) {
190
Log::trace
(
'Symlink or directory already deleted or missing: '
. $path);
191
return
false
;
192
}
193
194
// If it's a directory (including junctions/directory-symlinks), use rmdir
195
if
(is_dir($path)) {
196
// Attempt to remove as a symlink/junction first (non-recursive)
197
if
(@rmdir($path)) {
198
Log::debug
(
'Safely removed directory symlink/junction: '
. $path);
199
return
true
;
200
}
201
202
// If it failed and it's NOT a link, it's a real directory with content.
203
// We MUST NOT recursively delete it to avoid data loss.
204
if
(!is_link($path)) {
205
Log::error
(
'Symlink removal blocked - path is a real directory with content: '
. $path);
206
return
false
;
207
}
else
{
208
// If it's a link but rmdir failed, try unlink (e.g. file symlink)
209
if
(@unlink($path)) {
210
Log::debug
(
'Safely removed symlink via unlink: '
. $path);
211
return
true
;
212
}
213
}
214
}
215
216
// If it's a symlink but not a directory (e.g. file symlink), or if rmdir failed
217
if
(is_link($path)) {
218
if
(@unlink($path) || @rmdir($path)) {
219
Log::debug
(
'Safely removed symlink: '
. $path);
220
return
true
;
221
}
222
}
223
224
if
(is_link($path)) {
225
Log::error
(
'Failed to remove symlink: '
. $path);
226
return
false
;
227
}
228
229
// Regular files should not be deleted here
230
Log::warning
(
'Path is a regular file, not a symlink - refusing deletion: '
. $path);
231
return
false
;
232
}
233
248
public
static
function
deleteCurrentSymlinks
()
249
{
250
global
$bearsamppRoot
,
$bearsamppCore
;
251
252
// Check to see if purging is necessary
253
$appsPath =
Path::getAppsPath
();
254
$binPath =
Path::getBinPath
();
255
$toolsPath =
Path::getToolsPath
();
256
257
$array = [
258
self::APACHE_SYMLINK => $binPath .
'/apache/current'
,
259
self::BRUNO_SYMLINK => $toolsPath .
'/bruno/current'
,
260
self::COMPOSER_SYMLINK => $toolsPath .
'/composer/current'
,
261
self::GHOSTSCRIPT_SYMLINK => $toolsPath .
'/ghostscript/current'
,
262
self::GIT_SYMLINK => $toolsPath .
'/git/current'
,
263
self::MAILPIT_SYMLINK => $binPath .
'/mailpit/current'
,
264
self::MARIADB_SYMLINK => $binPath .
'/mariadb/current'
,
265
self::MEMCACHED_SYMLINK => $binPath .
'/memcached/current'
,
266
self::MYSQL_SYMLINK => $binPath .
'/mysql/current'
,
267
self::NGROK_SYMLINK => $toolsPath .
'/ngrok/current'
,
268
self::NODEJS_SYMLINK => $binPath .
'/nodejs/current'
,
269
self::PERL_SYMLINK => $toolsPath .
'/perl/current'
,
270
self::PHP_SYMLINK => $binPath .
'/php/current'
,
271
self::PHPMYADMIN_SYMLINK => $appsPath .
'/phpmyadmin/current'
,
272
self::PHPPGADMIN_SYMLINK => $appsPath .
'/phppgadmin/current'
,
273
self::POSTGRESQL_SYMLINK => $binPath .
'/postgresql/current'
,
274
self::POWERSHELL_SYMLINK => $toolsPath .
'/powershell/current'
,
275
self::PYTHON_SYMLINK => $toolsPath .
'/python/current'
,
276
self::RUBY_SYMLINK => $toolsPath .
'/ruby/current'
,
277
self::XLIGHT_SYMLINK => $binPath .
'/xlight/current'
,
278
];
279
280
// Fix for PHP 8.2: Add null checks before accessing array elements
281
if
(!is_array($array) || empty($array)) {
282
Log::error
(
'Current symlinks array is not initialized or empty.'
);
283
return
;
284
}
285
286
// Purge "current" symlinks with safety checks
287
foreach
($array as $name => $path) {
288
// Skip if path is null
289
if
(empty($path)) {
290
continue
;
291
}
292
293
if
(!file_exists($path) && !is_link($path)) {
294
// Log that the symlink was already missing
295
Log::trace
(
'Symlink already deleted or missing: '
. $path);
296
continue
;
297
}
298
299
// Use safe removal method with path validation and non-recursive guarantees
300
self::safeRemoveSymlink
($path);
301
}
302
}
303
}
$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:282
Batch\createSymlink
static createSymlink($src, $dest)
Definition
class.batch.php:268
Log\debug
static debug($data, $file=null)
Definition
class.log.php:616
Log\warning
static warning($data, $file=null)
Definition
class.log.php:638
Log\trace
static trace($data, $file=null)
Definition
class.log.php:605
Log\error
static error($data, $file=null)
Definition
class.log.php:650
Path\getToolsPath
static getToolsPath($aetrayPath=false)
Definition
class.path.php:951
Path\formatWindowsPath
static formatWindowsPath($path)
Definition
class.path.php:118
Path\getBinPath
static getBinPath($aetrayPath=false)
Definition
class.path.php:364
Path\getAppsPath
static getAppsPath($aetrayPath=false)
Definition
class.path.php:342
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:180
Symlinks\deleteCurrentSymlinks
static deleteCurrentSymlinks()
Definition
class.symlinks.php:248
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:128
Symlinks\POWERSHELL_SYMLINK
const POWERSHELL_SYMLINK
Definition
class.symlinks.php:32
Symlinks\createModuleSymlink
static createModuleSymlink($module)
Definition
class.symlinks.php:96
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:85
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:75
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:168
sandbox
core
classes
class.symlinks.php
Generated by
1.17.0