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