tool_paramhlp: use feof(3) to identify EOF correctly when using fread(3)

This loop was using the number of bytes read from the file as condition
to keep reading.

From Linux's fread(3) man page:
> On success, fread() and fwrite() return the number of items read or
> written. This number equals the number of bytes transferred only when
> size is 1. If an error occurs, or the end of the file is reached, the
> return value is a short item count (or zero).
>
> The file position indicator for the stream is advanced by the number
> of bytes successfully read or written.
>
> fread() does not distinguish between end-of-file and error, and
> callers must use feof(3) and ferror(3) to determine which occurred.

This means that nread!=0 doesn't make much sense as an end condition for
the loop: nread==0 doesn't necessarily mean that EOF has been reached or
an error has occured (but that is usually the case) and nread!=0 doesn't
necessarily mean that EOF has not been reached or that no read errors
have occured. feof(3) and ferror(3) should be uses when using fread(3).

Currently curl has to performs an extra fread(3) call to get a return
value equal to 0 to stop looping.

This usually "works" (even though nread==0 shouldn't be interpreted as
EOF) if stdin is a pipe because EOF usually marks the "real" end of the
stream, so the extra fread(3) call will return immediately and the extra
read syscall won't be noticeable:

    bash-5.1$ strace -e read curl -s -F file=@- 0x0.st <<< a 2>&1 |
    > tail -n 5
    read(0, "a\n", 4096)                    = 2
    read(0, "", 4096)                       = 0
    read(0, "", 4096)                       = 0
    http://0x0.st/oRs.txt
    +++ exited with 0 +++
    bash-5.1$

But this doesn't work if curl is reading from stdin, stdin is a
terminal, and the EOF is being emulated using a shell with ^D. Two
consecutive ^D will be required in this case to actually make curl stop
reading:

    bash-5.1$ curl -F file=@- 0x0.st
    a
    ^D^D
    http://0x0.st/oRs.txt
    bash-5.1$

A possible workaround to this issue is to use a program that handles EOF
correctly to indirectly send data to curl's stdin:

    bash-5.1$ cat - | curl -F file=@- 0x0.st
    a
    ^D
    http://0x0.st/oRs.txt
    bash-5.1$

This patch makes curl handle EOF properly when using fread(3) in
file2memory() so that the workaround is not necessary.

Since curl was previously ignoring read errors caused by this fread(3),
ferror(3) is also used in the condition of the loop: read errors and EOF
will have the same meaning; this is done to somewhat preserve the old
behaviour instead of making the command fail when a read error occurs.

Closes #8701
This commit is contained in:
Emanuele Torre 2022-04-17 11:36:28 +02:00 committed by Daniel Stenberg
parent 53399c72ff
commit 77a6bf8489
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
5 changed files with 25 additions and 15 deletions

View File

@ -125,21 +125,20 @@ static struct tool_mime *tool_mime_new_filedata(struct tool_mime *parent,
else { /* Not suitable for direct use, buffer stdin data. */
size_t stdinsize = 0;
if(file2memory(&data, &stdinsize, stdin) != PARAM_OK) {
/* Out of memory. */
switch(file2memory(&data, &stdinsize, stdin)) {
case PARAM_NO_MEM:
return m;
}
if(ferror(stdin)) {
case PARAM_READ_ERROR:
result = CURLE_READ_ERROR;
Curl_safefree(data);
data = NULL;
}
else if(!stdinsize) {
/* Zero-length data has been freed. Re-create it. */
data = strdup("");
if(!data)
return m;
break;
default:
if(!stdinsize) {
/* Zero-length data has been freed. Re-create it. */
data = strdup("");
if(!data)
return m;
}
break;
}
size = curlx_uztoso(stdinsize);
origin = 0;

View File

@ -7,7 +7,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 1998 - 2022, 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
@ -45,6 +45,7 @@ typedef enum {
PARAM_NO_NOT_BOOLEAN,
PARAM_CONTDISP_SHOW_HEADER, /* --include and --remote-header-name */
PARAM_CONTDISP_RESUME_FROM, /* --continue-at and --remote-header-name */
PARAM_READ_ERROR,
PARAM_LAST
} ParameterError;

View File

@ -72,6 +72,8 @@ const char *param2text(int res)
return "showing headers and --remote-header-name cannot be combined";
case PARAM_CONTDISP_RESUME_FROM:
return "--continue-at and --remote-header-name cannot be combined";
case PARAM_READ_ERROR:
return "error encountered when reading a file";
default:
return "unknown error";
}

View File

@ -2621,6 +2621,8 @@ CURLcode operate(struct GlobalConfig *global, int argc, argv_item_t argv[])
tool_list_engines();
else if(res == PARAM_LIBCURL_UNSUPPORTED_PROTOCOL)
result = CURLE_UNSUPPORTED_PROTOCOL;
else if(res == PARAM_READ_ERROR)
result = CURLE_READ_ERROR;
else
result = CURLE_FAILED_INIT;
}

View File

@ -94,10 +94,16 @@ ParameterError file2memory(char **bufp, size_t *size, FILE *file)
do {
char buffer[4096];
nread = fread(buffer, 1, sizeof(buffer), file);
if(ferror(file)) {
curlx_dyn_free(&dyn);
*size = 0;
*bufp = NULL;
return PARAM_READ_ERROR;
}
if(nread)
if(curlx_dyn_addn(&dyn, buffer, nread))
return PARAM_NO_MEM;
} while(nread);
} while(!feof(file));
*size = curlx_dyn_len(&dyn);
*bufp = curlx_dyn_ptr(&dyn);
}