diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ef1996eb1..d88206072 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 == 'fedora: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 fakeroot build-essential devscripts debhelper cmake xxd xsdcxx libxml-security-c-dev zlib1g-dev doxygen swig openjdk-8-jdk-headless libpython3-dev python3-distutils libboost-test-dev lintian + run: apt update -qq && apt install --no-install-recommends -y git lsb-release fakeroot build-essential devscripts debhelper cmake xxd xsdcxx libxml-security-c-dev libxml2-dev zlib1g-dev doxygen swig openjdk-8-jdk-headless libpython3-dev python3-distutils libboost-test-dev lintian - name: Checkout uses: actions/checkout@v4 with: diff --git a/CMakeLists.txt b/CMakeLists.txt index f457a90a1..ff84c4349 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,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/libdigidocpp.wxs b/libdigidocpp.wxs index 8b6452d2e..d10d203e3 100644 --- a/libdigidocpp.wxs +++ b/libdigidocpp.wxs @@ -63,6 +63,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/ASiContainer.cpp b/src/ASiContainer.cpp index 6efcd1ffb..dd623ab09 100644 --- a/src/ASiContainer.cpp +++ b/src/ASiContainer.cpp @@ -165,14 +165,14 @@ unique_ptr ASiContainer::dataStream(const string &path, const ZipSeria */ void ASiContainer::addDataFile(const string &path, const string &mediaType) { - string fileName = File::fileName(path); + string fileName(File::fileName(path)); addDataFileChecks(fileName, mediaType); if(!File::fileExists(path)) THROW("Document file '%s' does not exist.", path.c_str()); ZipSerialize::Properties prop { appInfo(), File::modifiedTime(path), File::fileSize(path) }; bool useTempFile = prop.size > MAX_MEM_FILE; - zproperty(File::fileName(path), std::move(prop)); + zproperty(fileName, std::move(prop)); unique_ptr is; if(useTempFile) { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 634aa8877..3e7062466 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 + xml/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 8592a5d35..22c4a7888 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) @@ -188,6 +190,7 @@ void digidoc::terminate() XMLPlatformUtils::Terminate(); util::File::deleteTempFiles(); + xmlCleanupParser(); } catch (...) { // Don't throw on terminate } diff --git a/src/SiVaContainer.cpp b/src/SiVaContainer.cpp index 250086d50..a4eb39b54 100644 --- a/src/SiVaContainer.cpp +++ b/src/SiVaContainer.cpp @@ -149,7 +149,7 @@ SiVaContainer::SiVaContainer(const string &path, ContainerOpenCB *cb, bool useHa { DEBUG("SiVaContainer::SiVaContainer(%s, %d)", path.c_str(), useHashCode); unique_ptr ifs = make_unique(File::encodeName(d->path = path), ifstream::binary); - auto fileName = File::fileName(path); + string fileName(File::fileName(path)); istream *is = ifs.get(); if(File::fileExtension(path, {"ddoc"})) { diff --git a/src/XmlConf.cpp b/src/XmlConf.cpp index 81631a8b4..4f1f4f1bc 100644 --- a/src/XmlConf.cpp +++ b/src/XmlConf.cpp @@ -22,59 +22,36 @@ #include "crypto/X509Cert.h" #include "util/File.h" #include "util/log.h" -#include "xml/conf.hxx" +#include "xml/XMLDocument.h" -#include +#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 = {}): name(_name), defaultValue(std::move(def)) {} - bool setValue(const Configuration::ParamType &p, bool global) + template + constexpr void setValue(V other, const A &defaultValue) { - 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 std::string_view name; const A defaultValue; bool locked = false; }; @@ -82,10 +59,10 @@ class XmlConfParam: public unique_ptr class XmlConf::Private { public: - Private(const string &path = {}, string schema = {}); + Private(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); @@ -107,7 +84,7 @@ class XmlConf::Private XmlConfParam TSLTimeOut{"tsl.timeOut", 10}; XmlConfParam verifyServiceUri{"verify.serivceUri"}; map ocsp; - std::set ocspTMProfiles; + set ocspTMProfiles; string SCHEMA_LOC; static const string USER_CONF_LOC; @@ -119,19 +96,6 @@ const string XmlConf::Private::USER_CONF_LOC = File::path(File::digidocppPath(), XmlConf::Private::Private(const string &path, string schema) : 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,6 +105,27 @@ XmlConf::Private::Private(const string &path, string schema) init(path, true); } +auto XmlConf::Private::loadDoc(const string &path) const { + LIBXML_TEST_VERSION + auto doc = XMLDocument(path, "configuration"); + if(!doc) + { + WARN("Failed to parse configuration: %s", path.c_str()); + return doc; + } + auto schema_parser = make_unique_xml(xmlSchemaNewParserCtxt(SCHEMA_LOC.c_str()), xmlSchemaFreeParserCtxt); + //xmlSchemaSetParserErrors(schema_parser.get() ...) + auto schema = make_unique_xml(xmlSchemaParse(schema_parser.get()), xmlSchemaFree); + auto schema_validate = make_unique_xml(xmlSchemaNewValidCtxt(schema.get()), xmlSchemaFreeValidCtxt); + xmlSchemaSetValidErrors(schema_validate.get(), schemaValidationError, schemaValidationWarning, nullptr); + if(xmlSchemaValidateDoc(schema_validate.get(), doc.get()) != 0) + { + WARN("Failed to validate configuration: %s (%s)", path.c_str(), SCHEMA_LOC.c_str()); + doc.reset(); + } + return doc; +} + /** * Load and parse xml from path. Initialize XmlConf member variables from xml. * @param path to use for initializing conf @@ -148,74 +133,63 @@ XmlConf::Private::Private(const string &path, string schema) void XmlConf::Private::init(const string& path, bool global) { DEBUG("XmlConfPrivate::init(%s, %u)", path.c_str(), global); - unique_ptr conf = read(path); - for(const Configuration::ParamType &p: conf->param()) - { - 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()); - } - - for(const Configuration::OcspType &o: conf->ocsp()) - ocsp[o.issuer()] = o; -} + if(File::fileSize(path) == 0) + return; -/** - * Parses xml configuration given path - * @param path to parse xml config - * @return returns parsed xml configuration - */ -unique_ptr XmlConf::Private::read(const string &path) const -{ - try + auto doc = loadDoc(path); + if(!doc.root) + return; + for(XMLNode elem: doc.root) { - if(File::fileExists(path) && File::fileSize(path) > 0) + if(auto name = elem.name(); name == "param") { - Properties props; - props.no_namespace_schema_location(SCHEMA_LOC); - return configuration(path, Flags::dont_initialize, props); + auto paramName = elem.property("name"); + auto value = elem.text(); + optional lock; + if(auto val = elem.property("name"); !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", param.defaultValue); + else if constexpr(is_integral_v) + param.setValue(atoi(value.data()), param.defaultValue); + else + param.setValue(value, param.defaultValue); + } + 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(name == "ocsp.tm.profile" && global) + ocspTMProfiles.emplace(value); } + else if(name == "ocsp") + ocsp.emplace(elem.property("issuer"), elem.text()); + else + WARN("Unknown configuration parameter %.*s", int(name.size()), name.data()); } - 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(); } /** @@ -231,35 +205,47 @@ void XmlConf::Private::setUserConf(XmlConfParam ¶m, A &&defined, const A { 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(value, defined); + + auto doc = loadDoc(USER_CONF_LOC); + xmlNodePtr root = xmlDocGetRootElement(doc.get()); + if(!doc) + { + doc = XMLDocument(); + root = xmlNewNode(nullptr, "configuration"_xch); + xmlNsPtr xsi = xmlNewNs(root, "http://www.w3.org/2001/XMLSchema-instance"_xch, "xsi"_xch); + xmlSetNsProp(root, xsi, "noNamespaceSchemaLocation"_xch, pcxmlChar(SCHEMA_LOC.data())); + xmlDocSetRootElement(doc.get(), root); + } + + // Remove old entries + for(xmlNodePtr cur_node = root->children, next_node = nullptr; cur_node; cur_node = next_node) { - if(param.name == it->name()) + next_node = cur_node->next; + if(cur_node->type != XML_ELEMENT_NODE) + continue; + if(XMLNode node{cur_node}; node.name() == "param" && node.property("name") == param.name) { - paramSeq.erase(it); - break; + xmlUnlinkNode(cur_node); + xmlFreeNode(cur_node); } } - 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.root.addChild("param"); + p.setProperty("name", param.name); + if constexpr(is_same_v) + p.setText(param.value() ? "true" : "false"); + else if constexpr(is_integral_v) + p.setText(to_string(param.value())); else - paramSeq.push_back(make_unique(std::move(value), param.name)); + p.setText(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(xmlSaveFormatFileEnc(USER_CONF_LOC.c_str(), doc.get(), "UTF-8", 1) != 0) + ERR("Failed to save configuration: %s (%s)", USER_CONF_LOC.c_str(), SCHEMA_LOC.c_str()); } @@ -363,7 +349,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(Conf::PROP())) #define SET1EX(TYPE, SET, VALUE) \ void XmlConf::SET(TYPE value) { VALUE; } \ diff --git a/src/crypto/TSL.cpp b/src/crypto/TSL.cpp index 94872557c..16f13184c 100644 --- a/src/crypto/TSL.cpp +++ b/src/crypto/TSL.cpp @@ -243,7 +243,7 @@ vector TSL::parse() string cache = CONF(TSLCache); vector cert = CONF(TSLCerts); File::createDirectory(cache); - return parse(url, cert, cache, File::fileName(url)); + return parse(url, cert, cache, string(File::fileName(url))); } vector TSL::parse(const string &url, const vector &certs, diff --git a/src/digidoc-tool.cpp b/src/digidoc-tool.cpp index f2eb87d43..d837092de 100644 --- a/src/digidoc-tool.cpp +++ b/src/digidoc-tool.cpp @@ -917,7 +917,7 @@ static int tslcmd(int /*argc*/, char* /*argv*/[]) { int returnCode = EXIT_SUCCESS; string cache = CONF(TSLCache); - TSL t(cache + "/" + File::fileName(CONF(TSLUrl))); + TSL t((cache + "/").append(File::fileName(CONF(TSLUrl)))); cout << "TSL: " << t.url() << endl << " Type: " << t.type() << endl << " Territory: " << t.territory() << endl diff --git a/src/util/File.cpp b/src/util/File.cpp index 3a094562f..750fd2288 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); } /** @@ -160,7 +161,7 @@ unsigned long File::fileSize(const string &path) * @param path full path of the file. * @return returns file name from the file full path in UTF-8. */ -string File::fileName(const string& path) +string_view File::fileName(string_view path) { size_t pos = path.find_last_of("/\\"); return pos == string::npos ? path : path.substr(pos + 1); @@ -191,10 +192,10 @@ string File::frameworkResourcesPath(string_view name) * @param path full path of the file. * @return returns directory part of the file full path. */ -string File::directory(const string& path) +string_view File::directory(string_view path) { size_t pos = path.find_last_of("/\\"); - return pos == string::npos ? string() : path.substr(0, pos); + return pos == string::npos ? string_view() : path.substr(0, pos); } /** @@ -247,12 +248,12 @@ fs::path File::tempFileName() * @param path full path of the directory created. * @throws IOException exception is thrown if the directory creation failed. */ -void File::createDirectory(string path) +void File::createDirectory(string_view path) { if(path.empty()) THROW("Can not create directory with no name."); if(path.back() == '/' || path.back() == '\\') - path.pop_back(); + path.remove_suffix(1); auto _path = fs::u8path(path); #ifdef _WIN32 int result = _wmkdir(_path.c_str()); @@ -261,13 +262,13 @@ void File::createDirectory(string path) #endif if(result == 0 || errno == EEXIST) { - DEBUG("Created directory or direcotry exists '%s'", path.c_str()); + DEBUG("Created directory or direcotry exists '%.*s'", int(path.size()), path.data()); return; } if(errno != ENOENT) - THROW("Failed to create directory '%s', errno = %d", path.c_str(), errno); + THROW("Failed to create directory '%.*s', errno = %d", int(path.size()), path.data(), errno); createDirectory(directory(path)); - createDirectory(std::move(path)); + createDirectory(path); } string File::digidocppPath() @@ -294,7 +295,7 @@ string File::digidocppPath() } /** - * Constructs the full file path in the format "file:///fullpath" in URI encoding. + * Constructs the full file path in the format "file:///fullpath" in URI encoding. * * @param fullDirectory full directory path to the relativeFilePath * @param relativeFilePath file name to be appended to the full path @@ -305,9 +306,9 @@ string File::fullPathUrl(string path) #ifdef _WIN32 // Under windows replace the path delimiters replace(path.begin(), path.end(), '\\', '/'); - return "file:///" + File::toUri(path); + return "file:///" + toUri(path); #else - return "file://" + File::toUri(path); + return "file://" + toUri(path); #endif } @@ -336,9 +337,9 @@ void File::deleteTempFiles() * @param str_in the string to be converted * @return the string converted to the URI format */ -string File::toUri(const string &path) +string File::toUri(string_view path) { - static const string legal_chars = "-_.!~*'();/?:@&=+$,"; + static const string_view legal_chars = "-_.!~*'();/?:@&=+$,"; static const locale locC("C"); ostringstream dst; for(const char &i: path) @@ -369,7 +370,7 @@ string File::toUri(const string &path) */ string File::toUriPath(const string &path) { - static const string unreserved = "-._~/"; + static const string_view unreserved = "-._~/"; //static string sub-delims = "!$&'()*+,;=" static const locale locC("C"); ostringstream dst; diff --git a/src/util/File.h b/src/util/File.h index f731b729a..d7cfd6596 100644 --- a/src/util/File.h +++ b/src/util/File.h @@ -37,20 +37,19 @@ namespace digidoc public: static std::string confPath(); static std::string digidocppPath(); + static std::string_view directory(std::string_view path); static std::filesystem::path encodeName(std::string_view fileName); static time_t modifiedTime(const std::string &path); 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 std::string fileName(const std::string& path); - static std::string directory(const std::string& path); + static std::string_view fileName(std::string_view path); + static unsigned long fileSize(std::string_view path) noexcept; static std::string path(std::string dir, std::string_view relativePath); static std::string fullPathUrl(std::string path); static std::filesystem::path tempFileName(); - static void createDirectory(std::string path); + static void createDirectory(std::string_view path); static void deleteTempFiles(); - static std::string toUri(const std::string &path); static std::string toUriPath(const std::string &path); static std::string fromUriPath(const std::string &path); static std::vector hexToBin(const std::string &in); @@ -62,6 +61,7 @@ namespace digidoc #ifdef __APPLE__ static std::string frameworkResourcesPath(std::string_view name); #endif + static std::string toUri(std::string_view path); static std::stack tempFiles; }; 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/src/xml/XMLDocument.h b/src/xml/XMLDocument.h new file mode 100644 index 000000000..d431a6b3e --- /dev/null +++ b/src/xml/XMLDocument.h @@ -0,0 +1,188 @@ +/* + * 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 + +#include "../util/log.h" + +#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_xml(T *p, D d) +{ + return {p, d}; +} + +using pcxmlChar = const xmlChar *; + +inline auto operator""_xch(const char* str, std::size_t /*size*/) +{ + return pcxmlChar(str); +} + +inline void schemaValidationError(void */*ctx*/, const char *msg, ...) +{ + 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()); +} + +inline void schemaValidationWarning(void */*ctx*/, const char *msg, ...) +{ + 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()); +} + +struct XMLNode +{ + struct Iterator + { + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = xmlNode; + using pointer = value_type*; + using reference = value_type&; + + XMLNode operator*() const { return {m_ptr}; } + pointer operator->() const { return m_ptr; } + Iterator& operator++() { m_ptr = xmlNextElementSibling(m_ptr); return *this; } + //Iterator operator++(int) { Iterator tmp = *this; ++(*this); return tmp; } + constexpr friend bool operator==(Iterator lhs, Iterator rhs) { return lhs.m_ptr == rhs.m_ptr; } + constexpr friend bool operator!=(Iterator lhs, Iterator rhs) { return !(lhs == rhs); } + + pointer m_ptr; + }; + + constexpr Iterator begin() const + { + //xmlFirstElementChild + for(xmlNodePtr cur = root ? root->children : nullptr; cur; cur = cur->next) { + if (cur->type == XML_ELEMENT_NODE) + return {cur}; + } + return {}; + } + constexpr Iterator end() const { return {}; } + + XMLNode addChild(std::string_view name) const + { + return {xmlNewChild(root, nullptr, pcxmlChar(name.data()), nullptr)}; + } + + template + constexpr void enumChildren(std::string_view name, std::string_view ns, T &&f) + { + for(XMLNode c: *this) + { + if(c.name() == name && c.ns() == ns) + f(c); + } + } + constexpr XMLNode findChild(std::string_view name, std::string_view ns = {}) const + { + for(XMLNode c: *this) + { + if(c.name() == name && c.ns() == ns) + return c; + } + return {}; + } + + std::string property(std::string_view name, std::string_view ns) const + { + return to_string(xmlGetNsProp(root, pcxmlChar(name.data()), pcxmlChar(ns.data()))); + } + + std::string property(std::string_view name) const + { + return to_string(xmlGetNoNsProp(root, pcxmlChar(name.data()))); + } + + void setProperty(std::string_view name, std::string_view value) const + { + xmlSetProp(root, pcxmlChar(name.data()), pcxmlChar(value.data())); + } + + std::string text() const + { + return to_string(xmlNodeGetContent(root)); + } + + void setText(std::string_view text) const + { + xmlNodeSetContent(root, pcxmlChar(text.data())); + } + + constexpr std::string_view name() const { return to_string_view(root ? root->name : nullptr); } + constexpr std::string_view ns() const { return to_string_view(root && root->ns ? root->ns->href : nullptr); } + + constexpr static std::string_view to_string_view(const xmlChar *str) + { + using sv = std::string_view; + return str ? sv(sv::const_pointer(str)) : sv(); + } + + static std::string to_string(const xmlChar *str) + { + return str ? std::string(std::string::const_pointer(str)) : std::string(); + } + + constexpr operator bool() const { return bool(root); } + xmlNodePtr root{}; +}; + +struct XMLDocument: public unique_xml_t +{ + using Base = std::unique_ptr; + + XMLDocument(std::string_view path, std::string_view rootElem = {}) + : Base(make_unique_xml(xmlParseFile(path.data()), xmlFreeDoc)) + , root{xmlDocGetRootElement(get())} + { + if(root && !rootElem.empty() && rootElem != root.name()) + root.root = nullptr; + } + + XMLDocument() + : Base(make_unique_xml(xmlNewDoc(nullptr), xmlFreeDoc)) + {} + + XMLNode root; +}; + +} 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": {