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