| /* GDBus - GLib D-Bus Library | |
| * | |
| * Copyright (C) 2008-2010 Red Hat, Inc. | |
| * | |
| * SPDX-License-Identifier: LGPL-2.1-or-later | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * | |
| * This library is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General | |
| * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. | |
| * | |
| * Author: David Zeuthen <davidz@redhat.com> | |
| */ | |
| /* Uncomment to debug serializer code */ | |
| /* #define DEBUG_SERIALIZER */ | |
| #include "config.h" | |
| #include <string.h> | |
| #include <errno.h> | |
| #include <sys/types.h> | |
| #include <sys/stat.h> | |
| #if MAJOR_IN_MKDEV | |
| #include <sys/mkdev.h> | |
| #elif MAJOR_IN_SYSMACROS | |
| #include <sys/sysmacros.h> | |
| #elif MAJOR_IN_TYPES | |
| #include <sys/types.h> | |
| #else | |
| #define MAJOR_MINOR_NOT_FOUND 1 | |
| #endif | |
| #include "gdbusutils.h" | |
| #include "gdbusmessage.h" | |
| #include "gdbuserror.h" | |
| #include "gioenumtypes.h" | |
| #include "ginputstream.h" | |
| #include "gdatainputstream.h" | |
| #include "gmemoryinputstream.h" | |
| #include "goutputstream.h" | |
| #include "gdataoutputstream.h" | |
| #include "gmemoryoutputstream.h" | |
| #include "gseekable.h" | |
| #include "gioerror.h" | |
| #include "gdbusprivate.h" | |
| #include "gutilsprivate.h" | |
| #ifdef G_OS_UNIX | |
| #include "gunixfdlist.h" | |
| #endif | |
| #include "glibintl.h" | |
| /* See https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-signature | |
| * This is 64 containers plus 1 value within them. */ | |
| #define G_DBUS_MAX_TYPE_DEPTH (64 + 1) | |
| typedef struct _GMemoryBuffer GMemoryBuffer; | |
| struct _GMemoryBuffer | |
| { | |
| gsize len; | |
| gsize valid_len; | |
| gsize pos; | |
| gchar *data; | |
| GDataStreamByteOrder byte_order; | |
| }; | |
| static gboolean | |
| g_memory_buffer_is_byteswapped (GMemoryBuffer *mbuf) | |
| { | |
| #if G_BYTE_ORDER == G_LITTLE_ENDIAN | |
| return mbuf->byte_order == G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN; | |
| #else | |
| return mbuf->byte_order == G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN; | |
| #endif | |
| } | |
| static guchar | |
| g_memory_buffer_read_byte (GMemoryBuffer *mbuf, | |
| GError **error) | |
| { | |
| g_return_val_if_fail (error == NULL || *error == NULL, 0); | |
| if (mbuf->pos >= mbuf->valid_len) | |
| { | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| "Unexpected end of message while reading byte."); | |
| return 0; | |
| } | |
| return mbuf->data [mbuf->pos++]; | |
| } | |
| static gint16 | |
| g_memory_buffer_read_int16 (GMemoryBuffer *mbuf, | |
| GError **error) | |
| { | |
| gint16 v; | |
| g_return_val_if_fail (error == NULL || *error == NULL, -1); | |
| if (mbuf->pos > mbuf->valid_len - 2) | |
| { | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| "Unexpected end of message while reading int16."); | |
| return 0; | |
| } | |
| memcpy (&v, mbuf->data + mbuf->pos, 2); | |
| mbuf->pos += 2; | |
| if (g_memory_buffer_is_byteswapped (mbuf)) | |
| v = GUINT16_SWAP_LE_BE (v); | |
| return v; | |
| } | |
| static guint16 | |
| g_memory_buffer_read_uint16 (GMemoryBuffer *mbuf, | |
| GError **error) | |
| { | |
| guint16 v; | |
| g_return_val_if_fail (error == NULL || *error == NULL, 0); | |
| if (mbuf->pos > mbuf->valid_len - 2) | |
| { | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| "Unexpected end of message while reading uint16."); | |
| return 0; | |
| } | |
| memcpy (&v, mbuf->data + mbuf->pos, 2); | |
| mbuf->pos += 2; | |
| if (g_memory_buffer_is_byteswapped (mbuf)) | |
| v = GUINT16_SWAP_LE_BE (v); | |
| return v; | |
| } | |
| static gint32 | |
| g_memory_buffer_read_int32 (GMemoryBuffer *mbuf, | |
| GError **error) | |
| { | |
| gint32 v; | |
| g_return_val_if_fail (error == NULL || *error == NULL, -1); | |
| if (mbuf->pos > mbuf->valid_len - 4) | |
| { | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| "Unexpected end of message while reading int32."); | |
| return 0; | |
| } | |
| memcpy (&v, mbuf->data + mbuf->pos, 4); | |
| mbuf->pos += 4; | |
| if (g_memory_buffer_is_byteswapped (mbuf)) | |
| v = GUINT32_SWAP_LE_BE (v); | |
| return v; | |
| } | |
| static guint32 | |
| g_memory_buffer_read_uint32 (GMemoryBuffer *mbuf, | |
| GError **error) | |
| { | |
| guint32 v; | |
| g_return_val_if_fail (error == NULL || *error == NULL, 0); | |
| if (mbuf->pos > mbuf->valid_len - 4) | |
| { | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| "Unexpected end of message while reading uint32."); | |
| return 0; | |
| } | |
| memcpy (&v, mbuf->data + mbuf->pos, 4); | |
| mbuf->pos += 4; | |
| if (g_memory_buffer_is_byteswapped (mbuf)) | |
| v = GUINT32_SWAP_LE_BE (v); | |
| return v; | |
| } | |
| static gint64 | |
| g_memory_buffer_read_int64 (GMemoryBuffer *mbuf, | |
| GError **error) | |
| { | |
| gint64 v; | |
| g_return_val_if_fail (error == NULL || *error == NULL, -1); | |
| if (mbuf->pos > mbuf->valid_len - 8) | |
| { | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| "Unexpected end of message while reading int64."); | |
| return 0; | |
| } | |
| memcpy (&v, mbuf->data + mbuf->pos, 8); | |
| mbuf->pos += 8; | |
| if (g_memory_buffer_is_byteswapped (mbuf)) | |
| v = GUINT64_SWAP_LE_BE (v); | |
| return v; | |
| } | |
| static guint64 | |
| g_memory_buffer_read_uint64 (GMemoryBuffer *mbuf, | |
| GError **error) | |
| { | |
| guint64 v; | |
| g_return_val_if_fail (error == NULL || *error == NULL, 0); | |
| if (mbuf->pos > mbuf->valid_len - 8) | |
| { | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| "Unexpected end of message while reading uint64."); | |
| return 0; | |
| } | |
| memcpy (&v, mbuf->data + mbuf->pos, 8); | |
| mbuf->pos += 8; | |
| if (g_memory_buffer_is_byteswapped (mbuf)) | |
| v = GUINT64_SWAP_LE_BE (v); | |
| return v; | |
| } | |
| #define MIN_ARRAY_SIZE 128 | |
| static void | |
| array_resize (GMemoryBuffer *mbuf, | |
| gsize size) | |
| { | |
| gpointer data; | |
| gsize len; | |
| if (mbuf->len == size) | |
| return; | |
| len = mbuf->len; | |
| data = g_realloc (mbuf->data, size); | |
| if (size > len) | |
| memset ((guint8 *)data + len, 0, size - len); | |
| mbuf->data = data; | |
| mbuf->len = size; | |
| if (mbuf->len < mbuf->valid_len) | |
| mbuf->valid_len = mbuf->len; | |
| } | |
| static gboolean | |
| g_memory_buffer_write (GMemoryBuffer *mbuf, | |
| const void *buffer, | |
| gsize count) | |
| { | |
| guint8 *dest; | |
| gsize new_size; | |
| if (count == 0) | |
| return TRUE; | |
| /* Check for address space overflow, but only if the buffer is resizable. | |
| Otherwise we just do a short write and don't worry. */ | |
| if (mbuf->pos + count < mbuf->pos) | |
| return FALSE; | |
| if (mbuf->pos + count > mbuf->len) | |
| { | |
| /* At least enough to fit the write, rounded up | |
| for greater than linear growth. | |
| TODO: This wastes a lot of memory at large buffer sizes. | |
| Figure out a more rational allocation strategy. */ | |
| new_size = g_nearest_pow (mbuf->pos + count); | |
| /* Check for overflow again. We have checked if | |
| pos + count > G_MAXSIZE, but now check if g_nearest_pow () has | |
| overflowed */ | |
| if (new_size == 0) | |
| return FALSE; | |
| new_size = MAX (new_size, MIN_ARRAY_SIZE); | |
| array_resize (mbuf, new_size); | |
| } | |
| dest = (guint8 *)mbuf->data + mbuf->pos; | |
| memcpy (dest, buffer, count); | |
| mbuf->pos += count; | |
| if (mbuf->pos > mbuf->valid_len) | |
| mbuf->valid_len = mbuf->pos; | |
| return TRUE; | |
| } | |
| static gboolean | |
| g_memory_buffer_put_byte (GMemoryBuffer *mbuf, | |
| guchar data) | |
| { | |
| return g_memory_buffer_write (mbuf, &data, 1); | |
| } | |
| static gboolean | |
| g_memory_buffer_put_int16 (GMemoryBuffer *mbuf, | |
| gint16 data) | |
| { | |
| switch (mbuf->byte_order) | |
| { | |
| case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: | |
| data = GINT16_TO_BE (data); | |
| break; | |
| case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: | |
| data = GINT16_TO_LE (data); | |
| break; | |
| case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: | |
| default: | |
| break; | |
| } | |
| return g_memory_buffer_write (mbuf, &data, 2); | |
| } | |
| static gboolean | |
| g_memory_buffer_put_uint16 (GMemoryBuffer *mbuf, | |
| guint16 data) | |
| { | |
| switch (mbuf->byte_order) | |
| { | |
| case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: | |
| data = GUINT16_TO_BE (data); | |
| break; | |
| case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: | |
| data = GUINT16_TO_LE (data); | |
| break; | |
| case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: | |
| default: | |
| break; | |
| } | |
| return g_memory_buffer_write (mbuf, &data, 2); | |
| } | |
| static gboolean | |
| g_memory_buffer_put_int32 (GMemoryBuffer *mbuf, | |
| gint32 data) | |
| { | |
| switch (mbuf->byte_order) | |
| { | |
| case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: | |
| data = GINT32_TO_BE (data); | |
| break; | |
| case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: | |
| data = GINT32_TO_LE (data); | |
| break; | |
| case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: | |
| default: | |
| break; | |
| } | |
| return g_memory_buffer_write (mbuf, &data, 4); | |
| } | |
| static gboolean | |
| g_memory_buffer_put_uint32 (GMemoryBuffer *mbuf, | |
| guint32 data) | |
| { | |
| switch (mbuf->byte_order) | |
| { | |
| case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: | |
| data = GUINT32_TO_BE (data); | |
| break; | |
| case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: | |
| data = GUINT32_TO_LE (data); | |
| break; | |
| case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: | |
| default: | |
| break; | |
| } | |
| return g_memory_buffer_write (mbuf, &data, 4); | |
| } | |
| static gboolean | |
| g_memory_buffer_put_int64 (GMemoryBuffer *mbuf, | |
| gint64 data) | |
| { | |
| switch (mbuf->byte_order) | |
| { | |
| case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: | |
| data = GINT64_TO_BE (data); | |
| break; | |
| case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: | |
| data = GINT64_TO_LE (data); | |
| break; | |
| case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: | |
| default: | |
| break; | |
| } | |
| return g_memory_buffer_write (mbuf, &data, 8); | |
| } | |
| static gboolean | |
| g_memory_buffer_put_uint64 (GMemoryBuffer *mbuf, | |
| guint64 data) | |
| { | |
| switch (mbuf->byte_order) | |
| { | |
| case G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN: | |
| data = GUINT64_TO_BE (data); | |
| break; | |
| case G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN: | |
| data = GUINT64_TO_LE (data); | |
| break; | |
| case G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN: | |
| default: | |
| break; | |
| } | |
| return g_memory_buffer_write (mbuf, &data, 8); | |
| } | |
| static gboolean | |
| g_memory_buffer_put_string (GMemoryBuffer *mbuf, | |
| const char *str) | |
| { | |
| g_return_val_if_fail (str != NULL, FALSE); | |
| return g_memory_buffer_write (mbuf, str, strlen (str)); | |
| } | |
| typedef struct _GDBusMessageClass GDBusMessageClass; | |
| /** | |
| * GDBusMessageClass: | |
| * | |
| * Class structure for #GDBusMessage. | |
| * | |
| * Since: 2.26 | |
| */ | |
| struct _GDBusMessageClass | |
| { | |
| /*< private >*/ | |
| GObjectClass parent_class; | |
| }; | |
| /** | |
| * GDBusMessage: | |
| * | |
| * A type for representing D-Bus messages that can be sent or received | |
| * on a [class@Gio.DBusConnection]. | |
| * | |
| * Since: 2.26 | |
| */ | |
| struct _GDBusMessage | |
| { | |
| /*< private >*/ | |
| GObject parent_instance; | |
| GDBusMessageType type; | |
| GDBusMessageFlags flags; | |
| gboolean locked; | |
| GDBusMessageByteOrder byte_order; | |
| guchar major_protocol_version; | |
| guint32 serial; | |
| GHashTable *headers; | |
| GVariant *body; | |
| GVariant *arg0_cache; /* (nullable) (owned) */ | |
| #ifdef G_OS_UNIX | |
| GUnixFDList *fd_list; | |
| #endif | |
| }; | |
| enum | |
| { | |
| PROP_0, | |
| PROP_LOCKED | |
| }; | |
| G_DEFINE_TYPE (GDBusMessage, g_dbus_message, G_TYPE_OBJECT) | |
| static void | |
| g_dbus_message_finalize (GObject *object) | |
| { | |
| GDBusMessage *message = G_DBUS_MESSAGE (object); | |
| if (message->headers != NULL) | |
| g_hash_table_unref (message->headers); | |
| if (message->body != NULL) | |
| g_variant_unref (message->body); | |
| g_clear_pointer (&message->arg0_cache, g_variant_unref); | |
| #ifdef G_OS_UNIX | |
| if (message->fd_list != NULL) | |
| g_object_unref (message->fd_list); | |
| #endif | |
| if (G_OBJECT_CLASS (g_dbus_message_parent_class)->finalize != NULL) | |
| G_OBJECT_CLASS (g_dbus_message_parent_class)->finalize (object); | |
| } | |
| static void | |
| g_dbus_message_get_property (GObject *object, | |
| guint prop_id, | |
| GValue *value, | |
| GParamSpec *pspec) | |
| { | |
| GDBusMessage *message = G_DBUS_MESSAGE (object); | |
| switch (prop_id) | |
| { | |
| case PROP_LOCKED: | |
| g_value_set_boolean (value, g_dbus_message_get_locked (message)); | |
| break; | |
| default: | |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); | |
| break; | |
| } | |
| } | |
| static void | |
| g_dbus_message_class_init (GDBusMessageClass *klass) | |
| { | |
| GObjectClass *gobject_class; | |
| gobject_class = G_OBJECT_CLASS (klass); | |
| gobject_class->finalize = g_dbus_message_finalize; | |
| gobject_class->get_property = g_dbus_message_get_property; | |
| /** | |
| * GDBusConnection:locked: | |
| * | |
| * A boolean specifying whether the message is locked. | |
| * | |
| * Since: 2.26 | |
| */ | |
| g_object_class_install_property (gobject_class, | |
| PROP_LOCKED, | |
| g_param_spec_boolean ("locked", NULL, NULL, | |
| FALSE, | |
| G_PARAM_READABLE | | |
| G_PARAM_STATIC_NAME | | |
| G_PARAM_STATIC_BLURB | | |
| G_PARAM_STATIC_NICK)); | |
| } | |
| static void | |
| g_dbus_message_init (GDBusMessage *message) | |
| { | |
| /* Any D-Bus implementation is supposed to handle both Big and | |
| * Little Endian encodings and the Endianness is part of the D-Bus | |
| * message - we prefer to use Big Endian (since it's Network Byte | |
| * Order and just easier to read for humans) but if the machine is | |
| * Little Endian we use that for performance reasons. | |
| */ | |
| #if G_BYTE_ORDER == G_LITTLE_ENDIAN | |
| message->byte_order = G_DBUS_MESSAGE_BYTE_ORDER_LITTLE_ENDIAN; | |
| #else | |
| /* this could also be G_PDP_ENDIAN */ | |
| message->byte_order = G_DBUS_MESSAGE_BYTE_ORDER_BIG_ENDIAN; | |
| #endif | |
| message->headers = g_hash_table_new_full (g_direct_hash, | |
| g_direct_equal, | |
| NULL, | |
| (GDestroyNotify) g_variant_unref); | |
| } | |
| /** | |
| * g_dbus_message_new: | |
| * | |
| * Creates a new empty #GDBusMessage. | |
| * | |
| * Returns: A #GDBusMessage. Free with g_object_unref(). | |
| * | |
| * Since: 2.26 | |
| */ | |
| GDBusMessage * | |
| g_dbus_message_new (void) | |
| { | |
| return g_object_new (G_TYPE_DBUS_MESSAGE, NULL); | |
| } | |
| /** | |
| * g_dbus_message_new_method_call: | |
| * @name: (nullable): A valid D-Bus name or %NULL. | |
| * @path: A valid object path. | |
| * @interface_: (nullable): A valid D-Bus interface name or %NULL. | |
| * @method: A valid method name. | |
| * | |
| * Creates a new #GDBusMessage for a method call. | |
| * | |
| * Returns: A #GDBusMessage. Free with g_object_unref(). | |
| * | |
| * Since: 2.26 | |
| */ | |
| GDBusMessage * | |
| g_dbus_message_new_method_call (const gchar *name, | |
| const gchar *path, | |
| const gchar *interface_, | |
| const gchar *method) | |
| { | |
| GDBusMessage *message; | |
| g_return_val_if_fail (name == NULL || g_dbus_is_name (name), NULL); | |
| g_return_val_if_fail (g_variant_is_object_path (path), NULL); | |
| g_return_val_if_fail (g_dbus_is_member_name (method), NULL); | |
| g_return_val_if_fail (interface_ == NULL || g_dbus_is_interface_name (interface_), NULL); | |
| message = g_dbus_message_new (); | |
| message->type = G_DBUS_MESSAGE_TYPE_METHOD_CALL; | |
| if (name != NULL) | |
| g_dbus_message_set_destination (message, name); | |
| g_dbus_message_set_path (message, path); | |
| g_dbus_message_set_member (message, method); | |
| if (interface_ != NULL) | |
| g_dbus_message_set_interface (message, interface_); | |
| return message; | |
| } | |
| /** | |
| * g_dbus_message_new_signal: | |
| * @path: A valid object path. | |
| * @interface_: A valid D-Bus interface name. | |
| * @signal: A valid signal name. | |
| * | |
| * Creates a new #GDBusMessage for a signal emission. | |
| * | |
| * Returns: A #GDBusMessage. Free with g_object_unref(). | |
| * | |
| * Since: 2.26 | |
| */ | |
| GDBusMessage * | |
| g_dbus_message_new_signal (const gchar *path, | |
| const gchar *interface_, | |
| const gchar *signal) | |
| { | |
| GDBusMessage *message; | |
| g_return_val_if_fail (g_variant_is_object_path (path), NULL); | |
| g_return_val_if_fail (g_dbus_is_member_name (signal), NULL); | |
| g_return_val_if_fail (g_dbus_is_interface_name (interface_), NULL); | |
| message = g_dbus_message_new (); | |
| message->type = G_DBUS_MESSAGE_TYPE_SIGNAL; | |
| message->flags = G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED; | |
| g_dbus_message_set_path (message, path); | |
| g_dbus_message_set_member (message, signal); | |
| g_dbus_message_set_interface (message, interface_); | |
| return message; | |
| } | |
| /** | |
| * g_dbus_message_new_method_reply: | |
| * @method_call_message: A message of type %G_DBUS_MESSAGE_TYPE_METHOD_CALL to | |
| * create a reply message to. | |
| * | |
| * Creates a new #GDBusMessage that is a reply to @method_call_message. | |
| * | |
| * Returns: (transfer full): #GDBusMessage. Free with g_object_unref(). | |
| * | |
| * Since: 2.26 | |
| */ | |
| GDBusMessage * | |
| g_dbus_message_new_method_reply (GDBusMessage *method_call_message) | |
| { | |
| GDBusMessage *message; | |
| const gchar *sender; | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (method_call_message), NULL); | |
| g_return_val_if_fail (g_dbus_message_get_message_type (method_call_message) == G_DBUS_MESSAGE_TYPE_METHOD_CALL, NULL); | |
| g_return_val_if_fail (g_dbus_message_get_serial (method_call_message) != 0, NULL); | |
| message = g_dbus_message_new (); | |
| message->type = G_DBUS_MESSAGE_TYPE_METHOD_RETURN; | |
| message->flags = G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED; | |
| /* reply with same endianness */ | |
| message->byte_order = method_call_message->byte_order; | |
| g_dbus_message_set_reply_serial (message, g_dbus_message_get_serial (method_call_message)); | |
| sender = g_dbus_message_get_sender (method_call_message); | |
| if (sender != NULL) | |
| g_dbus_message_set_destination (message, sender); | |
| return message; | |
| } | |
| /** | |
| * g_dbus_message_new_method_error: | |
| * @method_call_message: A message of type %G_DBUS_MESSAGE_TYPE_METHOD_CALL to | |
| * create a reply message to. | |
| * @error_name: A valid D-Bus error name. | |
| * @error_message_format: The D-Bus error message in a printf() format. | |
| * @...: Arguments for @error_message_format. | |
| * | |
| * Creates a new #GDBusMessage that is an error reply to @method_call_message. | |
| * | |
| * Returns: (transfer full): A #GDBusMessage. Free with g_object_unref(). | |
| * | |
| * Since: 2.26 | |
| */ | |
| GDBusMessage * | |
| g_dbus_message_new_method_error (GDBusMessage *method_call_message, | |
| const gchar *error_name, | |
| const gchar *error_message_format, | |
| ...) | |
| { | |
| GDBusMessage *ret; | |
| va_list var_args; | |
| va_start (var_args, error_message_format); | |
| ret = g_dbus_message_new_method_error_valist (method_call_message, | |
| error_name, | |
| error_message_format, | |
| var_args); | |
| va_end (var_args); | |
| return ret; | |
| } | |
| /** | |
| * g_dbus_message_new_method_error_literal: | |
| * @method_call_message: A message of type %G_DBUS_MESSAGE_TYPE_METHOD_CALL to | |
| * create a reply message to. | |
| * @error_name: A valid D-Bus error name. | |
| * @error_message: The D-Bus error message. | |
| * | |
| * Creates a new #GDBusMessage that is an error reply to @method_call_message. | |
| * | |
| * Returns: (transfer full): A #GDBusMessage. Free with g_object_unref(). | |
| * | |
| * Since: 2.26 | |
| */ | |
| GDBusMessage * | |
| g_dbus_message_new_method_error_literal (GDBusMessage *method_call_message, | |
| const gchar *error_name, | |
| const gchar *error_message) | |
| { | |
| GDBusMessage *message; | |
| const gchar *sender; | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (method_call_message), NULL); | |
| g_return_val_if_fail (g_dbus_message_get_message_type (method_call_message) == G_DBUS_MESSAGE_TYPE_METHOD_CALL, NULL); | |
| g_return_val_if_fail (g_dbus_message_get_serial (method_call_message) != 0, NULL); | |
| g_return_val_if_fail (g_dbus_is_name (error_name), NULL); | |
| g_return_val_if_fail (error_message != NULL, NULL); | |
| message = g_dbus_message_new (); | |
| message->type = G_DBUS_MESSAGE_TYPE_ERROR; | |
| message->flags = G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED; | |
| /* reply with same endianness */ | |
| message->byte_order = method_call_message->byte_order; | |
| g_dbus_message_set_reply_serial (message, g_dbus_message_get_serial (method_call_message)); | |
| g_dbus_message_set_error_name (message, error_name); | |
| g_dbus_message_set_body (message, g_variant_new ("(s)", error_message)); | |
| sender = g_dbus_message_get_sender (method_call_message); | |
| if (sender != NULL) | |
| g_dbus_message_set_destination (message, sender); | |
| return message; | |
| } | |
| /** | |
| * g_dbus_message_new_method_error_valist: | |
| * @method_call_message: A message of type %G_DBUS_MESSAGE_TYPE_METHOD_CALL to | |
| * create a reply message to. | |
| * @error_name: A valid D-Bus error name. | |
| * @error_message_format: The D-Bus error message in a printf() format. | |
| * @var_args: Arguments for @error_message_format. | |
| * | |
| * Like g_dbus_message_new_method_error() but intended for language bindings. | |
| * | |
| * Returns: (transfer full): A #GDBusMessage. Free with g_object_unref(). | |
| * | |
| * Since: 2.26 | |
| */ | |
| G_GNUC_PRINTF(3, 0) | |
| GDBusMessage * | |
| g_dbus_message_new_method_error_valist (GDBusMessage *method_call_message, | |
| const gchar *error_name, | |
| const gchar *error_message_format, | |
| va_list var_args) | |
| { | |
| GDBusMessage *ret; | |
| gchar *error_message; | |
| error_message = g_strdup_vprintf (error_message_format, var_args); | |
| ret = g_dbus_message_new_method_error_literal (method_call_message, | |
| error_name, | |
| error_message); | |
| g_free (error_message); | |
| return ret; | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /** | |
| * g_dbus_message_get_byte_order: | |
| * @message: A #GDBusMessage. | |
| * | |
| * Gets the byte order of @message. | |
| * | |
| * Returns: The byte order. | |
| */ | |
| GDBusMessageByteOrder | |
| g_dbus_message_get_byte_order (GDBusMessage *message) | |
| { | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), (GDBusMessageByteOrder) 0); | |
| return message->byte_order; | |
| } | |
| /** | |
| * g_dbus_message_set_byte_order: | |
| * @message: A #GDBusMessage. | |
| * @byte_order: The byte order. | |
| * | |
| * Sets the byte order of @message. | |
| */ | |
| void | |
| g_dbus_message_set_byte_order (GDBusMessage *message, | |
| GDBusMessageByteOrder byte_order) | |
| { | |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); | |
| if (message->locked) | |
| { | |
| g_warning ("%s: Attempted to modify a locked message", G_STRFUNC); | |
| return; | |
| } | |
| message->byte_order = byte_order; | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /* TODO: need GI annotations to specify that any guchar value goes for the type */ | |
| /** | |
| * g_dbus_message_get_message_type: | |
| * @message: A #GDBusMessage. | |
| * | |
| * Gets the type of @message. | |
| * | |
| * Returns: A 8-bit unsigned integer (typically a value from the #GDBusMessageType enumeration). | |
| * | |
| * Since: 2.26 | |
| */ | |
| GDBusMessageType | |
| g_dbus_message_get_message_type (GDBusMessage *message) | |
| { | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), G_DBUS_MESSAGE_TYPE_INVALID); | |
| return message->type; | |
| } | |
| /** | |
| * g_dbus_message_set_message_type: | |
| * @message: A #GDBusMessage. | |
| * @type: A 8-bit unsigned integer (typically a value from the #GDBusMessageType enumeration). | |
| * | |
| * Sets @message to be of @type. | |
| * | |
| * Since: 2.26 | |
| */ | |
| void | |
| g_dbus_message_set_message_type (GDBusMessage *message, | |
| GDBusMessageType type) | |
| { | |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); | |
| g_return_if_fail ((guint) type < 256); | |
| if (message->locked) | |
| { | |
| g_warning ("%s: Attempted to modify a locked message", G_STRFUNC); | |
| return; | |
| } | |
| message->type = type; | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /* TODO: need GI annotations to specify that any guchar value goes for flags */ | |
| /** | |
| * g_dbus_message_get_flags: | |
| * @message: A #GDBusMessage. | |
| * | |
| * Gets the flags for @message. | |
| * | |
| * Returns: Flags that are set (typically values from the #GDBusMessageFlags enumeration bitwise ORed together). | |
| * | |
| * Since: 2.26 | |
| */ | |
| GDBusMessageFlags | |
| g_dbus_message_get_flags (GDBusMessage *message) | |
| { | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), G_DBUS_MESSAGE_FLAGS_NONE); | |
| return message->flags; | |
| } | |
| /** | |
| * g_dbus_message_set_flags: | |
| * @message: A #GDBusMessage. | |
| * @flags: Flags for @message that are set (typically values from the #GDBusMessageFlags | |
| * enumeration bitwise ORed together). | |
| * | |
| * Sets the flags to set on @message. | |
| * | |
| * Since: 2.26 | |
| */ | |
| void | |
| g_dbus_message_set_flags (GDBusMessage *message, | |
| GDBusMessageFlags flags) | |
| { | |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); | |
| g_return_if_fail ((guint) flags < 256); | |
| if (message->locked) | |
| { | |
| g_warning ("%s: Attempted to modify a locked message", G_STRFUNC); | |
| return; | |
| } | |
| message->flags = flags; | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /** | |
| * g_dbus_message_get_serial: | |
| * @message: A #GDBusMessage. | |
| * | |
| * Gets the serial for @message. | |
| * | |
| * Returns: A #guint32. | |
| * | |
| * Since: 2.26 | |
| */ | |
| guint32 | |
| g_dbus_message_get_serial (GDBusMessage *message) | |
| { | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), 0); | |
| return message->serial; | |
| } | |
| /** | |
| * g_dbus_message_set_serial: | |
| * @message: A #GDBusMessage. | |
| * @serial: A #guint32. | |
| * | |
| * Sets the serial for @message. | |
| * | |
| * Since: 2.26 | |
| */ | |
| void | |
| g_dbus_message_set_serial (GDBusMessage *message, | |
| guint32 serial) | |
| { | |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); | |
| if (message->locked) | |
| { | |
| g_warning ("%s: Attempted to modify a locked message", G_STRFUNC); | |
| return; | |
| } | |
| message->serial = serial; | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /* TODO: need GI annotations to specify that any guchar value goes for header_field */ | |
| /** | |
| * g_dbus_message_get_header: | |
| * @message: A #GDBusMessage. | |
| * @header_field: A 8-bit unsigned integer (typically a value from the #GDBusMessageHeaderField enumeration) | |
| * | |
| * Gets a header field on @message. | |
| * | |
| * The caller is responsible for checking the type of the returned #GVariant | |
| * matches what is expected. | |
| * | |
| * Returns: (transfer none) (nullable): A #GVariant with the value if the header was found, %NULL | |
| * otherwise. Do not free, it is owned by @message. | |
| * | |
| * Since: 2.26 | |
| */ | |
| GVariant * | |
| g_dbus_message_get_header (GDBusMessage *message, | |
| GDBusMessageHeaderField header_field) | |
| { | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); | |
| g_return_val_if_fail ((guint) header_field < 256, NULL); | |
| return g_hash_table_lookup (message->headers, GUINT_TO_POINTER (header_field)); | |
| } | |
| /** | |
| * g_dbus_message_set_header: | |
| * @message: A #GDBusMessage. | |
| * @header_field: A 8-bit unsigned integer (typically a value from the #GDBusMessageHeaderField enumeration) | |
| * @value: (nullable): A #GVariant to set the header field or %NULL to clear the header field. | |
| * | |
| * Sets a header field on @message. | |
| * | |
| * If @value is floating, @message assumes ownership of @value. | |
| * | |
| * Since: 2.26 | |
| */ | |
| void | |
| g_dbus_message_set_header (GDBusMessage *message, | |
| GDBusMessageHeaderField header_field, | |
| GVariant *value) | |
| { | |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); | |
| g_return_if_fail ((guint) header_field < 256); | |
| if (message->locked) | |
| { | |
| g_warning ("%s: Attempted to modify a locked message", G_STRFUNC); | |
| return; | |
| } | |
| if (value == NULL) | |
| { | |
| g_hash_table_remove (message->headers, GUINT_TO_POINTER (header_field)); | |
| } | |
| else | |
| { | |
| g_hash_table_insert (message->headers, GUINT_TO_POINTER (header_field), g_variant_ref_sink (value)); | |
| } | |
| } | |
| /** | |
| * g_dbus_message_get_header_fields: | |
| * @message: A #GDBusMessage. | |
| * | |
| * Gets an array of all header fields on @message that are set. | |
| * | |
| * Returns: (array zero-terminated=1): An array of header fields | |
| * terminated by %G_DBUS_MESSAGE_HEADER_FIELD_INVALID. Each element | |
| * is a #guchar. Free with g_free(). | |
| * | |
| * Since: 2.26 | |
| */ | |
| guchar * | |
| g_dbus_message_get_header_fields (GDBusMessage *message) | |
| { | |
| GPtrArray *keys; | |
| GArray *array; | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); | |
| keys = g_hash_table_get_keys_as_ptr_array (message->headers); | |
| array = g_array_sized_new (FALSE, FALSE, sizeof (guchar), keys->len + 1); | |
| for (guint i = 0; i < keys->len; ++i) | |
| { | |
| guchar val = GPOINTER_TO_UINT (g_ptr_array_index (keys, i)); | |
| g_array_append_val (array, val); | |
| } | |
| g_assert (array->len == keys->len); | |
| g_clear_pointer (&keys, g_ptr_array_unref); | |
| guchar invalid_field = G_DBUS_MESSAGE_HEADER_FIELD_INVALID; | |
| g_array_append_val (array, invalid_field); | |
| return (guchar *) g_array_free (array, FALSE); | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /** | |
| * g_dbus_message_get_body: | |
| * @message: A #GDBusMessage. | |
| * | |
| * Gets the body of a message. | |
| * | |
| * Returns: (nullable) (transfer none): A #GVariant or %NULL if the body is | |
| * empty. Do not free, it is owned by @message. | |
| * | |
| * Since: 2.26 | |
| */ | |
| GVariant * | |
| g_dbus_message_get_body (GDBusMessage *message) | |
| { | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); | |
| return message->body; | |
| } | |
| /** | |
| * g_dbus_message_set_body: | |
| * @message: A #GDBusMessage. | |
| * @body: Either %NULL or a #GVariant that is a tuple. | |
| * | |
| * Sets the body @message. As a side-effect the | |
| * %G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE header field is set to the | |
| * type string of @body (or cleared if @body is %NULL). | |
| * | |
| * If @body is floating, @message assumes ownership of @body. | |
| * | |
| * Since: 2.26 | |
| */ | |
| void | |
| g_dbus_message_set_body (GDBusMessage *message, | |
| GVariant *body) | |
| { | |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); | |
| g_return_if_fail ((body == NULL) || g_variant_is_of_type (body, G_VARIANT_TYPE_TUPLE)); | |
| if (message->locked) | |
| { | |
| g_warning ("%s: Attempted to modify a locked message", G_STRFUNC); | |
| return; | |
| } | |
| if (message->body != NULL) | |
| g_variant_unref (message->body); | |
| g_clear_pointer (&message->arg0_cache, g_variant_unref); | |
| if (body == NULL) | |
| { | |
| message->body = NULL; | |
| g_dbus_message_set_signature (message, NULL); | |
| } | |
| else | |
| { | |
| const gchar *type_string; | |
| gsize type_string_len; | |
| gchar *signature; | |
| message->body = g_variant_ref_sink (body); | |
| if (g_variant_is_of_type (message->body, G_VARIANT_TYPE_TUPLE) && | |
| g_variant_n_children (message->body) > 0) | |
| message->arg0_cache = g_variant_get_child_value (message->body, 0); | |
| type_string = g_variant_get_type_string (body); | |
| type_string_len = strlen (type_string); | |
| g_assert (type_string_len >= 2); | |
| signature = g_strndup (type_string + 1, type_string_len - 2); | |
| g_dbus_message_set_signature (message, signature); | |
| g_free (signature); | |
| } | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| #ifdef G_OS_UNIX | |
| /** | |
| * g_dbus_message_get_unix_fd_list: | |
| * @message: A #GDBusMessage. | |
| * | |
| * Gets the UNIX file descriptors associated with @message, if any. | |
| * | |
| * This method is only available on UNIX. | |
| * | |
| * The file descriptors normally correspond to %G_VARIANT_TYPE_HANDLE | |
| * values in the body of the message. For example, | |
| * if g_variant_get_handle() returns 5, that is intended to be a reference | |
| * to the file descriptor that can be accessed by | |
| * `g_unix_fd_list_get (list, 5, ...)`. | |
| * | |
| * Returns: (nullable) (transfer none): A #GUnixFDList or %NULL if no file descriptors are | |
| * associated. Do not free, this object is owned by @message. | |
| * | |
| * Since: 2.26 | |
| */ | |
| GUnixFDList * | |
| g_dbus_message_get_unix_fd_list (GDBusMessage *message) | |
| { | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); | |
| return message->fd_list; | |
| } | |
| /** | |
| * g_dbus_message_set_unix_fd_list: | |
| * @message: A #GDBusMessage. | |
| * @fd_list: (nullable): A #GUnixFDList or %NULL. | |
| * | |
| * Sets the UNIX file descriptors associated with @message. As a | |
| * side-effect the %G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS header | |
| * field is set to the number of fds in @fd_list (or cleared if | |
| * @fd_list is %NULL). | |
| * | |
| * This method is only available on UNIX. | |
| * | |
| * When designing D-Bus APIs that are intended to be interoperable, | |
| * please note that non-GDBus implementations of D-Bus can usually only | |
| * access file descriptors if they are referenced by a value of type | |
| * %G_VARIANT_TYPE_HANDLE in the body of the message. | |
| * | |
| * Since: 2.26 | |
| */ | |
| void | |
| g_dbus_message_set_unix_fd_list (GDBusMessage *message, | |
| GUnixFDList *fd_list) | |
| { | |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); | |
| g_return_if_fail (fd_list == NULL || G_IS_UNIX_FD_LIST (fd_list)); | |
| if (message->locked) | |
| { | |
| g_warning ("%s: Attempted to modify a locked message", G_STRFUNC); | |
| return; | |
| } | |
| if (message->fd_list != NULL) | |
| g_object_unref (message->fd_list); | |
| if (fd_list != NULL) | |
| { | |
| message->fd_list = g_object_ref (fd_list); | |
| g_dbus_message_set_num_unix_fds (message, g_unix_fd_list_get_length (fd_list)); | |
| } | |
| else | |
| { | |
| message->fd_list = NULL; | |
| g_dbus_message_set_num_unix_fds (message, 0); | |
| } | |
| } | |
| #endif | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| static guint | |
| get_type_fixed_size (const GVariantType *type) | |
| { | |
| /* NB: we do not treat 'b' as fixed-size here because GVariant and | |
| * D-Bus disagree about the size. | |
| */ | |
| switch (*g_variant_type_peek_string (type)) | |
| { | |
| case 'y': | |
| return 1; | |
| case 'n': case 'q': | |
| return 2; | |
| case 'i': case 'u': case 'h': | |
| return 4; | |
| case 'x': case 't': case 'd': | |
| return 8; | |
| default: | |
| return 0; | |
| } | |
| } | |
| static const char * | |
| message_type_to_string (GDBusMessageType message_type) | |
| { | |
| switch (message_type) | |
| { | |
| case G_DBUS_MESSAGE_TYPE_INVALID: | |
| return "INVALID"; | |
| case G_DBUS_MESSAGE_TYPE_METHOD_CALL: | |
| return "METHOD_CALL"; | |
| case G_DBUS_MESSAGE_TYPE_METHOD_RETURN: | |
| return "METHOD_RETURN"; | |
| case G_DBUS_MESSAGE_TYPE_ERROR: | |
| return "ERROR"; | |
| case G_DBUS_MESSAGE_TYPE_SIGNAL: | |
| return "SIGNAL"; | |
| default: | |
| return "unknown-type"; | |
| } | |
| } | |
| static const char * | |
| message_header_field_to_string (GDBusMessageHeaderField field) | |
| { | |
| switch (field) | |
| { | |
| case G_DBUS_MESSAGE_HEADER_FIELD_INVALID: | |
| return "INVALID"; | |
| case G_DBUS_MESSAGE_HEADER_FIELD_PATH: | |
| return "PATH"; | |
| case G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE: | |
| return "INTERFACE"; | |
| case G_DBUS_MESSAGE_HEADER_FIELD_MEMBER: | |
| return "MEMBER"; | |
| case G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME: | |
| return "ERROR_NAME"; | |
| case G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL: | |
| return "REPLY_SERIAL"; | |
| case G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION: | |
| return "DESTINATION"; | |
| case G_DBUS_MESSAGE_HEADER_FIELD_SENDER: | |
| return "SENDER"; | |
| case G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE: | |
| return "SIGNATURE"; | |
| case G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS: | |
| return "NUM_UNIX_FDS"; | |
| default: | |
| return "unknown-field"; | |
| } | |
| } | |
| static gboolean | |
| validate_header (GDBusMessage *message, | |
| GDBusMessageHeaderField field, | |
| GVariant *header_value, | |
| const GVariantType *expected_type, | |
| GError **error) | |
| { | |
| g_assert (header_value != NULL); | |
| if (!g_variant_is_of_type (header_value, expected_type)) | |
| { | |
| char *expected_type_string = g_variant_type_dup_string (expected_type); | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("%s message: %s header field is invalid; expected a value of type ‘%s’"), | |
| message_type_to_string (message->type), | |
| message_header_field_to_string (field), | |
| expected_type_string); | |
| g_free (expected_type_string); | |
| return FALSE; | |
| } | |
| return TRUE; | |
| } | |
| static gboolean | |
| require_header (GDBusMessage *message, | |
| GDBusMessageHeaderField field, | |
| GError **error) | |
| { | |
| GVariant *header_value = g_dbus_message_get_header (message, field); | |
| if (header_value == NULL) | |
| { | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("%s message: %s header field is missing or invalid"), | |
| message_type_to_string (message->type), | |
| message_header_field_to_string (field)); | |
| return FALSE; | |
| } | |
| return TRUE; | |
| } | |
| /* Implement the validation rules given in | |
| * https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-header-fields */ | |
| static gboolean | |
| validate_headers (GDBusMessage *message, | |
| GError **error) | |
| { | |
| gboolean ret; | |
| GHashTableIter headers_iter; | |
| gpointer key; | |
| GVariant *header_value; | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), FALSE); | |
| g_return_val_if_fail (error == NULL || *error == NULL, FALSE); | |
| ret = FALSE; | |
| /* Validate the types of all known headers. */ | |
| g_hash_table_iter_init (&headers_iter, message->headers); | |
| while (g_hash_table_iter_next (&headers_iter, &key, (gpointer) &header_value)) | |
| { | |
| GDBusMessageHeaderField field_type = GPOINTER_TO_INT (key); | |
| switch (field_type) | |
| { | |
| case G_DBUS_MESSAGE_HEADER_FIELD_INVALID: | |
| /* The invalid header must be rejected as per | |
| * https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-header-fields */ | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("%s message: INVALID header field supplied"), | |
| message_type_to_string (message->type)); | |
| goto out; | |
| case G_DBUS_MESSAGE_HEADER_FIELD_PATH: | |
| if (!validate_header (message, field_type, header_value, G_VARIANT_TYPE_OBJECT_PATH, error)) | |
| goto out; | |
| if (g_strcmp0 (g_variant_get_string (header_value, NULL), DBUS_PATH_LOCAL) == 0) | |
| { | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| /* Translators: The first placeholder is a D-Bus message type, | |
| * the second is the name of a header field and the third is | |
| * a value that is reserved for the given field. */ | |
| _("%s message: %s header field is using the reserved value %s"), | |
| message_type_to_string (message->type), | |
| "PATH", | |
| DBUS_PATH_LOCAL); | |
| goto out; | |
| } | |
| break; | |
| case G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE: | |
| if (!validate_header (message, field_type, header_value, G_VARIANT_TYPE_STRING, error)) | |
| goto out; | |
| if (!g_dbus_is_interface_name (g_variant_get_string (header_value, NULL))) | |
| { | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("%s message: INTERFACE header field does not contain a valid interface name"), | |
| message_type_to_string (message->type)); | |
| goto out; | |
| } | |
| if (g_strcmp0 (g_variant_get_string (header_value, NULL), DBUS_INTERFACE_LOCAL) == 0) | |
| { | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("%s message: %s header field is using the reserved value %s"), | |
| message_type_to_string (message->type), | |
| "INTERFACE", | |
| DBUS_INTERFACE_LOCAL); | |
| goto out; | |
| } | |
| break; | |
| case G_DBUS_MESSAGE_HEADER_FIELD_MEMBER: | |
| if (!validate_header (message, field_type, header_value, G_VARIANT_TYPE_STRING, error)) | |
| goto out; | |
| if (!g_dbus_is_member_name (g_variant_get_string (header_value, NULL))) | |
| { | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("%s message: MEMBER header field does not contain a valid member name"), | |
| message_type_to_string (message->type)); | |
| goto out; | |
| } | |
| break; | |
| case G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME: | |
| if (!validate_header (message, field_type, header_value, G_VARIANT_TYPE_STRING, error)) | |
| goto out; | |
| if (!g_dbus_is_error_name (g_variant_get_string (header_value, NULL))) | |
| { | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("%s message: ERROR_NAME header field does not contain a valid error name"), | |
| message_type_to_string (message->type)); | |
| goto out; | |
| } | |
| break; | |
| case G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL: | |
| if (!validate_header (message, field_type, header_value, G_VARIANT_TYPE_UINT32, error)) | |
| goto out; | |
| break; | |
| case G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION: | |
| if (!validate_header (message, field_type, header_value, G_VARIANT_TYPE_STRING, error)) | |
| goto out; | |
| break; | |
| case G_DBUS_MESSAGE_HEADER_FIELD_SENDER: | |
| if (!validate_header (message, field_type, header_value, G_VARIANT_TYPE_STRING, error)) | |
| goto out; | |
| break; | |
| case G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE: | |
| if (!validate_header (message, field_type, header_value, G_VARIANT_TYPE_SIGNATURE, error)) | |
| goto out; | |
| break; | |
| case G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS: | |
| if (!validate_header (message, field_type, header_value, G_VARIANT_TYPE_UINT32, error)) | |
| goto out; | |
| break; | |
| default: | |
| /* Ignore unknown fields as per | |
| * https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-header-fields. */ | |
| continue; | |
| } | |
| } | |
| /* Check for message-type-specific required headers. */ | |
| switch (message->type) | |
| { | |
| case G_DBUS_MESSAGE_TYPE_INVALID: | |
| g_set_error_literal (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("type is INVALID")); | |
| goto out; | |
| case G_DBUS_MESSAGE_TYPE_METHOD_CALL: | |
| if (!require_header (message, G_DBUS_MESSAGE_HEADER_FIELD_PATH, error) || | |
| !require_header (message, G_DBUS_MESSAGE_HEADER_FIELD_MEMBER, error)) | |
| goto out; | |
| break; | |
| case G_DBUS_MESSAGE_TYPE_METHOD_RETURN: | |
| if (!require_header (message, G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL, error)) | |
| goto out; | |
| break; | |
| case G_DBUS_MESSAGE_TYPE_ERROR: | |
| if (!require_header (message, G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME, error) || | |
| !require_header (message, G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL, error)) | |
| goto out; | |
| break; | |
| case G_DBUS_MESSAGE_TYPE_SIGNAL: | |
| if (!require_header (message, G_DBUS_MESSAGE_HEADER_FIELD_PATH, error) || | |
| !require_header (message, G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE, error) || | |
| !require_header (message, G_DBUS_MESSAGE_HEADER_FIELD_MEMBER, error)) | |
| goto out; | |
| break; | |
| default: | |
| /* hitherto unknown type - nothing to check */ | |
| break; | |
| } | |
| ret = TRUE; | |
| out: | |
| g_assert (ret || (error == NULL || *error != NULL)); | |
| return ret; | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| static gboolean | |
| ensure_input_padding (GMemoryBuffer *buf, | |
| gsize padding_size) | |
| { | |
| gsize offset; | |
| gsize wanted_offset; | |
| offset = buf->pos; | |
| wanted_offset = ((offset + padding_size - 1) / padding_size) * padding_size; | |
| buf->pos = wanted_offset; | |
| return TRUE; | |
| } | |
| static const gchar * | |
| read_string (GMemoryBuffer *mbuf, | |
| gsize len, | |
| GError **error) | |
| { | |
| gchar *str; | |
| const gchar *end_valid; | |
| if G_UNLIKELY (mbuf->pos + len >= mbuf->valid_len || mbuf->pos + len < mbuf->pos) | |
| { | |
| mbuf->pos = mbuf->valid_len; | |
| /* G_GSIZE_FORMAT doesn't work with gettext, so we use %lu */ | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| g_dngettext (GETTEXT_PACKAGE, | |
| "Wanted to read %lu byte but only got %lu", | |
| "Wanted to read %lu bytes but only got %lu", | |
| (gulong)len), | |
| (gulong)len, | |
| (gulong)(mbuf->valid_len - mbuf->pos)); | |
| return NULL; | |
| } | |
| if G_UNLIKELY (mbuf->data[mbuf->pos + len] != '\0') | |
| { | |
| str = g_strndup (mbuf->data + mbuf->pos, len); | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Expected NUL byte after the string “%s” but found byte %d"), | |
| str, mbuf->data[mbuf->pos + len]); | |
| g_free (str); | |
| mbuf->pos += len + 1; | |
| return NULL; | |
| } | |
| str = mbuf->data + mbuf->pos; | |
| mbuf->pos += len + 1; | |
| if G_UNLIKELY (!g_utf8_validate (str, -1, &end_valid)) | |
| { | |
| gint offset; | |
| gchar *valid_str; | |
| offset = (gint) (end_valid - str); | |
| valid_str = g_strndup (str, offset); | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Expected valid UTF-8 string but found invalid bytes at byte offset %d (length of string is %d). " | |
| "The valid UTF-8 string up until that point was “%s”"), | |
| offset, | |
| (gint) len, | |
| valid_str); | |
| g_free (valid_str); | |
| return NULL; | |
| } | |
| return str; | |
| } | |
| static gconstpointer | |
| read_bytes (GMemoryBuffer *mbuf, | |
| gsize len, | |
| GError **error) | |
| { | |
| gconstpointer result; | |
| if G_UNLIKELY (mbuf->pos + len > mbuf->valid_len || mbuf->pos + len < mbuf->pos) | |
| { | |
| mbuf->pos = mbuf->valid_len; | |
| /* G_GSIZE_FORMAT doesn't work with gettext, so we use %lu */ | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| g_dngettext (GETTEXT_PACKAGE, | |
| "Wanted to read %lu byte but only got %lu", | |
| "Wanted to read %lu bytes but only got %lu", | |
| (gulong)len), | |
| (gulong)len, | |
| (gulong)(mbuf->valid_len - mbuf->pos)); | |
| return NULL; | |
| } | |
| result = mbuf->data + mbuf->pos; | |
| mbuf->pos += len; | |
| return result; | |
| } | |
| /* if just_align==TRUE, don't read a value, just align the input stream wrt padding */ | |
| /* returns a non-floating GVariant! */ | |
| static GVariant * | |
| parse_value_from_blob (GMemoryBuffer *buf, | |
| const GVariantType *type, | |
| guint max_depth, | |
| gboolean just_align, | |
| guint indent, | |
| GError **error) | |
| { | |
| GVariant *ret = NULL; | |
| GError *local_error = NULL; | |
| #ifdef DEBUG_SERIALIZER | |
| gboolean is_leaf; | |
| #endif /* DEBUG_SERIALIZER */ | |
| const gchar *type_string; | |
| if (max_depth == 0) | |
| { | |
| g_set_error_literal (&local_error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Value nested too deeply")); | |
| goto fail; | |
| } | |
| type_string = g_variant_type_peek_string (type); | |
| #ifdef DEBUG_SERIALIZER | |
| { | |
| gchar *s; | |
| s = g_variant_type_dup_string (type); | |
| g_print ("%*s%s type %s from offset 0x%04x", | |
| indent, "", | |
| just_align ? "Aligning" : "Reading", | |
| s, | |
| (gint) buf->pos); | |
| g_free (s); | |
| } | |
| #endif /* DEBUG_SERIALIZER */ | |
| #ifdef DEBUG_SERIALIZER | |
| is_leaf = TRUE; | |
| #endif /* DEBUG_SERIALIZER */ | |
| switch (type_string[0]) | |
| { | |
| case 'b': /* G_VARIANT_TYPE_BOOLEAN */ | |
| ensure_input_padding (buf, 4); | |
| if (!just_align) | |
| { | |
| gboolean v; | |
| v = g_memory_buffer_read_uint32 (buf, &local_error); | |
| if (local_error) | |
| goto fail; | |
| ret = g_variant_new_boolean (v); | |
| } | |
| break; | |
| case 'y': /* G_VARIANT_TYPE_BYTE */ | |
| if (!just_align) | |
| { | |
| guchar v; | |
| v = g_memory_buffer_read_byte (buf, &local_error); | |
| if (local_error) | |
| goto fail; | |
| ret = g_variant_new_byte (v); | |
| } | |
| break; | |
| case 'n': /* G_VARIANT_TYPE_INT16 */ | |
| ensure_input_padding (buf, 2); | |
| if (!just_align) | |
| { | |
| gint16 v; | |
| v = g_memory_buffer_read_int16 (buf, &local_error); | |
| if (local_error) | |
| goto fail; | |
| ret = g_variant_new_int16 (v); | |
| } | |
| break; | |
| case 'q': /* G_VARIANT_TYPE_UINT16 */ | |
| ensure_input_padding (buf, 2); | |
| if (!just_align) | |
| { | |
| guint16 v; | |
| v = g_memory_buffer_read_uint16 (buf, &local_error); | |
| if (local_error) | |
| goto fail; | |
| ret = g_variant_new_uint16 (v); | |
| } | |
| break; | |
| case 'i': /* G_VARIANT_TYPE_INT32 */ | |
| ensure_input_padding (buf, 4); | |
| if (!just_align) | |
| { | |
| gint32 v; | |
| v = g_memory_buffer_read_int32 (buf, &local_error); | |
| if (local_error) | |
| goto fail; | |
| ret = g_variant_new_int32 (v); | |
| } | |
| break; | |
| case 'u': /* G_VARIANT_TYPE_UINT32 */ | |
| ensure_input_padding (buf, 4); | |
| if (!just_align) | |
| { | |
| guint32 v; | |
| v = g_memory_buffer_read_uint32 (buf, &local_error); | |
| if (local_error) | |
| goto fail; | |
| ret = g_variant_new_uint32 (v); | |
| } | |
| break; | |
| case 'x': /* G_VARIANT_TYPE_INT64 */ | |
| ensure_input_padding (buf, 8); | |
| if (!just_align) | |
| { | |
| gint64 v; | |
| v = g_memory_buffer_read_int64 (buf, &local_error); | |
| if (local_error) | |
| goto fail; | |
| ret = g_variant_new_int64 (v); | |
| } | |
| break; | |
| case 't': /* G_VARIANT_TYPE_UINT64 */ | |
| ensure_input_padding (buf, 8); | |
| if (!just_align) | |
| { | |
| guint64 v; | |
| v = g_memory_buffer_read_uint64 (buf, &local_error); | |
| if (local_error) | |
| goto fail; | |
| ret = g_variant_new_uint64 (v); | |
| } | |
| break; | |
| case 'd': /* G_VARIANT_TYPE_DOUBLE */ | |
| ensure_input_padding (buf, 8); | |
| if (!just_align) | |
| { | |
| union { | |
| guint64 v_uint64; | |
| gdouble v_double; | |
| } u; | |
| G_STATIC_ASSERT (sizeof (gdouble) == sizeof (guint64)); | |
| u.v_uint64 = g_memory_buffer_read_uint64 (buf, &local_error); | |
| if (local_error) | |
| goto fail; | |
| ret = g_variant_new_double (u.v_double); | |
| } | |
| break; | |
| case 's': /* G_VARIANT_TYPE_STRING */ | |
| ensure_input_padding (buf, 4); | |
| if (!just_align) | |
| { | |
| guint32 len; | |
| const gchar *v; | |
| len = g_memory_buffer_read_uint32 (buf, &local_error); | |
| if (local_error) | |
| goto fail; | |
| v = read_string (buf, (gsize) len, &local_error); | |
| if (v == NULL) | |
| goto fail; | |
| ret = g_variant_new_string (v); | |
| } | |
| break; | |
| case 'o': /* G_VARIANT_TYPE_OBJECT_PATH */ | |
| ensure_input_padding (buf, 4); | |
| if (!just_align) | |
| { | |
| guint32 len; | |
| const gchar *v; | |
| len = g_memory_buffer_read_uint32 (buf, &local_error); | |
| if (local_error) | |
| goto fail; | |
| v = read_string (buf, (gsize) len, &local_error); | |
| if (v == NULL) | |
| goto fail; | |
| if (!g_variant_is_object_path (v)) | |
| { | |
| g_set_error (&local_error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Parsed value “%s” is not a valid D-Bus object path"), | |
| v); | |
| goto fail; | |
| } | |
| ret = g_variant_new_object_path (v); | |
| } | |
| break; | |
| case 'g': /* G_VARIANT_TYPE_SIGNATURE */ | |
| if (!just_align) | |
| { | |
| guchar len; | |
| const gchar *v; | |
| len = g_memory_buffer_read_byte (buf, &local_error); | |
| if (local_error) | |
| goto fail; | |
| v = read_string (buf, (gsize) len, &local_error); | |
| if (v == NULL) | |
| goto fail; | |
| if (!g_variant_is_signature (v)) | |
| { | |
| g_set_error (&local_error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Parsed value “%s” is not a valid D-Bus signature"), | |
| v); | |
| goto fail; | |
| } | |
| ret = g_variant_new_signature (v); | |
| } | |
| break; | |
| case 'h': /* G_VARIANT_TYPE_HANDLE */ | |
| ensure_input_padding (buf, 4); | |
| if (!just_align) | |
| { | |
| gint32 v; | |
| v = g_memory_buffer_read_int32 (buf, &local_error); | |
| if (local_error) | |
| goto fail; | |
| ret = g_variant_new_handle (v); | |
| } | |
| break; | |
| case 'a': /* G_VARIANT_TYPE_ARRAY */ | |
| ensure_input_padding (buf, 4); | |
| /* If we are only aligning for this array type, it is the child type of | |
| * another array, which is empty. So, we do not need to add padding for | |
| * this nonexistent array's elements: we only need to align for this | |
| * array itself (4 bytes). See | |
| * <https://bugzilla.gnome.org/show_bug.cgi?id=673612>. | |
| */ | |
| if (!just_align) | |
| { | |
| guint32 array_len; | |
| const GVariantType *element_type; | |
| guint fixed_size; | |
| array_len = g_memory_buffer_read_uint32 (buf, &local_error); | |
| if (local_error) | |
| goto fail; | |
| #ifdef DEBUG_SERIALIZER | |
| is_leaf = FALSE; | |
| g_print (": array spans 0x%04x bytes\n", array_len); | |
| #endif /* DEBUG_SERIALIZER */ | |
| if (array_len > (2<<26)) | |
| { | |
| /* G_GUINT32_FORMAT doesn't work with gettext, so use u */ | |
| g_set_error (&local_error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| g_dngettext (GETTEXT_PACKAGE, | |
| "Encountered array of length %u byte. Maximum length is 2<<26 bytes (64 MiB).", | |
| "Encountered array of length %u bytes. Maximum length is 2<<26 bytes (64 MiB).", | |
| array_len), | |
| array_len); | |
| goto fail; | |
| } | |
| element_type = g_variant_type_element (type); | |
| fixed_size = get_type_fixed_size (element_type); | |
| /* Fast-path the cases like 'ay', etc. */ | |
| if (fixed_size != 0) | |
| { | |
| gconstpointer array_data; | |
| if (array_len % fixed_size != 0) | |
| { | |
| g_set_error (&local_error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Encountered array of type “a%c”, expected to have a length a multiple " | |
| "of %u bytes, but found to be %u bytes in length"), | |
| g_variant_type_peek_string (element_type)[0], fixed_size, array_len); | |
| goto fail; | |
| } | |
| if (max_depth == 1) | |
| { | |
| /* If we had recursed into parse_value_from_blob() again to | |
| * parse the array values, this would have been emitted. */ | |
| g_set_error_literal (&local_error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Value nested too deeply")); | |
| goto fail; | |
| } | |
| ensure_input_padding (buf, fixed_size); | |
| array_data = read_bytes (buf, array_len, &local_error); | |
| if (array_data == NULL) | |
| goto fail; | |
| ret = g_variant_new_fixed_array (element_type, array_data, array_len / fixed_size, fixed_size); | |
| if (g_memory_buffer_is_byteswapped (buf)) | |
| { | |
| GVariant *tmp = g_variant_ref_sink (ret); | |
| ret = g_variant_byteswap (tmp); | |
| g_variant_unref (tmp); | |
| } | |
| } | |
| else | |
| { | |
| GVariantBuilder builder; | |
| goffset offset; | |
| goffset target; | |
| g_variant_builder_init (&builder, type); | |
| if (array_len == 0) | |
| { | |
| GVariant *item G_GNUC_UNUSED /* when compiling with G_DISABLE_ASSERT */; | |
| item = parse_value_from_blob (buf, | |
| element_type, | |
| max_depth - 1, | |
| TRUE, | |
| indent + 2, | |
| NULL); | |
| g_assert (item == NULL); | |
| } | |
| else | |
| { | |
| offset = buf->pos; | |
| target = offset + array_len; | |
| while (offset < target) | |
| { | |
| GVariant *item; | |
| item = parse_value_from_blob (buf, | |
| element_type, | |
| max_depth - 1, | |
| FALSE, | |
| indent + 2, | |
| &local_error); | |
| if (item == NULL) | |
| { | |
| g_variant_builder_clear (&builder); | |
| goto fail; | |
| } | |
| g_variant_builder_add_value (&builder, item); | |
| g_variant_unref (item); | |
| /* Array elements must not be zero-length. There are no | |
| * valid zero-length serialisations of any types which | |
| * can be array elements in the D-Bus wire format, so this | |
| * assertion should always hold. | |
| * | |
| * See https://gitlab.gnome.org/GNOME/glib/-/issues/2557 | |
| */ | |
| g_assert (buf->pos > (gsize) offset); | |
| offset = buf->pos; | |
| } | |
| } | |
| ret = g_variant_builder_end (&builder); | |
| } | |
| } | |
| break; | |
| default: | |
| if (g_variant_type_is_dict_entry (type)) | |
| { | |
| const GVariantType *key_type; | |
| const GVariantType *value_type; | |
| GVariant *key; | |
| GVariant *value; | |
| ensure_input_padding (buf, 8); | |
| #ifdef DEBUG_SERIALIZER | |
| is_leaf = FALSE; | |
| g_print ("\n"); | |
| #endif /* DEBUG_SERIALIZER */ | |
| if (!just_align) | |
| { | |
| key_type = g_variant_type_key (type); | |
| key = parse_value_from_blob (buf, | |
| key_type, | |
| max_depth - 1, | |
| FALSE, | |
| indent + 2, | |
| &local_error); | |
| if (key == NULL) | |
| goto fail; | |
| value_type = g_variant_type_value (type); | |
| value = parse_value_from_blob (buf, | |
| value_type, | |
| max_depth - 1, | |
| FALSE, | |
| indent + 2, | |
| &local_error); | |
| if (value == NULL) | |
| { | |
| g_variant_unref (key); | |
| goto fail; | |
| } | |
| ret = g_variant_new_dict_entry (key, value); | |
| g_variant_unref (key); | |
| g_variant_unref (value); | |
| } | |
| } | |
| else if (g_variant_type_is_tuple (type)) | |
| { | |
| ensure_input_padding (buf, 8); | |
| #ifdef DEBUG_SERIALIZER | |
| is_leaf = FALSE; | |
| g_print ("\n"); | |
| #endif /* DEBUG_SERIALIZER */ | |
| if (!just_align) | |
| { | |
| const GVariantType *element_type; | |
| GVariantBuilder builder; | |
| g_variant_builder_init (&builder, type); | |
| element_type = g_variant_type_first (type); | |
| if (!element_type) | |
| { | |
| g_variant_builder_clear (&builder); | |
| g_set_error_literal (&local_error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Empty structures (tuples) are not allowed in D-Bus")); | |
| goto fail; | |
| } | |
| while (element_type != NULL) | |
| { | |
| GVariant *item; | |
| item = parse_value_from_blob (buf, | |
| element_type, | |
| max_depth - 1, | |
| FALSE, | |
| indent + 2, | |
| &local_error); | |
| if (item == NULL) | |
| { | |
| g_variant_builder_clear (&builder); | |
| goto fail; | |
| } | |
| g_variant_builder_add_value (&builder, item); | |
| g_variant_unref (item); | |
| element_type = g_variant_type_next (element_type); | |
| } | |
| ret = g_variant_builder_end (&builder); | |
| } | |
| } | |
| else if (g_variant_type_is_variant (type)) | |
| { | |
| #ifdef DEBUG_SERIALIZER | |
| is_leaf = FALSE; | |
| g_print ("\n"); | |
| #endif /* DEBUG_SERIALIZER */ | |
| if (!just_align) | |
| { | |
| guchar siglen; | |
| const gchar *sig; | |
| GVariantType *variant_type; | |
| GVariant *value; | |
| siglen = g_memory_buffer_read_byte (buf, &local_error); | |
| if (local_error) | |
| goto fail; | |
| sig = read_string (buf, (gsize) siglen, &local_error); | |
| if (sig == NULL) | |
| goto fail; | |
| if (!g_variant_is_signature (sig) || | |
| !g_variant_type_string_is_valid (sig)) | |
| { | |
| /* A D-Bus signature can contain zero or more complete types, | |
| * but a GVariant has to be exactly one complete type. */ | |
| g_set_error (&local_error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Parsed value “%s” for variant is not a valid D-Bus signature"), | |
| sig); | |
| goto fail; | |
| } | |
| if (max_depth <= g_variant_type_string_get_depth_ (sig)) | |
| { | |
| /* Catch the type nesting being too deep without having to | |
| * parse the data. We don’t have to check this for static | |
| * container types (like arrays and tuples, above) because | |
| * the g_variant_type_string_is_valid() check performed before | |
| * the initial parse_value_from_blob() call should check the | |
| * static type nesting. */ | |
| g_set_error_literal (&local_error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Value nested too deeply")); | |
| goto fail; | |
| } | |
| variant_type = g_variant_type_new (sig); | |
| value = parse_value_from_blob (buf, | |
| variant_type, | |
| max_depth - 1, | |
| FALSE, | |
| indent + 2, | |
| &local_error); | |
| g_variant_type_free (variant_type); | |
| if (value == NULL) | |
| goto fail; | |
| ret = g_variant_new_variant (value); | |
| g_variant_unref (value); | |
| } | |
| } | |
| else | |
| { | |
| gchar *s; | |
| s = g_variant_type_dup_string (type); | |
| g_set_error (&local_error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Error deserializing GVariant with type string “%s” from the D-Bus wire format"), | |
| s); | |
| g_free (s); | |
| goto fail; | |
| } | |
| break; | |
| } | |
| g_assert ((just_align && ret == NULL) || (!just_align && ret != NULL)); | |
| #ifdef DEBUG_SERIALIZER | |
| if (ret != NULL) | |
| { | |
| if (is_leaf) | |
| { | |
| gchar *s; | |
| if (g_variant_type_equal (type, G_VARIANT_TYPE_BYTE)) | |
| { | |
| s = g_strdup_printf ("0x%02x '%c'", g_variant_get_byte (ret), g_variant_get_byte (ret)); | |
| } | |
| else | |
| { | |
| s = g_variant_print (ret, FALSE); | |
| } | |
| g_print (": %s\n", s); | |
| g_free (s); | |
| } | |
| } | |
| #endif /* DEBUG_SERIALIZER */ | |
| /* sink the reference, if floating */ | |
| if (ret != NULL) | |
| g_variant_take_ref (ret); | |
| return ret; | |
| fail: | |
| #ifdef DEBUG_SERIALIZER | |
| g_print ("\n" | |
| "%*sFAILURE: %s (%s, %d)\n", | |
| indent, "", | |
| local_error->message, | |
| g_quark_to_string (local_error->domain), | |
| local_error->code); | |
| #endif /* DEBUG_SERIALIZER */ | |
| g_propagate_error (error, local_error); | |
| return NULL; | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /* message_header must be at least 16 bytes */ | |
| /** | |
| * g_dbus_message_bytes_needed: | |
| * @blob: (array length=blob_len) (element-type guint8): A blob representing a binary D-Bus message. | |
| * @blob_len: The length of @blob (must be at least 16). | |
| * @error: Return location for error or %NULL. | |
| * | |
| * Utility function to calculate how many bytes are needed to | |
| * completely deserialize the D-Bus message stored at @blob. | |
| * | |
| * Returns: Number of bytes needed or -1 if @error is set (e.g. if | |
| * @blob contains invalid data or not enough data is available to | |
| * determine the size). | |
| * | |
| * Since: 2.26 | |
| */ | |
| gssize | |
| g_dbus_message_bytes_needed (guchar *blob, | |
| gsize blob_len, | |
| GError **error) | |
| { | |
| gssize ret; | |
| ret = -1; | |
| g_return_val_if_fail (blob != NULL, -1); | |
| g_return_val_if_fail (error == NULL || *error == NULL, -1); | |
| g_return_val_if_fail (blob_len >= 16, -1); | |
| if (blob[0] == 'l') | |
| { | |
| /* core header (12 bytes) + ARRAY of STRUCT of (BYTE,VARIANT) */ | |
| ret = 12 + 4 + GUINT32_FROM_LE (((guint32 *) blob)[3]); | |
| /* round up so it's a multiple of 8 */ | |
| ret = 8 * ((ret + 7)/8); | |
| /* finally add the body size */ | |
| ret += GUINT32_FROM_LE (((guint32 *) blob)[1]); | |
| } | |
| else if (blob[0] == 'B') | |
| { | |
| /* core header (12 bytes) + ARRAY of STRUCT of (BYTE,VARIANT) */ | |
| ret = 12 + 4 + GUINT32_FROM_BE (((guint32 *) blob)[3]); | |
| /* round up so it's a multiple of 8 */ | |
| ret = 8 * ((ret + 7)/8); | |
| /* finally add the body size */ | |
| ret += GUINT32_FROM_BE (((guint32 *) blob)[1]); | |
| } | |
| else | |
| { | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| "Unable to determine message blob length - given blob is malformed"); | |
| } | |
| if (ret > (1<<27)) | |
| { | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| "Blob indicates that message exceeds maximum message length (128MiB)"); | |
| ret = -1; | |
| } | |
| return ret; | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /** | |
| * g_dbus_message_new_from_blob: | |
| * @blob: (array length=blob_len) (element-type guint8): A blob representing a binary D-Bus message. | |
| * @blob_len: The length of @blob. | |
| * @capabilities: A #GDBusCapabilityFlags describing what protocol features are supported. | |
| * @error: Return location for error or %NULL. | |
| * | |
| * Creates a new #GDBusMessage from the data stored at @blob. The byte | |
| * order that the message was in can be retrieved using | |
| * g_dbus_message_get_byte_order(). | |
| * | |
| * If the @blob cannot be parsed, contains invalid fields, or contains invalid | |
| * headers, %G_IO_ERROR_INVALID_ARGUMENT will be returned. | |
| * | |
| * Returns: A new #GDBusMessage or %NULL if @error is set. Free with | |
| * g_object_unref(). | |
| * | |
| * Since: 2.26 | |
| */ | |
| GDBusMessage * | |
| g_dbus_message_new_from_blob (guchar *blob, | |
| gsize blob_len, | |
| GDBusCapabilityFlags capabilities, | |
| GError **error) | |
| { | |
| GError *local_error = NULL; | |
| GMemoryBuffer mbuf; | |
| GDBusMessage *message; | |
| guchar endianness; | |
| guchar major_protocol_version; | |
| guint32 message_body_len; | |
| GVariant *headers; | |
| GVariant *item; | |
| GVariantIter iter; | |
| GVariant *signature; | |
| /* TODO: check against @capabilities */ | |
| g_return_val_if_fail (blob != NULL, NULL); | |
| g_return_val_if_fail (error == NULL || *error == NULL, NULL); | |
| message = g_dbus_message_new (); | |
| memset (&mbuf, 0, sizeof (mbuf)); | |
| mbuf.data = (gchar *)blob; | |
| mbuf.len = mbuf.valid_len = blob_len; | |
| endianness = g_memory_buffer_read_byte (&mbuf, &local_error); | |
| if (local_error) | |
| goto fail; | |
| switch (endianness) | |
| { | |
| case 'l': | |
| mbuf.byte_order = G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN; | |
| message->byte_order = G_DBUS_MESSAGE_BYTE_ORDER_LITTLE_ENDIAN; | |
| break; | |
| case 'B': | |
| mbuf.byte_order = G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN; | |
| message->byte_order = G_DBUS_MESSAGE_BYTE_ORDER_BIG_ENDIAN; | |
| break; | |
| default: | |
| g_set_error (&local_error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Invalid endianness value. Expected 0x6c (“l”) or 0x42 (“B”) but found value 0x%02x"), | |
| endianness); | |
| goto fail; | |
| } | |
| message->type = g_memory_buffer_read_byte (&mbuf, &local_error); | |
| if (local_error) | |
| goto fail; | |
| message->flags = g_memory_buffer_read_byte (&mbuf, &local_error); | |
| if (local_error) | |
| goto fail; | |
| major_protocol_version = g_memory_buffer_read_byte (&mbuf, &local_error); | |
| if (local_error) | |
| goto fail; | |
| if (major_protocol_version != 1) | |
| { | |
| g_set_error (&local_error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Invalid major protocol version. Expected 1 but found %d"), | |
| major_protocol_version); | |
| goto fail; | |
| } | |
| message_body_len = g_memory_buffer_read_uint32 (&mbuf, &local_error); | |
| if (local_error) | |
| goto fail; | |
| message->serial = g_memory_buffer_read_uint32 (&mbuf, &local_error); | |
| if (local_error) | |
| goto fail; | |
| #ifdef DEBUG_SERIALIZER | |
| g_print ("Parsing blob (blob_len = 0x%04x bytes)\n", (gint) blob_len); | |
| { | |
| gchar *s; | |
| s = _g_dbus_hexdump ((const gchar *) blob, blob_len, 2); | |
| g_print ("%s\n", s); | |
| g_free (s); | |
| } | |
| #endif /* DEBUG_SERIALIZER */ | |
| #ifdef DEBUG_SERIALIZER | |
| g_print ("Parsing headers (blob_len = 0x%04x bytes)\n", (gint) blob_len); | |
| #endif /* DEBUG_SERIALIZER */ | |
| headers = parse_value_from_blob (&mbuf, | |
| G_VARIANT_TYPE ("a{yv}"), | |
| G_DBUS_MAX_TYPE_DEPTH + 2 /* for the a{yv} */, | |
| FALSE, | |
| 2, | |
| &local_error); | |
| if (headers == NULL) | |
| goto fail; | |
| g_variant_iter_init (&iter, headers); | |
| while ((item = g_variant_iter_next_value (&iter)) != NULL) | |
| { | |
| guchar header_field; | |
| GVariant *value; | |
| g_variant_get (item, | |
| "{yv}", | |
| &header_field, | |
| &value); | |
| g_dbus_message_set_header (message, header_field, value); | |
| g_variant_unref (value); | |
| g_variant_unref (item); | |
| } | |
| g_variant_unref (headers); | |
| signature = g_dbus_message_get_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE); | |
| if (signature != NULL) | |
| { | |
| const gchar *signature_str; | |
| gsize signature_str_len; | |
| if (!g_variant_is_of_type (signature, G_VARIANT_TYPE_SIGNATURE)) | |
| { | |
| g_set_error_literal (&local_error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Signature header found but is not of type signature")); | |
| goto fail; | |
| } | |
| signature_str = g_variant_get_string (signature, &signature_str_len); | |
| /* signature but no body */ | |
| if (message_body_len == 0 && signature_str_len > 0) | |
| { | |
| g_set_error (&local_error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Signature header with signature “%s” found but message body is empty"), | |
| signature_str); | |
| goto fail; | |
| } | |
| else if (signature_str_len > 0) | |
| { | |
| GVariantType *variant_type; | |
| gchar *tupled_signature_str = g_strdup_printf ("(%s)", signature_str); | |
| if (!g_variant_is_signature (signature_str) || | |
| !g_variant_type_string_is_valid (tupled_signature_str)) | |
| { | |
| g_set_error (&local_error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Parsed value “%s” is not a valid D-Bus signature (for body)"), | |
| signature_str); | |
| g_free (tupled_signature_str); | |
| goto fail; | |
| } | |
| variant_type = g_variant_type_new (tupled_signature_str); | |
| g_free (tupled_signature_str); | |
| #ifdef DEBUG_SERIALIZER | |
| g_print ("Parsing body (blob_len = 0x%04x bytes)\n", (gint) blob_len); | |
| #endif /* DEBUG_SERIALIZER */ | |
| message->body = parse_value_from_blob (&mbuf, | |
| variant_type, | |
| G_DBUS_MAX_TYPE_DEPTH + 1 /* for the surrounding tuple */, | |
| FALSE, | |
| 2, | |
| &local_error); | |
| g_variant_type_free (variant_type); | |
| if (message->body != NULL && | |
| g_variant_is_of_type (message->body, G_VARIANT_TYPE_TUPLE) && | |
| g_variant_n_children (message->body) > 0) | |
| message->arg0_cache = g_variant_get_child_value (message->body, 0); | |
| else | |
| message->arg0_cache = NULL; | |
| if (message->body == NULL) | |
| goto fail; | |
| } | |
| } | |
| else | |
| { | |
| /* no signature, this is only OK if the body is empty */ | |
| if (message_body_len != 0) | |
| { | |
| /* G_GUINT32_FORMAT doesn't work with gettext, just use %u */ | |
| g_set_error (&local_error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| g_dngettext (GETTEXT_PACKAGE, | |
| "No signature header in message but the message body is %u byte", | |
| "No signature header in message but the message body is %u bytes", | |
| message_body_len), | |
| message_body_len); | |
| goto fail; | |
| } | |
| } | |
| if (!validate_headers (message, &local_error)) | |
| { | |
| g_prefix_error (&local_error, _("Cannot deserialize message: ")); | |
| goto fail; | |
| } | |
| return message; | |
| fail: | |
| g_clear_object (&message); | |
| g_propagate_error (error, local_error); | |
| return NULL; | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| static gsize | |
| ensure_output_padding (GMemoryBuffer *mbuf, | |
| gsize padding_size) | |
| { | |
| gsize offset; | |
| gsize wanted_offset; | |
| gsize padding_needed; | |
| guint n; | |
| offset = mbuf->pos; | |
| wanted_offset = ((offset + padding_size - 1) / padding_size) * padding_size; | |
| padding_needed = wanted_offset - offset; | |
| for (n = 0; n < padding_needed; n++) | |
| g_memory_buffer_put_byte (mbuf, '\0'); | |
| return padding_needed; | |
| } | |
| /* note that value can be NULL for e.g. empty arrays - type is never NULL */ | |
| static gboolean | |
| append_value_to_blob (GVariant *value, | |
| const GVariantType *type, | |
| GMemoryBuffer *mbuf, | |
| gsize *out_padding_added, | |
| GError **error) | |
| { | |
| gsize padding_added; | |
| const gchar *type_string; | |
| type_string = g_variant_type_peek_string (type); | |
| padding_added = 0; | |
| switch (type_string[0]) | |
| { | |
| case 'b': /* G_VARIANT_TYPE_BOOLEAN */ | |
| padding_added = ensure_output_padding (mbuf, 4); | |
| if (value != NULL) | |
| { | |
| gboolean v = g_variant_get_boolean (value); | |
| g_memory_buffer_put_uint32 (mbuf, v); | |
| } | |
| break; | |
| case 'y': /* G_VARIANT_TYPE_BYTE */ | |
| if (value != NULL) | |
| { | |
| guint8 v = g_variant_get_byte (value); | |
| g_memory_buffer_put_byte (mbuf, v); | |
| } | |
| break; | |
| case 'n': /* G_VARIANT_TYPE_INT16 */ | |
| padding_added = ensure_output_padding (mbuf, 2); | |
| if (value != NULL) | |
| { | |
| gint16 v = g_variant_get_int16 (value); | |
| g_memory_buffer_put_int16 (mbuf, v); | |
| } | |
| break; | |
| case 'q': /* G_VARIANT_TYPE_UINT16 */ | |
| padding_added = ensure_output_padding (mbuf, 2); | |
| if (value != NULL) | |
| { | |
| guint16 v = g_variant_get_uint16 (value); | |
| g_memory_buffer_put_uint16 (mbuf, v); | |
| } | |
| break; | |
| case 'i': /* G_VARIANT_TYPE_INT32 */ | |
| padding_added = ensure_output_padding (mbuf, 4); | |
| if (value != NULL) | |
| { | |
| gint32 v = g_variant_get_int32 (value); | |
| g_memory_buffer_put_int32 (mbuf, v); | |
| } | |
| break; | |
| case 'u': /* G_VARIANT_TYPE_UINT32 */ | |
| padding_added = ensure_output_padding (mbuf, 4); | |
| if (value != NULL) | |
| { | |
| guint32 v = g_variant_get_uint32 (value); | |
| g_memory_buffer_put_uint32 (mbuf, v); | |
| } | |
| break; | |
| case 'x': /* G_VARIANT_TYPE_INT64 */ | |
| padding_added = ensure_output_padding (mbuf, 8); | |
| if (value != NULL) | |
| { | |
| gint64 v = g_variant_get_int64 (value); | |
| g_memory_buffer_put_int64 (mbuf, v); | |
| } | |
| break; | |
| case 't': /* G_VARIANT_TYPE_UINT64 */ | |
| padding_added = ensure_output_padding (mbuf, 8); | |
| if (value != NULL) | |
| { | |
| guint64 v = g_variant_get_uint64 (value); | |
| g_memory_buffer_put_uint64 (mbuf, v); | |
| } | |
| break; | |
| case 'd': /* G_VARIANT_TYPE_DOUBLE */ | |
| padding_added = ensure_output_padding (mbuf, 8); | |
| if (value != NULL) | |
| { | |
| union { | |
| guint64 v_uint64; | |
| gdouble v_double; | |
| } u; | |
| G_STATIC_ASSERT (sizeof (gdouble) == sizeof (guint64)); | |
| u.v_double = g_variant_get_double (value); | |
| g_memory_buffer_put_uint64 (mbuf, u.v_uint64); | |
| } | |
| break; | |
| case 's': /* G_VARIANT_TYPE_STRING */ | |
| padding_added = ensure_output_padding (mbuf, 4); | |
| if (value != NULL) | |
| { | |
| gsize len; | |
| const gchar *v; | |
| #ifndef G_DISABLE_ASSERT | |
| const gchar *end; | |
| #endif | |
| v = g_variant_get_string (value, &len); | |
| g_assert (g_utf8_validate (v, -1, &end) && (end == v + len)); | |
| g_memory_buffer_put_uint32 (mbuf, len); | |
| g_memory_buffer_put_string (mbuf, v); | |
| g_memory_buffer_put_byte (mbuf, '\0'); | |
| } | |
| break; | |
| case 'o': /* G_VARIANT_TYPE_OBJECT_PATH */ | |
| padding_added = ensure_output_padding (mbuf, 4); | |
| if (value != NULL) | |
| { | |
| gsize len; | |
| const gchar *v = g_variant_get_string (value, &len); | |
| g_assert (g_variant_is_object_path (v)); | |
| g_memory_buffer_put_uint32 (mbuf, len); | |
| g_memory_buffer_put_string (mbuf, v); | |
| g_memory_buffer_put_byte (mbuf, '\0'); | |
| } | |
| break; | |
| case 'g': /* G_VARIANT_TYPE_SIGNATURE */ | |
| if (value != NULL) | |
| { | |
| gsize len; | |
| const gchar *v = g_variant_get_string (value, &len); | |
| g_assert (g_variant_is_signature (v)); | |
| g_memory_buffer_put_byte (mbuf, len); | |
| g_memory_buffer_put_string (mbuf, v); | |
| g_memory_buffer_put_byte (mbuf, '\0'); | |
| } | |
| break; | |
| case 'h': /* G_VARIANT_TYPE_HANDLE */ | |
| padding_added = ensure_output_padding (mbuf, 4); | |
| if (value != NULL) | |
| { | |
| gint32 v = g_variant_get_handle (value); | |
| g_memory_buffer_put_int32 (mbuf, v); | |
| } | |
| break; | |
| case 'a': /* G_VARIANT_TYPE_ARRAY */ | |
| { | |
| const GVariantType *element_type; | |
| GVariant *item; | |
| GVariantIter iter; | |
| goffset array_len_offset; | |
| goffset array_payload_begin_offset; | |
| goffset cur_offset; | |
| gsize array_len; | |
| guint fixed_size; | |
| padding_added = ensure_output_padding (mbuf, 4); | |
| if (value != NULL) | |
| { | |
| /* array length - will be filled in later */ | |
| array_len_offset = mbuf->valid_len; | |
| g_memory_buffer_put_uint32 (mbuf, 0xF00DFACE); | |
| /* From the D-Bus spec: | |
| * | |
| * "A UINT32 giving the length of the array data in bytes, | |
| * followed by alignment padding to the alignment boundary of | |
| * the array element type, followed by each array element. The | |
| * array length is from the end of the alignment padding to | |
| * the end of the last element, i.e. it does not include the | |
| * padding after the length, or any padding after the last | |
| * element." | |
| * | |
| * Thus, we need to count how much padding the first element | |
| * contributes and subtract that from the array length. | |
| */ | |
| array_payload_begin_offset = mbuf->valid_len; | |
| element_type = g_variant_type_element (type); | |
| fixed_size = get_type_fixed_size (element_type); | |
| if (g_variant_n_children (value) == 0) | |
| { | |
| gsize padding_added_for_item; | |
| if (!append_value_to_blob (NULL, | |
| element_type, | |
| mbuf, | |
| &padding_added_for_item, | |
| error)) | |
| goto fail; | |
| array_payload_begin_offset += padding_added_for_item; | |
| } | |
| else if (fixed_size != 0) | |
| { | |
| GVariant *use_value; | |
| if (g_memory_buffer_is_byteswapped (mbuf)) | |
| use_value = g_variant_byteswap (value); | |
| else | |
| use_value = g_variant_ref (value); | |
| array_payload_begin_offset += ensure_output_padding (mbuf, fixed_size); | |
| array_len = g_variant_get_size (use_value); | |
| g_memory_buffer_write (mbuf, g_variant_get_data (use_value), array_len); | |
| g_variant_unref (use_value); | |
| } | |
| else | |
| { | |
| guint n; | |
| n = 0; | |
| g_variant_iter_init (&iter, value); | |
| while ((item = g_variant_iter_next_value (&iter)) != NULL) | |
| { | |
| gsize padding_added_for_item; | |
| if (!append_value_to_blob (item, | |
| g_variant_get_type (item), | |
| mbuf, | |
| &padding_added_for_item, | |
| error)) | |
| { | |
| g_variant_unref (item); | |
| goto fail; | |
| } | |
| g_variant_unref (item); | |
| if (n == 0) | |
| { | |
| array_payload_begin_offset += padding_added_for_item; | |
| } | |
| n++; | |
| } | |
| } | |
| cur_offset = mbuf->valid_len; | |
| array_len = cur_offset - array_payload_begin_offset; | |
| mbuf->pos = array_len_offset; | |
| g_memory_buffer_put_uint32 (mbuf, array_len); | |
| mbuf->pos = cur_offset; | |
| } | |
| } | |
| break; | |
| default: | |
| if (g_variant_type_is_dict_entry (type) || g_variant_type_is_tuple (type)) | |
| { | |
| if (!g_variant_type_first (type)) | |
| { | |
| g_set_error_literal (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Empty structures (tuples) are not allowed in D-Bus")); | |
| goto fail; | |
| } | |
| padding_added = ensure_output_padding (mbuf, 8); | |
| if (value != NULL) | |
| { | |
| GVariant *item; | |
| GVariantIter iter; | |
| g_variant_iter_init (&iter, value); | |
| while ((item = g_variant_iter_next_value (&iter)) != NULL) | |
| { | |
| if (!append_value_to_blob (item, | |
| g_variant_get_type (item), | |
| mbuf, | |
| NULL, | |
| error)) | |
| { | |
| g_variant_unref (item); | |
| goto fail; | |
| } | |
| g_variant_unref (item); | |
| } | |
| } | |
| } | |
| else if (g_variant_type_is_variant (type)) | |
| { | |
| if (value != NULL) | |
| { | |
| GVariant *child; | |
| const gchar *signature; | |
| child = g_variant_get_child_value (value, 0); | |
| signature = g_variant_get_type_string (child); | |
| g_memory_buffer_put_byte (mbuf, (guint8) strlen (signature)); /* signature is already validated to be this short */ | |
| g_memory_buffer_put_string (mbuf, signature); | |
| g_memory_buffer_put_byte (mbuf, '\0'); | |
| if (!append_value_to_blob (child, | |
| g_variant_get_type (child), | |
| mbuf, | |
| NULL, | |
| error)) | |
| { | |
| g_variant_unref (child); | |
| goto fail; | |
| } | |
| g_variant_unref (child); | |
| } | |
| } | |
| else | |
| { | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Error serializing GVariant with type string “%s” to the D-Bus wire format"), | |
| g_variant_get_type_string (value)); | |
| goto fail; | |
| } | |
| break; | |
| } | |
| if (out_padding_added != NULL) | |
| *out_padding_added = padding_added; | |
| return TRUE; | |
| fail: | |
| return FALSE; | |
| } | |
| static gboolean | |
| append_body_to_blob (GVariant *value, | |
| GMemoryBuffer *mbuf, | |
| GError **error) | |
| { | |
| GVariant *item; | |
| GVariantIter iter; | |
| if (!g_variant_is_of_type (value, G_VARIANT_TYPE_TUPLE)) | |
| { | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| "Expected a tuple for the body of the GDBusMessage."); | |
| goto fail; | |
| } | |
| g_variant_iter_init (&iter, value); | |
| while ((item = g_variant_iter_next_value (&iter)) != NULL) | |
| { | |
| if (!append_value_to_blob (item, | |
| g_variant_get_type (item), | |
| mbuf, | |
| NULL, | |
| error)) | |
| { | |
| g_variant_unref (item); | |
| goto fail; | |
| } | |
| g_variant_unref (item); | |
| } | |
| return TRUE; | |
| fail: | |
| return FALSE; | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /** | |
| * g_dbus_message_to_blob: | |
| * @message: A #GDBusMessage. | |
| * @out_size: Return location for size of generated blob. | |
| * @capabilities: A #GDBusCapabilityFlags describing what protocol features are supported. | |
| * @error: Return location for error. | |
| * | |
| * Serializes @message to a blob. The byte order returned by | |
| * g_dbus_message_get_byte_order() will be used. | |
| * | |
| * Returns: (array length=out_size) (transfer full): A pointer to a | |
| * valid binary D-Bus message of @out_size bytes generated by @message | |
| * or %NULL if @error is set. Free with g_free(). | |
| * | |
| * Since: 2.26 | |
| */ | |
| guchar * | |
| g_dbus_message_to_blob (GDBusMessage *message, | |
| gsize *out_size, | |
| GDBusCapabilityFlags capabilities, | |
| GError **error) | |
| { | |
| GMemoryBuffer mbuf; | |
| guchar *ret; | |
| gsize size; | |
| goffset body_len_offset; | |
| goffset body_start_offset; | |
| gsize body_size; | |
| GVariant *header_fields; | |
| GVariantBuilder builder; | |
| GHashTableIter hash_iter; | |
| gpointer key; | |
| GVariant *header_value; | |
| GVariant *signature; | |
| const gchar *signature_str; | |
| gint num_fds_in_message; | |
| gint num_fds_according_to_header; | |
| /* TODO: check against @capabilities */ | |
| ret = NULL; | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); | |
| g_return_val_if_fail (out_size != NULL, NULL); | |
| g_return_val_if_fail (error == NULL || *error == NULL, NULL); | |
| memset (&mbuf, 0, sizeof (mbuf)); | |
| mbuf.len = MIN_ARRAY_SIZE; | |
| mbuf.data = g_malloc (mbuf.len); | |
| mbuf.byte_order = G_DATA_STREAM_BYTE_ORDER_HOST_ENDIAN; | |
| switch (message->byte_order) | |
| { | |
| case G_DBUS_MESSAGE_BYTE_ORDER_BIG_ENDIAN: | |
| mbuf.byte_order = G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN; | |
| break; | |
| case G_DBUS_MESSAGE_BYTE_ORDER_LITTLE_ENDIAN: | |
| mbuf.byte_order = G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN; | |
| break; | |
| } | |
| /* Core header */ | |
| g_memory_buffer_put_byte (&mbuf, (guchar) message->byte_order); | |
| g_memory_buffer_put_byte (&mbuf, message->type); | |
| g_memory_buffer_put_byte (&mbuf, message->flags); | |
| g_memory_buffer_put_byte (&mbuf, 1); /* major protocol version */ | |
| body_len_offset = mbuf.valid_len; | |
| /* body length - will be filled in later */ | |
| g_memory_buffer_put_uint32 (&mbuf, 0xF00DFACE); | |
| g_memory_buffer_put_uint32 (&mbuf, message->serial); | |
| num_fds_in_message = 0; | |
| #ifdef G_OS_UNIX | |
| if (message->fd_list != NULL) | |
| num_fds_in_message = g_unix_fd_list_get_length (message->fd_list); | |
| #endif | |
| num_fds_according_to_header = g_dbus_message_get_num_unix_fds (message); | |
| if (num_fds_in_message != num_fds_according_to_header) | |
| { | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Number of file descriptors in message (%d) differs from header field (%d)"), | |
| num_fds_in_message, | |
| num_fds_according_to_header); | |
| goto out; | |
| } | |
| if (!validate_headers (message, error)) | |
| { | |
| g_prefix_error (error, _("Cannot serialize message: ")); | |
| goto out; | |
| } | |
| g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{yv}")); | |
| g_hash_table_iter_init (&hash_iter, message->headers); | |
| while (g_hash_table_iter_next (&hash_iter, &key, (gpointer) &header_value)) | |
| { | |
| g_variant_builder_add (&builder, | |
| "{yv}", | |
| (guchar) GPOINTER_TO_UINT (key), | |
| header_value); | |
| } | |
| header_fields = g_variant_builder_end (&builder); | |
| if (!append_value_to_blob (header_fields, | |
| g_variant_get_type (header_fields), | |
| &mbuf, | |
| NULL, | |
| error)) | |
| { | |
| g_variant_unref (header_fields); | |
| goto out; | |
| } | |
| g_variant_unref (header_fields); | |
| /* header size must be a multiple of 8 */ | |
| ensure_output_padding (&mbuf, 8); | |
| body_start_offset = mbuf.valid_len; | |
| signature = g_dbus_message_get_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE); | |
| if (signature != NULL && !g_variant_is_of_type (signature, G_VARIANT_TYPE_SIGNATURE)) | |
| { | |
| g_set_error_literal (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Signature header found but is not of type signature")); | |
| goto out; | |
| } | |
| signature_str = NULL; | |
| if (signature != NULL) | |
| signature_str = g_variant_get_string (signature, NULL); | |
| if (message->body != NULL) | |
| { | |
| gchar *tupled_signature_str; | |
| if (signature == NULL) | |
| { | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Message body has signature “%s” but there is no signature header"), | |
| g_variant_get_type_string (message->body)); | |
| goto out; | |
| } | |
| tupled_signature_str = g_strdup_printf ("(%s)", signature_str); | |
| if (g_strcmp0 (tupled_signature_str, g_variant_get_type_string (message->body)) != 0) | |
| { | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Message body has type signature “%s” but signature in the header field is “%s”"), | |
| g_variant_get_type_string (message->body), tupled_signature_str); | |
| g_free (tupled_signature_str); | |
| goto out; | |
| } | |
| g_free (tupled_signature_str); | |
| if (!append_body_to_blob (message->body, &mbuf, error)) | |
| goto out; | |
| } | |
| else | |
| { | |
| if (signature != NULL && strlen (signature_str) > 0) | |
| { | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_INVALID_ARGUMENT, | |
| _("Message body is empty but signature in the header field is “(%s)”"), | |
| signature_str); | |
| goto out; | |
| } | |
| } | |
| /* OK, we're done writing the message - set the body length */ | |
| size = mbuf.valid_len; | |
| body_size = size - body_start_offset; | |
| mbuf.pos = body_len_offset; | |
| g_memory_buffer_put_uint32 (&mbuf, body_size); | |
| *out_size = size; | |
| ret = (guchar *)mbuf.data; | |
| out: | |
| if (ret == NULL) | |
| g_free (mbuf.data); | |
| return ret; | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| static guint32 | |
| get_uint32_header (GDBusMessage *message, | |
| GDBusMessageHeaderField header_field) | |
| { | |
| GVariant *value; | |
| guint32 ret; | |
| ret = 0; | |
| value = g_hash_table_lookup (message->headers, GUINT_TO_POINTER (header_field)); | |
| if (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32)) | |
| ret = g_variant_get_uint32 (value); | |
| return ret; | |
| } | |
| static const gchar * | |
| get_string_header (GDBusMessage *message, | |
| GDBusMessageHeaderField header_field) | |
| { | |
| GVariant *value; | |
| const gchar *ret; | |
| ret = NULL; | |
| value = g_hash_table_lookup (message->headers, GUINT_TO_POINTER (header_field)); | |
| if (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) | |
| ret = g_variant_get_string (value, NULL); | |
| return ret; | |
| } | |
| static const gchar * | |
| get_object_path_header (GDBusMessage *message, | |
| GDBusMessageHeaderField header_field) | |
| { | |
| GVariant *value; | |
| const gchar *ret; | |
| ret = NULL; | |
| value = g_hash_table_lookup (message->headers, GUINT_TO_POINTER (header_field)); | |
| if (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_OBJECT_PATH)) | |
| ret = g_variant_get_string (value, NULL); | |
| return ret; | |
| } | |
| static const gchar * | |
| get_signature_header (GDBusMessage *message, | |
| GDBusMessageHeaderField header_field) | |
| { | |
| GVariant *value; | |
| const gchar *ret; | |
| ret = NULL; | |
| value = g_hash_table_lookup (message->headers, GUINT_TO_POINTER (header_field)); | |
| if (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_SIGNATURE)) | |
| ret = g_variant_get_string (value, NULL); | |
| return ret; | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| static void | |
| set_uint32_header (GDBusMessage *message, | |
| GDBusMessageHeaderField header_field, | |
| guint32 value) | |
| { | |
| g_dbus_message_set_header (message, | |
| header_field, | |
| g_variant_new_uint32 (value)); | |
| } | |
| static void | |
| set_string_header (GDBusMessage *message, | |
| GDBusMessageHeaderField header_field, | |
| const gchar *value) | |
| { | |
| g_dbus_message_set_header (message, | |
| header_field, | |
| value == NULL ? NULL : g_variant_new_string (value)); | |
| } | |
| static void | |
| set_object_path_header (GDBusMessage *message, | |
| GDBusMessageHeaderField header_field, | |
| const gchar *value) | |
| { | |
| g_dbus_message_set_header (message, | |
| header_field, | |
| value == NULL ? NULL : g_variant_new_object_path (value)); | |
| } | |
| static void | |
| set_signature_header (GDBusMessage *message, | |
| GDBusMessageHeaderField header_field, | |
| const gchar *value) | |
| { | |
| g_dbus_message_set_header (message, | |
| header_field, | |
| value == NULL ? NULL : g_variant_new_signature (value)); | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /** | |
| * g_dbus_message_get_reply_serial: | |
| * @message: A #GDBusMessage. | |
| * | |
| * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL header field. | |
| * | |
| * Returns: The value. | |
| * | |
| * Since: 2.26 | |
| */ | |
| guint32 | |
| g_dbus_message_get_reply_serial (GDBusMessage *message) | |
| { | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), 0); | |
| return get_uint32_header (message, G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL); | |
| } | |
| /** | |
| * g_dbus_message_set_reply_serial: | |
| * @message: A #GDBusMessage. | |
| * @value: The value to set. | |
| * | |
| * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL header field. | |
| * | |
| * Since: 2.26 | |
| */ | |
| void | |
| g_dbus_message_set_reply_serial (GDBusMessage *message, | |
| guint32 value) | |
| { | |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); | |
| set_uint32_header (message, G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL, value); | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /** | |
| * g_dbus_message_get_interface: | |
| * @message: A #GDBusMessage. | |
| * | |
| * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE header field. | |
| * | |
| * Returns: (nullable): The value. | |
| * | |
| * Since: 2.26 | |
| */ | |
| const gchar * | |
| g_dbus_message_get_interface (GDBusMessage *message) | |
| { | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); | |
| return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE); | |
| } | |
| /** | |
| * g_dbus_message_set_interface: | |
| * @message: A #GDBusMessage. | |
| * @value: (nullable): The value to set. | |
| * | |
| * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE header field. | |
| * | |
| * Since: 2.26 | |
| */ | |
| void | |
| g_dbus_message_set_interface (GDBusMessage *message, | |
| const gchar *value) | |
| { | |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); | |
| g_return_if_fail (value == NULL || g_dbus_is_interface_name (value)); | |
| set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE, value); | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /** | |
| * g_dbus_message_get_member: | |
| * @message: A #GDBusMessage. | |
| * | |
| * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_MEMBER header field. | |
| * | |
| * Returns: (nullable): The value. | |
| * | |
| * Since: 2.26 | |
| */ | |
| const gchar * | |
| g_dbus_message_get_member (GDBusMessage *message) | |
| { | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); | |
| return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_MEMBER); | |
| } | |
| /** | |
| * g_dbus_message_set_member: | |
| * @message: A #GDBusMessage. | |
| * @value: (nullable): The value to set. | |
| * | |
| * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_MEMBER header field. | |
| * | |
| * Since: 2.26 | |
| */ | |
| void | |
| g_dbus_message_set_member (GDBusMessage *message, | |
| const gchar *value) | |
| { | |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); | |
| g_return_if_fail (value == NULL || g_dbus_is_member_name (value)); | |
| set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_MEMBER, value); | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /** | |
| * g_dbus_message_get_path: | |
| * @message: A #GDBusMessage. | |
| * | |
| * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_PATH header field. | |
| * | |
| * Returns: (nullable): The value. | |
| * | |
| * Since: 2.26 | |
| */ | |
| const gchar * | |
| g_dbus_message_get_path (GDBusMessage *message) | |
| { | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); | |
| return get_object_path_header (message, G_DBUS_MESSAGE_HEADER_FIELD_PATH); | |
| } | |
| /** | |
| * g_dbus_message_set_path: | |
| * @message: A #GDBusMessage. | |
| * @value: (nullable): The value to set. | |
| * | |
| * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_PATH header field. | |
| * | |
| * Since: 2.26 | |
| */ | |
| void | |
| g_dbus_message_set_path (GDBusMessage *message, | |
| const gchar *value) | |
| { | |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); | |
| g_return_if_fail (value == NULL || g_variant_is_object_path (value)); | |
| set_object_path_header (message, G_DBUS_MESSAGE_HEADER_FIELD_PATH, value); | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /** | |
| * g_dbus_message_get_sender: | |
| * @message: A #GDBusMessage. | |
| * | |
| * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_SENDER header field. | |
| * | |
| * Returns: (nullable): The value. | |
| * | |
| * Since: 2.26 | |
| */ | |
| const gchar * | |
| g_dbus_message_get_sender (GDBusMessage *message) | |
| { | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); | |
| return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SENDER); | |
| } | |
| /** | |
| * g_dbus_message_set_sender: | |
| * @message: A #GDBusMessage. | |
| * @value: (nullable): The value to set. | |
| * | |
| * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_SENDER header field. | |
| * | |
| * Since: 2.26 | |
| */ | |
| void | |
| g_dbus_message_set_sender (GDBusMessage *message, | |
| const gchar *value) | |
| { | |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); | |
| g_return_if_fail (value == NULL || g_dbus_is_name (value)); | |
| set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SENDER, value); | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /** | |
| * g_dbus_message_get_destination: | |
| * @message: A #GDBusMessage. | |
| * | |
| * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION header field. | |
| * | |
| * Returns: (nullable): The value. | |
| * | |
| * Since: 2.26 | |
| */ | |
| const gchar * | |
| g_dbus_message_get_destination (GDBusMessage *message) | |
| { | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); | |
| return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION); | |
| } | |
| /** | |
| * g_dbus_message_set_destination: | |
| * @message: A #GDBusMessage. | |
| * @value: (nullable): The value to set. | |
| * | |
| * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION header field. | |
| * | |
| * Since: 2.26 | |
| */ | |
| void | |
| g_dbus_message_set_destination (GDBusMessage *message, | |
| const gchar *value) | |
| { | |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); | |
| g_return_if_fail (value == NULL || g_dbus_is_name (value)); | |
| set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION, value); | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /** | |
| * g_dbus_message_get_error_name: | |
| * @message: A #GDBusMessage. | |
| * | |
| * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME header field. | |
| * | |
| * Returns: (nullable): The value. | |
| * | |
| * Since: 2.26 | |
| */ | |
| const gchar * | |
| g_dbus_message_get_error_name (GDBusMessage *message) | |
| { | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); | |
| return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME); | |
| } | |
| /** | |
| * g_dbus_message_set_error_name: | |
| * @message: (nullable): A #GDBusMessage. | |
| * @value: The value to set. | |
| * | |
| * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME header field. | |
| * | |
| * Since: 2.26 | |
| */ | |
| void | |
| g_dbus_message_set_error_name (GDBusMessage *message, | |
| const gchar *value) | |
| { | |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); | |
| g_return_if_fail (value == NULL || g_dbus_is_error_name (value)); | |
| set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME, value); | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /** | |
| * g_dbus_message_get_signature: | |
| * @message: A #GDBusMessage. | |
| * | |
| * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE header field. | |
| * | |
| * This will always be non-%NULL, but may be an empty string. | |
| * | |
| * Returns: (not nullable): The value. | |
| * | |
| * Since: 2.26 | |
| */ | |
| const gchar * | |
| g_dbus_message_get_signature (GDBusMessage *message) | |
| { | |
| const gchar *ret; | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); | |
| ret = get_signature_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE); | |
| if (ret == NULL) | |
| ret = ""; | |
| return ret; | |
| } | |
| /** | |
| * g_dbus_message_set_signature: | |
| * @message: A #GDBusMessage. | |
| * @value: (nullable): The value to set. | |
| * | |
| * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE header field. | |
| * | |
| * Since: 2.26 | |
| */ | |
| void | |
| g_dbus_message_set_signature (GDBusMessage *message, | |
| const gchar *value) | |
| { | |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); | |
| g_return_if_fail (value == NULL || g_variant_is_signature (value)); | |
| set_signature_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE, value); | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /** | |
| * g_dbus_message_get_arg0: | |
| * @message: A #GDBusMessage. | |
| * | |
| * Convenience to get the first item in the body of @message. | |
| * | |
| * See [method@Gio.DBusMessage.get_arg0_path] for returning object-path-typed | |
| * arg0 values. | |
| * | |
| * Returns: (nullable): The string item or %NULL if the first item in the body of | |
| * @message is not a string. | |
| * | |
| * Since: 2.26 | |
| */ | |
| const gchar * | |
| g_dbus_message_get_arg0 (GDBusMessage *message) | |
| { | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); | |
| if (message->arg0_cache != NULL && | |
| g_variant_is_of_type (message->arg0_cache, G_VARIANT_TYPE_STRING)) | |
| return g_variant_get_string (message->arg0_cache, NULL); | |
| return NULL; | |
| } | |
| /** | |
| * g_dbus_message_get_arg0_path: | |
| * @message: A `GDBusMessage`. | |
| * | |
| * Convenience to get the first item in the body of @message. | |
| * | |
| * See [method@Gio.DBusMessage.get_arg0] for returning string-typed arg0 values. | |
| * | |
| * Returns: (nullable): The object path item or `NULL` if the first item in the | |
| * body of @message is not an object path. | |
| * | |
| * Since: 2.80 | |
| */ | |
| const gchar * | |
| g_dbus_message_get_arg0_path (GDBusMessage *message) | |
| { | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); | |
| if (message->arg0_cache != NULL && | |
| g_variant_is_of_type (message->arg0_cache, G_VARIANT_TYPE_OBJECT_PATH)) | |
| return g_variant_get_string (message->arg0_cache, NULL); | |
| return NULL; | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /** | |
| * g_dbus_message_get_num_unix_fds: | |
| * @message: A #GDBusMessage. | |
| * | |
| * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS header field. | |
| * | |
| * Returns: The value. | |
| * | |
| * Since: 2.26 | |
| */ | |
| guint32 | |
| g_dbus_message_get_num_unix_fds (GDBusMessage *message) | |
| { | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), 0); | |
| return get_uint32_header (message, G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS); | |
| } | |
| /** | |
| * g_dbus_message_set_num_unix_fds: | |
| * @message: A #GDBusMessage. | |
| * @value: The value to set. | |
| * | |
| * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS header field. | |
| * | |
| * Since: 2.26 | |
| */ | |
| void | |
| g_dbus_message_set_num_unix_fds (GDBusMessage *message, | |
| guint32 value) | |
| { | |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); | |
| set_uint32_header (message, G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS, value); | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| /** | |
| * g_dbus_message_to_gerror: | |
| * @message: A #GDBusMessage. | |
| * @error: The #GError to set. | |
| * | |
| * If @message is not of type %G_DBUS_MESSAGE_TYPE_ERROR does | |
| * nothing and returns %FALSE. | |
| * | |
| * Otherwise this method encodes the error in @message as a #GError | |
| * using g_dbus_error_set_dbus_error() using the information in the | |
| * %G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME header field of @message as | |
| * well as the first string item in @message's body. | |
| * | |
| * Returns: %TRUE if @error was set, %FALSE otherwise. | |
| * | |
| * Since: 2.26 | |
| */ | |
| gboolean | |
| g_dbus_message_to_gerror (GDBusMessage *message, | |
| GError **error) | |
| { | |
| gboolean ret; | |
| const gchar *error_name; | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), FALSE); | |
| ret = FALSE; | |
| if (message->type != G_DBUS_MESSAGE_TYPE_ERROR) | |
| goto out; | |
| error_name = g_dbus_message_get_error_name (message); | |
| if (error_name != NULL) | |
| { | |
| GVariant *body; | |
| body = g_dbus_message_get_body (message); | |
| if (body != NULL && g_variant_is_of_type (body, G_VARIANT_TYPE ("(s)"))) | |
| { | |
| const gchar *error_message; | |
| g_variant_get (body, "(&s)", &error_message); | |
| g_dbus_error_set_dbus_error (error, | |
| error_name, | |
| error_message, | |
| NULL); | |
| } | |
| else | |
| { | |
| /* these two situations are valid, yet pretty rare */ | |
| if (body != NULL) | |
| { | |
| g_dbus_error_set_dbus_error (error, | |
| error_name, | |
| "", | |
| _("Error return with body of type “%s”"), | |
| g_variant_get_type_string (body)); | |
| } | |
| else | |
| { | |
| g_dbus_error_set_dbus_error (error, | |
| error_name, | |
| "", | |
| _("Error return with empty body")); | |
| } | |
| } | |
| } | |
| else | |
| { | |
| /* TODO: this shouldn't happen - should check this at message serialization | |
| * time and disconnect the peer. | |
| */ | |
| g_set_error (error, | |
| G_IO_ERROR, | |
| G_IO_ERROR_FAILED, | |
| "Error return without error-name header!"); | |
| } | |
| ret = TRUE; | |
| out: | |
| return ret; | |
| } | |
| /* ---------------------------------------------------------------------------------------------------- */ | |
| static gchar * | |
| flags_to_string (GType flags_type, guint value) | |
| { | |
| GString *s; | |
| GFlagsClass *klass; | |
| guint n; | |
| klass = g_type_class_ref (flags_type); | |
| s = g_string_new (NULL); | |
| for (n = 0; n < 32; n++) | |
| { | |
| if ((value & (1<<n)) != 0) | |
| { | |
| GFlagsValue *flags_value; | |
| flags_value = g_flags_get_first_value (klass, (1<<n)); | |
| if (s->len > 0) | |
| g_string_append_c (s, ','); | |
| if (flags_value != NULL) | |
| g_string_append (s, flags_value->value_nick); | |
| else | |
| g_string_append_printf (s, "unknown (bit %d)", n); | |
| } | |
| } | |
| if (s->len == 0) | |
| g_string_append (s, "none"); | |
| g_type_class_unref (klass); | |
| return g_string_free (s, FALSE); | |
| } | |
| static gint | |
| _sort_keys_func (gconstpointer a, | |
| gconstpointer b) | |
| { | |
| gint ia; | |
| gint ib; | |
| ia = GPOINTER_TO_INT (a); | |
| ib = GPOINTER_TO_INT (b); | |
| return ia - ib; | |
| } | |
| /** | |
| * g_dbus_message_print: | |
| * @message: A #GDBusMessage. | |
| * @indent: Indentation level. | |
| * | |
| * Produces a human-readable multi-line description of @message. | |
| * | |
| * The contents of the description has no ABI guarantees, the contents | |
| * and formatting is subject to change at any time. Typical output | |
| * looks something like this: | |
| * ``` | |
| * Type: method-call | |
| * Flags: none | |
| * Version: 0 | |
| * Serial: 4 | |
| * Headers: | |
| * path -> objectpath '/org/gtk/GDBus/TestObject' | |
| * interface -> 'org.gtk.GDBus.TestInterface' | |
| * member -> 'GimmeStdout' | |
| * destination -> ':1.146' | |
| * Body: () | |
| * UNIX File Descriptors: | |
| * (none) | |
| * ``` | |
| * or | |
| * ``` | |
| * Type: method-return | |
| * Flags: no-reply-expected | |
| * Version: 0 | |
| * Serial: 477 | |
| * Headers: | |
| * reply-serial -> uint32 4 | |
| * destination -> ':1.159' | |
| * sender -> ':1.146' | |
| * num-unix-fds -> uint32 1 | |
| * Body: () | |
| * UNIX File Descriptors: | |
| * fd 12: dev=0:10,mode=020620,ino=5,uid=500,gid=5,rdev=136:2,size=0,atime=1273085037,mtime=1273085851,ctime=1272982635 | |
| * ``` | |
| * | |
| * Returns: (not nullable): A string that should be freed with [func@GLib.free]. | |
| * | |
| * Since: 2.26 | |
| */ | |
| gchar * | |
| g_dbus_message_print (GDBusMessage *message, | |
| guint indent) | |
| { | |
| GString *str; | |
| gchar *s; | |
| GList *keys; | |
| GList *l; | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); | |
| str = g_string_new (NULL); | |
| s = _g_dbus_enum_to_string (G_TYPE_DBUS_MESSAGE_TYPE, message->type); | |
| g_string_append_printf (str, "%*sType: %s\n", indent, "", s); | |
| g_free (s); | |
| s = flags_to_string (G_TYPE_DBUS_MESSAGE_FLAGS, message->flags); | |
| g_string_append_printf (str, "%*sFlags: %s\n", indent, "", s); | |
| g_free (s); | |
| g_string_append_printf (str, "%*sVersion: %d\n", indent, "", message->major_protocol_version); | |
| g_string_append_printf (str, "%*sSerial: %d\n", indent, "", message->serial); | |
| g_string_append_printf (str, "%*sHeaders:\n", indent, ""); | |
| keys = g_hash_table_get_keys (message->headers); | |
| keys = g_list_sort (keys, _sort_keys_func); | |
| if (keys != NULL) | |
| { | |
| for (l = keys; l != NULL; l = l->next) | |
| { | |
| gint key = GPOINTER_TO_INT (l->data); | |
| GVariant *value; | |
| gchar *value_str; | |
| value = g_hash_table_lookup (message->headers, l->data); | |
| g_assert (value != NULL); | |
| s = _g_dbus_enum_to_string (G_TYPE_DBUS_MESSAGE_HEADER_FIELD, key); | |
| value_str = g_variant_print (value, TRUE); | |
| g_string_append_printf (str, "%*s %s -> %s\n", indent, "", s, value_str); | |
| g_free (s); | |
| g_free (value_str); | |
| } | |
| } | |
| else | |
| { | |
| g_string_append_printf (str, "%*s (none)\n", indent, ""); | |
| } | |
| g_list_free (keys); | |
| g_string_append_printf (str, "%*sBody: ", indent, ""); | |
| if (message->body != NULL) | |
| { | |
| g_variant_print_string (message->body, | |
| str, | |
| TRUE); | |
| } | |
| else | |
| { | |
| g_string_append (str, "()"); | |
| } | |
| g_string_append (str, "\n"); | |
| #ifdef G_OS_UNIX | |
| g_string_append_printf (str, "%*sUNIX File Descriptors:\n", indent, ""); | |
| if (message->fd_list != NULL) | |
| { | |
| gint num_fds; | |
| const gint *fds; | |
| gint n; | |
| fds = g_unix_fd_list_peek_fds (message->fd_list, &num_fds); | |
| if (num_fds > 0) | |
| { | |
| for (n = 0; n < num_fds; n++) | |
| { | |
| GString *fs; | |
| struct stat statbuf; | |
| fs = g_string_new (NULL); | |
| if (fstat (fds[n], &statbuf) == 0) | |
| { | |
| #ifndef MAJOR_MINOR_NOT_FOUND | |
| g_string_append_printf (fs, "%s" "dev=%d:%d", fs->len > 0 ? "," : "", | |
| (gint) major (statbuf.st_dev), (gint) minor (statbuf.st_dev)); | |
| #endif | |
| g_string_append_printf (fs, "%s" "mode=0%o", fs->len > 0 ? "," : "", | |
| (guint) statbuf.st_mode); | |
| g_string_append_printf (fs, "%s" "ino=%" G_GUINT64_FORMAT, fs->len > 0 ? "," : "", | |
| (guint64) statbuf.st_ino); | |
| g_string_append_printf (fs, "%s" "uid=%u", fs->len > 0 ? "," : "", | |
| (guint) statbuf.st_uid); | |
| g_string_append_printf (fs, "%s" "gid=%u", fs->len > 0 ? "," : "", | |
| (guint) statbuf.st_gid); | |
| #ifndef MAJOR_MINOR_NOT_FOUND | |
| g_string_append_printf (fs, "%s" "rdev=%d:%d", fs->len > 0 ? "," : "", | |
| (gint) major (statbuf.st_rdev), (gint) minor (statbuf.st_rdev)); | |
| #endif | |
| g_string_append_printf (fs, "%s" "size=%" G_GUINT64_FORMAT, fs->len > 0 ? "," : "", | |
| (guint64) statbuf.st_size); | |
| g_string_append_printf (fs, "%s" "atime=%" G_GUINT64_FORMAT, fs->len > 0 ? "," : "", | |
| (guint64) statbuf.st_atime); | |
| g_string_append_printf (fs, "%s" "mtime=%" G_GUINT64_FORMAT, fs->len > 0 ? "," : "", | |
| (guint64) statbuf.st_mtime); | |
| g_string_append_printf (fs, "%s" "ctime=%" G_GUINT64_FORMAT, fs->len > 0 ? "," : "", | |
| (guint64) statbuf.st_ctime); | |
| } | |
| else | |
| { | |
| int errsv = errno; | |
| g_string_append_printf (fs, "(fstat failed: %s)", g_strerror (errsv)); | |
| } | |
| g_string_append_printf (str, "%*s fd %d: %s\n", indent, "", fds[n], fs->str); | |
| g_string_free (fs, TRUE); | |
| } | |
| } | |
| else | |
| { | |
| g_string_append_printf (str, "%*s (empty)\n", indent, ""); | |
| } | |
| } | |
| else | |
| { | |
| g_string_append_printf (str, "%*s (none)\n", indent, ""); | |
| } | |
| #endif | |
| return g_string_free (str, FALSE); | |
| } | |
| /** | |
| * g_dbus_message_get_locked: | |
| * @message: A #GDBusMessage. | |
| * | |
| * Checks whether @message is locked. To monitor changes to this | |
| * value, conncet to the #GObject::notify signal to listen for changes | |
| * on the #GDBusMessage:locked property. | |
| * | |
| * Returns: %TRUE if @message is locked, %FALSE otherwise. | |
| * | |
| * Since: 2.26 | |
| */ | |
| gboolean | |
| g_dbus_message_get_locked (GDBusMessage *message) | |
| { | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), FALSE); | |
| return message->locked; | |
| } | |
| /** | |
| * g_dbus_message_lock: | |
| * @message: A #GDBusMessage. | |
| * | |
| * If @message is locked, does nothing. Otherwise locks the message. | |
| * | |
| * Since: 2.26 | |
| */ | |
| void | |
| g_dbus_message_lock (GDBusMessage *message) | |
| { | |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); | |
| if (message->locked) | |
| goto out; | |
| message->locked = TRUE; | |
| g_object_notify (G_OBJECT (message), "locked"); | |
| out: | |
| ; | |
| } | |
| /** | |
| * g_dbus_message_copy: | |
| * @message: A #GDBusMessage. | |
| * @error: Return location for error or %NULL. | |
| * | |
| * Copies @message. The copy is a deep copy and the returned | |
| * #GDBusMessage is completely identical except that it is guaranteed | |
| * to not be locked. | |
| * | |
| * This operation can fail if e.g. @message contains file descriptors | |
| * and the per-process or system-wide open files limit is reached. | |
| * | |
| * Returns: (transfer full): A new #GDBusMessage or %NULL if @error is set. | |
| * Free with g_object_unref(). | |
| * | |
| * Since: 2.26 | |
| */ | |
| GDBusMessage * | |
| g_dbus_message_copy (GDBusMessage *message, | |
| GError **error) | |
| { | |
| GDBusMessage *ret; | |
| GHashTableIter iter; | |
| gpointer header_key; | |
| GVariant *header_value; | |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); | |
| g_return_val_if_fail (error == NULL || *error == NULL, NULL); | |
| ret = g_dbus_message_new (); | |
| ret->type = message->type; | |
| ret->flags = message->flags; | |
| ret->byte_order = message->byte_order; | |
| ret->major_protocol_version = message->major_protocol_version; | |
| ret->serial = message->serial; | |
| #ifdef G_OS_UNIX | |
| if (message->fd_list != NULL) | |
| { | |
| gint n; | |
| gint num_fds; | |
| const gint *fds; | |
| ret->fd_list = g_unix_fd_list_new (); | |
| fds = g_unix_fd_list_peek_fds (message->fd_list, &num_fds); | |
| for (n = 0; n < num_fds; n++) | |
| { | |
| if (g_unix_fd_list_append (ret->fd_list, | |
| fds[n], | |
| error) == -1) | |
| { | |
| g_object_unref (ret); | |
| ret = NULL; | |
| goto out; | |
| } | |
| } | |
| } | |
| #endif | |
| /* see https://bugzilla.gnome.org/show_bug.cgi?id=624546#c8 for why it's fine | |
| * to just ref (as opposed to deep-copying) the GVariant instances | |
| */ | |
| ret->body = message->body != NULL ? g_variant_ref (message->body) : NULL; | |
| ret->arg0_cache = message->arg0_cache != NULL ? g_variant_ref (message->arg0_cache) : NULL; | |
| g_hash_table_iter_init (&iter, message->headers); | |
| while (g_hash_table_iter_next (&iter, &header_key, (gpointer) &header_value)) | |
| g_hash_table_insert (ret->headers, header_key, g_variant_ref (header_value)); | |
| #ifdef G_OS_UNIX | |
| out: | |
| #endif | |
| return ret; | |
| } |