Logo Search packages:      
Sourcecode: dbus version File versions

signals.c

/* -*- mode: C; c-file-style: "gnu" -*- */
/* signals.c  Bus signal connection implementation
 *
 * Copyright (C) 2003  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
#include "signals.h"
#include "services.h"
#include "utils.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;
};

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);
    }
}

#ifdef DBUS_ENABLE_VERBOSE_MODE
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)
    {
      /* FIXME make type readable */
      if (!_dbus_string_append_printf (&str, "type='%d'", 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_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 (!_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;
}

dbus_bool_t
bus_match_rule_set_path (BusMatchRule *rule,
                         const char   *path)
{
  char *new;

  _dbus_assert (path != NULL);

  new = _dbus_strdup (path);
  if (new == NULL)
    return FALSE;

  rule->flags |= BUS_MATCH_PATH;
  dbus_free (rule->path);
  rule->path = new;

  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 '\'').
 *
 * 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_string_validate_service (&tmp_str, 0, len))
            {
              dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
                              "Sender service 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_string_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_string_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)
        {
          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 (!_dbus_string_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))
            {
              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_string_validate_service (&tmp_str, 0, len))
            {
              dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
                              "Destination service name '%s' is invalid\n", value);
              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;
    }
  

  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;
}

struct BusMatchmaker
{
  int refcount;

  DBusList *all_rules;
};

BusMatchmaker*
bus_matchmaker_new (void)
{
  BusMatchmaker *matchmaker;

  matchmaker = dbus_new0 (BusMatchmaker, 1);
  if (matchmaker == NULL)
    return NULL;

  matchmaker->refcount = 1;
  
  return matchmaker;
}

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)
    {
      while (matchmaker->all_rules != NULL)
        {
          BusMatchRule *rule;

          rule = matchmaker->all_rules->data;
          bus_match_rule_unref (rule);
          _dbus_list_remove_link (&matchmaker->all_rules,
                                  matchmaker->all_rules);
        }

      dbus_free (matchmaker);
    }
}

/* The rule can't be modified after it's added. */
dbus_bool_t
bus_matchmaker_add_rule (BusMatchmaker   *matchmaker,
                         BusMatchRule    *rule)
{
  _dbus_assert (bus_connection_is_active (rule->matches_go_to));

  if (!_dbus_list_append (&matchmaker->all_rules, rule))
    return FALSE;

  if (!bus_connection_add_match_rule (rule->matches_go_to, rule))
    {
      _dbus_list_remove_last (&matchmaker->all_rules, rule);
      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->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;

  return TRUE;
}

static void
bus_matchmaker_remove_rule_link (BusMatchmaker   *matchmaker,
                                 DBusList        *link)
{
  BusMatchRule *rule = link->data;
  
  bus_connection_remove_match_rule (rule->matches_go_to, rule);
  _dbus_list_remove_link (&matchmaker->all_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)
{
  bus_connection_remove_match_rule (rule->matches_go_to, rule);
  _dbus_list_remove (&matchmaker->all_rules, rule);

#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)
{
  /* FIXME this is an unoptimized linear scan */

  DBusList *link;

  /* we traverse backward because bus_connection_remove_match_rule()
   * removes the most-recently-added rule
   */
  link = _dbus_list_get_last_link (&matchmaker->all_rules);
  while (link != NULL)
    {
      BusMatchRule *rule;
      DBusList *prev;

      rule = link->data;
      prev = _dbus_list_get_prev_link (&matchmaker->all_rules, link);

      if (match_rule_equal (rule, value))
        {
          bus_matchmaker_remove_rule_link (matchmaker, 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;
    }

  return TRUE;
}

void
bus_matchmaker_disconnected (BusMatchmaker   *matchmaker,
                             DBusConnection  *disconnected)
{
  DBusList *link;

  /* 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 (disconnected));

  link = _dbus_list_get_first_link (&matchmaker->all_rules);
  while (link != NULL)
    {
      BusMatchRule *rule;
      DBusList *next;

      rule = link->data;
      next = _dbus_list_get_next_link (&matchmaker->all_rules, link);

      if (rule->matches_go_to == disconnected)
        {
          bus_matchmaker_remove_rule_link (matchmaker, 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 (disconnected);
          _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 (matchmaker, link);
            }
        }

      link = next;
    }
}

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_owner (service) == connection;
}

static dbus_bool_t
match_rule_matches (BusMatchRule    *rule,
                    BusConnections  *connections,
                    DBusConnection  *sender,
                    DBusConnection  *addressed_recipient,
                    DBusMessage     *message)
{
  /* 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)
   */
  
  if (rule->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 (rule->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 (rule->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 (rule->flags & BUS_MATCH_SENDER)
    {
      _dbus_assert (rule->sender != NULL);

      if (sender == NULL)
        {
          if (strcmp (rule->sender,
                      DBUS_SERVICE_ORG_FREEDESKTOP_DBUS) != 0)
            return FALSE;
        }
      else
        {
          if (!connection_is_primary_owner (sender, rule->sender))
            return FALSE;
        }
    }

  if (rule->flags & BUS_MATCH_DESTINATION)
    {
      const char *destination;

      _dbus_assert (rule->destination != NULL);

      destination = dbus_message_get_destination (message);
      if (destination == NULL)
        return FALSE;

      if (addressed_recipient == NULL)
        {          
          if (strcmp (rule->destination,
                      DBUS_SERVICE_ORG_FREEDESKTOP_DBUS) != 0)
            return FALSE;
        }
      else
        {
          if (!connection_is_primary_owner (addressed_recipient, rule->destination))
            return FALSE;
        }
    }

  if (rule->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;
    }

  return TRUE;
}

dbus_bool_t
bus_matchmaker_get_recipients (BusMatchmaker   *matchmaker,
                               BusConnections  *connections,
                               DBusConnection  *sender,
                               DBusConnection  *addressed_recipient,
                               DBusMessage     *message,
                               DBusList       **recipients_p)
{
  /* FIXME for now this is a wholly unoptimized linear search */
  /* Guessing the important optimization is to skip the signal-related
   * match lists when processing method call and exception messages.
   * So separate match rule lists for signals?
   */
  
  DBusList *link;

  _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);

  link = _dbus_list_get_first_link (&matchmaker->all_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, connections,
                              sender, addressed_recipient, message))
        {
          _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))
                goto nomem;
            }
#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 (&matchmaker->all_rules, link);
    }

  return TRUE;

 nomem:
  _dbus_list_clear (recipients_p);
  return FALSE;
}

#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);

  /* Reject broken valuess */
  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;
}

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");
  
  return TRUE;
}

#endif /* DBUS_BUILD_TESTS */


Generated by  Doxygen 1.6.0   Back to index