[daemon-fix] Registering starters: unwanted release_kdbus_name when no error was...
[platform/upstream/dbus.git] / dbus / dbus-address.c
index ef163ce..c4cfbbe 100644 (file)
@@ -1,10 +1,10 @@
-/* -*- mode: C; c-file-style: "gnu" -*- */
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
 /* dbus-address.c  Server address parser.
  *
  * Copyright (C) 2003  CodeFactory AB
- * Copyright (C) 2004  Red Hat, Inc.
+ * Copyright (C) 2004,2005  Red Hat, Inc.
  *
- * Licensed under the Academic Free License version 2.0
+ * 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
@@ -18,7 +18,7 @@
  * 
  * 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
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  *
  */
 
@@ -32,7 +32,7 @@
 /**
  * @defgroup DBusAddressInternals Address parsing
  * @ingroup  DBusInternals
- * @brief Implementation of parsing addresses of D-BUS servers.
+ * @brief Implementation of parsing addresses of D-Bus servers.
  *
  * @{
  */
@@ -48,6 +48,98 @@ struct DBusAddressEntry
   DBusList *values;  /**< List of values */
 };
 
+
+/**
+ *
+ * Sets #DBUS_ERROR_BAD_ADDRESS.
+ * If address_problem_type and address_problem_field are not #NULL,
+ * sets an error message about how the field is no good. Otherwise, sets
+ * address_problem_other as the error message.
+ * 
+ * @param error the error to set
+ * @param address_problem_type the address type of the bad address or #NULL
+ * @param address_problem_field the missing field of the bad address or #NULL
+ * @param address_problem_other any other error message or #NULL
+ */
+void
+_dbus_set_bad_address (DBusError  *error,
+                       const char *address_problem_type,
+                       const char *address_problem_field,
+                       const char *address_problem_other)
+{
+  if (address_problem_type != NULL)
+    dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS,
+                    "Server address of type %s was missing argument %s",
+                    address_problem_type, address_problem_field);
+  else
+    dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS,
+                    "Could not parse server address: %s",
+                    address_problem_other);
+}
+
+/**
+ * #TRUE if the byte need not be escaped when found in a dbus address.
+ * All other bytes are required to be escaped in a valid address.
+ */
+#define _DBUS_ADDRESS_OPTIONALLY_ESCAPED_BYTE(b)        \
+         (((b) >= 'a' && (b) <= 'z') ||                 \
+          ((b) >= 'A' && (b) <= 'Z') ||                 \
+          ((b) >= '0' && (b) <= '9') ||                 \
+          (b) == '-' ||                                 \
+          (b) == '_' ||                                 \
+          (b) == '/' ||                                 \
+          (b) == '\\' ||                                \
+          (b) == '*' ||                                \
+          (b) == '.')
+
+/**
+ * Appends an escaped version of one string to another string,
+ * using the D-Bus address escaping mechanism
+ *
+ * @param escaped the string to append to
+ * @param unescaped the string to escape
+ * @returns #FALSE if no memory
+ */
+dbus_bool_t
+_dbus_address_append_escaped (DBusString       *escaped,
+                              const DBusString *unescaped)
+{
+  const unsigned char *p;
+  const unsigned char *end;
+  dbus_bool_t ret;
+  int orig_len;
+
+  ret = FALSE;
+
+  orig_len = _dbus_string_get_length (escaped);
+  p = (const unsigned char *) _dbus_string_get_const_data (unescaped);
+  end = p + _dbus_string_get_length (unescaped);
+  while (p != end)
+    {
+      if (_DBUS_ADDRESS_OPTIONALLY_ESCAPED_BYTE (*p))
+        {
+          if (!_dbus_string_append_byte (escaped, *p))
+            goto out;
+        }
+      else
+        {
+          if (!_dbus_string_append_byte (escaped, '%'))
+            goto out;
+          if (!_dbus_string_append_byte_as_hex (escaped, *p))
+            goto out;
+        }
+      
+      ++p;
+    }
+
+  ret = TRUE;
+  
+ out:
+  if (!ret)
+    _dbus_string_set_length (escaped, orig_len);
+  return ret;
+}
+
 /** @} */ /* End of internals */
 
 static void
@@ -83,7 +175,7 @@ dbus_address_entry_free (DBusAddressEntry *entry)
 /**
  * @defgroup DBusAddress Address parsing
  * @ingroup  DBus
- * @brief Parsing addresses of D-BUS servers.
+ * @brief Parsing addresses of D-Bus servers.
  *
  * @{
  */
@@ -123,7 +215,9 @@ create_entry (void)
 }
 
 /**
- * Returns the method string of an address entry.
+ * Returns the method string of an address entry.  For example, given
+ * the address entry "tcp:host=example.com" it would return the string
+ * "tcp"
  *
  * @param entry the entry.
  * @returns a string describing the method. This string
@@ -136,11 +230,15 @@ dbus_address_entry_get_method (DBusAddressEntry *entry)
 }
 
 /**
- * Returns a value from a key of an entry.
+ * Returns a value from a key of an entry. For example,
+ * given the address "tcp:host=example.com,port=8073" if you asked
+ * for the key "host" you would get the value "example.com"
  *
+ * The returned value is already unescaped.
+ * 
  * @param entry the entry.
  * @param key the key.
- * @returns the key value. This string must not be fred.
+ * @returns the key value. This string must not be freed.
  */
 const char *
 dbus_address_entry_get_value (DBusAddressEntry *entry,
@@ -165,15 +263,95 @@ dbus_address_entry_get_value (DBusAddressEntry *entry,
   return NULL;
 }
 
+static dbus_bool_t
+append_unescaped_value (DBusString       *unescaped,
+                        const DBusString *escaped,
+                        int               escaped_start,
+                        int               escaped_len,
+                        DBusError        *error)
+{
+  const char *p;
+  const char *end;
+  dbus_bool_t ret;
+  
+  ret = FALSE;
+
+  p = _dbus_string_get_const_data (escaped) + escaped_start;
+  end = p + escaped_len;
+  while (p != end)
+    {
+      if (_DBUS_ADDRESS_OPTIONALLY_ESCAPED_BYTE (*p))
+        {
+          if (!_dbus_string_append_byte (unescaped, *p))
+            goto out;
+        }
+      else if (*p == '%')
+        {
+          /* Efficiency is king */
+          char buf[3];
+          DBusString hex;
+          int hex_end;
+          
+          ++p;
+
+          if ((p + 2) > end)
+            {
+              dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS,
+                              "In D-Bus address, percent character was not followed by two hex digits");
+              goto out;
+            }
+            
+          buf[0] = *p;
+          ++p;
+          buf[1] = *p;
+          buf[2] = '\0';
+
+          _dbus_string_init_const (&hex, buf);
+
+          if (!_dbus_string_hex_decode (&hex, 0, &hex_end,
+                                        unescaped,
+                                        _dbus_string_get_length (unescaped)))
+            goto out;
+
+          if (hex_end != 2)
+            {
+              dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS,
+                              "In D-Bus address, percent character was followed by characters other than hex digits");
+              goto out;
+            }
+        }
+      else
+        {
+          /* Error, should have been escaped */
+          dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS,
+                          "In D-Bus address, character '%c' should have been escaped\n",
+                          *p);
+          goto out;
+        }
+      
+      ++p;
+    }
+
+  ret = TRUE;
+  
+ out:
+  if (!ret && error && !dbus_error_is_set (error))
+    _DBUS_SET_OOM (error);
+
+  _dbus_assert (ret || error == NULL || dbus_error_is_set (error));
+  
+  return ret;
+}
+
 /**
  * Parses an address string of the form:
  *
  * method:key=value,key=value;method:key=value
  *
- * @todo document address format in the specification
+ * See the D-Bus specification for complete docs on the format.
  *
- * @todo need to be able to escape ';' and ',' in the
- * key values, and the parsing needs to handle that.
+ * When connecting to an address, the first address entries
+ * in the semicolon-separated list should be tried first.
  * 
  * @param address the address.
  * @param entry return location to an array of entries.
@@ -199,6 +377,13 @@ dbus_parse_address (const char         *address,
   entries = NULL;
   pos = 0;
   len = _dbus_string_get_length (&str);
+
+  if (len == 0)
+  {
+    dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS,
+                    "Empty address '%s'", address);
+    goto error;
+  }
   
   while (pos < len)
     {
@@ -306,9 +491,10 @@ dbus_parse_address (const char         *address,
                  goto error;
                }
 
-             if (!_dbus_string_copy_len (&str, equals_pos + 1, comma_pos - equals_pos - 1, value, 0))
+             if (!append_unescaped_value (value, &str, equals_pos + 1,
+                                           comma_pos - equals_pos - 1, error))
                {
-                 dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);             
+                  _dbus_assert (error == NULL || dbus_error_is_set (error));
                  _dbus_string_free (key);
                  _dbus_string_free (value);
 
@@ -386,20 +572,197 @@ dbus_parse_address (const char         *address,
   
 }
 
+/**
+ * Escapes the given string as a value in a key=value pair
+ * for a D-Bus address.
+ *
+ * @param value the unescaped value
+ * @returns newly-allocated escaped value or #NULL if no memory
+ */
+char*
+dbus_address_escape_value (const char *value)
+{
+  DBusString escaped;
+  DBusString unescaped;
+  char *ret;
+
+  ret = NULL;
+
+  _dbus_string_init_const (&unescaped, value);
+  
+  if (!_dbus_string_init (&escaped))
+    return NULL;
+
+  if (!_dbus_address_append_escaped (&escaped, &unescaped))
+    goto out;
+  
+  if (!_dbus_string_steal_data (&escaped, &ret))
+    goto out;
+
+ out:
+  _dbus_string_free (&escaped);
+  return ret;
+}
+
+/**
+ * Unescapes the given string as a value in a key=value pair
+ * for a D-Bus address. Note that dbus_address_entry_get_value()
+ * returns an already-unescaped value.
+ *
+ * @param value the escaped value
+ * @param error error to set if the unescaping fails
+ * @returns newly-allocated unescaped value or #NULL if no memory
+ */
+char*
+dbus_address_unescape_value (const char *value,
+                             DBusError  *error)
+{
+  DBusString unescaped;
+  DBusString escaped;
+  char *ret;
+  
+  ret = NULL;
+
+  _dbus_string_init_const (&escaped, value);
+  
+  if (!_dbus_string_init (&unescaped))
+    return NULL;
+
+  if (!append_unescaped_value (&unescaped, &escaped,
+                               0, _dbus_string_get_length (&escaped),
+                               error))
+    goto out;
+  
+  if (!_dbus_string_steal_data (&unescaped, &ret))
+    goto out;
+
+ out:
+  if (ret == NULL && error && !dbus_error_is_set (error))
+    _DBUS_SET_OOM (error);
+
+  _dbus_assert (ret != NULL || error == NULL || dbus_error_is_set (error));
+  
+  _dbus_string_free (&unescaped);
+  return ret;
+}
 
 /** @} */ /* End of public API */
 
-#ifdef DBUS_BUILD_TESTS
+#ifdef DBUS_ENABLE_EMBEDDED_TESTS
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+
 #include "dbus-test.h"
+#include <stdlib.h>
+
+typedef struct
+{
+  const char *escaped;
+  const char *unescaped;
+} EscapeTest;
+
+static const EscapeTest escape_tests[] = {
+  { "abcde", "abcde" },
+  { "", "" },
+  { "%20%20", "  " },
+  { "%24", "$" },
+  { "%25", "%" },
+  { "abc%24", "abc$" },
+  { "%24abc", "$abc" },
+  { "abc%24abc", "abc$abc" },
+  { "/", "/" },
+  { "-", "-" },
+  { "_", "_" },
+  { "A", "A" },
+  { "I", "I" },
+  { "Z", "Z" },
+  { "a", "a" },
+  { "i", "i" },
+  { "z", "z" },
+  /* Bug: https://bugs.freedesktop.org/show_bug.cgi?id=53499 */
+  { "%c3%b6", "\xc3\xb6" }
+};
+
+static const char* invalid_escaped_values[] = {
+  "%a",
+  "%q",
+  "%az",
+  "%%",
+  "%$$",
+  "abc%a",
+  "%axyz",
+  "%",
+  "$",
+  " ",
+};
 
 dbus_bool_t
 _dbus_address_test (void)
 {
   DBusAddressEntry **entries;
-  int len;  
-  DBusError error;
+  int len;
+  DBusError error = DBUS_ERROR_INIT;
+  int i;
+
+  i = 0;
+  while (i < _DBUS_N_ELEMENTS (escape_tests))
+    {
+      const EscapeTest *test = &escape_tests[i];
+      char *escaped;
+      char *unescaped;
+
+      escaped = dbus_address_escape_value (test->unescaped);
+      if (escaped == NULL)
+        _dbus_assert_not_reached ("oom");
+
+      if (strcmp (escaped, test->escaped) != 0)
+        {
+          _dbus_warn ("Escaped '%s' as '%s' should have been '%s'\n",
+                      test->unescaped, escaped, test->escaped);
+          exit (1);
+        }
+      dbus_free (escaped);
+
+      unescaped = dbus_address_unescape_value (test->escaped, &error);
+      if (unescaped == NULL)
+        {
+          _dbus_warn ("Failed to unescape '%s': %s\n",
+                      test->escaped, error.message);
+          dbus_error_free (&error);
+          exit (1);
+        }
+
+      if (strcmp (unescaped, test->unescaped) != 0)
+        {
+          _dbus_warn ("Unescaped '%s' as '%s' should have been '%s'\n",
+                      test->escaped, unescaped, test->unescaped);
+          exit (1);
+        }
+      dbus_free (unescaped);
+      
+      ++i;
+    }
 
-  dbus_error_init (&error);
+  i = 0;
+  while (i < _DBUS_N_ELEMENTS (invalid_escaped_values))
+    {
+      char *unescaped;
+
+      unescaped = dbus_address_unescape_value (invalid_escaped_values[i],
+                                               &error);
+      if (unescaped != NULL)
+        {
+          _dbus_warn ("Should not have successfully unescaped '%s' to '%s'\n",
+                      invalid_escaped_values[i], unescaped);
+          dbus_free (unescaped);
+          exit (1);
+        }
+
+      _dbus_assert (dbus_error_is_set (&error));
+      dbus_error_free (&error);
+
+      ++i;
+    }
   
   if (!dbus_parse_address ("unix:path=/tmp/foo;debug:name=test,sliff=sloff;",
                           &entries, &len, &error))
@@ -412,6 +775,11 @@ _dbus_address_test (void)
   dbus_address_entries_free (entries);
 
   /* Different possible errors */
+  if (dbus_parse_address ("", &entries, &len, &error))
+    _dbus_assert_not_reached ("Parsed incorrect address.");
+  else
+    dbus_error_free (&error);
+
   if (dbus_parse_address ("foo", &entries, &len, &error))
     _dbus_assert_not_reached ("Parsed incorrect address.");
   else
@@ -455,4 +823,6 @@ _dbus_address_test (void)
   return TRUE;
 }
 
+#endif /* !DOXYGEN_SHOULD_SKIP_THIS */
+
 #endif