Lua: Don't lua_error() out of context with pending dtors
[lsnes.git] / src / library / zip.cpp
blob89a8619c431e60ee1e7af57bfcdf8f608fb05d72
1 #include "zip.hpp"
2 #include "directory.hpp"
3 #include "serialization.hpp"
5 #include <cstdint>
6 #include <cstring>
7 #include <iostream>
8 #include <stdexcept>
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>
18 namespace zip
20 namespace
22 class file_input
24 public:
25 typedef char char_type;
26 typedef boost::iostreams::source_tag category;
27 file_input(std::ifstream& _stream, size_t* _refcnt)
28 : stream(_stream), stream_refcnt(*_refcnt)
30 stream_refcnt++;
31 position = stream.tellg();
32 left_unlimited = true;
35 file_input(std::ifstream& _stream, uint64_t size, size_t* _refcnt)
36 : stream(_stream), stream_refcnt(*_refcnt)
38 stream_refcnt++;
39 position = stream.tellg();
40 left_unlimited = false;
41 left = size;
44 void close()
48 std::streamsize read(char* s, std::streamsize n)
50 stream.clear();
51 stream.seekg(position, std::ios_base::beg);
52 if(stream.fail())
53 throw std::runtime_error("Can't seek ZIP file");
54 if(!left_unlimited && left == 0)
55 return -1;
56 if(!left_unlimited && n > (int64_t)left)
57 n = left;
58 stream.read(s, n);
59 std::streamsize r = stream.gcount();
60 if(r == 0 && stream.fail())
61 throw std::runtime_error("Can't read compressed data from ZIP file");
62 if(!stream && r == 0)
63 return -1;
64 position += r;
65 left -= r;
66 return r;
69 ~file_input()
71 if(!--stream_refcnt) {
72 delete &stream;
73 delete &stream_refcnt;
77 file_input(const file_input& f)
78 : stream(f.stream), stream_refcnt(f.stream_refcnt)
80 stream_refcnt++;
81 position = f.position;
82 left_unlimited = f.left_unlimited;
83 left = f.left;
85 protected:
86 std::ifstream& stream;
87 size_t& stream_refcnt;
88 std::streamoff position;
89 bool left_unlimited;
90 uint64_t left;
91 private:
92 file_input& operator=(const file_input& f);
95 class vector_output
97 public:
98 typedef char char_type;
99 typedef boost::iostreams::sink_tag category;
100 vector_output(std::vector<char>& _stream)
101 : stream(_stream)
105 void close()
109 std::streamsize write(const char* s, std::streamsize n)
111 size_t oldsize = stream.size();
112 stream.resize(oldsize + n);
113 memcpy(&stream[oldsize], s, n);
114 return n;
116 protected:
117 std::vector<char>& stream;
120 class size_and_crc_filter_impl
122 public:
123 typedef char char_type;
125 size_and_crc_filter_impl()
127 dsize = 0;
128 crc = ::crc32(0, NULL, 0);
131 void close()
135 bool filter(const char*& src_begin, const char* src_end, char*& dest_begin, char* dest_end,
136 bool flush)
138 ptrdiff_t amount = src_end - src_begin;
139 if(flush && amount == 0)
140 return false;
141 if(amount > dest_end - dest_begin)
142 amount = dest_end - dest_begin;
143 dsize += amount;
144 crc = ::crc32(crc, reinterpret_cast<const unsigned char*>(src_begin), amount);
145 memcpy(dest_begin, src_begin, amount);
146 src_begin += amount;
147 dest_begin += amount;
148 return true;
151 uint32_t size()
153 return dsize;
156 uint32_t crc32()
158 return crc;
160 private:
161 uint32_t dsize;
162 uint32_t crc;
165 class size_and_crc_filter : public boost::iostreams::symmetric_filter<size_and_crc_filter_impl,
166 std::allocator<char>>
168 typedef symmetric_filter<size_and_crc_filter_impl, std::allocator<char>> base_type;
169 public:
170 typedef typename base_type::char_type char_type;
171 typedef typename base_type::category category;
172 size_and_crc_filter(int bsize)
173 : base_type(bsize)
177 uint32_t size()
179 return filter().size();
182 uint32_t crc32()
184 return filter().crc32();
188 struct zipfile_member_info
190 bool central_directory_special; //Central directory, not real member.
191 uint16_t version_needed;
192 uint16_t flags;
193 uint16_t compression;
194 uint16_t mtime_time;
195 uint16_t mtime_day;
196 uint32_t crc;
197 uint32_t compressed_size;
198 uint32_t uncompressed_size;
199 std::string filename;
200 uint32_t header_offset;
201 uint32_t data_offset;
202 uint32_t next_offset;
205 //Parse member starting from current offset.
206 zipfile_member_info parse_member(std::ifstream& file)
208 zipfile_member_info info;
209 info.central_directory_special = false;
210 info.header_offset = file.tellg();
211 //The file header is 30 bytes (this could also hit central header, but that's even larger).
212 unsigned char buffer[30];
213 if(!(file.read(reinterpret_cast<char*>(buffer), 30)))
214 throw std::runtime_error("Can't read file header from ZIP file");
215 uint32_t magic = serialization::u32l(buffer);
216 if(magic == 0x02014b50) {
217 info.central_directory_special = true;
218 return info;
220 if(magic != 0x04034b50)
221 throw std::runtime_error("ZIP archive corrupt: Expected file or central directory magic");
222 info.version_needed = serialization::u16l(buffer + 4);
223 info.flags = serialization::u16l(buffer + 6);
224 info.compression = serialization::u16l(buffer + 8);
225 info.mtime_time = serialization::u16l(buffer + 10);
226 info.mtime_day = serialization::u16l(buffer + 12);
227 info.crc = serialization::u32l(buffer + 14);
228 info.compressed_size = serialization::u32l(buffer + 18);
229 info.uncompressed_size = serialization::u32l(buffer + 22);
230 uint16_t filename_len = serialization::u16l(buffer + 26);
231 uint16_t extra_len = serialization::u16l(buffer + 28);
232 if(!filename_len)
233 throw std::runtime_error("Unsupported ZIP feature: Empty filename not allowed");
234 if(info.version_needed > 20) {
235 throw std::runtime_error("Unsupported ZIP feature: Only ZIP versions up to 2.0 supported");
237 if(info.flags & 0x2001)
238 throw std::runtime_error("Unsupported ZIP feature: Encryption is not supported");
239 if(info.flags & 0x8)
240 throw std::runtime_error("Unsupported ZIP feature: Indeterminate length not supported");
241 if(info.flags & 0x20)
242 throw std::runtime_error("Unsupported ZIP feature: Binary patching is not supported");
243 if(info.compression != 0 && info.compression != 8)
244 throw std::runtime_error("Unsupported ZIP feature: Unsupported compression method");
245 if(info.compression == 0 && info.compressed_size != info.uncompressed_size)
246 throw std::runtime_error("ZIP archive corrupt: csize ≠ usize for stored member");
247 std::vector<unsigned char> filename_storage;
248 filename_storage.resize(filename_len);
249 if(!(file.read(reinterpret_cast<char*>(&filename_storage[0]), filename_len)))
250 throw std::runtime_error("Can't read file name from zip file");
251 info.filename = std::string(reinterpret_cast<char*>(&filename_storage[0]), filename_len);
252 info.data_offset = info.header_offset + 30 + filename_len + extra_len;
253 info.next_offset = info.data_offset + info.compressed_size;
254 return info;
258 bool reader::has_member(const std::string& name) throw()
260 return (offsets.count(name) > 0);
263 std::string reader::find_first() throw(std::bad_alloc)
265 if(offsets.empty())
266 return "";
267 else
268 return offsets.begin()->first;
271 std::string reader::find_next(const std::string& name) throw(std::bad_alloc)
273 auto i = offsets.upper_bound(name);
274 if(i == offsets.end())
275 return "";
276 else
277 return i->first;
280 std::istream& reader::operator[](const std::string& name) throw(std::bad_alloc, std::runtime_error)
282 if(!offsets.count(name))
283 throw std::runtime_error("No such file '" + name + "' in zip archive");
284 zipstream->clear();
285 zipstream->seekg(offsets[name], std::ios::beg);
286 zipfile_member_info info = parse_member(*zipstream);
287 zipstream->clear();
288 zipstream->seekg(info.data_offset, std::ios::beg);
289 if(info.compression == 0) {
290 return *new boost::iostreams::stream<file_input>(*zipstream, info.uncompressed_size, refcnt);
291 } else if(info.compression == 8) {
292 boost::iostreams::filtering_istream* s = new boost::iostreams::filtering_istream();
293 boost::iostreams::zlib_params params;
294 params.noheader = true;
295 s->push(boost::iostreams::zlib_decompressor(params));
296 s->push(file_input(*zipstream, info.compressed_size, refcnt));
297 return *s;
298 } else
299 throw std::runtime_error("Unsupported ZIP feature: Unsupported compression method");
302 reader::iterator reader::begin() throw(std::bad_alloc)
304 return iterator(offsets.begin());
307 reader::iterator reader::end() throw(std::bad_alloc)
309 return iterator(offsets.end());
312 reader::riterator reader::rbegin() throw(std::bad_alloc)
314 return riterator(offsets.rbegin());
317 reader::riterator reader::rend() throw(std::bad_alloc)
319 return riterator(offsets.rend());
322 reader::~reader() throw()
324 if(!--*refcnt) {
325 delete zipstream;
326 delete refcnt;
330 reader::reader(const std::string& zipfile) throw(std::bad_alloc, std::runtime_error)
332 if(!directory::is_regular(zipfile))
333 throw std::runtime_error("Zipfile '" + zipfile + "' is not regular file");
334 zipstream = NULL;
335 refcnt = NULL;
336 try {
337 zipfile_member_info info;
338 info.next_offset = 0;
339 zipstream = new std::ifstream;
340 zipstream->open(zipfile.c_str(), std::ios::binary);
341 refcnt = new size_t;
342 *refcnt = 1;
343 if(!*zipstream)
344 throw std::runtime_error("Can't open zipfile '" + zipfile + "' for reading");
345 do {
346 zipstream->clear();
347 zipstream->seekg(info.next_offset);
348 if(zipstream->fail())
349 throw std::runtime_error("Can't seek ZIP file");
350 info = parse_member(*zipstream);
351 if(info.central_directory_special)
352 break;
353 offsets[info.filename] = info.header_offset;
354 } while(1);
355 } catch(...) {
356 delete zipstream;
357 delete refcnt;
358 throw;
362 bool reader::read_linefile(const std::string& member, std::string& out, bool conditional)
363 throw(std::bad_alloc, std::runtime_error)
365 if(conditional && !has_member(member))
366 return false;
367 std::istream& m = (*this)[member];
368 try {
369 std::getline(m, out);
370 istrip_CR(out);
371 delete &m;
372 } catch(...) {
373 delete &m;
374 throw;
376 return true;
379 void reader::read_raw_file(const std::string& member, std::vector<char>& out) throw(std::bad_alloc,
380 std::runtime_error)
382 std::vector<char> _out;
383 std::istream& m = (*this)[member];
384 try {
385 boost::iostreams::back_insert_device<std::vector<char>> rd(_out);
386 boost::iostreams::copy(m, rd);
387 delete &m;
388 } catch(...) {
389 delete &m;
390 throw;
392 out = _out;
395 writer::writer(const std::string& zipfile, unsigned _compression) throw(std::bad_alloc, std::runtime_error)
397 compression = _compression;
398 zipfile_path = zipfile;
399 temp_path = zipfile + ".tmp";
400 zipstream = new std::ofstream(temp_path.c_str(), std::ios::binary);
401 if(!*zipstream)
402 throw std::runtime_error("Can't open zipfile '" + temp_path + "' for writing");
403 committed = false;
404 system_stream = true;
407 writer::writer(std::ostream& stream, unsigned _compression) throw(std::bad_alloc, std::runtime_error)
409 compression = _compression;
410 zipstream = &stream;
411 committed = false;
412 system_stream = false;
415 writer::~writer() throw()
417 if(!committed && system_stream)
418 remove(temp_path.c_str());
419 if(system_stream)
420 delete zipstream;
423 void writer::commit() throw(std::bad_alloc, std::logic_error, std::runtime_error)
425 if(committed)
426 throw std::logic_error("Can't commit twice");
427 if(open_file != "")
428 throw std::logic_error("Can't commit with file open");
429 std::vector<unsigned char> directory_entry;
430 uint32_t cdirsize = 0;
431 uint32_t cdiroff = zipstream->tellp();
432 if(cdiroff == (uint32_t)-1)
433 throw std::runtime_error("Can't read current ZIP stream position");
434 for(auto i : files) {
435 cdirsize += (46 + i.first.length());
436 directory_entry.resize(46 + i.first.length());
437 serialization::u32l(&directory_entry[0], 0x02014b50);
438 serialization::u16l(&directory_entry[4], 3);
439 serialization::u16l(&directory_entry[6], 20);
440 serialization::u16l(&directory_entry[8], 0);
441 serialization::u16l(&directory_entry[10], compression ? 8 : 0);
442 serialization::u16l(&directory_entry[12], 0);
443 serialization::u16l(&directory_entry[14], 10273);
444 serialization::u32l(&directory_entry[16], i.second.crc);
445 serialization::u32l(&directory_entry[20], i.second.compressed_size);
446 serialization::u32l(&directory_entry[24], i.second.uncompressed_size);
447 serialization::u16l(&directory_entry[28], i.first.length());
448 serialization::u16l(&directory_entry[30], 0);
449 serialization::u16l(&directory_entry[32], 0);
450 serialization::u16l(&directory_entry[34], 0);
451 serialization::u16l(&directory_entry[36], 0);
452 serialization::u32l(&directory_entry[38], 0);
453 serialization::u32l(&directory_entry[42], i.second.offset);
454 memcpy(&directory_entry[46], i.first.c_str(), i.first.length());
455 zipstream->write(reinterpret_cast<char*>(&directory_entry[0]), directory_entry.size());
456 if(!*zipstream)
457 throw std::runtime_error("Failed to write central directory entry to output file");
459 directory_entry.resize(22);
460 serialization::u32l(&directory_entry[0], 0x06054b50);
461 serialization::u16l(&directory_entry[4], 0);
462 serialization::u16l(&directory_entry[6], 0);
463 serialization::u16l(&directory_entry[8], files.size());
464 serialization::u16l(&directory_entry[10], files.size());
465 serialization::u32l(&directory_entry[12], cdirsize);
466 serialization::u32l(&directory_entry[16], cdiroff);
467 serialization::u16l(&directory_entry[20], 0);
468 zipstream->write(reinterpret_cast<char*>(&directory_entry[0]), directory_entry.size());
469 if(!*zipstream)
470 throw std::runtime_error("Failed to write central directory end marker to output file");
471 if(system_stream) {
472 dynamic_cast<std::ofstream*>(zipstream)->close();
473 std::string backup = zipfile_path + ".backup";
474 directory::rename_overwrite(zipfile_path.c_str(), backup.c_str());
475 if(directory::rename_overwrite(temp_path.c_str(), zipfile_path.c_str()) < 0)
476 throw std::runtime_error("Can't rename '" + temp_path + "' -> '" + zipfile_path + "'");
478 committed = true;
481 std::ostream& writer::create_file(const std::string& name) throw(std::bad_alloc, std::logic_error,
482 std::runtime_error)
484 if(open_file != "")
485 throw std::logic_error("Can't open file with file open");
486 if(name == "")
487 throw std::runtime_error("Bad member name");
488 current_compressed_file.resize(0);
489 s = new boost::iostreams::filtering_ostream();
490 s->push(size_and_crc_filter(4096));
491 if(compression) {
492 boost::iostreams::zlib_params params;
493 params.noheader = true;
494 s->push(boost::iostreams::zlib_compressor(params));
496 s->push(vector_output(current_compressed_file));
497 open_file = name;
498 return *s;
501 void writer::close_file() throw(std::bad_alloc, std::logic_error, std::runtime_error)
503 if(open_file == "")
504 throw std::logic_error("Can't close file with no file open");
505 uint32_t ucs, cs, crc32;
506 boost::iostreams::close(*s);
507 size_and_crc_filter& f = *s->component<size_and_crc_filter>(0);
508 cs = current_compressed_file.size();
509 ucs = f.size();
510 crc32 = f.crc32();
511 delete s;
513 base_offset = zipstream->tellp();
514 if(base_offset == (uint32_t)-1)
515 throw std::runtime_error("Can't read current ZIP stream position");
516 unsigned char header[30];
517 memset(header, 0, 30);
518 serialization::u32l(header, 0x04034b50);
519 header[4] = 20;
520 header[6] = 0;
521 header[8] = compression ? 8 : 0;
522 header[12] = 33;
523 header[13] = 40;
524 serialization::u32l(header + 14, crc32);
525 serialization::u32l(header + 18, cs);
526 serialization::u32l(header + 22, ucs);
527 serialization::u16l(header + 26, open_file.length());
528 zipstream->write(reinterpret_cast<char*>(header), 30);
529 zipstream->write(open_file.c_str(), open_file.length());
530 zipstream->write(&current_compressed_file[0], current_compressed_file.size());
531 if(!*zipstream)
532 throw std::runtime_error("Can't write member to ZIP file");
533 current_compressed_file.resize(0);
534 file_info info;
535 info.crc = crc32;
536 info.uncompressed_size = ucs;
537 info.compressed_size = cs;
538 info.offset = base_offset;
539 files[open_file] = info;
540 open_file = "";
543 void writer::write_linefile(const std::string& member, const std::string& value, bool conditional)
544 throw(std::bad_alloc, std::runtime_error)
546 if(conditional && value == "")
547 return;
548 std::ostream& m = create_file(member);
549 try {
550 m << value << std::endl;
551 close_file();
552 } catch(...) {
553 close_file();
554 throw;
558 void writer::write_raw_file(const std::string& member, const std::vector<char>& content) throw(std::bad_alloc,
559 std::runtime_error)
561 std::ostream& m = create_file(member);
562 try {
563 m.write(&content[0], content.size());
564 if(!m)
565 throw std::runtime_error("Can't write ZIP file member");
566 close_file();
567 } catch(...) {
568 close_file();
569 throw;
573 namespace
575 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
576 const char* path_splitters = "\\/";
577 bool drives_allowed = true;
578 #else
579 //Assume Unix(-like) system.
580 const char* path_splitters = "/";
581 bool drives_allowed = false;
582 #endif
584 const char* str_index(const char* str, int ch)
586 for(size_t i = 0; str[i]; i++)
587 if(str[i] == ch)
588 return str + i;
589 return NULL;
592 bool ispathsep(char ch)
594 return (str_index(path_splitters, static_cast<int>(static_cast<unsigned char>(ch))) != NULL);
597 bool isroot(const std::string& path)
599 if(path.length() == 1 && ispathsep(path[0]))
600 return true;
601 if(!drives_allowed)
602 //NO more cases for this.
603 return false;
604 if(path.length() == 3 && ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] < '<')) &&
605 path[1] == ':' && ispathsep(path[2]))
606 return true;
607 //UNC.
608 if(path.length() <= 3 || !ispathsep(path[0]) || !ispathsep(path[1]) ||
609 !ispathsep(path[path.length() - 1]))
610 return false;
611 return (path.find_first_of(path_splitters, 2) == path.length() - 1);
614 std::string walk(const std::string& path, const std::string& component)
616 if(component == "" || component == ".")
617 //Current directory.
618 return path;
619 else if(component == "..") {
620 //Parent directory.
621 if(path == "" || isroot(path))
622 throw std::runtime_error("Can't rise to containing directory");
623 std::string _path = path;
624 size_t split = _path.find_last_of(path_splitters);
625 if(split < _path.length())
626 return _path.substr(0, split);
627 else
628 return "";
629 } else if(path == "" || ispathsep(path[path.length() - 1]))
630 return path + component;
631 else
632 return path + "/" + component;
635 std::string combine_path(const std::string& _name, const std::string& _referencing_path)
637 std::string name = _name;
638 std::string referencing_path = _referencing_path;
639 size_t x = referencing_path.find_last_of(path_splitters);
640 if(x < referencing_path.length())
641 referencing_path = referencing_path.substr(0, x);
642 else
643 return name;
644 //Check if name is absolute.
645 if(ispathsep(name[0]))
646 return name;
647 if(drives_allowed && name.length() >= 3 && ((name[0] >= 'A' && name[0] <= 'Z') || (name[0] >= 'a' &&
648 name[0] <= 'z')) && name[1] == ':' && ispathsep(name[2]))
649 return name;
650 //It is not absolute.
651 std::string path = referencing_path;
652 size_t pindex = 0;
653 while(true) {
654 size_t split = name.find_first_of(path_splitters, pindex);
655 std::string c;
656 if(split < name.length())
657 c = name.substr(pindex, split - pindex);
658 else
659 c = name.substr(pindex);
660 path = walk(path, c);
661 if(split < name.length())
662 pindex = split + 1;
663 else
664 break;
666 //If path becomes empty, assume it means current directory.
667 if(path == "")
668 path = ".";
669 return path;
673 std::string resolverel(const std::string& name, const std::string& referencing_path) throw(std::bad_alloc,
674 std::runtime_error)
676 return combine_path(name, referencing_path);
679 std::istream& openrel(const std::string& name, const std::string& referencing_path) throw(std::bad_alloc,
680 std::runtime_error)
682 std::string path_to_open = combine_path(name, referencing_path);
683 std::string final_path = path_to_open;
684 //Try to open this from the main OS filesystem.
685 if(directory::is_regular(path_to_open)) {
686 std::ifstream* i = new std::ifstream(path_to_open.c_str(), std::ios::binary);
687 if(i->is_open()) {
688 return *i;
690 delete i;
692 //Didn't succeed. Try to open as ZIP archive.
693 std::string membername;
694 while(true) {
695 size_t split = path_to_open.find_last_of("/");
696 if(split >= path_to_open.length())
697 throw std::runtime_error("Can't open '" + final_path + "'");
698 //Move a component to member name.
699 if(membername != "")
700 membername = path_to_open.substr(split + 1) + "/" + membername;
701 else
702 membername = path_to_open.substr(split + 1);
703 path_to_open = path_to_open.substr(0, split);
704 if(directory::is_regular(path_to_open))
705 try {
706 reader r(path_to_open);
707 return r[membername];
708 } catch(std::bad_alloc& e) {
709 throw;
710 } catch(std::runtime_error& e) {
715 std::vector<char> readrel(const std::string& name, const std::string& referencing_path) throw(std::bad_alloc,
716 std::runtime_error)
718 std::vector<char> out;
719 std::istream& s = openrel(name, referencing_path);
720 boost::iostreams::back_insert_device<std::vector<char>> rd(out);
721 boost::iostreams::copy(s, rd);
722 delete &s;
723 return out;
726 bool file_exists(const std::string& name) throw(std::bad_alloc)
728 std::string path_to_open = name;
729 std::string final_path = path_to_open;
730 if(directory::is_regular(path_to_open))
731 return true;
732 //Didn't succeed. Try to open as ZIP archive.
733 std::string membername;
734 while(true) {
735 size_t split = path_to_open.find_last_of("/");
736 if(split >= path_to_open.length())
737 return false;
738 //Move a component to member name.
739 if(membername != "")
740 membername = path_to_open.substr(split + 1) + "/" + membername;
741 else
742 membername = path_to_open.substr(split + 1);
743 path_to_open = path_to_open.substr(0, split);
744 if(directory::is_regular(path_to_open))
745 try {
746 reader r(path_to_open);
747 return r.has_member(membername);
748 } catch(std::bad_alloc& e) {
749 throw;
750 } catch(std::runtime_error& e) {
753 return false;