c2pa-cpp
C++ API for the C2PA SDK
Loading...
Searching...
No Matches
c2pa_internal.hpp
Go to the documentation of this file.
1// Copyright 2024 Adobe. All rights reserved.
2// This file is licensed to you under the Apache License,
3// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
4// or the MIT license (http://opensource.org/licenses/MIT),
5// at your option.
6// Unless required by applicable law or agreed to in writing,
7// this software is distributed on an "AS IS" BASIS, WITHOUT
8// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
9// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
10// specific language governing permissions and limitations under
11// each license.
12
13/// @file c2pa_internal.hpp
14/// @brief Internal implementation details shared across c2pa_cpp source files.
15/// @details This header is private to the library implementation and not installed,
16/// as it is used to share code inside c2pa_cpp SDK.
17
18#ifndef C2PA_INTERNAL_HPP
19#define C2PA_INTERNAL_HPP
20
21#include <algorithm>
22#include <cctype>
23#include <cstring>
24#include <fstream>
25#include <filesystem>
26#include <string>
27#include <string_view>
28#include <vector>
29#include <memory>
30
31#include "c2pa.h"
32#include "c2pa.hpp"
33
34namespace c2pa {
35namespace detail {
36
37/// @brief True if the C2PA error message indicates no JUMBF / manifest in the asset (ManifestNotFound).
38inline bool error_indicates_manifest_not_found(const char* message) noexcept {
39 return message != nullptr && std::strstr(message, "ManifestNotFound") != nullptr;
40}
41
42/// @brief Converts a C array of C strings to a std::vector of std::string.
43/// @param mime_types Pointer to an array of C strings (const char*).
44/// @param count Number of elements in the array.
45/// @return A std::vector containing the strings from the input array.
46/// @details This function takes ownership of the input array and frees it
47/// using c2pa_free_string_array().
48inline std::vector<std::string> c_mime_types_to_vector(const char* const* mime_types, uintptr_t count) {
49 std::vector<std::string> result;
50 if (mime_types == nullptr) { return result; }
51
52 try {
53 result.reserve(count);
54 for(uintptr_t i = 0; i < count; i++) {
55 if (mime_types[i] != nullptr) {
56 result.emplace_back(mime_types[i]);
57 }
58 }
59 } catch (...) {
60 c2pa_free_string_array(mime_types, count);
61 throw;
62 }
63
64 c2pa_free_string_array(mime_types, count);
65 return result;
66}
67
68/// Maps C2PA seek mode to std::ios seek direction.
69constexpr std::ios_base::seekdir whence_to_seekdir(C2paSeekMode whence) noexcept {
70 switch (whence) {
71 case C2paSeekMode::Start: return std::ios_base::beg;
72 case C2paSeekMode::Current: return std::ios_base::cur;
73 case C2paSeekMode::End: return std::ios_base::end;
74 default: return std::ios_base::beg;
75 }
76}
77
78/// Check if stream is in valid state for I/O operations
79template<typename Stream>
80inline bool is_stream_usable(Stream* s) noexcept {
81 return s && !s->bad();
82}
83
84/// Traits (templated): how to seek and get position for a given stream type.
85template<typename Stream>
87
88template<>
89struct StreamSeekTraits<std::istream> {
90 static void seek(std::istream* s, intptr_t offset, std::ios_base::seekdir dir) {
91 s->seekg(offset, dir);
92 }
93 static int64_t tell(std::istream* s) {
94 return static_cast<int64_t>(s->tellg());
95 }
96};
97
98template<>
99struct StreamSeekTraits<std::ostream> {
100 static void seek(std::ostream* s, intptr_t offset, std::ios_base::seekdir dir) {
101 s->seekp(offset, dir);
102 }
103 static int64_t tell(std::ostream* s) {
104 return static_cast<int64_t>(s->tellp());
105 }
106};
107
108template<>
109struct StreamSeekTraits<std::iostream> {
110 static void seek(std::iostream* s, intptr_t offset, std::ios_base::seekdir dir) {
111 s->seekg(offset, dir);
112 s->seekp(offset, dir);
113 }
114 static int64_t tell(std::iostream* s) {
115 return static_cast<int64_t>(s->tellp());
116 }
117};
118
119/// Seeker impl.
120/// Exceptions must not unwind into Rust/C, so any throw
121/// is converted to an IoError return.
122template<typename Stream>
123intptr_t stream_seeker(StreamContext* context, intptr_t offset, C2paSeekMode whence) {
124 try {
125 auto* stream = reinterpret_cast<Stream*>(context);
126 if (!is_stream_usable(stream)) {
128 }
129 const std::ios_base::seekdir dir = whence_to_seekdir(whence);
130 stream->clear();
131 StreamSeekTraits<Stream>::seek(stream, offset, dir);
132 if (stream->fail()) {
134 }
135 if (stream->bad()) {
137 }
138 const int64_t pos = StreamSeekTraits<Stream>::tell(stream);
139 if (pos < 0) {
141 }
142 return static_cast<intptr_t>(pos);
143 } catch (...) {
145 }
146}
147
148/// Reader impl.
149/// Exceptions must not unwind into Rust/C, so any throw
150/// is converted to an IoError return.
151template<typename Stream>
152intptr_t stream_reader(StreamContext* context, uint8_t* buffer, intptr_t size) {
153 if (!context || !buffer) {
155 }
156 if (size < 0) {
158 }
159 if (size == 0) {
160 return 0;
161 }
162 try {
163 auto* stream = reinterpret_cast<Stream*>(context);
164 if (!is_stream_usable(stream)) {
166 }
167 stream->read(reinterpret_cast<char*>(buffer), size);
168 if (stream->fail()) {
169 if (!stream->eof()) {
171 }
172 }
173 if (stream->bad()) {
175 }
176 return static_cast<intptr_t>(stream->gcount());
177 } catch (...) {
179 }
180}
181
182/// Get stream from context, used by writer and flusher.
183/// Exceptions must not unwind into Rust/C, so any throw
184/// is converted to an IoError return.
185template<typename Stream, typename Op>
186intptr_t stream_op(StreamContext* context, Op op) {
187 try {
188 auto* stream = reinterpret_cast<Stream*>(context);
189 if (!is_stream_usable(stream)) {
191 }
192 const intptr_t result = op(stream);
193 if (stream->fail()) {
195 }
196 if (stream->bad()) {
198 }
199 return result;
200 } catch (...) {
202 }
203}
204
205/// Writer impl.
206template<typename Stream>
207intptr_t stream_writer(StreamContext* context, const uint8_t* buffer, intptr_t size) {
208 return stream_op<Stream>(context, [buffer, size](Stream* s) {
209 s->write(reinterpret_cast<const char*>(buffer), size);
210 return size;
211 });
212}
213
214/// Flusher impl.
215template<typename Stream>
217 return stream_op<Stream>(context, [](Stream* s) {
218 s->flush();
219 return 0;
220 });
221}
222
223/// @brief Open a binary file stream with error handling
224/// @tparam StreamType std::ifstream or std::ofstream
225/// @param path Path to the file
226/// @return Unique pointer to opened stream
227template<typename StreamType>
228inline std::unique_ptr<StreamType> open_file_binary(const std::filesystem::path &path)
229{
230 auto stream = std::make_unique<StreamType>(
231 path,
232 std::ios_base::binary
233 );
234 if (!stream->is_open()) {
235 throw C2paException("Failed to open file: " + path.string());
236 }
237 return stream;
238}
239
240/// @brief Extract file extension without the leading dot
241/// @param path Filesystem path
242/// @return Extension string (e.g., "jpg" not ".jpg")
243inline std::string extract_file_extension(const std::filesystem::path &path) noexcept {
244 auto ext = path.extension().string();
245 return ext.empty() ? "" : ext.substr(1);
246}
247
248/// @brief Format asking the library to determine the container type from content.
249/// @details The C API rejects a null format, so absent must be spelled empty.
250inline constexpr const char *kDetectFormatFromContent = "";
251
252/// @brief ASCII whitespace ignored around a format.
253/// @details Formats are MIME types or extensions, so non-ASCII spaces are
254/// deliberately excluded.
255inline constexpr const char *kFormatWhitespace = " \t\n\r\f\v";
256
257/// @brief Trim and lowercase a caller-supplied format; empty when @p format is blank.
258/// @details Empty means detection from content, so this alone is enough wherever
259/// a blank format is allowed to request that.
260[[nodiscard]] inline std::string normalize_format(const std::string &format) {
261 const auto first = format.find_first_not_of(kFormatWhitespace);
262 if (first == std::string::npos) {
263 return {}; // Empty or all whitespace.
264 }
265 const auto last = format.find_last_not_of(kFormatWhitespace);
266 std::string normalized(std::string_view(format).substr(first, last - first + 1));
267 std::transform(normalized.begin(), normalized.end(), normalized.begin(),
268 [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
269 return normalized;
270}
271
272/// @brief Resolve a format that must be stated explicitly.
273/// These paths never infer the container type from content,
274/// so a blank format is rejected.
275/// @return @p format trimmed and lowercased.
276/// @throws C2paException if @p format is blank.
277[[nodiscard]] inline std::string resolve_format(const std::string &format) {
278 std::string normalized = normalize_format(format);
279 if (normalized.empty()) {
280 throw C2paException("An explicit format is required.");
281 }
282 return normalized;
283}
284
285/// @brief Convert C string result to C++ string with cleanup
286/// @param c_result Raw C string from C API
287/// @return C++ string (throws if null)
288template<typename T>
289inline std::string c_string_to_string(T* c_result) {
290 if (c_result == nullptr) {
291 throw C2paException();
292 }
293 std::string str(c_result);
295 return str;
296}
297
298/// @brief Convert C byte array result to C++ vector
299/// @param data Raw byte array from C API
300/// @param size Size of the byte array (result from C API call)
301/// @return Vector containing the bytes (throws if null or negative size)
302/// @details This helper extracts the pattern of checking C API results,
303/// copying to a vector, and freeing the C-allocated memory.
304/// The C API contract is: if result < 0, the operation failed. A null
305/// data pointer with size == 0 is a valid empty result (the C API
306/// returns null for empty byte arrays).
307inline std::vector<unsigned char> to_byte_vector(const unsigned char* data, int64_t size) {
308 if (size < 0 || (data == nullptr && size > 0)) {
309 c2pa_free(data); // May be null or allocated, c2pa_free handles both
310 throw C2paException();
311 }
312 if (size == 0) {
314 return {};
315 }
316
317 auto result = std::vector<unsigned char>(data, data + size);
319 return result;
320}
321
322} // namespace detail
323} // namespace c2pa
324
325#endif // C2PA_INTERNAL_HPP
C++ wrapper for the C2PA C library.
Exception class for C2pa errors. This class is used to throw exceptions for errors encountered by the...
Definition c2pa.hpp:87
std::vector< std::string > c_mime_types_to_vector(const char *const *mime_types, uintptr_t count)
Converts a C array of C strings to a std::vector of std::string.
Definition c2pa_internal.hpp:48
intptr_t stream_writer(StreamContext *context, const uint8_t *buffer, intptr_t size)
Writer impl.
Definition c2pa_internal.hpp:207
intptr_t stream_op(StreamContext *context, Op op)
Definition c2pa_internal.hpp:186
std::string extract_file_extension(const std::filesystem::path &path) noexcept
Extract file extension without the leading dot.
Definition c2pa_internal.hpp:243
bool error_indicates_manifest_not_found(const char *message) noexcept
True if the C2PA error message indicates no JUMBF / manifest in the asset (ManifestNotFound).
Definition c2pa_internal.hpp:38
intptr_t stream_flusher(StreamContext *context)
Flusher impl.
Definition c2pa_internal.hpp:216
constexpr std::ios_base::seekdir whence_to_seekdir(C2paSeekMode whence) noexcept
Maps C2PA seek mode to std::ios seek direction.
Definition c2pa_internal.hpp:69
bool is_stream_usable(Stream *s) noexcept
Check if stream is in valid state for I/O operations.
Definition c2pa_internal.hpp:80
std::vector< unsigned char > to_byte_vector(const unsigned char *data, int64_t size)
Convert C byte array result to C++ vector.
Definition c2pa_internal.hpp:307
constexpr const char * kFormatWhitespace
ASCII whitespace ignored around a format.
Definition c2pa_internal.hpp:255
intptr_t stream_reader(StreamContext *context, uint8_t *buffer, intptr_t size)
Definition c2pa_internal.hpp:152
std::string resolve_format(const std::string &format)
Resolve a format that must be stated explicitly. These paths never infer the container type from cont...
Definition c2pa_internal.hpp:277
std::string c_string_to_string(T *c_result)
Convert C string result to C++ string with cleanup.
Definition c2pa_internal.hpp:289
intptr_t stream_seeker(StreamContext *context, intptr_t offset, C2paSeekMode whence)
Definition c2pa_internal.hpp:123
constexpr const char * kDetectFormatFromContent
Format asking the library to determine the container type from content.
Definition c2pa_internal.hpp:250
std::unique_ptr< StreamType > open_file_binary(const std::filesystem::path &path)
Open a binary file stream with error handling.
Definition c2pa_internal.hpp:228
std::string normalize_format(const std::string &format)
Trim and lowercase a caller-supplied format; empty when format is blank.
Definition c2pa_internal.hpp:260
Definition c2pa.hpp:52
int stream_error_return(StreamError e) noexcept
Set errno from StreamError and return error sentinel.
Definition c2pa.hpp:79
static void seek(std::iostream *s, intptr_t offset, std::ios_base::seekdir dir)
Definition c2pa_internal.hpp:110
static int64_t tell(std::iostream *s)
Definition c2pa_internal.hpp:114
static int64_t tell(std::istream *s)
Definition c2pa_internal.hpp:93
static void seek(std::istream *s, intptr_t offset, std::ios_base::seekdir dir)
Definition c2pa_internal.hpp:90
static void seek(std::ostream *s, intptr_t offset, std::ios_base::seekdir dir)
Definition c2pa_internal.hpp:100
static int64_t tell(std::ostream *s)
Definition c2pa_internal.hpp:103
Traits (templated): how to seek and get position for a given stream type.
Definition c2pa_internal.hpp:86