Coverage Report

Created: 2025-06-13 06:55

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