Coverage Report

Created: 2025-07-01 07:09

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