1/*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "path_tools.h"
17
18#include <cstdlib>
19
20#include <base/containers/string.h>
21#include <base/containers/string_view.h>
22#include <core/log.h>
23#include <core/namespace.h>
24
25#include "util/string_util.h"
26
27#if defined(__linux__) || defined(__APPLE__)
28#include <unistd.h>
29#elif defined(_WIN32)
30#include <direct.h>
31#endif
32
33CORE_BEGIN_NAMESPACE()
34using BASE_NS::string;
35using BASE_NS::string_view;
36
37bool IsRelative(const string_view path)
38{
39    if (path.empty()) {
40        return true;
41    }
42    return path[0] != '/';
43}
44
45bool ParseUri(string_view uri, string_view& protocol, string_view& path)
46{
47    const size_t index = uri.find(':');
48    if (index != string_view::npos) {
49        protocol = uri.substr(0, index);
50        // remove scheme and separator
51        uri.remove_prefix(index + 1U);
52        // remove the authority separator if it's there
53        if (uri.starts_with("//")) {
54            uri.remove_prefix(2U);
55        }
56        path = uri;
57        return true;
58    }
59
60    return false;
61}
62
63string NormalizePath(string_view path)
64{
65    if (path.empty()) {
66        return "/";
67    }
68    string res;
69    res.reserve(path.size());
70    res.push_back('/');
71    while (!path.empty()) {
72        if (path[0] == '/') {
73            path = path.substr(1);
74            continue;
75        }
76        auto pos = path.find_first_of('/', 0);
77        if (const string_view sub = path.substr(0, pos); sub == "..") {
78            if ((!res.empty()) && (res.back() == '/')) {
79                res.resize(res.size() - 1);
80            }
81
82            if (auto p = res.find_last_of('/'); string::npos != p) {
83                res.resize(p);
84            } else {
85                if (res.empty()) {
86                    // trying to back out of root. (ie. invalid path)
87                    return "";
88                }
89                res.clear();
90            }
91            if (pos == string::npos) {
92                res.push_back('/');
93                break;
94            }
95        } else if (sub == ".") {
96            path = path.substr(pos);
97            continue;
98        } else {
99            res.append(sub);
100        }
101        if (pos == string::npos) {
102            break;
103        }
104        res.push_back('/');
105        path = path.substr(pos);
106    }
107    return res;
108}
109
110string GetCurrentDirectory()
111{
112    string basePath;
113#if defined(__linux__) || defined(__APPLE__)
114    // OSX and linux both implement the "null buf" extension which allocates the required amount of space.
115    auto tmp = getcwd(nullptr, 0);
116    if (tmp) {
117        basePath = tmp;
118        if (basePath.back() != '/') {
119            basePath += '/';
120        }
121        free(tmp);
122    } else {
123        // fallback to root (either out-of-memory or the CWD is inaccessible for current user)
124        basePath = "/";
125        CORE_LOG_F("Could not get current working directory, initializing base path as '/'");
126    }
127#elif defined(_WIN32)
128    // Windows also implements the "null buf" extension, but uses a different name and "format".
129    auto tmp = _getcwd(nullptr, 0);
130    if (tmp) {
131        basePath = tmp;
132        StringUtil::FindAndReplaceAll(basePath, "\\", "/");
133        free(tmp);
134    } else {
135        // fallback to root (yes, technically it's the root of current drive, which again can change when ever.
136        // but then again _getcwd should always work, except in out-of-memory cases where this is the least of our
137        // problems.
138        basePath = "/";
139        CORE_LOG_F("Could not get current working directory, initializing base path as '/'");
140    }
141#else
142    // Unsupported platform.fallback to root.
143    basePath = "/";
144#endif
145
146    // Make sure that we end with a slash.
147    if (basePath.back() != '/') {
148        basePath.push_back('/');
149    }
150    // And make sure we start with a slash also.
151    if (basePath.front() != '/') {
152        basePath.insert(0, "/");
153    }
154    return basePath;
155}
156
157#if _WIN32
158void SplitPath(string_view pathIn, string_view& drive, string_view& path, string_view& filename, string_view& ext)
159{
160    drive = path = filename = ext = {};
161    if (pathIn[0] == '/') {
162        // see if there is a drive after
163        if (pathIn[2] == ':') { // 2: index of ':'
164            // yes.
165            // remove the first '/' to help later parsing
166            pathIn = pathIn.substr(1);
167        }
168    }
169    // extract the drive
170    if (pathIn[1] == ':') {
171        drive = pathIn.substr(0, 1);
172        pathIn = pathIn.substr(2); // 2: remove the drive part
173    }
174    auto lastSlash = pathIn.find_last_of('/');
175    if (lastSlash != string_view::npos) {
176        filename = pathIn.substr(lastSlash + 1);
177        path = pathIn.substr(0, lastSlash + 1);
178    } else {
179        filename = pathIn;
180    }
181    auto lastDot = filename.find_last_of('.');
182    if (lastDot != string_view::npos) {
183        ext = filename.substr(lastDot + 1);
184        filename = filename.substr(0, lastDot);
185    }
186}
187#endif
188CORE_END_NAMESPACE()
189