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