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