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  struct addon_name_char_illegal
34  {
35  /**
36  * Returns whether the given add-on name char is not whitelisted.
37  */
38  inline bool operator()(char c) const
39  {
40  switch(c)
41  {
42  case '-': // hyphen-minus
43  case '_': // low line
44  return false;
45  default:
46  return !isalnum(c);
47  }
48  }
49  };
50 
51  struct addon_filename_ucs4char_illegal
52  {
53  inline bool operator()(ucs4::char_t c) const
54  {
55  switch(c){
56  case ' ':
57  case '"':
58  case '*':
59  case '/':
60  case ':':
61  case '<':
62  case '>':
63  case '?':
64  case '\\':
65  case '|':
66  case '~':
67  case 0x7F: // DEL
68  return true;
69  default:
70  return (
71  c < 0x20 || // C0 control characters
72  (c >= 0x80 && c < 0xA0) || // C1 control characters
73  (c >= 0xD800 && c < 0xE000) // surrogate pairs
74  );
75  }
76  }
77  };
78 }
79 
81 {
82  if(name.empty() ||
83  std::find_if(name.begin(), name.end(), addon_name_char_illegal()) != name.end()) {
84  return false;
85  } else {
86  return true;
87  }
88 }
89 
91 {
92  if(name.empty() || name.back() == '.' ||
93  name.find("..") != std::string::npos ||
94  name.size() > 255) {
95  return false;
96  } else {
97  const ucs4::string name_ucs4 = unicode_cast<ucs4::string>(name);
98  const std::string name_utf8 = unicode_cast<utf8::string>(name_ucs4);
99  if(name != name_utf8){ // name is invalid UTF-8
100  return false;
101  }
102  return std::find_if(name_ucs4.begin(), name_ucs4.end(), addon_filename_ucs4char_illegal()) == name_ucs4.end();
103  }
104 }
105 
106 namespace {
107 
108 bool check_names_legal_internal(const config& dir, std::string current_prefix, std::vector<std::string>* badlist)
109 {
110  if (!current_prefix.empty()) {
111  current_prefix += '/';
112  }
113 
114  for(const config& path : dir.child_range("file")) {
115  const std::string& filename = path["name"];
116 
117  if(!addon_filename_legal(filename)) {
118  if(badlist) {
119  badlist->push_back(current_prefix + filename);
120  } else {
121  return false;
122  }
123  }
124  }
125 
126  for(const config& path : dir.child_range("dir")) {
127  const std::string& dirname = path["name"];
128  const std::string& new_prefix = current_prefix + dirname;
129 
130  if(!addon_filename_legal(dirname)) {
131  if(badlist) {
132  badlist->push_back(new_prefix + "/");
133  } else {
134  return false;
135  }
136  }
137 
138  // Recurse into subdir.
139  if(!check_names_legal_internal(path, new_prefix, badlist) && !badlist) {
140  return false;
141  }
142  }
143 
144  return badlist ? badlist->empty() : true;
145 }
146 
147 bool check_case_insensitive_duplicates_internal(const config& dir, std::string prefix, std::vector<std::string>* badlist){
148  typedef std::pair<bool, std::string> printed_and_original;
149  std::map<std::string, printed_and_original> filenames;
150  bool inserted;
151  bool printed;
152  std::string original;
153  for (const config &path : dir.child_range("file")) {
154  const config::attribute_value &filename = path["name"];
155  const std::string lowercase = boost::algorithm::to_lower_copy(filename.str(), std::locale::classic());
156  const std::string with_prefix = prefix + filename.str();
157  std::tie(std::ignore, inserted) = filenames.emplace(lowercase, std::make_pair(false, with_prefix));
158  if (!inserted){
159  if(badlist){
160  std::tie(printed, original) = filenames[lowercase];
161  if(!printed){
162  badlist->push_back(original);
163  filenames[lowercase] = make_pair(true, std::string());
164  }
165  badlist->push_back(with_prefix);
166  } else {
167  return false;
168  }
169  }
170  }
171  for (const config &path : dir.child_range("dir")) {
172  const config::attribute_value &filename = path["name"];
173  const std::string lowercase = boost::algorithm::to_lower_copy(filename.str(), std::locale::classic());
174  const std::string with_prefix = prefix + filename.str();
175  std::tie(std::ignore, inserted) = filenames.emplace(lowercase, std::make_pair(false, with_prefix));
176  if (!inserted) {
177  if(badlist){
178  std::tie(printed, original) = filenames[lowercase];
179  if(!printed){
180  badlist->push_back(original);
181  filenames[lowercase] = make_pair(true, std::string());
182  }
183  badlist->push_back(with_prefix);
184  } else {
185  return false;
186  }
187  }
188  if (!check_case_insensitive_duplicates_internal(path, prefix + filename + "/", badlist) && !badlist){
189  return false;
190  }
191  }
192 
193  return badlist ? badlist->empty() : true;
194 }
195 
196 } // end unnamed namespace 3
197 
198 bool check_names_legal(const config& dir, std::vector<std::string>* badlist)
199 {
200  // Usually our caller is passing us the root [dir] for an add-on, which
201  // shall contain a single subdir named after the add-on itself, so we can
202  // start with an empty display prefix and that'll reflect the addon
203  // structure correctly (e.g. "Addon_Name/~illegalfilename1").
204  return check_names_legal_internal(dir, "", badlist);
205 }
206 
207 bool check_case_insensitive_duplicates(const config& dir, std::vector<std::string>* badlist){
208  return check_case_insensitive_duplicates_internal(dir, "", badlist);
209 }
210 
212 {
213  if (str.empty())
214  return ADDON_UNKNOWN;
215 
216  unsigned addon_type_num = 0;
217 
218  while(++addon_type_num != ADDON_TYPES_COUNT) {
219  if(str == addon_type_strings[addon_type_num]) {
220  return ADDON_TYPE(addon_type_num);
221  }
222  }
223 
224  return ADDON_UNKNOWN;
225 }
226 
228 {
229  assert(type != ADDON_TYPES_COUNT);
230  return addon_type_strings[type];
231 }
232 
233 namespace {
234  const char escape_char = '\x01'; /**< Binary escape char. */
235 } // end unnamed namespace 2
236 
237 bool needs_escaping(char c) {
238  switch(c) {
239  case '\x00':
240  case escape_char:
241  case '\x0D': //Windows -- carriage return
242  case '\xFE': //Parser code -- textdomain or linenumber&filename
243  return true;
244  default:
245  return false;
246  }
247 }
248 
250 {
251  std::string res;
252  res.resize(str.size());
253  size_t n = 0;
254  for(std::string::const_iterator j = str.begin(); j != str.end(); ++j) {
255  if(needs_escaping(*j)) {
256  res.resize(res.size()+1);
257  res[n++] = escape_char;
258  res[n++] = *j + 1;
259  } else {
260  res[n++] = *j;
261  }
262  }
263 
264  return res;
265 }
266 
268 {
269  std::string res;
270  res.resize(str.size());
271 
272  size_t n = 0;
273  for(std::string::const_iterator j = str.begin(); j != str.end(); ++j) {
274  if(*j == escape_char && j+1 != str.end()) {
275  ++j;
276  res[n++] = *j - 1;
277  res.resize(res.size()-1);
278  } else {
279  res[n++] = *j;
280  }
281  }
282 
283  return res;
284 }
285 
286 
bool check_names_legal(const config &dir, std::vector< std::string > *badlist)
Scans an add-on archive for illegal names.
Definition: validation.cpp:198
bool check_case_insensitive_duplicates(const config &dir, std::vector< std::string > *badlist)
Scans an add-on archive for case-conflicts.
Definition: validation.cpp:207
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:249
std::string unencode_binary(const std::string &str)
Definition: validation.cpp:267
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:295
bool needs_escaping(char c)
Definition: validation.cpp:237
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:211
bool addon_name_legal(const std::string &name)
Checks whether an add-on id/name is legal or not.
Definition: validation.cpp:80
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:227
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:90