Revert "restore join_string() symbols in library"
[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 #include <cmath>    // for round()
31 #include <climits>
32
33 #include <wchar.h>
34 #include <stdlib.h>
35 #include <iconv.h>
36 #include <i18n.h>
37
38 #include <boost/numeric/conversion/cast.hpp>
39 #include <boost/foreach.hpp>
40
41 #include <boost/assert.hpp>
42 #include <boost/shared_ptr.hpp>
43 #include <openssl/bio.h>
44 #include <openssl/evp.h>
45
46 #include <stringfunc.hxx>
47
48 using namespace std;
49
50 namespace I2n
51 {
52
53
54 namespace
55 {
56
57 const std::string hexDigitsLower("0123456789abcdef");
58 const std::string hexDigitsUpper("0123456789ABCDEF");
59
60
61 struct UpperFunc
62 {
63    char operator() (char c)
64    {
65       return std::toupper(c);
66    }
67 }; // eo struct UpperFunc
68
69
70 struct LowerFunc
71 {
72    char operator() (char c)
73    {
74       return std::tolower(c);
75    }
76 }; // eo struct LowerFunc
77
78
79 } // eo namespace <anonymous>
80
81
82
83 /**
84  * default list of Whitespaces (" \t\r\n");
85  */
86 const std::string Whitespaces = " \t\r\n";
87
88 /**
89  * default list of lineendings ("\r\n");
90  */
91 const std::string LineEndings= "\r\n";
92
93
94
95 /**
96  * @brief checks if a string begins with a given prefix.
97  * @param[in,out] str the string which is tested
98  * @param prefix the prefix which should be tested for.
99  * @return @a true iff the prefix is not empty and the string begins with that prefix.
100  */
101 bool has_prefix(const std::string& str, const std::string& prefix)
102 {
103    if (prefix.empty() || str.empty() || str.size() < prefix.size() )
104    {
105       return false;
106    }
107    return str.compare(0, prefix.size(), prefix) == 0;
108 } // eo has_prefix(const std::string&,const std::string&)
109
110
111 /**
112  * @brief checks if a string ends with a given suffix.
113  * @param[in,out] str the string which is tested
114  * @param suffix the suffix which should be tested for.
115  * @return @a true iff the suffix is not empty and the string ends with that suffix.
116  */
117 bool has_suffix(const std::string& str, const std::string& suffix)
118 {
119    if (suffix.empty() || str.empty() || str.size() < suffix.size() )
120    {
121       return false;
122    }
123    return str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
124 } // eo has_suffix(const std::string&,const std::string&)
125
126
127 /**
128  * cut off characters from a given list from front and end of a string.
129  * @param[in,out] str the string which should be trimmed.
130  * @param charlist the list of characters to remove from beginning and end of string
131  * @return the result string.
132  */
133 std::string trim_mod(std::string& str, const std::string& charlist)
134 {
135    // first: trim the beginning:
136    std::string::size_type pos= str.find_first_not_of (charlist);
137    if (pos == std::string::npos)
138    {
139       // whole string consists of charlist (or is already empty)
140       str.clear();
141       return str;
142    }
143    else if (pos>0)
144    {
145       // str starts with charlist
146       str.erase(0,pos);
147    }
148    // now let's look at the tail:
149    pos= str.find_last_not_of(charlist) +1;  // note: we already know there is at least one other char!
150    if ( pos < str.size() )
151    {
152       str.erase(pos, str.size()-pos);
153    }
154    return str;
155 } // eo trim_mod(std::string&,const std::string&)
156
157
158
159 /**
160  * removes last character from a string when it is in a list of chars to be removed.
161  * @param[in,out] str the string.
162  * @param what the list of chars which will be tested for.
163  * @return the resulting string with last char removed (if applicable)
164  */
165 std::string chomp_mod(std::string& str, const std::string& what)
166 {
167    if (str.empty() || what.empty() )
168    {
169       return str;
170    }
171    if (what.find(str.at (str.size()-1) ) != std::string::npos)
172    {
173       str.erase(str.size() - 1);
174    }
175    return str;
176 } // eo chomp_mod(std::string&,const std::string&)
177
178
179 /**
180  * @brief converts a string to lower case.
181  * @param[in,out] str the string to modify.
182  * @return the string
183  */
184 std::string to_lower_mod(std::string& str)
185 {
186    std::transform(str.begin(), str.end(), str.begin(), LowerFunc() );
187    return str;
188 } // eo to_lower_mod(std::string&)
189
190
191 /**
192  * @brief converts a string to upper case.
193  * @param[in,out] str the string to modify.
194  * @return the string
195  */
196 std::string to_upper_mod(std::string& str)
197 {
198    std::transform( str.begin(), str.end(), str.begin(), UpperFunc() );
199    return str;
200 } // eo to_upper_mod(std::string&)
201
202
203
204 /**
205  * cut off characters from a given list from front and end of a string.
206  * @param str the string which should be trimmed.
207  * @param charlist the list of characters to remove from beginning and end of string
208  * @return the result string.
209  */
210 std::string trim (const std::string& str, const std::string& charlist)
211 {
212    // first: trim the beginning:
213    std::string::size_type pos0= str.find_first_not_of(charlist);
214    if (pos0 == std::string::npos)
215    {
216       // whole string consists of charlist (or is already empty)
217       return std::string();
218    }
219    // now let's look at the end:
220    std::string::size_type pos1= str.find_last_not_of(charlist);
221    return str.substr(pos0, pos1 - pos0 + 1);
222 } // eo trim(const std:.string&,const std::string&)
223
224
225 /**
226  * removes last character from a string when it is in a list of chars to be removed.
227  * @param str the string.
228  * @param what the list of chars which will be tested for.
229  * @return the resulting string with last char removed (if applicable)
230  */
231 std::string chomp (const std::string& str, const std::string& what)
232 {
233    if (str.empty() || what.empty() )
234    {
235       return str;
236    }
237    if (what.find(str.at (str.size()-1) ) != std::string::npos)
238    {
239       return str.substr(0, str.size()-1);
240    }
241    return str;
242 } // eo chomp(const std:.string&,const std::string&)
243
244
245 /**
246  * @brief returns a lower case version of a given string.
247  * @param str the string
248  * @return the lower case version of the string
249  */
250 std::string to_lower (const std::string& str)
251 {
252    std::string result(str);
253    return to_lower_mod(result);
254 } // eo to_lower(const std::string&)
255
256
257 /**
258  * @brief returns a upper case version of a given string.
259  * @param str the string
260  * @return the upper case version of the string
261  */
262 std::string to_upper(const std::string& str)
263 {
264    std::string result(str);
265    return to_upper_mod(result);
266 } // eo to_upper(const std::string&)
267
268
269
270 /**
271  * @brief removes a given suffix from a string.
272  * @param str the string.
273  * @param suffix the suffix which should be removed if the string ends with it.
274  * @return the string without the suffix.
275  *
276  * If the string ends with the suffix, it is removed. If the the string doesn't end
277  * with the suffix the original string is returned.
278  */
279 std::string remove_suffix(const std::string& str, const std::string& suffix)
280 {
281    if (has_suffix(str,suffix) )
282    {
283       return str.substr(0, str.size()-suffix.size() );
284    }
285    return str;
286 } // eo remove_suffix(const std::string&,const std::string&)
287
288
289
290 /**
291  * @brief removes a given prefix from a string.
292  * @param str the string.
293  * @param prefix the prefix which should be removed if the string begins with it.
294  * @return the string without the prefix.
295  *
296  * If the string begins with the prefix, it is removed. If the the string doesn't begin
297  * with the prefix the original string is returned.
298  */
299 std::string remove_prefix(const std::string& str, const std::string& prefix)
300 {
301    if (has_prefix(str,prefix) )
302    {
303       return str.substr( prefix.size() );
304    }
305    return str;
306 } // eo remove_prefix(const std::string&,const std::string&)
307
308
309 /**
310  * split a string to key and value delimited by a given delimiter.
311  * The resulting key and value strings are trimmed (Whitespaces removed at beginning and end).
312  * @param str the string which should be splitted.
313  * @param[out] key the resulting key
314  * @param[out] value the resulting value
315  * @param delimiter the delimiter between key and value; default is '='.
316  * @return @a true if the split was successful.
317  */
318 bool pair_split(
319    const std::string& str,
320    std::string& key,
321    std::string& value,
322    char delimiter)
323 {
324    std::string::size_type pos = str.find (delimiter);
325    if (pos == std::string::npos) return false;
326    key= str.substr(0,pos);
327    value= str.substr(pos+1);
328    trim_mod(key);
329    trim_mod(value);
330    return true;
331 } // eo pair_split(const std::string&,std::string&,std::string&,char)
332
333
334 /**
335  * splits a string by given delimiter
336  *
337  * @param[in] str the string which should be splitted.
338  * @param[out] result the list resulting from splitting  @a str.
339  * @param[in] delimiter the delimiter (word/phrase) at which @a str should be splitted.
340  * @param[in] omit_empty should empty parts not be stored?
341  * @param[in] trim_list list of characters the parts should be trimmed by.
342  *  (empty string results in no trim)
343  */
344 void split_string(
345    const std::string& str,
346    std::list<std::string>& result,
347    const std::string& delimiter,
348    bool omit_empty,
349    const std::string& trim_list
350 )
351 {
352    std::string::size_type pos, last_pos=0;
353    bool delimiter_found= false;
354    while ( last_pos < str.size()  && last_pos != std::string::npos)
355    {
356       pos= str.find(delimiter, last_pos);
357       std::string part;
358       if (pos == std::string::npos)
359       {
360          part= str.substr(last_pos);
361          delimiter_found= false;
362       }
363       else
364       {
365          part= str.substr(last_pos, pos-last_pos);
366          delimiter_found=true;
367       }
368       if (pos != std::string::npos)
369       {
370          last_pos= pos+ delimiter.size();
371       }
372       else
373       {
374          last_pos= std::string::npos;
375       }
376       if (!trim_list.empty() ) trim_mod (part, trim_list);
377       if (omit_empty && part.empty() ) continue;
378       result.push_back( part );
379    }
380    // if the string ends with a delimiter we need to append an empty string if no omit_empty
381    // was given.
382    // (this way we keep the split result consistent to a join operation)
383    if (delimiter_found && !omit_empty)
384    {
385       result.push_back("");
386    }
387 } // eo split_string(const std::string&,std::list< std::string >&,const std::string&,bool,const std::string&)
388
389
390 /** call split_string with list<string>, converts result to vector; vector is clear()-ed first
391  *
392  * Note: Uses 3 O(n)-operations: list.size, vector.resize and std::swap_ranges;
393  *       not sure whether there is a better way to do this
394  * */
395 void split_string(
396    const std::string& str,
397    std::vector<std::string>& result,
398    const std::string& delimiter,
399    bool omit_empty,
400    const std::string& trim_list
401 )
402 {
403     std::list<std::string> tmp;
404     split_string(str, tmp, delimiter, omit_empty, trim_list);
405     std::size_t size = tmp.size();   // this is O(n)
406     result.clear();
407     result.resize(size);             // also O(n)
408     std::swap_ranges(tmp.begin(), tmp.end(), result.begin());   // also O(n)
409 }
410
411 /**
412  * splits a string by a given delimiter
413  * @param str the string which should be splitted.
414  * @param delimiter delimiter the delimiter (word/phrase) at which @a str should be splitted.
415  * @param[in] omit_empty should empty parts not be stored?
416  * @param[in] trim_list list of characters the parts should be trimmed by.
417  *  (empty string results in no trim)
418  * @return the list resulting from splitting @a str.
419  */
420 std::list<std::string> split_string(
421    const std::string& str,
422    const std::string& delimiter,
423    bool omit_empty,
424    const std::string& trim_list
425 )
426 {
427    std::list<std::string> result;
428    split_string(str, result, delimiter, omit_empty, trim_list);
429    return result;
430 } // eo split_string(const std::string&,const std::string&,bool,const std::string&)
431
432
433 std::string join_string (
434    const char *const parts[], /* assumed NULL-terminated */
435    const std::string& delimiter
436 )
437 {
438    std::string result;
439
440    if (parts != NULL)
441    {
442        const char *const *cur = parts;
443
444        if (*cur != NULL) {
445            result = std::string (*cur);
446
447            while (*++cur != NULL) {
448                result += delimiter;
449                result += std::string (*cur);
450            }
451        }
452    }
453
454    return result;
455 }
456
457
458
459 /*
460 ** conversions
461 */
462
463
464 /**
465  * @brief returns a hex string from a binary string.
466  * @param str the (binary) string
467  * @param upper_case_digits determine whether to use upper case characters for digits A-F.
468  * @return the string in hex notation.
469  */
470 std::string convert_binary_to_hex(
471    const std::string& str,
472    bool upper_case_digits
473 )
474 {
475    std::string result;
476    std::string hexDigits(upper_case_digits ? hexDigitsUpper : hexDigitsLower);
477    for ( std::string::const_iterator it= str.begin();
478          it != str.end();
479          ++it)
480    {
481       result.push_back( hexDigits[ ( (*it) >> 4) & 0x0f ] );
482       result.push_back( hexDigits[ (*it) & 0x0f ] );
483    }
484    return result;
485 } // eo convert_binary_to_hex(const std::string&,bool)
486
487
488 /**
489  * @brief converts a hex digit string to binary string.
490  * @param str hex digit string
491  * @return the binary string.
492  *
493  * The hex digit string may contains white spaces or colons which are treated
494  * as delimiters between hex digit groups.
495  *
496  * @todo rework the handling of half nibbles (consistency)!
497  */
498 std::string convert_hex_to_binary(
499    const std::string& str
500 )
501 throw (std::runtime_error)
502 {
503    std::string result;
504    char c= 0;
505    bool hasNibble= false;
506    bool lastWasWS= true;
507    for ( std::string::const_iterator it= str.begin();
508          it != str.end();
509          ++it)
510    {
511       std::string::size_type p = hexDigitsLower.find( *it );
512       if (p== std::string::npos)
513       {
514          p= hexDigitsUpper.find( *it );
515       }
516       if (p == std::string::npos)
517       {
518          if (   ( Whitespaces.find( *it ) != std::string::npos) // is it a whitespace?
519                 or ( *it == ':') // or a colon?
520             )
521          {
522             // we treat that as a valid delimiter:
523             if (hasNibble)
524             {
525                // 1 nibble before WS is treate as lower part:
526                result.push_back(c);
527                // reset state:
528                hasNibble= false;
529             }
530             lastWasWS= true;
531             continue;
532          }
533       }
534       if (p == std::string::npos )
535       {
536          throw runtime_error("illegal character in hex digit string: " + str);
537       }
538       lastWasWS= false;
539       if (hasNibble)
540       {
541          c<<=4;
542       }
543       else
544       {
545          c=0;
546       }
547       c+= (p & 0x0f);
548       if (hasNibble)
549       {
550          //we already had a nibble, so a char is complete now:
551          result.push_back( c );
552          hasNibble=false;
553       }
554       else
555       {
556          // this is the first nibble of a new char:
557          hasNibble=true;
558       }
559    }
560    if (hasNibble)
561    {
562       //well, there is one nibble left
563       // let's do some heuristics:
564       if (lastWasWS)
565       {
566          // if the preceeding character was a white space (or a colon)
567          // we treat the nibble as lower part:
568          //( this is consistent with shortened hex notations where leading zeros are not noted)
569          result.push_back( c );
570       }
571       else
572       {
573          // if it was part of a hex digit chain, we treat it as UPPER part (!!)
574          result.push_back( c << 4 );
575       }
576    }
577    return result;
578 } // eo convert_hex_to_binary(const std::string&)
579
580
581 static list<string>& alloc_template_starts()
582 {
583     static list<string> result;
584     if (result.empty())
585     {
586         result.push_back("std::list");
587         result.push_back("std::vector");
588     }
589     return result;
590 }
591
592 string shorten_stl_types(const string &input)
593 {
594     string output = input;
595
596     // first: replace fixed string for std::string
597     replace_all(output, "std::basic_string<char, std::char_traits<char>, std::allocator<char> >",
598                         "std::string");
599
600     // loop over list/vector/... that have an allocator, e.g.
601     // std::list< some_type_here, std::allocator<some_type_here> >
602     string::size_type start, comma, end, len, start_text_len;
603     int n_open_brackets;
604     string allocator_text;
605     BOOST_FOREACH(const string &start_text, alloc_template_starts())
606     {
607         start = 0;
608         comma = 0;
609         end = 0;
610         start_text_len = start_text.length();
611         while( (start=output.find(start_text+"<", start)) != string::npos )
612         {
613             len = output.length();
614             start += start_text_len+1;   // start next iter and tests here after opening bracket
615
616             // now comes the tricky part: find matching ',' and the closing '>' even if "subtype" is template again
617             comma = start;
618             n_open_brackets = 1;    // the bracket right after start_text counts as first
619             while (comma < len && n_open_brackets > 0)
620             {
621                 if (output[comma] == ',' && n_open_brackets == 1)
622                     break;
623                 else if (output[comma] == '<')
624                     ++n_open_brackets;
625                 else if (output[comma] == '>')
626                     --n_open_brackets;
627                 ++comma;
628             }
629             end = comma+1;
630             while (end < len && n_open_brackets > 0)
631             {
632                 if (output[end] == '<')
633                     ++n_open_brackets;
634                 else if (output[end] == '>')
635                 {
636                     --n_open_brackets;
637                     if (n_open_brackets == 0)
638                         break;  // do not increment end
639                 }
640                 ++end;
641             }
642
643             // check that start < comma < end < len && n_open_brackets == 0
644             if (start >= comma || comma >= end || end >= len || n_open_brackets != 0)
645                 continue;   // input seems to be of unexpected form
646
647             // check that type in allocator is same as until comma
648             string type = output.substr(start, comma-start);
649             if (type[type.length()-1] == '>')
650                 allocator_text = string("std::allocator<") + type + " > ";
651             else
652                 allocator_text = string("std::allocator<") + type + "> ";
653             if (output.substr(comma+2, end-comma-2) == allocator_text)
654                 output.replace(comma+2, end-comma-2, "_alloc_");
655         }
656     }
657
658     return output;
659 }
660
661 typedef boost::shared_ptr<BIO> BIO_Ptr;
662
663 /**
664 * @brief Converts openssl generic input/output to std::string
665 *
666 * Code adapted from keymakerd.
667 *
668 * @param bio Openssl's generic input/output
669 * @return :string STL string
670 **/
671 static std::string _convert_BIO_to_string(BIO *input)
672 {
673     std::string rtn;
674
675     char *output = NULL;
676     long written = BIO_get_mem_data(input, &output);
677     if (written <= 0 || output == NULL)
678         return rtn;
679
680     rtn.assign(output, written);                                                                    //lint !e534 !e732
681     return rtn;
682 }                                                                                                   //lint !e1764
683
684 /**
685     * @brief base64 encode a string using OpenSSL base64 functions
686     *
687     * Data size limit is 2GB on 32 bit (LONG_MAX)
688     *
689     * @param input String to encode
690     * @param one_line Encode all data as one line, no wrapping with line feeds
691     * @return base64 encoded string
692     */
693 std::string base64_encode(const std::string &input, bool one_line)
694 {
695     // check for empty buffer
696     if (input.empty())
697         return input;
698
699     // safety check to ensure our check afer BIO_write() works
700     if (input.size() >= LONG_MAX)
701         throw runtime_error("base64 encode: Too much data");
702
703     // setup encoder. Note: BIO_free_all frees both BIOs.
704     BIO_Ptr base64_encoder(BIO_new(BIO_f_base64()), BIO_free_all);
705     BIO *encoder_bio = base64_encoder.get();
706     if (one_line)
707         BIO_set_flags(encoder_bio, BIO_FLAGS_BASE64_NO_NL);
708
709     // chain output buffer and encoder together
710     BIO *encoded_result = BIO_new(BIO_s_mem());
711     BIO_push(encoder_bio, encoded_result);
712
713     // encode
714     long written = BIO_write(encoder_bio, input.c_str(), input.size());
715     if ((unsigned)written != input.size())
716     {
717         ostringstream out;
718         out << "base64 encoding failed: input size: "
719             << input.size() << " vs. output size: " << written;
720         throw runtime_error(out.str());
721     }
722     if (BIO_flush(encoder_bio) != 1)
723         throw runtime_error("base64 encode: BIO_flush() failed");
724
725     return _convert_BIO_to_string(encoded_result);
726 }
727
728 /**
729     * @brief base64 decode a string using OpenSSL base64 functions
730     *
731     * @param input String to decode
732     * @param one_line Expect all base64 data in one line. Input with line feeds will fail.
733     * @return base64 decoded string
734     */
735 std::string base64_decode(const std::string &input, bool one_line)
736 {
737     // check for empty buffer
738     if (input.empty())
739         return input;
740
741     // safety check for BIO_new_mem_buf()
742     if (input.size() >= INT_MAX)
743         throw runtime_error("base64 decode: Too much data");
744
745     // setup encoder. Note: BIO_free_all frees both BIOs.
746     BIO_Ptr base64_decoder(BIO_new(BIO_f_base64()), BIO_free_all);
747     BIO *bio_base64 = base64_decoder.get();
748     if (one_line)
749         BIO_set_flags(bio_base64, BIO_FLAGS_BASE64_NO_NL);
750
751     // chain input buffer and decoder together
752     BIO *bio_input = BIO_new_mem_buf((void*)input.c_str(), input.size());
753     bio_input = BIO_push(bio_base64, bio_input);
754
755     BIO_Ptr decoded_result(BIO_new(BIO_s_mem()), BIO_free_all);
756     BIO *bio_decoded = decoded_result.get();
757     const int convbuf_size = 512;
758     char convbuf[convbuf_size];
759
760     long read_bytes = 0;
761     while((read_bytes = BIO_read(bio_input, convbuf, convbuf_size)) > 0)
762     {
763         BOOST_ASSERT(read_bytes <= convbuf_size);
764         long written_bytes = BIO_write(bio_decoded, convbuf, read_bytes);
765         if (written_bytes != read_bytes)
766         {
767             ostringstream out;
768             out << "base64 decoding failed: read_bytes: "
769                 << read_bytes << " vs. written_bytes: " << written_bytes;
770             throw runtime_error(out.str());
771         }
772     }
773     if (read_bytes == -2 || read_bytes == -1)
774         throw runtime_error("base64 decode: Error during decoding");
775
776     return _convert_BIO_to_string(bio_decoded);
777 }
778
779 } // eo namespace I2n
780
781
782
783
784 std::string iso_to_utf8(const std::string& isostring)
785 {
786    string result;
787
788    iconv_t i2utf8 = iconv_open("UTF-8", "ISO-8859-1");
789
790    if (iso_to_utf8 == (iconv_t)-1)
791       throw runtime_error("iconv can't convert from ISO-8859-1 to UTF-8");
792
793    size_t in_size=isostring.size();
794    size_t out_size=in_size*4;
795
796    char *buf = (char *)malloc(out_size+1);
797    if (buf == NULL)
798       throw runtime_error("out of memory for iconv buffer");
799
800    char *in = (char *)isostring.c_str();
801    char *out = buf;
802    iconv(i2utf8, &in, &in_size, &out, &out_size);
803
804    buf[isostring.size()*4-out_size]=0;
805
806    result=buf;
807
808    free(buf);
809    iconv_close(i2utf8);
810
811    return result;
812 }
813
814 std::string utf8_to_iso(const std::string& utf8string)
815 {
816    string result;
817
818    iconv_t utf82iso = iconv_open("ISO-8859-1","UTF-8");
819
820    if (utf82iso == (iconv_t)-1)
821       throw runtime_error("iconv can't convert from UTF-8 to ISO-8859-1");
822
823    size_t in_size=utf8string.size();
824    size_t out_size=in_size;
825
826    char *buf = (char *)malloc(out_size+1);
827    if (buf == NULL)
828       throw runtime_error("out of memory for iconv buffer");
829
830    char *in = (char *)utf8string.c_str();
831    char *out = buf;
832    iconv(utf82iso, &in, &in_size, &out, &out_size);
833
834    buf[utf8string.size()-out_size]=0;
835
836    result=buf;
837
838    free(buf);
839    iconv_close(utf82iso);
840
841    return result;
842 }
843
844 wchar_t* utf8_to_wbuf(const std::string& utf8string)
845 {
846    iconv_t utf82wstr = iconv_open("UCS-4LE","UTF-8");
847
848    if (utf82wstr == (iconv_t)-1)
849       throw runtime_error("iconv can't convert from UTF-8 to UCS-4");
850
851    size_t in_size=utf8string.size();
852    size_t out_size= (in_size+1)*sizeof(wchar_t);
853
854    wchar_t *buf = (wchar_t *)malloc(out_size);
855    if (buf == NULL)
856       throw runtime_error("out of memory for iconv buffer");
857
858    char *in = (char *)utf8string.c_str();
859    char *out = (char*) buf;
860    if (iconv(utf82wstr, &in, &in_size, &out, &out_size) == (size_t)-1)
861       throw runtime_error("error converting char encodings");
862
863    buf[ ( (utf8string.size()+1)*sizeof(wchar_t)-out_size) /sizeof(wchar_t) ]=0;
864
865    iconv_close(utf82wstr);
866
867    return buf;
868 }
869
870 std::string utf7imap_to_utf8(const std::string& utf7imapstring)
871 {
872    string result;
873
874    iconv_t utf7imap2utf8 = iconv_open("UTF-8","UTF-7-IMAP");
875
876    if (utf7imap2utf8 == (iconv_t)-1)
877       throw runtime_error("iconv can't convert from UTF-7-IMAP to UTF-8");
878
879    size_t in_size=utf7imapstring.size();
880    size_t out_size=in_size*4;
881
882    char *buf = (char *)malloc(out_size+1);
883    if (buf == NULL)
884       throw runtime_error("out of memory for iconv buffer");
885
886    char *in = (char *)utf7imapstring.c_str();
887    char *out = buf;
888    iconv(utf7imap2utf8, &in, &in_size, &out, &out_size);
889
890    buf[utf7imapstring.size()*4-out_size]=0;
891
892    result=buf;
893
894    free(buf);
895    iconv_close(utf7imap2utf8);
896
897    return result;
898 }
899
900 std::string utf8_to_utf7imap(const std::string& utf8string)
901 {
902    string result;
903
904    iconv_t utf82utf7imap = iconv_open("UTF-7-IMAP", "UTF-8");
905
906    if (utf82utf7imap == (iconv_t)-1)
907       throw runtime_error("iconv can't convert from UTF-7-IMAP to UTF-8");
908
909    // UTF-7 is base64 encoded, a buffer 10x as large
910    // as the utf-8 buffer should be enough. If not the string will be truncated.
911    size_t in_size=utf8string.size();
912    size_t out_size=in_size*10;
913
914    char *buf = (char *)malloc(out_size+1);
915    if (buf == NULL)
916       throw runtime_error("out of memory for iconv buffer");
917
918    char *in = (char *)utf8string.c_str();
919    char *out = buf;
920    iconv(utf82utf7imap, &in, &in_size, &out, &out_size);
921
922    buf[utf8string.size()*10-out_size]= 0;
923
924    result=buf;
925
926    free(buf);
927    iconv_close(utf82utf7imap);
928
929    return result;
930 }
931
932 // Tokenize string by (html) tags
933 void tokenize_by_tag(vector<pair<string,bool> > &tokenized, const std::string &input)
934 {
935    string::size_type pos, len = input.size();
936    bool inside_tag = false;
937    string current;
938
939    for (pos = 0; pos < len; pos++)
940    {
941       if (input[pos] == '<')
942       {
943          inside_tag = true;
944
945          if (!current.empty() )
946          {
947             tokenized.push_back( make_pair(current, false) );
948             current = "";
949          }
950
951          current += input[pos];
952       }
953       else if (input[pos] == '>' && inside_tag)
954       {
955          current += input[pos];
956          inside_tag = false;
957          if (!current.empty() )
958          {
959             tokenized.push_back( make_pair(current, true) );
960             current = "";
961          }
962       }
963       else
964          current += input[pos];
965    }
966
967    // String left over in buffer?
968    if (!current.empty() )
969       tokenized.push_back( make_pair(current, false) );
970 } // eo tokenize_by_tag
971
972
973 std::string strip_html_tags(const std::string &input)
974 {
975    // Pair first: string, second: isTag
976    vector<pair<string,bool> > tokenized;
977    tokenize_by_tag (tokenized, input);
978
979    string output;
980    vector<pair<string,bool> >::const_iterator token, tokens_end = tokenized.end();
981    for (token = tokenized.begin(); token != tokens_end; ++token)
982       if (!token->second)
983          output += token->first;
984
985    return output;
986 } // eo strip_html_tags
987
988
989 // Smart-encode HTML en
990 string smart_html_entities(const std::string &input)
991 {
992    // Pair first: string, second: isTag
993    vector<pair<string,bool> > tokenized;
994    tokenize_by_tag (tokenized, input);
995
996    string output;
997    vector<pair<string,bool> >::const_iterator token, tokens_end = tokenized.end();
998    for (token = tokenized.begin(); token != tokens_end; ++token)
999    {
1000       // keep HTML tags as they are
1001       if (token->second)
1002          output += token->first;
1003       else
1004          output += html_entities(token->first);
1005    }
1006
1007    return output;
1008 }
1009
1010
1011 string::size_type find_8bit(const std::string &str)
1012 {
1013    string::size_type l=str.size();
1014    for (string::size_type p=0; p < l; p++)
1015       if (static_cast<unsigned char>(str[p]) > 127)
1016          return p;
1017
1018    return string::npos;
1019 }
1020
1021 // encoded UTF-8 chars into HTML entities
1022 string html_entities(std::string str)
1023 {
1024    // Normal chars
1025    replace_all (str, "&", "&amp;");
1026    replace_all (str, "<", "&lt;");
1027    replace_all (str, ">", "&gt;");
1028    replace_all (str, "\"", "&quot;");
1029    replace_all (str, "'", "&#x27;");
1030    replace_all (str, "/", "&#x2F;");
1031
1032    // Umlauts
1033    replace_all (str, "\xC3\xA4", "&auml;");
1034    replace_all (str, "\xC3\xB6", "&ouml;");
1035    replace_all (str, "\xC3\xBC", "&uuml;");
1036    replace_all (str, "\xC3\x84", "&Auml;");
1037    replace_all (str, "\xC3\x96", "&Ouml;");
1038    replace_all (str, "\xC3\x9C", "&Uuml;");
1039
1040    // Misc
1041    replace_all (str, "\xC3\x9F", "&szlig;");
1042
1043    // conversion of remaining non-ASCII chars needed?
1044    // just do if needed because of performance
1045    if (find_8bit(str) != string::npos)
1046    {
1047       // convert to fixed-size encoding UTF-32
1048       wchar_t* wbuf=utf8_to_wbuf(str);
1049       ostringstream target;
1050
1051       // replace all non-ASCII chars with HTML representation
1052       for (int p=0; wbuf[p] != 0; p++)
1053       {
1054          unsigned int c=wbuf[p];
1055
1056          if (c <= 127)
1057             target << static_cast<unsigned char>(c);
1058          else
1059             target << "&#" << c << ';';
1060       }
1061
1062       free(wbuf);
1063
1064       str=target.str();
1065    }
1066
1067    return str;
1068 } // eo html_entities(std::string)
1069
1070 // convert HTML entities to something that can be viewed on a basic text console (restricted to ASCII-7)
1071 string html_entities_to_console(std::string str)
1072 {
1073    // Normal chars
1074    replace_all (str, "&amp;", "&");
1075    replace_all (str, "&lt;", "<");
1076    replace_all (str, "&gt;", ">");
1077    replace_all (str, "&quot;", "\"");
1078    replace_all (str, "&#x27;", "'");
1079    replace_all (str, "&#x2F;", "/");
1080
1081    // Umlauts
1082    replace_all (str, "&auml;", "ae");
1083    replace_all (str, "&ouml;", "oe");
1084    replace_all (str, "&uuml;", "ue");
1085    replace_all (str, "&Auml;", "Ae");
1086    replace_all (str, "&Ouml;", "Oe");
1087    replace_all (str, "&Uuml;", "Ue");
1088
1089    // Misc
1090    replace_all (str, "&szlig;", "ss");
1091
1092    return str;
1093 }
1094
1095 // find_html_comments + remove_html_comments(str, comments)
1096 void remove_html_comments(string &str)
1097 {
1098     vector<CommentZone> comments = find_html_comments(str);
1099     remove_html_comments(str, comments);
1100 }
1101
1102 // find all html comments, behaving correctly if they are nested; ignores comment tags ("<!--FOO .... BAR-->")
1103 // If there are invalid comments ("-->" before "<!--" or different number of closing and opening tags),
1104 // then the unknown index of corresponding start/end tag will be represented by a string::npos
1105 // Indices are from start of start tag until first index after closing tag
1106 vector<CommentZone> find_html_comments(const std::string &str)
1107 {
1108     static const string START = "<!--";
1109     static const string CLOSE = "-->";
1110     static const string::size_type START_LEN = START.length();
1111     static const string::size_type CLOSE_LEN = CLOSE.length();
1112
1113     vector<CommentZone> comments;
1114
1115     // in order to find nested comments, need either recursion or a stack
1116     vector<string::size_type> starts;      // stack of start tags
1117
1118     string::size_type pos = 0;
1119     string::size_type len = str.length();
1120     string::size_type next_start, next_close;
1121
1122     while (pos < len)     // not really needed but just in case
1123     {
1124         next_start = str.find(START, pos);
1125         next_close = str.find(CLOSE, pos);
1126
1127         if ( (next_start == string::npos) && (next_close == string::npos) )
1128             break;   // we are done
1129
1130         else if ( (next_start == string::npos) || (next_close < next_start) )  // close one comment (pop)
1131         {
1132             if (starts.empty())    // closing tag without a start
1133                 comments.push_back(CommentZone(string::npos, next_close+CLOSE_LEN));
1134             else
1135             {
1136                 comments.push_back(CommentZone(starts.back(), next_close+CLOSE_LEN));
1137                 starts.pop_back();
1138             }
1139             pos = next_close + CLOSE_LEN;
1140         }
1141
1142         else if ( (next_close == string::npos) || (next_start < next_close) )  // start a new comment (push)
1143         {
1144             starts.push_back(next_start);
1145             pos = next_start + START_LEN;
1146         }
1147     }
1148
1149     // add comments that have no closing tag from back to front (important for remove_html_comments!)
1150     while (!starts.empty())
1151     {
1152         comments.push_back(CommentZone(starts.back(), string::npos));
1153         starts.pop_back();
1154     }
1155
1156     return comments;
1157 }
1158
1159 // remove all html comments foundby find_html_comments
1160 void remove_html_comments(std::string &str, const vector<CommentZone> &comments)
1161 {
1162     // remember position where last removal started
1163     string::size_type last_removal_start = str.length();
1164
1165     // Go from back to front to not mess up indices.
1166     // This requires that bigger comments, that contain smaller comments, come AFTER
1167     // the small contained comments in the comments vector (i.e. comments are ordered by
1168     // their closing tag, not their opening tag). This is true for results from find_html_comments
1169     BOOST_REVERSE_FOREACH(const CommentZone &comment, comments)
1170     {
1171         if (comment.first == string::npos)
1172         {
1173             str = str.replace(0, comment.second, "");   // comment starts "before" str --> delete from start
1174             break;   // there can be no more
1175         }
1176         else if (comment.first >= last_removal_start)
1177         {
1178             continue;    // this comment is inside another comment that we have removed already
1179         }
1180         else if (comment.second == string::npos)   // comment ends "after" str --> delete until end
1181         {
1182             str = str.replace(comment.first, string::npos, "");
1183             last_removal_start = comment.first;
1184         }
1185         else
1186         {
1187             str = str.replace(comment.first, comment.second-comment.first, "");
1188             last_removal_start = comment.first;
1189         }
1190     }
1191 }
1192
1193 bool replace_all(string &base, const char *ist, const char *soll)
1194 {
1195    string i=ist;
1196    string s=soll;
1197    return replace_all(base,&i,&s);
1198 }
1199
1200 bool replace_all(string &base, const string &ist, const char *soll)
1201 {
1202    string s=soll;
1203    return replace_all(base,&ist,&s);
1204 }
1205
1206 bool replace_all(string &base, const string *ist, const string *soll)
1207 {
1208    return replace_all(base,*ist,*soll);
1209 }
1210
1211 bool replace_all(string &base, const char *ist, const string *soll)
1212 {
1213    string i=ist;
1214    return replace_all(base,&i,soll);
1215 }
1216
1217 bool replace_all(string &base, const string &ist, const string &soll)
1218 {
1219    bool found_ist = false;
1220    string::size_type a=0;
1221
1222    if (ist.empty() )
1223       throw runtime_error ("replace_all called with empty search string");
1224
1225    while ( (a=base.find(ist,a) ) != string::npos)
1226    {
1227       base.replace(a,ist.size(),soll);
1228       a=a+soll.size();
1229       found_ist = true;
1230    }
1231
1232    return found_ist;
1233 }
1234
1235 /**
1236  * @brief replaces all characters that could be problematic or impose a security risk when being logged
1237  * @param str the original string
1238  * @param replace_with the character to replace the unsafe chars with
1239  * @return a string that is safe to send to syslog or other logfiles
1240  *
1241  * All chars between 0x20 (space) and 0x7E (~) (including) are considered safe for logging.
1242  * See e.g. RFC 5424, section 8.2 or the posix character class "printable".
1243  * This eliminates all possible problems with NUL, control characters, 8 bit chars, UTF8.
1244  *
1245  */
1246 std::string sanitize_for_logging(const std::string &str, const char replace_with)
1247 {
1248     std::string output=str;
1249
1250     const string::size_type len = output.size();
1251     for (std::string::size_type p=0; p < len; p++)
1252         if (output[p] < 0x20 || output[p] > 0x7E)
1253             output[p]=replace_with;
1254
1255     return output;
1256 }
1257
1258 #if 0
1259 string to_lower(const string &src)
1260 {
1261    string dst = src;
1262
1263    string::size_type pos, end = dst.size();
1264    for (pos = 0; pos < end; pos++)
1265       dst[pos] = tolower(dst[pos]);
1266
1267    return dst;
1268 }
1269
1270 string to_upper(const string &src)
1271 {
1272    string dst = src;
1273
1274    string::size_type pos, end = dst.size();
1275    for (pos = 0; pos < end; pos++)
1276       dst[pos] = toupper(dst[pos]);
1277
1278    return dst;
1279 }
1280 #endif
1281
1282 const int MAX_UNIT_FORMAT_SYMBOLS = 6;
1283
1284 const string shortUnitFormatSymbols[MAX_UNIT_FORMAT_SYMBOLS] = {
1285         " B",
1286         " KB",
1287         " MB",
1288         " GB",
1289         " TB",
1290         " PB"
1291 };
1292
1293 const string longUnitFormatSymbols[MAX_UNIT_FORMAT_SYMBOLS] = {
1294         i18n_noop(" Bytes"),
1295         i18n_noop(" KBytes"),
1296         i18n_noop(" MBytes"),
1297         i18n_noop(" GBytes"),
1298         i18n_noop(" TBytes"),
1299         i18n_noop(" PBytes")
1300 };
1301
1302
1303 static long double rounding_upwards(
1304         const long double number,
1305         const int rounding_multiplier
1306 )
1307 {
1308     long double rounded_number;
1309     rounded_number = number * rounding_multiplier;
1310     rounded_number += 0.5;
1311     rounded_number = (int64_t) (rounded_number);
1312     rounded_number = (long double) (rounded_number) / (long double) (rounding_multiplier);
1313
1314     return rounded_number;
1315 }
1316
1317
1318 string nice_unit_format(
1319         const int64_t input,
1320         const UnitFormat format,
1321         const UnitBase base
1322 )
1323 {
1324    // select the system of units (decimal or binary)
1325    int multiple = 0;
1326    if (base == UnitBase1000)
1327    {
1328        multiple = 1000;
1329    }
1330    else
1331    {
1332        multiple = 1024;
1333    }
1334
1335    long double size = input;
1336
1337    // check the size of the input number to fit in the appropriate symbol
1338    int sizecount = 0;
1339    while (size > multiple)
1340    {
1341        size = size / multiple;
1342        sizecount++;
1343
1344        // rollback to the previous values and stop the loop when cannot
1345        // represent the number length.
1346        if (sizecount >= MAX_UNIT_FORMAT_SYMBOLS)
1347        {
1348            size = size * multiple;
1349            sizecount--;
1350            break;
1351        }
1352    }
1353
1354    // round the input number "half up" to multiples of 10
1355    const int rounding_multiplier = 10;
1356    size = rounding_upwards(size, rounding_multiplier);
1357
1358    // format the input number, placing the appropriate symbol
1359    ostringstream out;
1360    out.setf (ios::fixed);
1361    if (format == ShortUnitFormat)
1362    {
1363        out.precision(1);
1364        out << size << i18n( shortUnitFormatSymbols[sizecount].c_str() );
1365    }
1366    else
1367    {
1368        out.precision (2);
1369        out << size << i18n( longUnitFormatSymbols[sizecount].c_str() );
1370    }
1371
1372    return out.str();
1373 } // eo nice_unit_format(int input)
1374
1375
1376 string nice_unit_format(
1377         const double input,
1378         const UnitFormat format,
1379         const UnitBase base
1380 )
1381 {
1382     // round as double and cast to int64_t
1383     // cast raised overflow error near max val of int64_t (~9.2e18, see unittest)
1384     int64_t input_casted_and_rounded =
1385         boost::numeric_cast<int64_t>( round(input) );
1386
1387     // now call other
1388     return nice_unit_format( input_casted_and_rounded, format, base );
1389 } // eo nice_unit_format(double input)
1390
1391
1392 string escape(const string &s)
1393 {
1394    string out(s);
1395    string::size_type p;
1396
1397    p=0;
1398    while ( (p=out.find_first_of("\"\\",p) ) !=out.npos)
1399    {
1400       out.insert (p,"\\");
1401       p+=2;
1402    }
1403
1404    p=0;
1405    while ( (p=out.find_first_of("\r",p) ) !=out.npos)
1406    {
1407       out.replace (p,1,"\\r");
1408       p+=2;
1409    }
1410
1411    p=0;
1412    while ( (p=out.find_first_of("\n",p) ) !=out.npos)
1413    {
1414       out.replace (p,1,"\\n");
1415       p+=2;
1416    }
1417
1418    out='"'+out+'"';
1419
1420    return out;
1421 } // eo scape(const std::string&)
1422
1423
1424 string descape(const string &s, int startpos, int &endpos)
1425 {
1426    string out;
1427
1428    if (s.at(startpos) != '"')
1429       throw out_of_range("value not type escaped string");
1430
1431    out=s.substr(startpos+1);
1432    string::size_type p=0;
1433
1434    // search for the end of the string
1435    while ( (p=out.find("\"",p) ) !=out.npos)
1436    {
1437       int e=p-1;
1438       bool escaped=false;
1439
1440       // the " might be escaped with a backslash
1441       while (e>=0 && out.at (e) =='\\')
1442       {
1443          if (escaped == false)
1444             escaped=true;
1445          else
1446             escaped=false;
1447
1448          e--;
1449       }
1450
1451       if (escaped==false)
1452          break;
1453       else
1454          p++;
1455    }
1456
1457    // we now have the end of the string
1458    out=out.substr(0,p);
1459
1460    // tell calling prog about the endposition
1461    endpos=startpos+p+1;
1462
1463    // descape all \ stuff inside the string now
1464    p=0;
1465    while ( (p=out.find_first_of("\\",p) ) !=out.npos)
1466    {
1467       switch (out.at(p+1) )
1468       {
1469          case 'r':
1470             out.replace(p,2,"\r");
1471             break;
1472          case 'n':
1473             out.replace(p,2,"\n");
1474             break;
1475          default:
1476             out.erase(p,1);
1477       }
1478       p++;
1479    }
1480
1481    return out;
1482 } // eo descape(const std::string&,int,int&)
1483
1484
1485 string escape_shellarg(const string &input)
1486 {
1487    string output = "'";
1488    string::const_iterator it, it_end = input.end();
1489    for (it = input.begin(); it != it_end; ++it)
1490    {
1491       if ( (*it) == '\'')
1492          output += "'\\'";
1493
1494       output += *it;
1495    }
1496
1497    output += "'";
1498    return output;
1499 }