Luanti 5.15.0-dev
 
Loading...
Searching...
No Matches
string.h
Go to the documentation of this file.
1// Luanti
2// SPDX-License-Identifier: LGPL-2.1-or-later
3// Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5#pragma once
6
8#include "config.h" // IS_CLIENT_BUILD
9#if IS_CLIENT_BUILD
10#include "irrString.h"
11#endif
12#include <cstdlib>
13#include <string>
14#include <string_view>
15#include <cstring>
16#include <vector>
17#include <limits>
18#include <sstream>
19#include <iomanip>
20#include <cctype>
21#include <cwctype>
22#include <unordered_map>
23#include <optional>
24
25class Translations;
26
27#define STRINGIFY(x) #x
28#define TOSTRING(x) STRINGIFY(x)
29
30// Checks whether a value is an ASCII printable character
31#define IS_ASCII_PRINTABLE_CHAR(x) \
32 (((unsigned int)(x) >= 0x20) && \
33 ( (unsigned int)(x) <= 0x7e))
34
35// Checks whether a value is in a Unicode private use area
36#define IS_PRIVATE_USE_CHAR16(x) \
37 ((wchar_t)(x) >= 0xE000 && \
38 (wchar_t)(x) <= 0xF8FF)
39#define IS_PRIVATE_USE_CHAR32(x) \
40 (((wchar_t)(x) >= 0xF0000 && \
41 (wchar_t)(x) <= 0xFFFFD) || \
42 ((wchar_t)(x) >= 0x100000 && \
43 (wchar_t)(x) <= 0x10FFFD))
44#if WCHAR_MAX > 0xFFFF
45#define IS_PRIVATE_USE_CHAR(x) (IS_PRIVATE_USE_CHAR16(x) || IS_PRIVATE_USE_CHAR32(x))
46#else
47#define IS_PRIVATE_USE_CHAR(x) IS_PRIVATE_USE_CHAR16(x)
48#endif
49
50// Checks whether a byte is an inner byte for an utf-8 multibyte sequence
51#define IS_UTF8_MULTB_INNER(x) \
52 (((unsigned char)(x) >= 0x80) && \
53 ( (unsigned char)(x) <= 0xbf))
54
55// Checks whether a byte is a start byte for an utf-8 multibyte sequence
56#define IS_UTF8_MULTB_START(x) \
57 (((unsigned char)(x) >= 0xc2) && \
58 ( (unsigned char)(x) <= 0xf4))
59
60// Given a start byte x for an utf-8 multibyte sequence
61// it gives the length of the whole sequence in bytes.
62#define UTF8_MULTB_START_LEN(x) \
63 (((unsigned char)(x) < 0xe0) ? 2 : \
64 (((unsigned char)(x) < 0xf0) ? 3 : 4))
65
66typedef std::unordered_map<std::string, std::string> StringMap;
67
68struct FlagDesc {
69 const char *name;
70 u32 flag;
71};
72
73// Try to avoid converting between wide and UTF-8 unless you need to
74// input/output stuff via Irrlicht
75[[nodiscard]] std::wstring utf8_to_wide(std::string_view input);
76[[nodiscard]] std::string wide_to_utf8(std::wstring_view input);
77
78void wide_add_codepoint(std::wstring &result, char32_t codepoint);
79
80std::string urlencode(std::string_view str);
81std::string urldecode(std::string_view str);
82
83u32 readFlagString(std::string str, const FlagDesc *flagdesc, u32 *flagmask);
84std::string writeFlagString(u32 flags, const FlagDesc *flagdesc, u32 flagmask);
85
86size_t mystrlcpy(char *dst, const char *src, size_t size) noexcept;
87
89u64 read_seed(const char *str);
90
91bool parseColorString(const std::string &value, video::SColor &color, bool quiet,
92 unsigned char default_alpha = 0xff);
93std::string encodeHexColorString(video::SColor color);
94
100inline char my_tolower(char c)
101{
102 // By design this function cannot handle any Unicode (codepoints don't fit into char),
103 // but make sure to pass it through unchanged.
104 // tolower() can mangle it if the POSIX locale is not UTF-8.
105 if (static_cast<unsigned char>(c) > 0x7f)
106 return c;
107 // toupper(3): "If the argument c is of type char, it must be cast to unsigned char"
108 return tolower(static_cast<unsigned char>(c));
109}
110
116inline std::string padStringRight(std::string str, size_t len)
117{
118 if (len > str.size())
119 str.insert(str.end(), len - str.size(), ' ');
120
121 return str;
122}
123
135inline std::string_view removeStringEnd(std::string_view str,
136 const char *ends[])
137{
138 const char **p = ends;
139
140 for (; *p && (*p)[0] != '\0'; p++) {
141 std::string_view end(*p);
142 if (str.size() < end.size())
143 continue;
144 if (str.compare(str.size() - end.size(), end.size(), end) == 0)
145 return str.substr(0, str.size() - end.size());
146 }
147
148 return std::string_view();
149}
150
151
152#define MAKE_VARIANT(_name, _t0, _t1) \
153 template <typename T, typename... Args> \
154 inline auto _name(_t0 arg1, _t1 arg2, Args&&... args) \
155 { \
156 return (_name)(std::basic_string_view<T>(arg1), std::basic_string_view<T>(arg2), \
157 std::forward<Args>(args)...); \
158 }
159
160
170template <typename T>
171inline bool str_equal(std::basic_string_view<T> s1,
172 std::basic_string_view<T> s2,
173 bool case_insensitive = false)
174{
175 if (!case_insensitive)
176 return s1 == s2;
177
178 if (s1.size() != s2.size())
179 return false;
180
181 for (size_t i = 0; i < s1.size(); ++i)
182 if (my_tolower(s1[i]) != my_tolower(s2[i]))
183 return false;
184
185 return true;
186}
187
188// For some reason an std::string will not implicitly get converted
189// to an std::basic_string_view<char> in the template case above, so we need
190// these three wrappers. It works if you take out the template parameters.
191// see also <https://stackoverflow.com/questions/68380141/>
192MAKE_VARIANT(str_equal, const std::basic_string<T> &, const std::basic_string<T> &)
193
194MAKE_VARIANT(str_equal, std::basic_string_view<T>, const std::basic_string<T> &)
195
196MAKE_VARIANT(str_equal, const std::basic_string<T> &, std::basic_string_view<T>)
197
198
199
209template <typename T>
210inline bool str_starts_with(std::basic_string_view<T> str,
211 std::basic_string_view<T> prefix,
212 bool case_insensitive = false)
213{
214 if (str.size() < prefix.size())
215 return false;
216
217 if (!case_insensitive)
218 return str.compare(0, prefix.size(), prefix) == 0;
219
220 for (size_t i = 0; i < prefix.size(); ++i)
221 if (my_tolower(str[i]) != my_tolower(prefix[i]))
222 return false;
223 return true;
224}
225
226// (same conversion issue here)
227MAKE_VARIANT(str_starts_with, const std::basic_string<T> &, const std::basic_string<T> &)
228
229MAKE_VARIANT(str_starts_with, std::basic_string_view<T>, const std::basic_string<T> &)
230
231MAKE_VARIANT(str_starts_with, const std::basic_string<T> &, std::basic_string_view<T>)
232
233// (the same but with char pointers, only for the prefix argument)
234MAKE_VARIANT(str_starts_with, const std::basic_string<T> &, const T*)
235
236MAKE_VARIANT(str_starts_with, std::basic_string_view<T>, const T*)
237
238
239
249template <typename T>
250inline bool str_ends_with(std::basic_string_view<T> str,
251 std::basic_string_view<T> suffix,
252 bool case_insensitive = false)
253{
254 if (str.size() < suffix.size())
255 return false;
256
257 size_t start = str.size() - suffix.size();
258 if (!case_insensitive)
259 return str.compare(start, suffix.size(), suffix) == 0;
260
261 for (size_t i = 0; i < suffix.size(); ++i)
262 if (my_tolower(str[start + i]) != my_tolower(suffix[i]))
263 return false;
264 return true;
265}
266
267// (same conversion issue here)
268MAKE_VARIANT(str_ends_with, const std::basic_string<T> &, const std::basic_string<T> &)
269
270MAKE_VARIANT(str_ends_with, std::basic_string_view<T>, const std::basic_string<T> &)
271
272MAKE_VARIANT(str_ends_with, const std::basic_string<T> &, std::basic_string_view<T>)
273
274// (the same but with char pointers, only for the suffix argument)
275MAKE_VARIANT(str_ends_with, const std::basic_string<T> &, const T*)
276
277MAKE_VARIANT(str_ends_with, std::basic_string_view<T>, const T*)
278
279
280#undef MAKE_VARIANT
281
282
289template <typename T>
290[[nodiscard]]
291inline std::vector<std::basic_string<T> > str_split(
292 const std::basic_string<T> &str,
293 T delimiter)
294{
295 std::vector<std::basic_string<T> > parts;
296 std::basic_stringstream<T> sstr(str);
297 std::basic_string<T> part;
298
299 while (std::getline(sstr, part, delimiter))
300 parts.push_back(part);
301
302 return parts;
303}
304
305
310[[nodiscard]]
311inline std::string lowercase(std::string_view str)
312{
313 std::string s2;
314 s2.resize(str.size());
315 for (size_t i = 0; i < str.size(); i++)
316 s2[i] = my_tolower(str[i]);
317 return s2;
318}
319
320
321inline bool my_isspace(const char c)
322{
323 return std::isspace(c);
324}
325
326inline bool my_isspace(const wchar_t c)
327{
328 return std::iswspace(c);
329}
330
335template<typename T>
336[[nodiscard]]
337inline std::basic_string_view<T> trim(std::basic_string_view<T> str)
338{
339 size_t front = 0;
340 size_t back = str.size();
341
342 while (front < back && my_isspace(str[front]))
343 ++front;
344
345 while (back > front && my_isspace(str[back - 1]))
346 --back;
347
348 return str.substr(front, back - front);
349}
350
351// If input was a temporary string keep it one to make sure patterns like
352// trim(func_that_returns_str()) are predictable regarding memory allocation
353// and don't lead to UAF. ↓ ↓ ↓
354
359template<typename T>
360[[nodiscard]]
361inline std::basic_string<T> trim(std::basic_string<T> &&str)
362{
363 std::basic_string<T> ret(trim(std::basic_string_view<T>(str)));
364 return ret;
365}
366
367template<typename T>
368[[nodiscard]]
369inline std::basic_string_view<T> trim(const std::basic_string<T> &str)
370{
371 return trim(std::basic_string_view<T>(str));
372}
373
374// The above declaration causes ambiguity with char pointers so we have to fix that:
375template<typename T>
376[[nodiscard]]
377inline std::basic_string_view<T> trim(const T *str)
378{
379 return trim(std::basic_string_view<T>(str));
380}
381
382
389inline bool is_yes(std::string_view str)
390{
391 std::string s2 = lowercase(trim(str));
392
393 return s2 == "y" || s2 == "yes" || s2 == "true" || atoi(s2.c_str()) != 0;
394}
395
396
409inline s32 mystoi(const std::string &str, s32 min, s32 max)
410{
411 s32 i = atoi(str.c_str());
412
413 if (i < min)
414 i = min;
415 if (i > max)
416 i = max;
417
418 return i;
419}
420
425inline s32 mystoi(const std::string &str)
426{
427 return atoi(str.c_str());
428}
429
434inline float mystof(const std::string &str)
435{
436 return atof(str.c_str());
437}
438
439#define stoi mystoi
440#define stof mystof
441
443template <typename T>
444inline T from_string(const std::string &str)
445{
446 std::istringstream tmp(str);
447 T t;
448 tmp >> t;
449 return t;
450}
451
453inline s64 stoi64(const std::string &str) { return from_string<s64>(str); }
454
456inline std::string itos(s32 i) { return std::to_string(i); }
458inline std::string i64tos(s64 i) { return std::to_string(i); }
459
461inline std::string ftos(float f)
462{
463 std::ostringstream oss;
464 oss << std::setprecision(std::numeric_limits<float>::max_digits10) << f;
465 return oss.str();
466}
467
469std::string my_double_to_string(double number);
471std::optional<double> my_string_to_double(const std::string &s);
472
480inline void str_replace(std::string &str, std::string_view pattern,
481 std::string_view replacement)
482{
483 std::string::size_type start = str.find(pattern, 0);
484 while (start != str.npos) {
485 str.replace(start, pattern.size(), replacement);
486 start = str.find(pattern, start + replacement.size());
487 }
488}
489
493inline void str_formspec_escape(std::string &str)
494{
495 str_replace(str, "\\", "\\\\");
496 str_replace(str, "]", "\\]");
497 str_replace(str, "[", "\\[");
498 str_replace(str, ";", "\\;");
499 str_replace(str, ",", "\\,");
500 str_replace(str, "$", "\\$");
501}
502
506inline void str_texture_modifiers_escape(std::string &str)
507{
508 str_replace(str, "\\", "\\\\");
509 str_replace(str, "^", "\\^");
510 str_replace(str, ":", "\\:");
511}
512
520void str_replace(std::string &str, char from, char to);
521
522
533inline bool string_allowed(std::string_view str, std::string_view allowed_chars)
534{
535 return str.find_first_not_of(allowed_chars) == str.npos;
536}
537
538
549inline bool string_allowed_blacklist(std::string_view str,
550 std::string_view blacklisted_chars)
551{
552 return str.find_first_of(blacklisted_chars) == str.npos;
553}
554
555
572std::string wrap_rows(std::string_view from, unsigned row_len, bool has_color_codes = false);
573
574
578template <typename T>
579[[nodiscard]]
580inline std::basic_string<T> unescape_string(const std::basic_string<T> &s)
581{
582 std::basic_string<T> res;
583 res.reserve(s.size());
584
585 for (size_t i = 0; i < s.length(); i++) {
586 if (s[i] == '\\') {
587 i++;
588 if (i >= s.length())
589 break;
590 }
591 res += s[i];
592 }
593
594 return res;
595}
596
603template <typename T>
604[[nodiscard]]
605std::basic_string<T> unescape_enriched(std::basic_string_view<T> s)
606{
607 std::basic_string<T> output;
608 output.reserve(s.size());
609 size_t i = 0;
610 while (i < s.length()) {
611 if (s[i] == static_cast<T>('\x1b')) {
612 ++i;
613 if (i == s.length())
614 continue;
615 if (s[i] == static_cast<T>('(')) {
616 ++i;
617 while (i < s.length() && s[i] != static_cast<T>(')')) {
618 if (s[i] == static_cast<T>('\\'))
619 ++i;
620 ++i;
621 }
622 }
623 ++i;
624 continue;
625 }
626 output += s[i];
627 ++i;
628 }
629 return output;
630}
631
632// (same templating issue here)
633[[nodiscard]]
634inline std::string unescape_enriched(std::string_view s)
635{
636 return unescape_enriched<char>(s);
637}
638[[nodiscard]]
639inline std::wstring unescape_enriched(std::wstring_view s)
640{
642}
643
644template <typename T>
645[[nodiscard]]
646std::vector<std::basic_string<T> > split(const std::basic_string<T> &s, T delim)
647{
648 std::vector<std::basic_string<T> > tokens;
649
650 std::basic_string<T> current;
651 bool last_was_escape = false;
652 for (size_t i = 0; i < s.length(); i++) {
653 T si = s[i];
654 if (last_was_escape) {
655 current += '\\';
656 current += si;
657 last_was_escape = false;
658 } else {
659 if (si == delim) {
660 tokens.push_back(current);
661 current.clear();
662 last_was_escape = false;
663 } else if (si == '\\') {
664 last_was_escape = true;
665 } else {
666 current += si;
667 last_was_escape = false;
668 }
669 }
670 }
671 //push last element
672 tokens.push_back(current);
673
674 return tokens;
675}
676
677[[nodiscard]]
678std::wstring translate_string(std::wstring_view s, Translations *translations);
679
680[[nodiscard]]
681std::wstring translate_string(std::wstring_view s);
682
683[[nodiscard]]
684inline std::wstring unescape_translate(std::wstring_view s)
685{
687}
688
696inline bool is_number(std::string_view to_check)
697{
698 for (char i : to_check)
699 if (!std::isdigit(i))
700 return false;
701
702 return !to_check.empty();
703}
704
705
711inline const char *bool_to_cstr(bool val)
712{
713 return val ? "true" : "false";
714}
715
723inline const std::string duration_to_string(int sec)
724{
725 std::ostringstream ss;
726 const char *neg = "";
727 if (sec < 0) {
728 sec = -sec;
729 neg = "-";
730 }
731 int total_sec = sec;
732 int min = sec / 60;
733 sec %= 60;
734 int hour = min / 60;
735 min %= 60;
736 int day = hour / 24;
737 hour %= 24;
738
739 if (day > 0) {
740 ss << neg << day << "d";
741 if (hour > 0 || min > 0 || sec > 0)
742 ss << " ";
743 }
744
745 if (hour > 0) {
746 ss << neg << hour << "h";
747 if (min > 0 || sec > 0)
748 ss << " ";
749 }
750
751 if (min > 0) {
752 ss << neg << min << "min";
753 if (sec > 0)
754 ss << " ";
755 }
756
757 if (sec > 0 || total_sec == 0) {
758 ss << neg << sec << "s";
759 }
760
761 return ss.str();
762}
763
769[[nodiscard]]
770inline std::string str_join(const std::vector<std::string> &list,
771 std::string_view delimiter)
772{
773 std::ostringstream oss;
774 bool first = true;
775 for (const auto &part : list) {
776 if (!first)
777 oss << delimiter;
778 oss << part;
779 first = false;
780 }
781 return oss.str();
782}
783
784#if IS_CLIENT_BUILD
788[[nodiscard]]
789inline std::string stringw_to_utf8(const core::stringw &input)
790{
791 std::wstring_view sv(input.c_str(), input.size());
792 return wide_to_utf8(sv);
793}
794
798[[nodiscard]]
799inline core::stringw utf8_to_stringw(std::string_view input)
800{
801 std::wstring str = utf8_to_wide(input);
802 return core::stringw(str.c_str(), str.size());
803}
804#endif
805
812[[nodiscard]]
813std::string sanitizeDirName(std::string_view str, std::string_view optional_prefix);
814
822[[nodiscard]]
823std::string sanitize_untrusted(std::string_view str, bool keep_escapes = true);
824
831void safe_print_string(std::ostream &os, std::string_view str);
832
839std::optional<v3f> str_to_v3f(std::string_view str);
Definition translation.h:18
std::optional< v3f > str_to_v3f(std::string_view str)
Parses a string of form (1, 2, 3) or 1, 2, 4 to a v3f.
Definition string.cpp:1030
bool my_isspace(const char c)
Definition string.h:321
s64 stoi64(const std::string &str)
Returns a 64-bit signed value represented by the string str (decimal).
Definition string.h:453
std::string writeFlagString(u32 flags, const FlagDesc *flagdesc, u32 flagmask)
Definition string.cpp:261
bool string_allowed(std::string_view str, std::string_view allowed_chars)
Check that a string only contains whitelisted characters.
Definition string.h:533
std::string wide_to_utf8(std::wstring_view input)
Definition string.cpp:115
std::string my_double_to_string(double number)
Converts double to string. Handles high precision and inf/nan.
Definition string.cpp:1069
bool str_equal(std::basic_string_view< T > s1, std::basic_string_view< T > s2, bool case_insensitive=false)
Check two strings for equivalence.
Definition string.h:171
std::string sanitize_untrusted(std::string_view str, bool keep_escapes=true)
Sanitize an untrusted string (e.g.
Definition string.cpp:980
u32 readFlagString(std::string str, const FlagDesc *flagdesc, u32 *flagmask)
Definition string.cpp:225
std::optional< double > my_string_to_double(const std::string &s)
Converts string to double. Handles high precision and inf/nan.
Definition string.cpp:1083
std::wstring unescape_translate(std::wstring_view s)
Definition string.h:684
void wide_add_codepoint(std::wstring &result, char32_t codepoint)
Definition string.cpp:170
const std::string duration_to_string(int sec)
Converts a duration in seconds to a pretty-printed duration in days, hours, minutes and seconds.
Definition string.h:723
const char * bool_to_cstr(bool val)
Returns a C-string, either "true" or "false", corresponding to val.
Definition string.h:711
std::wstring translate_string(std::wstring_view s, Translations *translations)
Definition string.cpp:871
char my_tolower(char c)
Converts a letter to lowercase, with safe handling of the char type and non-ASCII.
Definition string.h:100
std::vector< std::basic_string< T > > split(const std::basic_string< T > &s, T delim)
Definition string.h:646
std::string wrap_rows(std::string_view from, unsigned row_len, bool has_color_codes=false)
Create a string based on from where a newline is forcefully inserted every row_len characters.
Definition string.cpp:594
bool parseColorString(const std::string &value, video::SColor &color, bool quiet, unsigned char default_alpha=0xff)
Definition string.cpp:559
void str_replace(std::string &str, std::string_view pattern, std::string_view replacement)
Replace all occurrences of pattern in str with replacement.
Definition string.h:480
T from_string(const std::string &str)
Returns a value represented by the string val.
Definition string.h:444
std::vector< std::basic_string< T > > str_split(const std::basic_string< T > &str, T delimiter)
Splits a string into its component parts separated by the character delimiter.
Definition string.h:291
std::string sanitizeDirName(std::string_view str, std::string_view optional_prefix)
Sanitize the name of a new directory.
Definition string.cpp:927
std::unordered_map< std::string, std::string > StringMap
Definition string.h:66
bool str_ends_with(std::basic_string_view< T > str, std::basic_string_view< T > suffix, bool case_insensitive=false)
Check whether str ends with the string suffix.
Definition string.h:250
std::string urlencode(std::string_view str)
Definition string.cpp:187
bool string_allowed_blacklist(std::string_view str, std::string_view blacklisted_chars)
Check that a string contains no blacklisted characters.
Definition string.h:549
size_t mystrlcpy(char *dst, const char *src, size_t size) noexcept
Definition string.cpp:282
std::string str_join(const std::vector< std::string > &list, std::string_view delimiter)
Joins a vector of strings by the string delimiter.
Definition string.h:770
void str_texture_modifiers_escape(std::string &str)
Escapes characters to nest texture modifiers.
Definition string.h:506
std::string urldecode(std::string_view str)
Definition string.cpp:206
std::string padStringRight(std::string str, size_t len)
Returns a copy of str with spaces inserted at the right hand side to ensure that the string is len ch...
Definition string.h:116
bool str_starts_with(std::basic_string_view< T > str, std::basic_string_view< T > prefix, bool case_insensitive=false)
Check whether str begins with the string prefix.
Definition string.h:210
float mystof(const std::string &str)
Returns a float reprensented by the string str (decimal).
Definition string.h:434
std::string itos(s32 i)
Returns a string representing the decimal value of the 32-bit value i.
Definition string.h:456
void str_formspec_escape(std::string &str)
Escapes characters that cannot be used in formspecs.
Definition string.h:493
void safe_print_string(std::ostream &os, std::string_view str)
Prints a sanitized version of a string without control characters.
Definition string.cpp:1015
std::string i64tos(s64 i)
Returns a string representing the decimal value of the 64-bit value i.
Definition string.h:458
std::string lowercase(std::string_view str)
Definition string.h:311
std::string ftos(float f)
Returns a string representing the exact decimal value of the float value f.
Definition string.h:461
std::string_view removeStringEnd(std::string_view str, const char *ends[])
Returns a version of str with the first occurrence of a string contained within ends[] removed from t...
Definition string.h:135
std::string encodeHexColorString(video::SColor color)
Definition string.cpp:575
#define MAKE_VARIANT(_name, _t0, _t1)
Definition string.h:152
std::wstring utf8_to_wide(std::string_view input)
Definition string.cpp:87
bool is_yes(std::string_view str)
Returns whether str should be regarded as (bool) true.
Definition string.h:389
std::basic_string< T > unescape_enriched(std::basic_string_view< T > s)
Remove all escape sequences in s.
Definition string.h:605
u64 read_seed(const char *str)
turn string into a map seed. either directly if it's a number or by hashing it.
Definition string.cpp:295
bool is_number(std::string_view to_check)
Checks that all characters in to_check are a decimal digits.
Definition string.h:696
std::basic_string< T > unescape_string(const std::basic_string< T > &s)
Removes backslashes from an escaped string (FormSpec strings)
Definition string.h:580
std::basic_string_view< T > trim(std::basic_string_view< T > str)
Definition string.h:337
s32 mystoi(const std::string &str, s32 min, s32 max)
Converts the string str to a signed 32-bit integer.
Definition string.h:409
Definition string.h:68
u32 flag
Definition string.h:70
const char * name
Definition string.h:69
static std::string p(std::string path)
Definition test_filesys.cpp:64