1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stddef.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <time.h>
7 #include <errno.h>
8 #include <sys/select.h>
9 
10 #include <curl/curl.h>
11 #include <curl/easy.h>
12 
13 #include "curl.h"
14 
15 /* curl globals */
16 static CURLM *curlm;
17 static fd_set rd, wr, ex;
18 
19 /* my globals */
20 static int url_debug   = 0;
21 static int url_timeout = 30;
22 
23 /* my structs */
24 struct iobuf {
25     off_t   start;
26     size_t  size;
27     char    *data;
28 };
29 
30 struct url_state {
31     char           *path;
32     CURL           *curl;
33     char           errmsg[CURL_ERROR_SIZE];
34     off_t          curl_pos;
35     off_t          buf_pos;
36     struct iobuf   buf;
37     int            eof;
38 };
39 
40 /* ---------------------------------------------------------------------- */
41 /* curl stuff                                                             */
42 
curl_init(void)43 static void __attribute__ ((constructor)) curl_init(void)
44 {
45     curl_global_init(CURL_GLOBAL_ALL);
46     curlm = curl_multi_init();
47 }
48 
curl_fini(void)49 static void __attribute__ ((destructor)) curl_fini(void)
50 {
51     curl_multi_cleanup(curlm);
52     curl_global_cleanup();
53 }
54 
curl_free_buffer(struct iobuf * buf)55 static void curl_free_buffer(struct iobuf *buf)
56 {
57     if (buf->data) {
58 	free(buf->data);
59 	memset(buf,0,sizeof(*buf));
60     }
61 }
62 
63 /* CURLOPT_WRITEFUNCTION */
curl_write(void * data,size_t size,size_t nmemb,void * handle)64 static int curl_write(void *data,  size_t  size, size_t nmemb, void *handle)
65 {
66     struct url_state *h = handle;
67 
68     curl_free_buffer(&h->buf);
69     h->buf.start = h->curl_pos;
70     h->buf.size  = size * nmemb;
71     h->buf.data  = malloc(h->buf.size);
72     memcpy(h->buf.data, data, h->buf.size);
73     if (url_debug)
74 	fprintf(stderr,"  put %5d @ %5d\n",
75 		(int)h->buf.size, (int)h->buf.start);
76 
77     h->curl_pos += h->buf.size;
78     return h->buf.size;
79 }
80 
81 /* do transfers */
curl_xfer(struct url_state * h)82 static int curl_xfer(struct url_state *h)
83 {
84     CURLMcode rc;
85     struct timeval tv;
86     int count, maxfd;
87 
88     FD_ZERO(&rd);
89     FD_ZERO(&wr);
90     FD_ZERO(&ex);
91     maxfd = -1;
92     rc = curl_multi_fdset(curlm, &rd, &wr, &ex, &maxfd);
93     if (CURLM_OK != rc) {
94 	fprintf(stderr,"curl_multi_fdset: %d %s\n",rc,h->errmsg);
95 	return -1;
96     }
97     if (-1 == maxfd) {
98 	/* wait 0.1 sec */
99 	if (url_debug)
100 	    fprintf(stderr,"wait 0.01 sec\n");
101 	tv.tv_sec  = 0;
102 	tv.tv_usec = 100000;
103     } else {
104 	/* wait for data */
105 	if (url_debug)
106 	    fprintf(stderr,"select for data [maxfd=%d]\n",maxfd);
107 	tv.tv_sec  = url_timeout;
108 	tv.tv_usec = 0;
109     }
110     switch (select(maxfd+1, &rd, &wr, &ex, &tv)) {
111     case -1:
112 	/* Huh? */
113 	perror("select");
114 	exit(1);
115     case 0:
116 	/* timeout */
117 	return -1;
118     }
119     for (;;) {
120 	rc = curl_multi_perform(curlm,&count);
121 	if (CURLM_CALL_MULTI_PERFORM == rc)
122 	    continue;
123 	if (CURLM_OK != rc) {
124 	    fprintf(stderr,"curl_multi_perform: %d %s\n",rc,h->errmsg);
125 	    return -1;
126 	}
127 	if (0 == count)
128 	    h->eof = 1;
129 	break;
130     }
131     return 0;
132 }
133 
134 /* curl setup */
curl_setup(struct url_state * h)135 static int curl_setup(struct url_state *h)
136 {
137     if (h->curl) {
138 	curl_multi_remove_handle(curlm,h->curl);
139 	curl_easy_cleanup(h->curl);
140     }
141 
142     h->curl = curl_easy_init();
143     curl_easy_setopt(h->curl, CURLOPT_URL,           h->path);
144     curl_easy_setopt(h->curl, CURLOPT_ERRORBUFFER,   h->errmsg);
145     curl_easy_setopt(h->curl, CURLOPT_WRITEFUNCTION, curl_write);
146     curl_easy_setopt(h->curl, CURLOPT_WRITEDATA,     h);
147     curl_multi_add_handle(curlm, h->curl);
148 
149     h->buf_pos  = 0;
150     h->curl_pos = 0;
151     h->eof      = 0;
152     return 0;
153 }
154 
155 /* ---------------------------------------------------------------------- */
156 /* GNU glibc custom stream interface                                      */
157 
url_read(void * handle,char * buf,size_t size)158 static ssize_t url_read(void *handle, char *buf, size_t size)
159 {
160     struct url_state *h = handle;
161     size_t bytes, total;
162     off_t off;
163     int count;
164 
165     if (url_debug)
166 	fprintf(stderr,"url_read(size=%d)\n",(int)size);
167     for (total = 0; size > 0;) {
168 	if (h->buf.start                <= h->buf_pos &&
169 	    h->buf.start + h->buf.size  >  h->buf_pos) {
170 	    /* can satisfy from current buffer */
171 	    bytes = h->buf.start + h->buf.size - h->buf_pos;
172 	    off   = h->buf_pos - h->buf.start;
173 	    if (bytes > size)
174 		bytes = size;
175 	    memcpy(buf+total, h->buf.data + off, bytes);
176 	    if (url_debug)
177 		fprintf(stderr,"  get %5d @ %5d [%5d]\n",
178 			(int)bytes, (int)h->buf_pos, (int)off);
179 	    size       -= bytes;
180 	    total      += bytes;
181 	    h->buf_pos += bytes;
182 	    continue;
183 	}
184 	if (h->buf_pos < h->buf.start) {
185 	    /* seeking backwards -- restart transfer */
186 	    if (url_debug)
187 		fprintf(stderr,"  rewind\n");
188 	    curl_free_buffer(&h->buf);
189 	    curl_setup(h);
190 	    while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(curlm,&count))
191 		/* nothing */;
192 	}
193 	if (h->eof)
194 	    /* stop on eof */
195 	    break;
196 	/* fetch more data */
197 	if (-1 == curl_xfer(h)) {
198 	    if (0 == total)
199 		return -1;
200 	    break;
201 	}
202     }
203     return total;
204 }
205 
206 #if 0
207 static ssize_t url_write(void *handle, const char *buf, size_t size)
208 {
209     //struct url_state *h = handle;
210 
211     if (url_debug)
212 	fprintf(stderr,"url_write(size=%d)\n",(int)size);
213     return -1;
214 }
215 #endif
216 
url_seek(void * handle,off64_t * pos,int whence)217 static int url_seek(void *handle, off64_t *pos, int whence)
218 {
219     struct url_state *h = handle;
220     int rc = 0;
221 
222     if (url_debug)
223 	fprintf(stderr,"url_seek(pos=%d,whence=%d)\n", (int)(*pos), whence);
224     switch (whence) {
225     case SEEK_SET:
226 	h->buf_pos = *pos;
227 	break;
228     case SEEK_CUR:
229 	h->buf_pos += *pos;
230 	break;
231     case SEEK_END:
232 	rc = -1;
233     }
234     *pos = h->buf_pos;
235     return rc;
236 }
237 
url_close(void * handle)238 static int url_close(void *handle)
239 {
240     struct url_state *h = handle;
241 
242     if (url_debug)
243 	fprintf(stderr,"url_close()\n");
244     curl_multi_remove_handle(curlm,h->curl);
245     curl_easy_cleanup(h->curl);
246     if (h->buf.data)
247 	free(h->buf.data);
248     free(h->path);
249     free(h);
250     return 0;
251 }
252 
253 static cookie_io_functions_t url_hooks = {
254     .read  = url_read,
255 #if 0
256     .write = url_write,
257 #endif
258     .seek  = url_seek,
259     .close = url_close,
260 };
261 
url_open(const char * path,const char * mode)262 static FILE *url_open(const char *path, const char *mode)
263 {
264     FILE *fp;
265     struct url_state *h;
266     int count;
267 
268     if (url_debug)
269 	fprintf(stderr,"url_open(%s,%s)\n",path,mode);
270 
271     h = malloc(sizeof(*h));
272     if (NULL == h)
273 	goto err;
274     memset(h,0,sizeof(*h));
275 
276     h->path = strdup(path);
277     if (NULL == h->path)
278 	goto err;
279 
280     /* setup */
281     curl_setup(h);
282     fp = fopencookie(h, mode, url_hooks);
283     if (NULL == fp)
284 	goto err;
285 
286     /* connect + start fetching */
287     while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(curlm,&count))
288 	/* nothing */;
289 
290     /* check for errors */
291     if (0 == count  &&  NULL == h->buf.data) {
292 	errno = ENOENT;
293 	goto fetch_err;
294     }
295 
296     /* all done */
297     return fp;
298 
299 
300  fetch_err:
301     curl_multi_remove_handle(curlm,h->curl);
302  err:
303     if (h->curl)
304 	curl_easy_cleanup(h->curl);
305     if (h->path)
306 	free(h->path);
307     if (h)
308 	free(h);
309     return NULL;
310 }
311 
312 /* ---------------------------------------------------------------------- */
313 /* hook into fopen using GNU ld's --wrap                                  */
314 
curl_is_url(const char * url)315 int curl_is_url(const char *url)
316 {
317     static char *protocols[] = {
318 	"ftp://",
319 	"http://",
320 	NULL,
321     };
322     int i;
323 
324     for (i = 0; protocols[i] != NULL; i++)
325 	if (0 == strncasecmp(url, protocols[i], strlen(protocols[i])))
326 	    return 1;
327     return 0;
328 }
329 
330 FILE *__wrap_fopen(const char *path, const char *mode);
331 FILE *__real_fopen(const char *path, const char *mode);
332 
__wrap_fopen(const char * path,const char * mode)333 FILE *__wrap_fopen(const char *path, const char *mode)
334 {
335     if (url_debug)
336 	fprintf(stderr,"fopen(%s,%s)\n",path,mode);
337 
338     /* catch URLs */
339     if (curl_is_url(path)) {
340 	if (strchr(mode,'w')) {
341 	    fprintf(stderr,"write access over ftp/http is not supported, sorry\n");
342 	    return NULL;
343 	}
344 	return url_open(path,mode);
345     }
346 
347     /* files passed to the real fopen */
348     return __real_fopen(path,mode);
349 }
350