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