The Battle for Wesnoth  1.17.21+dev
filesystem_common.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2017 - 2023
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 <fstream>
16 
17 #include "filesystem.hpp"
18 #include "wesconfig.h"
19 
20 #include "config.hpp"
21 #include "log.hpp"
24 
25 #include <boost/algorithm/string.hpp>
26 
27 static lg::log_domain log_filesystem("filesystem");
28 #define LOG_FS LOG_STREAM(info, log_filesystem)
29 #define ERR_FS LOG_STREAM(err, log_filesystem)
30 
31 namespace filesystem
32 {
33 
34 bool is_legal_user_file_name(const std::string& name, bool allow_whitespace)
35 {
36  //
37  // IMPORTANT NOTE:
38  //
39  // If you modify this function you must be aware that it is used by the
40  // add-on server validation routines both on the client and server sides.
41  // The addition or removal of any criteria here should be carefully
42  // evaluated with this in mind.
43  //
44 
45  if(name.empty() || name.back() == '.' || name.find("..") != std::string::npos || name.size() > 255) {
46  return false;
47  }
48 
49  // Reserved DOS device names on Windows.
50  static const std::set<std::string> dos_device_names = {
51  // Hardware devices
52  "NUL", "CON", "AUX", "PRN",
53  "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
54  "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
55  // Console API pseudo-devices
56  "CONIN$", "CONOUT$",
57  };
58 
59  // We can't use filesystem::base_name() here, because it returns the
60  // filename up to the *last* dot. "CON.foo.bar" is still redirected to
61  // "CON" on Windows, although "foo.CON.bar" and "foo.bar.CON" are not.
62  //
63  // Do also note that we're relying on the char-by-char check further below
64  // to flag the name as illegal if it contains a colon ':', the reason
65  // being that is valid to refer to DOS device names with a trailing colon
66  // (e.g. "CON:" is synonymous with "CON").
67 
68  const auto& first_name =
69  boost::algorithm::to_upper_copy(name.substr(0, name.find('.')), std::locale::classic());
70 
71  if(dos_device_names.count(first_name)) {
72  return false;
73  }
74 
75  const auto& name_ucs4 = unicode_cast<std::u32string>(name);
76  if(name != unicode_cast<std::string>(name_ucs4)){
77  return false; // name is an invalid UTF-8 sequence
78  }
79 
80  return name_ucs4.end() == std::find_if(name_ucs4.begin(), name_ucs4.end(), [=](char32_t c)
81  {
82  switch(c) {
83  case ' ':
84  return !allow_whitespace;
85  case '"':
86  case '*':
87  case '/':
88  case ':':
89  case '<':
90  case '>':
91  case '?':
92  case '\\':
93  case '|':
94  case '~':
95  case 0x7F: // DEL
96  return true;
97  default:
98  return c < 0x20 || // C0 control characters
99  (c >= 0x80 && c < 0xA0) || // C1 control characters
100  (c >= 0xD800 && c < 0xE000); // surrogate pairs
101  }
102  });
103 }
104 
105 void blacklist_pattern_list::remove_blacklisted_files_and_dirs(std::vector<std::string>& files, std::vector<std::string>& directories) const
106 {
107  files.erase(
108  std::remove_if(files.begin(), files.end(), [this](const std::string& name) { return match_file(name); }),
109  files.end());
110  directories.erase(
111  std::remove_if(directories.begin(), directories.end(), [this](const std::string& name) { return match_dir(name); }),
112  directories.end());
113 }
114 
115 bool blacklist_pattern_list::match_file(const std::string& name) const
116 {
117  return std::any_of(file_patterns_.begin(), file_patterns_.end(),
118  std::bind(&utils::wildcard_string_match, std::ref(name), std::placeholders::_1));
119 }
120 
121 bool blacklist_pattern_list::match_dir(const std::string& name) const
122 {
123  return std::any_of(directory_patterns_.begin(), directory_patterns_.end(),
124  std::bind(&utils::wildcard_string_match, std::ref(name), std::placeholders::_1));
125 }
126 
127 std::string get_prefs_file()
128 {
129  return get_user_config_dir() + "/preferences";
130 }
131 
132 std::string get_credentials_file()
133 {
134  return get_user_config_dir() + "/credentials-aes";
135 }
136 
138 {
139 #ifdef HAS_RELATIVE_DEFPREF
141 #else
143 #endif
144 }
145 
146 std::string get_save_index_file()
147 {
148  return get_user_data_dir() + "/save_index";
149 }
150 
151 std::string get_saves_dir()
152 {
153  const std::string dir_path = get_user_data_dir() + "/saves";
154  return get_dir(dir_path);
155 }
156 
157 std::string get_addons_data_dir()
158 {
159  const std::string dir_path = get_user_data_dir() + "/data";
160  return get_dir(dir_path);
161 }
162 
163 std::string get_addons_dir()
164 {
165  const std::string dir_path = get_addons_data_dir() + "/add-ons";
166  return get_dir(dir_path);
167 }
168 
169 std::string get_wml_persist_dir()
170 {
171  const std::string dir_path = get_user_data_dir() + "/persist";
172  return get_dir(dir_path);
173 }
174 
176 {
177  const std::string dir_path = get_user_data_dir() + "/editor";
178  return get_dir(dir_path);
179 }
180 
181 std::string get_current_editor_dir(const std::string& addon_id)
182 {
183  if(addon_id == "mainline") {
184  return get_dir(game_config::path) + "/data/multiplayer";
185  } else {
186  return get_addons_dir() + "/" + addon_id;
187  }
188 }
189 
190 std::string get_core_images_dir()
191 {
192  return get_dir(game_config::path + "/data/core/images");
193 }
194 
195 std::string get_intl_dir()
196 {
197 #ifdef _WIN32
198  return game_config::path + "/" LOCALEDIR;
199 #else
200 
201 #ifdef USE_INTERNAL_DATA
202  return get_cwd() + "/" LOCALEDIR;
203 #endif
204 
205 #if HAS_RELATIVE_LOCALEDIR
206  std::string res = game_config::path + "/" LOCALEDIR;
207 #else
208  std::string res = LOCALEDIR;
209 #endif
210 
211  return res;
212 #endif
213 }
214 
215 std::string get_screenshot_dir()
216 {
217  const std::string dir_path = get_user_data_dir() + "/screenshots";
218  return get_dir(dir_path);
219 }
220 
221 bool looks_like_pbl(const std::string& file)
222 {
223  return utils::wildcard_string_match(utf8::lowercase(file), "*.pbl");
224 }
225 
226 file_tree_checksum::file_tree_checksum()
227  : nfiles(0), sum_size(0), modified(0)
228 {}
229 
231  nfiles (cfg["nfiles"].to_size_t()),
232  sum_size(cfg["size"].to_size_t()),
233  modified(cfg["modified"].to_time_t())
234 {
235 }
236 
238 {
239  cfg["nfiles"] = nfiles;
240  cfg["size"] = sum_size;
241  cfg["modified"] = modified;
242 }
243 
245 {
246  return nfiles == rhs.nfiles && sum_size == rhs.sum_size &&
247  modified == rhs.modified;
248 }
249 
250 bool ends_with(const std::string& str, const std::string& suffix)
251 {
252  return str.size() >= suffix.size() && std::equal(suffix.begin(),suffix.end(),str.end()-suffix.size());
253 }
254 
255 std::string read_map(const std::string& name)
256 {
257  std::string res;
258  std::string map_location = get_wml_location(name);
259  if(map_location.empty()) {
260  // Consult [binary_path] for maps as well.
261  map_location = get_binary_file_location("maps", name);
262  }
263  if(!map_location.empty()) {
264  res = read_file(map_location);
265  }
266 
267  if(res.empty()) {
268  res = read_file(get_user_data_dir() + "/editor/maps/" + name);
269  }
270 
271  return res;
272 }
273 
274 std::string read_scenario(const std::string& name)
275 {
276  std::string res;
277  std::string file_location = get_wml_location(name);
278  if(file_location.empty()) {
279  // Consult [binary_path] for scenarios as well.
280  file_location = get_binary_file_location("scenarios", name);
281  }
282  if(!file_location.empty()) {
283  res = read_file(file_location);
284  }
285 
286  if(res.empty()) {
287  res = read_file(get_user_data_dir() + "/editor/scenarios/" + name);
288  }
289 
290  return res;
291 }
292 
293 static void get_file_tree_checksum_internal(const std::string& path, file_tree_checksum& res)
294 {
295 
296  std::vector<std::string> dirs;
298 
299  for(std::vector<std::string>::const_iterator j = dirs.begin(); j != dirs.end(); ++j) {
301  }
302 }
303 
305 {
306  static file_tree_checksum checksum;
307  if (reset)
308  checksum.reset();
309  if(checksum.nfiles == 0) {
310  get_file_tree_checksum_internal("data/",checksum);
311  get_file_tree_checksum_internal(get_user_data_dir() + "/data/",checksum);
312  LOG_FS << "calculated data tree checksum: "
313  << checksum.nfiles << " files; "
314  << checksum.sum_size << " bytes";
315  }
316 
317  return checksum;
318 }
319 
320 }
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:161
Declarations for File-IO.
static lg::log_domain log_filesystem("filesystem")
#define LOG_FS
Standard logging facilities (interface).
std::string get_legacy_editor_dir()
std::string get_user_config_dir()
Definition: filesystem.cpp:842
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:407
static bfs::path get_dir(const bfs::path &dirpath)
Definition: filesystem.cpp:332
std::string get_user_data_dir()
Definition: filesystem.cpp:871
std::string get_wml_persist_dir()
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_saves_dir()
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 or an empty string if the file isn't pres...
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.
bool ends_with(const std::string &str, const std::string &suffix)
std::string get_save_index_file()
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 or an empty string if the file isn't prese...
std::string get_prefs_file()
std::string read_scenario(const std::string &name)
std::string get_screenshot_dir()
std::string get_credentials_file()
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:963
std::string path
Definition: filesystem.cpp:86
std::string default_preferences_path
Definition: filesystem.cpp:92
std::string lowercase(const std::string &s)
Returns a lowercased version of the string.
Definition: unicode.cpp:52
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