-/* -*- 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,2005 Red Hat, Inc.
*
- * Licensed under the Academic Free License version 1.2
+ * 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
*
* 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
*
*/
#include "dbus-address.h"
#include "dbus-internals.h"
#include "dbus-list.h"
+#include "dbus-string.h"
+#include "dbus-protocol.h"
/**
- * @defgroup DBusAddress address parsing
- * @ingroup DBus
- * @brief Parsing addresses to DBus servers.
+ * @defgroup DBusAddressInternals Address parsing
+ * @ingroup DBusInternals
+ * @brief Implementation of parsing addresses of D-Bus servers.
*
* @{
*/
+
+/**
+ * Internals of DBusAddressEntry
+ */
struct DBusAddressEntry
{
- DBusString method;
+ DBusString method; /**< The address type (unix, tcp, etc.) */
- DBusList *keys;
- DBusList *values;
+ DBusList *keys; /**< List of keys */
+ 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
dbus_address_entry_free (DBusAddressEntry *entry)
{
link = _dbus_list_get_next_link (&entry->keys, link);
}
-
+ _dbus_list_clear (&entry->keys);
+
link = _dbus_list_get_first_link (&entry->values);
while (link != NULL)
{
link = _dbus_list_get_next_link (&entry->values, link);
}
+ _dbus_list_clear (&entry->values);
dbus_free (entry);
}
+/**
+ * @defgroup DBusAddress Address parsing
+ * @ingroup DBus
+ * @brief Parsing addresses of D-Bus servers.
+ *
+ * @{
+ */
/**
* Frees a #NULL-terminated array of address entries.
if (entry == NULL)
return NULL;
- if (!_dbus_string_init (&entry->method, _DBUS_INT_MAX))
- dbus_free (entry);
+ if (!_dbus_string_init (&entry->method))
+ {
+ dbus_free (entry);
+ return NULL;
+ }
return entry;
}
/**
- * 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
const char *
dbus_address_entry_get_method (DBusAddressEntry *entry)
{
- const char *method;
-
- _dbus_string_get_const_data (&entry->method, &method);
-
- return method;
+ return _dbus_string_get_const_data (&entry->method);
}
/**
- * 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,
_dbus_assert (values != NULL);
if (_dbus_string_equal_c_str (keys->data, key))
- {
- const char *str;
+ return _dbus_string_get_const_data (values->data);
- _dbus_string_get_const_data (values->data, &str);
- return str;
- }
keys = _dbus_list_get_next_link (&entry->keys, keys);
values = _dbus_list_get_next_link (&entry->values, values);
}
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
*
+ * See the D-Bus specification for complete docs on the format.
+ *
+ * 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.
* @param array_len return location for array length.
- * @param result return location for result code.
+ * @param error address where an error can be returned.
* @returns #TRUE on success, #FALSE otherwise.
*/
dbus_bool_t
dbus_parse_address (const char *address,
DBusAddressEntry ***entry,
int *array_len,
- DBusResultCode *result)
+ DBusError *error)
{
DBusString str;
int pos, end_pos, len, i;
DBusList *entries, *link;
DBusAddressEntry **entry_array;
+ _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+
_dbus_string_init_const (&str, 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)
{
entry = create_entry ();
if (!entry)
{
- dbus_set_result (result, DBUS_RESULT_NO_MEMORY);
+ dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
goto error;
}
/* Append the entry */
if (!_dbus_list_append (&entries, entry))
{
- dbus_set_result (result, DBUS_RESULT_NO_MEMORY);
+ dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
dbus_address_entry_free (entry);
goto error;
}
/* Look for the colon : */
if (!_dbus_string_find_to (&str, pos, end_pos, ":", &found_pos))
{
- dbus_set_result (result, DBUS_RESULT_BAD_ADDRESS);
+ dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS, "Address does not contain a colon");
goto error;
}
if (!_dbus_string_copy_len (&str, pos, found_pos - pos, &entry->method, 0))
{
- dbus_set_result (result, DBUS_RESULT_NO_MEMORY);
+ dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
goto error;
}
if (!_dbus_string_find_to (&str, pos, end_pos, ",", &comma_pos))
comma_pos = end_pos;
- if (!_dbus_string_find (&str, pos, "=", &equals_pos) ||
- equals_pos == pos || equals_pos + 1 == end_pos)
+ if (!_dbus_string_find_to (&str, pos, comma_pos, "=", &equals_pos) ||
+ equals_pos == pos || equals_pos + 1 == comma_pos)
{
- dbus_set_result (result, DBUS_RESULT_BAD_ADDRESS);
- goto error;
+ dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS,
+ "'=' character not found or has no value following it");
+ goto error;
}
else
{
if (!key)
{
- dbus_set_result (result, DBUS_RESULT_NO_MEMORY);
+ dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
goto error;
}
value = dbus_new0 (DBusString, 1);
if (!value)
{
- dbus_set_result (result, DBUS_RESULT_NO_MEMORY);
+ dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
dbus_free (key);
goto error;
}
- if (!_dbus_string_init (key, _DBUS_INT_MAX))
+ if (!_dbus_string_init (key))
{
- dbus_set_result (result, DBUS_RESULT_NO_MEMORY);
+ dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
dbus_free (key);
dbus_free (value);
goto error;
}
- if (!_dbus_string_init (value, _DBUS_INT_MAX))
+ if (!_dbus_string_init (value))
{
- dbus_set_result (result, DBUS_RESULT_NO_MEMORY);
+ dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
_dbus_string_free (key);
dbus_free (key);
if (!_dbus_string_copy_len (&str, pos, equals_pos - pos, key, 0))
{
- dbus_set_result (result, DBUS_RESULT_NO_MEMORY);
+ dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
_dbus_string_free (key);
_dbus_string_free (value);
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_result (result, DBUS_RESULT_NO_MEMORY);
+ _dbus_assert (error == NULL || dbus_error_is_set (error));
_dbus_string_free (key);
_dbus_string_free (value);
if (!_dbus_list_append (&entry->keys, key))
{
- dbus_set_result (result, DBUS_RESULT_NO_MEMORY);
+ dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
_dbus_string_free (key);
_dbus_string_free (value);
if (!_dbus_list_append (&entry->values, value))
{
- dbus_set_result (result, DBUS_RESULT_NO_MEMORY);
+ dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
_dbus_string_free (value);
dbus_free (value);
if (!entry_array)
{
- dbus_set_result (result, DBUS_RESULT_NO_MEMORY);
+ dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
goto error;
}
_dbus_list_clear (&entries);
*entry = entry_array;
- dbus_set_result (result, DBUS_RESULT_SUCCESS);
return TRUE;
error:
link = _dbus_list_get_next_link (&entries, link);
}
+ _dbus_list_clear (&entries);
+
return FALSE;
}
+/**
+ * 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_ENABLE_EMBEDDED_TESTS
-/** @} */
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
-#ifdef DBUS_BUILD_TESTS
#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;
- DBusResultCode result;
+ 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;
+ }
+
+ 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, &result))
+ &entries, &len, &error))
_dbus_assert_not_reached ("could not parse address");
_dbus_assert (len == 2);
_dbus_assert (strcmp (dbus_address_entry_get_value (entries[0], "path"), "/tmp/foo") == 0);
dbus_address_entries_free (entries);
/* Different possible errors */
- if (dbus_parse_address ("foo", &entries, &len, &result))
+ if (dbus_parse_address ("", &entries, &len, &error))
_dbus_assert_not_reached ("Parsed incorrect address.");
+ else
+ dbus_error_free (&error);
- if (dbus_parse_address ("foo:bar", &entries, &len, &result))
+ if (dbus_parse_address ("foo", &entries, &len, &error))
_dbus_assert_not_reached ("Parsed incorrect address.");
-
- if (dbus_parse_address ("foo:bar,baz", &entries, &len, &result))
+ else
+ dbus_error_free (&error);
+
+ if (dbus_parse_address ("foo:bar", &entries, &len, &error))
_dbus_assert_not_reached ("Parsed incorrect address.");
-
- if (dbus_parse_address ("foo:bar=foo,baz", &entries, &len, &result))
+ else
+ dbus_error_free (&error);
+
+ if (dbus_parse_address ("foo:bar,baz", &entries, &len, &error))
_dbus_assert_not_reached ("Parsed incorrect address.");
-
- if (dbus_parse_address ("foo:bar=foo;baz", &entries, &len, &result))
+ else
+ dbus_error_free (&error);
+
+ if (dbus_parse_address ("foo:bar=foo,baz", &entries, &len, &error))
_dbus_assert_not_reached ("Parsed incorrect address.");
-
- if (dbus_parse_address ("foo:=foo", &entries, &len, &result))
+ else
+ dbus_error_free (&error);
+
+ if (dbus_parse_address ("foo:bar=foo;baz", &entries, &len, &error))
_dbus_assert_not_reached ("Parsed incorrect address.");
-
- if (dbus_parse_address ("foo:foo=", &entries, &len, &result))
+ else
+ dbus_error_free (&error);
+
+ if (dbus_parse_address ("foo:=foo", &entries, &len, &error))
_dbus_assert_not_reached ("Parsed incorrect address.");
+ else
+ dbus_error_free (&error);
+ if (dbus_parse_address ("foo:foo=", &entries, &len, &error))
+ _dbus_assert_not_reached ("Parsed incorrect address.");
+ else
+ dbus_error_free (&error);
+
+ if (dbus_parse_address ("foo:foo,bar=baz", &entries, &len, &error))
+ _dbus_assert_not_reached ("Parsed incorrect address.");
+ else
+ dbus_error_free (&error);
+
return TRUE;
}
+#endif /* !DOXYGEN_SHOULD_SKIP_THIS */
+
#endif