# HG changeset patch # User Sebastien Jodogne # Date 1607427539 -3600 # Node ID 460a71988208ad7e6c7b9765f29be15262bc01b0 # Parent bcfb53d1bc56ad63ec9370245bbae5b09f62fd07 new class: ZipReader diff -r bcfb53d1bc56 -r 460a71988208 OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake --- a/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake Mon Dec 07 20:38:31 2020 +0100 +++ b/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake Tue Dec 08 12:38:59 2020 +0100 @@ -369,6 +369,7 @@ ${CMAKE_CURRENT_LIST_DIR}/../../Sources/Compression/DeflateBaseCompressor.cpp ${CMAKE_CURRENT_LIST_DIR}/../../Sources/Compression/GzipCompressor.cpp ${CMAKE_CURRENT_LIST_DIR}/../../Sources/Compression/IBufferCompressor.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../Sources/Compression/ZipReader.cpp ${CMAKE_CURRENT_LIST_DIR}/../../Sources/Compression/ZlibCompressor.cpp ) diff -r bcfb53d1bc56 -r 460a71988208 OrthancFramework/Sources/Compression/ZipReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancFramework/Sources/Compression/ZipReader.cpp Tue Dec 08 12:38:59 2020 +0100 @@ -0,0 +1,415 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 + * . + **/ + + +#include "../PrecompiledHeaders.h" + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include "ZipReader.h" + +#include "../OrthancException.h" +#include "../../Resources/ThirdParty/minizip/unzip.h" + +#if ORTHANC_SANDBOXED != 1 +# include "../SystemToolbox.h" +#endif + +#include + + +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(p)), + size_(size), + pos_(0) + { + } + + MemoryBuffer(const std::string& s) : + content_(s.empty() ? NULL : reinterpret_cast(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); + if (s + pos_ > size_) + { + s = size_ - pos_; + } + + if (s != 0) + { + memcpy(target, content_ + pos_, s); + } + + pos_ += s; + return s; + } + } + + ZPOS64_T Tell() const + { + return static_cast(pos_); + } + + long Seek(ZPOS64_T offset, + int origin) + { + ssize_t next; + + switch (origin) + { + case ZLIB_FILEFUNC_SEEK_CUR: + next = static_cast(offset) + static_cast(pos_); + break; + + case ZLIB_FILEFUNC_SEEK_SET: + next = static_cast(offset); + break; + + case ZLIB_FILEFUNC_SEEK_END: + next = static_cast(offset) + static_cast(size_); + break; + + default: // Should never occur + return 1; // Error + } + + if (next < 0) + { + pos_ = 0; + } + else if (next >= static_cast(size_)) + { + pos_ = size_; + } + else + { + pos_ = static_cast(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(opaque)->Read(buf, size); + } + + static ZPOS64_T TellWrapper(voidpf opaque, + voidpf stream) + { + assert(opaque != NULL); + return reinterpret_cast(opaque)->Tell(); + } + + static long SeekWrapper(voidpf opaque, + voidpf stream, + ZPOS64_T offset, + int origin) + { + assert(opaque != NULL); + return reinterpret_cast(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(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 std::string& 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 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); + } + + + 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); + } + + filename.resize(info.size_filename); + if (!filename.empty() && + unzGetCurrentFileInfo64(pimpl_->unzip_, &info, &filename[0], filename.size(), NULL, 0, NULL, 0) != 0) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + content.resize(info.uncompressed_size); + + if (!content.empty()) + { + if (unzOpenCurrentFile(pimpl_->unzip_) == 0) + { + bool success = (unzReadCurrentFile(pimpl_->unzip_, &content[0], content.size()) != 0); + + if (unzCloseCurrentFile(pimpl_->unzip_) != 0 || + !success) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + else + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + 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 reader(new ZipReader); + + reader->pimpl_->reader_.reset(new MemoryBuffer(buffer, size)); + if (reader->pimpl_->reader_.get() == NULL) + { + throw OrthancException(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 std::string& path) + { + if (!IsZipFile(path)) + { + throw OrthancException(ErrorCode_BadFileFormat, "The file doesn't contain a ZIP archive: " + path); + } + else + { + std::unique_ptr reader(new ZipReader); + + reader->pimpl_->unzip_ = unzOpen64(path.c_str()); + if (reader->pimpl_->unzip_ == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat, "Cannot open ZIP archive from file: " + path); + } + else + { + reader->SeekFirst(); + return reader.release(); + } + } + } +#endif +} diff -r bcfb53d1bc56 -r 460a71988208 OrthancFramework/Sources/Compression/ZipReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancFramework/Sources/Compression/ZipReader.h Tue Dec 08 12:38:59 2020 +0100 @@ -0,0 +1,86 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 + * . + **/ + + +#pragma once + +#include "../OrthancFramework.h" + +#if !defined(ORTHANC_SANDBOXED) +# error The macro ORTHANC_SANDBOXED must be defined +#endif + +#if !defined(ORTHANC_ENABLE_ZLIB) +# error The macro ORTHANC_ENABLE_ZLIB must be defined +#endif + +#if ORTHANC_ENABLE_ZLIB != 1 +# error ZLIB support must be enabled to include this file +#endif + + +#include +#include +#include +#include + + +namespace Orthanc +{ + class ORTHANC_PUBLIC ZipReader : public boost::noncopyable + { + private: + class MemoryBuffer; + + struct PImpl; + boost::shared_ptr pimpl_; + + ZipReader(); + + void SeekFirst(); + + public: + ~ZipReader(); + + uint64_t GetFilesCount() const; + + bool ReadNextFile(std::string& content, + std::string& filename); + + static ZipReader* CreateFromMemory(const void* buffer, + size_t size); + + static ZipReader* CreateFromMemory(const std::string& buffer); + +#if ORTHANC_SANDBOXED != 1 + static ZipReader* CreateFromFile(const std::string& path); +#endif + + static bool IsZipMemoryBuffer(const void* buffer, + size_t size); + + static bool IsZipMemoryBuffer(const std::string& content); + +#if ORTHANC_SANDBOXED != 1 + static bool IsZipFile(const std::string& path); +#endif + }; +} diff -r bcfb53d1bc56 -r 460a71988208 OrthancFramework/UnitTestsSources/ZipTests.cpp --- a/OrthancFramework/UnitTestsSources/ZipTests.cpp Mon Dec 07 20:38:31 2020 +0100 +++ b/OrthancFramework/UnitTestsSources/ZipTests.cpp Tue Dec 08 12:38:59 2020 +0100 @@ -27,9 +27,10 @@ #include +#include "../Sources/Compression/HierarchicalZipWriter.h" +#include "../Sources/Compression/ZipReader.h" #include "../Sources/OrthancException.h" -#include "../Sources/Compression/ZipWriter.h" -#include "../Sources/Compression/HierarchicalZipWriter.h" +#include "../Sources/TemporaryFile.h" #include "../Sources/Toolbox.h" @@ -182,59 +183,28 @@ } - - - -#include "../Resources/ThirdParty/minizip/unzip.h" - -TEST(ZipReader, DISABLED_Basic) +TEST(ZipReader, Basic) { - unzFile zip = unzOpen("/home/jodogne/DICOM/Demo/BRAINIX.zip"); - printf(">> %d\n", zip); - - unz_global_info info; - printf(">> %d\n", unzGetGlobalInfo(zip, &info)); - printf("%d %d\n", info.number_entry, info.size_comment); - - unsigned int count = 0; - printf(">> %d\n", unzGoToFirstFile(zip)); - for (;;) + TemporaryFile f; + { - char f[1024]; - unz_file_info64_s j; - unzGetCurrentFileInfo64(zip, &j, f, sizeof(f) - 1, NULL, 0, NULL, 0); - printf("[%s] %d %d\n", f, j.uncompressed_size, j.size_filename); - - - printf("%d\n", unzOpenCurrentFile(zip)); - - std::string content; - content.resize(j.uncompressed_size); - if (!content.empty()) - { - printf("%d\n", unzReadCurrentFile(zip, &content[0], content.size())); - - char g[1024]; - sprintf(g, "/tmp/i/zip-%06d.dcm", count); - FILE* h = fopen(g, "wb"); - fwrite(content.c_str(), content.size(), 1, h); - fclose(h); - } - - printf("%d\n", unzCloseCurrentFile(zip)); - - - count += 1; - int i = unzGoToNextFile(zip); - if (i != 0) - { - printf("done\n"); - break; - } + Orthanc::ZipWriter w; + w.SetOutputPath(f.GetPath().c_str()); + w.Open(); + w.OpenFile("world/hello"); + w.Write("Hello world"); } - printf("count: %d\n", count); + ASSERT_TRUE(ZipReader::IsZipFile(f.GetPath())); + + std::unique_ptr reader(ZipReader::CreateFromFile(f.GetPath())); + + ASSERT_EQ(1u, reader->GetFilesCount()); - unzClose(zip); + std::string filename, content; + ASSERT_TRUE(reader->ReadNextFile(filename, content)); + ASSERT_EQ("world/hello", filename); + ASSERT_EQ("Hello world", content); + ASSERT_FALSE(reader->ReadNextFile(filename, content)); }