Coverage Report

Created: 2026-02-26 07:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gpac/src/utils/url.c
Line
Count
Source
1
/*
2
 *      GPAC - Multimedia Framework C SDK
3
 *
4
 *      Authors: Jean Le Feuvre
5
 *      Copyright (c) Telecom ParisTech 2000-2025
6
 *          All rights reserved
7
 *
8
 *  This file is part of GPAC / common tools sub-project
9
 *
10
 *  GPAC is free software; you can redistribute it and/or modify
11
 *  it under the terms of the GNU Lesser General Public License as published by
12
 *  the Free Software Foundation; either version 2, or (at your option)
13
 *  any later version.
14
 *
15
 *  GPAC is distributed in the hope that it will be useful,
16
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 *  GNU Lesser General Public License for more details.
19
 *
20
 *  You should have received a copy of the GNU Lesser General Public
21
 *  License along with this library; see the file COPYING.  If not, write to
22
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23
 *
24
 */
25
26
#include <gpac/network.h>
27
28
/* the length of the URL separator ("://" || "|//") */
29
#define URL_SEP_LENGTH  3
30
31
/* our supported protocol types */
32
enum
33
{
34
  /*absolute path to file*/
35
  GF_URL_TYPE_FILE_PATH = 0,
36
37
  /*absolute file:// URI */
38
  GF_URL_TYPE_FILE_URI,
39
40
  /*relative path or URL*/
41
  GF_URL_TYPE_RELATIVE ,
42
43
  /*any other absolute URI*/
44
  GF_URL_TYPE_ANY_URI,
45
46
  /*invalid input */
47
  GF_URL_TYPE_INVALID,
48
};
49
50
/*resolve the protocol type, for a std URL: http:// or ftp:// ...*/
51
static u32 URL_GetProtocolType(const char *pathName)
52
2.42k
{
53
2.42k
  char *begin;
54
2.42k
  if (!pathName) return GF_URL_TYPE_INVALID;
55
56
  /* URL with the data scheme are not relative to avoid concatenation */
57
2.42k
  if (!strnicmp(pathName, "data:", 5)) return GF_URL_TYPE_ANY_URI;
58
59
60
  //conditions for a file path to be absolute:
61
  // - on posix: absolute iif starts with '/'
62
  // - on windows: absolute if
63
  //  * starts with \ or / (current drive)
64
  //  * OR starts with <LETTER>: and then \ or /
65
  //  * OR starts with \\host\share\<path> [NOT HANDLED HERE]
66
2.42k
#ifndef WIN32
67
2.42k
  if (pathName[0] == '/')
68
#else
69
  if ( (pathName[0] == '/') || (pathName[0] == '\\')
70
    || ( strlen(pathName)>2 && pathName[1]==':'
71
      && ((pathName[2] == '/') || (pathName[2] == '\\'))
72
       )
73
     )
74
#endif
75
0
    return GF_URL_TYPE_FILE_PATH;
76
77
78
2.42k
  begin = strstr(pathName, "://");
79
2.42k
  if (!begin)
80
0
    return GF_URL_TYPE_RELATIVE;
81
82
2.42k
  else if (!strnicmp(pathName, "file://", 7))
83
0
    return (strlen(pathName)>7 ? GF_URL_TYPE_FILE_URI : GF_URL_TYPE_INVALID);
84
85
2.42k
  return GF_URL_TYPE_ANY_URI;
86
2.42k
}
87
88
/*gets protocol type*/
89
GF_EXPORT
90
Bool gf_url_is_local(const char *pathName)
91
2.42k
{
92
2.42k
  u32 mode = URL_GetProtocolType(pathName);
93
2.42k
  return (mode!=GF_URL_TYPE_INVALID && mode!=GF_URL_TYPE_ANY_URI) ? GF_TRUE : GF_FALSE;
94
2.42k
}
95
96
GF_EXPORT
97
Bool gf_url_is_relative(const char *pathName)
98
0
{
99
0
  if (!pathName) return GF_TRUE;
100
0
  u32 mode = URL_GetProtocolType(pathName);
101
0
  return (mode==GF_URL_TYPE_RELATIVE) ? GF_TRUE : GF_FALSE;
102
0
}
103
104
GF_EXPORT
105
char *gf_url_get_absolute_path(const char *pathName, const char *parentPath)
106
0
{
107
0
  char* sep;
108
0
  u32 parent_type;
109
0
  char* res = NULL;
110
111
0
  u32 prot_type = URL_GetProtocolType(pathName);
112
113
0
  switch (prot_type) {
114
115
    // if it's already absolute, do nothing
116
0
    case GF_URL_TYPE_FILE_PATH:
117
0
    case GF_URL_TYPE_ANY_URI:
118
0
      res = gf_strdup(pathName);
119
0
      break;
120
121
    // if file URI, remove the scheme part
122
0
    case GF_URL_TYPE_FILE_URI:
123
124
0
      pathName += 6; // keep a slash in case it's forgotten
125
126
      /* Windows file URIs SHOULD BE in the form "file:///C:\..."
127
       * Unix file URIs SHOULD BE in the form "file:///home..."
128
       * anything before the 3rd '/' is a hostname
129
      */
130
0
      sep = strchr(pathName+1, '/');
131
0
      if (sep) {
132
0
        pathName = sep;
133
134
        // dirty way to say if windows
135
        // consume the third / in that case
136
0
        if (strlen(pathName) > 2 && pathName[2]==':')
137
0
          pathName++;
138
0
      }
139
0
      res = gf_strdup(pathName);
140
0
      break;
141
142
    // if it's relative, it depends on the parent
143
0
    case GF_URL_TYPE_RELATIVE:
144
0
      parent_type = URL_GetProtocolType(parentPath);
145
146
      // in this case the parent is of no help to find an absolute path so we do nothing
147
0
      if (parent_type == GF_URL_TYPE_RELATIVE || parent_type == GF_URL_TYPE_INVALID )
148
0
        res = gf_strdup(pathName);
149
0
      else
150
0
        res = gf_url_concatenate(parentPath, pathName);
151
152
0
      break;
153
154
0
  }
155
156
0
  return res;
157
158
0
}
159
160
//set to 0 to disable max URL len - we don't use MAX_PATH as it can be small on some systems
161
0
#define MAX_URL_LEN   4096
162
static char *gf_url_concatenate_ex(const char *parentName, const char *pathName, Bool relative_to_parent)
163
0
{
164
0
  u32 pathSepCount, i, prot_type;
165
0
  Bool had_sep_count = GF_FALSE;
166
0
  char *outPath, *name, *rad, *tmp2;
167
0
  char *tmp = NULL;
168
169
0
  if (!pathName && !parentName) return NULL;
170
0
  if (!pathName) return gf_strdup(parentName);
171
0
  if (!parentName || !strlen(parentName)) return gf_strdup(pathName);
172
173
0
  if (!strncmp(pathName, "data:", 5)) return gf_strdup(pathName);
174
0
  if (!strcmp(pathName, "stderr")) return gf_strdup(pathName);
175
0
  if (!strcmp(pathName, "stdin")) return gf_strdup(pathName);
176
0
  if (!strcmp(pathName, "stdout")) return gf_strdup(pathName);
177
0
  if (!strncmp(parentName, "gmem://", 7)) return NULL;
178
0
  if (!strncmp(parentName, "gfio://", 7)) {
179
0
    GF_Err e;
180
0
    GF_FileIO *gfio = gf_fileio_from_url(parentName);
181
0
    GF_FileIO *gfio_new = gf_fileio_open_url(gfio, pathName, "url", &e);
182
0
    if (!gfio_new)
183
0
      return NULL;
184
0
    return gf_strdup( gf_fileio_url(gfio_new) );
185
0
  }
186
187
0
#if MAX_URL_LEN
188
0
  if ((strlen(parentName) > MAX_URL_LEN) || (strlen(pathName) > MAX_URL_LEN)) {
189
0
    GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("URL too long for concatenation: \n%s\n", pathName));
190
0
    return NULL;
191
0
  }
192
0
#endif
193
194
0
  while (!strncmp(parentName, "./.", 3) || !strncmp(parentName, ".\\.", 3)) {
195
0
    parentName += 2;
196
0
  }
197
0
  while (!strncmp(pathName, "./.", 3) || !strncmp(pathName, ".\\.", 3)) {
198
0
    pathName += 2;
199
0
  }
200
  //empty path
201
0
  if (pathName[0]=='#') {
202
0
    outPath = gf_strdup(parentName);
203
0
    gf_dynstrcat(&outPath, pathName, NULL);
204
0
    goto check_spaces;
205
0
  }
206
207
0
  prot_type = URL_GetProtocolType(pathName);
208
0
  if (prot_type != GF_URL_TYPE_RELATIVE) {
209
0
    char *sep = NULL;
210
0
    if (pathName[0]=='/') sep = strstr(parentName, "://");
211
0
    if (sep) sep = strchr(sep+3, '/');
212
0
    if (sep) {
213
0
      u32 len;
214
0
      sep[0] = 0;
215
0
      len = (u32) strlen(parentName);
216
0
      outPath = (char*)gf_malloc(sizeof(char)*(len+1+strlen(pathName)));
217
0
      strcpy(outPath, parentName);
218
0
      strcat(outPath, pathName);
219
0
      sep[0] = '/';
220
0
    } else {
221
0
      outPath = gf_strdup(pathName);
222
0
    }
223
0
    goto check_spaces;
224
0
  }
225
226
  /*old upnp addressing a la Platinum*/
227
0
  rad = strstr(parentName, "%3fpath=");
228
0
  if (!rad) rad = strstr(parentName, "%3Fpath=");
229
0
  if (!rad) rad = strstr(parentName, "?path=");
230
0
  if (rad) {
231
0
    char *the_path;
232
0
    rad = strchr(rad, '=');
233
0
    rad[0] = 0;
234
0
    the_path = gf_strdup(rad+1);
235
0
    i=0;
236
0
    while (1) {
237
0
      if (the_path[i]==0) break;
238
0
      if (!strnicmp(the_path+i, "%5c", 3) || !strnicmp(the_path+i, "%2f", 3) ) {
239
0
        the_path[i] = '/';
240
0
        memmove(the_path+i+1, the_path+i+3, strlen(the_path+i+3)+1);
241
0
      }
242
0
      else if (!strnicmp(the_path+i, "%05c", 4) || !strnicmp(the_path+i, "%02f", 4) ) {
243
0
        the_path[i] = '/';
244
0
        memmove(the_path+i+1, the_path+i+4, strlen(the_path+i+4)+1);
245
0
      }
246
0
      i++;
247
0
    }
248
0
    name = gf_url_concatenate(the_path, pathName);
249
0
    outPath = (char*)gf_malloc(strlen(parentName) + strlen(name) + 2);
250
0
    sprintf(outPath, "%s=%s", parentName, name);
251
0
    rad[0] = '=';
252
0
    gf_free(name);
253
0
    gf_free(the_path);
254
0
    return outPath;
255
0
  }
256
257
  /*rewrite path to use / not % encoding*/
258
0
  rad = strchr(parentName, '%');
259
0
  if (rad && (!strnicmp(rad, "%5c", 3) || !strnicmp(rad, "%05c", 4) || !strnicmp(rad, "%2f", 3)  || !strnicmp(rad, "%02f", 4))) {
260
0
    char *the_path = gf_strdup(parentName);
261
0
    i=0;
262
0
    while (1) {
263
0
      if (the_path[i]==0) break;
264
0
      if (!strnicmp(the_path+i, "%5c", 3) || !strnicmp(the_path+i, "%2f", 3) ) {
265
0
        the_path[i] = '/';
266
0
        memmove(the_path+i+1, the_path+i+3, strlen(the_path+i+3)+1);
267
0
      }
268
0
      else if (!strnicmp(the_path+i, "%05c", 4) || !strnicmp(the_path+i, "%02f", 4) ) {
269
0
        the_path[i] = '/';
270
0
        memmove(the_path+i+1, the_path+i+4, strlen(the_path+i+4)+1);
271
0
      }
272
0
      i++;
273
0
    }
274
0
    name = gf_url_concatenate(the_path, pathName);
275
0
    gf_free(the_path);
276
0
    return name;
277
0
  }
278
279
280
0
  pathSepCount = 0;
281
0
  name = NULL;
282
0
  if (pathName[0] == '.') {
283
0
    if (!strcmp(pathName, "..")) {
284
0
      pathSepCount = 1;
285
0
      name = "";
286
0
    }
287
0
    if (!strcmp(pathName, "./")) {
288
0
      pathSepCount = 0;
289
0
      name = "";
290
0
    }
291
0
    for (i = 0; i< strlen(pathName) - 2; i++) {
292
      /*current dir*/
293
0
      if ( (pathName[i] == '.')
294
0
              && ( (pathName[i+1] == GF_PATH_SEPARATOR) || (pathName[i+1] == '/') ) ) {
295
0
        i++;
296
0
        continue;
297
0
      }
298
      /*parent dir*/
299
0
      if ( (pathName[i] == '.') && (pathName[i+1] == '.')
300
0
              && ( (pathName[i+2] == GF_PATH_SEPARATOR) || (pathName[i+2] == '/') )
301
0
         ) {
302
0
        pathSepCount ++;
303
0
        i+=2;
304
0
        name = (char *) &pathName[i+1];
305
0
      } else {
306
0
        name = (char *) &pathName[i];
307
0
        break;
308
0
      }
309
0
    }
310
0
  }
311
0
  if (!name) name = (char *) pathName;
312
313
0
  gf_dynstrcat(&tmp, parentName, NULL);
314
0
  if (!tmp) return NULL;
315
316
0
  while (strchr(" \r\n\t", tmp[strlen(tmp)-1])) {
317
0
    tmp[strlen(tmp)-1] = 0;
318
0
  }
319
  //strip query part or fragment part
320
0
  rad = strchr(tmp, '?');
321
0
  if (rad) rad[0] = 0;
322
0
  tmp2 = strrchr(tmp, '/');
323
0
  if (!tmp2) tmp2 = strrchr(tmp, '\\');
324
0
  if (!tmp2) tmp2 = tmp;
325
0
  rad = strchr(tmp2, '#');
326
0
  if (rad) rad[0] = 0;
327
328
  //rule out case where we have the same path
329
0
  if (relative_to_parent) {
330
0
    u32 l1=0, l2=0;
331
0
    rad = strrchr(tmp, '/');
332
0
    if (rad) l1 = (u32) (rad - tmp);
333
0
    rad = strrchr(pathName, '/');
334
0
    if (rad) l2 = (u32) (rad - pathName);
335
0
    if (l1 && l2 && (l1==l2) && !strncmp(pathName, parentName, l1)) {
336
0
      outPath = gf_strdup(pathName + l1 + 1);
337
0
      goto check_spaces;
338
0
    }
339
0
    if (!l1 && !l2) {
340
0
      outPath = gf_strdup(pathName);
341
0
      goto check_spaces;
342
0
    }
343
0
  }
344
345
0
  if (pathSepCount)
346
0
    had_sep_count = GF_TRUE;
347
  /*remove the last */
348
0
  for (i = (u32) strlen(tmp); i > 0; i--) {
349
    //break our path at each separator
350
0
    if ((tmp[i-1] == GF_PATH_SEPARATOR) || (tmp[i-1] == '/'))  {
351
0
      tmp[i-1] = 0;
352
0
      if (strcmp(tmp, ".")) {
353
0
        if (!pathSepCount) break;
354
0
        pathSepCount--;
355
0
      }
356
0
    }
357
0
  }
358
  //if i==0, the parent path was relative, just return the pathName
359
0
  if (!i) {
360
0
    tmp[i] = 0;
361
0
    while (pathSepCount) {
362
0
      gf_dynstrcat(&tmp, "../", NULL);
363
0
      pathSepCount--;
364
0
    }
365
0
  }
366
  //path is relative to current dir
367
0
  else if (!relative_to_parent && (pathName[0]=='.') && ((pathName[1]=='/') || (pathName[1]=='\\') ) ) {
368
0
    gf_dynstrcat(&tmp, "/", NULL);
369
0
  }
370
  //parent is relative to current dir
371
0
  else if (!had_sep_count && (pathName[0]=='.') && (tmp[0]=='.') && ((tmp[1]=='/') || (tmp[1]=='\\') ) ) {
372
0
    u32 nb_path_sep=0;
373
0
    u32 len = (u32) strlen(tmp);
374
375
0
    for (i=0; i<len; i++) {
376
0
      if ((tmp[i]=='/') || (tmp[i]=='\\') )
377
0
        nb_path_sep++;
378
0
    }
379
380
0
    const char *p_src = pathName+2;
381
0
    const char *p_tmp = tmp+2;
382
    //strip if same beginning
383
0
    while (1) {
384
0
      char *sep = strchr(p_src, '/');
385
0
      if (!sep) sep = strchr(p_src, '\\');
386
0
      if (!sep) break;
387
0
      u32 sep_len = (u32) (sep - p_src);
388
0
      if (!sep_len) break;
389
0
      if (sep_len > len) break;
390
0
      if (strncmp(p_tmp, p_src, sep_len)) break;
391
0
      if ((p_tmp[sep_len] != '/') && (p_tmp[sep_len] != '\\')) break;
392
0
      p_src += sep_len+1;
393
0
      name += sep_len+1;
394
0
      p_tmp += sep_len+1;
395
0
      len -= sep_len;
396
0
      nb_path_sep--;
397
0
    }
398
0
    if (tmp) gf_free(tmp);
399
0
    tmp=NULL;
400
0
    gf_dynstrcat(&tmp, "", NULL);
401
0
    while (nb_path_sep--)
402
0
      gf_dynstrcat(&tmp, "../", NULL);
403
0
  } else {
404
0
    gf_dynstrcat(&tmp, "/", NULL);
405
0
  }
406
407
0
  i = (u32) strlen(tmp);
408
0
  outPath = (char *) gf_malloc(i + strlen(name) + 1);
409
0
  sprintf(outPath, "%s%s", tmp, name);
410
411
  /*cleanup paths sep for win32*/
412
0
  for (i = 0; i<strlen(outPath); i++)
413
0
    if (outPath[i]=='\\') outPath[i] = '/';
414
415
0
check_spaces:
416
0
  i=0;
417
0
  while (outPath[i]) {
418
0
    if (outPath[i] == '?') break;
419
420
0
    if (outPath[i] != '%') {
421
0
      i++;
422
0
      continue;
423
0
    }
424
0
    if (!strnicmp(outPath+i, "%3f", 3)) break;
425
0
    if (!strnicmp(outPath+i, "%20", 3)) {
426
0
      outPath[i]=' ';
427
0
      memmove(outPath + i+1, outPath+i+3, strlen(outPath+i)-2);
428
0
    }
429
0
    i++;
430
0
  }
431
0
  if (tmp) gf_free(tmp);
432
0
  return outPath;
433
0
}
434
GF_EXPORT
435
char *gf_url_concatenate(const char *parentName, const char *pathName)
436
0
{
437
0
  return gf_url_concatenate_ex(parentName, pathName, GF_FALSE);
438
0
}
439
GF_EXPORT
440
char *gf_url_concatenate_parent(const char *parentName, const char *pathName)
441
0
{
442
0
  return gf_url_concatenate_ex(parentName, pathName, GF_TRUE);
443
0
}
444
445
GF_EXPORT
446
void gf_url_to_fs_path(char *sURL)
447
0
{
448
0
  if (!strnicmp(sURL, "file://", 7)) {
449
    /*file:///C:\ scheme*/
450
0
    if ((strlen(sURL)>=10) && (sURL[7]=='/') && (sURL[9]==':')) {
451
0
      memmove(sURL, sURL+8, strlen(sURL)-7);
452
0
    } else {
453
0
      memmove(sURL, sURL+7, strlen(sURL)-6);
454
0
    }
455
0
  }
456
457
0
  while (1) {
458
0
    char *sep = strstr(sURL, "%20");
459
0
    if (!sep) break;
460
0
    sep[0] = ' ';
461
0
    memmove(sep+1, sep+3, strlen(sep)-2);
462
0
  }
463
0
}
464
465
//TODO handle reserved characters
466
const char *pce_special = " %";
467
const char *pce_encoded = "0123456789ABCDEFabcdef";
468
469
char *gf_url_percent_encode(const char *path)
470
0
{
471
0
  char *outpath;
472
0
  u32 i, count, len;
473
0
  if (!path) return NULL;
474
475
0
  len = (u32) strlen(path);
476
0
  count = 0;
477
0
  for (i=0; i<len; i++) {
478
0
    u8 c = path[i];
479
0
    if (strchr(pce_special, c) != NULL) {
480
0
      if (c==' ') count+=2;
481
0
      else if ((i+2<len) && ((strchr(pce_encoded, path[i+1]) == NULL) || (strchr(pce_encoded, path[i+2]) == NULL))) {
482
0
        count+=2;
483
0
      }
484
0
    } else if (c>>7) {
485
0
      count+=2;
486
0
    }
487
0
  }
488
0
  if (!count) return gf_strdup(path);
489
0
  outpath = (char*)gf_malloc(sizeof(char) * (len + count + 1));
490
0
  strcpy(outpath, path);
491
492
0
  count = 0;
493
0
  for (i=0; i<len; i++) {
494
0
    Bool do_enc = GF_FALSE;
495
0
    u8 c = path[i];
496
497
0
    if (strchr(pce_special, c) != NULL) {
498
0
      if (c==' ')
499
0
        do_enc = GF_TRUE;
500
0
      else if ((i+2<len) && ((strchr(pce_encoded, path[i+1]) == NULL) || (strchr(pce_encoded, path[i+2]) == NULL))) {
501
0
        do_enc = GF_TRUE;
502
0
      }
503
0
    } else if (c>>7) {
504
0
      do_enc = GF_TRUE;
505
0
    }
506
507
0
    if (do_enc) {
508
0
      char szChar[3];
509
0
      sprintf(szChar, "%02X", c);
510
0
      outpath[i+count] = '%';
511
0
      outpath[i+count+1] = szChar[0];
512
0
      outpath[i+count+2] = szChar[1];
513
0
      count+=2;
514
0
    } else {
515
0
      outpath[i+count] = c;
516
0
    }
517
0
  }
518
0
  outpath[i+count] = 0;
519
0
  return outpath;
520
0
}
521
522
char *gf_url_percent_decode(const char *path)
523
0
{
524
0
  char *outpath;
525
0
  u32 i, count, len;
526
0
  if (!path) return NULL;
527
528
0
  len = (u32) strlen(path);
529
0
  count = 0;
530
0
  for (i=0; i<len; i++) {
531
0
    u8 c = path[i];
532
0
    if (c=='%') {
533
0
      i+= 2;
534
0
    }
535
0
    count++;
536
0
  }
537
0
  if (count==len) return gf_strdup(path);
538
0
  outpath = (char*)gf_malloc(sizeof(char) * (count + 1));
539
540
0
  u32 d_idx=0;
541
0
  for (i=0; i<len; i++) {
542
0
    u8 c = path[i];
543
0
    if (c=='%') {
544
0
      u32 res;
545
0
      char szChar[3];
546
0
      szChar[0] = path[i+1];
547
0
      szChar[1] = path[i+2];
548
0
      szChar[2] = 0;
549
0
      sscanf(szChar, "%02X", &res);
550
0
      i += 2;
551
0
      outpath[d_idx] = (char) res;
552
0
    } else {
553
0
      outpath[d_idx] = c;
554
0
    }
555
0
    d_idx++;
556
0
  }
557
0
  outpath[d_idx] = 0;
558
0
  return outpath;
559
0
}
560
561
GF_EXPORT
562
const char *gf_url_get_resource_name(const char *sURL)
563
0
{
564
0
  char *sep;
565
0
  if (!sURL) return NULL;
566
0
  sep = strrchr(sURL, '/');
567
0
  if (!sep) sep = strrchr(sURL, '\\');
568
0
  if (sep) return sep+1;
569
0
  return sURL;
570
0
}
571
572
GF_EXPORT
573
const char *gf_url_get_path(const char *sURL)
574
0
{
575
0
  char *sep = strstr(sURL, "://");
576
0
  if (!sep) return sURL;
577
0
  sep = strchr(sep + 3, '/');
578
0
  if (sep) return sep;
579
0
  return NULL;
580
0
}
581
582
//exported for python bindings
583
GF_EXPORT
584
void gf_url_free(char *sURL)
585
0
{
586
0
  if (sURL) gf_free(sURL);
587
0
}
588
589
#if 0 //unused
590
Bool gf_url_remove_last_delimiter(const char *sURL, char *res_path)
591
{
592
  strcpy(res_path, sURL);
593
  if (sURL[strlen(sURL)-1] == GF_PATH_SEPARATOR) {
594
    res_path[strlen(sURL)-1] = 0;
595
    return GF_TRUE;
596
  }
597
598
  return GF_FALSE;
599
}
600
601
const char* gf_url_get_resource_extension(const char *sURL) {
602
  const char *dot = strrchr(sURL, '.');
603
  if(!dot || dot == sURL) return "";
604
  return dot + 1;
605
}
606
#endif //unused