From c7fb0136075ab4d12b06e391bfd3e8c7bd8173d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Tue, 14 Jun 2022 20:03:22 +0200 Subject: [PATCH] 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. --- src/base/system.cpp | 59 +++++++++++++++++++++++++++++++++++++++++++++ src/base/system.h | 43 +++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/src/base/system.cpp b/src/base/system.cpp index 3f139fb1c..7454fdeab 100644 --- a/src/base/system.cpp +++ b/src/base/system.cpp @@ -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; diff --git a/src/base/system.h b/src/base/system.h index e73357057..8f4ceabee 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -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. *