SLProject  4.2.000
A platform independent 3D computer graphics framework for desktop OS, Android, iOS and online in web browsers
HttpUtils.cpp
Go to the documentation of this file.
1 /**
2  * \file HttpUtils.cpp
3  * \authors Luc Girod
4  * \date 2020
5  * \authors Luc Girod
6  * \copyright http://opensource.org/licenses/GPL-3.0
7  * \remarks Please use clangformat to format the code. See more code style on
8  * https://github.com/cpvrlab/SLProject4/wiki/SLProject-Coding-Style
9 */
10 
11 #ifdef SL_BUILD_WITH_OPENSSL
12 # include <HttpUtils.h>
13 # include <iostream>
14 # include <cstring>
15 # include <Utils.h>
16 # ifdef _WINDOWS
17 # else
18 # include <netdb.h>
19 # include <unistd.h>
20 # include <errno.h>
21 # endif
22 
23 //-----------------------------------------------------------------------------
24 /*!
25  *
26  */
27 void Socket::reset()
28 {
29  fd = 0;
30  memset(&sa, 0, sizeof(sa));
31 }
32 //-----------------------------------------------------------------------------
33 /*!
34  *
35  * \param host (ip or hostname)
36  * \param port
37  * \return
38  */
39 int Socket::connectTo(string ip,
40  int port)
41 {
42  struct addrinfo* res = NULL;
43  int ret = getaddrinfo(ip.c_str(), NULL, NULL, &res);
44  if (ret)
45  {
46  Utils::log("Socket ", "invalid address");
47  return -1;
48  }
49 
50  if (res->ai_family == AF_INET)
51  {
52  memset(&sa, 0, sizeof(sa));
53  sa.sin_family = AF_INET;
54  sa.sin_addr = (((struct sockaddr_in*)res->ai_addr))->sin_addr;
55  sa.sin_port = htons(port);
56  addrlen = sizeof(sa);
57  }
58  else if (res->ai_family == AF_INET6)
59  {
60  memset(&sa6, 0, sizeof(sa6));
61  sa6.sin6_family = AF_INET6;
62  sa6.sin6_addr = (((struct sockaddr_in6*)res->ai_addr))->sin6_addr;
63  sa6.sin6_port = htons(port);
64  addrlen = sizeof(sa6);
65  }
66  else
67  {
68  Utils::log("Socket ", "invalid address");
69  return -1;
70  }
71 
72  fd = (int)socket(res->ai_family, SOCK_STREAM, 0);
73  if (!fd)
74  {
75  Utils::log("Socket ", "Error creating socket");
76  return -1;
77  }
78 
79  freeaddrinfo(res);
80 
81  // Set timeout value to 10s
82 # ifndef WINDOWS
83  struct timeval tv;
84  tv.tv_sec = 15;
85  tv.tv_usec = 0;
86  setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);
87 # endif
88 
89  if (connect(fd, (struct sockaddr*)&sa, addrlen))
90  {
91  Utils::log("Socket ", "Error connecting to server.\n");
92  return -1;
93  }
94  return 0;
95 }
96 //-----------------------------------------------------------------------------
97 /*!
98  *
99  * \param data
100  * \param size
101  * \return
102  */
103 int Socket::sendData(const char* data,
104  size_t size)
105 {
106  int len = (int)send(fd, data, (int)size, 0);
107  if (len < 0)
108  return -1;
109  return 0;
110 }
111 
112 //-----------------------------------------------------------------------------
113 /*!
114  *
115  * \param data
116  * \param size
117  * \return
118  */
119 void Socket::disconnect()
120 {
121 
122 # ifdef _WINDOWS
123  closesocket(fd);
124 # else
125  close(fd);
126 # endif
127 }
128 //-----------------------------------------------------------------------------
129 /*!
130  *
131  */
132 # define BUFFER_SIZE 500
133 int Socket::receive(function<int(char* data, int size)> dataCB, int max)
134 {
135  int n = 0;
136  int len;
137  char buf[BUFFER_SIZE];
138  do
139  {
140  if (max != 0 && max - n <= BUFFER_SIZE)
141  {
142  len = (int)recv(fd, buf, max - n, 0);
143  if (len == -1)
144  {
145 # ifndef _WINDOWS
146  if (errno != ECONNRESET)
147  len = (int)recv(fd, buf, max - n, 0);
148 # else
149  len = (int)recv(fd, buf, max - n, 0);
150 # endif
151 
152  if (len == -1)
153  return -1;
154  }
155  if (dataCB(buf, len) != 0)
156  return -1;
157  return len;
158  }
159  else
160  {
161  len = (int)recv(fd, buf, BUFFER_SIZE, 0);
162 
163  if (len == -1)
164  {
165 # ifndef _WINDOWS
166  if (errno != ECONNRESET)
167  len = (int)recv(fd, buf, BUFFER_SIZE, 0);
168 # else
169  len = (int)recv(fd, buf, BUFFER_SIZE, 0);
170 # endif
171  return -1;
172  }
173 
174  n = n + len;
175  if (dataCB(buf, len) != 0)
176  return -1;
177  }
178 
179  } while (!_interrupt && len > 0);
180  _interrupt = false;
181  return n;
182 }
183 //-----------------------------------------------------------------------------
184 /*!
185  *
186  * \param ip
187  * \param port
188  * \return
189  */
190 int SecureSocket::connectTo(string ip, int port)
191 {
192  Socket::connectTo(ip, port);
193  SSL_load_error_strings();
194  const SSL_METHOD* meth = TLS_client_method();
195  SSL_CTX* ctx = SSL_CTX_new(meth);
196  ssl = SSL_new(ctx);
197  if (!ssl)
198  {
199  Utils::log("SecureSocket", "Error creating SSL");
200  return -1;
201  }
202  SSL_get_fd(ssl);
203  SSL_set_fd(ssl, fd);
204  int err = SSL_connect(ssl);
205  if (err <= 0)
206  {
207  Utils::log("SecureSocket", "Error creating SSL connection. err = %d", err);
208  return -1;
209  }
210 
211  return 0;
212 }
213 //-----------------------------------------------------------------------------
214 /*!
215  *
216  * \param data
217  * \param size
218  * \return
219  */
220 int SecureSocket::sendData(const char* data, size_t size)
221 {
222  int len = SSL_write(ssl, data, (int)size);
223  if (len < 0)
224  {
225  int err = SSL_get_error(ssl, len);
226  switch (err)
227  {
228  case SSL_ERROR_WANT_WRITE:
229  return 0;
230  case SSL_ERROR_WANT_READ:
231  return 0;
232  case SSL_ERROR_ZERO_RETURN:
233  case SSL_ERROR_SYSCALL:
234  case SSL_ERROR_SSL:
235  default:
236  return -1;
237  }
238  }
239  return 0;
240 }
241 //-----------------------------------------------------------------------------
242 /*!
243  *
244  * \param dataCB
245  * \param max
246  */
247 int SecureSocket::receive(function<int(char* data, int size)> dataCB,
248  int max)
249 {
250  int len;
251  int n = 0;
252  char buf[BUFFER_SIZE];
253  do
254  {
255  if (max != 0 && max - n <= BUFFER_SIZE)
256  {
257  len = SSL_read(ssl, buf, max - n);
258  if (len == -1)
259  {
260  len = SSL_read(ssl, buf, max - n); // retry
261  if (len == -1)
262  {
263  Utils::log("SecureSocket", "SSL_read return -1");
264  return -1;
265  }
266  }
267 
268  if (dataCB(buf, len) != 0)
269  return -1;
270  return len;
271  }
272  else
273  {
274  len = SSL_read(ssl, buf, BUFFER_SIZE);
275  if (len == -1)
276  {
277  len = SSL_read(ssl, buf, BUFFER_SIZE); // retry
278  if (len == -1)
279  {
280  Utils::log("SecureSocket", "SSL_read return -1");
281  return -1;
282  }
283  }
284 
285  n += len;
286  if (dataCB(buf, len) != 0)
287  return -1;
288  }
289  } while (!_interrupt && len > 0);
290  _interrupt = true;
291  return n;
292 }
293 //-----------------------------------------------------------------------------
294 /*!
295  *
296  * \param dataCB
297  * \param max
298  */
299 void SecureSocket::disconnect()
300 {
301  SSL_shutdown(ssl);
302  SSL_free(ssl);
303  ssl = nullptr;
304 }
305 //-----------------------------------------------------------------------------
306 /*!
307  *
308  * \param host
309  */
310 DNSRequest::DNSRequest(string host)
311 {
312  char s[250] = {'\0'};
313  int maxlen = 249;
314  struct hostent* h = nullptr;
315  struct addrinfo* res = NULL;
316  int ret = getaddrinfo(host.c_str(), NULL, NULL, &res);
317 
318  if (ret)
319  {
320  Utils::log("Socket ", "invalid address");
321  hostname = "";
322  addr = "";
323  return;
324  }
325 
326  if (res->ai_family == AF_INET)
327  {
328  struct sockaddr_in sa;
329  memset(&sa, 0, sizeof(sa));
330  sa.sin_family = AF_INET;
331  sa.sin_addr = (((struct sockaddr_in*)res->ai_addr))->sin_addr;
332  inet_ntop(AF_INET, &(((struct sockaddr_in*)res->ai_addr))->sin_addr, s, maxlen);
333 
334 # ifdef _WINDOWS
335  h = gethostbyaddr((const char*)&sa.sin_addr, sizeof(sa.sin_addr), sa.sin_family);
336 # else
337  h = gethostbyaddr(&sa.sin_addr, sizeof(sa.sin_addr), sa.sin_family);
338 # endif
339  }
340  else if (res->ai_family == AF_INET6)
341  {
342  struct sockaddr_in6 sa;
343  memset(&sa, 0, sizeof(sa));
344  sa.sin6_family = AF_INET6;
345  sa.sin6_addr = (((struct sockaddr_in6*)res->ai_addr))->sin6_addr;
346  inet_ntop(AF_INET6, &(((struct sockaddr_in6*)res->ai_addr))->sin6_addr, s, maxlen);
347 # ifdef _WINDOWS
348  h = gethostbyaddr((const char*)&sa.sin6_addr, sizeof(sa.sin6_addr), sa.sin6_family);
349 # else
350  h = gethostbyaddr(&sa.sin6_addr, sizeof(sa.sin6_addr), sa.sin6_family);
351 # endif
352  }
353 
354  if (h != nullptr && h->h_length > 0)
355  hostname = string(h->h_name);
356  else
357  hostname = "";
358 
359  addr = string(s);
360 }
361 //-----------------------------------------------------------------------------
362 /*!
363  *
364  * \return
365  */
366 string DNSRequest::getAddr()
367 {
368  return addr;
369 }
370 //-----------------------------------------------------------------------------
371 /*!
372  *
373  * \return
374  */
375 string DNSRequest::getHostname()
376 {
377  return hostname;
378 }
379 //-----------------------------------------------------------------------------
380 /*!
381  *
382  * \param data
383  * \return
384  */
385 static string base64(const string data)
386 {
387  static constexpr char sEncodingTable[] = {'A',
388  'B',
389  'C',
390  'D',
391  'E',
392  'F',
393  'G',
394  'H',
395  'I',
396  'J',
397  'K',
398  'L',
399  'M',
400  'N',
401  'O',
402  'P',
403  'Q',
404  'R',
405  'S',
406  'T',
407  'U',
408  'V',
409  'W',
410  'X',
411  'Y',
412  'Z',
413  'a',
414  'b',
415  'c',
416  'd',
417  'e',
418  'f',
419  'g',
420  'h',
421  'i',
422  'j',
423  'k',
424  'l',
425  'm',
426  'n',
427  'o',
428  'p',
429  'q',
430  'r',
431  's',
432  't',
433  'u',
434  'v',
435  'w',
436  'x',
437  'y',
438  'z',
439  '0',
440  '1',
441  '2',
442  '3',
443  '4',
444  '5',
445  '6',
446  '7',
447  '8',
448  '9',
449  '+',
450  '/'};
451 
452  size_t in_len = data.size();
453  size_t out_len = 4 * ((in_len + 2) / 3);
454  string ret(out_len, '\0');
455  size_t i;
456  char* p = const_cast<char*>(ret.c_str());
457 
458  for (i = 0; i < in_len - 2; i += 3)
459  {
460  *p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
461  *p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int)(data[i + 1] & 0xF0) >> 4)];
462  *p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) | ((int)(data[i + 2] & 0xC0) >> 6)];
463  *p++ = sEncodingTable[data[i + 2] & 0x3F];
464  }
465  if (i < in_len)
466  {
467  *p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
468  if (i == (in_len - 1))
469  {
470  *p++ = sEncodingTable[((data[i] & 0x3) << 4)];
471  *p++ = '=';
472  }
473  else
474  {
475  *p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int)(data[i + 1] & 0xF0) >> 4)];
476  *p++ = sEncodingTable[((data[i + 1] & 0xF) << 2)];
477  }
478  *p++ = '=';
479  }
480 
481  return ret;
482 }
483 
484 //-----------------------------------------------------------------------------
485 /*!
486  *
487  * \param url
488  * \param host
489  * \param path
490  * \param useTLS
491  */
492 static void parseURL(string url,
493  string& host,
494  string& path,
495  bool& useTLS)
496 {
497  host = "";
498  useTLS = false;
499 
500  string dir = "/";
501  string tmp;
502  size_t offset = 0;
503 
504  if (url.find("https://") != string::npos)
505  {
506  useTLS = true;
507  offset = 8;
508  }
509  else if (url.find("http://") != string::npos)
510  {
511  offset = 7;
512  }
513 
514  size_t pos = url.find("/", offset);
515  if (pos != string::npos)
516  {
517  path = url.substr(pos, url.length() - pos);
518  host = url.substr(offset, pos - offset);
519  }
520  else
521  {
522  path = "/";
523  host = url.substr(offset);
524  }
525 }
526 //-----------------------------------------------------------------------------
527 /*!
528  *
529  * \param url
530  * \param user
531  * \param pwd
532  */
533 HttpUtils::GetRequest::GetRequest(string url,
534  string user,
535  string pwd)
536 {
537  string path;
538  bool isSecure;
539 
540  parseURL(url, host, path, isSecure);
541 
542  DNSRequest dns(host);
543  host = dns.getHostname();
544  addr = dns.getAddr();
545 
546  request = "GET " + path + " HTTP/1.1\r\n";
547 
548  if (!user.empty())
549  {
550  request = request +
551  "Authorization: Basic " +
552  base64(user + ":" + pwd) + "\r\n";
553  }
554  request = request + "Host: " + host + "\r\n\r\n";
555 
556  if (isSecure)
557  {
558  s = new SecureSocket();
559  port = 443;
560  }
561  else
562  {
563  s = new Socket();
564  port = 80;
565  }
566 }
567 //-----------------------------------------------------------------------------
568 /*!
569  *
570  * \param data
571  * \return
572  */
573 int HttpUtils::GetRequest::processHttpHeaders(std::vector<char>& data)
574 {
575  string h = string(data.begin(),
576  data.begin() +
577  (data.size() > 1000 ? 1000 : data.size()));
578  size_t contentPos = h.find("\r\n\r\n");
579  if (contentPos == string::npos)
580  {
581  Utils::log("HttpUtils", "Invalid http response");
582  return -1;
583  }
584  headers = string(data.begin(), data.begin() + contentPos);
585  contentPos += 4; // to skip "\r\n\r\n" first byte
586 
587  std::cout << headers << std::endl;
588  size_t pos = headers.find("HTTP");
589 
590  if (pos != string::npos)
591  {
592  string str = headers.substr(pos, headers.find("\r", pos) - pos);
593  size_t codeIdx;
594  for (codeIdx = 0; codeIdx < str.length() && !isdigit(str.at(codeIdx)); codeIdx++)
595  ;
596 
597  if (codeIdx == str.length())
598  {
599  Utils::log("HttpUtils", "Invalid http response");
600  return -1;
601  }
602 
603  size_t endCodeIdx = str.find(" ", codeIdx);
604 
605  stoi(str.substr(codeIdx, endCodeIdx - codeIdx));
606  status = str.substr(endCodeIdx);
607  status = Utils::trimString(status, " ");
608  }
609 
610  pos = headers.find("Content-Length:");
611  if (pos != string::npos)
612  {
613  string str = headers.substr(pos + 16,
614  headers.find("\r", pos + 16) - pos - 16);
615  contentLength = stoi(Utils::trimString(str, " "));
616  }
617 
618  pos = headers.find("Content-Type:");
619  if (pos != string::npos)
620  {
621  string str = headers.substr(pos + 13,
622  headers.find("\r", pos + 13) - pos - 13);
623  contentType = Utils::trimString(str, " ");
624  }
625  return (int)contentPos;
626 }
627 //-----------------------------------------------------------------------------
628 /*!
629  *
630  * \return
631  */
632 int HttpUtils::GetRequest::send()
633 {
634  if (s->connectTo(addr, port) < 0)
635  {
636  Utils::log("HttpUtils", "Could not connect");
637  return -1;
638  }
639 
640  s->sendData(request.c_str(), request.length() + 1);
641 
642  std::vector<char>* v = &firstBytes;
643  int ret;
644  ret = s->receive([v](char* buf, int size) -> int
645  {
646  v->reserve(v->size() + size);
647  copy(&buf[0], &buf[size], back_inserter(*v));
648  return 0; },
649  1000);
650 
651  contentOffset = processHttpHeaders(firstBytes);
652  s->disconnect();
653  if (ret == -1)
654  return 1;
655  return 0;
656 }
657 //-----------------------------------------------------------------------------
658 /*!
659  *
660  * \param contentCB
661  */
662 int HttpUtils::GetRequest::getContent(function<int(char* buf, int size)> contentCB)
663 {
664  if (s->connectTo(addr, port) < 0)
665  {
666  Utils::log("HttpUtils", "Could not connect");
667  return 1;
668  }
669 
670  s->sendData(request.c_str(), request.length() + 1);
671  std::vector<char>* v = &firstBytes;
672 
673  s->receive([v](char* buf, int size) -> int
674  {
675  v->reserve(v->size() + size);
676  copy(&buf[0], &buf[size], back_inserter(*v));
677  return 0; },
678  contentOffset);
679 
680  int ret = s->receive(contentCB, 0);
681  s->disconnect();
682  return ret;
683 }
684 //-----------------------------------------------------------------------------
685 /*!
686  *
687  * \return
688  */
689 std::vector<string> HttpUtils::GetRequest::getListing()
690 {
691  std::vector<char> content;
692  getContent([&content](char* buf, int size) -> int
693  {
694  content.reserve(content.size() + size);
695  copy(&buf[0], &buf[size], back_inserter(content));
696  return 0; });
697 
698  string c = string(content.data());
699  std::vector<string> listing;
700 
701  size_t pos = 0;
702  size_t end = 0;
703  while (1)
704  {
705  pos = c.find("<", pos);
706  end = c.find(">", pos) + 1;
707  if (pos == string::npos || end == string::npos)
708  break;
709 
710  string token = c.substr(pos + 1, end - pos - 2);
711  token = Utils::trimString(token, " ");
712 
713  if (string(token.begin(), token.begin() + 2) == "a ")
714  {
715  size_t href = token.find("href");
716  if (href != string::npos)
717  {
718  href = token.find("\"", href);
719  size_t hrefend = token.find("\"", href + 1);
720  if (token.find("?") == string::npos)
721  {
722  token = token.substr(href + 1, hrefend - href - 1);
723  if (token == "../" || token == ".")
724  {
725  pos = end;
726  continue;
727  }
728  listing.push_back(token);
729  }
730  }
731  }
732  pos = end;
733  }
734  return listing;
735 }
736 //-----------------------------------------------------------------------------
737 /*!
738  *
739  * \param url
740  * \param processFile
741  * \param writeChunk
742  * \param processDir
743  * \param user
744  * \param pwd
745  * \param base
746  */
747 int HttpUtils::download(string url,
748  function<int(string path, string file, size_t size)> processFile,
749  function<int(char* data, int size)> writeChunk,
750  function<int(string)> processDir,
751  string user,
752  string pwd,
753  string base)
754 {
755  HttpUtils::GetRequest req = HttpUtils::GetRequest(url, user, pwd);
756  if (req.send() < 0)
757  return SERVER_NOT_REACHABLE;
758  base = Utils::unifySlashes(base);
759 
760  if (req.contentType == "text/html")
761  {
762  if (url.back() != '/')
763  url = url + "/";
764 
765  if (!processDir(base))
766  return CANT_CREATE_DIR;
767 
768  std::vector<string> listing = req.getListing();
769 
770  for (string str : listing)
771  {
772  if (str.at(0) != '/')
773  return download(url + str,
774  processFile,
775  writeChunk,
776  processDir,
777  user,
778  pwd,
779  base + str);
780  }
781  }
782  else
783  {
784  string file = url.substr(url.rfind("/") + 1);
785 
786  int possibleSplit = (int)base.rfind(file);
787  if (possibleSplit != string::npos && base.size() - possibleSplit - 1 == file.size())
788  base = base.substr(0, possibleSplit);
789 
790  base = Utils::unifySlashes(base);
791 
792  if (processFile(base, file, req.contentLength) != 0)
793  return CANT_CREATE_FILE;
794 
795  if (req.getContent(writeChunk) == -1)
796  return CONNECTION_CLOSED;
797 
798  return 0;
799  }
800  return 1;
801 }
802 //-----------------------------------------------------------------------------
803 /*!
804  *
805  * \param url
806  * \param dst
807  * \param user
808  * \param pwd
809  * \param progress
810  */
811 int HttpUtils::download(string url,
812  string dst,
813  string user,
814  string pwd,
815  function<int(size_t curr, size_t filesize)> progress)
816 {
817  std::ofstream fs;
818  size_t totalBytes = 0;
819  size_t writtenByte = 0;
820 
821  bool dstIsDir = true;
822 
823  if (!Utils::fileExists(dst))
824  {
826  dstIsDir = true;
827  }
828 
829  return download(
830  url,
831  [&fs, &totalBytes, &dst, &dstIsDir](string path,
832  string file,
833  size_t size) -> int
834  {
835  try
836  {
837  if (dstIsDir)
838  fs.open(path + file, std::ios::out | std::ios::binary);
839  else
840  fs.open(dst, std::ios::out | std::ios::binary);
841  }
842  catch (std::exception& e)
843  {
844  std::cerr << e.what() << '\n';
845  return 1;
846  }
847  totalBytes = size;
848  return 0;
849  },
850  [&fs, progress, &writtenByte, &totalBytes](char* data, int size) -> int
851  {
852  if (size > 0)
853  {
854  try
855  {
856  fs.write(data, size);
857  if (progress && progress(writtenByte += size, totalBytes) != 0)
858  {
859  fs.close();
860  return 1;
861  }
862  }
863  catch (const std::exception& e)
864  {
865  std::cerr << e.what() << '\n';
866  return 1;
867  }
868  return 0;
869  }
870  else
871  {
872  if (progress)
873  progress(totalBytes, totalBytes);
874  fs.close();
875  return 0;
876  }
877  },
878  [&dstIsDir](string dir) -> int
879  {
880  if (!dstIsDir)
881  return 1;
882  return Utils::makeDir(dir) == 0;
883  },
884  user,
885  pwd,
886  dst);
887 }
888 //-----------------------------------------------------------------------------
889 /*!
890  * HttpUtils::download return 0 on success otherwise an error code if the
891  * download of the file at url to the destination at dst was successful.
892  * \param url A uniform resource locator file address of the file to download
893  * \param dst A uniform resource locator folder address as destination folder
894  * \param progress A function object that is called during the progress
895  */
896 int HttpUtils::download(string url,
897  string dst,
898  function<int(size_t curr, size_t filesize)> progress)
899 {
900  return download(url, dst, "", "", progress);
901 }
902 //-----------------------------------------------------------------------------
903 
904 int HttpUtils::length(string url, string user, string pwd)
905 {
906  HttpUtils::GetRequest req = HttpUtils::GetRequest(url, user, pwd);
907  if (req.send() < 0)
908  return -1;
909 
910  return (int)req.contentLength;
911 }
912 //-----------------------------------------------------------------------------
913 
914 #endif // SL_BUILD_WITH_OPENSSL
The SLScene class represents the top level instance holding the scene structure.
Definition: SLScene.h:47
void close(SLIOStream *stream)
Closes and deletes a stream.
string unifySlashes(const string &inputDir, bool withTrailingSlash)
Returns the inputDir string with unified forward slashes, e.g.: "dirA/dirB/".
Definition: Utils.cpp:368
bool fileExists(const string &pathfilename)
Returns true if a file exists.
Definition: Utils.cpp:897
bool makeDir(const string &path)
Creates a directory with given path.
Definition: Utils.cpp:810
string trimString(const string &s, const string &drop)
Trims a string at both end.
Definition: Utils.cpp:128
bool makeDirRecurse(std::string path)
Definition: Utils.cpp:826
void log(const char *tag, const char *format,...)
logs a formatted string platform independently
Definition: Utils.cpp:1103