gdummyfile.c revision cefebf7ff2e80aaf9ced426598b033482fef039b
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#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 40static void g_dummy_file_file_iface_init (GFileIface *iface); 41 42typedef 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 52struct _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 61G_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 67static char * _g_encode_uri (GDecodedUri *decoded); 68static void _g_decoded_uri_free (GDecodedUri *decoded); 69static GDecodedUri *_g_decode_uri (const char *uri); 70static GDecodedUri *_g_decoded_uri_new (void); 71 72static char * unescape_string (const gchar *escaped_string, 73 const gchar *escaped_string_end, 74 const gchar *illegal_characters); 75 76static void g_string_append_encoded (GString *string, 77 const char *encoded, 78 const char *reserved_chars_allowed); 79 80static void 81g_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 95static void 96g_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 103static void 104g_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 **/ 114GFile * 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 128static gboolean 129g_dummy_file_is_native (GFile *file) 130{ 131 return FALSE; 132} 133 134static char * 135g_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 144static char * 145g_dummy_file_get_path (GFile *file) 146{ 147 return NULL; 148} 149 150static char * 151g_dummy_file_get_uri (GFile *file) 152{ 153 return g_strdup (G_DUMMY_FILE (file)->text_uri); 154} 155 156static char * 157g_dummy_file_get_parse_name (GFile *file) 158{ 159 return g_strdup (G_DUMMY_FILE (file)->text_uri); 160} 161 162static GFile * 163g_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 194static GFile * 195g_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 202static guint 203g_dummy_file_hash (GFile *file) 204{ 205 GDummyFile *dummy = G_DUMMY_FILE (file); 206 207 return g_str_hash (dummy->text_uri); 208} 209 210static gboolean 211g_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 220static int 221safe_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 232static gboolean 233uri_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 248static const char * 249match_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 260static gboolean 261g_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 300static char * 301g_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 342static GFile * 343g_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 379static GFile * 380g_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 387static gboolean 388g_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 398static char * 399g_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 410static void 411g_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 432static int 433unescape_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 449static char * 450unescape_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 499void 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 514GDecodedUri * 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 525GDecodedUri * 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 673static gboolean 674is_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 690static void 691g_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 718static 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