Add io_read_all, io_read_all_str and mem_has_null:

- `io_read_all` reads all bytes from a file handle into a new buffer. It should only need one allocation per file in cases where the actual file size matches the expected file size. Otherwise it falls back to doubling the buffer size if the actual file size is larger than expected to avoid TOCTOU problems.
- `io_read_all_str` reads all bytes from a file handle into a new buffer and also ensures that the buffer is null-terminated and contains no other null-characters.
- `mem_has_null` is a utility used by `io_read_all_str` to ensure that no null-characters exist in the bytes read from the file.
This commit is contained in:
Robert Müller 2022-06-14 20:03:22 +02:00
parent d6d3b2d392
commit c7fb013607
2 changed files with 102 additions and 0 deletions

View file

@ -266,6 +266,51 @@ unsigned io_read(IOHANDLE io, void *buffer, unsigned size)
return fread(buffer, 1, size, (FILE *)io);
}
void io_read_all(IOHANDLE io, void **result, unsigned *result_len)
{
long signed_len = io_length(io);
unsigned len = signed_len < 0 ? 1024 : (unsigned)signed_len; // use default initial size if we couldn't get the length
char *buffer = (char *)malloc(len + 1);
unsigned read = io_read(io, buffer, len + 1); // +1 to check if the file size is larger than expected
if(read < len)
{
buffer = (char *)realloc(buffer, read + 1);
len = read;
}
else if(read > len)
{
unsigned cap = 2 * read;
len = read;
buffer = (char *)realloc(buffer, cap);
while((read = io_read(io, buffer + len, cap - len)) != 0)
{
len += read;
if(len == cap)
{
cap *= 2;
buffer = (char *)realloc(buffer, cap);
}
}
buffer = (char *)realloc(buffer, len + 1);
}
buffer[len] = 0;
*result = buffer;
*result_len = len;
}
char *io_read_all_str(IOHANDLE io)
{
void *buffer;
unsigned len;
io_read_all(io, &buffer, &len);
if(mem_has_null(buffer, len))
{
free(buffer);
return nullptr;
}
return (char *)buffer;
}
unsigned io_skip(IOHANDLE io, int size)
{
fseek((FILE *)io, size, SEEK_CUR);
@ -3244,6 +3289,20 @@ int mem_comp(const void *a, const void *b, int size)
return memcmp(a, b, size);
}
int mem_has_null(const void *block, unsigned size)
{
const unsigned char *bytes = (const unsigned char *)block;
unsigned i;
for(i = 0; i < size; i++)
{
if(bytes[i] == 0)
{
return 1;
}
}
return 0;
}
void net_stats(NETSTATS *stats_inout)
{
*stats_inout = network_stats;

View file

@ -163,6 +163,19 @@ void mem_zero(void *block, unsigned size);
*/
int mem_comp(const void *a, const void *b, int size);
/**
* Checks whether a block of memory contains null bytes.
*
* @ingroup Memory
*
* @param block Pointer to the block to check for nulls.
* @param size Size of the block.
*
* @return 1 - The block has a null byte.
* @return 0 - The block does not have a null byte.
*/
int mem_has_null(const void *block, unsigned size);
/**
* @defgroup File-IO
*
@ -217,6 +230,36 @@ IOHANDLE io_open(const char *filename, int flags);
*/
unsigned io_read(IOHANDLE io, void *buffer, unsigned size);
/**
* Reads the rest of the file into a buffer.
*
* @ingroup File-IO
*
* @param io Handle to the file to read data from.
* @param result Receives the file's remaining contents.
* @param result_len Receives the file's remaining length.
*
* @remark Does NOT guarantee that there are no internal null bytes.
* @remark The result must be freed after it has been used.
*/
void io_read_all(IOHANDLE io, void **result, unsigned *result_len);
/**
* Reads the rest of the file into a zero-terminated buffer with
* no internal null bytes.
*
* @ingroup File-IO
*
* @param io Handle to the file to read data from.
*
* @return The file's remaining contents or null on failure.
*
* @remark Guarantees that there are no internal null bytes.
* @remark Guarantees that result will contain zero-termination.
* @remark The result must be freed after it has been used.
*/
char *io_read_all_str(IOHANDLE io);
/**
* Skips data in a file.
*