Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use libxml2 for configuration parsing #584

Merged
merged 1 commit into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -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)
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
3 changes: 2 additions & 1 deletion 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 Expand Up @@ -106,7 +107,7 @@
<ComponentGroup Id="Headers" Directory="INSTALLFOLDER" Subdirectory="include">
<Files Include="$(var.libdigidocpp)\include\**" />
</ComponentGroup>

<?ifdef var.docLocation ?>
<ComponentGroup Id="Documentation" Directory="INSTALLFOLDER" Subdirectory="documentation">
<Files Include="$(var.docLocation)\**" />
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 @@ -186,6 +188,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
Loading