changeset 4355:460a71988208

new class: ZipReader
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 08 Dec 2020 12:38:59 +0100
parents bcfb53d1bc56
children 18c94a82f3d4
files OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake OrthancFramework/Sources/Compression/ZipReader.cpp OrthancFramework/Sources/Compression/ZipReader.h OrthancFramework/UnitTestsSources/ZipTests.cpp
diffstat 4 files changed, 523 insertions(+), 51 deletions(-) [+]
line wrap: on
line diff
--- 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
     )
 
--- /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
+}
--- /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
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#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 <stdint.h>
+#include <string>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+
+namespace Orthanc
+{
+  class ORTHANC_PUBLIC ZipReader : public boost::noncopyable
+  {
+  private:
+    class MemoryBuffer;
+    
+    struct PImpl;
+    boost::shared_ptr<PImpl>   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
+  };
+}
--- 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 <gtest/gtest.h>
 
+#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<ZipReader> 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));
 }