Merge branch 'dbus-1.10'
[platform/upstream/dbus.git] / bus / signals.c
index c85a88d..6b7a464 100644 (file)
@@ -22,6 +22,9 @@
  */
 
 #include <config.h>
+
+#include <string.h>
+
 #include "signals.h"
 #include "services.h"
 #include "utils.h"
@@ -47,8 +50,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)
 {
@@ -61,7 +67,7 @@ bus_match_rule_new (DBusConnection *matches_go_to)
   rule->refcount = 1;
   rule->matches_go_to = matches_go_to;
 
-#ifndef DBUS_BUILD_TESTS
+#ifndef DBUS_ENABLE_EMBEDDED_TESTS
   _dbus_assert (rule->matches_go_to != NULL);
 #endif
   
@@ -115,10 +121,46 @@ bus_match_rule_unref (BusMatchRule *rule)
     }
 }
 
-#ifdef DBUS_ENABLE_VERBOSE_MODE
-/* Note this function does not do escaping, so it's only
- * good for debug spew at the moment
- */
+#if defined(DBUS_ENABLE_VERBOSE_MODE) || defined(DBUS_ENABLE_STATS)
+static dbus_bool_t
+append_key_and_escaped_value (DBusString *str, const char *token, const char *value)
+{
+  const char *p = value;
+
+  if (!_dbus_string_append_printf (str, "%s='", token))
+    return FALSE;
+
+  while (*p != '\0')
+    {
+      const char *next = strchr (p, '\'');
+
+      if (next)
+        {
+          if (!_dbus_string_append_printf (str, "%.*s", (int) (next - p), p))
+            return FALSE;
+          /* Horrible escape sequence: single quote cannot be escaped inside
+           * a single quoted string. So we close the single quote, escape the
+           * single quote, and reopen a single quote.
+           */
+          if (!_dbus_string_append_printf (str, "'\\''"))
+            return FALSE;
+          p = next + 1;
+        }
+      else
+        {
+          if (!_dbus_string_append_printf (str, "%s", p))
+            return FALSE;
+          break;
+        }
+    }
+
+  if (!_dbus_string_append_byte (str, '\''))
+    return FALSE;
+
+  return TRUE;
+}
+
+/* returns NULL if no memory */
 static char*
 match_rule_to_string (BusMatchRule *rule)
 {
@@ -127,44 +169,14 @@ match_rule_to_string (BusMatchRule *rule)
   
   if (!_dbus_string_init (&str))
     {
-      char *s;
-      while ((s = _dbus_strdup ("nomem")) == NULL)
-        ; /* only OK for debug spew... */
-      return s;
+      return NULL;
     }
   
   if (rule->flags & BUS_MATCH_MESSAGE_TYPE)
     {
-      if (rule->message_type == DBUS_MESSAGE_TYPE_INVALID)
-        {
-          if (!_dbus_string_append_printf (&str, "type='INVALID'"))
-            goto nomem;
-        }
-      else if (rule->message_type == DBUS_MESSAGE_TYPE_METHOD_CALL)
-        {
-          if (!_dbus_string_append_printf (&str, "type='method_call'"))
-            goto nomem;
-        }
-      else if (rule->message_type == DBUS_MESSAGE_TYPE_METHOD_RETURN)
-        {
-          if (!_dbus_string_append_printf (&str, "type='method_return'"))
-            goto nomem;
-        }
-      else if (rule->message_type == DBUS_MESSAGE_TYPE_ERROR)
-        {
-          if (!_dbus_string_append_printf (&str, "type='error'"))
-            goto nomem;
-        }
-      else if (rule->message_type == DBUS_MESSAGE_TYPE_SIGNAL)
-        {
-          if (!_dbus_string_append_printf (&str, "type='signal'"))
-            goto nomem;
-        }
-      else
-        {
-          if (!_dbus_string_append_printf (&str, "type='%d'", rule->message_type))
-            goto nomem;
-        }
+      if (!append_key_and_escaped_value (&str, "type",
+            dbus_message_type_to_string (rule->message_type)))
+        goto nomem;
     }
 
   if (rule->flags & BUS_MATCH_INTERFACE)
@@ -175,7 +187,7 @@ match_rule_to_string (BusMatchRule *rule)
             goto nomem;
         }
       
-      if (!_dbus_string_append_printf (&str, "interface='%s'", rule->interface))
+      if (!append_key_and_escaped_value (&str, "interface", rule->interface))
         goto nomem;
     }
 
@@ -187,7 +199,7 @@ match_rule_to_string (BusMatchRule *rule)
             goto nomem;
         }
       
-      if (!_dbus_string_append_printf (&str, "member='%s'", rule->member))
+      if (!append_key_and_escaped_value (&str, "member", rule->member))
         goto nomem;
     }
 
@@ -199,7 +211,19 @@ match_rule_to_string (BusMatchRule *rule)
             goto nomem;
         }
       
-      if (!_dbus_string_append_printf (&str, "path='%s'", rule->path))
+      if (!append_key_and_escaped_value (&str, "path", 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 (!append_key_and_escaped_value (&str, "path_namespace", rule->path))
         goto nomem;
     }
 
@@ -211,7 +235,7 @@ match_rule_to_string (BusMatchRule *rule)
             goto nomem;
         }
       
-      if (!_dbus_string_append_printf (&str, "sender='%s'", rule->sender))
+      if (!append_key_and_escaped_value (&str, "sender", rule->sender))
         goto nomem;
     }
 
@@ -223,7 +247,21 @@ match_rule_to_string (BusMatchRule *rule)
             goto nomem;
         }
       
-      if (!_dbus_string_append_printf (&str, "destination='%s'", rule->destination))
+      if (!append_key_and_escaped_value (&str, "destination", 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 (!append_key_and_escaped_value (&str, "eavesdrop",
+            (rule->flags & BUS_MATCH_CLIENT_IS_EAVESDROPPING) ?
+            "true" : "false"))
         goto nomem;
     }
 
@@ -238,7 +276,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)
                 {
@@ -247,11 +285,15 @@ 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" : "",
-                                               rule->args[i]))
+                                               "arg%d%s",
+                                               i,
+                                               is_path ? "path" :
+                                               is_namespace ? "namespace" : ""))
+                goto nomem;
+              if (!append_key_and_escaped_value (&str, "", rule->args[i]))
                 goto nomem;
             }
           
@@ -267,14 +309,9 @@ match_rule_to_string (BusMatchRule *rule)
   
  nomem:
   _dbus_string_free (&str);
-  {
-    char *s;
-    while ((s = _dbus_strdup ("nomem")) == NULL)
-      ;  /* only OK for debug spew... */
-    return s;
-  }
+  return NULL;
 }
-#endif /* DBUS_ENABLE_VERBOSE_MODE */
+#endif /* defined(DBUS_ENABLE_VERBOSE_MODE) || defined(DBUS_ENABLE_STATS) */
 
 dbus_bool_t
 bus_match_rule_set_message_type (BusMatchRule *rule,
@@ -363,9 +400,29 @@ bus_match_rule_set_destination (BusMatchRule *rule,
   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_get_client_is_eavesdropping (BusMatchRule *rule)
+{
+  if (rule->flags & BUS_MATCH_CLIENT_IS_EAVESDROPPING)
+    return TRUE;
+  else
+    return FALSE;
+}
+
 dbus_bool_t
 bus_match_rule_set_path (BusMatchRule *rule,
-                         const char   *path)
+                         const char   *path,
+                         dbus_bool_t   is_namespace)
 {
   char *new;
 
@@ -375,7 +432,13 @@ bus_match_rule_set_path (BusMatchRule *rule,
   if (new == NULL)
     return FALSE;
 
-  rule->flags |= BUS_MATCH_PATH;
+  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;
 
@@ -386,7 +449,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;
@@ -453,6 +517,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);
@@ -747,7 +814,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;
@@ -778,16 +846,35 @@ 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;
-    }
+      int len1 = strlen ("path");
+      if ((end + len1) == 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;
 
-  is_path = end != length;
+          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 (%lu). Only 'arg%lupath' (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)
@@ -802,11 +889,11 @@ bus_match_rule_parse_arg_match (BusMatchRule     *rule,
       rule->args[arg] != NULL)
     {
       dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
-                      "Argument %d matched more than once in match rule\n", key);
+                      "Argument %s matched more than once in match rule\n", key);
       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;
@@ -962,12 +1049,15 @@ bus_match_rule_parse (DBusConnection   *matches_go_to,
               goto failed;
             }
         }
-      else if (strcmp (key, "path") == 0)
+      else if (strcmp (key, "path") == 0 ||
+          strcmp (key, "path_namespace") == 0)
         {
-          if (rule->flags & BUS_MATCH_PATH)
+          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,
-                              "Key %s specified twice in match rule\n", key);
+                              "path or path_namespace specified twice in match rule\n");
               goto failed;
             }
 
@@ -978,7 +1068,7 @@ bus_match_rule_parse (DBusConnection   *matches_go_to,
               goto failed;
             }
 
-          if (!bus_match_rule_set_path (rule, value))
+          if (!bus_match_rule_set_path (rule, value, is_namespace))
             {
               BUS_SET_OOM (error);
               goto failed;
@@ -1006,6 +1096,31 @@ bus_match_rule_parse (DBusConnection   *matches_go_to,
               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))
@@ -1068,6 +1183,74 @@ struct BusMatchmaker
   RulePool rules_by_type[DBUS_NUM_MESSAGE_TYPES];
 };
 
+#ifdef DBUS_ENABLE_STATS
+dbus_bool_t
+bus_match_rule_dump (BusMatchmaker *matchmaker,
+                     DBusConnection *conn_filter,
+                     DBusMessageIter *arr_iter)
+{
+  int i;
+
+  for (i = 0 ; i < DBUS_NUM_MESSAGE_TYPES ; i++)
+    {
+      DBusHashIter iter;
+      DBusList **list;
+      DBusList *link;
+
+      _dbus_hash_iter_init (matchmaker->rules_by_type[i].rules_by_iface, &iter);
+      while (_dbus_hash_iter_next (&iter))
+        {
+          list =  _dbus_hash_iter_get_value (&iter);
+          for (link = _dbus_list_get_first_link (list);
+               link != NULL;
+               link = _dbus_list_get_next_link (list, link))
+            {
+              BusMatchRule *rule = link->data;
+
+              if (rule->matches_go_to == conn_filter)
+                {
+                  char *s = match_rule_to_string (rule);
+
+                  if (s == NULL)
+                    return FALSE;
+
+                  if (!dbus_message_iter_append_basic (arr_iter, DBUS_TYPE_STRING, &s))
+                    {
+                      dbus_free (s);
+                      return FALSE;
+                    }
+                  dbus_free (s);
+                }
+            }
+        }
+      list = &matchmaker->rules_by_type[i].rules_without_iface;
+      for (link = _dbus_list_get_first_link (list);
+           link != NULL;
+           link = _dbus_list_get_next_link (list, link))
+        {
+          BusMatchRule *rule = link->data;
+
+          if (rule->matches_go_to == conn_filter)
+            {
+              char *s = match_rule_to_string (rule);
+
+              if (s == NULL)
+                return FALSE;
+
+              if (!dbus_message_iter_append_basic (arr_iter, DBUS_TYPE_STRING, &s))
+                {
+                  dbus_free (s);
+                  return FALSE;
+                }
+              dbus_free (s);
+            }
+        }
+    }
+
+  return TRUE;
+}
+#endif
+
 static void
 rule_list_free (DBusList **rules)
 {
@@ -1286,7 +1469,7 @@ bus_matchmaker_add_rule (BusMatchmaker   *matchmaker,
     char *s = match_rule_to_string (rule);
 
     _dbus_verbose ("Added match rule %s to connection %p\n",
-                   s, rule->matches_go_to);
+                   s ? s : "nomem", rule->matches_go_to);
     dbus_free (s);
   }
 #endif
@@ -1315,7 +1498,7 @@ match_rule_equal (BusMatchRule *a,
   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;
@@ -1328,6 +1511,9 @@ match_rule_equal (BusMatchRule *a,
       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;
@@ -1346,7 +1532,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)
             {
@@ -1376,7 +1562,7 @@ bus_matchmaker_remove_rule_link (DBusList       **rules,
     char *s = match_rule_to_string (rule);
 
     _dbus_verbose ("Removed match rule %s for connection %p\n",
-                   s, rule->matches_go_to);
+                   s ? s : "nomem", rule->matches_go_to);
     dbus_free (s);
   }
 #endif
@@ -1413,7 +1599,7 @@ bus_matchmaker_remove_rule (BusMatchmaker   *matchmaker,
     char *s = match_rule_to_string (rule);
 
     _dbus_verbose ("Removed match rule %s for connection %p\n",
-                   s, rule->matches_go_to);
+                   s ? s : "nomem", rule->matches_go_to);
     dbus_free (s);
   }
 #endif
@@ -1578,12 +1764,24 @@ connection_is_primary_owner (DBusConnection *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,
@@ -1598,6 +1796,9 @@ match_rule_matches (BusMatchRule    *rule,
   /* 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);
@@ -1651,6 +1852,24 @@ match_rule_matches (BusMatchRule    *rule,
         }
     }
 
+  /* 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;
@@ -1659,12 +1878,27 @@ match_rule_matches (BusMatchRule    *rule,
 
       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)
+        {
+          /* If the message is going to be delivered to the dbus-daemon
+           * itself, its destination will be "org.freedesktop.DBus",
+           * which we again match against the rule (see bus_dispatch()
+           * in bus/dispatch.c, which checks for o.fd.DBus first).
+           *
+           * If we are monitoring and we don't know who is going to receive
+           * the message (for instance because they haven't been activated yet),
+           * assume they will own the requested destination name and no other,
+           * and match the rule's destination against that.
+           */
+          if (strcmp (rule->destination, destination) != 0)
             return FALSE;
         }
       else
@@ -1672,6 +1906,19 @@ match_rule_matches (BusMatchRule    *rule,
           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)
@@ -1688,6 +1935,34 @@ match_rule_matches (BusMatchRule    *rule,
         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.
+       *
+       * Special case: the only valid path of length 1, "/",
+       * matches everything.
+       */
+      if (len > 1 && path[len] != '\0' && path[len] != '/')
+        return FALSE;
+    }
+
   if (flags & BUS_MATCH_ARGS)
     {
       int i;
@@ -1703,11 +1978,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);
 
@@ -1715,8 +1991,9 @@ match_rule_matches (BusMatchRule    *rule,
             {
               const char *actual_arg;
               int actual_length;
-              
-              if (current_type != DBUS_TYPE_STRING)
+
+              if (current_type != DBUS_TYPE_STRING &&
+                  (!is_path || current_type != DBUS_TYPE_OBJECT_PATH))
                 return FALSE;
 
               actual_arg = NULL;
@@ -1739,6 +2016,32 @@ 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.
+                       */
+                      if (actual_arg[expected_length] != '.')
+                        return FALSE;
+                    }
+                  /* otherwise we had an exact match. */
+                }
               else
                 {
                   if (expected_length != actual_length ||
@@ -1782,7 +2085,7 @@ get_recipients_from_list (DBusList       **rules,
         char *s = match_rule_to_string (rule);
 
         _dbus_verbose ("Checking whether message matches rule %s for connection %p\n",
-                       s, rule->matches_go_to);
+                       s ? s : "nomem", rule->matches_go_to);
         dbus_free (s);
       }
 #endif
@@ -1799,12 +2102,10 @@ get_recipients_from_list (DBusList       **rules,
               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);
@@ -1875,7 +2176,7 @@ bus_matchmaker_get_recipients (BusMatchmaker   *matchmaker,
   return TRUE;
 }
 
-#ifdef DBUS_BUILD_TESTS
+#ifdef DBUS_ENABLE_EMBEDDED_TESTS
 #include "test.h"
 #include <stdlib.h>
 
@@ -1900,7 +2201,7 @@ check_parse (dbus_bool_t should_succeed,
 
   if (should_succeed && rule == NULL)
     {
-      _dbus_warn ("Failed to parse: %s: %s: \"%s\"\n",
+      _dbus_warn ("Failed to parse: %s: %s: \"%s\"",
                   error.name, error.message,
                   _dbus_string_get_const_data (&str));
       exit (1);
@@ -1908,7 +2209,7 @@ check_parse (dbus_bool_t should_succeed,
 
   if (!should_succeed && rule != NULL)
     {
-      _dbus_warn ("Failed to fail to parse: \"%s\"\n",
+      _dbus_warn ("Failed to fail to parse: \"%s\"",
                   _dbus_string_get_const_data (&str));
       exit (1);
     }
@@ -2053,7 +2354,73 @@ test_parsing (void *data)
 
       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);
@@ -2074,6 +2441,24 @@ test_parsing (void *data)
   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);
@@ -2134,7 +2519,13 @@ 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'" },
-  { "member='food'", "member='food'" }
+  { "arg0namespace='fool'", "arg0namespace='fool'" },
+  { "member='food'", "member='food'" },
+  { "member=escape", "member='escape'" },
+  { "member=icecream", "member=ice'cream'" },
+  { "arg0='comma,type=comma',type=signal", "type=signal,arg0='comma,type=comma'" },
+  { "arg0=escap\\e", "arg0='escap\\e'" },
+  { "arg0=Time: 8 o\\'clock", "arg0='Time: 8 o'\\''clock'" },
 };
 
 static void
@@ -2147,6 +2538,8 @@ test_equality (void)
     {
       BusMatchRule *first;
       BusMatchRule *second;
+      char *first_str, *second_str;
+      BusMatchRule *first_reparsed, *second_reparsed;
       int j;
       
       first = check_parse (TRUE, equality_tests[i].first);
@@ -2156,12 +2549,29 @@ test_equality (void)
 
       if (!match_rule_equal (first, second))
         {
-          _dbus_warn ("rule %s and %s should have been equal\n",
+          _dbus_warn ("rule %s and %s should have been equal",
                       equality_tests[i].first,
                       equality_tests[i].second);
           exit (1);
         }
 
+      /* Check match_rule_to_string */
+      first_str = match_rule_to_string (first);
+      _dbus_assert (first_str != NULL);
+      second_str = match_rule_to_string (second);
+      _dbus_assert (second_str != NULL);
+      _dbus_assert (strcmp (first_str, second_str) == 0);
+      first_reparsed = check_parse (TRUE, first_str);
+      _dbus_assert (first_reparsed != NULL);
+      second_reparsed = check_parse (TRUE, second_str);
+      _dbus_assert (second_reparsed != NULL);
+      _dbus_assert (match_rule_equal (first, first_reparsed));
+      _dbus_assert (match_rule_equal (second, second_reparsed));
+      bus_match_rule_unref (first_reparsed);
+      bus_match_rule_unref (second_reparsed);
+      dbus_free (first_str);
+      dbus_free (second_str);
+
       bus_match_rule_unref (second);
 
       /* Check that the rule is not equal to any of the
@@ -2173,10 +2583,11 @@ test_equality (void)
           if (i != j)
             {
               second = check_parse (TRUE, equality_tests[j].second);
+              _dbus_assert (second != NULL);
 
               if (match_rule_equal (first, second))
                 {
-                  _dbus_warn ("rule %s and %s should not have been equal\n",
+                  _dbus_warn ("rule %s and %s should not have been equal",
                               equality_tests[i].first,
                               equality_tests[j].second);
                   exit (1);
@@ -2203,6 +2614,13 @@ should_match_message_1[] = {
   "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
 };
 
@@ -2221,6 +2639,44 @@ should_not_match_message_1[] = {
   "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
 };
 
@@ -2241,7 +2697,7 @@ check_matches (dbus_bool_t  expected_to_match,
 
   if (matched != expected_to_match)
     {
-      _dbus_warn ("Expected rule %s to %s message %d, failed\n",
+      _dbus_warn ("Expected rule %s to %s message %d, failed",
                   rule_text, expected_to_match ?
                   "match" : "not match", number);
       exit (1);
@@ -2276,7 +2732,7 @@ check_matching (DBusMessage *message,
 static void
 test_matching (void)
 {
-  DBusMessage *message1;
+  DBusMessage *message1, *message2;
   const char *v_STRING;
   dbus_int32_t v_INT32;
 
@@ -2298,6 +2754,210 @@ 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/'"
+
+/* 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",
+                  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='/'",
+  "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='/'",
+  "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[] = {
+  "type='signal',path_namespace='/'",
+  NULL
+};
+
+static const char*
+path_namespace_should_not_match_message_3[] = {
+  "type='signal',path_namespace='/foo/TheObjectManager'",
+  NULL
+};
+
+static const char*
+path_namespace_should_match_message_4[] = {
+  "type='signal',path_namespace='/'",
+  NULL
+};
+
+static const char*
+path_namespace_should_not_match_message_4[] = {
+  "type='signal',path_namespace='/foo/TheObjectManager'",
+  NULL
+};
+
+static void
+test_matching_path_namespace (void)
+{
+  DBusMessage *message1;
+  DBusMessage *message2;
+  DBusMessage *message3;
+  DBusMessage *message4;
+
+  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");
+
+  message4 = dbus_message_new (DBUS_MESSAGE_TYPE_SIGNAL);
+  _dbus_assert (message4 != NULL);
+  if (!dbus_message_set_path (message4, "/"))
+    _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);
+  check_matching (message4, 4,
+                  path_namespace_should_match_message_4,
+                  path_namespace_should_not_match_message_4);
+
+  dbus_message_unref (message4);
+  dbus_message_unref (message3);
+  dbus_message_unref (message2);
+  dbus_message_unref (message1);
 }
 
 dbus_bool_t
@@ -2314,11 +2974,12 @@ bus_signals_test (const DBusString *test_data_dir)
     _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 */
+#endif /* DBUS_ENABLE_EMBEDDED_TESTS */