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
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
cmake_minimum_required(VERSION 3.27)

# This is the current version of this C++ project
project(c2pa-c VERSION 0.19.0)
project(c2pa-c VERSION 0.19.1)

# Set the version of the c2pa_rs library used
set(C2PA_VERSION "0.78.6")
Expand Down
11 changes: 11 additions & 0 deletions include/c2pa.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,17 @@ namespace c2pa
[[deprecated("Use Reader(IContextProvider& context, source_path) instead")]]
Reader(const std::filesystem::path &source_path);

/// @brief Try to open a Reader from a context and file path when the asset may lack C2PA data.
/// @return A Reader if JUMBF (c2pa/manifest) data is present; std::nullopt if none.
/// @throws C2paException for errors other than a missing manifest (e.g. invalid asset).
/// @throws std::system_error if the file cannot be opened.
static std::optional<Reader> from_asset(IContextProvider& context, const std::filesystem::path &source_path);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume we don't support context-less Reader because it's deprecated?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I almost added it and then realized it probably wasn't brilliant to add a new API that was flagged as deprecated.


/// @brief Try to create a Reader from a context and stream when the asset may lack C2PA data.
/// @return A Reader if JUMBF (c2pa/manifest) data is present; std::nullopt if none.
/// @throws C2paException for errors other than a missing manifest.
static std::optional<Reader> from_asset(IContextProvider& context, const std::string &format, std::istream &stream);

// Non-copyable
Reader(const Reader&) = delete;

Expand Down
2 changes: 1 addition & 1 deletion src/c2pa_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ namespace c2pa
if (result == nullptr)
{
auto C2paException = c2pa::C2paException();
if (strstr(C2paException.what(), "ManifestNotFound") != nullptr)
if (detail::error_indicates_manifest_not_found(C2paException.what()))
{
return std::nullopt;
}
Expand Down
5 changes: 5 additions & 0 deletions src/c2pa_internal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
namespace c2pa {
namespace detail {

/// @brief True if the C2PA error message indicates no JUMBF / manifest in the asset (ManifestNotFound).
inline bool error_indicates_manifest_not_found(const char* message) noexcept {
return message != nullptr && std::strstr(message, "ManifestNotFound") != nullptr;
}

/// @brief Converts a C array of C strings to a std::vector of std::string.
/// @param mime_types Pointer to an array of C strings (const char*).
/// @param count Number of elements in the array.
Expand Down
24 changes: 24 additions & 0 deletions src/c2pa_reader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@
#include "c2pa.hpp"
#include "c2pa_internal.hpp"

namespace {

template <typename F>
std::optional<c2pa::Reader> reader_from_asset_impl(F&& construct_reader) {
try {
return construct_reader();
} catch (const c2pa::C2paException& e) {
if (c2pa::detail::error_indicates_manifest_not_found(e.what())) {
return std::nullopt;
}
throw;
}
}

} // namespace

namespace c2pa
{
/// Reader class for reading manifests
Expand Down Expand Up @@ -154,4 +170,12 @@ namespace c2pa
auto ptr = c2pa_reader_supported_mime_types(&count);
return detail::c_mime_types_to_vector(ptr, count);
}

std::optional<Reader> Reader::from_asset(IContextProvider& context, const std::filesystem::path& source_path) {
return reader_from_asset_impl([&]() { return Reader(context, source_path); });
}

std::optional<Reader> Reader::from_asset(IContextProvider& context, const std::string& format, std::istream& stream) {
return reader_from_asset_impl([&]() { return Reader(context, format, stream); });
}
} // namespace c2pa
57 changes: 57 additions & 0 deletions tests/reader.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,63 @@ TEST_F(ReaderTest, FileNoManifest)
EXPECT_THROW({ auto reader = c2pa::Reader(test_file); }, c2pa::C2paException);
};

TEST_F(ReaderTest, FromAssetNoManifestReturnsNullopt)
{
fs::path current_dir = fs::path(__FILE__).parent_path();
fs::path test_file = current_dir / "../tests/fixtures/A.jpg";
auto context = c2pa::Context();
auto reader = c2pa::Reader::from_asset(context, test_file);
EXPECT_FALSE(reader.has_value());
}

TEST_F(ReaderTest, FromAssetWithManifestReturnsReader)
{
fs::path current_dir = fs::path(__FILE__).parent_path();
fs::path test_file = current_dir / "../tests/fixtures/C.jpg";
auto context = c2pa::Context();
auto reader = c2pa::Reader::from_asset(context, test_file);
ASSERT_TRUE(reader.has_value());
EXPECT_TRUE(reader->json().find("C.jpg") != std::string::npos);
}

TEST_F(ReaderTest, FromAssetStreamNoManifestReturnsNullopt)
{
fs::path current_dir = fs::path(__FILE__).parent_path();
fs::path test_file = current_dir / "../tests/fixtures/A.jpg";
std::ifstream stream(test_file, std::ios::binary);
ASSERT_TRUE(stream);
auto context = c2pa::Context();
auto reader = c2pa::Reader::from_asset(context, "image/jpeg", stream);
EXPECT_FALSE(reader.has_value());
}

TEST_F(ReaderTest, FromAssetStreamWithManifestReturnsReader)
{
fs::path current_dir = fs::path(__FILE__).parent_path();
fs::path test_file = current_dir / "../tests/fixtures/C.jpg";
std::ifstream stream(test_file, std::ios::binary);
ASSERT_TRUE(stream);
auto context = c2pa::Context();
auto reader = c2pa::Reader::from_asset(context, "image/jpeg", stream);
ASSERT_TRUE(reader.has_value());
EXPECT_TRUE(reader->json().find("C.jpg") != std::string::npos);
}

TEST_F(ReaderTest, FromAssetEmptyFileStillThrows)
{
fs::path empty_file = get_temp_path("from_asset_empty");
{
std::ofstream f(empty_file, std::ios::binary);
ASSERT_TRUE(f);
}
auto context = c2pa::Context();
EXPECT_THROW(
{
(void)c2pa::Reader::from_asset(context, empty_file);
},
c2pa::C2paException);
}

class RemoteUrlTests
: public ::testing::TestWithParam<std::tuple<std::string, bool>> {
public:
Expand Down
Loading