2017-04-22 09:58:40 +08:00
|
|
|
// Copyright (C) 2016-2017 Thomas Fussell
|
|
|
|
// Copyright (C) 2002-2007 Ariya Hidayat (ariya@kde.org).
|
|
|
|
//
|
|
|
|
// Redistribution and use in source and binary forms, with or without
|
|
|
|
// modification, are permitted provided that the following conditions
|
|
|
|
// are met:
|
|
|
|
//
|
|
|
|
// 1. Redistributions of source code must retain the above copyright
|
|
|
|
// notice, this list of conditions and the following disclaimer.
|
|
|
|
// 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
|
|
// documentation and/or other materials provided with the distribution.
|
|
|
|
//
|
|
|
|
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
|
|
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
|
|
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
|
|
// IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
|
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
|
|
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
|
|
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
2017-04-22 07:52:02 +08:00
|
|
|
#include <array>
|
|
|
|
#include <algorithm>
|
|
|
|
#include <cstring>
|
2017-04-26 06:14:47 +08:00
|
|
|
#include <iostream>
|
2017-04-22 07:52:02 +08:00
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
|
|
|
|
2017-04-24 06:18:35 +08:00
|
|
|
#include <detail/binary.hpp>
|
2017-04-25 10:16:03 +08:00
|
|
|
#include <detail/unicode.hpp>
|
2017-04-22 07:52:02 +08:00
|
|
|
#include <detail/cryptography/compound_document.hpp>
|
|
|
|
#include <xlnt/utils/exceptions.hpp>
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
using namespace xlnt::detail;
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
int compare_keys(const std::u16string &left, const std::u16string &right)
|
2017-04-22 07:52:02 +08:00
|
|
|
{
|
2017-04-26 06:14:47 +08:00
|
|
|
auto to_lower = [](std::u16string s)
|
2017-04-23 02:25:27 +08:00
|
|
|
{
|
2017-04-26 06:14:47 +08:00
|
|
|
static const auto locale = std::locale();
|
|
|
|
std::use_facet<std::ctype<char16_t>>(locale).tolower(&s[0], &s[0] + s.size());
|
|
|
|
return s;
|
|
|
|
};
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
return to_lower(left).compare(to_lower(right));
|
2017-04-24 08:27:16 +08:00
|
|
|
}
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
bool header_is_valid(const compound_document_header &h)
|
2017-04-22 07:52:02 +08:00
|
|
|
{
|
2017-04-24 08:27:16 +08:00
|
|
|
if (h.threshold != 4096)
|
2017-04-23 02:25:27 +08:00
|
|
|
{
|
2017-04-24 08:27:16 +08:00
|
|
|
return false;
|
2017-04-23 02:25:27 +08:00
|
|
|
}
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
if (h.num_msat_sectors == 0
|
|
|
|
|| (h.num_msat_sectors > 109 && h.num_msat_sectors > (h.num_extra_msat_sectors * 127) + 109)
|
|
|
|
|| ((h.num_msat_sectors < 109) && (h.num_extra_msat_sectors != 0)))
|
2017-04-23 02:25:27 +08:00
|
|
|
{
|
2017-04-24 08:27:16 +08:00
|
|
|
return false;
|
2017-04-23 02:25:27 +08:00
|
|
|
}
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-24 08:27:16 +08:00
|
|
|
if (h.short_sector_size_power > h.sector_size_power
|
|
|
|
|| h.sector_size_power <= 6
|
|
|
|
|| h.sector_size_power >= 31)
|
2017-04-22 07:52:02 +08:00
|
|
|
{
|
2017-04-24 08:27:16 +08:00
|
|
|
return false;
|
2017-04-22 07:52:02 +08:00
|
|
|
}
|
|
|
|
|
2017-04-24 08:27:16 +08:00
|
|
|
return true;
|
|
|
|
}
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
std::vector<std::u16string> split_path(const std::u16string &path_string)
|
2017-04-25 10:16:03 +08:00
|
|
|
{
|
2017-04-26 06:14:47 +08:00
|
|
|
auto sep = path_string.find(u'/');
|
|
|
|
auto prev = std::size_t(0);
|
|
|
|
auto split = std::vector<std::u16string>();
|
|
|
|
|
|
|
|
while (sep != std::u16string::npos)
|
|
|
|
{
|
|
|
|
split.push_back(path_string.substr(prev, sep - prev));
|
|
|
|
prev = sep;
|
|
|
|
sep = path_string.find(u'/');
|
|
|
|
}
|
|
|
|
|
|
|
|
return split;
|
2017-04-25 10:16:03 +08:00
|
|
|
}
|
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
const sector_id FreeSector = -1;
|
|
|
|
const directory_id End = -1;
|
|
|
|
|
|
|
|
} // namespace
|
2017-04-25 10:16:03 +08:00
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
namespace xlnt {
|
|
|
|
namespace detail {
|
|
|
|
|
|
|
|
class red_black_tree
|
2017-04-25 10:16:03 +08:00
|
|
|
{
|
2017-04-26 06:14:47 +08:00
|
|
|
public:
|
|
|
|
using entry_color = compound_document_entry::entry_color;
|
|
|
|
|
|
|
|
red_black_tree(std::vector<compound_document_entry> &entries)
|
|
|
|
: entries_(entries)
|
2017-04-25 10:16:03 +08:00
|
|
|
{
|
2017-04-26 06:14:47 +08:00
|
|
|
initialize_tree();
|
2017-04-25 10:16:03 +08:00
|
|
|
}
|
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
void initialize_tree()
|
|
|
|
{
|
|
|
|
if (entries_.empty()) return;
|
|
|
|
|
|
|
|
auto stack = std::vector<directory_id>();
|
|
|
|
auto storage_siblings = std::vector<directory_id>();
|
|
|
|
auto stream_siblings = std::vector<directory_id>();
|
|
|
|
|
|
|
|
auto directory_stack = std::vector<directory_id>();
|
|
|
|
directory_stack.push_back(directory_id(0));
|
|
|
|
|
|
|
|
while (!directory_stack.empty())
|
|
|
|
{
|
|
|
|
auto current_storage_id = directory_stack.back();
|
|
|
|
directory_stack.pop_back();
|
|
|
|
|
|
|
|
if (entries_[current_storage_id].child < 0) continue;
|
|
|
|
|
|
|
|
auto storage_stack = std::vector<directory_id>();
|
|
|
|
auto storage_root_id = entries_[current_storage_id].child;
|
|
|
|
parent_[storage_root_id] = End;
|
|
|
|
storage_stack.push_back(storage_root_id);
|
|
|
|
|
|
|
|
while (!storage_stack.empty())
|
|
|
|
{
|
|
|
|
auto current_entry_id = storage_stack.back();
|
|
|
|
auto ¤t_entry = entries_[current_entry_id];
|
|
|
|
storage_stack.pop_back();
|
|
|
|
|
|
|
|
parent_storage_[current_entry_id] = current_storage_id;
|
|
|
|
|
|
|
|
if (current_entry.type == compound_document_entry::entry_type::UserStorage)
|
|
|
|
{
|
|
|
|
directory_stack.push_back(current_entry_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (current_entry.prev >= 0)
|
|
|
|
{
|
|
|
|
storage_stack.push_back(current_entry.prev);
|
|
|
|
parent_[current_entry.prev] = current_entry_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (current_entry.next >= 0)
|
|
|
|
{
|
|
|
|
storage_stack.push_back(entries_[current_entry_id].next);
|
|
|
|
parent_[entries_[current_entry_id].next] = current_entry_id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-04-25 10:16:03 +08:00
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
void insert(directory_id new_id, directory_id storage_id)
|
2017-04-25 10:16:03 +08:00
|
|
|
{
|
2017-04-26 06:14:47 +08:00
|
|
|
parent_storage_[new_id] = storage_id;
|
|
|
|
|
|
|
|
left(new_id) = End;
|
|
|
|
right(new_id) = End;
|
|
|
|
|
|
|
|
if (root(new_id) == End)
|
|
|
|
{
|
|
|
|
if (new_id != 0)
|
|
|
|
{
|
|
|
|
root(new_id) = new_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
color(new_id, entry_color::Black);
|
|
|
|
parent(new_id) = End;
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// normal tree insert
|
|
|
|
// (will probably unbalance the tree, fix after)
|
|
|
|
auto x = root(new_id);
|
|
|
|
auto y = End;
|
|
|
|
|
|
|
|
while (x >= 0)
|
|
|
|
{
|
|
|
|
y = x;
|
|
|
|
|
|
|
|
if (compare_keys(key(x), key(new_id)) > 0)
|
|
|
|
{
|
|
|
|
x = right(x);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
x = left(x);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
parent(new_id) = y;
|
|
|
|
|
|
|
|
if (compare_keys(key(y), key(new_id)) > 0)
|
|
|
|
{
|
|
|
|
right(y) = new_id;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
left(y) = new_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
insert_fixup(new_id);
|
2017-04-25 10:16:03 +08:00
|
|
|
}
|
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
std::vector<directory_id> path(directory_id id)
|
2017-04-25 10:16:03 +08:00
|
|
|
{
|
2017-04-26 06:14:47 +08:00
|
|
|
auto storage_id = parent_storage_[id];
|
|
|
|
auto storages = std::vector<directory_id>();
|
|
|
|
storages.push_back(storage_id);
|
|
|
|
|
|
|
|
while (storage_id > 0)
|
|
|
|
{
|
|
|
|
storage_id = parent_storage_[storage_id];
|
|
|
|
storages.push_back(storage_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
return storages;
|
2017-04-25 10:16:03 +08:00
|
|
|
}
|
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
private:
|
|
|
|
void rotate_left(directory_id x)
|
|
|
|
{
|
|
|
|
auto y = right(x);
|
|
|
|
|
|
|
|
// turn y's left subtree into x's right subtree
|
|
|
|
right(x) = left(y);
|
|
|
|
|
|
|
|
if (left(y) != End)
|
|
|
|
{
|
|
|
|
parent(left(y)) = x;
|
|
|
|
}
|
|
|
|
|
|
|
|
// link x's parent to y
|
|
|
|
parent(y) = parent(x);
|
|
|
|
|
|
|
|
if (parent(x) == End)
|
|
|
|
{
|
|
|
|
root(x) = y;
|
|
|
|
}
|
|
|
|
else if (x == left(parent(x)))
|
|
|
|
{
|
|
|
|
left(parent(x)) = y;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
right(parent(x)) = y;
|
|
|
|
}
|
|
|
|
|
|
|
|
// put x on y's left
|
|
|
|
left(y) = x;
|
|
|
|
parent(x) = y;
|
|
|
|
}
|
|
|
|
|
|
|
|
void rotate_right(directory_id y)
|
|
|
|
{
|
|
|
|
auto x = left(y);
|
|
|
|
|
|
|
|
// turn x's right subtree into y's left subtree
|
|
|
|
left(y) = right(x);
|
|
|
|
|
|
|
|
if (right(x) != End)
|
|
|
|
{
|
|
|
|
parent(right(x)) = y;
|
|
|
|
}
|
|
|
|
|
|
|
|
// link y's parent to x
|
|
|
|
parent(x) = parent(y);
|
|
|
|
|
|
|
|
if (parent(y) == End)
|
|
|
|
{
|
|
|
|
root(y) = x;
|
|
|
|
}
|
|
|
|
else if (y == left(parent(y)))
|
|
|
|
{
|
|
|
|
left(parent(y)) = x;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
right(parent(y)) = x;
|
|
|
|
}
|
|
|
|
|
|
|
|
// put y on x's right
|
|
|
|
right(x) = y;
|
|
|
|
parent(y) = x;
|
|
|
|
}
|
|
|
|
|
|
|
|
void insert_fixup(directory_id x)
|
|
|
|
{
|
|
|
|
color(x, entry_color::Red);
|
|
|
|
|
|
|
|
while (x != root(x) && color(parent(x)) == entry_color::Red)
|
|
|
|
{
|
|
|
|
if (parent(x) == left(parent(parent(x))))
|
|
|
|
{
|
|
|
|
auto y = right(parent(parent(x)));
|
|
|
|
|
|
|
|
if (color(y) == entry_color::Red)
|
|
|
|
{
|
|
|
|
// case 1
|
|
|
|
color(parent(x), entry_color::Black);
|
|
|
|
color(y, entry_color::Black);
|
|
|
|
color(parent(parent(x)), entry_color::Red);
|
|
|
|
x = parent(parent(x));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (x == right(parent(x)))
|
|
|
|
{
|
|
|
|
// case 2
|
|
|
|
x = parent(x);
|
|
|
|
rotate_left(x);
|
|
|
|
}
|
|
|
|
|
|
|
|
// case 3
|
|
|
|
color(parent(x), entry_color::Black);
|
|
|
|
color(parent(parent(x)), entry_color::Red);
|
|
|
|
rotate_right(parent(parent(x)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else // same as above with left and right switched
|
|
|
|
{
|
|
|
|
auto y = left(parent(parent(x)));
|
|
|
|
|
|
|
|
if (color(y) == entry_color::Red)
|
|
|
|
{
|
|
|
|
//case 1
|
|
|
|
color(parent(x), entry_color::Black);
|
|
|
|
color(y, entry_color::Black);
|
|
|
|
color(parent(parent(x)), entry_color::Red);
|
|
|
|
x = parent(parent(x));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (x == left(parent(x)))
|
|
|
|
{
|
|
|
|
// case 2
|
|
|
|
x = parent(x);
|
|
|
|
rotate_right(x);
|
|
|
|
}
|
|
|
|
|
|
|
|
// case 3
|
|
|
|
color(parent(x), entry_color::Black);
|
|
|
|
color(parent(parent(x)), entry_color::Red);
|
|
|
|
rotate_left(parent(parent(x)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-04-25 10:16:03 +08:00
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
color(root(x), entry_color::Black);
|
|
|
|
}
|
|
|
|
|
|
|
|
void color(directory_id id, entry_color c)
|
2017-04-25 10:16:03 +08:00
|
|
|
{
|
2017-04-26 06:14:47 +08:00
|
|
|
entries_.at(id).color = c;
|
2017-04-25 10:16:03 +08:00
|
|
|
}
|
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
entry_color color(directory_id id)
|
|
|
|
{
|
|
|
|
return entries_.at(id).color;
|
|
|
|
}
|
2017-04-25 10:16:03 +08:00
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
directory_id &parent(directory_id id)
|
|
|
|
{
|
|
|
|
if (parent_.find(id) == parent_.end())
|
|
|
|
{
|
|
|
|
parent_[id] = End;
|
|
|
|
}
|
2017-04-25 10:16:03 +08:00
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
return parent_[id];
|
|
|
|
}
|
2017-04-25 10:16:03 +08:00
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
directory_id &parent_storage(directory_id id)
|
|
|
|
{
|
|
|
|
if (parent_storage_.find(id) == parent_storage_.end())
|
|
|
|
{
|
|
|
|
throw xlnt::exception("not found");
|
|
|
|
}
|
2017-04-25 04:06:58 +08:00
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
return parent_storage_[id];
|
|
|
|
}
|
2017-04-25 10:16:03 +08:00
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
directory_id &root(directory_id id)
|
2017-04-25 10:16:03 +08:00
|
|
|
{
|
2017-04-26 06:14:47 +08:00
|
|
|
return entries_[parent_storage(id)].child;
|
2017-04-25 10:16:03 +08:00
|
|
|
}
|
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
directory_id &left(directory_id id)
|
|
|
|
{
|
|
|
|
return entries_.at(id).prev;
|
|
|
|
}
|
2017-04-25 10:16:03 +08:00
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
directory_id &right(directory_id id)
|
|
|
|
{
|
|
|
|
return entries_.at(id).next;
|
|
|
|
}
|
2017-04-25 10:16:03 +08:00
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
std::u16string key(directory_id id)
|
|
|
|
{
|
|
|
|
return entries_.at(id).name();
|
|
|
|
}
|
2017-04-25 10:16:03 +08:00
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
private:
|
|
|
|
std::vector<compound_document_entry> &entries_;
|
|
|
|
std::unordered_map<directory_id, directory_id> parent_storage_;
|
|
|
|
std::unordered_map<directory_id, directory_id> parent_;
|
|
|
|
};
|
2017-04-25 04:06:58 +08:00
|
|
|
|
|
|
|
|
|
|
|
compound_document::compound_document(std::vector<std::uint8_t> &data)
|
|
|
|
: writer_(new binary_writer(data)),
|
2017-04-26 06:14:47 +08:00
|
|
|
reader_(new binary_reader(data)),
|
|
|
|
rb_tree_(new red_black_tree(entries_))
|
2017-04-25 04:06:58 +08:00
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
header_ = compound_document_header();
|
|
|
|
header_.directory_start = allocate_sectors(1);
|
2017-04-25 04:06:58 +08:00
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
write_header();
|
2017-04-25 04:06:58 +08:00
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
insert_entry(u"Root Entry", compound_document_entry::entry_type::RootStorage);
|
2017-04-25 04:06:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
compound_document::compound_document(const std::vector<std::uint8_t> &data)
|
|
|
|
: reader_(new binary_reader(data))
|
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
read_header();
|
|
|
|
|
|
|
|
if (!header_is_valid(header_))
|
2017-04-23 02:25:27 +08:00
|
|
|
{
|
2017-04-25 04:06:58 +08:00
|
|
|
throw xlnt::exception("bad ole");
|
2017-04-23 02:25:27 +08:00
|
|
|
}
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
read_msat();
|
|
|
|
read_sat();
|
|
|
|
read_ssat();
|
2017-04-25 07:23:51 +08:00
|
|
|
read_directory_tree();
|
2017-04-25 04:06:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
compound_document::~compound_document()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
std::size_t compound_document::sector_size()
|
2017-04-25 04:06:58 +08:00
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
return static_cast<std::size_t>(1) << header_.sector_size_power;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::size_t compound_document::short_sector_size()
|
|
|
|
{
|
|
|
|
return static_cast<std::size_t>(1) << header_.short_sector_size_power;
|
|
|
|
}
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
std::vector<std::uint8_t> compound_document::read_stream(const std::u16string &name)
|
|
|
|
{
|
|
|
|
const auto &entry = find_entry(name);
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
auto stream_data = (entry.size < header_.threshold)
|
|
|
|
? read_short(entry.start)
|
|
|
|
: read(entry.start);
|
2017-04-25 04:06:58 +08:00
|
|
|
stream_data.resize(entry.size);
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
return stream_data;
|
|
|
|
}
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
void compound_document::write_stream(const std::u16string &name, const std::vector<std::uint8_t> &data)
|
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
const auto num_sectors = data.size() / sector_size() + (data.size() % sector_size() ? 1 : 0);
|
|
|
|
|
|
|
|
auto &entry = contains_entry(name)
|
|
|
|
? find_entry(name)
|
|
|
|
: insert_entry(name, compound_document_entry::entry_type::UserStream);
|
|
|
|
entry.start = allocate_sectors(num_sectors);
|
2017-04-25 04:06:58 +08:00
|
|
|
|
|
|
|
write(data, entry.start);
|
2017-04-25 07:23:51 +08:00
|
|
|
write_directory_tree();
|
2017-04-25 04:06:58 +08:00
|
|
|
}
|
2017-04-23 02:25:27 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
void compound_document::write(const std::vector<byte> &data, sector_id start)
|
|
|
|
{
|
|
|
|
const auto header_size = sizeof(compound_document_header);
|
2017-04-25 07:23:51 +08:00
|
|
|
const auto num_sectors = data.size() / sector_size() + (data.size() % sector_size() ? 1 : 0);
|
2017-04-25 04:06:58 +08:00
|
|
|
auto current_sector = start;
|
|
|
|
|
|
|
|
for (auto i = std::size_t(0); i < num_sectors; ++i)
|
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
auto stream_position = i * sector_size();
|
|
|
|
auto stream_size = std::min(sector_size(), data.size() - stream_position);
|
|
|
|
writer_->offset(header_size + sector_size() * current_sector);
|
2017-04-25 04:06:58 +08:00
|
|
|
writer_->append(data, stream_position, stream_size);
|
|
|
|
current_sector = sat_[current_sector];
|
2017-04-22 07:52:02 +08:00
|
|
|
}
|
2017-04-25 04:06:58 +08:00
|
|
|
}
|
2017-04-23 02:25:27 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
void compound_document::write_short(const std::vector<byte> &data, sector_id start)
|
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
const auto num_sectors = data.size() / sector_size() + (data.size() % sector_size() ? 1 : 0);
|
2017-04-25 04:06:58 +08:00
|
|
|
auto current_sector = start;
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
for (auto i = std::size_t(0); i < num_sectors; ++i)
|
2017-04-22 07:52:02 +08:00
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
auto position = sector_size() * current_sector;
|
|
|
|
auto current_sector_size = data.size() % sector_size();
|
2017-04-25 04:06:58 +08:00
|
|
|
writer_->append(data, position, current_sector_size);
|
|
|
|
current_sector = ssat_[current_sector];
|
|
|
|
}
|
|
|
|
}
|
2017-04-23 02:25:27 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
std::vector<byte> compound_document::read(sector_id current)
|
|
|
|
{
|
|
|
|
const auto header_size = sizeof(compound_document_header);
|
|
|
|
auto sector_data = std::vector<byte>();
|
|
|
|
auto sector_data_writer = binary_writer(sector_data);
|
2017-04-23 02:25:27 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
while (current >= 0)
|
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
reader_->offset(header_size + sector_size() * current);
|
2017-04-26 06:14:47 +08:00
|
|
|
auto current_sector_data = reader_->read_vector<byte>(sector_size());
|
|
|
|
sector_data_writer.append(current_sector_data);
|
2017-04-25 04:06:58 +08:00
|
|
|
current = sat_[current];
|
|
|
|
}
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
return sector_data;
|
|
|
|
}
|
2017-04-23 02:25:27 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
std::vector<byte> compound_document::read_short(sector_id current_short)
|
|
|
|
{
|
|
|
|
const auto header_size = sizeof(compound_document_header);
|
|
|
|
auto sector_data = std::vector<byte>();
|
|
|
|
auto sector_data_writer = binary_writer(sector_data);
|
|
|
|
auto first_short_sector = find_entry(u"Root Entry").start;
|
2017-04-23 02:25:27 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
while (current_short >= 0)
|
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
auto short_position = static_cast<std::size_t>(short_sector_size() * current_short);
|
2017-04-25 04:06:58 +08:00
|
|
|
auto current_sector_index = 0;
|
|
|
|
auto current_sector = first_short_sector;
|
2017-04-23 02:25:27 +08:00
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
while (current_sector_index < short_position / sector_size())
|
2017-04-25 04:06:58 +08:00
|
|
|
{
|
|
|
|
current_sector = sat_[current_sector];
|
|
|
|
++current_sector_index;
|
2017-04-22 07:52:02 +08:00
|
|
|
}
|
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
auto offset = short_position % sector_size();
|
|
|
|
reader_->offset(header_size + sector_size() * current_sector + offset);
|
2017-04-26 06:14:47 +08:00
|
|
|
auto current_sector_data = reader_->read_vector<byte>(short_sector_size());
|
|
|
|
sector_data_writer.append(current_sector_data);
|
2017-04-25 04:06:58 +08:00
|
|
|
|
|
|
|
current_short = ssat_[current_short];
|
2017-04-23 02:25:27 +08:00
|
|
|
}
|
2017-04-25 04:06:58 +08:00
|
|
|
|
|
|
|
return sector_data;
|
|
|
|
}
|
|
|
|
|
|
|
|
void compound_document::read_msat()
|
|
|
|
{
|
|
|
|
msat_ = sector_chain(
|
2017-04-25 07:23:51 +08:00
|
|
|
header_.msat.begin(),
|
|
|
|
header_.msat.begin()
|
|
|
|
+ std::min(header_.msat.size(),
|
|
|
|
static_cast<std::size_t>(header_.num_msat_sectors)));
|
2017-04-25 04:06:58 +08:00
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
if (header_.num_msat_sectors > std::uint32_t(109))
|
2017-04-23 02:25:27 +08:00
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
auto current_sector = header_.extra_msat_start;
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
for (auto r = std::uint32_t(0); r < header_.num_extra_msat_sectors; ++r)
|
2017-04-22 07:52:02 +08:00
|
|
|
{
|
2017-04-25 04:06:58 +08:00
|
|
|
auto current_sector_data = read({ current_sector });
|
|
|
|
auto current_sector_reader = binary_reader(current_sector_data);
|
|
|
|
auto current_sector_sectors = current_sector_reader.to_vector<sector_id>();
|
|
|
|
|
|
|
|
current_sector = current_sector_sectors.back();
|
|
|
|
current_sector_sectors.pop_back();
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
std::copy(
|
|
|
|
current_sector_sectors.begin(),
|
|
|
|
current_sector_sectors.end(),
|
|
|
|
std::back_inserter(msat_));
|
|
|
|
}
|
2017-04-23 02:25:27 +08:00
|
|
|
}
|
2017-04-25 04:06:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void compound_document::read_sat()
|
|
|
|
{
|
|
|
|
sat_.clear();
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
for (auto msat_sector : msat_)
|
2017-04-23 02:25:27 +08:00
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
reader_->offset(sector_data_start() + sector_size() * msat_sector);
|
|
|
|
auto sat_sectors = reader_->read_vector<sector_id>(sector_size() / sizeof(sector_id));
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
std::copy(
|
|
|
|
sat_sectors.begin(),
|
|
|
|
sat_sectors.end(),
|
|
|
|
std::back_inserter(sat_));
|
2017-04-23 02:25:27 +08:00
|
|
|
}
|
2017-04-25 04:06:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void compound_document::read_ssat()
|
|
|
|
{
|
|
|
|
ssat_.clear();
|
2017-04-25 07:23:51 +08:00
|
|
|
auto current = header_.short_table_start;
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
while (current >= 0)
|
2017-04-22 07:52:02 +08:00
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
reader_->offset(sector_data_start() + sector_size() * current);
|
|
|
|
auto ssat_sectors = reader_->read_vector<sector_id>(sector_size() / sizeof(sector_id));
|
2017-04-23 02:25:27 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
std::copy(
|
|
|
|
ssat_sectors.begin(),
|
|
|
|
ssat_sectors.end(),
|
|
|
|
std::back_inserter(ssat_));
|
2017-04-23 02:25:27 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
current = sat_[current];
|
2017-04-22 07:52:02 +08:00
|
|
|
}
|
2017-04-25 04:06:58 +08:00
|
|
|
}
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
sector_id compound_document::allocate_sectors(std::size_t count)
|
2017-04-25 04:06:58 +08:00
|
|
|
{
|
|
|
|
const auto sector_data_start = sizeof(compound_document_header);
|
2017-04-25 07:23:51 +08:00
|
|
|
const auto sectors_per_sector = sector_size() / sizeof(sector_id);
|
2017-04-26 06:14:47 +08:00
|
|
|
auto num_free = static_cast<std::size_t>(std::count(sat_.begin(), sat_.end(), FreeSector));
|
2017-04-23 02:25:27 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
if (num_free < count)
|
|
|
|
{
|
|
|
|
// increase size of allocation table, plus extra sector for sat table
|
|
|
|
auto new_size = sat_.size() + count - num_free + 1;
|
|
|
|
new_size = (new_size / sectors_per_sector
|
|
|
|
+ (new_size % sectors_per_sector != 0 ? 1 : 0)) * sectors_per_sector;
|
|
|
|
sat_.resize(new_size, FreeSector);
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
// allocate new sat sector
|
2017-04-25 07:23:51 +08:00
|
|
|
auto new_sat_sector = allocate_sectors(1);
|
2017-04-25 04:06:58 +08:00
|
|
|
msat_.push_back(new_sat_sector);
|
2017-04-25 07:23:51 +08:00
|
|
|
++header_.num_msat_sectors;
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
writer_->offset(sector_data_start + new_sat_sector * sector_size());
|
2017-04-25 04:06:58 +08:00
|
|
|
writer_->append(sat_, sat_.size() - sectors_per_sector, sectors_per_sector);
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
if (msat_.size() > std::size_t(109))
|
2017-04-23 02:25:27 +08:00
|
|
|
{
|
2017-04-25 04:06:58 +08:00
|
|
|
// allocate extra msat sector
|
2017-04-25 07:23:51 +08:00
|
|
|
++header_.num_extra_msat_sectors;
|
|
|
|
auto new_msat_sector = allocate_sectors(1);
|
|
|
|
writer_->offset(sector_data_start + new_msat_sector * sector_size());
|
2017-04-25 04:06:58 +08:00
|
|
|
writer_->write(new_msat_sector);
|
2017-04-23 02:25:27 +08:00
|
|
|
}
|
2017-04-25 04:06:58 +08:00
|
|
|
else
|
2017-04-23 08:43:26 +08:00
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
header_.msat.at(msat_.size() - 1) = new_sat_sector;
|
2017-04-23 08:43:26 +08:00
|
|
|
}
|
2017-04-25 04:06:58 +08:00
|
|
|
}
|
2017-04-23 08:43:26 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
auto allocated = 0;
|
|
|
|
const auto start_iter = std::find(sat_.begin(), sat_.end(), FreeSector);
|
|
|
|
const auto start = sector_id(start_iter - sat_.begin());
|
|
|
|
auto current = start;
|
2017-04-23 08:43:26 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
while (allocated < count)
|
|
|
|
{
|
|
|
|
const auto next_iter = std::find(sat_.begin() + current + 1, sat_.end(), FreeSector);
|
|
|
|
const auto next = sector_id(next_iter - sat_.begin());
|
|
|
|
sat_[current] = (allocated == count - 1) ? -2 : next;
|
2017-04-23 08:43:26 +08:00
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
if (sector_data_start + (current + 1) * sector_size() > writer_->size())
|
2017-04-23 08:43:26 +08:00
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
writer_->extend(sector_size());
|
2017-04-23 08:43:26 +08:00
|
|
|
}
|
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
current = next;
|
|
|
|
++allocated;
|
|
|
|
}
|
2017-04-23 08:43:26 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
return start;
|
|
|
|
}
|
2017-04-23 08:43:26 +08:00
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
void compound_document::reallocate_sectors(sector_id start, std::size_t count)
|
|
|
|
{
|
|
|
|
auto current_sector = start;
|
|
|
|
|
|
|
|
for (auto i = std::size_t(0); i < count; ++i)
|
|
|
|
{
|
|
|
|
if (current_sector == -2)
|
|
|
|
{
|
|
|
|
sat_[current_sector] = allocate_sectors(count - i);
|
|
|
|
}
|
|
|
|
|
|
|
|
current_sector = sat_[current_sector];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
compound_document_entry &compound_document::insert_entry(
|
|
|
|
const std::u16string &name,
|
|
|
|
compound_document_entry::entry_type type)
|
2017-04-25 04:06:58 +08:00
|
|
|
{
|
2017-04-26 06:14:47 +08:00
|
|
|
auto any_empty_entry = false;
|
|
|
|
auto entry_id = directory_id(0);
|
2017-04-25 10:16:03 +08:00
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
for (auto &entry : entries_)
|
2017-04-25 10:16:03 +08:00
|
|
|
{
|
2017-04-26 06:14:47 +08:00
|
|
|
if (entry.type == compound_document_entry::entry_type::Empty)
|
2017-04-25 10:16:03 +08:00
|
|
|
{
|
2017-04-26 06:14:47 +08:00
|
|
|
any_empty_entry = true;
|
|
|
|
break;
|
2017-04-25 10:16:03 +08:00
|
|
|
}
|
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
++entry_id;
|
|
|
|
}
|
2017-04-25 10:16:03 +08:00
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
if (!any_empty_entry)
|
|
|
|
{
|
|
|
|
const auto entries_per_sector = static_cast<directory_id>(sector_size()
|
|
|
|
/ sizeof(compound_document_entry));
|
|
|
|
|
|
|
|
for (auto i = std::size_t(0); i < entries_per_sector; ++i)
|
|
|
|
{
|
|
|
|
auto empty_entry = compound_document_entry();
|
|
|
|
empty_entry.type = compound_document_entry::entry_type::Empty;
|
|
|
|
entries_.push_back(empty_entry);
|
2017-04-25 10:16:03 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
auto &entry = entries_[entry_id];
|
|
|
|
|
|
|
|
entry.name(name);
|
|
|
|
entry.type = type;
|
|
|
|
|
|
|
|
rb_tree_->insert(entry_id, 0);
|
2017-04-25 07:23:51 +08:00
|
|
|
write_directory_tree();
|
2017-04-23 08:43:26 +08:00
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
return entry;
|
|
|
|
}
|
2017-04-24 08:51:50 +08:00
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
void compound_document::write_directory_tree()
|
|
|
|
{
|
|
|
|
const auto entries_per_sector = static_cast<directory_id>(sector_size()
|
|
|
|
/ sizeof(compound_document_entry));
|
2017-04-26 06:14:47 +08:00
|
|
|
const auto required_sectors = entries_.size() / entries_per_sector;
|
2017-04-25 07:23:51 +08:00
|
|
|
auto current_sector_id = header_.directory_start;
|
|
|
|
auto entry_index = directory_id(0);
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
reallocate_sectors(header_.directory_start, required_sectors);
|
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
for (auto &e : entries_)
|
2017-04-23 02:25:27 +08:00
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
writer_->offset(sector_data_start() + current_sector_id * sector_size());
|
|
|
|
writer_->write(e);
|
2017-04-24 06:18:35 +08:00
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
++entry_index;
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
if (entry_index % entries_per_sector == 0)
|
2017-04-23 02:25:27 +08:00
|
|
|
{
|
2017-04-25 04:06:58 +08:00
|
|
|
current_sector_id = sat_[current_sector_id];
|
2017-04-25 07:23:51 +08:00
|
|
|
}
|
2017-04-22 07:52:02 +08:00
|
|
|
}
|
2017-04-25 07:23:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
std::size_t compound_document::sector_data_start()
|
|
|
|
{
|
|
|
|
return sizeof(compound_document_header);
|
|
|
|
}
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
bool compound_document::contains_entry(const std::u16string &path)
|
|
|
|
{
|
|
|
|
for (auto &e : entries_)
|
2017-04-23 02:25:27 +08:00
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
if (e.name() == path)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
2017-04-25 04:06:58 +08:00
|
|
|
}
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
return false;
|
|
|
|
}
|
2017-04-23 02:25:27 +08:00
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
void compound_document::write_header()
|
|
|
|
{
|
|
|
|
writer_->offset(0);
|
|
|
|
writer_->write(header_);
|
|
|
|
}
|
2017-04-23 02:25:27 +08:00
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
void compound_document::read_header()
|
|
|
|
{
|
|
|
|
reader_->offset(0);
|
|
|
|
header_ = reader_->read<compound_document_header>();
|
2017-04-25 04:06:58 +08:00
|
|
|
}
|
2017-04-23 02:25:27 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
compound_document_entry &compound_document::find_entry(const std::u16string &name)
|
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
for (auto &e : entries_)
|
2017-04-22 07:52:02 +08:00
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
if (e.name() == name)
|
2017-04-23 02:25:27 +08:00
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
return e;
|
2017-04-23 02:25:27 +08:00
|
|
|
}
|
2017-04-22 07:52:02 +08:00
|
|
|
}
|
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
throw xlnt::exception("not found");
|
|
|
|
}
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
void compound_document::read_directory_tree()
|
2017-04-25 04:06:58 +08:00
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
entries_.clear();
|
|
|
|
auto current_sector_id = header_.directory_start;
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
while (current_sector_id >= 0)
|
2017-04-24 04:56:01 +08:00
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
reader_->offset(sector_data_start() + current_sector_id * sector_size());
|
2017-04-24 04:56:01 +08:00
|
|
|
|
2017-04-25 07:23:51 +08:00
|
|
|
for (auto i = std::size_t(0); i < sector_size() / sizeof(compound_document_entry); ++i)
|
2017-04-24 04:56:01 +08:00
|
|
|
{
|
2017-04-25 07:23:51 +08:00
|
|
|
entries_.push_back(reader_->read<compound_document_entry>());
|
2017-04-24 04:56:01 +08:00
|
|
|
}
|
2017-04-22 07:52:02 +08:00
|
|
|
|
2017-04-25 04:06:58 +08:00
|
|
|
current_sector_id = sat_[current_sector_id];
|
2017-04-23 02:25:27 +08:00
|
|
|
}
|
2017-04-22 07:52:02 +08:00
|
|
|
}
|
|
|
|
|
2017-04-26 06:14:47 +08:00
|
|
|
void compound_document::print_directory()
|
|
|
|
{
|
|
|
|
red_black_tree rb_tree(entries_);
|
|
|
|
|
|
|
|
for (auto entry_id = directory_id(0); entry_id < directory_id(entries_.size()); ++entry_id)
|
|
|
|
{
|
|
|
|
if (entries_[entry_id].type != compound_document_entry::entry_type::UserStream) continue;
|
|
|
|
|
|
|
|
auto path = std::string("/");
|
|
|
|
|
|
|
|
for (auto part : rb_tree.path(entry_id))
|
|
|
|
{
|
|
|
|
if (part > 0)
|
|
|
|
{
|
|
|
|
path.append(utf16_to_utf8(entries_[part].name()) + "/");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::cout << path << utf16_to_utf8(entries_[entry_id].name()) << std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-22 07:52:02 +08:00
|
|
|
} // namespace detail
|
|
|
|
} // namespace xlnt
|