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 - 2025-Feb-03 v1.00 Increased use of references and pointers
34 @version 1.00
35 @author Randolf Richardson
36 *///=========================================================================
37 class rtools {
38
39 public:
40 /*======================================================================*//**
41 @brief
42 This character set is suggested by RFC4648 (see page 8) as "safe" for use in
43 URLs and filenames.
44 @see base64_decode
45 @see base64_encode
46 *///=========================================================================
47 inline static const char base64_set_minus_underscore[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
48
49 /*======================================================================*//**
50 @brief
51 This character set is normally used to encode IMAP4 mailbox names.
52 @see base64_decode
53 @see base64_encode
54 *///=========================================================================
55 inline static const char base64_set_plus_comma[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
56
57 /*======================================================================*//**
58 @brief
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.
61 @note
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).
69 @see base64_decode
70 @see base64_encode
71 *///=========================================================================
72 inline static const char base64_set_plus_slash[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
73
74 /*======================================================================*//**
75 @brief
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).
78 @note
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.
83 @see base64_decode
84 @see base64_encode
85 *///=========================================================================
86 inline static const char base64_set_plus_tilde[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+~";
87
88 /*======================================================================*//**
89 @brief
90 Decode a Base64-encoded @c ASCIIZ&nbsp;string.
91
92 All invalid characters are simply ignored.
93 @returns Decoded string
94 @see base64_encode
95 @see base64_set_plus_slash
96 *///=========================================================================
97 static std::string base64_decode(
98 /// ASCIIZ string to decode
99 const char* in,
100 /// Base64 character set to use
101 const std::string& b = base64_set_plus_slash) {
102 std::string out;
103 std::vector<int> T(256, -1);
104 int val = 0;
105 int valb = -8;
106 for (int i = 0; i < 64; i++) T[b[i]] = i;
107
108 unsigned char c;
109 while ((c = (unsigned char)*in++) != 0) {
110 if (T[c] == -1) break;
111 val = (val << 6) + T[c];
112 valb += 6;
113 if (valb >= 0) {
114 out.push_back(char((val >> valb) & 0xFF));
115 valb -= 8;
116 } // -x- if valb -x-
117 } // -x- while c -x-
118 return out;
119 } // -x- std::string base64_decode -x-
120
121 /*======================================================================*//**
122 @brief
123 Decode a Base64-encoded @c std::string.
124
125 All invalid characters are simply ignored.
126 @returns Decoded string
127 @see base64_encode
128 @see base64_set_plus_slash
129 *///=========================================================================
130 static std::string base64_decode(
131 /// String to decode
132 const std::string& in,
133 /// Base64 character set to use
134 const std::string& b = base64_set_plus_slash) {
135 std::string out;
136 std::vector<int> T(256, -1);
137 int val = 0;
138 int valb = -8;
139 for (int i = 0; i < 64; i++) T[b[i]] = i;
140
141 for (unsigned char c: in) {
142 if (T[c] == -1) break;
143 val = (val << 6) + T[c];
144 valb += 6;
145 if (valb >= 0) {
146 out.push_back(char((val >> valb) & 0xFF));
147 valb -= 8;
148 } // -x- if valb -x-
149 } // -x- for c -x-
150 return out;
151 } // -x- std::string base64_decode -x-
152
153 /*======================================================================*//**
154 @brief
155 Encode an @c ASCIIZ&nbsp;string into Base64 format.
156
157 All invalid characters are simply ignored.
158 @returns Base64-encoded string
159 @see base64_decode
160 @see base64_set_plus_slash
161 *///=========================================================================
162 static std::string base64_encode(
163 /// String to encode
164 const char* in,
165 /// Base64 character set to use
166 const std::string& b = base64_set_plus_slash) {
167 std::string out;
168 int val = 0;
169 int valb =- 6;
170
171 unsigned char c;
172 while ((c = (unsigned char)*in++) != 0) {
173 val = (val << 8) + c;
174 valb += 8;
175 while (valb >= 0) {
176 out.push_back(b[(val >> valb) & 0x3F]);
177 valb -= 6;
178 } // -x- while valb -x-
179 } // -x- for c -x-
180 if (valb >- 6) out.push_back(b[((val << 8) >> (valb + 8)) & 0x3F]);
181 while (out.size() % 4) out.push_back('=');
182 return out;
183 } // -x- std::string base64_encode -x-
184
185 /*======================================================================*//**
186 @brief
187 Encode an @c std::string into Base64 format.
188
189 All invalid characters are simply ignored.
190 @returns Base64-encoded string
191 @see base64_decode
192 @see base64_set_plus_slash
193 *///=========================================================================
194 static std::string base64_encode(
195 /// String to encode
196 const std::string& in,
197 /// Base64 character set to use
198 const std::string& b = base64_set_plus_slash) {
199 std::string out;
200 int val = 0;
201 int valb =- 6;
202
203 for (unsigned char c: in) {
204 val = (val << 8) + c;
205 valb += 8;
206 while (valb >= 0) {
207 out.push_back(b[(val >> valb) & 0x3F]);
208 valb -= 6;
209 } // -x- while valb -x-
210 } // -x- for c -x-
211 if (valb >- 6) out.push_back(b[((val << 8) >> (valb + 8)) & 0x3F]);
212 while (out.size() % 4) out.push_back('=');
213 return out;
214 } // -x- std::string base64_encode -x-
215
216 /*======================================================================*//**
217 @brief
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
227 const char* value,
228 /// Length of value (in bytes), or 0 to auto-detect length if value string is an ASCIIZ string
229 size_t len = 0,
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 {
240
241 // --------------------------------------------------------------------------
242 // Measure size of format string if an ASCIIZ string was indicated.
243 // --------------------------------------------------------------------------
244 if (len == 0) len = std::strlen(value);
245
246 // --------------------------------------------------------------------------
247 // Find the dot, and adjust len accordingly.
248 // --------------------------------------------------------------------------
249 for (int i = len - 1; i > 0; i--) {
250 if (value[i] == dot) {
251 len = i;
252 break;
253 } // -x- if dot -x-
254 } // -x- for i -x-
255
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
262
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
271 } // -x- if m -x-
272 } // -x- for i -x-
273
274 return v;
275 } // -x- std::string insert_commas -x-
276
277 /*======================================================================*//**
278 @brief
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) {
291
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();
303
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
314 break;
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
322 break;
323 case ' ': // Space
324 if (!quoteMode) throw std::runtime_error("White space characters not permitted at offset " + std::to_string(offset));
325 temp.push_back(ch);
326 break;
327 case '=': // Equal sign (signifies end of key, and beginning of value)
328 if (quoteMode) {
329 temp.push_back(ch);
330 break;
331 }
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
337 break;
338 case ',': // Comma delimiter (signifies end of value)
339 if (quoteMode) {
340 temp.push_back(ch);
341 break;
342 }
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);
347 temp = "";
348 break;
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));
352 temp.push_back(ch);
353 break;
354 } // -x- switch ch -x-
355 } // -x- for ch -x-
356
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));
363
364 // --------------------------------------------------------------------------
365 // Save last key-value pair if we're not in keyMode.
366 // --------------------------------------------------------------------------
367 if (!keyMode) map[key].push_back(temp);
368
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-
382
383 // --------------------------------------------------------------------------
384 // Return the newly-created unordered_map.
385 // --------------------------------------------------------------------------
386 return map;
387
388 } // -x- std::unordered_map<std::string, std::vector<std::string>> parse_sasl_exchange -x-
389
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
399 const int len = -1,
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-
406
407 /*======================================================================*//**
408 @brief
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.
414
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
419 important.
420
421 @pre
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.
424
425 @returns Pointer to an array of "atoms" (strings) stored in an @c std::vector
426 object
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)
432 const char* str,
433 /// Length of string (in bytes), or -1 if @c str is an ASCIIZ string (NULL-terminated)
434 const int len = -1,
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
439
440 // --------------------------------------------------------------------------
441 // Internal variables.
442 //
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
448 std::string atom;
449
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)
457
458 // --------------------------------------------------------------------------
459 // Main loop.
460 // --------------------------------------------------------------------------
461 do {
462 if (*str == delimiter) { // Delimiter character detected
463 if (len == 0) break;
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-
471
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
478
479 // --------------------------------------------------------------------------
480 // Shrink the array before returning it so sizing can be considered properly
481 // and unneeded memory can be freed.
482 // --------------------------------------------------------------------------
483 ary.shrink_to_fit();
484
485 return ary;
486 } // -x- std::vector<std::string> split -x-
487
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
495 const int len = -1,
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-
502
503 /*======================================================================*//**
504 @brief
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.
510
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.
513
514 @note
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):
517 - @c 32 space
518 - @c 10 linefeed
519 - @c 13 carriage return
520 - @c 9 horizontal tab
521 - @c 0 NULL terminator
522
523 @returns Pointer to an array of "atoms" (strings) stored in an @c std::vector
524 object
525 *///=========================================================================
526 static std::vector<std::string> split(
527 /// Source string (to be split)
528 const char* str,
529 /// Length of string (in bytes), or -1 if @c str is an ASCIIZ string (NULL-terminated)
530 const int len = -1,
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
535
536 // --------------------------------------------------------------------------
537 // Internal variables.
538 //
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
544 std::string atom;
545
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)
553
554 // --------------------------------------------------------------------------
555 // Main loop.
556 // --------------------------------------------------------------------------
557 do {
558 if (*str == 32 || *str == 10 || *str == 13 || *str == 9 || *str == 0) { // Whitespace character detected
559 if (len == 0) break;
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-
567
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
574
575 // --------------------------------------------------------------------------
576 // Shrink the array before returning it so sizing can be considered properly
577 // and unneeded memory can be freed.
578 // --------------------------------------------------------------------------
579 ary.shrink_to_fit();
580
581 return ary;
582 } // -x- std::vector<std::string> split -x-
583
584 /*======================================================================*//**
585 @brief
586 Convert an array of octets (8-bit bytes) to hexadecimal.
587
588 @returns std::string of hexadecimal characters (in lower case)
589 *///=========================================================================
590 static std::string to_hex(
591 /// Binary data to convert to hexadecimal
592 const void* data,
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
596 int len = 1,
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]);
607 h.append(buf);
608 } // -x- for i -x-
609 return h;
610 } // -x- std::string to_hex -x-
611
612 /*======================================================================*//**
613 @brief
614 Convert an std::string's internal array of octets (8-bit bytes) to
615 hexadecimal.
616
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-
628
629 /*======================================================================*//**
630 @brief
631 Convert a 32-bit integer to hexadecimal.
632
633 This method is needed because std::to_string() doesn't include an option to
634 specify the radix.
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 {
640 std::string h;
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
643 return h;
644 } // -x- std::string to_hex -x-
645
646 /*======================================================================*//**
647 @brief
648 Convert a 32-bit unsigned integer to hexadecimal.
649
650 This method is needed because std::to_string() doesn't include an option to
651 specify the radix.
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 {
657 std::string h;
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
660 return h;
661 } // -x- std::string to_hex -x-
662
663 /*======================================================================*//**
664 @brief
665 Convert ASCII characters in an std::string to lower case. UTF-8 characters
666 are not converted.
667 @returns Copy of std::string, in lower-case form
668 @see to_upper
669 *///=========================================================================
670 static std::string to_lower(
671 /// Source string
672 std::string& str,
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
677 const int begin = 0,
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 {
682
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
688
689 // --------------------------------------------------------------------------
690 // Perform in-place conversion.
691 // --------------------------------------------------------------------------
692 if (in_place_conversion) {
693 for (int i = BEGIN; i < MAX; i++) {
694 char ch = str.at(i);
695 if (ch >= 'A' && ch <= 'Z') str.at(i) = ch += 32; // Convert to lower-case
696 } // -x- for i -x-
697 return str; // Return updated string
698 } // -x- if in_place_conversion -x-
699
700 // --------------------------------------------------------------------------
701 // Internal variables.
702 // --------------------------------------------------------------------------
703 std::string new_string = str; // Copy string (this allocates additional memory)
704
705 // --------------------------------------------------------------------------
706 // Perform isolated conversion.
707 // --------------------------------------------------------------------------
708 for (int i = BEGIN; i < MAX; i++) {
709 char ch = str.at(i);
710 if (ch >= 'A' && ch <= 'Z') new_string.at(i) = ch += 32; // Convert to lower-case
711 } // -x- for i -x-
712 return new_string; // Return new string
713
714 } // -x- std::string to_lower -x-
715
716 /*======================================================================*//**
717 @brief
718 Convert ASCII characters in an std::string to upper case. UTF-8 characters
719 are not converted.
720 @returns Copy of std::string, in upper-case form
721 @see to_lower
722 *///=========================================================================
723 static std::string to_upper(
724 /// Source string
725 std::string& str,
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
730 const int begin = 0,
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 {
735
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
741
742 // --------------------------------------------------------------------------
743 // Perform in-place conversion.
744 // --------------------------------------------------------------------------
745 if (in_place_conversion) {
746 for (int i = BEGIN; i < MAX; i++) {
747 char ch = str.at(i);
748 if (ch >= 'a' && ch <= 'z') str.at(i) = ch -= 32; // Convert to upper-case
749 } // -x- for i -x-
750 return str; // Return updated string
751 } // -x- if in_place_conversion -x-
752
753 // --------------------------------------------------------------------------
754 // Internal variables.
755 // --------------------------------------------------------------------------
756 std::string new_string = str; // Copy string (this allocates additional memory)
757
758 // --------------------------------------------------------------------------
759 // Perform isolated conversion.
760 // --------------------------------------------------------------------------
761 for (int i = BEGIN; i < MAX; i++) {
762 char ch = str.at(i);
763 if (ch >= 'a' && ch <= 'z') new_string.at(i) = ch -= 32; // Convert to upper-case
764 } // -x- for i -x-
765 return new_string; // Return new string
766
767 } // -x- std::string to_upper -x-
768
769 /*======================================================================*//**
770 @brief
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
774 present)
775 *///=========================================================================
776 static std::string trim_quotes(
777 /// Source string
778 const std::string& str) noexcept {
779
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
785
786 // --------------------------------------------------------------------------
787 // Process string.
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
791
792 } // -x- std::string trim_quotes -x-
793
794 /*======================================================================*//**
795 @brief
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.
800 @warning
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 *///=========================================================================
805 static void wipe(
806 /// String to wipe
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-
810
811 /*======================================================================*//**
812 @brief
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.
815 @warning
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 *///=========================================================================
820 static void wipe(
821 /// String to wipe
822 const char* data,
823 /// Length of string (-1 = ASCIIZ string)
824 int len = -1,
825 /// Number of passes (default is 1)
826 unsigned int passes = 1) {
827
828 // --------------------------------------------------------------------------
829 // Internal variables.
830 // --------------------------------------------------------------------------
831 if (len == -1) len = std::strlen(data);
832 std::timespec ts;
833 std::timespec_get(&ts, TIME_UTC);
834
835 // --------------------------------------------------------------------------
836 // Seed random number generator with high-resolution time data.
837 // --------------------------------------------------------------------------
838 std::srand(ts.tv_sec * ts.tv_nsec);
839
840 // --------------------------------------------------------------------------
841 // Data wipe loop.
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-
847
848 } // -x- void wipe -x-
849
850 }; // -x- class rtools -x-
851
852} // -x- namespace randolf -x-