1 /* GIO - GLib Input, Output and Streaming Library 2 * 3 * Copyright (C) 2006-2007 Red Hat, Inc. 4 * 5 * This library is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU Lesser General Public 7 * License as published by the Free Software Foundation; either 8 * version 2 of the License, or (at your option) any later version. 9 * 10 * This library is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 * Lesser General Public License for more details. 14 * 15 * You should have received a copy of the GNU Lesser General 16 * Public License along with this library; if not, write to the 17 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, 18 * Boston, MA 02111-1307, USA. 19 * 20 * Author: Alexander Larsson <alexl (at) redhat.com> 21 */ 22 23 #include "config.h" 24 25 #include <sys/types.h> 26 #include <sys/stat.h> 27 #include <string.h> 28 #include <errno.h> 29 #include <fcntl.h> 30 #ifdef HAVE_UNISTD_H 31 #include <unistd.h> 32 #endif 33 #include <stdlib.h> 34 35 #include "gdummyfile.h" 36 #include "gfile.h" 37 38 #include "gioalias.h" 39 40 static void g_dummy_file_file_iface_init (GFileIface *iface); 41 42 typedef struct { 43 char *scheme; 44 char *userinfo; 45 char *host; 46 int port; /* -1 => not in uri */ 47 char *path; 48 char *query; 49 char *fragment; 50 } GDecodedUri; 51 52 struct _GDummyFile 53 { 54 GObject parent_instance; 55 56 GDecodedUri *decoded_uri; 57 char *text_uri; 58 }; 59 60 #define g_dummy_file_get_type _g_dummy_file_get_type 61 G_DEFINE_TYPE_WITH_CODE (GDummyFile, g_dummy_file, G_TYPE_OBJECT, 62 G_IMPLEMENT_INTERFACE (G_TYPE_FILE, 63 g_dummy_file_file_iface_init)) 64 65 #define SUB_DELIM_CHARS "!$&'()*+,;=" 66 67 static char * _g_encode_uri (GDecodedUri *decoded); 68 static void _g_decoded_uri_free (GDecodedUri *decoded); 69 static GDecodedUri *_g_decode_uri (const char *uri); 70 static GDecodedUri *_g_decoded_uri_new (void); 71 72 static char * unescape_string (const gchar *escaped_string, 73 const gchar *escaped_string_end, 74 const gchar *illegal_characters); 75 76 static void g_string_append_encoded (GString *string, 77 const char *encoded, 78 const char *reserved_chars_allowed); 79 80 static void 81 g_dummy_file_finalize (GObject *object) 82 { 83 GDummyFile *dummy; 84 85 dummy = G_DUMMY_FILE (object); 86 87 if (dummy->decoded_uri) 88 _g_decoded_uri_free (dummy->decoded_uri); 89 90 g_free (dummy->text_uri); 91 92 G_OBJECT_CLASS (g_dummy_file_parent_class)->finalize (object); 93 } 94 95 static void 96 g_dummy_file_class_init (GDummyFileClass *klass) 97 { 98 GObjectClass *gobject_class = G_OBJECT_CLASS (klass); 99 100 gobject_class->finalize = g_dummy_file_finalize; 101 } 102 103 static void 104 g_dummy_file_init (GDummyFile *dummy) 105 { 106 } 107 108 /** 109 * g_dummy_file_new: 110 * @uri: Universal Resource Identifier for the dummy file object. 111 * 112 * Returns: a new #GFile. 113 **/ 114 GFile * 115 _g_dummy_file_new (const char *uri) 116 { 117 GDummyFile *dummy; 118 119 g_return_val_if_fail (uri != NULL, NULL); 120 121 dummy = g_object_new (G_TYPE_DUMMY_FILE, NULL); 122 dummy->text_uri = g_strdup (uri); 123 dummy->decoded_uri = _g_decode_uri (uri); 124 125 return G_FILE (dummy); 126 } 127 128 static gboolean 129 g_dummy_file_is_native (GFile *file) 130 { 131 return FALSE; 132 } 133 134 static char * 135 g_dummy_file_get_basename (GFile *file) 136 { 137 GDummyFile *dummy = G_DUMMY_FILE (file); 138 139 if (dummy->decoded_uri) 140 return g_path_get_basename (dummy->decoded_uri->path); 141 return g_strdup (dummy->text_uri); 142 } 143 144 static char * 145 g_dummy_file_get_path (GFile *file) 146 { 147 return NULL; 148 } 149 150 static char * 151 g_dummy_file_get_uri (GFile *file) 152 { 153 return g_strdup (G_DUMMY_FILE (file)->text_uri); 154 } 155 156 static char * 157 g_dummy_file_get_parse_name (GFile *file) 158 { 159 return g_strdup (G_DUMMY_FILE (file)->text_uri); 160 } 161 162 static GFile * 163 g_dummy_file_get_parent (GFile *file) 164 { 165 GDummyFile *dummy = G_DUMMY_FILE (file); 166 GFile *parent; 167 char *dirname; 168 char *uri; 169 GDecodedUri new_decoded_uri; 170 171 if (dummy->decoded_uri == NULL || 172 g_strcmp0 (dummy->decoded_uri->path, "/") == 0) 173 return NULL; 174 175 dirname = g_path_get_dirname (dummy->decoded_uri->path); 176 177 if (strcmp (dirname, ".") == 0) 178 { 179 g_free (dirname); 180 return NULL; 181 } 182 183 new_decoded_uri = *dummy->decoded_uri; 184 new_decoded_uri.path = dirname; 185 uri = _g_encode_uri (&new_decoded_uri); 186 g_free (dirname); 187 188 parent = _g_dummy_file_new (uri); 189 g_free (uri); 190 191 return parent; 192 } 193 194 static GFile * 195 g_dummy_file_dup (GFile *file) 196 { 197 GDummyFile *dummy = G_DUMMY_FILE (file); 198 199 return _g_dummy_file_new (dummy->text_uri); 200 } 201 202 static guint 203 g_dummy_file_hash (GFile *file) 204 { 205 GDummyFile *dummy = G_DUMMY_FILE (file); 206 207 return g_str_hash (dummy->text_uri); 208 } 209 210 static gboolean 211 g_dummy_file_equal (GFile *file1, 212 GFile *file2) 213 { 214 GDummyFile *dummy1 = G_DUMMY_FILE (file1); 215 GDummyFile *dummy2 = G_DUMMY_FILE (file2); 216 217 return g_str_equal (dummy1->text_uri, dummy2->text_uri); 218 } 219 220 static int 221 safe_strcmp (const char *a, 222 const char *b) 223 { 224 if (a == NULL) 225 a = ""; 226 if (b == NULL) 227 b = ""; 228 229 return strcmp (a, b); 230 } 231 232 static gboolean 233 uri_same_except_path (GDecodedUri *a, 234 GDecodedUri *b) 235 { 236 if (safe_strcmp (a->scheme, b->scheme) != 0) 237 return FALSE; 238 if (safe_strcmp (a->userinfo, b->userinfo) != 0) 239 return FALSE; 240 if (safe_strcmp (a->host, b->host) != 0) 241 return FALSE; 242 if (a->port != b->port) 243 return FALSE; 244 245 return TRUE; 246 } 247 248 static const char * 249 match_prefix (const char *path, 250 const char *prefix) 251 { 252 int prefix_len; 253 254 prefix_len = strlen (prefix); 255 if (strncmp (path, prefix, prefix_len) != 0) 256 return NULL; 257 return path + prefix_len; 258 } 259 260 static gboolean 261 g_dummy_file_prefix_matches (GFile *parent, GFile *descendant) 262 { 263 GDummyFile *parent_dummy = G_DUMMY_FILE (parent); 264 GDummyFile *descendant_dummy = G_DUMMY_FILE (descendant); 265 const char *remainder; 266 267 if (parent_dummy->decoded_uri != NULL && 268 descendant_dummy->decoded_uri != NULL) 269 { 270 if (uri_same_except_path (parent_dummy->decoded_uri, 271 descendant_dummy->decoded_uri)) 272 { 273 remainder = match_prefix (descendant_dummy->decoded_uri->path, 274 parent_dummy->decoded_uri->path); 275 if (remainder != NULL && *remainder == '/') 276 { 277 while (*remainder == '/') 278 remainder++; 279 if (*remainder != 0) 280 return TRUE; 281 } 282 } 283 } 284 else 285 { 286 remainder = match_prefix (descendant_dummy->text_uri, 287 parent_dummy->text_uri); 288 if (remainder != NULL && *remainder == '/') 289 { 290 while (*remainder == '/') 291 remainder++; 292 if (*remainder != 0) 293 return TRUE; 294 } 295 } 296 297 return FALSE; 298 } 299 300 static char * 301 g_dummy_file_get_relative_path (GFile *parent, 302 GFile *descendant) 303 { 304 GDummyFile *parent_dummy = G_DUMMY_FILE (parent); 305 GDummyFile *descendant_dummy = G_DUMMY_FILE (descendant); 306 const char *remainder; 307 308 if (parent_dummy->decoded_uri != NULL && 309 descendant_dummy->decoded_uri != NULL) 310 { 311 if (uri_same_except_path (parent_dummy->decoded_uri, 312 descendant_dummy->decoded_uri)) 313 { 314 remainder = match_prefix (descendant_dummy->decoded_uri->path, 315 parent_dummy->decoded_uri->path); 316 if (remainder != NULL && *remainder == '/') 317 { 318 while (*remainder == '/') 319 remainder++; 320 if (*remainder != 0) 321 return g_strdup (remainder); 322 } 323 } 324 } 325 else 326 { 327 remainder = match_prefix (descendant_dummy->text_uri, 328 parent_dummy->text_uri); 329 if (remainder != NULL && *remainder == '/') 330 { 331 while (*remainder == '/') 332 remainder++; 333 if (*remainder != 0) 334 return unescape_string (remainder, NULL, "/"); 335 } 336 } 337 338 return NULL; 339 } 340 341 342 static GFile * 343 g_dummy_file_resolve_relative_path (GFile *file, 344 const char *relative_path) 345 { 346 GDummyFile *dummy = G_DUMMY_FILE (file); 347 GFile *child; 348 char *uri; 349 GDecodedUri new_decoded_uri; 350 GString *str; 351 352 if (dummy->decoded_uri == NULL) 353 { 354 str = g_string_new (dummy->text_uri); 355 g_string_append (str, "/"); 356 g_string_append_encoded (str, relative_path, SUB_DELIM_CHARS ":@/"); 357 child = _g_dummy_file_new (str->str); 358 g_string_free (str, TRUE); 359 } 360 else 361 { 362 new_decoded_uri = *dummy->decoded_uri; 363 364 if (g_path_is_absolute (relative_path)) 365 new_decoded_uri.path = g_strdup (relative_path); 366 else 367 new_decoded_uri.path = g_build_filename (new_decoded_uri.path, relative_path, NULL); 368 369 uri = _g_encode_uri (&new_decoded_uri); 370 g_free (new_decoded_uri.path); 371 372 child = _g_dummy_file_new (uri); 373 g_free (uri); 374 } 375 376 return child; 377 } 378 379 static GFile * 380 g_dummy_file_get_child_for_display_name (GFile *file, 381 const char *display_name, 382 GError **error) 383 { 384 return g_file_get_child (file, display_name); 385 } 386 387 static gboolean 388 g_dummy_file_has_uri_scheme (GFile *file, 389 const char *uri_scheme) 390 { 391 GDummyFile *dummy = G_DUMMY_FILE (file); 392 393 if (dummy->decoded_uri) 394 return g_ascii_strcasecmp (uri_scheme, dummy->decoded_uri->scheme) == 0; 395 return FALSE; 396 } 397 398 static char * 399 g_dummy_file_get_uri_scheme (GFile *file) 400 { 401 GDummyFile *dummy = G_DUMMY_FILE (file); 402 403 if (dummy->decoded_uri) 404 return g_strdup (dummy->decoded_uri->scheme); 405 406 return NULL; 407 } 408 409 410 static void 411 g_dummy_file_file_iface_init (GFileIface *iface) 412 { 413 iface->dup = g_dummy_file_dup; 414 iface->hash = g_dummy_file_hash; 415 iface->equal = g_dummy_file_equal; 416 iface->is_native = g_dummy_file_is_native; 417 iface->has_uri_scheme = g_dummy_file_has_uri_scheme; 418 iface->get_uri_scheme = g_dummy_file_get_uri_scheme; 419 iface->get_basename = g_dummy_file_get_basename; 420 iface->get_path = g_dummy_file_get_path; 421 iface->get_uri = g_dummy_file_get_uri; 422 iface->get_parse_name = g_dummy_file_get_parse_name; 423 iface->get_parent = g_dummy_file_get_parent; 424 iface->prefix_matches = g_dummy_file_prefix_matches; 425 iface->get_relative_path = g_dummy_file_get_relative_path; 426 iface->resolve_relative_path = g_dummy_file_resolve_relative_path; 427 iface->get_child_for_display_name = g_dummy_file_get_child_for_display_name; 428 } 429 430 /* Uri handling helper functions: */ 431 432 static int 433 unescape_character (const char *scanner) 434 { 435 int first_digit; 436 int second_digit; 437 438 first_digit = g_ascii_xdigit_value (*scanner++); 439 if (first_digit < 0) 440 return -1; 441 442 second_digit = g_ascii_xdigit_value (*scanner++); 443 if (second_digit < 0) 444 return -1; 445 446 return (first_digit << 4) | second_digit; 447 } 448 449 static char * 450 unescape_string (const gchar *escaped_string, 451 const gchar *escaped_string_end, 452 const gchar *illegal_characters) 453 { 454 const gchar *in; 455 gchar *out, *result; 456 gint character; 457 458 if (escaped_string == NULL) 459 return NULL; 460 461 if (escaped_string_end == NULL) 462 escaped_string_end = escaped_string + strlen (escaped_string); 463 464 result = g_malloc (escaped_string_end - escaped_string + 1); 465 466 out = result; 467 for (in = escaped_string; in < escaped_string_end; in++) 468 { 469 character = *in; 470 if (*in == '%') 471 { 472 in++; 473 if (escaped_string_end - in < 2) 474 { 475 g_free (result); 476 return NULL; 477 } 478 479 character = unescape_character (in); 480 481 /* Check for an illegal character. We consider '\0' illegal here. */ 482 if (character <= 0 || 483 (illegal_characters != NULL && 484 strchr (illegal_characters, (char)character) != NULL)) 485 { 486 g_free (result); 487 return NULL; 488 } 489 in++; /* The other char will be eaten in the loop header */ 490 } 491 *out++ = (char)character; 492 } 493 494 *out = '\0'; 495 g_warn_if_fail (out - result <= strlen (escaped_string)); 496 return result; 497 } 498 499 void 500 _g_decoded_uri_free (GDecodedUri *decoded) 501 { 502 if (decoded == NULL) 503 return; 504 505 g_free (decoded->scheme); 506 g_free (decoded->query); 507 g_free (decoded->fragment); 508 g_free (decoded->userinfo); 509 g_free (decoded->host); 510 g_free (decoded->path); 511 g_free (decoded); 512 } 513 514 GDecodedUri * 515 _g_decoded_uri_new (void) 516 { 517 GDecodedUri *uri; 518 519 uri = g_new0 (GDecodedUri, 1); 520 uri->port = -1; 521 522 return uri; 523 } 524 525 GDecodedUri * 526 _g_decode_uri (const char *uri) 527 { 528 GDecodedUri *decoded; 529 const char *p, *in, *hier_part_start, *hier_part_end, *query_start, *fragment_start; 530 char *out; 531 char c; 532 533 /* From RFC 3986 Decodes: 534 * URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] 535 */ 536 537 p = uri; 538 539 /* Decode scheme: 540 scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) 541 */ 542 543 if (!g_ascii_isalpha (*p)) 544 return NULL; 545 546 while (1) 547 { 548 c = *p++; 549 550 if (c == ':') 551 break; 552 553 if (!(g_ascii_isalnum(c) || 554 c == '+' || 555 c == '-' || 556 c == '.')) 557 return NULL; 558 } 559 560 decoded = _g_decoded_uri_new (); 561 562 decoded->scheme = g_malloc (p - uri); 563 out = decoded->scheme; 564 for (in = uri; in < p - 1; in++) 565 *out++ = g_ascii_tolower (*in); 566 *out = 0; 567 568 hier_part_start = p; 569 570 query_start = strchr (p, '?'); 571 if (query_start) 572 { 573 hier_part_end = query_start++; 574 fragment_start = strchr (query_start, '#'); 575 if (fragment_start) 576 { 577 decoded->query = g_strndup (query_start, fragment_start - query_start); 578 decoded->fragment = g_strdup (fragment_start+1); 579 } 580 else 581 { 582 decoded->query = g_strdup (query_start); 583 decoded->fragment = NULL; 584 } 585 } 586 else 587 { 588 /* No query */ 589 decoded->query = NULL; 590 fragment_start = strchr (p, '#'); 591 if (fragment_start) 592 { 593 hier_part_end = fragment_start++; 594 decoded->fragment = g_strdup (fragment_start); 595 } 596 else 597 { 598 hier_part_end = p + strlen (p); 599 decoded->fragment = NULL; 600 } 601 } 602 603 /* 3: 604 hier-part = "//" authority path-abempty 605 / path-absolute 606 / path-rootless 607 / path-empty 608 609 */ 610 611 if (hier_part_start[0] == '/' && 612 hier_part_start[1] == '/') 613 { 614 const char *authority_start, *authority_end; 615 const char *userinfo_start, *userinfo_end; 616 const char *host_start, *host_end; 617 const char *port_start; 618 619 authority_start = hier_part_start + 2; 620 /* authority is always followed by / or nothing */ 621 authority_end = memchr (authority_start, '/', hier_part_end - authority_start); 622 if (authority_end == NULL) 623 authority_end = hier_part_end; 624 625 /* 3.2: 626 authority = [ userinfo "@" ] host [ ":" port ] 627 */ 628 629 userinfo_end = memchr (authority_start, '@', authority_end - authority_start); 630 if (userinfo_end) 631 { 632 userinfo_start = authority_start; 633 decoded->userinfo = unescape_string (userinfo_start, userinfo_end, NULL); 634 if (decoded->userinfo == NULL) 635 { 636 _g_decoded_uri_free (decoded); 637 return NULL; 638 } 639 host_start = userinfo_end + 1; 640 } 641 else 642 host_start = authority_start; 643 644 port_start = memchr (host_start, ':', authority_end - host_start); 645 if (port_start) 646 { 647 host_end = port_start++; 648 649 decoded->port = atoi(port_start); 650 } 651 else 652 { 653 host_end = authority_end; 654 decoded->port = -1; 655 } 656 657 decoded->host = g_strndup (host_start, host_end - host_start); 658 659 hier_part_start = authority_end; 660 } 661 662 decoded->path = unescape_string (hier_part_start, hier_part_end, "/"); 663 664 if (decoded->path == NULL) 665 { 666 _g_decoded_uri_free (decoded); 667 return NULL; 668 } 669 670 return decoded; 671 } 672 673 static gboolean 674 is_valid (char c, const char *reserved_chars_allowed) 675 { 676 if (g_ascii_isalnum (c) || 677 c == '-' || 678 c == '.' || 679 c == '_' || 680 c == '~') 681 return TRUE; 682 683 if (reserved_chars_allowed && 684 strchr (reserved_chars_allowed, c) != NULL) 685 return TRUE; 686 687 return FALSE; 688 } 689 690 static void 691 g_string_append_encoded (GString *string, 692 const char *encoded, 693 const char *reserved_chars_allowed) 694 { 695 unsigned char c; 696 const char *end; 697 static const gchar hex[16] = "0123456789ABCDEF"; 698 699 end = encoded + strlen (encoded); 700 701 while ((c = *encoded) != 0) 702 { 703 if (is_valid (c, reserved_chars_allowed)) 704 { 705 g_string_append_c (string, c); 706 encoded++; 707 } 708 else 709 { 710 g_string_append_c (string, '%'); 711 g_string_append_c (string, hex[((guchar)c) >> 4]); 712 g_string_append_c (string, hex[((guchar)c) & 0xf]); 713 encoded++; 714 } 715 } 716 } 717 718 static char * 719 _g_encode_uri (GDecodedUri *decoded) 720 { 721 GString *uri; 722 723 uri = g_string_new (NULL); 724 725 g_string_append (uri, decoded->scheme); 726 g_string_append (uri, "://"); 727 728 if (decoded->host != NULL) 729 { 730 if (decoded->userinfo) 731 { 732 /* userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) */ 733 g_string_append_encoded (uri, decoded->userinfo, SUB_DELIM_CHARS ":"); 734 g_string_append_c (uri, '@'); 735 } 736 737 g_string_append (uri, decoded->host); 738 739 if (decoded->port != -1) 740 { 741 g_string_append_c (uri, ':'); 742 g_string_append_printf (uri, "%d", decoded->port); 743 } 744 } 745 746 g_string_append_encoded (uri, decoded->path, SUB_DELIM_CHARS ":@/"); 747 748 if (decoded->query) 749 { 750 g_string_append_c (uri, '?'); 751 g_string_append (uri, decoded->query); 752 } 753 754 if (decoded->fragment) 755 { 756 g_string_append_c (uri, '#'); 757 g_string_append (uri, decoded->fragment); 758 } 759 760 return g_string_free (uri, FALSE); 761 } 762