From c09a282cc5c6633d3197a6808c971b49c16ff18e Mon Sep 17 00:00:00 2001 From: pcarana Date: Thu, 21 Nov 2019 11:12:30 -0600 Subject: [PATCH] Add XML parsing structs and methods. +Validate and parse an Update Notification file whenever is found at a certificate. Currently this does nothing else, is merely to download and validate the file; rsync is still utilized to fetch the repository data. +Add libxml2 dependency, utilized to validate XML files using relaxNG schema. +Initialize and cleanup XML parser on main thread. +Currently libxml2 doesn't seems to support Relax NG compact form, but due to its license, it's the best option to use. The RRDP schema was transformed to Relax NG schema with the tool rnc2rng. +Add basic unit test to parse an RRDP XML file. --- configure.ac | 1 + src/Makefile.am | 9 +- src/http/http.h | 1 + src/main.c | 7 + src/object/certificate.c | 7 +- src/rrdp/rrdp_objects.c | 127 ++++++++++++++++ src/rrdp/rrdp_objects.h | 82 +++++++++++ src/rrdp/rrdp_parser.c | 300 ++++++++++++++++++++++++++++++++++++++ src/rrdp/rrdp_parser.h | 9 ++ src/xml/relax_ng.c | 83 +++++++++++ src/xml/relax_ng.h | 170 +++++++++++++++++++++ test/Makefile.am | 6 +- test/xml/notification.xml | 8 + test/xml_test.c | 50 +++++++ 14 files changed, 854 insertions(+), 6 deletions(-) create mode 100644 src/rrdp/rrdp_objects.c create mode 100644 src/rrdp/rrdp_objects.h create mode 100644 src/rrdp/rrdp_parser.c create mode 100644 src/rrdp/rrdp_parser.h create mode 100644 src/xml/relax_ng.c create mode 100644 src/xml/relax_ng.h create mode 100644 test/xml/notification.xml create mode 100644 test/xml_test.c diff --git a/configure.ac b/configure.ac index 3b9ed766..220cec9c 100644 --- a/configure.ac +++ b/configure.ac @@ -41,6 +41,7 @@ AC_SEARCH_LIBS([backtrace],[execinfo],[], # typical obscure bullshit autotools reasoning) I have no idea why. PKG_CHECK_MODULES([JANSSON], [jansson]) PKG_CHECK_MODULES([CURL], [libcurl]) +PKG_CHECK_MODULES([XML2], [libxml-2.0]) PKG_CHECK_MODULES([CHECK], [check], [usetests=yes], [usetests=no]) AM_CONDITIONAL([USE_TESTS], [test "x$usetests" = "xyes"]) diff --git a/src/Makefile.am b/src/Makefile.am index 08575b1e..9bf88d94 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -79,6 +79,9 @@ fort_SOURCES += resource/ip4.h resource/ip4.c fort_SOURCES += resource/ip6.h resource/ip6.c fort_SOURCES += resource/asn.h resource/asn.c +fort_SOURCES += rrdp/rrdp_objects.h rrdp/rrdp_objects.c +fort_SOURCES += rrdp/rrdp_parser.h rrdp/rrdp_parser.c + fort_SOURCES += rsync/rsync.h rsync/rsync.c fort_SOURCES += rtr/err_pdu.c rtr/err_pdu.h @@ -100,6 +103,8 @@ fort_SOURCES += slurm/db_slurm.c slurm/db_slurm.h fort_SOURCES += slurm/slurm_loader.c slurm/slurm_loader.h fort_SOURCES += slurm/slurm_parser.c slurm/slurm_parser.h +fort_SOURCES += xml/relax_ng.c xml/relax_ng.h + # I'm placing these at the end because they rarely change, and I want warnings # to appear as soon as possible. include asn1/asn1c/Makefile.include @@ -108,9 +113,9 @@ fort_SOURCES += $(ASN_MODULE_SRCS) $(ASN_MODULE_HDRS) fort_CFLAGS = -Wall -Wno-cpp # Feel free to temporarily remove this one if you're not using gcc 7.3.0. #fort_CFLAGS += $(GCC_WARNS) -fort_CFLAGS += -std=gnu11 -O2 -g $(FORT_FLAGS) +fort_CFLAGS += -std=gnu11 -O2 -g $(FORT_FLAGS) ${XML2_CFLAGS} fort_LDFLAGS = $(LDFLAGS_DEBUG) -fort_LDADD = ${JANSSON_LIBS} ${CURL_LIBS} +fort_LDADD = ${JANSSON_LIBS} ${CURL_LIBS} ${XML2_LIBS} # I'm tired of scrolling up, but feel free to comment this out. GCC_WARNS = -fmax-errors=1 diff --git a/src/http/http.h b/src/http/http.h index e9ea1a98..25cce94f 100644 --- a/src/http/http.h +++ b/src/http/http.h @@ -4,6 +4,7 @@ #include #include "uri.h" +/* Init on the main process */ int http_init(void); void http_cleanup(void); diff --git a/src/main.c b/src/main.c index d0e4736c..cac02d9f 100644 --- a/src/main.c +++ b/src/main.c @@ -7,6 +7,7 @@ #include "http/http.h" #include "rtr/rtr.h" #include "rtr/db/vrps.h" +#include "xml/relax_ng.h" static int start_rtr_server(void) @@ -53,8 +54,14 @@ __main(int argc, char **argv) if (error) goto revert_nid; + error = relax_ng_init(); + if (error) + goto revert_http; + error = start_rtr_server(); + relax_ng_cleanup(); +revert_http: http_cleanup(); revert_nid: nid_destroy(); diff --git a/src/object/certificate.c b/src/object/certificate.c index 76677953..45707133 100644 --- a/src/object/certificate.c +++ b/src/object/certificate.c @@ -19,6 +19,7 @@ #include "object/name.h" #include "object/manifest.h" #include "object/signed_object.h" +#include "rrdp/rrdp_parser.h" #include "rsync/rsync.h" /* Just to prevent some line breaking. */ @@ -1982,13 +1983,13 @@ certificate_traverse(struct rpp *rpp_parent, struct rpki_uri *cert_uri) goto revert_refs; } - /* Currently there's support for 2 access methods */ /* FIXME (now) Download files according to the preferred method */ if (sia_uris.rpkiNotify.uri != NULL && - sia_uris.caRepository.position > sia_uris.rpkiNotify.position) + sia_uris.caRepository.position > sia_uris.rpkiNotify.position) { pr_info("Preferred Access Method: RRDP, but doing rsync anyways"); - else + rrdp_parse_notification(sia_uris.rpkiNotify.uri); + } else pr_info("Preferred Access Method: RSYNC [found RRDP? %s]", (sia_uris.rpkiNotify.uri == NULL ? "NO" : "YES")); diff --git a/src/rrdp/rrdp_objects.c b/src/rrdp/rrdp_objects.c new file mode 100644 index 00000000..05ae9805 --- /dev/null +++ b/src/rrdp/rrdp_objects.c @@ -0,0 +1,127 @@ +#include "rrdp_objects.h" + +#include "log.h" + +struct xml_source { + xmlDoc *doc; +}; + +int +global_data_init(struct global_data *data) +{ + data->session_id = NULL; + return 0; +} + +void +global_data_cleanup(struct global_data *data) +{ + free(data->session_id); +} + +int +doc_data_init(struct doc_data *data) +{ + data->hash = NULL; + data->uri = NULL; + return 0; +} + +void +doc_data_cleanup(struct doc_data *data) +{ + free(data->hash); + free(data->uri); +} + +int +xml_source_create(struct xml_source **src) +{ + struct xml_source *tmp; + + tmp = malloc(sizeof(struct xml_source)); + if (tmp == NULL) + return pr_enomem(); + + *src = tmp; + return 0; +} + +void +xml_source_destroy(struct xml_source *src){ + if (src != NULL) { + xmlFreeDoc(src->doc); + free(src); + } +} + + +int +xml_source_set(struct xml_source *src, xmlDoc *orig) +{ + xmlDoc *cpy; + + cpy = xmlCopyDoc(orig, 1); + if (cpy == NULL) + return pr_enomem(); + + src->doc = cpy; + return 0; +} + +int +update_notification_create(struct update_notification **file) +{ + struct update_notification *tmp; + + tmp = malloc(sizeof(struct update_notification)); + if (tmp == NULL) + return pr_enomem(); + + global_data_init(&tmp->gdata); + doc_data_init(&tmp->snapshot); + + SLIST_INIT(&tmp->deltas_list); + + *file = tmp; + return 0; +} + +void +update_notification_destroy(struct update_notification *file) +{ + struct deltas_head *list; + struct delta_head *head; + + list = &file->deltas_list; + while (!SLIST_EMPTY(list)) { + head = list->slh_first; + SLIST_REMOVE_HEAD(list, next); + doc_data_cleanup(&head->doc_data); + free(head); + } + doc_data_cleanup(&file->snapshot); + global_data_cleanup(&file->gdata); + + free(file); +} + +/* URI and HASH must already be allocated */ +int +update_notification_deltas_add(struct deltas_head *deltas, unsigned long serial, + char **uri, unsigned char **hash, size_t hash_len) +{ + struct delta_head *elem; + + elem = malloc(sizeof(struct delta_head)); + if (elem == NULL) + return pr_enomem(); + + elem->serial = serial; + elem->doc_data.uri = *uri; + elem->doc_data.hash = *hash; + elem->doc_data.hash_len = hash_len; + SLIST_INSERT_HEAD(deltas, elem, next); + + return 0; +} diff --git a/src/rrdp/rrdp_objects.h b/src/rrdp/rrdp_objects.h new file mode 100644 index 00000000..2090a7ac --- /dev/null +++ b/src/rrdp/rrdp_objects.h @@ -0,0 +1,82 @@ +#ifndef SRC_RRDP_RRDP_OBJECTS_H_ +#define SRC_RRDP_RRDP_OBJECTS_H_ + +#include +#include +#include + +/* Common structures */ +struct xml_source; + +struct global_data { + char *session_id; + unsigned long serial; +}; + +struct doc_data { + char *uri; + unsigned char *hash; + size_t hash_len; +}; + +struct publish { + struct doc_data doc_data; + unsigned char *content; + SLIST_ENTRY(publish) next; +}; + +struct withdraw { + struct doc_data doc_data; + SLIST_ENTRY(withdraw) next; +}; + +/* Delta file structs */ +SLIST_HEAD(publish_list, publish); +SLIST_HEAD(withdrawn_list, withdraw); + +struct delta { + struct global_data global_data; + struct publish_list publish_list; + struct withdrawn_list withdraw_list; + struct xml_source *source; +}; + +/* Snapshot file structs */ +struct snapshot { + struct global_data global_data; + struct publish_list publish_list; + struct xml_source *source; +}; + +/* Update notification file structs */ +struct delta_head { + unsigned long serial; + struct doc_data doc_data; + SLIST_ENTRY(delta_head) next; +}; + +SLIST_HEAD(deltas_head, delta_head); + +struct update_notification { + struct global_data gdata; + struct doc_data snapshot; + struct deltas_head deltas_list; +}; + +int global_data_init(struct global_data *); +void global_data_cleanup(struct global_data *); + +int doc_data_init(struct doc_data *); +void doc_data_cleanup(struct doc_data *); + +int xml_source_create(struct xml_source **); +void xml_source_destroy(struct xml_source *); +int xml_source_set(struct xml_source *, xmlDoc *); + +int update_notification_create(struct update_notification **); +void update_notification_destroy(struct update_notification *); + +int update_notification_deltas_add(struct deltas_head *, unsigned long, char **, + unsigned char **, size_t); + +#endif /* SRC_RRDP_RRDP_OBJECTS_H_ */ diff --git a/src/rrdp/rrdp_parser.c b/src/rrdp/rrdp_parser.c new file mode 100644 index 00000000..ec01475c --- /dev/null +++ b/src/rrdp/rrdp_parser.c @@ -0,0 +1,300 @@ +#include "rrdp_parser.h" + +#include +#include +#include +#include + +#include "http/http.h" +#include "xml/relax_ng.h" +#include "log.h" + +/* XML Elements */ +#define RRDP_ELEM_NOTIFICATION "notification" +#define RRDP_ELEM_SNAPSHOT "snapshot" +#define RRDP_ELEM_DELTA "delta" + +/* XML Attributes */ +#define RRDP_ATTR_VERSION "version" +#define RRDP_ATTR_SESSION_ID "session_id" +#define RRDP_ATTR_SERIAL "serial" +#define RRDP_ATTR_URI "uri" +#define RRDP_ATTR_HASH "hash" + + +static int +get_root_element(xmlDoc *doc, xmlNode **result) +{ + xmlNode *tmp; + + tmp = xmlDocGetRootElement(doc); + if (tmp == NULL) + return pr_err("XML file doesn't have a root element"); + + *result = tmp; + return 0; +} + +static int +parse_string(xmlNode *root, char const *attr, char **result) +{ + xmlChar *xml_value; + char *tmp; + + xml_value = xmlGetProp(root, BAD_CAST attr); + if (xml_value == NULL) + return pr_err("RRDP file: Couldn't find xml attribute %s", + attr); + + tmp = malloc(xmlStrlen(xml_value) + 1); + if (tmp == NULL) { + xmlFree(xml_value); + return pr_enomem(); + } + + memcpy(tmp, xml_value, xmlStrlen(xml_value)); + tmp[xmlStrlen(xml_value)] = '\0'; + xmlFree(xml_value); + + *result = tmp; + return 0; +} + +static int +parse_long(xmlNode *root, char const *attr, unsigned long *result) +{ + xmlChar *xml_value; + unsigned long tmp; + + xml_value = xmlGetProp(root, BAD_CAST attr); + if (xml_value == NULL) + return pr_err("RRDP file: Couldn't find xml attribute %s", + attr); + + errno = 0; + tmp = strtoul((char *) xml_value, NULL, 10); + if (errno) { + xmlFree(xml_value); + pr_errno(errno, "RRDP file: Invalid long value '%s'", + xml_value); + return -EINVAL; + } + xmlFree(xml_value); + + (*result) = tmp; + return 0; +} + +static int +parse_hex_string(xmlNode *root, char const *attr, unsigned char **result, + size_t *result_len) +{ + xmlChar *xml_value; + unsigned char *tmp, *ptr; + char *xml_cur; + char buf[2]; + size_t tmp_len; + + xml_value = xmlGetProp(root, BAD_CAST attr); + if (xml_value == NULL) + return pr_err("RRDP file: Couldn't find xml attribute %s", + attr); + + /* The rest of the checks are done at the schema */ + if (xmlStrlen(xml_value) % 2 != 0) { + xmlFree(xml_value); + return pr_err("RRDP file: Attribute %s isn't a valid hash", + attr); + } + + tmp_len = xmlStrlen(xml_value) / 2; + tmp = malloc(tmp_len); + if (tmp == NULL) { + xmlFree(xml_value); + return pr_enomem(); + } + memset(tmp, 0, tmp_len); + + ptr = tmp; + xml_cur = (char *) xml_value; + while (ptr - tmp < tmp_len) { + memcpy(buf, xml_cur, 2); + *ptr = strtol(buf, NULL, 16); + xml_cur+=2; + ptr++; + } + xmlFree(xml_value); + + *result = tmp; + (*result_len) = tmp_len; + return 0; +} + +/* @gdata elements are allocated */ +static int +parse_global_data(xmlNode *root, struct global_data *gdata) +{ + int error; + + error = parse_string(root, RRDP_ATTR_SESSION_ID, &gdata->session_id); + if (error) + return error; + + error = parse_long(root, RRDP_ATTR_SERIAL, &gdata->serial); + if (error) { + free(gdata->session_id); + return error; + } + + return 0; +} + +/* @data elements are allocated */ +static int +parse_doc_data(xmlNode *root, bool parse_hash, struct doc_data *data) +{ + int error; + + error = parse_string(root, RRDP_ATTR_URI, &data->uri); + if (error) + return error; + + if (!parse_hash) + return 0; + + error = parse_hex_string(root, RRDP_ATTR_HASH, &data->hash, + &data->hash_len); + if (error) { + free(data->uri); + return error; + } + + return 0; +} + +static int +parse_notification_deltas(xmlNode *root, struct deltas_head *deltas) +{ + struct delta_head delta; + int error; + + error = parse_long(root, RRDP_ATTR_SERIAL, &delta.serial); + if (error) + return error; + + error = parse_doc_data(root, true, &delta.doc_data); + if (error) + return error; + + error = update_notification_deltas_add(deltas, delta.serial, + &delta.doc_data.uri, &delta.doc_data.hash, delta.doc_data.hash_len); + if (error) { + doc_data_cleanup(&delta.doc_data); + return error; + } + + return 0; +} + +static int +parse_notification_data(xmlNode *root, struct update_notification *file) +{ + xmlNode *cur_node; + int error; + + for (cur_node = root->children; cur_node; cur_node = cur_node->next) { + if (xmlStrEqual(cur_node->name, BAD_CAST RRDP_ELEM_DELTA)) + error = parse_notification_deltas(cur_node, + &file->deltas_list); + else if (xmlStrEqual(cur_node->name, + BAD_CAST RRDP_ELEM_SNAPSHOT)) + error = parse_doc_data(cur_node, true, &file->snapshot); + + if (error) + return error; + } + + return 0; +} + +static int +parse_notification(const char *path, + struct update_notification **file) +{ + xmlDoc *doc; + xmlNode *root; + struct update_notification *tmp; + int error; + + root = NULL; + + error = relax_ng_validate(path, &doc); + if (error) + return error; + + error = update_notification_create(&tmp); + if (error) + goto release_doc; + + error = get_root_element(doc, &root); + if (error) + return error; + + /* FIXME (now) validate version, namespace, etc. */ + error = parse_global_data(root, &tmp->gdata); + if (error) + goto release_update; + + error = parse_notification_data(root, tmp); + if (error) + goto release_update; + + *file = tmp; + /* Error 0 is ok */ + goto release_doc; + +release_update: + update_notification_destroy(tmp); +release_doc: + xmlFreeDoc(doc); + return error; +} + +static size_t +write_local(unsigned char *content, size_t size, size_t nmemb, void *arg) +{ + FILE *fd = arg; + size_t read = size * nmemb; + size_t written; + + written = fwrite(content, size, nmemb, fd); + if (written != nmemb) + return -EINVAL; + + return read; +} + +/* FIXME (now) Receive an **update_notification? */ +int +rrdp_parse_notification(struct rpki_uri *uri) +{ + struct update_notification *tmp; + int error; + + if (uri == NULL || uri_is_rsync(uri)) + pr_crit("Wrong call, trying to parse a non HTTPS URI"); + + error = http_download_file(uri, write_local); + if (error) + return error; + + error = parse_notification(uri_get_local(uri), &tmp); + if (error) + return error; + + /* FIXME (now) This is just a test, must be removed */ + update_notification_destroy(tmp); + + /* result = tmp; */ + return 0; +} diff --git a/src/rrdp/rrdp_parser.h b/src/rrdp/rrdp_parser.h new file mode 100644 index 00000000..edfd591b --- /dev/null +++ b/src/rrdp/rrdp_parser.h @@ -0,0 +1,9 @@ +#ifndef SRC_RRDP_RRDP_PARSER_H_ +#define SRC_RRDP_RRDP_PARSER_H_ + +#include "rrdp/rrdp_objects.h" +#include "uri.h" + +int rrdp_parse_notification(struct rpki_uri *); + +#endif /* SRC_RRDP_RRDP_PARSER_H_ */ diff --git a/src/xml/relax_ng.c b/src/xml/relax_ng.c new file mode 100644 index 00000000..c4d29a67 --- /dev/null +++ b/src/xml/relax_ng.c @@ -0,0 +1,83 @@ +#include "relax_ng.h" + +#include +#include +#include +#include +#include + +#include "log.h" + +xmlRelaxNGPtr schema; +xmlRelaxNGValidCtxtPtr validctxt; +xmlRelaxNGParserCtxtPtr rngparser; + +/* Initialize global schema to parse RRDP files */ +int +relax_ng_init(void) +{ + int error; + + xmlInitParser(); + + rngparser = xmlRelaxNGNewMemParserCtxt(RRDP_V1_RNG, RRDP_V1_RNG_SIZE); + if (rngparser == NULL) { + error = pr_err("xmlRelaxNGNewMemParserCtxt() returned NULL"); + goto cleanup_parser; + } + + schema = xmlRelaxNGParse(rngparser); + if (schema == NULL) { + error = pr_err("xmlRelaxNGParse() returned NULL"); + goto free_parser_ctx; + } + + validctxt = xmlRelaxNGNewValidCtxt(schema); + if (validctxt == NULL) { + error = pr_err("xmlRelaxNGNewValidCtxt() returned NULL"); + goto free_schema; + } + + /* + * FIXME (now) Use xmlRelaxNGValidityErrorFunc and + * xmlRelaxNGValidityWarningFunc? + */ + return 0; +free_schema: + xmlRelaxNGFree(schema); +free_parser_ctx: + xmlRelaxNGFreeParserCtxt(rngparser); +cleanup_parser: + xmlCleanupParser(); + return error; +} + +/* + * Validate file at @path against globally loaded schema. If the file is valid, + * the result is set at @doc, returns error otherwise + */ +int +relax_ng_validate(const char *path, xmlDoc **doc) +{ + xmlDoc *tmp; + int error; + + tmp = xmlParseFile(path); + error = xmlRelaxNGValidateDoc(validctxt, tmp); + if (error) { + xmlFreeDoc(tmp); + return -EINVAL; + } + + *doc = tmp; + return 0; +} + +void +relax_ng_cleanup(void) +{ + xmlRelaxNGFreeValidCtxt(validctxt); + xmlRelaxNGFree(schema); + xmlRelaxNGFreeParserCtxt(rngparser); + xmlCleanupParser(); +} diff --git a/src/xml/relax_ng.h b/src/xml/relax_ng.h new file mode 100644 index 00000000..6bcb3073 --- /dev/null +++ b/src/xml/relax_ng.h @@ -0,0 +1,170 @@ +#ifndef SRC_XML_RELAX_NG_H_ +#define SRC_XML_RELAX_NG_H_ + +#include +#include + +/* + * Schema obtained from RFC 8182, converted using the tool rnc2rng + * (https://github.com/djc/rnc2rng) + */ +#define RRDP_V1_RNG \ +"" \ +""\ +" " \ +" " \ +" 1" \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" [\\-0-9a-fA-F]+" \ +" " \ +" " \ +" " \ +" " \ +" [0-9a-fA-F]+" \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" notification" \ +" " \ +" version" \ +" " \ +" " \ +" " \ +" session_id" \ +" " \ +" " \ +" " \ +" serial" \ +" " \ +" " \ +" " \ +" snapshot" \ +" " \ +" uri" \ +" " \ +" " \ +" " \ +" hash" \ +" " \ +" " \ +" " \ +" " \ +" " \ +" delta" \ +" " \ +" serial" \ +" " \ +" " \ +" " \ +" uri" \ +" " \ +" " \ +" " \ +" hash" \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" snapshot" \ +" " \ +" version" \ +" " \ +" " \ +" " \ +" session_id" \ +" " \ +" " \ +" " \ +" serial" \ +" " \ +" " \ +" " \ +" " \ +" publish" \ +" " \ +" uri" \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" delta" \ +" " \ +" version" \ +" " \ +" " \ +" " \ +" session_id" \ +" " \ +" " \ +" " \ +" serial" \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" publish" \ +" " \ +" uri" \ +" " \ +" " \ +" " \ +" " \ +" hash" \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" withdraw" \ +" " \ +" uri" \ +" " \ +" " \ +" " \ +" hash" \ +" " \ +" " \ +" " \ +" " \ +"" + +#define RRDP_V1_RNG_SIZE strlen(RRDP_V1_RNG) + + +int relax_ng_init(void); +int relax_ng_validate(const char *, xmlDoc **); +void relax_ng_cleanup(void); + +#endif /* SRC_XML_RELAX_NG_H_ */ diff --git a/test/Makefile.am b/test/Makefile.am index 39624601..fba74f5d 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -12,7 +12,7 @@ if USE_TESTS # _CFLAGS is not defined. # Otherwise it must be included manually: # mumble_mumble_CFLAGS = ${AM_CFLAGS} flag1 flag2 flag3 ... -AM_CFLAGS = -pedantic -Wall -std=gnu11 -I../src -DUNIT_TESTING ${CHECK_CFLAGS} +AM_CFLAGS = -pedantic -Wall -std=gnu11 -I../src -DUNIT_TESTING ${CHECK_CFLAGS} ${XML2_CFLAGS} # Reminder: As opposed to AM_CFLAGS, "AM_LDADD" is not idiomatic automake, and # autotools will even reprehend us if we declare it. Therefore, I came up with # "my" own "ldadd". Unlike AM_CFLAGS, it needs to be manually added to every @@ -29,6 +29,7 @@ check_PROGRAMS += rsync.test check_PROGRAMS += tal.test check_PROGRAMS += vcard.test check_PROGRAMS += vrps.test +check_PROGRAMS += xml.test check_PROGRAMS += rtr/pdu.test check_PROGRAMS += rtr/primitive_reader.test TESTS = ${check_PROGRAMS} @@ -63,6 +64,9 @@ vcard_test_LDADD = ${MY_LDADD} vrps_test_SOURCES = rtr/db/vrps_test.c vrps_test_LDADD = ${MY_LDADD} ${JANSSON_LIBS} +xml_test_SOURCES = xml_test.c +xml_test_LDADD = ${MY_LDADD} ${XML2_LIBS} + rtr_pdu_test_SOURCES = rtr/pdu_test.c rtr_pdu_test_LDADD = ${MY_LDADD} diff --git a/test/xml/notification.xml b/test/xml/notification.xml new file mode 100644 index 00000000..6bdf65a6 --- /dev/null +++ b/test/xml/notification.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/test/xml_test.c b/test/xml_test.c new file mode 100644 index 00000000..64d84265 --- /dev/null +++ b/test/xml_test.c @@ -0,0 +1,50 @@ +#include +#include +#include + +#include +#include "impersonator.c" +#include "log.c" +#include "xml/relax_ng.c" + +START_TEST(relax_ng_valid) +{ + char const *url = "xml/notification.xml"; + xmlDoc *doc; + + relax_ng_init(); + ck_assert_int_eq(relax_ng_validate(url, &doc), 0); + xmlFreeDoc(doc); + relax_ng_cleanup(); +} +END_TEST + +Suite *xml_load_suite(void) +{ + Suite *suite; + TCase *validate; + + validate = tcase_create("Validate"); + tcase_add_test(validate, relax_ng_valid); + + suite = suite_create("xml_test()"); + suite_add_tcase(suite, validate); + + return suite; +} + +int main(void) +{ + Suite *suite; + SRunner *runner; + int tests_failed; + + suite = xml_load_suite(); + + runner = srunner_create(suite); + srunner_run_all(runner, CK_NORMAL); + tests_failed = srunner_ntests_failed(runner); + srunner_free(runner); + + return (tests_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} -- 2.47.2