The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
filesystem_common.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2017 - 2025
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"
23#include "utils/general.hpp"
24
25#include <boost/algorithm/string.hpp>
26
27static 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
31namespace filesystem
32{
33
34bool 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
105void blacklist_pattern_list::remove_blacklisted_files_and_dirs(std::vector<std::string>& files, std::vector<std::string>& directories) const
106{
107 utils::erase_if(files, [this](const std::string& name) { return match_file(name); });
108 utils::erase_if(directories, [this](const std::string& name) { return match_dir(name); });
109}
110
111bool blacklist_pattern_list::match_file(const std::string& name) const
112{
113 return std::any_of(file_patterns_.begin(), file_patterns_.end(),
114 [&name](const std::string& pattern) { return utils::wildcard_string_match(name, pattern); });
115}
116
117bool blacklist_pattern_list::match_dir(const std::string& name) const
118{
119 return std::any_of(directory_patterns_.begin(), directory_patterns_.end(),
120 [&name](const std::string& pattern) { return utils::wildcard_string_match(name, pattern); });
121}
122
123std::string autodetect_game_data_dir(std::string exe_dir)
124{
125 std::string auto_dir;
126
127 // scons leaves the resulting binaries at the root of the source
128 // tree by default.
129 if(filesystem::file_exists(exe_dir + "/data/_main.cfg")) {
130 auto_dir = std::move(exe_dir);
131 }
132 // cmake encourages creating a subdir at the root of the source
133 // tree for the build, and the resulting binaries are found in it.
134 else if(filesystem::file_exists(exe_dir + "/../data/_main.cfg")) {
135 auto_dir = filesystem::normalize_path(exe_dir + "/..");
136 }
137 // Allow using the current working directory as the game data dir
138 else if(filesystem::file_exists(filesystem::get_cwd() + "/data/_main.cfg")) {
139 auto_dir = filesystem::get_cwd();
140 }
141#ifdef _WIN32
142 // In Windows builds made using Visual Studio and its CMake
143 // integration, the EXE is placed a few levels below the game data
144 // dir (e.g. .\out\build\x64-Debug).
145 else if(filesystem::file_exists(exe_dir + "/../../build") && filesystem::file_exists(exe_dir + "/../../../out")
146 && filesystem::file_exists(exe_dir + "/../../../data/_main.cfg")) {
147 auto_dir = filesystem::normalize_path(exe_dir + "/../../..");
148 }
149#endif
150
151 return auto_dir;
152}
153
155{
156 return get_sync_dir() + "/preferences";
157}
158
160{
161 return get_user_data_dir() + "/preferences";
162}
163
165{
166 return get_user_data_dir() + "/credentials-aes";
167}
168
170{
171#ifdef HAS_RELATIVE_DEFPREF
173#else
175#endif
176}
177
179{
180 return get_user_data_dir() + "/save_index";
181}
182
184{
185 return get_sync_dir() + "/lua_command_history";
186}
187
188std::string get_sync_dir()
189{
190 return get_user_data_dir() + "/sync";
191}
192
193std::string get_saves_dir()
194{
195 const std::string dir_path = get_sync_dir() + "/saves";
196 return get_dir(dir_path);
197}
198
200{
201 const std::string dir_path = get_user_data_dir() + "/data";
202 return get_dir(dir_path);
203}
204
205std::string get_addons_dir()
206{
207 const std::string dir_path = get_addons_data_dir() + "/add-ons";
208 return get_dir(dir_path);
209}
210
212{
213 const std::string dir_path = get_sync_dir() + "/persist";
214 return get_dir(dir_path);
215}
216
218{
219 const std::string dir_path = get_sync_dir() + "/editor";
220 return get_dir(dir_path);
221}
222
223std::string get_current_editor_dir(const std::string& addon_id)
224{
225 if(addon_id == "mainline") {
226 return game_config::path + "/data/multiplayer";
227 } else {
228 return get_addons_dir() + "/" + addon_id;
229 }
230}
231
233{
234 return game_config::path + "/data/core/images";
235}
236
237std::string get_intl_dir()
238{
239#if HAS_RELATIVE_LOCALEDIR
240 return game_config::path + "/" LOCALEDIR;
241#else
242 return LOCALEDIR;
243#endif
244}
245
247{
248 const std::string dir_path = get_user_data_dir() + "/screenshots";
249 return get_dir(dir_path);
250}
251
252bool looks_like_pbl(const std::string& file)
253{
254 return utils::wildcard_string_match(utf8::lowercase(file), "*.pbl");
255}
256
257file_tree_checksum::file_tree_checksum(const config& cfg)
258 : nfiles(cfg["nfiles"].to_size_t())
259 , sum_size(cfg["size"].to_size_t())
260 , modified(chrono::parse_timestamp(cfg["modified"]))
261{
262}
263
265{
266 cfg["nfiles"] = nfiles;
267 cfg["size"] = sum_size;
269}
270
272{
273 return nfiles == rhs.nfiles && sum_size == rhs.sum_size &&
274 modified == rhs.modified;
275}
276
278{
279static std::string get_file_by_type(const std::string& name, const std::string& type)
280{
281 if(utils::optional map_location = get_wml_location(name)) {
282 return *map_location;
283 }
284
285 // Consult [binary_path] as well.
286 if(utils::optional map_location = get_binary_file_location(type, name)) {
287 return *map_location;
288 }
289
290 // TODO: should really be using fs::path
291 return get_legacy_editor_dir() + "/" + type + "/" + base_name(name);
292}
293
294} // implementation
295
296std::string get_map_file(const std::string& name)
297{
298 return implementation::get_file_by_type(name, "maps");
299}
300
301std::string get_scenario_file(const std::string& name)
302{
303 return implementation::get_file_by_type(name, "scenarios");
304}
305
306std::string read_map(const std::string& name)
307{
308 return read_file(get_map_file(name));
309}
310
311std::string read_scenario(const std::string& name)
312{
313 return read_file(get_scenario_file(name));
314}
315
316static void get_file_tree_checksum_internal(const std::string& path, file_tree_checksum& res)
317{
318
319 std::vector<std::string> dirs;
321
322 for(std::vector<std::string>::const_iterator j = dirs.begin(); j != dirs.end(); ++j) {
324 }
325}
326
328{
329 static file_tree_checksum checksum;
330 if (reset)
331 checksum = file_tree_checksum{};
332 if(checksum.nfiles == 0) {
333 get_file_tree_checksum_internal("data/",checksum);
335 LOG_FS << "calculated data tree checksum: "
336 << checksum.nfiles << " files; "
337 << checksum.sum_size << " bytes";
338 }
339
340 return checksum;
341}
342
343}
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
Definitions for the interface to Wesnoth Markup Language (WML).
const config * cfg
#define LOG_FS
Declarations for File-IO.
static lg::log_domain log_filesystem("filesystem")
Standard logging facilities (interface).
auto serialize_timestamp(const std::chrono::system_clock::time_point &time)
Definition chrono.hpp:58
static std::string get_file_by_type(const std::string &name, const std::string &type)
std::string get_legacy_editor_dir()
bool file_exists(const std::string &name)
Returns true if a file or directory with such name already exists.
void get_files_in_dir(const std::string &dir, std::vector< std::string > *files, std::vector< std::string > *dirs=nullptr, name_mode mode=name_mode::FILE_NAME_ONLY, filter_mode filter=filter_mode::NO_FILTER, reorder_mode reorder=reorder_mode::DONT_REORDER, file_tree_checksum *checksum=nullptr)
Get a list of all files and/or directories in a given directory.
std::string get_user_data_dir()
std::string base_name(const std::string &file, const bool remove_extension=false)
Returns the base filename of a file, with directory name stripped.
std::string get_wml_persist_dir()
std::string get_map_file(const std::string &name)
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...
utils::optional< std::string > get_wml_location(const std::string &path, const utils::optional< std::string > &current_dir=utils::nullopt)
Returns a translated path to the actual file or directory, if it exists.
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
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 get_dir(const std::string &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_scenario_file(const std::string &name)
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()
std::string normalize_path(const std::string &path, bool normalize_separators=false, bool resolve_dot_entries=false)
Returns the absolute path of a file.
std::string path
std::string default_preferences_path
Contains the implementation details for lexical_cast and shouldn't be used directly.
std::string lowercase(std::string_view s)
Returns a lowercased version of the string.
Definition unicode.cpp:50
bool wildcard_string_match(std::string_view str, std::string_view pat) noexcept
Performs pattern matching with wildcards.
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition general.hpp:108
bool operator==(const file_tree_checksum &rhs) const
std::chrono::system_clock::time_point modified
Encapsulates the map of the game.
Definition location.hpp:46
Some defines: VERSION, PACKAGE, MIN_SAVEGAME_VERSION.
#define LOCALEDIR
Definition wesconfig.h:19
#define c