From 9dea0253a3ded0e32daa33f11850797109018326 Mon Sep 17 00:00:00 2001 From: Ryan Lortie Date: Sun, 14 Mar 2010 15:55:48 -0400 Subject: [PATCH] GVariant: add loading, byteswapping, normalisation --- docs/reference/glib/glib-sections.txt | 4 + glib/glib.symbols | 5 + glib/gvariant-core.c | 61 +++++++++- glib/gvariant.c | 207 ++++++++++++++++++++++++++++++++++ glib/gvariant.h | 10 ++ glib/tests/gvariant.c | 72 ++++++++++++ 6 files changed, 355 insertions(+), 4 deletions(-) diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index 5ae7413..05b06cd 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -2857,6 +2857,10 @@ g_variant_get_fixed_array g_variant_get_size g_variant_get_data g_variant_store +g_variant_new_from_data +g_variant_byteswap +g_variant_get_normal_form +g_variant_is_normal_form g_variant_hash diff --git a/glib/glib.symbols b/glib/glib.symbols index 9d0c2a1..ee9da31 100644 --- a/glib/glib.symbols +++ b/glib/glib.symbols @@ -1705,6 +1705,7 @@ g_variant_get_child_value g_variant_get_size g_variant_get_data g_variant_store +g_variant_is_normal_form #endif #if IN_FILE(__G_VARIANT_C__) @@ -1788,6 +1789,10 @@ g_variant_builder_add g_variant_get_child g_variant_iter_next g_variant_iter_loop + +g_variant_new_from_data +g_variant_get_normal_form +g_variant_byteswap #endif #endif diff --git a/glib/gvariant-core.c b/glib/gvariant-core.c index 51e1fac..183fa09 100644 --- a/glib/gvariant-core.c +++ b/glib/gvariant-core.c @@ -709,10 +709,9 @@ g_variant_get_size (GVariant *value) * @returns: the serialised form of @value, or %NULL * * Returns a pointer to the serialised form of a #GVariant instance. - * The returned data is in machine native byte order but may not be in - * fully-normalised form if read from an untrusted source. The returned - * data must not be freed; it remains valid for as long as @value - * exists. + * The returned data may not be in fully-normalised form if read from an + * untrusted source. The returned data must not be freed; it remains + * valid for as long as @value exists. * * If @value is a fixed-sized value that was deserialised from a * corrupted serialised container then %NULL may be returned. In this @@ -875,5 +874,59 @@ g_variant_store (GVariant *value, g_variant_unlock (value); } +/** + * g_variant_is_normal_form: + * @value: a #GVariant instance + * @returns: %TRUE if @value is in normal form + * + * Checks if @value is in normal form. + * + * The main reason to do this is to detect if a given chunk of + * serialised data is in normal form: load the data into a #GVariant + * using g_variant_create_from_data() and then use this function to + * check. + * + * If @value is found to be in normal form then it will be marked as + * being trusted. If the value was already marked as being trusted then + * this function will immediately return %TRUE. + * + * Since: 2.24 + **/ +gboolean +g_variant_is_normal_form (GVariant *value) +{ + if (value->state & STATE_TRUSTED) + return TRUE; + + g_variant_lock (value); + + if (value->state & STATE_SERIALISED) + { + GVariantSerialised serialised = { + value->type_info, + (gpointer) value->contents.serialised.data, + value->size + }; + + if (g_variant_serialised_is_normal (serialised)) + value->state |= STATE_TRUSTED; + } + else + { + gboolean normal = TRUE; + gsize i; + + for (i = 0; i < value->contents.tree.n_children; i++) + normal &= g_variant_is_normal_form (value->contents.tree.children[i]); + + if (normal) + value->state |= STATE_TRUSTED; + } + + g_variant_unlock (value); + + return (value->state & STATE_TRUSTED) != 0; +} + #define __G_VARIANT_CORE_C__ #include "galiasdef.c" diff --git a/glib/gvariant.c b/glib/gvariant.c index 1cacb63..342eeeb 100644 --- a/glib/gvariant.c +++ b/glib/gvariant.c @@ -3912,6 +3912,213 @@ g_variant_iter_loop (GVariantIter *iter, return value != NULL; } +/* Serialised data {{{1 */ +static GVariant * +g_variant_deep_copy (GVariant *value) +{ + switch (g_variant_classify (value)) + { + case G_VARIANT_CLASS_MAYBE: + case G_VARIANT_CLASS_ARRAY: + case G_VARIANT_CLASS_TUPLE: + case G_VARIANT_CLASS_DICT_ENTRY: + case G_VARIANT_CLASS_VARIANT: + { + GVariantBuilder builder; + GVariantIter iter; + GVariant *child; + + g_variant_builder_init (&builder, g_variant_get_type (value)); + g_variant_iter_init (&iter, value); + + while ((child = g_variant_iter_next_value (&iter))) + { + g_variant_builder_add_value (&builder, g_variant_deep_copy (child)); + g_variant_unref (child); + } + + return g_variant_builder_end (&builder); + } + + case G_VARIANT_CLASS_BOOLEAN: + return g_variant_new_boolean (g_variant_get_boolean (value)); + + case G_VARIANT_CLASS_BYTE: + return g_variant_new_byte (g_variant_get_byte (value)); + + case G_VARIANT_CLASS_INT16: + return g_variant_new_int16 (g_variant_get_int16 (value)); + + case G_VARIANT_CLASS_UINT16: + return g_variant_new_uint16 (g_variant_get_uint16 (value)); + + case G_VARIANT_CLASS_INT32: + return g_variant_new_int32 (g_variant_get_int32 (value)); + + case G_VARIANT_CLASS_UINT32: + return g_variant_new_uint32 (g_variant_get_uint32 (value)); + + case G_VARIANT_CLASS_INT64: + return g_variant_new_int64 (g_variant_get_int64 (value)); + + case G_VARIANT_CLASS_UINT64: + return g_variant_new_uint64 (g_variant_get_uint64 (value)); + + case G_VARIANT_CLASS_HANDLE: + return g_variant_new_handle (g_variant_get_handle (value)); + + case G_VARIANT_CLASS_DOUBLE: + return g_variant_new_double (g_variant_get_double (value)); + + case G_VARIANT_CLASS_STRING: + return g_variant_new_string (g_variant_get_string (value, NULL)); + + case G_VARIANT_CLASS_OBJECT_PATH: + return g_variant_new_object_path (g_variant_get_string (value, NULL)); + + case G_VARIANT_CLASS_SIGNATURE: + return g_variant_new_signature (g_variant_get_string (value, NULL)); + } + + g_assert_not_reached (); +} + +/** + * g_variant_get_normal_form: + * @value: a #GVariant + * @returns: a trusted #GVariant + * + * Gets a #GVariant instance that has the same value as @value and is + * trusted to be in normal form. + * + * If @value is already trusted to be in normal form then a new + * reference to @value is returned. + * + * If @value is not already trusted, then it is scanned to check if it + * is in normal form. If it is found to be in normal form then it is + * marked as trusted and a new reference to it is returned. + * + * If @value is found not to be in normal form then a new trusted + * #GVariant is created with the same value as @value. + * + * It makes sense to call this function if you've received #GVariant + * data from untrusted sources and you want to ensure your serialised + * output is definitely in normal form. + * + * Since: 2.24 + **/ +GVariant * +g_variant_get_normal_form (GVariant *value) +{ + GVariant *trusted; + + if (g_variant_is_normal_form (value)) + return g_variant_ref (value); + + trusted = g_variant_deep_copy (value); + g_assert (g_variant_is_trusted (trusted)); + + return g_variant_ref_sink (trusted); +} + +/** + * g_variant_byteswap: + * @value: a #GVariant + * @returns: the byteswapped form of @value + * + * Performs a byteswapping operation on the contents of @value. The + * result is that all multi-byte numeric data contained in @value is + * byteswapped. That includes 16, 32, and 64bit signed and unsigned + * integers as well as file handles and double precision floating point + * values. + * + * This function is an identity mapping on any value that does not + * contain multi-byte numeric data. That include strings, booleans, + * bytes and containers containing only these things (recursively). + * + * The returned value is always in normal form and is marked as trusted. + * + * Since: 2.24 + **/ +GVariant * +g_variant_byteswap (GVariant *value) +{ + GVariantSerialised serialised; + GVariant *trusted; + GBuffer *buffer; + GVariant *new; + + trusted = g_variant_get_normal_form (value); + serialised.type_info = g_variant_get_type_info (trusted); + serialised.size = g_variant_get_size (trusted); + serialised.data = g_malloc (serialised.size); + g_variant_store (trusted, serialised.data); + g_variant_unref (trusted); + + g_variant_serialised_byteswap (serialised); + + buffer = g_buffer_new_take_data (serialised.data, serialised.size); + new = g_variant_new_from_buffer (g_variant_get_type (value), buffer, TRUE); + g_buffer_unref (buffer); + + return g_variant_ref_sink (new); +} + +/** + * g_variant_new_from_data: + * @type: a #GVariantType + * @data: the serialised data + * @size: the size of @data + * @trusted: %TRUE if @data is definitely in normal form + * @notify: function to call when @data is no longer needed + * @user_data: data for @notify + * @returns: a new floating #GVariant of type @type + * + * Creates a new #GVariant instance from serialised data. + * + * @type is the type of #GVariant instance that will be constructed. + * The interpretation of @data depends on knowing the type. + * + * @data is not modified by this function and must remain valid with an + * unchanging value until such a time as @notify is called with + * @user_data. If the contents of @data change before that time then + * the result is undefined. + * + * If @data is trusted to be serialised data in normal form then + * @trusted should be %TRUE. This applies to serialised data created + * within this process or read from a trusted location on the disk (such + * as a file installed in /usr/lib alongside your application). You + * should set trusted to %FALSE if @data is read from the network, a + * file in the user's home directory, etc. + * + * @notify will be called with @user_data when @data is no longer + * needed. The exact time of this call is unspecified and might even be + * before this function returns. + * + * Since: 2.24 + **/ +GVariant * +g_variant_new_from_data (const GVariantType *type, + gconstpointer data, + gsize size, + gboolean trusted, + GDestroyNotify notify, + gpointer user_data) +{ + GVariant *value; + GBuffer *buffer; + + if (notify) + buffer = g_buffer_new_from_pointer (data, size, notify, user_data); + else + buffer = g_buffer_new_from_static_data (data, size); + + value = g_variant_new_from_buffer (type, buffer, trusted); + g_buffer_unref (buffer); + + return value; +} + /* Epilogue {{{1 */ #define __G_VARIANT_C__ #include "galiasdef.c" diff --git a/glib/gvariant.h b/glib/gvariant.h index abf8102..19c57c6 100644 --- a/glib/gvariant.h +++ b/glib/gvariant.h @@ -139,6 +139,16 @@ guint g_variant_hash (gconstp gboolean g_variant_equal (gconstpointer one, gconstpointer two); +GVariant * g_variant_get_normal_form (GVariant *variant); +gboolean g_variant_is_normal_form (GVariant *variant); +GVariant * g_variant_byteswap (GVariant *variant); +GVariant * g_variant_new_from_data (const GVariantType *type, + gconstpointer data, + gsize size, + gboolean trusted, + GDestroyNotify notify, + gpointer user_data); + typedef struct _GVariantIter GVariantIter; struct _GVariantIter { /*< private >*/ diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c index af648a7..550fdac 100644 --- a/glib/tests/gvariant.c +++ b/glib/tests/gvariant.c @@ -3385,6 +3385,77 @@ test_hashing (void) g_variant_type_info_assert_no_infos (); } +static void +test_gv_byteswap () +{ +#if G_BYTE_ORDER == G_LITTLE_ENDIAN +# define native16(x) x, 0 +# define swapped16(x) 0, x +#else +# define native16(x) 0, x +# define swapped16(x) x, 0 +#endif + /* all kinds of of crazy randomised testing already performed on the + * byteswapper in the /gvariant/serialiser/byteswap test and all kinds + * of crazy randomised testing performed against the serialiser + * normalisation functions in the /gvariant/serialiser/fuzz/ tests. + * + * just test a few simple cases here to make sure they each work + */ + guchar valid_data[] = { 'a', '\0', swapped16(66), 2, + 0, + 'b', '\0', swapped16(77), 2, + 5, 11 }; + guchar corrupt_data[] = { 'a', '\0', swapped16(66), 2, + 0, + 'b', '\0', swapped16(77), 2, + 6, 11 }; + GVariant *value, *swapped, *reswapped; + gchar *string, *string2; + + + /* trusted */ + value = g_variant_new_from_data (G_VARIANT_TYPE ("a(sn)"), + valid_data, sizeof valid_data, TRUE, + NULL, NULL); + swapped = g_variant_byteswap (value); + g_variant_unref (value); + g_assert (g_variant_get_size (swapped) == 13); + string = g_variant_print (swapped, FALSE); + g_variant_unref (swapped); + g_assert_cmpstr (string, ==, "[('a', 66), ('b', 77)]"); + g_free (string); + + /* untrusted but valid */ + value = g_variant_new_from_data (G_VARIANT_TYPE ("a(sn)"), + valid_data, sizeof valid_data, FALSE, + NULL, NULL); + swapped = g_variant_byteswap (value); + g_variant_unref (value); + g_assert (g_variant_get_size (swapped) == 13); + string = g_variant_print (swapped, FALSE); + g_variant_unref (swapped); + g_assert_cmpstr (string, ==, "[('a', 66), ('b', 77)]"); + g_free (string); + + /* untrusted, invalid */ + value = g_variant_new_from_data (G_VARIANT_TYPE ("a(sn)"), + corrupt_data, sizeof corrupt_data, FALSE, + NULL, NULL); + string = g_variant_print (value, FALSE); + swapped = g_variant_byteswap (value); + g_variant_unref (value); + g_assert (g_variant_get_size (swapped) == 13); + value = g_variant_byteswap (swapped); + g_variant_unref (swapped); + string2 = g_variant_print (value, FALSE); + g_assert (g_variant_get_size (value) == 13); + g_variant_unref (value); + g_assert_cmpstr (string, ==, string2); + g_free (string2); + g_free (string); +} + int main (int argc, char **argv) { @@ -3418,6 +3489,7 @@ main (int argc, char **argv) g_test_add_func ("/gvariant/valist", test_valist); g_test_add_func ("/gvariant/builder-memory", test_builder_memory); g_test_add_func ("/gvariant/hashing", test_hashing); + g_test_add_func ("/gvariant/byteswap", test_gv_byteswap); return g_test_run (); } -- 2.7.4