add sanitize_for_logging() to stringfunc
[libi2ncommon] / src / stringfunc.cpp
1 /*
2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
4
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
7
8 As a special exception, if other files instantiate templates or use macros
9 or inline functions from this file, or you compile this file and link it
10 with other works to produce a work based on this file, this file
11 does not by itself cause the resulting work to be covered
12 by the GNU General Public License.
13
14 However the source code for this file must still be made available
15 in accordance with section (3) of the GNU General Public License.
16
17 This exception does not invalidate any other reasons why a work based
18 on this file might be covered by the GNU General Public License.
19 */
20 /** @file
21  *
22  * (c) Copyright 2007-2008 by Intra2net AG
23  */
24
25 #include <iostream>
26 #include <string>
27 #include <sstream>
28 #include <stdexcept>
29 #include <algorithm>
30
31 #include <wchar.h>
32 #include <stdlib.h>
33 #include <iconv.h>
34 #include <i18n.h>
35
36 #include <stringfunc.hxx>
37
38 using namespace std;
39
40 namespace I2n
41 {
42
43
44 namespace
45 {
46
47 const std::string hexDigitsLower("0123456789abcdef");
48 const std::string hexDigitsUpper("0123456789ABCDEF");
49
50
51 struct UpperFunc
52 {
53    char operator() (char c)
54    {
55       return std::toupper(c);
56    }
57 }; // eo struct UpperFunc
58
59
60 struct LowerFunc
61 {
62    char operator() (char c)
63    {
64       return std::tolower(c);
65    }
66 }; // eo struct LowerFunc
67
68
69 } // eo namespace <anonymous>
70
71
72
73 /**
74  * default list of Whitespaces (" \t\r\n");
75  */
76 const std::string Whitespaces = " \t\r\n";
77
78 /**
79  * default list of lineendings ("\r\n");
80  */
81 const std::string LineEndings= "\r\n";
82
83
84
85 /**
86  * @brief checks if a string begins with a given prefix.
87  * @param[in,out] str the string which is tested
88  * @param prefix the prefix which should be tested for.
89  * @return @a true iff the prefix is not empty and the string begins with that prefix.
90  */
91 bool has_prefix(const std::string& str, const std::string& prefix)
92 {
93    if (prefix.empty() || str.empty() || str.size() < prefix.size() )
94    {
95       return false;
96    }
97    return str.compare(0, prefix.size(), prefix) == 0;
98 } // eo has_prefix(const std::string&,const std::string&)
99
100
101 /**
102  * @brief checks if a string ends with a given suffix.
103  * @param[in,out] str the string which is tested
104  * @param suffix the suffix which should be tested for.
105  * @return @a true iff the suffix is not empty and the string ends with that suffix.
106  */
107 bool has_suffix(const std::string& str, const std::string& suffix)
108 {
109    if (suffix.empty() || str.empty() || str.size() < suffix.size() )
110    {
111       return false;
112    }
113    return str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
114 } // eo has_suffix(const std::string&,const std::string&)
115
116
117 /**
118  * cut off characters from a given list from front and end of a string.
119  * @param[in,out] str the string which should be trimmed.
120  * @param charlist the list of characters to remove from beginning and end of string
121  * @return the result string.
122  */
123 std::string trim_mod(std::string& str, const std::string& charlist)
124 {
125    // first: trim the beginning:
126    std::string::size_type pos= str.find_first_not_of (charlist);
127    if (pos == std::string::npos)
128    {
129       // whole string consists of charlist (or is already empty)
130       str.clear();
131       return str;
132    }
133    else if (pos>0)
134    {
135       // str starts with charlist
136       str.erase(0,pos);
137    }
138    // now let's look at the tail:
139    pos= str.find_last_not_of(charlist) +1;  // note: we already know there is at least one other char!
140    if ( pos < str.size() )
141    {
142       str.erase(pos, str.size()-pos);
143    }
144    return str;
145 } // eo trim_mod(std::string&,const std::string&)
146
147
148
149 /**
150  * removes last character from a string when it is in a list of chars to be removed.
151  * @param[in,out] str the string.
152  * @param what the list of chars which will be tested for.
153  * @return the resulting string with last char removed (if applicable)
154  */
155 std::string chomp_mod(std::string& str, const std::string& what)
156 {
157    if (str.empty() || what.empty() )
158    {
159       return str;
160    }
161    if (what.find(str.at (str.size()-1) ) != std::string::npos)
162    {
163       str.erase(str.size() - 1);
164    }
165    return str;
166 } // eo chomp_mod(std::string&,const std::string&)
167
168
169 /**
170  * @brief converts a string to lower case.
171  * @param[in,out] str the string to modify.
172  * @return the string
173  */
174 std::string to_lower_mod(std::string& str)
175 {
176    std::transform(str.begin(), str.end(), str.begin(), LowerFunc() );
177    return str;
178 } // eo to_lower_mod(std::string&)
179
180
181 /**
182  * @brief converts a string to upper case.
183  * @param[in,out] str the string to modify.
184  * @return the string
185  */
186 std::string to_upper_mod(std::string& str)
187 {
188    std::transform( str.begin(), str.end(), str.begin(), UpperFunc() );
189    return str;
190 } // eo to_upper_mod(std::string&)
191
192
193
194 /**
195  * cut off characters from a given list from front and end of a string.
196  * @param str the string which should be trimmed.
197  * @param charlist the list of characters to remove from beginning and end of string
198  * @return the result string.
199  */
200 std::string trim (const std::string& str, const std::string& charlist)
201 {
202    // first: trim the beginning:
203    std::string::size_type pos0= str.find_first_not_of(charlist);
204    if (pos0 == std::string::npos)
205    {
206       // whole string consists of charlist (or is already empty)
207       return std::string();
208    }
209    // now let's look at the end:
210    std::string::size_type pos1= str.find_last_not_of(charlist);
211    return str.substr(pos0, pos1 - pos0 + 1);
212 } // eo trim(const std:.string&,const std::string&)
213
214
215 /**
216  * removes last character from a string when it is in a list of chars to be removed.
217  * @param str the string.
218  * @param what the list of chars which will be tested for.
219  * @return the resulting string with last char removed (if applicable)
220  */
221 std::string chomp (const std::string& str, const std::string& what)
222 {
223    if (str.empty() || what.empty() )
224    {
225       return str;
226    }
227    if (what.find(str.at (str.size()-1) ) != std::string::npos)
228    {
229       return str.substr(0, str.size()-1);
230    }
231    return str;
232 } // eo chomp(const std:.string&,const std::string&)
233
234
235 /**
236  * @brief returns a lower case version of a given string.
237  * @param str the string
238  * @return the lower case version of the string
239  */
240 std::string to_lower (const std::string& str)
241 {
242    std::string result(str);
243    return to_lower_mod(result);
244 } // eo to_lower(const std::string&)
245
246
247 /**
248  * @brief returns a upper case version of a given string.
249  * @param str the string
250  * @return the upper case version of the string
251  */
252 std::string to_upper(const std::string& str)
253 {
254    std::string result(str);
255    return to_upper_mod(result);
256 } // eo to_upper(const std::string&)
257
258
259
260 /**
261  * @brief removes a given suffix from a string.
262  * @param str the string.
263  * @param suffix the suffix which should be removed if the string ends with it.
264  * @return the string without the suffix.
265  *
266  * If the string ends with the suffix, it is removed. If the the string doesn't end
267  * with the suffix the original string is returned.
268  */
269 std::string remove_suffix(const std::string& str, const std::string& suffix)
270 {
271    if (has_suffix(str,suffix) )
272    {
273       return str.substr(0, str.size()-suffix.size() );
274    }
275    return str;
276 } // eo remove_suffix(const std::string&,const std::string&)
277
278
279
280 /**
281  * @brief removes a given prefix from a string.
282  * @param str the string.
283  * @param prefix the prefix which should be removed if the string begins with it.
284  * @return the string without the prefix.
285  *
286  * If the string begins with the prefix, it is removed. If the the string doesn't begin
287  * with the prefix the original string is returned.
288  */
289 std::string remove_prefix(const std::string& str, const std::string& prefix)
290 {
291    if (has_prefix(str,prefix) )
292    {
293       return str.substr( prefix.size() );
294    }
295    return str;
296 } // eo remove_prefix(const std::string&,const std::string&)
297
298
299 /**
300  * split a string to key and value delimited by a given delimiter.
301  * The resulting key and value strings are trimmed (Whitespaces removed at beginning and end).
302  * @param str the string which should be splitted.
303  * @param[out] key the resulting key
304  * @param[out] value the resulting value
305  * @param delimiter the delimiter between key and value; default is '='.
306  * @return @a true if the split was successful.
307  */
308 bool pair_split(
309    const std::string& str,
310    std::string& key,
311    std::string& value,
312    char delimiter)
313 {
314    std::string::size_type pos = str.find (delimiter);
315    if (pos == std::string::npos) return false;
316    key= str.substr(0,pos);
317    value= str.substr(pos+1);
318    trim_mod(key);
319    trim_mod(value);
320    return true;
321 } // eo pair_split(const std::string&,std::string&,std::string&,char)
322
323
324 /**
325  * splits a string by given delimiter
326  *
327  * @param[in] str the string which should be splitted.
328  * @param[out] result the list resulting from splitting  @a str.
329  * @param[in] delimiter the delimiter (word/phrase) at which @a str should be splitted.
330  * @param[in] omit_empty should empty parts not be stored?
331  * @param[in] trim_list list of characters the parts should be trimmed by.
332  *  (empty string results in no trim)
333  */
334 void split_string(
335    const std::string& str,
336    std::list<std::string>& result,
337    const std::string& delimiter,
338    bool omit_empty,
339    const std::string& trim_list
340 )
341 {
342    std::string::size_type pos, last_pos=0;
343    bool delimiter_found= false;
344    while ( last_pos < str.size()  && last_pos != std::string::npos)
345    {
346       pos= str.find(delimiter, last_pos);
347       std::string part;
348       if (pos == std::string::npos)
349       {
350          part= str.substr(last_pos);
351          delimiter_found= false;
352       }
353       else
354       {
355          part= str.substr(last_pos, pos-last_pos);
356          delimiter_found=true;
357       }
358       if (pos != std::string::npos)
359       {
360          last_pos= pos+ delimiter.size();
361       }
362       else
363       {
364          last_pos= std::string::npos;
365       }
366       if (!trim_list.empty() ) trim_mod (part, trim_list);
367       if (omit_empty && part.empty() ) continue;
368       result.push_back( part );
369    }
370    // if the string ends with a delimiter we need to append an empty string if no omit_empty
371    // was given.
372    // (this way we keep the split result consistent to a join operation)
373    if (delimiter_found && !omit_empty)
374    {
375       result.push_back("");
376    }
377 } // eo split_string(const std::string&,std::list< std::string >&,const std::string&,bool,const std::string&)
378
379
380 /**
381  * splits a string by a given delimiter
382  * @param str the string which should be splitted.
383  * @param delimiter delimiter the delimiter (word/phrase) at which @a str should be splitted.
384  * @param[in] omit_empty should empty parts not be stored?
385  * @param[in] trim_list list of characters the parts should be trimmed by.
386  *  (empty string results in no trim)
387  * @return the list resulting from splitting @a str.
388  */
389 std::list<std::string> split_string(
390    const std::string& str,
391    const std::string& delimiter,
392    bool omit_empty,
393    const std::string& trim_list
394 )
395 {
396    std::list<std::string> result;
397    split_string(str, result, delimiter, omit_empty, trim_list);
398    return result;
399 } // eo split_string(const std::string&,const std::string&,bool,const std::string&)
400
401
402 /**
403  * @brief joins a list of strings into a single string.
404  *
405  * This funtion is (basically) the reverse operation of @a split_string.
406  *
407  * @param parts the list of strings.
408  * @param delimiter the delimiter which is inserted between the strings.
409  * @return the joined string.
410  */
411 std::string join_string(
412    const std::list< std::string >& parts,
413    const std::string& delimiter
414 )
415 {
416    std::string result;
417    if (! parts.empty() )
418    {
419       std::list< std::string >::const_iterator it= parts.begin();
420       result = *it;
421       while ( ++it != parts.end() )
422       {
423          result+= delimiter;
424          result+= *it;
425       }
426    }
427    return result;
428 } // eo join_string(const std::list< std::string >&,const std::string&)
429
430
431
432 /*
433 ** conversions
434 */
435
436
437 /**
438  * @brief returns a hex string from a binary string.
439  * @param str the (binary) string
440  * @param upper_case_digits determine whether to use upper case characters for digits A-F.
441  * @return the string in hex notation.
442  */
443 std::string convert_binary_to_hex(
444    const std::string& str,
445    bool upper_case_digits
446 )
447 {
448    std::string result;
449    std::string hexDigits(upper_case_digits ? hexDigitsUpper : hexDigitsLower);
450    for ( std::string::const_iterator it= str.begin();
451          it != str.end();
452          ++it)
453    {
454       result.push_back( hexDigits[ ( (*it) >> 4) & 0x0f ] );
455       result.push_back( hexDigits[ (*it) & 0x0f ] );
456    }
457    return result;
458 } // eo convert_binary_to_hex(const std::string&,bool)
459
460
461 /**
462  * @brief converts a hex digit string to binary string.
463  * @param str hex digit string
464  * @return the binary string.
465  *
466  * The hex digit string may contains white spaces or colons which are treated
467  * as delimiters between hex digit groups.
468  *
469  * @todo rework the handling of half nibbles (consistency)!
470  */
471 std::string convert_hex_to_binary(
472    const std::string& str
473 )
474 throw (std::runtime_error)
475 {
476    std::string result;
477    char c= 0;
478    bool hasNibble= false;
479    bool lastWasWS= true;
480    for ( std::string::const_iterator it= str.begin();
481          it != str.end();
482          ++it)
483    {
484       std::string::size_type p = hexDigitsLower.find( *it );
485       if (p== std::string::npos)
486       {
487          p= hexDigitsUpper.find( *it );
488       }
489       if (p == std::string::npos)
490       {
491          if (   ( Whitespaces.find( *it ) != std::string::npos) // is it a whitespace?
492                 or ( *it == ':') // or a colon?
493             )
494          {
495             // we treat that as a valid delimiter:
496             if (hasNibble)
497             {
498                // 1 nibble before WS is treate as lower part:
499                result.push_back(c);
500                // reset state:
501                hasNibble= false;
502             }
503             lastWasWS= true;
504             continue;
505          }
506       }
507       if (p == std::string::npos )
508       {
509          throw runtime_error("illegal character in hex digit string: " + str);
510       }
511       lastWasWS= false;
512       if (hasNibble)
513       {
514          c<<=4;
515       }
516       else
517       {
518          c=0;
519       }
520       c+= (p & 0x0f);
521       if (hasNibble)
522       {
523          //we already had a nibble, so a char is complete now:
524          result.push_back( c );
525          hasNibble=false;
526       }
527       else
528       {
529          // this is the first nibble of a new char:
530          hasNibble=true;
531       }
532    }
533    if (hasNibble)
534    {
535       //well, there is one nibble left
536       // let's do some heuristics:
537       if (lastWasWS)
538       {
539          // if the preceeding character was a white space (or a colon)
540          // we treat the nibble as lower part:
541          //( this is consistent with shortened hex notations where leading zeros are not noted)
542          result.push_back( c );
543       }
544       else
545       {
546          // if it was part of a hex digit chain, we treat it as UPPER part (!!)
547          result.push_back( c << 4 );
548       }
549    }
550    return result;
551 } // eo convert_hex_to_binary(const std::string&)
552
553
554 } // eo namespace I2n
555
556
557
558
559 std::string iso_to_utf8(const std::string& isostring)
560 {
561    string result;
562
563    iconv_t i2utf8 = iconv_open("UTF-8", "ISO-8859-1");
564
565    if (iso_to_utf8 == (iconv_t)-1)
566       throw runtime_error("iconv can't convert from ISO-8859-1 to UTF-8");
567
568    size_t in_size=isostring.size();
569    size_t out_size=in_size*4;
570
571    char *buf = (char *)malloc(out_size+1);
572    if (buf == NULL)
573       throw runtime_error("out of memory for iconv buffer");
574
575    char *in = (char *)isostring.c_str();
576    char *out = buf;
577    iconv(i2utf8, &in, &in_size, &out, &out_size);
578
579    buf[isostring.size()*4-out_size]=0;
580
581    result=buf;
582
583    free(buf);
584    iconv_close(i2utf8);
585
586    return result;
587 }
588
589 std::string utf8_to_iso(const std::string& utf8string)
590 {
591    string result;
592
593    iconv_t utf82iso = iconv_open("ISO-8859-1","UTF-8");
594
595    if (utf82iso == (iconv_t)-1)
596       throw runtime_error("iconv can't convert from UTF-8 to ISO-8859-1");
597
598    size_t in_size=utf8string.size();
599    size_t out_size=in_size;
600
601    char *buf = (char *)malloc(out_size+1);
602    if (buf == NULL)
603       throw runtime_error("out of memory for iconv buffer");
604
605    char *in = (char *)utf8string.c_str();
606    char *out = buf;
607    iconv(utf82iso, &in, &in_size, &out, &out_size);
608
609    buf[utf8string.size()-out_size]=0;
610
611    result=buf;
612
613    free(buf);
614    iconv_close(utf82iso);
615
616    return result;
617 }
618
619 wchar_t* utf8_to_wbuf(const std::string& utf8string)
620 {
621    iconv_t utf82wstr = iconv_open("UCS-4LE","UTF-8");
622
623    if (utf82wstr == (iconv_t)-1)
624       throw runtime_error("iconv can't convert from UTF-8 to UCS-4");
625
626    size_t in_size=utf8string.size();
627    size_t out_size= (in_size+1)*sizeof(wchar_t);
628
629    wchar_t *buf = (wchar_t *)malloc(out_size);
630    if (buf == NULL)
631       throw runtime_error("out of memory for iconv buffer");
632
633    char *in = (char *)utf8string.c_str();
634    char *out = (char*) buf;
635    if (iconv(utf82wstr, &in, &in_size, &out, &out_size) == (size_t)-1)
636       throw runtime_error("error converting char encodings");
637
638    buf[ ( (utf8string.size()+1)*sizeof(wchar_t)-out_size) /sizeof(wchar_t) ]=0;
639
640    iconv_close(utf82wstr);
641
642    return buf;
643 }
644
645 std::string utf7imap_to_utf8(const std::string& utf7imapstring)
646 {
647    string result;
648
649    iconv_t utf7imap2utf8 = iconv_open("UTF-8","UTF-7-IMAP");
650
651    if (utf7imap2utf8 == (iconv_t)-1)
652       throw runtime_error("iconv can't convert from UTF-7-IMAP to UTF-8");
653
654    size_t in_size=utf7imapstring.size();
655    size_t out_size=in_size*4;
656
657    char *buf = (char *)malloc(out_size+1);
658    if (buf == NULL)
659       throw runtime_error("out of memory for iconv buffer");
660
661    char *in = (char *)utf7imapstring.c_str();
662    char *out = buf;
663    iconv(utf7imap2utf8, &in, &in_size, &out, &out_size);
664
665    buf[utf7imapstring.size()*4-out_size]=0;
666
667    result=buf;
668
669    free(buf);
670    iconv_close(utf7imap2utf8);
671
672    return result;
673 }
674
675 std::string utf8_to_utf7imap(const std::string& utf8string)
676 {
677    string result;
678
679    iconv_t utf82utf7imap = iconv_open("UTF-7-IMAP", "UTF-8");
680
681    if (utf82utf7imap == (iconv_t)-1)
682       throw runtime_error("iconv can't convert from UTF-7-IMAP to UTF-8");
683
684    // UTF-7 is base64 encoded, a buffer 10x as large
685    // as the utf-8 buffer should be enough. If not the string will be truncated.
686    size_t in_size=utf8string.size();
687    size_t out_size=in_size*10;
688
689    char *buf = (char *)malloc(out_size+1);
690    if (buf == NULL)
691       throw runtime_error("out of memory for iconv buffer");
692
693    char *in = (char *)utf8string.c_str();
694    char *out = buf;
695    iconv(utf82utf7imap, &in, &in_size, &out, &out_size);
696
697    buf[utf8string.size()*10-out_size]= 0;
698
699    result=buf;
700
701    free(buf);
702    iconv_close(utf82utf7imap);
703
704    return result;
705 }
706
707 // Tokenize string by (html) tags
708 void tokenize_by_tag(vector<pair<string,bool> > &tokenized, const std::string &input)
709 {
710    string::size_type pos, len = input.size();
711    bool inside_tag = false;
712    string current;
713
714    for (pos = 0; pos < len; pos++)
715    {
716       if (input[pos] == '<')
717       {
718          inside_tag = true;
719
720          if (!current.empty() )
721          {
722             tokenized.push_back( make_pair(current, false) );
723             current = "";
724          }
725
726          current += input[pos];
727       }
728       else if (input[pos] == '>' && inside_tag)
729       {
730          current += input[pos];
731          inside_tag = false;
732          if (!current.empty() )
733          {
734             tokenized.push_back( make_pair(current, true) );
735             current = "";
736          }
737       }
738       else
739          current += input[pos];
740    }
741
742    // String left over in buffer?
743    if (!current.empty() )
744       tokenized.push_back( make_pair(current, false) );
745 } // eo tokenize_by_tag
746
747
748 std::string strip_html_tags(const std::string &input)
749 {
750    // Pair first: string, second: isTag
751    vector<pair<string,bool> > tokenized;
752    tokenize_by_tag (tokenized, input);
753
754    string output;
755    vector<pair<string,bool> >::const_iterator token, tokens_end = tokenized.end();
756    for (token = tokenized.begin(); token != tokens_end; ++token)
757       if (!token->second)
758          output += token->first;
759
760    return output;
761 } // eo strip_html_tags
762
763
764 // Smart-encode HTML en
765 string smart_html_entities(const std::string &input)
766 {
767    // Pair first: string, second: isTag
768    vector<pair<string,bool> > tokenized;
769    tokenize_by_tag (tokenized, input);
770
771    string output;
772    vector<pair<string,bool> >::const_iterator token, tokens_end = tokenized.end();
773    for (token = tokenized.begin(); token != tokens_end; ++token)
774    {
775       // keep HTML tags as they are
776       if (token->second)
777          output += token->first;
778       else
779          output += html_entities(token->first);
780    }
781
782    return output;
783 }
784
785
786 string::size_type find_8bit(const std::string &str)
787 {
788    string::size_type l=str.size();
789    for (string::size_type p=0; p < l; p++)
790       if (static_cast<unsigned char>(str[p]) > 127)
791          return p;
792
793    return string::npos;
794 }
795
796 // encoded UTF-8 chars into HTML entities
797 string html_entities(std::string str)
798 {
799    // Normal chars
800    replace_all (str, "&", "&amp;");
801    replace_all (str, "<", "&lt;");
802    replace_all (str, ">", "&gt;");
803    replace_all (str, "\"", "&quot;");
804    replace_all (str, "'", "&#x27;");
805    replace_all (str, "/", "&#x2F;");
806
807    // Umlauts
808    replace_all (str, "\xC3\xA4", "&auml;");
809    replace_all (str, "\xC3\xB6", "&ouml;");
810    replace_all (str, "\xC3\xBC", "&uuml;");
811    replace_all (str, "\xC3\x84", "&Auml;");
812    replace_all (str, "\xC3\x96", "&Ouml;");
813    replace_all (str, "\xC3\x9C", "&Uuml;");
814
815    // Misc
816    replace_all (str, "\xC3\x9F", "&szlig;");
817
818    // conversion of remaining non-ASCII chars needed?
819    // just do if needed because of performance
820    if (find_8bit(str) != string::npos)
821    {
822       // convert to fixed-size encoding UTF-32
823       wchar_t* wbuf=utf8_to_wbuf(str);
824       ostringstream target;
825
826       // replace all non-ASCII chars with HTML representation
827       for (int p=0; wbuf[p] != 0; p++)
828       {
829          unsigned int c=wbuf[p];
830
831          if (c <= 127)
832             target << static_cast<unsigned char>(c);
833          else
834             target << "&#" << c << ';';
835       }
836
837       free(wbuf);
838
839       str=target.str();
840    }
841
842    return str;
843 } // eo html_entities(std::string)
844
845 // convert HTML entities to something that can be viewed on a basic text console (restricted to ASCII-7)
846 string html_entities_to_console(std::string str)
847 {
848    // Normal chars
849    replace_all (str, "&amp;", "&");
850    replace_all (str, "&lt;", "<");
851    replace_all (str, "&gt;", ">");
852    replace_all (str, "&quot;", "\"");
853    replace_all (str, "&#x27;", "'");
854    replace_all (str, "&#x2F;", "/");
855
856    // Umlauts
857    replace_all (str, "&auml;", "ae");
858    replace_all (str, "&ouml;", "oe");
859    replace_all (str, "&uuml;", "ue");
860    replace_all (str, "&Auml;", "Ae");
861    replace_all (str, "&Ouml;", "Oe");
862    replace_all (str, "&Uuml;", "Ue");
863
864    // Misc
865    replace_all (str, "&szlig;", "ss");
866
867    return str;
868 }
869
870 bool replace_all(string &base, const char *ist, const char *soll)
871 {
872    string i=ist;
873    string s=soll;
874    return replace_all(base,&i,&s);
875 }
876
877 bool replace_all(string &base, const string &ist, const char *soll)
878 {
879    string s=soll;
880    return replace_all(base,&ist,&s);
881 }
882
883 bool replace_all(string &base, const string *ist, const string *soll)
884 {
885    return replace_all(base,*ist,*soll);
886 }
887
888 bool replace_all(string &base, const char *ist, const string *soll)
889 {
890    string i=ist;
891    return replace_all(base,&i,soll);
892 }
893
894 bool replace_all(string &base, const string &ist, const string &soll)
895 {
896    bool found_ist = false;
897    string::size_type a=0;
898
899    if (ist.empty() )
900       throw runtime_error ("replace_all called with empty search string");
901
902    while ( (a=base.find(ist,a) ) != string::npos)
903    {
904       base.replace(a,ist.size(),soll);
905       a=a+soll.size();
906       found_ist = true;
907    }
908
909    return found_ist;
910 }
911
912 /**
913  * @brief replaces all characters that could be problematic or impose a security risk when being logged
914  * @param str the original string
915  * @param replace_with the character to replace the unsafe chars with
916  * @return a string that is safe to send to syslog or other logfiles
917  *
918  * All chars between 0x20 (space) and 0x7E (~) (including) are considered safe for logging.
919  * See e.g. RFC 5424, section 8.2 or the posix character class "printable".
920  * This eliminates all possible problems with NUL, control characters, 8 bit chars, UTF8.
921  *
922  */
923 std::string sanitize_for_logging(const std::string &str, const char replace_with)
924 {
925     std::string output=str;
926
927     for (std::string::size_type p=0; p < output.size(); p++)
928         if (output[p] < 0x20 || output[p] > 0x7E)
929             output[p]=replace_with;
930
931     return output;
932 }
933
934 #if 0
935 string to_lower(const string &src)
936 {
937    string dst = src;
938
939    string::size_type pos, end = dst.size();
940    for (pos = 0; pos < end; pos++)
941       dst[pos] = tolower(dst[pos]);
942
943    return dst;
944 }
945
946 string to_upper(const string &src)
947 {
948    string dst = src;
949
950    string::size_type pos, end = dst.size();
951    for (pos = 0; pos < end; pos++)
952       dst[pos] = toupper(dst[pos]);
953
954    return dst;
955 }
956 #endif
957
958 const int MAX_UNIT_FORMAT_SYMBOLS = 6;
959
960 const string shortUnitFormatSymbols[MAX_UNIT_FORMAT_SYMBOLS] = {
961         " B",
962         " KB",
963         " MB",
964         " GB",
965         " TB",
966         " PB"
967 };
968
969 const string longUnitFormatSymbols[MAX_UNIT_FORMAT_SYMBOLS] = {
970         i18n_noop(" Bytes"),
971         i18n_noop(" KBytes"),
972         i18n_noop(" MBytes"),
973         i18n_noop(" GBytes"),
974         i18n_noop(" TBytes"),
975         i18n_noop(" PBytes")
976 };
977
978
979 long double rounding_upwards(
980         long double number,
981         const int rounding_multiplier
982 )
983 {
984     long double rounded_number;
985     rounded_number = number * rounding_multiplier;
986     rounded_number += 0.5;
987     rounded_number = (int64_t) (rounded_number);
988     rounded_number = (long double) (rounded_number) / (long double) (rounding_multiplier);
989
990     return rounded_number;
991 }
992
993
994 string nice_unit_format(
995         const int64_t input,
996         const UnitFormat format,
997         const UnitBase base
998 )
999 {
1000    // select the system of units (decimal or binary)
1001    int multiple = 0;
1002    if (base == UnitBase1000)
1003    {
1004        multiple = 1000;
1005    }
1006    else
1007    {
1008        multiple = 1024;
1009    }
1010
1011    long double size = input;
1012
1013    // check the size of the input number to fit in the appropriate symbol
1014    int sizecount = 0;
1015    while (size > multiple)
1016    {
1017        size = size / multiple;
1018        sizecount++;
1019
1020        // rollback to the previous values and stop the loop when cannot
1021        // represent the number length.
1022        if (sizecount >= MAX_UNIT_FORMAT_SYMBOLS)
1023        {
1024            size = size * multiple;
1025            sizecount--;
1026            break;
1027        }
1028    }
1029
1030    // round the input number "half up" to multiples of 10
1031    const int rounding_multiplier = 10;
1032    size = rounding_upwards(size, rounding_multiplier);
1033
1034    // format the input number, placing the appropriate symbol
1035    ostringstream out;
1036    out.setf (ios::fixed);
1037    if (format == ShortUnitFormat)
1038    {
1039        out.precision(1);
1040        out << size << i18n( shortUnitFormatSymbols[sizecount].c_str() );
1041    }
1042    else
1043    {
1044        out.precision (2);
1045        out << size << i18n( longUnitFormatSymbols[sizecount].c_str() );
1046    }
1047
1048    return out.str();
1049 } // eo nice_unit_format(int input)
1050
1051
1052 string escape(const string &s)
1053 {
1054    string out(s);
1055    string::size_type p;
1056
1057    p=0;
1058    while ( (p=out.find_first_of("\"\\",p) ) !=out.npos)
1059    {
1060       out.insert (p,"\\");
1061       p+=2;
1062    }
1063
1064    p=0;
1065    while ( (p=out.find_first_of("\r",p) ) !=out.npos)
1066    {
1067       out.replace (p,1,"\\r");
1068       p+=2;
1069    }
1070
1071    p=0;
1072    while ( (p=out.find_first_of("\n",p) ) !=out.npos)
1073    {
1074       out.replace (p,1,"\\n");
1075       p+=2;
1076    }
1077
1078    out='"'+out+'"';
1079
1080    return out;
1081 } // eo scape(const std::string&)
1082
1083
1084 string descape(const string &s, int startpos, int &endpos)
1085 {
1086    string out;
1087
1088    if (s.at(startpos) != '"')
1089       throw out_of_range("value not type escaped string");
1090
1091    out=s.substr(startpos+1);
1092    string::size_type p=0;
1093
1094    // search for the end of the string
1095    while ( (p=out.find("\"",p) ) !=out.npos)
1096    {
1097       int e=p-1;
1098       bool escaped=false;
1099
1100       // the " might be escaped with a backslash
1101       while (e>=0 && out.at (e) =='\\')
1102       {
1103          if (escaped == false)
1104             escaped=true;
1105          else
1106             escaped=false;
1107
1108          e--;
1109       }
1110
1111       if (escaped==false)
1112          break;
1113       else
1114          p++;
1115    }
1116
1117    // we now have the end of the string
1118    out=out.substr(0,p);
1119
1120    // tell calling prog about the endposition
1121    endpos=startpos+p+1;
1122
1123    // descape all \ stuff inside the string now
1124    p=0;
1125    while ( (p=out.find_first_of("\\",p) ) !=out.npos)
1126    {
1127       switch (out.at(p+1) )
1128       {
1129          case 'r':
1130             out.replace(p,2,"\r");
1131             break;
1132          case 'n':
1133             out.replace(p,2,"\n");
1134             break;
1135          default:
1136             out.erase(p,1);
1137       }
1138       p++;
1139    }
1140
1141    return out;
1142 } // eo descape(const std::string&,int,int&)
1143
1144
1145 string escape_shellarg(const string &input)
1146 {
1147    string output = "'";
1148    string::const_iterator it, it_end = input.end();
1149    for (it = input.begin(); it != it_end; ++it)
1150    {
1151       if ( (*it) == '\'')
1152          output += "'\\'";
1153
1154       output += *it;
1155    }
1156
1157    output += "'";
1158    return output;
1159 }