diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e81f097ff..7f543f83d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,6 +27,7 @@ jobs: brew unlink python@3.10 || true brew unlink python@3.11 || true brew unlink python@3.12 || true + brew unlink xz - name: Cache uses: actions/cache@v4 id: cache @@ -45,6 +46,9 @@ jobs: - name: Build xml-security-c if: steps.cache.outputs.cache-hit != 'true' run: ./prepare_osx_build_environment.sh xmlsec ${{ matrix.target }} + - name: Build libxml2 + if: steps.cache.outputs.cache-hit != 'true' + run: ./prepare_osx_build_environment.sh libxml2 ${{ matrix.target }} - name: Move to cache if: steps.cache.outputs.cache-hit != 'true' run: | @@ -84,7 +88,7 @@ jobs: - name: Install Deps run: | dnf install -y --setopt=install_weak_deps=False \ - git gcc-c++ cmake rpm-build xml-security-c-devel zlib-devel vim-common doxygen boost-test swig python3-devel java-1.8.0-openjdk-devel xsd minizip-devel + git gcc-c++ cmake rpm-build xml-security-c-devel libxml2-devel zlib-devel vim-common doxygen boost-test swig python3-devel java-1.8.0-openjdk-devel xsd minizip-devel - name: Install CMake if: matrix.container == 39 run: | @@ -118,7 +122,7 @@ jobs: DEBEMAIL: github-actions@github.com steps: - name: Install dependencies - run: apt update -qq && apt install --no-install-recommends -y git lsb-release build-essential devscripts debhelper cmake xxd xsdcxx libxml-security-c-dev zlib1g-dev doxygen swig openjdk-8-jdk-headless libpython3-dev python3-setuptools libboost-test-dev lintian + run: apt update -qq && apt install --no-install-recommends -y git lsb-release build-essential devscripts debhelper cmake xxd xsdcxx libxml-security-c-dev libxml2-dev zlib1g-dev doxygen swig openjdk-8-jdk-headless libpython3-dev python3-setuptools libboost-test-dev lintian - name: Checkout uses: actions/checkout@v4 with: diff --git a/CMakeLists.txt b/CMakeLists.txt index 29fe204aa..4b9a40615 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,7 @@ find_package(OpenSSL 1.1.1 REQUIRED) find_package(PKCS11) #find_package(PoDoFo) find_package(Threads) +find_package(LibXml2 REQUIRED) find_package(XmlSecurityC REQUIRED) find_package(XSD 4.0 REQUIRED) find_package(ZLIB REQUIRED) diff --git a/README.md b/README.md index f41cfd455..2cc400e92 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ 1. Install dependencies # Ubuntu - sudo apt install cmake xxd libxml-security-c-dev xsdcxx libssl-dev zlib1g-dev + sudo apt install cmake xxd libxml-security-c-dev xsdcxx libxml2-dev libssl-dev zlib1g-dev # Fedora - sudo dnf install cmake gcc-c++ openssl-devel xerces-c-devel xml-security-c-devel xsd zlib-devel vim-common + sudo dnf install cmake gcc-c++ openssl-devel xerces-c-devel xml-security-c-devel xsd libxml2-devel zlib-devel vim-common * doxygen - Optional, for API documentation * libboost-test-dev - Optional, for unittests diff --git a/debian/control b/debian/control index ca9d572b6..4edc2df78 100644 --- a/debian/control +++ b/debian/control @@ -7,6 +7,7 @@ Build-Depends: cmake, libxml-security-c-dev, xsdcxx (>= 4.0) | xsd (>= 4.0), + libxml2-dev, xxd, doxygen, swig, diff --git a/libdigidocpp.wxs b/libdigidocpp.wxs index 8d521069d..748d0f79a 100644 --- a/libdigidocpp.wxs +++ b/libdigidocpp.wxs @@ -54,6 +54,7 @@ + @@ -106,7 +107,7 @@ - + diff --git a/prepare_osx_build_environment.sh b/prepare_osx_build_environment.sh index 7c9b02225..7869d874c 100755 --- a/prepare_osx_build_environment.sh +++ b/prepare_osx_build_environment.sh @@ -6,7 +6,7 @@ XALAN_DIR=xalan_c-1.12 XMLSEC_DIR=xml-security-c-2.0.4 XSD=xsd-4.0.0-i686-macosx OPENSSL_DIR=openssl-3.0.13 -LIBXML2_DIR=libxml2-2.11.5 +LIBXML2_DIR=libxml2-2.12.5 ANDROID_NDK=android-ndk-r26b FREETYPE_DIR=freetype-2.10.1 FONTCONFIG_DIR=fontconfig-2.13.1 @@ -232,11 +232,11 @@ function libxml2 { return 0 ;; esac - if [ ! -f ${LIBXML2_DIR}.tar.gz ]; then - curl -O -L http://xmlsoft.org/sources/${LIBXML2_DIR}.tar.gz + if [ ! -f ${LIBXML2_DIR}.tar.xz ]; then + curl -O -L https://download.gnome.org/sources/libxml2/2.12/${LIBXML2_DIR}.tar.xz fi rm -rf ${LIBXML2_DIR} - tar xf ${LIBXML2_DIR}.tar.gz + tar xf ${LIBXML2_DIR}.tar.xz cd ${LIBXML2_DIR} ./configure --prefix=${TARGET_PATH} ${CONFIGURE} --without-python # Android is missing glob.h @@ -429,6 +429,7 @@ case "$@" in openssl xalan xml_security + libxml2 ;; *) echo "Usage:" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c3931d75b..e6a610707 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,8 +14,6 @@ configure_file( ${CMAKE_SOURCE_DIR}/etc/digidocpp.conf.cmake digidocpp.conf ) set(SCHEMA_DIR ${CMAKE_SOURCE_DIR}/etc/schema) set(XML_DIR ${CMAKE_CURRENT_BINARY_DIR}/xml) -XSD_SCHEMA( xsd_SRCS IGNORE ${XML_DIR} ${SCHEMA_DIR}/conf.xsd - --root-element configuration ) XSD_SCHEMA( xsd_SRCS IGNORE ${XML_DIR} ${SCHEMA_DIR}/OpenDocument_manifest.xsd --root-element manifest --namespace-map urn:oasis:names:tc:opendocument:xmlns:manifest:1.0=digidoc::manifest ) @@ -161,6 +159,7 @@ add_library(digidocpp_priv STATIC xml/SecureDOMParser.cpp xml/UnsignedSignaturePropertiesType.cpp xml/URIResolver.cpp + XMLDocument.h ) set_target_properties(digidocpp_util digidocpp_priv PROPERTIES @@ -183,6 +182,7 @@ target_link_libraries(digidocpp_priv digidocpp_util XmlSecurityC::XmlSecurityC ZLIB::ZLIB + LibXml2::LibXml2 $<$:Ws2_32> ) diff --git a/src/Container.cpp b/src/Container.cpp index 1abf0a267..932351f60 100644 --- a/src/Container.cpp +++ b/src/Container.cpp @@ -30,6 +30,8 @@ #include "util/File.h" #include "util/log.h" +#include + DIGIDOCPP_WARNING_PUSH DIGIDOCPP_WARNING_DISABLE_CLANG("-Wnull-conversion") DIGIDOCPP_WARNING_DISABLE_MSVC(4005) @@ -186,6 +188,7 @@ void digidoc::terminate() } catch (...) { // Don't throw on terminate } + xmlCleanupParser(); m_createList.clear(); m_openList.clear(); m_appName.clear(); diff --git a/src/XMLDocument.h b/src/XMLDocument.h new file mode 100644 index 000000000..cd02f4f82 --- /dev/null +++ b/src/XMLDocument.h @@ -0,0 +1,242 @@ +/* + * libdigidocpp + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#pragma once + +#include "util/log.h" + +#include +#include + +#include + +namespace digidoc { + +template struct unique_xml; +template +struct unique_xml +{ + using type = std::unique_ptr; +}; + +template +using unique_xml_t = typename unique_xml::type; + +template +constexpr std::unique_ptr make_unique_ptr(T *p, D d) noexcept +{ + return {p, d}; +} + +template +struct XMLElem +{ + using value_type = T; + using pointer = value_type*; + using sv = std::string_view; + using pcxmlChar = const xmlChar *; + + template + constexpr static C find(C n, xmlElementType type) noexcept + { + for(; n; n = n->next) + if(n->type == type) + return n; + return {}; + } + + constexpr static sv to_string_view(const xmlChar *str) noexcept + { + return str ? sv(sv::const_pointer(str)) : sv(); + } + + constexpr sv name() const noexcept + { + return to_string_view(d ? d->name : nullptr); + } + + constexpr sv ns() const noexcept + { + return to_string_view(d && d->ns ? d->ns->href : nullptr); + } + + constexpr operator bool() const noexcept + { + return bool(d); + } + + constexpr auto& operator++() noexcept + { + d = d ? find(d->next, d->type) : nullptr; + return *this; + } + + constexpr operator sv() const noexcept + { + auto text = find(d ? d->children : nullptr, XML_TEXT_NODE); + return to_string_view(text ? text->content : nullptr); + } + + pointer d{}; +}; + +struct XMLNode: public XMLElem +{ + struct Name_NS { + sv name, ns; + }; + + struct iterator: XMLElem + { + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + + constexpr XMLNode operator*() const noexcept { return {d}; } + }; + + constexpr iterator begin() const noexcept + { + return {find(d ? d->children : nullptr, XML_ELEMENT_NODE)}; + } + + constexpr iterator end() const noexcept + { + return {}; + } + + XMLNode addChild(sv name, sv ns = {}) const noexcept + { + return {xmlNewChild(d, searchNS(ns), pcxmlChar(name.data()), nullptr)}; + } + + xmlNsPtr addNS(sv href, sv prefix = {}) const noexcept + { + return xmlNewNs(d, pcxmlChar(href.data()), prefix.empty() ? nullptr : pcxmlChar(prefix.data())); + } + + xmlNsPtr searchNS(sv ns) const noexcept + { + return xmlSearchNsByHref(nullptr, d, ns.empty() ? nullptr : pcxmlChar(ns.data())); + } + + constexpr sv property(sv name, sv ns = {}) const noexcept + { + for(XMLElem a{d ? d->properties : nullptr}; a; ++a) + { + if(a.name() == name && a.ns() == ns) + return a; + } + return {}; + } + + void setProperty(sv name, sv value, xmlNsPtr ns = {}) const noexcept + { + xmlSetNsProp(d, ns, pcxmlChar(name.data()), pcxmlChar(value.data())); + } + + static iterator erase(iterator pos) noexcept + { + iterator next = pos; + ++next; + xmlUnlinkNode(pos.d); + xmlFreeNode(pos.d); + return next; + } + + XMLNode& operator=(sv text) noexcept + { + if(!d) + return *this; + xmlChar *content = xmlEncodeSpecialChars(d->doc, pcxmlChar(text.data())); + xmlNodeSetContent(d, content); + xmlFree(content); + return *this; + } +}; + +struct XMLDocument: public unique_xml_t, public XMLNode +{ + using XMLNode::operator bool; + + XMLDocument(element_type *ptr, std::string_view _name = {}, std::string_view _ns = {}) noexcept + : std::unique_ptr(ptr, xmlFreeDoc) + , XMLNode{xmlDocGetRootElement(get())} + { + if(d && !_name.empty() && _name != name() && !_ns.empty() && _ns != ns()) + d = {}; + } + + XMLDocument(std::string_view path, std::string_view name = {}) noexcept + : XMLDocument(xmlParseFile(path.data()), name) + {} + + static XMLDocument create(std::string_view name = {}, std::string_view href = {}, std::string_view prefix = {}) noexcept + { + XMLDocument doc(xmlNewDoc(nullptr)); + if(!name.empty()) + { + doc.d = xmlNewNode(nullptr, pcxmlChar(name.data())); + if(!href.empty()) + xmlSetNs(doc.d, doc.addNS(href, prefix)); + xmlDocSetRootElement(doc.get(), doc.d); + } + return doc; + } + + bool save(std::string_view path) const noexcept + { + return xmlSaveFormatFileEnc(path.data(), get(), "UTF-8", 1) > 0; + } + + bool validateSchema(const std::string &schemaPath) const noexcept + { + auto parser = make_unique_ptr(xmlSchemaNewParserCtxt(schemaPath.c_str()), xmlSchemaFreeParserCtxt); + if(!parser) + return false; + xmlSchemaSetParserErrors(parser.get(), schemaValidationError, schemaValidationWarning, nullptr); + auto schema = make_unique_ptr(xmlSchemaParse(parser.get()), xmlSchemaFree); + if(!schema) + return false; + auto validate = make_unique_ptr(xmlSchemaNewValidCtxt(schema.get()), xmlSchemaFreeValidCtxt); + if(!validate) + return false; + xmlSchemaSetValidErrors(validate.get(), schemaValidationError, schemaValidationWarning, nullptr); + return xmlSchemaValidateDoc(validate.get(), get()) == 0; + } + + static void schemaValidationError(void */*ctx*/, const char *msg, ...) noexcept + { + va_list args{}; + va_start(args, msg); + std::string m = Log::formatArgList(msg, args); + va_end(args); + ERR("Schema validation error: %s", m.c_str()); + } + + static void schemaValidationWarning(void */*ctx*/, const char *msg, ...) noexcept + { + va_list args{}; + va_start(args, msg); + std::string m = Log::formatArgList(msg, args); + va_end(args); + WARN("Schema validation warning: %s", m.c_str()); + } +}; + +} diff --git a/src/XmlConf.cpp b/src/XmlConf.cpp index bc8aba641..4735899ca 100644 --- a/src/XmlConf.cpp +++ b/src/XmlConf.cpp @@ -19,62 +19,38 @@ #include "XmlConf.h" +#include "XMLDocument.h" #include "crypto/X509Cert.h" #include "util/File.h" -#include "util/log.h" -#include "xml/conf.hxx" -#include +#include +#include using namespace std; using namespace digidoc; using namespace digidoc::util; -using namespace xercesc; -using namespace xml_schema; namespace digidoc { template -class XmlConfParam: public unique_ptr +class XmlConfParam: public optional { public: - XmlConfParam(string &&_name, A &&def = {}): name(std::move(_name)), defaultValue(std::move(def)) {} + constexpr XmlConfParam(std::string_view _name, A &&def = {}) noexcept + : name(_name), defaultValue(std::move(def)) + {} - bool setValue(const Configuration::ParamType &p, bool global) + template + constexpr void setValue(V other) { - if(p.name() != name) - return false; - if(global && p.lock().present()) locked = p.lock().get(); - if(global || !locked) - { - if constexpr(is_same::value) - operator =(p == "true"); - else if constexpr(is_integral::value) - operator =(stoi(p)); - else - operator =(p); - } - return true; - } - - XmlConfParam &operator=(const A &other) - { - unique_ptr::reset(defaultValue != other ? new A(other) : nullptr); - return *this; - } - - operator A() const - { - return value(defaultValue); - } - - A value(const A &def) const - { - return unique_ptr::get() ? *unique_ptr::get(): def; + if(defaultValue == other) + optional::reset(); + else + optional::emplace(std::forward(other)); } - const string name; + const string_view name; const A defaultValue; bool locked = false; }; @@ -82,32 +58,32 @@ class XmlConfParam: public unique_ptr class XmlConf::Private { public: - Private(const string &path = {}, string schema = {}); + Private(Conf *self, const string &path, string schema); + auto loadDoc(const string &path) const; void init(const string &path, bool global); - unique_ptr read(const string &path) const; template - void setUserConf(XmlConfParam ¶m, A &&defined, const A &value); - - XmlConfParam logLevel{"log.level", 3}; - XmlConfParam logFile{"log.file"}; - XmlConfParam digestUri{"signer.digestUri"}; - XmlConfParam signatureDigestUri{"signer.signatureDigestUri"}; - XmlConfParam PKCS11Driver{"pkcs11.driver.path"}; - XmlConfParam proxyForceSSL{"proxy.forceSSL", false}; - XmlConfParam proxyTunnelSSL{"proxy.tunnelSSL", true}; - XmlConfParam proxyHost{"proxy.host"}; - XmlConfParam proxyPort{"proxy.port"}; - XmlConfParam proxyUser{"proxy.user"}; - XmlConfParam proxyPass{"proxy.pass"}; - XmlConfParam TSUrl{"ts.url"}; - XmlConfParam TSLAutoUpdate{"tsl.autoupdate", true}; - XmlConfParam TSLCache{"tsl.cache"}; - XmlConfParam TSLOnlineDigest{"tsl.onlineDigest", true}; - XmlConfParam TSLTimeOut{"tsl.timeOut", 10}; - XmlConfParam verifyServiceUri{"verify.serivceUri"}; + void setUserConf(XmlConfParam ¶m, A value); + + XmlConfParam logLevel; + XmlConfParam logFile; + XmlConfParam digestUri; + XmlConfParam signatureDigestUri; + XmlConfParam PKCS11Driver; + XmlConfParam proxyForceSSL; + XmlConfParam proxyTunnelSSL; + XmlConfParam proxyHost; + XmlConfParam proxyPort; + XmlConfParam proxyUser; + XmlConfParam proxyPass; + XmlConfParam TSUrl; + XmlConfParam TSLAutoUpdate; + XmlConfParam TSLCache; + XmlConfParam TSLOnlineDigest; + XmlConfParam TSLTimeOut; + XmlConfParam verifyServiceUri; map ocsp; - std::set ocspTMProfiles; + set ocspTMProfiles; string SCHEMA_LOC; static const string USER_CONF_LOC; @@ -116,22 +92,26 @@ class XmlConf::Private const string XmlConf::Private::USER_CONF_LOC = File::path(File::digidocppPath(), "digidocpp.conf"); -XmlConf::Private::Private(const string &path, string schema) - : SCHEMA_LOC(std::move(schema)) +XmlConf::Private::Private(Conf *self, const string &path, string schema) + : logLevel{"log.level", self->Conf::logLevel()} + , logFile{"log.file", self->Conf::logFile()} + , digestUri{"signer.digestUri", self->Conf::digestUri()} + , signatureDigestUri{"signer.signatureDigestUri", self->Conf::digestUri()} + , PKCS11Driver{"pkcs11.driver.path", self->Conf::PKCS11Driver()} + , proxyForceSSL{"proxy.forceSSL", self->Conf::proxyForceSSL()} + , proxyTunnelSSL{"proxy.tunnelSSL", self->Conf::proxyTunnelSSL()} + , proxyHost{"proxy.host", self->Conf::proxyHost()} + , proxyPort{"proxy.port", self->Conf::proxyPort()} + , proxyUser{"proxy.user", self->Conf::proxyUser()} + , proxyPass{"proxy.pass", self->Conf::proxyPass()} + , TSUrl{"ts.url", self->Conf::TSUrl()} + , TSLAutoUpdate{"tsl.autoupdate", self->Conf::TSLAutoUpdate()} + , TSLCache{"tsl.cache", self->Conf::TSLCache()} + , TSLOnlineDigest{"tsl.onlineDigest", self->Conf::TSLOnlineDigest()} + , TSLTimeOut{"tsl.timeOut", self->Conf::TSLTimeOut()} + , verifyServiceUri{"verify.serivceUri", self->Conf::verifyServiceUri()} + , SCHEMA_LOC(std::move(schema)) { - try { - if(!XMLPlatformUtils::fgMemoryManager) - XMLPlatformUtils::Initialize(); - } - catch (const XMLException &e) { - try { - string result = xsd::cxx::xml::transcode(e.getMessage()); - THROW("Error during initialization of Xerces: %s", result.c_str()); - } catch(const xsd::cxx::xml::invalid_utf16_string & /* ex */) { - THROW("Error during initialization of Xerces"); - } - } - if(path.empty()) { init(File::path(File::confPath(), "digidocpp.conf"), true); @@ -141,81 +121,85 @@ XmlConf::Private::Private(const string &path, string schema) init(path, true); } -/** - * Load and parse xml from path. Initialize XmlConf member variables from xml. - * @param path to use for initializing conf - */ -void XmlConf::Private::init(const string& path, bool global) +auto XmlConf::Private::loadDoc(const string &path) const { - DEBUG("XmlConfPrivate::init(%s, %u)", path.c_str(), global); - unique_ptr conf = read(path); - for(const Configuration::ParamType &p: conf->param()) + LIBXML_TEST_VERSION + auto doc = XMLDocument(path, "configuration"); + if(!doc) { - if(logLevel.setValue(p, global) || - logFile.setValue(p, global) || - digestUri.setValue(p, global) || - signatureDigestUri.setValue(p, global) || - PKCS11Driver.setValue(p, global) || - proxyForceSSL.setValue(p, global) || - proxyTunnelSSL.setValue(p, global) || - proxyHost.setValue(p, global) || - proxyPort.setValue(p, global) || - proxyUser.setValue(p, global) || - proxyPass.setValue(p, global) || - TSUrl.setValue(p, global) || - TSLAutoUpdate.setValue(p, global) || - TSLCache.setValue(p, global) || - TSLOnlineDigest.setValue(p, global) || - TSLTimeOut.setValue(p, global) || - verifyServiceUri.setValue(p, global)) - continue; - if(p.name() == "ocsp.tm.profile" && global) - ocspTMProfiles.emplace(p); - else - WARN("Unknown configuration parameter %s", p.name().c_str()); + WARN("Failed to parse configuration: %s", path.c_str()); + return doc; } - - for(const Configuration::OcspType &o: conf->ocsp()) - ocsp[o.issuer()] = o; + if(!doc.validateSchema(SCHEMA_LOC)) + { + WARN("Failed to validate configuration: %s (%s)", path.c_str(), SCHEMA_LOC.c_str()); + doc.reset(); + } + return doc; } /** - * Parses xml configuration given path - * @param path to parse xml config - * @return returns parsed xml configuration + * Load and parse xml from path. Initialize XmlConf member variables from xml. + * @param path to use for initializing conf */ -unique_ptr XmlConf::Private::read(const string &path) const +void XmlConf::Private::init(const string& path, bool global) { - try + DEBUG("XmlConfPrivate::init(%s, %u)", path.c_str(), global); + if(File::fileSize(path) == 0) + return; + + auto doc = loadDoc(path); + if(!doc) + return; + for(XMLNode elem: doc) { - if(File::fileExists(path) && File::fileSize(path) > 0) + if(elem.name() == "ocsp") { - Properties props; - props.no_namespace_schema_location(SCHEMA_LOC); - return configuration(path, Flags::dont_initialize, props); + ocsp.emplace(elem.property("issuer"), elem); + continue; } + auto paramName = elem.property("name"); + string_view value = elem; + optional lock; + if(auto val = elem.property("lock"); !val.empty()) + lock = val == "true"; + auto setValue = [&](auto ¶m) { + if(paramName != param.name) + return false; + if(global && lock.has_value()) param.locked = lock.value(); + if(global || !param.locked) + { + using type = typename remove_reference_t::value_type; + if constexpr(is_same_v) + param.setValue(value == "true"); + else if constexpr(is_integral_v) + param.setValue(atoi(value.data())); + else + param.setValue(value); + } + return true; + }; + if(setValue(logLevel) || + setValue(logFile) || + setValue(digestUri) || + setValue(signatureDigestUri) || + setValue(PKCS11Driver) || + setValue(proxyForceSSL) || + setValue(proxyTunnelSSL) || + setValue(proxyHost) || + setValue(proxyPort) || + setValue(proxyUser) || + setValue(proxyPass) || + setValue(TSUrl) || + setValue(TSLAutoUpdate) || + setValue(TSLCache) || + setValue(TSLOnlineDigest) || + setValue(TSLTimeOut) || + setValue(verifyServiceUri)) + continue; + if(paramName == "ocsp.tm.profile" && global) + ocspTMProfiles.emplace(value); } - catch(const xml_schema::Exception& e) - { - WARN("Failed to parse configuration: %s (%s) - %s", - path.c_str(), SCHEMA_LOC.c_str(), e.what()); - } - catch(const xsd::cxx::xml::properties::argument & /* e */) - { - WARN("Failed to parse configuration: %s (%s)", - path.c_str(), SCHEMA_LOC.c_str()); - } - catch(const xsd::cxx::xml::invalid_utf8_string & /* e */) - { - WARN("Failed to parse configuration: %s (%s)", - path.c_str(), SCHEMA_LOC.c_str()); - } - catch(const xsd::cxx::xml::invalid_utf16_string & /* e */) - { - WARN("Failed to parse configuration: %s (%s)", - path.c_str(), SCHEMA_LOC.c_str()); - } - return make_unique(); } /** @@ -227,39 +211,44 @@ unique_ptr XmlConf::Private::read(const string &path) const * @throws Exception exception is thrown if reading, writing or creating of a user configuration file fails. */ template -void XmlConf::Private::setUserConf(XmlConfParam ¶m, A &&defined, const A &value) +void XmlConf::Private::setUserConf(XmlConfParam ¶m, A value) { if(param.locked) return; - param = value; - unique_ptr conf = read(USER_CONF_LOC); - Configuration::ParamSequence ¶mSeq = conf->param(); - for(auto it = paramSeq.begin(); it != paramSeq.end(); ++it) + param.setValue(std::forward(value)); + + auto doc = loadDoc(USER_CONF_LOC); + if(!doc) { - if(param.name == it->name()) - { - paramSeq.erase(it); - break; - } + doc = XMLDocument::create("configuration"); + doc.setProperty("noNamespaceSchemaLocation", SCHEMA_LOC, + doc.addNS("http://www.w3.org/2001/XMLSchema-instance", "xsi")); + } + + // Remove old entries + for(auto i = doc.begin(); i != doc.end();) + { + if(XMLNode n{*i}; n.name() == "param" && n.property("name") == param.name) + i = XMLNode::erase(i); + else + ++i; } - if(defined != value) //if it's a new parameter + + if(param.has_value()) { - if constexpr(is_same::value) - paramSeq.push_back(make_unique(value ? "true" : "false", param.name)); - else if constexpr(is_integral::value) - paramSeq.push_back(make_unique(to_string(value), param.name)); + XMLNode p = doc.addChild("param"); + p.setProperty("name", param.name); + if constexpr(is_same_v) + p = param.value() ? "true" : "false"; + else if constexpr(is_integral_v) + p = to_string(param.value()); else - paramSeq.push_back(make_unique(std::move(value), param.name)); + p = param.value(); } File::createDirectory(File::directory(USER_CONF_LOC)); - ofstream ofs(File::encodeName(USER_CONF_LOC)); - if (ofs.fail()) - THROW("Failed to open configuration: %s", USER_CONF_LOC.c_str()); - NamespaceInfomap map{{ - {{}, {{}, SCHEMA_LOC}} - }}; - configuration(ofs, *conf, map, "UTF-8", Flags::dont_initialize); + if(!doc.save(USER_CONF_LOC)) + ERR("Failed to save configuration: %s (%s)", USER_CONF_LOC.c_str(), SCHEMA_LOC.c_str()); } @@ -275,7 +264,7 @@ void XmlConf::Private::setUserConf(XmlConfParam ¶m, A &&defined, const A * @see digidoc::Conf */ XmlConf::XmlConf(const string &path, const string &schema) - : d(make_unique(path, schema.empty() ? File::path(xsdPath(), "conf.xsd") : schema)) + : d(make_unique(this, path, schema.empty() ? File::path(xsdPath(), "conf.xsd") : schema)) {} XmlConf::~XmlConf() = default; @@ -291,7 +280,7 @@ XmlConf* XmlConf::instance() { return dynamic_cast(Conf::instance()); * @see digidoc::ConfV2 */ XmlConfV2::XmlConfV2(const string &path, const string &schema) - : d(make_unique(path, schema.empty() ? File::path(xsdPath(), "conf.xsd") : schema)) + : d(make_unique(this, path, schema.empty() ? File::path(xsdPath(), "conf.xsd") : schema)) {} XmlConfV2::~XmlConfV2() = default; @@ -307,7 +296,7 @@ XmlConfV2* XmlConfV2::instance() { return dynamic_cast(Conf::instanc * @see digidoc::ConfV3 */ XmlConfV3::XmlConfV3(const string &path, const string &schema) - : d(make_unique(path, schema.empty() ? File::path(xsdPath(), "conf.xsd") : schema)) + : d(make_unique(this, path, schema.empty() ? File::path(xsdPath(), "conf.xsd") : schema)) {} XmlConfV3::~XmlConfV3() = default; @@ -326,7 +315,7 @@ XmlConfV3* XmlConfV3::instance() { return dynamic_cast(Conf::instanc * Initialize xml conf from path */ XmlConfV4::XmlConfV4(const string &path, const string &schema) - : d(make_unique(path, schema.empty() ? File::path(xsdPath(), "conf.xsd") : schema)) + : d(make_unique(this, path, schema.empty() ? File::path(xsdPath(), "conf.xsd") : schema)) {} XmlConfV4::~XmlConfV4() = default; @@ -344,7 +333,7 @@ XmlConfV4* XmlConfV4::instance() { return dynamic_cast(Conf::instanc * Initialize xml conf from path */ XmlConfV5::XmlConfV5(const string &path, const string &schema) - : d(make_unique(path, schema.empty() ? File::path(xsdPath(), "conf.xsd") : schema)) + : d(make_unique(this, path, schema.empty() ? File::path(xsdPath(), "conf.xsd") : schema)) {} XmlConfV5::~XmlConfV5() = default; @@ -363,7 +352,7 @@ TYPE XmlConfV4::PROP() const { return VALUE; } \ TYPE XmlConfV5::PROP() const { return VALUE; } #define GET1(TYPE, PROP) \ -GET1EX(TYPE, PROP, d->PROP.value(Conf::PROP())) +GET1EX(TYPE, PROP, d->PROP.value_or(d->PROP.defaultValue)) #define SET1EX(TYPE, SET, VALUE) \ void XmlConf::SET(TYPE value) { VALUE; } \ @@ -373,7 +362,7 @@ void XmlConfV4::SET(TYPE value) { VALUE; } \ void XmlConfV5::SET(TYPE value) { VALUE; } #define SET1(TYPE, SET, PROP) \ -SET1EX(TYPE, SET, d->setUserConf(d->PROP, Conf::PROP(), value)) +SET1EX(TYPE, SET, d->setUserConf(d->PROP, value)) #define SET1CONSTEX(TYPE, SET, VALUE) \ void XmlConf::SET(const TYPE &value) { VALUE; } \ @@ -383,7 +372,7 @@ void XmlConfV4::SET(const TYPE &value) { VALUE; } \ void XmlConfV5::SET(const TYPE &value) { VALUE; } #define SET1CONST(TYPE, SET, PROP) \ -SET1CONSTEX(TYPE, SET, d->setUserConf(d->PROP, Conf::PROP(), value)) +SET1CONSTEX(TYPE, SET, d->setUserConf(d->PROP, value)) GET1(int, logLevel) GET1(string, logFile) diff --git a/src/util/File.cpp b/src/util/File.cpp index 90c26b865..17830793f 100644 --- a/src/util/File.cpp +++ b/src/util/File.cpp @@ -149,9 +149,10 @@ bool File::fileExtension(string_view path, initializer_list list) /** * Returns file size */ -unsigned long File::fileSize(const string &path) +unsigned long File::fileSize(string_view path) noexcept { - return fs::file_size(fs::u8path(path)); + error_code ec; + return fs::file_size(fs::u8path(path), ec); } /** diff --git a/src/util/File.h b/src/util/File.h index f731b729a..254cc8a48 100644 --- a/src/util/File.h +++ b/src/util/File.h @@ -42,7 +42,7 @@ namespace digidoc static void updateModifiedTime(const std::string &path, time_t time); static bool fileExists(const std::string& path); static bool fileExtension(std::string_view path, std::initializer_list list); - static unsigned long fileSize(const std::string &path); + static unsigned long fileSize(std::string_view path) noexcept; static std::string fileName(const std::string& path); static std::string directory(const std::string& path); static std::string path(std::string dir, std::string_view relativePath); diff --git a/src/util/log.cpp b/src/util/log.cpp index e8593f812..208ab915f 100644 --- a/src/util/log.cpp +++ b/src/util/log.cpp @@ -41,7 +41,7 @@ using namespace std; */ string Log::format(const char* fmt, ...) { - va_list args; + va_list args{}; va_start(args, fmt); string s = formatArgList(fmt, args); va_end(args); @@ -58,13 +58,14 @@ string Log::format(const char* fmt, ...) */ string Log::formatArgList(const char* fmt, va_list args) { + string result; if(!fmt) - return ""; - string result(2048, 0); - int size = vsnprintf(&result[0], result.size() + 1, fmt, args); - if(size == -1) - return {}; - result.resize(size_t(size)); + return result; + result.resize(2048, 0); + if(int size = vsnprintf(result.data(), result.size() + 1, fmt, args); size != -1) + result.resize(size_t(size)); + else + result.clear(); return result; } @@ -78,31 +79,29 @@ void Log::out(LogType type, const char *file, unsigned int line, const char *for fstream f; if(!conf->logFile().empty()) { - f.open(File::encodeName(conf->logFile()).c_str(), fstream::out|fstream::app); + f.open(File::encodeName(conf->logFile()), fstream::out|fstream::app); o = &f; } - char outtime[] = "0000-00-00T00:00:00Z"; time_t t = time(nullptr); - struct tm tm {}; + tm tm {}; #ifdef _WIN32 - if(gmtime_s(&tm, &t) == 0) + gmtime_s(&tm, &t); #else - if(gmtime_r(&t, &tm) != nullptr) + gmtime_r(&t, &tm); #endif - strftime(outtime, sizeof(outtime), "%Y-%m-%dT%TZ", &tm); - *o << outtime << " "; + *o << put_time(&tm, "%Y-%m-%dT%TZ") << ' '; switch(type) { - case ErrorType: *o << "E"; break; - case WarnType: *o << "W"; break; - case InfoType: *o << "I"; break; - case DebugType: *o << "D"; break; + case ErrorType: *o << 'E'; break; + case WarnType: *o << 'W'; break; + case InfoType: *o << 'I'; break; + case DebugType: *o << 'D'; break; } - *o << " [" << File::fileName(file) << ":" << line << "] - "; + *o << " [" << File::fileName(file) << ':' << line << "] - "; - va_list args; + va_list args{}; va_start(args, format); - *o << formatArgList(format, args).c_str() << "\n"; + *o << formatArgList(format, args) << '\n'; va_end(args); } @@ -116,14 +115,14 @@ void Log::dbgPrintfMemImpl(const char *msg, const void *ptr, size_t size, const fstream f; if(!conf->logFile().empty()) { - f.open(File::encodeName(conf->logFile()).c_str(), fstream::out|fstream::app); + f.open(File::encodeName(conf->logFile()), fstream::out|fstream::app); o = &f; } const unsigned char *data = (const unsigned char*)ptr; - *o << "DEBUG [" << File::fileName(file) << ":" << line << "] - " << msg << " { "; + *o << "DEBUG [" << File::fileName(file) << ':' << line << "] - " << msg << " { "; *o << hex << uppercase << setfill('0'); for(size_t i = 0; i < size; ++i) *o << setw(2) << static_cast(data[i]) << ' '; - *o << dec << nouppercase << setfill(' ') <<"}:" << size << "\n"; + *o << dec << nouppercase << setfill(' ') << "}:" << size << '\n'; } diff --git a/src/util/log.h b/src/util/log.h index cc64ce923..cdcc17046 100644 --- a/src/util/log.h +++ b/src/util/log.h @@ -39,8 +39,6 @@ namespace digidoc static std::string format(const char *fmt, ...); static void out(LogType type, const char *file, unsigned int line, const char *format, ...); static void dbgPrintfMemImpl(const char *msg, const void *ptr, size_t size, const char *file, int line); - - private: static std::string formatArgList(const char *fmt, va_list args); }; } diff --git a/vcpkg.json b/vcpkg.json index c299785cc..2f9ddbcaa 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -3,6 +3,13 @@ "version": "3.18.0", "dependencies": [ "xml-security-c", + { + "name": "libxml2", + "features": [ + "zlib" + ], + "default-features": false + }, "zlib" ], "features": {