1 /*
2 * Copyright (c) 2022 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 #if defined(__OHOS_PLATFORM__) || defined(__linux__) || defined(__APPLE__)
26 #include <unistd.h>
27 #elif defined(_WIN32)
28 #include <direct.h>
29
30 #include "util/string_util.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 const auto pos = path.find_first_of('/', 0);
77 const string_view sub = path.substr(0, pos);
78 if (sub == "..") {
79 if ((!res.empty()) && (res.back() == '/')) {
80 res.resize(res.size() - 1);
81 }
82 if (res.empty()) {
83 // trying to back out of root. (i.e. invalid path)
84 return "";
85 }
86
87 if (const auto p = res.find_last_of('/'); p != string::npos) {
88 res.resize(p);
89 } else {
90 res.clear();
91 }
92 if (pos == string_view::npos) {
93 res.push_back('/');
94 break;
95 }
96 } else if (sub == ".") {
97 path = path.substr(pos);
98 continue;
99 } else {
100 res.append(sub);
101 }
102
103 if (pos == string_view::npos) {
104 break;
105 }
106 res.push_back('/');
107 path = path.substr(pos);
108 }
109 return res;
110 }
111
GetCurrentDirectory()112 string GetCurrentDirectory()
113 {
114 string basePath;
115 #if defined(__OHOS_PLATFORM__) || defined(__linux__) || defined(__APPLE__)
116 // OSX and linux both implement the "null buf" extension which allocates the required amount of space.
117 auto tmp = getcwd(nullptr, 0);
118 if (tmp) {
119 basePath = tmp;
120 if (basePath.back() != '/') {
121 basePath += '/';
122 }
123 free(tmp);
124 } else {
125 // fallback to root (either out-of-memory or the CWD is inaccessible for current user)
126 basePath = "/";
127 CORE_LOG_F("Could not get current working directory, initializing base path as '/'");
128 }
129 #elif defined(_WIN32)
130 // Windows also implements the "null buf" extension, but uses a different name and "format".
131 auto tmp = _getcwd(nullptr, 0);
132 if (tmp) {
133 basePath = tmp;
134 StringUtil::FindAndReplaceAll(basePath, "\\", "/");
135 free(tmp);
136 } else {
137 // fallback to root (yes, technically it's the root of current drive, which again can change when ever.
138 // but then again _getcwd should always work, except in out-of-memory cases where this is the least of our
139 // problems.
140 basePath = "/";
141 CORE_LOG_F("Could not get current working directory, initializing base path as '/'");
142 }
143 #else
144 // Unsupported platform.fallback to root.
145 basePath = "/";
146 #endif
147
148 // Make sure that we end with a slash.
149 if (basePath.back() != '/') {
150 basePath.push_back('/');
151 }
152 // And make sure we start with a slash also.
153 if (basePath.front() != '/') {
154 basePath.insert(0, "/");
155 }
156 return basePath;
157 }
158
159 #if _WIN32
SplitPath(string_view pathIn,string_view & drive,string_view & path,string_view & filename,string_view & ext)160 void SplitPath(string_view pathIn, string_view& drive, string_view& path, string_view& filename, string_view& ext)
161 {
162 drive = path = filename = ext = {};
163 if (pathIn[0] == '/') {
164 // see if there is a drive after
165 if (pathIn[2] == ':') { // 2: index of ':'
166 // yes.
167 // remove the first '/' to help later parsing
168 pathIn = pathIn.substr(1);
169 }
170 }
171 // extract the drive
172 if (pathIn[1] == ':') {
173 drive = pathIn.substr(0, 1);
174 pathIn = pathIn.substr(2); // 2: remove the drive part
175 }
176 auto lastSlash = pathIn.find_last_of('/');
177 if (lastSlash != string_view::npos) {
178 filename = pathIn.substr(lastSlash + 1);
179 path = pathIn.substr(0, lastSlash + 1);
180 } else {
181 filename = pathIn;
182 }
183 auto lastDot = filename.find_last_of('.');
184 if (lastDot != string_view::npos) {
185 ext = filename.substr(lastDot + 1);
186 filename = filename.substr(0, lastDot);
187 }
188 }
189 #endif
190 CORE_END_NAMESPACE()
191