]> git.ipfire.org Git - thirdparty/FORT-validator.git/commitdiff
Wednesday
authorAlberto Leiva Popper <ydahhrk@gmail.com>
Thu, 11 Jul 2024 16:27:52 +0000 (10:27 -0600)
committerAlberto Leiva Popper <ydahhrk@gmail.com>
Thu, 11 Jul 2024 16:48:24 +0000 (10:48 -0600)
Oh dear. I misunderstood RFC 9589. I'm going to have to restore RRDP
caging.

Aaaaaaaaaahhhhhhhhhhhhhhh. Think I'm going to have to out-of-scope 9589
for this release.

Unstable, squash later.

14 files changed:
src/cache/cachent.c
src/cache/cachent.h
src/cache/local_cache.c
src/cache/local_cache.h
src/object/certificate.c
src/types/str.h
src/types/url.c [new file with mode: 0644]
src/types/url.h [new file with mode: 0644]
test/Makefile.am
test/cache/cachent_test.c
test/cache/common.c
test/cache/common.h
test/cache/local_cache_test.c
test/types/url_test.c [new file with mode: 0644]

index 161ca83efbe40e422d293913c7c27841a88e348e..36a0acfecb825fef38fb976351662ec1e2e351a1 100644 (file)
@@ -4,6 +4,7 @@
 #include "config.h"
 #include "data_structure/common.h"
 #include "data_structure/path_builder.h"
+#include "types/url.h"
 
 /* @schema must contain a colon suffix, otherwise lookups won't work */
 struct cache_node *
@@ -79,56 +80,6 @@ end: pb_cleanup(&pb);
        return error;
 }
 
-static char *
-path_rewind(char const *root, char *cursor)
-{
-       for (cursor -= 2; root <= cursor; cursor--)
-               if (*cursor == '/')
-                       return cursor + 1;
-       return NULL;
-}
-
-/* Collapses '//' (after the schema), '.' and '..'. */
-static char *
-normalize(char const *url)
-{
-       char *normal, *dst, *root;
-       struct tokenizer tkn;
-
-       if (strncmp(url, "rsync://", RPKI_SCHEMA_LEN) &&
-           strncmp(url, "https://", RPKI_SCHEMA_LEN))
-               return NULL;
-
-       normal = pstrdup(url);
-       dst = normal + RPKI_SCHEMA_LEN;
-       root = dst - 1;
-       token_init(&tkn, url + RPKI_SCHEMA_LEN);
-
-       while (token_next(&tkn)) {
-               if (tkn.len == 1 && tkn.str[0] == '.')
-                       continue;
-               if (tkn.len == 2 && tkn.str[0] == '.' && tkn.str[1] == '.') {
-                       dst = path_rewind(root, dst);
-                       if (!dst)
-                               goto fail;
-                       continue;
-               }
-               strncpy(dst, tkn.str, tkn.len);
-               dst[tkn.len] = '/';
-               dst += tkn.len + 1;
-       }
-
-       /* Reject URL if there's nothing after the schema. Maybe unnecessary. */
-       if (dst == normal + RPKI_SCHEMA_LEN)
-               goto fail;
-
-       dst[-1] = '\0';
-       return normal;
-
-fail:  free(normal);
-       return NULL;
-}
-
 /* Get or create parent's child. */
 static struct cache_node *
 provide(struct cache_node *parent, char const *url,
@@ -143,6 +94,8 @@ provide(struct cache_node *parent, char const *url,
        child = pzalloc(sizeof(struct cache_node));
        child->url = pstrndup(url, name - url + namelen);
        child->name = child->url + (name - url);
+       if (parent->flags & RSYNC_INHERIT)
+               child->flags = RSYNC_INHERIT;
        child->parent = parent;
        HASH_ADD_KEYPTR(hh, parent->children, child->name, namelen, child);
        return child;
@@ -170,7 +123,7 @@ cachent_provide(struct cache_node *ancestor, char const *url)
        array_index i;
        struct tokenizer tkn;
 
-       normal = normalize(url);
+       normal = url_normalize(url);
        if (!normal)
                return NULL;
 
@@ -241,13 +194,18 @@ print_node(struct cache_node *node, unsigned int tabs)
        for (i = 0; i < tabs; i++)
                printf("\t");
 
-       printf("%s ", node->name);
-       printf("%s", (node->flags & CNF_RSYNC) ? "RSYNC " : "");
-       printf("%s", (node->flags & CNF_FRESH) ? "Fresh " : "");
-       printf("%s", (node->flags & CNF_TOUCHED) ? "Touched " : "");
-       printf("%s", (node->flags & CNF_VALID) ? "Valid " : "");
-       printf("%s\n", (node->flags & CNF_WITHDRAWN) ? "Withdrawn " : "");
-
+       printf("%s -- ", node->name);
+       printf("%s", (node->flags & CNF_RSYNC) ? "rsync " : "");
+       printf("%s", (node->flags & CNF_CACHED) ? "cached " : "");
+       printf("%s", (node->flags & CNF_FRESH) ? "fresh " : "");
+       printf("%s", (node->flags & CNF_CHANGED) ? "changed " : "");
+       printf("%s", (node->flags & CNF_TOUCHED) ? "touched " : "");
+       printf("%s", (node->flags & CNF_VALID) ? "valid " : "");
+       printf("%s", (node->flags & CNF_NOTIFICATION) ? "notification " : "");
+       printf("%s", (node->flags & CNF_WITHDRAWN) ? "withdrawn " : "");
+       printf(" -- %s", node->tmpdir);
+
+       printf("\n");
        HASH_ITER(hh, node->children, child, tmp)
                print_node(child, tabs + 1);
 }
index c6841c1e37a67f6fc8c841350c093c1e6ec49c1f..5f9107b78be83585e3889ab82bffb700eca99542 100644 (file)
@@ -3,18 +3,26 @@
 
 /* CACHE ENTity, CACHE elemENT, CACHE componENT */
 
+#include <stdbool.h>
 #include "data_structure/uthash.h"
 
-#define RPKI_SCHEMA_LEN 8 /* strlen("rsync://"), strlen("https://") */
-
 /* XXX rename "touched" and "validated" into "preserve"? */
 
 #define CNF_RSYNC              (1 << 0)
-/* Do we have a copy in the cache? */
+/*
+ * Do we have a (full) copy in the cache?
+ * If disabled, we don't know (because an ancestor was recursively rsync'd).
+ */
 #define CNF_CACHED             (1 << 1)
-/* Was it downloaded during the current cycle? */
+/*
+ * Was it (allegedly) downloaded during the current cycle?
+ * "Allegedly" because we might have rsync'd an ancestor.
+ */
 #define CNF_FRESH              (1 << 2)
-/* Did it change between the previous cycle and the current one? */
+/*
+ * Did it change between the previous cycle and the current one?
+ * (This is HTTP and RRDP only; rsync doesn't tell us.)
+ */
 #define CNF_CHANGED            (1 << 3)
 /* Was it read during the current cycle? */
 #define CNF_TOUCHED            (1 << 4)
 /* Withdrawn by RRDP? */
 #define CNF_WITHDRAWN          (1 << 7)
 
+/*
+ * Flags for children of downloaded rsync nodes that should be cleaned later.
+ * (FRESH prevents redownload.)
+ */
+#define RSYNC_INHERIT          (CNF_RSYNC | CNF_FRESH)
+
 // XXX rename to cache_entity or cachent
 struct cache_node {
        char const *name; /* Points to the last component of @url */
@@ -46,6 +60,8 @@ struct cache_node {
         */
        char *tmpdir;
 
+       struct cache_node *rpp; // XXX delete?
+
        /* Only if flags & CNF_NOTIFICATION. */
 //     struct cachefile_notification notif;
 
index b63dee4f5ab2f650b932c95b6917e5e1871ffbc8..52cba691fbdc3bc34cada13c6466f478ab4a4d9f 100644 (file)
@@ -25,6 +25,7 @@
 #include "http/http.h"
 #include "rsync/rsync.h"
 #include "types/str.h"
+#include "types/url.h"
 
 /* XXX force RRDP if one RPP fails to validate by rsync? */
 
@@ -510,10 +511,48 @@ find_msm(struct cache_node *root, char *path, struct cache_node **msm)
        return node;
 }
 
+/*
+ * The "rsync module" is the component immediately after the domain.
+ *
+ * get_rsync_module(rsync://a.b.c/d/e/f/potato.mft) = d
+ */
+static struct cache_node *
+get_rsync_module(struct cache_node *node)
+{
+       struct cache_node *gp; /* Grandparent */
+
+       if (!node || !node->parent || !node->parent->parent)
+               return NULL;
+
+       for (gp = node->parent->parent; gp->parent != NULL; gp = gp->parent)
+               node = node->parent;
+       return node;
+}
+
+static struct cache_node *
+get_rsync_rpp(char const *caRepository, struct cache_node *rpkiManifest)
+{
+       struct cache_node *node;
+       char *normal;
+
+       normal = url_normalize(caRepository);
+       if (normal == NULL)
+               return NULL;
+
+       for (node = rpkiManifest; node != NULL; node = node->parent)
+               if (strcmp(node->url, normal) == 0) {
+                       free(normal);
+                       return node;
+               }
+
+       free(normal);
+       return NULL;
+}
+
 static int
-dl_rsync(char const *uri, struct cache_node *node)
+dl_rsync(char const *caRepository, struct cache_node *mft)
 {
-       struct cache_node *module;
+       struct cache_node *module, *node;
        char *path;
        int error;
 
@@ -522,10 +561,14 @@ dl_rsync(char const *uri, struct cache_node *node)
                return 1;
        }
 
-       /* XXX this is probably wrong; get the real module. */
-       module = node;
-       while (module->parent != NULL)
-               module = module->parent;
+       module = get_rsync_module(mft);
+       if (module == NULL) {
+               return -EINVAL; // XXX
+       }
+       mft->rpp = get_rsync_rpp(caRepository, mft);
+       if (!mft->rpp)
+               return pr_val_err("Manifest '%s' does not seem to be inside its publication point '%s'.",
+                   mft->url, caRepository);
 
        error = cache_tmpfile(&path);
        if (error)
@@ -533,23 +576,26 @@ dl_rsync(char const *uri, struct cache_node *node)
 
        // XXX looks like the third argument is redundant now.
        error = rsync_download(module->url, path, true);
-       if (error)
-               goto cancel;
+       if (error) {
+               free(path);
+               return error;
+       }
 
-       module->flags |= CNF_FRESH;
+       module->flags |= CNF_RSYNC | CNF_CACHED | CNF_FRESH;
        module->mtim = time(NULL); // XXX catch -1
        module->tmpdir = path;
 
-       while (node != NULL) {
-               node->flags |= CNF_FRESH;
+       for (node = mft; node != module; node = node->parent) {
+               node->flags |= RSYNC_INHERIT;
                node->mtim = module->mtim;
-               node = node->parent;
+               if (mft) {
+                       node->rpp = mft->rpp;
+                       if (node == mft->rpp)
+                               mft = NULL;
+               }
        }
 
        return 0;
-
-cancel:        free(path);
-       return error;
 }
 
 static int
@@ -598,6 +644,8 @@ download(char const *uri, enum map_type type, struct cache_node *mft)
 
 /*
  * XXX review result sign
+ *
+ * uri is a rpkiNotify or a caRepository.
  */
 static int
 try_uri(struct cache_node *mft, char const *uri, enum map_type type,
@@ -615,7 +663,7 @@ try_uri(struct cache_node *mft, char const *uri, enum map_type type,
                }
        }
 
-       error = cb(mft, arg);
+       error = cb(mft->rpp, arg);
        if (error) {
                pr_val_debug("RPP validation failed.");
                return error;
@@ -642,7 +690,6 @@ cache_download_alt(struct sia_uris *uris, maps_dl_cb cb, void *arg)
 {
        struct cache_node *mft;
        int online;
-       char **uri;
        int error = 0;
 
        /* XXX mutex */
@@ -650,19 +697,19 @@ cache_download_alt(struct sia_uris *uris, maps_dl_cb cb, void *arg)
        mft = cachent_provide(cache.rsync, uris->rpkiManifest);
 
        if (mft->flags & CNF_FRESH)
-               return cb(mft, arg);
+               return cb(mft->rpp, arg); // XXX can rpp be NULL?
 
        for (online = 1; online >= 0; online--) {
-               ARRAYLIST_FOREACH(&uris->rpkiNotify, uri) {
-                       error = try_uri(mft, *uri, MAP_NOTIF, online, cb, arg);
-                       if (error <= 0)
-                               return error;
-               }
-               ARRAYLIST_FOREACH(&uris->caRepository, uri) {
-                       error = try_uri(mft, *uri, MAP_RSYNC, online, cb, arg);
+               if (uris->rpkiNotify) {
+                       error = try_uri(mft, uris->rpkiNotify, MAP_NOTIF,
+                           online, cb, arg);
                        if (error <= 0)
                                return error;
                }
+               error = try_uri(mft, uris->caRepository, MAP_RSYNC,
+                   online, cb, arg);
+               if (error <= 0)
+                       return error;
        }
 
        return error;
@@ -945,15 +992,13 @@ cache_commit(void)
 void
 sias_init(struct sia_uris *sias)
 {
-       strlist_init(&sias->caRepository);
-       strlist_init(&sias->rpkiNotify);
-       sias->rpkiManifest = NULL;
+       memset(sias, 0, sizeof(*sias));
 }
 
 void
 sias_cleanup(struct sia_uris *sias)
 {
-       strlist_cleanup(&sias->caRepository);
-       strlist_cleanup(&sias->rpkiNotify);
+       free(sias->caRepository);
+       free(sias->rpkiNotify);
        free(sias->rpkiManifest);
 }
index 02b83aa9fb524a004557b574c1913cf2fd465b04..9a899a89ee9f46907dc479ad2b6652222fb99627 100644 (file)
@@ -2,7 +2,6 @@
 #define SRC_CACHE_LOCAL_CACHE_H_
 
 #include "cache/cachent.h"
-#include "types/str.h"
 
 void cache_setup(void);                /* Init this module */
 void cache_teardown(void);     /* Destroy this module */
@@ -14,8 +13,8 @@ void cache_commit(void);      /* Finish successful validation cycle */
 /* XXX Huh. Looks like this could use a cache_rollback() */
 
 struct sia_uris {
-       struct strlist caRepository; /* rsync RPPs */
-       struct strlist rpkiNotify; /* RRDP Notifications */
+       char *caRepository;     /* RPP cage */
+       char *rpkiNotify;       /* RRDP Notification */
        char *rpkiManifest;
 };
 
@@ -31,7 +30,7 @@ void sias_cleanup(struct sia_uris *);
  *
  * XXX rename
  */
-typedef int (*maps_dl_cb)(struct cache_node *, void *);
+typedef int (*maps_dl_cb)(struct cache_node *rpp, void *arg);
 int cache_download_alt(struct sia_uris *, maps_dl_cb, void *);
 
 void cache_print(void); /* Dump cache in stdout. Recursive; tests only */
index e44d503878acf249b4b2f1cfed8d389d11237cb8..293bad1250d3031f3d967611fc93c8b39b404bec 100644 (file)
@@ -96,22 +96,6 @@ static const struct ad_metadata RPKI_MANIFEST = {
        .required = true,
 };
 
-static void
-sia_uris_init(struct sia_uris *uris)
-{
-       strlist_init(&uris->caRepository);
-       strlist_init(&uris->rpkiNotify);
-       uris->rpkiManifest = NULL;
-}
-
-static void
-sia_uris_cleanup(struct sia_uris *uris)
-{
-       strlist_cleanup(&uris->caRepository);
-       strlist_cleanup(&uris->rpkiNotify);
-       free(uris->rpkiManifest);
-}
-
 static void
 debug_serial_number(BIGNUM *number)
 {
@@ -1193,23 +1177,45 @@ static void
 handle_rpkiManifest(char *uri, void *arg)
 {
        struct sia_uris *uris = arg;
-       uris->rpkiManifest = uri;
+
+       pr_val_debug("rpkiManifest: %s", uri);
+
+       if (uris->rpkiManifest != NULL) {
+               pr_val_warn("Ignoring additional rpkiManifest: %s", uri);
+               free(uri);
+       } else {
+               uris->rpkiManifest = uri;
+       }
 }
 
 static void
 handle_caRepository(char *uri, void *arg)
 {
        struct sia_uris *uris = arg;
+
        pr_val_debug("caRepository: %s", uri);
-       strlist_add(&uris->caRepository, uri);
+
+       if (uris->caRepository != NULL) {
+               pr_val_warn("Ignoring additional caRepository: %s", uri);
+               free(uri);
+       } else {
+               uris->caRepository = uri;
+       }
 }
 
 static void
 handle_rpkiNotify(char *uri, void *arg)
 {
        struct sia_uris *uris = arg;
+
        pr_val_debug("rpkiNotify: %s", uri);
-       strlist_add(&uris->rpkiNotify, uri);
+
+       if (uris->rpkiNotify != NULL) {
+               pr_val_warn("Ignoring additional rpkiNotify: %s", uri);
+               free(uri);
+       } else {
+               uris->rpkiNotify = uri;
+       }
 }
 
 static void
@@ -1217,6 +1223,7 @@ handle_signedObject(char *uri, void *arg)
 {
        struct certificate_refs *refs = arg;
        pr_val_debug("signedObject: %s", uri);
+       // XXX Maybe it's time to review this API.
        refs->signedObject = uri;
 }
 
@@ -1799,15 +1806,6 @@ certificate_validate_aia(char const *caIssuers, X509 *cert)
        return 0;
 }
 
-static int
-download_rpp(struct sia_uris *uris)
-{
-       if (uris->caRepository.len == 0 && uris->rpkiNotify.len == 0)
-               return pr_val_err("SIA lacks both caRepository and rpkiNotify.");
-
-       return cache_download_alt(uris, MAP_NOTIF, NULL, NULL);
-}
-
 /** Boilerplate code for CA certificate validation and recursive traversal. */
 int
 certificate_traverse(struct rpp *rpp_parent, struct cache_mapping *cert_map)
@@ -1883,7 +1881,7 @@ certificate_traverse(struct rpp *rpp_parent, struct cache_mapping *cert_map)
        if (error)
                goto revert_uris;
 
-       error = download_rpp(&sia_uris);
+       error = cache_download_alt(&sia_uris, MAP_NOTIF, NULL, NULL);
        if (error)
                goto revert_uris;
 
@@ -1904,7 +1902,7 @@ certificate_traverse(struct rpp *rpp_parent, struct cache_mapping *cert_map)
        rpp_refput(pp);
 
 revert_uris:
-       sia_uris_cleanup(&sia_uris);
+       sias_cleanup(&sia_uris);
 revert_cert:
        if (cert != NULL)
                X509_free(cert);
index b49c3e8a2bd257577cb0a0d04b14453a7f1e1dba..0de5bca8874b7383048d49d0935073efee3015e6 100644 (file)
@@ -3,6 +3,7 @@
 
 #include "data_structure/array_list.h"
 
+/* XXX delete? */
 DEFINE_ARRAY_LIST_STRUCT(strlist, char *);
 
 void strlist_init(struct strlist *);
diff --git a/src/types/url.c b/src/types/url.c
new file mode 100644 (file)
index 0000000..9b0162e
--- /dev/null
@@ -0,0 +1,54 @@
+#include "types/url.h"
+
+#include "alloc.h"
+#include "data_structure/path_builder.h"
+
+static char *
+path_rewind(char const *root, char *cursor)
+{
+       for (cursor -= 2; root <= cursor; cursor--)
+               if (*cursor == '/')
+                       return cursor + 1;
+       return NULL;
+}
+
+/* Collapses '//' (after the schema), '.' and '..'. */
+char *
+url_normalize(char const *url)
+{
+       char *normal, *dst, *root;
+       struct tokenizer tkn;
+
+       if (strncmp(url, "rsync://", RPKI_SCHEMA_LEN) &&
+           strncmp(url, "https://", RPKI_SCHEMA_LEN))
+               return NULL;
+
+       normal = pstrdup(url);
+       dst = normal + RPKI_SCHEMA_LEN;
+       root = dst - 1;
+       token_init(&tkn, url + RPKI_SCHEMA_LEN);
+
+       while (token_next(&tkn)) {
+               if (tkn.len == 1 && tkn.str[0] == '.')
+                       continue;
+               if (tkn.len == 2 && tkn.str[0] == '.' && tkn.str[1] == '.') {
+                       dst = path_rewind(root, dst);
+                       if (!dst)
+                               goto fail;
+                       continue;
+               }
+               strncpy(dst, tkn.str, tkn.len);
+               dst[tkn.len] = '/';
+               dst += tkn.len + 1;
+       }
+
+       /* Reject URL if there's nothing after the schema. Maybe unnecessary. */
+       if (dst == normal + RPKI_SCHEMA_LEN)
+               goto fail;
+
+       dst[-1] = '\0';
+       return normal;
+
+fail:  free(normal);
+       return NULL;
+}
diff --git a/src/types/url.h b/src/types/url.h
new file mode 100644 (file)
index 0000000..4560718
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef SRC_TYPES_URL_H_
+#define SRC_TYPES_URL_H_
+
+#define RPKI_SCHEMA_LEN 8 /* strlen("rsync://"), strlen("https://") */
+
+char *url_normalize(char const *);
+
+#endif /* SRC_TYPES_URL_H_ */
index 79c88a47461c946ea06f79d3977dde0a0ef9630a..a0ff28aa5e9a32f6483a615fef60ac4030a0b276 100644 (file)
@@ -22,10 +22,14 @@ AM_CFLAGS += -I../src -DUNIT_TESTING ${CHECK_CFLAGS} ${XML2_CFLAGS} ${JANSSON_CF
 # target.
 MY_LDADD = ${CHECK_LIBS} ${JANSSON_LIBS}
 
-check_PROGRAMS  = cachent.test
+check_PROGRAMS  = url.test
+#check_PROGRAMS += cachent.test
 check_PROGRAMS += cache.test
 TESTS = ${check_PROGRAMS}
 
+url_test_SOURCES = types/url_test.c
+url_test_LDADD = ${MY_LDADD} ${JANSSON_LIBS}
+
 cache_test_SOURCES = cache/local_cache_test.c
 cache_test_LDADD = ${MY_LDADD} ${JANSSON_LIBS}
 
index bec2105441c3b10e8be41692ab2e624f019a6554..17df8b42244e33fcda7bb7f95a1121992be6f428 100644 (file)
@@ -5,6 +5,7 @@
 #include "cache/common.c"
 #include "data_structure/path_builder.c"
 #include "mock.c"
+#include "types/url.c"
 
 static char deleted[16][5];
 static unsigned int dn;
@@ -222,33 +223,6 @@ START_TEST(test_traverse)
 }
 END_TEST
 
-#define TEST_NORMALIZE(dirty, clean)                                   \
-       normal = normalize(dirty);                                      \
-       ck_assert_str_eq(clean, normal);                                \
-       free(normal)
-
-START_TEST(test_normalize)
-{
-       char *normal;
-
-       TEST_NORMALIZE("rsync://a.b.c", "rsync://a.b.c");
-       TEST_NORMALIZE("rsync://a.b.c/", "rsync://a.b.c");
-       TEST_NORMALIZE("rsync://a.b.c//////", "rsync://a.b.c");
-       TEST_NORMALIZE("rsync://a.b.c/d/e", "rsync://a.b.c/d/e");
-       TEST_NORMALIZE("rsync://a.b.c/d/e/.", "rsync://a.b.c/d/e");
-       TEST_NORMALIZE("rsync://a.b.c/d/e/.", "rsync://a.b.c/d/e");
-       TEST_NORMALIZE("rsync://a.b.c/d/./e/.", "rsync://a.b.c/d/e");
-       TEST_NORMALIZE("rsync://a.b.c/d/../d/../d/e/", "rsync://a.b.c/d/e");
-       TEST_NORMALIZE("rsync://a.b.c/../x/y/z", "rsync://x/y/z");
-       TEST_NORMALIZE("rsync://x//y/z/../../../m/./n/o", "rsync://m/n/o");
-       ck_assert_ptr_eq(NULL, normalize("rsync://"));
-       ck_assert_ptr_eq(NULL, normalize("rsync://.."));
-       ck_assert_ptr_eq(NULL, normalize("rsync://a.b.c/.."));
-       ck_assert_ptr_eq(NULL, normalize("rsync://a.b.c/d/e/../../.."));
-       ck_assert_ptr_eq(NULL, normalize("abcde://a.b.c/d"));
-}
-END_TEST
-
 START_TEST(test_provide)
 {
        struct cache_node *rsync, *abc, *d, *e, *f, *g, *h, *ee;
@@ -342,7 +316,6 @@ static Suite *thread_pool_suite(void)
        tcase_add_test(traverses, test_traverse);
 
        provide = tcase_create("provide");
-       tcase_add_test(provide, test_normalize);
        tcase_add_test(provide, test_provide);
 
        suite = suite_create("cachent");
index fac9b2cdc82bbb39d29efe2d206f43a01e3ce165..ebb67dc63e3b85050d25f474d2e2a810d938874a 100644 (file)
@@ -5,7 +5,7 @@
 #include "data_structure/uthash.h"
 
 struct cache_node *
-node(char const *url, int flags, ...)
+node(char const *url, int flags, char const *tmpdir, ...)
 {
        struct cache_node *result;
        struct cache_node *child;
@@ -17,8 +17,9 @@ node(char const *url, int flags, ...)
        slash = strrchr(url, '/');
        result->name = slash ? (slash + 1) : result->url;
        result->flags = flags;
+       result->tmpdir = tmpdir ? pstrdup(tmpdir) : NULL;
 
-       va_start(args, flags);
+       va_start(args, tmpdir);
        while ((child = va_arg(args, struct cache_node *)) != NULL) {
                HASH_ADD_KEYPTR(hh, result->children, child->name,
                    strlen(child->name), child);
index 8708d338dae668088f1d5fdf552d19a45097868c..d8b2ec3c7fce50fa614798b6f0386f6798e9f073 100644 (file)
@@ -3,6 +3,6 @@
 
 #include "cache/cachent.h"
 
-struct cache_node *node(char const *, int , ...);
+struct cache_node *node(char const *, int , char const *, ...);
 
 #endif /* TEST_CACHE_COMMON_H_ */
index 82885f63dc1ca1aa1c46b659d2caf51cfe836a0b..5b99e27ad106d30ed018cd9b762ec4d355dbf339 100644 (file)
@@ -6,7 +6,7 @@
 #include <check.h>
 //#include <stdarg.h>
 #include <sys/queue.h>
-//
+
 #include "alloc.c"
 //#include "json_util.c"
 #include "mock.c"
@@ -15,7 +15,7 @@
 #include "cache/local_cache.c"
 #include "data_structure/path_builder.c"
 #include "types/str.c"
-//#include "types/map.c"
+#include "types/url.c"
 
 /* Mocks */
 
@@ -127,6 +127,8 @@ setup_test(void)
 static int
 okay(struct cache_node *node, void *arg)
 {
+       // XXX ensure the rsync and RRDP codes do this
+       node->flags |= CNF_VALID;
        return 0;
 }
 
@@ -136,8 +138,8 @@ run_dl_rsync(char const *caRepository, char const *rpkiManifest,
 {
        static struct sia_uris sias;
 
-       sias_init(&sias);
-       strlist_add(&sias.caRepository, pstrdup(caRepository));
+       sias.caRepository = pstrdup(caRepository);
+       sias.rpkiNotify = NULL;
        sias.rpkiManifest = pstrdup(rpkiManifest);
 
        rsync_counter = 0;
@@ -162,13 +164,17 @@ find_downloaded_path(struct cache_node *node)
 {
        struct downloaded_path *path;
 
-       SLIST_FOREACH(path, &downloaded, hook)
+       if (!node->tmpdir)
+               return NULL;
+
+       SLIST_FOREACH(path, &downloaded, hook) {
                if (strcmp(node->tmpdir, path->path) == 0) {
                        if (path->visited)
                                ck_abort_msg("Looked up twice: %s", path->path);
                        path->visited = true;
                        return path;
                }
+       }
 
        return NULL;
 }
@@ -179,14 +185,14 @@ check_path(struct cache_node *node, char const *_)
        struct downloaded_path *path;
 
        path = find_downloaded_path(node);
-       if (node->flags & CNF_CACHED) {
+       if (node->tmpdir) {
                if (path == NULL)
                        ck_abort_msg("Cached file is missing: %s",
                            node->tmpdir);
        } else {
                if (path != NULL)
                        ck_abort_msg("Cached file should not exist: %s",
-                           node->tmpdir);
+                           path->path);
        }
 
        return true;
@@ -259,7 +265,7 @@ ck_assert_cachent_eq(struct cache_node *expected, struct cache_node *actual)
 }
 
 static void
-validate_cache(struct cache_node *rsync, struct cache_node *https)
+ck_cache(struct cache_node *rsync, struct cache_node *https)
 {
        struct downloaded_path *path;
 
@@ -288,6 +294,9 @@ validate_cache(struct cache_node *rsync, struct cache_node *https)
        /* Compare expected and actual */
        ck_assert_cachent_eq(rsync, cache.rsync);
        ck_assert_cachent_eq(https, cache.https);
+
+       cachent_delete(rsync);
+       cachent_delete(https);
 }
 
 //static void
@@ -329,44 +338,83 @@ cleanup_test(void)
 
 START_TEST(test_cache_download_rsync)
 {
-       int flags = CNF_CACHED | CNF_FRESH | CNF_VALID;
+       static const int SUCCESS = CNF_RSYNC | CNF_CACHED | CNF_FRESH | CNF_VALID;
 
        setup_test();
 
        run_dl_rsync("rsync://a.b.c/d", "rsync://a.b.c/d/mft", 0, 1);
-       validate_cache(
-               node("rsync:", flags,
-                       node("rsync://a.b.c", flags,
-                               node("rsync://a.b.c/d", flags, NULL)),
+       ck_cache(
+               node("rsync:", 0, NULL,
+                       node("rsync://a.b.c", 0, NULL,
+                               node("rsync://a.b.c/d", SUCCESS, "tmp/tmp/0",
+                                       node("rsync://a.b.c/d/mft", RSYNC_INHERIT, NULL, NULL),
+                                       NULL),
+                               NULL),
+                       NULL),
+               node("https:", 0, NULL, NULL));
+
+       /* Redownload same file, nothing should happen */
+       run_dl_rsync("rsync://a.b.c/d", "rsync://a.b.c/d/mft", 0, 0);
+       ck_cache(
+               node("rsync:", 0, NULL,
+                       node("rsync://a.b.c", 0, NULL,
+                               node("rsync://a.b.c/d", SUCCESS, "tmp/tmp/0",
+                                       node("rsync://a.b.c/d/mft", RSYNC_INHERIT, NULL, NULL),
+                                       NULL),
+                               NULL),
+                       NULL),
+               node("https:", 0, NULL, NULL));
+
+       /*
+        * rsyncs are recursive, which means if we've been recently asked to
+        * download d, we needn't bother redownloading d/e.
+        */
+       run_dl_rsync("rsync://a.b.c/d/e", "rsync://a.b.c/d/e/mft", 0, 0);
+       ck_cache(
+               node("rsync:", 0, NULL,
+                       node("rsync://a.b.c", 0, NULL,
+                               node("rsync://a.b.c/d", SUCCESS, "tmp/tmp/0",
+                                       node("rsync://a.b.c/d/e", RSYNC_INHERIT, NULL,
+                                               node("rsync://a.b.c/d/e/mft", RSYNC_INHERIT, NULL, NULL),
+                                               NULL),
+                                       node("rsync://a.b.c/d/mft", RSYNC_INHERIT, NULL, NULL),
+                                       NULL),
+                               NULL),
+                       NULL),
+               node("https:", 0, NULL, NULL));
+
+       /*
+        * rsyncs get truncated, because it results in much faster
+        * synchronization in practice.
+        * This is not defined in any RFCs; it's an effective standard,
+        * and there would be consequences for violating it.
+        */
+       run_dl_rsync("rsync://x.y.z/m/n/o", "rsync://x.y.z/m/n/o/mft", 0, 1);
+       ck_cache(
+               node("rsync:", 0, NULL,
+                       node("rsync://a.b.c", 0, NULL,
+                               node("rsync://a.b.c/d", SUCCESS, "tmp/tmp/0",
+                                       node("rsync://a.b.c/d/e", RSYNC_INHERIT, NULL,
+                                               node("rsync://a.b.c/d/e/mft", RSYNC_INHERIT, NULL, NULL),
+                                               NULL),
+                                       node("rsync://a.b.c/d/mft", RSYNC_INHERIT, NULL, NULL),
+                                       NULL),
+                               NULL),
+                       node("rsync://x.y.z", 0, NULL,
+                               node("rsync://x.y.z/m", SUCCESS, "tmp/tmp/1",
+                                       node("rsync://x.y.z/m/n", RSYNC_INHERIT, NULL,
+                                               node("rsync://x.y.z/m/n/o", RSYNC_INHERIT, NULL,
+                                                       node("rsync://x.y.z/m/n/o/mft", RSYNC_INHERIT, NULL, NULL),
+                                                       NULL),
+                                               NULL),
+                                       NULL),
+                               NULL),
                        NULL),
-               node("https:", 0, NULL));
+               node("https:", 0, NULL, NULL));
 
-//     /* Redownload same file, nothing should happen */
-//     run_dl_rsync("rsync://a.b.c/d", "rsync://a.b.c/d/mft", 0, 0);
-//     validate_cache(0, NODE("rsync://a.b.c/d/", 0, 1, true), NULL);
-//
-//     /*
-//      * rsyncs are recursive, which means if we've been recently asked to
-//      * download d, we needn't bother redownloading d/e.
-//      */
-//     run_dl_rsync("rsync://a.b.c/d/e", "rsync://a.b.c/d/e/mft", 0, 0);
-//     validate_cache(0, NODE("rsync://a.b.c/d/", 0, 1, true), NULL);
-//
-//     /*
-//      * rsyncs get truncated, because it results in much faster
-//      * synchronization in practice.
-//      * This is not defined in any RFCs; it's an effective standard,
-//      * and there would be consequences for violating it.
-//      */
-//     run_dl_rsync("rsync://x.y.z/m/n/o", "rsync://x.y.z/m/n/o/mft", 0, 1);
-//     validate_cache(0,
-//         NODE("rsync://a.b.c/d/", 0, 1, true),
-//         NODE("rsync://x.y.z/m/", 0, 1, true),
-//         NULL);
-//
 //     /* Sibling */
 //     run_dl_rsync("rsync://a.b.c/e/f", "rsync://a.b.c/e/f/mft", 0, 1);
-//     validate_cache(0,
+//     ck_cache(
 //         NODE("rsync://a.b.c/d/", 0, 1, true),
 //         NODE("rsync://a.b.c/e/", 0, 1, true),
 //         NODE("rsync://x.y.z/m/", 0, 1, true),
@@ -384,7 +432,7 @@ END_TEST
 //     run_cache_download("rsync://a.b.c/d", 0, 1, 0);
 //     dl_error = true;
 //     run_cache_download("rsync://a.b.c/e", -EINVAL, 1, 0);
-//     validate_cache(0,
+//     ck_cache(
 //         NODE("rsync://a.b.c/d/", 0, 1, true),
 //         NODE("rsync://a.b.c/e/", -EINVAL, 0, false),
 //         NULL);
@@ -392,14 +440,14 @@ END_TEST
 //     /* Regardless of error, not reattempted because same iteration */
 //     dl_error = true;
 //     run_cache_download("rsync://a.b.c/e", -EINVAL, 0, 0);
-//     validate_cache(0,
+//     ck_cache(
 //         NODE("rsync://a.b.c/d/", 0, 1, true),
 //         NODE("rsync://a.b.c/e/", -EINVAL, 0, false),
 //         NULL);
 //
 //     dl_error = false;
 //     run_cache_download("rsync://a.b.c/e", -EINVAL, 0, 0);
-//     validate_cache(0,
+//     ck_cache(
 //         NODE("rsync://a.b.c/d/", 0, 1, true),
 //         NODE("rsync://a.b.c/e/", -EINVAL, 0, false),
 //         NULL);
@@ -420,7 +468,7 @@ END_TEST
 //     run_cache_download("rsync://a.b.c/d", 0, 1, 0);
 //     run_cache_download("rsync://a.b.c/e", 0, 1, 0);
 //     cache_cleanup(cache);
-//     validate_cache(0,
+//     ck_cache(
 //         NODE("rsync://a.b.c/d/", 0, 1, true),
 //         NODE("rsync://a.b.c/e/", 0, 1, true),
 //         NULL);
@@ -430,7 +478,7 @@ END_TEST
 //     run_cache_download("rsync://a.b.c/d", 0, 1, 0);
 //     run_cache_download("rsync://a.b.c/e", 0, 1, 0);
 //     cache_cleanup(cache);
-//     validate_cache(0,
+//     ck_cache(
 //             NODE("rsync://a.b.c/d/", 0, 1, true),
 //             NODE("rsync://a.b.c/e/", 0, 1, true),
 //             NULL);
@@ -441,7 +489,7 @@ END_TEST
 //     run_cache_download("rsync://a.b.c/e", 0, 1, 0);
 //     run_cache_download("rsync://a.b.c/f", 0, 1, 0);
 //     cache_cleanup(cache);
-//     validate_cache(0,
+//     ck_cache(
 //             NODE("rsync://a.b.c/d/", 0, 1, true),
 //             NODE("rsync://a.b.c/e/", 0, 1, true),
 //             NODE("rsync://a.b.c/f/", 0, 1, true),
@@ -450,7 +498,7 @@ END_TEST
 //     /* Nodes don't get updated, but they're still too young. */
 //     new_iteration(false);
 //     cache_cleanup(cache);
-//     validate_cache(0,
+//     ck_cache(
 //             NODE("rsync://a.b.c/d/", 0, 1, true),
 //             NODE("rsync://a.b.c/e/", 0, 1, true),
 //             NODE("rsync://a.b.c/f/", 0, 1, true),
@@ -460,42 +508,42 @@ END_TEST
 //     new_iteration(true);
 //     run_cache_download("rsync://a.b.c/d", 0, 1, 0);
 //     cache_cleanup(cache);
-//     validate_cache(0, NODE("rsync://a.b.c/d/", 0, 1, true), NULL);
+//     ck_cache(NODE("rsync://a.b.c/d/", 0, 1, true), NULL);
 //
 //     /* Remove old branch and add sibling at the same time */
 //     new_iteration(true);
 //     run_cache_download("rsync://a.b.c/e", 0, 1, 0);
 //     cache_cleanup(cache);
-//     validate_cache(0, NODE("rsync://a.b.c/e/", 0, 1, true), NULL);
+//     ck_cache(NODE("rsync://a.b.c/e/", 0, 1, true), NULL);
 //
 //     /* Try child */
 //     new_iteration(true);
 //     run_cache_download("rsync://a.b.c/e/f/g", 0, 1, 0);
 //     cache_cleanup(cache);
-//     validate_cache(0, NODE("rsync://a.b.c/e/", 0, 1, true), NULL);
+//     ck_cache(NODE("rsync://a.b.c/e/", 0, 1, true), NULL);
 //
 //     /* Parent again */
 //     new_iteration(true);
 //     run_cache_download("rsync://a.b.c/e", 0, 1, 0);
 //     cache_cleanup(cache);
-//     validate_cache(0, NODE("rsync://a.b.c/e/", 0, 1, true), NULL);
+//     ck_cache(NODE("rsync://a.b.c/e/", 0, 1, true), NULL);
 //
 //     /* Empty the tree */
 //     new_iteration(true);
 //     cache_cleanup(cache);
-//     validate_cache(0, NULL);
+//     ck_cache(NULL);
 //
 //     /* Node exists, but file doesn't */
 //     new_iteration(true);
 //     run_cache_download("rsync://a.b.c/e", 0, 1, 0);
 //     run_cache_download("rsync://a.b.c/f", 0, 1, 0);
-//     validate_cache(0,
+//     ck_cache(
 //             NODE("rsync://a.b.c/e/", 0, 1, true),
 //             NODE("rsync://a.b.c/f/", 0, 1, true),
 //             NULL);
 //     ck_assert_int_eq(0, file_rm_rf("tmp/rsync/a.b.c/f"));
 //     cache_cleanup(cache);
-//     validate_cache(0, NODE("rsync://a.b.c/e/", 0, 1, true), NULL);
+//     ck_cache(NODE("rsync://a.b.c/e/", 0, 1, true), NULL);
 //
 //     cleanup_test();
 //}
@@ -510,14 +558,14 @@ END_TEST
 //     run_cache_download("rsync://a.b.c/d", 0, 1, 0);
 //     dl_error = true;
 //     run_cache_download("rsync://a.b.c/e", -EINVAL, 1, 0);
-//     validate_cache(0,
+//     ck_cache(
 //             NODE("rsync://a.b.c/d/", 0, 1, true),
 //             NODE("rsync://a.b.c/e/", -EINVAL, 0, false),
 //             NULL);
 //
 //     /* Node gets deleted because cached file doesn't exist */
 //     cache_cleanup(cache);
-//     validate_cache(0, NODE("rsync://a.b.c/d/", 0, 1, true), NULL);
+//     ck_cache(NODE("rsync://a.b.c/d/", 0, 1, true), NULL);
 //
 //     /*
 //      * Node and file do not get deleted, because the failure is still not
@@ -527,12 +575,12 @@ END_TEST
 //     new_iteration(false);
 //     dl_error = true;
 //     run_cache_download("rsync://a.b.c/d", -EINVAL, 1, 0);
-//     validate_cache(0, NODE("rsync://a.b.c/d/", -EINVAL, 1, true), NULL);
+//     ck_cache(NODE("rsync://a.b.c/d/", -EINVAL, 1, true), NULL);
 //
 //     /* Error is old; gets deleted */
 //     new_iteration(true);
 //     cache_cleanup(cache);
-//     validate_cache(0, NULL);
+//     ck_cache(NULL);
 //
 //     cleanup_test();
 //}
@@ -544,18 +592,18 @@ END_TEST
 //
 //     /* Download *file* e. */
 //     run_cache_download("https://a.b.c/d/e", 0, 0, 1);
-//     validate_cache(0, NODE("https://a.b.c/d/e", 0, 1, 1), NULL);
+//     ck_cache(NODE("https://a.b.c/d/e", 0, 1, 1), NULL);
 //
 //     /* Download something else 1 */
 //     run_cache_download("https://a.b.c/e", 0, 0, 1);
-//     validate_cache(0,
+//     ck_cache(
 //         NODE("https://a.b.c/d/e", 0, 1, 1),
 //         NODE("https://a.b.c/e", 0, 1, 1),
 //         NULL);
 //
 //     /* Download something else 2 */
 //     run_cache_download("https://x.y.z/e", 0, 0, 1);
-//     validate_cache(0,
+//     ck_cache(
 //         NODE("https://a.b.c/d/e", 0, 1, 1),
 //         NODE("https://a.b.c/e", 0, 1, 1),
 //         NODE("https://x.y.z/e", 0, 1, 1),
@@ -573,7 +621,7 @@ END_TEST
 //     run_cache_download("https://a.b.c/d", 0, 0, 1);
 //     dl_error = true;
 //     run_cache_download("https://a.b.c/e", -EINVAL, 0, 1);
-//     validate_cache(0,
+//     ck_cache(
 //         NODE("https://a.b.c/d", 0, 1, 1),
 //         NODE("https://a.b.c/e", -EINVAL, 0, 0),
 //         NULL);
@@ -583,7 +631,7 @@ END_TEST
 //     run_cache_download("https://a.b.c/d", 0, 0, 0);
 //     dl_error = false;
 //     run_cache_download("https://a.b.c/e", -EINVAL, 0, 0);
-//     validate_cache(0,
+//     ck_cache(
 //         NODE("https://a.b.c/d", 0, 1, 1),
 //         NODE("https://a.b.c/e", -EINVAL, 0, 0),
 //         NULL);
@@ -601,7 +649,7 @@ END_TEST
 //     run_cache_download("https://a.b.c/d", 0, 0, 1);
 //     run_cache_download("https://a.b.c/e", 0, 0, 1);
 //     cache_cleanup(cache);
-//     validate_cache(0,
+//     ck_cache(
 //             NODE("https://a.b.c/d", 0, 1, 1),
 //             NODE("https://a.b.c/e", 0, 1, 1),
 //             NULL);
@@ -610,19 +658,19 @@ END_TEST
 //     new_iteration(true);
 //     run_cache_download("https://a.b.c/d", 0, 0, 1);
 //     cache_cleanup(cache);
-//     validate_cache(0, NODE("https://a.b.c/d", 0, 1, 1), NULL);
+//     ck_cache(NODE("https://a.b.c/d", 0, 1, 1), NULL);
 //
 //     /* Change the one branch */
 //     new_iteration(true);
 //     run_cache_download("https://a.b.c/e", 0, 0, 1);
 //     cache_cleanup(cache);
-//     validate_cache(0, NODE("https://a.b.c/e", 0, 1, 1), NULL);
+//     ck_cache(NODE("https://a.b.c/e", 0, 1, 1), NULL);
 //
 //     /* Add a child to the same branch, do not update the old one */
 //     new_iteration(true);
 //     run_cache_download("https://a.b.c/e/f/g", 0, 0, 1);
 //     cache_cleanup(cache);
-//     validate_cache(0,
+//     ck_cache(
 //             NODE("https://a.b.c/e/f/g", 0, 1, 1), NULL);
 //
 //     /*
@@ -632,30 +680,30 @@ END_TEST
 //     new_iteration(true);
 //     run_cache_download("https://a.b.c/e/f", 0, 0, 1);
 //     cache_cleanup(cache);
-//     validate_cache(0, NODE("https://a.b.c/e/f", 0, 1, 1), NULL);
+//     ck_cache(NODE("https://a.b.c/e/f", 0, 1, 1), NULL);
 //
 //     /* Do it again. */
 //     new_iteration(true);
 //     run_cache_download("https://a.b.c/e", 0, 0, 1);
 //     cache_cleanup(cache);
-//     validate_cache(0, NODE("https://a.b.c/e", 0, 1, 1), NULL);
+//     ck_cache(NODE("https://a.b.c/e", 0, 1, 1), NULL);
 //
 //     /* Empty the tree */
 //     new_iteration(true);
 //     cache_cleanup(cache);
-//     validate_cache(0, NULL);
+//     ck_cache(NULL);
 //
 //     /* Node exists, but file doesn't */
 //     new_iteration(true);
 //     run_cache_download("https://a.b.c/e", 0, 0, 1);
 //     run_cache_download("https://a.b.c/f/g/h", 0, 0, 1);
-//     validate_cache(0,
+//     ck_cache(
 //         NODE("https://a.b.c/e", 0, 1, 1),
 //         NODE("https://a.b.c/f/g/h", 0, 1, 1),
 //         NULL);
 //     ck_assert_int_eq(0, file_rm_rf("tmp/https/a.b.c/f/g/h"));
 //     cache_cleanup(cache);
-//     validate_cache(0, NODE("https://a.b.c/e", 0, 1, 1), NULL);
+//     ck_cache(NODE("https://a.b.c/e", 0, 1, 1), NULL);
 //
 //     cleanup_test();
 //}
@@ -670,14 +718,14 @@ END_TEST
 //     run_cache_download("https://a.b.c/d", 0, 0, 1);
 //     dl_error = true;
 //     run_cache_download("https://a.b.c/e", -EINVAL, 0, 1);
-//     validate_cache(0,
+//     ck_cache(
 //         NODE("https://a.b.c/d", 0, 1, 1),
 //         NODE("https://a.b.c/e", -EINVAL, 0, 0),
 //         NULL);
 //
 //     /* Deleted because file ENOENT. */
 //     cache_cleanup(cache);
-//     validate_cache(0,
+//     ck_cache(
 //         NODE("https://a.b.c/d", 0, 1, 1),
 //         NULL);
 //
@@ -685,17 +733,17 @@ END_TEST
 //     new_iteration(false);
 //     dl_error = true;
 //     run_cache_download("https://a.b.c/d", -EINVAL, 0, 1);
-//     validate_cache(0, NODE("https://a.b.c/d", -EINVAL, 1, 1), NULL);
+//     ck_cache(NODE("https://a.b.c/d", -EINVAL, 1, 1), NULL);
 //
 //     /* Not deleted, because not old */
 //     new_iteration(false);
 //     cache_cleanup(cache);
-//     validate_cache(0, NODE("https://a.b.c/d", -EINVAL, 1, 1), NULL);
+//     ck_cache(NODE("https://a.b.c/d", -EINVAL, 1, 1), NULL);
 //
 //     /* Become old */
 //     new_iteration(true);
 //     cache_cleanup(cache);
-//     validate_cache(0, NULL);
+//     ck_cache(NULL);
 //
 //     cleanup_test();
 //}
@@ -706,16 +754,16 @@ START_TEST(test_dots)
 //     setup_test();
 //
 //     run_cache_download("https://a.b.c/d", 0, 0, 1);
-//     validate_cache(0, NODE("https://a.b.c/d", 0, 1, 1), NULL);
+//     ck_cache(NODE("https://a.b.c/d", 0, 1, 1), NULL);
 //
 //     run_cache_download("https://a.b.c/d/.", 0, 0, 0);
-//     validate_cache(0, NODE("https://a.b.c/d", 0, 1, 1), NULL);
+//     ck_cache(NODE("https://a.b.c/d", 0, 1, 1), NULL);
 //
 //     run_cache_download("https://a.b.c/d/e/..", 0, 0, 0);
-//     validate_cache(0, NODE("https://a.b.c/d", 0, 1, 1), NULL);
+//     ck_cache(NODE("https://a.b.c/d", 0, 1, 1), NULL);
 //
 //     run_cache_download("https://a.b.c/./d/../e", 0, 0, 1);
-//     validate_cache(0,
+//     ck_cache(
 //         NODE("https://a.b.c/d", 0, 1, 1),
 //         NODE("https://a.b.c/./d/../e", 0, 1, 1),
 //         NULL);
@@ -760,7 +808,7 @@ END_TEST
 //     load_tal_json(cache);
 //     ck_assert_ptr_ne(NULL, cache->ht);
 //
-//     validate_cache(0,
+//     ck_cache(
 //         NODE("rsync://a.b.c/d", 0, 1, 0),
 //         NODE("rsync://a.b.c/e", 1, 0, 0),
 //         NODE("rsync://x.y.z/e", 0, 1, 0),
diff --git a/test/types/url_test.c b/test/types/url_test.c
new file mode 100644 (file)
index 0000000..0497cb8
--- /dev/null
@@ -0,0 +1,64 @@
+#include <check.h>
+#include <stdlib.h>
+
+#include "alloc.c"
+#include "mock.c"
+#include "data_structure/path_builder.c"
+#include "types/url.c"
+
+#define TEST_NORMALIZE(dirty, clean)                                   \
+       normal = url_normalize(dirty);                                  \
+       ck_assert_str_eq(clean, normal);                                \
+       free(normal)
+
+START_TEST(test_normalize)
+{
+       char *normal;
+
+       TEST_NORMALIZE("rsync://a.b.c", "rsync://a.b.c");
+       TEST_NORMALIZE("rsync://a.b.c/", "rsync://a.b.c");
+       TEST_NORMALIZE("rsync://a.b.c//////", "rsync://a.b.c");
+       TEST_NORMALIZE("rsync://a.b.c/d/e", "rsync://a.b.c/d/e");
+       TEST_NORMALIZE("rsync://a.b.c/d/e/.", "rsync://a.b.c/d/e");
+       TEST_NORMALIZE("rsync://a.b.c/d/e/.", "rsync://a.b.c/d/e");
+       TEST_NORMALIZE("rsync://a.b.c/d/./e/.", "rsync://a.b.c/d/e");
+       TEST_NORMALIZE("rsync://a.b.c/d/../d/../d/e/", "rsync://a.b.c/d/e");
+       TEST_NORMALIZE("rsync://a.b.c/../x/y/z", "rsync://x/y/z");
+       TEST_NORMALIZE("rsync://x//y/z/../../../m/./n/o", "rsync://m/n/o");
+       ck_assert_ptr_eq(NULL, url_normalize("rsync://"));
+       ck_assert_ptr_eq(NULL, url_normalize("rsync://.."));
+       ck_assert_ptr_eq(NULL, url_normalize("rsync://a.b.c/.."));
+       ck_assert_ptr_eq(NULL, url_normalize("rsync://a.b.c/d/e/../../.."));
+       ck_assert_ptr_eq(NULL, url_normalize("abcde://a.b.c/d"));
+}
+END_TEST
+
+static Suite *thread_pool_suite(void)
+{
+       Suite *suite;
+       TCase *normalize;
+
+       normalize = tcase_create("normalize");
+       tcase_add_test(normalize, test_normalize);
+
+       suite = suite_create("url");
+       suite_add_tcase(suite, normalize);
+
+       return suite;
+}
+
+int main(void)
+{
+       Suite *suite;
+       SRunner *runner;
+       int tests_failed;
+
+       suite = thread_pool_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;
+}