]> git.lizzy.rs Git - dragonfireclient.git/blob - src/util/string.cpp
Merge branch 'master' of https://github.com/minetest/minetest
[dragonfireclient.git] / src / util / string.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "string.h"
21 #include "pointer.h"
22 #include "numeric.h"
23 #include "log.h"
24
25 #include "hex.h"
26 #include "porting.h"
27 #include "translation.h"
28
29 #include <algorithm>
30 #include <array>
31 #include <sstream>
32 #include <iomanip>
33 #include <map>
34
35 #ifndef _WIN32
36         #include <iconv.h>
37 #else
38         #define _WIN32_WINNT 0x0501
39         #include <windows.h>
40 #endif
41
42 #if defined(_ICONV_H_) && (defined(__FreeBSD__) || defined(__NetBSD__) || \
43         defined(__OpenBSD__) || defined(__DragonFly__))
44         #define BSD_ICONV_USED
45 #endif
46
47 static bool parseHexColorString(const std::string &value, video::SColor &color,
48                 unsigned char default_alpha = 0xff);
49 static bool parseNamedColorString(const std::string &value, video::SColor &color);
50
51 #ifndef _WIN32
52
53 static bool convert(const char *to, const char *from, char *outbuf,
54                 size_t *outbuf_size, char *inbuf, size_t inbuf_size)
55 {
56         iconv_t cd = iconv_open(to, from);
57
58 #ifdef BSD_ICONV_USED
59         const char *inbuf_ptr = inbuf;
60 #else
61         char *inbuf_ptr = inbuf;
62 #endif
63         char *outbuf_ptr = outbuf;
64
65         size_t *inbuf_left_ptr = &inbuf_size;
66
67         const size_t old_outbuf_size = *outbuf_size;
68         size_t old_size = inbuf_size;
69         while (inbuf_size > 0) {
70                 iconv(cd, &inbuf_ptr, inbuf_left_ptr, &outbuf_ptr, outbuf_size);
71                 if (inbuf_size == old_size) {
72                         iconv_close(cd);
73                         return false;
74                 }
75                 old_size = inbuf_size;
76         }
77
78         iconv_close(cd);
79         *outbuf_size = old_outbuf_size - *outbuf_size;
80         return true;
81 }
82
83 #ifdef __ANDROID__
84 // On Android iconv disagrees how big a wchar_t is for whatever reason
85 const char *DEFAULT_ENCODING = "UTF-32LE";
86 #else
87 const char *DEFAULT_ENCODING = "WCHAR_T";
88 #endif
89
90 std::wstring utf8_to_wide(const std::string &input)
91 {
92         const size_t inbuf_size = input.length();
93         // maximum possible size, every character is sizeof(wchar_t) bytes
94         size_t outbuf_size = input.length() * sizeof(wchar_t);
95
96         char *inbuf = new char[inbuf_size]; // intentionally NOT null-terminated
97         memcpy(inbuf, input.c_str(), inbuf_size);
98         std::wstring out;
99         out.resize(outbuf_size / sizeof(wchar_t));
100
101 #ifdef __ANDROID__
102         SANITY_CHECK(sizeof(wchar_t) == 4);
103 #endif
104
105         char *outbuf = reinterpret_cast<char*>(&out[0]);
106         if (!convert(DEFAULT_ENCODING, "UTF-8", outbuf, &outbuf_size, inbuf, inbuf_size)) {
107                 infostream << "Couldn't convert UTF-8 string 0x" << hex_encode(input)
108                         << " into wstring" << std::endl;
109                 delete[] inbuf;
110                 return L"<invalid UTF-8 string>";
111         }
112         delete[] inbuf;
113
114         out.resize(outbuf_size / sizeof(wchar_t));
115         return out;
116 }
117
118 std::string wide_to_utf8(const std::wstring &input)
119 {
120         const size_t inbuf_size = input.length() * sizeof(wchar_t);
121         // maximum possible size: utf-8 encodes codepoints using 1 up to 4 bytes
122         size_t outbuf_size = input.length() * 4;
123
124         char *inbuf = new char[inbuf_size]; // intentionally NOT null-terminated
125         memcpy(inbuf, input.c_str(), inbuf_size);
126         std::string out;
127         out.resize(outbuf_size);
128
129         if (!convert("UTF-8", DEFAULT_ENCODING, &out[0], &outbuf_size, inbuf, inbuf_size)) {
130                 infostream << "Couldn't convert wstring 0x" << hex_encode(inbuf, inbuf_size)
131                         << " into UTF-8 string" << std::endl;
132                 delete[] inbuf;
133                 return "<invalid wide string>";
134         }
135         delete[] inbuf;
136
137         out.resize(outbuf_size);
138         return out;
139 }
140
141 #else // _WIN32
142
143 std::wstring utf8_to_wide(const std::string &input)
144 {
145         size_t outbuf_size = input.size() + 1;
146         wchar_t *outbuf = new wchar_t[outbuf_size];
147         memset(outbuf, 0, outbuf_size * sizeof(wchar_t));
148         MultiByteToWideChar(CP_UTF8, 0, input.c_str(), input.size(),
149                 outbuf, outbuf_size);
150         std::wstring out(outbuf);
151         delete[] outbuf;
152         return out;
153 }
154
155 std::string wide_to_utf8(const std::wstring &input)
156 {
157         size_t outbuf_size = (input.size() + 1) * 6;
158         char *outbuf = new char[outbuf_size];
159         memset(outbuf, 0, outbuf_size);
160         WideCharToMultiByte(CP_UTF8, 0, input.c_str(), input.size(),
161                 outbuf, outbuf_size, NULL, NULL);
162         std::string out(outbuf);
163         delete[] outbuf;
164         return out;
165 }
166
167 #endif // _WIN32
168
169 wchar_t *utf8_to_wide_c(const char *str)
170 {
171         std::wstring ret = utf8_to_wide(std::string(str));
172         size_t len = ret.length();
173         wchar_t *ret_c = new wchar_t[len + 1];
174         memcpy(ret_c, ret.c_str(), (len + 1) * sizeof(wchar_t));
175         return ret_c;
176 }
177
178
179 std::string urlencode(const std::string &str)
180 {
181         // Encodes non-unreserved URI characters by a percent sign
182         // followed by two hex digits. See RFC 3986, section 2.3.
183         static const char url_hex_chars[] = "0123456789ABCDEF";
184         std::ostringstream oss(std::ios::binary);
185         for (unsigned char c : str) {
186                 if (isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
187                         oss << c;
188                 } else {
189                         oss << "%"
190                                 << url_hex_chars[(c & 0xf0) >> 4]
191                                 << url_hex_chars[c & 0x0f];
192                 }
193         }
194         return oss.str();
195 }
196
197 std::string urldecode(const std::string &str)
198 {
199         // Inverse of urlencode
200         std::ostringstream oss(std::ios::binary);
201         for (u32 i = 0; i < str.size(); i++) {
202                 unsigned char highvalue, lowvalue;
203                 if (str[i] == '%' &&
204                                 hex_digit_decode(str[i+1], highvalue) &&
205                                 hex_digit_decode(str[i+2], lowvalue)) {
206                         oss << (char) ((highvalue << 4) | lowvalue);
207                         i += 2;
208                 } else {
209                         oss << str[i];
210                 }
211         }
212         return oss.str();
213 }
214
215 u32 readFlagString(std::string str, const FlagDesc *flagdesc, u32 *flagmask)
216 {
217         u32 result = 0;
218         u32 mask = 0;
219         char *s = &str[0];
220         char *flagstr;
221         char *strpos = nullptr;
222
223         while ((flagstr = strtok_r(s, ",", &strpos))) {
224                 s = nullptr;
225
226                 while (*flagstr == ' ' || *flagstr == '\t')
227                         flagstr++;
228
229                 bool flagset = true;
230                 if (!strncasecmp(flagstr, "no", 2)) {
231                         flagset = false;
232                         flagstr += 2;
233                 }
234
235                 for (int i = 0; flagdesc[i].name; i++) {
236                         if (!strcasecmp(flagstr, flagdesc[i].name)) {
237                                 mask |= flagdesc[i].flag;
238                                 if (flagset)
239                                         result |= flagdesc[i].flag;
240                                 break;
241                         }
242                 }
243         }
244
245         if (flagmask)
246                 *flagmask = mask;
247
248         return result;
249 }
250
251 std::string writeFlagString(u32 flags, const FlagDesc *flagdesc, u32 flagmask)
252 {
253         std::string result;
254
255         for (int i = 0; flagdesc[i].name; i++) {
256                 if (flagmask & flagdesc[i].flag) {
257                         if (!(flags & flagdesc[i].flag))
258                                 result += "no";
259
260                         result += flagdesc[i].name;
261                         result += ", ";
262                 }
263         }
264
265         size_t len = result.length();
266         if (len >= 2)
267                 result.erase(len - 2, 2);
268
269         return result;
270 }
271
272 size_t mystrlcpy(char *dst, const char *src, size_t size)
273 {
274         size_t srclen  = strlen(src) + 1;
275         size_t copylen = MYMIN(srclen, size);
276
277         if (copylen > 0) {
278                 memcpy(dst, src, copylen);
279                 dst[copylen - 1] = '\0';
280         }
281
282         return srclen;
283 }
284
285 char *mystrtok_r(char *s, const char *sep, char **lasts)
286 {
287         char *t;
288
289         if (!s)
290                 s = *lasts;
291
292         while (*s && strchr(sep, *s))
293                 s++;
294
295         if (!*s)
296                 return nullptr;
297
298         t = s;
299         while (*t) {
300                 if (strchr(sep, *t)) {
301                         *t++ = '\0';
302                         break;
303                 }
304                 t++;
305         }
306
307         *lasts = t;
308         return s;
309 }
310
311 u64 read_seed(const char *str)
312 {
313         char *endptr;
314         u64 num;
315
316         if (str[0] == '0' && str[1] == 'x')
317                 num = strtoull(str, &endptr, 16);
318         else
319                 num = strtoull(str, &endptr, 10);
320
321         if (*endptr)
322                 num = murmur_hash_64_ua(str, (int)strlen(str), 0x1337);
323
324         return num;
325 }
326
327 bool parseColorString(const std::string &value, video::SColor &color, bool quiet,
328                 unsigned char default_alpha)
329 {
330         bool success;
331
332         if (value[0] == '#')
333                 success = parseHexColorString(value, color, default_alpha);
334         else
335                 success = parseNamedColorString(value, color);
336
337         if (!success && !quiet)
338                 errorstream << "Invalid color: \"" << value << "\"" << std::endl;
339
340         return success;
341 }
342
343 static bool parseHexColorString(const std::string &value, video::SColor &color,
344                 unsigned char default_alpha)
345 {
346         unsigned char components[] = { 0x00, 0x00, 0x00, default_alpha }; // R,G,B,A
347
348         if (value[0] != '#')
349                 return false;
350
351         size_t len = value.size();
352         bool short_form;
353
354         if (len == 9 || len == 7) // #RRGGBBAA or #RRGGBB
355                 short_form = false;
356         else if (len == 5 || len == 4) // #RGBA or #RGB
357                 short_form = true;
358         else
359                 return false;
360
361         bool success = true;
362
363         for (size_t pos = 1, cc = 0; pos < len; pos++, cc++) {
364                 assert(cc < sizeof components / sizeof components[0]);
365                 if (short_form) {
366                         unsigned char d;
367                         if (!hex_digit_decode(value[pos], d)) {
368                                 success = false;
369                                 break;
370                         }
371                         components[cc] = (d & 0xf) << 4 | (d & 0xf);
372                 } else {
373                         unsigned char d1, d2;
374                         if (!hex_digit_decode(value[pos], d1) ||
375                                         !hex_digit_decode(value[pos+1], d2)) {
376                                 success = false;
377                                 break;
378                         }
379                         components[cc] = (d1 & 0xf) << 4 | (d2 & 0xf);
380                         pos++;  // skip the second digit -- it's already used
381                 }
382         }
383
384         if (success) {
385                 color.setRed(components[0]);
386                 color.setGreen(components[1]);
387                 color.setBlue(components[2]);
388                 color.setAlpha(components[3]);
389         }
390
391         return success;
392 }
393
394 struct ColorContainer {
395         ColorContainer();
396         std::map<const std::string, u32> colors;
397 };
398
399 ColorContainer::ColorContainer()
400 {
401         colors["aliceblue"]              = 0xf0f8ff;
402         colors["antiquewhite"]           = 0xfaebd7;
403         colors["aqua"]                   = 0x00ffff;
404         colors["aquamarine"]             = 0x7fffd4;
405         colors["azure"]                  = 0xf0ffff;
406         colors["beige"]                  = 0xf5f5dc;
407         colors["bisque"]                 = 0xffe4c4;
408         colors["black"]                  = 00000000;
409         colors["blanchedalmond"]         = 0xffebcd;
410         colors["blue"]                   = 0x0000ff;
411         colors["blueviolet"]             = 0x8a2be2;
412         colors["brown"]                  = 0xa52a2a;
413         colors["burlywood"]              = 0xdeb887;
414         colors["cadetblue"]              = 0x5f9ea0;
415         colors["chartreuse"]             = 0x7fff00;
416         colors["chocolate"]              = 0xd2691e;
417         colors["coral"]                  = 0xff7f50;
418         colors["cornflowerblue"]         = 0x6495ed;
419         colors["cornsilk"]               = 0xfff8dc;
420         colors["crimson"]                = 0xdc143c;
421         colors["cyan"]                   = 0x00ffff;
422         colors["darkblue"]               = 0x00008b;
423         colors["darkcyan"]               = 0x008b8b;
424         colors["darkgoldenrod"]          = 0xb8860b;
425         colors["darkgray"]               = 0xa9a9a9;
426         colors["darkgreen"]              = 0x006400;
427         colors["darkgrey"]               = 0xa9a9a9;
428         colors["darkkhaki"]              = 0xbdb76b;
429         colors["darkmagenta"]            = 0x8b008b;
430         colors["darkolivegreen"]         = 0x556b2f;
431         colors["darkorange"]             = 0xff8c00;
432         colors["darkorchid"]             = 0x9932cc;
433         colors["darkred"]                = 0x8b0000;
434         colors["darksalmon"]             = 0xe9967a;
435         colors["darkseagreen"]           = 0x8fbc8f;
436         colors["darkslateblue"]          = 0x483d8b;
437         colors["darkslategray"]          = 0x2f4f4f;
438         colors["darkslategrey"]          = 0x2f4f4f;
439         colors["darkturquoise"]          = 0x00ced1;
440         colors["darkviolet"]             = 0x9400d3;
441         colors["deeppink"]               = 0xff1493;
442         colors["deepskyblue"]            = 0x00bfff;
443         colors["dimgray"]                = 0x696969;
444         colors["dimgrey"]                = 0x696969;
445         colors["dodgerblue"]             = 0x1e90ff;
446         colors["firebrick"]              = 0xb22222;
447         colors["floralwhite"]            = 0xfffaf0;
448         colors["forestgreen"]            = 0x228b22;
449         colors["fuchsia"]                = 0xff00ff;
450         colors["gainsboro"]              = 0xdcdcdc;
451         colors["ghostwhite"]             = 0xf8f8ff;
452         colors["gold"]                   = 0xffd700;
453         colors["goldenrod"]              = 0xdaa520;
454         colors["gray"]                   = 0x808080;
455         colors["green"]                  = 0x008000;
456         colors["greenyellow"]            = 0xadff2f;
457         colors["grey"]                   = 0x808080;
458         colors["honeydew"]               = 0xf0fff0;
459         colors["hotpink"]                = 0xff69b4;
460         colors["indianred"]              = 0xcd5c5c;
461         colors["indigo"]                 = 0x4b0082;
462         colors["ivory"]                  = 0xfffff0;
463         colors["khaki"]                  = 0xf0e68c;
464         colors["lavender"]               = 0xe6e6fa;
465         colors["lavenderblush"]          = 0xfff0f5;
466         colors["lawngreen"]              = 0x7cfc00;
467         colors["lemonchiffon"]           = 0xfffacd;
468         colors["lightblue"]              = 0xadd8e6;
469         colors["lightcoral"]             = 0xf08080;
470         colors["lightcyan"]              = 0xe0ffff;
471         colors["lightgoldenrodyellow"]   = 0xfafad2;
472         colors["lightgray"]              = 0xd3d3d3;
473         colors["lightgreen"]             = 0x90ee90;
474         colors["lightgrey"]              = 0xd3d3d3;
475         colors["lightpink"]              = 0xffb6c1;
476         colors["lightsalmon"]            = 0xffa07a;
477         colors["lightseagreen"]          = 0x20b2aa;
478         colors["lightskyblue"]           = 0x87cefa;
479         colors["lightslategray"]         = 0x778899;
480         colors["lightslategrey"]         = 0x778899;
481         colors["lightsteelblue"]         = 0xb0c4de;
482         colors["lightyellow"]            = 0xffffe0;
483         colors["lime"]                   = 0x00ff00;
484         colors["limegreen"]              = 0x32cd32;
485         colors["linen"]                  = 0xfaf0e6;
486         colors["magenta"]                = 0xff00ff;
487         colors["maroon"]                 = 0x800000;
488         colors["mediumaquamarine"]       = 0x66cdaa;
489         colors["mediumblue"]             = 0x0000cd;
490         colors["mediumorchid"]           = 0xba55d3;
491         colors["mediumpurple"]           = 0x9370db;
492         colors["mediumseagreen"]         = 0x3cb371;
493         colors["mediumslateblue"]        = 0x7b68ee;
494         colors["mediumspringgreen"]      = 0x00fa9a;
495         colors["mediumturquoise"]        = 0x48d1cc;
496         colors["mediumvioletred"]        = 0xc71585;
497         colors["midnightblue"]           = 0x191970;
498         colors["mintcream"]              = 0xf5fffa;
499         colors["mistyrose"]              = 0xffe4e1;
500         colors["moccasin"]               = 0xffe4b5;
501         colors["navajowhite"]            = 0xffdead;
502         colors["navy"]                   = 0x000080;
503         colors["oldlace"]                = 0xfdf5e6;
504         colors["olive"]                  = 0x808000;
505         colors["olivedrab"]              = 0x6b8e23;
506         colors["orange"]                 = 0xffa500;
507         colors["orangered"]              = 0xff4500;
508         colors["orchid"]                 = 0xda70d6;
509         colors["palegoldenrod"]          = 0xeee8aa;
510         colors["palegreen"]              = 0x98fb98;
511         colors["paleturquoise"]          = 0xafeeee;
512         colors["palevioletred"]          = 0xdb7093;
513         colors["papayawhip"]             = 0xffefd5;
514         colors["peachpuff"]              = 0xffdab9;
515         colors["peru"]                   = 0xcd853f;
516         colors["pink"]                   = 0xffc0cb;
517         colors["plum"]                   = 0xdda0dd;
518         colors["powderblue"]             = 0xb0e0e6;
519         colors["purple"]                 = 0x800080;
520         colors["red"]                    = 0xff0000;
521         colors["rosybrown"]              = 0xbc8f8f;
522         colors["royalblue"]              = 0x4169e1;
523         colors["saddlebrown"]            = 0x8b4513;
524         colors["salmon"]                 = 0xfa8072;
525         colors["sandybrown"]             = 0xf4a460;
526         colors["seagreen"]               = 0x2e8b57;
527         colors["seashell"]               = 0xfff5ee;
528         colors["sienna"]                 = 0xa0522d;
529         colors["silver"]                 = 0xc0c0c0;
530         colors["skyblue"]                = 0x87ceeb;
531         colors["slateblue"]              = 0x6a5acd;
532         colors["slategray"]              = 0x708090;
533         colors["slategrey"]              = 0x708090;
534         colors["snow"]                   = 0xfffafa;
535         colors["springgreen"]            = 0x00ff7f;
536         colors["steelblue"]              = 0x4682b4;
537         colors["tan"]                    = 0xd2b48c;
538         colors["teal"]                   = 0x008080;
539         colors["thistle"]                = 0xd8bfd8;
540         colors["tomato"]                 = 0xff6347;
541         colors["turquoise"]              = 0x40e0d0;
542         colors["violet"]                 = 0xee82ee;
543         colors["wheat"]                  = 0xf5deb3;
544         colors["white"]                  = 0xffffff;
545         colors["whitesmoke"]             = 0xf5f5f5;
546         colors["yellow"]                 = 0xffff00;
547         colors["yellowgreen"]            = 0x9acd32;
548
549 }
550
551 static const ColorContainer named_colors;
552
553 static bool parseNamedColorString(const std::string &value, video::SColor &color)
554 {
555         std::string color_name;
556         std::string alpha_string;
557
558         /* If the string has a # in it, assume this is the start of a specified
559          * alpha value (if it isn't the string is invalid and the error will be
560          * caught later on, either because the color name won't be found or the
561          * alpha value will fail conversion)
562          */
563         size_t alpha_pos = value.find('#');
564         if (alpha_pos != std::string::npos) {
565                 color_name = value.substr(0, alpha_pos);
566                 alpha_string = value.substr(alpha_pos + 1);
567         } else {
568                 color_name = value;
569         }
570
571         color_name = lowercase(color_name);
572
573         std::map<const std::string, unsigned>::const_iterator it;
574         it = named_colors.colors.find(color_name);
575         if (it == named_colors.colors.end())
576                 return false;
577
578         u32 color_temp = it->second;
579
580         /* An empty string for alpha is ok (none of the color table entries
581          * have an alpha value either). Color strings without an alpha specified
582          * are interpreted as fully opaque
583          *
584          * For named colors the supplied alpha string (representing a hex value)
585          * must be exactly two digits. For example:  colorname#08
586          */
587         if (!alpha_string.empty()) {
588                 if (alpha_string.length() != 2)
589                         return false;
590
591                 unsigned char d1, d2;
592                 if (!hex_digit_decode(alpha_string.at(0), d1)
593                                 || !hex_digit_decode(alpha_string.at(1), d2))
594                         return false;
595                 color_temp |= ((d1 & 0xf) << 4 | (d2 & 0xf)) << 24;
596         } else {
597                 color_temp |= 0xff << 24;  // Fully opaque
598         }
599
600         color = video::SColor(color_temp);
601
602         return true;
603 }
604
605 void str_replace(std::string &str, char from, char to)
606 {
607         std::replace(str.begin(), str.end(), from, to);
608 }
609
610 /* Translated strings have the following format:
611  * \x1bT marks the beginning of a translated string
612  * \x1bE marks its end
613  *
614  * \x1bF marks the beginning of an argument, and \x1bE its end.
615  *
616  * Arguments are *not* translated, as they may contain escape codes.
617  * Thus, if you want a translated argument, it should be inside \x1bT/\x1bE tags as well.
618  *
619  * This representation is chosen so that clients ignoring escape codes will
620  * see untranslated strings.
621  *
622  * For instance, suppose we have a string such as "@1 Wool" with the argument "White"
623  * The string will be sent as "\x1bT\x1bF\x1bTWhite\x1bE\x1bE Wool\x1bE"
624  * To translate this string, we extract what is inside \x1bT/\x1bE tags.
625  * When we notice the \x1bF tag, we recursively extract what is there up to the \x1bE end tag,
626  * translating it as well.
627  * We get the argument "White", translated, and create a template string with "@1" instead of it.
628  * We finally get the template "@1 Wool" that was used in the beginning, which we translate
629  * before filling it again.
630  */
631
632 void translate_all(const std::wstring &s, size_t &i,
633                 Translations *translations, std::wstring &res);
634
635 void translate_string(const std::wstring &s, Translations *translations,
636                 const std::wstring &textdomain, size_t &i, std::wstring &res)
637 {
638         std::wostringstream output;
639         std::vector<std::wstring> args;
640         int arg_number = 1;
641         while (i < s.length()) {
642                 // Not an escape sequence: just add the character.
643                 if (s[i] != '\x1b') {
644                         output.put(s[i]);
645                         // The character is a literal '@'; add it twice
646                         // so that it is not mistaken for an argument.
647                         if (s[i] == L'@')
648                                 output.put(L'@');
649                         ++i;
650                         continue;
651                 }
652
653                 // We have an escape sequence: locate it and its data
654                 // It is either a single character, or it begins with '('
655                 // and extends up to the following ')', with '\' as an escape character.
656                 ++i;
657                 size_t start_index = i;
658                 size_t length;
659                 if (i == s.length()) {
660                         length = 0;
661                 } else if (s[i] == L'(') {
662                         ++i;
663                         ++start_index;
664                         while (i < s.length() && s[i] != L')') {
665                                 if (s[i] == L'\\')
666                                         ++i;
667                                 ++i;
668                         }
669                         length = i - start_index;
670                         ++i;
671                         if (i > s.length())
672                                 i = s.length();
673                 } else {
674                         ++i;
675                         length = 1;
676                 }
677                 std::wstring escape_sequence(s, start_index, length);
678
679                 // The escape sequence is now reconstructed.
680                 std::vector<std::wstring> parts = split(escape_sequence, L'@');
681                 if (parts[0] == L"E") {
682                         // "End of translation" escape sequence. We are done locating the string to translate.
683                         break;
684                 } else if (parts[0] == L"F") {
685                         // "Start of argument" escape sequence.
686                         // Recursively translate the argument, and add it to the argument list.
687                         // Add an "@n" instead of the argument to the template to translate.
688                         if (arg_number >= 10) {
689                                 errorstream << "Ignoring too many arguments to translation" << std::endl;
690                                 std::wstring arg;
691                                 translate_all(s, i, translations, arg);
692                                 args.push_back(arg);
693                                 continue;
694                         }
695                         output.put(L'@');
696                         output << arg_number;
697                         ++arg_number;
698                         std::wstring arg;
699                         translate_all(s, i, translations, arg);
700                         args.push_back(arg);
701                 } else {
702                         // This is an escape sequence *inside* the template string to translate itself.
703                         // This should not happen, show an error message.
704                         errorstream << "Ignoring escape sequence '"
705                                 << wide_to_utf8(escape_sequence) << "' in translation" << std::endl;
706                 }
707         }
708
709         std::wstring toutput;
710         // Translate the template.
711         if (translations != nullptr)
712                 toutput = translations->getTranslation(
713                                 textdomain, output.str());
714         else
715                 toutput = output.str();
716
717         // Put back the arguments in the translated template.
718         std::wostringstream result;
719         size_t j = 0;
720         while (j < toutput.length()) {
721                 // Normal character, add it to output and continue.
722                 if (toutput[j] != L'@' || j == toutput.length() - 1) {
723                         result.put(toutput[j]);
724                         ++j;
725                         continue;
726                 }
727
728                 ++j;
729                 // Literal escape for '@'.
730                 if (toutput[j] == L'@') {
731                         result.put(L'@');
732                         ++j;
733                         continue;
734                 }
735
736                 // Here we have an argument; get its index and add the translated argument to the output.
737                 int arg_index = toutput[j] - L'1';
738                 ++j;
739                 if (0 <= arg_index && (size_t)arg_index < args.size()) {
740                         result << args[arg_index];
741                 } else {
742                         // This is not allowed: show an error message
743                         errorstream << "Ignoring out-of-bounds argument escape sequence in translation" << std::endl;
744                 }
745         }
746         res = result.str();
747 }
748
749 void translate_all(const std::wstring &s, size_t &i,
750                 Translations *translations, std::wstring &res)
751 {
752         std::wostringstream output;
753         while (i < s.length()) {
754                 // Not an escape sequence: just add the character.
755                 if (s[i] != '\x1b') {
756                         output.put(s[i]);
757                         ++i;
758                         continue;
759                 }
760
761                 // We have an escape sequence: locate it and its data
762                 // It is either a single character, or it begins with '('
763                 // and extends up to the following ')', with '\' as an escape character.
764                 size_t escape_start = i;
765                 ++i;
766                 size_t start_index = i;
767                 size_t length;
768                 if (i == s.length()) {
769                         length = 0;
770                 } else if (s[i] == L'(') {
771                         ++i;
772                         ++start_index;
773                         while (i < s.length() && s[i] != L')') {
774                                 if (s[i] == L'\\') {
775                                         ++i;
776                                 }
777                                 ++i;
778                         }
779                         length = i - start_index;
780                         ++i;
781                         if (i > s.length())
782                                 i = s.length();
783                 } else {
784                         ++i;
785                         length = 1;
786                 }
787                 std::wstring escape_sequence(s, start_index, length);
788
789                 // The escape sequence is now reconstructed.
790                 std::vector<std::wstring> parts = split(escape_sequence, L'@');
791                 if (parts[0] == L"E") {
792                         // "End of argument" escape sequence. Exit.
793                         break;
794                 } else if (parts[0] == L"T") {
795                         // Beginning of translated string.
796                         std::wstring textdomain;
797                         if (parts.size() > 1)
798                                 textdomain = parts[1];
799                         std::wstring translated;
800                         translate_string(s, translations, textdomain, i, translated);
801                         output << translated;
802                 } else {
803                         // Another escape sequence, such as colors. Preserve it.
804                         output << std::wstring(s, escape_start, i - escape_start);
805                 }
806         }
807
808         res = output.str();
809 }
810
811 // Translate string server side
812 std::wstring translate_string(const std::wstring &s, Translations *translations)
813 {
814         size_t i = 0;
815         std::wstring res;
816         translate_all(s, i, translations, res);
817         return res;
818 }
819
820 // Translate string client side
821 std::wstring translate_string(const std::wstring &s)
822 {
823 #ifdef SERVER
824         return translate_string(s, nullptr);
825 #else
826         return translate_string(s, g_client_translations);
827 #endif
828 }
829
830 static const std::array<std::wstring, 22> disallowed_dir_names = {
831         // Problematic filenames from here:
832         // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#file-and-directory-names
833         L"CON",
834         L"PRN",
835         L"AUX",
836         L"NUL",
837         L"COM1",
838         L"COM2",
839         L"COM3",
840         L"COM4",
841         L"COM5",
842         L"COM6",
843         L"COM7",
844         L"COM8",
845         L"COM9",
846         L"LPT1",
847         L"LPT2",
848         L"LPT3",
849         L"LPT4",
850         L"LPT5",
851         L"LPT6",
852         L"LPT7",
853         L"LPT8",
854         L"LPT9",
855 };
856
857 /**
858  * List of characters that are blacklisted from created directories
859  */
860 static const std::wstring disallowed_path_chars = L"<>:\"/\\|?*.";
861
862 /**
863  * Sanitize the name of a new directory. This consists of two stages:
864  * 1. Check for 'reserved filenames' that can't be used on some filesystems
865  *      and add a prefix to them
866  * 2. Remove 'unsafe' characters from the name by replacing them with '_'
867  */
868 std::string sanitizeDirName(const std::string &str, const std::string &optional_prefix)
869 {
870         std::wstring safe_name = utf8_to_wide(str);
871
872         for (std::wstring disallowed_name : disallowed_dir_names) {
873                 if (str_equal(safe_name, disallowed_name, true)) {
874                         safe_name = utf8_to_wide(optional_prefix) + safe_name;
875                         break;
876                 }
877         }
878
879         for (unsigned long i = 0; i < safe_name.length(); i++) {
880                 bool is_valid = true;
881
882                 // Unlikely, but control characters should always be blacklisted
883                 if (safe_name[i] < 32) {
884                         is_valid = false;
885                 } else if (safe_name[i] < 128) {
886                         is_valid = disallowed_path_chars.find_first_of(safe_name[i])
887                                         == std::wstring::npos;
888                 }
889
890                 if (!is_valid)
891                         safe_name[i] = '_';
892         }
893
894         return wide_to_utf8(safe_name);
895 }