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