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