57abd278e985c47a302dd0368226851e0fa3bb1f
[platform/upstream/glib.git] / gio / gdummyfile.c
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
37 static void g_dummy_file_file_iface_init (GFileIface *iface);
38
39 typedef 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
49 struct _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
58 G_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
64 static char *       _g_encode_uri       (GDecodedUri *decoded);
65 static void         _g_decoded_uri_free (GDecodedUri *decoded);
66 static GDecodedUri *_g_decode_uri       (const char  *uri);
67 static GDecodedUri *_g_decoded_uri_new  (void);
68
69 static char * unescape_string (const gchar *escaped_string,
70                                const gchar *escaped_string_end,
71                                const gchar *illegal_characters);
72
73 static void g_string_append_encoded (GString    *string, 
74                                      const char *encoded,
75                                      const char *reserved_chars_allowed);
76
77 static void
78 g_dummy_file_finalize (GObject *object)
79 {
80   GDummyFile *dummy;
81
82   dummy = G_DUMMY_FILE (object);
83
84   if (dummy->decoded_uri)
85     _g_decoded_uri_free (dummy->decoded_uri);
86   
87   g_free (dummy->text_uri);
88   
89   if (G_OBJECT_CLASS (g_dummy_file_parent_class)->finalize)
90     (*G_OBJECT_CLASS (g_dummy_file_parent_class)->finalize) (object);
91 }
92
93 static void
94 g_dummy_file_class_init (GDummyFileClass *klass)
95 {
96   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
97
98   gobject_class->finalize = g_dummy_file_finalize;
99 }
100
101 static void
102 g_dummy_file_init (GDummyFile *dummy)
103 {
104 }
105
106 /**
107  * g_dummy_file_new:
108  * @uri: Universal Resource Identifier for the dummy file object.
109  * 
110  * Returns: a new #GFile. 
111  **/
112 GFile *
113 _g_dummy_file_new (const char *uri)
114 {
115   GDummyFile *dummy;
116
117   g_return_val_if_fail (uri != NULL, NULL);
118
119   dummy = g_object_new (G_TYPE_DUMMY_FILE, NULL);
120   dummy->text_uri = g_strdup (uri);
121   dummy->decoded_uri = _g_decode_uri (uri);
122   
123   return G_FILE (dummy);
124 }
125
126 static gboolean
127 g_dummy_file_is_native (GFile *file)
128 {
129   return FALSE;
130 }
131
132 static char *
133 g_dummy_file_get_basename (GFile *file)
134 {
135   GDummyFile *dummy = G_DUMMY_FILE (file);
136   
137   if (dummy->decoded_uri)
138     return g_path_get_basename (dummy->decoded_uri->path);
139   return g_strdup (dummy->text_uri);
140 }
141
142 static char *
143 g_dummy_file_get_path (GFile *file)
144 {
145   GDummyFile *dummy = G_DUMMY_FILE (file);
146
147   if (dummy->decoded_uri)
148     return g_strdup (dummy->decoded_uri->path);
149   return NULL;
150 }
151
152 static char *
153 g_dummy_file_get_uri (GFile *file)
154 {
155   return g_strdup (G_DUMMY_FILE (file)->text_uri);
156 }
157
158 static char *
159 g_dummy_file_get_parse_name (GFile *file)
160 {
161   return g_strdup (G_DUMMY_FILE (file)->text_uri);
162 }
163
164 static GFile *
165 g_dummy_file_get_parent (GFile *file)
166 {
167   GDummyFile *dummy = G_DUMMY_FILE (file);
168   GFile *parent;
169   char *dirname;
170   char *uri;
171   GDecodedUri new_decoded_uri;
172
173   if (dummy->decoded_uri == NULL)
174     return NULL;
175
176   dirname = g_path_get_dirname (dummy->decoded_uri->path);
177   
178   if (strcmp (dirname, ".") == 0)
179     {
180       g_free (dirname);
181       return NULL;
182     }
183   
184   new_decoded_uri = *dummy->decoded_uri;
185   new_decoded_uri.path = dirname;
186   uri = _g_encode_uri (&new_decoded_uri);
187   g_free (dirname);
188   
189   parent = _g_dummy_file_new (uri);
190   g_free (uri);
191   
192   return parent;
193 }
194
195 static GFile *
196 g_dummy_file_dup (GFile *file)
197 {
198   GDummyFile *dummy = G_DUMMY_FILE (file);
199
200   return _g_dummy_file_new (dummy->text_uri);
201 }
202
203 static guint
204 g_dummy_file_hash (GFile *file)
205 {
206   GDummyFile *dummy = G_DUMMY_FILE (file);
207   
208   return g_str_hash (dummy->text_uri);
209 }
210
211 static gboolean
212 g_dummy_file_equal (GFile *file1,
213                     GFile *file2)
214 {
215   GDummyFile *dummy1 = G_DUMMY_FILE (file1);
216   GDummyFile *dummy2 = G_DUMMY_FILE (file2);
217
218   return g_str_equal (dummy1->text_uri, dummy2->text_uri);
219 }
220
221 static int
222 safe_strcmp (const char *a, 
223              const char *b)
224 {
225   if (a == NULL)
226     a = "";
227   if (b == NULL)
228     b = "";
229
230   return strcmp (a, b);
231 }
232
233 static gboolean
234 uri_same_except_path (GDecodedUri *a,
235                       GDecodedUri *b)
236 {
237   if (safe_strcmp (a->scheme, b->scheme) != 0)
238     return FALSE;
239   if (safe_strcmp (a->userinfo, b->userinfo) != 0)
240     return FALSE;
241   if (safe_strcmp (a->host, b->host) != 0)
242     return FALSE;
243   if (a->port != b->port)
244     return FALSE;
245
246   return TRUE;
247 }
248
249 static const char *
250 match_prefix (const char *path, 
251               const char *prefix)
252 {
253   int prefix_len;
254
255   prefix_len = strlen (prefix);
256   if (strncmp (path, prefix, prefix_len) != 0)
257     return NULL;
258   return path + prefix_len;
259 }
260
261 static gboolean
262 g_dummy_file_contains_file (GFile *parent,
263                             GFile *descendant)
264 {
265   GDummyFile *parent_dummy = G_DUMMY_FILE (parent);
266   GDummyFile *descendant_dummy = G_DUMMY_FILE (descendant);
267   const char *remainder;
268
269   if (parent_dummy->decoded_uri != NULL &&
270       descendant_dummy->decoded_uri != NULL)
271     {
272       if (uri_same_except_path (parent_dummy->decoded_uri,
273                                 descendant_dummy->decoded_uri)) {
274         remainder = match_prefix (descendant_dummy->decoded_uri->path,
275                                   parent_dummy->decoded_uri->path);
276         if (remainder != NULL && *remainder == '/')
277           {
278             while (*remainder == '/')
279               remainder++;
280             if (*remainder != 0)
281               return TRUE;
282           }
283       }
284     }
285   else
286     {
287       remainder = match_prefix (descendant_dummy->text_uri,
288                                 parent_dummy->text_uri);
289       if (remainder != NULL && *remainder == '/')
290           {
291             while (*remainder == '/')
292               remainder++;
293             if (*remainder != 0)
294               return TRUE;
295           }
296     }
297   
298   return FALSE;
299 }
300
301 static char *
302 g_dummy_file_get_relative_path (GFile *parent,
303                                 GFile *descendant)
304 {
305   GDummyFile *parent_dummy = G_DUMMY_FILE (parent);
306   GDummyFile *descendant_dummy = G_DUMMY_FILE (descendant);
307   const char *remainder;
308
309   if (parent_dummy->decoded_uri != NULL &&
310       descendant_dummy->decoded_uri != NULL)
311     {
312       if (uri_same_except_path (parent_dummy->decoded_uri,
313                                 descendant_dummy->decoded_uri)) {
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->contains_file = g_dummy_file_contains_file;
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     character = *in;
469     if (*in == '%') {
470       in++;
471       if (escaped_string_end - in < 2)
472         {
473           g_free (result);
474           return NULL;
475         }
476       
477       character = unescape_character (in);
478       
479       /* Check for an illegal character. We consider '\0' illegal here. */
480       if (character <= 0 ||
481           (illegal_characters != NULL &&
482            strchr (illegal_characters, (char)character) != NULL))
483         {
484           g_free (result);
485           return NULL;
486         }
487       in++; /* The other char will be eaten in the loop header */
488     }
489     *out++ = (char)character;
490   }
491   
492   *out = '\0';
493   g_assert (out - result <= strlen (escaped_string));
494   return result;
495 }
496
497 void
498 _g_decoded_uri_free (GDecodedUri *decoded)
499 {
500   if (decoded == NULL)
501     return;
502
503   g_free (decoded->scheme);
504   g_free (decoded->query);
505   g_free (decoded->fragment);
506   g_free (decoded->userinfo);
507   g_free (decoded->host);
508   g_free (decoded->path);
509   g_free (decoded);
510 }
511
512 GDecodedUri *
513 _g_decoded_uri_new (void)
514 {
515   GDecodedUri *uri;
516
517   uri = g_new0 (GDecodedUri, 1);
518   uri->port = -1;
519
520   return uri;
521 }
522
523 GDecodedUri *
524 _g_decode_uri (const char *uri)
525 {
526   GDecodedUri *decoded;
527   const char *p, *in, *hier_part_start, *hier_part_end, *query_start, *fragment_start;
528   char *out;
529   char c;
530
531   /* From RFC 3986 Decodes:
532    * URI         = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
533    */ 
534
535   p = uri;
536   
537   /* Decode scheme:
538      scheme      = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
539   */
540
541   if (!g_ascii_isalpha (*p))
542     return NULL;
543
544   while (1)
545     {
546       c = *p++;
547
548       if (c == ':')
549         break;
550       
551       if (!(g_ascii_isalnum(c) ||
552             c == '+' ||
553             c == '-' ||
554             c == '.'))
555         return NULL;
556     }
557
558   decoded = _g_decoded_uri_new ();
559   
560   decoded->scheme = g_malloc (p - uri);
561   out = decoded->scheme;
562   for (in = uri; in < p - 1; in++)
563     *out++ = g_ascii_tolower (*in);
564   *out = 0;
565
566   hier_part_start = p;
567
568   query_start = strchr (p, '?');
569   if (query_start)
570     {
571       hier_part_end = query_start++;
572       fragment_start = strchr (query_start, '#');
573       if (fragment_start)
574         {
575           decoded->query = g_strndup (query_start, fragment_start - query_start);
576           decoded->fragment = g_strdup (fragment_start+1);
577         }
578       else
579         {
580           decoded->query = g_strdup (query_start);
581           decoded->fragment = NULL;
582         }
583     }
584   else
585     {
586       /* No query */
587       decoded->query = NULL;
588       fragment_start = strchr (p, '#');
589       if (fragment_start)
590         {
591           hier_part_end = fragment_start++;
592           decoded->fragment = g_strdup (fragment_start);
593         }
594       else
595         {
596           hier_part_end = p + strlen (p);
597           decoded->fragment = NULL;
598         }
599     }
600
601   /*  3:
602       hier-part   = "//" authority path-abempty
603                   / path-absolute
604                   / path-rootless
605                   / path-empty
606
607   */
608
609   if (hier_part_start[0] == '/' &&
610       hier_part_start[1] == '/')
611     {
612       const char *authority_start, *authority_end;
613       const char *userinfo_start, *userinfo_end;
614       const char *host_start, *host_end;
615       const char *port_start;
616       
617       authority_start = hier_part_start + 2;
618       /* authority is always followed by / or nothing */
619       authority_end = memchr (authority_start, '/', hier_part_end - authority_start);
620       if (authority_end == NULL)
621         authority_end = hier_part_end;
622
623       /* 3.2:
624               authority   = [ userinfo "@" ] host [ ":" port ]
625       */
626
627       userinfo_end = memchr (authority_start, '@', authority_end - authority_start);
628       if (userinfo_end)
629         {
630           userinfo_start = authority_start;
631           decoded->userinfo = unescape_string (userinfo_start, userinfo_end, NULL);
632           if (decoded->userinfo == NULL)
633             {
634               _g_decoded_uri_free (decoded);
635               return NULL;
636             }
637           host_start = userinfo_end + 1;
638         }
639       else
640         host_start = authority_start;
641
642       port_start = memchr (host_start, ':', authority_end - host_start);
643       if (port_start)
644         {
645           host_end = port_start++;
646
647           decoded->port = atoi(port_start);
648         }
649       else
650         {
651           host_end = authority_end;
652           decoded->port = -1;
653         }
654
655       decoded->host = g_strndup (host_start, host_end - host_start);
656
657       hier_part_start = authority_end;
658     }
659
660   decoded->path = unescape_string (hier_part_start, hier_part_end, "/");
661
662   if (decoded->path == NULL)
663     {
664       _g_decoded_uri_free (decoded);
665       return NULL;
666     }
667   
668   return decoded;
669 }
670
671 static gboolean
672 is_valid (char c, const char *reserved_chars_allowed)
673 {
674   if (g_ascii_isalnum (c) ||
675       c == '-' ||
676       c == '.' ||
677       c == '_' ||
678       c == '~')
679     return TRUE;
680
681   if (reserved_chars_allowed &&
682       strchr (reserved_chars_allowed, c) != NULL)
683     return TRUE;
684   
685   return FALSE;
686 }
687
688 static void
689 g_string_append_encoded (GString    *string, 
690                          const char *encoded,
691                          const char *reserved_chars_allowed)
692 {
693   unsigned char c;
694   const char *end;
695   static const gchar hex[16] = "0123456789ABCDEF";
696
697   end = encoded + strlen (encoded);
698   
699   while ((c = *encoded) != 0)
700     {
701       if (is_valid (c, reserved_chars_allowed))
702         {
703           g_string_append_c (string, c);
704           encoded++;
705         }
706       else
707         {
708           g_string_append_c (string, '%');
709           g_string_append_c (string, hex[((guchar)c) >> 4]);
710           g_string_append_c (string, hex[((guchar)c) & 0xf]);
711           encoded++;
712         }
713     }
714 }
715
716 static char *
717 _g_encode_uri (GDecodedUri *decoded)
718 {
719   GString *uri;
720
721   uri = g_string_new (NULL);
722
723   g_string_append (uri, decoded->scheme);
724   g_string_append (uri, "://");
725
726   if (decoded->host != NULL)
727     {
728       if (decoded->userinfo)
729         {
730           /* userinfo    = *( unreserved / pct-encoded / sub-delims / ":" ) */
731           g_string_append_encoded (uri, decoded->userinfo, SUB_DELIM_CHARS ":");
732           g_string_append_c (uri, '@');
733         }
734       
735       g_string_append (uri, decoded->host);
736       
737       if (decoded->port != -1)
738         {
739           g_string_append_c (uri, ':');
740           g_string_append_printf (uri, "%d", decoded->port);
741         }
742     }
743
744   g_string_append_encoded (uri, decoded->path, SUB_DELIM_CHARS ":@/");
745   
746   if (decoded->query)
747     {
748       g_string_append_c (uri, '?');
749       g_string_append (uri, decoded->query);
750     }
751     
752   if (decoded->fragment)
753     {
754       g_string_append_c (uri, '#');
755       g_string_append (uri, decoded->fragment);
756     }
757
758   return g_string_free (uri, FALSE);
759 }