incrementaltp: respect physics overrides
[waspsaliva.git] / src / util / string.cpp
blob8381a29c5acce46f2184dfc0dee6f26f3e125ffa
1 /*
2 Minetest
3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
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.
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.
20 #include "string.h"
21 #include "pointer.h"
22 #include "numeric.h"
23 #include "log.h"
25 #include "hex.h"
26 #include "porting.h"
27 #include "translation.h"
29 #include <algorithm>
30 #include <array>
31 #include <sstream>
32 #include <iomanip>
33 #include <map>
35 #ifndef _WIN32
36 #include <iconv.h>
37 #else
38 #define _WIN32_WINNT 0x0501
39 #include <windows.h>
40 #endif
42 #if defined(_ICONV_H_) && (defined(__FreeBSD__) || defined(__NetBSD__) || \
43 defined(__OpenBSD__) || defined(__DragonFly__))
44 #define BSD_ICONV_USED
45 #endif
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);
51 #ifndef _WIN32
53 bool convert(const char *to, const char *from, char *outbuf,
54 size_t outbuf_size, char *inbuf, size_t inbuf_size)
56 iconv_t cd = iconv_open(to, from);
58 #ifdef BSD_ICONV_USED
59 const char *inbuf_ptr = inbuf;
60 #else
61 char *inbuf_ptr = inbuf;
62 #endif
64 char *outbuf_ptr = outbuf;
66 size_t *inbuf_left_ptr = &inbuf_size;
67 size_t *outbuf_left_ptr = &outbuf_size;
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;
76 old_size = inbuf_size;
79 iconv_close(cd);
80 return true;
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
90 std::wstring utf8_to_wide(const std::string &input)
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);
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);
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
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>";
113 std::wstring out((wchar_t *)outbuf);
115 delete[] inbuf;
116 delete[] outbuf;
118 return out;
121 std::string wide_to_utf8(const std::wstring &input)
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;
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);
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>";
139 std::string out(outbuf);
141 delete[] inbuf;
142 delete[] outbuf;
144 return out;
147 #else // _WIN32
149 std::wstring utf8_to_wide(const std::string &input)
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;
161 std::string wide_to_utf8(const std::wstring &input)
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;
173 #endif // _WIN32
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)
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;
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)
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);
200 #else
201 size_t len = strlen(str);
202 nstr = new wchar_t[len + 1];
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
209 return nstr;
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
227 std::string wide_to_narrow(const std::wstring &wcs)
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!";
238 mbs[len] = 0;
239 return *mbs;
240 #endif
244 std::string urlencode(const std::string &str)
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];
259 return oss.str();
262 std::string urldecode(const std::string &str)
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];
277 return oss.str();
280 u32 readFlagString(std::string str, const FlagDesc *flagdesc, u32 *flagmask)
282 u32 result = 0;
283 u32 mask = 0;
284 char *s = &str[0];
285 char *flagstr;
286 char *strpos = nullptr;
288 while ((flagstr = strtok_r(s, ",", &strpos))) {
289 s = nullptr;
291 while (*flagstr == ' ' || *flagstr == '\t')
292 flagstr++;
294 bool flagset = true;
295 if (!strncasecmp(flagstr, "no", 2)) {
296 flagset = false;
297 flagstr += 2;
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;
310 if (flagmask)
311 *flagmask = mask;
313 return result;
316 std::string writeFlagString(u32 flags, const FlagDesc *flagdesc, u32 flagmask)
318 std::string result;
320 for (int i = 0; flagdesc[i].name; i++) {
321 if (flagmask & flagdesc[i].flag) {
322 if (!(flags & flagdesc[i].flag))
323 result += "no";
325 result += flagdesc[i].name;
326 result += ", ";
330 size_t len = result.length();
331 if (len >= 2)
332 result.erase(len - 2, 2);
334 return result;
337 size_t mystrlcpy(char *dst, const char *src, size_t size)
339 size_t srclen = strlen(src) + 1;
340 size_t copylen = MYMIN(srclen, size);
342 if (copylen > 0) {
343 memcpy(dst, src, copylen);
344 dst[copylen - 1] = '\0';
347 return srclen;
350 char *mystrtok_r(char *s, const char *sep, char **lasts)
352 char *t;
354 if (!s)
355 s = *lasts;
357 while (*s && strchr(sep, *s))
358 s++;
360 if (!*s)
361 return nullptr;
363 t = s;
364 while (*t) {
365 if (strchr(sep, *t)) {
366 *t++ = '\0';
367 break;
369 t++;
372 *lasts = t;
373 return s;
376 u64 read_seed(const char *str)
378 char *endptr;
379 u64 num;
381 if (str[0] == '0' && str[1] == 'x')
382 num = strtoull(str, &endptr, 16);
383 else
384 num = strtoull(str, &endptr, 10);
386 if (*endptr)
387 num = murmur_hash_64_ua(str, (int)strlen(str), 0x1337);
389 return num;
392 bool parseColorString(const std::string &value, video::SColor &color, bool quiet,
393 unsigned char default_alpha)
395 bool success;
397 if (value[0] == '#')
398 success = parseHexColorString(value, color, default_alpha);
399 else
400 success = parseNamedColorString(value, color);
402 if (!success && !quiet)
403 errorstream << "Invalid color: \"" << value << "\"" << std::endl;
405 return success;
408 static bool parseHexColorString(const std::string &value, video::SColor &color,
409 unsigned char default_alpha)
411 unsigned char components[] = { 0x00, 0x00, 0x00, default_alpha }; // R,G,B,A
413 if (value[0] != '#')
414 return false;
416 size_t len = value.size();
417 bool short_form;
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;
426 bool success = true;
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;
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;
444 components[cc] = (d1 & 0xf) << 4 | (d2 & 0xf);
445 pos++; // skip the second digit -- it's already used
449 if (success) {
450 color.setRed(components[0]);
451 color.setGreen(components[1]);
452 color.setBlue(components[2]);
453 color.setAlpha(components[3]);
456 return success;
459 struct ColorContainer {
460 ColorContainer();
461 std::map<const std::string, u32> colors;
464 ColorContainer::ColorContainer()
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;
616 static const ColorContainer named_colors;
618 static bool parseNamedColorString(const std::string &value, video::SColor &color)
620 std::string color_name;
621 std::string alpha_string;
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)
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;
636 color_name = lowercase(value);
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;
643 u32 color_temp = it->second;
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
649 * For named colors the supplied alpha string (representing a hex value)
650 * must be exactly two digits. For example: colorname#08
652 if (!alpha_string.empty()) {
653 if (alpha_string.length() != 2)
654 return false;
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
665 color = video::SColor(color_temp);
667 return true;
670 void str_replace(std::string &str, char from, char to)
672 std::replace(str.begin(), str.end(), from, to);
675 /* Translated strings have the following format:
676 * \x1bT marks the beginning of a translated string
677 * \x1bE marks its end
679 * \x1bF marks the beginning of an argument, and \x1bE its end.
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.
684 * This representation is chosen so that clients ignoring escape codes will
685 * see untranslated strings.
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.
697 void translate_all(const std::wstring &s, size_t &i,
698 Translations *translations, std::wstring &res);
700 void translate_string(const std::wstring &s, Translations *translations,
701 const std::wstring &textdomain, size_t &i, std::wstring &res)
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;
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;
734 length = i - start_index;
735 ++i;
736 if (i > s.length())
737 i = s.length();
738 } else {
739 ++i;
740 length = 1;
742 std::wstring escape_sequence(s, start_index, length);
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;
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;
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();
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;
792 ++j;
793 // Literal escape for '@'.
794 if (toutput[j] == L'@') {
795 result.put(L'@');
796 ++j;
797 continue;
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;
810 res = result.str();
813 void translate_all(const std::wstring &s, size_t &i,
814 Translations *translations, std::wstring &res)
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;
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;
841 ++i;
843 length = i - start_index;
844 ++i;
845 if (i > s.length())
846 i = s.length();
847 } else {
848 ++i;
849 length = 1;
851 std::wstring escape_sequence(s, start_index, length);
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);
872 res = output.str();
875 // Translate string server side
876 std::wstring translate_string(const std::wstring &s, Translations *translations)
878 size_t i = 0;
879 std::wstring res;
880 translate_all(s, i, translations, res);
881 return res;
884 // Translate string client side
885 std::wstring translate_string(const std::wstring &s)
887 #ifdef SERVER
888 return translate_string(s, nullptr);
889 #else
890 return translate_string(s, g_client_translations);
891 #endif
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",
922 * List of characters that are blacklisted from created directories
924 static const std::wstring disallowed_path_chars = L"<>:\"/\\|?*.";
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 '_'
932 std::string sanitizeDirName(const std::string &str, const std::string &optional_prefix)
934 std::wstring safe_name = utf8_to_wide(str);
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;
943 for (unsigned long i = 0; i < safe_name.length(); i++) {
944 bool is_valid = true;
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;
954 if (!is_valid)
955 safe_name[i] = '_';
958 return wide_to_utf8(safe_name);