Path: blob/main/external/curl/tests/libtest/cli_hx_download.c
2653 views
/***************************************************************************1* _ _ ____ _2* Project ___| | | | _ \| |3* / __| | | | |_) | |4* | (__| |_| | _ <| |___5* \___|\___/|_| \_\_____|6*7* Copyright (C) Daniel Stenberg, <[email protected]>, et al.8*9* This software is licensed as described in the file COPYING, which10* you should have received as part of this distribution. The terms11* are also available at https://curl.se/docs/copyright.html.12*13* You may opt to use, copy, modify, merge, publish, distribute and/or sell14* copies of the Software, and permit persons to whom the Software is15* furnished to do so, under the terms of the COPYING file.16*17* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY18* KIND, either express or implied.19*20* SPDX-License-Identifier: curl21*22***************************************************************************/23#include "first.h"2425#include "testtrace.h"2627#include "curl_mem_undef.h"2829#if defined(USE_QUICHE) || defined(USE_OPENSSL)30#include <openssl/ssl.h>31#endif32#ifdef USE_WOLFSSL33#include <wolfssl/options.h>34#include <wolfssl/version.h>35#include <wolfssl/ssl.h>36#endif37#ifdef USE_GNUTLS38#include <gnutls/gnutls.h>39#endif40#ifdef USE_MBEDTLS41#include <mbedtls/ssl.h>42#endif43#ifdef USE_RUSTLS44#include <rustls.h>45#endif4647#include "memdebug.h"4849static int verbose_d = 1;5051struct transfer_d {52size_t idx;53CURL *curl;54char filename[128];55FILE *out;56curl_off_t recv_size;57curl_off_t fail_at;58curl_off_t pause_at;59curl_off_t abort_at;60int started;61int paused;62int resumed;63int done;64int checked_ssl;65CURLcode result;66};6768static size_t transfer_count_d = 1;69static struct transfer_d *transfer_d;70static int forbid_reuse_d = 0;7172static struct transfer_d *get_transfer_for_easy_d(CURL *curl)73{74size_t i;75for(i = 0; i < transfer_count_d; ++i) {76if(curl == transfer_d[i].curl)77return &transfer_d[i];78}79return NULL;80}8182static size_t my_write_d_cb(char *buf, size_t nitems, size_t buflen,83void *userdata)84{85struct transfer_d *t = userdata;86size_t blen = (nitems * buflen);87size_t nwritten;8889curl_mfprintf(stderr, "[t-%zu] RECV %zu bytes, "90"total=%" CURL_FORMAT_CURL_OFF_T ", "91"pause_at=%" CURL_FORMAT_CURL_OFF_T "\n",92t->idx, blen, t->recv_size, t->pause_at);93if(!t->out) {94curl_msnprintf(t->filename, sizeof(t->filename)-1, "download_%zu.data",95t->idx);96t->out = curlx_fopen(t->filename, "wb");97if(!t->out)98return 0;99}100101if(!t->resumed &&102t->recv_size < t->pause_at &&103((t->recv_size + (curl_off_t)blen) >= t->pause_at)) {104curl_mfprintf(stderr, "[t-%zu] PAUSE\n", t->idx);105t->paused = 1;106return CURL_WRITEFUNC_PAUSE;107}108109nwritten = fwrite(buf, nitems, buflen, t->out);110if(nwritten < blen) {111curl_mfprintf(stderr, "[t-%zu] write failure\n", t->idx);112return 0;113}114t->recv_size += (curl_off_t)nwritten;115if(t->fail_at > 0 && t->recv_size >= t->fail_at) {116curl_mfprintf(stderr, "[t-%zu] FAIL by write callback at "117"%" CURL_FORMAT_CURL_OFF_T " bytes\n", t->idx, t->recv_size);118return CURL_WRITEFUNC_ERROR;119}120121return (size_t)nwritten;122}123124static int my_progress_d_cb(void *userdata,125curl_off_t dltotal, curl_off_t dlnow,126curl_off_t ultotal, curl_off_t ulnow)127{128struct transfer_d *t = userdata;129(void)ultotal;130(void)ulnow;131(void)dltotal;132if(t->abort_at > 0 && dlnow >= t->abort_at) {133curl_mfprintf(stderr, "[t-%zu] ABORT by progress_cb at "134"%" CURL_FORMAT_CURL_OFF_T " bytes\n", t->idx, dlnow);135return 1;136}137138#if defined(USE_QUICHE) || defined(USE_OPENSSL) || defined(USE_WOLFSSL) || \139defined(USE_GNUTLS) || defined(USE_MBEDTLS) || defined(USE_RUSTLS)140if(!t->checked_ssl && dlnow > 0) {141struct curl_tlssessioninfo *tls;142CURLcode res;143144t->checked_ssl = TRUE;145res = curl_easy_getinfo(t->curl, CURLINFO_TLS_SSL_PTR, &tls);146if(res) {147curl_mfprintf(stderr, "[t-%zu] info CURLINFO_TLS_SSL_PTR failed: %d\n",148t->idx, res);149assert(0);150}151else {152switch(tls->backend) {153#if defined(USE_QUICHE) || defined(USE_OPENSSL)154case CURLSSLBACKEND_OPENSSL: {155const char *version = SSL_get_version((SSL*)tls->internals);156assert(version);157assert(strcmp(version, "unknown"));158curl_mfprintf(stderr, "[t-%zu] info OpenSSL using %s\n",159t->idx, version);160break;161}162#endif163#ifdef USE_WOLFSSL164case CURLSSLBACKEND_WOLFSSL: {165const char *version = wolfSSL_get_version((WOLFSSL*)tls->internals);166assert(version);167assert(strcmp(version, "unknown"));168curl_mfprintf(stderr, "[t-%zu] info wolfSSL using %s\n",169t->idx, version);170break;171}172#endif173#ifdef USE_GNUTLS174case CURLSSLBACKEND_GNUTLS: {175int v = gnutls_protocol_get_version((gnutls_session_t)tls->internals);176assert(v);177curl_mfprintf(stderr, "[t-%zu] info GnuTLS using %s\n",178t->idx, gnutls_protocol_get_name(v));179break;180}181#endif182#ifdef USE_MBEDTLS183case CURLSSLBACKEND_MBEDTLS: {184const char *version = mbedtls_ssl_get_version(185(mbedtls_ssl_context*)tls->internals);186assert(version);187assert(strcmp(version, "unknown"));188curl_mfprintf(stderr, "[t-%zu] info mbedTLS using %s\n",189t->idx, version);190break;191}192#endif193#ifdef USE_RUSTLS194case CURLSSLBACKEND_RUSTLS: {195int v = rustls_connection_get_protocol_version(196(struct rustls_connection*)tls->internals);197assert(v);198curl_mfprintf(stderr, "[t-%zu] info rustls TLS version 0x%x\n",199t->idx, v);200break;201}202#endif203default:204curl_mfprintf(stderr, "[t-%zu] info SSL_PTR backend=%d, ptr=%p\n",205t->idx, tls->backend, (void *)tls->internals);206break;207}208}209}210#endif211return 0;212}213214static int setup_hx_download(CURL *curl, const char *url, struct transfer_d *t,215long http_version, struct curl_slist *host,216CURLSH *share, int use_earlydata,217int fresh_connect)218{219curl_easy_setopt(curl, CURLOPT_SHARE, share);220curl_easy_setopt(curl, CURLOPT_URL, url);221curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, http_version);222curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);223curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);224curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");225curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, (long)(128 * 1024));226curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, my_write_d_cb);227curl_easy_setopt(curl, CURLOPT_WRITEDATA, t);228curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);229curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, my_progress_d_cb);230curl_easy_setopt(curl, CURLOPT_XFERINFODATA, t);231if(use_earlydata)232curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_EARLYDATA);233if(forbid_reuse_d)234curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1L);235if(host)236curl_easy_setopt(curl, CURLOPT_RESOLVE, host);237if(fresh_connect)238curl_easy_setopt(curl, CURLOPT_FRESH_CONNECT, 1L);239240/* please be verbose */241if(verbose_d) {242curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);243curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, cli_debug_cb);244}245246/* wait for pipe connection to confirm */247curl_easy_setopt(curl, CURLOPT_PIPEWAIT, 1L);248249return 0; /* all is good */250}251252static void usage_hx_download(const char *msg)253{254if(msg)255curl_mfprintf(stderr, "%s\n", msg);256curl_mfprintf(stderr,257"usage: [options] url\n"258" download a url with following options:\n"259" -a abort paused transfer\n"260" -m number max parallel downloads\n"261" -e use TLS early data when possible\n"262" -f forbid connection reuse\n"263" -n number total downloads\n");264curl_mfprintf(stderr,265" -A number abort transfer after `number` response bytes\n"266" -F number fail writing response after `number` response bytes\n"267" -M number max concurrent connections to a host\n"268" -P number pause transfer after `number` response bytes\n"269" -r <host>:<port>:<addr> resolve information\n"270" -T number max concurrent connections total\n"271" -V http_version (http/1.1, h2, h3) http version to use\n"272);273}274275/*276* Download a file over HTTP/2, take care of server push.277*/278static CURLcode test_cli_hx_download(const char *URL)279{280CURLM *multi = NULL;281struct CURLMsg *m;282CURLSH *share = NULL;283const char *url;284size_t i, n, max_parallel = 1;285size_t active_transfers;286size_t pause_offset = 0;287size_t abort_offset = 0;288size_t fail_offset = 0;289int abort_paused = 0, use_earlydata = 0;290struct transfer_d *t = NULL;291long http_version = CURL_HTTP_VERSION_2_0;292int ch;293struct curl_slist *host = NULL;294char *resolve = NULL;295size_t max_host_conns = 0;296size_t max_total_conns = 0;297int fresh_connect = 0;298CURLcode result = CURLE_OK;299300(void)URL;301302while((ch = cgetopt(test_argc, test_argv, "aefhm:n:xA:F:M:P:r:T:V:"))303!= -1) {304switch(ch) {305case 'h':306usage_hx_download(NULL);307result = (CURLcode)2;308goto optcleanup;309case 'a':310abort_paused = 1;311break;312case 'e':313use_earlydata = 1;314break;315case 'f':316forbid_reuse_d = 1;317break;318case 'm':319max_parallel = (size_t)atol(coptarg);320break;321case 'n':322transfer_count_d = (size_t)atol(coptarg);323break;324case 'x':325fresh_connect = 1;326break;327case 'A':328abort_offset = (size_t)atol(coptarg);329break;330case 'F':331fail_offset = (size_t)atol(coptarg);332break;333case 'M':334max_host_conns = (size_t)atol(coptarg);335break;336case 'P':337pause_offset = (size_t)atol(coptarg);338break;339case 'r':340free(resolve);341resolve = strdup(coptarg);342break;343case 'T':344max_total_conns = (size_t)atol(coptarg);345break;346case 'V': {347if(!strcmp("http/1.1", coptarg))348http_version = CURL_HTTP_VERSION_1_1;349else if(!strcmp("h2", coptarg))350http_version = CURL_HTTP_VERSION_2_0;351else if(!strcmp("h3", coptarg))352http_version = CURL_HTTP_VERSION_3ONLY;353else {354usage_hx_download("invalid http version");355result = (CURLcode)1;356goto optcleanup;357}358break;359}360default:361usage_hx_download("invalid option");362result = (CURLcode)1;363goto optcleanup;364}365}366test_argc -= coptind;367test_argv += coptind;368369curl_global_trace("ids,time,http/2,http/3");370371if(test_argc != 1) {372usage_hx_download("not enough arguments");373result = (CURLcode)2;374goto optcleanup;375}376url = test_argv[0];377378if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {379curl_mfprintf(stderr, "curl_global_init() failed\n");380result = (CURLcode)3;381goto optcleanup;382}383384if(resolve)385host = curl_slist_append(NULL, resolve);386387share = curl_share_init();388if(!share) {389curl_mfprintf(stderr, "error allocating share\n");390result = (CURLcode)1;391goto cleanup;392}393curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);394curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);395curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);396/* curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); */397curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL);398curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS);399400transfer_d = calloc(transfer_count_d, sizeof(*transfer_d));401if(!transfer_d) {402curl_mfprintf(stderr, "error allocating transfer structs\n");403result = (CURLcode)1;404goto cleanup;405}406407multi = curl_multi_init();408curl_multi_setopt(multi, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);409curl_multi_setopt(multi, CURLMOPT_MAX_TOTAL_CONNECTIONS,410(long)max_total_conns);411curl_multi_setopt(multi, CURLMOPT_MAX_HOST_CONNECTIONS,412(long)max_host_conns);413414active_transfers = 0;415for(i = 0; i < transfer_count_d; ++i) {416t = &transfer_d[i];417t->idx = i;418t->abort_at = (curl_off_t)abort_offset;419t->fail_at = (curl_off_t)fail_offset;420t->pause_at = (curl_off_t)pause_offset;421}422423n = (max_parallel < transfer_count_d) ? max_parallel : transfer_count_d;424for(i = 0; i < n; ++i) {425t = &transfer_d[i];426t->curl = curl_easy_init();427if(!t->curl ||428setup_hx_download(t->curl, url, t, http_version, host, share,429use_earlydata, fresh_connect)) {430curl_mfprintf(stderr, "[t-%zu] FAILED setup\n", i);431result = (CURLcode)1;432goto cleanup;433}434curl_multi_add_handle(multi, t->curl);435t->started = 1;436++active_transfers;437curl_mfprintf(stderr, "[t-%zu] STARTED\n", t->idx);438}439440do {441int still_running; /* keep number of running handles */442CURLMcode mc = curl_multi_perform(multi, &still_running);443444if(still_running) {445/* wait for activity, timeout or "nothing" */446mc = curl_multi_poll(multi, NULL, 0, 1000, NULL);447}448449if(mc)450break;451452do {453int msgq = 0;454m = curl_multi_info_read(multi, &msgq);455if(m && (m->msg == CURLMSG_DONE)) {456CURL *easy = m->easy_handle;457--active_transfers;458curl_multi_remove_handle(multi, easy);459t = get_transfer_for_easy_d(easy);460if(t) {461t->done = 1;462t->result = m->data.result;463curl_mfprintf(stderr, "[t-%zu] FINISHED with result %d\n",464t->idx, t->result);465if(use_earlydata) {466curl_off_t sent;467curl_easy_getinfo(easy, CURLINFO_EARLYDATA_SENT_T, &sent);468curl_mfprintf(stderr, "[t-%zu] EarlyData: "469"%" CURL_FORMAT_CURL_OFF_T "\n", t->idx, sent);470}471}472else {473curl_easy_cleanup(easy);474curl_mfprintf(stderr, "unknown FINISHED???\n");475}476}477478/* nothing happening, maintenance */479if(abort_paused) {480/* abort paused transfers */481for(i = 0; i < transfer_count_d; ++i) {482t = &transfer_d[i];483if(!t->done && t->paused && t->curl) {484curl_multi_remove_handle(multi, t->curl);485t->done = 1;486active_transfers--;487curl_mfprintf(stderr, "[t-%zu] ABORTED\n", t->idx);488}489}490}491else {492/* resume one paused transfer */493for(i = 0; i < transfer_count_d; ++i) {494t = &transfer_d[i];495if(!t->done && t->paused) {496t->resumed = 1;497t->paused = 0;498curl_easy_pause(t->curl, CURLPAUSE_CONT);499curl_mfprintf(stderr, "[t-%zu] RESUMED\n", t->idx);500break;501}502}503}504505while(active_transfers < max_parallel) {506for(i = 0; i < transfer_count_d; ++i) {507t = &transfer_d[i];508if(!t->started) {509t->curl = curl_easy_init();510if(!t->curl ||511setup_hx_download(t->curl, url, t, http_version, host, share,512use_earlydata, fresh_connect)) {513curl_mfprintf(stderr, "[t-%zu] FAILED setup\n", i);514result = (CURLcode)1;515goto cleanup;516}517curl_multi_add_handle(multi, t->curl);518t->started = 1;519++active_transfers;520curl_mfprintf(stderr, "[t-%zu] STARTED\n", t->idx);521break;522}523}524/* all started */525if(i == transfer_count_d)526break;527}528} while(m);529530} while(active_transfers); /* as long as we have transfers going */531532cleanup:533534curl_multi_cleanup(multi);535536if(transfer_d) {537for(i = 0; i < transfer_count_d; ++i) {538t = &transfer_d[i];539if(t->out) {540curlx_fclose(t->out);541t->out = NULL;542}543if(t->curl) {544curl_easy_cleanup(t->curl);545t->curl = NULL;546}547if(t->result)548result = t->result;549else /* on success we expect ssl to have been checked */550assert(t->checked_ssl);551}552free(transfer_d);553}554555curl_share_cleanup(share);556curl_slist_free_all(host);557558curl_global_cleanup();559560optcleanup:561562free(resolve);563564return result;565}566567568