xref: /plugin/combo/ComboStrap/LocalPath.php (revision 912bb8fd1ce711527489122369901d9502f45720)
1c3437056SNickeau<?php
2c3437056SNickeau
3c3437056SNickeau
4c3437056SNickeaunamespace ComboStrap;
5c3437056SNickeau
604fd306cSNickeauuse ComboStrap\Web\Url;
704fd306cSNickeau
8c3437056SNickeau/**
9c3437056SNickeau * Class LocalPath
10c3437056SNickeau * @package ComboStrap
11c3437056SNickeau * A local file system path
1204fd306cSNickeau *
1304fd306cSNickeau * File protocol Uri:
1404fd306cSNickeau *
1504fd306cSNickeau * file://[HOST]/[PATH]
16c3437056SNickeau */
17c3437056SNickeauclass LocalPath extends PathAbs
18c3437056SNickeau{
19c3437056SNickeau
20c3437056SNickeau
21e4026cd1Sgerardnico    /**
22e4026cd1Sgerardnico     * The characters that cannot be in the path for windows
23e4026cd1Sgerardnico     * @var string[]
24e4026cd1Sgerardnico     */
25e4026cd1Sgerardnico    public const RESERVED_WINDOWS_CHARACTERS = ["\\", "/", ":", "*", "?", "\"", "<", ">", "|"];
26e4026cd1Sgerardnico
2704fd306cSNickeau    const RELATIVE_CURRENT = ".";
2804fd306cSNickeau    const RELATIVE_PARENT = "..";
2904fd306cSNickeau    const LINUX_SEPARATOR = "/";
3004fd306cSNickeau    const WINDOWS_SEPARATOR = '\\';
3104fd306cSNickeau    const CANONICAL = "support";
3204fd306cSNickeau
3304fd306cSNickeau    /**
3404fd306cSNickeau     * @throws ExceptionBadArgument
3504fd306cSNickeau     */
3604fd306cSNickeau    public static function createFromUri($uri): LocalPath
3704fd306cSNickeau    {
3804fd306cSNickeau        if (strpos($uri, LocalFileSystem::SCHEME) !== 0) {
3904fd306cSNickeau            throw new ExceptionBadArgument("$uri is not a local path uri");
4004fd306cSNickeau        }
4104fd306cSNickeau        return new LocalPath($uri);
4204fd306cSNickeau    }
4304fd306cSNickeau
4404fd306cSNickeau
4504fd306cSNickeau    /**
4604fd306cSNickeau     * @throws ExceptionBadArgument
4704fd306cSNickeau     * @throws ExceptionCast
4804fd306cSNickeau     */
4904fd306cSNickeau    public static function createFromPathObject(Path $path): LocalPath
5004fd306cSNickeau    {
5104fd306cSNickeau        if ($path instanceof LocalPath) {
5204fd306cSNickeau            return $path;
5304fd306cSNickeau        }
5404fd306cSNickeau        if ($path instanceof WikiPath) {
5504fd306cSNickeau            return $path->toLocalPath();
5604fd306cSNickeau        }
5704fd306cSNickeau        throw new ExceptionBadArgument("The path is not a local path nor a wiki path, we can't transform it");
5804fd306cSNickeau    }
5904fd306cSNickeau
6004fd306cSNickeau    /**
6104fd306cSNickeau     *
6204fd306cSNickeau     * @throws ExceptionNotFound - if the env directory is not found
6304fd306cSNickeau     */
6404fd306cSNickeau    public static function createDesktopDirectory(): LocalPath
6504fd306cSNickeau    {
6604fd306cSNickeau        return LocalPath::createHomeDirectory()->resolve("Desktop");
6704fd306cSNickeau    }
6804fd306cSNickeau
6904fd306cSNickeau
7004fd306cSNickeau    public function toUriString(): string
7104fd306cSNickeau    {
7204fd306cSNickeau        return $this->getUrl()->toString();
7304fd306cSNickeau    }
7404fd306cSNickeau
75c3437056SNickeau    private $path;
7604fd306cSNickeau    /**
7704fd306cSNickeau     * @var mixed
7804fd306cSNickeau     */
7904fd306cSNickeau    private $sep = DIRECTORY_SEPARATOR;
8004fd306cSNickeau
8104fd306cSNickeau    private ?string $host = null;
82c3437056SNickeau
83c3437056SNickeau    /**
84c3437056SNickeau     * LocalPath constructor.
8504fd306cSNickeau     * @param string $path - relative or absolute, or a locale file uri
8604fd306cSNickeau     * @param string|null $sep - the directory separator - it permits to test linux path on windows, and vice-versa
87c3437056SNickeau     */
8804fd306cSNickeau    public function __construct(string $path, string $sep = null)
89c3437056SNickeau    {
9004fd306cSNickeau        /**
9104fd306cSNickeau         * php mon amour,
9204fd306cSNickeau         * if we pass a {@link LocalPath}, no error,
9304fd306cSNickeau         * it just pass the {@link PathAbs::__toString()}
9404fd306cSNickeau         */
95*912bb8fdSgerardnico        if (strpos($path, LocalFileSystem::SCHEME.'://') === 0) {
9604fd306cSNickeau            try {
9704fd306cSNickeau                $path = Url::createFromString($path)->getPath();
9804fd306cSNickeau                LogUtility::errorIfDevOrTest("The path given as constructor should not be an uri or a path object");
9904fd306cSNickeau            } catch (ExceptionBadArgument|ExceptionBadSyntax|ExceptionNotFound $e) {
10004fd306cSNickeau                LogUtility::internalError("The uri path could not be created", self::CANONICAL, $e);
10104fd306cSNickeau            }
10204fd306cSNickeau        }
10304fd306cSNickeau        if ($sep != null) {
10404fd306cSNickeau            $this->sep = $sep;
10504fd306cSNickeau        }
10604fd306cSNickeau        // The network share windows/wiki styles with with two \\ and not //
10704fd306cSNickeau        $networkShare = "\\\\";
10804fd306cSNickeau        if (substr($path, 0, 2) === $networkShare) {
10904fd306cSNickeau            // window share
11004fd306cSNickeau            $pathWithoutNetworkShare = substr($path, 2);
11104fd306cSNickeau            $pathWithoutNetworkShare = str_replace("\\", "/", $pathWithoutNetworkShare);
11204fd306cSNickeau            [$this->host, $relativePath] = explode("/", $pathWithoutNetworkShare, 2);
11304fd306cSNickeau            $this->path = "/$relativePath";
11404fd306cSNickeau            return;
11504fd306cSNickeau        }
11604fd306cSNickeau        $this->path = self::normalizeToOsSeparator($path);
117c3437056SNickeau    }
118c3437056SNickeau
119c3437056SNickeau
1204cadd4f8SNickeau    /**
1214cadd4f8SNickeau     * @param string $filePath
1224cadd4f8SNickeau     * @return LocalPath
12304fd306cSNickeau     * @deprecated for {@link LocalPath::createFromPathString()}
1244cadd4f8SNickeau     */
125c3437056SNickeau    public static function create(string $filePath): LocalPath
126c3437056SNickeau    {
127c3437056SNickeau        return new LocalPath($filePath);
128c3437056SNickeau    }
129c3437056SNickeau
13004fd306cSNickeau    /**
13104fd306cSNickeau     * @param $path
13204fd306cSNickeau     * @return array|string|string[]
13304fd306cSNickeau     *
13404fd306cSNickeau     * For whatever reason, it seems that php/dokuwiki uses always the / separator on windows also
13504fd306cSNickeau     * but not always (ie  https://www.php.net/manual/en/function.realpath.php output \ on windows)
13604fd306cSNickeau     *
13704fd306cSNickeau     * Because we want to be able to copy the path value and to be able to use
13804fd306cSNickeau     * it directly, we normalize it to the OS separator at build time
13904fd306cSNickeau     */
14004fd306cSNickeau    private function normalizeToOsSeparator($path)
141c3437056SNickeau    {
14204fd306cSNickeau        if ($path === self::RELATIVE_CURRENT || $path === self::RELATIVE_PARENT) {
14304fd306cSNickeau            return realpath($path);
14404fd306cSNickeau        }
14504fd306cSNickeau        $directorySeparator = $this->getDirectorySeparator();
14604fd306cSNickeau        if ($directorySeparator === self::WINDOWS_SEPARATOR) {
14704fd306cSNickeau            return str_replace(self::LINUX_SEPARATOR, self::WINDOWS_SEPARATOR, $path);
14804fd306cSNickeau        } else {
14904fd306cSNickeau            return str_replace(self::WINDOWS_SEPARATOR, self::LINUX_SEPARATOR, $path);
15004fd306cSNickeau        }
15104fd306cSNickeau    }
15204fd306cSNickeau
15304fd306cSNickeau    /**
15404fd306cSNickeau     * @throws ExceptionNotFound
15504fd306cSNickeau     */
15604fd306cSNickeau    public static function createHomeDirectory(): LocalPath
15704fd306cSNickeau    {
15804fd306cSNickeau        $home = getenv("HOME");
15904fd306cSNickeau        if ($home === false) {
16004fd306cSNickeau            $home = getenv("USERPROFILE");
16104fd306cSNickeau        }
16204fd306cSNickeau        if ($home === false) {
16304fd306cSNickeau            throw new ExceptionNotFound(" The home directory variable could not be found");
16404fd306cSNickeau        }
16504fd306cSNickeau        return LocalPath::createFromPathString($home);
16604fd306cSNickeau    }
16704fd306cSNickeau
16804fd306cSNickeau
16904fd306cSNickeau    public static function createFromPathString(string $string, string $sep = null): LocalPath
17004fd306cSNickeau    {
17104fd306cSNickeau        return new LocalPath($string, $sep);
172c3437056SNickeau    }
173c3437056SNickeau
174c3437056SNickeau    function getScheme(): string
175c3437056SNickeau    {
17604fd306cSNickeau        return LocalFileSystem::SCHEME;
177c3437056SNickeau    }
178c3437056SNickeau
17904fd306cSNickeau    function getLastName(): string
180c3437056SNickeau    {
181c3437056SNickeau        $names = $this->getNames();
182c3437056SNickeau        $sizeof = sizeof($names);
183c3437056SNickeau        if ($sizeof === 0) {
18404fd306cSNickeau            throw new ExceptionNotFound("No last name for the path ($this)");
185c3437056SNickeau        }
186c3437056SNickeau        return $names[$sizeof - 1];
187c3437056SNickeau
188c3437056SNickeau    }
189c3437056SNickeau
19004fd306cSNickeau
19104fd306cSNickeau    public function getExtension(): string
192c3437056SNickeau    {
19304fd306cSNickeau        $extension = pathinfo($this->path, PATHINFO_EXTENSION);
19404fd306cSNickeau        if ($extension === "") {
19504fd306cSNickeau            throw new ExceptionNotFound("No extension found for the path ($this)");
19604fd306cSNickeau        }
19704fd306cSNickeau        return $extension;
198c3437056SNickeau    }
199c3437056SNickeau
200c3437056SNickeau    function getNames()
201c3437056SNickeau    {
202c3437056SNickeau        $directorySeparator = $this->getDirectorySeparator();
203c3437056SNickeau        return explode($directorySeparator, $this->path);
204c3437056SNickeau    }
205c3437056SNickeau
206c3437056SNickeau
20704fd306cSNickeau    function toAbsoluteId(): string
208c3437056SNickeau    {
209c3437056SNickeau        return $this->path;
210c3437056SNickeau    }
211c3437056SNickeau
21204fd306cSNickeau    public function getParent(): Path
213c3437056SNickeau    {
214c3437056SNickeau        $absolutePath = pathinfo($this->path, PATHINFO_DIRNAME);
21504fd306cSNickeau        if ($absolutePath === $this->path || empty($absolutePath)) {
21604fd306cSNickeau            // the directory on windows of the root (ie C:\) is (C:\), yolo !
21704fd306cSNickeau            throw new ExceptionNotFound("No parent");
218c3437056SNickeau        }
219c3437056SNickeau        return new LocalPath($absolutePath);
220c3437056SNickeau    }
221c3437056SNickeau
222c3437056SNickeau    function toAbsolutePath(): Path
223c3437056SNickeau    {
2244cadd4f8SNickeau
2254cadd4f8SNickeau        if ($this->isAbsolute()) {
226c3437056SNickeau            return $this;
2274cadd4f8SNickeau        }
2284cadd4f8SNickeau
22904fd306cSNickeau        return $this->toCanonicalAbsolutePath();
230c3437056SNickeau
231c3437056SNickeau    }
232c3437056SNickeau
233c3437056SNickeau
2344cadd4f8SNickeau    /**
23504fd306cSNickeau     * @throws ExceptionBadArgument - if the path is not inside a drive
2364cadd4f8SNickeau     */
23704fd306cSNickeau    public function toWikiPath(): WikiPath
2384cadd4f8SNickeau    {
23904fd306cSNickeau        return WikiPath::createFromPathObject($this);
2404cadd4f8SNickeau    }
2414cadd4f8SNickeau
2424cadd4f8SNickeau    public function resolve(string $name): LocalPath
2434cadd4f8SNickeau    {
2444cadd4f8SNickeau
24504fd306cSNickeau        $newPath = $this->toCanonicalAbsolutePath()->toAbsoluteId() . $this->getDirectorySeparator() . utf8_encodeFN($name);
24604fd306cSNickeau        return self::createFromPathString($newPath);
2474cadd4f8SNickeau
2484cadd4f8SNickeau    }
2494cadd4f8SNickeau
2504cadd4f8SNickeau    /**
25104fd306cSNickeau     * @throws ExceptionBadArgument - if the path cannot be relativized
2524cadd4f8SNickeau     */
2534cadd4f8SNickeau    public function relativize(LocalPath $localPath): LocalPath
2544cadd4f8SNickeau    {
2554cadd4f8SNickeau
25604fd306cSNickeau        /**
25704fd306cSNickeau         * One of the problem of relativization is
25804fd306cSNickeau         * that it may be:
25904fd306cSNickeau         * * logical (when using a symling)
26004fd306cSNickeau         * * physical
26104fd306cSNickeau         */
26204fd306cSNickeau        if (!$this->isAbsolute() || $this->isShortName()) {
26304fd306cSNickeau            /**
26404fd306cSNickeau             * This is not a logical resolution
26504fd306cSNickeau             * (if the path is logically not absolute and is a symlink,
26604fd306cSNickeau             * we have a problem)
26704fd306cSNickeau             */
26804fd306cSNickeau            $actualPath = $this->toCanonicalAbsolutePath();
26904fd306cSNickeau        } else {
27004fd306cSNickeau            $actualPath = $this;
27104fd306cSNickeau        }
27204fd306cSNickeau        if (!$localPath->isAbsolute() || $localPath->isShortName()) {
27304fd306cSNickeau            $localPath = $localPath->toCanonicalAbsolutePath();
27404fd306cSNickeau        }
27504fd306cSNickeau
27604fd306cSNickeau        if (strpos($actualPath->toAbsoluteId(), $localPath->toAbsoluteId()) === 0) {
27704fd306cSNickeau            if ($actualPath->toAbsoluteId() === $localPath->toAbsoluteId()) {
27804fd306cSNickeau                return LocalPath::createFromPathString("");
2794cadd4f8SNickeau            }
2804cadd4f8SNickeau            $sepCharacter = 1; // delete the sep characters
28104fd306cSNickeau            $relativePath = substr($actualPath->toAbsoluteId(), strlen($localPath->toAbsoluteId()) + $sepCharacter);
28204fd306cSNickeau            $relativePath = str_replace($this->getDirectorySeparator(), WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT, $relativePath);
28304fd306cSNickeau            return LocalPath::createFromPathString($relativePath);
28404fd306cSNickeau        }
28504fd306cSNickeau        /**
28604fd306cSNickeau         * May be a symlink link
28704fd306cSNickeau         */
28804fd306cSNickeau        if ($this->isSymlink()) {
28904fd306cSNickeau            $realPath = $this->toCanonicalAbsolutePath();
29004fd306cSNickeau            return $realPath->relativize($localPath);
29104fd306cSNickeau        }
29204fd306cSNickeau        if ($localPath->isSymlink()) {
29304fd306cSNickeau            $localPath = $localPath->toCanonicalAbsolutePath();
29404fd306cSNickeau            $this->relativize($localPath);
29504fd306cSNickeau        }
29604fd306cSNickeau        throw new ExceptionBadArgument("The path ($localPath) is not a parent path of the actual path ($actualPath)");
2974cadd4f8SNickeau
2984cadd4f8SNickeau    }
2994cadd4f8SNickeau
3004cadd4f8SNickeau    public function isAbsolute(): bool
3014cadd4f8SNickeau    {
3024cadd4f8SNickeau        /**
3034cadd4f8SNickeau         * /
30404fd306cSNickeau         * or a-z:\
3054cadd4f8SNickeau         */
30604fd306cSNickeau        if (preg_match("/^(\/|[a-z]:\\\\?).*/i", $this->path)) {
3074cadd4f8SNickeau            return true;
3084cadd4f8SNickeau        }
3094cadd4f8SNickeau        return false;
3104cadd4f8SNickeau
3114cadd4f8SNickeau    }
3124cadd4f8SNickeau
3134cadd4f8SNickeau    /**
3144cadd4f8SNickeau     * An absolute path may not be canonical
3154cadd4f8SNickeau     * (ie windows short name or the path separator is not consistent (ie / in place of \ on windows)
3164cadd4f8SNickeau     *
3174cadd4f8SNickeau     * This function makes the path canonical meaning that two canonical path can be compared.
31804fd306cSNickeau     * This is also needed when you path a path string to a php function such as `clearstatcache`
31904fd306cSNickeau     *
32004fd306cSNickeau     * If this is a symlink, it will resolve it to the real path
3214cadd4f8SNickeau     */
32204fd306cSNickeau    public function toCanonicalAbsolutePath(): LocalPath
3234cadd4f8SNickeau    {
3244cadd4f8SNickeau
3254cadd4f8SNickeau        /**
3264cadd4f8SNickeau         * realpath() is just a system/library call to actual realpath() function supported by OS.
3274cadd4f8SNickeau         * real path handle also the windows name ie USERNAME~
3284cadd4f8SNickeau         */
32904fd306cSNickeau        $isSymlink = $this->isSymlink();
3304cadd4f8SNickeau        $realPath = realpath($this->path);
33104fd306cSNickeau        if($isSymlink){
33204fd306cSNickeau            /**
33304fd306cSNickeau             *
33404fd306cSNickeau             * What fucked is fucked up
33504fd306cSNickeau             *
33604fd306cSNickeau             * With the symlink
33704fd306cSNickeau             * D:/dokuwiki-animals/combo.nico.lan/data/pages
33804fd306cSNickeau             * if you pass it to realpath:
33904fd306cSNickeau             * ```
34004fd306cSNickeau             * realpath("D:/dokuwiki-animals/combo.nico.lan/data/pages")
34104fd306cSNickeau             * ```
34204fd306cSNickeau             * you get: `d:\dokuwiki\website\pages`
34304fd306cSNickeau             * if you pass the result again in realpath
34404fd306cSNickeau             * ```
34504fd306cSNickeau             * realpath(d:\dokuwiki\website\pages)
34604fd306cSNickeau             * ```
34704fd306cSNickeau             * we get another result `D:\dokuwiki\website\pages`
34804fd306cSNickeau             *
34904fd306cSNickeau             */
35004fd306cSNickeau            $realPath = realpath($realPath);
35104fd306cSNickeau        }
3524cadd4f8SNickeau        if ($realPath !== false) {
35304fd306cSNickeau            return LocalPath::createFromPathString($realPath);
3544cadd4f8SNickeau        }
3554cadd4f8SNickeau
3564cadd4f8SNickeau        /**
3574cadd4f8SNickeau         * It returns false on on file that does not exists.
3584cadd4f8SNickeau         * The suggestion on the realpath man page
3594cadd4f8SNickeau         * is to look for an existing parent directory.
3604cadd4f8SNickeau         * https://man7.org/linux/man-pages/man3/realpath.3.html
3614cadd4f8SNickeau         */
3624cadd4f8SNickeau        $parts = null;
3634cadd4f8SNickeau        $isRoot = false;
3644cadd4f8SNickeau        $counter = 0; // breaker
3654cadd4f8SNickeau        $workingPath = $this->path;
3664cadd4f8SNickeau        while ($realPath === false) {
3674cadd4f8SNickeau            $counter++;
3684cadd4f8SNickeau            $parent = dirname($workingPath);
3694cadd4f8SNickeau            /**
3704cadd4f8SNickeau             * From the doc: https://www.php.net/manual/en/function.dirname.php
3714cadd4f8SNickeau             * dirname('.');    // Will return '.'.
3724cadd4f8SNickeau             * dirname('/');    // Will return `\` on Windows and '/' on *nix systems.
3734cadd4f8SNickeau             * dirname('\\');   // Will return `\` on Windows and '.' on *nix systems.
3744cadd4f8SNickeau             * dirname('C:\\'); // Will return 'C:\' on Windows and '.' on *nix systems.
3754cadd4f8SNickeau             * dirname('\');    // Will return `C:\` on Windows and ??? on *nix systems.
3764cadd4f8SNickeau             */
3774cadd4f8SNickeau            if (preg_match("/^(\.|\/|\\\\|[a-z]:\\\\)$/i", $parent)
3784cadd4f8SNickeau                || $parent === $workingPath
3794cadd4f8SNickeau                || $parent === "\\" // bug on regexp
3804cadd4f8SNickeau            ) {
3814cadd4f8SNickeau                $isRoot = true;
3824cadd4f8SNickeau            }
3834cadd4f8SNickeau            // root, no need to delete the last sep
3844cadd4f8SNickeau            $lastSep = 1;
3854cadd4f8SNickeau            if ($isRoot) {
3864cadd4f8SNickeau                $lastSep = 0;
3874cadd4f8SNickeau            }
3884cadd4f8SNickeau            $parts[] = substr($workingPath, strlen($parent) + $lastSep);
3894cadd4f8SNickeau
3904cadd4f8SNickeau            $realPath = realpath($parent);
3914cadd4f8SNickeau            if ($isRoot) {
3924cadd4f8SNickeau                break;
3934cadd4f8SNickeau            }
3944cadd4f8SNickeau            if ($counter > 200) {
3954cadd4f8SNickeau                $message = "Bad absolute local path file ($this->path)";
3964cadd4f8SNickeau                if (PluginUtility::isDevOrTest()) {
39704fd306cSNickeau                    throw new ExceptionRuntime($message);
3984cadd4f8SNickeau                } else {
3994cadd4f8SNickeau                    LogUtility::msg($message);
4004cadd4f8SNickeau                }
4014cadd4f8SNickeau                return $this;
4024cadd4f8SNickeau            }
4034cadd4f8SNickeau            if ($realPath === false) {
4044cadd4f8SNickeau                // loop
4054cadd4f8SNickeau                $workingPath = $parent;
4064cadd4f8SNickeau            }
4074cadd4f8SNickeau        }
4084cadd4f8SNickeau        if ($parts !== null) {
4094cadd4f8SNickeau            if (!$isRoot) {
41004fd306cSNickeau                $realPath .= $this->getDirectorySeparator();
4114cadd4f8SNickeau            }
4124cadd4f8SNickeau            $parts = array_reverse($parts);
41304fd306cSNickeau            $realPath .= implode($this->getDirectorySeparator(), $parts);
4144cadd4f8SNickeau        }
41504fd306cSNickeau        return LocalPath::createFromPathString($realPath);
41604fd306cSNickeau    }
41704fd306cSNickeau
41804fd306cSNickeau    public function getDirectorySeparator()
41904fd306cSNickeau    {
42004fd306cSNickeau        return $this->sep;
4214cadd4f8SNickeau    }
4224cadd4f8SNickeau
4234cadd4f8SNickeau
42404fd306cSNickeau    function getUrl(): Url
42504fd306cSNickeau    {
42604fd306cSNickeau
42704fd306cSNickeau        /**
42804fd306cSNickeau         * file://host/path
42904fd306cSNickeau         */
43004fd306cSNickeau        $uri = LocalFileSystem::SCHEME . '://';
43104fd306cSNickeau        try {
43204fd306cSNickeau            // Windows share host
43304fd306cSNickeau            $uri = "$uri{$this->getHost()}";
43404fd306cSNickeau        } catch (ExceptionNotFound $e) {
43504fd306cSNickeau            // ok
43604fd306cSNickeau        }
43704fd306cSNickeau        $pathNormalized = str_replace(self::WINDOWS_SEPARATOR, self::LINUX_SEPARATOR, $this->path);
43804fd306cSNickeau        if ($pathNormalized[0] !== "/") {
43904fd306cSNickeau            $uri = $uri . "/" . $pathNormalized;
44004fd306cSNickeau        } else {
44104fd306cSNickeau            $uri = $uri . $pathNormalized;
44204fd306cSNickeau        }
44304fd306cSNickeau        try {
44404fd306cSNickeau            return Url::createFromString($uri);
44504fd306cSNickeau        } catch (ExceptionBadSyntax|ExceptionBadArgument $e) {
44604fd306cSNickeau            $message = "Local Uri Path has a bad syntax ($uri)";
44704fd306cSNickeau            // should not happen
44804fd306cSNickeau            LogUtility::internalError($message);
44904fd306cSNickeau            throw new ExceptionRuntime($message);
45004fd306cSNickeau        }
45104fd306cSNickeau
45204fd306cSNickeau    }
45304fd306cSNickeau
45404fd306cSNickeau    /**
45504fd306cSNickeau     * @throws ExceptionNotFound
45604fd306cSNickeau     */
45704fd306cSNickeau    function getHost(): string
45804fd306cSNickeau    {
45904fd306cSNickeau        if ($this->host === null) {
46004fd306cSNickeau            throw new ExceptionNotFound("No host. Localhost should be the default");
46104fd306cSNickeau        }
46204fd306cSNickeau        return $this->host;
46304fd306cSNickeau    }
46404fd306cSNickeau
46504fd306cSNickeau    public function isSymlink(): bool
46604fd306cSNickeau    {
46704fd306cSNickeau        return is_link($this->path);
46804fd306cSNickeau    }
46904fd306cSNickeau
47004fd306cSNickeau    private function isShortName(): bool
47104fd306cSNickeau    {
47204fd306cSNickeau        /**
47304fd306cSNickeau         * See short name in windows
47404fd306cSNickeau         * https://datacadamia.com/os/windows/path#pathname
47504fd306cSNickeau         */
47604fd306cSNickeau        return strpos($this->path, "~1") !== false;
47704fd306cSNickeau    }
478c3437056SNickeau}
479