| /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ |
| /* signals.c Bus signal connection implementation |
| * |
| * Copyright (C) 2003, 2005 Red Hat, Inc. |
| * |
| * Licensed under the Academic Free License version 2.1 |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program 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 General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #include <config.h> |
| #include "signals.h" |
| #include "services.h" |
| #include "utils.h" |
| #include <dbus/dbus-marshal-validate.h> |
| |
| struct BusMatchRule |
| { |
| int refcount; /**< reference count */ |
| |
| DBusConnection *matches_go_to; /**< Owner of the rule */ |
| |
| unsigned int flags; /**< BusMatchFlags */ |
| |
| int message_type; |
| char *interface; |
| char *member; |
| char *sender; |
| char *destination; |
| char *path; |
| |
| unsigned int *arg_lens; |
| char **args; |
| int args_len; |
| }; |
| |
| #define BUS_MATCH_ARG_NAMESPACE 0x4000000u |
| #define BUS_MATCH_ARG_IS_PATH 0x8000000u |
| |
| #define BUS_MATCH_ARG_FLAGS (BUS_MATCH_ARG_NAMESPACE | BUS_MATCH_ARG_IS_PATH) |
| |
| BusMatchRule* |
| bus_match_rule_new (DBusConnection *matches_go_to) |
| { |
| BusMatchRule *rule; |
| |
| rule = dbus_new0 (BusMatchRule, 1); |
| if (rule == NULL) |
| return NULL; |
| |
| rule->refcount = 1; |
| rule->matches_go_to = matches_go_to; |
| |
| #ifndef DBUS_BUILD_TESTS |
| _dbus_assert (rule->matches_go_to != NULL); |
| #endif |
| |
| return rule; |
| } |
| |
| BusMatchRule * |
| bus_match_rule_ref (BusMatchRule *rule) |
| { |
| _dbus_assert (rule->refcount > 0); |
| |
| rule->refcount += 1; |
| |
| return rule; |
| } |
| |
| void |
| bus_match_rule_unref (BusMatchRule *rule) |
| { |
| _dbus_assert (rule->refcount > 0); |
| |
| rule->refcount -= 1; |
| if (rule->refcount == 0) |
| { |
| dbus_free (rule->interface); |
| dbus_free (rule->member); |
| dbus_free (rule->sender); |
| dbus_free (rule->destination); |
| dbus_free (rule->path); |
| dbus_free (rule->arg_lens); |
| |
| /* can't use dbus_free_string_array() since there |
| * are embedded NULL |
| */ |
| if (rule->args) |
| { |
| int i; |
| |
| i = 0; |
| while (i < rule->args_len) |
| { |
| if (rule->args[i]) |
| dbus_free (rule->args[i]); |
| ++i; |
| } |
| |
| dbus_free (rule->args); |
| } |
| |
| dbus_free (rule); |
| } |
| } |
| |
| #ifdef DBUS_ENABLE_VERBOSE_MODE |
| /* Note this function does not do escaping, so it's only |
| * good for debug spew at the moment |
| */ |
| static char* |
| match_rule_to_string (BusMatchRule *rule) |
| { |
| DBusString str; |
| char *ret; |
| |
| if (!_dbus_string_init (&str)) |
| { |
| char *s; |
| while ((s = _dbus_strdup ("nomem")) == NULL) |
| ; /* only OK for debug spew... */ |
| return s; |
| } |
| |
| if (rule->flags & BUS_MATCH_MESSAGE_TYPE) |
| { |
| if (!_dbus_string_append_printf (&str, "type='%s'", |
| dbus_message_type_to_string (rule->message_type))) |
| goto nomem; |
| } |
| |
| if (rule->flags & BUS_MATCH_INTERFACE) |
| { |
| if (_dbus_string_get_length (&str) > 0) |
| { |
| if (!_dbus_string_append (&str, ",")) |
| goto nomem; |
| } |
| |
| if (!_dbus_string_append_printf (&str, "interface='%s'", rule->interface)) |
| goto nomem; |
| } |
| |
| if (rule->flags & BUS_MATCH_MEMBER) |
| { |
| if (_dbus_string_get_length (&str) > 0) |
| { |
| if (!_dbus_string_append (&str, ",")) |
| goto nomem; |
| } |
| |
| if (!_dbus_string_append_printf (&str, "member='%s'", rule->member)) |
| goto nomem; |
| } |
| |
| if (rule->flags & BUS_MATCH_PATH) |
| { |
| if (_dbus_string_get_length (&str) > 0) |
| { |
| if (!_dbus_string_append (&str, ",")) |
| goto nomem; |
| } |
| |
| if (!_dbus_string_append_printf (&str, "path='%s'", rule->path)) |
| goto nomem; |
| } |
| |
| if (rule->flags & BUS_MATCH_PATH_NAMESPACE) |
| { |
| if (_dbus_string_get_length (&str) > 0) |
| { |
| if (!_dbus_string_append (&str, ",")) |
| goto nomem; |
| } |
| |
| if (!_dbus_string_append_printf (&str, "path_namespace='%s'", rule->path)) |
| goto nomem; |
| } |
| |
| if (rule->flags & BUS_MATCH_SENDER) |
| { |
| if (_dbus_string_get_length (&str) > 0) |
| { |
| if (!_dbus_string_append (&str, ",")) |
| goto nomem; |
| } |
| |
| if (!_dbus_string_append_printf (&str, "sender='%s'", rule->sender)) |
| goto nomem; |
| } |
| |
| if (rule->flags & BUS_MATCH_DESTINATION) |
| { |
| if (_dbus_string_get_length (&str) > 0) |
| { |
| if (!_dbus_string_append (&str, ",")) |
| goto nomem; |
| } |
| |
| if (!_dbus_string_append_printf (&str, "destination='%s'", rule->destination)) |
| goto nomem; |
| } |
| |
| if (rule->flags & BUS_MATCH_CLIENT_IS_EAVESDROPPING) |
| { |
| if (_dbus_string_get_length (&str) > 0) |
| { |
| if (!_dbus_string_append (&str, ",")) |
| goto nomem; |
| } |
| |
| if (!_dbus_string_append_printf (&str, "eavesdrop='%s'", |
| (rule->flags & BUS_MATCH_CLIENT_IS_EAVESDROPPING) ? |
| "true" : "false")) |
| goto nomem; |
| } |
| |
| if (rule->flags & BUS_MATCH_ARGS) |
| { |
| int i; |
| |
| _dbus_assert (rule->args != NULL); |
| |
| i = 0; |
| while (i < rule->args_len) |
| { |
| if (rule->args[i] != NULL) |
| { |
| dbus_bool_t is_path, is_namespace; |
| |
| if (_dbus_string_get_length (&str) > 0) |
| { |
| if (!_dbus_string_append (&str, ",")) |
| goto nomem; |
| } |
| |
| is_path = (rule->arg_lens[i] & BUS_MATCH_ARG_IS_PATH) != 0; |
| is_namespace = (rule->arg_lens[i] & BUS_MATCH_ARG_NAMESPACE) != 0; |
| |
| if (!_dbus_string_append_printf (&str, |
| "arg%d%s='%s'", |
| i, |
| is_path ? "path" : |
| is_namespace ? "namespace" : "", |
| rule->args[i])) |
| goto nomem; |
| } |
| |
| ++i; |
| } |
| } |
| |
| if (!_dbus_string_steal_data (&str, &ret)) |
| goto nomem; |
| |
| _dbus_string_free (&str); |
| return ret; |
| |
| nomem: |
| _dbus_string_free (&str); |
| { |
| char *s; |
| while ((s = _dbus_strdup ("nomem")) == NULL) |
| ; /* only OK for debug spew... */ |
| return s; |
| } |
| } |
| #endif /* DBUS_ENABLE_VERBOSE_MODE */ |
| |
| dbus_bool_t |
| bus_match_rule_set_message_type (BusMatchRule *rule, |
| int type) |
| { |
| rule->flags |= BUS_MATCH_MESSAGE_TYPE; |
| |
| rule->message_type = type; |
| |
| return TRUE; |
| } |
| |
| dbus_bool_t |
| bus_match_rule_set_interface (BusMatchRule *rule, |
| const char *interface) |
| { |
| char *new; |
| |
| _dbus_assert (interface != NULL); |
| |
| new = _dbus_strdup (interface); |
| if (new == NULL) |
| return FALSE; |
| |
| rule->flags |= BUS_MATCH_INTERFACE; |
| dbus_free (rule->interface); |
| rule->interface = new; |
| |
| return TRUE; |
| } |
| |
| dbus_bool_t |
| bus_match_rule_set_member (BusMatchRule *rule, |
| const char *member) |
| { |
| char *new; |
| |
| _dbus_assert (member != NULL); |
| |
| new = _dbus_strdup (member); |
| if (new == NULL) |
| return FALSE; |
| |
| rule->flags |= BUS_MATCH_MEMBER; |
| dbus_free (rule->member); |
| rule->member = new; |
| |
| return TRUE; |
| } |
| |
| dbus_bool_t |
| bus_match_rule_set_sender (BusMatchRule *rule, |
| const char *sender) |
| { |
| char *new; |
| |
| _dbus_assert (sender != NULL); |
| |
| new = _dbus_strdup (sender); |
| if (new == NULL) |
| return FALSE; |
| |
| rule->flags |= BUS_MATCH_SENDER; |
| dbus_free (rule->sender); |
| rule->sender = new; |
| |
| return TRUE; |
| } |
| |
| dbus_bool_t |
| bus_match_rule_set_destination (BusMatchRule *rule, |
| const char *destination) |
| { |
| char *new; |
| |
| _dbus_assert (destination != NULL); |
| |
| new = _dbus_strdup (destination); |
| if (new == NULL) |
| return FALSE; |
| |
| rule->flags |= BUS_MATCH_DESTINATION; |
| dbus_free (rule->destination); |
| rule->destination = new; |
| |
| return TRUE; |
| } |
| |
| void |
| bus_match_rule_set_client_is_eavesdropping (BusMatchRule *rule, |
| dbus_bool_t is_eavesdropping) |
| { |
| if (is_eavesdropping) |
| rule->flags |= BUS_MATCH_CLIENT_IS_EAVESDROPPING; |
| else |
| rule->flags &= ~(BUS_MATCH_CLIENT_IS_EAVESDROPPING); |
| } |
| |
| dbus_bool_t |
| bus_match_rule_set_path (BusMatchRule *rule, |
| const char *path, |
| dbus_bool_t is_namespace) |
| { |
| char *new; |
| |
| _dbus_assert (path != NULL); |
| |
| new = _dbus_strdup (path); |
| if (new == NULL) |
| return FALSE; |
| |
| rule->flags &= ~(BUS_MATCH_PATH|BUS_MATCH_PATH_NAMESPACE); |
| |
| if (is_namespace) |
| rule->flags |= BUS_MATCH_PATH_NAMESPACE; |
| else |
| rule->flags |= BUS_MATCH_PATH; |
| |
| dbus_free (rule->path); |
| rule->path = new; |
| |
| return TRUE; |
| } |
| |
| dbus_bool_t |
| bus_match_rule_set_arg (BusMatchRule *rule, |
| int arg, |
| const DBusString *value, |
| dbus_bool_t is_path, |
| dbus_bool_t is_namespace) |
| { |
| int length; |
| char *new; |
| |
| _dbus_assert (value != NULL); |
| |
| /* args_len is the number of args not including null termination |
| * in the char** |
| */ |
| if (arg >= rule->args_len) |
| { |
| unsigned int *new_arg_lens; |
| char **new_args; |
| int new_args_len; |
| int i; |
| |
| new_args_len = arg + 1; |
| |
| /* add another + 1 here for null termination */ |
| new_args = dbus_realloc (rule->args, |
| sizeof (char *) * (new_args_len + 1)); |
| if (new_args == NULL) |
| return FALSE; |
| |
| /* NULL the new slots */ |
| i = rule->args_len; |
| while (i <= new_args_len) /* <= for null termination */ |
| { |
| new_args[i] = NULL; |
| ++i; |
| } |
| |
| rule->args = new_args; |
| |
| /* and now add to the lengths */ |
| new_arg_lens = dbus_realloc (rule->arg_lens, |
| sizeof (int) * (new_args_len + 1)); |
| |
| if (new_arg_lens == NULL) |
| return FALSE; |
| |
| /* zero the new slots */ |
| i = rule->args_len; |
| while (i <= new_args_len) /* <= for null termination */ |
| { |
| new_arg_lens[i] = 0; |
| ++i; |
| } |
| |
| rule->arg_lens = new_arg_lens; |
| rule->args_len = new_args_len; |
| } |
| |
| length = _dbus_string_get_length (value); |
| if (!_dbus_string_copy_data (value, &new)) |
| return FALSE; |
| |
| rule->flags |= BUS_MATCH_ARGS; |
| |
| dbus_free (rule->args[arg]); |
| rule->arg_lens[arg] = length; |
| rule->args[arg] = new; |
| |
| if (is_path) |
| rule->arg_lens[arg] |= BUS_MATCH_ARG_IS_PATH; |
| |
| if (is_namespace) |
| rule->arg_lens[arg] |= BUS_MATCH_ARG_NAMESPACE; |
| |
| /* NULL termination didn't get busted */ |
| _dbus_assert (rule->args[rule->args_len] == NULL); |
| _dbus_assert (rule->arg_lens[rule->args_len] == 0); |
| |
| return TRUE; |
| } |
| |
| #define ISWHITE(c) (((c) == ' ') || ((c) == '\t') || ((c) == '\n') || ((c) == '\r')) |
| |
| static dbus_bool_t |
| find_key (const DBusString *str, |
| int start, |
| DBusString *key, |
| int *value_pos, |
| DBusError *error) |
| { |
| const char *p; |
| const char *s; |
| const char *key_start; |
| const char *key_end; |
| |
| _DBUS_ASSERT_ERROR_IS_CLEAR (error); |
| |
| s = _dbus_string_get_const_data (str); |
| |
| p = s + start; |
| |
| while (*p && ISWHITE (*p)) |
| ++p; |
| |
| key_start = p; |
| |
| while (*p && *p != '=' && !ISWHITE (*p)) |
| ++p; |
| |
| key_end = p; |
| |
| while (*p && ISWHITE (*p)) |
| ++p; |
| |
| if (key_start == key_end) |
| { |
| /* Empty match rules or trailing whitespace are OK */ |
| *value_pos = p - s; |
| return TRUE; |
| } |
| |
| if (*p != '=') |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "Match rule has a key with no subsequent '=' character"); |
| return FALSE; |
| } |
| ++p; |
| |
| if (!_dbus_string_append_len (key, key_start, key_end - key_start)) |
| { |
| BUS_SET_OOM (error); |
| return FALSE; |
| } |
| |
| *value_pos = p - s; |
| |
| return TRUE; |
| } |
| |
| static dbus_bool_t |
| find_value (const DBusString *str, |
| int start, |
| const char *key, |
| DBusString *value, |
| int *value_end, |
| DBusError *error) |
| { |
| const char *p; |
| const char *s; |
| char quote_char; |
| int orig_len; |
| |
| _DBUS_ASSERT_ERROR_IS_CLEAR (error); |
| |
| orig_len = _dbus_string_get_length (value); |
| |
| s = _dbus_string_get_const_data (str); |
| |
| p = s + start; |
| |
| quote_char = '\0'; |
| |
| while (*p) |
| { |
| if (quote_char == '\0') |
| { |
| switch (*p) |
| { |
| case '\0': |
| goto done; |
| |
| case '\'': |
| quote_char = '\''; |
| goto next; |
| |
| case ',': |
| ++p; |
| goto done; |
| |
| case '\\': |
| quote_char = '\\'; |
| goto next; |
| |
| default: |
| if (!_dbus_string_append_byte (value, *p)) |
| { |
| BUS_SET_OOM (error); |
| goto failed; |
| } |
| } |
| } |
| else if (quote_char == '\\') |
| { |
| /* \ only counts as an escape if escaping a quote mark */ |
| if (*p != '\'') |
| { |
| if (!_dbus_string_append_byte (value, '\\')) |
| { |
| BUS_SET_OOM (error); |
| goto failed; |
| } |
| } |
| |
| if (!_dbus_string_append_byte (value, *p)) |
| { |
| BUS_SET_OOM (error); |
| goto failed; |
| } |
| |
| quote_char = '\0'; |
| } |
| else |
| { |
| _dbus_assert (quote_char == '\''); |
| |
| if (*p == '\'') |
| { |
| quote_char = '\0'; |
| } |
| else |
| { |
| if (!_dbus_string_append_byte (value, *p)) |
| { |
| BUS_SET_OOM (error); |
| goto failed; |
| } |
| } |
| } |
| |
| next: |
| ++p; |
| } |
| |
| done: |
| |
| if (quote_char == '\\') |
| { |
| if (!_dbus_string_append_byte (value, '\\')) |
| { |
| BUS_SET_OOM (error); |
| goto failed; |
| } |
| } |
| else if (quote_char == '\'') |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "Unbalanced quotation marks in match rule"); |
| goto failed; |
| } |
| else |
| _dbus_assert (quote_char == '\0'); |
| |
| /* Zero-length values are allowed */ |
| |
| *value_end = p - s; |
| |
| return TRUE; |
| |
| failed: |
| _DBUS_ASSERT_ERROR_IS_SET (error); |
| _dbus_string_set_length (value, orig_len); |
| return FALSE; |
| } |
| |
| /* duplicates aren't allowed so the real legitimate max is only 6 or |
| * so. Leaving extra so we don't have to bother to update it. |
| * FIXME this is sort of busted now with arg matching, but we let |
| * you match on up to 10 args for now |
| */ |
| #define MAX_RULE_TOKENS 16 |
| |
| /* this is slightly too high level to be termed a "token" |
| * but let's not be pedantic. |
| */ |
| typedef struct |
| { |
| char *key; |
| char *value; |
| } RuleToken; |
| |
| static dbus_bool_t |
| tokenize_rule (const DBusString *rule_text, |
| RuleToken tokens[MAX_RULE_TOKENS], |
| DBusError *error) |
| { |
| int i; |
| int pos; |
| DBusString key; |
| DBusString value; |
| dbus_bool_t retval; |
| |
| retval = FALSE; |
| |
| if (!_dbus_string_init (&key)) |
| { |
| BUS_SET_OOM (error); |
| return FALSE; |
| } |
| |
| if (!_dbus_string_init (&value)) |
| { |
| _dbus_string_free (&key); |
| BUS_SET_OOM (error); |
| return FALSE; |
| } |
| |
| i = 0; |
| pos = 0; |
| while (i < MAX_RULE_TOKENS && |
| pos < _dbus_string_get_length (rule_text)) |
| { |
| _dbus_assert (tokens[i].key == NULL); |
| _dbus_assert (tokens[i].value == NULL); |
| |
| if (!find_key (rule_text, pos, &key, &pos, error)) |
| goto out; |
| |
| if (_dbus_string_get_length (&key) == 0) |
| goto next; |
| |
| if (!_dbus_string_steal_data (&key, &tokens[i].key)) |
| { |
| BUS_SET_OOM (error); |
| goto out; |
| } |
| |
| if (!find_value (rule_text, pos, tokens[i].key, &value, &pos, error)) |
| goto out; |
| |
| if (!_dbus_string_steal_data (&value, &tokens[i].value)) |
| { |
| BUS_SET_OOM (error); |
| goto out; |
| } |
| |
| next: |
| ++i; |
| } |
| |
| retval = TRUE; |
| |
| out: |
| if (!retval) |
| { |
| i = 0; |
| while (tokens[i].key || tokens[i].value) |
| { |
| dbus_free (tokens[i].key); |
| dbus_free (tokens[i].value); |
| tokens[i].key = NULL; |
| tokens[i].value = NULL; |
| ++i; |
| } |
| } |
| |
| _dbus_string_free (&key); |
| _dbus_string_free (&value); |
| |
| return retval; |
| } |
| |
| static dbus_bool_t |
| bus_match_rule_parse_arg_match (BusMatchRule *rule, |
| const char *key, |
| const DBusString *value, |
| DBusError *error) |
| { |
| dbus_bool_t is_path = FALSE; |
| dbus_bool_t is_namespace = FALSE; |
| DBusString key_str; |
| unsigned long arg; |
| int length; |
| int end; |
| |
| /* For now, arg0='foo' always implies that 'foo' is a |
| * DBUS_TYPE_STRING. Someday we could add an arg0type='int32' thing |
| * if we wanted, which would specify another type, in which case |
| * arg0='5' would have the 5 parsed as an int rather than string. |
| */ |
| |
| /* First we need to parse arg0 = 0, arg27 = 27 */ |
| |
| _dbus_string_init_const (&key_str, key); |
| length = _dbus_string_get_length (&key_str); |
| |
| if (_dbus_string_get_length (&key_str) < 4) |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "Key '%s' in match rule starts with 'arg' but lacks an arg number. Should be 'arg0' or 'arg7' for example.\n", key); |
| goto failed; |
| } |
| |
| if (!_dbus_string_parse_uint (&key_str, 3, &arg, &end)) |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "Key '%s' in match rule starts with 'arg' but could not parse arg number. Should be 'arg0' or 'arg7' for example.\n", key); |
| goto failed; |
| } |
| |
| if (end != length) |
| { |
| if ((end + strlen ("path")) == length && |
| _dbus_string_ends_with_c_str (&key_str, "path")) |
| { |
| is_path = TRUE; |
| } |
| else if (_dbus_string_equal_c_str (&key_str, "arg0namespace")) |
| { |
| int value_len = _dbus_string_get_length (value); |
| |
| is_namespace = TRUE; |
| |
| if (!_dbus_validate_bus_namespace (value, 0, value_len)) |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "arg0namespace='%s' is not a valid prefix of a bus name", |
| _dbus_string_get_const_data (value)); |
| goto failed; |
| } |
| } |
| else |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "Key '%s' in match rule contains junk after argument number (%u). Only 'arg%upath' (for example) or 'arg0namespace' are valid", key, arg, arg); |
| goto failed; |
| } |
| } |
| |
| /* If we didn't check this we could allocate a huge amount of RAM */ |
| if (arg > DBUS_MAXIMUM_MATCH_RULE_ARG_NUMBER) |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "Key '%s' in match rule has arg number %lu but the maximum is %d.\n", key, (unsigned long) arg, DBUS_MAXIMUM_MATCH_RULE_ARG_NUMBER); |
| goto failed; |
| } |
| |
| if ((rule->flags & BUS_MATCH_ARGS) && |
| rule->args_len > (int) arg && |
| rule->args[arg] != NULL) |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "Argument %d matched more than once in match rule\n", key); |
| goto failed; |
| } |
| |
| if (!bus_match_rule_set_arg (rule, arg, value, is_path, is_namespace)) |
| { |
| BUS_SET_OOM (error); |
| goto failed; |
| } |
| |
| return TRUE; |
| |
| failed: |
| _DBUS_ASSERT_ERROR_IS_SET (error); |
| return FALSE; |
| } |
| |
| /* |
| * The format is comma-separated with strings quoted with single quotes |
| * as for the shell (to escape a literal single quote, use '\''). |
| * |
| * type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='Foo', |
| * path='/bar/foo',destination=':452345.34' |
| * |
| */ |
| BusMatchRule* |
| bus_match_rule_parse (DBusConnection *matches_go_to, |
| const DBusString *rule_text, |
| DBusError *error) |
| { |
| BusMatchRule *rule; |
| RuleToken tokens[MAX_RULE_TOKENS+1]; /* NULL termination + 1 */ |
| int i; |
| |
| _DBUS_ASSERT_ERROR_IS_CLEAR (error); |
| |
| if (_dbus_string_get_length (rule_text) > DBUS_MAXIMUM_MATCH_RULE_LENGTH) |
| { |
| dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED, |
| "Match rule text is %d bytes, maximum is %d", |
| _dbus_string_get_length (rule_text), |
| DBUS_MAXIMUM_MATCH_RULE_LENGTH); |
| return NULL; |
| } |
| |
| memset (tokens, '\0', sizeof (tokens)); |
| |
| rule = bus_match_rule_new (matches_go_to); |
| if (rule == NULL) |
| { |
| BUS_SET_OOM (error); |
| goto failed; |
| } |
| |
| if (!tokenize_rule (rule_text, tokens, error)) |
| goto failed; |
| |
| i = 0; |
| while (tokens[i].key != NULL) |
| { |
| DBusString tmp_str; |
| int len; |
| const char *key = tokens[i].key; |
| const char *value = tokens[i].value; |
| |
| _dbus_string_init_const (&tmp_str, value); |
| len = _dbus_string_get_length (&tmp_str); |
| |
| if (strcmp (key, "type") == 0) |
| { |
| int t; |
| |
| if (rule->flags & BUS_MATCH_MESSAGE_TYPE) |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "Key %s specified twice in match rule\n", key); |
| goto failed; |
| } |
| |
| t = dbus_message_type_from_string (value); |
| |
| if (t == DBUS_MESSAGE_TYPE_INVALID) |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "Invalid message type (%s) in match rule\n", value); |
| goto failed; |
| } |
| |
| if (!bus_match_rule_set_message_type (rule, t)) |
| { |
| BUS_SET_OOM (error); |
| goto failed; |
| } |
| } |
| else if (strcmp (key, "sender") == 0) |
| { |
| if (rule->flags & BUS_MATCH_SENDER) |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "Key %s specified twice in match rule\n", key); |
| goto failed; |
| } |
| |
| if (!_dbus_validate_bus_name (&tmp_str, 0, len)) |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "Sender name '%s' is invalid\n", value); |
| goto failed; |
| } |
| |
| if (!bus_match_rule_set_sender (rule, value)) |
| { |
| BUS_SET_OOM (error); |
| goto failed; |
| } |
| } |
| else if (strcmp (key, "interface") == 0) |
| { |
| if (rule->flags & BUS_MATCH_INTERFACE) |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "Key %s specified twice in match rule\n", key); |
| goto failed; |
| } |
| |
| if (!_dbus_validate_interface (&tmp_str, 0, len)) |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "Interface name '%s' is invalid\n", value); |
| goto failed; |
| } |
| |
| if (!bus_match_rule_set_interface (rule, value)) |
| { |
| BUS_SET_OOM (error); |
| goto failed; |
| } |
| } |
| else if (strcmp (key, "member") == 0) |
| { |
| if (rule->flags & BUS_MATCH_MEMBER) |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "Key %s specified twice in match rule\n", key); |
| goto failed; |
| } |
| |
| if (!_dbus_validate_member (&tmp_str, 0, len)) |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "Member name '%s' is invalid\n", value); |
| goto failed; |
| } |
| |
| if (!bus_match_rule_set_member (rule, value)) |
| { |
| BUS_SET_OOM (error); |
| goto failed; |
| } |
| } |
| else if (strcmp (key, "path") == 0 || |
| strcmp (key, "path_namespace") == 0) |
| { |
| dbus_bool_t is_namespace = (strcmp (key, "path_namespace") == 0); |
| |
| if (rule->flags & (BUS_MATCH_PATH | BUS_MATCH_PATH_NAMESPACE)) |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "path or path_namespace specified twice in match rule\n"); |
| goto failed; |
| } |
| |
| if (!_dbus_validate_path (&tmp_str, 0, len)) |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "Path '%s' is invalid\n", value); |
| goto failed; |
| } |
| |
| if (!bus_match_rule_set_path (rule, value, is_namespace)) |
| { |
| BUS_SET_OOM (error); |
| goto failed; |
| } |
| } |
| else if (strcmp (key, "destination") == 0) |
| { |
| if (rule->flags & BUS_MATCH_DESTINATION) |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "Key %s specified twice in match rule\n", key); |
| goto failed; |
| } |
| |
| if (!_dbus_validate_bus_name (&tmp_str, 0, len)) |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "Destination name '%s' is invalid\n", value); |
| goto failed; |
| } |
| |
| if (!bus_match_rule_set_destination (rule, value)) |
| { |
| BUS_SET_OOM (error); |
| goto failed; |
| } |
| } |
| else if (strcmp (key, "eavesdrop") == 0) |
| { |
| /* do not detect "eavesdrop" being used more than once in rule: |
| * 1) it's not possible, it's only in the flags |
| * 2) it might be used twice to disable eavesdropping when it's |
| * automatically added (eg dbus-monitor/bustle) */ |
| |
| /* we accept only "true|false" as possible values */ |
| if ((strcmp (value, "true") == 0)) |
| { |
| bus_match_rule_set_client_is_eavesdropping (rule, TRUE); |
| } |
| else if (strcmp (value, "false") == 0) |
| { |
| bus_match_rule_set_client_is_eavesdropping (rule, FALSE); |
| } |
| else |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "eavesdrop='%s' is invalid, " |
| "it should be 'true' or 'false'\n", |
| value); |
| goto failed; |
| } |
| } |
| else if (strncmp (key, "arg", 3) == 0) |
| { |
| if (!bus_match_rule_parse_arg_match (rule, key, &tmp_str, error)) |
| goto failed; |
| } |
| else |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID, |
| "Unknown key \"%s\" in match rule", |
| key); |
| goto failed; |
| } |
| |
| ++i; |
| } |
| |
| |
| goto out; |
| |
| failed: |
| _DBUS_ASSERT_ERROR_IS_SET (error); |
| if (rule) |
| { |
| bus_match_rule_unref (rule); |
| rule = NULL; |
| } |
| |
| out: |
| |
| i = 0; |
| while (tokens[i].key || tokens[i].value) |
| { |
| _dbus_assert (i < MAX_RULE_TOKENS); |
| dbus_free (tokens[i].key); |
| dbus_free (tokens[i].value); |
| ++i; |
| } |
| |
| return rule; |
| } |
| |
| typedef struct RulePool RulePool; |
| struct RulePool |
| { |
| /* Maps non-NULL interface names to non-NULL (DBusList **)s */ |
| DBusHashTable *rules_by_iface; |
| |
| /* List of BusMatchRules which don't specify an interface */ |
| DBusList *rules_without_iface; |
| }; |
| |
| struct BusMatchmaker |
| { |
| int refcount; |
| |
| /* Pools of rules, grouped by the type of message they match. 0 |
| * (DBUS_MESSAGE_TYPE_INVALID) represents rules that do not specify a message |
| * type. |
| */ |
| RulePool rules_by_type[DBUS_NUM_MESSAGE_TYPES]; |
| }; |
| |
| static void |
| rule_list_free (DBusList **rules) |
| { |
| while (*rules != NULL) |
| { |
| BusMatchRule *rule; |
| |
| rule = (*rules)->data; |
| bus_match_rule_unref (rule); |
| _dbus_list_remove_link (rules, *rules); |
| } |
| } |
| |
| static void |
| rule_list_ptr_free (DBusList **list) |
| { |
| /* We have to cope with NULL because the hash table frees the "existing" |
| * value (which is NULL) when creating a new table entry... |
| */ |
| if (list != NULL) |
| { |
| rule_list_free (list); |
| dbus_free (list); |
| } |
| } |
| |
| BusMatchmaker* |
| bus_matchmaker_new (void) |
| { |
| BusMatchmaker *matchmaker; |
| int i; |
| |
| matchmaker = dbus_new0 (BusMatchmaker, 1); |
| if (matchmaker == NULL) |
| return NULL; |
| |
| matchmaker->refcount = 1; |
| |
| for (i = DBUS_MESSAGE_TYPE_INVALID; i < DBUS_NUM_MESSAGE_TYPES; i++) |
| { |
| RulePool *p = matchmaker->rules_by_type + i; |
| |
| p->rules_by_iface = _dbus_hash_table_new (DBUS_HASH_STRING, |
| dbus_free, (DBusFreeFunction) rule_list_ptr_free); |
| |
| if (p->rules_by_iface == NULL) |
| goto nomem; |
| } |
| |
| return matchmaker; |
| |
| nomem: |
| for (i = DBUS_MESSAGE_TYPE_INVALID; i < DBUS_NUM_MESSAGE_TYPES; i++) |
| { |
| RulePool *p = matchmaker->rules_by_type + i; |
| |
| if (p->rules_by_iface == NULL) |
| break; |
| else |
| _dbus_hash_table_unref (p->rules_by_iface); |
| } |
| dbus_free (matchmaker); |
| |
| return NULL; |
| } |
| |
| static DBusList ** |
| bus_matchmaker_get_rules (BusMatchmaker *matchmaker, |
| int message_type, |
| const char *interface, |
| dbus_bool_t create) |
| { |
| RulePool *p; |
| |
| _dbus_assert (message_type >= 0); |
| _dbus_assert (message_type < DBUS_NUM_MESSAGE_TYPES); |
| |
| _dbus_verbose ("Looking up rules for message_type %d, interface %s\n", |
| message_type, |
| interface != NULL ? interface : "<null>"); |
| |
| p = matchmaker->rules_by_type + message_type; |
| |
| if (interface == NULL) |
| { |
| return &p->rules_without_iface; |
| } |
| else |
| { |
| DBusList **list; |
| |
| list = _dbus_hash_table_lookup_string (p->rules_by_iface, interface); |
| |
| if (list == NULL && create) |
| { |
| char *dupped_interface; |
| |
| list = dbus_new0 (DBusList *, 1); |
| if (list == NULL) |
| return NULL; |
| |
| dupped_interface = _dbus_strdup (interface); |
| if (dupped_interface == NULL) |
| { |
| dbus_free (list); |
| return NULL; |
| } |
| |
| _dbus_verbose ("Adding list for type %d, iface %s\n", message_type, |
| interface); |
| |
| if (!_dbus_hash_table_insert_string (p->rules_by_iface, |
| dupped_interface, list)) |
| { |
| dbus_free (list); |
| dbus_free (dupped_interface); |
| return NULL; |
| } |
| } |
| |
| return list; |
| } |
| } |
| |
| static void |
| bus_matchmaker_gc_rules (BusMatchmaker *matchmaker, |
| int message_type, |
| const char *interface, |
| DBusList **rules) |
| { |
| RulePool *p; |
| |
| if (interface == NULL) |
| return; |
| |
| if (*rules != NULL) |
| return; |
| |
| _dbus_verbose ("GCing HT entry for message_type %u, interface %s\n", |
| message_type, interface); |
| |
| p = matchmaker->rules_by_type + message_type; |
| |
| _dbus_assert (_dbus_hash_table_lookup_string (p->rules_by_iface, interface) |
| == rules); |
| |
| _dbus_hash_table_remove_string (p->rules_by_iface, interface); |
| } |
| |
| BusMatchmaker * |
| bus_matchmaker_ref (BusMatchmaker *matchmaker) |
| { |
| _dbus_assert (matchmaker->refcount > 0); |
| |
| matchmaker->refcount += 1; |
| |
| return matchmaker; |
| } |
| |
| void |
| bus_matchmaker_unref (BusMatchmaker *matchmaker) |
| { |
| _dbus_assert (matchmaker->refcount > 0); |
| |
| matchmaker->refcount -= 1; |
| if (matchmaker->refcount == 0) |
| { |
| int i; |
| |
| for (i = DBUS_MESSAGE_TYPE_INVALID; i < DBUS_NUM_MESSAGE_TYPES; i++) |
| { |
| RulePool *p = matchmaker->rules_by_type + i; |
| |
| _dbus_hash_table_unref (p->rules_by_iface); |
| rule_list_free (&p->rules_without_iface); |
| } |
| |
| dbus_free (matchmaker); |
| } |
| } |
| |
| /* The rule can't be modified after it's added. */ |
| dbus_bool_t |
| bus_matchmaker_add_rule (BusMatchmaker *matchmaker, |
| BusMatchRule *rule) |
| { |
| DBusList **rules; |
| |
| _dbus_assert (bus_connection_is_active (rule->matches_go_to)); |
| |
| _dbus_verbose ("Adding rule with message_type %d, interface %s\n", |
| rule->message_type, |
| rule->interface != NULL ? rule->interface : "<null>"); |
| |
| rules = bus_matchmaker_get_rules (matchmaker, rule->message_type, |
| rule->interface, TRUE); |
| |
| if (rules == NULL) |
| return FALSE; |
| |
| if (!_dbus_list_append (rules, rule)) |
| return FALSE; |
| |
| if (!bus_connection_add_match_rule (rule->matches_go_to, rule)) |
| { |
| _dbus_list_remove_last (rules, rule); |
| bus_matchmaker_gc_rules (matchmaker, rule->message_type, |
| rule->interface, rules); |
| return FALSE; |
| } |
| |
| bus_match_rule_ref (rule); |
| |
| #ifdef DBUS_ENABLE_VERBOSE_MODE |
| { |
| char *s = match_rule_to_string (rule); |
| |
| _dbus_verbose ("Added match rule %s to connection %p\n", |
| s, rule->matches_go_to); |
| dbus_free (s); |
| } |
| #endif |
| |
| return TRUE; |
| } |
| |
| static dbus_bool_t |
| match_rule_equal (BusMatchRule *a, |
| BusMatchRule *b) |
| { |
| if (a->flags != b->flags) |
| return FALSE; |
| |
| if (a->matches_go_to != b->matches_go_to) |
| return FALSE; |
| |
| if ((a->flags & BUS_MATCH_MESSAGE_TYPE) && |
| a->message_type != b->message_type) |
| return FALSE; |
| |
| if ((a->flags & BUS_MATCH_MEMBER) && |
| strcmp (a->member, b->member) != 0) |
| return FALSE; |
| |
| if ((a->flags & BUS_MATCH_PATH) && |
| strcmp (a->path, b->path) != 0) |
| return FALSE; |
| |
| if ((a->flags & BUS_MATCH_INTERFACE) && |
| strcmp (a->interface, b->interface) != 0) |
| return FALSE; |
| |
| if ((a->flags & BUS_MATCH_SENDER) && |
| strcmp (a->sender, b->sender) != 0) |
| return FALSE; |
| |
| if ((a->flags & BUS_MATCH_DESTINATION) && |
| strcmp (a->destination, b->destination) != 0) |
| return FALSE; |
| |
| /* we already compared the value of flags, and |
| * BUS_MATCH_CLIENT_IS_EAVESDROPPING does not have another struct member */ |
| |
| if (a->flags & BUS_MATCH_ARGS) |
| { |
| int i; |
| |
| if (a->args_len != b->args_len) |
| return FALSE; |
| |
| i = 0; |
| while (i < a->args_len) |
| { |
| int length; |
| |
| if ((a->args[i] != NULL) != (b->args[i] != NULL)) |
| return FALSE; |
| |
| if (a->arg_lens[i] != b->arg_lens[i]) |
| return FALSE; |
| |
| length = a->arg_lens[i] & ~BUS_MATCH_ARG_FLAGS; |
| |
| if (a->args[i] != NULL) |
| { |
| _dbus_assert (b->args[i] != NULL); |
| if (memcmp (a->args[i], b->args[i], length) != 0) |
| return FALSE; |
| } |
| |
| ++i; |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| static void |
| bus_matchmaker_remove_rule_link (DBusList **rules, |
| DBusList *link) |
| { |
| BusMatchRule *rule = link->data; |
| |
| bus_connection_remove_match_rule (rule->matches_go_to, rule); |
| _dbus_list_remove_link (rules, link); |
| |
| #ifdef DBUS_ENABLE_VERBOSE_MODE |
| { |
| char *s = match_rule_to_string (rule); |
| |
| _dbus_verbose ("Removed match rule %s for connection %p\n", |
| s, rule->matches_go_to); |
| dbus_free (s); |
| } |
| #endif |
| |
| bus_match_rule_unref (rule); |
| } |
| |
| void |
| bus_matchmaker_remove_rule (BusMatchmaker *matchmaker, |
| BusMatchRule *rule) |
| { |
| DBusList **rules; |
| |
| _dbus_verbose ("Removing rule with message_type %d, interface %s\n", |
| rule->message_type, |
| rule->interface != NULL ? rule->interface : "<null>"); |
| |
| bus_connection_remove_match_rule (rule->matches_go_to, rule); |
| |
| rules = bus_matchmaker_get_rules (matchmaker, rule->message_type, |
| rule->interface, FALSE); |
| |
| /* We should only be asked to remove a rule by identity right after it was |
| * added, so there should be a list for it. |
| */ |
| _dbus_assert (rules != NULL); |
| |
| _dbus_list_remove (rules, rule); |
| bus_matchmaker_gc_rules (matchmaker, rule->message_type, rule->interface, |
| rules); |
| |
| #ifdef DBUS_ENABLE_VERBOSE_MODE |
| { |
| char *s = match_rule_to_string (rule); |
| |
| _dbus_verbose ("Removed match rule %s for connection %p\n", |
| s, rule->matches_go_to); |
| dbus_free (s); |
| } |
| #endif |
| |
| bus_match_rule_unref (rule); |
| } |
| |
| /* Remove a single rule which is equal to the given rule by value */ |
| dbus_bool_t |
| bus_matchmaker_remove_rule_by_value (BusMatchmaker *matchmaker, |
| BusMatchRule *value, |
| DBusError *error) |
| { |
| DBusList **rules; |
| DBusList *link = NULL; |
| |
| _dbus_verbose ("Removing rule by value with message_type %d, interface %s\n", |
| value->message_type, |
| value->interface != NULL ? value->interface : "<null>"); |
| |
| rules = bus_matchmaker_get_rules (matchmaker, value->message_type, |
| value->interface, FALSE); |
| |
| if (rules != NULL) |
| { |
| /* we traverse backward because bus_connection_remove_match_rule() |
| * removes the most-recently-added rule |
| */ |
| link = _dbus_list_get_last_link (rules); |
| while (link != NULL) |
| { |
| BusMatchRule *rule; |
| DBusList *prev; |
| |
| rule = link->data; |
| prev = _dbus_list_get_prev_link (rules, link); |
| |
| if (match_rule_equal (rule, value)) |
| { |
| bus_matchmaker_remove_rule_link (rules, link); |
| break; |
| } |
| |
| link = prev; |
| } |
| } |
| |
| if (link == NULL) |
| { |
| dbus_set_error (error, DBUS_ERROR_MATCH_RULE_NOT_FOUND, |
| "The given match rule wasn't found and can't be removed"); |
| return FALSE; |
| } |
| |
| bus_matchmaker_gc_rules (matchmaker, value->message_type, value->interface, |
| rules); |
| |
| return TRUE; |
| } |
| |
| static void |
| rule_list_remove_by_connection (DBusList **rules, |
| DBusConnection *connection) |
| { |
| DBusList *link; |
| |
| link = _dbus_list_get_first_link (rules); |
| while (link != NULL) |
| { |
| BusMatchRule *rule; |
| DBusList *next; |
| |
| rule = link->data; |
| next = _dbus_list_get_next_link (rules, link); |
| |
| if (rule->matches_go_to == connection) |
| { |
| bus_matchmaker_remove_rule_link (rules, link); |
| } |
| else if (((rule->flags & BUS_MATCH_SENDER) && *rule->sender == ':') || |
| ((rule->flags & BUS_MATCH_DESTINATION) && *rule->destination == ':')) |
| { |
| /* The rule matches to/from a base service, see if it's the |
| * one being disconnected, since we know this service name |
| * will never be recycled. |
| */ |
| const char *name; |
| |
| name = bus_connection_get_name (connection); |
| _dbus_assert (name != NULL); /* because we're an active connection */ |
| |
| if (((rule->flags & BUS_MATCH_SENDER) && |
| strcmp (rule->sender, name) == 0) || |
| ((rule->flags & BUS_MATCH_DESTINATION) && |
| strcmp (rule->destination, name) == 0)) |
| { |
| bus_matchmaker_remove_rule_link (rules, link); |
| } |
| } |
| |
| link = next; |
| } |
| } |
| |
| void |
| bus_matchmaker_disconnected (BusMatchmaker *matchmaker, |
| DBusConnection *connection) |
| { |
| int i; |
| |
| /* FIXME |
| * |
| * This scans all match rules on the bus. We could avoid that |
| * for the rules belonging to the connection, since we keep |
| * a list of those; but for the rules that just refer to |
| * the connection we'd need to do something more elaborate. |
| */ |
| |
| _dbus_assert (bus_connection_is_active (connection)); |
| |
| _dbus_verbose ("Removing all rules for connection %p\n", connection); |
| |
| for (i = DBUS_MESSAGE_TYPE_INVALID; i < DBUS_NUM_MESSAGE_TYPES; i++) |
| { |
| RulePool *p = matchmaker->rules_by_type + i; |
| DBusHashIter iter; |
| |
| rule_list_remove_by_connection (&p->rules_without_iface, connection); |
| |
| _dbus_hash_iter_init (p->rules_by_iface, &iter); |
| while (_dbus_hash_iter_next (&iter)) |
| { |
| DBusList **items = _dbus_hash_iter_get_value (&iter); |
| |
| rule_list_remove_by_connection (items, connection); |
| |
| if (*items == NULL) |
| _dbus_hash_iter_remove_entry (&iter); |
| } |
| } |
| } |
| |
| static dbus_bool_t |
| connection_is_primary_owner (DBusConnection *connection, |
| const char *service_name) |
| { |
| BusService *service; |
| DBusString str; |
| BusRegistry *registry; |
| |
| _dbus_assert (connection != NULL); |
| |
| registry = bus_connection_get_registry (connection); |
| |
| _dbus_string_init_const (&str, service_name); |
| service = bus_registry_lookup (registry, &str); |
| |
| if (service == NULL) |
| return FALSE; /* Service doesn't exist so connection can't own it. */ |
| |
| return bus_service_get_primary_owners_connection (service) == connection; |
| } |
| |
| static dbus_bool_t |
| str_has_prefix (const char *str, const char *prefix) |
| { |
| size_t prefix_len; |
| prefix_len = strlen (prefix); |
| if (strncmp (str, prefix, prefix_len) == 0) |
| return TRUE; |
| else |
| return FALSE; |
| } |
| |
| static dbus_bool_t |
| match_rule_matches (BusMatchRule *rule, |
| DBusConnection *sender, |
| DBusConnection *addressed_recipient, |
| DBusMessage *message, |
| BusMatchFlags already_matched) |
| { |
| dbus_bool_t wants_to_eavesdrop = FALSE; |
| int flags; |
| |
| /* All features of the match rule are AND'd together, |
| * so FALSE if any of them don't match. |
| */ |
| |
| /* sender/addressed_recipient of #NULL may mean bus driver, |
| * or for addressed_recipient may mean a message with no |
| * specific recipient (i.e. a signal) |
| */ |
| |
| /* Don't bother re-matching features we've already checked implicitly. */ |
| flags = rule->flags & (~already_matched); |
| |
| if (flags & BUS_MATCH_CLIENT_IS_EAVESDROPPING) |
| wants_to_eavesdrop = TRUE; |
| |
| if (flags & BUS_MATCH_MESSAGE_TYPE) |
| { |
| _dbus_assert (rule->message_type != DBUS_MESSAGE_TYPE_INVALID); |
| |
| if (rule->message_type != dbus_message_get_type (message)) |
| return FALSE; |
| } |
| |
| if (flags & BUS_MATCH_INTERFACE) |
| { |
| const char *iface; |
| |
| _dbus_assert (rule->interface != NULL); |
| |
| iface = dbus_message_get_interface (message); |
| if (iface == NULL) |
| return FALSE; |
| |
| if (strcmp (iface, rule->interface) != 0) |
| return FALSE; |
| } |
| |
| if (flags & BUS_MATCH_MEMBER) |
| { |
| const char *member; |
| |
| _dbus_assert (rule->member != NULL); |
| |
| member = dbus_message_get_member (message); |
| if (member == NULL) |
| return FALSE; |
| |
| if (strcmp (member, rule->member) != 0) |
| return FALSE; |
| } |
| |
| if (flags & BUS_MATCH_SENDER) |
| { |
| _dbus_assert (rule->sender != NULL); |
| |
| if (sender == NULL) |
| { |
| if (strcmp (rule->sender, |
| DBUS_SERVICE_DBUS) != 0) |
| return FALSE; |
| } |
| else |
| { |
| if (!connection_is_primary_owner (sender, rule->sender)) |
| return FALSE; |
| } |
| } |
| |
| /* Note: this part is relevant for eavesdropper rules: |
| * Two cases: |
| * 1) rule has a destination to be matched |
| * (flag BUS_MATCH_DESTINATION present). Rule will match if: |
| * - rule->destination matches the addressed_recipient |
| * AND |
| * - wants_to_eavesdrop=TRUE |
| * |
| * Note: (the case in which addressed_recipient is the actual rule owner |
| * is handled elsewere in dispatch.c:bus_dispatch_matches(). |
| * |
| * 2) rule has no destination. Rule will match if: |
| * - message has no specified destination (ie broadcasts) |
| * (Note: this will rule out unicast method calls and unicast signals, |
| * fixing FDO#269748) |
| * OR |
| * - wants_to_eavesdrop=TRUE (destination-catch-all situation) |
| */ |
| if (flags & BUS_MATCH_DESTINATION) |
| { |
| const char *destination; |
| |
| _dbus_assert (rule->destination != NULL); |
| |
| destination = dbus_message_get_destination (message); |
| if (destination == NULL) |
| /* broadcast, but this rule specified a destination: no match */ |
| return FALSE; |
| |
| /* rule owner does not intend to eavesdrop: we'll deliver only msgs |
| * directed to it, NOT MATCHING */ |
| if (!wants_to_eavesdrop) |
| return FALSE; |
| |
| if (addressed_recipient == NULL) |
| { |
| if (strcmp (rule->destination, |
| DBUS_SERVICE_DBUS) != 0) |
| return FALSE; |
| } |
| else |
| { |
| if (!connection_is_primary_owner (addressed_recipient, rule->destination)) |
| return FALSE; |
| } |
| } else { /* no destination in rule */ |
| dbus_bool_t msg_is_broadcast; |
| |
| _dbus_assert (rule->destination == NULL); |
| |
| msg_is_broadcast = (dbus_message_get_destination (message) == NULL); |
| |
| if (!wants_to_eavesdrop && !msg_is_broadcast) |
| return FALSE; |
| |
| /* if we are here rule owner intends to eavesdrop |
| * OR |
| * message is being broadcasted */ |
| } |
| |
| if (flags & BUS_MATCH_PATH) |
| { |
| const char *path; |
| |
| _dbus_assert (rule->path != NULL); |
| |
| path = dbus_message_get_path (message); |
| if (path == NULL) |
| return FALSE; |
| |
| if (strcmp (path, rule->path) != 0) |
| return FALSE; |
| } |
| |
| if (flags & BUS_MATCH_PATH_NAMESPACE) |
| { |
| const char *path; |
| int len; |
| |
| _dbus_assert (rule->path != NULL); |
| |
| path = dbus_message_get_path (message); |
| if (path == NULL) |
| return FALSE; |
| |
| if (!str_has_prefix (path, rule->path)) |
| return FALSE; |
| |
| len = strlen (rule->path); |
| |
| /* Check that the actual argument is within the expected |
| * namespace, rather than just starting with that string, |
| * by checking that the matched prefix is followed by a '/' |
| * or the end of the path. |
| */ |
| if (path[len] != '\0' && path[len] != '/') |
| return FALSE; |
| } |
| |
| if (flags & BUS_MATCH_ARGS) |
| { |
| int i; |
| DBusMessageIter iter; |
| |
| _dbus_assert (rule->args != NULL); |
| |
| dbus_message_iter_init (message, &iter); |
| |
| i = 0; |
| while (i < rule->args_len) |
| { |
| int current_type; |
| const char *expected_arg; |
| int expected_length; |
| dbus_bool_t is_path, is_namespace; |
| |
| expected_arg = rule->args[i]; |
| expected_length = rule->arg_lens[i] & ~BUS_MATCH_ARG_FLAGS; |
| is_path = (rule->arg_lens[i] & BUS_MATCH_ARG_IS_PATH) != 0; |
| is_namespace = (rule->arg_lens[i] & BUS_MATCH_ARG_NAMESPACE) != 0; |
| |
| current_type = dbus_message_iter_get_arg_type (&iter); |
| |
| if (expected_arg != NULL) |
| { |
| const char *actual_arg; |
| int actual_length; |
| |
| if (current_type != DBUS_TYPE_STRING && |
| (!is_path || current_type != DBUS_TYPE_OBJECT_PATH)) |
| return FALSE; |
| |
| actual_arg = NULL; |
| dbus_message_iter_get_basic (&iter, &actual_arg); |
| _dbus_assert (actual_arg != NULL); |
| |
| actual_length = strlen (actual_arg); |
| |
| if (is_path) |
| { |
| if (actual_length < expected_length && |
| actual_arg[actual_length - 1] != '/') |
| return FALSE; |
| |
| if (expected_length < actual_length && |
| expected_arg[expected_length - 1] != '/') |
| return FALSE; |
| |
| if (memcmp (actual_arg, expected_arg, |
| MIN (actual_length, expected_length)) != 0) |
| return FALSE; |
| } |
| else if (is_namespace) |
| { |
| if (expected_length > actual_length) |
| return FALSE; |
| |
| /* If the actual argument doesn't start with the expected |
| * namespace, then we don't match. |
| */ |
| if (memcmp (expected_arg, actual_arg, expected_length) != 0) |
| return FALSE; |
| |
| if (expected_length < actual_length) |
| { |
| /* Check that the actual argument is within the expected |
| * namespace, rather than just starting with that string, |
| * by checking that the matched prefix ends in a '.'. |
| * |
| * This doesn't stop "foo.bar." matching "foo.bar..baz" |
| * which is an invalid namespace, but at some point the |
| * daemon can't cover up for broken services. |
| */ |
| if (actual_arg[expected_length] != '.') |
| return FALSE; |
| } |
| /* otherwise we had an exact match. */ |
| } |
| else |
| { |
| if (expected_length != actual_length || |
| memcmp (expected_arg, actual_arg, expected_length) != 0) |
| return FALSE; |
| } |
| |
| } |
| |
| if (current_type != DBUS_TYPE_INVALID) |
| dbus_message_iter_next (&iter); |
| |
| ++i; |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| static dbus_bool_t |
| get_recipients_from_list (DBusList **rules, |
| DBusConnection *sender, |
| DBusConnection *addressed_recipient, |
| DBusMessage *message, |
| DBusList **recipients_p) |
| { |
| DBusList *link; |
| |
| if (rules == NULL) |
| return TRUE; |
| |
| link = _dbus_list_get_first_link (rules); |
| while (link != NULL) |
| { |
| BusMatchRule *rule; |
| |
| rule = link->data; |
| |
| #ifdef DBUS_ENABLE_VERBOSE_MODE |
| { |
| char *s = match_rule_to_string (rule); |
| |
| _dbus_verbose ("Checking whether message matches rule %s for connection %p\n", |
| s, rule->matches_go_to); |
| dbus_free (s); |
| } |
| #endif |
| |
| if (match_rule_matches (rule, |
| sender, addressed_recipient, message, |
| BUS_MATCH_MESSAGE_TYPE | BUS_MATCH_INTERFACE)) |
| { |
| _dbus_verbose ("Rule matched\n"); |
| |
| /* Append to the list if we haven't already */ |
| if (bus_connection_mark_stamp (rule->matches_go_to)) |
| { |
| if (!_dbus_list_append (recipients_p, rule->matches_go_to)) |
| return FALSE; |
| } |
| #ifdef DBUS_ENABLE_VERBOSE_MODE |
| else |
| { |
| _dbus_verbose ("Connection already receiving this message, so not adding again\n"); |
| } |
| #endif /* DBUS_ENABLE_VERBOSE_MODE */ |
| } |
| |
| link = _dbus_list_get_next_link (rules, link); |
| } |
| |
| return TRUE; |
| } |
| |
| dbus_bool_t |
| bus_matchmaker_get_recipients (BusMatchmaker *matchmaker, |
| BusConnections *connections, |
| DBusConnection *sender, |
| DBusConnection *addressed_recipient, |
| DBusMessage *message, |
| DBusList **recipients_p) |
| { |
| int type; |
| const char *interface; |
| DBusList **neither, **just_type, **just_iface, **both; |
| |
| _dbus_assert (*recipients_p == NULL); |
| |
| /* This avoids sending same message to the same connection twice. |
| * Purpose of the stamp instead of a bool is to avoid iterating over |
| * all connections resetting the bool each time. |
| */ |
| bus_connections_increment_stamp (connections); |
| |
| /* addressed_recipient is already receiving the message, don't add to list. |
| * NULL addressed_recipient means either bus driver, or this is a signal |
| * and thus lacks a specific addressed_recipient. |
| */ |
| if (addressed_recipient != NULL) |
| bus_connection_mark_stamp (addressed_recipient); |
| |
| type = dbus_message_get_type (message); |
| interface = dbus_message_get_interface (message); |
| |
| neither = bus_matchmaker_get_rules (matchmaker, DBUS_MESSAGE_TYPE_INVALID, |
| NULL, FALSE); |
| just_type = just_iface = both = NULL; |
| |
| if (interface != NULL) |
| just_iface = bus_matchmaker_get_rules (matchmaker, |
| DBUS_MESSAGE_TYPE_INVALID, interface, FALSE); |
| |
| if (type > DBUS_MESSAGE_TYPE_INVALID && type < DBUS_NUM_MESSAGE_TYPES) |
| { |
| just_type = bus_matchmaker_get_rules (matchmaker, type, NULL, FALSE); |
| |
| if (interface != NULL) |
| both = bus_matchmaker_get_rules (matchmaker, type, interface, FALSE); |
| } |
| |
| if (!(get_recipients_from_list (neither, sender, addressed_recipient, |
| message, recipients_p) && |
| get_recipients_from_list (just_iface, sender, addressed_recipient, |
| message, recipients_p) && |
| get_recipients_from_list (just_type, sender, addressed_recipient, |
| message, recipients_p) && |
| get_recipients_from_list (both, sender, addressed_recipient, |
| message, recipients_p))) |
| { |
| _dbus_list_clear (recipients_p); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| #ifdef DBUS_BUILD_TESTS |
| #include "test.h" |
| #include <stdlib.h> |
| |
| static BusMatchRule* |
| check_parse (dbus_bool_t should_succeed, |
| const char *text) |
| { |
| BusMatchRule *rule; |
| DBusString str; |
| DBusError error; |
| |
| dbus_error_init (&error); |
| |
| _dbus_string_init_const (&str, text); |
| |
| rule = bus_match_rule_parse (NULL, &str, &error); |
| if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY)) |
| { |
| dbus_error_free (&error); |
| return NULL; |
| } |
| |
| if (should_succeed && rule == NULL) |
| { |
| _dbus_warn ("Failed to parse: %s: %s: \"%s\"\n", |
| error.name, error.message, |
| _dbus_string_get_const_data (&str)); |
| exit (1); |
| } |
| |
| if (!should_succeed && rule != NULL) |
| { |
| _dbus_warn ("Failed to fail to parse: \"%s\"\n", |
| _dbus_string_get_const_data (&str)); |
| exit (1); |
| } |
| |
| dbus_error_free (&error); |
| |
| return rule; |
| } |
| |
| static void |
| assert_large_rule (BusMatchRule *rule) |
| { |
| _dbus_assert (rule->flags & BUS_MATCH_MESSAGE_TYPE); |
| _dbus_assert (rule->flags & BUS_MATCH_SENDER); |
| _dbus_assert (rule->flags & BUS_MATCH_INTERFACE); |
| _dbus_assert (rule->flags & BUS_MATCH_MEMBER); |
| _dbus_assert (rule->flags & BUS_MATCH_DESTINATION); |
| _dbus_assert (rule->flags & BUS_MATCH_PATH); |
| |
| _dbus_assert (rule->message_type == DBUS_MESSAGE_TYPE_SIGNAL); |
| _dbus_assert (rule->interface != NULL); |
| _dbus_assert (rule->member != NULL); |
| _dbus_assert (rule->sender != NULL); |
| _dbus_assert (rule->destination != NULL); |
| _dbus_assert (rule->path != NULL); |
| |
| _dbus_assert (strcmp (rule->interface, "org.freedesktop.DBusInterface") == 0); |
| _dbus_assert (strcmp (rule->sender, "org.freedesktop.DBusSender") == 0); |
| _dbus_assert (strcmp (rule->member, "Foo") == 0); |
| _dbus_assert (strcmp (rule->path, "/bar/foo") == 0); |
| _dbus_assert (strcmp (rule->destination, ":452345.34") == 0); |
| } |
| |
| static dbus_bool_t |
| test_parsing (void *data) |
| { |
| BusMatchRule *rule; |
| |
| rule = check_parse (TRUE, "type='signal',sender='org.freedesktop.DBusSender',interface='org.freedesktop.DBusInterface',member='Foo',path='/bar/foo',destination=':452345.34'"); |
| if (rule != NULL) |
| { |
| assert_large_rule (rule); |
| bus_match_rule_unref (rule); |
| } |
| |
| /* With extra whitespace and useless quotes */ |
| rule = check_parse (TRUE, " type='signal', \tsender='org.freedes''ktop.DBusSender', interface='org.freedesktop.DBusInterface''''', \tmember='Foo',path='/bar/foo',destination=':452345.34'''''"); |
| if (rule != NULL) |
| { |
| assert_large_rule (rule); |
| bus_match_rule_unref (rule); |
| } |
| |
| |
| /* A simple signal connection */ |
| rule = check_parse (TRUE, "type='signal',path='/foo',interface='org.Bar'"); |
| if (rule != NULL) |
| { |
| _dbus_assert (rule->flags & BUS_MATCH_MESSAGE_TYPE); |
| _dbus_assert (rule->flags & BUS_MATCH_INTERFACE); |
| _dbus_assert (rule->flags & BUS_MATCH_PATH); |
| |
| _dbus_assert (rule->message_type == DBUS_MESSAGE_TYPE_SIGNAL); |
| _dbus_assert (rule->interface != NULL); |
| _dbus_assert (rule->path != NULL); |
| |
| _dbus_assert (strcmp (rule->interface, "org.Bar") == 0); |
| _dbus_assert (strcmp (rule->path, "/foo") == 0); |
| |
| bus_match_rule_unref (rule); |
| } |
| |
| /* argN */ |
| rule = check_parse (TRUE, "arg0='foo'"); |
| if (rule != NULL) |
| { |
| _dbus_assert (rule->flags == BUS_MATCH_ARGS); |
| _dbus_assert (rule->args != NULL); |
| _dbus_assert (rule->args_len == 1); |
| _dbus_assert (rule->args[0] != NULL); |
| _dbus_assert (rule->args[1] == NULL); |
| _dbus_assert (strcmp (rule->args[0], "foo") == 0); |
| |
| bus_match_rule_unref (rule); |
| } |
| |
| rule = check_parse (TRUE, "arg1='foo'"); |
| if (rule != NULL) |
| { |
| _dbus_assert (rule->flags == BUS_MATCH_ARGS); |
| _dbus_assert (rule->args != NULL); |
| _dbus_assert (rule->args_len == 2); |
| _dbus_assert (rule->args[0] == NULL); |
| _dbus_assert (rule->args[1] != NULL); |
| _dbus_assert (rule->args[2] == NULL); |
| _dbus_assert (strcmp (rule->args[1], "foo") == 0); |
| |
| bus_match_rule_unref (rule); |
| } |
| |
| rule = check_parse (TRUE, "arg2='foo'"); |
| if (rule != NULL) |
| { |
| _dbus_assert (rule->flags == BUS_MATCH_ARGS); |
| _dbus_assert (rule->args != NULL); |
| _dbus_assert (rule->args_len == 3); |
| _dbus_assert (rule->args[0] == NULL); |
| _dbus_assert (rule->args[1] == NULL); |
| _dbus_assert (rule->args[2] != NULL); |
| _dbus_assert (rule->args[3] == NULL); |
| _dbus_assert (strcmp (rule->args[2], "foo") == 0); |
| |
| bus_match_rule_unref (rule); |
| } |
| |
| rule = check_parse (TRUE, "arg40='foo'"); |
| if (rule != NULL) |
| { |
| _dbus_assert (rule->flags == BUS_MATCH_ARGS); |
| _dbus_assert (rule->args != NULL); |
| _dbus_assert (rule->args_len == 41); |
| _dbus_assert (rule->args[0] == NULL); |
| _dbus_assert (rule->args[1] == NULL); |
| _dbus_assert (rule->args[40] != NULL); |
| _dbus_assert (rule->args[41] == NULL); |
| _dbus_assert (strcmp (rule->args[40], "foo") == 0); |
| |
| bus_match_rule_unref (rule); |
| } |
| |
| rule = check_parse (TRUE, "arg63='foo'"); |
| if (rule != NULL) |
| { |
| _dbus_assert (rule->flags == BUS_MATCH_ARGS); |
| _dbus_assert (rule->args != NULL); |
| _dbus_assert (rule->args_len == 64); |
| _dbus_assert (rule->args[0] == NULL); |
| _dbus_assert (rule->args[1] == NULL); |
| _dbus_assert (rule->args[63] != NULL); |
| _dbus_assert (rule->args[64] == NULL); |
| _dbus_assert (strcmp (rule->args[63], "foo") == 0); |
| |
| bus_match_rule_unref (rule); |
| } |
| |
| rule = check_parse (TRUE, "arg7path='/foo'"); |
| if (rule != NULL) |
| { |
| _dbus_assert (rule->flags = BUS_MATCH_ARGS); |
| _dbus_assert (rule->args != NULL); |
| _dbus_assert (rule->args_len == 8); |
| _dbus_assert (rule->args[7] != NULL); |
| _dbus_assert (rule->args[8] == NULL); |
| _dbus_assert (strcmp (rule->args[7], "/foo") == 0); |
| _dbus_assert ((rule->arg_lens[7] & BUS_MATCH_ARG_IS_PATH) |
| == BUS_MATCH_ARG_IS_PATH); |
| |
| bus_match_rule_unref (rule); |
| } |
| |
| /* Arg 0 namespace matches */ |
| rule = check_parse (TRUE, "arg0namespace='foo'"); |
| if (rule != NULL) |
| { |
| _dbus_assert (rule->flags == BUS_MATCH_ARGS); |
| _dbus_assert (rule->args != NULL); |
| _dbus_assert (rule->args_len == 1); |
| _dbus_assert (strcmp (rule->args[0], "foo") == 0); |
| _dbus_assert ((rule->arg_lens[0] & BUS_MATCH_ARG_NAMESPACE) |
| == BUS_MATCH_ARG_NAMESPACE); |
| |
| bus_match_rule_unref (rule); |
| } |
| |
| rule = check_parse (TRUE, "arg0namespace='foo.bar'"); |
| if (rule != NULL) |
| { |
| _dbus_assert (rule->flags == BUS_MATCH_ARGS); |
| _dbus_assert (rule->args != NULL); |
| _dbus_assert (rule->args_len == 1); |
| _dbus_assert (strcmp (rule->args[0], "foo.bar") == 0); |
| _dbus_assert ((rule->arg_lens[0] & BUS_MATCH_ARG_NAMESPACE) |
| == BUS_MATCH_ARG_NAMESPACE); |
| |
| bus_match_rule_unref (rule); |
| } |
| |
| /* Only arg0namespace is supported. */ |
| rule = check_parse (FALSE, "arg1namespace='foo'"); |
| _dbus_assert (rule == NULL); |
| |
| /* An empty string isn't a valid namespace prefix (you should just not |
| * specify this key at all). |
| */ |
| rule = check_parse (FALSE, "arg0namespace=''"); |
| _dbus_assert (rule == NULL); |
| |
| /* Trailing periods aren't allowed (earlier versions of the arg0namespace |
| * spec allowed a single trailing period, which altered the semantics) */ |
| rule = check_parse (FALSE, "arg0namespace='foo.'"); |
| _dbus_assert (rule == NULL); |
| |
| rule = check_parse (FALSE, "arg0namespace='foo.bar.'"); |
| _dbus_assert (rule == NULL); |
| |
| rule = check_parse (FALSE, "arg0namespace='foo..'"); |
| _dbus_assert (rule == NULL); |
| |
| rule = check_parse (FALSE, "arg0namespace='foo.bar..'"); |
| _dbus_assert (rule == NULL); |
| |
| /* Too-large argN */ |
| rule = check_parse (FALSE, "arg300='foo'"); |
| _dbus_assert (rule == NULL); |
| rule = check_parse (FALSE, "arg64='foo'"); |
| _dbus_assert (rule == NULL); |
| |
| /* No N in argN */ |
| rule = check_parse (FALSE, "arg='foo'"); |
| _dbus_assert (rule == NULL); |
| rule = check_parse (FALSE, "argv='foo'"); |
| _dbus_assert (rule == NULL); |
| rule = check_parse (FALSE, "arg3junk='foo'"); |
| _dbus_assert (rule == NULL); |
| rule = check_parse (FALSE, "argument='foo'"); |
| _dbus_assert (rule == NULL); |
| |
| /* Reject duplicates */ |
| rule = check_parse (FALSE, "type='signal',type='method_call'"); |
| _dbus_assert (rule == NULL); |
| |
| rule = check_parse (TRUE, "path_namespace='/foo/bar'"); |
| if (rule != NULL) |
| { |
| _dbus_assert (rule->flags == BUS_MATCH_PATH_NAMESPACE); |
| _dbus_assert (rule->path != NULL); |
| _dbus_assert (strcmp (rule->path, "/foo/bar") == 0); |
| |
| bus_match_rule_unref (rule); |
| } |
| |
| /* Almost a duplicate */ |
| rule = check_parse (FALSE, "path='/foo',path_namespace='/foo'"); |
| _dbus_assert (rule == NULL); |
| |
| /* Trailing / was supported in the initial proposal, but now isn't */ |
| rule = check_parse (FALSE, "path_namespace='/foo/'"); |
| _dbus_assert (rule == NULL); |
| |
| /* Duplicates with the argN code */ |
| rule = check_parse (FALSE, "arg0='foo',arg0='bar'"); |
| _dbus_assert (rule == NULL); |
| rule = check_parse (FALSE, "arg3='foo',arg3='bar'"); |
| _dbus_assert (rule == NULL); |
| rule = check_parse (FALSE, "arg30='foo',arg30='bar'"); |
| _dbus_assert (rule == NULL); |
| |
| /* Reject broken keys */ |
| rule = check_parse (FALSE, "blah='signal'"); |
| _dbus_assert (rule == NULL); |
| |
| /* Reject broken values */ |
| rule = check_parse (FALSE, "type='chouin'"); |
| _dbus_assert (rule == NULL); |
| rule = check_parse (FALSE, "interface='abc@def++'"); |
| _dbus_assert (rule == NULL); |
| rule = check_parse (FALSE, "service='youpi'"); |
| _dbus_assert (rule == NULL); |
| |
| /* Allow empty rule */ |
| rule = check_parse (TRUE, ""); |
| if (rule != NULL) |
| { |
| _dbus_assert (rule->flags == 0); |
| |
| bus_match_rule_unref (rule); |
| } |
| |
| /* All-whitespace rule is the same as empty */ |
| rule = check_parse (TRUE, " \t"); |
| if (rule != NULL) |
| { |
| _dbus_assert (rule->flags == 0); |
| |
| bus_match_rule_unref (rule); |
| } |
| |
| /* But with non-whitespace chars and no =value, it's not OK */ |
| rule = check_parse (FALSE, "type"); |
| _dbus_assert (rule == NULL); |
| |
| return TRUE; |
| } |
| |
| static struct { |
| const char *first; |
| const char *second; |
| } equality_tests[] = { |
| { "type='signal'", "type='signal'" }, |
| { "type='signal',interface='foo.bar'", "interface='foo.bar',type='signal'" }, |
| { "type='signal',member='bar'", "member='bar',type='signal'" }, |
| { "type='method_call',sender=':1.0'", "sender=':1.0',type='method_call'" }, |
| { "type='method_call',destination=':1.0'", "destination=':1.0',type='method_call'" }, |
| { "type='method_call',path='/foo/bar'", "path='/foo/bar',type='method_call'" }, |
| { "type='method_call',arg0='blah'", "arg0='blah',type='method_call'" }, |
| { "type='method_call',arg0='boo'", "arg0='boo',type='method_call'" }, |
| { "type='method_call',arg0='blah',arg1='baz'", "arg0='blah',arg1='baz',type='method_call'" }, |
| { "type='method_call',arg3='foosh'", "arg3='foosh',type='method_call'" }, |
| { "arg3='fool'", "arg3='fool'" }, |
| { "arg0namespace='fool'", "arg0namespace='fool'" }, |
| { "member='food'", "member='food'" } |
| }; |
| |
| static void |
| test_equality (void) |
| { |
| int i; |
| |
| i = 0; |
| while (i < _DBUS_N_ELEMENTS (equality_tests)) |
| { |
| BusMatchRule *first; |
| BusMatchRule *second; |
| int j; |
| |
| first = check_parse (TRUE, equality_tests[i].first); |
| _dbus_assert (first != NULL); |
| second = check_parse (TRUE, equality_tests[i].second); |
| _dbus_assert (second != NULL); |
| |
| if (!match_rule_equal (first, second)) |
| { |
| _dbus_warn ("rule %s and %s should have been equal\n", |
| equality_tests[i].first, |
| equality_tests[i].second); |
| exit (1); |
| } |
| |
| bus_match_rule_unref (second); |
| |
| /* Check that the rule is not equal to any of the |
| * others besides its pair match |
| */ |
| j = 0; |
| while (j < _DBUS_N_ELEMENTS (equality_tests)) |
| { |
| if (i != j) |
| { |
| second = check_parse (TRUE, equality_tests[j].second); |
| |
| if (match_rule_equal (first, second)) |
| { |
| _dbus_warn ("rule %s and %s should not have been equal\n", |
| equality_tests[i].first, |
| equality_tests[j].second); |
| exit (1); |
| } |
| |
| bus_match_rule_unref (second); |
| } |
| |
| ++j; |
| } |
| |
| bus_match_rule_unref (first); |
| |
| ++i; |
| } |
| } |
| |
| static const char* |
| should_match_message_1[] = { |
| "type='signal'", |
| "member='Frobated'", |
| "arg0='foobar'", |
| "type='signal',member='Frobated'", |
| "type='signal',member='Frobated',arg0='foobar'", |
| "member='Frobated',arg0='foobar'", |
| "type='signal',arg0='foobar'", |
| /* The definition of argXpath matches says: "As with normal argument matches, |
| * if the argument is exactly equal to the string given in the match rule |
| * then the rule is satisfied." So this should match (even though the |
| * argument is not a valid path)! |
| */ |
| "arg0path='foobar'", |
| "arg0namespace='foobar'", |
| NULL |
| }; |
| |
| static const char* |
| should_not_match_message_1[] = { |
| "type='method_call'", |
| "type='error'", |
| "type='method_return'", |
| "type='signal',member='Oopsed'", |
| "arg0='blah'", |
| "arg1='foobar'", |
| "arg2='foobar'", |
| "arg3='foobar'", |
| "arg0='3'", |
| "arg1='3'", |
| "arg0='foobar',arg1='abcdef'", |
| "arg0='foobar',arg1='abcdef',arg2='abcdefghi',arg3='abcdefghi',arg4='abcdefghi'", |
| "arg0='foobar',arg1='abcdef',arg4='abcdefghi',arg3='abcdefghi',arg2='abcdefghi'", |
| "arg0path='foo'", |
| "arg0path='foobar/'", |
| "arg1path='3'", |
| "arg0namespace='foo'", |
| "arg0namespace='foo',arg1='abcdef'", |
| "arg0namespace='moo'", |
| NULL |
| }; |
| |
| #define EXAMPLE_NAME "com.example.backend.foo" |
| |
| static const char * |
| should_match_message_2[] = { |
| /* EXAMPLE_NAME is in all of these namespaces */ |
| "arg0namespace='com.example.backend'", |
| "arg0namespace='com.example'", |
| "arg0namespace='com'", |
| |
| /* If the client specifies the name exactly, with no trailing period, then |
| * it should match. |
| */ |
| "arg0namespace='com.example.backend.foo'", |
| |
| NULL |
| }; |
| |
| static const char * |
| should_not_match_message_2[] = { |
| /* These are not even prefixes */ |
| "arg0namespace='com.example.backend.foo.bar'", |
| "arg0namespace='com.example.backend.foobar'", |
| |
| /* These are prefixes, but they're not parent namespaces. */ |
| "arg0namespace='com.example.backend.fo'", |
| "arg0namespace='com.example.backen'", |
| "arg0namespace='com.exampl'", |
| "arg0namespace='co'", |
| |
| NULL |
| }; |
| |
| static void |
| check_matches (dbus_bool_t expected_to_match, |
| int number, |
| DBusMessage *message, |
| const char *rule_text) |
| { |
| BusMatchRule *rule; |
| dbus_bool_t matched; |
| |
| rule = check_parse (TRUE, rule_text); |
| _dbus_assert (rule != NULL); |
| |
| /* We can't test sender/destination rules since we pass NULL here */ |
| matched = match_rule_matches (rule, NULL, NULL, message, 0); |
| |
| if (matched != expected_to_match) |
| { |
| _dbus_warn ("Expected rule %s to %s message %d, failed\n", |
| rule_text, expected_to_match ? |
| "match" : "not match", number); |
| exit (1); |
| } |
| |
| bus_match_rule_unref (rule); |
| } |
| |
| static void |
| check_matching (DBusMessage *message, |
| int number, |
| const char **should_match, |
| const char **should_not_match) |
| { |
| int i; |
| |
| i = 0; |
| while (should_match[i] != NULL) |
| { |
| check_matches (TRUE, number, message, should_match[i]); |
| ++i; |
| } |
| |
| i = 0; |
| while (should_not_match[i] != NULL) |
| { |
| check_matches (FALSE, number, message, should_not_match[i]); |
| ++i; |
| } |
| } |
| |
| static void |
| test_matching (void) |
| { |
| DBusMessage *message1, *message2; |
| const char *v_STRING; |
| dbus_int32_t v_INT32; |
| |
| message1 = dbus_message_new (DBUS_MESSAGE_TYPE_SIGNAL); |
| _dbus_assert (message1 != NULL); |
| if (!dbus_message_set_member (message1, "Frobated")) |
| _dbus_assert_not_reached ("oom"); |
| |
| v_STRING = "foobar"; |
| v_INT32 = 3; |
| if (!dbus_message_append_args (message1, |
| DBUS_TYPE_STRING, &v_STRING, |
| DBUS_TYPE_INT32, &v_INT32, |
| NULL)) |
| _dbus_assert_not_reached ("oom"); |
| |
| check_matching (message1, 1, |
| should_match_message_1, |
| should_not_match_message_1); |
| |
| dbus_message_unref (message1); |
| |
| message2 = dbus_message_new (DBUS_MESSAGE_TYPE_SIGNAL); |
| _dbus_assert (message2 != NULL); |
| if (!dbus_message_set_member (message2, "NameOwnerChanged")) |
| _dbus_assert_not_reached ("oom"); |
| |
| /* Obviously this isn't really a NameOwnerChanged signal. */ |
| v_STRING = EXAMPLE_NAME; |
| if (!dbus_message_append_args (message2, |
| DBUS_TYPE_STRING, &v_STRING, |
| NULL)) |
| _dbus_assert_not_reached ("oom"); |
| |
| check_matching (message2, 2, |
| should_match_message_2, |
| should_not_match_message_2); |
| |
| dbus_message_unref (message2); |
| } |
| |
| #define PATH_MATCH_RULE "arg0path='/aa/bb/'" |
| |
| /* This is a list of paths that should be matched by PATH_MATCH_RULE, taken |
| * from the specification. Notice that not all of them are actually legal D-Bus |
| * paths. |
| * |
| * The author of this test takes no responsibility for the semantics of |
| * this match rule key. |
| */ |
| static const char *paths_that_should_be_matched[] = { |
| "/aa/", |
| "/aa/bb/", |
| "/aa/bb/cc/", |
| #define FIRST_VALID_PATH_WHICH_SHOULD_MATCH 3 |
| "/", |
| "/aa/bb/cc", |
| NULL |
| }; |
| |
| /* These paths should not be matched by PATH_MATCH_RULE. */ |
| static const char *paths_that_should_not_be_matched[] = { |
| "/aa/b", |
| "/aa", |
| /* or even... */ |
| "/aa/bb", |
| NULL |
| }; |
| |
| static void |
| test_path_match (int type, |
| const char *path, |
| const char *rule_text, |
| BusMatchRule *rule, |
| dbus_bool_t should_match) |
| { |
| DBusMessage *message = dbus_message_new (DBUS_MESSAGE_TYPE_SIGNAL); |
| dbus_bool_t matched; |
| |
| _dbus_assert (message != NULL); |
| if (!dbus_message_set_member (message, "Foo")) |
| _dbus_assert_not_reached ("oom"); |
| |
| if (!dbus_message_append_args (message, |
| type, &path, |
| NULL)) |
| _dbus_assert_not_reached ("oom"); |
| |
| matched = match_rule_matches (rule, NULL, NULL, message, 0); |
| |
| if (matched != should_match) |
| { |
| _dbus_warn ("Expected rule %s to %s message " |
| "with first arg %s of type '%c', failed\n", |
| rule_text, |
| should_match ? "match" : "not match", |
| path, |
| (char) type); |
| exit (1); |
| } |
| |
| dbus_message_unref (message); |
| } |
| |
| static void |
| test_path_matching (void) |
| { |
| BusMatchRule *rule; |
| const char **s; |
| |
| rule = check_parse (TRUE, PATH_MATCH_RULE); |
| _dbus_assert (rule != NULL); |
| |
| for (s = paths_that_should_be_matched; *s != NULL; s++) |
| test_path_match (DBUS_TYPE_STRING, *s, PATH_MATCH_RULE, rule, TRUE); |
| |
| for (s = paths_that_should_be_matched + FIRST_VALID_PATH_WHICH_SHOULD_MATCH; |
| *s != NULL; s++) |
| test_path_match (DBUS_TYPE_OBJECT_PATH, *s, PATH_MATCH_RULE, rule, TRUE); |
| |
| for (s = paths_that_should_not_be_matched; *s != NULL; s++) |
| { |
| test_path_match (DBUS_TYPE_STRING, *s, PATH_MATCH_RULE, rule, FALSE); |
| test_path_match (DBUS_TYPE_OBJECT_PATH, *s, PATH_MATCH_RULE, rule, FALSE); |
| } |
| |
| bus_match_rule_unref (rule); |
| } |
| |
| static const char* |
| path_namespace_should_match_message_1[] = { |
| "type='signal',path_namespace='/foo'", |
| "type='signal',path_namespace='/foo/TheObjectManager'", |
| NULL |
| }; |
| |
| static const char* |
| path_namespace_should_not_match_message_1[] = { |
| "type='signal',path_namespace='/bar'", |
| "type='signal',path_namespace='/bar/TheObjectManager'", |
| NULL |
| }; |
| |
| static const char* |
| path_namespace_should_match_message_2[] = { |
| "type='signal',path_namespace='/foo/TheObjectManager'", |
| NULL |
| }; |
| |
| static const char* |
| path_namespace_should_not_match_message_2[] = { |
| NULL |
| }; |
| |
| static const char* |
| path_namespace_should_match_message_3[] = { |
| NULL |
| }; |
| |
| static const char* |
| path_namespace_should_not_match_message_3[] = { |
| "type='signal',path_namespace='/foo/TheObjectManager'", |
| NULL |
| }; |
| |
| static void |
| test_matching_path_namespace (void) |
| { |
| DBusMessage *message1; |
| DBusMessage *message2; |
| DBusMessage *message3; |
| |
| message1 = dbus_message_new (DBUS_MESSAGE_TYPE_SIGNAL); |
| _dbus_assert (message1 != NULL); |
| if (!dbus_message_set_path (message1, "/foo/TheObjectManager")) |
| _dbus_assert_not_reached ("oom"); |
| |
| message2 = dbus_message_new (DBUS_MESSAGE_TYPE_SIGNAL); |
| _dbus_assert (message2 != NULL); |
| if (!dbus_message_set_path (message2, "/foo/TheObjectManager/child_object")) |
| _dbus_assert_not_reached ("oom"); |
| |
| message3 = dbus_message_new (DBUS_MESSAGE_TYPE_SIGNAL); |
| _dbus_assert (message3 != NULL); |
| if (!dbus_message_set_path (message3, "/foo/TheObjectManagerOther")) |
| _dbus_assert_not_reached ("oom"); |
| |
| check_matching (message1, 1, |
| path_namespace_should_match_message_1, |
| path_namespace_should_not_match_message_1); |
| check_matching (message2, 2, |
| path_namespace_should_match_message_2, |
| path_namespace_should_not_match_message_2); |
| check_matching (message3, 3, |
| path_namespace_should_match_message_3, |
| path_namespace_should_not_match_message_3); |
| |
| dbus_message_unref (message3); |
| dbus_message_unref (message2); |
| dbus_message_unref (message1); |
| } |
| |
| dbus_bool_t |
| bus_signals_test (const DBusString *test_data_dir) |
| { |
| BusMatchmaker *matchmaker; |
| |
| matchmaker = bus_matchmaker_new (); |
| bus_matchmaker_ref (matchmaker); |
| bus_matchmaker_unref (matchmaker); |
| bus_matchmaker_unref (matchmaker); |
| |
| if (!_dbus_test_oom_handling ("parsing match rules", test_parsing, NULL)) |
| _dbus_assert_not_reached ("Parsing match rules test failed"); |
| |
| test_equality (); |
| test_matching (); |
| test_path_matching (); |
| test_matching_path_namespace (); |
| |
| return TRUE; |
| } |
| |
| #endif /* DBUS_BUILD_TESTS */ |
| |