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