Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add option "kCurlHttp_CaInfo" & "kCurlHttp_CaInfoBlob", allow to retrieve properties from a stream-class #82

Merged
merged 19 commits into from
Dec 4, 2023
Merged
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.15)
cmake_policy(SET CMP0091 NEW) # enable new "MSVC runtime library selection" (https://cmake.org/cmake/help/latest/variable/CMAKE_MSVC_RUNTIME_LIBRARY.html)

project(libCZI
VERSION 0.55.1
VERSION 0.56.0
HOMEPAGE_URL "https://github.com/ZEISS/libczi"
DESCRIPTION "libCZI is an Open Source Cross-Platform C++ library to read and write CZI")

Expand Down
2 changes: 1 addition & 1 deletion Src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ if (LIBCZI_BUILD_CURL_BASED_STREAM)
message(STATUS "Using CURL lib(s): ${CURL_LIBRARIES}")

else(LIBCZI_BUILD_PREFER_EXTERNALPACKAGE_LIBCURL)
message(STATUS "Could not find libcURL. This dependency will be downloaded.")
message(STATUS "Attempting to download and build libcURL.")
include(FetchContent)
# It seems for MacOS, the secure transport API is deprecated (-> https://curl.se/mail/lib-2023-09/0027.html),
# using OpenSSL seems to be the way to go here - so, we better do not default to secure transport here.
Expand Down
12 changes: 8 additions & 4 deletions Src/CZICmd/cmdlineoptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1901,16 +1901,20 @@ void CCmdLineOptions::PrintHelpBuildInfo()
stringstream ss;
ss << "version : " << majorVer << "." << minorVer << "." << patchVer;
this->GetLog()->WriteLineStdOut(ss.str());
ss = stringstream();
ss.clear();
ss.str("");
ss << "compiler : " << buildInfo.compilerIdentification;
this->GetLog()->WriteLineStdOut(ss.str());
ss = stringstream();
ss.clear();
ss.str("");
ss << "repository-URL : " << buildInfo.repositoryUrl;
this->GetLog()->WriteLineStdOut(ss.str());
ss = stringstream();
ss.clear();
ss.str("");
ss << "repository-branch: " << buildInfo.repositoryBranch;
this->GetLog()->WriteLineStdOut(ss.str());
ss = stringstream();
ss.clear();
ss.str("");
ss << "repository-tag : " << buildInfo.repositoryTag;
this->GetLog()->WriteLineStdOut(ss.str());
}
Expand Down
13 changes: 7 additions & 6 deletions Src/libCZI/CziMetadataDocumentInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,14 @@ CCziMetadataDocumentInfo::CCziMetadataDocumentInfo(std::shared_ptr<CCziMetadata>

static const struct
{
char dimChar;
wchar_t dimChar;
double(ScalingInfoEx::* scaleVarPtr);
std::wstring(ScalingInfoEx::* defaultUnit);
} dimScalingData[] =
{
{'X', &ScalingInfoEx::scaleX, &ScalingInfoEx::defaultUnitFormatX},
{'Y', &ScalingInfoEx::scaleY, &ScalingInfoEx::defaultUnitFormatY},
{'Z', &ScalingInfoEx::scaleZ, &ScalingInfoEx::defaultUnitFormatZ},
{L'X', &ScalingInfoEx::scaleX, &ScalingInfoEx::defaultUnitFormatX},
{L'Y', &ScalingInfoEx::scaleY, &ScalingInfoEx::defaultUnitFormatY},
{L'Z', &ScalingInfoEx::scaleZ, &ScalingInfoEx::defaultUnitFormatZ},
};

for (const auto d : dimScalingData)
Expand All @@ -114,12 +114,13 @@ CCziMetadataDocumentInfo::CCziMetadataDocumentInfo(std::shared_ptr<CCziMetadata>
scalingInfo.*d.scaleVarPtr = nodeScalingValue.node().text().as_double();
}

ss = wstringstream();
ss.clear();
ss.str(L"");
ss << L"Items/Distance[@Id='" << d.dimChar << L"']/DefaultUnitFormat";
auto nodeScalingDefaultUnit = np.select_node(ss.str().c_str());
if (!nodeScalingDefaultUnit.node().empty())
{
scalingInfo.*d.defaultUnit = nodeScalingDefaultUnit.node().text().as_string();
scalingInfo.*d.defaultUnit = nodeScalingDefaultUnit.node().text().get();
}
}

Expand Down
49 changes: 48 additions & 1 deletion Src/libCZI/StreamsLib/curlhttpinputstream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,31 @@
return string_stream.str();
}

/*static*/libCZI::StreamsFactory::Property CurlHttpInputStream::GetClassProperty(const char* property_name)
{
if (property_name != nullptr)
{
if (strcmp(property_name, StreamsFactory::kStreamClassInfoProperty_CurlHttp_CaInfo) == 0)
{
const auto version_info = curl_version_info(CURLVERSION_NOW);
if (version_info->cainfo != nullptr)
{
return StreamsFactory::Property(version_info->cainfo);

Check warning on line 64 in Src/libCZI/StreamsLib/curlhttpinputstream.cpp

View check run for this annotation

Codecov / codecov/patch

Src/libCZI/StreamsLib/curlhttpinputstream.cpp#L64

Added line #L64 was not covered by tests
}
}
else if (strcmp(property_name, StreamsFactory::kStreamClassInfoProperty_CurlHttp_CaPath) == 0)
{
const auto version_info = curl_version_info(CURLVERSION_NOW);
if (version_info->capath != nullptr)
{
return StreamsFactory::Property(version_info->capath);

Check warning on line 72 in Src/libCZI/StreamsLib/curlhttpinputstream.cpp

View check run for this annotation

Codecov / codecov/patch

Src/libCZI/StreamsLib/curlhttpinputstream.cpp#L72

Added line #L72 was not covered by tests
}
}
}

return {};
}

CurlHttpInputStream::CurlHttpInputStream(const std::string& url, const std::map<int, libCZI::StreamsFactory::Property>& property_bag)
{
/* init the curl session */
Expand Down Expand Up @@ -180,6 +205,28 @@
ThrowIfCurlSetOptError(return_code, "CURLOPT_MAXREDIRS");
}

property = property_bag.find(StreamsFactory::StreamProperties::kCurlHttp_CaInfo);
if (property != property_bag.end())
{
return_code = curl_easy_setopt(up_curl_handle.get(), CURLOPT_CAINFO, property->second.GetAsStringOrThrow().c_str());
ThrowIfCurlSetOptError(return_code, "CURLOPT_CAINFO");

Check warning on line 212 in Src/libCZI/StreamsLib/curlhttpinputstream.cpp

View check run for this annotation

Codecov / codecov/patch

Src/libCZI/StreamsLib/curlhttpinputstream.cpp#L211-L212

Added lines #L211 - L212 were not covered by tests
}

property = property_bag.find(StreamsFactory::StreamProperties::kCurlHttp_CaInfoBlob);
if (property != property_bag.end())
{
string ca_info_blob = property->second.GetAsStringOrThrow();
if (!ca_info_blob.empty())

Check warning on line 219 in Src/libCZI/StreamsLib/curlhttpinputstream.cpp

View check run for this annotation

Codecov / codecov/patch

Src/libCZI/StreamsLib/curlhttpinputstream.cpp#L218-L219

Added lines #L218 - L219 were not covered by tests
{
struct curl_blob blob;
blob.data = &ca_info_blob[0];
blob.len = ca_info_blob.size();
blob.flags = CURL_BLOB_COPY;
return_code = curl_easy_setopt(up_curl_handle.get(), CURLOPT_CAINFO_BLOB, &blob);
ThrowIfCurlSetOptError(return_code, "CURLOPT_CAINFO_BLOB");

Check warning on line 226 in Src/libCZI/StreamsLib/curlhttpinputstream.cpp

View check run for this annotation

Codecov / codecov/patch

Src/libCZI/StreamsLib/curlhttpinputstream.cpp#L222-L226

Added lines #L222 - L226 were not covered by tests
}
}

Check warning on line 228 in Src/libCZI/StreamsLib/curlhttpinputstream.cpp

View check run for this annotation

Codecov / codecov/patch

Src/libCZI/StreamsLib/curlhttpinputstream.cpp#L228

Added line #L228 was not covered by tests

this->curl_handle_ = up_curl_handle.release();
this->curl_url_handle_ = up_curl_url_handle.release();
}
Expand All @@ -193,7 +240,7 @@
std::lock_guard<std::mutex> lck(this->request_mutex_);

// TODO(JBL): We may be able to use a "header-function" (https://curl.se/libcurl/c/CURLOPT_HEADERFUNCTION.html) in order to find out
// whether the server accepted out "Range-Request". According to https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests,
// whether the server accepted our "Range-Request". According to https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests,
// we can expect to have a line "something like 'Accept-Ranges: bytes'" in the response header with a server that supports range
// requests (and a line 'Accept-Ranges: none') would tell us explicitly that range requests are *not* supported.

Expand Down
1 change: 1 addition & 0 deletions Src/libCZI/StreamsLib/curlhttpinputstream.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class CurlHttpInputStream : public libCZI::IStream
static void OneTimeGlobalCurlInitialization();

static std::string GetBuildInformation();
static libCZI::StreamsFactory::Property GetClassProperty(const char* property_name);
private:
/// This struct is passed to the WriteData function as user-data.
struct WriteDataContext
Expand Down
11 changes: 7 additions & 4 deletions Src/libCZI/StreamsLib/streamsFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

using namespace libCZI;

/*static*/const char* StreamsFactory::kStreamClassInfoProperty_CurlHttp_CaInfo = "CurlHttp_CaInfo";
/*static*/const char* StreamsFactory::kStreamClassInfoProperty_CurlHttp_CaPath = "CurlHttp_CaPath";

static const struct
{
StreamsFactory::StreamClassInfo stream_class_info;
Expand All @@ -31,7 +34,7 @@ static const struct
{
#if LIBCZI_CURL_BASED_STREAM_AVAILABLE
{
{ "curl_http_inputstream", "curl-based http/https stream", CurlHttpInputStream::GetBuildInformation },
{ "curl_http_inputstream", "curl-based http/https stream", CurlHttpInputStream::GetBuildInformation, CurlHttpInputStream::GetClassProperty },
[](const StreamsFactory::CreateStreamInfo& stream_info, const std::string& file_name) -> std::shared_ptr<libCZI::IStream>
{
return std::make_shared<CurlHttpInputStream>(file_name, stream_info.property_bag);
Expand All @@ -41,7 +44,7 @@ static const struct
#endif // LIBCZI_CURL_BASED_STREAM_AVAILABLE
#if _WIN32
{
{ "windows_file_inputstream", "stream implementation based on Windows-API" },
{ "windows_file_inputstream", "stream implementation based on Windows-API", nullptr, nullptr },
[](const StreamsFactory::CreateStreamInfo& stream_info, const std::string& file_name) -> std::shared_ptr<libCZI::IStream>
{
return std::make_shared<WindowsFileInputStream>(file_name);
Expand All @@ -54,7 +57,7 @@ static const struct
#endif // _WIN32
#if LIBCZI_USE_PREADPWRITEBASED_STREAMIMPL
{
{ "pread_file_inputstream", "stream implementation based on pread-API" },
{ "pread_file_inputstream", "stream implementation based on pread-API", nullptr, nullptr },
[](const StreamsFactory::CreateStreamInfo& stream_info, const std::string& file_name) -> std::shared_ptr<libCZI::IStream>
{
return std::make_shared<PreadFileInputStream>(file_name);
Expand All @@ -63,7 +66,7 @@ static const struct
},
#endif // LIBCZI_USE_PREADPWRITEBASED_STREAMIMPL
{
{ "c_runtime_file_inputstream", "stream implementation based on C-runtime library" },
{ "c_runtime_file_inputstream", "stream implementation based on C-runtime library", nullptr, nullptr },
[](const StreamsFactory::CreateStreamInfo& stream_info, const std::string& file_name) -> std::shared_ptr<libCZI::IStream>
{
return std::make_shared<SimpleFileInputStream>(file_name);
Expand Down
28 changes: 26 additions & 2 deletions Src/libCZI/libCZI_StreamsLib.h
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,10 @@ namespace libCZI
kCurlHttp_FollowLocation = 108, ///< For CurlHttpInputStream, type bool: a boolean indicating whether redirects are to be followed, c.f. https://curl.se/libcurl/c/CURLOPT_FOLLOWLOCATION.html for more information.

kCurlHttp_MaxRedirs = 109, ///< For CurlHttpInputStream, type int32: gives the maximum number of redirects to follow, c.f. https://curl.se/libcurl/c/CURLOPT_MAXREDIRS.html for more information.

kCurlHttp_CaInfo = 110, ///< For CurlHttpInputStream, type string: gives the directory to check for CA certificate bundle , c.f. https://curl.se/libcurl/c/CURLOPT_CAINFO.html for more information.

kCurlHttp_CaInfoBlob = 111, ///< For CurlHttpInputStream, type string: give PEM encoded content holding one or more certificates to verify the HTTPS server with, c.f. https://curl.se/libcurl/c/CURLOPT_CAINFO_BLOB.html for more information.
};
};

Expand Down Expand Up @@ -269,8 +273,18 @@ namespace libCZI
struct LIBCZI_API StreamClassInfo
{
std::string class_name; ///< Name of the class (this uniquely identifies the class).
std::string short_description; ///< A short and informal description of the class.
std::function<std::string()> get_build_info; ///< A function which returns a string with build information for the class (e.g. version information).
std::string short_description; ///< A short and informal description of the class.

/// A function which returns a string with build information for the class (e.g. version information). Note
/// that this field may be null, in which case no information is available.
std::function<std::string()> get_build_info;

/// A function which returns a class-specific property about the class. This is e.g. intended for
/// providing information about build-time options for a specific class. Currently, it is used for
/// the libcurl-based stream-class to provide information about the build-time configured paths for
/// the CA certificates.
/// Note that this field may be null, in which case no information is available.
std::function<Property(const char* property_name)> get_property;
};

/// Gets information about a stream class available in the factory. The function returns false if the index is out of range.
Expand Down Expand Up @@ -300,5 +314,15 @@ namespace libCZI
///
/// \returns A new instance of a streams-objects for reading the specified file from the file-system.
static std::shared_ptr<libCZI::IStream> CreateDefaultStreamForFile(const wchar_t* filename);

/// A static string for the property_name for the get_property-function of the StreamClassInfo identifying the
/// build-time configured file holding one or more certificates to verify the peer with. C.f. https://curl.se/libcurl/c/curl_version_info.html, this
/// property gives the value of the "cainfo"-field. If it is null, then an invalid property is returned.
static const char* kStreamClassInfoProperty_CurlHttp_CaInfo;

/// A static string for the property_name for the get_property-function of the StreamClassInfo identifying the
/// build-time configured directory holding CA certificates. C.f. https://curl.se/libcurl/c/curl_version_info.html, this
/// property gives the value of the "capath"-field. If it is null, then an invalid property is returned.
static const char* kStreamClassInfoProperty_CurlHttp_CaPath;
};
}
40 changes: 20 additions & 20 deletions Src/libCZI_UnitTests/test_metadatareading.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ TEST(MetadataReading, ScalingInfoExTest)
EXPECT_DOUBLE_EQ(scalingInfo.scaleY, 1.6432520108980473e-07);
EXPECT_FALSE(scalingInfo.IsScaleZValid());

EXPECT_TRUE(scalingInfo.defaultUnitFormatX == L"um");
EXPECT_TRUE(scalingInfo.defaultUnitFormatY == L"um");
EXPECT_STREQ(scalingInfo.defaultUnitFormatX.c_str(), L"um");
EXPECT_STREQ(scalingInfo.defaultUnitFormatY.c_str(), L"um");
EXPECT_TRUE(scalingInfo.defaultUnitFormatZ.empty());
}

Expand Down Expand Up @@ -274,8 +274,8 @@ static void EnumAllRecursively(IXmlNodeRead* node, std::function<bool(std::share
return false;
}

EnumAllRecursively(n.get(), func);
return true;
EnumAllRecursively(n.get(), func);
return true;
});
}

Expand All @@ -291,7 +291,7 @@ TEST(MetadataReading, WalkChildrenTest1)
[&](std::shared_ptr<IXmlNodeRead> n)->bool
{
names.push_back(utf8_conv.to_bytes(n->Name()));
return true;
return true;
});

auto cnt = md.use_count();
Expand All @@ -317,15 +317,15 @@ TEST(MetadataReading, WalkChildrenTest2)
{
string s(utf8_conv.to_bytes(n->Name()));

n->EnumAttributes(
[&](const std::wstring& attribName, const std::wstring& attribValue)->bool
{
s += ":" + utf8_conv.to_bytes(attribName) + "=" + utf8_conv.to_bytes(attribValue);
return true;
});
n->EnumAttributes(
[&](const std::wstring& attribName, const std::wstring& attribValue)->bool
{
s += ":" + utf8_conv.to_bytes(attribName) + "=" + utf8_conv.to_bytes(attribValue);
return true;
});

namesAndAttributes.push_back(s);
return true;
namesAndAttributes.push_back(s);
return true;
});

auto cnt = md.use_count();
Expand All @@ -350,14 +350,14 @@ TEST(MetadataReading, WalkChildrenTest3)
[&](std::shared_ptr<IXmlNodeRead> n)->bool
{
string s(utf8_conv.to_bytes(n->Name()));
wstring value;
if (n->TryGetValue(&value))
{
s += " -> " + utf8_conv.to_bytes(value);
}
wstring value;
if (n->TryGetValue(&value))
{
s += " -> " + utf8_conv.to_bytes(value);
}

namesAndValue.push_back(s);
return true;
namesAndValue.push_back(s);
return true;
});

auto cnt = md.use_count();
Expand Down
23 changes: 21 additions & 2 deletions Src/libCZI_UnitTests/test_reader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,25 @@ using namespace std;

TEST(DimCoordinate, ReaderException)
{
class MyException : public std::exception
{
private:
std::string exception_text_;
std::error_code code_;
public:
MyException(const std::string& exceptionText, std::error_code code) :exception_text_(exceptionText), code_(code) {}

const char* what() const noexcept override
{
return this->exception_text_.c_str();
}

std::error_code code() const noexcept
{
return this->code_;
}
};

class CTestStreamImp :public libCZI::IStream
{
private:
Expand All @@ -20,7 +39,7 @@ TEST(DimCoordinate, ReaderException)

virtual void Read(std::uint64_t offset, void* pv, std::uint64_t size, std::uint64_t* ptrBytesRead) override
{
throw std::ios_base::failure(this->exceptionText, this->code);
throw MyException(this->exceptionText, this->code);
}
};

Expand All @@ -39,7 +58,7 @@ TEST(DimCoordinate, ReaderException)
{
excp.rethrow_nested();
}
catch (std::ios_base::failure& innerExcp)
catch (MyException& innerExcp)
{
// according to standard, the content of the what()-test is implementation-specific,
// so it is not suited for checking - but it seems that the code goes unaltered
Expand Down
Loading