Passed
Pull Request — master (#1531)
by Michael
09:49
created

PathStuffController::sanitizePath()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 2
nop 1
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * See the enclosed file license.txt for licensing information.
5
 * If you did not receive this file, get it at https://www.gnu.org/licenses/gpl-2.0.html
6
 *
7
 * @copyright    (c) 2000-2016 XOOPS Project (www.xoops.org)
8
 * @license          GNU GPL 2.0 or later (https://www.gnu.org/licenses/gpl-2.0.html)
9
 * @package          installer
10
 * @since            2.3.0
11
 * @author           Haruki Setoyama  <[email protected]>
12
 * @author           Kazumi Ono <[email protected]>
13
 * @author           Skalpa Keo <[email protected]>
14
 * @author           Taiwen Jiang <[email protected]>
15
 * @author           DuGris (aka L. JEN) <[email protected]>
16
 **/
17
class PathStuffController
18
{
19
    /**
20
     * @var array
21
     */
22
    public array $xoopsPath = [
23
        'root' => '',
24
        'data' => '',
25
        'lib'  => '',
26
    ];
27
    /**
28
     * @var array
29
     */
30
    public array $xoopsPathDefault = [
31
        'data' => 'xoops_data',
32
        'lib'  => 'xoops_lib',
33
    ];
34
    /**
35
     * @var array
36
     */
37
    public array $dataPath = [
38
        'caches'    => [
39
            'smarty_cache',
40
            'smarty_compile',
41
            'xoops_cache',
42
        ],
43
        'configs'   => [
44
            'captcha',
45
            'textsanitizer',
46
        ],
47
        'data'      => null,
48
        'protector' => null,
49
    ];
50
    /**
51
     * @var array
52
     */
53
    public array $path_lookup = [
54
        'root' => 'ROOT_PATH',
55
        'data' => 'VAR_PATH',
56
        'lib'  => 'PATH',
57
    ];
58
    public $xoopsUrl = '';
59
    public $xoopsCookieDomain = '';
60
    /**
61
     * @var array
62
     */
63
    public array $validPath = [
64
        'root' => 0,
65
        'data' => 0,
66
        'lib'  => 0,
67
    ];
68
    /**
69
     * @var bool
70
     */
71
    public bool $validUrl = false;
72
    /**
73
     * @var array
74
     */
75
    public array $permErrors = [
76
        'root' => null,
77
        'data' => null,
78
    ];
79
80
    /**
81
     * @param $xoopsPathDefault
82
     * @param $dataPath
83
     */
84
    public function __construct($xoopsPathDefault, $dataPath)
85
    {
86
        $this->xoopsPathDefault = $xoopsPathDefault;
87
        $this->dataPath         = $dataPath;
88
89
        if (isset($_SESSION['settings']['ROOT_PATH'])) {
90
            foreach ($this->path_lookup as $req => $sess) {
91
                $this->xoopsPath[$req] = $_SESSION['settings'][$sess];
92
            }
93
        } else {
94
            $path = str_replace("\\", '/', realpath(dirname(__DIR__, 2) . '/'));
95
            if (substr($path, -1) === '/') {
96
                $path = substr($path, 0, -1);
97
            }
98
            if (file_exists("$path/mainfile.dist.php")) {
99
                $this->xoopsPath['root'] = $path;
100
            }
101
            // Firstly, locate XOOPS lib folder out of XOOPS root folder
102
            $this->xoopsPath['lib'] = dirname($path) . '/' . $this->xoopsPathDefault['lib'];
103
            // If the folder is not created, re-locate XOOPS lib folder inside XOOPS root folder
104
            if (!is_dir($this->xoopsPath['lib'] . '/')) {
105
                $this->xoopsPath['lib'] = $path . '/' . $this->xoopsPathDefault['lib'];
106
            }
107
            // Firstly, locate XOOPS data folder out of XOOPS root folder
108
            $this->xoopsPath['data'] = dirname($path) . '/' . $this->xoopsPathDefault['data'];
109
            // If the folder is not created, re-locate XOOPS data folder inside XOOPS root folder
110
            if (!is_dir($this->xoopsPath['data'] . '/')) {
111
                $this->xoopsPath['data'] = $path . '/' . $this->xoopsPathDefault['data'];
112
            }
113
        }
114
        if (isset($_SESSION['settings']['URL'])) {
115
            $this->xoopsUrl = $_SESSION['settings']['URL'];
116
        } else {
117
            $path           = $GLOBALS['wizard']->baseLocation();
118
            $this->xoopsUrl = substr($path, 0, strrpos($path, '/'));
119
        }
120
        if (isset($_SESSION['settings']['COOKIE_DOMAIN'])) {
121
            $this->xoopsCookieDomain = $_SESSION['settings']['COOKIE_DOMAIN'];
122
        } else {
123
//            $this->xoopsCookieDomain = xoops_getBaseDomain($this->xoopsUrl);
124
            $this->xoopsCookieDomain = $this->xoops_getBaseDomain($this->xoopsUrl);
125
        }
126
    }
127
128
//=================================================
129
130
131
    /**
132
     * Determine the base domain name for a URL. The primary use for this is to set the domain
133
     * used for cookies to represent any subdomains.
134
     *
135
     * The registrable domain is determined using the public suffix list. If the domain is not
136
     * registrable, an empty string is returned. This empty string can be used in setcookie()
137
     * as the domain, which restricts cookie to just the current host.
138
     *
139
     * @param string $url URL or hostname to process
140
     *
141
     * @return string the registrable domain or an empty string
142
     */
143
    private function xoops_getBaseDomain($url)
144
    {
145
        $parts = parse_url($url);
146
        $host = '';
147
        if (!empty($parts['host'])) {
148
            $host = $parts['host'];
149
            if (strtolower($host) === 'localhost') {
150
                return 'localhost';
151
            }
152
// bail if this is an IPv4 address (IPv6 will fail later)
153
            if (false !== filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
154
                return '';
155
            }
156
//            $regdom = new \Xoops\RegDom\RegisteredDomain();
157
//            $host = $regdom->getRegisteredDomain($host);
158
159
160
            $host = $this->getRegisteredDomain($host);
161
        }
162
        return $host ?? '';
163
    }
164
165
166
    // Define a simplified getRegisteredDomain function
167
    function getRegisteredDomain($host) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
168
        $hostParts = explode('.', $host);
169
        $numParts = count($hostParts);
170
171
        if ($numParts >= 2) {
172
            // For simplicity, assume the domain is the last two parts
173
            return $hostParts[$numParts - 2] . '.' . $hostParts[$numParts - 1];
174
        }
175
176
        return $host; // Return as is if it's a top-level domain
177
    }
178
179
180
    public function updateXoopsTrustPath($newTrustPath) {
181
        // 1. Update the session variable
182
        $_SESSION['settings']['TRUST_PATH'] = $newTrustPath;
183
184
        // 2. Update the defined constant (if not already defined)
185
        if (!defined('XOOPS_TRUST_PATH')) {
186
            define('XOOPS_TRUST_PATH', $newTrustPath);
187
        }
188
189
190
        // Firstly, locate XOOPS lib folder out of XOOPS root folder
191
//        $this->xoopsPath['lib'] = dirname($path) . '/' . $this->xoopsPathDefault['lib'];
192
        $this->xoopsPath['lib'] = $newTrustPath;
193
        // If the folder is not created, re-locate XOOPS lib folder inside XOOPS root folder
194
//        if (!is_dir($this->xoopsPath['lib'] . '/')) {
195
//            $this->xoopsPath['lib'] = $path . '/' . $this->xoopsPathDefault['lib'];
196
//        }
197
198
199
200
201
202
203
        // 3. Re-register the autoloader
204
        try {
205
            $this->registerAutoloader($newTrustPath);
206
        } catch (Exception $e) {
207
            // Log or handle error
208
            error_log('Failed to register autoloader: ' . $e->getMessage());
209
            throw new RuntimeException("Could not configure autoloader for the new library path.");
210
        }
211
    }
212
213
    private function registerAutoloader($trustPath) {
214
        // Composer's autoloader (if it exists)
215
        $composerAutoloader = $trustPath . '/vendor/autoload.php';
216
        if (file_exists($composerAutoloader)) {
217
            include_once $composerAutoloader;
218
            return;
219
        }
220
221
        // Notify about missing Composer autoloader
222
        throw new RuntimeException("Autoloader not found in {$trustPath}. Ensure the vendor folder is intact.");
223
    }
224
225
// install/class/pathcontroller.php
226
227
    public function sanitizePath($path) {
228
        // Normalize the path and resolve symbolic links
229
        $realPath = realpath($path);
230
        if ($realPath && is_dir($realPath)) {
231
            // Ensure no trailing slashes for consistency
232
            return rtrim(str_replace('\\', '/', $realPath), '/');
233
        }
234
        return false; // Return false for invalid paths
235
    }
236
237
238
    //========================================
239
    public function execute()
240
    {
241
        $this->readRequest();
242
        $valid = $this->validate();
243
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
244
            foreach ($this->path_lookup as $req => $sess) {
245
                $_SESSION['settings'][$sess] = $this->xoopsPath[$req];
246
            }
247
            $_SESSION['settings']['URL'] = $this->xoopsUrl;
248
            $_SESSION['settings']['COOKIE_DOMAIN'] = $this->xoopsCookieDomain;
249
            if ($valid) {
250
                $GLOBALS['wizard']->redirectToPage('+1');
251
            } else {
252
                $GLOBALS['wizard']->redirectToPage('+0');
253
            }
254
        }
255
    }
256
257
    public function readRequest()
258
    {
259
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
260
            $request = $_POST;
261
            foreach ($this->path_lookup as $req => $sess) {
262
                if (isset($request[$req])) {
263
                    $request[$req] = str_replace("\\", '/', trim($request[$req]));
264
                    if (substr($request[$req], -1) === '/') {
265
                        $request[$req] = substr($request[$req], 0, -1);
266
                    }
267
                    $this->xoopsPath[$req] = $request[$req];
268
                }
269
            }
270
            if (isset($request['URL'])) {
271
                $request['URL'] = trim($request['URL']);
272
                if (substr($request['URL'], -1) === '/') {
273
                    $request['URL'] = substr($request['URL'], 0, -1);
274
                }
275
                $this->xoopsUrl = $request['URL'];
276
            }
277
            if (isset($request['COOKIE_DOMAIN'])) {
278
                $tempCookieDomain = trim($request['COOKIE_DOMAIN']);
279
                $tempParts = parse_url($tempCookieDomain);
280
                if (!empty($tempParts['host'])) {
281
                    $tempCookieDomain = $tempParts['host'];
282
                }
283
                $request['COOKIE_DOMAIN'] = $tempCookieDomain;
284
                $this->xoopsCookieDomain = $tempCookieDomain;
285
            }
286
        }
287
    }
288
289
    /**
290
     * @return bool
291
     */
292
    public function validate()
293
    {
294
        foreach (array_keys($this->xoopsPath) as $path) {
295
            if ($this->checkPath($path)) {
296
                $this->checkPermissions($path);
297
            }
298
        }
299
        $this->validUrl = !empty($this->xoopsUrl);
300
        $validPaths     = (array_sum(array_values($this->validPath)) == count(array_keys($this->validPath))) ? 1 : 0;
301
        $validPerms     = true;
302
        foreach ($this->permErrors as $key => $errs) {
303
            if (empty($errs)) {
304
                continue;
305
            }
306
            foreach ($errs as $path => $status) {
307
                if (empty($status)) {
308
                    $validPerms = false;
309
                    break;
310
                }
311
            }
312
        }
313
314
        return ($validPaths && $this->validUrl && $validPerms);
315
    }
316
317
    /**
318
     * @param string $PATH
319
     *
320
     * @return int
321
     */
322
    public function checkPath($PATH = '')
323
    {
324
        $ret = 1;
325
        if ($PATH === 'root' || empty($PATH)) {
326
            $path = 'root';
327
            if (is_dir($this->xoopsPath[$path]) && is_readable($this->xoopsPath[$path])) {
328
                $versionFile = "{$this->xoopsPath[$path]}/include/version.php";
329
                if (file_exists($versionFile)) {
330
                    include_once $versionFile;
331
                }
332
                if (defined('XOOPS_VERSION') && file_exists("{$this->xoopsPath[$path]}/mainfile.dist.php")) {
333
                    $this->validPath[$path] = 1;
334
                }
335
            }
336
            $ret *= $this->validPath[$path];
337
        }
338
        if ($PATH === 'lib' || empty($PATH)) {
339
            $path = 'lib';
340
            if (is_dir($this->xoopsPath[$path]) && is_readable($this->xoopsPath[$path])) {
341
                $this->validPath[$path] = 1;
342
            }
343
            $ret *= $this->validPath[$path];
344
        }
345
        if ($PATH === 'data' || empty($PATH)) {
346
            $path = 'data';
347
            if (is_dir($this->xoopsPath[$path]) && is_readable($this->xoopsPath[$path])) {
348
                $this->validPath[$path] = 1;
349
            }
350
            $ret *= $this->validPath[$path];
351
        }
352
353
        return $ret;
354
    }
355
356
    /**
357
     * @param $parent
358
     * @param $path
359
     * @param $error
360
     * @return null
361
     */
362
    public function setPermission($parent, $path, &$error)
363
    {
364
        if (is_array($path)) {
365
            foreach (array_keys($path) as $item) {
366
                if (is_string($item)) {
367
                    $error[$parent . '/' . $item] = $this->makeWritable($parent . '/' . $item);
368
                    if (empty($path[$item])) {
369
                        continue;
370
                    }
371
                    foreach ($path[$item] as $child) {
372
                        $this->setPermission($parent . '/' . $item, $child, $error);
373
                    }
374
                } else {
375
                    $error[$parent . '/' . $path[$item]] = $this->makeWritable($parent . '/' . $path[$item]);
376
                }
377
            }
378
        } else {
379
            $error[$parent . '/' . $path] = $this->makeWritable($parent . '/' . $path);
380
        }
381
382
        return null;
383
    }
384
385
    /**
386
     * @param $path
387
     *
388
     * @return bool
389
     */
390
    public function checkPermissions($path)
391
    {
392
        $paths  = [
393
            'root' => [
394
                'mainfile.php',
395
                'uploads',
396
            ],
397
            'data' => $this->dataPath,
398
        ];
399
        $errors = [
400
            'root' => null,
401
            'data' => null,
402
        ];
403
404
        if (!isset($this->xoopsPath[$path])) {
405
            return false;
406
        }
407
        if (!isset($errors[$path])) {
408
            return true;
409
        }
410
        $this->setPermission($this->xoopsPath[$path], $paths[$path], $errors[$path]);
411
        if (in_array(false, $errors[$path])) {
0 ignored issues
show
Bug introduced by
$errors[$path] of type null is incompatible with the type array expected by parameter $haystack of in_array(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

411
        if (in_array(false, /** @scrutinizer ignore-type */ $errors[$path])) {
Loading history...
412
            $this->permErrors[$path] = $errors[$path];
413
        }
414
415
        return true;
416
    }
417
418
    /**
419
     * Write-enable the specified folder
420
     *
421
     * @param string $path
422
     * @param bool   $create
423
     *
424
     * @internal param bool $recurse
425
     * @return false on failure, method (u-ser,g-roup,w-orld) on success
426
     */
427
    public function makeWritable($path, $create = true)
428
    {
429
        $mode = intval('0777', 8);
430
        if (!is_dir($path)) {
431
            if (!$create) {
432
                return false;
433
            } else {
434
                if (!mkdir($path, $mode) && !is_dir($path)) {
435
                    throw new \RuntimeException(sprintf('Directory "%s" was not created', $path));
436
                }
437
            }
438
        }
439
        if (!is_writable($path)) {
440
            chmod($path, $mode);
441
        }
442
        clearstatcache();
443
        if (is_writable($path)) {
444
            $info = stat($path);
445
            if ($info['mode'] & 0002) {
446
                return 'w';
0 ignored issues
show
Bug Best Practice introduced by
The expression return 'w' returns the type string which is incompatible with the documented return type false.
Loading history...
447
            } elseif ($info['mode'] & 0020) {
448
                return 'g';
0 ignored issues
show
Bug Best Practice introduced by
The expression return 'g' returns the type string which is incompatible with the documented return type false.
Loading history...
449
            }
450
451
            return 'u';
0 ignored issues
show
Bug Best Practice introduced by
The expression return 'u' returns the type string which is incompatible with the documented return type false.
Loading history...
452
        }
453
454
        return false;
455
    }
456
}
457