Path: blob/main/external/curl/tests/http/clients/hx-upload.c
2066 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/* <DESC>24* HTTP upload tests and tweaks25* </DESC>26*/27/* curl stuff */28#include <curl/curl.h>2930#include <stdio.h>31#include <stdlib.h>32#include <string.h>3334#ifndef _MSC_VER35/* somewhat Unix-specific */36#include <unistd.h> /* getopt() */37#endif3839#ifndef CURLPIPE_MULTIPLEX40#error "too old libcurl"41#endif4243#ifndef _MSC_VER44static int verbose = 1;4546static void log_line_start(FILE *log, const char *idsbuf, curl_infotype type)47{48/*49* This is the trace look that is similar to what libcurl makes on its50* own.51*/52static const char * const s_infotype[] = {53"* ", "< ", "> ", "{ ", "} ", "{ ", "} "54};55if(idsbuf && *idsbuf)56fprintf(log, "%s%s", idsbuf, s_infotype[type]);57else58fputs(s_infotype[type], log);59}6061#define TRC_IDS_FORMAT_IDS_1 "[%" CURL_FORMAT_CURL_OFF_T "-x] "62#define TRC_IDS_FORMAT_IDS_2 "[%" CURL_FORMAT_CURL_OFF_T "-%" \63CURL_FORMAT_CURL_OFF_T "] "64/*65** callback for CURLOPT_DEBUGFUNCTION66*/67static int debug_cb(CURL *handle, curl_infotype type,68char *data, size_t size,69void *userdata)70{71FILE *output = stderr;72static int newl = 0;73static int traced_data = 0;74char idsbuf[60];75curl_off_t xfer_id, conn_id;7677(void)handle; /* not used */78(void)userdata;7980if(!curl_easy_getinfo(handle, CURLINFO_XFER_ID, &xfer_id) && xfer_id >= 0) {81if(!curl_easy_getinfo(handle, CURLINFO_CONN_ID, &conn_id) &&82conn_id >= 0) {83curl_msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_2, xfer_id,84conn_id);85}86else {87curl_msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_1, xfer_id);88}89}90else91idsbuf[0] = 0;9293switch(type) {94case CURLINFO_HEADER_OUT:95if(size > 0) {96size_t st = 0;97size_t i;98for(i = 0; i < size - 1; i++) {99if(data[i] == '\n') { /* LF */100if(!newl) {101log_line_start(output, idsbuf, type);102}103(void)fwrite(data + st, i - st + 1, 1, output);104st = i + 1;105newl = 0;106}107}108if(!newl)109log_line_start(output, idsbuf, type);110(void)fwrite(data + st, i - st + 1, 1, output);111}112newl = (size && (data[size - 1] != '\n')) ? 1 : 0;113traced_data = 0;114break;115case CURLINFO_TEXT:116case CURLINFO_HEADER_IN:117if(!newl)118log_line_start(output, idsbuf, type);119(void)fwrite(data, size, 1, output);120newl = (size && (data[size - 1] != '\n')) ? 1 : 0;121traced_data = 0;122break;123case CURLINFO_DATA_OUT:124case CURLINFO_DATA_IN:125case CURLINFO_SSL_DATA_IN:126case CURLINFO_SSL_DATA_OUT:127if(!traced_data) {128if(!newl)129log_line_start(output, idsbuf, type);130fprintf(output, "[%ld bytes data]\n", (long)size);131newl = 0;132traced_data = 1;133}134break;135default: /* nada */136newl = 0;137traced_data = 1;138break;139}140141return 0;142}143144struct transfer {145int idx;146CURL *easy;147const char *method;148char filename[128];149FILE *out;150curl_off_t send_total;151curl_off_t recv_size;152curl_off_t send_size;153curl_off_t fail_at;154curl_off_t pause_at;155curl_off_t abort_at;156int started;157int paused;158int resumed;159int done;160};161162static size_t transfer_count = 1;163static struct transfer *transfers;164static int forbid_reuse = 0;165166static struct transfer *get_transfer_for_easy(CURL *easy)167{168size_t i;169for(i = 0; i < transfer_count; ++i) {170if(easy == transfers[i].easy)171return &transfers[i];172}173return NULL;174}175176static size_t my_write_cb(char *buf, size_t nitems, size_t buflen,177void *userdata)178{179struct transfer *t = userdata;180size_t blen = (nitems * buflen);181size_t nwritten;182183fprintf(stderr, "[t-%d] RECV %ld bytes, total=%ld, pause_at=%ld\n",184t->idx, (long)blen, (long)t->recv_size, (long)t->pause_at);185if(!t->out) {186curl_msnprintf(t->filename, sizeof(t->filename)-1, "download_%u.data",187t->idx);188t->out = fopen(t->filename, "wb");189if(!t->out)190return 0;191}192193nwritten = fwrite(buf, nitems, buflen, t->out);194if(nwritten < blen) {195fprintf(stderr, "[t-%d] write failure\n", t->idx);196return 0;197}198t->recv_size += (curl_off_t)nwritten;199return (size_t)nwritten;200}201202static size_t my_read_cb(char *buf, size_t nitems, size_t buflen,203void *userdata)204{205struct transfer *t = userdata;206size_t blen = (nitems * buflen);207size_t nread;208209if(t->send_total <= t->send_size)210nread = 0;211else if((t->send_total - t->send_size) < (curl_off_t)blen)212nread = (size_t)(t->send_total - t->send_size);213else214nread = blen;215216fprintf(stderr, "[t-%d] SEND %ld bytes, total=%ld, pause_at=%ld\n",217t->idx, (long)nread, (long)t->send_total, (long)t->pause_at);218219if(!t->resumed &&220t->send_size < t->pause_at &&221((t->send_size + (curl_off_t)blen) >= t->pause_at)) {222fprintf(stderr, "[t-%d] PAUSE\n", t->idx);223t->paused = 1;224return CURL_READFUNC_PAUSE;225}226227memset(buf, 'x', nread);228t->send_size += (curl_off_t)nread;229if(t->fail_at > 0 && t->send_size >= t->fail_at) {230fprintf(stderr, "[t-%d] ABORT by read callback at %ld bytes\n",231t->idx, (long)t->send_size);232return CURL_READFUNC_ABORT;233}234return (size_t)nread;235}236237static int my_progress_cb(void *userdata,238curl_off_t dltotal, curl_off_t dlnow,239curl_off_t ultotal, curl_off_t ulnow)240{241struct transfer *t = userdata;242(void)ultotal;243(void)dlnow;244(void)dltotal;245if(t->abort_at > 0 && ulnow >= t->abort_at) {246fprintf(stderr, "[t-%d] ABORT by progress_cb at %ld bytes sent\n",247t->idx, (long)ulnow);248return 1;249}250return 0;251}252253static int setup(CURL *hnd, const char *url, struct transfer *t,254long http_version, struct curl_slist *host,255CURLSH *share, int use_earlydata, int announce_length)256{257curl_easy_setopt(hnd, CURLOPT_SHARE, share);258curl_easy_setopt(hnd, CURLOPT_URL, url);259curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, http_version);260curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L);261curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 0L);262curl_easy_setopt(hnd, CURLOPT_BUFFERSIZE, (long)(128 * 1024));263curl_easy_setopt(hnd, CURLOPT_FOLLOWLOCATION, CURLFOLLOW_OBEYCODE);264curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, my_write_cb);265curl_easy_setopt(hnd, CURLOPT_WRITEDATA, t);266if(use_earlydata)267curl_easy_setopt(hnd, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_EARLYDATA);268269if(!t->method || !strcmp("PUT", t->method))270curl_easy_setopt(hnd, CURLOPT_UPLOAD, 1L);271else if(!strcmp("POST", t->method))272curl_easy_setopt(hnd, CURLOPT_POST, 1L);273else {274fprintf(stderr, "unsupported method '%s'\n", t->method);275return 1;276}277curl_easy_setopt(hnd, CURLOPT_READFUNCTION, my_read_cb);278curl_easy_setopt(hnd, CURLOPT_READDATA, t);279if(announce_length)280curl_easy_setopt(hnd, CURLOPT_INFILESIZE_LARGE, t->send_total);281282curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 0L);283curl_easy_setopt(hnd, CURLOPT_XFERINFOFUNCTION, my_progress_cb);284curl_easy_setopt(hnd, CURLOPT_XFERINFODATA, t);285if(forbid_reuse)286curl_easy_setopt(hnd, CURLOPT_FORBID_REUSE, 1L);287if(host)288curl_easy_setopt(hnd, CURLOPT_RESOLVE, host);289290/* please be verbose */291if(verbose) {292curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L);293curl_easy_setopt(hnd, CURLOPT_DEBUGFUNCTION, debug_cb);294}295296#if (CURLPIPE_MULTIPLEX > 0)297/* wait for pipe connection to confirm */298curl_easy_setopt(hnd, CURLOPT_PIPEWAIT, 1L);299#endif300return 0; /* all is good */301}302303static void usage(const char *msg)304{305if(msg)306fprintf(stderr, "%s\n", msg);307fprintf(stderr,308"usage: [options] url\n"309" upload to a url with following options:\n"310" -a abort paused transfer\n"311" -e use TLS earlydata\n"312" -m number max parallel uploads\n"313" -n number total uploads\n"314" -A number abort transfer after `number` request body bytes\n"315" -F number fail reading request body after `number` of bytes\n"316" -P number pause transfer after `number` request body bytes\n"317" -r <host>:<port>:<addr> resolve information\n"318" -S number size to upload\n"319" -V http_version (http/1.1, h2, h3) http version to use\n"320);321}322#endif /* !_MSC_VER */323324/*325* Download a file over HTTP/2, take care of server push.326*/327int main(int argc, char *argv[])328{329#ifndef _MSC_VER330CURLM *multi_handle;331CURLSH *share;332const char *url;333const char *method = "PUT";334size_t i, n, max_parallel = 1;335size_t active_transfers;336size_t pause_offset = 0;337size_t abort_offset = 0;338size_t fail_offset = 0;339size_t send_total = (128 * 1024);340int abort_paused = 0;341int reuse_easy = 0;342int use_earlydata = 0;343int announce_length = 0;344struct transfer *t;345int http_version = CURL_HTTP_VERSION_2_0;346struct curl_slist *host = NULL;347const char *resolve = NULL;348int ch;349350while((ch = getopt(argc, argv, "aefhlm:n:A:F:M:P:r:RS:V:")) != -1) {351switch(ch) {352case 'h':353usage(NULL);354return 2;355case 'a':356abort_paused = 1;357break;358case 'e':359use_earlydata = 1;360break;361case 'f':362forbid_reuse = 1;363break;364case 'l':365announce_length = 1;366break;367case 'm':368max_parallel = (size_t)strtol(optarg, NULL, 10);369break;370case 'n':371transfer_count = (size_t)strtol(optarg, NULL, 10);372break;373case 'A':374abort_offset = (size_t)strtol(optarg, NULL, 10);375break;376case 'F':377fail_offset = (size_t)strtol(optarg, NULL, 10);378break;379case 'M':380method = optarg;381break;382case 'P':383pause_offset = (size_t)strtol(optarg, NULL, 10);384break;385case 'r':386resolve = optarg;387break;388case 'R':389reuse_easy = 1;390break;391case 'S':392send_total = (size_t)strtol(optarg, NULL, 10);393break;394case 'V': {395if(!strcmp("http/1.1", optarg))396http_version = CURL_HTTP_VERSION_1_1;397else if(!strcmp("h2", optarg))398http_version = CURL_HTTP_VERSION_2_0;399else if(!strcmp("h3", optarg))400http_version = CURL_HTTP_VERSION_3ONLY;401else {402usage("invalid http version");403return 1;404}405break;406}407default:408usage("invalid option");409return 1;410}411}412argc -= optind;413argv += optind;414415if(max_parallel > 1 && reuse_easy) {416usage("cannot mix -R and -P");417return 2;418}419420curl_global_init(CURL_GLOBAL_DEFAULT);421curl_global_trace("ids,time,http/2,http/3");422423if(argc != 1) {424usage("not enough arguments");425return 2;426}427url = argv[0];428429if(resolve)430host = curl_slist_append(NULL, resolve);431432share = curl_share_init();433if(!share) {434fprintf(stderr, "error allocating share\n");435return 1;436}437curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);438curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);439curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);440curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);441curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL);442curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS);443444transfers = calloc(transfer_count, sizeof(*transfers));445if(!transfers) {446fprintf(stderr, "error allocating transfer structs\n");447return 1;448}449450active_transfers = 0;451for(i = 0; i < transfer_count; ++i) {452t = &transfers[i];453t->idx = (int)i;454t->method = method;455t->send_total = (curl_off_t)send_total;456t->abort_at = (curl_off_t)abort_offset;457t->fail_at = (curl_off_t)fail_offset;458t->pause_at = (curl_off_t)pause_offset;459}460461if(reuse_easy) {462CURL *easy = curl_easy_init();463CURLcode rc = CURLE_OK;464if(!easy) {465fprintf(stderr, "failed to init easy handle\n");466return 1;467}468for(i = 0; i < transfer_count; ++i) {469t = &transfers[i];470t->easy = easy;471if(setup(t->easy, url, t, http_version, host, share, use_earlydata,472announce_length)) {473fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);474return 1;475}476477fprintf(stderr, "[t-%d] STARTING\n", t->idx);478rc = curl_easy_perform(easy);479fprintf(stderr, "[t-%d] DONE -> %d\n", t->idx, rc);480t->easy = NULL;481curl_easy_reset(easy);482}483curl_easy_cleanup(easy);484}485else {486multi_handle = curl_multi_init();487curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);488489n = (max_parallel < transfer_count) ? max_parallel : transfer_count;490for(i = 0; i < n; ++i) {491t = &transfers[i];492t->easy = curl_easy_init();493if(!t->easy || setup(t->easy, url, t, http_version, host, share,494use_earlydata, announce_length)) {495fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);496return 1;497}498curl_multi_add_handle(multi_handle, t->easy);499t->started = 1;500++active_transfers;501fprintf(stderr, "[t-%d] STARTED\n", t->idx);502}503504do {505int still_running; /* keep number of running handles */506CURLMcode mc = curl_multi_perform(multi_handle, &still_running);507struct CURLMsg *m;508509if(still_running) {510/* wait for activity, timeout or "nothing" */511mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL);512}513514if(mc)515break;516517do {518int msgq = 0;519m = curl_multi_info_read(multi_handle, &msgq);520if(m && (m->msg == CURLMSG_DONE)) {521CURL *e = m->easy_handle;522--active_transfers;523curl_multi_remove_handle(multi_handle, e);524t = get_transfer_for_easy(e);525if(t) {526long res_status;527curl_easy_getinfo(e, CURLINFO_RESPONSE_CODE, &res_status);528t->done = 1;529fprintf(stderr, "[t-%d] FINISHED, result=%d, response=%ld\n",530t->idx, m->data.result, res_status);531if(use_earlydata) {532curl_off_t sent;533curl_easy_getinfo(e, CURLINFO_EARLYDATA_SENT_T, &sent);534fprintf(stderr, "[t-%d] EarlyData: %ld\n", t->idx, (long)sent);535}536}537else {538curl_easy_cleanup(e);539fprintf(stderr, "unknown FINISHED???\n");540}541}542543544/* nothing happening, maintenance */545if(abort_paused) {546/* abort paused transfers */547for(i = 0; i < transfer_count; ++i) {548t = &transfers[i];549if(!t->done && t->paused && t->easy) {550curl_multi_remove_handle(multi_handle, t->easy);551t->done = 1;552active_transfers--;553fprintf(stderr, "[t-%d] ABORTED\n", t->idx);554}555}556}557else {558/* resume one paused transfer */559for(i = 0; i < transfer_count; ++i) {560t = &transfers[i];561if(!t->done && t->paused) {562t->resumed = 1;563t->paused = 0;564curl_easy_pause(t->easy, CURLPAUSE_CONT);565fprintf(stderr, "[t-%d] RESUMED\n", t->idx);566break;567}568}569}570571while(active_transfers < max_parallel) {572for(i = 0; i < transfer_count; ++i) {573t = &transfers[i];574if(!t->started) {575t->easy = curl_easy_init();576if(!t->easy || setup(t->easy, url, t, http_version, host,577share, use_earlydata, announce_length)) {578fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);579return 1;580}581curl_multi_add_handle(multi_handle, t->easy);582t->started = 1;583++active_transfers;584fprintf(stderr, "[t-%d] STARTED\n", t->idx);585break;586}587}588/* all started */589if(i == transfer_count)590break;591}592} while(m);593594} while(active_transfers); /* as long as we have transfers going */595596curl_multi_cleanup(multi_handle);597}598599for(i = 0; i < transfer_count; ++i) {600t = &transfers[i];601if(t->out) {602fclose(t->out);603t->out = NULL;604}605if(t->easy) {606curl_easy_cleanup(t->easy);607t->easy = NULL;608}609}610free(transfers);611curl_share_cleanup(share);612613return 0;614#else615(void)argc;616(void)argv;617fprintf(stderr, "Not supported with this compiler.\n");618return 1;619#endif /* !_MSC_VER */620}621622623