1/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2/* dbus-address.c  Server address parser.
3 *
4 * Copyright (C) 2003  CodeFactory AB
5 * Copyright (C) 2004,2005  Red Hat, Inc.
6 *
7 * Licensed under the Academic Free License version 2.1
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 *
23 */
24
25#include <config.h>
26#include "dbus-address.h"
27#include "dbus-internals.h"
28#include "dbus-list.h"
29#include "dbus-string.h"
30#include "dbus-protocol.h"
31
32/**
33 * @defgroup DBusAddressInternals Address parsing
34 * @ingroup  DBusInternals
35 * @brief Implementation of parsing addresses of D-Bus servers.
36 *
37 * @{
38 */
39
40/**
41 * Internals of DBusAddressEntry
42 */
43struct DBusAddressEntry
44{
45  DBusString method; /**< The address type (unix, tcp, etc.) */
46
47  DBusList *keys;    /**< List of keys */
48  DBusList *values;  /**< List of values */
49};
50
51
52/**
53 *
54 * Sets #DBUS_ERROR_BAD_ADDRESS.
55 * If address_problem_type and address_problem_field are not #NULL,
56 * sets an error message about how the field is no good. Otherwise, sets
57 * address_problem_other as the error message.
58 *
59 * @param error the error to set
60 * @param address_problem_type the address type of the bad address or #NULL
61 * @param address_problem_field the missing field of the bad address or #NULL
62 * @param address_problem_other any other error message or #NULL
63 */
64void
65_dbus_set_bad_address (DBusError  *error,
66                       const char *address_problem_type,
67                       const char *address_problem_field,
68                       const char *address_problem_other)
69{
70  if (address_problem_type != NULL)
71    dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS,
72                    "Server address of type %s was missing argument %s",
73                    address_problem_type, address_problem_field);
74  else
75    dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS,
76                    "Could not parse server address: %s",
77                    address_problem_other);
78}
79
80/**
81 * #TRUE if the byte need not be escaped when found in a dbus address.
82 * All other bytes are required to be escaped in a valid address.
83 */
84#define _DBUS_ADDRESS_OPTIONALLY_ESCAPED_BYTE(b)        \
85         (((b) >= 'a' && (b) <= 'z') ||                 \
86          ((b) >= 'A' && (b) <= 'Z') ||                 \
87          ((b) >= '0' && (b) <= '9') ||                 \
88          (b) == '-' ||                                 \
89          (b) == '_' ||                                 \
90          (b) == '/' ||                                 \
91          (b) == '\\' ||                                \
92          (b) == '*' ||                                \
93          (b) == '.')
94
95/**
96 * Appends an escaped version of one string to another string,
97 * using the D-Bus address escaping mechanism
98 *
99 * @param escaped the string to append to
100 * @param unescaped the string to escape
101 * @returns #FALSE if no memory
102 */
103dbus_bool_t
104_dbus_address_append_escaped (DBusString       *escaped,
105                              const DBusString *unescaped)
106{
107  const char *p;
108  const char *end;
109  dbus_bool_t ret;
110  int orig_len;
111
112  ret = FALSE;
113
114  orig_len = _dbus_string_get_length (escaped);
115  p = _dbus_string_get_const_data (unescaped);
116  end = p + _dbus_string_get_length (unescaped);
117  while (p != end)
118    {
119      if (_DBUS_ADDRESS_OPTIONALLY_ESCAPED_BYTE (*p))
120        {
121          if (!_dbus_string_append_byte (escaped, *p))
122            goto out;
123        }
124      else
125        {
126          if (!_dbus_string_append_byte (escaped, '%'))
127            goto out;
128          if (!_dbus_string_append_byte_as_hex (escaped, *p))
129            goto out;
130        }
131
132      ++p;
133    }
134
135  ret = TRUE;
136
137 out:
138  if (!ret)
139    _dbus_string_set_length (escaped, orig_len);
140  return ret;
141}
142
143/** @} */ /* End of internals */
144
145static void
146dbus_address_entry_free (DBusAddressEntry *entry)
147{
148  DBusList *link;
149
150  _dbus_string_free (&entry->method);
151
152  link = _dbus_list_get_first_link (&entry->keys);
153  while (link != NULL)
154    {
155      _dbus_string_free (link->data);
156      dbus_free (link->data);
157
158      link = _dbus_list_get_next_link (&entry->keys, link);
159    }
160  _dbus_list_clear (&entry->keys);
161
162  link = _dbus_list_get_first_link (&entry->values);
163  while (link != NULL)
164    {
165      _dbus_string_free (link->data);
166      dbus_free (link->data);
167
168      link = _dbus_list_get_next_link (&entry->values, link);
169    }
170  _dbus_list_clear (&entry->values);
171
172  dbus_free (entry);
173}
174
175/**
176 * @defgroup DBusAddress Address parsing
177 * @ingroup  DBus
178 * @brief Parsing addresses of D-Bus servers.
179 *
180 * @{
181 */
182
183/**
184 * Frees a #NULL-terminated array of address entries.
185 *
186 * @param entries the array.
187 */
188void
189dbus_address_entries_free (DBusAddressEntry **entries)
190{
191  int i;
192
193  for (i = 0; entries[i] != NULL; i++)
194    dbus_address_entry_free (entries[i]);
195  dbus_free (entries);
196}
197
198static DBusAddressEntry *
199create_entry (void)
200{
201  DBusAddressEntry *entry;
202
203  entry = dbus_new0 (DBusAddressEntry, 1);
204
205  if (entry == NULL)
206    return NULL;
207
208  if (!_dbus_string_init (&entry->method))
209    {
210      dbus_free (entry);
211      return NULL;
212    }
213
214  return entry;
215}
216
217/**
218 * Returns the method string of an address entry.  For example, given
219 * the address entry "tcp:host=example.com" it would return the string
220 * "tcp"
221 *
222 * @param entry the entry.
223 * @returns a string describing the method. This string
224 * must not be freed.
225 */
226const char *
227dbus_address_entry_get_method (DBusAddressEntry *entry)
228{
229  return _dbus_string_get_const_data (&entry->method);
230}
231
232/**
233 * Returns a value from a key of an entry. For example,
234 * given the address "tcp:host=example.com,port=8073" if you asked
235 * for the key "host" you would get the value "example.com"
236 *
237 * The returned value is already unescaped.
238 *
239 * @param entry the entry.
240 * @param key the key.
241 * @returns the key value. This string must not be freed.
242 */
243const char *
244dbus_address_entry_get_value (DBusAddressEntry *entry,
245			      const char       *key)
246{
247  DBusList *values, *keys;
248
249  keys = _dbus_list_get_first_link (&entry->keys);
250  values = _dbus_list_get_first_link (&entry->values);
251
252  while (keys != NULL)
253    {
254      _dbus_assert (values != NULL);
255
256      if (_dbus_string_equal_c_str (keys->data, key))
257        return _dbus_string_get_const_data (values->data);
258
259      keys = _dbus_list_get_next_link (&entry->keys, keys);
260      values = _dbus_list_get_next_link (&entry->values, values);
261    }
262
263  return NULL;
264}
265
266static dbus_bool_t
267append_unescaped_value (DBusString       *unescaped,
268                        const DBusString *escaped,
269                        int               escaped_start,
270                        int               escaped_len,
271                        DBusError        *error)
272{
273  const char *p;
274  const char *end;
275  dbus_bool_t ret;
276
277  ret = FALSE;
278
279  p = _dbus_string_get_const_data (escaped) + escaped_start;
280  end = p + escaped_len;
281  while (p != end)
282    {
283      if (_DBUS_ADDRESS_OPTIONALLY_ESCAPED_BYTE (*p))
284        {
285          if (!_dbus_string_append_byte (unescaped, *p))
286            goto out;
287        }
288      else if (*p == '%')
289        {
290          /* Efficiency is king */
291          char buf[3];
292          DBusString hex;
293          int hex_end;
294
295          ++p;
296
297          if ((p + 2) > end)
298            {
299              dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS,
300                              "In D-Bus address, percent character was not followed by two hex digits");
301              goto out;
302            }
303
304          buf[0] = *p;
305          ++p;
306          buf[1] = *p;
307          buf[2] = '\0';
308
309          _dbus_string_init_const (&hex, buf);
310
311          if (!_dbus_string_hex_decode (&hex, 0, &hex_end,
312                                        unescaped,
313                                        _dbus_string_get_length (unescaped)))
314            goto out;
315
316          if (hex_end != 2)
317            {
318              dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS,
319                              "In D-Bus address, percent character was followed by characters other than hex digits");
320              goto out;
321            }
322        }
323      else
324        {
325          /* Error, should have been escaped */
326          dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS,
327                          "In D-Bus address, character '%c' should have been escaped\n",
328                          *p);
329          goto out;
330        }
331
332      ++p;
333    }
334
335  ret = TRUE;
336
337 out:
338  if (!ret && error && !dbus_error_is_set (error))
339    _DBUS_SET_OOM (error);
340
341  _dbus_assert (ret || error == NULL || dbus_error_is_set (error));
342
343  return ret;
344}
345
346/**
347 * Parses an address string of the form:
348 *
349 * method:key=value,key=value;method:key=value
350 *
351 * See the D-Bus specification for complete docs on the format.
352 *
353 * When connecting to an address, the first address entries
354 * in the semicolon-separated list should be tried first.
355 *
356 * @param address the address.
357 * @param entry return location to an array of entries.
358 * @param array_len return location for array length.
359 * @param error address where an error can be returned.
360 * @returns #TRUE on success, #FALSE otherwise.
361 */
362dbus_bool_t
363dbus_parse_address (const char         *address,
364		    DBusAddressEntry ***entry,
365		    int                *array_len,
366                    DBusError          *error)
367{
368  DBusString str;
369  int pos, end_pos, len, i;
370  DBusList *entries, *link;
371  DBusAddressEntry **entry_array;
372
373  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
374
375  _dbus_string_init_const (&str, address);
376
377  entries = NULL;
378  pos = 0;
379  len = _dbus_string_get_length (&str);
380
381  if (len == 0)
382  {
383    dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS,
384                    "Empty address '%s'", address);
385    goto error;
386  }
387
388  while (pos < len)
389    {
390      DBusAddressEntry *entry;
391
392      int found_pos;
393
394      entry = create_entry ();
395      if (!entry)
396	{
397	  dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
398
399	  goto error;
400	}
401
402      /* Append the entry */
403      if (!_dbus_list_append (&entries, entry))
404	{
405	  dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
406	  dbus_address_entry_free (entry);
407	  goto error;
408	}
409
410      /* Look for a semi-colon */
411      if (!_dbus_string_find (&str, pos, ";", &end_pos))
412	end_pos = len;
413
414      /* Look for the colon : */
415      if (!_dbus_string_find_to (&str, pos, end_pos, ":", &found_pos))
416	{
417	  dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS, "Address does not contain a colon");
418	  goto error;
419	}
420
421      if (!_dbus_string_copy_len (&str, pos, found_pos - pos, &entry->method, 0))
422	{
423	  dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
424	  goto error;
425	}
426
427      pos = found_pos + 1;
428
429      while (pos < end_pos)
430	{
431	  int comma_pos, equals_pos;
432
433	  if (!_dbus_string_find_to (&str, pos, end_pos, ",", &comma_pos))
434	    comma_pos = end_pos;
435
436	  if (!_dbus_string_find_to (&str, pos, comma_pos, "=", &equals_pos) ||
437	      equals_pos == pos || equals_pos + 1 == comma_pos)
438	    {
439	      dbus_set_error (error, DBUS_ERROR_BAD_ADDRESS,
440                              "'=' character not found or has no value following it");
441              goto error;
442	    }
443	  else
444	    {
445	      DBusString *key;
446	      DBusString *value;
447
448	      key = dbus_new0 (DBusString, 1);
449
450	      if (!key)
451		{
452		  dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
453		  goto error;
454		}
455
456	      value = dbus_new0 (DBusString, 1);
457	      if (!value)
458		{
459		  dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
460		  dbus_free (key);
461		  goto error;
462		}
463
464	      if (!_dbus_string_init (key))
465		{
466		  dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
467		  dbus_free (key);
468		  dbus_free (value);
469
470		  goto error;
471		}
472
473	      if (!_dbus_string_init (value))
474		{
475		  dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
476		  _dbus_string_free (key);
477
478		  dbus_free (key);
479		  dbus_free (value);
480		  goto error;
481		}
482
483	      if (!_dbus_string_copy_len (&str, pos, equals_pos - pos, key, 0))
484		{
485		  dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
486		  _dbus_string_free (key);
487		  _dbus_string_free (value);
488
489		  dbus_free (key);
490		  dbus_free (value);
491		  goto error;
492		}
493
494	      if (!append_unescaped_value (value, &str, equals_pos + 1,
495                                           comma_pos - equals_pos - 1, error))
496		{
497                  _dbus_assert (error == NULL || dbus_error_is_set (error));
498		  _dbus_string_free (key);
499		  _dbus_string_free (value);
500
501		  dbus_free (key);
502		  dbus_free (value);
503		  goto error;
504		}
505
506	      if (!_dbus_list_append (&entry->keys, key))
507		{
508		  dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
509		  _dbus_string_free (key);
510		  _dbus_string_free (value);
511
512		  dbus_free (key);
513		  dbus_free (value);
514		  goto error;
515		}
516
517	      if (!_dbus_list_append (&entry->values, value))
518		{
519		  dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
520		  _dbus_string_free (value);
521
522		  dbus_free (value);
523		  goto error;
524		}
525	    }
526
527	  pos = comma_pos + 1;
528	}
529
530      pos = end_pos + 1;
531    }
532
533  *array_len = _dbus_list_get_length (&entries);
534
535  entry_array = dbus_new (DBusAddressEntry *, *array_len + 1);
536
537  if (!entry_array)
538    {
539      dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
540
541      goto error;
542    }
543
544  entry_array [*array_len] = NULL;
545
546  link = _dbus_list_get_first_link (&entries);
547  i = 0;
548  while (link != NULL)
549    {
550      entry_array[i] = link->data;
551      i++;
552      link = _dbus_list_get_next_link (&entries, link);
553    }
554
555  _dbus_list_clear (&entries);
556  *entry = entry_array;
557
558  return TRUE;
559
560 error:
561
562  link = _dbus_list_get_first_link (&entries);
563  while (link != NULL)
564    {
565      dbus_address_entry_free (link->data);
566      link = _dbus_list_get_next_link (&entries, link);
567    }
568
569  _dbus_list_clear (&entries);
570
571  return FALSE;
572
573}
574
575/**
576 * Escapes the given string as a value in a key=value pair
577 * for a D-Bus address.
578 *
579 * @param value the unescaped value
580 * @returns newly-allocated escaped value or #NULL if no memory
581 */
582char*
583dbus_address_escape_value (const char *value)
584{
585  DBusString escaped;
586  DBusString unescaped;
587  char *ret;
588
589  ret = NULL;
590
591  _dbus_string_init_const (&unescaped, value);
592
593  if (!_dbus_string_init (&escaped))
594    return NULL;
595
596  if (!_dbus_address_append_escaped (&escaped, &unescaped))
597    goto out;
598
599  if (!_dbus_string_steal_data (&escaped, &ret))
600    goto out;
601
602 out:
603  _dbus_string_free (&escaped);
604  return ret;
605}
606
607/**
608 * Unescapes the given string as a value in a key=value pair
609 * for a D-Bus address. Note that dbus_address_entry_get_value()
610 * returns an already-unescaped value.
611 *
612 * @param value the escaped value
613 * @param error error to set if the unescaping fails
614 * @returns newly-allocated unescaped value or #NULL if no memory
615 */
616char*
617dbus_address_unescape_value (const char *value,
618                             DBusError  *error)
619{
620  DBusString unescaped;
621  DBusString escaped;
622  char *ret;
623
624  ret = NULL;
625
626  _dbus_string_init_const (&escaped, value);
627
628  if (!_dbus_string_init (&unescaped))
629    return NULL;
630
631  if (!append_unescaped_value (&unescaped, &escaped,
632                               0, _dbus_string_get_length (&escaped),
633                               error))
634    goto out;
635
636  if (!_dbus_string_steal_data (&unescaped, &ret))
637    goto out;
638
639 out:
640  if (ret == NULL && error && !dbus_error_is_set (error))
641    _DBUS_SET_OOM (error);
642
643  _dbus_assert (ret != NULL || error == NULL || dbus_error_is_set (error));
644
645  _dbus_string_free (&unescaped);
646  return ret;
647}
648
649/** @} */ /* End of public API */
650
651#ifdef DBUS_BUILD_TESTS
652
653#ifndef DOXYGEN_SHOULD_SKIP_THIS
654
655#include "dbus-test.h"
656#include <stdlib.h>
657
658typedef struct
659{
660  const char *escaped;
661  const char *unescaped;
662} EscapeTest;
663
664static const EscapeTest escape_tests[] = {
665  { "abcde", "abcde" },
666  { "", "" },
667  { "%20%20", "  " },
668  { "%24", "$" },
669  { "%25", "%" },
670  { "abc%24", "abc$" },
671  { "%24abc", "$abc" },
672  { "abc%24abc", "abc$abc" },
673  { "/", "/" },
674  { "-", "-" },
675  { "_", "_" },
676  { "A", "A" },
677  { "I", "I" },
678  { "Z", "Z" },
679  { "a", "a" },
680  { "i", "i" },
681  { "z", "z" }
682};
683
684static const char* invalid_escaped_values[] = {
685  "%a",
686  "%q",
687  "%az",
688  "%%",
689  "%$$",
690  "abc%a",
691  "%axyz",
692  "%",
693  "$",
694  " ",
695};
696
697dbus_bool_t
698_dbus_address_test (void)
699{
700  DBusAddressEntry **entries;
701  int len;
702  DBusError error = DBUS_ERROR_INIT;
703  int i;
704
705  i = 0;
706  while (i < _DBUS_N_ELEMENTS (escape_tests))
707    {
708      const EscapeTest *test = &escape_tests[i];
709      char *escaped;
710      char *unescaped;
711
712      escaped = dbus_address_escape_value (test->unescaped);
713      if (escaped == NULL)
714        _dbus_assert_not_reached ("oom");
715
716      if (strcmp (escaped, test->escaped) != 0)
717        {
718          _dbus_warn ("Escaped '%s' as '%s' should have been '%s'\n",
719                      test->unescaped, escaped, test->escaped);
720          exit (1);
721        }
722      dbus_free (escaped);
723
724      unescaped = dbus_address_unescape_value (test->escaped, &error);
725      if (unescaped == NULL)
726        {
727          _dbus_warn ("Failed to unescape '%s': %s\n",
728                      test->escaped, error.message);
729          dbus_error_free (&error);
730          exit (1);
731        }
732
733      if (strcmp (unescaped, test->unescaped) != 0)
734        {
735          _dbus_warn ("Unescaped '%s' as '%s' should have been '%s'\n",
736                      test->escaped, unescaped, test->unescaped);
737          exit (1);
738        }
739      dbus_free (unescaped);
740
741      ++i;
742    }
743
744  i = 0;
745  while (i < _DBUS_N_ELEMENTS (invalid_escaped_values))
746    {
747      char *unescaped;
748
749      unescaped = dbus_address_unescape_value (invalid_escaped_values[i],
750                                               &error);
751      if (unescaped != NULL)
752        {
753          _dbus_warn ("Should not have successfully unescaped '%s' to '%s'\n",
754                      invalid_escaped_values[i], unescaped);
755          dbus_free (unescaped);
756          exit (1);
757        }
758
759      _dbus_assert (dbus_error_is_set (&error));
760      dbus_error_free (&error);
761
762      ++i;
763    }
764
765  if (!dbus_parse_address ("unix:path=/tmp/foo;debug:name=test,sliff=sloff;",
766			   &entries, &len, &error))
767    _dbus_assert_not_reached ("could not parse address");
768  _dbus_assert (len == 2);
769  _dbus_assert (strcmp (dbus_address_entry_get_value (entries[0], "path"), "/tmp/foo") == 0);
770  _dbus_assert (strcmp (dbus_address_entry_get_value (entries[1], "name"), "test") == 0);
771  _dbus_assert (strcmp (dbus_address_entry_get_value (entries[1], "sliff"), "sloff") == 0);
772
773  dbus_address_entries_free (entries);
774
775  /* Different possible errors */
776  if (dbus_parse_address ("", &entries, &len, &error))
777    _dbus_assert_not_reached ("Parsed incorrect address.");
778  else
779    dbus_error_free (&error);
780
781  if (dbus_parse_address ("foo", &entries, &len, &error))
782    _dbus_assert_not_reached ("Parsed incorrect address.");
783  else
784    dbus_error_free (&error);
785
786  if (dbus_parse_address ("foo:bar", &entries, &len, &error))
787    _dbus_assert_not_reached ("Parsed incorrect address.");
788  else
789    dbus_error_free (&error);
790
791  if (dbus_parse_address ("foo:bar,baz", &entries, &len, &error))
792    _dbus_assert_not_reached ("Parsed incorrect address.");
793  else
794    dbus_error_free (&error);
795
796  if (dbus_parse_address ("foo:bar=foo,baz", &entries, &len, &error))
797    _dbus_assert_not_reached ("Parsed incorrect address.");
798  else
799    dbus_error_free (&error);
800
801  if (dbus_parse_address ("foo:bar=foo;baz", &entries, &len, &error))
802    _dbus_assert_not_reached ("Parsed incorrect address.");
803  else
804    dbus_error_free (&error);
805
806  if (dbus_parse_address ("foo:=foo", &entries, &len, &error))
807    _dbus_assert_not_reached ("Parsed incorrect address.");
808  else
809    dbus_error_free (&error);
810
811  if (dbus_parse_address ("foo:foo=", &entries, &len, &error))
812    _dbus_assert_not_reached ("Parsed incorrect address.");
813  else
814    dbus_error_free (&error);
815
816  if (dbus_parse_address ("foo:foo,bar=baz", &entries, &len, &error))
817    _dbus_assert_not_reached ("Parsed incorrect address.");
818  else
819    dbus_error_free (&error);
820
821  return TRUE;
822}
823
824#endif /* !DOXYGEN_SHOULD_SKIP_THIS */
825
826#endif
827