diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index 8f16358d8..b0a9c8fc6 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -2842,8 +2842,9 @@ g_variant_is_object_path g_variant_new_signature g_variant_is_signature g_variant_new_variant -g_variant_new_byte_array g_variant_new_strv +g_variant_new_bytestring +g_variant_new_bytestring_array g_variant_get_boolean @@ -2859,10 +2860,12 @@ g_variant_get_double g_variant_get_string g_variant_dup_string g_variant_get_variant -g_variant_new_byte_array -g_variant_get_byte_array g_variant_get_strv g_variant_dup_strv +g_variant_get_bytestring +g_variant_dup_bytestring +g_variant_get_bytestring_array +g_variant_dup_bytestring_array g_variant_new_maybe diff --git a/docs/reference/glib/gvariant-varargs.xml b/docs/reference/glib/gvariant-varargs.xml index 2eb945fe8..2d92af0e0 100644 --- a/docs/reference/glib/gvariant-varargs.xml +++ b/docs/reference/glib/gvariant-varargs.xml @@ -46,8 +46,8 @@ '&s' '&o', '&g', '^as', - '^ao', '^ag', '^a&s', '^a&o' or - '^a&g' + '^a&s', '^ay', '^&ay', '^aay' + or '^a&ay'. @@ -935,39 +935,155 @@ value2 = g_variant_new ("(@(iii)*)", value1, g_variant_new_string ("foo")); - The '^' character currently only has one purpose: to convert to and from - G_TYPE_STRV type arrays of strings. It is always used with - arrays of strings (or other string types). It has two forms. + The '^' character currently supports conversion to and from bytestrings or to and from arrays + of strings or bytestrings. It has a number of forms. - - - - '^as' (or o or g) - - - - - '^a&s' (or o or g) - - - + - When used with g_variant_new() both forms are equivalent. - A (const gchar * const *) is collected. This must be a pointer to the - array of NULL-terminated pointers to strings. This array is - converted to a GVariant instance. Copies are made, so the original - array may be freed immediately. - - - When used with g_variant_get() the two forms have - different meaning. Both return a freshly allocated - NULL-terminated array of pointers to strings. In the case of - '^as', the strings are owned by the caller -- it is appropriate to free the array with - g_strfreev(). In the case of '^a&s', - a shallow copy is made; the strings themselves are embedded in the serialised data and owned by the original - GVariant instance -- it is only appropriate to free the outer array - with g_free(). + In all forms, when used with g_variant_new() one + pointer value is collected from the variable arguments and passed to a function (as given in the table below). + The result of that function is used as the value for this position. When used with + g_variant_get() one pointer value is produced by using + the function (given in the table) and returned by reference. + + + + + + + + + + + + Conversion + + + + + + Used with g_variant_new() + + + + + + + Used with g_variant_get() + + + + + + + + + + ^as + + + + + + equivalent to g_variant_new_strv() + + + + + equivalent to g_variant_dup_strv() + + + + + + + + + ^a&s + + + + + + equivalent to g_variant_get_strv() + + + + + + + + + ^ay + + + + + + equivalent to g_variant_new_bytestring() + + + + + equivalent to g_variant_dup_bytestring() + + + + + + + + + ^&ay + + + + + + equivalent to g_variant_get_bytestring() + + + + + + + + + ^aay + + + + + + equivalent to g_variant_new_bytestring_array() + + + + + equivalent to g_variant_dup_bytestring_array() + + + + + + + + + ^a&ay + + + + + + equivalent to g_variant_get_bytestring_array() + + + + + + + diff --git a/docs/reference/glib/tmpl/glib-unused.sgml b/docs/reference/glib/tmpl/glib-unused.sgml index 882426691..7469a5052 100644 --- a/docs/reference/glib/tmpl/glib-unused.sgml +++ b/docs/reference/glib/tmpl/glib-unused.sgml @@ -877,7 +877,7 @@ Turns the argument into a string literal by using the '#' stringizing operator. @x: text to convert to a literal string. - + @@ -886,46 +886,12 @@ Turns the argument into a string literal by using the '#' stringizing operator. @length: @Returns: - + -@value: -@length: -@Returns: - - - - - - -@value: -@Returns: - - - - - - -@value: -@length: -@Returns: - - - - - - -@string: -@Returns: - - - - - - -@strv: +@array: @length: @Returns: diff --git a/gio/gapplication.c b/gio/gapplication.c index 2996d1b9f..2e3a3e5da 100644 --- a/gio/gapplication.c +++ b/gio/gapplication.c @@ -332,7 +332,7 @@ append_cwd_to_platform_data (GVariant *platform_data) if (cwd) g_variant_builder_add (&builder, "{sv}", "cwd", - g_variant_new_byte_array (cwd, -1)); + g_variant_new_bytestring (cwd)); g_free (cwd); if (platform_data) @@ -351,27 +351,6 @@ append_cwd_to_platform_data (GVariant *platform_data) return result; } -static GVariant * -variant_from_argv (int argc, - char **argv) -{ - GVariantBuilder builder; - int i; - - g_variant_builder_init (&builder, G_VARIANT_TYPE ("aay")); - - for (i = 1; i < argc; i++) - { - guint8 *argv_bytes; - - argv_bytes = (guint8*) argv[i]; - g_variant_builder_add_value (&builder, - g_variant_new_byte_array (argv_bytes, -1)); - } - - return g_variant_builder_end (&builder); -} - static gboolean timeout_handle_actions_changed (gpointer user_data) { @@ -468,6 +447,7 @@ g_application_new (const gchar *appid, int argc, char **argv) { + const gchar * const *args = (const gchar **) argv; GObject *app; GError *error = NULL; GVariant *argv_variant; @@ -476,7 +456,7 @@ g_application_new (const gchar *appid, g_return_val_if_fail (appid != NULL, NULL); - argv_variant = variant_from_argv (argc, argv); + argv_variant = g_variant_new_bytestring_array (args, argc); app = g_initable_new (G_TYPE_APPLICATION, NULL, @@ -514,13 +494,14 @@ g_application_try_new (const gchar *appid, char **argv, GError **error) { + const gchar * const *args = (const gchar **) argv; GVariant *argv_variant; g_type_init (); g_return_val_if_fail (appid != NULL, NULL); - argv_variant = variant_from_argv (argc, argv); + argv_variant = g_variant_new_bytestring_array (args, argc); return G_APPLICATION (g_initable_new (G_TYPE_APPLICATION, NULL, @@ -551,13 +532,14 @@ g_application_unregistered_try_new (const gchar *appid, char **argv, GError **error) { + const gchar * const *args = (const gchar **) argv; GVariant *argv_variant; g_type_init (); g_return_val_if_fail (appid != NULL, NULL); - argv_variant = variant_from_argv (argc, argv); + argv_variant = g_variant_new_bytestring_array (args, argc); return G_APPLICATION (g_initable_new (G_TYPE_APPLICATION, NULL, diff --git a/gio/gsettings-mapping.c b/gio/gsettings-mapping.c index d85fae187..f83e23832 100644 --- a/gio/gsettings-mapping.c +++ b/gio/gsettings-mapping.c @@ -369,8 +369,8 @@ g_settings_set_mapping (const GValue *value, return NULL; else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_STRING)) return g_variant_new_string (g_value_get_string (value)); - else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE ("ay"))) - return g_variant_new_byte_array (g_value_get_string (value), -1); + else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_BYTESTRING)) + return g_variant_new_bytestring (g_value_get_string (value)); else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_OBJECT_PATH)) return g_variant_new_object_path (g_value_get_string (value)); else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_SIGNATURE)) @@ -528,9 +528,9 @@ g_settings_get_mapping (GValue *value, return TRUE; } } - else if (g_variant_is_of_type (variant, G_VARIANT_TYPE ("ay"))) + else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_BYTESTRING)) { - g_value_set_string (value, g_variant_get_byte_array (variant, NULL)); + g_value_set_string (value, g_variant_get_bytestring (variant)); return TRUE; } diff --git a/gio/tests/gsettings.c b/gio/tests/gsettings.c index 06f98bf24..f3727e015 100644 --- a/gio/tests/gsettings.c +++ b/gio/tests/gsettings.c @@ -1006,7 +1006,7 @@ test_simple_binding (void) g_object_set (obj, "string", "non-unicode:\315", NULL); value = g_settings_get_value (settings, "chararray"); - g_assert_cmpstr (g_variant_get_byte_array (value, NULL), ==, "non-unicode:\315"); + g_assert_cmpstr (g_variant_get_bytestring (value), ==, "non-unicode:\315"); g_variant_unref (value); g_settings_bind (settings, "double", obj, "double", G_SETTINGS_BIND_DEFAULT); diff --git a/gio/tests/testapp.c b/gio/tests/testapp.c index caed83015..c583645a4 100644 --- a/gio/tests/testapp.c +++ b/gio/tests/testapp.c @@ -41,11 +41,11 @@ on_app_activated (GApplication *application, while (g_variant_iter_next (&iter, "{&sv}", &key, &value)) { const char *activate_cwd; - gsize *len; + if (strcmp (key, "cwd") != 0) continue; - activate_cwd = g_variant_get_byte_array (value, &len); + activate_cwd = g_variant_get_bytestring (value); g_assert_cmpstr (cwd, ==, activate_cwd); g_variant_unref (value); } diff --git a/glib/glib.symbols b/glib/glib.symbols index 41ef82b73..648812a25 100644 --- a/glib/glib.symbols +++ b/glib/glib.symbols @@ -1743,8 +1743,9 @@ g_variant_is_object_path g_variant_new_signature g_variant_is_signature g_variant_new_variant -g_variant_new_byte_array g_variant_new_strv +g_variant_new_bytestring +g_variant_new_bytestring_array g_variant_get_boolean g_variant_get_byte @@ -1759,9 +1760,12 @@ g_variant_get_double g_variant_get_string g_variant_dup_string g_variant_get_variant -g_variant_get_byte_array g_variant_get_strv g_variant_dup_strv +g_variant_get_bytestring +g_variant_dup_bytestring +g_variant_get_bytestring_array +g_variant_dup_bytestring_array g_variant_new_maybe g_variant_new_array diff --git a/glib/gvariant-parser.c b/glib/gvariant-parser.c index 93a5dd372..7932d855f 100644 --- a/glib/gvariant-parser.c +++ b/glib/gvariant-parser.c @@ -163,7 +163,22 @@ token_stream_prepare (TokenStream *stream) break; break; - case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'b': + if (stream->stream[1] == '\'' || stream->stream[1] == '"') + { + for (end = stream->stream + 2; end != stream->end; end++) + if (*end == stream->stream[1] || *end == '\0' || + (*end == '\\' && (++end == stream->end || *end == '\0'))) + break; + + if (end != stream->end && *end) + end++; + break; + } + + else /* ↓↓↓ */; + + case 'a': /* 'b' */ case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': @@ -173,23 +188,6 @@ token_stream_prepare (TokenStream *stream) break; break; - case '@': case '%': - /* stop at the first space, comma, colon or unmatched bracket. - * deals nicely with cases like (%i, %i) or {%i: %i}. - */ - for (end = stream->stream + 1; - end != stream->end && *end != ',' && - *end != ':' && !g_ascii_isspace (*end); - end++) - - if (*end == '(' || *end == '{') - brackets++; - - else if ((*end == ')' || *end == '}') && !brackets--) - break; - - break; - case '\'': case '"': for (end = stream->stream + 1; end != stream->end; end++) if (*end == stream->stream[0] || *end == '\0' || @@ -200,6 +198,23 @@ token_stream_prepare (TokenStream *stream) end++; break; + case '@': case '%': + /* stop at the first space, comma, colon or unmatched bracket. + * deals nicely with cases like (%i, %i) or {%i: %i}. + */ + for (end = stream->stream + 1; + end != stream->end && *end != ',' && + *end != ':' && *end != '>' && !g_ascii_isspace (*end); + end++) + + if (*end == '(' || *end == '{') + brackets++; + + else if ((*end == ')' || *end == '}') && !brackets--) + break; + + break; + default: end = stream->stream + 1; break; @@ -224,12 +239,24 @@ token_stream_peek (TokenStream *stream, return stream->this[0] == first_char; } +static gboolean +token_stream_peek2 (TokenStream *stream, + gchar first_char, + gchar second_char) +{ + token_stream_prepare (stream); + + return stream->this[0] == first_char && + stream->this[1] == second_char; +} + static gboolean token_stream_is_keyword (TokenStream *stream) { token_stream_prepare (stream); - return g_ascii_isalpha (stream->this[0]); + return g_ascii_isalpha (stream->this[0]) && + g_ascii_isalpha (stream->this[1]); } static gboolean @@ -1539,6 +1566,128 @@ string_parse (TokenStream *stream, return (AST *) string; } +typedef struct +{ + AST ast; + gchar *string; +} ByteString; + +static gchar * +bytestring_get_pattern (AST *ast, + GError **error) +{ + return g_strdup ("May"); +} + +static GVariant * +bytestring_get_value (AST *ast, + const GVariantType *type, + GError **error) +{ + ByteString *string = (ByteString *) ast; + + g_assert (g_variant_type_equal (type, G_VARIANT_TYPE_BYTESTRING)); + + return g_variant_new_bytestring (string->string); +} + +static void +bytestring_free (AST *ast) +{ + ByteString *string = (ByteString *) ast; + + g_free (string->string); + g_slice_free (ByteString, string); +} + +static AST * +bytestring_parse (TokenStream *stream, + va_list *app, + GError **error) +{ + static const ASTClass bytestring_class = { + bytestring_get_pattern, + maybe_wrapper, bytestring_get_value, + bytestring_free + }; + ByteString *string; + SourceRef ref; + gchar *token; + gsize length; + gchar quote; + gchar *str; + gint i, j; + + token_stream_start_ref (stream, &ref); + token = token_stream_get (stream); + token_stream_end_ref (stream, &ref); + g_assert (token[0] == 'b'); + length = strlen (token); + quote = token[1]; + + str = g_malloc (length); + g_assert (quote == '"' || quote == '\''); + j = 0; + i = 2; + while (token[i] != quote) + switch (token[i]) + { + case '\0': + parser_set_error (error, &ref, NULL, + "unterminated string constant"); + g_free (token); + return NULL; + + case '\\': + switch (token[++i]) + { + case '\0': + parser_set_error (error, &ref, NULL, + "unterminated string constant"); + g_free (token); + return NULL; + + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + { + /* up to 3 characters */ + guchar val = token[i++] - '0'; + + if ('0' <= token[i] && token[i] < '8') + val = (val << 3) | (token[i++] - '0'); + + if ('0' <= token[i] && token[i] < '8') + val = (val << 3) | (token[i++] - '0'); + + str[j++] = val; + } + continue; + + case 'a': str[j++] = '\a'; i++; continue; + case 'b': str[j++] = '\b'; i++; continue; + case 'f': str[j++] = '\f'; i++; continue; + case 'n': str[j++] = '\n'; i++; continue; + case 'r': str[j++] = '\r'; i++; continue; + case 't': str[j++] = '\t'; i++; continue; + case 'v': str[j++] = '\v'; i++; continue; + case '\n': i++; continue; + } + + default: + str[j++] = token[i++]; + } + str[j++] = '\0'; + g_free (token); + + string = g_slice_new (ByteString); + string->ast.class = &bytestring_class; + string->string = str; + + token_stream_next (stream); + + return (AST *) string; +} + typedef struct { AST ast; @@ -2043,6 +2192,10 @@ parse (TokenStream *stream, token_stream_peek (stream, '"')) result = string_parse (stream, app, error); + else if (token_stream_peek2 (stream, 'b', '\'') || + token_stream_peek2 (stream, 'b', '"')) + result = bytestring_parse (stream, app, error); + else { token_stream_set_error (stream, error, FALSE, "expected value"); diff --git a/glib/gvariant.c b/glib/gvariant.c index 2819eda29..03c09ff04 100644 --- a/glib/gvariant.c +++ b/glib/gvariant.c @@ -1164,96 +1164,16 @@ g_variant_dup_string (GVariant *value, return g_strdup (g_variant_get_string (value, length)); } -/** - * g_variant_new_byte_array: - * @array: (array length=length): a pointer to an array of bytes - * @length: the length of @array, or -1 - * @returns: a new floating #GVariant instance - * - * Constructs an array of bytes #GVariant from the given array of bytes. - * - * If @length is -1 then @array is taken to be a normal C string (in the - * sense that it is terminated by a nul character). The nul character - * is included in the array. If length is not -1 then it gives the - * length of @array which may then contain nul chracters with no special - * meaning. - * - * Since: 2.26 - **/ -GVariant * -g_variant_new_byte_array (gconstpointer array, - gssize length) -{ - if (length == -1) - { - const gchar *bytes = array; - - length = 0; - while (bytes[length++]); - } - - return g_variant_new_from_trusted (G_VARIANT_TYPE ("ay"), - array, length); -} - -/** - * g_variant_get_byte_array: - * @value: an array of bytes #GVariant - * @length: (allow-none): the length of the result, or %NULL - * @returns: (array length=length): a pointer to the byte data, or %NULL - * - * Gets the contents of an array of bytes #GVariant. - * - * If @length is non-%NULL then it points to a location at which to - * store the length of the array and nul bytes contained within the - * array have no special meaning. - * - * If @length is %NULL then the caller has no way to determine what the - * length of the returned data might be. In this case, the function - * ensures that the last byte of the array is a nul byte and, if it is - * not, returns %NULL instead. In this way, the caller is assured that - * any non-%NULL pointer that is returned will be nul-terminated. - * - * The return value remains valid as long as @value exists. - * - * Since: 2.26 - **/ -gconstpointer -g_variant_get_byte_array (GVariant *value, - gsize *length) -{ - gconstpointer data; - gsize size; - - TYPE_CHECK (value, G_VARIANT_TYPE ("ay"), NULL); - - data = g_variant_get_data (value); - size = g_variant_get_size (value); - - if (length == NULL) - { - const gchar *bytes = data; - - if (bytes[size - 1] != '\0') - return NULL; - } - else - *length = size; - - return data; -} - /** * g_variant_new_strv: - * @strv: an array of strings + * @strv: (array length=length): an array of strings * @length: the length of @strv, or -1 - * @returns: (array length=length): a new floating #GVariant instance + * @returns: a new floating #GVariant instance * * Constructs an array of strings #GVariant from the given array of * strings. * - * If @length is not -1 then it gives the maximum length of @strv. In - * any case, a %NULL pointer in @strv is taken as a terminator. + * If @length is -1 then @strv is %NULL-terminated. * * Since: 2.24 **/ @@ -1273,7 +1193,7 @@ g_variant_new_strv (const gchar * const *strv, for (i = 0; i < length; i++) strings[i] = g_variant_ref_sink (g_variant_new_string (strv[i])); - return g_variant_new_from_children (G_VARIANT_TYPE ("as"), + return g_variant_new_from_children (G_VARIANT_TYPE_STRING_ARRAY, strings, length, TRUE); } @@ -1304,10 +1224,7 @@ g_variant_get_strv (GVariant *value, gsize n; gsize i; - g_return_val_if_fail (g_variant_is_of_type (value, G_VARIANT_TYPE ("as")) || - g_variant_is_of_type (value, G_VARIANT_TYPE ("ao")) || - g_variant_is_of_type (value, G_VARIANT_TYPE ("ag")), - NULL); + TYPE_CHECK (value, G_VARIANT_TYPE_STRING_ARRAY, NULL); g_variant_get_data (value); n = g_variant_n_children (value); @@ -1333,7 +1250,7 @@ g_variant_get_strv (GVariant *value, * g_variant_dup_strv: * @value: an array of strings #GVariant * @length: (allow-none): the length of the result, or %NULL - * @returns: (array length=length): an array of constant strings + * @returns: (array length=length): an array of strings * * Gets the contents of an array of strings #GVariant. This call * makes a deep copy; the return result should be released with @@ -1356,10 +1273,7 @@ g_variant_dup_strv (GVariant *value, gsize n; gsize i; - g_return_val_if_fail (g_variant_is_of_type (value, G_VARIANT_TYPE ("as")) || - g_variant_is_of_type (value, G_VARIANT_TYPE ("ao")) || - g_variant_is_of_type (value, G_VARIANT_TYPE ("ag")), - NULL); + TYPE_CHECK (value, G_VARIANT_TYPE_STRING_ARRAY, NULL); n = g_variant_n_children (value); strv = g_new (gchar *, n + 1); @@ -1380,6 +1294,234 @@ g_variant_dup_strv (GVariant *value, return strv; } +/** + * g_variant_new_bytestring: + * @string: a normal utf8 nul-terminated string + * @returns: a new bytestring #GVariant instance + * + * Creates an array-of-bytes #GVariant with the contents of @string. + * This function is just like g_variant_new_string() except that the + * string need not be valid utf8. + * + * The nul terminator character at the end of the string is stored in + * the array. + * + * Since: 2.26 + **/ +GVariant * +g_variant_new_bytestring (const gchar *string) +{ + g_return_val_if_fail (string != NULL, NULL); + + return g_variant_new_from_trusted (G_VARIANT_TYPE_BYTESTRING, + string, strlen (string) + 1); +} + +/** + * g_variant_get_bytestring: + * @value: an array-of-bytes #GVariant instance + * @returns: the constant string + * + * Returns the string value of a #GVariant instance with an + * array-of-bytes type. The string has no particular encoding. + * + * If the array does not end with a nul terminator character, the empty + * string is returned. For this reason, you can always trust that a + * non-%NULL nul-terminated string will be returned by this function. + * + * If the array contains a nul terminator character somewhere other than + * the last byte then the returned string is the string, up to the first + * such nul character. + * + * It is an error to call this function with a @value that is not an + * array of bytes. + * + * The return value remains valid as long as @value exists. + * + * Since: 2.26 + **/ +const gchar * +g_variant_get_bytestring (GVariant *value) +{ + const gchar *string; + gsize size; + + TYPE_CHECK (value, G_VARIANT_TYPE_BYTESTRING, NULL); + + /* Won't be NULL since this is an array type */ + string = g_variant_get_data (value); + size = g_variant_get_size (value); + + if (string[size - 1] == '\0') + return string; + else + return ""; +} + +/** + * g_variant_dup_bytestring: + * @value: an array-of-bytes #GVariant instance + * @length: (allow-none) (default NULL): a pointer to a #gsize, to store + * the length (not including the nul terminator) + * @returns: a newly allocated string + * + * Similar to g_variant_get_bytestring() except that instead of + * returning a constant string, the string is duplicated. + * + * The return value must be freed using g_free(). + * + * Since: 2.26 + **/ +gchar * +g_variant_dup_bytestring (GVariant *value, + gsize *length) +{ + const gchar *original = g_variant_get_bytestring (value); + gsize size; + + /* don't crash in case get_bytestring() had an assert failure */ + if (original == NULL) + return NULL; + + size = strlen (original); + + if (length) + *length = size; + + return g_memdup (original, size + 1); +} + +/** + * g_variant_new_bytestring_array: + * @strv (array length=length): an array of strings + * @length: the length of @strv, or -1 + * @returns: a new floating #GVariant instance + * + * Constructs an array of bytestring #GVariant from the given array of + * strings. + * + * If @length is -1 then @strv is %NULL-terminated. + * + * Since: 2.26 + **/ +GVariant * +g_variant_new_bytestring_array (const gchar * const *strv, + gssize length) +{ + GVariant **strings; + gsize i; + + g_return_val_if_fail (length == 0 || strv != NULL, NULL); + + if (length < 0) + length = g_strv_length ((gchar **) strv); + + strings = g_new (GVariant *, length); + for (i = 0; i < length; i++) + strings[i] = g_variant_ref_sink (g_variant_new_bytestring (strv[i])); + + return g_variant_new_from_children (G_VARIANT_TYPE_BYTESTRING_ARRAY, + strings, length, TRUE); +} + +/** + * g_variant_get_bytestring_array: + * @value: an array of array of bytes #GVariant ('aay') + * @length: (allow-none): the length of the result, or %NULL + * @returns: (array length=length): an array of constant strings + * + * Gets the contents of an array of array of bytes #GVariant. This call + * makes a shallow copy; the return result should be released with + * g_free(), but the individual strings must not be modified. + * + * If @length is non-%NULL then the number of elements in the result is + * stored there. In any case, the resulting array will be + * %NULL-terminated. + * + * For an empty array, @length will be set to 0 and a pointer to a + * %NULL pointer will be returned. + * + * Since: 2.26 + **/ +const gchar ** +g_variant_get_bytestring_array (GVariant *value, + gsize *length) +{ + const gchar **strv; + gsize n; + gsize i; + + TYPE_CHECK (value, G_VARIANT_TYPE_BYTESTRING_ARRAY, NULL); + + g_variant_get_data (value); + n = g_variant_n_children (value); + strv = g_new (const gchar *, n + 1); + + for (i = 0; i < n; i++) + { + GVariant *string; + + string = g_variant_get_child_value (value, i); + strv[i] = g_variant_get_bytestring (string); + g_variant_unref (string); + } + strv[i] = NULL; + + if (length) + *length = n; + + return strv; +} + +/** + * g_variant_dup_bytestring_array: + * @value: an array of array of bytes #GVariant ('aay') + * @length: (allow-none): the length of the result, or %NULL + * @returns: (array length=length): an array of strings + * + * Gets the contents of an array of array of bytes #GVariant. This call + * makes a deep copy; the return result should be released with + * g_strfreev(). + * + * If @length is non-%NULL then the number of elements in the result is + * stored there. In any case, the resulting array will be + * %NULL-terminated. + * + * For an empty array, @length will be set to 0 and a pointer to a + * %NULL pointer will be returned. + * + * Since: 2.26 + **/ +gchar ** +g_variant_dup_bytestring_array (GVariant *value, + gsize *length) +{ + gchar **strv; + gsize n; + gsize i; + + TYPE_CHECK (value, G_VARIANT_TYPE_BYTESTRING_ARRAY, NULL); + + g_variant_get_data (value); + n = g_variant_n_children (value); + strv = g_new (gchar *, n + 1); + + for (i = 0; i < n; i++) + { + GVariant *string; + + string = g_variant_get_child_value (value, i); + strv[i] = g_variant_dup_bytestring (string, NULL); + g_variant_unref (string); + } + strv[i] = NULL; + + if (length) + *length = n; + + return strv; +} + /* Type checking and querying {{{1 */ /** * g_variant_get_type: @@ -1573,6 +1715,45 @@ g_variant_print_string (GVariant *value, case G_VARIANT_CLASS_ARRAY: /* it's an array so the first character of the type string is 'a' * + * if the first two characters are 'ay' then it's a bytestring. + * under certain conditions we print those as strings. + */ + if (g_variant_get_type_string (value)[1] == 'y') + { + const gchar *str; + gsize size; + gsize i; + + /* first determine if it is a byte string. + * that's when there's a single nul character: at the end. + */ + str = g_variant_get_data (value); + size = g_variant_get_size (value); + + for (i = 0; i < size; i++) + if (str[i] == '\0') + break; + + /* first nul byte is the last byte -> it's a byte string. */ + if (i == size - 1) + { + gchar *escaped = g_strescape (str, NULL); + + /* use double quotes only if a ' is in the string */ + if (strchr (str, '\'')) + g_string_append_printf (string, "b\"%s\"", escaped); + else + g_string_append_printf (string, "b'%s'", escaped); + + g_free (escaped); + break; + } + + else + /* fall through and handle normally... */; + } + + /* * if the first two characters are 'a{' then it's an array of * dictionary entries (ie: a dictionary) so we print that * differently. @@ -3015,19 +3196,43 @@ g_variant_format_string_scan (const gchar *string, break; - case '^': /* '^as' or '^a&s' only */ - if (next_char() != 'a') - return FALSE; + case '^': + if ((c = next_char()) == 'a') + { + if ((c = next_char()) == '&') + { + if ((c = next_char()) == 'a') + { + if ((c = next_char()) == 'y') + break; /* '^a&ay' */ + } - if (peek_char() == '&') - next_char (); + else if (c == 's') + break; /* '^a&s' */ + } - c = next_char (); + else if (c == 'a') + { + if ((c = next_char()) == 'y') + break; /* '^aay' */ + } - if (c != 's' && c != 'o' && c != 'g') - return FALSE; + else if (c == 's') + break; /* '^as' */ - break; + else if (c == 'y') + break; /* '^ay' */ + } + else if (c == '&') + { + if ((c = next_char()) == 'a') + { + if ((c = next_char()) == 'y') + break; /* '^&ay' */ + } + } + + return FALSE; case '&': c = next_char(); @@ -3242,6 +3447,29 @@ g_variant_valist_free_nnp (const gchar *str, } } +static gchar +g_variant_scan_convenience (const gchar **str, + gboolean *constant, + guint *arrays) +{ + *constant = FALSE; + *arrays = 0; + + for (;;) + { + char c = *(*str)++; + + if (c == '&') + *constant = TRUE; + + else if (c == 'a') + (*arrays)++; + + else + return c; + } +} + static GVariant * g_variant_valist_new_nnp (const gchar **str, gpointer ptr) @@ -3288,31 +3516,16 @@ g_variant_valist_new_nnp (const gchar **str, case '^': { - const GVariantType *type; - GVariantType *array_type; - GVariant **children; - gchar **strv = ptr; - GVariant *value; - guint length, i; + gboolean constant; + guint arrays; - if ((*str)[1] == '&') /* '^a&s' */ - (*str) += 2; - else /* '^as' */ - (*str)++; + if (g_variant_scan_convenience (str, &constant, &arrays) == 's') + return g_variant_new_strv (ptr, -1); - type = (GVariantType *) (*str)++; - array_type = g_variant_type_new_array (type); - length = g_strv_length (strv); - children = g_new (GVariant *, length); - for (i = 0; i < length; i++) - children[i] = g_variant_ref_sink ( - g_variant_new_from_trusted (type, strv[i], strlen (strv[i]) + 1)); + if (arrays > 1) + return g_variant_new_bytestring_array (ptr, -1); - value = g_variant_new_from_children (array_type, children, - length, TRUE); - g_variant_type_free (array_type); - - return value; + return g_variant_new_bytestring (ptr); } case '@': @@ -3373,16 +3586,34 @@ g_variant_valist_get_nnp (const gchar **str, return g_variant_dup_string (value, NULL); case '^': - if ((*str)[1] == '&') /* '^a&s' */ - { - (*str) += 3; - return g_variant_get_strv (value, NULL); - } - else /* '^as' */ - { - (*str) += 2; - return g_variant_dup_strv (value, NULL); - } + { + gboolean constant; + guint arrays; + + if (g_variant_scan_convenience (str, &constant, &arrays) == 's') + { + if (constant) + return g_variant_get_strv (value, NULL); + else + return g_variant_dup_strv (value, NULL); + } + + else if (arrays > 1) + { + if (constant) + return g_variant_get_bytestring_array (value, NULL); + else + return g_variant_dup_bytestring_array (value, NULL); + } + + else + { + if (constant) + return (gchar *) g_variant_get_bytestring (value); + else + return g_variant_dup_bytestring (value, NULL); + } + } case '@': g_variant_type_string_scan (*str, NULL, str); diff --git a/glib/gvariant.h b/glib/gvariant.h index aa270f6f2..528492b68 100644 --- a/glib/gvariant.h +++ b/glib/gvariant.h @@ -83,10 +83,11 @@ gboolean g_variant_is_object_path (const g GVariant * g_variant_new_signature (const gchar *signature); gboolean g_variant_is_signature (const gchar *string); GVariant * g_variant_new_variant (GVariant *value); -GVariant * g_variant_new_byte_array (gconstpointer array, - gssize length); GVariant * g_variant_new_strv (const gchar * const *strv, gssize length); +GVariant * g_variant_new_bytestring (const gchar *string); +GVariant * g_variant_new_bytestring_array (const gchar * const *strv, + gssize length); gboolean g_variant_get_boolean (GVariant *value); guchar g_variant_get_byte (GVariant *value); @@ -103,12 +104,17 @@ const gchar * g_variant_get_string (GVarian gsize *length); gchar * g_variant_dup_string (GVariant *value, gsize *length); -gconstpointer g_variant_get_byte_array (GVariant *value, - gsize *length); const gchar ** g_variant_get_strv (GVariant *value, gsize *length); gchar ** g_variant_dup_strv (GVariant *value, gsize *length); +const gchar * g_variant_get_bytestring (GVariant *value); +gchar * g_variant_dup_bytestring (GVariant *value, + gsize *length); +const gchar ** g_variant_get_bytestring_array (GVariant *value, + gsize *length); +gchar ** g_variant_dup_bytestring_array (GVariant *value, + gsize *length); GVariant * g_variant_new_maybe (const GVariantType *child_type, GVariant *child); diff --git a/glib/gvarianttype.h b/glib/gvarianttype.h index 2203ccbb3..124fa46b5 100644 --- a/glib/gvarianttype.h +++ b/glib/gvarianttype.h @@ -231,6 +231,31 @@ typedef struct _GVariantType GVariantType; **/ #define G_VARIANT_TYPE_DICTIONARY ((const GVariantType *) "a{?*}") +/** + * G_VARIANT_TYPE_STRING_ARRAY: + * + * The type of an array of strings. + **/ +#define G_VARIANT_TYPE_STRING_ARRAY ((const GVariantType *) "as") + +/** + * G_VARIANT_TYPE_BYTESTRING: + * + * The type of an array of bytes. This type is commonly used to pass + * around strings that may not be valid utf8. In that case, the + * convention is that the nul terminator character should be included as + * the last character in the array. + **/ +#define G_VARIANT_TYPE_BYTESTRING ((const GVariantType *) "ay") + +/** + * G_VARIANT_TYPE_BYTESTRING_ARRAY: + * + * The type of an array of byte strings (an array of arrays of bytes). + **/ +#define G_VARIANT_TYPE_BYTESTRING_ARRAY ((const GVariantType *) "aay") + + /** * G_VARIANT_TYPE: * @type_string: a well-formed #GVariantType type string diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c index 23302f89c..7da3830b4 100644 --- a/glib/tests/gvariant.c +++ b/glib/tests/gvariant.c @@ -2981,16 +2981,16 @@ test_varargs (void) gchar *str; gint i; - g_variant_builder_init (&builder, G_VARIANT_TYPE ("ao")); - g_variant_builder_add (&builder, "o", "/foo"); - g_variant_builder_add (&builder, "o", "/bar"); - g_variant_builder_add (&builder, "o", "/baz"); - value = g_variant_new("(ao^ao^a&o)", &builder, strvector, strvector); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); + g_variant_builder_add (&builder, "s", "/foo"); + g_variant_builder_add (&builder, "s", "/bar"); + g_variant_builder_add (&builder, "s", "/baz"); + value = g_variant_new("(as^as^a&s)", &builder, strvector, strvector); g_variant_iter_init (&tuple, value); - g_variant_iter_next (&tuple, "ao", &array); + g_variant_iter_next (&tuple, "as", &array); i = 0; - while (g_variant_iter_loop (array, "o", &str)) + while (g_variant_iter_loop (array, "s", &str)) g_assert_cmpstr (str, ==, test_strs[i++]); g_assert (i == 3); @@ -2998,17 +2998,17 @@ test_varargs (void) /* start over */ g_variant_iter_init (&tuple, value); - g_variant_iter_next (&tuple, "ao", &array); + g_variant_iter_next (&tuple, "as", &array); i = 0; - while (g_variant_iter_loop (array, "&o", &str)) + while (g_variant_iter_loop (array, "&s", &str)) g_assert_cmpstr (str, ==, test_strs[i++]); g_assert (i == 3); g_variant_iter_free (array); - g_variant_iter_next (&tuple, "^a&o", &strv); - g_variant_iter_next (&tuple, "^ao", &my_strv); + g_variant_iter_next (&tuple, "^a&s", &strv); + g_variant_iter_next (&tuple, "^as", &my_strv); g_assert_cmpstr (strv[0], ==, "/hello"); g_assert_cmpstr (strv[1], ==, "/world"); @@ -3033,46 +3033,46 @@ test_varargs (void) gchar **strv; gint i; - g_variant_builder_init (&builder, G_VARIANT_TYPE ("aag")); - g_variant_builder_open (&builder, G_VARIANT_TYPE ("ag")); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("aas")); + g_variant_builder_open (&builder, G_VARIANT_TYPE ("as")); for (i = 0; i < 6; i++) if (i & 1) - g_variant_builder_add (&builder, "g", strvector[i]); + g_variant_builder_add (&builder, "s", strvector[i]); else - g_variant_builder_add (&builder, "&g", strvector[i]); + g_variant_builder_add (&builder, "&s", strvector[i]); g_variant_builder_close (&builder); - g_variant_builder_add (&builder, "^ag", strvector); - g_variant_builder_add (&builder, "^ag", strvector); - value = g_variant_new ("aag", &builder); + g_variant_builder_add (&builder, "^as", strvector); + g_variant_builder_add (&builder, "^as", strvector); + value = g_variant_new ("aas", &builder); g_variant_iter_init (&iter, value); - while (g_variant_iter_loop (&iter, "^ag", &strv)) + while (g_variant_iter_loop (&iter, "^as", &strv)) for (i = 0; i < 6; i++) g_assert_cmpstr (strv[i], ==, strvector[i]); g_variant_iter_init (&iter, value); - while (g_variant_iter_loop (&iter, "^a&g", &strv)) + while (g_variant_iter_loop (&iter, "^a&s", &strv)) for (i = 0; i < 6; i++) g_assert_cmpstr (strv[i], ==, strvector[i]); g_variant_iter_init (&iter, value); - while (g_variant_iter_loop (&iter, "ag", &i2)) + while (g_variant_iter_loop (&iter, "as", &i2)) { gchar *str; i = 0; - while (g_variant_iter_loop (i2, "g", &str)) + while (g_variant_iter_loop (i2, "s", &str)) g_assert_cmpstr (str, ==, strvector[i++]); g_assert (i == 6); } g_variant_iter_init (&iter, value); i3 = g_variant_iter_copy (&iter); - while (g_variant_iter_loop (&iter, "@ag", &sub)) + while (g_variant_iter_loop (&iter, "@as", &sub)) { gchar *str = g_variant_print (sub, TRUE); g_assert_cmpstr (str, ==, - "[signature 'i', 'ii', 'iii', 'iv', 'v', 'vi']"); + "['i', 'ii', 'iii', 'iv', 'v', 'vi']"); g_free (str); } @@ -3087,7 +3087,7 @@ test_varargs (void) { gchar *str = g_variant_print (sub, TRUE); g_assert_cmpstr (str, ==, - "[signature 'i', 'ii', 'iii', 'iv', 'v', 'vi']"); + "['i', 'ii', 'iii', 'iv', 'v', 'vi']"); g_free (str); } @@ -3104,11 +3104,11 @@ test_varargs (void) const gchar *str = NULL; GVariant *cval; - g_variant_get_child (sub, j, "&g", &str); + g_variant_get_child (sub, j, "&s", &str); g_assert_cmpstr (str, ==, strvector[j]); cval = g_variant_get_child_value (sub, j); - g_variant_get (cval, "&g", &str); + g_variant_get (cval, "&s", &str); g_assert_cmpstr (str, ==, strvector[j]); g_variant_unref (cval); } @@ -3812,6 +3812,80 @@ test_floating (void) g_variant_unref (value); } +static void +test_bytestring (void) +{ + const gchar *test_string = "foo,bar,baz,quux,\xffoooo"; + GVariant *value; + gchar **strv; + gchar *str; + + strv = g_strsplit (test_string, ",", 0); + + value = g_variant_new_bytestring_array ((const gchar **) strv, -1); + g_assert (g_variant_is_floating (value)); + g_strfreev (strv); + + str = g_variant_print (value, FALSE); + g_variant_unref (value); + + value = g_variant_parse (NULL, str, NULL, NULL, NULL); + g_free (str); + + strv = g_variant_dup_bytestring_array (value, NULL); + g_variant_unref (value); + + str = g_strjoinv (",", strv); + g_strfreev (strv); + + g_assert_cmpstr (str, ==, test_string); + g_free (str); + + strv = g_strsplit (test_string, ",", 0); + value = g_variant_new ("(^aay^a&ay^ay^&ay)", + strv, strv, strv[0], strv[0]); + g_strfreev (strv); + + g_variant_get_child (value, 0, "^a&ay", &strv); + str = g_strjoinv (",", strv); + g_free (strv); + g_assert_cmpstr (str, ==, test_string); + g_free (str); + + g_variant_get_child (value, 0, "^aay", &strv); + str = g_strjoinv (",", strv); + g_strfreev (strv); + g_assert_cmpstr (str, ==, test_string); + g_free (str); + + g_variant_get_child (value, 1, "^a&ay", &strv); + str = g_strjoinv (",", strv); + g_free (strv); + g_assert_cmpstr (str, ==, test_string); + g_free (str); + + g_variant_get_child (value, 1, "^aay", &strv); + str = g_strjoinv (",", strv); + g_strfreev (strv); + g_assert_cmpstr (str, ==, test_string); + g_free (str); + + g_variant_get_child (value, 2, "^ay", &str); + g_assert_cmpstr (str, ==, "foo"); + g_free (str); + + g_variant_get_child (value, 2, "^&ay", &str); + g_assert_cmpstr (str, ==, "foo"); + + g_variant_get_child (value, 3, "^ay", &str); + g_assert_cmpstr (str, ==, "foo"); + g_free (str); + + g_variant_get_child (value, 3, "^&ay", &str); + g_assert_cmpstr (str, ==, "foo"); + g_variant_unref (value); +} + int main (int argc, char **argv) { @@ -3851,6 +3925,7 @@ main (int argc, char **argv) g_test_add_func ("/gvariant/parse-failures", test_parse_failures); g_test_add_func ("/gvariant/parse-positional", test_parse_positional); g_test_add_func ("/gvariant/floating", test_floating); + g_test_add_func ("/gvariant/bytestring", test_bytestring); return g_test_run (); }