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 
33 CORE_BEGIN_NAMESPACE()
34 using BASE_NS::string;
35 using BASE_NS::string_view;
36 
IsRelative(const string_view path)37 bool IsRelative(const string_view path)
38 {
39     if (path.empty()) {
40         return true;
41     }
42     return path[0] != '/';
43 }
44 
ParseUri(string_view uri,string_view & protocol,string_view & path)45 bool 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 
NormalizePath(string_view path)63 string 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 
GetCurrentDirectory()110 string 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
SplitPath(string_view pathIn,string_view & drive,string_view & path,string_view & filename,string_view & ext)158 void 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
188 CORE_END_NAMESPACE()
189