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
29 /* curl stuff */
30 #include <curl/curl.h>
31 #include <curl/mprintf.h>
32
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36
37 /* somewhat unix-specific */
38 #include <sys/time.h>
39 #include <unistd.h>
40
41 #ifndef CURLPIPE_MULTIPLEX
42 #error "too old libcurl, cannot do HTTP/2 server push!"
43 #endif
44
45 static int verbose = 1;
46
log_line_start(FILE * log,const char * idsbuf,curl_infotype type)47 static void log_line_start(FILE *log, const char *idsbuf, curl_infotype type)
48 {
49 /*
50 * This is the trace look that is similar to what libcurl makes on its
51 * own.
52 */
53 static const char * const s_infotype[] = {
54 "* ", "< ", "> ", "{ ", "} ", "{ ", "} "
55 };
56 if(idsbuf && *idsbuf)
57 fprintf(log, "%s%s", idsbuf, s_infotype[type]);
58 else
59 fputs(s_infotype[type], log);
60 }
61
62 #define TRC_IDS_FORMAT_IDS_1 "[%" CURL_FORMAT_CURL_OFF_T "-x] "
63 #define TRC_IDS_FORMAT_IDS_2 "[%" CURL_FORMAT_CURL_OFF_T "-%" \
64 CURL_FORMAT_CURL_OFF_T "] "
65 /*
66 ** callback for CURLOPT_DEBUGFUNCTION
67 */
debug_cb(CURL * handle,curl_infotype type,char * data,size_t size,void * userdata)68 static int debug_cb(CURL *handle, curl_infotype type,
69 char *data, size_t size,
70 void *userdata)
71 {
72 FILE *output = stderr;
73 static int newl = 0;
74 static int traced_data = 0;
75 char idsbuf[60];
76 curl_off_t xfer_id, conn_id;
77
78 (void)handle; /* not used */
79 (void)userdata;
80
81 if(!curl_easy_getinfo(handle, CURLINFO_XFER_ID, &xfer_id) && xfer_id >= 0) {
82 if(!curl_easy_getinfo(handle, CURLINFO_CONN_ID, &conn_id) &&
83 conn_id >= 0) {
84 curl_msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_2,
85 xfer_id, conn_id);
86 }
87 else {
88 curl_msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_1, xfer_id);
89 }
90 }
91 else
92 idsbuf[0] = 0;
93
94 switch(type) {
95 case CURLINFO_HEADER_OUT:
96 if(size > 0) {
97 size_t st = 0;
98 size_t i;
99 for(i = 0; i < size - 1; i++) {
100 if(data[i] == '\n') { /* LF */
101 if(!newl) {
102 log_line_start(output, idsbuf, type);
103 }
104 (void)fwrite(data + st, i - st + 1, 1, output);
105 st = i + 1;
106 newl = 0;
107 }
108 }
109 if(!newl)
110 log_line_start(output, idsbuf, type);
111 (void)fwrite(data + st, i - st + 1, 1, output);
112 }
113 newl = (size && (data[size - 1] != '\n')) ? 1 : 0;
114 traced_data = 0;
115 break;
116 case CURLINFO_TEXT:
117 case CURLINFO_HEADER_IN:
118 if(!newl)
119 log_line_start(output, idsbuf, type);
120 (void)fwrite(data, size, 1, output);
121 newl = (size && (data[size - 1] != '\n')) ? 1 : 0;
122 traced_data = 0;
123 break;
124 case CURLINFO_DATA_OUT:
125 case CURLINFO_DATA_IN:
126 case CURLINFO_SSL_DATA_IN:
127 case CURLINFO_SSL_DATA_OUT:
128 if(!traced_data) {
129 if(!newl)
130 log_line_start(output, idsbuf, type);
131 fprintf(output, "[%ld bytes data]\n", (long)size);
132 newl = 0;
133 traced_data = 1;
134 }
135 break;
136 default: /* nada */
137 newl = 0;
138 traced_data = 1;
139 break;
140 }
141
142 return 0;
143 }
144
145 struct transfer {
146 int idx;
147 CURL *easy;
148 char filename[128];
149 FILE *out;
150 curl_off_t recv_size;
151 curl_off_t fail_at;
152 curl_off_t pause_at;
153 curl_off_t abort_at;
154 int started;
155 int paused;
156 int resumed;
157 int done;
158 };
159
160 static size_t transfer_count = 1;
161 static struct transfer *transfers;
162
get_transfer_for_easy(CURL * easy)163 static struct transfer *get_transfer_for_easy(CURL *easy)
164 {
165 size_t i;
166 for(i = 0; i < transfer_count; ++i) {
167 if(easy == transfers[i].easy)
168 return &transfers[i];
169 }
170 return NULL;
171 }
172
my_write_cb(char * buf,size_t nitems,size_t buflen,void * userdata)173 static size_t my_write_cb(char *buf, size_t nitems, size_t buflen,
174 void *userdata)
175 {
176 struct transfer *t = userdata;
177 size_t blen = (nitems * buflen);
178 size_t nwritten;
179
180 fprintf(stderr, "[t-%d] RECV %ld bytes, total=%ld, pause_at=%ld\n",
181 t->idx, (long)blen, (long)t->recv_size, (long)t->pause_at);
182 if(!t->out) {
183 curl_msnprintf(t->filename, sizeof(t->filename)-1, "download_%u.data",
184 t->idx);
185 t->out = fopen(t->filename, "wb");
186 if(!t->out)
187 return 0;
188 }
189
190 if(!t->resumed &&
191 t->recv_size < t->pause_at &&
192 ((t->recv_size + (curl_off_t)blen) >= t->pause_at)) {
193 fprintf(stderr, "[t-%d] PAUSE\n", t->idx);
194 t->paused = 1;
195 return CURL_WRITEFUNC_PAUSE;
196 }
197
198 nwritten = fwrite(buf, nitems, buflen, t->out);
199 if(nwritten < blen) {
200 fprintf(stderr, "[t-%d] write failure\n", t->idx);
201 return 0;
202 }
203 t->recv_size += (curl_off_t)nwritten;
204 if(t->fail_at > 0 && t->recv_size >= t->fail_at) {
205 fprintf(stderr, "[t-%d] FAIL by write callback at %ld bytes\n",
206 t->idx, (long)t->recv_size);
207 return CURL_WRITEFUNC_ERROR;
208 }
209
210 return (size_t)nwritten;
211 }
212
my_progress_cb(void * userdata,curl_off_t dltotal,curl_off_t dlnow,curl_off_t ultotal,curl_off_t ulnow)213 static int my_progress_cb(void *userdata,
214 curl_off_t dltotal, curl_off_t dlnow,
215 curl_off_t ultotal, curl_off_t ulnow)
216 {
217 struct transfer *t = userdata;
218 (void)ultotal;
219 (void)ulnow;
220 (void)dltotal;
221 if(t->abort_at > 0 && dlnow >= t->abort_at) {
222 fprintf(stderr, "[t-%d] ABORT by progress_cb at %ld bytes\n",
223 t->idx, (long)dlnow);
224 return 1;
225 }
226 return 0;
227 }
228
setup(CURL * hnd,const char * url,struct transfer * t,int http_version)229 static int setup(CURL *hnd, const char *url, struct transfer *t,
230 int http_version)
231 {
232 curl_easy_setopt(hnd, CURLOPT_URL, url);
233 curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, http_version);
234 curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L);
235 curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 0L);
236 curl_easy_setopt(hnd, CURLOPT_BUFFERSIZE, (long)(128 * 1024));
237 curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, my_write_cb);
238 curl_easy_setopt(hnd, CURLOPT_WRITEDATA, t);
239 curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 0L);
240 curl_easy_setopt(hnd, CURLOPT_XFERINFOFUNCTION, my_progress_cb);
241 curl_easy_setopt(hnd, CURLOPT_XFERINFODATA, t);
242
243 /* please be verbose */
244 if(verbose) {
245 curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L);
246 curl_easy_setopt(hnd, CURLOPT_DEBUGFUNCTION, debug_cb);
247 }
248
249 #if (CURLPIPE_MULTIPLEX > 0)
250 /* wait for pipe connection to confirm */
251 curl_easy_setopt(hnd, CURLOPT_PIPEWAIT, 1L);
252 #endif
253 return 0; /* all is good */
254 }
255
usage(const char * msg)256 static void usage(const char *msg)
257 {
258 if(msg)
259 fprintf(stderr, "%s\n", msg);
260 fprintf(stderr,
261 "usage: [options] url\n"
262 " download a url with following options:\n"
263 " -a abort paused transfer\n"
264 " -m number max parallel downloads\n"
265 " -n number total downloads\n"
266 " -A number abort transfer after `number` response bytes\n"
267 " -F number fail writing response after `number` response bytes\n"
268 " -P number pause transfer after `number` response bytes\n"
269 " -V http_version (http/1.1, h2, h3) http version to use\n"
270 );
271 }
272
273 /*
274 * Download a file over HTTP/2, take care of server push.
275 */
main(int argc,char * argv[])276 int main(int argc, char *argv[])
277 {
278 CURLM *multi_handle;
279 struct CURLMsg *m;
280 const char *url;
281 size_t i, n, max_parallel = 1;
282 size_t active_transfers;
283 size_t pause_offset = 0;
284 size_t abort_offset = 0;
285 size_t fail_offset = 0;
286 int abort_paused = 0;
287 struct transfer *t;
288 int http_version = CURL_HTTP_VERSION_2_0;
289 int ch;
290
291 while((ch = getopt(argc, argv, "ahm:n:A:F:P:V:")) != -1) {
292 switch(ch) {
293 case 'h':
294 usage(NULL);
295 return 2;
296 case 'a':
297 abort_paused = 1;
298 break;
299 case 'm':
300 max_parallel = (size_t)strtol(optarg, NULL, 10);
301 break;
302 case 'n':
303 transfer_count = (size_t)strtol(optarg, NULL, 10);
304 break;
305 case 'A':
306 abort_offset = (size_t)strtol(optarg, NULL, 10);
307 break;
308 case 'F':
309 fail_offset = (size_t)strtol(optarg, NULL, 10);
310 break;
311 case 'P':
312 pause_offset = (size_t)strtol(optarg, NULL, 10);
313 break;
314 case 'V': {
315 if(!strcmp("http/1.1", optarg))
316 http_version = CURL_HTTP_VERSION_1_1;
317 else if(!strcmp("h2", optarg))
318 http_version = CURL_HTTP_VERSION_2_0;
319 else if(!strcmp("h3", optarg))
320 http_version = CURL_HTTP_VERSION_3ONLY;
321 else {
322 usage("invalid http version");
323 return 1;
324 }
325 break;
326 }
327 default:
328 usage("invalid option");
329 return 1;
330 }
331 }
332 argc -= optind;
333 argv += optind;
334
335 curl_global_init(CURL_GLOBAL_DEFAULT);
336 curl_global_trace("ids,time,http/2,http/3");
337
338 if(argc != 1) {
339 usage("not enough arguments");
340 return 2;
341 }
342 url = argv[0];
343
344 transfers = calloc(transfer_count, sizeof(*transfers));
345 if(!transfers) {
346 fprintf(stderr, "error allocating transfer structs\n");
347 return 1;
348 }
349
350 multi_handle = curl_multi_init();
351 curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
352
353 active_transfers = 0;
354 for(i = 0; i < transfer_count; ++i) {
355 t = &transfers[i];
356 t->idx = (int)i;
357 t->abort_at = (curl_off_t)abort_offset;
358 t->fail_at = (curl_off_t)fail_offset;
359 t->pause_at = (curl_off_t)pause_offset;
360 }
361
362 n = (max_parallel < transfer_count)? max_parallel : transfer_count;
363 for(i = 0; i < n; ++i) {
364 t = &transfers[i];
365 t->easy = curl_easy_init();
366 if(!t->easy || setup(t->easy, url, t, http_version)) {
367 fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);
368 return 1;
369 }
370 curl_multi_add_handle(multi_handle, t->easy);
371 t->started = 1;
372 ++active_transfers;
373 fprintf(stderr, "[t-%d] STARTED\n", t->idx);
374 }
375
376 do {
377 int still_running; /* keep number of running handles */
378 CURLMcode mc = curl_multi_perform(multi_handle, &still_running);
379
380 if(still_running) {
381 /* wait for activity, timeout or "nothing" */
382 mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL);
383 }
384
385 if(mc)
386 break;
387
388 do {
389 int msgq = 0;
390 m = curl_multi_info_read(multi_handle, &msgq);
391 if(m && (m->msg == CURLMSG_DONE)) {
392 CURL *e = m->easy_handle;
393 --active_transfers;
394 curl_multi_remove_handle(multi_handle, e);
395 t = get_transfer_for_easy(e);
396 if(t) {
397 t->done = 1;
398 fprintf(stderr, "[t-%d] FINISHED\n", t->idx);
399 }
400 else {
401 curl_easy_cleanup(e);
402 fprintf(stderr, "unknown FINISHED???\n");
403 }
404 }
405
406
407 /* nothing happening, maintenance */
408 if(abort_paused) {
409 /* abort paused transfers */
410 for(i = 0; i < transfer_count; ++i) {
411 t = &transfers[i];
412 if(!t->done && t->paused && t->easy) {
413 curl_multi_remove_handle(multi_handle, t->easy);
414 t->done = 1;
415 active_transfers--;
416 fprintf(stderr, "[t-%d] ABORTED\n", t->idx);
417 }
418 }
419 }
420 else {
421 /* resume one paused transfer */
422 for(i = 0; i < transfer_count; ++i) {
423 t = &transfers[i];
424 if(!t->done && t->paused) {
425 t->resumed = 1;
426 t->paused = 0;
427 curl_easy_pause(t->easy, CURLPAUSE_CONT);
428 fprintf(stderr, "[t-%d] RESUMED\n", t->idx);
429 break;
430 }
431 }
432 }
433
434 while(active_transfers < max_parallel) {
435 for(i = 0; i < transfer_count; ++i) {
436 t = &transfers[i];
437 if(!t->started) {
438 t->easy = curl_easy_init();
439 if(!t->easy || setup(t->easy, url, t, http_version)) {
440 fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);
441 return 1;
442 }
443 curl_multi_add_handle(multi_handle, t->easy);
444 t->started = 1;
445 ++active_transfers;
446 fprintf(stderr, "[t-%d] STARTED\n", t->idx);
447 break;
448 }
449 }
450 /* all started */
451 if(i == transfer_count)
452 break;
453 }
454 } while(m);
455
456 } while(active_transfers); /* as long as we have transfers going */
457
458 for(i = 0; i < transfer_count; ++i) {
459 t = &transfers[i];
460 if(t->out) {
461 fclose(t->out);
462 t->out = NULL;
463 }
464 if(t->easy) {
465 curl_easy_cleanup(t->easy);
466 t->easy = NULL;
467 }
468 }
469 free(transfers);
470
471 curl_multi_cleanup(multi_handle);
472
473 return 0;
474 }
475