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 "util/path_util.h"
17
18 #include <base/math/mathf.h>
19
20 using namespace BASE_NS;
21
22 UTIL_BEGIN_NAMESPACE()
23
24 namespace PathUtil {
25
NormalizePath(string_view path)26 string NormalizePath(string_view path)
27 {
28 if (path.empty()) {
29 return "";
30 }
31
32 string res;
33 if (path[0] != '/') {
34 res.reserve(path.size() + 1);
35 res.push_back('/');
36 } else {
37 res.reserve(path.size());
38 }
39 while (!path.empty()) {
40 if (path[0] == '/') {
41 if (res.empty()) {
42 res.push_back('/');
43 }
44 path = path.substr(1);
45 continue;
46 }
47 auto pos = path.find_first_of("/", 0);
48 string_view sub = path.substr(0, pos);
49 if (sub == ".") {
50 path = path.substr(pos);
51 continue;
52 } else if (sub == "..") {
53 if ((!res.empty()) && (res.back() == '/')) {
54 res.resize(res.size() - 1);
55 }
56 if (auto p = res.find_last_of('/'); string::npos != p) {
57 res.resize(p);
58 } else {
59 if (res.empty()) {
60 // trying to back out of root. (ie. invalid path)
61 return "";
62 }
63 res.clear();
64 }
65 if (pos == string::npos) {
66 res.push_back('/');
67 break;
68 }
69 } else {
70 res.append(sub);
71 }
72 if (pos == string::npos) {
73 break;
74 } else {
75 res.push_back('/');
76 }
77 path = path.substr(pos);
78 }
79 if (res[0] != '/') {
80 res.insert(0, "/");
81 }
82 return res;
83 }
84
GetParentPath(string_view path)85 string GetParentPath(string_view path)
86 {
87 if (path.size() > 1 && path[path.size() - 1] == '/' && path[path.size() - 2] != '/') {
88 // Allow (ignore) trailing '/' for folders.
89 path = path.substr(0, path.size() - 1);
90 }
91
92 const size_t separatorPos = path.rfind('/');
93 if (separatorPos == string::npos) {
94 return "";
95 } else {
96 return string(path.substr(0, separatorPos + 1));
97 }
98 }
99
ResolvePath(string_view parent,string_view uri,bool allowQueryString)100 string ResolvePath(string_view parent, string_view uri, bool allowQueryString)
101 {
102 size_t queryPos = allowQueryString ? string::npos : uri.find('?');
103 string_view path = (string::npos != queryPos) ? uri.substr(0, queryPos) : uri;
104
105 if (parent.empty()) {
106 return string(path);
107 }
108
109 if (path.empty()) {
110 return string(parent);
111 }
112
113 if (path[0] == '/') {
114 path = path.substr(1);
115 } else if (path.find("://") != path.npos) {
116 return string(path);
117 }
118
119 // NOTE: Resolve always assumes the parent path is a directory even if there is no '/' in the end
120 if (parent.back() == '/') {
121 return parent + path;
122 } else {
123 return parent + "/" + path;
124 }
125 }
126
GetRelativePath(string_view path,string_view relativeTo)127 string GetRelativePath(string_view path, string_view relativeTo)
128 {
129 // remove the common prefix
130 {
131 int lastSeparator = -1;
132
133 for (size_t i = 0, iMax = Math::min(path.size(), relativeTo.size()); i < iMax; ++i) {
134 if (path[i] != relativeTo[i]) {
135 break;
136 }
137 if (path[i] == '/') {
138 lastSeparator = int(i);
139 }
140 }
141
142 const auto lastPos = static_cast<size_t>(static_cast<size_t>(lastSeparator) + 1);
143 path.remove_prefix(lastPos);
144 relativeTo.remove_prefix(lastPos);
145 }
146
147 if (path[1] == ':' && relativeTo[1] == ':' && path[0] != relativeTo[0]) {
148 // files are on different drives
149 return string(path);
150 }
151
152 // count and remove the directories left in relative_to
153 auto directoriesCount = 0;
154 {
155 auto nextSeparator = relativeTo.find('/');
156 while (nextSeparator != string::npos) {
157 relativeTo.remove_prefix(nextSeparator + 1);
158 nextSeparator = relativeTo.find('/');
159 ++directoriesCount;
160 }
161 }
162
163 string relativePath = "";
164 for (auto i = 0, iMax = directoriesCount; i < iMax; ++i) {
165 relativePath.append("../");
166 }
167
168 relativePath.append(path);
169
170 return relativePath;
171 }
172
GetFilename(string_view path)173 string GetFilename(string_view path)
174 {
175 if (!path.empty() && path[path.size() - 1] == '/') {
176 // Return a name also for folders.
177 path = path.substr(0, path.size() - 1);
178 }
179
180 size_t cutPos = path.find_last_of("\\/");
181 if (string::npos != cutPos) {
182 return string(path.substr(cutPos + 1));
183 } else {
184 return string(path);
185 }
186 }
187
GetExtension(string_view path)188 string GetExtension(string_view path)
189 {
190 size_t fileExtCut = path.rfind('.');
191 if (fileExtCut != string::npos) {
192 size_t queryCut = path.find('?', fileExtCut);
193 if (queryCut != string::npos) {
194 return string(path.substr(fileExtCut + 1, queryCut));
195 } else {
196 return string(path.substr(fileExtCut + 1, queryCut));
197 }
198 }
199 return "";
200 }
201
GetBaseName(string_view path)202 string GetBaseName(string_view path)
203 {
204 auto filename = GetFilename(path);
205 size_t fileExtCut = filename.rfind(".");
206 if (string::npos != fileExtCut) {
207 filename.erase(fileExtCut);
208 }
209 return filename;
210 }
211
GetUriParameters(string_view uri)212 unordered_map<string, string> GetUriParameters(string_view uri)
213 {
214 const size_t queryPos = uri.find('?');
215 if (queryPos != string::npos) {
216 unordered_map<string, string> params;
217 size_t paramStartPos = queryPos;
218 while (paramStartPos < uri.size()) {
219 size_t paramValuePos = uri.find('=', paramStartPos + 1);
220 size_t paramEndPos = uri.find('&', paramStartPos + 1);
221 if (paramEndPos == string::npos) {
222 paramEndPos = uri.size();
223 }
224 if (paramValuePos != string::npos && paramValuePos < paramEndPos) {
225 auto key = uri.substr(paramStartPos + 1, paramValuePos - paramStartPos - 1);
226 auto value = uri.substr(paramValuePos + 1, paramEndPos - paramValuePos - 1);
227 params[key] = value;
228 } else {
229 auto key = uri.substr(paramStartPos + 1, paramEndPos - paramStartPos - 1);
230 params[key] = key;
231 }
232 paramStartPos = paramEndPos;
233 }
234 return params;
235 }
236 return {};
237 }
238
ResolveUri(string_view contextUri,string_view uri,bool allowQueryString)239 string ResolveUri(string_view contextUri, string_view uri, bool allowQueryString)
240 {
241 return ResolvePath(GetParentPath(contextUri), uri, allowQueryString);
242 }
243
244 } // namespace PathUtil
245
246 UTIL_END_NAMESPACE()
247