xref: /curl/tests/http/clients/hx-download.c (revision 46093d9e)
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at https://curl.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  * SPDX-License-Identifier: curl
22  *
23  ***************************************************************************/
24 /* <DESC>
25  * HTTP/2 server push
26  * </DESC>
27  */
28 /* curl stuff */
29 #include <curl/curl.h>
30 
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 
35 #ifndef _MSC_VER
36 /* somewhat Unix-specific */
37 #include <unistd.h>  /* getopt() */
38 #endif
39 
40 #ifdef _WIN32
41 #define strdup _strdup
42 #endif
43 
44 #ifndef CURLPIPE_MULTIPLEX
45 #error "too old libcurl, cannot do HTTP/2 server push!"
46 #endif
47 
48 #ifndef _MSC_VER
49 static int verbose = 1;
50 
log_line_start(FILE * log,const char * idsbuf,curl_infotype type)51 static void log_line_start(FILE *log, const char *idsbuf, curl_infotype type)
52 {
53   /*
54    * This is the trace look that is similar to what libcurl makes on its
55    * own.
56    */
57   static const char * const s_infotype[] = {
58     "* ", "< ", "> ", "{ ", "} ", "{ ", "} "
59   };
60   if(idsbuf && *idsbuf)
61     fprintf(log, "%s%s", idsbuf, s_infotype[type]);
62   else
63     fputs(s_infotype[type], log);
64 }
65 
66 #define TRC_IDS_FORMAT_IDS_1  "[%" CURL_FORMAT_CURL_OFF_T "-x] "
67 #define TRC_IDS_FORMAT_IDS_2  "[%" CURL_FORMAT_CURL_OFF_T "-%" \
68                                    CURL_FORMAT_CURL_OFF_T "] "
69 /*
70 ** callback for CURLOPT_DEBUGFUNCTION
71 */
debug_cb(CURL * handle,curl_infotype type,char * data,size_t size,void * userdata)72 static int debug_cb(CURL *handle, curl_infotype type,
73                     char *data, size_t size,
74                     void *userdata)
75 {
76   FILE *output = stderr;
77   static int newl = 0;
78   static int traced_data = 0;
79   char idsbuf[60];
80   curl_off_t xfer_id, conn_id;
81 
82   (void)handle; /* not used */
83   (void)userdata;
84 
85   if(!curl_easy_getinfo(handle, CURLINFO_XFER_ID, &xfer_id) && xfer_id >= 0) {
86     if(!curl_easy_getinfo(handle, CURLINFO_CONN_ID, &conn_id) &&
87        conn_id >= 0) {
88       curl_msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_2, xfer_id,
89                      conn_id);
90     }
91     else {
92       curl_msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_1, xfer_id);
93     }
94   }
95   else
96     idsbuf[0] = 0;
97 
98   switch(type) {
99   case CURLINFO_HEADER_OUT:
100     if(size > 0) {
101       size_t st = 0;
102       size_t i;
103       for(i = 0; i < size - 1; i++) {
104         if(data[i] == '\n') { /* LF */
105           if(!newl) {
106             log_line_start(output, idsbuf, type);
107           }
108           (void)fwrite(data + st, i - st + 1, 1, output);
109           st = i + 1;
110           newl = 0;
111         }
112       }
113       if(!newl)
114         log_line_start(output, idsbuf, type);
115       (void)fwrite(data + st, i - st + 1, 1, output);
116     }
117     newl = (size && (data[size - 1] != '\n')) ? 1 : 0;
118     traced_data = 0;
119     break;
120   case CURLINFO_TEXT:
121   case CURLINFO_HEADER_IN:
122     if(!newl)
123       log_line_start(output, idsbuf, type);
124     (void)fwrite(data, size, 1, output);
125     newl = (size && (data[size - 1] != '\n')) ? 1 : 0;
126     traced_data = 0;
127     break;
128   case CURLINFO_DATA_OUT:
129   case CURLINFO_DATA_IN:
130   case CURLINFO_SSL_DATA_IN:
131   case CURLINFO_SSL_DATA_OUT:
132     if(!traced_data) {
133       if(!newl)
134         log_line_start(output, idsbuf, type);
135       fprintf(output, "[%ld bytes data]\n", (long)size);
136       newl = 0;
137       traced_data = 1;
138     }
139     break;
140   default: /* nada */
141     newl = 0;
142     traced_data = 1;
143     break;
144   }
145 
146   return 0;
147 }
148 
149 struct transfer {
150   int idx;
151   CURL *easy;
152   char filename[128];
153   FILE *out;
154   curl_off_t recv_size;
155   curl_off_t fail_at;
156   curl_off_t pause_at;
157   curl_off_t abort_at;
158   int started;
159   int paused;
160   int resumed;
161   int done;
162 };
163 
164 static size_t transfer_count = 1;
165 static struct transfer *transfers;
166 static int forbid_reuse = 0;
167 
get_transfer_for_easy(CURL * easy)168 static struct transfer *get_transfer_for_easy(CURL *easy)
169 {
170   size_t i;
171   for(i = 0; i < transfer_count; ++i) {
172     if(easy == transfers[i].easy)
173       return &transfers[i];
174   }
175   return NULL;
176 }
177 
my_write_cb(char * buf,size_t nitems,size_t buflen,void * userdata)178 static size_t my_write_cb(char *buf, size_t nitems, size_t buflen,
179                           void *userdata)
180 {
181   struct transfer *t = userdata;
182   size_t blen = (nitems * buflen);
183   size_t nwritten;
184 
185   fprintf(stderr, "[t-%d] RECV %ld bytes, total=%ld, pause_at=%ld\n",
186           t->idx, (long)blen, (long)t->recv_size, (long)t->pause_at);
187   if(!t->out) {
188     curl_msnprintf(t->filename, sizeof(t->filename)-1, "download_%u.data",
189                    t->idx);
190     t->out = fopen(t->filename, "wb");
191     if(!t->out)
192       return 0;
193   }
194 
195   if(!t->resumed &&
196      t->recv_size < t->pause_at &&
197      ((t->recv_size + (curl_off_t)blen) >= t->pause_at)) {
198     fprintf(stderr, "[t-%d] PAUSE\n", t->idx);
199     t->paused = 1;
200     return CURL_WRITEFUNC_PAUSE;
201   }
202 
203   nwritten = fwrite(buf, nitems, buflen, t->out);
204   if(nwritten < blen) {
205     fprintf(stderr, "[t-%d] write failure\n", t->idx);
206     return 0;
207   }
208   t->recv_size += (curl_off_t)nwritten;
209   if(t->fail_at > 0 && t->recv_size >= t->fail_at) {
210     fprintf(stderr, "[t-%d] FAIL by write callback at %ld bytes\n",
211             t->idx, (long)t->recv_size);
212     return CURL_WRITEFUNC_ERROR;
213   }
214 
215   return (size_t)nwritten;
216 }
217 
my_progress_cb(void * userdata,curl_off_t dltotal,curl_off_t dlnow,curl_off_t ultotal,curl_off_t ulnow)218 static int my_progress_cb(void *userdata,
219                           curl_off_t dltotal, curl_off_t dlnow,
220                           curl_off_t ultotal, curl_off_t ulnow)
221 {
222   struct transfer *t = userdata;
223   (void)ultotal;
224   (void)ulnow;
225   (void)dltotal;
226   if(t->abort_at > 0 && dlnow >= t->abort_at) {
227     fprintf(stderr, "[t-%d] ABORT by progress_cb at %ld bytes\n",
228             t->idx, (long)dlnow);
229     return 1;
230   }
231   return 0;
232 }
233 
setup(CURL * hnd,const char * url,struct transfer * t,int http_version,struct curl_slist * host,CURLSH * share,int use_earlydata,int fresh_connect)234 static int setup(CURL *hnd, const char *url, struct transfer *t,
235                  int http_version, struct curl_slist *host,
236                  CURLSH *share, int use_earlydata, int fresh_connect)
237 {
238   curl_easy_setopt(hnd, CURLOPT_SHARE, share);
239   curl_easy_setopt(hnd, CURLOPT_URL, url);
240   curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, http_version);
241   curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L);
242   curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 0L);
243   curl_easy_setopt(hnd, CURLOPT_BUFFERSIZE, (long)(128 * 1024));
244   curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, my_write_cb);
245   curl_easy_setopt(hnd, CURLOPT_WRITEDATA, t);
246   curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 0L);
247   curl_easy_setopt(hnd, CURLOPT_XFERINFOFUNCTION, my_progress_cb);
248   curl_easy_setopt(hnd, CURLOPT_XFERINFODATA, t);
249   if(use_earlydata)
250     curl_easy_setopt(hnd, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_EARLYDATA);
251   if(forbid_reuse)
252     curl_easy_setopt(hnd, CURLOPT_FORBID_REUSE, 1L);
253   if(host)
254     curl_easy_setopt(hnd, CURLOPT_RESOLVE, host);
255   if(fresh_connect)
256     curl_easy_setopt(hnd, CURLOPT_FRESH_CONNECT, 1L);
257 
258   /* please be verbose */
259   if(verbose) {
260     curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L);
261     curl_easy_setopt(hnd, CURLOPT_DEBUGFUNCTION, debug_cb);
262   }
263 
264 #if (CURLPIPE_MULTIPLEX > 0)
265   /* wait for pipe connection to confirm */
266   curl_easy_setopt(hnd, CURLOPT_PIPEWAIT, 1L);
267 #endif
268   return 0; /* all is good */
269 }
270 
usage(const char * msg)271 static void usage(const char *msg)
272 {
273   if(msg)
274     fprintf(stderr, "%s\n", msg);
275   fprintf(stderr,
276     "usage: [options] url\n"
277     "  download a url with following options:\n"
278     "  -a         abort paused transfer\n"
279     "  -m number  max parallel downloads\n"
280     "  -e         use TLS early data when possible\n"
281     "  -f         forbid connection reuse\n"
282     "  -n number  total downloads\n");
283   fprintf(stderr,
284     "  -A number  abort transfer after `number` response bytes\n"
285     "  -F number  fail writing response after `number` response bytes\n"
286     "  -P number  pause transfer after `number` response bytes\n"
287     "  -r <host>:<port>:<addr>  resolve information\n"
288     "  -V http_version (http/1.1, h2, h3) http version to use\n"
289   );
290 }
291 #endif /* !_MSC_VER */
292 
293 /*
294  * Download a file over HTTP/2, take care of server push.
295  */
main(int argc,char * argv[])296 int main(int argc, char *argv[])
297 {
298 #ifndef _MSC_VER
299   CURLM *multi_handle;
300   struct CURLMsg *m;
301   CURLSH *share;
302   const char *url;
303   size_t i, n, max_parallel = 1;
304   size_t active_transfers;
305   size_t pause_offset = 0;
306   size_t abort_offset = 0;
307   size_t fail_offset = 0;
308   int abort_paused = 0, use_earlydata = 0;
309   struct transfer *t;
310   int http_version = CURL_HTTP_VERSION_2_0;
311   int ch;
312   struct curl_slist *host = NULL;
313   char *resolve = NULL;
314   size_t max_host_conns = 0;
315   int fresh_connect = 0;
316 
317   while((ch = getopt(argc, argv, "aefhm:n:xA:F:M:P:r:V:")) != -1) {
318     switch(ch) {
319     case 'h':
320       usage(NULL);
321       return 2;
322     case 'a':
323       abort_paused = 1;
324       break;
325     case 'e':
326       use_earlydata = 1;
327       break;
328     case 'f':
329       forbid_reuse = 1;
330       break;
331     case 'm':
332       max_parallel = (size_t)strtol(optarg, NULL, 10);
333       break;
334     case 'n':
335       transfer_count = (size_t)strtol(optarg, NULL, 10);
336       break;
337     case 'x':
338       fresh_connect = 1;
339       break;
340     case 'A':
341       abort_offset = (size_t)strtol(optarg, NULL, 10);
342       break;
343     case 'F':
344       fail_offset = (size_t)strtol(optarg, NULL, 10);
345       break;
346     case 'M':
347       max_host_conns = (size_t)strtol(optarg, NULL, 10);
348       break;
349     case 'P':
350       pause_offset = (size_t)strtol(optarg, NULL, 10);
351       break;
352     case 'r':
353       resolve = strdup(optarg);
354       break;
355     case 'V': {
356       if(!strcmp("http/1.1", optarg))
357         http_version = CURL_HTTP_VERSION_1_1;
358       else if(!strcmp("h2", optarg))
359         http_version = CURL_HTTP_VERSION_2_0;
360       else if(!strcmp("h3", optarg))
361         http_version = CURL_HTTP_VERSION_3ONLY;
362       else {
363         usage("invalid http version");
364         return 1;
365       }
366       break;
367     }
368     default:
369      usage("invalid option");
370      return 1;
371     }
372   }
373   argc -= optind;
374   argv += optind;
375 
376   curl_global_init(CURL_GLOBAL_DEFAULT);
377   curl_global_trace("ids,time,http/2,http/3");
378 
379   if(argc != 1) {
380     usage("not enough arguments");
381     return 2;
382   }
383   url = argv[0];
384 
385   if(resolve)
386     host = curl_slist_append(NULL, resolve);
387 
388   share = curl_share_init();
389   if(!share) {
390     fprintf(stderr, "error allocating share\n");
391     return 1;
392   }
393   curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
394   curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
395   curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
396   /* curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); */
397   curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL);
398   curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS);
399 
400   transfers = calloc(transfer_count, sizeof(*transfers));
401   if(!transfers) {
402     fprintf(stderr, "error allocating transfer structs\n");
403     return 1;
404   }
405 
406   multi_handle = curl_multi_init();
407   curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
408   curl_multi_setopt(multi_handle, CURLMOPT_MAX_HOST_CONNECTIONS,
409                     (long)max_host_conns);
410 
411   active_transfers = 0;
412   for(i = 0; i < transfer_count; ++i) {
413     t = &transfers[i];
414     t->idx = (int)i;
415     t->abort_at = (curl_off_t)abort_offset;
416     t->fail_at = (curl_off_t)fail_offset;
417     t->pause_at = (curl_off_t)pause_offset;
418   }
419 
420   n = (max_parallel < transfer_count) ? max_parallel : transfer_count;
421   for(i = 0; i < n; ++i) {
422     t = &transfers[i];
423     t->easy = curl_easy_init();
424     if(!t->easy ||
425        setup(t->easy, url, t, http_version, host, share, use_earlydata,
426              fresh_connect)) {
427       fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);
428       return 1;
429     }
430     curl_multi_add_handle(multi_handle, t->easy);
431     t->started = 1;
432     ++active_transfers;
433     fprintf(stderr, "[t-%d] STARTED\n", t->idx);
434   }
435 
436   do {
437     int still_running; /* keep number of running handles */
438     CURLMcode mc = curl_multi_perform(multi_handle, &still_running);
439 
440     if(still_running) {
441       /* wait for activity, timeout or "nothing" */
442       mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL);
443     }
444 
445     if(mc)
446       break;
447 
448     do {
449       int msgq = 0;
450       m = curl_multi_info_read(multi_handle, &msgq);
451       if(m && (m->msg == CURLMSG_DONE)) {
452         CURL *e = m->easy_handle;
453         --active_transfers;
454         curl_multi_remove_handle(multi_handle, e);
455         t = get_transfer_for_easy(e);
456         if(t) {
457           t->done = 1;
458           fprintf(stderr, "[t-%d] FINISHED\n", t->idx);
459           if(use_earlydata) {
460             curl_off_t sent;
461             curl_easy_getinfo(e, CURLINFO_EARLYDATA_SENT_T, &sent);
462             fprintf(stderr, "[t-%d] EarlyData: %ld\n", t->idx, (long)sent);
463           }
464         }
465         else {
466           curl_easy_cleanup(e);
467           fprintf(stderr, "unknown FINISHED???\n");
468         }
469       }
470 
471 
472       /* nothing happening, maintenance */
473       if(abort_paused) {
474         /* abort paused transfers */
475         for(i = 0; i < transfer_count; ++i) {
476           t = &transfers[i];
477           if(!t->done && t->paused && t->easy) {
478             curl_multi_remove_handle(multi_handle, t->easy);
479             t->done = 1;
480             active_transfers--;
481             fprintf(stderr, "[t-%d] ABORTED\n", t->idx);
482           }
483         }
484       }
485       else {
486         /* resume one paused transfer */
487         for(i = 0; i < transfer_count; ++i) {
488           t = &transfers[i];
489           if(!t->done && t->paused) {
490             t->resumed = 1;
491             t->paused = 0;
492             curl_easy_pause(t->easy, CURLPAUSE_CONT);
493             fprintf(stderr, "[t-%d] RESUMED\n", t->idx);
494             break;
495           }
496         }
497       }
498 
499       while(active_transfers < max_parallel) {
500         for(i = 0; i < transfer_count; ++i) {
501           t = &transfers[i];
502           if(!t->started) {
503             t->easy = curl_easy_init();
504             if(!t->easy ||
505                setup(t->easy, url, t, http_version, host, share,
506                      use_earlydata, fresh_connect)) {
507               fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);
508               return 1;
509             }
510             curl_multi_add_handle(multi_handle, t->easy);
511             t->started = 1;
512             ++active_transfers;
513             fprintf(stderr, "[t-%d] STARTED\n", t->idx);
514             break;
515           }
516         }
517         /* all started */
518         if(i == transfer_count)
519           break;
520       }
521     } while(m);
522 
523   } while(active_transfers); /* as long as we have transfers going */
524 
525   curl_multi_cleanup(multi_handle);
526 
527   for(i = 0; i < transfer_count; ++i) {
528     t = &transfers[i];
529     if(t->out) {
530       fclose(t->out);
531       t->out = NULL;
532     }
533     if(t->easy) {
534       curl_easy_cleanup(t->easy);
535       t->easy = NULL;
536     }
537   }
538   free(transfers);
539 
540   curl_share_cleanup(share);
541   curl_slist_free_all(host);
542   free(resolve);
543 
544   return 0;
545 #else
546   (void)argc;
547   (void)argv;
548   fprintf(stderr, "Not supported with this compiler.\n");
549   return 1;
550 #endif /* !_MSC_VER */
551 }
552