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