1 /*****************************************************************************
2  *
3  * This example source code introduces a c library buffered I/O interface to
4  * URL reads it supports fopen(), fread(), fgets(), feof(), fclose(),
5  * rewind(). Supported functions have identical prototypes to their normal c
6  * lib namesakes and are preceaded by url_ .
7  *
8  * Using this code you can replace your program's fopen() with url_fopen()
9  * and fread() with url_fread() and it become possible to read remote streams
10  * instead of (only) local files. Local files (ie those that can be directly
11  * fopened) will drop back to using the underlying clib implementations
12  *
13  * See the main() function at the bottom that shows an app that retrives from a
14  * specified url using fgets() and fread() and saves as two output files.
15  *
16  * Copyright (c) 2003 Simtec Electronics
17  *
18  * Re-implemented by Vincent Sanders <vince@kyllikki.org> with extensive
19  * reference to original curl example code
20  *
21  * Redistribution and use in source and binary forms, with or without
22  * modification, are permitted provided that the following conditions
23  * are met:
24  * 1. Redistributions of source code must retain the above copyright
25  *    notice, this list of conditions and the following disclaimer.
26  * 2. Redistributions in binary form must reproduce the above copyright
27  *    notice, this list of conditions and the following disclaimer in the
28  *    documentation and/or other materials provided with the distribution.
29  * 3. The name of the author may not be used to endorse or promote products
30  *    derived from this software without specific prior written permission.
31  *
32  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
33  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
34  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
35  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
36  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
38  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
39  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
40  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
41  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42  *
43  * This example requires libcurl 7.9.7 or later.
44  */
45 
46 #include <stdio.h>
47 #include <string.h>
48 #ifndef WIN32
49 #  include <sys/time.h>
50 #endif
51 #include <stdlib.h>
52 #include <errno.h>
53 
54 #include <curl/curl.h>
55 
56 enum fcurl_type_e {
57   CFTYPE_NONE=0,
58   CFTYPE_FILE=1,
59   CFTYPE_CURL=2
60 };
61 
62 struct fcurl_data
63 {
64   enum fcurl_type_e type;     /* type of handle */
65   union {
66     CURL *curl;
67     FILE *file;
68   } handle;                   /* handle */
69 
70   char *buffer;               /* buffer to store cached data*/
71   size_t buffer_len;          /* currently allocated buffers length */
72   size_t buffer_pos;          /* end of data in buffer*/
73   int still_running;          /* Is background url fetch still in progress */
74 };
75 
76 typedef struct fcurl_data URL_FILE;
77 
78 /* exported functions */
79 URL_FILE *url_fopen(const char *url,const char *operation);
80 int url_fclose(URL_FILE *file);
81 int url_feof(URL_FILE *file);
82 size_t url_fread(void *ptr, size_t size, size_t nmemb, URL_FILE *file);
83 char * url_fgets(char *ptr, size_t size, URL_FILE *file);
84 void url_rewind(URL_FILE *file);
85 
86 /* we use a global one for convenience */
87 CURLM *multi_handle;
88 
89 /* curl calls this routine to get more data */
write_callback(char * buffer,size_t size,size_t nitems,void * userp)90 static size_t write_callback(char *buffer,
91                              size_t size,
92                              size_t nitems,
93                              void *userp)
94 {
95   char *newbuff;
96   size_t rembuff;
97 
98   URL_FILE *url = (URL_FILE *)userp;
99   size *= nitems;
100 
101   rembuff=url->buffer_len - url->buffer_pos; /* remaining space in buffer */
102 
103   if(size > rembuff) {
104     /* not enough space in buffer */
105     newbuff=realloc(url->buffer,url->buffer_len + (size - rembuff));
106     if(newbuff==NULL) {
107       fprintf(stderr,"callback buffer grow failed\n");
108       size=rembuff;
109     }
110     else {
111       /* realloc succeeded increase buffer size*/
112       url->buffer_len+=size - rembuff;
113       url->buffer=newbuff;
114     }
115   }
116 
117   memcpy(&url->buffer[url->buffer_pos], buffer, size);
118   url->buffer_pos += size;
119 
120   return size;
121 }
122 
123 /* use to attempt to fill the read buffer up to requested number of bytes */
fill_buffer(URL_FILE * file,size_t want)124 static int fill_buffer(URL_FILE *file, size_t want)
125 {
126   fd_set fdread;
127   fd_set fdwrite;
128   fd_set fdexcep;
129   struct timeval timeout;
130   int rc;
131   CURLMcode mc; /* curl_multi_fdset() return code */
132 
133   /* only attempt to fill buffer if transactions still running and buffer
134    * doesn't exceed required size already
135    */
136   if((!file->still_running) || (file->buffer_pos > want))
137     return 0;
138 
139   /* attempt to fill buffer */
140   do {
141     int maxfd = -1;
142     long curl_timeo = -1;
143 
144     FD_ZERO(&fdread);
145     FD_ZERO(&fdwrite);
146     FD_ZERO(&fdexcep);
147 
148     /* set a suitable timeout to fail on */
149     timeout.tv_sec = 60; /* 1 minute */
150     timeout.tv_usec = 0;
151 
152     curl_multi_timeout(multi_handle, &curl_timeo);
153     if(curl_timeo >= 0) {
154       timeout.tv_sec = curl_timeo / 1000;
155       if(timeout.tv_sec > 1)
156         timeout.tv_sec = 1;
157       else
158         timeout.tv_usec = (curl_timeo % 1000) * 1000;
159     }
160 
161     /* get file descriptors from the transfers */
162     mc = curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
163 
164     if(mc != CURLM_OK)
165     {
166       fprintf(stderr, "curl_multi_fdset() failed, code %d.\n", mc);
167       break;
168     }
169 
170     /* On success the value of maxfd is guaranteed to be >= -1. We call
171        select(maxfd + 1, ...); specially in case of (maxfd == -1) there are
172        no fds ready yet so we call select(0, ...) --or Sleep() on Windows--
173        to sleep 100ms, which is the minimum suggested value in the
174        curl_multi_fdset() doc. */
175 
176     if(maxfd == -1) {
177 #ifdef _WIN32
178       Sleep(100);
179       rc = 0;
180 #else
181       /* Portable sleep for platforms other than Windows. */
182       struct timeval wait = { 0, 100 * 1000 }; /* 100ms */
183       rc = select(0, NULL, NULL, NULL, &wait);
184 #endif
185     }
186     else {
187       /* Note that on some platforms 'timeout' may be modified by select().
188          If you need access to the original value save a copy beforehand. */
189       rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);
190     }
191 
192     switch(rc) {
193     case -1:
194       /* select error */
195       break;
196 
197     case 0:
198     default:
199       /* timeout or readable/writable sockets */
200       curl_multi_perform(multi_handle, &file->still_running);
201       break;
202     }
203   } while(file->still_running && (file->buffer_pos < want));
204   return 1;
205 }
206 
207 /* use to remove want bytes from the front of a files buffer */
use_buffer(URL_FILE * file,size_t want)208 static int use_buffer(URL_FILE *file, size_t want)
209 {
210   /* sort out buffer */
211   if((file->buffer_pos - want) <=0) {
212     /* ditch buffer - write will recreate */
213     free(file->buffer);
214     file->buffer=NULL;
215     file->buffer_pos=0;
216     file->buffer_len=0;
217   }
218   else {
219     /* move rest down make it available for later */
220     memmove(file->buffer,
221             &file->buffer[want],
222             (file->buffer_pos - want));
223 
224     file->buffer_pos -= want;
225   }
226   return 0;
227 }
228 
url_fopen(const char * url,const char * operation)229 URL_FILE *url_fopen(const char *url,const char *operation)
230 {
231   /* this code could check for URLs or types in the 'url' and
232      basically use the real fopen() for standard files */
233 
234   URL_FILE *file;
235   (void)operation;
236 
237   file = malloc(sizeof(URL_FILE));
238   if(!file)
239     return NULL;
240 
241   memset(file, 0, sizeof(URL_FILE));
242 
243   if((file->handle.file=fopen(url,operation)))
244     file->type = CFTYPE_FILE; /* marked as URL */
245 
246   else {
247     file->type = CFTYPE_CURL; /* marked as URL */
248     file->handle.curl = curl_easy_init();
249 
250     curl_easy_setopt(file->handle.curl, CURLOPT_URL, url);
251     curl_easy_setopt(file->handle.curl, CURLOPT_WRITEDATA, file);
252     curl_easy_setopt(file->handle.curl, CURLOPT_VERBOSE, 0L);
253     curl_easy_setopt(file->handle.curl, CURLOPT_WRITEFUNCTION, write_callback);
254 
255     if(!multi_handle)
256       multi_handle = curl_multi_init();
257 
258     curl_multi_add_handle(multi_handle, file->handle.curl);
259 
260     /* lets start the fetch */
261     curl_multi_perform(multi_handle, &file->still_running);
262 
263     if((file->buffer_pos == 0) && (!file->still_running)) {
264       /* if still_running is 0 now, we should return NULL */
265 
266       /* make sure the easy handle is not in the multi handle anymore */
267       curl_multi_remove_handle(multi_handle, file->handle.curl);
268 
269       /* cleanup */
270       curl_easy_cleanup(file->handle.curl);
271 
272       free(file);
273 
274       file = NULL;
275     }
276   }
277   return file;
278 }
279 
url_fclose(URL_FILE * file)280 int url_fclose(URL_FILE *file)
281 {
282   int ret=0;/* default is good return */
283 
284   switch(file->type) {
285   case CFTYPE_FILE:
286     ret=fclose(file->handle.file); /* passthrough */
287     break;
288 
289   case CFTYPE_CURL:
290     /* make sure the easy handle is not in the multi handle anymore */
291     curl_multi_remove_handle(multi_handle, file->handle.curl);
292 
293     /* cleanup */
294     curl_easy_cleanup(file->handle.curl);
295     break;
296 
297   default: /* unknown or supported type - oh dear */
298     ret=EOF;
299     errno=EBADF;
300     break;
301   }
302 
303   free(file->buffer);/* free any allocated buffer space */
304   free(file);
305 
306   return ret;
307 }
308 
url_feof(URL_FILE * file)309 int url_feof(URL_FILE *file)
310 {
311   int ret=0;
312 
313   switch(file->type) {
314   case CFTYPE_FILE:
315     ret=feof(file->handle.file);
316     break;
317 
318   case CFTYPE_CURL:
319     if((file->buffer_pos == 0) && (!file->still_running))
320       ret = 1;
321     break;
322 
323   default: /* unknown or supported type - oh dear */
324     ret=-1;
325     errno=EBADF;
326     break;
327   }
328   return ret;
329 }
330 
url_fread(void * ptr,size_t size,size_t nmemb,URL_FILE * file)331 size_t url_fread(void *ptr, size_t size, size_t nmemb, URL_FILE *file)
332 {
333   size_t want;
334 
335   switch(file->type) {
336   case CFTYPE_FILE:
337     want=fread(ptr,size,nmemb,file->handle.file);
338     break;
339 
340   case CFTYPE_CURL:
341     want = nmemb * size;
342 
343     fill_buffer(file,want);
344 
345     /* check if theres data in the buffer - if not fill_buffer()
346      * either errored or EOF */
347     if(!file->buffer_pos)
348       return 0;
349 
350     /* ensure only available data is considered */
351     if(file->buffer_pos < want)
352       want = file->buffer_pos;
353 
354     /* xfer data to caller */
355     memcpy(ptr, file->buffer, want);
356 
357     use_buffer(file,want);
358 
359     want = want / size;     /* number of items */
360     break;
361 
362   default: /* unknown or supported type - oh dear */
363     want=0;
364     errno=EBADF;
365     break;
366 
367   }
368   return want;
369 }
370 
url_fgets(char * ptr,size_t size,URL_FILE * file)371 char *url_fgets(char *ptr, size_t size, URL_FILE *file)
372 {
373   size_t want = size - 1;/* always need to leave room for zero termination */
374   size_t loop;
375 
376   switch(file->type) {
377   case CFTYPE_FILE:
378     ptr = fgets(ptr, (int)size, file->handle.file);
379     break;
380 
381   case CFTYPE_CURL:
382     fill_buffer(file,want);
383 
384     /* check if theres data in the buffer - if not fill either errored or
385      * EOF */
386     if(!file->buffer_pos)
387       return NULL;
388 
389     /* ensure only available data is considered */
390     if(file->buffer_pos < want)
391       want = file->buffer_pos;
392 
393     /*buffer contains data */
394     /* look for newline or eof */
395     for(loop=0;loop < want;loop++) {
396       if(file->buffer[loop] == '\n') {
397         want=loop+1;/* include newline */
398         break;
399       }
400     }
401 
402     /* xfer data to caller */
403     memcpy(ptr, file->buffer, want);
404     ptr[want]=0;/* allways null terminate */
405 
406     use_buffer(file,want);
407 
408     break;
409 
410   default: /* unknown or supported type - oh dear */
411     ptr=NULL;
412     errno=EBADF;
413     break;
414   }
415 
416   return ptr;/*success */
417 }
418 
url_rewind(URL_FILE * file)419 void url_rewind(URL_FILE *file)
420 {
421   switch(file->type) {
422   case CFTYPE_FILE:
423     rewind(file->handle.file); /* passthrough */
424     break;
425 
426   case CFTYPE_CURL:
427     /* halt transaction */
428     curl_multi_remove_handle(multi_handle, file->handle.curl);
429 
430     /* restart */
431     curl_multi_add_handle(multi_handle, file->handle.curl);
432 
433     /* ditch buffer - write will recreate - resets stream pos*/
434     free(file->buffer);
435     file->buffer=NULL;
436     file->buffer_pos=0;
437     file->buffer_len=0;
438 
439     break;
440 
441   default: /* unknown or supported type - oh dear */
442     break;
443   }
444 }
445 
446 /* Small main program to retrive from a url using fgets and fread saving the
447  * output to two test files (note the fgets method will corrupt binary files if
448  * they contain 0 chars */
main(int argc,char * argv[])449 int main(int argc, char *argv[])
450 {
451   URL_FILE *handle;
452   FILE *outf;
453 
454   size_t nread;
455   char buffer[256];
456   const char *url;
457 
458   if(argc < 2)
459     url="http://192.168.7.3/testfile";/* default to testurl */
460   else
461     url=argv[1];/* use passed url */
462 
463   /* copy from url line by line with fgets */
464   outf=fopen("fgets.test","w+");
465   if(!outf) {
466     perror("couldn't open fgets output file\n");
467     return 1;
468   }
469 
470   handle = url_fopen(url, "r");
471   if(!handle) {
472     printf("couldn't url_fopen() %s\n", url);
473     fclose(outf);
474     return 2;
475   }
476 
477   while(!url_feof(handle)) {
478     url_fgets(buffer,sizeof(buffer),handle);
479     fwrite(buffer,1,strlen(buffer),outf);
480   }
481 
482   url_fclose(handle);
483 
484   fclose(outf);
485 
486 
487   /* Copy from url with fread */
488   outf=fopen("fread.test","w+");
489   if(!outf) {
490     perror("couldn't open fread output file\n");
491     return 1;
492   }
493 
494   handle = url_fopen("testfile", "r");
495   if(!handle) {
496     printf("couldn't url_fopen() testfile\n");
497     fclose(outf);
498     return 2;
499   }
500 
501   do {
502     nread = url_fread(buffer, 1, sizeof(buffer), handle);
503     fwrite(buffer,1,nread,outf);
504   } while(nread);
505 
506   url_fclose(handle);
507 
508   fclose(outf);
509 
510 
511   /* Test rewind */
512   outf=fopen("rewind.test","w+");
513   if(!outf) {
514     perror("couldn't open fread output file\n");
515     return 1;
516   }
517 
518   handle = url_fopen("testfile", "r");
519   if(!handle) {
520     printf("couldn't url_fopen() testfile\n");
521     fclose(outf);
522     return 2;
523   }
524 
525   nread = url_fread(buffer, 1,sizeof(buffer), handle);
526   fwrite(buffer,1,nread,outf);
527   url_rewind(handle);
528 
529   buffer[0]='\n';
530   fwrite(buffer,1,1,outf);
531 
532   nread = url_fread(buffer, 1,sizeof(buffer), handle);
533   fwrite(buffer,1,nread,outf);
534 
535 
536   url_fclose(handle);
537 
538   fclose(outf);
539 
540 
541   return 0;/* all done */
542 }
543