blob: 63a18a740a0fad680bb2ded60b1a2c4ae19557f4 [file] [edit]
/*
* GPAC - Multimedia Framework C SDK
*
* Authors: Jean Le Feuvre
* Copyright (c) Telecom ParisTech 2000-2026
* All rights reserved
*
* This file is part of GPAC / common tools sub-project
*
* GPAC is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* GPAC is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <gpac/network.h>
/* the length of the URL separator ("://" || "|//") */
#define URL_SEP_LENGTH 3
/* our supported protocol types */
enum
{
/*absolute path to file*/
GF_URL_TYPE_FILE_PATH = 0,
/*absolute file:// URI */
GF_URL_TYPE_FILE_URI,
/*relative path or URL*/
GF_URL_TYPE_RELATIVE ,
/*any other absolute URI*/
GF_URL_TYPE_ANY_URI,
/*invalid input */
GF_URL_TYPE_INVALID,
};
/*resolve the protocol type, for a std URL: http:// or ftp:// ...*/
static u32 URL_GetProtocolType(const char *pathName)
{
char *begin;
if (!pathName) return GF_URL_TYPE_INVALID;
/* URL with the data scheme are not relative to avoid concatenation */
if (!strnicmp(pathName, "data:", 5)) return GF_URL_TYPE_ANY_URI;
//conditions for a file path to be absolute:
// - on posix: absolute iif starts with '/'
// - on windows: absolute if
// * starts with \ or / (current drive)
// * OR starts with <LETTER>: and then \ or /
// * OR starts with \\host\share\<path> [NOT HANDLED HERE]
#ifndef WIN32
if (pathName[0] == '/')
#else
if ( (pathName[0] == '/') || (pathName[0] == '\\')
|| ( strlen(pathName)>2 && pathName[1]==':'
&& ((pathName[2] == '/') || (pathName[2] == '\\'))
)
)
#endif
return GF_URL_TYPE_FILE_PATH;
begin = strstr(pathName, "://");
if (!begin)
return GF_URL_TYPE_RELATIVE;
else if (!strnicmp(pathName, "file://", 7))
return (strlen(pathName)>7 ? GF_URL_TYPE_FILE_URI : GF_URL_TYPE_INVALID);
return GF_URL_TYPE_ANY_URI;
}
/*gets protocol type*/
GF_EXPORT
Bool gf_url_is_local(const char *pathName)
{
u32 mode = URL_GetProtocolType(pathName);
return (mode!=GF_URL_TYPE_INVALID && mode!=GF_URL_TYPE_ANY_URI) ? GF_TRUE : GF_FALSE;
}
GF_EXPORT
Bool gf_url_is_relative(const char *pathName)
{
if (!pathName) return GF_TRUE;
u32 mode = URL_GetProtocolType(pathName);
return (mode==GF_URL_TYPE_RELATIVE) ? GF_TRUE : GF_FALSE;
}
GF_EXPORT
char *gf_url_get_absolute_path(const char *pathName, const char *parentPath)
{
char* sep;
u32 parent_type;
char* res = NULL;
u32 prot_type = URL_GetProtocolType(pathName);
switch (prot_type) {
// if it's already absolute, do nothing
case GF_URL_TYPE_FILE_PATH:
case GF_URL_TYPE_ANY_URI:
res = gf_strdup(pathName);
break;
// if file URI, remove the scheme part
case GF_URL_TYPE_FILE_URI:
pathName += 6; // keep a slash in case it's forgotten
/* Windows file URIs SHOULD BE in the form "file:///C:\..."
* Unix file URIs SHOULD BE in the form "file:///home..."
* anything before the 3rd '/' is a hostname
*/
sep = strchr(pathName+1, '/');
if (sep) {
pathName = sep;
// dirty way to say if windows
// consume the third / in that case
if (strlen(pathName) > 2 && pathName[2]==':')
pathName++;
}
res = gf_strdup(pathName);
break;
// if it's relative, it depends on the parent
case GF_URL_TYPE_RELATIVE:
parent_type = URL_GetProtocolType(parentPath);
// in this case the parent is of no help to find an absolute path so we do nothing
if (parent_type == GF_URL_TYPE_RELATIVE || parent_type == GF_URL_TYPE_INVALID )
res = gf_strdup(pathName);
else
res = gf_url_concatenate(parentPath, pathName);
break;
}
return res;
}
//set to 0 to disable max URL len - we don't use MAX_PATH as it can be small on some systems
#define MAX_URL_LEN 4096
static char *gf_url_concatenate_ex(const char *parentName, const char *pathName, Bool relative_to_parent)
{
u32 pathSepCount, i, prot_type;
Bool had_sep_count = GF_FALSE;
char *outPath, *name, *rad, *tmp2;
char *tmp = NULL;
if (!pathName && !parentName) return NULL;
if (!pathName) return gf_strdup(parentName);
if (!parentName || !strlen(parentName)) return gf_strdup(pathName);
if (!strncmp(pathName, "data:", 5)) return gf_strdup(pathName);
if (!strcmp(pathName, "stderr")) return gf_strdup(pathName);
if (!strcmp(pathName, "stdin")) return gf_strdup(pathName);
if (!strcmp(pathName, "stdout")) return gf_strdup(pathName);
if (!strncmp(parentName, "gmem://", 7)) return NULL;
if (!strncmp(parentName, "gfio://", 7)) {
GF_Err e;
GF_FileIO *gfio = gf_fileio_from_url(parentName);
GF_FileIO *gfio_new = gf_fileio_open_url(gfio, pathName, "url", &e);
if (!gfio_new)
return NULL;
return gf_strdup( gf_fileio_url(gfio_new) );
}
#if MAX_URL_LEN
if ((strlen(parentName) > MAX_URL_LEN) || (strlen(pathName) > MAX_URL_LEN)) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("URL too long for concatenation: \n%s\n", pathName));
return NULL;
}
#endif
while (!strncmp(parentName, "./.", 3) || !strncmp(parentName, ".\\.", 3)) {
parentName += 2;
}
while (!strncmp(pathName, "./.", 3) || !strncmp(pathName, ".\\.", 3)) {
pathName += 2;
}
//empty path
if (pathName[0]=='#') {
outPath = gf_strdup(parentName);
gf_dynstrcat(&outPath, pathName, NULL);
goto check_spaces;
}
prot_type = URL_GetProtocolType(pathName);
if (prot_type != GF_URL_TYPE_RELATIVE) {
char *sep = NULL;
if (pathName[0]=='/') sep = strstr(parentName, "://");
if (sep) sep = strchr(sep+3, '/');
if (sep) {
sep[0] = 0;
outPath = gf_strdup(parentName);
gf_dynstrcat(&outPath, pathName, NULL);
sep[0] = '/';
} else {
outPath = gf_strdup(pathName);
}
goto check_spaces;
}
/*old upnp addressing a la Platinum*/
rad = strstr(parentName, "%3fpath=");
if (!rad) rad = strstr(parentName, "%3Fpath=");
if (!rad) rad = strstr(parentName, "?path=");
if (rad) {
char *the_path;
rad = strchr(rad, '=');
rad[0] = 0;
the_path = gf_strdup(rad+1);
i=0;
while (1) {
if (the_path[i]==0) break;
if (!strnicmp(the_path+i, "%5c", 3) || !strnicmp(the_path+i, "%2f", 3) ) {
the_path[i] = '/';
memmove(the_path+i+1, the_path+i+3, strlen(the_path+i+3)+1);
}
else if (!strnicmp(the_path+i, "%05c", 4) || !strnicmp(the_path+i, "%02f", 4) ) {
the_path[i] = '/';
memmove(the_path+i+1, the_path+i+4, strlen(the_path+i+4)+1);
}
i++;
}
name = gf_url_concatenate(the_path, pathName);
outPath = (char*)gf_malloc(strlen(parentName) + strlen(name) + 2);
sprintf(outPath, "%s=%s", parentName, name);
rad[0] = '=';
gf_free(name);
gf_free(the_path);
return outPath;
}
/*rewrite path to use / not % encoding*/
rad = strchr(parentName, '%');
if (rad && (!strnicmp(rad, "%5c", 3) || !strnicmp(rad, "%05c", 4) || !strnicmp(rad, "%2f", 3) || !strnicmp(rad, "%02f", 4))) {
char *the_path = gf_strdup(parentName);
i=0;
while (1) {
if (the_path[i]==0) break;
if (!strnicmp(the_path+i, "%5c", 3) || !strnicmp(the_path+i, "%2f", 3) ) {
the_path[i] = '/';
memmove(the_path+i+1, the_path+i+3, strlen(the_path+i+3)+1);
}
else if (!strnicmp(the_path+i, "%05c", 4) || !strnicmp(the_path+i, "%02f", 4) ) {
the_path[i] = '/';
memmove(the_path+i+1, the_path+i+4, strlen(the_path+i+4)+1);
}
i++;
}
name = gf_url_concatenate(the_path, pathName);
gf_free(the_path);
return name;
}
pathSepCount = 0;
name = NULL;
if (pathName[0] == '.') {
if (!strcmp(pathName, "..")) {
pathSepCount = 1;
name = "";
}
if (!strcmp(pathName, "./")) {
pathSepCount = 0;
name = "";
}
for (i = 0; i< strlen(pathName) - 2; i++) {
/*current dir*/
if ( (pathName[i] == '.')
&& ( (pathName[i+1] == GF_PATH_SEPARATOR) || (pathName[i+1] == '/') ) ) {
i++;
continue;
}
/*parent dir*/
if ( (pathName[i] == '.') && (pathName[i+1] == '.')
&& ( (pathName[i+2] == GF_PATH_SEPARATOR) || (pathName[i+2] == '/') )
) {
pathSepCount ++;
i+=2;
name = (char *) &pathName[i+1];
} else {
name = (char *) &pathName[i];
break;
}
}
}
if (!name) name = (char *) pathName;
gf_dynstrcat(&tmp, parentName, NULL);
if (!tmp) return NULL;
while (strlen(tmp) && strchr(" \r\n\t", tmp[strlen(tmp)-1])) {
tmp[strlen(tmp)-1] = 0;
}
//strip query part or fragment part
rad = strchr(tmp, '?');
if (rad) rad[0] = 0;
tmp2 = strrchr(tmp, '/');
if (!tmp2) tmp2 = strrchr(tmp, '\\');
if (!tmp2) tmp2 = tmp;
rad = strchr(tmp2, '#');
if (rad) rad[0] = 0;
//rule out case where we have the same path
if (relative_to_parent) {
u32 l1=0, l2=0;
rad = strrchr(tmp, '/');
if (rad) l1 = (u32) (rad - tmp);
rad = strrchr(pathName, '/');
if (rad) l2 = (u32) (rad - pathName);
if (l1 && l2 && (l1==l2) && !strncmp(pathName, parentName, l1)) {
outPath = gf_strdup(pathName + l1 + 1);
goto check_spaces;
}
if (!l1 && !l2) {
outPath = gf_strdup(pathName);
goto check_spaces;
}
}
if (pathSepCount)
had_sep_count = GF_TRUE;
/*remove the last */
for (i = (u32) strlen(tmp); i > 0; i--) {
//break our path at each separator
if ((tmp[i-1] == GF_PATH_SEPARATOR) || (tmp[i-1] == '/')) {
tmp[i-1] = 0;
if (strcmp(tmp, ".")) {
if (!pathSepCount) break;
pathSepCount--;
}
}
}
//if i==0, the parent path was relative, just return the pathName
if (!i) {
tmp[i] = 0;
while (pathSepCount) {
gf_dynstrcat(&tmp, "../", NULL);
pathSepCount--;
}
}
//path is relative to current dir
else if (!relative_to_parent && (pathName[0]=='.') && ((pathName[1]=='/') || (pathName[1]=='\\') ) ) {
gf_dynstrcat(&tmp, "/", NULL);
}
//parent is relative to current dir
else if (!had_sep_count && (pathName[0]=='.') && (tmp[0]=='.') && ((tmp[1]=='/') || (tmp[1]=='\\') ) ) {
u32 nb_path_sep=0;
u32 len = (u32) strlen(tmp);
for (i=0; i<len; i++) {
if ((tmp[i]=='/') || (tmp[i]=='\\') )
nb_path_sep++;
}
const char *p_src = pathName+2;
const char *p_tmp = tmp+2;
//strip if same beginning
while (1) {
char *sep = strchr(p_src, '/');
if (!sep) sep = strchr(p_src, '\\');
if (!sep) break;
u32 sep_len = (u32) (sep - p_src);
if (!sep_len) break;
if (sep_len > len) break;
if (strncmp(p_tmp, p_src, sep_len)) break;
if ((p_tmp[sep_len] != '/') && (p_tmp[sep_len] != '\\')) break;
p_src += sep_len+1;
name += sep_len+1;
p_tmp += sep_len+1;
len -= sep_len;
nb_path_sep--;
}
if (tmp) gf_free(tmp);
tmp=NULL;
gf_dynstrcat(&tmp, "", NULL);
while (nb_path_sep--)
gf_dynstrcat(&tmp, "../", NULL);
} else {
gf_dynstrcat(&tmp, "/", NULL);
}
i = (u32) strlen(tmp);
outPath = (char *) gf_malloc(i + strlen(name) + 1);
sprintf(outPath, "%s%s", tmp, name);
/*cleanup paths sep for win32*/
for (i = 0; i<strlen(outPath); i++)
if (outPath[i]=='\\') outPath[i] = '/';
check_spaces:
i=0;
while (outPath[i]) {
if (outPath[i] == '?') break;
if (outPath[i] != '%') {
i++;
continue;
}
if (!strnicmp(outPath+i, "%3f", 3)) break;
if (!strnicmp(outPath+i, "%20", 3)) {
outPath[i]=' ';
memmove(outPath + i+1, outPath+i+3, strlen(outPath+i)-2);
}
i++;
}
if (tmp) gf_free(tmp);
return outPath;
}
GF_EXPORT
char *gf_url_concatenate(const char *parentName, const char *pathName)
{
return gf_url_concatenate_ex(parentName, pathName, GF_FALSE);
}
GF_EXPORT
char *gf_url_concatenate_parent(const char *parentName, const char *pathName)
{
return gf_url_concatenate_ex(parentName, pathName, GF_TRUE);
}
GF_EXPORT
void gf_url_to_fs_path(char *sURL)
{
if (!strnicmp(sURL, "file://", 7)) {
/*file:///C:\ scheme*/
if ((strlen(sURL)>=10) && (sURL[7]=='/') && (sURL[9]==':')) {
memmove(sURL, sURL+8, strlen(sURL)-7);
} else {
memmove(sURL, sURL+7, strlen(sURL)-6);
}
}
while (1) {
char *sep = strstr(sURL, "%20");
if (!sep) break;
sep[0] = ' ';
memmove(sep+1, sep+3, strlen(sep)-2);
}
}
//TODO handle reserved characters
const char *pce_special = " %";
const char *pce_encoded = "0123456789ABCDEFabcdef";
char *gf_url_percent_encode(const char *path)
{
char *outpath;
u32 i, count, len;
if (!path) return NULL;
len = (u32) strlen(path);
count = 0;
for (i=0; i<len; i++) {
u8 c = path[i];
if (strchr(pce_special, c) != NULL) {
if (c==' ') count+=2;
else if ((i+2<len) && ((strchr(pce_encoded, path[i+1]) == NULL) || (strchr(pce_encoded, path[i+2]) == NULL))) {
count+=2;
}
} else if (c>>7) {
count+=2;
}
}
if (!count) return gf_strdup(path);
outpath = (char*)gf_malloc(sizeof(char) * (len + count + 1));
memcpy(outpath, path, len+1);
count = 0;
for (i=0; i<len; i++) {
Bool do_enc = GF_FALSE;
u8 c = path[i];
if (strchr(pce_special, c) != NULL) {
if (c==' ')
do_enc = GF_TRUE;
else if ((i+2<len) && ((strchr(pce_encoded, path[i+1]) == NULL) || (strchr(pce_encoded, path[i+2]) == NULL))) {
do_enc = GF_TRUE;
}
} else if (c>>7) {
do_enc = GF_TRUE;
}
if (do_enc) {
char szChar[3];
sprintf(szChar, "%02X", c);
outpath[i+count] = '%';
outpath[i+count+1] = szChar[0];
outpath[i+count+2] = szChar[1];
count+=2;
} else {
outpath[i+count] = c;
}
}
outpath[i+count] = 0;
return outpath;
}
char *gf_url_percent_decode(const char *path)
{
char *outpath;
u32 i, count, len;
if (!path) return NULL;
len = (u32) strlen(path);
count = 0;
for (i=0; i<len; i++) {
u8 c = path[i];
if (c=='%') {
i+= 2;
}
count++;
}
if (count==len) return gf_strdup(path);
outpath = (char*)gf_malloc(sizeof(char) * (count + 1));
u32 d_idx=0;
for (i=0; i<len; i++) {
u8 c = path[i];
if (c=='%') {
u32 res;
char szChar[3];
szChar[0] = path[i+1];
szChar[1] = path[i+2];
szChar[2] = 0;
sscanf(szChar, "%02X", &res);
i += 2;
outpath[d_idx] = (char) res;
} else {
outpath[d_idx] = c;
}
d_idx++;
}
outpath[d_idx] = 0;
return outpath;
}
GF_EXPORT
const char *gf_url_get_resource_name(const char *sURL)
{
char *sep;
if (!sURL) return NULL;
sep = strrchr(sURL, '/');
if (!sep) sep = strrchr(sURL, '\\');
if (sep) return sep+1;
return sURL;
}
GF_EXPORT
const char *gf_url_get_path(const char *sURL)
{
char *sep = strstr(sURL, "://");
if (!sep) return sURL;
sep = strchr(sep + 3, '/');
if (sep) return sep;
return NULL;
}
//exported for python bindings
GF_EXPORT
void gf_url_free(char *sURL)
{
if (sURL) gf_free(sURL);
}
#if 0 //unused
Bool gf_url_remove_last_delimiter(const char *sURL, char *res_path)
{
gf_strcpy(res_path, sURL);
if (sURL[strlen(sURL)-1] == GF_PATH_SEPARATOR) {
res_path[strlen(sURL)-1] = 0;
return GF_TRUE;
}
return GF_FALSE;
}
const char* gf_url_get_resource_extension(const char *sURL) {
const char *dot = strrchr(sURL, '.');
if(!dot || dot == sURL) return "";
return dot + 1;
}
#endif //unused