#include #include #include "common/zip_file.hpp" #include "common/exceptions.hpp" namespace xlnt { zip_file::zip_file(const std::string &filename, file_mode mode, file_access access) : zip_file_(nullptr), unzip_file_(nullptr), current_state_(state::closed), filename_(filename), modified_(false), // mode_(mode), access_(access) { switch(mode) { case file_mode::open: read_all(); break; case file_mode::open_or_create: if(file_exists(filename)) { read_all(); } else { flush(true); } break; case file_mode::create: flush(true); break; case file_mode::create_new: if(file_exists(filename)) { throw std::runtime_error("file exists"); } flush(true); break; case file_mode::truncate: if((int)access & (int)file_access::read) { throw std::runtime_error("cannot read from file opened with file_mode truncate"); } flush(true); break; case file_mode::append: read_all(); break; } } zip_file::~zip_file() { change_state(state::closed); } std::string zip_file::get_file_contents(const std::string &filename) const { return files_.at(filename); } void zip_file::set_file_contents(const std::string &filename, const std::string &contents) { if(!has_file(filename) || files_[filename] != contents) { modified_ = true; } files_[filename] = contents; } void zip_file::delete_file(const std::string &filename) { files_.erase(filename); } bool zip_file::has_file(const std::string &filename) { return files_.find(filename) != files_.end(); } void zip_file::flush(bool force_write) { if(modified_ || force_write) { write_all(); } } void zip_file::read_all() { if(!((int)access_ & (int)file_access::read)) { throw std::runtime_error("don't have read access"); } change_state(state::read); int result = unzGoToFirstFile(unzip_file_); std::array file_name_buffer = {{'\0'}}; std::vector file_buffer; while(result == UNZ_OK) { unz_file_info file_info; file_name_buffer.fill('\0'); result = unzGetCurrentFileInfo(unzip_file_, &file_info, file_name_buffer.data(), static_cast(file_name_buffer.size()), nullptr, 0, nullptr, 0); if(result != UNZ_OK) { throw result; } result = unzOpenCurrentFile(unzip_file_); if(result != UNZ_OK) { throw result; } if(file_buffer.size() < file_info.uncompressed_size + 1) { file_buffer.resize(file_info.uncompressed_size + 1); } file_buffer[file_info.uncompressed_size] = '\0'; result = unzReadCurrentFile(unzip_file_, file_buffer.data(), file_info.uncompressed_size); if(result != static_cast(file_info.uncompressed_size)) { throw result; } std::string current_filename(file_name_buffer.begin(), file_name_buffer.begin() + file_info.size_filename); std::string contents(file_buffer.begin(), file_buffer.begin() + file_info.uncompressed_size); if(current_filename.back() != '/') { files_[current_filename] = contents; } else { directories_.push_back(current_filename); } result = unzCloseCurrentFile(unzip_file_); if(result != UNZ_OK) { throw result; } result = unzGoToNextFile(unzip_file_); } } void zip_file::write_all() { if(!((int)access_ & (int)file_access::write)) { throw std::runtime_error("don't have write access"); } change_state(state::write, false); for(auto file : files_) { write_to_zip(file.first, file.second, true); } modified_ = false; } std::string zip_file::read_from_zip(const std::string &filename) { if(!((int)access_ & (int)file_access::read)) { throw std::runtime_error("don't have read access"); } change_state(state::read); auto result = unzLocateFile(unzip_file_, filename.c_str(), 1); if(result != UNZ_OK) { throw result; } result = unzOpenCurrentFile(unzip_file_); if(result != UNZ_OK) { throw result; } unz_file_info file_info; std::array file_name_buffer; std::array extra_field_buffer; std::array comment_buffer; unzGetCurrentFileInfo(unzip_file_, &file_info, file_name_buffer.data(), static_cast(file_name_buffer.size()), extra_field_buffer.data(), static_cast(extra_field_buffer.size()), comment_buffer.data(), static_cast(comment_buffer.size())); std::vector file_buffer(file_info.uncompressed_size + 1, '\0'); result = unzReadCurrentFile(unzip_file_, file_buffer.data(), file_info.uncompressed_size); if(result != static_cast(file_info.uncompressed_size)) { throw result; } result = unzCloseCurrentFile(unzip_file_); if(result != UNZ_OK) { throw result; } return std::string(file_buffer.begin(), file_buffer.end()); } void zip_file::write_to_zip(const std::string &filename, const std::string &content, bool append) { if(!((int)access_ & (int)file_access::write)) { throw std::runtime_error("don't have write access"); } change_state(state::write, append); zip_fileinfo file_info; int result = zipOpenNewFileInZip(zip_file_, filename.c_str(), &file_info, nullptr, 0, nullptr, 0, nullptr, Z_DEFLATED, Z_DEFAULT_COMPRESSION); if(result != UNZ_OK) { throw result; } result = zipWriteInFileInZip(zip_file_, content.data(), static_cast(content.size())); if(result != UNZ_OK) { throw result; } result = zipCloseFileInZip(zip_file_); if(result != UNZ_OK) { throw result; } } void zip_file::change_state(state new_state, bool append) { if(new_state == current_state_ && append) { return; } switch(new_state) { case state::closed: if(current_state_ == state::write) { stop_write(); } else if(current_state_ == state::read) { stop_read(); } break; case state::read: if(current_state_ == state::write) { stop_write(); } start_read(); break; case state::write: if(current_state_ == state::read) { stop_read(); } if(current_state_ != state::write) { start_write(append); } break; default: throw std::runtime_error("bad enum"); } current_state_ = new_state; } bool zip_file::file_exists(const std::string& name) { std::ifstream f(name.c_str()); return f.good(); } void zip_file::start_read() { if(unzip_file_ != nullptr || zip_file_ != nullptr) { throw std::runtime_error("bad state"); } unzip_file_ = unzOpen(filename_.c_str()); if(unzip_file_ == nullptr) { throw invalid_file_exception(filename_); } } void zip_file::stop_read() { if(unzip_file_ == nullptr) { throw std::runtime_error("bad state"); } int result = unzClose(unzip_file_); if(result != UNZ_OK) { throw result; } unzip_file_ = nullptr; } void zip_file::start_write(bool append) { if(unzip_file_ != nullptr || zip_file_ != nullptr) { throw std::runtime_error("bad state"); } int append_status; if(append) { if(!file_exists(filename_)) { throw std::runtime_error("can't append to non-existent file"); } append_status = APPEND_STATUS_ADDINZIP; } else { append_status = APPEND_STATUS_CREATE; } zip_file_ = zipOpen(filename_.c_str(), append_status); if(zip_file_ == nullptr) { if(append) { throw std::runtime_error("couldn't append to zip file"); } else { throw std::runtime_error("couldn't create zip file"); } } } void zip_file::stop_write() { if(zip_file_ == nullptr) { throw std::runtime_error("bad state"); } flush(); int result = zipClose(zip_file_, nullptr); if(result != UNZ_OK) { throw result; } zip_file_ = nullptr; } }