Supporting matching argument 0 as a namespace

Rather like "arg0path='/foo/'" matching all object paths starting with
"/foo/", this adds support for matching a prefix of a string argument
with "arg0namespace='org.freedesktop.Telepathy.Client.'" (for example).

This is mostly intended for use with NameOwnerChanged and
PropertiesChanged; thus, only matching the 0th argument is permitted.
(This also means it could work with the multicast-plus-socket-filters
model being considered for DBus-in-the-kernel without having to hash
every period-separated prefix of every string argument.)
This commit is contained in:
Will Thompson 2009-07-09 21:33:24 +01:00
parent 2f618faa2d
commit 2f7b11158b
2 changed files with 154 additions and 18 deletions

View file

@ -47,8 +47,11 @@ struct BusMatchRule
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)
{
@ -211,7 +214,7 @@ match_rule_to_string (BusMatchRule *rule)
{
if (rule->args[i] != NULL)
{
dbus_bool_t is_path;
dbus_bool_t is_path, is_namespace;
if (_dbus_string_get_length (&str) > 0)
{
@ -220,10 +223,13 @@ match_rule_to_string (BusMatchRule *rule)
}
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" : "",
i,
is_path ? "path" :
is_namespace ? "namespace" : "",
rule->args[i]))
goto nomem;
}
@ -359,7 +365,8 @@ dbus_bool_t
bus_match_rule_set_arg (BusMatchRule *rule,
int arg,
const DBusString *value,
dbus_bool_t is_path)
dbus_bool_t is_path,
dbus_bool_t is_namespace)
{
int length;
char *new;
@ -426,6 +433,9 @@ bus_match_rule_set_arg (BusMatchRule *rule,
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);
@ -720,7 +730,8 @@ bus_match_rule_parse_arg_match (BusMatchRule *rule,
const DBusString *value,
DBusError *error)
{
dbus_bool_t is_path;
dbus_bool_t is_path = FALSE;
dbus_bool_t is_namespace = FALSE;
DBusString key_str;
unsigned long arg;
int length;
@ -751,17 +762,25 @@ bus_match_rule_parse_arg_match (BusMatchRule *rule,
goto failed;
}
if (end != length &&
((end + 4) != length ||
!_dbus_string_ends_with_c_str (&key_str, "path")))
if (end != length)
{
dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
"Key '%s' in match rule contains junk after argument number. Only 'path' is optionally valid ('arg0path' for example).\n", key);
goto failed;
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"))
{
is_namespace = TRUE;
}
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;
}
}
is_path = end != length;
/* If we didn't check this we could allocate a huge amount of RAM */
if (arg > DBUS_MAXIMUM_MATCH_RULE_ARG_NUMBER)
{
@ -779,7 +798,7 @@ bus_match_rule_parse_arg_match (BusMatchRule *rule,
goto failed;
}
if (!bus_match_rule_set_arg (rule, arg, value, is_path))
if (!bus_match_rule_set_arg (rule, arg, value, is_path, is_namespace))
{
BUS_SET_OOM (error);
goto failed;
@ -1318,7 +1337,7 @@ match_rule_equal (BusMatchRule *a,
if (a->arg_lens[i] != b->arg_lens[i])
return FALSE;
length = a->arg_lens[i] & ~BUS_MATCH_ARG_IS_PATH;
length = a->arg_lens[i] & ~BUS_MATCH_ARG_FLAGS;
if (a->args[i] != NULL)
{
@ -1675,11 +1694,12 @@ match_rule_matches (BusMatchRule *rule,
int current_type;
const char *expected_arg;
int expected_length;
dbus_bool_t is_path;
dbus_bool_t is_path, is_namespace;
expected_arg = rule->args[i];
expected_length = rule->arg_lens[i] & ~BUS_MATCH_ARG_IS_PATH;
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);
@ -1712,6 +1732,39 @@ match_rule_matches (BusMatchRule *rule,
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.
*/
int expected_period_index;
if (expected_arg[expected_length - 1] == '.')
expected_period_index = expected_length - 1;
else
expected_period_index = expected_length;
if (actual_arg[expected_period_index] != '.')
return FALSE;
}
/* otherwise we had an exact match. */
}
else
{
if (expected_length != actual_length ||
@ -2042,6 +2095,23 @@ test_parsing (void *data)
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 (FALSE, "arg1namespace='foo'");
_dbus_assert (rule == NULL);
/* Too-large argN */
rule = check_parse (FALSE, "arg300='foo'");
_dbus_assert (rule == NULL);
@ -2122,6 +2192,7 @@ static struct {
{ "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'" }
};
@ -2197,6 +2268,7 @@ should_match_message_1[] = {
* argument is not a valid path)!
*/
"arg0path='foobar'",
"arg0namespace='foobar'",
NULL
};
@ -2218,6 +2290,51 @@ should_not_match_message_1[] = {
"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, specified with and without a
* trailing period */
"arg0namespace='com.example.backend.'",
"arg0namespace='com.example.backend'",
"arg0namespace='com.example.'",
"arg0namespace='com.example'",
"arg0namespace='com.'",
"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'",
"arg0namespace='com.example.backend.fo.'",
/* This should match anything within the namespace com.example.backend.foo,
* not including com.example.backend.foo itself.
*/
"arg0namespace='com.example.backend.foo.'",
/* These are prefixes, but they're not parent namespaces. */
"arg0namespace='com.example.backend.fo'",
"arg0namespace='com.example.backen'",
"arg0namespace='com.exampl'",
"arg0namespace='co'",
NULL
};
@ -2273,7 +2390,7 @@ check_matching (DBusMessage *message,
static void
test_matching (void)
{
DBusMessage *message1;
DBusMessage *message1, *message2;
const char *v_STRING;
dbus_int32_t v_INT32;
@ -2295,6 +2412,24 @@ test_matching (void)
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/'"

View file

@ -59,7 +59,8 @@ dbus_bool_t bus_match_rule_set_path (BusMatchRule *rule,
dbus_bool_t bus_match_rule_set_arg (BusMatchRule *rule,
int arg,
const DBusString *value,
dbus_bool_t is_path);
dbus_bool_t is_path,
dbus_bool_t prefix);
BusMatchRule* bus_match_rule_parse (DBusConnection *matches_go_to,
const DBusString *rule_text,