From c01511f0656d8a3dcd21185e11438afc9c6b6d47 Mon Sep 17 00:00:00 2001 From: Vivien Kraus Date: Sat, 13 May 2023 02:39:01 +0200 Subject: Add initial support for json-ld activity objects. --- configure.ac | 10 +- guix.scm | 9 +- src/libdisfluid/Makefile.am | 1 + src/libdisfluid/disfluid-activity-object.h | 100 +++++++++++++++++++ src/libdisfluid/disfluid-tests.h | 154 +++++++++++++++++++++++++++++ src/libdisfluid/main.c | 1 + 6 files changed, 270 insertions(+), 5 deletions(-) create mode 100644 src/libdisfluid/disfluid-activity-object.h diff --git a/configure.ac b/configure.ac index 2d8bec8..408eedc 100644 --- a/configure.ac +++ b/configure.ac @@ -52,6 +52,11 @@ PKG_CHECK_MODULES([GNUTLS], [gnutls], [ CFLAGS="$CFLAGS $GNUTLS_CFLAGS" LIBS="$LIBS $GNUTLS_LIBS" ], [AC_MSG_WARN([pkg-config does not know the "gnutls" module])]) +PKG_CHECK_MODULES([JANSSON], [jansson], [ + CPPFLAGS="$CPPFLAGS $JANSSON_CFLAGS" + CFLAGS="$CFLAGS $JANSSON_CFLAGS" + LIBS="$LIBS $JANSSON_LIBS" +], [AC_MSG_WARN([pkg-config does not know the "jansson" module])]) AS_IF([test "x$with_gobject" != xno], [PKG_CHECK_MODULES([GOBJECT], [gobject-2.0],, [AC_MSG_WARN([pkg-config does not know the "gobject-2.0" module])])]) AS_IF([test "x$with_gtk" != xno], @@ -59,7 +64,7 @@ AS_IF([test "x$with_gtk" != xno], PKG_CHECK_MODULES([ADW], [libadwaita-1],, [AC_MSG_WARN([pkg-config does not know the "libadwaita-1" module])])]) # Checks for header files. -AC_CHECK_HEADERS([check.h gnutls/gnutls.h gnutls/crypto.h],, +AC_CHECK_HEADERS([check.h gnutls/gnutls.h gnutls/crypto.h jansson.h],, [AC_MSG_ERROR([Required library headers not found.])]) SAVE_CPPFLAGS="$CPPFLAGS" @@ -95,8 +100,9 @@ gl_VISIBILITY # Checks for library functions. AC_SEARCH_LIBS([srunner_create], [check]) AC_SEARCH_LIBS([gnutls_hmac_init], [gnutls]) +AC_SEARCH_LIBS([json_loads], [jansson]) -AC_CHECK_FUNCS([srunner_create gnutls_hmac_init],, +AC_CHECK_FUNCS([srunner_create gnutls_hmac_init json_loads],, [AC_MSG_ERROR([Required library functions not found.])]) SAVE_LIBS="$LIBS" diff --git a/guix.scm b/guix.scm index 78747a9..800f445 100644 --- a/guix.scm +++ b/guix.scm @@ -20,6 +20,7 @@ #:use-module (gnu packages tls) #:use-module (gnu packages valgrind) #:use-module (gnu packages version-control) + #:use-module (gnu packages web) #:use-module ((guix licenses) #:prefix license:) #:use-module (guix build utils) #:use-module (guix build-system gnu) @@ -230,7 +231,9 @@ '("autopull.sh" "autogen.sh")) (substitute* "bootstrap-funclib.sh" (("\\$gnulib_tool \\$gnulib_tool_options") - "sh $gnulib_tool $gnulib_tool_options")))) + "sh $gnulib_tool $gnulib_tool_options")) + (substitute* "maint.mk" + (("lcov") "lcov --no-external")))) (add-after 'bootstrap 'fix-/bin/sh-in-po (lambda _ (substitute* @@ -351,7 +354,7 @@ gobject-introspection imagemagick indent cppi vala lcov perl-gd)) (inputs - (list gtk libadwaita check gnu-gettext gnutls)) + (list gtk libadwaita check gnu-gettext gnutls jansson)) (home-page "https://labo.planete-kraus.eu/disfluid.git") (synopsis "Demanding Interoperability to Strengthen the Free/Libre Web: Introducing Disfluid") (description "This provides tools for web interoperability.") @@ -395,4 +398,4 @@ (list valgrind (list glibc "debug") pkg-config texinfo (texlive-updmap.cfg (list texlive)) tar gzip)) (inputs - (list gnu-gettext gtk libadwaita check gnutls))) + (list gnu-gettext gtk libadwaita check gnutls jansson))) diff --git a/src/libdisfluid/Makefile.am b/src/libdisfluid/Makefile.am index 33000e8..55abf4e 100644 --- a/src/libdisfluid/Makefile.am +++ b/src/libdisfluid/Makefile.am @@ -1,6 +1,7 @@ lib_LTLIBRARIES += %D%/libdisfluid.la %C%_libdisfluid_la_SOURCES = \ + %D%/disfluid-activity-object.h \ %D%/disfluid-authors.h \ %D%/disfluid-append-only-file.h \ %D%/disfluid-cache-entry.h \ diff --git a/src/libdisfluid/disfluid-activity-object.h b/src/libdisfluid/disfluid-activity-object.h new file mode 100644 index 0000000..d079f89 --- /dev/null +++ b/src/libdisfluid/disfluid-activity-object.h @@ -0,0 +1,100 @@ +#ifndef DISFLUID_ACTIVITY_OBJECT_INCLUDED +# define DISFLUID_ACTIVITY_OBJECT_INCLUDED + +# include +# include "string-desc.h" +# include + +MAYBE_UNUSED static int +activity_object_context_prefix (json_t * object, + string_desc_t * context_prefix); + +# include "disfluid-append-only-file.h" +# include "safe-alloc.h" + +static bool +activity_object_is_as (json_t * value, bool with_hash) +{ + static const char *as = "https://www.w3.org/ns/activitystreams#"; + const size_t as_len_h = strlen (as); + const size_t as_len_s = as_len_h - 1; + size_t as_len = as_len_s; + if (with_hash) + { + as_len = as_len_h; + } + return (json_is_string (value) + && json_string_length (value) == as_len + && strncmp (json_string_value (value), as, as_len) == 0); +} + +static int +activity_object_context_prefix (json_t * object, + string_desc_t * context_prefix) +{ + context_prefix->_nbytes = 0; + context_prefix->_data = NULL; + if (!json_is_object (object)) + { + return -1; + } + json_t *context = json_object_get (object, "@context"); + if (activity_object_is_as (context, false)) + { + /* This is the only context. */ + context_prefix->_nbytes = 0; + context_prefix->_data = NULL; + return 0; + } + else if (json_is_object (context)) + { + json_t *vocab = json_object_get (context, "@vocab"); + if (activity_object_is_as (vocab, true)) + { + /* Also not prefixed. */ + context_prefix->_nbytes = 0; + context_prefix->_data = NULL; + return 0; + } + /* Maybe it is a value of the context object. */ + const char *key; + json_t *value; + context_prefix->_nbytes = 0; + context_prefix->_data = NULL; + json_object_foreach (context, key, value) + { + if (key[0] != '@' && activity_object_is_as (value, true)) + { + FREE (context_prefix->_data); + context_prefix->_nbytes = strlen (key) + 1; /* for the final : */ + if (ALLOC_N (context_prefix->_data, context_prefix->_nbytes) < 0) + { + return -2; + } + memcpy (context_prefix->_data, key, context_prefix->_nbytes - 1); + context_prefix->_data[context_prefix->_nbytes - 1] = ':'; + } + } + if (context_prefix->_data != NULL) + { + return 0; + } + } + else if (json_is_array (context)) + { + size_t n = json_array_size (context); + for (size_t i = 0; i < n; i++) + { + json_t *value = json_array_get (context, i); + if (activity_object_is_as (value, false)) + { + context_prefix->_nbytes = 0; + context_prefix->_data = NULL; + return 0; + } + } + } + return -1; +} + +#endif /* not DISFLUID_ACTIVITY_OBJECT_INCLUDED */ diff --git a/src/libdisfluid/disfluid-tests.h b/src/libdisfluid/disfluid-tests.h index bcc50b4..98cbdf3 100644 --- a/src/libdisfluid/disfluid-tests.h +++ b/src/libdisfluid/disfluid-tests.h @@ -20,6 +20,9 @@ static inline int test_append_count (const char *filename, size_t *n); # include "disfluid-append-only-file.h" # include "disfluid-trie.h" # include "disfluid-cache-group.h" +# include "disfluid-activity-object.h" + +# include # define BYTES * 1 @@ -1220,6 +1223,150 @@ START_TEST (test_aof_cache_group) END_TEST /* *INDENT-ON* */ +/* *INDENT-OFF* */ +START_TEST (test_activity_context_1) +/* *INDENT-ON* */ + +{ + static const char *activity = + "{" + "\"@context\": \"https://www.w3.org/ns/activitystreams\"," + "\"summary\": \"A note\"," + "\"type\": \"Note\"," "\"content\": \"My dog has fleas.\"" "}"; + json_error_t error; + json_t *object = json_loads (activity, 0, &error); + ck_assert_ptr_nonnull (object); + string_desc_t prefix; + int ctx_error = activity_object_context_prefix (object, &prefix); + ck_assert_int_eq (ctx_error, 0); + ck_assert_int_eq (prefix._nbytes, 0); + ck_assert_ptr_null (prefix._data); + json_decref (object); +} +/* *INDENT-OFF* */ +END_TEST +/* *INDENT-ON* */ + +/* *INDENT-OFF* */ +START_TEST (test_activity_context_2) +/* *INDENT-ON* */ + +{ + static const char *activity = + "{" + "\"@context\": {" + "\"@vocab\": \"https://www.w3.org/ns/activitystreams#\"," + "\"ext\": \"https://canine-extension.example/terms/\"," + "\"@language\": \"en\"" + "}," + "\"summary\": \"A note\"," + "\"type\": \"Note\"," + "\"content\": \"My dog has fleas.\"," + "\"ext:nose\": 0," "\"ext:smell\": \"terrible\"" "}"; + json_error_t error; + json_t *object = json_loads (activity, 0, &error); + ck_assert_ptr_nonnull (object); + string_desc_t prefix; + int ctx_error = activity_object_context_prefix (object, &prefix); + ck_assert_int_eq (ctx_error, 0); + ck_assert_int_eq (prefix._nbytes, 0); + ck_assert_ptr_null (prefix._data); + json_decref (object); +} +/* *INDENT-OFF* */ +END_TEST +/* *INDENT-ON* */ + +/* *INDENT-OFF* */ +START_TEST (test_activity_context_2bis) +/* *INDENT-ON* */ + +{ + static const char *activity = + "{" + "\"@context\": {" + "\"@vocab\": \"https://example.com/\"," + "\"as\": \"https://www.w3.org/ns/activitystreams#\"," + "\"ext\": \"https://canine-extension.example/terms/\"," + "\"@language\": \"en\"" + "}," + "\"as:summary\": \"A note\"," + "\"as:type\": \"Note\"," + "\"as:content\": \"My dog has fleas.\"," + "\"ext:nose\": 0," "\"ext:smell\": \"terrible\"" "}"; + json_error_t error; + json_t *object = json_loads (activity, 0, &error); + ck_assert_ptr_nonnull (object); + string_desc_t prefix; + int ctx_error = activity_object_context_prefix (object, &prefix); + ck_assert_int_eq (ctx_error, 0); + ck_assert_int_eq (prefix._nbytes, 3); + ck_assert_int_eq (memcmp (prefix._data, "as:", 3), 0); + FREE (prefix._data); + json_decref (object); +} +/* *INDENT-OFF* */ +END_TEST +/* *INDENT-ON* */ + +/* *INDENT-OFF* */ +START_TEST (test_activity_context_3) +/* *INDENT-ON* */ + +{ + static const char *activity = + "{" + "\"@context\": [" + "\"https://www.w3.org/ns/activitystreams\"," + "{" + "\"css\": \"http://www.w3.org/ns/oa#styledBy\"" + "}" + "]," + "\"summary\": \"A note\"," + "\"type\": \"Note\"," + "\"content\": \"My dog has fleas.\"," + "\"css\": \"http://www.csszengarden.com/217/217.css?v=8may2013\"" "}"; + json_error_t error; + json_t *object = json_loads (activity, 0, &error); + ck_assert_ptr_nonnull (object); + string_desc_t prefix; + int ctx_error = activity_object_context_prefix (object, &prefix); + ck_assert_int_eq (ctx_error, 0); + ck_assert_int_eq (prefix._nbytes, 0); + ck_assert_ptr_null (prefix._data); + json_decref (object); +} +/* *INDENT-OFF* */ +END_TEST +/* *INDENT-ON* */ + +/* *INDENT-OFF* */ +START_TEST (test_activity_context_none) +/* *INDENT-ON* */ + +{ + static const char *activity = + "{" + "\"http://schema.org/name\": \"Manu Sporny\"," + "\"http://schema.org/url\": {" + "\"@id\": \"http://manu.sporny.org/\"" + "}," + "\"http://schema.org/image\": {" + "\"@id\": \"http://manu.sporny.org/images/manu.png\"" "}" "}"; + json_error_t error; + json_t *object = json_loads (activity, 0, &error); + ck_assert_ptr_nonnull (object); + string_desc_t prefix; + int ctx_error = activity_object_context_prefix (object, &prefix); + ck_assert_int_lt (ctx_error, 0); + ck_assert_int_eq (prefix._nbytes, 0); + ck_assert_ptr_null (prefix._data); + json_decref (object); +} +/* *INDENT-OFF* */ +END_TEST +/* *INDENT-ON* */ + static inline char * tests_read_whole_file (int file) { @@ -1281,6 +1428,13 @@ run_tests (size_t *n_tests, size_t *n_errors) tcase_add_test (aof, test_aof_can_read_locked_file); tcase_add_test (aof, test_aof_trie_fold); suite_add_tcase (suite, aof); + TCase *activity = tcase_create (_("activity")); + tcase_add_test (activity, test_activity_context_1); + tcase_add_test (activity, test_activity_context_2); + tcase_add_test (activity, test_activity_context_2bis); + tcase_add_test (activity, test_activity_context_3); + tcase_add_test (activity, test_activity_context_none); + suite_add_tcase (suite, activity); SRunner *runner = srunner_create (suite); char log_file_name[] = "/tmp/disfluid-unit-tests-XXXXXX"; int log_file = mkstemp (log_file_name); diff --git a/src/libdisfluid/main.c b/src/libdisfluid/main.c index 3a6c5e3..792dc84 100644 --- a/src/libdisfluid/main.c +++ b/src/libdisfluid/main.c @@ -20,6 +20,7 @@ #define _(String) dgettext (PACKAGE, (String)) #define N_(String) (String) +#include "disfluid-activity-object.h" #include "disfluid-authors.h" #include "disfluid-cache-entry.h" #include "disfluid-cache-entry-key.h" -- cgit v1.2.3