From ed07f59841b6398f8bafe51ec8dafedddf9c1177 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sat, 24 May 2025 00:30:22 +0200 Subject: [PATCH] tool_operate: split up single_transfer Complexity reduced from 124 to 83 Remove whitelisting of this function from the complexity script. Closes #17437 --- scripts/top-complexity | 3 +- src/tool_operate.c | 553 ++++++++++++++++++++++------------------- 2 files changed, 299 insertions(+), 257 deletions(-) diff --git a/scripts/top-complexity b/scripts/top-complexity index c7129a1eb8..675d83b185 100755 --- a/scripts/top-complexity +++ b/scripts/top-complexity @@ -75,8 +75,7 @@ my @output=`$cmd`; # these functions can have these scores, but not higher my %whitelist = ( - 'getparameter' => 142, - 'single_transfer' => 124 + 'getparameter' => 142 ); # functions with complexity above this level causes the function to return error diff --git a/src/tool_operate.c b/src/tool_operate.c index 1406b8790f..af9632c781 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -796,6 +796,283 @@ static CURLcode append2query(struct GlobalConfig *global, return result; } +static CURLcode etag_compare(struct GlobalConfig *global, + struct OperationConfig *config) +{ + CURLcode result = CURLE_OK; + char *etag_from_file = NULL; + char *header = NULL; + ParameterError pe; + + /* open file for reading: */ + FILE *file = fopen(config->etag_compare_file, FOPEN_READTEXT); + if(!file) + warnf(global, "Failed to open %s: %s", config->etag_compare_file, + strerror(errno)); + + if((PARAM_OK == file2string(&etag_from_file, file)) && + etag_from_file) { + header = aprintf("If-None-Match: %s", etag_from_file); + tool_safefree(etag_from_file); + } + else + header = aprintf("If-None-Match: \"\""); + + if(!header) { + if(file) + fclose(file); + errorf(global, + "Failed to allocate memory for custom etag header"); + return CURLE_OUT_OF_MEMORY; + } + + /* add Etag from file to list of custom headers */ + pe = add2list(&config->headers, header); + tool_safefree(header); + + if(file) + fclose(file); + if(pe != PARAM_OK) + result = CURLE_OUT_OF_MEMORY; + return result; +} + +static CURLcode etag_store(struct GlobalConfig *global, + struct OperationConfig *config, + struct OutStruct *etag_save, + bool *skip) +{ + if(config->create_dirs) { + CURLcode result = create_dir_hierarchy(config->etag_save_file, global); + if(result) + return result; + } + + /* open file for output: */ + if(strcmp(config->etag_save_file, "-")) { + FILE *newfile = fopen(config->etag_save_file, "ab"); + if(!newfile) { + struct State *state = &config->state; + warnf(global, "Failed creating file for saving etags: \"%s\". " + "Skip this transfer", config->etag_save_file); + tool_safefree(state->outfiles); + glob_cleanup(&state->urls); + *skip = TRUE; + return CURLE_OK; + } + else { + etag_save->filename = config->etag_save_file; + etag_save->s_isreg = TRUE; + etag_save->fopened = TRUE; + etag_save->stream = newfile; + } + } + else { + /* always use binary mode for protocol header output */ + CURL_SET_BINMODE(etag_save->stream); + } + return CURLE_OK; +} + +static CURLcode setup_headerfile(struct GlobalConfig *global, + struct OperationConfig *config, + struct per_transfer *per, + struct OutStruct *heads) +{ + /* open file for output: */ + if(!strcmp(config->headerfile, "%")) { + heads->stream = stderr; + /* use binary mode for protocol header output */ + CURL_SET_BINMODE(heads->stream); + } + else if(strcmp(config->headerfile, "-")) { + FILE *newfile; + + /* + * Since every transfer has its own file handle for dumping + * the headers, we need to open it in append mode, since transfers + * might finish in any order. + * The first transfer just clears the file. + * + * Consider placing the file handle inside the OperationConfig, so + * that it does not need to be opened/closed for every transfer. + */ + if(config->create_dirs) { + CURLcode result = create_dir_hierarchy(config->headerfile, global); + /* create_dir_hierarchy shows error upon CURLE_WRITE_ERROR */ + if(result) + return result; + } + if(!per->prev || per->prev->config != config) { + newfile = fopen(config->headerfile, "wb"); + if(newfile) + fclose(newfile); + } + newfile = fopen(config->headerfile, "ab"); + + if(!newfile) { + errorf(global, "Failed to open %s", config->headerfile); + return CURLE_WRITE_ERROR; + } + else { + heads->filename = config->headerfile; + heads->s_isreg = TRUE; + heads->fopened = TRUE; + heads->stream = newfile; + } + } + else { + /* always use binary mode for protocol header output */ + CURL_SET_BINMODE(heads->stream); + } + return CURLE_OK; +} + +static CURLcode setup_outfile(struct GlobalConfig *global, + struct OperationConfig *config, + struct per_transfer *per, + struct OutStruct *outs, + bool *skipped) +{ + /* + * We have specified a filename to store the result in, or we have + * decided we want to use the remote filename. + */ + struct State *state = &config->state; + + if(!per->outfile) { + /* extract the filename from the URL */ + CURLcode result = get_url_file_name(global, &per->outfile, per->url); + if(result) { + errorf(global, "Failed to extract a filename" + " from the URL to use for storage"); + return result; + } + } + else if(state->urls) { + /* fill '#1' ... '#9' terms from URL pattern */ + char *storefile = per->outfile; + CURLcode result = glob_match_url(&per->outfile, storefile, state->urls); + tool_safefree(storefile); + if(result) { + /* bad globbing */ + warnf(global, "bad output glob"); + return result; + } + if(!*per->outfile) { + warnf(global, "output glob produces empty string"); + return CURLE_WRITE_ERROR; + } + } + DEBUGASSERT(per->outfile); + + if(config->output_dir && *config->output_dir) { + char *d = aprintf("%s/%s", config->output_dir, per->outfile); + if(!d) + return CURLE_WRITE_ERROR; + free(per->outfile); + per->outfile = d; + } + /* Create the directory hierarchy, if not pre-existent to a multiple + file output call */ + + if(config->create_dirs) { + CURLcode result = create_dir_hierarchy(per->outfile, global); + /* create_dir_hierarchy shows error upon CURLE_WRITE_ERROR */ + if(result) + return result; + } + + if(config->skip_existing) { + struct_stat fileinfo; + if(!stat(per->outfile, &fileinfo)) { + /* file is present */ + notef(global, "skips transfer, \"%s\" exists locally", + per->outfile); + per->skip = TRUE; + *skipped = TRUE; + } + } + + if(config->resume_from_current) { + /* We are told to continue from where we are now. Get the size + of the file as it is now and open it for append instead */ + struct_stat fileinfo; + /* VMS -- Danger, the filesize is only valid for stream files */ + if(0 == stat(per->outfile, &fileinfo)) + /* set offset to current file size: */ + config->resume_from = fileinfo.st_size; + else + /* let offset be 0 */ + config->resume_from = 0; + } + + if(config->resume_from) { +#ifdef __VMS + /* open file for output, forcing VMS output format into stream + mode which is needed for stat() call above to always work. */ + FILE *file = fopen(outfile, "ab", + "ctx=stm", "rfm=stmlf", "rat=cr", "mrs=0"); +#else + /* open file for output: */ + FILE *file = fopen(per->outfile, "ab"); +#endif + if(!file) { + errorf(global, "cannot open '%s'", per->outfile); + return CURLE_WRITE_ERROR; + } + outs->fopened = TRUE; + outs->stream = file; + outs->init = config->resume_from; + } + else { + outs->stream = NULL; /* open when needed */ + } + outs->filename = per->outfile; + outs->s_isreg = TRUE; + return CURLE_OK; +} + +static void check_stdin_upload(struct GlobalConfig *global, + struct OperationConfig *config, + struct per_transfer *per) +{ + /* count to see if there are more than one auth bit set + in the authtype field */ + int authbits = 0; + int bitcheck = 0; + while(bitcheck < 32) { + if(config->authtype & (1UL << bitcheck++)) { + authbits++; + if(authbits > 1) { + /* more than one, we are done! */ + break; + } + } + } + + /* + * If the user has also selected --anyauth or --proxy-anyauth + * we should warn them. + */ + if(config->proxyanyauth || (authbits > 1)) { + warnf(global, + "Using --anyauth or --proxy-anyauth with upload from stdin" + " involves a big risk of it not working. Use a temporary" + " file or a fixed auth type instead"); + } + + DEBUGASSERT(per->infdopen == FALSE); + DEBUGASSERT(per->infd == STDIN_FILENO); + + CURL_SET_BINMODE(stdin); + if(!strcmp(per->uploadfile, ".")) { + if(curlx_nonblock((curl_socket_t)per->infd, TRUE) < 0) + warnf(global, + "fcntl failed on fd=%d: %s", per->infd, strerror(errno)); + } +} + /* create the next (singular) transfer */ static CURLcode single_transfer(struct GlobalConfig *global, struct OperationConfig *config, @@ -931,73 +1208,16 @@ static CURLcode single_transfer(struct GlobalConfig *global, /* --etag-compare */ if(config->etag_compare_file) { - char *etag_from_file = NULL; - char *header = NULL; - ParameterError pe; - - /* open file for reading: */ - FILE *file = fopen(config->etag_compare_file, FOPEN_READTEXT); - if(!file) - warnf(global, "Failed to open %s: %s", config->etag_compare_file, - strerror(errno)); - - if((PARAM_OK == file2string(&etag_from_file, file)) && - etag_from_file) { - header = aprintf("If-None-Match: %s", etag_from_file); - tool_safefree(etag_from_file); - } - else - header = aprintf("If-None-Match: \"\""); - - if(!header) { - if(file) - fclose(file); - errorf(global, - "Failed to allocate memory for custom etag header"); - result = CURLE_OUT_OF_MEMORY; + result = etag_compare(global, config); + if(result) break; - } - - /* add Etag from file to list of custom headers */ - pe = add2list(&config->headers, header); - tool_safefree(header); - - if(file) - fclose(file); - if(pe != PARAM_OK) { - result = CURLE_OUT_OF_MEMORY; - break; - } } if(config->etag_save_file) { - if(config->create_dirs) { - result = create_dir_hierarchy(config->etag_save_file, global); - if(result) - break; - } - - /* open file for output: */ - if(strcmp(config->etag_save_file, "-")) { - FILE *newfile = fopen(config->etag_save_file, "ab"); - if(!newfile) { - warnf(global, "Failed creating file for saving etags: \"%s\". " - "Skip this transfer", config->etag_save_file); - tool_safefree(state->outfiles); - glob_cleanup(&state->urls); - return CURLE_OK; - } - else { - etag_save->filename = config->etag_save_file; - etag_save->s_isreg = TRUE; - etag_save->fopened = TRUE; - etag_save->stream = newfile; - } - } - else { - /* always use binary mode for protocol header output */ - CURL_SET_BINMODE(etag_save->stream); - } + bool badetag = FALSE; + result = etag_store(global, config, etag_save, &badetag); + if(result || badetag) + break; } curl = curl_easy_init(); @@ -1037,55 +1257,10 @@ static CURLcode single_transfer(struct GlobalConfig *global, /* Single header file for all URLs */ if(config->headerfile) { - /* open file for output: */ - if(!strcmp(config->headerfile, "%")) { - heads->stream = stderr; - /* use binary mode for protocol header output */ - CURL_SET_BINMODE(heads->stream); - } - else if(strcmp(config->headerfile, "-")) { - FILE *newfile; - - /* - * Since every transfer has its own file handle for dumping - * the headers, we need to open it in append mode, since transfers - * might finish in any order. - * The first transfer just clears the file. - * - * Consider placing the file handle inside the OperationConfig, so - * that it does not need to be opened/closed for every transfer. - */ - if(config->create_dirs) { - result = create_dir_hierarchy(config->headerfile, global); - /* create_dir_hierarchy shows error upon CURLE_WRITE_ERROR */ - if(result) - break; - } - if(!per->prev || per->prev->config != config) { - newfile = fopen(config->headerfile, "wb"); - if(newfile) - fclose(newfile); - } - newfile = fopen(config->headerfile, "ab"); - - if(!newfile) { - errorf(global, "Failed to open %s", config->headerfile); - result = CURLE_WRITE_ERROR; - break; - } - else { - heads->filename = config->headerfile; - heads->s_isreg = TRUE; - heads->fopened = TRUE; - heads->stream = newfile; - } - } - else { - /* always use binary mode for protocol header output */ - CURL_SET_BINMODE(heads->stream); - } + result = setup_headerfile(global, config, per, heads); + if(result) + break; } - hdrcbdata = &per->hdrcbdata; outs = &per->outs; @@ -1124,155 +1299,23 @@ static CURLcode single_transfer(struct GlobalConfig *global, if((urlnode->useremote || (per->outfile && strcmp("-", per->outfile)))) { - - /* - * We have specified a filename to store the result in, or we have - * decided we want to use the remote filename. - */ - - if(!per->outfile) { - /* extract the filename from the URL */ - result = get_url_file_name(global, &per->outfile, per->url); - if(result) { - errorf(global, "Failed to extract a filename" - " from the URL to use for storage"); - break; - } - } - else if(state->urls) { - /* fill '#1' ... '#9' terms from URL pattern */ - char *storefile = per->outfile; - result = glob_match_url(&per->outfile, storefile, state->urls); - tool_safefree(storefile); - if(result) { - /* bad globbing */ - warnf(global, "bad output glob"); - break; - } - if(!*per->outfile) { - warnf(global, "output glob produces empty string"); - result = CURLE_WRITE_ERROR; - break; - } - } - DEBUGASSERT(per->outfile); - - if(config->output_dir && *config->output_dir) { - char *d = aprintf("%s/%s", config->output_dir, per->outfile); - if(!d) { - result = CURLE_WRITE_ERROR; - break; - } - free(per->outfile); - per->outfile = d; - } - /* Create the directory hierarchy, if not pre-existent to a multiple - file output call */ - - if(config->create_dirs) { - result = create_dir_hierarchy(per->outfile, global); - /* create_dir_hierarchy shows error upon CURLE_WRITE_ERROR */ - if(result) - break; - } - - if(config->skip_existing) { - struct_stat fileinfo; - if(!stat(per->outfile, &fileinfo)) { - /* file is present */ - notef(global, "skips transfer, \"%s\" exists locally", - per->outfile); - per->skip = TRUE; - *skipped = TRUE; - } - } - if(urlnode->useremote && config->content_disposition) { - /* Our header callback MIGHT set the filename */ - DEBUGASSERT(!outs->filename); - } - - if(config->resume_from_current) { - /* We are told to continue from where we are now. Get the size - of the file as it is now and open it for append instead */ - struct_stat fileinfo; - /* VMS -- Danger, the filesize is only valid for stream files */ - if(0 == stat(per->outfile, &fileinfo)) - /* set offset to current file size: */ - config->resume_from = fileinfo.st_size; - else - /* let offset be 0 */ - config->resume_from = 0; - } - - if(config->resume_from) { -#ifdef __VMS - /* open file for output, forcing VMS output format into stream - mode which is needed for stat() call above to always work. */ - FILE *file = fopen(outfile, "ab", - "ctx=stm", "rfm=stmlf", "rat=cr", "mrs=0"); -#else - /* open file for output: */ - FILE *file = fopen(per->outfile, "ab"); -#endif - if(!file) { - errorf(global, "cannot open '%s'", per->outfile); - result = CURLE_WRITE_ERROR; - break; - } - outs->fopened = TRUE; - outs->stream = file; - outs->init = config->resume_from; - } - else { - outs->stream = NULL; /* open when needed */ - } - outs->filename = per->outfile; - outs->s_isreg = TRUE; - } - - if(per->uploadfile && !stdin_upload(per->uploadfile)) { - /* - * We have specified a file to upload and it is not "-". - */ - result = add_file_name_to_url(per->curl, &per->url, - per->uploadfile); + result = setup_outfile(global, config, per, outs, skipped); if(result) break; } - else if(per->uploadfile && stdin_upload(per->uploadfile)) { - /* count to see if there are more than one auth bit set - in the authtype field */ - int authbits = 0; - int bitcheck = 0; - while(bitcheck < 32) { - if(config->authtype & (1UL << bitcheck++)) { - authbits++; - if(authbits > 1) { - /* more than one, we are done! */ - break; - } - } - } - /* - * If the user has also selected --anyauth or --proxy-anyauth - * we should warn them. - */ - if(config->proxyanyauth || (authbits > 1)) { - warnf(global, - "Using --anyauth or --proxy-anyauth with upload from stdin" - " involves a big risk of it not working. Use a temporary" - " file or a fixed auth type instead"); - } + if(per->uploadfile) { - DEBUGASSERT(per->infdopen == FALSE); - DEBUGASSERT(per->infd == STDIN_FILENO); - - CURL_SET_BINMODE(stdin); - if(!strcmp(per->uploadfile, ".")) { - if(curlx_nonblock((curl_socket_t)per->infd, TRUE) < 0) - warnf(global, - "fcntl failed on fd=%d: %s", per->infd, strerror(errno)); + if(stdin_upload(per->uploadfile)) + check_stdin_upload(global, config, per); + else { + /* + * We have specified a file to upload and it is not "-". + */ + result = add_file_name_to_url(per->curl, &per->url, + per->uploadfile); + if(result) + break; } }