Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/pkg
Path: blob/main/external/curl/tests/http/clients/hx-download.c
2066 views
1
/***************************************************************************
2
* _ _ ____ _
3
* Project ___| | | | _ \| |
4
* / __| | | | |_) | |
5
* | (__| |_| | _ <| |___
6
* \___|\___/|_| \_\_____|
7
*
8
* Copyright (C) Daniel Stenberg, <[email protected]>, 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
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
*/
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
CURLcode result;
163
};
164
165
static size_t transfer_count = 1;
166
static struct transfer *transfers;
167
static int forbid_reuse = 0;
168
169
static struct transfer *get_transfer_for_easy(CURL *easy)
170
{
171
size_t i;
172
for(i = 0; i < transfer_count; ++i) {
173
if(easy == transfers[i].easy)
174
return &transfers[i];
175
}
176
return NULL;
177
}
178
179
static size_t my_write_cb(char *buf, size_t nitems, size_t buflen,
180
void *userdata)
181
{
182
struct transfer *t = userdata;
183
size_t blen = (nitems * buflen);
184
size_t nwritten;
185
186
fprintf(stderr, "[t-%d] RECV %ld bytes, total=%ld, pause_at=%ld\n",
187
t->idx, (long)blen, (long)t->recv_size, (long)t->pause_at);
188
if(!t->out) {
189
curl_msnprintf(t->filename, sizeof(t->filename)-1, "download_%u.data",
190
t->idx);
191
t->out = fopen(t->filename, "wb");
192
if(!t->out)
193
return 0;
194
}
195
196
if(!t->resumed &&
197
t->recv_size < t->pause_at &&
198
((t->recv_size + (curl_off_t)blen) >= t->pause_at)) {
199
fprintf(stderr, "[t-%d] PAUSE\n", t->idx);
200
t->paused = 1;
201
return CURL_WRITEFUNC_PAUSE;
202
}
203
204
nwritten = fwrite(buf, nitems, buflen, t->out);
205
if(nwritten < blen) {
206
fprintf(stderr, "[t-%d] write failure\n", t->idx);
207
return 0;
208
}
209
t->recv_size += (curl_off_t)nwritten;
210
if(t->fail_at > 0 && t->recv_size >= t->fail_at) {
211
fprintf(stderr, "[t-%d] FAIL by write callback at %ld bytes\n",
212
t->idx, (long)t->recv_size);
213
return CURL_WRITEFUNC_ERROR;
214
}
215
216
return (size_t)nwritten;
217
}
218
219
static int my_progress_cb(void *userdata,
220
curl_off_t dltotal, curl_off_t dlnow,
221
curl_off_t ultotal, curl_off_t ulnow)
222
{
223
struct transfer *t = userdata;
224
(void)ultotal;
225
(void)ulnow;
226
(void)dltotal;
227
if(t->abort_at > 0 && dlnow >= t->abort_at) {
228
fprintf(stderr, "[t-%d] ABORT by progress_cb at %ld bytes\n",
229
t->idx, (long)dlnow);
230
return 1;
231
}
232
return 0;
233
}
234
235
static int setup(CURL *hnd, const char *url, struct transfer *t,
236
long http_version, struct curl_slist *host,
237
CURLSH *share, int use_earlydata, int fresh_connect)
238
{
239
curl_easy_setopt(hnd, CURLOPT_SHARE, share);
240
curl_easy_setopt(hnd, CURLOPT_URL, url);
241
curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, http_version);
242
curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L);
243
curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 0L);
244
curl_easy_setopt(hnd, CURLOPT_ACCEPT_ENCODING, "");
245
curl_easy_setopt(hnd, CURLOPT_BUFFERSIZE, (long)(128 * 1024));
246
curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, my_write_cb);
247
curl_easy_setopt(hnd, CURLOPT_WRITEDATA, t);
248
curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 0L);
249
curl_easy_setopt(hnd, CURLOPT_XFERINFOFUNCTION, my_progress_cb);
250
curl_easy_setopt(hnd, CURLOPT_XFERINFODATA, t);
251
if(use_earlydata)
252
curl_easy_setopt(hnd, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_EARLYDATA);
253
if(forbid_reuse)
254
curl_easy_setopt(hnd, CURLOPT_FORBID_REUSE, 1L);
255
if(host)
256
curl_easy_setopt(hnd, CURLOPT_RESOLVE, host);
257
if(fresh_connect)
258
curl_easy_setopt(hnd, CURLOPT_FRESH_CONNECT, 1L);
259
260
/* please be verbose */
261
if(verbose) {
262
curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L);
263
curl_easy_setopt(hnd, CURLOPT_DEBUGFUNCTION, debug_cb);
264
}
265
266
#if (CURLPIPE_MULTIPLEX > 0)
267
/* wait for pipe connection to confirm */
268
curl_easy_setopt(hnd, CURLOPT_PIPEWAIT, 1L);
269
#endif
270
return 0; /* all is good */
271
}
272
273
static void usage(const char *msg)
274
{
275
if(msg)
276
fprintf(stderr, "%s\n", msg);
277
fprintf(stderr,
278
"usage: [options] url\n"
279
" download a url with following options:\n"
280
" -a abort paused transfer\n"
281
" -m number max parallel downloads\n"
282
" -e use TLS early data when possible\n"
283
" -f forbid connection reuse\n"
284
" -n number total downloads\n");
285
fprintf(stderr,
286
" -A number abort transfer after `number` response bytes\n"
287
" -F number fail writing response after `number` response bytes\n"
288
" -M number max concurrent connections to a host\n"
289
" -P number pause transfer after `number` response bytes\n"
290
" -r <host>:<port>:<addr> resolve information\n"
291
" -T number max concurrent connections total\n"
292
" -V http_version (http/1.1, h2, h3) http version to use\n"
293
);
294
}
295
#endif /* !_MSC_VER */
296
297
/*
298
* Download a file over HTTP/2, take care of server push.
299
*/
300
int main(int argc, char *argv[])
301
{
302
#ifndef _MSC_VER
303
CURLM *multi_handle;
304
struct CURLMsg *m;
305
CURLSH *share;
306
const char *url;
307
size_t i, n, max_parallel = 1;
308
size_t active_transfers;
309
size_t pause_offset = 0;
310
size_t abort_offset = 0;
311
size_t fail_offset = 0;
312
int abort_paused = 0, use_earlydata = 0;
313
struct transfer *t;
314
int http_version = CURL_HTTP_VERSION_2_0;
315
int ch;
316
struct curl_slist *host = NULL;
317
char *resolve = NULL;
318
size_t max_host_conns = 0;
319
size_t max_total_conns = 0;
320
int fresh_connect = 0;
321
int result = 0;
322
323
while((ch = getopt(argc, argv, "aefhm:n:xA:F:M:P:r:T:V:")) != -1) {
324
switch(ch) {
325
case 'h':
326
usage(NULL);
327
result = 2;
328
goto cleanup;
329
case 'a':
330
abort_paused = 1;
331
break;
332
case 'e':
333
use_earlydata = 1;
334
break;
335
case 'f':
336
forbid_reuse = 1;
337
break;
338
case 'm':
339
max_parallel = (size_t)strtol(optarg, NULL, 10);
340
break;
341
case 'n':
342
transfer_count = (size_t)strtol(optarg, NULL, 10);
343
break;
344
case 'x':
345
fresh_connect = 1;
346
break;
347
case 'A':
348
abort_offset = (size_t)strtol(optarg, NULL, 10);
349
break;
350
case 'F':
351
fail_offset = (size_t)strtol(optarg, NULL, 10);
352
break;
353
case 'M':
354
max_host_conns = (size_t)strtol(optarg, NULL, 10);
355
break;
356
case 'P':
357
pause_offset = (size_t)strtol(optarg, NULL, 10);
358
break;
359
case 'r':
360
free(resolve);
361
resolve = strdup(optarg);
362
break;
363
case 'T':
364
max_total_conns = (size_t)strtol(optarg, NULL, 10);
365
break;
366
case 'V': {
367
if(!strcmp("http/1.1", optarg))
368
http_version = CURL_HTTP_VERSION_1_1;
369
else if(!strcmp("h2", optarg))
370
http_version = CURL_HTTP_VERSION_2_0;
371
else if(!strcmp("h3", optarg))
372
http_version = CURL_HTTP_VERSION_3ONLY;
373
else {
374
usage("invalid http version");
375
result = 1;
376
goto cleanup;
377
}
378
break;
379
}
380
default:
381
usage("invalid option");
382
result = 1;
383
goto cleanup;
384
}
385
}
386
argc -= optind;
387
argv += optind;
388
389
curl_global_init(CURL_GLOBAL_DEFAULT);
390
curl_global_trace("ids,time,http/2,http/3");
391
392
if(argc != 1) {
393
usage("not enough arguments");
394
result = 2;
395
goto cleanup;
396
}
397
url = argv[0];
398
399
if(resolve)
400
host = curl_slist_append(NULL, resolve);
401
402
share = curl_share_init();
403
if(!share) {
404
fprintf(stderr, "error allocating share\n");
405
result = 1;
406
goto cleanup;
407
}
408
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
409
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
410
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
411
/* curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); */
412
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL);
413
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS);
414
415
transfers = calloc(transfer_count, sizeof(*transfers));
416
if(!transfers) {
417
fprintf(stderr, "error allocating transfer structs\n");
418
result = 1;
419
goto cleanup;
420
}
421
422
multi_handle = curl_multi_init();
423
curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
424
curl_multi_setopt(multi_handle, CURLMOPT_MAX_TOTAL_CONNECTIONS,
425
(long)max_total_conns);
426
curl_multi_setopt(multi_handle, CURLMOPT_MAX_HOST_CONNECTIONS,
427
(long)max_host_conns);
428
429
active_transfers = 0;
430
for(i = 0; i < transfer_count; ++i) {
431
t = &transfers[i];
432
t->idx = (int)i;
433
t->abort_at = (curl_off_t)abort_offset;
434
t->fail_at = (curl_off_t)fail_offset;
435
t->pause_at = (curl_off_t)pause_offset;
436
}
437
438
n = (max_parallel < transfer_count) ? max_parallel : transfer_count;
439
for(i = 0; i < n; ++i) {
440
t = &transfers[i];
441
t->easy = curl_easy_init();
442
if(!t->easy ||
443
setup(t->easy, url, t, http_version, host, share, use_earlydata,
444
fresh_connect)) {
445
fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);
446
result = 1;
447
goto cleanup;
448
}
449
curl_multi_add_handle(multi_handle, t->easy);
450
t->started = 1;
451
++active_transfers;
452
fprintf(stderr, "[t-%d] STARTED\n", t->idx);
453
}
454
455
do {
456
int still_running; /* keep number of running handles */
457
CURLMcode mc = curl_multi_perform(multi_handle, &still_running);
458
459
if(still_running) {
460
/* wait for activity, timeout or "nothing" */
461
mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL);
462
}
463
464
if(mc)
465
break;
466
467
do {
468
int msgq = 0;
469
m = curl_multi_info_read(multi_handle, &msgq);
470
if(m && (m->msg == CURLMSG_DONE)) {
471
CURL *e = m->easy_handle;
472
--active_transfers;
473
curl_multi_remove_handle(multi_handle, e);
474
t = get_transfer_for_easy(e);
475
if(t) {
476
t->done = 1;
477
t->result = m->data.result;
478
fprintf(stderr, "[t-%d] FINISHED with result %d\n",
479
t->idx, t->result);
480
if(use_earlydata) {
481
curl_off_t sent;
482
curl_easy_getinfo(e, CURLINFO_EARLYDATA_SENT_T, &sent);
483
fprintf(stderr, "[t-%d] EarlyData: %ld\n", t->idx, (long)sent);
484
}
485
}
486
else {
487
curl_easy_cleanup(e);
488
fprintf(stderr, "unknown FINISHED???\n");
489
}
490
}
491
492
/* nothing happening, maintenance */
493
if(abort_paused) {
494
/* abort paused transfers */
495
for(i = 0; i < transfer_count; ++i) {
496
t = &transfers[i];
497
if(!t->done && t->paused && t->easy) {
498
curl_multi_remove_handle(multi_handle, t->easy);
499
t->done = 1;
500
active_transfers--;
501
fprintf(stderr, "[t-%d] ABORTED\n", t->idx);
502
}
503
}
504
}
505
else {
506
/* resume one paused transfer */
507
for(i = 0; i < transfer_count; ++i) {
508
t = &transfers[i];
509
if(!t->done && t->paused) {
510
t->resumed = 1;
511
t->paused = 0;
512
curl_easy_pause(t->easy, CURLPAUSE_CONT);
513
fprintf(stderr, "[t-%d] RESUMED\n", t->idx);
514
break;
515
}
516
}
517
}
518
519
while(active_transfers < max_parallel) {
520
for(i = 0; i < transfer_count; ++i) {
521
t = &transfers[i];
522
if(!t->started) {
523
t->easy = curl_easy_init();
524
if(!t->easy ||
525
setup(t->easy, url, t, http_version, host, share,
526
use_earlydata, fresh_connect)) {
527
fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);
528
result = 1;
529
goto cleanup;
530
}
531
curl_multi_add_handle(multi_handle, t->easy);
532
t->started = 1;
533
++active_transfers;
534
fprintf(stderr, "[t-%d] STARTED\n", t->idx);
535
break;
536
}
537
}
538
/* all started */
539
if(i == transfer_count)
540
break;
541
}
542
} while(m);
543
544
} while(active_transfers); /* as long as we have transfers going */
545
546
curl_multi_cleanup(multi_handle);
547
548
for(i = 0; i < transfer_count; ++i) {
549
t = &transfers[i];
550
if(t->out) {
551
fclose(t->out);
552
t->out = NULL;
553
}
554
if(t->easy) {
555
curl_easy_cleanup(t->easy);
556
t->easy = NULL;
557
}
558
if(t->result)
559
result = t->result;
560
}
561
free(transfers);
562
563
curl_share_cleanup(share);
564
curl_slist_free_all(host);
565
cleanup:
566
free(resolve);
567
568
return result;
569
#else
570
(void)argc;
571
(void)argv;
572
fprintf(stderr, "Not supported with this compiler.\n");
573
return 1;
574
#endif /* !_MSC_VER */
575
}
576
577