don't leak iconv context on OOM
[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    {
799       iconv_close(i2utf8);
800       throw runtime_error("out of memory for iconv buffer");
801    }
802
803    char *in = (char *)isostring.c_str();
804    char *out = buf;
805    iconv(i2utf8, &in, &in_size, &out, &out_size);
806
807    buf[isostring.size()*4-out_size]=0;
808
809    result=buf;
810
811    free(buf);
812    iconv_close(i2utf8);
813
814    return result;
815 }
816
817 std::string utf8_to_iso(const std::string& utf8string)
818 {
819    string result;
820
821    iconv_t utf82iso = iconv_open("ISO-8859-1","UTF-8");
822
823    if (utf82iso == (iconv_t)-1)
824       throw runtime_error("iconv can't convert from UTF-8 to ISO-8859-1");
825
826    size_t in_size=utf8string.size();
827    size_t out_size=in_size;
828
829    char *buf = (char *)malloc(out_size+1);
830    if (buf == NULL)
831    {
832       iconv_close(utf82iso);
833       throw runtime_error("out of memory for iconv buffer");
834    }
835
836    char *in = (char *)utf8string.c_str();
837    char *out = buf;
838    iconv(utf82iso, &in, &in_size, &out, &out_size);
839
840    buf[utf8string.size()-out_size]=0;
841
842    result=buf;
843
844    free(buf);
845    iconv_close(utf82iso);
846
847    return result;
848 }
849
850 wchar_t* utf8_to_wbuf(const std::string& utf8string)
851 {
852    iconv_t utf82wstr = iconv_open("UCS-4LE","UTF-8");
853
854    if (utf82wstr == (iconv_t)-1)
855       throw runtime_error("iconv can't convert from UTF-8 to UCS-4");
856
857    size_t in_size=utf8string.size();
858    size_t out_size= (in_size+1)*sizeof(wchar_t);
859
860    wchar_t *buf = (wchar_t *)malloc(out_size);
861    if (buf == NULL)
862    {
863       iconv_close(utf82wstr);
864       throw runtime_error("out of memory for iconv buffer");
865    }
866
867    char *in = (char *)utf8string.c_str();
868    char *out = (char*) buf;
869    if (iconv(utf82wstr, &in, &in_size, &out, &out_size) == (size_t)-1)
870       throw runtime_error("error converting char encodings");
871
872    buf[ ( (utf8string.size()+1)*sizeof(wchar_t)-out_size) /sizeof(wchar_t) ]=0;
873
874    iconv_close(utf82wstr);
875
876    return buf;
877 }
878
879 std::string utf7imap_to_utf8(const std::string& utf7imapstring)
880 {
881    string result;
882
883    iconv_t utf7imap2utf8 = iconv_open("UTF-8","UTF-7-IMAP");
884
885    if (utf7imap2utf8 == (iconv_t)-1)
886       throw runtime_error("iconv can't convert from UTF-7-IMAP to UTF-8");
887
888    size_t in_size=utf7imapstring.size();
889    size_t out_size=in_size*4;
890
891    char *buf = (char *)malloc(out_size+1);
892    if (buf == NULL)
893    {
894       iconv_close(utf7imap2utf8);
895       throw runtime_error("out of memory for iconv buffer");
896    }
897
898    char *in = (char *)utf7imapstring.c_str();
899    char *out = buf;
900    iconv(utf7imap2utf8, &in, &in_size, &out, &out_size);
901
902    buf[utf7imapstring.size()*4-out_size]=0;
903
904    result=buf;
905
906    free(buf);
907    iconv_close(utf7imap2utf8);
908
909    return result;
910 }
911
912 std::string utf8_to_utf7imap(const std::string& utf8string)
913 {
914    string result;
915
916    iconv_t utf82utf7imap = iconv_open("UTF-7-IMAP", "UTF-8");
917
918    if (utf82utf7imap == (iconv_t)-1)
919       throw runtime_error("iconv can't convert from UTF-7-IMAP to UTF-8");
920
921    // UTF-7 is base64 encoded, a buffer 10x as large
922    // as the utf-8 buffer should be enough. If not the string will be truncated.
923    size_t in_size=utf8string.size();
924    size_t out_size=in_size*10;
925
926    char *buf = (char *)malloc(out_size+1);
927    if (buf == NULL)
928    {
929       iconv_close(utf82utf7imap);
930       throw runtime_error("out of memory for iconv buffer");
931    }
932
933    char *in = (char *)utf8string.c_str();
934    char *out = buf;
935    iconv(utf82utf7imap, &in, &in_size, &out, &out_size);
936
937    buf[utf8string.size()*10-out_size]= 0;
938
939    result=buf;
940
941    free(buf);
942    iconv_close(utf82utf7imap);
943
944    return result;
945 }
946
947 // Tokenize string by (html) tags
948 void tokenize_by_tag(vector<pair<string,bool> > &tokenized, const std::string &input)
949 {
950    string::size_type pos, len = input.size();
951    bool inside_tag = false;
952    string current;
953
954    for (pos = 0; pos < len; pos++)
955    {
956       if (input[pos] == '<')
957       {
958          inside_tag = true;
959
960          if (!current.empty() )
961          {
962             tokenized.push_back( make_pair(current, false) );
963             current = "";
964          }
965
966          current += input[pos];
967       }
968       else if (input[pos] == '>' && inside_tag)
969       {
970          current += input[pos];
971          inside_tag = false;
972          if (!current.empty() )
973          {
974             tokenized.push_back( make_pair(current, true) );
975             current = "";
976          }
977       }
978       else
979          current += input[pos];
980    }
981
982    // String left over in buffer?
983    if (!current.empty() )
984       tokenized.push_back( make_pair(current, false) );
985 } // eo tokenize_by_tag
986
987
988 std::string strip_html_tags(const std::string &input)
989 {
990    // Pair first: string, second: isTag
991    vector<pair<string,bool> > tokenized;
992    tokenize_by_tag (tokenized, input);
993
994    string output;
995    vector<pair<string,bool> >::const_iterator token, tokens_end = tokenized.end();
996    for (token = tokenized.begin(); token != tokens_end; ++token)
997       if (!token->second)
998          output += token->first;
999
1000    return output;
1001 } // eo strip_html_tags
1002
1003
1004 // Smart-encode HTML en
1005 string smart_html_entities(const std::string &input)
1006 {
1007    // Pair first: string, second: isTag
1008    vector<pair<string,bool> > tokenized;
1009    tokenize_by_tag (tokenized, input);
1010
1011    string output;
1012    vector<pair<string,bool> >::const_iterator token, tokens_end = tokenized.end();
1013    for (token = tokenized.begin(); token != tokens_end; ++token)
1014    {
1015       // keep HTML tags as they are
1016       if (token->second)
1017          output += token->first;
1018       else
1019          output += html_entities(token->first);
1020    }
1021
1022    return output;
1023 }
1024
1025
1026 string::size_type find_8bit(const std::string &str)
1027 {
1028    string::size_type l=str.size();
1029    for (string::size_type p=0; p < l; p++)
1030       if (static_cast<unsigned char>(str[p]) > 127)
1031          return p;
1032
1033    return string::npos;
1034 }
1035
1036 // encoded UTF-8 chars into HTML entities
1037 string html_entities(std::string str)
1038 {
1039    // Normal chars
1040    replace_all (str, "&", "&amp;");
1041    replace_all (str, "<", "&lt;");
1042    replace_all (str, ">", "&gt;");
1043    replace_all (str, "\"", "&quot;");
1044    replace_all (str, "'", "&#x27;");
1045    replace_all (str, "/", "&#x2F;");
1046
1047    // Umlauts
1048    replace_all (str, "\xC3\xA4", "&auml;");
1049    replace_all (str, "\xC3\xB6", "&ouml;");
1050    replace_all (str, "\xC3\xBC", "&uuml;");
1051    replace_all (str, "\xC3\x84", "&Auml;");
1052    replace_all (str, "\xC3\x96", "&Ouml;");
1053    replace_all (str, "\xC3\x9C", "&Uuml;");
1054
1055    // Misc
1056    replace_all (str, "\xC3\x9F", "&szlig;");
1057
1058    // conversion of remaining non-ASCII chars needed?
1059    // just do if needed because of performance
1060    if (find_8bit(str) != string::npos)
1061    {
1062       // convert to fixed-size encoding UTF-32
1063       wchar_t* wbuf=utf8_to_wbuf(str);
1064       ostringstream target;
1065
1066       // replace all non-ASCII chars with HTML representation
1067       for (int p=0; wbuf[p] != 0; p++)
1068       {
1069          unsigned int c=wbuf[p];
1070
1071          if (c <= 127)
1072             target << static_cast<unsigned char>(c);
1073          else
1074             target << "&#" << c << ';';
1075       }
1076
1077       free(wbuf);
1078
1079       str=target.str();
1080    }
1081
1082    return str;
1083 } // eo html_entities(std::string)
1084
1085 // convert HTML entities to something that can be viewed on a basic text console (restricted to ASCII-7)
1086 string html_entities_to_console(std::string str)
1087 {
1088    // Normal chars
1089    replace_all (str, "&amp;", "&");
1090    replace_all (str, "&lt;", "<");
1091    replace_all (str, "&gt;", ">");
1092    replace_all (str, "&quot;", "\"");
1093    replace_all (str, "&#x27;", "'");
1094    replace_all (str, "&#x2F;", "/");
1095
1096    // Umlauts
1097    replace_all (str, "&auml;", "ae");
1098    replace_all (str, "&ouml;", "oe");
1099    replace_all (str, "&uuml;", "ue");
1100    replace_all (str, "&Auml;", "Ae");
1101    replace_all (str, "&Ouml;", "Oe");
1102    replace_all (str, "&Uuml;", "Ue");
1103
1104    // Misc
1105    replace_all (str, "&szlig;", "ss");
1106
1107    return str;
1108 }
1109
1110 // find_html_comments + remove_html_comments(str, comments)
1111 void remove_html_comments(string &str)
1112 {
1113     vector<CommentZone> comments = find_html_comments(str);
1114     remove_html_comments(str, comments);
1115 }
1116
1117 // find all html comments, behaving correctly if they are nested; ignores comment tags ("<!--FOO .... BAR-->")
1118 // If there are invalid comments ("-->" before "<!--" or different number of closing and opening tags),
1119 // then the unknown index of corresponding start/end tag will be represented by a string::npos
1120 // Indices are from start of start tag until first index after closing tag
1121 vector<CommentZone> find_html_comments(const std::string &str)
1122 {
1123     static const string START = "<!--";
1124     static const string CLOSE = "-->";
1125     static const string::size_type START_LEN = START.length();
1126     static const string::size_type CLOSE_LEN = CLOSE.length();
1127
1128     vector<CommentZone> comments;
1129
1130     // in order to find nested comments, need either recursion or a stack
1131     vector<string::size_type> starts;      // stack of start tags
1132
1133     string::size_type pos = 0;
1134     string::size_type len = str.length();
1135     string::size_type next_start, next_close;
1136
1137     while (pos < len)     // not really needed but just in case
1138     {
1139         next_start = str.find(START, pos);
1140         next_close = str.find(CLOSE, pos);
1141
1142         if ( (next_start == string::npos) && (next_close == string::npos) )
1143             break;   // we are done
1144
1145         else if ( (next_start == string::npos) || (next_close < next_start) )  // close one comment (pop)
1146         {
1147             if (starts.empty())    // closing tag without a start
1148                 comments.push_back(CommentZone(string::npos, next_close+CLOSE_LEN));
1149             else
1150             {
1151                 comments.push_back(CommentZone(starts.back(), next_close+CLOSE_LEN));
1152                 starts.pop_back();
1153             }
1154             pos = next_close + CLOSE_LEN;
1155         }
1156
1157         else if ( (next_close == string::npos) || (next_start < next_close) )  // start a new comment (push)
1158         {
1159             starts.push_back(next_start);
1160             pos = next_start + START_LEN;
1161         }
1162     }
1163
1164     // add comments that have no closing tag from back to front (important for remove_html_comments!)
1165     while (!starts.empty())
1166     {
1167         comments.push_back(CommentZone(starts.back(), string::npos));
1168         starts.pop_back();
1169     }
1170
1171     return comments;
1172 }
1173
1174 // remove all html comments foundby find_html_comments
1175 void remove_html_comments(std::string &str, const vector<CommentZone> &comments)
1176 {
1177     // remember position where last removal started
1178     string::size_type last_removal_start = str.length();
1179
1180     // Go from back to front to not mess up indices.
1181     // This requires that bigger comments, that contain smaller comments, come AFTER
1182     // the small contained comments in the comments vector (i.e. comments are ordered by
1183     // their closing tag, not their opening tag). This is true for results from find_html_comments
1184     BOOST_REVERSE_FOREACH(const CommentZone &comment, comments)
1185     {
1186         if (comment.first == string::npos)
1187         {
1188             str = str.replace(0, comment.second, "");   // comment starts "before" str --> delete from start
1189             break;   // there can be no more
1190         }
1191         else if (comment.first >= last_removal_start)
1192         {
1193             continue;    // this comment is inside another comment that we have removed already
1194         }
1195         else if (comment.second == string::npos)   // comment ends "after" str --> delete until end
1196         {
1197             str = str.replace(comment.first, string::npos, "");
1198             last_removal_start = comment.first;
1199         }
1200         else
1201         {
1202             str = str.replace(comment.first, comment.second-comment.first, "");
1203             last_removal_start = comment.first;
1204         }
1205     }
1206 }
1207
1208 bool replace_all(string &base, const char *ist, const char *soll)
1209 {
1210    string i=ist;
1211    string s=soll;
1212    return replace_all(base,&i,&s);
1213 }
1214
1215 bool replace_all(string &base, const string &ist, const char *soll)
1216 {
1217    string s=soll;
1218    return replace_all(base,&ist,&s);
1219 }
1220
1221 bool replace_all(string &base, const string *ist, const string *soll)
1222 {
1223    return replace_all(base,*ist,*soll);
1224 }
1225
1226 bool replace_all(string &base, const char *ist, const string *soll)
1227 {
1228    string i=ist;
1229    return replace_all(base,&i,soll);
1230 }
1231
1232 bool replace_all(string &base, const string &ist, const string &soll)
1233 {
1234    bool found_ist = false;
1235    string::size_type a=0;
1236
1237    if (ist.empty() )
1238       throw runtime_error ("replace_all called with empty search string");
1239
1240    while ( (a=base.find(ist,a) ) != string::npos)
1241    {
1242       base.replace(a,ist.size(),soll);
1243       a=a+soll.size();
1244       found_ist = true;
1245    }
1246
1247    return found_ist;
1248 }
1249
1250 /**
1251  * @brief replaces all characters that could be problematic or impose a security risk when being logged
1252  * @param str the original string
1253  * @param replace_with the character to replace the unsafe chars with
1254  * @return a string that is safe to send to syslog or other logfiles
1255  *
1256  * All chars between 0x20 (space) and 0x7E (~) (including) are considered safe for logging.
1257  * See e.g. RFC 5424, section 8.2 or the posix character class "printable".
1258  * This eliminates all possible problems with NUL, control characters, 8 bit chars, UTF8.
1259  *
1260  */
1261 std::string sanitize_for_logging(const std::string &str, const char replace_with)
1262 {
1263     std::string output=str;
1264
1265     const string::size_type len = output.size();
1266     for (std::string::size_type p=0; p < len; p++)
1267         if (output[p] < 0x20 || output[p] > 0x7E)
1268             output[p]=replace_with;
1269
1270     return output;
1271 }
1272
1273 #if 0
1274 string to_lower(const string &src)
1275 {
1276    string dst = src;
1277
1278    string::size_type pos, end = dst.size();
1279    for (pos = 0; pos < end; pos++)
1280       dst[pos] = tolower(dst[pos]);
1281
1282    return dst;
1283 }
1284
1285 string to_upper(const string &src)
1286 {
1287    string dst = src;
1288
1289    string::size_type pos, end = dst.size();
1290    for (pos = 0; pos < end; pos++)
1291       dst[pos] = toupper(dst[pos]);
1292
1293    return dst;
1294 }
1295 #endif
1296
1297 const int MAX_UNIT_FORMAT_SYMBOLS = 6;
1298
1299 const string shortUnitFormatSymbols[MAX_UNIT_FORMAT_SYMBOLS] = {
1300         " B",
1301         " KB",
1302         " MB",
1303         " GB",
1304         " TB",
1305         " PB"
1306 };
1307
1308 const string longUnitFormatSymbols[MAX_UNIT_FORMAT_SYMBOLS] = {
1309         i18n_noop(" Bytes"),
1310         i18n_noop(" KBytes"),
1311         i18n_noop(" MBytes"),
1312         i18n_noop(" GBytes"),
1313         i18n_noop(" TBytes"),
1314         i18n_noop(" PBytes")
1315 };
1316
1317
1318 static long double rounding_upwards(
1319         const long double number,
1320         const int rounding_multiplier
1321 )
1322 {
1323     long double rounded_number;
1324     rounded_number = number * rounding_multiplier;
1325     rounded_number += 0.5;
1326     rounded_number = (int64_t) (rounded_number);
1327     rounded_number = (long double) (rounded_number) / (long double) (rounding_multiplier);
1328
1329     return rounded_number;
1330 }
1331
1332
1333 string nice_unit_format(
1334         const int64_t input,
1335         const UnitFormat format,
1336         const UnitBase base
1337 )
1338 {
1339    // select the system of units (decimal or binary)
1340    int multiple = 0;
1341    if (base == UnitBase1000)
1342    {
1343        multiple = 1000;
1344    }
1345    else
1346    {
1347        multiple = 1024;
1348    }
1349
1350    long double size = input;
1351
1352    // check the size of the input number to fit in the appropriate symbol
1353    int sizecount = 0;
1354    while (size > multiple)
1355    {
1356        size = size / multiple;
1357        sizecount++;
1358
1359        // rollback to the previous values and stop the loop when cannot
1360        // represent the number length.
1361        if (sizecount >= MAX_UNIT_FORMAT_SYMBOLS)
1362        {
1363            size = size * multiple;
1364            sizecount--;
1365            break;
1366        }
1367    }
1368
1369    // round the input number "half up" to multiples of 10
1370    const int rounding_multiplier = 10;
1371    size = rounding_upwards(size, rounding_multiplier);
1372
1373    // format the input number, placing the appropriate symbol
1374    ostringstream out;
1375    out.setf (ios::fixed);
1376    if (format == ShortUnitFormat)
1377    {
1378        out.precision(1);
1379        out << size << i18n( shortUnitFormatSymbols[sizecount].c_str() );
1380    }
1381    else
1382    {
1383        out.precision (2);
1384        out << size << i18n( longUnitFormatSymbols[sizecount].c_str() );
1385    }
1386
1387    return out.str();
1388 } // eo nice_unit_format(int input)
1389
1390
1391 string nice_unit_format(
1392         const double input,
1393         const UnitFormat format,
1394         const UnitBase base
1395 )
1396 {
1397     // round as double and cast to int64_t
1398     // cast raised overflow error near max val of int64_t (~9.2e18, see unittest)
1399     int64_t input_casted_and_rounded =
1400         boost::numeric_cast<int64_t>( round(input) );
1401
1402     // now call other
1403     return nice_unit_format( input_casted_and_rounded, format, base );
1404 } // eo nice_unit_format(double input)
1405
1406
1407 string escape(const string &s)
1408 {
1409    string out(s);
1410    string::size_type p;
1411
1412    p=0;
1413    while ( (p=out.find_first_of("\"\\",p) ) !=out.npos)
1414    {
1415       out.insert (p,"\\");
1416       p+=2;
1417    }
1418
1419    p=0;
1420    while ( (p=out.find_first_of("\r",p) ) !=out.npos)
1421    {
1422       out.replace (p,1,"\\r");
1423       p+=2;
1424    }
1425
1426    p=0;
1427    while ( (p=out.find_first_of("\n",p) ) !=out.npos)
1428    {
1429       out.replace (p,1,"\\n");
1430       p+=2;
1431    }
1432
1433    out='"'+out+'"';
1434
1435    return out;
1436 } // eo scape(const std::string&)
1437
1438
1439 string descape(const string &s, int startpos, int &endpos)
1440 {
1441    string out;
1442
1443    if (s.at(startpos) != '"')
1444       throw out_of_range("value not type escaped string");
1445
1446    out=s.substr(startpos+1);
1447    string::size_type p=0;
1448
1449    // search for the end of the string
1450    while ( (p=out.find("\"",p) ) !=out.npos)
1451    {
1452       int e=p-1;
1453       bool escaped=false;
1454
1455       // the " might be escaped with a backslash
1456       while (e>=0 && out.at (e) =='\\')
1457       {
1458          if (escaped == false)
1459             escaped=true;
1460          else
1461             escaped=false;
1462
1463          e--;
1464       }
1465
1466       if (escaped==false)
1467          break;
1468       else
1469          p++;
1470    }
1471
1472    // we now have the end of the string
1473    out=out.substr(0,p);
1474
1475    // tell calling prog about the endposition
1476    endpos=startpos+p+1;
1477
1478    // descape all \ stuff inside the string now
1479    p=0;
1480    while ( (p=out.find_first_of("\\",p) ) !=out.npos)
1481    {
1482       switch (out.at(p+1) )
1483       {
1484          case 'r':
1485             out.replace(p,2,"\r");
1486             break;
1487          case 'n':
1488             out.replace(p,2,"\n");
1489             break;
1490          default:
1491             out.erase(p,1);
1492       }
1493       p++;
1494    }
1495
1496    return out;
1497 } // eo descape(const std::string&,int,int&)
1498
1499
1500 string escape_shellarg(const string &input)
1501 {
1502    string output = "'";
1503    string::const_iterator it, it_end = input.end();
1504    for (it = input.begin(); it != it_end; ++it)
1505    {
1506       if ( (*it) == '\'')
1507          output += "'\\'";
1508
1509       output += *it;
1510    }
1511
1512    output += "'";
1513    return output;
1514 }