3#include <algorithm> // std::max and std::min
4#include <array> // std::array
5#include <cstring> // std::strlen
6#include <ctime> // std::time
7#include <exception> // std::exception::runtime_error
8#include <iostream> // std::cout
9#include <stdexcept> // std::runtime_error
10#include <string> // std::string
11#include <unordered_map> // std::unordered_map
12#include <vector> // std::vector
16 /*======================================================================*//**
18 This @ref rtools class primarily provides a collection of static methods that
19 facilitate a variety of general-purpose computer programming needs. Separate
20 classes may also be added in the future for more sophisticated needs.
22 - 2023-May-17 v1.00 Initial version
23 - 2024-Oct-23 v1.00 Added three more well-known base64 sets, added support
24 for base64 encoding/decoding to use ASCIIZ strings
25 for inputs, and made various minor improvements to the
26 documentation since the previous update
27 - 2024-Nov-24 v1.00 Added @c delimiter parameter to two @ref to_hex methods
28 - 2024-Nov-25 v1.00 Added support for negative positions in @ref to_lower
29 and @ref to_upper methods so that they count backward
30 from the end of the string
31 - 2025-Jan-20 v1.00 Removed atomize methods after rebuilding these in a
32 separate class called @ref randolf::Atomize
33 - 2025-Feb-03 v1.00 Increased use of references and pointers
35 @author Randolf Richardson
36 *///=========================================================================
40 /*======================================================================*//**
42 This character set is suggested by RFC4648 (see page 8) as "safe" for use in
46 *///=========================================================================
47 inline static const char base64_set_minus_underscore[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
49 /*======================================================================*//**
51 This character set is normally used to encode IMAP4 mailbox names.
54 *///=========================================================================
55 inline static const char base64_set_plus_comma[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
57 /*======================================================================*//**
59 This character set is the default in every @c base64_ method in this library
60 because it's the most commonly used for base64 encoding.
62 Although other well-known base64 character sets are included here, or you can
63 create your own, which has a simple format -- it must be 64 characters long
64 without a NULL terminator (additional characters will be ignored), and each
65 character can only be specified once (or else encoding or decoding will fail
66 to render consistent results; there's no checking performed beforehand since
67 the software developers providing these customized character sets are trusted
68 to not introduce such problems).
71 *///=========================================================================
72 inline static const char base64_set_plus_slash[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
74 /*======================================================================*//**
76 This is an alternate character set that is rarely used, but is mentioned in
77 RFC4648 (see page 7, second paragraph of section 5).
79 RFC4648 incorrectly specifies the 63rd character when it's the 64th character
80 (the slash) that's being replaced with a tilde. This error likely came from
81 not counting the zero "value" label when referencing the set in which the 1st
82 character is labeled as having a value of 0.
85 *///=========================================================================
86 inline static const char base64_set_plus_tilde[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+~";
88 /*======================================================================*//**
90 Decode a Base64-encoded @c ASCIIZ string.
92 All invalid characters are simply ignored.
93 @returns Decoded string
95 @see base64_set_plus_slash
96 *///=========================================================================
97 static std::string base64_decode(
98 /// ASCIIZ string to decode
100 /// Base64 character set to use
101 const std::string& b = base64_set_plus_slash) {
103 std::vector<int> T(256, -1);
106 for (int i = 0; i < 64; i++) T[b[i]] = i;
109 while ((c = (unsigned char)*in++) != 0) {
110 if (T[c] == -1) break;
111 val = (val << 6) + T[c];
114 out.push_back(char((val >> valb) & 0xFF));
119 } // -x- std::string base64_decode -x-
121 /*======================================================================*//**
123 Decode a Base64-encoded @c std::string.
125 All invalid characters are simply ignored.
126 @returns Decoded string
128 @see base64_set_plus_slash
129 *///=========================================================================
130 static std::string base64_decode(
132 const std::string& in,
133 /// Base64 character set to use
134 const std::string& b = base64_set_plus_slash) {
136 std::vector<int> T(256, -1);
139 for (int i = 0; i < 64; i++) T[b[i]] = i;
141 for (unsigned char c: in) {
142 if (T[c] == -1) break;
143 val = (val << 6) + T[c];
146 out.push_back(char((val >> valb) & 0xFF));
151 } // -x- std::string base64_decode -x-
153 /*======================================================================*//**
155 Encode an @c ASCIIZ string into Base64 format.
157 All invalid characters are simply ignored.
158 @returns Base64-encoded string
160 @see base64_set_plus_slash
161 *///=========================================================================
162 static std::string base64_encode(
165 /// Base64 character set to use
166 const std::string& b = base64_set_plus_slash) {
172 while ((c = (unsigned char)*in++) != 0) {
173 val = (val << 8) + c;
176 out.push_back(b[(val >> valb) & 0x3F]);
178 } // -x- while valb -x-
180 if (valb >- 6) out.push_back(b[((val << 8) >> (valb + 8)) & 0x3F]);
181 while (out.size() % 4) out.push_back('=');
183 } // -x- std::string base64_encode -x-
185 /*======================================================================*//**
187 Encode an @c std::string into Base64 format.
189 All invalid characters are simply ignored.
190 @returns Base64-encoded string
192 @see base64_set_plus_slash
193 *///=========================================================================
194 static std::string base64_encode(
196 const std::string& in,
197 /// Base64 character set to use
198 const std::string& b = base64_set_plus_slash) {
203 for (unsigned char c: in) {
204 val = (val << 8) + c;
207 out.push_back(b[(val >> valb) & 0x3F]);
209 } // -x- while valb -x-
211 if (valb >- 6) out.push_back(b[((val << 8) >> (valb + 8)) & 0x3F]);
212 while (out.size() % 4) out.push_back('=');
214 } // -x- std::string base64_encode -x-
216 /*======================================================================*//**
218 Insert commas into the last numeric sequence of digits in the supplied string
219 and insert spaces before that (commas and spaces are configurable). If a
220 decimal point is found, then comma insertions will only occur before that
221 (this is also configurable).
222 @returns Numeric value as a char* array converted to a properly-delimited
223 string as an std::string
224 *///=========================================================================
225 static std::string insert_commas(
226 /// Pointer to ASCII representation of numeric value
228 /// Length of value (in bytes), or 0 to auto-detect length if value string is an ASCIIZ string
230 /// Don't insert any commas after the last period (or whatever string is set as the @c dot character)
231 bool skip_dot = true,
232 /// Number of digits between commas
233 const int digits = 3,
234 /// Pointer to ASCIIZ comma character string (nullptr = disabled)
235 const char* comma = ",",
236 /// Pointer to ASCIIZ space character string (nullptr = disabled) used instead of commas for non-digit fill-ins where commas would normally be inserted
237 const char* space = " ",
238 /// Period character used when @c skip_dot is enabled
239 const char dot = '.') noexcept {
241 // --------------------------------------------------------------------------
242 // Measure size of format string if an ASCIIZ string was indicated.
243 // --------------------------------------------------------------------------
244 if (len == 0) len = std::strlen(value);
246 // --------------------------------------------------------------------------
247 // Find the dot, and adjust len accordingly.
248 // --------------------------------------------------------------------------
249 for (int i = len - 1; i > 0; i--) {
250 if (value[i] == dot) {
256 // --------------------------------------------------------------------------
257 // Internal variables.
258 // --------------------------------------------------------------------------
259 std::string v(value, len);
260 const int m = len % digits; // Modulus for every 3rd character
261 bool blank = false; // Add blank space instead of comma
263 // --------------------------------------------------------------------------
264 // Insert commas as long as there are digits.
265 // --------------------------------------------------------------------------
266 for (int i = len - 1; i > 0; i--) {
267 if (v[i] < '0' || v[i] > '9') blank = true; // Not a digit, so we're switching from commas to blanks (spaces)
268 if ((i % digits) == m) { // This is where a separator belongs
269 if (!blank && comma != nullptr) v.insert(i, comma); // Insert comma, if one is defined
270 else if (blank && space != nullptr) v.insert(i, space); // Insert space, if one is defined
275 } // -x- std::string insert_commas -x-
277 /*======================================================================*//**
279 Parses a SASL exchange, returning an @c std::unordered_map of the key-value
280 pairs that were encountered.
281 @throws std::runtime_error If a SASL message is improperly formatted (the
282 error message includes the offset where the format problem occurred)
283 @returns Key-value pairs in an unordered map where the key is an std::string
284 object and the value is a vector of std::string objects
285 *///=========================================================================
286 static std::unordered_map<std::string, std::vector<std::string>> parse_sasl_exchange(
287 /// Unparsed SASL exchange string (must not be Base64-encoded)
288 const std::string& sasl,
289 /// Ensure the following keys exist, each with one empty string: nonce, nc, cnonce, qop, realm, username, digest-uri, authzid
290 const bool add_missing_keys = false) {
292 // --------------------------------------------------------------------------
293 // Internal variables.
294 // --------------------------------------------------------------------------
295 std::unordered_map<std::string, std::vector<std::string>> map;
296 char ch; // Used in string parsing
297 std::string temp; // Used to build current element
298 std::string key; // Key name
299 bool keyMode = true; // TRUE = parsing key name; FALSE = parsing value
300 bool quoteMode = false; // Used for tracking "quote mode" with quotation mark usage
301 int offset = -1; // Offset within the unparsed string of the character currently being processed
302 const int MAX = sasl.size();
304 // --------------------------------------------------------------------------
305 // Parse the string, one character at a time.
306 // --------------------------------------------------------------------------
307 for (offset = 0; offset < MAX; offset++) {
308 switch (ch = sasl[offset]) {
309 case '"': // Quotation mark
310 if (keyMode) throw std::runtime_error("Key names can't contain quotation marks at offset " + std::to_string(offset));
311 if (!quoteMode && temp.length() > 0) throw std::runtime_error("Malformed quoted value at offset " + std::to_string(offset));
312 quoteMode = !quoteMode; // Toggle "quote mode"
313 temp.push_back(ch); // Save quotation mark
315 case '\\': // Back-slash
316 if (keyMode) throw std::runtime_error("Key names can't contain escaped literal characters at offset " + std::to_string(offset));
317 ch = sasl[++offset]; // Get next character after incrementing offset
318 if (offset >= MAX) throw std::runtime_error("Literal character is missing at offset " + std::to_string(offset));
319 if (ch < ' ' || ch == 127) throw std::runtime_error("Invalid charater at offset " + std::to_string(offset)); // Any characters except CTLs and separators
320 temp.push_back('\\'); // Save backslash
321 temp.push_back(ch); // Save literal character
324 if (!quoteMode) throw std::runtime_error("White space characters not permitted at offset " + std::to_string(offset));
327 case '=': // Equal sign (signifies end of key, and beginning of value)
332 if (!keyMode) throw std::runtime_error("Invalid character at offset " + std::to_string(offset));
333 keyMode = !keyMode; // Toggle flag
334 if (temp.length() == 0) throw std::runtime_error("Missing key name at offset " + std::to_string(offset));
335 key = temp; // Save string for later
336 temp = ""; // Clear temporary string
338 case ',': // Comma delimiter (signifies end of value)
343 if (keyMode) throw std::runtime_error("Invalid character at offset " + std::to_string(offset));
344 keyMode = !keyMode; // Toggle flag
345 if (temp[0] == '"' && temp[temp.length() - 1] != '"') throw std::runtime_error("Malformed quoted value at offset " + std::to_string(offset));
346 map[key].push_back(temp);
349 default: // Everything else is a literal character
350 if (ch < ' ' || ch == 127) throw std::runtime_error("Invalid character at offset " + std::to_string(offset)); // Any characters except CTLs and separators
351 if (keyMode && (std::string("()<>@,;:\\\"/[]?={} ").find_first_of(ch) != std::string::npos)) throw std::runtime_error("Invalid character at offset " + std::to_string(offset));
354 } // -x- switch ch -x-
357 // --------------------------------------------------------------------------
358 // Syntax checks. There is no need to check for additional data because we
359 // added a comma to the unparsed_string -- if the original unparsed_string
360 // had a trailing comma, then an exception would have been thrown already.
361 // --------------------------------------------------------------------------
362 if (quoteMode) throw std::runtime_error("Incomplete value at offset " + std::to_string(offset));
364 // --------------------------------------------------------------------------
365 // Save last key-value pair if we're not in keyMode.
366 // --------------------------------------------------------------------------
367 if (!keyMode) map[key].push_back(temp);
369 // --------------------------------------------------------------------------
370 // Add missing keys, each with one empty string.
371 // --------------------------------------------------------------------------
372 if (add_missing_keys) {
373 if (map.try_emplace("nonce", std::vector<std::string>()).second) { map["nonce" ].push_back(""); };
374 if (map.try_emplace("nc", std::vector<std::string>()).second) { map["nc" ].push_back(""); };
375 if (map.try_emplace("cnonce", std::vector<std::string>()).second) { map["cnonce" ].push_back(""); };
376 if (map.try_emplace("qop", std::vector<std::string>()).second) { map["qop" ].push_back(""); };
377 if (map.try_emplace("realm", std::vector<std::string>()).second) { map["realm" ].push_back(""); };
378 if (map.try_emplace("username", std::vector<std::string>()).second) { map["username" ].push_back(""); };
379 if (map.try_emplace("digest-uri", std::vector<std::string>()).second) { map["digest-uri"].push_back(""); };
380 if (map.try_emplace("authzid", std::vector<std::string>()).second) { map["authzid" ].push_back(""); };
381 } // -x- if add_missing_keys -x-
383 // --------------------------------------------------------------------------
384 // Return the newly-created unordered_map.
385 // --------------------------------------------------------------------------
388 } // -x- std::unordered_map<std::string, std::vector<std::string>> parse_sasl_exchange -x-
390 /*======================================================================*//**
391 @copydoc split(const char, const char*, const int, const bool)
392 *///=========================================================================
393 static std::vector<std::string> split(
394 /// Character to use for the delimiter
395 const char delimiter,
396 /// Source string (to be split into atoms)
397 const std::string& str,
398 /// Length of string (in bytes), or 0 if using the full length of the string
400 /// Whether to include empty entries@n
401 /// TRUE = keep empty entries (default) @n
402 /// FALSE = exclude empty entries
403 const bool keep_empties = true) {
404 return split(delimiter, str.data(), len >= -1 ? str.size() : len, keep_empties);
405 } // -x- std::vector<std::string> split -x-
407 /*======================================================================*//**
409 Split string into an @c std::vector that's perfectly-sized to store all the
410 elements separated by a delimiter character. If no delimiters are
411 encountered, the resulting vector will contain the entire string as its only
412 element. If the string is empty, the resulting vector will contain an empty
413 string as its only element.
415 By default, we do @c keep_empties when a delimiter character is specified
416 because the character may be a @c comma ("`,`") for use in parsing a simple
417 list of items where position is important (e.g., simple CSV data), or may be
418 a @c tab (ASCII character @c 9) for use in parsing where position is also
422 Using (char)0 as a delimiter necessitates specifying the length of the source
423 string, otherwise the resulting vector will contain only the first element.
425 @returns Pointer to an array of "atoms" (strings) stored in an @c std::vector
427 *///=========================================================================
428 static std::vector<std::string> split(
429 /// Character to use for the delimiter
430 const char delimiter,
431 /// Source string (to be split)
433 /// Length of string (in bytes), or -1 if @c str is an ASCIIZ string (NULL-terminated)
435 /// Whether to include empty entries@n
436 /// TRUE = keep empty entries (default) @n
437 /// FALSE = exclude empty entries
438 const bool keep_empties = true) { // TODO: Add support for a "maximum_elements" parameter
440 // --------------------------------------------------------------------------
441 // Internal variables.
443 // Includes not measuring size of string if an ASCIIZ string was indicated
444 // because "if (len == -1) len = std::strlen(str);" is less optimal than what
445 // the main loop already does in this regard.
446 // --------------------------------------------------------------------------
447 const char* MAX = len == -1 ? str + std::strlen(str) : str + len; // Loop pre-optimization
450 // --------------------------------------------------------------------------
451 // Pre-allocate std::vector with a size that's half the length of the string,
452 // because this is the maximum number of atoms there could be if each atom is
453 // 1 byte long and each delimiting whitespace character is also 1 byte long.
454 // --------------------------------------------------------------------------
455 std::vector<std::string> ary;
456 ary.reserve(len >> 1); // Ensure array-like efficiency (in nearly all scenarios)
458 // --------------------------------------------------------------------------
460 // --------------------------------------------------------------------------
462 if (*str == delimiter) { // Delimiter character detected
464 if (!(!keep_empties && atom.empty())) // unless(!keep_empties && ary.empty())
465 ary.push_back(atom); // Save current atom to vector
466 atom.clear(); // Create new atom (string)
467 } else { // -x- if *str -x-
468 atom.append(str, 1); // Add this character to the atom
469 } // -x- if (char)0 -x-
470 } while (++str < MAX); // -x- while str -x-
472 // --------------------------------------------------------------------------
473 // Capture final atom. (This is a typical edge case because the final
474 // character in the string being split is usually not a delimiter character.)
475 // --------------------------------------------------------------------------
476 if (!(!keep_empties && atom.empty())) // unless(!keep_empties && ary.empty())
477 ary.push_back(atom); // Capture final atom
479 // --------------------------------------------------------------------------
480 // Shrink the array before returning it so sizing can be considered properly
481 // and unneeded memory can be freed.
482 // --------------------------------------------------------------------------
486 } // -x- std::vector<std::string> split -x-
488 /*======================================================================*//**
489 @copydoc split(const char*, const int, const bool)
490 *///=========================================================================
491 static std::vector<std::string> split(
492 /// Source string (to be split into atoms)
493 const std::string& str,
494 /// Length of string (in bytes), or 0 if using the full length of the string
496 /// Whether to include empty entries@n
497 /// TRUE = keep empty entries@n
498 /// FALSE = exclude empty entries (default)
499 const bool keep_empties = false) {
500 return split(str.data(), len >= -1 ? str.size() : len, keep_empties);
501 } // -x- std::vector<std::string> split -x-
503 /*======================================================================*//**
505 Split string into an @c std::vector that's perfectly-sized to store all the
506 elements separated by whitespace characters. If no whitespace characters are
507 encountered, the resulting vector will contain the entire string as its only
508 element. If the string is empty, the resulting vector will contain an empty
509 string as its only element.
511 By default, we don't @c keep_empties when filtering on hardcoded internal
512 whitespace because the @c CRLF EoL (End of Line) sequence is common.
515 The following are whitespace characters, and they are tested for in the order
516 shown here (which is optimized for what is most common):
519 - @c 13 carriage return
520 - @c 9 horizontal tab
521 - @c 0 NULL terminator
523 @returns Pointer to an array of "atoms" (strings) stored in an @c std::vector
525 *///=========================================================================
526 static std::vector<std::string> split(
527 /// Source string (to be split)
529 /// Length of string (in bytes), or -1 if @c str is an ASCIIZ string (NULL-terminated)
531 /// Whether to include empty entries@n
532 /// TRUE = keep empty entries@n
533 /// FALSE = exclude empty entries (default)
534 const bool keep_empties = false) { // TODO: Add support for a "maximum_elements" parameter
536 // --------------------------------------------------------------------------
537 // Internal variables.
539 // Includes not measuring size of string if an ASCIIZ string was indicated
540 // because "if (len == -1) len = std::strlen(str);" is less optimal than what
541 // the main loop already does in this regard.
542 // --------------------------------------------------------------------------
543 const char* MAX = len == -1 ? str + std::strlen(str) : str + len; // Loop pre-optimization
546 // --------------------------------------------------------------------------
547 // Pre-allocate std::vector with a size that's half the length of the string,
548 // because this is the maximum number of atoms there could be if each atom is
549 // 1 byte long and each delimiting whitespace character is also 1 byte long.
550 // --------------------------------------------------------------------------
551 std::vector<std::string> ary;
552 ary.reserve(len >> 1); // Ensure array-like efficiency (in nearly all scenarios)
554 // --------------------------------------------------------------------------
556 // --------------------------------------------------------------------------
558 if (*str == 32 || *str == 10 || *str == 13 || *str == 9 || *str == 0) { // Whitespace character detected
560 if (!(!keep_empties && atom.empty())) // unless(!keep_empties && ary.empty())
561 ary.push_back(atom); // Save current atom to vector
562 atom.clear(); // Create new atom (string)
563 } else { // -x- if *str -x-
564 atom.append(str, 1); // Add this character to the atom
565 } // -x- if (char)0 -x-
566 } while (++str < MAX); // -x- while str -x-
568 // --------------------------------------------------------------------------
569 // Capture final atom. (This is a typical edge case because the final
570 // character in the string being split is usually not a delimiter character.)
571 // --------------------------------------------------------------------------
572 if (!(!keep_empties && atom.empty())) // unless(!keep_empties && ary.empty())
573 ary.push_back(atom); // Capture final atom
575 // --------------------------------------------------------------------------
576 // Shrink the array before returning it so sizing can be considered properly
577 // and unneeded memory can be freed.
578 // --------------------------------------------------------------------------
582 } // -x- std::vector<std::string> split -x-
584 /*======================================================================*//**
586 Convert an array of octets (8-bit bytes) to hexadecimal.
588 @returns std::string of hexadecimal characters (in lower case)
589 *///=========================================================================
590 static std::string to_hex(
591 /// Binary data to convert to hexadecimal
593 /// Length of array (in 8-bit bytes), which can be as short as 0; if -1, then
594 /// the length of the data will be measured as an ASCIIZ string; default is 1
595 /// if not specified since this is the safest option
597 /// Delimiter character sequence (ASCIIZ string) to insert between multiple
598 /// pairs of nybbles@n
599 /// @c nullptr = no delimiter (default)
600 const char* delimiter = nullptr) noexcept {
601 std::string h; // Target string
602 char buf[3]; // Temporary buffer for use by snprintf()
603 if (len == -1) len = std::strlen((const char*)data); // Measure as if ASCIIZ string if length is 0
604 for(int i = 0; i < len; i++) {
605 if (delimiter != nullptr && i > 0) h.append(delimiter);
606 snprintf(buf, (size_t)3, "%02x", ((const unsigned char*)data)[i]);
610 } // -x- std::string to_hex -x-
612 /*======================================================================*//**
614 Convert an std::string's internal array of octets (8-bit bytes) to
617 @returns std::string of hexadecimal characters (in lower case)
618 *///=========================================================================
619 static std::string to_hex(
620 /// Binary data to convert to hexadecimal
621 const std::string& data,
622 /// Delimiter character sequence (ASCIIZ string) to insert between multiple
623 /// pairs of nybbles@n
624 /// @c nullptr = no delimiter (default)
625 const char* delimiter = nullptr) noexcept {
626 return to_hex(data.data(), data.size(), delimiter);
627 } // -x- std::string to_hex -x-
629 /*======================================================================*//**
631 Convert a 32-bit integer to hexadecimal.
633 This method is needed because std::to_string() doesn't include an option to
635 @returns Up to 8 hexadecimal characters
636 *///=========================================================================
637 static std::string to_hex(
638 /// Integer to convert to hexadecimal
639 const int i) noexcept {
641 h.resize(9); // 32-bit integer needs 8 nybbles to be represented in hexadecimal plus a NULL terminator
642 h.resize(snprintf(h.data(), h.size(), "%x", i)); // Convert to hexadecimal, and truncate NULL terminator
644 } // -x- std::string to_hex -x-
646 /*======================================================================*//**
648 Convert a 32-bit unsigned integer to hexadecimal.
650 This method is needed because std::to_string() doesn't include an option to
652 @returns Up to 8 hexadecimal characters
653 *///=========================================================================
654 static std::string to_hex(
655 /// Integer to convert to hexadecimal
656 const unsigned int i) noexcept {
658 h.resize(9); // 32-bit integer needs 8 nybbles to be represented in hexadecimal plus a NULL terminator
659 h.resize(snprintf(h.data(), h.size(), "%x", i)); // Convert to hexadecimal, and truncate NULL terminator
661 } // -x- std::string to_hex -x-
663 /*======================================================================*//**
665 Convert ASCII characters in an std::string to lower case. UTF-8 characters
667 @returns Copy of std::string, in lower-case form
669 *///=========================================================================
670 static std::string to_lower(
673 /// Perform in-place conversion (default is FALSE / non-destructive)
674 bool in_place_conversion = false,
675 /// Begin conversion from this position (0 = first character)@n
676 /// Negative positions are caculated backward from the end of the string
678 /// Number of characters to convert (values exceeding string length will not
679 /// cause any exceptions as the excess will be effectively ignored)@n
680 /// -1 = maximum number of characters (a.k.a., until end of string)
681 const int len = -1) noexcept {
683 // --------------------------------------------------------------------------
684 // Internal variables.
685 // --------------------------------------------------------------------------
686 const int BEGIN = begin >= 0 ? begin : str.length() + begin; // Calculate negative values from end of string
687 const int MAX = len == -1 ? str.length() : std::min(str.length(), (size_t)(begin + len)); // Optimization: For faster loop operations
689 // --------------------------------------------------------------------------
690 // Perform in-place conversion.
691 // --------------------------------------------------------------------------
692 if (in_place_conversion) {
693 for (int i = BEGIN; i < MAX; i++) {
695 if (ch >= 'A' && ch <= 'Z') str.at(i) = ch += 32; // Convert to lower-case
697 return str; // Return updated string
698 } // -x- if in_place_conversion -x-
700 // --------------------------------------------------------------------------
701 // Internal variables.
702 // --------------------------------------------------------------------------
703 std::string new_string = str; // Copy string (this allocates additional memory)
705 // --------------------------------------------------------------------------
706 // Perform isolated conversion.
707 // --------------------------------------------------------------------------
708 for (int i = BEGIN; i < MAX; i++) {
710 if (ch >= 'A' && ch <= 'Z') new_string.at(i) = ch += 32; // Convert to lower-case
712 return new_string; // Return new string
714 } // -x- std::string to_lower -x-
716 /*======================================================================*//**
718 Convert ASCII characters in an std::string to upper case. UTF-8 characters
720 @returns Copy of std::string, in upper-case form
722 *///=========================================================================
723 static std::string to_upper(
726 /// Perform in-place conversion (default is FALSE / non-destructive)
727 bool in_place_conversion = false,
728 /// Begin conversion from this position (0 = first character)@n
729 /// Negative positions are caculated backward from the end of the string
731 /// Number of characters to convert (values exceeding string length will not
732 /// cause any exceptions as the excess will be effectively ignored)@n
733 /// -1 = maximum number of characters (a.k.a., until end of string)
734 const int len = -1) noexcept {
736 // --------------------------------------------------------------------------
737 // Internal variables.
738 // --------------------------------------------------------------------------
739 const int BEGIN = begin >= 0 ? begin : str.length() + begin; // Calculate negative values from end of string
740 const int MAX = len == -1 ? str.length() : std::min(str.length(), (size_t)(begin + len)); // Optimization: For faster loop operations
742 // --------------------------------------------------------------------------
743 // Perform in-place conversion.
744 // --------------------------------------------------------------------------
745 if (in_place_conversion) {
746 for (int i = BEGIN; i < MAX; i++) {
748 if (ch >= 'a' && ch <= 'z') str.at(i) = ch -= 32; // Convert to upper-case
750 return str; // Return updated string
751 } // -x- if in_place_conversion -x-
753 // --------------------------------------------------------------------------
754 // Internal variables.
755 // --------------------------------------------------------------------------
756 std::string new_string = str; // Copy string (this allocates additional memory)
758 // --------------------------------------------------------------------------
759 // Perform isolated conversion.
760 // --------------------------------------------------------------------------
761 for (int i = BEGIN; i < MAX; i++) {
763 if (ch >= 'a' && ch <= 'z') new_string.at(i) = ch -= 32; // Convert to upper-case
765 return new_string; // Return new string
767 } // -x- std::string to_upper -x-
769 /*======================================================================*//**
771 Removes the outer-most/enclosing set of quotation marks from the beginning
772 and end of the specified String, but only if both are present.
773 @returns Copy of std::string, with quotation marks removed (if both were
775 *///=========================================================================
776 static std::string trim_quotes(
778 const std::string& str) noexcept {
780 // --------------------------------------------------------------------------
781 // Internal variables.
782 // --------------------------------------------------------------------------
783 const int LAST = str.length() - 1; // Optimization: For faster loop operations
784 if (LAST < 2) return str; // Less than two characters, so return
786 // --------------------------------------------------------------------------
788 // --------------------------------------------------------------------------
789 std::string new_string = (str[0] == '"' && str[LAST] == '"') ? str.substr(1, LAST - 1) : str; // This allocates additional memory
790 return new_string; // Return new string
792 } // -x- std::string trim_quotes -x-
794 /*======================================================================*//**
796 Wipe the contents of the supplied string with random data by XOR'ing random
797 unsigned char values with every character in the string. The clear() method
798 is not used because it's a waste of CPU cycles for a string that's just going
799 to be de-allocated anyway.
801 This method calls @c srand() with high resolution time once before starting
802 the loop that calls the @c std::rand() function. (Only the first 8 bits
803 returned by @c std::rand() are used; the remaining higher bits are not used.)
804 *///=========================================================================
807 const std::string& str,
808 /// Number of passes (default is 1)
809 const unsigned int passes = 1) { wipe(str.data(), str.capacity(), passes); } // -x- void wipe -x-
811 /*======================================================================*//**
813 Wipe the contents of the supplied data with random data by XOR'ing random
814 unsigned char values with every character in the string.
816 This method calls @c srand() with high resolution time once before starting
817 the loop that calls the @c std::rand() function. (Only the first 8 bits
818 returned by @c std::rand() are used; the remaining higher bits are not used.)
819 *///=========================================================================
823 /// Length of string (-1 = ASCIIZ string)
825 /// Number of passes (default is 1)
826 unsigned int passes = 1) {
828 // --------------------------------------------------------------------------
829 // Internal variables.
830 // --------------------------------------------------------------------------
831 if (len == -1) len = std::strlen(data);
833 std::timespec_get(&ts, TIME_UTC);
835 // --------------------------------------------------------------------------
836 // Seed random number generator with high-resolution time data.
837 // --------------------------------------------------------------------------
838 std::srand(ts.tv_sec * ts.tv_nsec);
840 // --------------------------------------------------------------------------
842 // --------------------------------------------------------------------------
843 while (passes-- > 0) {
844 for (int i = len; i >= 0; i--)
845 ((unsigned char*)data)[i] ^= (unsigned char)std::rand();
846 } // -x- while passes -x-
848 } // -x- void wipe -x-
850 }; // -x- class rtools -x-
852} // -x- namespace randolf -x-