FreeWRL/FreeX3D  3.0.0
io_http.c
1 /*
2 
3  FreeWRL support library.
4  IO with HTTP protocol.
5 
6 */
7 
8 /****************************************************************************
9  This file is part of the FreeWRL/FreeX3D Distribution.
10 
11  Copyright 2009 CRC Canada. (http://www.crc.gc.ca)
12 
13  FreeWRL/FreeX3D is free software: you can redistribute it and/or modify
14  it under the terms of the GNU Lesser Public License as published by
15  the Free Software Foundation, either version 3 of the License, or
16  (at your option) any later version.
17 
18  FreeWRL/FreeX3D is distributed in the hope that it will be useful,
19  but WITHOUT ANY WARRANTY; without even the implied warranty of
20  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  GNU General Public License for more details.
22 
23  You should have received a copy of the GNU General Public License
24  along with FreeWRL/FreeX3D. If not, see <http://www.gnu.org/licenses/>.
25 ****************************************************************************/
26 
27 
28 
29 #include <config.h>
30 #include <system.h>
31 //#include <resources.h>
32 //#include <display.h>
33 #include <internal.h>
34 
35 #include <libFreeWRL.h>
36 //#include <list.h>
37 //
38 //#include <io_files.h>
39 //#include <io_http.h>
40 //#include <threads.h>
41 //#include "scenegraph/Vector.h"
42 //#include "main/ProdCon.h"
43 
44 
45 #ifdef _MSC_VER
46 #define strncasecmp _strnicmp
47 #endif
48 
49 bool is_url(const char *url)
50 {
51 #define MAX_PROTOS 4
52  static const char *protos[MAX_PROTOS] = { "ftp", "http", "https", "urn" };
53 
54  int i;
55  char *pat;
56  unsigned long delta = 0;
57 
58  pat = strstr(url, "://");
59  if (!pat) {
60  return FALSE;
61  }
62 
63  delta = (long)pat - (long)url;
64  if (delta > 5) {
65  return FALSE;
66  }
67 
68  for (i = 0; i < MAX_PROTOS ; i++) {
69  if (strncasecmp(protos[i], url, strlen(protos[i])) == 0) {
70  return TRUE;
71  }
72  }
73  return FALSE;
74 }
75 
76 // we need to substitute %20 for ' ', as well replace other unsafe chars
77 // ftp://ftp.gnu.org/old-gnu/Manuals/wget-1.8.1/html_chapter/wget_2.html#SEC3
78 // http://www.rfc-base.org/txt/rfc-1738.txt
79 
80 static char *RFC1738_unsafe = " <>{}|\\^~[]`#%";
81 static int is_unsafe(char c){
82  int j, unsafe = 0;
83  if(c < 32 || c > 126)
84  unsafe = 1;
85  else{
86  for(j=0;j<strlen(RFC1738_unsafe);j++){
87  if( c == RFC1738_unsafe[j]){
88  unsafe = 1;
89  break; //from j loop
90  }
91  }
92  }
93  return unsafe;
94 }
95 static int count_unsafe(char *str){
96  int i, count, len;
97  len = (int)strlen(str);
98  count = 0;
99  for(i=0;i<len;i++)
100  if(is_unsafe(str[i]))
101  count++;
102  return count;
103 }
104 static char *hexdigits = "0123456789ABCDEF";
105 static char *replace_unsafe(char *str){
106  int i,j,n, len;
107  char *s;
108  len = (int)strlen(str);
109  n = count_unsafe(str);
110  if(n == 0) return strdup(str);
111  //printf("unsafe string=[%s]\n",str);
112  s = malloc(n*3 + len - n +1);
113  j = 0;
114  for(i=0;i<len;i++){
115  if(is_unsafe(str[i])){
116  s[j] = '%';
117  s[j+1] = hexdigits[str[i] >> 4];
118  s[j+2] = hexdigits[str[i] & 0x0f];
119  //if(str[i] != ' ')
120  //printf("unsafe char=%d %c\n",(int)str[i],str[i]);
121  j += 3;
122  }else{
123  s[j] = str[i];
124  j++;
125  }
126  }
127  s[j] = 0;
128  return s;
129 }
130 
131 /*
132  New FreeWRL internal API for HTTP[S] downloads
133 
134  - enqueue an URL for download
135  - test if the download is over
136  - open the downloaded file
137  - remove the downloaded file when neede
138  - {same as temp file}
139 
140  * structure:
141  - download structure with URL, temp filename, status
142 */
143 
144 #if defined(HAVE_LIBCURL)
145 
146 /*
147  To be effectively used, libcurl has to be enabled
148  during configure, then it has to be enabled on
149  the command line (see main program: options.c).
150 
151  Instead of downloading files at once with wget,
152  I propose to try libCurl which provides some nice
153  mechanism to reuse open connections and to multi-
154  thread downloads.
155 
156  At the moment I've only hacked a basic mise en oeuvre
157  of this library.
158 */
159 
160 #include <curl/curl.h>
161 
162 int with_libcurl = TRUE;
163 
164 static int curl_initialized = 0;
165 
166 /*
167  libCurl needs to be initialized once.
168  We've choosen the very simple method of curl_easy_init
169  but, in the future we'll use the full features.
170 */
171 void init_curl()
172 {
173  CURLcode c;
174 
175  if ( (c=curl_global_init(CURL_GLOBAL_ALL)) != 0 ) {
176  ERROR_MSG("Curl init failed: %d\n", (int)c);
177  curl_initialized = 0;
178  exit(1);
179  } else {
180  curl_initialized = 1;
181  }
182 }
183 
184 /* return the temp file where we got the contents of the URL requested */
185 /* old char* download_url_curl(const char *url, const char *tmp) */
186 char* download_url_curl_OLD(char *parsed_request, char *temp_dir)
187 {
188  CURL *curl_h = NULL;
189  CURLcode success;
190  char *temp;
191  FILE *file;
192 
193  if (temp_dir) {
194  temp = STRDUP(temp_dir);
195  } else {
196  temp = tempnam(gglobal()->Mainloop.tmpFileLocation, "freewrl_download_curl_XXXXXXXX");
197  if (!temp) {
198  PERROR_MSG("download_url_curl: can't create temporary name.\n");
199  return NULL;
200  }
201  }
202 
203  file = fopen(temp, "w");
204  if (!file) {
205  FREE(temp);
206  ERROR_MSG("Cannot create temp file (fopen)\n");
207  return NULL;
208  }
209 
210  if (curl_initialized != 0) {
211  init_curl();
212  }
213 
214  curl_h = curl_easy_init();
215 
216  /*
217  Ask libCurl to download one url at once,
218  and to write it to the specified file.
219  */
220  curl_easy_setopt(curl_h, CURLOPT_URL, parsed_request);
221 
222  curl_easy_setopt(curl_h, CURLOPT_WRITEDATA, file);
223 
224  success = curl_easy_perform(curl_h);
225 
226  if (success != CURLE_OK) {
227  ERROR_MSG("Download failed for url %s\n", res->parsed_request);
228  fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(success));
229  fclose(file);
230  unlink(temp);
231  FREE(temp);
232  return NULL;
233  } else {
234 #ifdef TRACE_DOWNLOADS
235  TRACE_MSG("Download sucessfull [curl] for url %s\n", parsed_request);
236 #endif
237  fclose(file);
238  return temp;
239  }
240 }
241 
242 char* download_url_curl(char *parsed_request, char *temp_dir)
243 {
244  static CURL *curl_h = NULL;
245  CURLcode success;
246  char *temp, *safe_url;
247  FILE *file;
248 
249  if (temp_dir) {
250  temp = STRDUP(temp_dir);
251  } else {
252  temp = tempnam(gglobal()->Mainloop.tmpFileLocation, "freewrl_download_curl_XXXXXXXX");
253  if (!temp) {
254  PERROR_MSG("download_url_curl: can't create temporary name.\n");
255  return NULL;
256  }
257  }
258 
259  file = fopen(temp, "w");
260  if (!file) {
261  FREE(temp);
262  ERROR_MSG("Cannot create temp file (fopen)\n");
263  return NULL;
264  }
265 
266  if (curl_initialized == 0) {
267  init_curl();
268  curl_h = curl_easy_init();
269  }
270 
271  // curl_h = curl_easy_init();
272  safe_url = replace_unsafe(parsed_request);
273  /*
274  Ask libCurl to download one url at once,
275  and to write it to the specified file.
276  */
277  curl_easy_setopt(curl_h, CURLOPT_URL, safe_url);
278 
279  curl_easy_setopt(curl_h, CURLOPT_WRITEDATA, file);
280 
281  success = curl_easy_perform(curl_h);
282 
283  if(success == CURLE_OK){
284  // easy_perform returns CURLE_OK even if it couldn't download the file
285  // https://curl.haxx.se/docs/faq.html#Why_do_I_get_downloaded_data_eve
286  //
287  // https://curl.haxx.se/libcurl/c/curl_easy_getinfo.html
288  // CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ... ); where ... is int, double ... as per the request
289  long response_code;
290  curl_easy_getinfo(curl_h, CURLINFO_RESPONSE_CODE, &response_code);
291  if(response_code == 200){
292  fclose(file);
293  free(safe_url);
294  return temp;
295  }
296  }
297  //else if (success != CURLE_OK) or response == 404
298  //if (success != CURLE_OK) fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(success));
299  fclose(file);
300  unlink(temp);
301  free(safe_url);
302  free(temp);
303  return NULL;
304 }
305 
306 
307 #endif /* HAVE_LIBCURL */
308 
309 
310 #if defined(HAVE_WININET)
311 
312 /*
313 ms windows native methods WinInet - for clients (like freewrl)
314 http://msdn.microsoft.com/en-us/library/aa385331(VS.85).aspx C - what I used below
315 http://msdn.microsoft.com/en-us/library/sb35xf67.aspx sample browser in C++
316 */
317 #include <WinInet.h>
318 
319 static HINTERNET hWinInet = NULL; //static for an application, although multiple inits OK
320 HINTERNET winInetInit()
321 {
322 //winInet_h = InternetOpen(
323 // __in LPCTSTR lpszAgent,
324 // __in DWORD dwAccessType,
325 // __in LPCTSTR lpszProxyName,
326 // __in LPCTSTR lpszProxyBypass,
327 // __in DWORD dwFlags
328 //);
329 
330 
331  if(hWinInet == NULL)
332  hWinInet = InternetOpen("freewrl",INTERNET_OPEN_TYPE_DIRECT,NULL,NULL,0); //INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY/*/INTERNET_OPEN_TYPE_PRECONFIG*/,NULL,NULL,0);//INTERNET_FLAG_ASYNC - for callback);
333  return hWinInet;
334 }
335 void closeWinInetHandle()
336 {
337  InternetCloseHandle(hWinInet);
338 }
339 
340 //#define ERROR_MSG ConsoleMessage
341 //#define PERROR_MSG ConsoleMessage
342  /* return the temp file where we got the contents of the URL requested */
343 /* char* download_url_WinInet(const char *url, const char *tmp) */
344 char* download_url_WinInet(char *parsed_request, char *temp_dir)
345 {
346  char *temp;
347  temp = NULL;
348  if(!hWinInet)
349  {
350  hWinInet = winInetInit();
351  }
352  if(!hWinInet)
353  return temp;
354  else
355  {
356  DWORD dataLength, len;
357  HINTERNET hOpenUrl;
358  DWORD buflen;
359  //DWORD dwError;
360  DWORD InfoLevel, Index;
361  char buffer[1024];
362 
363  if(0){
364  static FILE* fp = NULL;
365  if (!fp) fp = fopen("http_log.txt", "w+");
366  fprintf(fp,"[%s]\n", parsed_request);
367  }
368  hOpenUrl=InternetOpenUrl(hWinInet,parsed_request,NULL,0,0,0); //INTERNET_FLAG_NO_UI|INTERNET_FLAG_RELOAD/*|INTERNET_FLAG_IGNORE_CERT_CN_INVALID install the cert instead*/,0);
369  buflen = 1023;
370  //if(InternetGetLastResponseInfo(&dwError,buffer,&buflen)){
371  // printf("error=%s\n",buffer);
372  //}else{
373  // printf("no error\n");
374  //}
375 // BOOL HttpQueryInfo(
376 // _In_ HINTERNET hRequest,
377 // _In_ DWORD dwInfoLevel,
378 // _Inout_ LPVOID lpvBuffer,
379 // _Inout_ LPDWORD lpdwBufferLength,
380 // _Inout_ LPDWORD lpdwIndex
381 //);
382  //http://msdn.microsoft.com/en-us/library/aa384238(v=vs.85).aspx
383  buflen = 1023;
384  Index = 0;
385  InfoLevel = HTTP_QUERY_RAW_HEADERS_CRLF;
386  if(HttpQueryInfo(hOpenUrl,InfoLevel,buffer,&buflen,NULL)){
387  //printf("query buffer=%s\n",buffer);
388  if(strstr(buffer,"404 Not Found")){
389  //HTTP/1.1 404 Not Found
390  ERROR_MSG("Download failed1 for url %s\n", parsed_request);
391  return temp;
392  }
393  //else 200 OK
394  }
395  //else{
396  // printf("no query buffer\n");
397  //}
398 
399 
400  //DWORD err = GetLastError();
401  if (!(hOpenUrl))
402  {
403  ERROR_MSG("Download failed2 for url %s\n", parsed_request);
404  return temp;
405  }
406  else
407  {
408  FILE *file;
409 
410  if (temp_dir) {
411  temp = STRDUP(temp_dir);
412  } else {
413  //temp = _tempnam(gglobal()->Mainloop.tmpFileLocation, "freewrl_download_XXXXXXXX");
414  char *tmp1;
415  tmp1 = _tempnam(NULL, "freewrl_download_XXXXXXXX");
416  if (!tmp1) {
417  PERROR_MSG("download_url: can't create temporary name.\n");
418  return tmp1;
419  }
420  temp = STRDUP(tmp1); //these 2 lines help DEBUG_MALLOC because later we use FREE_IF_NZ on actual_file
421  free(tmp1);
422  }
423 
424  file = fopen(temp, "wb");
425  if (!file) {
426  FREE_IF_NZ(temp);
427  ERROR_MSG("Cannot create temp file (fopen)\n");
428  return temp;
429  }
430 
431  dataLength=0;
432  len=0;
433 
434  while((InternetQueryDataAvailable(hOpenUrl,&dataLength,0,0))&&(dataLength>0))
435  {
436  void *block = MALLOC(void *, dataLength);
437  if ((InternetReadFile(hOpenUrl,(void*)block,dataLength,&len))&&(len>0))
438  {
439  fwrite(block,dataLength,1,file);
440  }
441  FREE(block);
442  }
443  InternetCloseHandle(hOpenUrl);
444  hOpenUrl=NULL;
445  fclose(file);
446  return temp;
447  }
448  }
449  return temp;
450 }
451 
452 #endif
453 
454 /* lets try this... this should be in the config files */
455 #ifdef WGET
456 #define HAVE_WGET
457 #endif
458 
459 #ifdef HAVE_WGET
460 
461 
467 char* download_url_wget(char *parsed_request, char *temp_dir)
468 {
469  char *temp, *wgetcmd, *safe;
470  int ret;
471 
472 // OLD_IPHONE_AQUA #if defined (TARGET_AQUA)
473 // OLD_IPHONE_AQUA #define WGET_OPTIONS ""
474 // OLD_IPHONE_AQUA #define WGET_OUTPUT_DIRECT "-o"
475 // OLD_IPHONE_AQUA #else
476 
477  // move this to another place (where we check wget options)
478  #define WGET_OPTIONS "--no-check-certificate"
479  #define WGET_OUTPUT_DIRECT "-O"
480 
481 // OLD_IPHONE_AQUA #endif
482 
483  // create temp filename
484  if (temp_dir) {
485  temp = STRDUP(temp_dir);
486  } else {
487  temp = tempnam(gglobal()->Mainloop.tmpFileLocation, "freewrl_download_wget_XXXXXXXX");
488  if (!temp) {
489  PERROR_MSG("download_url_wget: can't create temporary name.\n");
490  return NULL;
491  }
492  }
493 
494  // create wget command line
495  safe = replace_unsafe(parsed_request);
496  wgetcmd = MALLOC(void *, strlen(WGET) +
497  strlen(WGET_OPTIONS) +
498  strlen(safe) +
499  strlen(temp) + 6 +1+1);
500 
501 // OLD_IPHONE_AQUA #if defined (TARGET_AQUA)
502 // OLD_IPHONE_AQUA /* AQUA - we DO NOT have the options, but we can not have the space - it screws the freewrlSystem up */
503 // OLD_IPHONE_AQUA sprintf(wgetcmd, "%s %s %s %s", WGET, safe, WGET_OUTPUT_DIRECT, temp);
504 // OLD_IPHONE_AQUA #else
505 
506  sprintf(wgetcmd, "%s %s %s %s %s", WGET, WGET_OPTIONS, safe, WGET_OUTPUT_DIRECT, temp);
507 
508 // OLD_IPHONE_AQUA #endif
509 
510  FREE_IF_NZ(safe);
511  /* printf ("wgetcmd is %s\n",wgetcmd); */
512 
513  // call wget
514  ret = freewrlSystem(wgetcmd);
515  if (ret < 0) {
516  ERROR_MSG("Error in wget (%s)\n", wgetcmd);
517  FREE(temp);
518  FREE(wgetcmd);
519  return NULL;
520  }
521  FREE(wgetcmd);
522  return temp;
523 }
524 #endif /* HAVE_WGET */
525 
526 
527 /* get the file from the network, UNLESS the front end gets files. Old way - the freewrl
528  library tries to get files; new way, the front end gets the files from the network. This
529  allows, for instance, the browser plugin to use proxy servers and cache for getting files.
530 
531  So, if the FRONTEND_GETS_FILES is set, we simply pass the http:// filename along....
532 */
533 void close_internetHandles()
534 {
535 #ifdef HAVE_WININET
536  closeWinInetHandle();
537 #endif
538 }
539 void download_url(void *res)
540 {
541  char *temp_dir, *parsed_request, *actual_file;
542  parsed_request = fwl_resitem_getURL(res);
543  temp_dir = fwl_resitem_getTempDir(res);
544 
545 
546 #if defined(HAVE_LIBCURL)
547  if (with_libcurl) {
548  actual_file = download_url_curl(parsed_request,temp_dir);
549  } else {
550  actual_file = download_url_wget(parsed_request,temp_dir);
551  }
552 
553 #elif defined (HAVE_WGET)
554  actual_file = download_url_wget(parsed_request,temp_dir);
555 
556 
557 #elif defined (HAVE_WININET)
558  actual_file = download_url_WinInet(parsed_request,temp_dir);
559 #endif
560 
561  /* status indication now */
562  if (actual_file) {
563  /* download succeeded */
564  //res->status = ress_downloaded;
565  fwl_resitem_setStatus(res,ress_downloaded);
566  fwl_resitem_setActualFile(res,actual_file);
567  //moved inside setActualFile:
568  //if(strcmp(actual_file,parsed_request)){
569  // //it's a temp file
570  // s_list_t *item;
571  // item = ml_new(res->actual_file);
572  // if (!res->cached_files)
573  // res->cached_files = (void *)item;
574  // else
575  // res->cached_files = ml_append(res->cached_files,item);
576  //}
577  } else {
578  /* download failed */
579  //res->status = ress_failed;
580  fwl_resitem_setStatus(res,ress_failed);
581  ERROR_MSG("resource_fetch: download failed for url: %s\n", parsed_request);
582  }
583 }