Bearsampp
2026.9.23
Toggle main menu visibility
Loading...
Searching...
No Matches
class.core.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
18
class
Core
19
{
20
// Constants for various file names and versions
21
const
isRoot_FILE
=
'root.php'
;
22
const
PATH_WIN_PLACEHOLDER
=
'~BEARSAMPP_WIN_PATH~'
;
23
const
PATH_LIN_PLACEHOLDER
=
'~BEARSAMPP_LIN_PATH~'
;
24
25
const
PHP_EXE
=
'php-win.exe'
;
26
const
SETENV_EXE
=
'SetEnv.exe'
;
27
const
NSSM_EXE
=
'nssm.exe'
;
28
const
OPENSSL_EXE
=
'openssl.exe'
;
29
const
OPENSSL_CONF
=
'openssl.cfg'
;
30
const
HOSTSEDITOR_EXE
=
'hEdit_x64.exe'
;
31
const
LN_EXE
=
'ln.exe'
;
32
const
PWGEN_EXE
=
"PwTech.exe"
;
33
34
const
APP_VERSION
=
'version.dat'
;
35
const
LAST_PATH
=
'lastPath.dat'
;
36
const
EXEC
=
'exec.dat'
;
37
const
LOADING_PID
=
'loading.pid'
;
38
44
public
function
__construct
()
45
{
46
if
(extension_loaded(
'winbinder'
)) {
47
require_once
Path::getLibsPath
() .
'/winbinder/winbinder.php'
;
48
}
49
}
50
56
public
function
getAppVersion
()
57
{
58
global
$bearsamppLang
;
59
60
$filePath =
Path::getResourcesPath
() .
'/'
. self::APP_VERSION;
61
if
(!is_file($filePath)) {
62
Log::error
(sprintf(
$bearsamppLang
->getValue(
Lang::ERROR_CONF_NOT_FOUND
),
APP_TITLE
, $filePath));
63
64
return
null
;
65
}
66
67
return
trim(file_get_contents($filePath));
68
}
69
75
public
function
getLastPathContent
()
76
{
77
return
@file_get_contents(
Path::getLastPath
());
78
}
79
85
public
function
getPreviousExec
()
86
{
87
$file = $this->
getExec
();
88
if
(file_exists($file)) {
89
return
trim(file_get_contents($file));
90
}
91
92
return
false
;
93
}
94
102
public
function
getExec
($aetrayPath =
false
)
103
{
104
return
Path::getTmpPath
($aetrayPath) .
'/'
. self::EXEC;
105
}
106
112
public
function
setExec
($action)
113
{
114
file_put_contents($this->
getExec
(), $action);
115
}
116
124
public
function
getLoadingPid
($aetrayPath =
false
)
125
{
126
return
Path::getResourcesPath
($aetrayPath) .
'/'
. self::LOADING_PID;
127
}
128
134
public
function
addLoadingPid
($pid)
135
{
136
file_put_contents($this->
getLoadingPid
(), $pid . PHP_EOL, FILE_APPEND);
137
}
138
144
public
function
__toString
()
145
{
146
return
'core object'
;
147
}
148
170
public
function
unzipFile
($filePath, $destination, $progressCallback =
null
)
171
{
172
global
$bearsamppRoot
;
173
174
$sevenZipPath =
Path::getLibsPath
() .
'/7zip/7za.exe'
;
175
176
if
(!file_exists($sevenZipPath)) {
177
Log::error
(
'7za.exe not found at: '
. $sevenZipPath);
178
179
return
false
;
180
}
181
182
if
($progressCallback) {
183
call_user_func($progressCallback,
'Analyzing archive...'
);
184
}
185
186
// Defensive path-traversal scan: parse the structured `7z l -slt` output into
187
// each individual entry path and validate every one relative to the destination,
188
// so extraction can never escape the intended directory. This must fail closed:
189
// any listing/parse failure aborts the operation rather than proceeding unverified.
190
// The scan also returns the number of archive entries, used as the extraction
191
// file count. A standalone integrity test is not needed: the archive's SHA-256
192
// was already verified against its .sha256 sidecar upstream, and `7za x`
193
// CRC-checks every file as it is extracted.
194
$numFiles =
self::isSafeDestinationFileList
($sevenZipPath, $filePath);
195
if
($numFiles ===
false
) {
196
Log::error
(
'Archive path-traversal scan failed or rejected for: '
. $filePath);
197
198
return
false
;
199
}
200
Log::trace
(
'Number of files to be extracted: '
. $numFiles);
201
202
if
($progressCallback) {
203
call_user_func($progressCallback,
'Initializing extraction...'
);
204
}
205
// Extract the archive, streaming progress line-by-line
206
$returnVar =
CommandRunner::stream
(
207
$sevenZipPath,
208
[
'x'
, $filePath,
'-y'
,
'-bsp1'
,
'-bb0'
,
'-o'
. $destination],
209
function
(
string
$line) use ($progressCallback) {
210
Log::trace
(
"Processing line: $line"
);
211
if
($line ===
'Everything is Ok'
) {
212
if
($progressCallback) {
213
Log::trace
(
'Extraction progress: 100%'
);
214
call_user_func($progressCallback, 100);
215
}
216
} elseif ($progressCallback && preg_match(
'/(?:^|\s)(\d+)%/'
, $line, $matches)) {
217
$currentPercentage = intval($matches[1]);
218
Log::trace
(
"Extraction progress: $currentPercentage%"
);
219
call_user_func($progressCallback, $currentPercentage);
220
}
else
{
221
Log::trace
(
"Line did not match pattern: $line"
);
222
}
223
}
224
);
225
226
if
($returnVar ===
false
) {
227
Log::error
(
'Failed to open process for: '
. $sevenZipPath);
228
229
return
[
'error'
=>
'Failed to open process'
,
'numFiles'
=> $numFiles];
230
}
231
232
Log::trace
(
'Command return value: '
. $returnVar);
233
234
if
($returnVar === 0 && $progressCallback) {
235
Log::trace
(
'Extraction completed successfully. Setting progress to 100%'
);
236
call_user_func($progressCallback, 100);
237
usleep(100000);
// 100 milliseconds
238
}
239
240
if
($returnVar === 0) {
241
Log::debug
(
'Successfully unzipped file to: '
. $destination);
242
243
return
[
'success'
=>
true
,
'numFiles'
=> $numFiles];
244
}
245
246
Log::error
(
'Failed to unzip file. Command return value: '
. $returnVar);
247
248
return
[
'error'
=>
'Failed to unzip file'
,
'numFiles'
=> $numFiles];
249
}
250
272
private
static
function
isSafeDestinationFileList
($sevenZipPath, $filePath)
273
{
274
$listingFile =
Path::getTmpPath
() .
'/7z-listing-'
. uniqid(
''
,
true
) .
'.txt'
;
275
$stderrFile =
Path::getTmpPath
() .
'/7z-listing-'
. uniqid(
''
,
true
) .
'.err'
;
276
277
$returnVar =
CommandRunner::execToFile
($sevenZipPath, [
'l'
,
'-slt'
, $filePath], $listingFile, $stderrFile);
278
if
($returnVar ===
false
) {
279
Log::error
(
'Path-traversal scan: unable to list archive: '
. $filePath);
280
281
return
false
;
282
}
283
284
$listingOutput = @file_get_contents($listingFile);
285
@unlink($listingFile);
286
@unlink($stderrFile);
287
288
if
(!is_string($listingOutput) || $listingOutput ===
''
) {
289
Log::error
(
'Path-traversal scan: unable to list archive: '
. $filePath);
290
291
return
false
;
292
}
293
294
// 7-Zip separates the `-slt` entry blocks with blank lines (a single dash
295
// line precedes the first entry, and the banner/header references the archive
296
// itself). Normalize line endings and split on blank lines so each block is
297
// one archive entry.
298
$listingOutput = str_replace(
"\r\n"
,
"\n"
, $listingOutput);
299
$blocks = preg_split(
'/\n[ \t]*\n/'
, $listingOutput);
300
if
(empty($blocks)) {
301
// No entries listed at all - cannot validate, fail closed.
302
Log::error
(
'Path-traversal scan: no entries found in archive: '
. $filePath);
303
304
return
false
;
305
}
306
307
// The header block's `Path = <archive itself>` entry must not be validated
308
// or counted; skip any block that resolves to the archive file.
309
$archivePathNormalized = strtolower(str_replace(
'/'
,
'\\'
, $filePath));
310
311
$numFiles = 0;
312
foreach
($blocks as $block) {
313
$block = trim($block);
314
if
($block ===
''
) {
315
continue
;
316
}
317
318
if
(!preg_match(
'/^Path = (.*)$/m'
, $block, $match)) {
319
// Not an entry block (banner/scanning preamble lines) - skip.
320
continue
;
321
}
322
323
$entryPath = trim($match[1]);
324
if
(strtolower(str_replace(
'/'
,
'\\'
, $entryPath)) === $archivePathNormalized) {
325
// Banner/header (and any trailing archive-summary) block.
326
continue
;
327
}
328
329
if
(self::isUnsafeArchiveEntryPath($entryPath)) {
330
Log::error
(
'Archive contains an unsafe entry path ("'
. $entryPath .
'"): '
. $filePath);
331
332
return
false
;
333
}
334
335
// Directories are validated above but not counted as files, matching the
336
// file count previously reported by `7z t` (`Files:` excludes folders).
337
if
(preg_match(
'/^Attributes = D\s*$/m'
, $block)) {
338
continue
;
339
}
340
341
$numFiles++;
342
}
343
344
if
($numFiles === 0) {
345
// Nothing to extract - cannot validate, fail closed.
346
Log::error
(
'Path-traversal scan: no file entries found in archive: '
. $filePath);
347
348
return
false
;
349
}
350
351
return
$numFiles;
352
}
353
365
private
static
function
isUnsafeArchiveEntryPath
($entryPath)
366
{
367
if
($entryPath ===
''
) {
368
return
true
;
369
}
370
371
// Parent-directory traversal (matches any path segment equal to '..').
372
if
(preg_match(
'/(?:^|[\\/\\\\])\.\.(?:[\\/\\\\]|$)/'
, $entryPath)) {
373
return
true
;
374
}
375
376
// Absolute / rooted paths.
377
if
(preg_match(
'#^(?:[A-Za-z]:[\\/\\\\]|[\\/\\\\])#'
, $entryPath)) {
378
return
true
;
379
}
380
381
return
false
;
382
}
383
}
$bearsamppLang
global $bearsamppLang
Definition
ajax.apache.php:16
$bearsamppRoot
global $bearsamppRoot
Definition
ajax.apache.php:16
CommandRunner\execToFile
static execToFile(string $executable, array $args, string $stdoutFile, string $stderrFile)
Definition
class.commandrunner.php:103
CommandRunner\stream
static stream(string $executable, array $args, callable $lineCallback)
Definition
class.commandrunner.php:177
Core
Definition
class.core.php:19
Core\OPENSSL_CONF
const OPENSSL_CONF
Definition
class.core.php:29
Core\unzipFile
unzipFile($filePath, $destination, $progressCallback=null)
Definition
class.core.php:170
Core\__construct
__construct()
Definition
class.core.php:44
Core\PHP_EXE
const PHP_EXE
Definition
class.core.php:25
Core\PWGEN_EXE
const PWGEN_EXE
Definition
class.core.php:32
Core\isRoot_FILE
const isRoot_FILE
Definition
class.core.php:21
Core\getAppVersion
getAppVersion()
Definition
class.core.php:56
Core\addLoadingPid
addLoadingPid($pid)
Definition
class.core.php:134
Core\getPreviousExec
getPreviousExec()
Definition
class.core.php:85
Core\isSafeDestinationFileList
static isSafeDestinationFileList($sevenZipPath, $filePath)
Definition
class.core.php:272
Core\getExec
getExec($aetrayPath=false)
Definition
class.core.php:102
Core\getLastPathContent
getLastPathContent()
Definition
class.core.php:75
Core\__toString
__toString()
Definition
class.core.php:144
Core\isUnsafeArchiveEntryPath
static isUnsafeArchiveEntryPath($entryPath)
Definition
class.core.php:365
Core\PATH_LIN_PLACEHOLDER
const PATH_LIN_PLACEHOLDER
Definition
class.core.php:23
Core\EXEC
const EXEC
Definition
class.core.php:36
Core\getLoadingPid
getLoadingPid($aetrayPath=false)
Definition
class.core.php:124
Core\NSSM_EXE
const NSSM_EXE
Definition
class.core.php:27
Core\SETENV_EXE
const SETENV_EXE
Definition
class.core.php:26
Core\LN_EXE
const LN_EXE
Definition
class.core.php:31
Core\PATH_WIN_PLACEHOLDER
const PATH_WIN_PLACEHOLDER
Definition
class.core.php:22
Core\LOADING_PID
const LOADING_PID
Definition
class.core.php:37
Core\setExec
setExec($action)
Definition
class.core.php:112
Core\HOSTSEDITOR_EXE
const HOSTSEDITOR_EXE
Definition
class.core.php:30
Core\APP_VERSION
const APP_VERSION
Definition
class.core.php:34
Core\OPENSSL_EXE
const OPENSSL_EXE
Definition
class.core.php:28
Core\LAST_PATH
const LAST_PATH
Definition
class.core.php:35
Lang\ERROR_CONF_NOT_FOUND
const ERROR_CONF_NOT_FOUND
Definition
class.lang.php:154
Log\debug
static debug($data, $file=null)
Definition
class.log.php:726
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\getResourcesPath
static getResourcesPath($aetrayPath=false)
Definition
class.path.php:1002
Path\getTmpPath
static getTmpPath($aetrayPath=false)
Definition
class.path.php:1110
Path\getLibsPath
static getLibsPath($aetrayPath=false)
Definition
class.path.php:619
Path\getLastPath
static getLastPath($aetrayPath=false)
Definition
class.path.php:607
APP_TITLE
const APP_TITLE
Definition
root.php:19
sandbox
core
classes
class.core.php
Generated by
1.17.0