The Battle for Wesnoth  1.19.2+dev
filesystem_common.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2017 - 2024
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #include "filesystem.hpp"
16 #include "wesconfig.h"
17 
18 #include "config.hpp"
19 #include "log.hpp"
22 
23 #include <boost/algorithm/string.hpp>
24 
25 static lg::log_domain log_filesystem("filesystem");
26 #define LOG_FS LOG_STREAM(info, log_filesystem)
27 #define ERR_FS LOG_STREAM(err, log_filesystem)
28 
29 namespace filesystem
30 {
31 
32 bool is_legal_user_file_name(const std::string& name, bool allow_whitespace)
33 {
34  //
35  // IMPORTANT NOTE:
36  //
37  // If you modify this function you must be aware that it is used by the
38  // add-on server validation routines both on the client and server sides.
39  // The addition or removal of any criteria here should be carefully
40  // evaluated with this in mind.
41  //
42 
43  if(name.empty() || name.back() == '.' || name.find("..") != std::string::npos || name.size() > 255) {
44  return false;
45  }
46 
47  // Reserved DOS device names on Windows.
48  static const std::set<std::string> dos_device_names = {
49  // Hardware devices
50  "NUL", "CON", "AUX", "PRN",
51  "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
52  "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
53  // Console API pseudo-devices
54  "CONIN$", "CONOUT$",
55  };
56 
57  // We can't use filesystem::base_name() here, because it returns the
58  // filename up to the *last* dot. "CON.foo.bar" is still redirected to
59  // "CON" on Windows, although "foo.CON.bar" and "foo.bar.CON" are not.
60  //
61  // Do also note that we're relying on the char-by-char check further below
62  // to flag the name as illegal if it contains a colon ':', the reason
63  // being that is valid to refer to DOS device names with a trailing colon
64  // (e.g. "CON:" is synonymous with "CON").
65 
66  const auto& first_name =
67  boost::algorithm::to_upper_copy(name.substr(0, name.find('.')), std::locale::classic());
68 
69  if(dos_device_names.count(first_name)) {
70  return false;
71  }
72 
73  const auto& name_ucs4 = unicode_cast<std::u32string>(name);
74  if(name != unicode_cast<std::string>(name_ucs4)){
75  return false; // name is an invalid UTF-8 sequence
76  }
77 
78  return name_ucs4.end() == std::find_if(name_ucs4.begin(), name_ucs4.end(), [=](char32_t c)
79  {
80  switch(c) {
81  case ' ':
82  return !allow_whitespace;
83  case '"':
84  case '*':
85  case '/':
86  case ':':
87  case '<':
88  case '>':
89  case '?':
90  case '\\':
91  case '|':
92  case '~':
93  case 0x7F: // DEL
94  return true;
95  default:
96  return c < 0x20 || // C0 control characters
97  (c >= 0x80 && c < 0xA0) || // C1 control characters
98  (c >= 0xD800 && c < 0xE000); // surrogate pairs
99  }
100  });
101 }
102 
103 void blacklist_pattern_list::remove_blacklisted_files_and_dirs(std::vector<std::string>& files, std::vector<std::string>& directories) const
104 {
105  files.erase(
106  std::remove_if(files.begin(), files.end(), [this](const std::string& name) { return match_file(name); }),
107  files.end());
108  directories.erase(
109  std::remove_if(directories.begin(), directories.end(), [this](const std::string& name) { return match_dir(name); }),
110  directories.end());
111 }
112 
113 bool blacklist_pattern_list::match_file(const std::string& name) const
114 {
115  return std::any_of(file_patterns_.begin(), file_patterns_.end(),
116  std::bind(&utils::wildcard_string_match, std::ref(name), std::placeholders::_1));
117 }
118 
119 bool blacklist_pattern_list::match_dir(const std::string& name) const
120 {
121  return std::any_of(directory_patterns_.begin(), directory_patterns_.end(),
122  std::bind(&utils::wildcard_string_match, std::ref(name), std::placeholders::_1));
123 }
124 
125 std::string autodetect_game_data_dir(std::string exe_dir)
126 {
127  std::string auto_dir;
128 
129  // scons leaves the resulting binaries at the root of the source
130  // tree by default.
131  if(filesystem::file_exists(exe_dir + "/data/_main.cfg")) {
132  auto_dir = std::move(exe_dir);
133  }
134  // cmake encourages creating a subdir at the root of the source
135  // tree for the build, and the resulting binaries are found in it.
136  else if(filesystem::file_exists(exe_dir + "/../data/_main.cfg")) {
137  auto_dir = filesystem::normalize_path(exe_dir + "/..");
138  }
139  // Allow using the current working directory as the game data dir
140  else if(filesystem::file_exists(filesystem::get_cwd() + "/data/_main.cfg")) {
141  auto_dir = filesystem::get_cwd();
142  }
143 #ifdef _WIN32
144  // In Windows builds made using Visual Studio and its CMake
145  // integration, the EXE is placed a few levels below the game data
146  // dir (e.g. .\out\build\x64-Debug).
147  else if(filesystem::file_exists(exe_dir + "/../../build") && filesystem::file_exists(exe_dir + "/../../../out")
148  && filesystem::file_exists(exe_dir + "/../../../data/_main.cfg")) {
149  auto_dir = filesystem::normalize_path(exe_dir + "/../../..");
150  }
151 #endif
152 
153  return auto_dir;
154 }
155 
157 {
158  return get_sync_dir() + "/preferences";
159 }
160 
162 {
163  return get_user_data_dir() + "/preferences";
164 }
165 
166 std::string get_credentials_file()
167 {
168  return get_user_data_dir() + "/credentials-aes";
169 }
170 
172 {
173 #ifdef HAS_RELATIVE_DEFPREF
175 #else
177 #endif
178 }
179 
180 std::string get_save_index_file()
181 {
182  return get_user_data_dir() + "/save_index";
183 }
184 
185 std::string get_lua_history_file()
186 {
187  return get_sync_dir() + "/lua_command_history";
188 }
189 
190 std::string get_sync_dir()
191 {
192  return get_user_data_dir() + "/sync";
193 }
194 
195 std::string get_saves_dir()
196 {
197  const std::string dir_path = get_sync_dir() + "/saves";
198  return get_dir(dir_path);
199 }
200 
201 std::string get_addons_data_dir()
202 {
203  const std::string dir_path = get_user_data_dir() + "/data";
204  return get_dir(dir_path);
205 }
206 
207 std::string get_addons_dir()
208 {
209  const std::string dir_path = get_addons_data_dir() + "/add-ons";
210  return get_dir(dir_path);
211 }
212 
213 std::string get_wml_persist_dir()
214 {
215  const std::string dir_path = get_sync_dir() + "/persist";
216  return get_dir(dir_path);
217 }
218 
220 {
221  const std::string dir_path = get_sync_dir() + "/editor";
222  return get_dir(dir_path);
223 }
224 
225 std::string get_current_editor_dir(const std::string& addon_id)
226 {
227  if(addon_id == "mainline") {
228  return get_dir(game_config::path) + "/data/multiplayer";
229  } else {
230  return get_addons_dir() + "/" + addon_id;
231  }
232 }
233 
234 std::string get_core_images_dir()
235 {
236  return get_dir(game_config::path + "/data/core/images");
237 }
238 
239 std::string get_intl_dir()
240 {
241 #ifdef _WIN32
242  return game_config::path + "/" LOCALEDIR;
243 #else
244 
245 #ifdef USE_INTERNAL_DATA
246  return get_cwd() + "/" LOCALEDIR;
247 #endif
248 
249 #if HAS_RELATIVE_LOCALEDIR
250  std::string res = game_config::path + "/" LOCALEDIR;
251 #else
252  std::string res = LOCALEDIR;
253 #endif
254 
255  return res;
256 #endif
257 }
258 
259 std::string get_screenshot_dir()
260 {
261  const std::string dir_path = get_user_data_dir() + "/screenshots";
262  return get_dir(dir_path);
263 }
264 
265 bool looks_like_pbl(const std::string& file)
266 {
267  return utils::wildcard_string_match(utf8::lowercase(file), "*.pbl");
268 }
269 
270 file_tree_checksum::file_tree_checksum()
271  : nfiles(0), sum_size(0), modified(0)
272 {}
273 
275  nfiles (cfg["nfiles"].to_size_t()),
276  sum_size(cfg["size"].to_size_t()),
277  modified(cfg["modified"].to_time_t())
278 {
279 }
280 
282 {
283  cfg["nfiles"] = nfiles;
284  cfg["size"] = sum_size;
285  cfg["modified"] = modified;
286 }
287 
289 {
290  return nfiles == rhs.nfiles && sum_size == rhs.sum_size &&
291  modified == rhs.modified;
292 }
293 
294 bool ends_with(const std::string& str, const std::string& suffix)
295 {
296  return str.size() >= suffix.size() && std::equal(suffix.begin(),suffix.end(),str.end()-suffix.size());
297 }
298 
299 std::string read_map(const std::string& name)
300 {
301  std::string res;
302  auto map_location = get_wml_location(name);
303  if(!map_location) {
304  // Consult [binary_path] for maps as well.
305  map_location = get_binary_file_location("maps", name);
306  }
307  if(map_location) {
308  res = read_file(map_location.value());
309  }
310 
311  if(res.empty()) {
312  res = read_file(get_user_data_dir() + "/editor/maps/" + name);
313  }
314 
315  return res;
316 }
317 
318 std::string read_scenario(const std::string& name)
319 {
320  std::string res;
321  auto file_location = get_wml_location(name);
322  if(!file_location) {
323  // Consult [binary_path] for scenarios as well.
324  file_location = get_binary_file_location("scenarios", name);
325  }
326  if(file_location) {
327  res = read_file(file_location.value());
328  }
329 
330  if(res.empty()) {
331  res = read_file(get_user_data_dir() + "/editor/scenarios/" + name);
332  }
333 
334  return res;
335 }
336 
337 static void get_file_tree_checksum_internal(const std::string& path, file_tree_checksum& res)
338 {
339 
340  std::vector<std::string> dirs;
342 
343  for(std::vector<std::string>::const_iterator j = dirs.begin(); j != dirs.end(); ++j) {
345  }
346 }
347 
349 {
350  static file_tree_checksum checksum;
351  if (reset)
352  checksum.reset();
353  if(checksum.nfiles == 0) {
354  get_file_tree_checksum_internal("data/",checksum);
355  get_file_tree_checksum_internal(get_user_data_dir() + "/data/",checksum);
356  LOG_FS << "calculated data tree checksum: "
357  << checksum.nfiles << " files; "
358  << checksum.sum_size << " bytes";
359  }
360 
361  return checksum;
362 }
363 
364 }
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:159
Declarations for File-IO.
static lg::log_domain log_filesystem("filesystem")
#define LOG_FS
Standard logging facilities (interface).
std::string get_legacy_editor_dir()
utils::optional< std::string > get_wml_location(const std::string &filename, const std::string &current_dir)
Returns a complete path to the actual WML file or directory, if either exists.
void get_files_in_dir(const std::string &dir, std::vector< std::string > *files, std::vector< std::string > *dirs, name_mode mode, filter_mode filter, reorder_mode reorder, file_tree_checksum *checksum)
Get a list of all files and/or directories in a given directory.
Definition: filesystem.cpp:410
static bfs::path get_dir(const bfs::path &dirpath)
Definition: filesystem.cpp:335
std::string get_user_data_dir()
Definition: filesystem.cpp:793
std::string get_wml_persist_dir()
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:324
bool is_legal_user_file_name(const std::string &name, bool allow_whitespace=true)
Returns whether the given filename is a legal name for a user-created file.
std::string get_synced_prefs_file()
location of preferences file containing preferences that are synced between computers note that wesno...
std::string get_saves_dir()
const file_tree_checksum & data_tree_checksum(bool reset=false)
Get the time at which the data/ tree was last modified at.
std::string read_file(const std::string &fname)
Basic disk I/O - read file.
std::string get_unsynced_prefs_file()
location of preferences file containing preferences that aren't synced between computers
bool ends_with(const std::string &str, const std::string &suffix)
std::string get_save_index_file()
utils::optional< std::string > get_binary_file_location(const std::string &type, const std::string &filename)
Returns a complete path to the actual file of a given type, if it exists.
std::string get_lua_history_file()
std::string autodetect_game_data_dir(std::string exe_dir)
Try to autodetect the location of the game data dir.
std::string read_scenario(const std::string &name)
std::string get_screenshot_dir()
std::string get_credentials_file()
std::string get_sync_dir()
parent directory for everything that should be synced between systems.
bool looks_like_pbl(const std::string &file)
std::string get_addons_data_dir()
std::string get_default_prefs_file()
std::string get_addons_dir()
std::string get_intl_dir()
std::string get_core_images_dir()
static void get_file_tree_checksum_internal(const std::string &path, file_tree_checksum &res)
std::string get_current_editor_dir(const std::string &addon_id)
std::string read_map(const std::string &name)
std::string get_cwd()
Definition: filesystem.cpp:885
std::string normalize_path(const std::string &fpath, bool normalize_separators, bool resolve_dot_entries)
Returns the absolute path of a file.
std::string path
Definition: filesystem.cpp:89
std::string default_preferences_path
Definition: filesystem.cpp:95
std::string lowercase(const std::string &s)
Returns a lowercased version of the string.
Definition: unicode.cpp:50
bool wildcard_string_match(const std::string &str, const std::string &match)
Match using '*' as any number of characters (including none), '+' as one or more characters,...
bool operator==(const file_tree_checksum &rhs) const
Encapsulates the map of the game.
Definition: location.hpp:38
mock_char c
Some defines: VERSION, PACKAGE, MIN_SAVEGAME_VERSION.
#define LOCALEDIR
Definition: wesconfig.h:19