curl: add byte range support to --variable reading from file

Allowing --variable read a portion of provided files, makes curl work on
partial files for any options that accepts strings. Like --data and others.

The byte offset is provided within brackets, with a semicolon separator
like: --variable name@file;[100-200]"

Inspired by #14479
Assisted-by: Manuel Einfalt

Test 784 - 789. Documentation update provided.

Closes #15739
This commit is contained in:
Daniel Stenberg 2024-12-21 11:46:27 +01:00
parent fc3e1cbc50
commit 40c264db61
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
13 changed files with 453 additions and 11 deletions

View file

@ -348,6 +348,7 @@ typedef enum {
PARAM_READ_ERROR,
PARAM_EXPAND_ERROR, /* --expand problem */
PARAM_BLANK_STRING,
PARAM_VAR_SYNTAX, /* --variable syntax error */
PARAM_LAST
} ParameterError;

View file

@ -75,6 +75,8 @@ const char *param2text(ParameterError error)
return "variable expansion failure";
case PARAM_BLANK_STRING:
return "blank argument where content is expected";
case PARAM_VAR_SYNTAX:
return "syntax error in --variable argument";
default:
return "unknown error";
}

View file

@ -124,15 +124,45 @@ ParameterError file2string(char **bufp, FILE *file)
return PARAM_OK;
}
ParameterError file2memory(char **bufp, size_t *size, FILE *file)
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);
@ -140,9 +170,35 @@ ParameterError file2memory(char **bufp, size_t *size, FILE *file)
*bufp = NULL;
return PARAM_READ_ERROR;
}
if(nread)
if(curlx_dyn_addn(&dyn, buffer, nread))
return PARAM_NO_MEM;
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);
@ -154,6 +210,11 @@ ParameterError file2memory(char **bufp, size_t *size, FILE *file)
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.

View file

@ -37,6 +37,8 @@ ParameterError file2string(char **bufp, FILE *file);
#endif
ParameterError file2memory(char **bufp, size_t *size, FILE *file);
ParameterError file2memory_range(char **bufp, size_t *size, FILE *file,
curl_off_t starto, curl_off_t endo);
ParameterError str2num(long *val, const char *str);
ParameterError str2unum(long *val, const char *str);

View file

@ -374,6 +374,8 @@ static ParameterError addvariable(struct GlobalConfig *global,
return PARAM_NO_MEM;
}
#define MAX_FILENAME 10000
ParameterError setvariable(struct GlobalConfig *global,
const char *input)
{
@ -427,21 +429,56 @@ ParameterError setvariable(struct GlobalConfig *global,
/* read from file or stdin */
FILE *file;
bool use_stdin;
char *range;
struct dynbuf fname;
curl_off_t startoffset = 0;
curl_off_t endoffset = CURL_OFF_T_MAX;
line++;
Curl_dyn_init(&fname, MAX_FILENAME);
/* is there a byte range specified? ;[num-num] */
range = strstr(line, ";[");
if(range && ISDIGIT(range[2])) {
char *p = range;
char *endp;
if(curlx_strtoofft(&p[2], &endp, 10, &startoffset) || (*endp != '-'))
return PARAM_VAR_SYNTAX;
else {
p = endp + 1; /* pass the '-' */
if(*p != ']') {
if(curlx_strtoofft(p, &endp, 10, &endoffset) || (*endp != ']'))
return PARAM_VAR_SYNTAX;
}
}
if(startoffset > endoffset)
return PARAM_VAR_SYNTAX;
/* create a dynbuf for the filename without the range */
if(Curl_dyn_addn(&fname, line, (range - line)))
return PARAM_NO_MEM;
/* point to the new file name buffer */
line = Curl_dyn_ptr(&fname);
}
use_stdin = !strcmp(line, "-");
if(use_stdin)
file = stdin;
else {
file = fopen(line, "rb");
if(!file) {
errorf(global, "Failed to open %s", line);
return PARAM_READ_ERROR;
errorf(global, "Failed to open %s: %s", line,
strerror(errno));
err = PARAM_READ_ERROR;
}
}
err = file2memory(&content, &clen, file);
/* in case of out of memory, this should fail the entire operation */
contalloc = TRUE;
if(!use_stdin)
if(!err) {
err = file2memory_range(&content, &clen, file, startoffset, endoffset);
/* in case of out of memory, this should fail the entire operation */
if(clen)
contalloc = TRUE;
}
Curl_dyn_free(&fname);
if(!use_stdin && file)
fclose(file);
if(err)
return err;