change permissions before fsync()
[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 /**
434  * @brief joins a list of strings into a single string.
435  *
436  * This funtion is (basically) the reverse operation of @a split_string.
437  *
438  * @param parts the list of strings.
439  * @param delimiter the delimiter which is inserted between the strings.
440  * @return the joined string.
441  */
442 std::string join_string(
443    const std::list< std::string >& parts,
444    const std::string& delimiter
445 )
446 {
447    std::string result;
448    if (! parts.empty() )
449    {
450       std::list< std::string >::const_iterator it= parts.begin();
451       result = *it;
452       while ( ++it != parts.end() )
453       {
454          result+= delimiter;
455          result+= *it;
456       }
457    }
458    return result;
459 } // eo join_string(const std::list< std::string >&,const std::string&)
460
461
462 /** @brief same as join_string for list, except uses a vector */
463 std::string join_string(
464    const std::vector< std::string >& parts,
465    const std::string& delimiter
466 )
467 {
468    std::string result;
469    if (! parts.empty() )
470    {
471       std::vector< std::string >::const_iterator it= parts.begin();
472       result = *it;
473       while ( ++it != parts.end() )
474       {
475          result+= delimiter;
476          result+= *it;
477       }
478    }
479    return result;
480 } // eo join_string(const std::vector< std::string >&,const std::string&)
481
482
483
484 /*
485 ** conversions
486 */
487
488
489 /**
490  * @brief returns a hex string from a binary string.
491  * @param str the (binary) string
492  * @param upper_case_digits determine whether to use upper case characters for digits A-F.
493  * @return the string in hex notation.
494  */
495 std::string convert_binary_to_hex(
496    const std::string& str,
497    bool upper_case_digits
498 )
499 {
500    std::string result;
501    std::string hexDigits(upper_case_digits ? hexDigitsUpper : hexDigitsLower);
502    for ( std::string::const_iterator it= str.begin();
503          it != str.end();
504          ++it)
505    {
506       result.push_back( hexDigits[ ( (*it) >> 4) & 0x0f ] );
507       result.push_back( hexDigits[ (*it) & 0x0f ] );
508    }
509    return result;
510 } // eo convert_binary_to_hex(const std::string&,bool)
511
512
513 /**
514  * @brief converts a hex digit string to binary string.
515  * @param str hex digit string
516  * @return the binary string.
517  *
518  * The hex digit string may contains white spaces or colons which are treated
519  * as delimiters between hex digit groups.
520  *
521  * @todo rework the handling of half nibbles (consistency)!
522  */
523 std::string convert_hex_to_binary(
524    const std::string& str
525 )
526 throw (std::runtime_error)
527 {
528    std::string result;
529    char c= 0;
530    bool hasNibble= false;
531    bool lastWasWS= true;
532    for ( std::string::const_iterator it= str.begin();
533          it != str.end();
534          ++it)
535    {
536       std::string::size_type p = hexDigitsLower.find( *it );
537       if (p== std::string::npos)
538       {
539          p= hexDigitsUpper.find( *it );
540       }
541       if (p == std::string::npos)
542       {
543          if (   ( Whitespaces.find( *it ) != std::string::npos) // is it a whitespace?
544                 or ( *it == ':') // or a colon?
545             )
546          {
547             // we treat that as a valid delimiter:
548             if (hasNibble)
549             {
550                // 1 nibble before WS is treate as lower part:
551                result.push_back(c);
552                // reset state:
553                hasNibble= false;
554             }
555             lastWasWS= true;
556             continue;
557          }
558       }
559       if (p == std::string::npos )
560       {
561          throw runtime_error("illegal character in hex digit string: " + str);
562       }
563       lastWasWS= false;
564       if (hasNibble)
565       {
566          c<<=4;
567       }
568       else
569       {
570          c=0;
571       }
572       c+= (p & 0x0f);
573       if (hasNibble)
574       {
575          //we already had a nibble, so a char is complete now:
576          result.push_back( c );
577          hasNibble=false;
578       }
579       else
580       {
581          // this is the first nibble of a new char:
582          hasNibble=true;
583       }
584    }
585    if (hasNibble)
586    {
587       //well, there is one nibble left
588       // let's do some heuristics:
589       if (lastWasWS)
590       {
591          // if the preceeding character was a white space (or a colon)
592          // we treat the nibble as lower part:
593          //( this is consistent with shortened hex notations where leading zeros are not noted)
594          result.push_back( c );
595       }
596       else
597       {
598          // if it was part of a hex digit chain, we treat it as UPPER part (!!)
599          result.push_back( c << 4 );
600       }
601    }
602    return result;
603 } // eo convert_hex_to_binary(const std::string&)
604
605
606 static list<string>& alloc_template_starts()
607 {
608     static list<string> result;
609     if (result.empty())
610     {
611         result.push_back("std::list");
612         result.push_back("std::vector");
613     }
614     return result;
615 }
616
617 string shorten_stl_types(const string &input)
618 {
619     string output = input;
620
621     // first: replace fixed string for std::string
622     replace_all(output, "std::basic_string<char, std::char_traits<char>, std::allocator<char> >",
623                         "std::string");
624
625     // loop over list/vector/... that have an allocator, e.g.
626     // std::list< some_type_here, std::allocator<some_type_here> >
627     string::size_type start, comma, end, len, start_text_len;
628     int n_open_brackets;
629     string allocator_text;
630     BOOST_FOREACH(const string &start_text, alloc_template_starts())
631     {
632         start = 0;
633         comma = 0;
634         end = 0;
635         start_text_len = start_text.length();
636         while( (start=output.find(start_text+"<", start)) != string::npos )
637         {
638             len = output.length();
639             start += start_text_len+1;   // start next iter and tests here after opening bracket
640
641             // now comes the tricky part: find matching ',' and the closing '>' even if "subtype" is template again
642             comma = start;
643             n_open_brackets = 1;    // the bracket right after start_text counts as first
644             while (comma < len && n_open_brackets > 0)
645             {
646                 if (output[comma] == ',' && n_open_brackets == 1)
647                     break;
648                 else if (output[comma] == '<')
649                     ++n_open_brackets;
650                 else if (output[comma] == '>')
651                     --n_open_brackets;
652                 ++comma;
653             }
654             end = comma+1;
655             while (end < len && n_open_brackets > 0)
656             {
657                 if (output[end] == '<')
658                     ++n_open_brackets;
659                 else if (output[end] == '>')
660                 {
661                     --n_open_brackets;
662                     if (n_open_brackets == 0)
663                         break;  // do not increment end
664                 }
665                 ++end;
666             }
667
668             // check that start < comma < end < len && n_open_brackets == 0
669             if (start >= comma || comma >= end || end >= len || n_open_brackets != 0)
670                 continue;   // input seems to be of unexpected form
671
672             // check that type in allocator is same as until comma
673             string type = output.substr(start, comma-start);
674             if (type[type.length()-1] == '>')
675                 allocator_text = string("std::allocator<") + type + " > ";
676             else
677                 allocator_text = string("std::allocator<") + type + "> ";
678             if (output.substr(comma+2, end-comma-2) == allocator_text)
679                 output.replace(comma+2, end-comma-2, "_alloc_");
680         }
681     }
682
683     return output;
684 }
685
686 typedef boost::shared_ptr<BIO> BIO_Ptr;
687
688 /**
689 * @brief Converts openssl generic input/output to std::string
690 *
691 * Code adapted from keymakerd.
692 *
693 * @param bio Openssl's generic input/output
694 * @return :string STL string
695 **/
696 static std::string _convert_BIO_to_string(BIO *input)
697 {
698     std::string rtn;
699
700     char *output = NULL;
701     long written = BIO_get_mem_data(input, &output);
702     if (written <= 0 || output == NULL)
703         return rtn;
704
705     rtn.assign(output, written);                                                                    //lint !e534 !e732
706     return rtn;
707 }                                                                                                   //lint !e1764
708
709 /**
710     * @brief base64 encode a string using OpenSSL base64 functions
711     *
712     * Data size limit is 2GB on 32 bit (LONG_MAX)
713     *
714     * @param input String to encode
715     * @param one_line Encode all data as one line, no wrapping with line feeds
716     * @return base64 encoded string
717     */
718 std::string base64_encode(const std::string &input, bool one_line)
719 {
720     // check for empty buffer
721     if (input.empty())
722         return input;
723
724     // safety check to ensure our check afer BIO_write() works
725     if (input.size() >= LONG_MAX)
726         throw runtime_error("base64 encode: Too much data");
727
728     // setup encoder. Note: BIO_free_all frees both BIOs.
729     BIO_Ptr base64_encoder(BIO_new(BIO_f_base64()), BIO_free_all);
730     BIO *encoder_bio = base64_encoder.get();
731     if (one_line)
732         BIO_set_flags(encoder_bio, BIO_FLAGS_BASE64_NO_NL);
733
734     // chain output buffer and encoder together
735     BIO *encoded_result = BIO_new(BIO_s_mem());
736     BIO_push(encoder_bio, encoded_result);
737
738     // encode
739     long written = BIO_write(encoder_bio, input.c_str(), input.size());
740     if ((unsigned)written != input.size())
741     {
742         ostringstream out;
743         out << "base64 encoding failed: input size: "
744             << input.size() << " vs. output size: " << written;
745         throw runtime_error(out.str());
746     }
747     if (BIO_flush(encoder_bio) != 1)
748         throw runtime_error("base64 encode: BIO_flush() failed");
749
750     return _convert_BIO_to_string(encoded_result);
751 }
752
753 /**
754     * @brief base64 decode a string using OpenSSL base64 functions
755     *
756     * @param input String to decode
757     * @param one_line Expect all base64 data in one line. Input with line feeds will fail.
758     * @return base64 decoded string
759     */
760 std::string base64_decode(const std::string &input, bool one_line)
761 {
762     // check for empty buffer
763     if (input.empty())
764         return input;
765
766     // safety check for BIO_new_mem_buf()
767     if (input.size() >= INT_MAX)
768         throw runtime_error("base64 decode: Too much data");
769
770     // setup encoder. Note: BIO_free_all frees both BIOs.
771     BIO_Ptr base64_decoder(BIO_new(BIO_f_base64()), BIO_free_all);
772     BIO *bio_base64 = base64_decoder.get();
773     if (one_line)
774         BIO_set_flags(bio_base64, BIO_FLAGS_BASE64_NO_NL);
775
776     // chain input buffer and decoder together
777     BIO *bio_input = BIO_new_mem_buf((void*)input.c_str(), input.size());
778     bio_input = BIO_push(bio_base64, bio_input);
779
780     BIO_Ptr decoded_result(BIO_new(BIO_s_mem()), BIO_free_all);
781     BIO *bio_decoded = decoded_result.get();
782     const int convbuf_size = 512;
783     char convbuf[convbuf_size];
784
785     long read_bytes = 0;
786     while((read_bytes = BIO_read(bio_input, convbuf, convbuf_size)) > 0)
787     {
788         BOOST_ASSERT(read_bytes <= convbuf_size);
789         long written_bytes = BIO_write(bio_decoded, convbuf, read_bytes);
790         if (written_bytes != read_bytes)
791         {
792             ostringstream out;
793             out << "base64 decoding failed: read_bytes: "
794                 << read_bytes << " vs. written_bytes: " << written_bytes;
795             throw runtime_error(out.str());
796         }
797     }
798     if (read_bytes == -2 || read_bytes == -1)
799         throw runtime_error("base64 decode: Error during decoding");
800
801     return _convert_BIO_to_string(bio_decoded);
802 }
803
804 } // eo namespace I2n
805
806
807
808
809 std::string iso_to_utf8(const std::string& isostring)
810 {
811    string result;
812
813    iconv_t i2utf8 = iconv_open("UTF-8", "ISO-8859-1");
814
815    if (iso_to_utf8 == (iconv_t)-1)
816       throw runtime_error("iconv can't convert from ISO-8859-1 to UTF-8");
817
818    size_t in_size=isostring.size();
819    size_t out_size=in_size*4;
820
821    char *buf = (char *)malloc(out_size+1);
822    if (buf == NULL)
823       throw runtime_error("out of memory for iconv buffer");
824
825    char *in = (char *)isostring.c_str();
826    char *out = buf;
827    iconv(i2utf8, &in, &in_size, &out, &out_size);
828
829    buf[isostring.size()*4-out_size]=0;
830
831    result=buf;
832
833    free(buf);
834    iconv_close(i2utf8);
835
836    return result;
837 }
838
839 std::string utf8_to_iso(const std::string& utf8string)
840 {
841    string result;
842
843    iconv_t utf82iso = iconv_open("ISO-8859-1","UTF-8");
844
845    if (utf82iso == (iconv_t)-1)
846       throw runtime_error("iconv can't convert from UTF-8 to ISO-8859-1");
847
848    size_t in_size=utf8string.size();
849    size_t out_size=in_size;
850
851    char *buf = (char *)malloc(out_size+1);
852    if (buf == NULL)
853       throw runtime_error("out of memory for iconv buffer");
854
855    char *in = (char *)utf8string.c_str();
856    char *out = buf;
857    iconv(utf82iso, &in, &in_size, &out, &out_size);
858
859    buf[utf8string.size()-out_size]=0;
860
861    result=buf;
862
863    free(buf);
864    iconv_close(utf82iso);
865
866    return result;
867 }
868
869 wchar_t* utf8_to_wbuf(const std::string& utf8string)
870 {
871    iconv_t utf82wstr = iconv_open("UCS-4LE","UTF-8");
872
873    if (utf82wstr == (iconv_t)-1)
874       throw runtime_error("iconv can't convert from UTF-8 to UCS-4");
875
876    size_t in_size=utf8string.size();
877    size_t out_size= (in_size+1)*sizeof(wchar_t);
878
879    wchar_t *buf = (wchar_t *)malloc(out_size);
880    if (buf == NULL)
881       throw runtime_error("out of memory for iconv buffer");
882
883    char *in = (char *)utf8string.c_str();
884    char *out = (char*) buf;
885    if (iconv(utf82wstr, &in, &in_size, &out, &out_size) == (size_t)-1)
886       throw runtime_error("error converting char encodings");
887
888    buf[ ( (utf8string.size()+1)*sizeof(wchar_t)-out_size) /sizeof(wchar_t) ]=0;
889
890    iconv_close(utf82wstr);
891
892    return buf;
893 }
894
895 std::string utf7imap_to_utf8(const std::string& utf7imapstring)
896 {
897    string result;
898
899    iconv_t utf7imap2utf8 = iconv_open("UTF-8","UTF-7-IMAP");
900
901    if (utf7imap2utf8 == (iconv_t)-1)
902       throw runtime_error("iconv can't convert from UTF-7-IMAP to UTF-8");
903
904    size_t in_size=utf7imapstring.size();
905    size_t out_size=in_size*4;
906
907    char *buf = (char *)malloc(out_size+1);
908    if (buf == NULL)
909       throw runtime_error("out of memory for iconv buffer");
910
911    char *in = (char *)utf7imapstring.c_str();
912    char *out = buf;
913    iconv(utf7imap2utf8, &in, &in_size, &out, &out_size);
914
915    buf[utf7imapstring.size()*4-out_size]=0;
916
917    result=buf;
918
919    free(buf);
920    iconv_close(utf7imap2utf8);
921
922    return result;
923 }
924
925 std::string utf8_to_utf7imap(const std::string& utf8string)
926 {
927    string result;
928
929    iconv_t utf82utf7imap = iconv_open("UTF-7-IMAP", "UTF-8");
930
931    if (utf82utf7imap == (iconv_t)-1)
932       throw runtime_error("iconv can't convert from UTF-7-IMAP to UTF-8");
933
934    // UTF-7 is base64 encoded, a buffer 10x as large
935    // as the utf-8 buffer should be enough. If not the string will be truncated.
936    size_t in_size=utf8string.size();
937    size_t out_size=in_size*10;
938
939    char *buf = (char *)malloc(out_size+1);
940    if (buf == NULL)
941       throw runtime_error("out of memory for iconv buffer");
942
943    char *in = (char *)utf8string.c_str();
944    char *out = buf;
945    iconv(utf82utf7imap, &in, &in_size, &out, &out_size);
946
947    buf[utf8string.size()*10-out_size]= 0;
948
949    result=buf;
950
951    free(buf);
952    iconv_close(utf82utf7imap);
953
954    return result;
955 }
956
957 // Tokenize string by (html) tags
958 void tokenize_by_tag(vector<pair<string,bool> > &tokenized, const std::string &input)
959 {
960    string::size_type pos, len = input.size();
961    bool inside_tag = false;
962    string current;
963
964    for (pos = 0; pos < len; pos++)
965    {
966       if (input[pos] == '<')
967       {
968          inside_tag = true;
969
970          if (!current.empty() )
971          {
972             tokenized.push_back( make_pair(current, false) );
973             current = "";
974          }
975
976          current += input[pos];
977       }
978       else if (input[pos] == '>' && inside_tag)
979       {
980          current += input[pos];
981          inside_tag = false;
982          if (!current.empty() )
983          {
984             tokenized.push_back( make_pair(current, true) );
985             current = "";
986          }
987       }
988       else
989          current += input[pos];
990    }
991
992    // String left over in buffer?
993    if (!current.empty() )
994       tokenized.push_back( make_pair(current, false) );
995 } // eo tokenize_by_tag
996
997
998 std::string strip_html_tags(const std::string &input)
999 {
1000    // Pair first: string, second: isTag
1001    vector<pair<string,bool> > tokenized;
1002    tokenize_by_tag (tokenized, input);
1003
1004    string output;
1005    vector<pair<string,bool> >::const_iterator token, tokens_end = tokenized.end();
1006    for (token = tokenized.begin(); token != tokens_end; ++token)
1007       if (!token->second)
1008          output += token->first;
1009
1010    return output;
1011 } // eo strip_html_tags
1012
1013
1014 // Smart-encode HTML en
1015 string smart_html_entities(const std::string &input)
1016 {
1017    // Pair first: string, second: isTag
1018    vector<pair<string,bool> > tokenized;
1019    tokenize_by_tag (tokenized, input);
1020
1021    string output;
1022    vector<pair<string,bool> >::const_iterator token, tokens_end = tokenized.end();
1023    for (token = tokenized.begin(); token != tokens_end; ++token)
1024    {
1025       // keep HTML tags as they are
1026       if (token->second)
1027          output += token->first;
1028       else
1029          output += html_entities(token->first);
1030    }
1031
1032    return output;
1033 }
1034
1035
1036 string::size_type find_8bit(const std::string &str)
1037 {
1038    string::size_type l=str.size();
1039    for (string::size_type p=0; p < l; p++)
1040       if (static_cast<unsigned char>(str[p]) > 127)
1041          return p;
1042
1043    return string::npos;
1044 }
1045
1046 // encoded UTF-8 chars into HTML entities
1047 string html_entities(std::string str)
1048 {
1049    // Normal chars
1050    replace_all (str, "&", "&amp;");
1051    replace_all (str, "<", "&lt;");
1052    replace_all (str, ">", "&gt;");
1053    replace_all (str, "\"", "&quot;");
1054    replace_all (str, "'", "&#x27;");
1055    replace_all (str, "/", "&#x2F;");
1056
1057    // Umlauts
1058    replace_all (str, "\xC3\xA4", "&auml;");
1059    replace_all (str, "\xC3\xB6", "&ouml;");
1060    replace_all (str, "\xC3\xBC", "&uuml;");
1061    replace_all (str, "\xC3\x84", "&Auml;");
1062    replace_all (str, "\xC3\x96", "&Ouml;");
1063    replace_all (str, "\xC3\x9C", "&Uuml;");
1064
1065    // Misc
1066    replace_all (str, "\xC3\x9F", "&szlig;");
1067
1068    // conversion of remaining non-ASCII chars needed?
1069    // just do if needed because of performance
1070    if (find_8bit(str) != string::npos)
1071    {
1072       // convert to fixed-size encoding UTF-32
1073       wchar_t* wbuf=utf8_to_wbuf(str);
1074       ostringstream target;
1075
1076       // replace all non-ASCII chars with HTML representation
1077       for (int p=0; wbuf[p] != 0; p++)
1078       {
1079          unsigned int c=wbuf[p];
1080
1081          if (c <= 127)
1082             target << static_cast<unsigned char>(c);
1083          else
1084             target << "&#" << c << ';';
1085       }
1086
1087       free(wbuf);
1088
1089       str=target.str();
1090    }
1091
1092    return str;
1093 } // eo html_entities(std::string)
1094
1095 // convert HTML entities to something that can be viewed on a basic text console (restricted to ASCII-7)
1096 string html_entities_to_console(std::string str)
1097 {
1098    // Normal chars
1099    replace_all (str, "&amp;", "&");
1100    replace_all (str, "&lt;", "<");
1101    replace_all (str, "&gt;", ">");
1102    replace_all (str, "&quot;", "\"");
1103    replace_all (str, "&#x27;", "'");
1104    replace_all (str, "&#x2F;", "/");
1105
1106    // Umlauts
1107    replace_all (str, "&auml;", "ae");
1108    replace_all (str, "&ouml;", "oe");
1109    replace_all (str, "&uuml;", "ue");
1110    replace_all (str, "&Auml;", "Ae");
1111    replace_all (str, "&Ouml;", "Oe");
1112    replace_all (str, "&Uuml;", "Ue");
1113
1114    // Misc
1115    replace_all (str, "&szlig;", "ss");
1116
1117    return str;
1118 }
1119
1120 // find_html_comments + remove_html_comments(str, comments)
1121 void remove_html_comments(string &str)
1122 {
1123     vector<CommentZone> comments = find_html_comments(str);
1124     remove_html_comments(str, comments);
1125 }
1126
1127 // find all html comments, behaving correctly if they are nested; ignores comment tags ("<!--FOO .... BAR-->")
1128 // If there are invalid comments ("-->" before "<!--" or different number of closing and opening tags),
1129 // then the unknown index of corresponding start/end tag will be represented by a string::npos
1130 // Indices are from start of start tag until first index after closing tag
1131 vector<CommentZone> find_html_comments(const std::string &str)
1132 {
1133     static const string START = "<!--";
1134     static const string CLOSE = "-->";
1135     static const string::size_type START_LEN = START.length();
1136     static const string::size_type CLOSE_LEN = CLOSE.length();
1137
1138     vector<CommentZone> comments;
1139
1140     // in order to find nested comments, need either recursion or a stack
1141     vector<string::size_type> starts;      // stack of start tags
1142
1143     string::size_type pos = 0;
1144     string::size_type len = str.length();
1145     string::size_type next_start, next_close;
1146
1147     while (pos < len)     // not really needed but just in case
1148     {
1149         next_start = str.find(START, pos);
1150         next_close = str.find(CLOSE, pos);
1151
1152         if ( (next_start == string::npos) && (next_close == string::npos) )
1153             break;   // we are done
1154
1155         else if ( (next_start == string::npos) || (next_close < next_start) )  // close one comment (pop)
1156         {
1157             if (starts.empty())    // closing tag without a start
1158                 comments.push_back(CommentZone(string::npos, next_close+CLOSE_LEN));
1159             else
1160             {
1161                 comments.push_back(CommentZone(starts.back(), next_close+CLOSE_LEN));
1162                 starts.pop_back();
1163             }
1164             pos = next_close + CLOSE_LEN;
1165         }
1166
1167         else if ( (next_close == string::npos) || (next_start < next_close) )  // start a new comment (push)
1168         {
1169             starts.push_back(next_start);
1170             pos = next_start + START_LEN;
1171         }
1172     }
1173
1174     // add comments that have no closing tag from back to front (important for remove_html_comments!)
1175     while (!starts.empty())
1176     {
1177         comments.push_back(CommentZone(starts.back(), string::npos));
1178         starts.pop_back();
1179     }
1180
1181     return comments;
1182 }
1183
1184 // remove all html comments foundby find_html_comments
1185 void remove_html_comments(std::string &str, const vector<CommentZone> &comments)
1186 {
1187     // remember position where last removal started
1188     string::size_type last_removal_start = str.length();
1189
1190     // Go from back to front to not mess up indices.
1191     // This requires that bigger comments, that contain smaller comments, come AFTER
1192     // the small contained comments in the comments vector (i.e. comments are ordered by
1193     // their closing tag, not their opening tag). This is true for results from find_html_comments
1194     BOOST_REVERSE_FOREACH(const CommentZone &comment, comments)
1195     {
1196         if (comment.first == string::npos)
1197         {
1198             str = str.replace(0, comment.second, "");   // comment starts "before" str --> delete from start
1199             break;   // there can be no more
1200         }
1201         else if (comment.first >= last_removal_start)
1202         {
1203             continue;    // this comment is inside another comment that we have removed already
1204         }
1205         else if (comment.second == string::npos)   // comment ends "after" str --> delete until end
1206         {
1207             str = str.replace(comment.first, string::npos, "");
1208             last_removal_start = comment.first;
1209         }
1210         else
1211         {
1212             str = str.replace(comment.first, comment.second-comment.first, "");
1213             last_removal_start = comment.first;
1214         }
1215     }
1216 }
1217
1218 bool replace_all(string &base, const char *ist, const char *soll)
1219 {
1220    string i=ist;
1221    string s=soll;
1222    return replace_all(base,&i,&s);
1223 }
1224
1225 bool replace_all(string &base, const string &ist, const char *soll)
1226 {
1227    string s=soll;
1228    return replace_all(base,&ist,&s);
1229 }
1230
1231 bool replace_all(string &base, const string *ist, const string *soll)
1232 {
1233    return replace_all(base,*ist,*soll);
1234 }
1235
1236 bool replace_all(string &base, const char *ist, const string *soll)
1237 {
1238    string i=ist;
1239    return replace_all(base,&i,soll);
1240 }
1241
1242 bool replace_all(string &base, const string &ist, const string &soll)
1243 {
1244    bool found_ist = false;
1245    string::size_type a=0;
1246
1247    if (ist.empty() )
1248       throw runtime_error ("replace_all called with empty search string");
1249
1250    while ( (a=base.find(ist,a) ) != string::npos)
1251    {
1252       base.replace(a,ist.size(),soll);
1253       a=a+soll.size();
1254       found_ist = true;
1255    }
1256
1257    return found_ist;
1258 }
1259
1260 /**
1261  * @brief replaces all characters that could be problematic or impose a security risk when being logged
1262  * @param str the original string
1263  * @param replace_with the character to replace the unsafe chars with
1264  * @return a string that is safe to send to syslog or other logfiles
1265  *
1266  * All chars between 0x20 (space) and 0x7E (~) (including) are considered safe for logging.
1267  * See e.g. RFC 5424, section 8.2 or the posix character class "printable".
1268  * This eliminates all possible problems with NUL, control characters, 8 bit chars, UTF8.
1269  *
1270  */
1271 std::string sanitize_for_logging(const std::string &str, const char replace_with)
1272 {
1273     std::string output=str;
1274
1275     const string::size_type len = output.size();
1276     for (std::string::size_type p=0; p < len; p++)
1277         if (output[p] < 0x20 || output[p] > 0x7E)
1278             output[p]=replace_with;
1279
1280     return output;
1281 }
1282
1283 #if 0
1284 string to_lower(const string &src)
1285 {
1286    string dst = src;
1287
1288    string::size_type pos, end = dst.size();
1289    for (pos = 0; pos < end; pos++)
1290       dst[pos] = tolower(dst[pos]);
1291
1292    return dst;
1293 }
1294
1295 string to_upper(const string &src)
1296 {
1297    string dst = src;
1298
1299    string::size_type pos, end = dst.size();
1300    for (pos = 0; pos < end; pos++)
1301       dst[pos] = toupper(dst[pos]);
1302
1303    return dst;
1304 }
1305 #endif
1306
1307 const int MAX_UNIT_FORMAT_SYMBOLS = 6;
1308
1309 const string shortUnitFormatSymbols[MAX_UNIT_FORMAT_SYMBOLS] = {
1310         " B",
1311         " KB",
1312         " MB",
1313         " GB",
1314         " TB",
1315         " PB"
1316 };
1317
1318 const string longUnitFormatSymbols[MAX_UNIT_FORMAT_SYMBOLS] = {
1319         i18n_noop(" Bytes"),
1320         i18n_noop(" KBytes"),
1321         i18n_noop(" MBytes"),
1322         i18n_noop(" GBytes"),
1323         i18n_noop(" TBytes"),
1324         i18n_noop(" PBytes")
1325 };
1326
1327
1328 static long double rounding_upwards(
1329         const long double number,
1330         const int rounding_multiplier
1331 )
1332 {
1333     long double rounded_number;
1334     rounded_number = number * rounding_multiplier;
1335     rounded_number += 0.5;
1336     rounded_number = (int64_t) (rounded_number);
1337     rounded_number = (long double) (rounded_number) / (long double) (rounding_multiplier);
1338
1339     return rounded_number;
1340 }
1341
1342
1343 string nice_unit_format(
1344         const int64_t input,
1345         const UnitFormat format,
1346         const UnitBase base
1347 )
1348 {
1349    // select the system of units (decimal or binary)
1350    int multiple = 0;
1351    if (base == UnitBase1000)
1352    {
1353        multiple = 1000;
1354    }
1355    else
1356    {
1357        multiple = 1024;
1358    }
1359
1360    long double size = input;
1361
1362    // check the size of the input number to fit in the appropriate symbol
1363    int sizecount = 0;
1364    while (size > multiple)
1365    {
1366        size = size / multiple;
1367        sizecount++;
1368
1369        // rollback to the previous values and stop the loop when cannot
1370        // represent the number length.
1371        if (sizecount >= MAX_UNIT_FORMAT_SYMBOLS)
1372        {
1373            size = size * multiple;
1374            sizecount--;
1375            break;
1376        }
1377    }
1378
1379    // round the input number "half up" to multiples of 10
1380    const int rounding_multiplier = 10;
1381    size = rounding_upwards(size, rounding_multiplier);
1382
1383    // format the input number, placing the appropriate symbol
1384    ostringstream out;
1385    out.setf (ios::fixed);
1386    if (format == ShortUnitFormat)
1387    {
1388        out.precision(1);
1389        out << size << i18n( shortUnitFormatSymbols[sizecount].c_str() );
1390    }
1391    else
1392    {
1393        out.precision (2);
1394        out << size << i18n( longUnitFormatSymbols[sizecount].c_str() );
1395    }
1396
1397    return out.str();
1398 } // eo nice_unit_format(int input)
1399
1400
1401 string nice_unit_format(
1402         const double input,
1403         const UnitFormat format,
1404         const UnitBase base
1405 )
1406 {
1407     // round as double and cast to int64_t
1408     // cast raised overflow error near max val of int64_t (~9.2e18, see unittest)
1409     int64_t input_casted_and_rounded =
1410         boost::numeric_cast<int64_t>( round(input) );
1411
1412     // now call other
1413     return nice_unit_format( input_casted_and_rounded, format, base );
1414 } // eo nice_unit_format(double input)
1415
1416
1417 string escape(const string &s)
1418 {
1419    string out(s);
1420    string::size_type p;
1421
1422    p=0;
1423    while ( (p=out.find_first_of("\"\\",p) ) !=out.npos)
1424    {
1425       out.insert (p,"\\");
1426       p+=2;
1427    }
1428
1429    p=0;
1430    while ( (p=out.find_first_of("\r",p) ) !=out.npos)
1431    {
1432       out.replace (p,1,"\\r");
1433       p+=2;
1434    }
1435
1436    p=0;
1437    while ( (p=out.find_first_of("\n",p) ) !=out.npos)
1438    {
1439       out.replace (p,1,"\\n");
1440       p+=2;
1441    }
1442
1443    out='"'+out+'"';
1444
1445    return out;
1446 } // eo scape(const std::string&)
1447
1448
1449 string descape(const string &s, int startpos, int &endpos)
1450 {
1451    string out;
1452
1453    if (s.at(startpos) != '"')
1454       throw out_of_range("value not type escaped string");
1455
1456    out=s.substr(startpos+1);
1457    string::size_type p=0;
1458
1459    // search for the end of the string
1460    while ( (p=out.find("\"",p) ) !=out.npos)
1461    {
1462       int e=p-1;
1463       bool escaped=false;
1464
1465       // the " might be escaped with a backslash
1466       while (e>=0 && out.at (e) =='\\')
1467       {
1468          if (escaped == false)
1469             escaped=true;
1470          else
1471             escaped=false;
1472
1473          e--;
1474       }
1475
1476       if (escaped==false)
1477          break;
1478       else
1479          p++;
1480    }
1481
1482    // we now have the end of the string
1483    out=out.substr(0,p);
1484
1485    // tell calling prog about the endposition
1486    endpos=startpos+p+1;
1487
1488    // descape all \ stuff inside the string now
1489    p=0;
1490    while ( (p=out.find_first_of("\\",p) ) !=out.npos)
1491    {
1492       switch (out.at(p+1) )
1493       {
1494          case 'r':
1495             out.replace(p,2,"\r");
1496             break;
1497          case 'n':
1498             out.replace(p,2,"\n");
1499             break;
1500          default:
1501             out.erase(p,1);
1502       }
1503       p++;
1504    }
1505
1506    return out;
1507 } // eo descape(const std::string&,int,int&)
1508
1509
1510 string escape_shellarg(const string &input)
1511 {
1512    string output = "'";
1513    string::const_iterator it, it_end = input.end();
1514    for (it = input.begin(); it != it_end; ++it)
1515    {
1516       if ( (*it) == '\'')
1517          output += "'\\'";
1518
1519       output += *it;
1520    }
1521
1522    output += "'";
1523    return output;
1524 }