From 8449b9e46894e58150d88c9323535727706cf20d Mon Sep 17 00:00:00 2001 From: Kumataro Date: Tue, 5 May 2026 07:41:59 +0900 Subject: [PATCH] core: safe handling of compressed file level extension (.gz[0-9]) - Replace unsafe pointer arithmetic and direct buffer modification with std::string methods. - Update documentation to clarify that the last digit is used as compression level and truncated from the actual filename. - Add test cases for .gz and .gz0-9 --- .../core/include/opencv2/core/persistence.hpp | 8 +++-- modules/core/src/persistence.cpp | 33 ++++++++++++++----- modules/core/test/test_io.cpp | 14 ++++++-- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/modules/core/include/opencv2/core/persistence.hpp b/modules/core/include/opencv2/core/persistence.hpp index c6ddf6f0ca..11dccb70e0 100644 --- a/modules/core/include/opencv2/core/persistence.hpp +++ b/modules/core/include/opencv2/core/persistence.hpp @@ -306,8 +306,12 @@ public: before opening the file. @param filename Name of the file to open or the text string to read the data from. Extension of the file (.xml, .yml/.yaml or .json) determines its format (XML, YAML or JSON - respectively). Also you can append .gz to work with compressed files, for example myHugeMatrix.xml.gz. If both - FileStorage::WRITE and FileStorage::MEMORY flags are specified, source is used just to specify + respectively). Also you can append .gz to work with compressed files, for example myHugeMatrix.xml.gz. + You can also specify a compression level from 0 to 9 by appending it to the extension + (e.g. ".gz0" for no compression, ".gz9" for high compression). + The last digit will be truncated internally to write/read. + (e.g. If "a.xml.gz9" is specified, "a.xml.gz" is used for the actual file name.) + If both FileStorage::WRITE and FileStorage::MEMORY flags are specified, source is used just to specify the output file format (e.g. mydata.xml, .yml etc.). A file name can also contain parameters. You can use this format, "*?base64" (e.g. "file.json?base64" (case sensitive)), as an alternative to FileStorage::BASE64 flag. diff --git a/modules/core/src/persistence.cpp b/modules/core/src/persistence.cpp index 38d69b8f70..b10980c07e 100644 --- a/modules/core/src/persistence.cpp +++ b/modules/core/src/persistence.cpp @@ -544,18 +544,30 @@ bool FileStorage::Impl::open(const char *filename_or_buf, int _flags, const char flags = _flags; if (!mem_mode) { - char *dot_pos = strrchr((char *) filename.c_str(), '.'); + size_t dot_idx = filename.find_last_of('.'); char compression = '\0'; - if (dot_pos && dot_pos[1] == 'g' && dot_pos[2] == 'z' && - (dot_pos[3] == '\0' || (cv_isdigit(dot_pos[3]) && dot_pos[4] == '\0'))) { - if (append) { - CV_Error(cv::Error::StsNotImplemented, "Appending data to compressed file is not implemented"); + if (dot_idx != std::string::npos) + { + std::string ext = filename.substr(dot_idx); + + // Instead of `ext.starts_with(".gz") + if (ext.size() >= 3 && (ext[1] == 'g') && (ext[2] == 'z') ) + { + if (ext.size() == 3) + { + // ".gz" + isGZ = true; + } + + else if(ext.size() == 4 && cv_isdigit(ext[3])) + { + // ".gz[0-9]" + isGZ = true; + compression = ext[3]; + filename.pop_back(); // Replace `gz[0-9]' to 'gz'. + } } - isGZ = true; - compression = dot_pos[3]; - if (compression) - dot_pos[3] = '\0'; } if (!isGZ) { @@ -567,6 +579,9 @@ bool FileStorage::Impl::open(const char *filename_or_buf, int _flags, const char } } else { #if USE_ZLIB + if (append) { + CV_Error(cv::Error::StsNotImplemented, "Appending data to compressed file is not implemented"); + } char mode[] = {write_mode ? 'w' : 'r', 'b', compression ? compression : '3', '\0'}; gzfile = gzopen(filename.c_str(), mode); if (!gzfile) diff --git a/modules/core/test/test_io.cpp b/modules/core/test/test_io.cpp index a6d44a3316..42e49c5b93 100644 --- a/modules/core/test/test_io.cpp +++ b/modules/core/test/test_io.cpp @@ -2108,9 +2108,19 @@ T fsWriteRead(const T& expectedValue, const char* ext) fs_w.release(); FileStorage fs_r(fname, FileStorage::READ); - T value; fs_r["value"] >> value; + fs_r.release(); + + // If ext is `.gz[0-9]`, fname on storage will end with `.gz`. + // FileStorage::Impl::open() truncates the last digit internally. + if (isdigit(fname.back())) + { + fname.pop_back(); + } + + remove(fname.c_str()); + return value; } @@ -2143,7 +2153,7 @@ TEST_P(FileStorage_exact_type, long_int) } INSTANTIATE_TEST_CASE_P(Core_InputOutput, - FileStorage_exact_type, Values(".yml", ".xml", ".json") + FileStorage_exact_type, Values(".yml", ".xml", ".json", ".xml.gz", ".xml.gz0", ".xml.gz9") ); }} // namespace