Mercurial > hg > orthanc
view OrthancFramework/Sources/Compression/ZipReader.cpp @ 7129:662d58ce7ddf Orthanc-1.13.0
Orthanc-1.13.0
| author | Sebastien Jodogne <s.jodogne@gmail.com> |
|---|---|
| date | Sat, 15 Aug 2026 13:53:33 +0200 |
| parents | 91a63f8a96ec |
| children | 4ba287b00973 |
line wrap: on
line source
/** * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * Copyright (C) 2017-2023 Osimis S.A., Belgium * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/>. **/ #include "../PrecompiledHeaders.h" #ifndef NOMINMAX #define NOMINMAX #endif #if !defined(ORTHANC_USE_SYSTEM_MINIZIP) # error The macro ORTHANC_USE_SYSTEM_MINIZIP must be defined #endif #include "ZipReader.h" #if ORTHANC_USE_SYSTEM_MINIZIP == 1 # include <minizip/unzip.h> #else # include "../../Resources/ThirdParty/minizip/unzip.h" #endif #include "../ChunkedBuffer.h" #include "../OrthancException.h" #include "../MultiThreading/ReaderWriterLock.h" #if ORTHANC_SANDBOXED != 1 # include "../SystemToolbox.h" #endif /** * I have not been able to correctly define "ssize_t" on all versions * of Visual Studio. As a consequence, I preferred to switch "ssize_t" * to "SSIZE_T", that is properly defined on both MSVC 2008 and 2015. * I define the macro "SSIZE_T" as an alias to "ssize_t" on * POSIX-compliant platforms that wouldn't have "SSIZE_T" defined. **/ #if defined(_MSC_VER) # include <BaseTsd.h> // Definition of SSIZE_T #else # if !defined(SSIZE_T) typedef ssize_t SSIZE_T; # endif #endif #include <string.h> static Orthanc::ReaderWriterLock maximumUncompressedFileSizeMutex_; static bool hasMaximumUncompressedFileSize_ = false; static size_t maximumUncompressedFileSize_ = 0; namespace Orthanc { // ZPOS64_T corresponds to "uint64_t" class ZipReader::MemoryBuffer : public boost::noncopyable { private: const uint8_t* content_; size_t size_; size_t pos_; public: MemoryBuffer(const void* p, size_t size) : content_(reinterpret_cast<const uint8_t*>(p)), size_(size), pos_(0) { } explicit MemoryBuffer(const std::string& s) : content_(s.empty() ? NULL : reinterpret_cast<const uint8_t*>(s.c_str())), size_(s.size()), pos_(0) { } // Returns the number of bytes actually read uLong Read(void *target, uLong size) { if (size <= 0) { return 0; } else { size_t s = static_cast<size_t>(size); if (s + pos_ > size_) { s = size_ - pos_; } if (s != 0) { memcpy(target, content_ + pos_, s); } pos_ += s; return static_cast<uLong>(s); } } ZPOS64_T Tell() const { return static_cast<ZPOS64_T>(pos_); } long Seek(ZPOS64_T offset, int origin) { SSIZE_T next; switch (origin) { case ZLIB_FILEFUNC_SEEK_CUR: next = static_cast<SSIZE_T>(offset) + static_cast<SSIZE_T>(pos_); break; case ZLIB_FILEFUNC_SEEK_SET: next = static_cast<SSIZE_T>(offset); break; case ZLIB_FILEFUNC_SEEK_END: next = static_cast<SSIZE_T>(offset) + static_cast<SSIZE_T>(size_); break; default: // Should never occur return 1; // Error } if (next < 0) { pos_ = 0; } else if (next >= static_cast<long>(size_)) { pos_ = size_; } else { pos_ = static_cast<long>(next); } return 0; } static voidpf OpenWrapper(voidpf opaque, const void* filename, int mode) { // Don't return NULL to make "unzip.c" happy return opaque; } static uLong ReadWrapper(voidpf opaque, voidpf stream, void* buf, uLong size) { assert(opaque != NULL); return reinterpret_cast<MemoryBuffer*>(opaque)->Read(buf, size); } static ZPOS64_T TellWrapper(voidpf opaque, voidpf stream) { assert(opaque != NULL); return reinterpret_cast<MemoryBuffer*>(opaque)->Tell(); } static long SeekWrapper(voidpf opaque, voidpf stream, ZPOS64_T offset, int origin) { assert(opaque != NULL); return reinterpret_cast<MemoryBuffer*>(opaque)->Seek(offset, origin); } static int CloseWrapper(voidpf opaque, voidpf stream) { return 0; } static int TestErrorWrapper(voidpf opaque, voidpf stream) { return 0; // ?? } }; ZipReader* ZipReader::CreateFromMemory(const std::string& buffer) { if (buffer.empty()) { return CreateFromMemory(NULL, 0); } else { return CreateFromMemory(buffer.c_str(), buffer.size()); } } bool ZipReader::IsZipMemoryBuffer(const void* buffer, size_t size) { if (size < 4) { return false; } else { const uint8_t* c = reinterpret_cast<const uint8_t*>(buffer); return (c[0] == 0x50 && // 'P' c[1] == 0x4b && // 'K' ((c[2] == 0x03 && c[3] == 0x04) || (c[2] == 0x05 && c[3] == 0x06) || (c[2] == 0x07 && c[3] == 0x08))); } } bool ZipReader::IsZipMemoryBuffer(const std::string& content) { if (content.empty()) { return false; } else { return IsZipMemoryBuffer(content.c_str(), content.size()); } } #if ORTHANC_SANDBOXED != 1 bool ZipReader::IsZipFile(const boost::filesystem::path& path) { std::string content; SystemToolbox::ReadFileRange(content, path, 0, 4, false /* don't throw if file is too small */); return IsZipMemoryBuffer(content); } #endif struct ZipReader::PImpl { unzFile unzip_; std::unique_ptr<MemoryBuffer> reader_; bool done_; PImpl() : unzip_(NULL), done_(true) { } }; ZipReader::ZipReader() : pimpl_(new PImpl) { } ZipReader::~ZipReader() { if (pimpl_->unzip_ != NULL) { unzClose(pimpl_->unzip_); pimpl_->unzip_ = NULL; } } uint64_t ZipReader::GetFilesCount() const { assert(pimpl_->unzip_ != NULL); unz_global_info64_s info; if (unzGetGlobalInfo64(pimpl_->unzip_, &info) == 0) { return info.number_entry; } else { throw OrthancException(ErrorCode_BadFileFormat); } } void ZipReader::SeekFirst() { assert(pimpl_->unzip_ != NULL); pimpl_->done_ = (unzGoToFirstFile(pimpl_->unzip_) != 0); } static bool ReadInternal(std::string& content, const unzFile& unzip, uint64_t size) ORTHANC_NOEXCEPT { #if 0 /** * This was the code using in Orthanc <= 1.12.10 **/ content.resize(static_cast<size_t>(size)); return (unzReadCurrentFile(unzip, &content[0], static_cast<uLong>(content.size())) != 0); #else /** * Read chunk by chunk to disarm ZIP bombs **/ ChunkedBuffer buffer; std::string chunk; chunk.resize(static_cast<size_t>(10) * 1024 * 1024); // Read by chunks of 10MB for (;;) { int r = unzReadCurrentFile(unzip, &chunk[0], chunk.size()); if (r == 0) { // We're done if (buffer.GetNumBytes() == size) { buffer.Flatten(content); return true; } else { return false; // Presumably a malicious ZIP file } } else if (r < 0) { // Error (might come from a malicious ZIP file) return false; } else if (static_cast<uint64_t>(buffer.GetNumBytes()) + r > size) { // Presumably a ZIP bomb return false; } else { buffer.AddChunk(&chunk[0], r); } } #endif } bool ZipReader::ReadNextFile(std::string& filename, std::string& content) { assert(pimpl_->unzip_ != NULL); if (pimpl_->done_) { return false; } else { unz_file_info64_s info; if (unzGetCurrentFileInfo64(pimpl_->unzip_, &info, NULL, 0, NULL, 0, NULL, 0) != 0) { throw OrthancException(ErrorCode_BadFileFormat); } if (static_cast<uint64_t>(static_cast<size_t>(info.uncompressed_size)) != info.uncompressed_size) { // Too large file for a 32bit architecture throw OrthancException(ErrorCode_NotEnoughMemory); } filename.resize(info.size_filename); if (!filename.empty() && unzGetCurrentFileInfo64(pimpl_->unzip_, &info, &filename[0], static_cast<uLong>(filename.size()), NULL, 0, NULL, 0) != 0) { throw OrthancException(ErrorCode_BadFileFormat); } { // Prevent ZIP bombs ReaderWriterLock::ReadLock lock(maximumUncompressedFileSizeMutex_); if (hasMaximumUncompressedFileSize_ && info.uncompressed_size > maximumUncompressedFileSize_) { char s[32]; sprintf(s, "%0.1f", static_cast<float>(maximumUncompressedFileSize_) / (1024.0f * 1024.0f)); throw OrthancException(ErrorCode_BadFileFormat, "Uncompressed size exceeds limit (" + std::string(s) + "MB)"); } } if (info.uncompressed_size == 0) { content.clear(); } else { if (unzOpenCurrentFile(pimpl_->unzip_) == 0) { bool success = ReadInternal(content, pimpl_->unzip_, info.uncompressed_size); if (unzCloseCurrentFile(pimpl_->unzip_) != 0 || !success) { throw OrthancException(ErrorCode_BadFileFormat); } } else { throw OrthancException(ErrorCode_BadFileFormat, "Invalid file or unsupported compression method (e.g. Deflate64)"); } } pimpl_->done_ = (unzGoToNextFile(pimpl_->unzip_) != 0); return true; } } ZipReader* ZipReader::CreateFromMemory(const void* buffer, size_t size) { if (!IsZipMemoryBuffer(buffer, size)) { throw OrthancException(ErrorCode_BadFileFormat, "The memory buffer doesn't contain a ZIP archive"); } else { std::unique_ptr<ZipReader> reader(new ZipReader); reader->pimpl_->reader_.reset(new MemoryBuffer(buffer, size)); if (reader->pimpl_->reader_.get() == NULL) { THROW_WITH_FILE_AND_LINE_INFO(ErrorCode_InternalError); } zlib_filefunc64_def funcs; memset(&funcs, 0, sizeof(funcs)); funcs.opaque = reader->pimpl_->reader_.get(); funcs.zopen64_file = MemoryBuffer::OpenWrapper; funcs.zread_file = MemoryBuffer::ReadWrapper; funcs.ztell64_file = MemoryBuffer::TellWrapper; funcs.zseek64_file = MemoryBuffer::SeekWrapper; funcs.zclose_file = MemoryBuffer::CloseWrapper; funcs.zerror_file = MemoryBuffer::TestErrorWrapper; reader->pimpl_->unzip_ = unzOpen2_64(NULL, &funcs); if (reader->pimpl_->unzip_ == NULL) { throw OrthancException(ErrorCode_BadFileFormat, "Cannot open ZIP archive from memory buffer"); } else { reader->SeekFirst(); return reader.release(); } } } #if ORTHANC_SANDBOXED != 1 ZipReader* ZipReader::CreateFromFile(const boost::filesystem::path& path) { if (!IsZipFile(path)) { throw OrthancException(ErrorCode_BadFileFormat, "The file doesn't contain a ZIP archive: " + SystemToolbox::PathToUtf8(path)); } else { std::unique_ptr<ZipReader> reader(new ZipReader); reader->pimpl_->unzip_ = unzOpen64(SystemToolbox::PathToUtf8(path).c_str()); if (reader->pimpl_->unzip_ == NULL) { throw OrthancException(ErrorCode_BadFileFormat, "Cannot open ZIP archive from file: " + SystemToolbox::PathToUtf8(path)); } else { reader->SeekFirst(); return reader.release(); } } } #endif void ZipReader::SetMaximumUncompressedFileSize(uint64_t size) { if (static_cast<uint64_t>(static_cast<size_t>(size)) != size) { throw OrthancException(ErrorCode_NotEnoughMemory); } else { ReaderWriterLock::WriteLock lock(maximumUncompressedFileSizeMutex_); hasMaximumUncompressedFileSize_ = true; maximumUncompressedFileSize_ = size; } } }
