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