Passed
Push — master ( 60a772...32eaa7 )
by Michael
18:28 queued 08:27
created

PathStuffController::sanitizePath()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 2
nop 1
dl 0
loc 9
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
     * @var string Stores the error message
82
     */
83
    public $errorMessage = '';
84
85
    /**
86
     * @param $xoopsPathDefault
87
     * @param $dataPath
88
     */
89
    public function __construct($xoopsPathDefault, $dataPath)
90
    {
91
        $this->xoopsPathDefault = $xoopsPathDefault;
92
        $this->dataPath         = $dataPath;
93
94
        if (isset($_SESSION['settings']['ROOT_PATH'])) {
95
            foreach ($this->path_lookup as $req => $sess) {
96
                $this->xoopsPath[$req] = $_SESSION['settings'][$sess];
97
            }
98
        } else {
99
            $path = str_replace("\\", '/', realpath(dirname(__DIR__, 2) . '/'));
100
            if (substr($path, -1) === '/') {
101
                $path = substr($path, 0, -1);
102
            }
103
            if (file_exists("$path/mainfile.dist.php")) {
104
                $this->xoopsPath['root'] = $path;
105
            }
106
            // Firstly, locate XOOPS lib folder out of XOOPS root folder
107
            $this->xoopsPath['lib'] = dirname($path) . '/' . $this->xoopsPathDefault['lib'];
108
            // If the folder is not created, re-locate XOOPS lib folder inside XOOPS root folder
109
            if (!is_dir($this->xoopsPath['lib'] . '/')) {
110
                $this->xoopsPath['lib'] = $path . '/' . $this->xoopsPathDefault['lib'];
111
            }
112
            // Firstly, locate XOOPS data folder out of XOOPS root folder
113
            $this->xoopsPath['data'] = dirname($path) . '/' . $this->xoopsPathDefault['data'];
114
            // If the folder is not created, re-locate XOOPS data folder inside XOOPS root folder
115
            if (!is_dir($this->xoopsPath['data'] . '/')) {
116
                $this->xoopsPath['data'] = $path . '/' . $this->xoopsPathDefault['data'];
117
            }
118
        }
119
        if (isset($_SESSION['settings']['URL'])) {
120
            $this->xoopsUrl = $_SESSION['settings']['URL'];
121
        } else {
122
            $path           = $GLOBALS['wizard']->baseLocation();
123
            $this->xoopsUrl = substr($path, 0, strrpos($path, '/'));
124
        }
125
        if (isset($_SESSION['settings']['COOKIE_DOMAIN'])) {
126
            $this->xoopsCookieDomain = $_SESSION['settings']['COOKIE_DOMAIN'];
127
        } else {
128
            //            $this->xoopsCookieDomain = xoops_getBaseDomain($this->xoopsUrl);
129
            $this->xoopsCookieDomain = $this->xoops_getBaseDomain($this->xoopsUrl);
130
        }
131
    }
132
133
    //=================================================
134
135
    /**
136
     * Determine the base domain name for a URL. The primary use for this is to set the domain
137
     * used for cookies to represent any subdomains.
138
     *
139
     * The registrable domain is determined using the public suffix list. If the domain is not
140
     * registrable, an empty string is returned. This empty string can be used in setcookie()
141
     * as the domain, which restricts cookie to just the current host.
142
     *
143
     * @param string $url URL or hostname to process
144
     *
145
     * @return string the registrable domain or an empty string
146
     */
147
    private function xoops_getBaseDomain($url)
148
    {
149
        $parts = parse_url($url);
150
        $host  = '';
151
        if (!empty($parts['host'])) {
152
            $host = $parts['host'];
153
            if (strtolower($host) === 'localhost') {
154
                return 'localhost';
155
            }
156
            // bail if this is an IPv4 address (IPv6 will fail later)
157
            if (false !== filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
158
                return '';
159
            }
160
            //            $regdom = new \Xoops\RegDom\RegisteredDomain();
161
            //            $host = $regdom->getRegisteredDomain($host);
162
163
            $host = $this->getRegisteredDomain($host);
164
        }
165
        return $host ?? '';
166
    }
167
168
    // Define a simplified getRegisteredDomain function
169
    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...
170
    {
171
        $hostParts = explode('.', $host);
172
        $numParts  = count($hostParts);
173
174
        if ($numParts >= 2) {
175
            // For simplicity, assume the domain is the last two parts
176
            return $hostParts[$numParts - 2] . '.' . $hostParts[$numParts - 1];
177
        }
178
179
        return $host; // Return as is if it's a top-level domain
180
    }
181
182
    public function updateXoopsTrustPath($newTrustPath)
183
    {
184
        // 1. Update the session variable
185
        $_SESSION['settings']['TRUST_PATH'] = $newTrustPath;
186
187
        // 2. Update the defined constant (if not already defined)
188
        if (!defined('XOOPS_TRUST_PATH')) {
189
            define('XOOPS_TRUST_PATH', $newTrustPath);
190
        }
191
192
        // Firstly, locate XOOPS lib folder out of XOOPS root folder
193
        //        $this->xoopsPath['lib'] = dirname($path) . '/' . $this->xoopsPathDefault['lib'];
194
        $this->xoopsPath['lib'] = $newTrustPath;
195
        // If the folder is not created, re-locate XOOPS lib folder inside XOOPS root folder
196
        //        if (!is_dir($this->xoopsPath['lib'] . '/')) {
197
        //            $this->xoopsPath['lib'] = $path . '/' . $this->xoopsPathDefault['lib'];
198
        //        }
199
200
        // 3. Re-register the autoloader
201
        try {
202
            $this->registerAutoloader($newTrustPath);
203
        } catch (Exception $e) {
204
            // Log or handle error
205
            error_log('Failed to register autoloader: ' . $e->getMessage());
206
            throw new RuntimeException("Could not configure autoloader for the new library path.");
207
        }
208
    }
209
210
    private function registerAutoloader($trustPath)
211
    {
212
        // Composer's autoloader (if it exists)
213
        $composerAutoloader = $trustPath . '/vendor/autoload.php';
214
        if (file_exists($composerAutoloader)) {
215
            include_once $composerAutoloader;
216
            return;
217
        }
218
219
        // Notify about missing Composer autoloader
220
        throw new RuntimeException("Autoloader not found in {$trustPath}. Ensure the vendor folder is intact.");
221
    }
222
223
    // install/class/pathcontroller.php
224
225
    public function sanitizePath($path)
226
    {
227
        // Normalize the path and resolve symbolic links
228
        $realPath = realpath($path);
229
        if ($realPath && is_dir($realPath)) {
230
            // Ensure no trailing slashes for consistency
231
            return rtrim(str_replace('\\', '/', $realPath), '/');
232
        }
233
        return false; // Return false for invalid paths
234
    }
235
236
    //========================================
237
    public function execute()
238
    {
239
        $this->readRequest();
240
        $valid = $this->validate();
241
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
242
            foreach ($this->path_lookup as $req => $sess) {
243
                $_SESSION['settings'][$sess] = $this->xoopsPath[$req];
244
            }
245
            $_SESSION['settings']['URL']           = $this->xoopsUrl;
246
            $_SESSION['settings']['COOKIE_DOMAIN'] = $this->xoopsCookieDomain;
247
            if ($valid) {
248
                $GLOBALS['wizard']->redirectToPage('+1');
249
            } else {
250
                $GLOBALS['wizard']->redirectToPage('+0');
251
            }
252
        }
253
    }
254
255
    public function readRequest()
256
    {
257
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
258
            $request = $_POST;
259
            foreach ($this->path_lookup as $req => $sess) {
260
                if (isset($request[$req])) {
261
                    $request[$req] = str_replace("\\", '/', trim($request[$req]));
262
                    if (substr($request[$req], -1) === '/') {
263
                        $request[$req] = substr($request[$req], 0, -1);
264
                    }
265
                    $this->xoopsPath[$req] = $request[$req];
266
                }
267
            }
268
            if (isset($request['URL'])) {
269
                $request['URL'] = trim($request['URL']);
270
                if (substr($request['URL'], -1) === '/') {
271
                    $request['URL'] = substr($request['URL'], 0, -1);
272
                }
273
                $this->xoopsUrl = $request['URL'];
274
            }
275
            if (isset($request['COOKIE_DOMAIN'])) {
276
                $tempCookieDomain = trim($request['COOKIE_DOMAIN']);
277
                $tempParts        = parse_url($tempCookieDomain);
278
                if (!empty($tempParts['host'])) {
279
                    $tempCookieDomain = $tempParts['host'];
280
                }
281
                $request['COOKIE_DOMAIN'] = $tempCookieDomain;
282
                $this->xoopsCookieDomain  = $tempCookieDomain;
283
            }
284
        }
285
    }
286
287
    /**
288
     * @return bool
289
     */
290
    public function validate()
291
    {
292
        foreach (array_keys($this->xoopsPath) as $path) {
293
            if ($this->checkPath($path)) {
294
                $this->checkPermissions($path);
295
            }
296
        }
297
        $this->validUrl = !empty($this->xoopsUrl);
298
        $validPaths     = (array_sum(array_values($this->validPath)) == count(array_keys($this->validPath))) ? 1 : 0;
299
        $validPerms     = true;
300
        foreach ($this->permErrors as $key => $errs) {
301
            if (empty($errs)) {
302
                continue;
303
            }
304
            foreach ($errs as $path => $status) {
305
                if (empty($status)) {
306
                    $validPerms = false;
307
                    break;
308
                }
309
            }
310
        }
311
312
        return ($validPaths && $this->validUrl && $validPerms);
313
    }
314
315
    /**
316
     * @param string $PATH
317
     *
318
     * @return int
319
     */
320
    public function checkPath($PATH = '')
321
    {
322
        $ret = 1;
323
        if ($PATH === 'root' || empty($PATH)) {
324
            $path = 'root';
325
            if (is_dir($this->xoopsPath[$path]) && is_readable($this->xoopsPath[$path])) {
326
                $versionFile = "{$this->xoopsPath[$path]}/include/version.php";
327
                if (file_exists($versionFile)) {
328
                    include_once $versionFile;
329
                }
330
                if (defined('XOOPS_VERSION') && file_exists("{$this->xoopsPath[$path]}/mainfile.dist.php")) {
331
                    $this->validPath[$path] = 1;
332
                }
333
            }
334
            $ret *= $this->validPath[$path];
335
        }
336
        if ($PATH === 'lib' || empty($PATH)) {
337
            $path = 'lib';
338
            if (is_dir($this->xoopsPath[$path]) && is_readable($this->xoopsPath[$path])) {
339
                $this->validPath[$path] = 1;
340
            }
341
            $ret *= $this->validPath[$path];
342
        }
343
        if ($PATH === 'data' || empty($PATH)) {
344
            $path = 'data';
345
            if (is_dir($this->xoopsPath[$path]) && is_readable($this->xoopsPath[$path])) {
346
                $this->validPath[$path] = 1;
347
            }
348
            $ret *= $this->validPath[$path];
349
        }
350
351
        return $ret;
352
    }
353
354
    /**
355
     * @param $parent
356
     * @param $path
357
     * @param $error
358
     * @return null
359
     */
360
    public function setPermission($parent, $path, &$error)
361
    {
362
        if (is_array($path)) {
363
            foreach (array_keys($path) as $item) {
364
                if (is_string($item)) {
365
                    $error[$parent . '/' . $item] = $this->makeWritable($parent . '/' . $item);
366
                    if (empty($path[$item])) {
367
                        continue;
368
                    }
369
                    foreach ($path[$item] as $child) {
370
                        $this->setPermission($parent . '/' . $item, $child, $error);
371
                    }
372
                } else {
373
                    $error[$parent . '/' . $path[$item]] = $this->makeWritable($parent . '/' . $path[$item]);
374
                }
375
            }
376
        } else {
377
            $error[$parent . '/' . $path] = $this->makeWritable($parent . '/' . $path);
378
        }
379
380
        return null;
381
    }
382
383
    /**
384
     * @param $path
385
     *
386
     * @return bool
387
     */
388
    public function checkPermissions($path)
389
    {
390
        $paths  = [
391
            'root' => [
392
                'mainfile.php',
393
                'uploads',
394
            ],
395
            'data' => $this->dataPath,
396
        ];
397
        $errors = [
398
            'root' => null,
399
            'data' => null,
400
        ];
401
402
        if (!isset($this->xoopsPath[$path])) {
403
            return false;
404
        }
405
        if (!isset($errors[$path])) {
406
            return true;
407
        }
408
        $this->setPermission($this->xoopsPath[$path], $paths[$path], $errors[$path]);
409
        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

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