blob: 681590a2e6132ecd7a7bb463eb4e2211f4e8d3c7 [file] [log] [blame]
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* SPDX-License-Identifier: curl
*
***************************************************************************/
#include "tool_setup.h"
#include "strcase.h"
#include "curlx.h"
#include "tool_cfgable.h"
#include "tool_getparam.h"
#include "tool_getpass.h"
#include "tool_msgs.h"
#include "tool_paramhlp.h"
#include "tool_libinfo.h"
#include "tool_util.h"
#include "tool_version.h"
#include "dynbuf.h"
#include "memdebug.h" /* keep this as LAST include */
struct getout *new_getout(struct OperationConfig *config)
{
struct getout *node = calloc(1, sizeof(struct getout));
struct getout *last = config->url_last;
if(node) {
static int outnum = 0;
/* append this new node last in the list */
if(last)
last->next = node;
else
config->url_list = node; /* first node */
/* move the last pointer */
config->url_last = node;
node->flags = config->default_node_flags;
node->num = outnum++;
}
return node;
}
#define ISCRLF(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0'))
/* memcrlf() has two modes. Both operate on a given memory area with
a specified size.
countcrlf FALSE - return number of bytes from the start that DO NOT include
any CR or LF or NULL
countcrlf TRUE - return number of bytes from the start that are ONLY CR or
LF or NULL.
*/
static size_t memcrlf(char *orig,
bool countcrlf, /* TRUE if we count CRLF, FALSE
if we count non-CRLF */
size_t max)
{
char *ptr;
size_t total = max;
for(ptr = orig; max; max--, ptr++) {
bool crlf = ISCRLF(*ptr);
if(countcrlf ^ crlf)
return ptr - orig;
}
return total; /* no delimiter found */
}
#define MAX_FILE2STRING MAX_FILE2MEMORY
ParameterError file2string(char **bufp, FILE *file)
{
struct curlx_dynbuf dyn;
curlx_dyn_init(&dyn, MAX_FILE2STRING);
if(file) {
do {
char buffer[4096];
char *ptr;
size_t nread = fread(buffer, 1, sizeof(buffer), file);
if(ferror(file)) {
curlx_dyn_free(&dyn);
*bufp = NULL;
return PARAM_READ_ERROR;
}
ptr = buffer;
while(nread) {
size_t nlen = memcrlf(ptr, FALSE, nread);
if(curlx_dyn_addn(&dyn, ptr, nlen))
return PARAM_NO_MEM;
nread -= nlen;
if(nread) {
ptr += nlen;
nlen = memcrlf(ptr, TRUE, nread);
ptr += nlen;
nread -= nlen;
}
}
} while(!feof(file));
}
*bufp = curlx_dyn_ptr(&dyn);
return PARAM_OK;
}
static int myfseek(void *stream, curl_off_t offset, int whence)
{
#if defined(_WIN32) && defined(USE_WIN32_LARGE_FILES)
return _fseeki64(stream, (__int64)offset, whence);
#elif defined(HAVE_FSEEKO) && defined(HAVE_DECL_FSEEKO)
return fseeko(stream, (off_t)offset, whence);
#else
if(offset > LONG_MAX)
return -1;
return fseek(stream, (long)offset, whence);
#endif
}
ParameterError file2memory_range(char **bufp, size_t *size, FILE *file,
curl_off_t starto, curl_off_t endo)
{
if(file) {
size_t nread;
struct curlx_dynbuf dyn;
curl_off_t offset = 0;
curl_off_t throwaway = 0;
if(starto) {
if(file != stdin) {
if(myfseek(file, starto, SEEK_SET))
return PARAM_READ_ERROR;
offset = starto;
}
else
/* we can't seek stdin, read 'starto' bytes and throw them away */
throwaway = starto;
}
/* The size needs to fit in an int later */
curlx_dyn_init(&dyn, MAX_FILE2MEMORY);
do {
char buffer[4096];
size_t n_add;
char *ptr_add;
nread = fread(buffer, 1, sizeof(buffer), file);
if(ferror(file)) {
curlx_dyn_free(&dyn);
*size = 0;
*bufp = NULL;
return PARAM_READ_ERROR;
}
n_add = nread;
ptr_add = buffer;
if(nread) {
if(throwaway) {
if(throwaway >= (curl_off_t)nread) {
throwaway -= nread;
offset += nread;
n_add = 0; /* nothing to add */
}
else {
/* append the trailing piece */
n_add = (size_t)(nread - throwaway);
ptr_add = &buffer[throwaway];
offset += throwaway;
throwaway = 0;
}
}
if(n_add) {
if((curl_off_t)(n_add + offset) > endo)
n_add = (size_t)(endo - offset + 1);
if(curlx_dyn_addn(&dyn, ptr_add, n_add))
return PARAM_NO_MEM;
offset += n_add;
if(offset > endo)
break;
}
}
} while(!feof(file));
*size = curlx_dyn_len(&dyn);
*bufp = curlx_dyn_ptr(&dyn);
}
else {
*size = 0;
*bufp = NULL;
}
return PARAM_OK;
}
ParameterError file2memory(char **bufp, size_t *size, FILE *file)
{
return file2memory_range(bufp, size, file, 0, CURL_OFF_T_MAX);
}
/*
* Parse the string and write the long in the given address. Return PARAM_OK
* on success, otherwise a parameter specific error enum.
*
* Since this function gets called with the 'nextarg' pointer from within the
* getparameter a lot, we must check it for NULL before accessing the str
* data.
*/
static ParameterError getnum(long *val, const char *str, int base)
{
DEBUGASSERT((base == 8) || (base == 10));
if(str) {
curl_off_t num;
bool is_neg = FALSE;
if(base == 10) {
is_neg = (*str == '-');
if(is_neg)
str++;
if(curlx_str_number(&str, &num, LONG_MAX))
return PARAM_BAD_NUMERIC;
}
else { /* base == 8 */
if(curlx_str_octal(&str, &num, LONG_MAX))
return PARAM_BAD_NUMERIC;
}
if(!curlx_str_single(&str, '\0')) {
*val = (long)num;
if(is_neg)
*val = -*val;
return PARAM_OK; /* Ok */
}
}
return PARAM_BAD_NUMERIC; /* badness */
}
ParameterError str2num(long *val, const char *str)
{
return getnum(val, str, 10);
}
ParameterError oct2nummax(long *val, const char *str, long max)
{
ParameterError result = getnum(val, str, 8);
if(result != PARAM_OK)
return result;
else if(*val > max)
return PARAM_NUMBER_TOO_LARGE;
else if(*val < 0)
return PARAM_NEGATIVE_NUMERIC;
return PARAM_OK;
}
/*
* Parse the string and write the long in the given address. Return PARAM_OK
* on success, otherwise a parameter error enum. ONLY ACCEPTS POSITIVE NUMBERS!
*
* Since this function gets called with the 'nextarg' pointer from within the
* getparameter a lot, we must check it for NULL before accessing the str
* data.
*/
ParameterError str2unum(long *val, const char *str)
{
ParameterError result = getnum(val, str, 10);
if(result != PARAM_OK)
return result;
if(*val < 0)
return PARAM_NEGATIVE_NUMERIC;
return PARAM_OK;
}
/*
* Parse the string and write the long in the given address if it is below the
* maximum allowed value. Return PARAM_OK on success, otherwise a parameter
* error enum. ONLY ACCEPTS POSITIVE NUMBERS!
*
* Since this function gets called with the 'nextarg' pointer from within the
* getparameter a lot, we must check it for NULL before accessing the str
* data.
*/
ParameterError str2unummax(long *val, const char *str, long max)
{
ParameterError result = str2unum(val, str);
if(result != PARAM_OK)
return result;
if(*val > max)
return PARAM_NUMBER_TOO_LARGE;
return PARAM_OK;
}
/*
* Parse the string as seconds with decimals, and write the number of
* milliseconds that corresponds in the given address. Return PARAM_OK on
* success, otherwise a parameter error enum. ONLY ACCEPTS POSITIVE NUMBERS!
*
* The 'max' argument is the maximum value allowed, as the numbers are often
* multiplied when later used.
*
* Since this function gets called with the 'nextarg' pointer from within the
* getparameter a lot, we must check it for NULL before accessing the str
* data.
*/
ParameterError secs2ms(long *valp, const char *str)
{
curl_off_t secs;
long ms = 0;
const unsigned int digs[] = { 1, 10, 100, 1000, 10000, 1000000,
1000000, 10000000, 100000000 };
if(!str ||
curlx_str_number(&str, &secs, LONG_MAX/1000 - 1))
return PARAM_BAD_NUMERIC;
if(!curlx_str_single(&str, '.')) {
curl_off_t fracs;
const char *s = str;
size_t len;
if(curlx_str_number(&str, &fracs, CURL_OFF_T_MAX))
return PARAM_NUMBER_TOO_LARGE;
/* how many milliseconds are in fracs ? */
len = (str - s);
while((len > sizeof(CURL_ARRAYSIZE(digs)) || (fracs > LONG_MAX/100))) {
fracs /= 10;
len--;
}
ms = ((long)fracs * 100) / digs[len - 1];
}
*valp = (long)secs * 1000 + ms;
return PARAM_OK;
}
/*
* Implement protocol sets in null-terminated array of protocol name pointers.
*/
/* Return index of prototype token in set, card(set) if not found.
Can be called with proto == NULL to get card(set). */
static size_t protoset_index(const char * const *protoset, const char *proto)
{
const char * const *p = protoset;
DEBUGASSERT(proto == proto_token(proto)); /* Ensure it is tokenized. */
for(; *p; p++)
if(proto == *p)
break;
return p - protoset;
}
/* Include protocol token in set. */
static void protoset_set(const char **protoset, const char *proto)
{
if(proto) {
size_t n = protoset_index(protoset, proto);
if(!protoset[n]) {
DEBUGASSERT(n < proto_count);
protoset[n] = proto;
protoset[n + 1] = NULL;
}
}
}
/* Exclude protocol token from set. */
static void protoset_clear(const char **protoset, const char *proto)
{
if(proto) {
size_t n = protoset_index(protoset, proto);
if(protoset[n]) {
size_t m = protoset_index(protoset, NULL) - 1;
protoset[n] = protoset[m];
protoset[m] = NULL;
}
}
}
/*
* Parse the string and provide an allocated libcurl compatible protocol
* string output. Return non-zero on failure, zero on success.
*
* The string is a list of protocols
*
* Since this function gets called with the 'nextarg' pointer from within the
* getparameter a lot, we must check it for NULL before accessing the str
* data.
*/
#define MAX_PROTOSTRING (64*11) /* Enough room for 64 10-chars proto names. */
ParameterError proto2num(struct OperationConfig *config,
const char * const *val, char **ostr, const char *str)
{
const char **protoset;
struct curlx_dynbuf obuf;
size_t proto;
CURLcode result;
curlx_dyn_init(&obuf, MAX_PROTOSTRING);
protoset = malloc((proto_count + 1) * sizeof(*protoset));
if(!protoset)
return PARAM_NO_MEM;
/* Preset protocol set with default values. */
protoset[0] = NULL;
for(; *val; val++) {
const char *p = proto_token(*val);
if(p)
protoset_set(protoset, p);
}
while(*str) {
const char *next = strchr(str, ',');
size_t plen;
enum e_action { allow, deny, set } action = allow;
if(next) {
if(str == next) {
str++;
continue;
}
plen = next - str - 1;
}
else
plen = strlen(str) - 1;
/* Process token modifiers */
switch(*str++) {
case '=':
action = set;
break;
case '-':
action = deny;
break;
case '+':
action = allow;
break;
default:
/* no modifier */
str--;
plen++;
break;
}
if((plen == 3) && curl_strnequal(str, "all", 3)) {
switch(action) {
case deny:
protoset[0] = NULL;
break;
case allow:
case set:
memcpy((char *) protoset,
built_in_protos, (proto_count + 1) * sizeof(*protoset));
break;
}
}
else {
char buffer[32];
const char *p;
msnprintf(buffer, sizeof(buffer), "%.*s", (int)plen, str);
p = proto_token(buffer);
if(p)
switch(action) {
case deny:
protoset_clear(protoset, p);
break;
case set:
protoset[0] = NULL;
FALLTHROUGH();
case allow:
protoset_set(protoset, p);
break;
}
else { /* unknown protocol */
/* If they have specified only this protocol, we say treat it as
if no protocols are allowed */
if(action == set)
protoset[0] = NULL;
warnf(config->global, "unrecognized protocol '%s'", buffer);
}
}
if(next)
str = next + 1;
else
break;
}
/* We need the protocols in alphabetic order for CI tests requirements. */
qsort((char *) protoset, protoset_index(protoset, NULL), sizeof(*protoset),
struplocompare4sort);
result = curlx_dyn_addn(&obuf, "", 0);
for(proto = 0; protoset[proto] && !result; proto++)
result = curlx_dyn_addf(&obuf, "%s,", protoset[proto]);
free((char *) protoset);
curlx_dyn_setlen(&obuf, curlx_dyn_len(&obuf) - 1);
free(*ostr);
*ostr = curlx_dyn_ptr(&obuf);
return *ostr ? PARAM_OK : PARAM_NO_MEM;
}
/**
* Check if the given string is a protocol supported by libcurl
*
* @param str the protocol name
* @return PARAM_OK protocol supported
* @return PARAM_LIBCURL_UNSUPPORTED_PROTOCOL protocol not supported
* @return PARAM_REQUIRES_PARAMETER missing parameter
*/
ParameterError check_protocol(const char *str)
{
if(!str)
return PARAM_REQUIRES_PARAMETER;
if(proto_token(str))
return PARAM_OK;
return PARAM_LIBCURL_UNSUPPORTED_PROTOCOL;
}
/**
* Parses the given string looking for an offset (which may be a
* larger-than-integer value). The offset CANNOT be negative!
*
* @param val the offset to populate
* @param str the buffer containing the offset
* @return PARAM_OK if successful, a parameter specific error enum if failure.
*/
ParameterError str2offset(curl_off_t *val, const char *str)
{
if(curlx_str_number(&str, val, CURL_OFF_T_MAX) ||
curlx_str_single(&str, '\0'))
return PARAM_BAD_NUMERIC;
return PARAM_OK;
}
#define MAX_USERPWDLENGTH (100*1024)
static CURLcode checkpasswd(const char *kind, /* for what purpose */
const size_t i, /* operation index */
const bool last, /* TRUE if last operation */
char **userpwd) /* pointer to allocated string */
{
char *psep;
char *osep;
if(!*userpwd)
return CURLE_OK;
/* Attempt to find the password separator */
psep = strchr(*userpwd, ':');
/* Attempt to find the options separator */
osep = strchr(*userpwd, ';');
if(!psep && **userpwd != ';') {
/* no password present, prompt for one */
char passwd[2048] = "";
char prompt[256];
struct curlx_dynbuf dyn;
curlx_dyn_init(&dyn, MAX_USERPWDLENGTH);
if(osep)
*osep = '\0';
/* build a nice-looking prompt */
if(!i && last)
msnprintf(prompt, sizeof(prompt),
"Enter %s password for user '%s':",
kind, *userpwd);
else
msnprintf(prompt, sizeof(prompt),
"Enter %s password for user '%s' on URL #%zu:",
kind, *userpwd, i + 1);
/* get password */
getpass_r(prompt, passwd, sizeof(passwd));
if(osep)
*osep = ';';
if(curlx_dyn_addf(&dyn, "%s:%s", *userpwd, passwd))
return CURLE_OUT_OF_MEMORY;
/* return the new string */
free(*userpwd);
*userpwd = curlx_dyn_ptr(&dyn);
}
return CURLE_OK;
}
ParameterError add2list(struct curl_slist **list, const char *ptr)
{
struct curl_slist *newlist = curl_slist_append(*list, ptr);
if(newlist)
*list = newlist;
else
return PARAM_NO_MEM;
return PARAM_OK;
}
int ftpfilemethod(struct OperationConfig *config, const char *str)
{
if(curl_strequal("singlecwd", str))
return CURLFTPMETHOD_SINGLECWD;
if(curl_strequal("nocwd", str))
return CURLFTPMETHOD_NOCWD;
if(curl_strequal("multicwd", str))
return CURLFTPMETHOD_MULTICWD;
warnf(config->global, "unrecognized ftp file method '%s', using default",
str);
return CURLFTPMETHOD_MULTICWD;
}
int ftpcccmethod(struct OperationConfig *config, const char *str)
{
if(curl_strequal("passive", str))
return CURLFTPSSL_CCC_PASSIVE;
if(curl_strequal("active", str))
return CURLFTPSSL_CCC_ACTIVE;
warnf(config->global, "unrecognized ftp CCC method '%s', using default",
str);
return CURLFTPSSL_CCC_PASSIVE;
}
long delegation(struct OperationConfig *config, const char *str)
{
if(curl_strequal("none", str))
return CURLGSSAPI_DELEGATION_NONE;
if(curl_strequal("policy", str))
return CURLGSSAPI_DELEGATION_POLICY_FLAG;
if(curl_strequal("always", str))
return CURLGSSAPI_DELEGATION_FLAG;
warnf(config->global, "unrecognized delegation method '%s', using none",
str);
return CURLGSSAPI_DELEGATION_NONE;
}
/*
* my_useragent: returns allocated string with default user agent
*/
static char *my_useragent(void)
{
return strdup(CURL_NAME "/" CURL_VERSION);
}
#define isheadersep(x) ((((x)==':') || ((x)==';')))
/*
* inlist() returns true if the given 'checkfor' header is present in the
* header list.
*/
static bool inlist(const struct curl_slist *head,
const char *checkfor)
{
size_t thislen = strlen(checkfor);
DEBUGASSERT(thislen);
DEBUGASSERT(checkfor[thislen-1] != ':');
for(; head; head = head->next) {
if(curl_strnequal(head->data, checkfor, thislen) &&
isheadersep(head->data[thislen]) )
return TRUE;
}
return FALSE;
}
CURLcode get_args(struct OperationConfig *config, const size_t i)
{
CURLcode result = CURLE_OK;
bool last = (config->next ? FALSE : TRUE);
if(config->jsoned) {
ParameterError err = PARAM_OK;
/* --json also implies json Content-Type: and Accept: headers - if
they are not set with -H */
if(!inlist(config->headers, "Content-Type"))
err = add2list(&config->headers, "Content-Type: application/json");
if(!err && !inlist(config->headers, "Accept"))
err = add2list(&config->headers, "Accept: application/json");
if(err)
return CURLE_OUT_OF_MEMORY;
}
/* Check we have a password for the given host user */
if(config->userpwd && !config->oauth_bearer) {
result = checkpasswd("host", i, last, &config->userpwd);
if(result)
return result;
}
/* Check we have a password for the given proxy user */
if(config->proxyuserpwd) {
result = checkpasswd("proxy", i, last, &config->proxyuserpwd);
if(result)
return result;
}
/* Check we have a user agent */
if(!config->useragent) {
config->useragent = my_useragent();
if(!config->useragent) {
errorf(config->global, "out of memory");
result = CURLE_OUT_OF_MEMORY;
}
}
return result;
}
/*
* Parse the string and modify ssl_version in the val argument. Return PARAM_OK
* on success, otherwise a parameter error enum. ONLY ACCEPTS POSITIVE NUMBERS!
*
* Since this function gets called with the 'nextarg' pointer from within the
* getparameter a lot, we must check it for NULL before accessing the str
* data.
*/
ParameterError str2tls_max(long *val, const char *str)
{
static struct s_tls_max {
const char *tls_max_str;
long tls_max;
} const tls_max_array[] = {
{ "default", CURL_SSLVERSION_MAX_DEFAULT },
{ "1.0", CURL_SSLVERSION_MAX_TLSv1_0 },
{ "1.1", CURL_SSLVERSION_MAX_TLSv1_1 },
{ "1.2", CURL_SSLVERSION_MAX_TLSv1_2 },
{ "1.3", CURL_SSLVERSION_MAX_TLSv1_3 }
};
size_t i = 0;
if(!str)
return PARAM_REQUIRES_PARAMETER;
for(i = 0; i < CURL_ARRAYSIZE(tls_max_array); i++) {
if(!strcmp(str, tls_max_array[i].tls_max_str)) {
*val = tls_max_array[i].tls_max;
return PARAM_OK;
}
}
return PARAM_BAD_USE;
}