staticlib_compress  1.2.0
 All Classes Namespaces Files Functions Enumerations
zip_sink.hpp
Go to the documentation of this file.
1 /*
2  * Copyright 2016, alex at staticlibs.net
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 /*
18  * File: zip_sink.hpp
19  * Author: alex
20  *
21  * Created on February 13, 2016, 5:30 PM
22  */
23 
24 #ifndef STATICLIB_COMPRESS_ZIP_SINK_HPP
25 #define STATICLIB_COMPRESS_ZIP_SINK_HPP
26 
27 #include <ios>
28 #include <memory>
29 #include <string>
30 #include <vector>
31 
32 #include "zlib.h"
33 
34 #include "staticlib/config.hpp"
35 #include "staticlib/io.hpp"
36 #include "staticlib/endian.hpp"
37 
41 
42 namespace staticlib {
43 namespace compress {
44 
45 namespace detail {
46 
50 class Header {
51  std::string filename;
52  uint16_t compression_method;
53 
54  uint32_t offset = 0;
55  uint32_t compressed_size = 0;
56  uint32_t uncompressed_size = 0;
57  uint32_t crc = 0;
58 
59 public:
60 
61  Header(std::string filename, uint16_t compression_method) :
62  filename(std::move(filename)),
63  compression_method(compression_method) { }
64 
65  template <typename Sink>
66  void write_local_file_header(Sink& sink, uint32_t offset) {
67  namespace en = staticlib::endian;
68  // Local file header signature
69  en::write_32_le(sink, 0x04034b50);
70  // Version needed to extract (minimum)
71  en::write_16_le(sink, 10);
72  // General purpose bit flag
73  en::write_16_le(sink, 8);
74  // Compression method
75  en::write_16_le(sink, compression_method);
76  // File last modification time
77  en::write_16_le(sink, 0);
78  // File last modification date
79  en::write_16_le(sink, 0);
80  // CRC-32
81  en::write_32_le(sink, 0);
82  // Compressed size
83  en::write_32_le(sink, 0);
84  // Uncompressed size
85  en::write_32_le(sink, 0);
86  // File name length (n)
87  en::write_16_le(sink, filename.length());
88  // Extra field length (m)
89  en::write_16_le(sink, 0);
90  // File name
91  io::write_all(sink, {filename.data(), filename.length()});
92  // save offset for CD
93  this->offset = offset;
94  }
95 
96  template <typename Sink>
97  void write_data_descriptor(Sink& sink, uint32_t compressed_size, uint32_t uncompressed_size, uint32_t crc) {
98  namespace en = staticlib::endian;
99  // Optional data descriptor signature
100  en::write_32_le(sink, 0x08074b50);
101  // CRC-32
102  en::write_32_le(sink, crc);
103  // Compressed size
104  en::write_32_le(sink, compressed_size);
105  // Uncompressed size
106  en::write_32_le(sink, uncompressed_size);
107  // save sizes for CD
108  this->compressed_size = compressed_size;
109  this->uncompressed_size = uncompressed_size;
110  // save crc for CD
111  this->crc = crc;
112  }
113 
114  template <typename Sink>
115  void write_cd_file_header(Sink& sink) {
116  namespace en = staticlib::endian;
117  // Central directory file header signature
118  en::write_32_le(sink, 0x02014b50);
119  // Version made by
120  en::write_16_le(sink, 10);
121  // Version needed to extract (minimum)
122  en::write_16_le(sink, 10);
123  // General purpose bit flag
124  en::write_16_le(sink, 8);
125  // Compression method
126  en::write_16_le(sink, compression_method);
127  // File last modification time
128  en::write_16_le(sink, 0);
129  // File last modification date
130  en::write_16_le(sink, 0);
131  // CRC-32
132  en::write_32_le(sink, crc);
133  // Compressed size
134  en::write_32_le(sink, compressed_size);
135  // Uncompressed size
136  en::write_32_le(sink, uncompressed_size);
137  // File name length (n)
138  en::write_16_le(sink, filename.length());
139  // Extra field length (m)
140  en::write_16_le(sink, 0);
141  // File comment length (k)
142  en::write_16_le(sink, 0);
143  // Disk number where file starts
144  en::write_16_le(sink, 0);
145  // Internal file attributes
146  en::write_16_le(sink, 0);
147  // External file attributes
148  en::write_32_le(sink, 0);
149  // Relative offset of local file header.
150  en::write_32_le(sink, offset);
151  // File name
152  io::write_all(sink, {filename.data(), filename.length()});
153  }
154 
155 };
156 
157 template <typename Sink>
158 void write_eocd(Sink& sink, uint16_t files_count, uint32_t cd_offset, uint32_t cd_length) {
159  namespace en = staticlib::endian;
160  // End of central directory signature
161  en::write_32_le(sink, 0x06054b50);
162  // Number of this disk
163  en::write_16_le(sink, 0);
164  // Disk where central directory starts
165  en::write_16_le(sink, 0);
166  // Number of central directory records on this disk
167  en::write_16_le(sink, files_count);
168  // Total number of central directory records
169  en::write_16_le(sink, files_count);
170  // Size of central directory (bytes)
171  en::write_32_le(sink, cd_length);
172  // Offset of start of central directory
173  en::write_32_le(sink, cd_offset);
174  // Comment length (n)
175  en::write_16_le(sink, 0);
176 }
177 
178 } // namespace
179 
184 template <typename Sink>
185 class zip_sink {
186 private:
187  // stream shortcuts
188  using sink_ref_type = sl::io::reference_sink<sl::io::counting_sink<Sink>>;
189  using entry_counter_ref_type = sl::io::reference_sink<sl::io::counting_sink<sink_ref_type>>;
190  using deflater_type = sl::io::counting_sink<deflate_sink<entry_counter_ref_type>>;
191 
195  zip_compression_method method = zip_compression_method::deflate;
196  sl::io::counting_sink<Sink> sink;
197  std::vector<detail::Header> headers;
198  bool cd_written = false;
199 
200  sl::io::counting_sink<sink_ref_type> entry_counter;
201  std::unique_ptr<deflater_type> entry_deflater;
202  uint32_t entry_crc = 0;
203 
204 public:
205 
211  zip_sink(Sink&& sink) :
212  sink(sl::io::make_counting_sink(std::move(sink))),
213  entry_counter(sl::io::make_counting_sink(this->sink)),
214  entry_deflater(nullptr) { }
215 
219  ~zip_sink() STATICLIB_NOEXCEPT {
220  try {
221  finalize();
222  } catch(...) {
223  // ignore
224  }
225  }
226 
232  zip_sink(const zip_sink&) = delete;
233 
240  zip_sink& operator=(const zip_sink&) = delete;
241 
247  zip_sink(zip_sink&& other) :
248  sink(std::move(other.sink)),
249  headers(std::move(other.headers)),
250  cd_written(other.cd_written),
251  entry_counter(std::move(other.entry_counter)),
252  entry_deflater(std::move(other.entry_deflater)),
253  entry_crc(other.entry_crc) { }
254 
262  sink = std::move(other.sink);
263  headers = std::move(other.headers);
264  cd_written = other.cd_written;
265  entry_counter = std::move(other.entry_counter);
266  entry_deflater = std::move(other.entry_deflater);
267  entry_crc = other.entry_crc;
268  return *this;
269  }
270 
277  std::streamsize write(sl::io::span<const char> span) {
278  if (nullptr != entry_deflater.get()) {
279  size_t count_before = entry_deflater->get_count();
280  entry_deflater->write(span);
281  size_t count = entry_deflater->get_count() - count_before;
282  this->entry_crc = ::crc32(entry_crc, reinterpret_cast<const Bytef*>(span.data()), static_cast<uInt>(count));
283  return static_cast<std::streamsize>(count);
284  } else {
285  throw compress_exception(TRACEMSG("Invalid ZIP sink state: add ZIP entry before writing the data"));
286  }
287  }
288 
294  std::streamsize flush() {
295  return sink.flush();
296  }
297 
303  void add_entry(const std::string& filename) {
304  if (filename.empty()) throw compress_exception(TRACEMSG("Invalid empty entry name specified"));
305  if (cd_written) throw new compress_exception(TRACEMSG(
306  "Invalid entry add attempt for finalized ZIP stream"));
307  if (headers.size() > 0) {
308  write_entry_data_descriptor();
309  }
310  // add new entry
311  headers.emplace_back(std::string(filename.data(), filename.length()), static_cast<uint16_t>(method));
312  headers.back().write_local_file_header(sink, static_cast<uint32_t>(sink.get_count()));
313  entry_deflater.reset(new sl::io::counting_sink<deflate_sink<entry_counter_ref_type>>(make_deflate_sink(entry_counter)));
314  entry_crc = ::crc32(0L, Z_NULL, 0);
315  }
316 
322  void finalize() {
323  if (!cd_written && headers.size() > 0) {
324  write_entry_data_descriptor();
325  uint32_t cd_offset = static_cast<uint32_t>(sink.get_count());
326  for (detail::Header& he : headers) {
327  he.write_cd_file_header(sink);
328  }
329  size_t cd_len = sink.get_count() - cd_offset;
330  detail::write_eocd(sink, static_cast<uint16_t>(headers.size()), cd_offset, static_cast<uint32_t>(cd_len));
331  cd_written = true;
332  }
333  }
334 
335 private:
336  void write_entry_data_descriptor() {
337  // get size and close current entry
338  uint32_t uncompressed_size = static_cast<uint32_t>(entry_deflater->get_count());
339  entry_deflater.reset(nullptr);
340  // get size and reset counter
341  uint32_t compressed_size = static_cast<uint32_t>(entry_counter.get_count());
342  entry_counter = sl::io::make_counting_sink(sink);
343  headers.back().write_data_descriptor(sink, compressed_size, uncompressed_size, entry_crc);
344  }
345 
346 };
347 
355 template <typename Sink,
356 class = typename std::enable_if<!std::is_lvalue_reference<Sink>::value>::type>
358  return zip_sink<Sink>(std::move(sink));
359 }
360 
368 template <typename Sink>
371  sl::io::make_reference_sink(sink));
372 }
373 
374 } // namespace
375 }
376 
377 #endif /* STATICLIB_COMPRESS_ZIP_SINK_HPP */
378