3 #include "library/zip.hpp"
9 #include <boost/iostreams/categories.hpp>
10 #include <boost/iostreams/copy.hpp>
11 #include <boost/iostreams/stream.hpp>
12 #include <boost/iostreams/stream_buffer.hpp>
13 #include <boost/iostreams/filter/symmetric.hpp>
14 #include <boost/iostreams/filter/zlib.hpp>
15 #include <boost/iostreams/filtering_stream.hpp>
16 #include <boost/iostreams/device/back_inserter.hpp>
20 uint32_t read32(const unsigned char* buf
, unsigned offset
= 0, unsigned modulo
= 4) throw()
22 return (uint32_t)buf
[offset
% modulo
] |
23 ((uint32_t)buf
[(offset
+ 1) % modulo
] << 8) |
24 ((uint32_t)buf
[(offset
+ 2) % modulo
] << 16) |
25 ((uint32_t)buf
[(offset
+ 3) % modulo
] << 24);
28 uint16_t read16(const unsigned char* buf
) throw()
30 return (uint16_t)buf
[0] | ((uint16_t)buf
[1] << 8);
33 void write16(unsigned char* buf
, uint16_t value
) throw()
35 buf
[0] = (value
) & 0xFF;
36 buf
[1] = (value
>> 8) & 0xFF;
39 void write32(unsigned char* buf
, uint32_t value
) throw()
41 buf
[0] = (value
) & 0xFF;
42 buf
[1] = (value
>> 8) & 0xFF;
43 buf
[2] = (value
>> 16) & 0xFF;
44 buf
[3] = (value
>> 24) & 0xFF;
50 typedef char char_type
;
51 typedef boost::iostreams::source_tag category
;
52 file_input(std::ifstream
& _stream
, size_t* _refcnt
)
53 : stream(_stream
), stream_refcnt(*_refcnt
)
56 position
= stream
.tellg();
57 left_unlimited
= true;
60 file_input(std::ifstream
& _stream
, uint32_t size
, size_t* _refcnt
)
61 : stream(_stream
), stream_refcnt(*_refcnt
)
64 position
= stream
.tellg();
65 left_unlimited
= false;
73 std::streamsize
read(char* s
, std::streamsize n
)
76 stream
.seekg(position
, std::ios_base::beg
);
78 throw std::runtime_error("Can't seek ZIP file");
79 if(!left_unlimited
&& left
== 0)
81 if(!left_unlimited
&& n
> left
)
84 std::streamsize r
= stream
.gcount();
85 if(r
== 0 && stream
.fail())
86 throw std::runtime_error("Can't read compressed data from ZIP file");
96 if(!--stream_refcnt
) {
98 delete &stream_refcnt
;
102 file_input(const file_input
& f
)
103 : stream(f
.stream
), stream_refcnt(f
.stream_refcnt
)
106 position
= f
.position
;
107 left_unlimited
= f
.left_unlimited
;
111 std::ifstream
& stream
;
112 size_t& stream_refcnt
;
113 std::streamoff position
;
117 file_input
& operator=(const file_input
& f
);
123 typedef char char_type
;
124 typedef boost::iostreams::sink_tag category
;
125 vector_output(std::vector
<char>& _stream
)
134 std::streamsize
write(const char* s
, std::streamsize n
)
136 size_t oldsize
= stream
.size();
137 stream
.resize(oldsize
+ n
);
138 memcpy(&stream
[oldsize
], s
, n
);
142 std::vector
<char>& stream
;
145 class size_and_crc_filter_impl
148 typedef char char_type
;
150 size_and_crc_filter_impl()
153 crc
= ::crc32(0, NULL
, 0);
160 bool filter(const char*& src_begin
, const char* src_end
, char*& dest_begin
, char* dest_end
,
163 ptrdiff_t amount
= src_end
- src_begin
;
164 if(flush
&& amount
== 0)
166 if(amount
> dest_end
- dest_begin
)
167 amount
= dest_end
- dest_begin
;
169 crc
= ::crc32(crc
, reinterpret_cast<const unsigned char*>(src_begin
), amount
);
170 memcpy(dest_begin
, src_begin
, amount
);
172 dest_begin
+= amount
;
190 class size_and_crc_filter
: public boost::iostreams::symmetric_filter
<size_and_crc_filter_impl
,
191 std::allocator
<char>>
193 typedef symmetric_filter
<size_and_crc_filter_impl
, std::allocator
<char>> base_type
;
195 typedef typename
base_type::char_type char_type
;
196 typedef typename
base_type::category category
;
197 size_and_crc_filter(int bsize
)
204 return filter().size();
209 return filter().crc32();
213 struct zipfile_member_info
215 bool central_directory_special
; //Central directory, not real member.
216 uint16_t version_needed
;
218 uint16_t compression
;
222 uint32_t compressed_size
;
223 uint32_t uncompressed_size
;
224 std::string filename
;
225 uint32_t header_offset
;
226 uint32_t data_offset
;
227 uint32_t next_offset
;
230 //Parse member starting from current offset.
231 zipfile_member_info
parse_member(std::ifstream
& file
)
233 zipfile_member_info info
;
234 info
.central_directory_special
= false;
235 info
.header_offset
= file
.tellg();
236 //The file header is 30 bytes (this could also hit central header, but that's even larger).
237 unsigned char buffer
[30];
238 if(!(file
.read(reinterpret_cast<char*>(buffer
), 30)))
239 throw std::runtime_error("Can't read file header from ZIP file");
240 uint32_t magic
= read32(buffer
);
241 if(magic
== 0x02014b50) {
242 info
.central_directory_special
= true;
245 if(magic
!= 0x04034b50)
246 throw std::runtime_error("ZIP archive corrupt: Expected file or central directory magic");
247 info
.version_needed
= read16(buffer
+ 4);
248 info
.flags
= read16(buffer
+ 6);
249 info
.compression
= read16(buffer
+ 8);
250 info
.mtime_time
= read16(buffer
+ 10);
251 info
.mtime_day
= read16(buffer
+ 12);
252 info
.crc
= read32(buffer
+ 14);
253 info
.compressed_size
= read32(buffer
+ 18);
254 info
.uncompressed_size
= read32(buffer
+ 22);
255 uint16_t filename_len
= read16(buffer
+ 26);
256 uint16_t extra_len
= read16(buffer
+ 28);
258 throw std::runtime_error("Unsupported ZIP feature: Empty filename not allowed");
259 if(info
.version_needed
> 20) {
260 throw std::runtime_error("Unsupported ZIP feature: Only ZIP versions up to 2.0 supported");
262 if(info
.flags
& 0x2001)
263 throw std::runtime_error("Unsupported ZIP feature: Encryption is not supported");
265 throw std::runtime_error("Unsupported ZIP feature: Indeterminate length not supported");
266 if(info
.flags
& 0x20)
267 throw std::runtime_error("Unsupported ZIP feature: Binary patching is not supported");
268 if(info
.compression
!= 0 && info
.compression
!= 8)
269 throw std::runtime_error("Unsupported ZIP feature: Unsupported compression method");
270 if(info
.compression
== 0 && info
.compressed_size
!= info
.uncompressed_size
)
271 throw std::runtime_error("ZIP archive corrupt: csize ≠usize for stored member");
272 std::vector
<unsigned char> filename_storage
;
273 filename_storage
.resize(filename_len
);
274 if(!(file
.read(reinterpret_cast<char*>(&filename_storage
[0]), filename_len
)))
275 throw std::runtime_error("Can't read file name from zip file");
276 info
.filename
= std::string(reinterpret_cast<char*>(&filename_storage
[0]), filename_len
);
277 info
.data_offset
= info
.header_offset
+ 30 + filename_len
+ extra_len
;
278 info
.next_offset
= info
.data_offset
+ info
.compressed_size
;
283 bool zip_reader::has_member(const std::string
& name
) throw()
285 return (offsets
.count(name
) > 0);
288 std::string
zip_reader::find_first() throw(std::bad_alloc
)
293 return offsets
.begin()->first
;
296 std::string
zip_reader::find_next(const std::string
& name
) throw(std::bad_alloc
)
298 auto i
= offsets
.upper_bound(name
);
299 if(i
== offsets
.end())
305 std::istream
& zip_reader::operator[](const std::string
& name
) throw(std::bad_alloc
, std::runtime_error
)
307 if(!offsets
.count(name
))
308 throw std::runtime_error("No such file '" + name
+ "' in zip archive");
310 zipstream
->seekg(offsets
[name
], std::ios::beg
);
311 zipfile_member_info info
= parse_member(*zipstream
);
313 zipstream
->seekg(info
.data_offset
, std::ios::beg
);
314 if(info
.compression
== 0) {
315 return *new boost::iostreams::stream
<file_input
>(*zipstream
, info
.uncompressed_size
, refcnt
);
316 } else if(info
.compression
== 8) {
317 boost::iostreams::filtering_istream
* s
= new boost::iostreams::filtering_istream();
318 boost::iostreams::zlib_params params
;
319 params
.noheader
= true;
320 s
->push(boost::iostreams::zlib_decompressor(params
));
321 s
->push(file_input(*zipstream
, info
.compressed_size
, refcnt
));
324 throw std::runtime_error("Unsupported ZIP feature: Unsupported compression method");
327 zip_reader::iterator
zip_reader::begin() throw(std::bad_alloc
)
329 return iterator(offsets
.begin());
332 zip_reader::iterator
zip_reader::end() throw(std::bad_alloc
)
334 return iterator(offsets
.end());
337 zip_reader::riterator
zip_reader::rbegin() throw(std::bad_alloc
)
339 return riterator(offsets
.rbegin());
342 zip_reader::riterator
zip_reader::rend() throw(std::bad_alloc
)
344 return riterator(offsets
.rend());
347 zip_reader::~zip_reader() throw()
355 zip_reader::zip_reader(const std::string
& zipfile
) throw(std::bad_alloc
, std::runtime_error
)
357 zipfile_member_info info
;
358 info
.next_offset
= 0;
359 zipstream
= new std::ifstream
;
360 zipstream
->open(zipfile
.c_str(), std::ios::binary
);
364 throw std::runtime_error("Can't open zipfile '" + zipfile
+ "' for reading");
367 zipstream
->seekg(info
.next_offset
);
368 if(zipstream
->fail())
369 throw std::runtime_error("Can't seek ZIP file");
370 info
= parse_member(*zipstream
);
371 if(info
.central_directory_special
)
373 offsets
[info
.filename
] = info
.header_offset
;
377 zip_writer::zip_writer(const std::string
& zipfile
, unsigned _compression
) throw(std::bad_alloc
, std::runtime_error
)
379 compression
= _compression
;
380 zipfile_path
= zipfile
;
381 temp_path
= zipfile
+ ".tmp";
382 zipstream
.open(temp_path
.c_str(), std::ios::binary
);
384 throw std::runtime_error("Can't open zipfile '" + temp_path
+ "' for writing");
388 zip_writer::~zip_writer() throw()
391 remove(temp_path
.c_str());
394 void zip_writer::commit() throw(std::bad_alloc
, std::logic_error
, std::runtime_error
)
397 throw std::logic_error("Can't commit twice");
399 throw std::logic_error("Can't commit with file open");
400 std::vector
<unsigned char> directory_entry
;
401 uint32_t cdirsize
= 0;
402 uint32_t cdiroff
= zipstream
.tellp();
403 if(cdiroff
== (uint32_t)-1)
404 throw std::runtime_error("Can't read current ZIP stream position");
405 for(auto i
: files
) {
406 cdirsize
+= (46 + i
.first
.length());
407 directory_entry
.resize(46 + i
.first
.length());
408 write32(&directory_entry
[0], 0x02014b50);
409 write16(&directory_entry
[4], 3);
410 write16(&directory_entry
[6], 20);
411 write16(&directory_entry
[8], 0);
412 write16(&directory_entry
[10], compression
? 8 : 0);
413 write16(&directory_entry
[12], 0);
414 write16(&directory_entry
[14], 10273);
415 write32(&directory_entry
[16], i
.second
.crc
);
416 write32(&directory_entry
[20], i
.second
.compressed_size
);
417 write32(&directory_entry
[24], i
.second
.uncompressed_size
);
418 write16(&directory_entry
[28], i
.first
.length());
419 write16(&directory_entry
[30], 0);
420 write16(&directory_entry
[32], 0);
421 write16(&directory_entry
[34], 0);
422 write16(&directory_entry
[36], 0);
423 write32(&directory_entry
[38], 0);
424 write32(&directory_entry
[42], i
.second
.offset
);
425 memcpy(&directory_entry
[46], i
.first
.c_str(), i
.first
.length());
426 zipstream
.write(reinterpret_cast<char*>(&directory_entry
[0]), directory_entry
.size());
428 throw std::runtime_error("Failed to write central directory entry to output file");
430 directory_entry
.resize(22);
431 write32(&directory_entry
[0], 0x06054b50);
432 write16(&directory_entry
[4], 0);
433 write16(&directory_entry
[6], 0);
434 write16(&directory_entry
[8], files
.size());
435 write16(&directory_entry
[10], files
.size());
436 write32(&directory_entry
[12], cdirsize
);
437 write32(&directory_entry
[16], cdiroff
);
438 write16(&directory_entry
[20], 0);
439 zipstream
.write(reinterpret_cast<char*>(&directory_entry
[0]), directory_entry
.size());
441 throw std::runtime_error("Failed to write central directory end marker to output file");
443 std::string backup
= zipfile_path
+ ".backup";
444 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
445 //Grumble, Windows seemingly can't do this atomically.
446 remove(backup
.c_str());
448 rename(zipfile_path
.c_str(), backup
.c_str());
449 if(rename(temp_path
.c_str(), zipfile_path
.c_str()) < 0)
450 throw std::runtime_error("Can't rename '" + temp_path
+ "' -> '" + zipfile_path
+ "'");
454 std::ostream
& zip_writer::create_file(const std::string
& name
) throw(std::bad_alloc
, std::logic_error
,
458 throw std::logic_error("Can't open file with file open");
460 throw std::runtime_error("Bad member name");
461 current_compressed_file
.resize(0);
462 s
= new boost::iostreams::filtering_ostream();
463 s
->push(size_and_crc_filter(4096));
465 boost::iostreams::zlib_params params
;
466 params
.noheader
= true;
467 s
->push(boost::iostreams::zlib_compressor(params
));
469 s
->push(vector_output(current_compressed_file
));
474 void zip_writer::close_file() throw(std::bad_alloc
, std::logic_error
, std::runtime_error
)
477 throw std::logic_error("Can't close file with no file open");
478 uint32_t ucs
, cs
, crc32
;
479 boost::iostreams::close(*s
);
480 size_and_crc_filter
& f
= *s
->component
<size_and_crc_filter
>(0);
481 cs
= current_compressed_file
.size();
486 base_offset
= zipstream
.tellp();
487 if(base_offset
== (uint32_t)-1)
488 throw std::runtime_error("Can't read current ZIP stream position");
489 unsigned char header
[30];
490 memset(header
, 0, 30);
491 write32(header
, 0x04034b50);
494 header
[8] = compression
? 8 : 0;
497 write32(header
+ 14, crc32
);
498 write32(header
+ 18, cs
);
499 write32(header
+ 22, ucs
);
500 write16(header
+ 26, open_file
.length());
501 zipstream
.write(reinterpret_cast<char*>(header
), 30);
502 zipstream
.write(open_file
.c_str(), open_file
.length());
503 zipstream
.write(¤t_compressed_file
[0], current_compressed_file
.size());
505 throw std::runtime_error("Can't write member to ZIP file");
506 current_compressed_file
.resize(0);
509 info
.uncompressed_size
= ucs
;
510 info
.compressed_size
= cs
;
511 info
.offset
= base_offset
;
512 files
[open_file
] = info
;
518 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
519 const char* path_splitters
= "\\/";
520 bool drives_allowed
= true;
522 //Assume Unix(-like) system.
523 const char* path_splitters
= "/";
524 bool drives_allowed
= false;
527 const char* str_index(const char* str
, int ch
)
535 bool ispathsep(char ch
)
537 return (str_index(path_splitters
, static_cast<int>(static_cast<unsigned char>(ch
))) != NULL
);
540 bool isroot(const std::string
& path
)
542 if(path
.length() == 1 && ispathsep(path
[0]))
545 //NO more cases for this.
547 if(path
.length() == 3 && path
[0] >= 'A' && path
[0] <= 'Z' && path
[1] == ':' && ispathsep(path
[2]))
550 if(path
.length() <= 3 || !ispathsep(path
[0]) || !ispathsep(path
[1]) ||
551 !ispathsep(path
[path
.length() - 1]))
553 return (path
.find_first_of(path_splitters
, 2) == path
.length() - 1);
556 std::string
walk(const std::string
& path
, const std::string
& component
)
558 if(component
== "" || component
== ".")
561 else if(component
== "..") {
563 if(path
== "" || isroot(path
))
564 throw std::runtime_error("Can't rise to containing directory");
565 std::string _path
= path
;
566 size_t split
= _path
.find_last_of(path_splitters
);
567 if(split
< _path
.length())
568 return _path
.substr(0, split
);
571 } else if(path
== "" || ispathsep(path
[path
.length() - 1]))
572 return path
+ component
;
574 return path
+ "/" + component
;
577 std::string
combine_path(const std::string
& _name
, const std::string
& _referencing_path
)
579 std::string name
= _name
;
580 std::string referencing_path
= _referencing_path
;
581 size_t x
= referencing_path
.find_last_of(path_splitters
);
582 if(x
< referencing_path
.length())
583 referencing_path
= referencing_path
.substr(0, x
);
586 //Check if name is absolute.
587 if(ispathsep(name
[0]))
589 if(drives_allowed
&& name
.length() >= 3 && name
[0] >= 'A' && name
[0] <= 'Z' && name
[1] == ':' &&
592 //It is not absolute.
593 std::string path
= referencing_path
;
596 size_t split
= name
.find_first_of(path_splitters
, pindex
);
598 if(split
< name
.length())
599 c
= name
.substr(pindex
, split
- pindex
);
601 c
= name
.substr(pindex
);
602 path
= walk(path
, c
);
603 if(split
< name
.length())
608 //If path becomes empty, assume it means current directory.
615 std::string
resolve_file_relative(const std::string
& name
, const std::string
& referencing_path
) throw(std::bad_alloc
,
618 return combine_path(name
, referencing_path
);
621 std::istream
& open_file_relative(const std::string
& name
, const std::string
& referencing_path
) throw(std::bad_alloc
,
624 std::string path_to_open
= combine_path(name
, referencing_path
);
625 std::string final_path
= path_to_open
;
626 //Try to open this from the main OS filesystem.
627 std::ifstream
* i
= new std::ifstream(path_to_open
.c_str(), std::ios::binary
);
632 //Didn't succeed. Try to open as ZIP archive.
633 std::string membername
;
635 size_t split
= path_to_open
.find_last_of("/");
636 if(split
>= path_to_open
.length())
637 throw std::runtime_error("Can't open '" + final_path
+ "'");
638 //Move a component to member name.
640 membername
= path_to_open
.substr(split
+ 1) + "/" + membername
;
642 membername
= path_to_open
.substr(split
+ 1);
643 path_to_open
= path_to_open
.substr(0, split
);
645 zip_reader
r(path_to_open
);
646 return r
[membername
];
647 } catch(std::bad_alloc
& e
) {
649 } catch(std::runtime_error
& e
) {
654 std::vector
<char> read_file_relative(const std::string
& name
, const std::string
& referencing_path
) throw(std::bad_alloc
,
657 std::vector
<char> out
;
658 std::istream
& s
= open_file_relative(name
, referencing_path
);
659 boost::iostreams::back_insert_device
<std::vector
<char>> rd(out
);
660 boost::iostreams::copy(s
, rd
);