Skip to content

Commit

Permalink
Use libxml2 for configuration parsing
Browse files Browse the repository at this point in the history
IB-7946

Signed-off-by: Raul Metsma <raul@metsma.ee>
  • Loading branch information
metsma committed Apr 23, 2024
1 parent fb896ab commit 65cf019
Show file tree
Hide file tree
Showing 13 changed files with 451 additions and 205 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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: |
Expand Down Expand Up @@ -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: |
Expand Down Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Build-Depends:
cmake,
libxml-security-c-dev,
xsdcxx (>= 4.0) | xsd (>= 4.0),
libxml2-dev,
xxd,
doxygen,
swig,
Expand Down
1 change: 1 addition & 0 deletions libdigidocpp.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
<File Name="Xalan-C_1_12.dll" />
<File Name="XalanMessages_1_12.dll" />
<File Name="xsec_2_0.dll" />
<File Name="libxml2.dll" />
</ComponentGroup>

<ComponentGroup Id="Libraries" Source="$(var.libdigidocpp)\bin">
Expand Down
9 changes: 5 additions & 4 deletions prepare_osx_build_environment.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -429,6 +429,7 @@ case "$@" in
openssl
xalan
xml_security
libxml2
;;
*)
echo "Usage:"
Expand Down
4 changes: 2 additions & 2 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down Expand Up @@ -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
Expand All @@ -183,6 +182,7 @@ target_link_libraries(digidocpp_priv
digidocpp_util
XmlSecurityC::XmlSecurityC
ZLIB::ZLIB
LibXml2::LibXml2
$<$<C_COMPILER_ID:MSVC>:Ws2_32>
)

Expand Down
3 changes: 3 additions & 0 deletions src/Container.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
#include "util/File.h"
#include "util/log.h"

#include <libxml/parser.h>

DIGIDOCPP_WARNING_PUSH
DIGIDOCPP_WARNING_DISABLE_CLANG("-Wnull-conversion")
DIGIDOCPP_WARNING_DISABLE_MSVC(4005)
Expand Down Expand Up @@ -191,6 +193,7 @@ void digidoc::terminate()
} catch (...) {
// Don't throw on terminate
}
xmlCleanupParser();
m_createList.clear();
m_openList.clear();
m_appName.clear();
Expand Down
242 changes: 242 additions & 0 deletions src/XMLDocument.h
Original file line number Diff line number Diff line change
@@ -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 <libxml/parser.h>
#include <libxml/xmlschemas.h>

#include <memory>

namespace digidoc {

template<typename> struct unique_xml;
template<class T>
struct unique_xml<void(T *)>
{
using type = std::unique_ptr<T,void(*)(T *)>;
};

template<typename T>
using unique_xml_t = typename unique_xml<T>::type;

template<class T, typename D>
constexpr std::unique_ptr<T, D> make_unique_ptr(T *p, D d) noexcept
{
return {p, d};
}

template<class T>
struct XMLElem
{
using value_type = T;
using pointer = value_type*;
using sv = std::string_view;
using pcxmlChar = const xmlChar *;

template<class C>
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<xmlNode>
{
struct Name_NS {
sv name, ns;
};

struct iterator: XMLElem<xmlNode>
{
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<xmlAttr> 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<decltype(xmlFreeDoc)>, public XMLNode
{
using XMLNode::operator bool;

XMLDocument(element_type *ptr, std::string_view _name = {}, std::string_view _ns = {}) noexcept
: std::unique_ptr<element_type, deleter_type>(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());
}
};

}
Loading

0 comments on commit 65cf019

Please sign in to comment.