1 #include "filesystem.hpp"
2 #include "process.hpp"
3 #include "utility.hpp"
4 #include <algorithm>
5 #include <fstream>
6 #include <iostream>
7 #include <sstream>
8 
9 boost::optional<boost::filesystem::path> filesystem::rust_sysroot_path;
10 boost::optional<boost::filesystem::path> filesystem::rust_nightly_sysroot_path;
11 boost::optional<std::vector<boost::filesystem::path>> filesystem::executable_search_paths;
12 
13 //Only use on small files
read(const std::string & path)14 std::string filesystem::read(const std::string &path) {
15   std::string str;
16   std::ifstream input(path, std::ios::binary);
17   if(input) {
18     input.seekg(0, std::ios::end);
19     auto size = input.tellg();
20     input.seekg(0, std::ios::beg);
21     str.reserve(size);
22     str.assign(std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>());
23     input.close();
24   }
25   return str;
26 }
27 
28 //Only use on small files
write(const std::string & path,const std::string & new_content)29 bool filesystem::write(const std::string &path, const std::string &new_content) {
30   std::ofstream output(path, std::ios::binary);
31   if(output)
32     output << new_content;
33   else
34     return false;
35   output.close();
36   return true;
37 }
38 
escape_argument(const std::string & argument)39 std::string filesystem::escape_argument(const std::string &argument) noexcept {
40   auto escaped = argument;
41   for(size_t pos = 0; pos < escaped.size(); ++pos) {
42     if(escaped[pos] == ' ' || escaped[pos] == '(' || escaped[pos] == ')' || escaped[pos] == '\'' || escaped[pos] == '"') {
43       escaped.insert(pos, "\\");
44       ++pos;
45     }
46   }
47   return escaped;
48 }
49 
unescape_argument(const std::string & argument)50 std::string filesystem::unescape_argument(const std::string &argument) noexcept {
51   auto unescaped = argument;
52 
53   if(unescaped.size() >= 2) {
54     if((unescaped[0] == '\'' && unescaped[unescaped.size() - 1] == '\'') ||
55        (unescaped[0] == '"' && unescaped[unescaped.size() - 1] == '"')) {
56       char quotation_mark = unescaped[0];
57       unescaped = unescaped.substr(1, unescaped.size() - 2);
58       size_t backslash_count = 0;
59       for(size_t pos = 0; pos < unescaped.size(); ++pos) {
60         if(backslash_count % 2 == 1 && (unescaped[pos] == '\\' || unescaped[pos] == quotation_mark)) {
61           unescaped.erase(pos - 1, 1);
62           --pos;
63           backslash_count = 0;
64         }
65         else if(unescaped[pos] == '\\')
66           ++backslash_count;
67         else
68           backslash_count = 0;
69       }
70       return unescaped;
71     }
72   }
73 
74   size_t backslash_count = 0;
75   for(size_t pos = 0; pos < unescaped.size(); ++pos) {
76     if(backslash_count % 2 == 1 && (unescaped[pos] == '\\' || unescaped[pos] == ' ' || unescaped[pos] == '(' || unescaped[pos] == ')' || unescaped[pos] == '\'' || unescaped[pos] == '"')) {
77       unescaped.erase(pos - 1, 1);
78       --pos;
79       backslash_count = 0;
80     }
81     else if(unescaped[pos] == '\\')
82       ++backslash_count;
83     else
84       backslash_count = 0;
85   }
86   return unescaped;
87 }
88 
get_current_path()89 const boost::filesystem::path &filesystem::get_current_path() noexcept {
90   auto get_path = [] {
91 #ifdef _WIN32
92     boost::system::error_code ec;
93     auto path = boost::filesystem::current_path(ec);
94     if(!ec)
95       return path;
96     return boost::filesystem::path();
97 #else
98     std::string path;
99     TinyProcessLib::Process process("pwd", "", [&path](const char *buffer, size_t length) {
100       path += std::string(buffer, length);
101     });
102     if(process.get_exit_status() == 0) {
103       if(!path.empty() && path.back() == '\n')
104         path.pop_back();
105       return boost::filesystem::path(path);
106     }
107     return boost::filesystem::path();
108 #endif
109   };
110 
111   static boost::filesystem::path path = get_path();
112   return path;
113 }
114 
get_home_path()115 const boost::filesystem::path &filesystem::get_home_path() noexcept {
116   auto get_path = [] {
117     std::vector<std::string> environment_variables = {"HOME", "AppData"};
118     for(auto &variable : environment_variables) {
119       if(auto ptr = std::getenv(variable.c_str())) {
120         boost::system::error_code ec;
121         boost::filesystem::path path(ptr);
122         if(boost::filesystem::exists(path, ec))
123           return path;
124       }
125     }
126     return boost::filesystem::path();
127   };
128 
129   static boost::filesystem::path path = get_path();
130   return path;
131 }
132 
get_rust_sysroot_path()133 const boost::filesystem::path &filesystem::get_rust_sysroot_path() noexcept {
134   auto get_path = [] {
135     std::string path;
136     TinyProcessLib::Process process(
137         "rustc --print sysroot", "",
138         [&path](const char *buffer, size_t length) {
139           path += std::string(buffer, length);
140         },
141         [](const char *buffer, size_t n) {});
142     if(process.get_exit_status() == 0) {
143       while(!path.empty() && (path.back() == '\n' || path.back() == '\r'))
144         path.pop_back();
145       return boost::filesystem::path(path);
146     }
147     return boost::filesystem::path();
148   };
149 
150   if(!rust_sysroot_path)
151     rust_sysroot_path = get_path();
152   return *rust_sysroot_path;
153 }
154 
get_rust_nightly_sysroot_path()155 boost::filesystem::path filesystem::get_rust_nightly_sysroot_path() noexcept {
156   auto get_path = [] {
157     std::string path;
158     TinyProcessLib::Process process(
159         // Slightly complicated since "RUSTUP_TOOLCHAIN=nightly rustc --print sysroot" actually installs nightly toolchain if missing...
160         "rustup toolchain list|grep nightly > /dev/null && RUSTUP_TOOLCHAIN=nightly rustc --print sysroot", "",
161         [&path](const char *buffer, size_t length) {
162           path += std::string(buffer, length);
163         },
164         [](const char *buffer, size_t n) {});
165     if(process.get_exit_status() == 0) {
166       while(!path.empty() && (path.back() == '\n' || path.back() == '\r'))
167         path.pop_back();
168       return boost::filesystem::path(path);
169     }
170     return boost::filesystem::path();
171   };
172 
173   if(!rust_nightly_sysroot_path)
174     rust_nightly_sysroot_path = get_path();
175   return *rust_nightly_sysroot_path;
176 }
177 
get_short_path(const boost::filesystem::path & path)178 boost::filesystem::path filesystem::get_short_path(const boost::filesystem::path &path) noexcept {
179 #ifdef _WIN32
180   return path;
181 #else
182   auto home_path = get_home_path();
183   if(!home_path.empty() && file_in_path(path, home_path))
184     return "~" / get_relative_path(path, home_path);
185   return path;
186 #endif
187 }
188 
get_long_path(const boost::filesystem::path & path)189 boost::filesystem::path filesystem::get_long_path(const boost::filesystem::path &path) noexcept {
190 #ifdef _WIN32
191   return path;
192 #else
193   if(!path.empty() && *path.begin() == "~") {
194     auto long_path = get_home_path();
195     if(!long_path.empty()) {
196       auto it = path.begin();
197       ++it;
198       for(; it != path.end(); ++it)
199         long_path /= *it;
200       return long_path;
201     }
202   }
203   return path;
204 #endif
205 }
206 
file_in_path(const boost::filesystem::path & file_path,const boost::filesystem::path & path)207 bool filesystem::file_in_path(const boost::filesystem::path &file_path, const boost::filesystem::path &path) noexcept {
208   if(std::distance(file_path.begin(), file_path.end()) < std::distance(path.begin(), path.end()))
209     return false;
210   return std::equal(path.begin(), path.end(), file_path.begin());
211 }
212 
find_file_in_path_parents(const std::string & file_name,const boost::filesystem::path & path)213 boost::filesystem::path filesystem::find_file_in_path_parents(const std::string &file_name, const boost::filesystem::path &path) noexcept {
214   auto current_path = path;
215   boost::system::error_code ec;
216   while(true) {
217     auto test_path = current_path / file_name;
218     if(boost::filesystem::exists(test_path, ec))
219       return test_path;
220     if(current_path == current_path.root_directory())
221       return boost::filesystem::path();
222     current_path = current_path.parent_path();
223   }
224 }
225 
get_normal_path(const boost::filesystem::path & path)226 boost::filesystem::path filesystem::get_normal_path(const boost::filesystem::path &path) noexcept {
227   boost::filesystem::path normal_path;
228 
229   for(auto &e : path) {
230     if(e == ".")
231       continue;
232     else if(e == "..") {
233       auto parent_path = normal_path.parent_path();
234       if(!parent_path.empty())
235         normal_path = parent_path;
236       else
237         normal_path /= e;
238     }
239     else if(e.empty())
240       continue;
241     else
242       normal_path /= e;
243   }
244 
245   return normal_path;
246 }
247 
get_relative_path(const boost::filesystem::path & path,const boost::filesystem::path & base)248 boost::filesystem::path filesystem::get_relative_path(const boost::filesystem::path &path, const boost::filesystem::path &base) noexcept {
249   boost::filesystem::path relative_path;
250   auto base_it = base.begin();
251   auto path_it = path.begin();
252   while(path_it != path.end() && base_it != base.end() && *path_it == *base_it) {
253     ++path_it;
254     ++base_it;
255   }
256   while(base_it != base.end()) {
257     relative_path /= "..";
258     ++base_it;
259   }
260   while(path_it != path.end()) {
261     relative_path /= *path_it;
262     ++path_it;
263   }
264   return relative_path;
265 }
266 
get_absolute_path(const boost::filesystem::path & path,const boost::filesystem::path & base)267 boost::filesystem::path filesystem::get_absolute_path(const boost::filesystem::path &path, const boost::filesystem::path &base) noexcept {
268   boost::filesystem::path absolute_path;
269   for(auto path_it = path.begin(); path_it != path.end(); ++path_it) {
270     if(path_it == path.begin() && (!path.has_root_path() && *path_it != "~"))
271       absolute_path /= base;
272     absolute_path /= *path_it;
273   }
274   return absolute_path;
275 }
276 
get_executable(const boost::filesystem::path & executable_name)277 boost::filesystem::path filesystem::get_executable(const boost::filesystem::path &executable_name) noexcept {
278 #if defined(__APPLE__) || defined(_WIN32)
279   return executable_name;
280 #endif
281 
282   try {
283     for(auto &path : get_executable_search_paths()) {
284       if(is_executable(path / executable_name))
285         return executable_name;
286     }
287 
288     auto executable_name_str = executable_name.string();
289     for(auto &folder : get_executable_search_paths()) {
290       boost::filesystem::path latest_executable;
291       std::string latest_version;
292       for(boost::filesystem::directory_iterator it(folder), end; it != end; ++it) {
293         const auto &file = it->path();
294         auto filename = file.filename().string();
295         if(starts_with(filename, executable_name_str)) {
296           if(filename.size() > executable_name_str.size() && filename[executable_name_str.size()] >= '0' && filename[executable_name_str.size()] <= '9' && is_executable(file)) {
297             auto version = filename.substr(executable_name_str.size());
298             if(version_compare(version, latest_version) > 0) {
299               latest_executable = file;
300               latest_version = version;
301             }
302           }
303           else if(filename.size() > executable_name_str.size() + 1 && filename[executable_name_str.size()] == '-' && filename[executable_name_str.size() + 1] >= '0' && filename[executable_name_str.size() + 1] <= '9' && is_executable(file)) {
304             auto version = filename.substr(executable_name_str.size() + 1);
305             if(version_compare(version, latest_version) > 0) {
306               latest_executable = file;
307               latest_version = version;
308             }
309           }
310         }
311       }
312       if(!latest_executable.empty())
313         return latest_executable;
314     }
315   }
316   catch(...) {
317   }
318 
319   return executable_name;
320 }
321 
322 // Based on https://stackoverflow.com/a/11295568
get_executable_search_paths()323 const std::vector<boost::filesystem::path> &filesystem::get_executable_search_paths() noexcept {
324   auto get_paths = [] {
325     std::vector<boost::filesystem::path> paths;
326 
327     auto c_env = std::getenv("PATH");
328     if(!c_env)
329       return paths;
330     const std::string env = c_env;
331 #ifdef _WIN32
332     const char delimiter = ';';
333 #else
334     const char delimiter = ':';
335 #endif
336 
337     size_t previous = 0;
338     size_t pos;
339     while((pos = env.find(delimiter, previous)) != std::string::npos) {
340       paths.emplace_back(env.substr(previous, pos - previous));
341       previous = pos + 1;
342     }
343     paths.emplace_back(env.substr(previous));
344 
345     return paths;
346   };
347 
348   if(!executable_search_paths)
349     executable_search_paths = get_paths();
350   return *executable_search_paths;
351 }
352 
find_executable(const std::string & executable_name)353 boost::filesystem::path filesystem::find_executable(const std::string &executable_name) noexcept {
354   for(auto &path : get_executable_search_paths()) {
355     auto executable_path = path / executable_name;
356     if(is_executable(executable_path))
357       return executable_path;
358   }
359   return boost::filesystem::path();
360 }
361 
get_uri_from_path(const boost::filesystem::path & path)362 std::string filesystem::get_uri_from_path(const boost::filesystem::path &path) noexcept {
363   std::string uri{"file://"};
364 
365   static auto hex_chars = "0123456789ABCDEF";
366 
367   for(auto &chr : path.string()) {
368     static std::string encode_exceptions{"-._~!$&'()*+,;=:@?/\\"};
369     if(!((chr >= '0' && chr <= '9') || (chr >= 'A' && chr <= 'Z') || (chr >= 'a' && chr <= 'z') ||
370          std::any_of(encode_exceptions.begin(), encode_exceptions.end(), [chr](char e) { return chr == e; })))
371       uri += std::string("%") + hex_chars[static_cast<unsigned char>(chr) >> 4] + hex_chars[static_cast<unsigned char>(chr) & 15];
372     else
373       uri += chr;
374   }
375 
376 #ifdef _WIN32
377   if(uri.size() > 9 && ((uri[7] >= 'a' && uri[7] <= 'z') || (uri[7] >= 'A' && uri[7] <= 'Z')) && uri[8] == ':' && uri[9] == '/')
378     uri.insert(7, "/");
379 #endif
380 
381   return uri;
382 }
383 
get_path_from_uri(const std::string & uri)384 boost::filesystem::path filesystem::get_path_from_uri(const std::string &uri) noexcept {
385   std::string encoded;
386 
387   if(starts_with(uri, "file://"))
388     encoded = uri.substr(7);
389   else
390     encoded = uri;
391 
392   std::string unencoded;
393   for(size_t i = 0; i < encoded.size(); ++i) {
394     if(encoded[i] == '%' && i + 2 < encoded.size()) {
395       auto hex = encoded.substr(i + 1, 2);
396       auto decoded_chr = static_cast<char>(std::strtol(hex.c_str(), nullptr, 16));
397       unencoded += decoded_chr;
398       i += 2;
399     }
400     else if(encoded[i] == '+')
401       unencoded += ' ';
402     else
403       unencoded += encoded[i];
404   }
405 
406 #ifdef _WIN32
407   if(unencoded.size() > 3 && unencoded[0] == '/' && ((unencoded[1] >= 'a' && unencoded[1] <= 'z') || (unencoded[1] >= 'A' && unencoded[1] <= 'Z')) && unencoded[2] == ':' && unencoded[3] == '/') {
408     unencoded.erase(0, 1);
409     unencoded[0] = std::toupper(unencoded[0]);
410   }
411 #endif
412 
413   return unencoded;
414 }
415 
get_canonical_path(const boost::filesystem::path & path)416 boost::filesystem::path filesystem::get_canonical_path(const boost::filesystem::path &path) noexcept {
417   try {
418     return boost::filesystem::canonical(path);
419   }
420   catch(...) {
421     return path;
422   }
423 }
424 
is_executable(const boost::filesystem::path & path)425 bool filesystem::is_executable(const boost::filesystem::path &path) noexcept {
426   if(path.empty())
427     return false;
428   boost::system::error_code ec;
429 #ifdef _WIN32
430   // Cannot for sure identify executable files in MSYS2
431   if(boost::filesystem::exists(path, ec))
432     return !boost::filesystem::is_directory(path, ec);
433   auto filename = path.filename().string() + ".exe";
434   return boost::filesystem::exists(path.has_parent_path() ? path.parent_path() / filename : filename, ec);
435 #else
436   return boost::filesystem::exists(path, ec) && !boost::filesystem::is_directory(path, ec) && boost::filesystem::status(path, ec).permissions() & (boost::filesystem::perms::owner_exe | boost::filesystem::perms::group_exe | boost::filesystem::perms::others_exe);
437 #endif
438 }
439