1 /*
2 * Copyright (C) 2023 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 #include "path_tools.h"
16
17 #include "util/string_util.h"
18
19 #if defined(__linux__) || defined(__APPLE__)
20 #include <unistd.h>
21 #elif defined(_WIN32)
22 #include <direct.h>
23 #endif
24
25 CORE_BEGIN_NAMESPACE()
26 using BASE_NS::string;
27 using BASE_NS::string_view;
28
IsRelative(const string_view path)29 bool IsRelative(const string_view path)
30 {
31 if (path.empty()) {
32 return true;
33 }
34 return path[0] != '/';
35 }
36
ParseUri(const string_view uri,string_view & protocol,string_view & path)37 bool ParseUri(const string_view uri, string_view& protocol, string_view& path)
38 {
39 const size_t index = uri.find("://");
40 if (index != string_view::npos) {
41 protocol = uri.substr(0, index);
42 path = uri.substr(index + 3); // 3: path size without protocol
43 return true;
44 }
45
46 return false;
47 }
48
NormalizePath(string_view path)49 string NormalizePath(string_view path)
50 {
51 if (path.empty()) {
52 return "/";
53 }
54 string res;
55 res.reserve(path.size());
56 res.push_back('/');
57 while (!path.empty()) {
58 if (path[0] == '/') {
59 path = path.substr(1);
60 continue;
61 }
62 auto pos = path.find_first_of('/', 0);
63 if (const string_view sub = path.substr(0, pos); sub == ".") {
64 path = path.substr(pos);
65 continue;
66 } else if (sub == "..") {
67 if ((!res.empty()) && (res.back() == '/')) {
68 res.resize(res.size() - 1);
69 }
70
71 if (auto p = res.find_last_of('/'); string::npos != p) {
72 res.resize(p);
73 } else {
74 if (res.empty()) {
75 // trying to back out of root. (ie. invalid path)
76 return "";
77 }
78 res.clear();
79 }
80 if (pos == string::npos) {
81 res.push_back('/');
82 break;
83 }
84 } else {
85 res.append(sub);
86 }
87 if (pos == string::npos) {
88 break;
89 } else {
90 res.push_back('/');
91 }
92 path = path.substr(pos);
93 }
94 return res;
95 }
96
GetCurrentDirectory()97 string GetCurrentDirectory()
98 {
99 string basePath;
100 #if defined(__linux__) || defined(__APPLE__)
101 // OSX and linux both implement the "null buf" extension which allocates the required amount of space.
102 auto tmp = getcwd(nullptr, 0);
103 if ((tmp) && (string_view(tmp).back() == '/')) {
104 basePath = tmp;
105 free(tmp);
106 } else {
107 // fallback to root (either out-of-memory or the CWD is inaccessible for current user)
108 basePath = "/";
109 CORE_LOG_F("Could not get current working directory, initializing base path as '/'");
110 }
111 #elif defined(_WIN32)
112 // Windows also implements the "null buf" extension, but uses a different name and "format".
113 auto tmp = _getcwd(nullptr, 0);
114 if (tmp) {
115 basePath = tmp;
116 StringUtil::FindAndReplaceAll(basePath, "\\", "/");
117 free(tmp);
118 } else {
119 // fallback to root (yes, technically it's the root of current drive, which again can change when ever.
120 // but then again _getcwd should always work, except in out-of-memory cases where this is the least of our
121 // problems.
122 basePath = "/";
123 CORE_LOG_F("Could not get current working directory, initializing base path as '/'");
124 }
125 #else
126 // Unsupported platform.fallback to root.
127 basePath = "/";
128 #endif
129
130 // Make sure that we end with a slash.
131 if (basePath.back() != '/') {
132 basePath.push_back('/');
133 }
134 // And make sure we start with a slash also.
135 if (basePath.front() != '/') {
136 basePath.insert(0, "/");
137 }
138 return basePath;
139 }
140
141 #if _WIN32
SplitPath(string_view pathIn,string_view & drive,string_view & path,string_view & filename,string_view & ext)142 void SplitPath(string_view pathIn, string_view& drive, string_view& path, string_view& filename, string_view& ext)
143 {
144 drive = path = filename = ext = {};
145 if (pathIn[0] == '/') {
146 // see if there is a drive after
147 if (pathIn[2] == ':') { // 2: index of ':'
148 // yes.
149 // remove the first '/' to help later parsing
150 pathIn = pathIn.substr(1);
151 }
152 }
153 // extract the drive
154 if (pathIn[1] == ':') {
155 drive = pathIn.substr(0, 1);
156 pathIn = pathIn.substr(2); // 2: remove the drive part
157 }
158 auto lastSlash = pathIn.find_last_of('/');
159 if (lastSlash != string_view::npos) {
160 filename = pathIn.substr(lastSlash + 1);
161 path = pathIn.substr(0, lastSlash + 1);
162 } else {
163 filename = pathIn;
164 }
165 auto lastDot = filename.find_last_of('.');
166 if (lastDot != string_view::npos) {
167 ext = filename.substr(lastDot + 1);
168 filename = filename.substr(0, lastDot);
169 }
170 }
171 #endif
172 CORE_END_NAMESPACE()
173