randolf.ca  1.00
Randolf Richardson's C++ classes
Loading...
Searching...
No Matches
rtools
1#pragma once
2
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
13
14namespace randolf {
15
16 /*======================================================================*//**
17 @brief
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.
21 @par History
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&nbsp;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 @version 1.00
34 @author Randolf Richardson
35 *///=========================================================================
36 class rtools {
37
38 public:
39 /*======================================================================*//**
40 @brief
41 This character set is suggested by RFC4648 (see page 8) as "safe" for use in
42 URLs and filenames.
43 @see base64_decode
44 @see base64_encode
45 *///=========================================================================
46 inline static const char base64_set_minus_underscore[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
47
48 /*======================================================================*//**
49 @brief
50 This character set is normally used to encode IMAP4 mailbox names.
51 @see base64_decode
52 @see base64_encode
53 *///=========================================================================
54 inline static const char base64_set_plus_comma[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
55
56 /*======================================================================*//**
57 @brief
58 This character set is the default in every @c base64_ method in this library
59 because it's the most commonly used for base64 encoding.
60 @note
61 Although other well-known base64 character sets are included here, or you can
62 create your own, which has a simple format -- it must be 64 characters long
63 without a NULL terminator (additional characters will be ignored), and each
64 character can only be specified once (or else encoding or decoding will fail
65 to render consistent results; there's no checking performed beforehand since
66 the software developers providing these customized character sets are trusted
67 to not introduce such problems).
68 @see base64_decode
69 @see base64_encode
70 *///=========================================================================
71 inline static const char base64_set_plus_slash[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
72
73 /*======================================================================*//**
74 @brief
75 This is an alternate character set that is rarely used, but is mentioned in
76 RFC4648 (see page 7, second paragraph of section 5).
77 @note
78 RFC4648 incorrectly specifies the 63rd character when it's the 64th character
79 (the slash) that's being replaced with a tilde. This error likely came from
80 not counting the zero "value" label when referencing the set in which the 1st
81 character is labeled as having a value of 0.
82 @see base64_decode
83 @see base64_encode
84 *///=========================================================================
85 inline static const char base64_set_plus_tilde[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+~";
86
87 /*======================================================================*//**
88 @brief
89 Decode a Base64-encoded @c ASCIIZ&nbsp;string.
90
91 All invalid characters are simply ignored.
92 @returns Decoded string
93 @see base64_encode
94 @see base64_set_plus_slash
95 *///=========================================================================
96 static std::string base64_decode(
97 /// ASCIIZ string to decode
98 const char *in,
99 /// Base64 character set to use
100 const std::string b = base64_set_plus_slash) {
101 std::string out;
102 std::vector<int> T(256, -1);
103 int val = 0;
104 int valb = -8;
105 for (int i = 0; i < 64; i++) T[b[i]] = i;
106
107 unsigned char c;
108 while ((c = (unsigned char)*in++) != 0) {
109 if (T[c] == -1) break;
110 val = (val << 6) + T[c];
111 valb += 6;
112 if (valb >= 0) {
113 out.push_back(char((val >> valb) & 0xFF));
114 valb -= 8;
115 } // -x- if valb -x-
116 } // -x- while c -x-
117 return out;
118 } // -x- std::string base64_decode -x-
119
120 /*======================================================================*//**
121 @brief
122 Decode a Base64-encoded @c std::string.
123
124 All invalid characters are simply ignored.
125 @returns Decoded string
126 @see base64_encode
127 @see base64_set_plus_slash
128 *///=========================================================================
129 static std::string base64_decode(
130 /// String to decode
131 const std::string &in,
132 /// Base64 character set to use
133 const std::string b = base64_set_plus_slash) {
134 std::string out;
135 std::vector<int> T(256, -1);
136 int val = 0;
137 int valb = -8;
138 for (int i = 0; i < 64; i++) T[b[i]] = i;
139
140 for (unsigned char c: in) {
141 if (T[c] == -1) break;
142 val = (val << 6) + T[c];
143 valb += 6;
144 if (valb >= 0) {
145 out.push_back(char((val >> valb) & 0xFF));
146 valb -= 8;
147 } // -x- if valb -x-
148 } // -x- for c -x-
149 return out;
150 } // -x- std::string base64_decode -x-
151
152 /*======================================================================*//**
153 @brief
154 Encode an @c ASCIIZ&nbsp;string into Base64 format.
155
156 All invalid characters are simply ignored.
157 @returns Base64-encoded string
158 @see base64_decode
159 @see base64_set_plus_slash
160 *///=========================================================================
161 static std::string base64_encode(
162 /// String to encode
163 const char *in,
164 /// Base64 character set to use
165 const std::string b = base64_set_plus_slash) {
166 std::string out;
167 int val = 0;
168 int valb =- 6;
169
170 unsigned char c;
171 while ((c = (unsigned char)*in++) != 0) {
172 val = (val << 8) + c;
173 valb += 8;
174 while (valb >= 0) {
175 out.push_back(b[(val >> valb) & 0x3F]);
176 valb -= 6;
177 } // -x- while valb -x-
178 } // -x- for c -x-
179 if (valb >- 6) out.push_back(b[((val << 8) >> (valb + 8)) & 0x3F]);
180 while (out.size() % 4) out.push_back('=');
181 return out;
182 } // -x- std::string base64_encode -x-
183
184 /*======================================================================*//**
185 @brief
186 Encode an @c std::string into Base64 format.
187
188 All invalid characters are simply ignored.
189 @returns Base64-encoded string
190 @see base64_decode
191 @see base64_set_plus_slash
192 *///=========================================================================
193 static std::string base64_encode(
194 /// String to encode
195 const std::string &in,
196 /// Base64 character set to use
197 const std::string b = base64_set_plus_slash) {
198 std::string out;
199 int val = 0;
200 int valb =- 6;
201
202 for (unsigned char c: in) {
203 val = (val << 8) + c;
204 valb += 8;
205 while (valb >= 0) {
206 out.push_back(b[(val >> valb) & 0x3F]);
207 valb -= 6;
208 } // -x- while valb -x-
209 } // -x- for c -x-
210 if (valb >- 6) out.push_back(b[((val << 8) >> (valb + 8)) & 0x3F]);
211 while (out.size() % 4) out.push_back('=');
212 return out;
213 } // -x- std::string base64_encode -x-
214
215 /*======================================================================*//**
216 @brief
217 Insert commas into the last numeric sequence of digits in the supplied string
218 and insert spaces before that (commas and spaces are configurable). If a
219 decimal point is found, then comma insertions will only occur before that
220 (this is also configurable).
221 @returns Numeric value as a char* array converted to a properly-delimited
222 string as an std::string
223 *///=========================================================================
224 static std::string insert_commas(
225 /// Pointer to ASCII representation of numeric value
226 const char* value,
227 /// Length of value (in bytes), or 0 to auto-detect length if value string is an ASCIIZ string
228 size_t len = 0,
229 /// Don't insert any commas after the last period (or whatever string is set as the @c dot character)
230 bool skip_dot = true,
231 /// Number of digits between commas
232 const int digits = 3,
233 /// Pointer to ASCIIZ comma character string (nullptr = disabled)
234 const char* comma = ",",
235 /// Pointer to ASCIIZ space character string (nullptr = disabled) used instead of commas for non-digit fill-ins where commas would normally be inserted
236 const char* space = " ",
237 /// Period character used when @c skip_dot is enabled
238 const char dot = '.') noexcept {
239
240 // --------------------------------------------------------------------------
241 // Measure size of format string if an ASCIIZ string was indicated.
242 // --------------------------------------------------------------------------
243 if (len == 0) len = std::strlen(value);
244
245 // --------------------------------------------------------------------------
246 // Find the dot, and adjust len accordingly.
247 // --------------------------------------------------------------------------
248 for (int i = len - 1; i > 0; i--) {
249 if (value[i] == dot) {
250 len = i;
251 break;
252 } // -x- if dot -x-
253 } // -x- for i -x-
254
255 // --------------------------------------------------------------------------
256 // Internal variables.
257 // --------------------------------------------------------------------------
258 std::string v(value, len);
259 const int m = len % digits; // Modulus for every 3rd character
260 bool blank = false; // Add blank space instead of comma
261
262 // --------------------------------------------------------------------------
263 // Insert commas as long as there are digits.
264 // --------------------------------------------------------------------------
265 for (int i = len - 1; i > 0; i--) {
266 if (v[i] < '0' || v[i] > '9') blank = true; // Not a digit, so we're switching from commas to blanks (spaces)
267 if ((i % digits) == m) { // This is where a separator belongs
268 if (!blank && comma != nullptr) v.insert(i, comma); // Insert comma, if one is defined
269 else if (blank && space != nullptr) v.insert(i, space); // Insert space, if one is defined
270 } // -x- if m -x-
271 } // -x- for i -x-
272
273 return v;
274 } // -x- std::string insert_commas -x-
275
276 /*======================================================================*//**
277 @brief
278 Parses a SASL exchange, returning an @c std::unordered_map of the key-value
279 pairs that were encountered.
280 @throws std::runtime_error If a SASL message is improperly formatted (the
281 error message includes the offset where the format problem occurred)
282 @returns Key-value pairs in an unordered map where the key is an std::string
283 object and the value is a vector of std::string objects
284 *///=========================================================================
285 static std::unordered_map<std::string, std::vector<std::string>> parse_sasl_exchange(
286 /// Unparsed SASL exchange string (must not be Base64-encoded)
287 const std::string sasl,
288 /// Ensure the following keys exist, each with one empty string: nonce, nc, cnonce, qop, realm, username, digest-uri, authzid
289 const bool add_missing_keys = false) {
290
291 // --------------------------------------------------------------------------
292 // Internal variables.
293 // --------------------------------------------------------------------------
294 std::unordered_map<std::string, std::vector<std::string>> map;
295 char ch; // Used in string parsing
296 std::string temp; // Used to build current element
297 std::string key; // Key name
298 bool keyMode = true; // TRUE = parsing key name; FALSE = parsing value
299 bool quoteMode = false; // Used for tracking "quote mode" with quotation mark usage
300 int offset = -1; // Offset within the unparsed string of the character currently being processed
301 const int MAX = sasl.size();
302
303 // --------------------------------------------------------------------------
304 // Parse the string, one character at a time.
305 // --------------------------------------------------------------------------
306 for (int offset = 0; offset < MAX; offset++) {
307 switch (ch = sasl[offset]) {
308 case '"': // Quotation mark
309 if (keyMode) throw std::runtime_error("Key names can't contain quotation marks at offset " + std::to_string(offset));
310 if (!quoteMode && temp.length() > 0) throw std::runtime_error("Malformed quoted value at offset " + std::to_string(offset));
311 quoteMode = !quoteMode; // Toggle "quote mode"
312 temp.push_back(ch); // Save quotation mark
313 break;
314 case '\\': // Back-slash
315 if (keyMode) throw std::runtime_error("Key names can't contain escaped literal characters at offset " + std::to_string(offset));
316 ch = sasl[++offset]; // Get next character after incrementing offset
317 if (offset >= MAX) throw std::runtime_error("Literal character is missing at offset " + std::to_string(offset));
318 if (ch < ' ' || ch == 127) throw std::runtime_error("Invalid charater at offset " + std::to_string(offset)); // Any characters except CTLs and separators
319 temp.push_back('\\'); // Save backslash
320 temp.push_back(ch); // Save literal character
321 break;
322 case ' ': // Space
323 if (!quoteMode) throw std::runtime_error("White space characters not permitted at offset " + std::to_string(offset));
324 temp.push_back(ch);
325 break;
326 case '=': // Equal sign (signifies end of key, and beginning of value)
327 if (quoteMode) {
328 temp.push_back(ch);
329 break;
330 }
331 if (!keyMode) throw std::runtime_error("Invalid character at offset " + std::to_string(offset));
332 keyMode = !keyMode; // Toggle flag
333 if (temp.length() == 0) throw std::runtime_error("Missing key name at offset " + std::to_string(offset));
334 key = temp; // Save string for later
335 temp = ""; // Clear temporary string
336 break;
337 case ',': // Comma delimiter (signifies end of value)
338 if (quoteMode) {
339 temp.push_back(ch);
340 break;
341 }
342 if (keyMode) throw std::runtime_error("Invalid character at offset " + std::to_string(offset));
343 keyMode = !keyMode; // Toggle flag
344 if (temp[0] == '"' && temp[temp.length() - 1] != '"') throw std::runtime_error("Malformed quoted value at offset " + std::to_string(offset));
345 map[key].push_back(temp);
346 temp = "";
347 break;
348 default: // Everything else is a literal character
349 if (ch < ' ' || ch == 127) throw std::runtime_error("Invalid character at offset " + std::to_string(offset)); // Any characters except CTLs and separators
350 if (keyMode && (std::string("()<>@,;:\\\"/[]?={} ").find_first_of(ch) != std::string::npos)) throw std::runtime_error("Invalid character at offset " + std::to_string(offset));
351 temp.push_back(ch);
352 break;
353 } // -x- switch ch -x-
354 } // -x- for ch -x-
355
356 // --------------------------------------------------------------------------
357 // Syntax checks. There is no need to check for additional data because we
358 // added a comma to the unparsed_string -- if the original unparsed_string
359 // had a trailing comma, then an exception would have been thrown already.
360 // --------------------------------------------------------------------------
361 if (quoteMode) throw std::runtime_error("Incomplete value at offset " + std::to_string(offset));
362
363 // --------------------------------------------------------------------------
364 // Save last key-value pair if we're not in keyMode.
365 // --------------------------------------------------------------------------
366 if (!keyMode) map[key].push_back(temp);
367
368 // --------------------------------------------------------------------------
369 // Add missing keys, each with one empty string.
370 // --------------------------------------------------------------------------
371 if (add_missing_keys) {
372 if (map.try_emplace("nonce", std::vector<std::string>()).second) { map["nonce" ].push_back(""); };
373 if (map.try_emplace("nc", std::vector<std::string>()).second) { map["nc" ].push_back(""); };
374 if (map.try_emplace("cnonce", std::vector<std::string>()).second) { map["cnonce" ].push_back(""); };
375 if (map.try_emplace("qop", std::vector<std::string>()).second) { map["qop" ].push_back(""); };
376 if (map.try_emplace("realm", std::vector<std::string>()).second) { map["realm" ].push_back(""); };
377 if (map.try_emplace("username", std::vector<std::string>()).second) { map["username" ].push_back(""); };
378 if (map.try_emplace("digest-uri", std::vector<std::string>()).second) { map["digest-uri"].push_back(""); };
379 if (map.try_emplace("authzid", std::vector<std::string>()).second) { map["authzid" ].push_back(""); };
380 } // -x- if add_missing_keys -x-
381
382 // --------------------------------------------------------------------------
383 // Return the newly-created unordered_map.
384 // --------------------------------------------------------------------------
385 return map;
386
387 } // -x- std::unordered_map<std::string, std::vector<std::string>> parse_sasl_exchange -x-
388
389 /*======================================================================*//**
390 @copydoc split(const char, const char*, const size_t)
391 *///=========================================================================
392 static std::vector<std::string> split(
393 /// Character to use for the delimiter
394 const char delimiter,
395 /// Source string (to be split into atoms)
396 std::string str,
397 /// Length of string (in bytes), or 0 if using the full length of the string
398 const size_t len = -1) {
399 return split(delimiter, str.data(), len >= -1 ? str.size() : len);
400 } // -x- std::vector<std::string> split -x-
401
402 /*======================================================================*//**
403 @brief
404 Split string into an @c std::vector that's perfectly-sized to store all the
405 elements separated by a delimiter character. If no delimiters are
406 encountered, the resulting vector will contain the entire string as its only
407 element. If the string is empty, the resulting vector will contain an empty
408 string as its only element.
409
410 @pre
411 Using (char)0 as a delimiter necessitates specifying the length of the source
412 string, otherwise the resulting vector will contain only the first element
413 (this behaviour might change in the future, so don't rely on it).
414
415 @returns Pointer to an array of "atoms" (strings) stored in an @c std::vector
416 object
417 *///=========================================================================
418 static std::vector<std::string> split(
419 /// Character to use for the delimiter
420 const char delimiter,
421 /// Source string (to be split)
422 const char* str,
423 /// Length of string (in bytes), or -1 if @c str is an ASCIIZ string (NULL-terminated)
424 const size_t len = -1) { // TODO: Add support for a "maximum_elements" parameter
425
426 // --------------------------------------------------------------------------
427 // Internal variables.
428 //
429 // Includes not measuring size of string if an ASCIIZ string was indicated
430 // because "if (len == -1) len = std::strlen(str);" is less optimal than what
431 // the main loop already does in this regard.
432 // --------------------------------------------------------------------------
433 const char* MAX = len == -1 ? (char*)-1 : str + len; // Loop pre-optimization
434 std::string* atom = new std::string();
435
436 // --------------------------------------------------------------------------
437 // Pre-allocate std::vector with a size that's half the length of the string,
438 // because this is the maximum number of atoms there could be if each atom is
439 // 1 byte long and each delimiting whitespace character is also 1 byte long.
440 // --------------------------------------------------------------------------
441 std::vector<std::string> ary;
442 if (len > 0) ary.reserve(len >> (1 + 1)); // Ensure array-like efficiency
443
444 // --------------------------------------------------------------------------
445 // Main loops.
446 //
447 // Optimization: By using separate loops for a non-NULL delimiter from the
448 // NULL delimiter, both loops are faster and less complicated.
449 // --------------------------------------------------------------------------
450 if (delimiter != (char)0) { // Process using non-NULL delimiter
451 do {
452 if (*str == delimiter) { // Delimiter character
453 ary.push_back(*atom); // Save current atom to vector
454 atom = new std::string(); // Create new atom (string)
455 } else {
456 atom->append(str, 1); // Add this character to the atom
457 } // -x- if delimiter -x-
458 } while (*str != (char)0 && ++str < MAX); // -x- while str -x-
459
460 // --------------------------------------------------------------------------
461 // If we're not in delimited mode, and the current string is not empty, then
462 // add it to the vector as the final atom.
463 //
464 // Memory leak prevention (we don't need any free radicals taking up memory!
465 // {yes, I had fun writing this, and whether or not you like it, you can't
466 // deny the cleverness of this pun}): Because of the way the loop (above) is
467 // structured, there will never be a memory leak here resulting from trailing
468 // non-delimiter characters because a new atom is created only when a first
469 // delimiter character is encountered.
470 // --------------------------------------------------------------------------
471 ary.push_back(*atom);
472
473 // --------------------------------------------------------------------------
474 // Process NULL delimiter.
475 // --------------------------------------------------------------------------
476 } else { // Process using NULL delimiter
477 do {
478 if (*str == (char)0) { // NULL delimiter
479 if (len == 0) break;
480 ary.push_back(*atom); // Save current atom to vector
481 atom = new std::string(); // Create new atom (string)
482 } else { // -x- if *str -x-
483 atom->append(str, 1); // Add this character to the atom
484 } // -x- if (char)0 -x-
485 } while (str++ < MAX); // -x- while str -x-
486 } // -x- if delimiter -x-
487
488 // --------------------------------------------------------------------------
489 // Shrink the array before returning it so sizing can be considered properly
490 // and unneeded memory can be freed.
491 // --------------------------------------------------------------------------
492 ary.shrink_to_fit(); // TODO: Make this optional with a flag called RTOOLS_SHRINK
493
494 return ary;
495 } // -x- std::vector<std::string> split -x-
496
497 /*======================================================================*//**
498 @brief
499 Convert an array of octets (8-bit bytes) to hexadecimal.
500
501 @returns std::string of hexadecimal characters (in lower case)
502 *///=========================================================================
503 static std::string to_hex(
504 /// Binary data to convert to hexadecimal
505 const void* data,
506 /// Length of array (in 8-bit bytes), which can be as short as 0; if -1, then
507 /// the length of the data will be measured as an ASCIIZ string; default is 1
508 /// if not specified since this is the safest option
509 size_t len = 1,
510 /// Delimiter character sequence (ASCIIZ string) to insert between multiple
511 /// pairs of nybbles@n
512 /// @c nullptr = no delimiter (default)
513 const char* delimiter = nullptr) noexcept {
514 std::string h; // Target string
515 char buf[3]; // Temporary buffer for use by snprintf()
516 if (len == -1) len = std::strlen((const char*)data); // Measure as if ASCIIZ string if length is 0
517 for(int i = 0; i < len; i++) {
518 if (delimiter != nullptr && i > 0) h.append(delimiter);
519 snprintf(buf, (size_t)3, "%02x", ((const unsigned char*)data)[i]);
520 h.append(buf);
521 } // -x- for i -x-
522 return h;
523 } // -x- std::string to_hex -x-
524
525 /*======================================================================*//**
526 @brief
527 Convert an std::string's internal array of octets (8-bit bytes) to
528 hexadecimal.
529
530 @returns std::string of hexadecimal characters (in lower case)
531 *///=========================================================================
532 static std::string to_hex(
533 /// Binary data to convert to hexadecimal
534 std::string data,
535 /// Delimiter character sequence (ASCIIZ string) to insert between multiple
536 /// pairs of nybbles@n
537 /// @c nullptr = no delimiter (default)
538 const char* delimiter = nullptr) noexcept { return to_hex(data.data(), data.size(), delimiter); } // -x- std::string to_hex -x-
539
540 /*======================================================================*//**
541 @brief
542 Convert a 32-bit integer to hexadecimal.
543
544 This method is needed because std::to_string() doesn't include an option to
545 specify the radix.
546 @returns Up to 8 hexadecimal characters
547 *///=========================================================================
548 static std::string to_hex(
549 /// Integer to convert to hexadecimal
550 const int i) noexcept {
551 std::string h;
552 h.resize(9); // 32-bit integer needs 8 nybbles to be represented in hexadecimal plus a NULL terminator
553 h.resize(snprintf(h.data(), h.size(), "%x", i)); // Convert to hexadecimal, and truncate NULL terminator
554 return h;
555 } // -x- std::string to_hex -x-
556
557 /*======================================================================*//**
558 @brief
559 Convert a 32-bit unsigned integer to hexadecimal.
560
561 This method is needed because std::to_string() doesn't include an option to
562 specify the radix.
563 @returns Up to 8 hexadecimal characters
564 *///=========================================================================
565 static std::string to_hex(
566 /// Integer to convert to hexadecimal
567 const unsigned int i) noexcept {
568 std::string h;
569 h.resize(9); // 32-bit integer needs 8 nybbles to be represented in hexadecimal plus a NULL terminator
570 h.resize(snprintf(h.data(), h.size(), "%x", i)); // Convert to hexadecimal, and truncate NULL terminator
571 return h;
572 } // -x- std::string to_hex -x-
573
574 /*======================================================================*//**
575 @brief
576 Convert ASCII characters in an std::string to lower case. UTF-8 characters
577 are not converted.
578 @returns Copy of std::string, in lower-case form
579 @see to_upper
580 *///=========================================================================
581 static std::string to_lower(
582 /// Source string
583 std::string& str,
584 /// Perform in-place conversion (default is FALSE / non-destructive)
585 bool in_place_conversion = false,
586 /// Begin conversion from this position (0 = first character)@n
587 /// Negative positions are caculated backward from the end of the string
588 const int begin = 0,
589 /// Number of characters to convert (values exceeding string length will not
590 /// cause any exceptions as the excess will be effectively ignored)@n
591 /// -1 = maximum number of characters (a.k.a., until end of string)
592 const int len = -1) noexcept {
593
594 // --------------------------------------------------------------------------
595 // Internal variables.
596 // --------------------------------------------------------------------------
597 const int BEGIN = begin >= 0 ? begin : str.length() + begin; // Calculate negative values from end of string
598 const int MAX = len == -1 ? str.length() : std::min(str.length(), (size_t)(begin + len)); // Optimization: For faster loop operations
599
600 // --------------------------------------------------------------------------
601 // Perform in-place conversion.
602 // --------------------------------------------------------------------------
603 if (in_place_conversion) {
604 for (int i = BEGIN; i < MAX; i++) {
605 char ch = str.at(i);
606 if (ch >= 'A' && ch <= 'Z') str.at(i) = ch += 32; // Convert to lower-case
607 } // -x- for i -x-
608 return str; // Return updated string
609 } // -x- if in_place_conversion -x-
610
611 // --------------------------------------------------------------------------
612 // Internal variables.
613 // --------------------------------------------------------------------------
614 std::string new_string = str; // Copy string (this allocates additional memory)
615
616 // --------------------------------------------------------------------------
617 // Perform isolated conversion.
618 // --------------------------------------------------------------------------
619 for (int i = BEGIN; i < MAX; i++) {
620 char ch = str.at(i);
621 if (ch >= 'A' && ch <= 'Z') new_string.at(i) = ch += 32; // Convert to lower-case
622 } // -x- for i -x-
623 return new_string; // Return new string
624
625 } // -x- std::string to_lower -x-
626
627 /*======================================================================*//**
628 @brief
629 Convert ASCII characters in an std::string to upper case. UTF-8 characters
630 are not converted.
631 @returns Copy of std::string, in upper-case form
632 @see to_lower
633 *///=========================================================================
634 static std::string to_upper(
635 /// Source string
636 std::string& str,
637 /// Perform in-place conversion (default is FALSE / non-destructive)
638 bool in_place_conversion = false,
639 /// Begin conversion from this position (0 = first character)@n
640 /// Negative positions are caculated backward from the end of the string
641 const int begin = 0,
642 /// Number of characters to convert (values exceeding string length will not
643 /// cause any exceptions as the excess will be effectively ignored)@n
644 /// -1 = maximum number of characters (a.k.a., until end of string)
645 const int len = -1) noexcept {
646
647 // --------------------------------------------------------------------------
648 // Internal variables.
649 // --------------------------------------------------------------------------
650 const int BEGIN = begin >= 0 ? begin : str.length() + begin; // Calculate negative values from end of string
651 const int MAX = len == -1 ? str.length() : std::min(str.length(), (size_t)(begin + len)); // Optimization: For faster loop operations
652
653 // --------------------------------------------------------------------------
654 // Perform in-place conversion.
655 // --------------------------------------------------------------------------
656 if (in_place_conversion) {
657 for (int i = BEGIN; i < MAX; i++) {
658 char ch = str.at(i);
659 if (ch >= 'a' && ch <= 'z') str.at(i) = ch -= 32; // Convert to upper-case
660 } // -x- for i -x-
661 return str; // Return updated string
662 } // -x- if in_place_conversion -x-
663
664 // --------------------------------------------------------------------------
665 // Internal variables.
666 // --------------------------------------------------------------------------
667 std::string new_string = str; // Copy string (this allocates additional memory)
668
669 // --------------------------------------------------------------------------
670 // Perform isolated conversion.
671 // --------------------------------------------------------------------------
672 for (int i = BEGIN; i < MAX; i++) {
673 char ch = str.at(i);
674 if (ch >= 'a' && ch <= 'z') new_string.at(i) = ch -= 32; // Convert to upper-case
675 } // -x- for i -x-
676 return new_string; // Return new string
677
678 } // -x- std::string to_upper -x-
679
680 /*======================================================================*//**
681 @brief
682 Removes the outer-most/enclosing set of quotation marks from the beginning
683 and end of the specified String, but only if both are present.
684 @returns Copy of std::string, with quotation marks removed (if both were
685 present)
686 *///=========================================================================
687 static std::string trim_quotes(
688 /// Source string
689 std::string str) noexcept {
690
691 // --------------------------------------------------------------------------
692 // Internal variables.
693 // --------------------------------------------------------------------------
694 const int LAST = str.length() - 1; // Optimization: For faster loop operations
695 if (LAST < 2) return str; // Less than two characters, so return
696
697 // --------------------------------------------------------------------------
698 // Process string.
699 // --------------------------------------------------------------------------
700 std::string new_string = (str[0] == '"' && str[LAST] == '"') ? str.substr(1, LAST - 1) : str; // This allocates additional memory
701 return new_string; // Return new string
702
703 } // -x- std::string trim_quotes -x-
704
705 /*======================================================================*//**
706 @brief
707 Wipe the contents of the supplied string with random data by XOR'ing random
708 unsigned char values with every character in the string. The clear() method
709 is not used because it's a waste of CPU cycles for a string that's just going
710 to be de-allocated anyway.
711 @warning
712 This method calls @c srand() with high resolution time once before starting
713 the loop that calls the @c std::rand() function. (Only the first 8 bits
714 returned by @c std::rand() are used; the remaining higher bits are not used.)
715 *///=========================================================================
716 static void wipe(
717 /// String to wipe
718 std::string& str,
719 /// Number of passes (default is 1)
720 unsigned int passes = 1) { wipe(str.data(), str.capacity(), passes); } // -x- void wipe -x-
721
722 /*======================================================================*//**
723 @brief
724 Wipe the contents of the supplied data with random data by XOR'ing random
725 unsigned char values with every character in the string.
726 @warning
727 This method calls @c srand() with high resolution time once before starting
728 the loop that calls the @c std::rand() function. (Only the first 8 bits
729 returned by @c std::rand() are used; the remaining higher bits are not used.)
730 *///=========================================================================
731 static void wipe(
732 /// String to wipe
733 char* data,
734 /// Length of string (-1 = ASCIIZ string)
735 size_t len = -1,
736 /// Number of passes (default is 1)
737 unsigned int passes = 1) {
738
739 // --------------------------------------------------------------------------
740 // Internal variables.
741 // --------------------------------------------------------------------------
742 if (len == -1) len = std::strlen(data);
743 std::timespec ts;
744 std::timespec_get(&ts, TIME_UTC);
745
746 // --------------------------------------------------------------------------
747 // Seed random number generator with high-resolution time data.
748 // --------------------------------------------------------------------------
749 std::srand(ts.tv_sec * ts.tv_nsec);
750
751 // --------------------------------------------------------------------------
752 // Data wipe loop.
753 // --------------------------------------------------------------------------
754 while (passes-- > 0) {
755 for (int i = len; i >= 0; i--)
756 ((unsigned char*)data)[i] ^= (unsigned char)std::rand();
757 } // -x- while passes -x-
758
759 } // -x- void wipe -x-
760
761 }; // -x- class rtools -x-
762
763} // -x- namespace randolf -x-