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 */ 43 struct 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 */ 64 void 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 */ 103 dbus_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 145 static void 146 dbus_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 */ 188 void 189 dbus_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 198 static DBusAddressEntry * 199 create_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 */ 226 const char * 227 dbus_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 */ 243 const char * 244 dbus_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 266 static dbus_bool_t 267 append_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 */ 362 dbus_bool_t 363 dbus_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 */ 582 char* 583 dbus_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 */ 616 char* 617 dbus_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 658 typedef struct 659 { 660 const char *escaped; 661 const char *unescaped; 662 } EscapeTest; 663 664 static 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 684 static const char* invalid_escaped_values[] = { 685 "%a", 686 "%q", 687 "%az", 688 "%%", 689 "%$$", 690 "abc%a", 691 "%axyz", 692 "%", 693 "$", 694 " ", 695 }; 696 697 dbus_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