Mercurial > hg > orthanc
diff OrthancFramework/Sources/Compression/ZipReader.cpp @ 4355:460a71988208
new class: ZipReader
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 08 Dec 2020 12:38:59 +0100 |
parents | |
children | 18c94a82f3d4 |
line wrap: on
line diff
--- /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 + * <http://www.gnu.org/licenses/>. + **/ + + +#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 <string.h> + + +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) + { + } + + 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 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 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<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); + } + + + 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<ZipReader> 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<ZipReader> 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 +}