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