Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ The release run heads these entries with the version and opens a fresh

## Unreleased

- **Fix**: a zip entry name with a leading slash is read relative to the
archive root rather than throwing, and one named `/` alone is dropped. An
`.odt` carrying such an entry now opens; LibreOffice still refuses it.

- The rendered sheet exposes `odr.editing`: `enable()` / `disable()` turn the
mode on, `lockAt()` answers for a cell, and a refusal reaches the host as
`odr.onEditRefused` / `odr.onEditModeChange`, whose codes share the space
Expand Down
4 changes: 4 additions & 0 deletions src/odr/internal/zip/zip_archive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ ZipArchive::ZipArchive() = default;
ZipArchive::ZipArchive(const std::shared_ptr<util::Archive> &archive) {
for (auto &&entry : *archive) {
RelPath path(entry.path());
// an entry named "/" addresses nothing
if (path.empty()) {
continue;
}
if (entry.is_file()) {
std::uint8_t compression_level = 6;
if (entry.method() == util::Method::STORED) {
Expand Down
3 changes: 2 additions & 1 deletion src/odr/internal/zip/zip_util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ RelPath Archive::Entry::path() const {
std::array<char, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE> filename{};
mz_zip_reader_get_filename(m_archive->zip(), m_index, filename.data(),
static_cast<mz_uint>(filename.size()));
return RelPath(filename.data());
// a leading slash is malformed (APPNOTE.TXT 4.4.17.1) and read away
return Path(filename.data()).make_relative();
}

Method Archive::Entry::method() const {
Expand Down
4 changes: 2 additions & 2 deletions test/data.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ odr_test_data(
odr_test_data(
PATH "input/odr-private"
URL "https://github.com/opendocument-app/OpenDocument.test-private.git"
REVISION "d0bedc89b67e73a2e4113f96f27d1d1cdf30c69f")
REVISION "c4efe97d67c9a12aa08965d696a0cb7a3dc4d025")

odr_test_data(
PATH "reference-output/odr-public"
Expand All @@ -22,4 +22,4 @@ odr_test_data(
odr_test_data(
PATH "reference-output/odr-private"
URL "https://github.com/opendocument-app/OpenDocument.test-private.output.git"
REVISION "c43ef03e9ca239489141a8d91c0187ed9602436a")
REVISION "666ce5a24a1a2d4f74b1abf0f004c0778e9ebddd")
38 changes: 38 additions & 0 deletions test/src/internal/zip/zip_archive_test.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <odr/exceptions.hpp>

#include <odr/internal/abstract/filesystem.hpp>
#include <odr/internal/common/file.hpp>
#include <odr/internal/zip/zip_archive.hpp>
#include <odr/internal/zip/zip_file.hpp>
Expand All @@ -9,9 +10,11 @@

#include <gtest/gtest.h>

#include <algorithm>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <memory>
#include <string>
#include <thread>
Expand Down Expand Up @@ -130,6 +133,41 @@ TEST(ZipArchive, create_order) {
}
}

/// A leading slash, forbidden by APPNOTE.TXT 4.4.17.1, is read away; a name
/// that is nothing but the root addresses no entry.
TEST(ZipArchive, absolute_entry_name) {
const std::string path =
(std::filesystem::current_path() / "absolute-name.zip").string();

{
// miniz refuses the name, so a placeholder goes in and is patched out
mz_zip_archive archive{};
ASSERT_TRUE(mz_zip_writer_init_file(&archive, path.c_str(), 0));
ASSERT_TRUE(mz_zip_writer_add_mem(&archive, "@", nullptr, 0, 0));
ASSERT_TRUE(mz_zip_writer_add_mem(&archive, "@one.txt", "abc", 3, 0));
ASSERT_TRUE(mz_zip_writer_finalize_archive(&archive));
ASSERT_TRUE(mz_zip_writer_end(&archive));

std::string data;
{
std::ifstream in(path, std::ios::binary);
data.assign(std::istreambuf_iterator<char>(in),
std::istreambuf_iterator<char>());
}
std::ranges::replace(data, '@', '/');
std::ofstream out(path, std::ios::binary);
out.write(data.data(), static_cast<std::streamsize>(data.size()));
}

const auto zip =
std::make_shared<util::Archive>(std::make_shared<DiskFile>(path));
EXPECT_EQ(3, zip->find(RelPath("one.txt"))->file()->size());

const ZipArchive read(zip);
EXPECT_EQ(1, std::distance(read.begin(), read.end()));
EXPECT_TRUE(read.as_filesystem()->is_file(AbsPath("/one.txt")));
}

/// The read callback has to be re-entrant, for a memory and a stream source.
TEST(ZipArchive, concurrent_entry_reads) {
const std::string path =
Expand Down
Loading