dbus/bus bus.c,1.39,1.40 config-parser.c,1.22,1.23 dispatch.c,1.51,1.52 policy.c,1.14,1.15 signals.c,1.2,1.3

Havoc Pennington hp@pdx.freedesktop.org
Thu, 09 Oct 2003 19:42:23 -0700


Update of /cvs/dbus/dbus/bus
In directory pdx:/tmp/cvs-serv16249/bus

Modified Files:
	bus.c config-parser.c dispatch.c policy.c signals.c 
Log Message:
2003-10-09  Havoc Pennington  <hp@redhat.com>

        Make matching rules theoretically work (add parser).
	
	* bus/bus.c (bus_context_check_security_policy): fix up to handle
	the case where destination is explicitly specified as bus driver
	and someone else is eavesdropping.
	
	* bus/policy.c (bus_client_policy_check_can_receive): fix up
	definition of eavesdropping and assertion

	* tools/dbus-send.c (main): use dbus_message_type_from_string

	* bus/signals.c (bus_match_rule_parse): implement

	* dbus/dbus-message.c (dbus_message_type_from_string): new

	* dbus/dbus-errors.h (DBUS_ERROR_MATCH_RULE_INVALID): add



Index: bus.c
===================================================================
RCS file: /cvs/dbus/dbus/bus/bus.c,v
retrieving revision 1.39
retrieving revision 1.40
diff -u -d -r1.39 -r1.40
--- bus.c	30 Sep 2003 02:32:50 -0000	1.39
+++ bus.c	10 Oct 2003 02:42:20 -0000	1.40
@@ -872,6 +872,19 @@
   return context->limits.max_match_rules_per_connection;
 }
 
+/*
+ * addressed_recipient is the recipient specified in the message.
+ *
+ * proposed_recipient is the recipient we're considering sending
+ * to right this second, and may be an eavesdropper.
+ *
+ * sender is the sender of the message.
+ *
+ * NULL for proposed_recipient or sender definitely means the bus driver.
+ *
+ * NULL for addressed_recipient may mean the bus driver, or may mean
+ * no destination was specified in the message (e.g. a signal).
+ */
 dbus_bool_t
 bus_context_check_security_policy (BusContext     *context,
                                    DBusConnection *sender,
@@ -883,15 +896,9 @@
   BusClientPolicy *sender_policy;
   BusClientPolicy *recipient_policy;
 
-  /* NULL sender, proposed_recipient means the bus driver.  NULL
-   * addressed_recipient means the message didn't specify an explicit
-   * target. If proposed_recipient is NULL, then addressed_recipient
-   * is also NULL but is implicitly the bus driver.
-   */
-
-  _dbus_assert (proposed_recipient == NULL ||
-                (dbus_message_get_destination (message) == NULL ||
-                 addressed_recipient != NULL));
+  _dbus_assert (dbus_message_get_destination (message) == NULL || /* Signal */
+                (addressed_recipient != NULL ||
+                 strcmp (dbus_message_get_destination (message), DBUS_SERVICE_ORG_FREEDESKTOP_DBUS) == 0)); /* Destination specified or is the bus driver */
   
   if (sender != NULL)
     {

Index: config-parser.c
===================================================================
RCS file: /cvs/dbus/dbus/bus/config-parser.c,v
retrieving revision 1.22
retrieving revision 1.23
diff -u -d -r1.22 -r1.23
--- config-parser.c	30 Sep 2003 02:32:50 -0000	1.22
+++ config-parser.c	10 Oct 2003 02:42:20 -0000	1.23
@@ -813,21 +813,6 @@
     }
 }
 
-static int
-message_type_from_string (const char *type_str)
-{
-  if (strcmp (type_str, "method_call") == 0)
-    return DBUS_MESSAGE_TYPE_METHOD_CALL;
-  if (strcmp (type_str, "method_return") == 0)
-    return DBUS_MESSAGE_TYPE_METHOD_RETURN;
-  else if (strcmp (type_str, "signal") == 0)
-    return DBUS_MESSAGE_TYPE_SIGNAL;
-  else if (strcmp (type_str, "error") == 0)
-    return DBUS_MESSAGE_TYPE_ERROR;
-  else
-    return DBUS_MESSAGE_TYPE_INVALID;
-}
-
 static dbus_bool_t
 append_rule_from_element (BusConfigParser   *parser,
                           const char        *element_name,
@@ -1027,7 +1012,7 @@
       message_type = DBUS_MESSAGE_TYPE_INVALID;
       if (send_type != NULL)
         {
-          message_type = message_type_from_string (send_type);
+          message_type = dbus_message_type_from_string (send_type);
           if (message_type == DBUS_MESSAGE_TYPE_INVALID)
             {
               dbus_set_error (error, DBUS_ERROR_FAILED,
@@ -1079,7 +1064,7 @@
       message_type = DBUS_MESSAGE_TYPE_INVALID;
       if (receive_type != NULL)
         {
-          message_type = message_type_from_string (receive_type);
+          message_type = dbus_message_type_from_string (receive_type);
           if (message_type == DBUS_MESSAGE_TYPE_INVALID)
             {
               dbus_set_error (error, DBUS_ERROR_FAILED,

Index: dispatch.c
===================================================================
RCS file: /cvs/dbus/dbus/bus/dispatch.c,v
retrieving revision 1.51
retrieving revision 1.52
diff -u -d -r1.51 -r1.52
--- dispatch.c	30 Sep 2003 02:32:50 -0000	1.51
+++ dispatch.c	10 Oct 2003 02:42:20 -0000	1.52
@@ -920,7 +920,8 @@
   if (message == NULL)
     return TRUE;
 
-  if (!dbus_message_append_args (message, DBUS_TYPE_STRING, "", /* FIXME */
+  /* empty string match rule matches everything */
+  if (!dbus_message_append_args (message, DBUS_TYPE_STRING, "",
                                  DBUS_TYPE_INVALID))
     {
       dbus_message_unref (message);

Index: policy.c
===================================================================
RCS file: /cvs/dbus/dbus/bus/policy.c,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -d -r1.14 -r1.15
--- policy.c	30 Sep 2003 02:32:50 -0000	1.14
+++ policy.c	10 Oct 2003 02:42:20 -0000	1.15
@@ -913,6 +913,9 @@
   return allowed;
 }
 
+/* See docs on what the args mean on bus_context_check_security_policy()
+ * comment
+ */
 dbus_bool_t
 bus_client_policy_check_can_receive (BusClientPolicy *policy,
                                      BusRegistry     *registry,
@@ -924,20 +927,10 @@
   DBusList *link;
   dbus_bool_t allowed;
   dbus_bool_t eavesdropping;
-  
-  /* NULL sender, proposed_recipient means the bus driver.  NULL
-   * addressed_recipient means the message didn't specify an explicit
-   * target. If proposed_recipient is NULL, then addressed_recipient
-   * is also NULL but is implicitly the bus driver.
-   */
 
-  _dbus_assert (proposed_recipient == NULL ||
-                (dbus_message_get_destination (message) == NULL ||
-                 addressed_recipient != NULL));
-  
   eavesdropping =
-    (proposed_recipient == NULL || /* explicitly to bus driver */
-     (addressed_recipient && addressed_recipient != proposed_recipient)); /* explicitly to a different recipient */
+    addressed_recipient != proposed_recipient &&
+    dbus_message_get_destination (message) != NULL;
   
   /* policy->rules is in the order the rules appeared
    * in the config file, i.e. last rule that applies wins

Index: signals.c
===================================================================
RCS file: /cvs/dbus/dbus/bus/signals.c,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- signals.c	30 Sep 2003 02:32:50 -0000	1.2
+++ signals.c	10 Oct 2003 02:42:20 -0000	1.3
@@ -52,6 +52,10 @@
   rule->refcount = 1;
   rule->matches_go_to = matches_go_to;
 
+#ifndef DBUS_BUILD_TESTS
+  _dbus_assert (rule->matches_go_to != NULL);
+#endif
+  
   return rule;
 }
 
@@ -285,6 +289,285 @@
   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.
+ */
+#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;
+}
+
 /*
  * The format is comma-separated with strings quoted with single quotes
  * as for the shell (to escape a literal single quote, use '\'').
@@ -299,24 +582,157 @@
                       DBusError        *error)
 {
   BusMatchRule *rule;
-
+  RuleToken tokens[MAX_RULE_TOKENS+1]; /* NULL termination + 1 */
+  int i;
+  
+  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+  
+  memset (tokens, '\0', sizeof (tokens));
+  
   rule = bus_match_rule_new (matches_go_to);
   if (rule == NULL)
-    goto oom;
-
-  /* FIXME implement for real */
+    {
+      BUS_SET_OOM (error);
+      goto failed;
+    }
   
-  if (!bus_match_rule_set_message_type (rule,
-                                        DBUS_MESSAGE_TYPE_SIGNAL))
-    goto oom;
+  if (!tokenize_rule (rule_text, tokens, error))
+    goto failed;
   
-  return rule;
+  i = 0;
+  while (tokens[i].key != NULL)
+    {
+      const char *key = tokens[i].key;
+      const char *value = tokens[i].value;
+      
+      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 (!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 (!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 (!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 (!bus_match_rule_set_member (rule, value))
+            {
+              BUS_SET_OOM (error);
+              goto failed;
+            }
+        }
+      else if (strcmp (key, "path") == 0)
+        {
+          if (rule->flags & BUS_MATCH_PATH)
+            {
+              dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                              "Key %s specified twice in match rule\n", key);
+              goto failed;
+            }
+
+          if (!bus_match_rule_set_path (rule, value))
+            {
+              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 (!bus_match_rule_set_destination (rule, value))
+            {
+              BUS_SET_OOM (error);
+              goto failed;
+            }
+        }
+      else
+        {
+          dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                          "Unknown key \"%s\" in match rule",
+                          key);
+          goto failed;
+        }
+
+      ++i;
+    }
   
- oom:
+
+  goto out;
+  
+ failed:
+  _DBUS_ASSERT_ERROR_IS_SET (error);
   if (rule)
-    bus_match_rule_unref (rule);
-  BUS_SET_OOM (error);
-  return NULL;
+    {
+      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;
 }
 
 struct BusMatchmaker
@@ -760,6 +1176,186 @@
 
 #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);
+    }
+
+  /* Reject duplicates */
+  rule = check_parse (FALSE, "type='signal',type='method_call'");
+  _dbus_assert (rule == NULL);
+
+  /* Reject broken keys */
+  rule = check_parse (FALSE, "blah='signal'");
+  _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);
+
+  /* Empty string values are allowed at the moment */
+  rule = check_parse (TRUE, "interface=");
+  if (rule != NULL)
+    {
+      _dbus_assert (rule->flags == BUS_MATCH_INTERFACE);
+      _dbus_assert (rule->interface);
+      _dbus_assert (strlen (rule->interface) == 0);
+      
+      bus_match_rule_unref (rule);
+    }
+
+  /* Empty string expressed with quotes */
+  rule = check_parse (TRUE, "interface=''");
+  if (rule != NULL)
+    {
+      _dbus_assert (rule->flags == BUS_MATCH_INTERFACE);
+      _dbus_assert (rule->interface);
+      _dbus_assert (strlen (rule->interface) == 0);
+      
+      bus_match_rule_unref (rule);
+    }
+
+  /* Check whitespace in a value */
+  rule = check_parse (TRUE, "interface=   ");
+  if (rule != NULL)
+    {
+      _dbus_assert (rule->flags == BUS_MATCH_INTERFACE);
+      _dbus_assert (rule->interface);
+      _dbus_assert (strcmp (rule->interface, "   ") == 0);
+      
+      bus_match_rule_unref (rule);
+    }
+
+  /* Check whitespace mixed with non-whitespace in a value */
+  rule = check_parse (TRUE, "interface= foo ");
+  if (rule != NULL)
+    {
+      _dbus_assert (rule->flags == BUS_MATCH_INTERFACE);
+      _dbus_assert (rule->interface);
+      _dbus_assert (strcmp (rule->interface, " foo ") == 0);
+      
+      bus_match_rule_unref (rule);
+    }
+  
+  return TRUE;
+}
 
 dbus_bool_t
 bus_signals_test (const DBusString *test_data_dir)
@@ -770,6 +1366,9 @@
   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");
   
   return TRUE;
 }