Skip to content

Commit

Permalink
add option "kCurlHttp_CaInfo" & "kCurlHttp_CaInfoBlob", allow to retr…
Browse files Browse the repository at this point in the history
…ieve properties from a stream-class (#82)

* add CURLOPT_CAINFO and CURLOPT_CAINFO_BLOB

* fix

* test

* fix

* cosmetic

* fix

* cosmetic

* ...add test-code

* Revert "...add test-code"

This reverts commit ca8c2ec.

* cosmetic

* bump version

* add a test

* fix incompatibility with older GCC (8.3)

* comaptibility-issue with older GCC

* test

* cosmetic

* fix issue with older GCC

* fix issue with older GCC
  • Loading branch information
ptahmose authored Dec 4, 2023
1 parent 8cd30f6 commit 9092aea
Show file tree
Hide file tree
Showing 11 changed files with 166 additions and 41 deletions.
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 @@ using namespace libCZI;
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);
}
}
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);
}
}
}

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 @@ CurlHttpInputStream::CurlHttpInputStream(const std::string& url, const std::map<
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");
}

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())
{
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");
}
}

this->curl_handle_ = up_curl_handle.release();
this->curl_url_handle_ = up_curl_url_handle.release();
}
Expand All @@ -193,7 +240,7 @@ CurlHttpInputStream::CurlHttpInputStream(const std::string& url, const std::map<
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

0 comments on commit 9092aea

Please sign in to comment.