1 /*
2  * Copyright (C) 2008 Emweb bv, Herent, Belgium.
3  *
4  * See the LICENSE file for terms of use.
5  */
6 
7 #include "WebUtils.h"
8 #include "DomElement.h"
9 #include "3rdparty/rapidxml/rapidxml.hpp"
10 #include "Wt/WException.h"
11 #include "Wt/WString.h"
12 #include "Wt/Utils.h"
13 
14 #include <boost/algorithm/string.hpp>
15 #include <boost/version.hpp>
16 
17 #include <cctype>
18 #include <cfloat>
19 #include <cstdio>
20 #include <fstream>
21 #include <iomanip>
22 
23 #ifdef WT_WIN32
24 #define WIN32_LEAN_AND_MEAN
25 #define NOMINMAX
26 #include <windows.h>
27 #define snprintf _snprintf
28 #else
29 #include <cstdlib>
30 #endif // WIN32
31 
32 #if !defined(WT_NO_SPIRIT) && BOOST_VERSION >= 104700 && BOOST_VERSION < 107600
33 #  define SPIRIT_FLOAT_FORMAT
34 #endif
35 
36 #ifdef SPIRIT_FLOAT_FORMAT
37 #include <boost/config/warning_disable.hpp>
38 #include <boost/spirit/include/karma.hpp>
39 #else
40 #include <boost/math/special_functions/fpclassify.hpp>
41 #include <boost/math/special_functions/sign.hpp>
42 #endif // SPIRIT_FLOAT_FORMAT
43 
44 #include <boost/spirit/include/qi_parse.hpp>
45 #include <boost/spirit/include/qi_numeric.hpp>
46 
47 // Qnx gcc 4.4.2
48 #ifdef isnan
49 #undef isnan
50 #endif
51 #ifdef isinf
52 #undef isinf
53 #endif
54 
55 namespace Wt {
56   namespace Utils {
57 
append(const std::string & s,char c)58 std::string append(const std::string& s, char c)
59 {
60   if (s.empty() || s[s.length() - 1] != c)
61     return s + c;
62   else
63     return s;
64 }
65 
prepend(const std::string & s,char c)66 std::string prepend(const std::string& s, char c)
67 {
68   if (s.empty() || s[0] != c)
69     return c + s;
70   else
71     return s;
72 }
73 
replace(std::string & s,char c,const std::string & r)74 std::string& replace(std::string& s, char c, const std::string& r)
75 {
76   std::string::size_type p = 0;
77 
78   while ((p = s.find(c, p)) != std::string::npos) {
79     s.replace(p, 1, r);
80     p += r.length();
81   }
82 
83   return s;
84 }
85 
replace(std::string & s,const std::string & k,const std::string & r)86 std::string& replace(std::string& s, const std::string& k, const std::string& r)
87 {
88   std::string::size_type p = 0;
89 
90   while ((p = s.find(k, p)) != std::string::npos) {
91     s.replace(p, k.length(), r);
92     p += r.length();
93   }
94 
95   return s;
96 }
97 
lowerCase(const std::string & s)98 std::string lowerCase(const std::string& s)
99 {
100   std::string result = s;
101   for (unsigned i = 0; i < result.length(); ++i)
102     result[i] = tolower(result[i]);
103   return result;
104 }
105 
sanitizeUnicode(EscapeOStream & sout,const std::string & text)106 void sanitizeUnicode(EscapeOStream& sout, const std::string& text)
107 {
108   char buf[4];
109 
110   for (const char *c = text.c_str(); *c;) {
111     char *b = buf;
112     // but copy_check_utf8() does not declare the following ranges illegal:
113     //  U+D800-U+DFFF
114     //  U+FFFE-U+FFFF
115     Wt::rapidxml::xml_document<>::copy_check_utf8(c, b);
116     for (char *i = buf; i < b; ++i)
117       sout << *i;
118   }
119 }
120 
eraseWord(const std::string & s,const std::string & w)121 std::string eraseWord(const std::string& s, const std::string& w)
122 {
123   std::string::size_type p;
124   std::string::size_type pos = 0;
125 
126   while ((p = s.find(w, pos)) != std::string::npos) {
127     std::string::size_type e = p + w.length();
128     if ((p == 0          || s[p-1] == ' ') &&
129 	(e == s.length() || s[e] == ' ')) {
130       std::string ss = s;
131       ss.erase(ss.begin() + p, ss.begin() + e);
132       if (p > 1)
133 	ss.erase(ss.begin() + (p - 1));
134       else if (e < ss.length())
135 	ss.erase(ss.begin() + p);
136 
137       return ss;
138     }
139 
140     pos = p + 1;
141   }
142 
143   return s;
144 }
145 
addWord(const std::string & s,const std::string & w)146 std::string addWord(const std::string& s, const std::string& w)
147 {
148   if (s.empty())
149     return w;
150   else
151     return s + ' ' + w;
152 }
153 
itoa(int value,char * result,int base)154 char *itoa(int value, char *result, int base) {
155   char* out = result;
156   int quotient = value;
157 
158   if (quotient < 0)
159     quotient = -quotient;
160 
161   do {
162     *out =
163       "0123456789abcdefghijklmnopqrstuvwxyz"[quotient % base];
164     ++out;
165     quotient /= base;
166   } while (quotient);
167 
168   if (value < 0 && base == 10)
169     *out++ = '-';
170 
171   std::reverse(result, out);
172   *out = 0;
173 
174   return result;
175 }
176 
utoa(unsigned int value,char * result,int base)177 char *utoa(unsigned int value, char* result, int base) {
178   char* out = result;
179   unsigned int quotient = value;
180 
181   do {
182     *out =
183       "0123456789abcdefghijklmnopqrstuvwxyz"[quotient % base];
184     ++out;
185     quotient /= base;
186   } while (quotient);
187 
188   std::reverse(result, out);
189   *out = 0;
190 
191   return result;
192 }
193 
lltoa(long long value,char * result,int base)194 char *lltoa(long long value, char *result, int base) {
195   char* out = result;
196   long long quotient = value;
197 
198   if (quotient < 0)
199     quotient = -quotient;
200 
201   do {
202     *out =
203       "0123456789abcdefghijklmnopqrstuvwxyz"[ quotient % base ];
204     ++out;
205     quotient /= base;
206   } while (quotient);
207 
208   if (value < 0 && base == 10)
209     *out++ = '-';
210   std::reverse(result, out);
211   *out = 0;
212 
213   return result;
214 }
215 
pad_itoa(int value,int length,char * result)216 char *pad_itoa(int value, int length, char *result) {
217   static const int exp[] = { 1, 10, 100, 1000, 10000, 100000, 100000 };
218 
219   result[length] = 0;
220 
221   for (int i = 0; i < length; ++i) {
222     int b = exp[length - i - 1];
223     if (value >= b)
224       result[i] = '0' + (value / b) % 10;
225     else
226       result[i] = '0';
227   }
228 
229   return result;
230 }
231 
232 #ifdef SPIRIT_FLOAT_FORMAT
233 namespace {
234   using namespace boost::spirit;
235   using namespace boost::spirit::karma;
236 
237   // adjust rendering for JS flaots
238   template <typename T, int Precision>
239   struct JavaScriptPolicy : karma::real_policies<T>
240   {
241     // not 'nan', but 'NaN'
242     template <typename CharEncoding, typename Tag, typename OutputIterator>
nanJavaScriptPolicy243     static bool nan (OutputIterator& sink, T n, bool force_sign)
244     {
245       return string_inserter<CharEncoding, Tag>::call(sink, "NaN");
246     }
247 
248     // not 'inf', but 'Infinity'
249     template <typename CharEncoding, typename Tag, typename OutputIterator>
infJavaScriptPolicy250     static bool inf (OutputIterator& sink, T n, bool force_sign)
251     {
252       return sign_inserter::call(sink, false, (n<0), force_sign) &&
253         string_inserter<CharEncoding, Tag>::call(sink, "Infinity");
254     }
255 
floatfieldJavaScriptPolicy256     static int floatfield(T t) {
257       return (t != 0.0) && ((t < 0.001) || (t > 1E8)) ?
258 	karma::real_policies<T>::fmtflags::scientific :
259 	karma::real_policies<T>::fmtflags::fixed;
260     }
261 
262     // 7 significant numbers; about float precision
precisionJavaScriptPolicy263     static unsigned precision(T) { return Precision; }
264 
265   };
266 
267   typedef real_generator<double, JavaScriptPolicy<double, 7> >
268     KarmaJavaScriptReal;
269   typedef real_generator<double, JavaScriptPolicy<double, 15> >
270     KarmaJavaScriptDouble;
271 
272 }
273 
generic_double_to_str(double d,int precision,char * buf)274 static inline char *generic_double_to_str(double d, int precision, char *buf)
275 {
276   using namespace boost::spirit;
277   using namespace boost::spirit::karma;
278   char *p = buf;
279   if (d != 0) {
280       if (fabs(d) < DBL_MIN) {
281         std::stringstream ss;
282         ss.imbue(std::locale("C"));
283         ss << std::setprecision(precision) << d;
284         std::string str = ss.str();
285         memcpy(p, str.c_str(), str.length());
286         p += str.length();
287       } else {
288         if (precision <= 7)
289           generate(p, KarmaJavaScriptReal(), d);
290         else
291           generate(p, KarmaJavaScriptDouble(), d);
292       }
293   }  else
294     *p++ = '0';
295   *p = '\0';
296   return buf;
297 }
298 #else
generic_double_to_str(double d,char * buf)299 static inline char *generic_double_to_str(double d, char *buf)
300 {
301   if (!boost::math::isnan(d)) {
302     if (!boost::math::isinf(d)) {
303       sprintf(buf, "%.7g", d);
304     } else {
305       if (d > 0) {
306         sprintf(buf, "Infinity");
307       } else {
308         sprintf(buf, "-Infinity");
309       }
310     }
311   } else {
312     sprintf(buf, "NaN");
313   }
314   return buf;
315 }
316 #endif
317 
round_css_str(double d,int digits,char * buf)318 char *round_css_str(double d, int digits, char *buf)
319 {
320   static const int exp[] = { 1, 10, 100, 1000, 10000, 100000, 1000000 };
321 
322   long long i
323     = static_cast<long long>(d * exp[digits] + (d > 0 ? 0.49 : -0.49));
324 
325   lltoa(i, buf);
326   char *num = buf;
327 
328   if (num[0] == '-')
329     ++num;
330   int len = std::strlen(num);
331 
332   if (len <= digits) {
333     int shift = digits + 1 - len;
334     for (int i = digits + 1; i >= 0; --i) {
335       if (i >= shift)
336 	num[i] = num[i - shift];
337       else
338 	num[i] = '0';
339     }
340     len = digits + 1;
341   }
342 
343   int dotPos = (std::max)(len - digits, 0);
344 
345   for (int i = digits + 1; i >= 0; --i)
346     num[dotPos + i + 1] = num[dotPos + i];
347 
348   num[dotPos] = '.';
349 
350   return buf;
351 }
352 
round_js_str(double d,int digits,char * buf)353 char *round_js_str(double d, int digits, char *buf) {
354 #ifdef SPIRIT_FLOAT_FORMAT
355   return generic_double_to_str(d, digits, buf);
356 #else
357   return generic_double_to_str(d, buf);
358 #endif
359 }
360 
urlEncode(const std::string & url,const std::string & allowed)361 std::string urlEncode(const std::string& url, const std::string& allowed)
362 {
363   return DomElement::urlEncodeS(url, allowed);
364 }
365 
dataUrlDecode(const std::string & url,std::vector<unsigned char> & data)366 std::string dataUrlDecode(const std::string& url,
367 			  std::vector<unsigned char> &data)
368 {
369   return std::string();
370 }
371 
inplaceUrlDecode(std::string & text)372 void inplaceUrlDecode(std::string &text)
373 {
374   // Note: there is a Java-too duplicate of this function in Wt/Utils.C
375   std::size_t j = 0;
376 
377   for (std::size_t i = 0; i < text.length(); ++i) {
378     char c = text[i];
379 
380     if (c == '+') {
381       text[j++] = ' ';
382     } else if (c == '%' && i + 2 < text.length()) {
383       std::string h = text.substr(i + 1, 2);
384       char *e = 0;
385       int hval = std::strtol(h.c_str(), &e, 16);
386 
387       if (*e == 0) {
388 	text[j++] = (char)hval;
389 	i += 2;
390       } else {
391 	// not a proper %XX with XX hexadecimal format
392 	text[j++] = c;
393       }
394     } else
395       text[j++] = c;
396   }
397 
398   text.erase(j);
399 }
400 
EncodeHttpHeaderField(const std::string & fieldname,const WString & fieldValue)401 std::string EncodeHttpHeaderField(const std::string &fieldname,
402                                   const WString &fieldValue)
403 {
404   // This implements RFC 5987
405   return fieldname + "*=UTF-8''" + urlEncode(fieldValue.toUTF8());
406 }
407 
readFile(const std::string & fname)408 std::string readFile(const std::string& fname)
409 {
410   std::ifstream f(fname.c_str(), std::ios::in | std::ios::binary);
411 
412   if (!f)
413     throw WException("Could not load " + fname);
414 
415   f.seekg(0, std::ios::end);
416   int length = f.tellg();
417   f.seekg(0, std::ios::beg);
418 
419   std::unique_ptr<char[]> ftext(new char[length + 1]);
420   f.read(ftext.get(), length);
421   ftext[length] = 0;
422 
423   return std::string(ftext.get());
424 }
425 
formatFloat(const WString & format,double value)426 WString formatFloat(const WString &format, double value)
427 {
428   std::string f = format.toUTF8();
429   int buflen = f.length() + 15;
430 
431   char *buf = new char[buflen];
432 
433   snprintf(buf, buflen, f.c_str(), value);
434   buf[buflen - 1] = 0;
435 
436   WString result = WT_USTRING::fromUTF8(buf);
437 
438   delete[] buf;
439 
440   return result;
441 }
442 
443 template<typename ResultType, typename SpiritType>
convert(const char * fname,const SpiritType & t,const std::string & v)444 ResultType convert(const char *fname, const SpiritType &t, const std::string& v)
445 {
446   auto is_space = [](char c) { return c == ' '; };
447   auto it = std::find_if_not(v.cbegin(), v.cend(), is_space);
448   ResultType result{0};
449   bool success =
450       it < v.cend() &&
451       boost::spirit::qi::parse(it, v.cend(), t, result) &&
452       std::all_of(it, v.cend(), is_space);
453   if (!success)
454     throw std::invalid_argument(std::string(fname) + "() of " + v + " failed");
455   return result;
456 }
457 
stol(const std::string & v)458 long stol(const std::string& v)
459 {
460   return convert<long>("stol", boost::spirit::long_, v);
461 }
462 
stoul(const std::string & v)463 unsigned long stoul(const std::string& v)
464 {
465   return convert<unsigned long>("stoul", boost::spirit::ulong_, v);
466 }
467 
stoll(const std::string & v)468 long long stoll(const std::string& v)
469 {
470   return convert<long long>("stoll", boost::spirit::long_long, v);
471 }
472 
stoull(const std::string & v)473 unsigned long long stoull(const std::string& v)
474 {
475   return convert<unsigned long long>("stoull", boost::spirit::ulong_long, v);
476 }
477 
stoi(const std::string & v)478 int stoi(const std::string& v)
479 {
480   return convert<int>("stoi", boost::spirit::int_, v);
481 }
482 
stod(const std::string & v)483 double stod(const std::string& v)
484 {
485   return convert<double>("stod", boost::spirit::double_, v);
486 }
487 
stof(const std::string & v)488 float stof(const std::string& v)
489 {
490   return convert<float>("stof", boost::spirit::float_, v);
491 }
492 
493 
494   }
495 }
496