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