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