- Add GetFileSizeInBytes Filesystem Callback

- Add feature to limit file size for external resources(images, buffers)
- Use strlen to correctly retrieve a string from a string which contains multiple null-characters.
- Return fail when opening a directory(Posix only). Fixes #416
This commit is contained in:
Syoyo Fujita 2023-04-23 21:31:30 +09:00
parent 5a6c55870e
commit ecfd37dee2
2 changed files with 220 additions and 14 deletions

View File

@ -721,3 +721,20 @@ TEST_CASE("serialize-image-failure", "[issue-394]") {
REQUIRE(false == result);
REQUIRE(os.str().size() == 0);
}
TEST_CASE("filesize-check", "[issue-416]") {
tinygltf::Model model;
tinygltf::TinyGLTF ctx;
std::string err;
std::string warn;
ctx.SetMaxExternalFileSize(10); // 10 bytes. will fail to load texture image.
bool ret = ctx.LoadASCIIFromFile(&model, &err, &warn, "../models/Cube/Cube.gltf");
if (!err.empty()) {
std::cerr << err << std::endl;
}
REQUIRE(false == ret);
}

View File

@ -1301,6 +1301,12 @@ typedef bool (*WriteWholeFileFunction)(std::string *, const std::string &,
const std::vector<unsigned char> &,
void *);
///
/// GetFileSizeFunction type. Signature for custom filesystem callbacks.
///
typedef bool (*GetFileSizeFunction)(size_t *filesize_out, std::string *err, const std::string &abs_filename,
void *userdata);
///
/// A structure containing all required filesystem callbacks and a pointer to
/// their user data.
@ -1310,6 +1316,7 @@ struct FsCallbacks {
ExpandFilePathFunction ExpandFilePath;
ReadWholeFileFunction ReadWholeFile;
WriteWholeFileFunction WriteWholeFile;
GetFileSizeFunction GetFileSizeInBytes; // To avoid GetFileSize Win32 API, add `InBytes` suffix.
void *user_data; // An argument that is passed to all fs callbacks
};
@ -1333,6 +1340,9 @@ bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
bool WriteWholeFile(std::string *err, const std::string &filepath,
const std::vector<unsigned char> &contents, void *);
bool GetFileSizeInBytes(size_t *filesize_out, std::string *err, const std::string &filepath,
void *);
#endif
///
@ -1472,6 +1482,19 @@ class TinyGLTF {
preserve_image_channels_ = onoff;
}
///
/// Set maximum allowed external file size in bytes.
/// Default: 2GB
/// Only effective for built-in ReadWholeFileFunction FS function.
///
void SetMaxExternalFileSize(size_t max_bytes) {
max_external_file_size_ = max_bytes;
}
size_t GetMaxExternalFileSize() const {
return max_external_file_size_;
}
bool GetPreserveImageChannels() const { return preserve_image_channels_; }
private:
@ -1496,6 +1519,8 @@ class TinyGLTF {
bool preserve_image_channels_ = false; /// Default false(expand channels to
/// RGBA) for backward compatibility.
size_t max_external_file_size_{(std::numeric_limits<int32_t>::max)()}; // Default 2GB
// Warning & error messages
std::string warn_;
std::string err_;
@ -1503,11 +1528,11 @@ class TinyGLTF {
FsCallbacks fs = {
#ifndef TINYGLTF_NO_FS
&tinygltf::FileExists, &tinygltf::ExpandFilePath,
&tinygltf::ReadWholeFile, &tinygltf::WriteWholeFile,
&tinygltf::ReadWholeFile, &tinygltf::WriteWholeFile, &tinygltf::GetFileSizeInBytes,
nullptr // Fs callback user data
#else
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr // Fs callback user data
#endif
@ -1554,6 +1579,7 @@ class TinyGLTF {
#ifndef TINYGLTF_NO_FS
#include <cstdio>
#include <fstream>
#include <sys/stat.h> // for is_directory check
#endif
#include <sstream>
@ -2107,9 +2133,19 @@ static std::string FindFile(const std::vector<std::string> &paths,
return std::string();
}
// https://github.com/syoyo/tinygltf/issues/416
// Use strlen() since std::string's size/length reports the number of elements in the buffer, not the length of string(null-terminated)
// strip null-character in the middle of string.
size_t slength = strlen(filepath.c_str());
if (slength == 0) {
return std::string();
}
std::string cleaned_filepath = std::string(filepath.c_str());
for (size_t i = 0; i < paths.size(); i++) {
std::string absPath =
fs->ExpandFilePath(JoinPath(paths[i], filepath), fs->user_data);
fs->ExpandFilePath(JoinPath(paths[i], cleaned_filepath), fs->user_data);
if (fs->FileExists(absPath, fs->user_data)) {
return absPath;
}
@ -2355,7 +2391,7 @@ bool URIDecode(const std::string &in_uri, std::string *out_uri,
static bool LoadExternalFile(std::vector<unsigned char> *out, std::string *err,
std::string *warn, const std::string &filename,
const std::string &basedir, bool required,
size_t reqBytes, bool checkSize, FsCallbacks *fs) {
size_t reqBytes, bool checkSize, size_t maxFileSize, FsCallbacks *fs) {
if (fs == nullptr || fs->FileExists == nullptr ||
fs->ExpandFilePath == nullptr || fs->ReadWholeFile == nullptr) {
// This is a developer error, assert() ?
@ -2381,6 +2417,29 @@ static bool LoadExternalFile(std::vector<unsigned char> *out, std::string *err,
return false;
}
// Check file size
if (fs->GetFileSizeInBytes) {
size_t file_size{0};
std::string _err;
bool ok = fs-GetFileSizeInBytes(&file_size, &_err, filepath, fs->user_data);
if (!ok) {
if (_err.size()) {
if (failMsgOut) {
(*failMsgOut) += "Getting file size failed : " + filename + ", err = " + _err + "\n";
}
}
return false;
}
if (file_size > maxFileSize) {
if (failMsgOut) {
(*failMsgOut) += "File size " + std::to_string(file_size) + " exceeds maximum allowed file size " + std::to_string(maxFileSize) + " : " + filepath + "\n";
}
return false;
}
}
std::vector<unsigned char> buf;
std::string fileReadErr;
bool fileRead =
@ -2687,12 +2746,24 @@ bool FileExists(const std::string &abs_filename, void *) {
#else
#ifdef _WIN32
#if defined(_MSC_VER) || defined(__GLIBCXX__) || defined(_LIBCPP_VERSION)
// First check if a file is a directory.
DWORD result = GetFileAttributesW(UTF8ToWchar(abs_filename).c_str());
if (result == INVALID_FILE_ATTRIBUTES) {
return false;
}
if (result & FILE_ATTRIBUTE_DIRECTORY) {
return false;
}
}
FILE *fp = nullptr;
errno_t err = _wfopen_s(&fp, UTF8ToWchar(abs_filename).c_str(), L"rb");
if (err != 0) {
return false;
}
#else
// TODO: is_directory check
FILE *fp = nullptr;
errno_t err = fopen_s(&fp, abs_filename.c_str(), "rb");
if (err != 0) {
@ -2701,6 +2772,14 @@ bool FileExists(const std::string &abs_filename, void *) {
#endif
#else
struct stat sb;
if (stat(abs_filename.c_str(), &sb)) {
return false;
}
if (S_ISDIR(sb.st_mode)) {
return false;
}
FILE *fp = fopen(abs_filename.c_str(), "rb");
#endif
if (fp) {
@ -2777,6 +2856,100 @@ std::string ExpandFilePath(const std::string &filepath, void *) {
#endif
}
bool GetFileSizeInBytes(size_t *filesize_out, std::string *err,
const std::string &filepath, void *userdata) {
(void)userdata;
#ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS
if (asset_manager) {
AAsset *asset = AAssetManager_open(asset_manager, filepath.c_str(),
AASSET_MODE_STREAMING);
if (!asset) {
if (err) {
(*err) += "File open error : " + filepath + "\n";
}
return false;
}
size_t size = AAsset_getLength(asset);
if (size == 0) {
if (err) {
(*err) += "Invalid file size : " + filepath +
" (does the path point to a directory?)";
}
return false;
}
return true;
} else {
if (err) {
(*err) += "No asset manager specified : " + filepath + "\n";
}
return false;
}
#else
#ifdef _WIN32
#if defined(__GLIBCXX__) // mingw
int file_descriptor =
_wopen(UTF8ToWchar(filepath).c_str(), _O_RDONLY | _O_BINARY);
__gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor, std::ios_base::in);
std::istream f(&wfile_buf);
#elif defined(_MSC_VER) || defined(_LIBCPP_VERSION)
// For libcxx, assume _LIBCPP_HAS_OPEN_WITH_WCHAR is defined to accept
// `wchar_t *`
std::ifstream f(UTF8ToWchar(filepath).c_str(), std::ifstream::binary);
#else
// Unknown compiler/runtime
std::ifstream f(filepath.c_str(), std::ifstream::binary);
#endif
#else
std::ifstream f(filepath.c_str(), std::ifstream::binary);
#endif
if (!f) {
if (err) {
(*err) += "File open error : " + filepath + "\n";
}
return false;
}
// For directory(and pipe?), peek() will fail(Posix gnustl/libc++ only)
int buf = f.peek();
if (!f) {
if (err) {
(*err) += "File read error. Maybe empty file or invalid file : " + filepath + "\n";
}
return false;
}
f.seekg(0, f.end);
size_t sz = static_cast<size_t>(f.tellg());
//std::cout << "sz = " << sz << "\n";
f.seekg(0, f.beg);
if (int64_t(sz) < 0) {
if (err) {
(*err) += "Invalid file size : " + filepath +
" (does the path point to a directory?)";
}
return false;
} else if (sz == 0) {
if (err) {
(*err) += "File is empty : " + filepath + "\n";
}
return false;
} else if (sz >= (std::numeric_limits<std::streamoff>::max)()) {
if (err) {
(*err) += "Invalid file size : " + filepath + "\n";
}
return false;
}
(*filesize_out) = sz;
return true;
#endif
}
bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
const std::string &filepath, void *) {
#ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS
@ -2832,8 +3005,19 @@ bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
return false;
}
// For directory(and pipe?), peek() will fail(Posix gnustl/libc++ only)
int buf = f.peek();
if (!f) {
if (err) {
(*err) += "File read error. Maybe empty file or invalid file : " + filepath + "\n";
}
return false;
}
f.seekg(0, f.end);
size_t sz = static_cast<size_t>(f.tellg());
//std::cout << "sz = " << sz << "\n";
f.seekg(0, f.beg);
if (int64_t(sz) < 0) {
@ -2847,6 +3031,11 @@ bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
(*err) += "File is empty : " + filepath + "\n";
}
return false;
} else if (sz >= (std::numeric_limits<std::streamoff>::max)()) {
if (err) {
(*err) += "Invalid file size : " + filepath + "\n";
}
return false;
}
out->resize(sz);
@ -3873,7 +4062,7 @@ static bool ParseAsset(Asset *asset, std::string *err, const detail::json &o,
static bool ParseImage(Image *image, const int image_idx, std::string *err,
std::string *warn, const detail::json &o,
bool store_original_json_for_extras_and_extensions,
const std::string &basedir, FsCallbacks *fs,
const std::string &basedir, const size_t max_file_size, FsCallbacks *fs,
const URICallbacks *uri_cb,
LoadImageDataFunction *LoadImageData = nullptr,
void *load_image_user_data = nullptr) {
@ -3973,8 +4162,8 @@ static bool ParseImage(Image *image, const int image_idx, std::string *err,
if (!DecodeDataURI(&img, image->mimeType, uri, 0, false)) {
if (err) {
(*err) += "Failed to decode 'uri' for image[" +
std::to_string(image_idx) + "] name = [" + image->name +
"]\n";
std::to_string(image_idx) + "] name = \"" + image->name +
"\"\n";
}
return false;
}
@ -3999,10 +4188,10 @@ static bool ParseImage(Image *image, const int image_idx, std::string *err,
if (!LoadExternalFile(&img, err, warn, decoded_uri, basedir,
/* required */ false, /* required bytes */ 0,
/* checksize */ false, fs)) {
/* checksize */ false, /* max file size */ max_file_size, fs)) {
if (warn) {
(*warn) += "Failed to load external 'uri' for image[" +
std::to_string(image_idx) + "] name = [" + image->name +
std::to_string(image_idx) + "] name = [" + decoded_uri +
"]\n";
}
// If the image cannot be loaded, keep uri as image->uri.
@ -4176,7 +4365,7 @@ static bool ParseOcclusionTextureInfo(
static bool ParseBuffer(Buffer *buffer, std::string *err, const detail::json &o,
bool store_original_json_for_extras_and_extensions,
FsCallbacks *fs, const URICallbacks *uri_cb,
const std::string &basedir, bool is_binary = false,
const std::string &basedir, const size_t max_buffer_size, bool is_binary = false,
const unsigned char *bin_data = nullptr,
size_t bin_size = 0) {
size_t byteLength;
@ -4228,7 +4417,7 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const detail::json &o,
}
if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr,
decoded_uri, basedir, /* required */ true,
byteLength, /* checkSize */ true, fs)) {
byteLength, /* checkSize */ true, /* max_file_size */max_buffer_size, fs)) {
return false;
}
}
@ -4276,7 +4465,7 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const detail::json &o,
}
if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, decoded_uri,
basedir, /* required */ true, byteLength,
/* checkSize */ true, fs)) {
/* checkSize */ true, /* max file size */max_buffer_size, fs)) {
return false;
}
}
@ -5811,7 +6000,7 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
Buffer buffer;
if (!ParseBuffer(&buffer, err, o,
store_original_json_for_extras_and_extensions_, &fs,
&uri_cb, base_dir, is_binary_, bin_data_, bin_size_)) {
&uri_cb, base_dir, max_external_file_size_, is_binary_, bin_data_, bin_size_)) {
return false;
}
@ -6082,7 +6271,7 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
Image image;
if (!ParseImage(&image, idx, err, warn, o,
store_original_json_for_extras_and_extensions_, base_dir,
&fs, &uri_cb, &this->LoadImageData,
max_external_file_size_, &fs, &uri_cb, &this->LoadImageData,
load_image_user_data)) {
return false;
}