1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/resources/chromeos/zip_archiver/cpp/compressor_archive_minizip.h"
6 
7 #include <algorithm>
8 #include <cerrno>
9 #include <cstring>
10 #include <utility>
11 
12 #include "base/logging.h"
13 #include "base/notreached.h"
14 #include "base/time/time.h"
15 #include "chrome/browser/resources/chromeos/zip_archiver/cpp/compressor_io_javascript_stream.h"
16 #include "chrome/browser/resources/chromeos/zip_archiver/cpp/compressor_stream.h"
17 #include "third_party/minizip/src/mz.h"
18 #include "third_party/minizip/src/mz_strm.h"
19 #include "third_party/minizip/src/mz_zip.h"
20 
21 namespace {
22 
23 const char kCreateArchiveError[] = "Failed to create archive.";
24 const char kAddToArchiveError[] = "Failed to add entry to archive.";
25 const char kCloseArchiveError[] = "Failed to close archive.";
26 
27 // We need at least 256KB for MiniZip.
28 const int64_t kMaximumDataChunkSize = 512 * 1024;
29 
MinizipIsOpen(void * stream)30 int32_t MinizipIsOpen(void* stream) {
31   // The stream is always in an open state since it's tied to the life of the
32   // CompressorArchiveMinizip instance.
33   return MZ_OK;
34 }
35 
36 }  // namespace
37 
38 // vtable for the archive write stream provided to minizip. Only functions which
39 // are necessary for compression are implemented.
40 mz_stream_vtbl CompressorArchiveMinizip::minizip_vtable = {
41     nullptr,                                          /* open */
42     MinizipIsOpen, nullptr,                           /* read */
43     MinizipWrite,  MinizipTell, MinizipSeek, nullptr, /* close */
44     nullptr,                                          /* error */
45     nullptr,                                          /* create */
46     nullptr,                                          /* destroy */
47     nullptr,                                          /* get_prop_int64 */
48     nullptr                                           /* set_prop_int64 */
49 };
50 
51 // Stream object used by minizip to access archive files.
52 struct CompressorArchiveMinizip::MinizipStream {
53   // Must be the first element because minizip internally will cast this
54   // struct into a mz_stream.
55   mz_stream stream_ = {
56       &minizip_vtable, nullptr,
57   };
58 
59   CompressorArchiveMinizip* archive_;
60 
MinizipStreamCompressorArchiveMinizip::MinizipStream61   explicit MinizipStream(CompressorArchiveMinizip* archive)
62       : archive_(archive) {
63     DCHECK(archive);
64   }
65 };
66 
67 // Called when data chunk must be written on the archive. It copies data
68 // from the given buffer processed by minizip to an array buffer and passes
69 // it to compressor_stream.
MinizipWrite(void * stream,const void * buffer,int32_t length)70 int32_t CompressorArchiveMinizip::MinizipWrite(void* stream,
71                                                const void* buffer,
72                                                int32_t length) {
73   return static_cast<MinizipStream*>(stream)->archive_->StreamWrite(buffer,
74                                                                     length);
75 }
76 
StreamWrite(const void * buffer,int32_t write_size)77 int32_t CompressorArchiveMinizip::StreamWrite(const void* buffer,
78                                               int32_t write_size) {
79   int64_t written_bytes = compressor_stream()->Write(
80       offset_, write_size, static_cast<const char*>(buffer));
81 
82   if (written_bytes != write_size)
83     return MZ_STREAM_ERROR;
84 
85   // Update offset_ and length_.
86   offset_ += written_bytes;
87   if (offset_ > length_)
88     length_ = offset_;
89   return write_size;
90 }
91 
92 // Returns the offset from the beginning of the data.
MinizipTell(void * stream)93 int64_t CompressorArchiveMinizip::MinizipTell(void* stream) {
94   return static_cast<MinizipStream*>(stream)->archive_->StreamTell();
95 }
96 
StreamTell()97 int64_t CompressorArchiveMinizip::StreamTell() {
98   return offset_;
99 }
100 
101 // Moves the current offset to the specified position.
MinizipSeek(void * stream,int64_t offset,int32_t origin)102 int32_t CompressorArchiveMinizip::MinizipSeek(void* stream,
103                                               int64_t offset,
104                                               int32_t origin) {
105   return static_cast<MinizipStream*>(stream)->archive_->StreamSeek(offset,
106                                                                    origin);
107 }
108 
StreamSeek(int64_t offset,int32_t origin)109 int32_t CompressorArchiveMinizip::StreamSeek(int64_t offset, int32_t origin) {
110   switch (origin) {
111     case MZ_SEEK_SET:
112       offset_ = std::min(offset, length_);
113       break;
114     case MZ_SEEK_CUR:
115       offset_ = std::min(offset_ + offset, length_);
116       break;
117     case MZ_SEEK_END:
118       offset_ = std::max(length_ + offset, static_cast<int64_t>(0));
119       break;
120     default:
121       NOTREACHED();
122       return MZ_STREAM_ERROR;
123   }
124 
125   return MZ_OK;
126 }
127 
CompressorArchiveMinizip(CompressorStream * compressor_stream)128 CompressorArchiveMinizip::CompressorArchiveMinizip(
129     CompressorStream* compressor_stream)
130     : CompressorArchive(compressor_stream),
131       compressor_stream_(compressor_stream),
132       stream_(std::make_unique<MinizipStream>(this)),
133       zip_file_(nullptr),
134       destination_buffer_(std::make_unique<char[]>(kMaximumDataChunkSize)),
135       offset_(0),
136       length_(0) {
137   static_assert(offsetof(CompressorArchiveMinizip::MinizipStream, stream_) == 0,
138                 "Bad mz_stream offset");
139 }
140 
141 CompressorArchiveMinizip::~CompressorArchiveMinizip() = default;
142 
CreateArchive()143 bool CompressorArchiveMinizip::CreateArchive() {
144   zip_file_.reset(mz_zip_create(nullptr));
145   int32_t result = mz_zip_open(zip_file_.get(), stream_.get(),
146                                MZ_OPEN_MODE_WRITE | MZ_OPEN_MODE_CREATE);
147   if (result != MZ_OK) {
148     set_error_message(kCreateArchiveError);
149     return false;
150   }
151   return true /* Success */;
152 }
153 
AddToArchive(const std::string & filename,int64_t file_size,base::Time modification_time,bool is_directory)154 bool CompressorArchiveMinizip::AddToArchive(const std::string& filename,
155                                             int64_t file_size,
156                                             base::Time modification_time,
157                                             bool is_directory) {
158   // Minizip takes filenames that end with '/' as directories.
159   std::string normalized_filename = filename;
160   if (is_directory) {
161     normalized_filename += "/";
162     // For a directory, some file systems much give us a non-zero file size
163     // (i.e. 4096, page size). Normalize the size of a directory to zero.
164     file_size = 0;
165   }
166 
167   mz_zip_file file_info = {};
168   file_info.creation_date = modification_time.ToTimeT();
169   file_info.modified_date = file_info.creation_date;
170   file_info.accessed_date = file_info.creation_date;
171   file_info.uncompressed_size = file_size;
172   file_info.filename = normalized_filename.c_str();
173   file_info.filename_size = normalized_filename.length();
174   file_info.flag = MZ_ZIP_FLAG_UTF8;
175 
176   // PKWARE .ZIP File Format Specification version 6.3.x
177   const uint16_t ZIP_SPECIFICATION_VERSION_CODE = 63;
178   file_info.version_madeby =
179       MZ_HOST_SYSTEM_UNIX << 8 | ZIP_SPECIFICATION_VERSION_CODE;
180 
181   // Compression options.
182   file_info.compression_method = MZ_COMPRESS_METHOD_DEFLATE;
183   file_info.zip64 = MZ_ZIP64_AUTO;
184 
185   int32_t open_result = mz_zip_entry_write_open(
186       zip_file_.get(), &file_info, MZ_COMPRESS_LEVEL_DEFAULT, 0 /* raw */,
187       nullptr /* password */);
188   if (open_result != MZ_OK) {
189     LOG(ERROR) << "Archive creation error " << open_result;
190     CloseArchive(true /* has_error */);
191     set_error_message(kAddToArchiveError);
192     return false /* Error */;
193   }
194 
195   bool has_error = false;
196   if (!is_directory) {
197     int64_t remaining_size = file_size;
198     while (remaining_size > 0) {
199       int64_t chunk_size = std::min(remaining_size, kMaximumDataChunkSize);
200       DCHECK_GT(chunk_size, 0);
201 
202       int64_t read_bytes =
203           compressor_stream_->Read(chunk_size, destination_buffer_.get());
204       // Negative read_bytes indicates an error occurred when reading chunks.
205       // 0 just means there is no more data available, but here we need positive
206       // length of bytes, so this is also an error here.
207       if (read_bytes <= 0) {
208         has_error = true;
209         break;
210       }
211 
212       if (canceled_) {
213         break;
214       }
215 
216       int32_t write_result = mz_zip_entry_write(
217           zip_file_.get(), destination_buffer_.get(), read_bytes);
218       if (write_result != read_bytes) {
219         LOG(ERROR) << "Zip entry write error " << write_result;
220         has_error = true;
221         break;
222       }
223       remaining_size -= read_bytes;
224     }
225   }
226 
227   int32_t close_result = mz_zip_entry_close(zip_file_.get());
228   if (close_result != MZ_OK) {
229     LOG(ERROR) << "Zip entry close error " << close_result;
230     has_error = true;
231   }
232 
233   if (has_error) {
234     CloseArchive(true /* has_error */);
235     set_error_message(kAddToArchiveError);
236     return false /* Error */;
237   }
238 
239   if (canceled_) {
240     CloseArchive(true /* has_error */);
241     return false /* Error */;
242   }
243 
244   return true /* Success */;
245 }
246 
CloseArchive(bool has_error)247 bool CompressorArchiveMinizip::CloseArchive(bool has_error) {
248   int32_t result = mz_zip_close(zip_file_.get());
249   if (result != MZ_OK) {
250     set_error_message(kCloseArchiveError);
251     return false /* Error */;
252   }
253   if (!has_error) {
254     if (compressor_stream()->Flush() < 0) {
255       set_error_message(kCloseArchiveError);
256       return false /* Error */;
257     }
258   }
259   return true /* Success */;
260 }
261 
CancelArchive()262 void CompressorArchiveMinizip::CancelArchive() {
263   canceled_ = true;
264 }
265