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