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