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