The Battle for Wesnoth  1.13.10+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
validation.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2008 by David White <dave@whitevine.net>
3  2008 - 2015 by Ignacio R. Morelle <shadowm2006@gmail.com>
4  Part of the Battle for Wesnoth Project http://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "addon/validation.hpp"
17 #include "config.hpp"
19 
20 #include <algorithm>
21 #include <boost/algorithm/string.hpp>
22 #include <set>
23 
24 const unsigned short default_campaignd_port = 15008;
25 
26 namespace {
27  const std::string addon_type_strings[] {
28  "unknown", "core", "campaign", "scenario", "campaign_sp_mp", "campaign_mp",
29  "scenario_mp", "map_pack", "era", "faction", "mod_mp", /*"gui", */ "media",
30  "other", ""
31  };
32 
33  // Reserved DOS device names on Windows XP and later.
34  const std::set<std::string> dos_device_names = {
35  "NUL", "CON", "AUX", "PRN",
36  // Console API devices
37  "CONIN$", "CONOUT$",
38  // Configuration-dependent devices
39  "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
40  "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
41  };
42 
43  struct addon_name_char_illegal
44  {
45  /**
46  * Returns whether the given add-on name char is not whitelisted.
47  */
48  inline bool operator()(char c) const
49  {
50  switch(c)
51  {
52  case '-': // hyphen-minus
53  case '_': // low line
54  return false;
55  default:
56  return !isalnum(c);
57  }
58  }
59  };
60 
61  struct addon_filename_ucs4char_illegal
62  {
63  inline bool operator()(ucs4::char_t c) const
64  {
65  switch(c){
66  case ' ':
67  case '"':
68  case '*':
69  case '/':
70  case ':':
71  case '<':
72  case '>':
73  case '?':
74  case '\\':
75  case '|':
76  case '~':
77  case 0x7F: // DEL
78  return true;
79  default:
80  return (
81  c < 0x20 || // C0 control characters
82  (c >= 0x80 && c < 0xA0) || // C1 control characters
83  (c >= 0xD800 && c < 0xE000) // surrogate pairs
84  );
85  }
86  }
87  };
88 }
89 
91 {
92  if(name.empty() ||
93  std::find_if(name.begin(), name.end(), addon_name_char_illegal()) != name.end()) {
94  return false;
95  } else {
96  return true;
97  }
98 }
99 
101 {
102  if(name.empty() || name.back() == '.' ||
103  name.find("..") != std::string::npos ||
104  name.size() > 255) {
105  return false;
106  } else {
107  // NOTE: We can't use filesystem::base_name() here, since it returns
108  // the filename up to the *last* dot. "CON.foo.bar" in
109  // "CON.foo.bar.baz" is still redirected to "CON" on Windows;
110  // the base_name() approach would cause the name to not match
111  // any entries on our blacklist.
112  // Do also note that we're relying on the next check after this
113  // to flag the name as illegal if it contains a ':' -- a
114  // trailing colon is a valid way to refer to DOS device names,
115  // meaning that e.g. "CON:" is equivalent to "CON".
116  const std::string stem = boost::algorithm::to_upper_copy(name.substr(0, name.find('.')), std::locale::classic());
117  if(dos_device_names.find(stem) != dos_device_names.end()) {
118  return false;
119  }
120 
121  const ucs4::string name_ucs4 = unicode_cast<ucs4::string>(name);
122  const std::string name_utf8 = unicode_cast<utf8::string>(name_ucs4);
123  if(name != name_utf8){ // name is invalid UTF-8
124  return false;
125  }
126  return std::find_if(name_ucs4.begin(), name_ucs4.end(), addon_filename_ucs4char_illegal()) == name_ucs4.end();
127  }
128 }
129 
130 namespace {
131 
132 bool check_names_legal_internal(const config& dir, std::string current_prefix, std::vector<std::string>* badlist)
133 {
134  if (!current_prefix.empty()) {
135  current_prefix += '/';
136  }
137 
138  for(const config& path : dir.child_range("file")) {
139  const std::string& filename = path["name"];
140 
141  if(!addon_filename_legal(filename)) {
142  if(badlist) {
143  badlist->push_back(current_prefix + filename);
144  } else {
145  return false;
146  }
147  }
148  }
149 
150  for(const config& path : dir.child_range("dir")) {
151  const std::string& dirname = path["name"];
152  const std::string& new_prefix = current_prefix + dirname;
153 
154  if(!addon_filename_legal(dirname)) {
155  if(badlist) {
156  badlist->push_back(new_prefix + "/");
157  } else {
158  return false;
159  }
160  }
161 
162  // Recurse into subdir.
163  if(!check_names_legal_internal(path, new_prefix, badlist) && !badlist) {
164  return false;
165  }
166  }
167 
168  return badlist ? badlist->empty() : true;
169 }
170 
171 bool check_case_insensitive_duplicates_internal(const config& dir, std::string prefix, std::vector<std::string>* badlist){
172  typedef std::pair<bool, std::string> printed_and_original;
173  std::map<std::string, printed_and_original> filenames;
174  bool inserted;
175  bool printed;
176  std::string original;
177  for (const config &path : dir.child_range("file")) {
178  const config::attribute_value &filename = path["name"];
179  const std::string lowercase = boost::algorithm::to_lower_copy(filename.str(), std::locale::classic());
180  const std::string with_prefix = prefix + filename.str();
181  std::tie(std::ignore, inserted) = filenames.emplace(lowercase, std::make_pair(false, with_prefix));
182  if (!inserted){
183  if(badlist){
184  std::tie(printed, original) = filenames[lowercase];
185  if(!printed){
186  badlist->push_back(original);
187  filenames[lowercase] = make_pair(true, std::string());
188  }
189  badlist->push_back(with_prefix);
190  } else {
191  return false;
192  }
193  }
194  }
195  for (const config &path : dir.child_range("dir")) {
196  const config::attribute_value &filename = path["name"];
197  const std::string lowercase = boost::algorithm::to_lower_copy(filename.str(), std::locale::classic());
198  const std::string with_prefix = prefix + filename.str();
199  std::tie(std::ignore, inserted) = filenames.emplace(lowercase, std::make_pair(false, with_prefix));
200  if (!inserted) {
201  if(badlist){
202  std::tie(printed, original) = filenames[lowercase];
203  if(!printed){
204  badlist->push_back(original);
205  filenames[lowercase] = make_pair(true, std::string());
206  }
207  badlist->push_back(with_prefix);
208  } else {
209  return false;
210  }
211  }
212  if (!check_case_insensitive_duplicates_internal(path, prefix + filename + "/", badlist) && !badlist){
213  return false;
214  }
215  }
216 
217  return badlist ? badlist->empty() : true;
218 }
219 
220 } // end unnamed namespace 3
221 
222 bool check_names_legal(const config& dir, std::vector<std::string>* badlist)
223 {
224  // Usually our caller is passing us the root [dir] for an add-on, which
225  // shall contain a single subdir named after the add-on itself, so we can
226  // start with an empty display prefix and that'll reflect the addon
227  // structure correctly (e.g. "Addon_Name/~illegalfilename1").
228  return check_names_legal_internal(dir, "", badlist);
229 }
230 
231 bool check_case_insensitive_duplicates(const config& dir, std::vector<std::string>* badlist){
232  return check_case_insensitive_duplicates_internal(dir, "", badlist);
233 }
234 
236 {
237  if (str.empty())
238  return ADDON_UNKNOWN;
239 
240  unsigned addon_type_num = 0;
241 
242  while(++addon_type_num != ADDON_TYPES_COUNT) {
243  if(str == addon_type_strings[addon_type_num]) {
244  return ADDON_TYPE(addon_type_num);
245  }
246  }
247 
248  return ADDON_UNKNOWN;
249 }
250 
252 {
253  assert(type != ADDON_TYPES_COUNT);
254  return addon_type_strings[type];
255 }
256 
257 namespace {
258  const char escape_char = '\x01'; /**< Binary escape char. */
259 } // end unnamed namespace 2
260 
261 bool needs_escaping(char c) {
262  switch(c) {
263  case '\x00':
264  case escape_char:
265  case '\x0D': //Windows -- carriage return
266  case '\xFE': //Parser code -- textdomain or linenumber&filename
267  return true;
268  default:
269  return false;
270  }
271 }
272 
274 {
275  std::string res;
276  res.resize(str.size());
277  size_t n = 0;
278  for(std::string::const_iterator j = str.begin(); j != str.end(); ++j) {
279  if(needs_escaping(*j)) {
280  res.resize(res.size()+1);
281  res[n++] = escape_char;
282  res[n++] = *j + 1;
283  } else {
284  res[n++] = *j;
285  }
286  }
287 
288  return res;
289 }
290 
292 {
293  std::string res;
294  res.resize(str.size());
295 
296  size_t n = 0;
297  for(std::string::const_iterator j = str.begin(); j != str.end(); ++j) {
298  if(*j == escape_char && j+1 != str.end()) {
299  ++j;
300  res[n++] = *j - 1;
301  res.resize(res.size()-1);
302  } else {
303  res[n++] = *j;
304  }
305  }
306 
307  return res;
308 }
309 
310 
bool check_names_legal(const config &dir, std::vector< std::string > *badlist)
Scans an add-on archive for illegal names.
Definition: validation.cpp:222
bool check_case_insensitive_duplicates(const config &dir, std::vector< std::string > *badlist)
Scans an add-on archive for case-conflicts.
Definition: validation.cpp:231
std::vector< char_t > string
std::string str(const std::string &fallback="") const
ADDON_TYPE
Values used for add-on classification; UI-only at the moment, in the future it could be used for dire...
Definition: validation.hpp:40
Variant for storing WML attributes.
std::string encode_binary(const std::string &str)
Definition: validation.cpp:273
std::string unencode_binary(const std::string &str)
Definition: validation.cpp:291
ucs4_convert_impl::enableif< TD, typename TS::value_type >::type unicode_cast(const TS &source)
child_itors child_range(config_key_type key)
Definition: config.cpp:343
bool needs_escaping(char c)
Definition: validation.cpp:261
utf8::string lowercase(const utf8::string &s)
Returns a lowercased version of the string.
Definition: unicode.cpp:51
uint32_t char_t
Definitions for the interface to Wesnoth Markup Language (WML).
std::string path
Definition: game_config.cpp:56
ADDON_TYPE get_addon_type(const std::string &str)
Definition: validation.cpp:235
bool addon_name_legal(const std::string &name)
Checks whether an add-on id/name is legal or not.
Definition: validation.cpp:90
const unsigned short default_campaignd_port
Default port number for the addon server.
Definition: validation.cpp:24
static const char * name(const std::vector< SDL_Joystick * > &joysticks, const size_t index)
Definition: joystick.cpp:48
std::string get_addon_type_string(ADDON_TYPE type)
Definition: validation.cpp:251
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:93
mock_char c
static map_location::DIRECTION n
std::string string
bool addon_filename_legal(const std::string &name)
Checks whether an add-on file name is legal or not.
Definition: validation.cpp:100