Home | History | Annotate | Download | only in gio
      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