#include <string.h>
#include <assert.h>
#include <ctype.h>
#include <apr_uri.h>
#include "svn_private_config.h"
#include "svn_string.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "private_uri.h"
#define SVN_EMPTY_PATH ""
#define SVN_PATH_IS_EMPTY(s) ((s)[0] == '\0')
#define SVN_PATH_IS_PLATFORM_EMPTY(s,n) ((n) == 1 && (s)[0] == '.')
typedef enum {
type_uri,
type_dirent
} path_type_t;
static const char *
internal_style(path_type_t type, const char *path, apr_pool_t *pool)
{
#if '/' != SVN_PATH_LOCAL_SEPARATOR
{
char *p = apr_pstrdup(pool, path);
path = p;
for (; *p != '\0'; ++p)
if (*p == SVN_PATH_LOCAL_SEPARATOR)
*p = '/';
}
#endif
return type == type_uri ? svn_uri_canonicalize(path, pool)
: svn_dirent_canonicalize(path, pool);
}
static const char *
local_style(path_type_t type, const char *path, apr_pool_t *pool)
{
path = type == type_uri ? svn_uri_canonicalize(path, pool)
: svn_dirent_canonicalize(path, pool);
if (SVN_PATH_IS_EMPTY(path))
return ".";
if (type == type_uri && svn_path_is_url(path))
return apr_pstrdup(pool, path);
#if '/' != SVN_PATH_LOCAL_SEPARATOR
{
char *p = apr_pstrdup(pool, path);
path = p;
for (; *p != '\0'; ++p)
if (*p == '/')
*p = SVN_PATH_LOCAL_SEPARATOR;
}
#endif
return path;
}
static char
canonicalize_to_lower(char c)
{
if (c < 'A' || c > 'Z')
return c;
else
return c - 'A' + 'a';
}
#if defined(WIN32) || defined(__CYGWIN__)
static char
canonicalize_to_upper(char c)
{
if (c < 'a' || c > 'z')
return c;
else
return c - 'a' + 'A';
}
#endif
static apr_size_t
dirent_previous_segment(const char *dirent,
apr_size_t len)
{
if (len == 0)
return 0;
--len;
while (len > 0 && dirent[len] != '/'
#if defined(WIN32) || defined(__CYGWIN__)
&& dirent[len] != ':'
#endif
)
--len;
if (svn_dirent_is_root(dirent, len + 1))
return len + 1;
else
return len;
}
static apr_size_t
uri_previous_segment(const char *uri,
apr_size_t len)
{
if (len == 0)
return 0;
--len;
while (len > 0 && uri[len] != '/')
--len;
if (svn_uri_is_root(uri, len + 1))
return len + 1;
else
return len;
}
static const char *
canonicalize(path_type_t type, const char *path, apr_pool_t *pool)
{
char *canon, *dst;
const char *src;
apr_size_t seglen;
apr_size_t schemelen = 0;
apr_size_t canon_segments = 0;
svn_boolean_t url = FALSE;
if (SVN_PATH_IS_EMPTY(path))
return path;
dst = canon = apr_pcalloc(pool, strlen(path) + 1);
url = FALSE;
src = path;
if (type == type_uri && *src != '/')
{
while (*src && (*src != '/') && (*src != ':'))
src++;
if (*src == ':' && *(src+1) == '/' && *(src+2) == '/')
{
const char *seg;
url = TRUE;
src = path;
while (*src != ':')
{
*(dst++) = canonicalize_to_lower((*src++));
schemelen++;
}
*(dst++) = ':';
*(dst++) = '/';
*(dst++) = '/';
src += 3;
schemelen += 3;
seg = src;
while (*src && (*src != '/') && (*src != '@'))
src++;
if (*src == '@')
{
seglen = src - seg + 1;
memcpy(dst, seg, seglen);
dst += seglen;
src++;
}
else
src = seg;
while (*src && (*src != '/'))
*(dst++) = canonicalize_to_lower((*src++));
*(dst) = *(src);
if (*src)
{
src++;
dst++;
}
canon_segments = 1;
}
}
if (! url)
{
src = path;
if (*src == '/')
{
*(dst++) = *(src++);
#if defined(WIN32) || defined(__CYGWIN__)
if ((type == type_dirent) && *src == '/')
*(dst++) = *(src++);
#endif
}
}
while (*src)
{
const char *next = src;
while (*next && (*next != '/'))
++next;
seglen = next - src;
if (seglen == 0 || (seglen == 1 && src[0] == '.'))
{
}
#if defined(WIN32) || defined(__CYGWIN__)
else if (url && canon_segments == 1 && seglen == 2 &&
(strncmp(canon, "file:", 5) == 0) &&
src[0] >= 'a' && src[0] <= 'z' && src[1] == ':')
{
*(dst++) = canonicalize_to_upper(src[0]);
*(dst++) = ':';
if (*next)
*(dst++) = *next;
canon_segments++;
}
#endif
else
{
if (*next)
seglen++;
memcpy(dst, src, seglen);
dst += seglen;
canon_segments++;
}
src = next;
if (*src)
src++;
}
if ((canon_segments > 0 && *(dst - 1) == '/')
&& ! (url && path[schemelen] == '\0'))
{
dst --;
}
*dst = '\0';
#if defined(WIN32) || defined(__CYGWIN__)
if ((type == type_dirent) && canon[0] == '/' && canon[1] == '/')
{
if (canon_segments < 2)
return canon + 1;
else
{
dst = canon + 2;
while (*dst && *dst != '/')
*(dst++) = canonicalize_to_lower(*dst);
}
}
#endif
return canon;
}
static apr_size_t
get_longest_ancestor_length(path_type_t types,
const char *path1,
const char *path2,
apr_pool_t *pool)
{
apr_size_t path1_len, path2_len;
apr_size_t i = 0;
apr_size_t last_dirsep = 0;
#if defined(WIN32) || defined(__CYGWIN__)
svn_boolean_t unc = FALSE;
#endif
path1_len = strlen(path1);
path2_len = strlen(path2);
if (SVN_PATH_IS_EMPTY(path1) || SVN_PATH_IS_EMPTY(path2))
return 0;
while (path1[i] == path2[i])
{
if (path1[i] == '/')
last_dirsep = i;
i++;
if ((i == path1_len) || (i == path2_len))
break;
}
if (i == 1 && path1[0] == '/' && path2[0] == '/')
return 1;
if (types == type_dirent && i == 0)
return 0;
#if defined(WIN32) || defined(__CYGWIN__)
if (types == type_dirent)
{
if (last_dirsep == 1 && path1[0] == '/' && path1[1] == '/')
{
last_dirsep = 0;
unc = TRUE;
}
if (i == 3 && path1[2] == '/' && path1[1] == ':')
return i;
assert(i > 0);
if ((path1[i - 1] == ':' && path2[i] == '/') ||
(path2[i - 1] == ':' && path1[i] == '/'))
return 0;
if (path1[i - 1] == ':' || path2[i - 1] == ':')
return i;
}
#endif
if (((i == path1_len) && (path2[i] == '/'))
|| ((i == path2_len) && (path1[i] == '/'))
|| ((i == path1_len) && (i == path2_len)))
return i;
else
{
#if defined(WIN32) || defined(__CYGWIN__)
if (! unc)
{
if ((types == type_dirent) &&
last_dirsep == 2 && path1[1] == ':' && path1[2] == '/'
&& path2[1] == ':' && path2[2] == '/')
return 3;
#endif
if (last_dirsep == 0 && path1[0] == '/' && path2[0] == '/')
return 1;
#if defined(WIN32) || defined(__CYGWIN__)
}
#endif
}
return last_dirsep;
}
static const char *
is_child(path_type_t type, const char *path1, const char *path2,
apr_pool_t *pool)
{
apr_size_t i;
if (SVN_PATH_IS_EMPTY(path1))
{
if (SVN_PATH_IS_EMPTY(path2))
return NULL;
if ((type == type_uri && svn_uri_is_absolute(path2)) ||
(type == type_dirent && svn_dirent_is_absolute(path2)))
return NULL;
else
return pool ? apr_pstrdup(pool, path2) : path2;
}
for (i = 0; path1[i] && path2[i]; i++)
if (path1[i] != path2[i])
return NULL;
if (path1[i] == '\0' && path2[i])
{
if (path1[i - 1] == '/'
#if defined(WIN32) || defined(__CYGWIN__)
|| ((type == type_dirent) && path1[i - 1] == ':')
#endif
)
{
if (path2[i] == '/')
return NULL;
else
return pool ? apr_pstrdup(pool, path2 + i) : path2 + i;
}
else if (path2[i] == '/')
{
if (path2[i + 1])
return pool ? apr_pstrdup(pool, path2 + i + 1) : path2 + i + 1;
else
return NULL;
}
}
return NULL;
}
static svn_boolean_t
is_ancestor(path_type_t type, const char *path1, const char *path2)
{
apr_size_t path1_len;
if (SVN_PATH_IS_EMPTY(path1))
{
return type == type_uri ? ! svn_uri_is_absolute(path2)
: ! svn_dirent_is_absolute(path2);
}
path1_len = strlen(path1);
if (strncmp(path1, path2, path1_len) == 0)
return path1[path1_len - 1] == '/'
#if defined(WIN32) || defined(__CYGWIN__)
|| ((type == type_dirent) && path1[path1_len - 1] == ':')
#endif
|| (path2[path1_len] == '/' || path2[path1_len] == '\0');
return FALSE;
}
const char *
svn_dirent_internal_style(const char *dirent, apr_pool_t *pool)
{
return internal_style(type_dirent, dirent, pool);
}
const char *
svn_dirent_local_style(const char *dirent, apr_pool_t *pool)
{
return local_style(type_dirent, dirent, pool);
}
const char *
svn_uri_internal_style(const char *uri, apr_pool_t *pool)
{
return internal_style(type_uri, uri, pool);
}
const char *
svn_uri_local_style(const char *uri, apr_pool_t *pool)
{
return local_style(type_uri, uri, pool);
}
svn_boolean_t
svn_dirent_is_root(const char *dirent, apr_size_t len)
{
if (len == 1 && dirent[0] == '/')
return TRUE;
#if defined(WIN32) || defined(__CYGWIN__)
if ((len == 2 || len == 3) &&
(dirent[1] == ':') &&
((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
(dirent[0] >= 'a' && dirent[0] <= 'z')) &&
(len == 2 || (dirent[2] == '/' && len == 3)))
return TRUE;
if (len >= 2 && dirent[0] == '/' && dirent[1] == '/'
&& dirent[len - 1] != '/')
{
int segments = 0;
int i;
for (i = len; i >= 2; i--)
{
if (dirent[i] == '/')
{
segments ++;
if (segments > 1)
return FALSE;
}
}
return (segments <= 1);
}
#endif
return FALSE;
}
svn_boolean_t
svn_uri_is_root(const char *uri, apr_size_t len)
{
if (len == 1 && uri[0] == '/')
return TRUE;
return FALSE;
}
char *svn_dirent_join(const char *base,
const char *component,
apr_pool_t *pool)
{
apr_size_t blen = strlen(base);
apr_size_t clen = strlen(component);
char *dirent;
int add_separator;
assert(svn_dirent_is_canonical(base, pool));
assert(svn_dirent_is_canonical(component, pool));
if (svn_dirent_is_absolute(component))
return apr_pmemdup(pool, component, clen + 1);
if (SVN_PATH_IS_EMPTY(base))
return apr_pmemdup(pool, component, clen + 1);
if (SVN_PATH_IS_EMPTY(component))
return apr_pmemdup(pool, base, blen + 1);
add_separator = 1;
if (base[blen - 1] == '/'
#if defined(WIN32) || defined(__CYGWIN__)
|| base[blen - 1] == ':'
#endif
)
add_separator = 0;
dirent = apr_palloc(pool, blen + add_separator + clen + 1);
memcpy(dirent, base, blen);
if (add_separator)
dirent[blen] = '/';
memcpy(dirent + blen + add_separator, component, clen + 1);
return dirent;
}
char *svn_dirent_join_many(apr_pool_t *pool, const char *base, ...)
{
#define MAX_SAVED_LENGTHS 10
apr_size_t saved_lengths[MAX_SAVED_LENGTHS];
apr_size_t total_len;
int nargs;
va_list va;
const char *s;
apr_size_t len;
char *dirent;
char *p;
int add_separator;
int base_arg = 0;
total_len = strlen(base);
assert(svn_dirent_is_canonical(base, pool));
add_separator = 1;
if (total_len == 0
|| base[total_len - 1] == '/'
#if defined(WIN32) || defined(__CYGWIN__)
|| base[total_len - 1] == ':'
#endif
)
add_separator = 0;
saved_lengths[0] = total_len;
nargs = 0;
va_start(va, base);
while ((s = va_arg(va, const char *)) != NULL)
{
len = strlen(s);
assert(svn_dirent_is_canonical(s, pool));
if (SVN_PATH_IS_EMPTY(s))
continue;
if (nargs++ < MAX_SAVED_LENGTHS)
saved_lengths[nargs] = len;
if (svn_dirent_is_absolute(s))
{
total_len = len;
base_arg = nargs;
add_separator = 1;
if (s[len - 1] == '/'
#if defined(WIN32) || defined(__CYGWIN__)
|| s[len - 1] == ':'
#endif
)
add_separator = 0;
}
else if (nargs == base_arg + 1)
{
total_len += add_separator + len;
}
else
{
total_len += 1 + len;
}
}
va_end(va);
if (add_separator == 0 && total_len == 1)
return apr_pmemdup(pool, "/", 2);
dirent = p = apr_palloc(pool, total_len + 1);
if (base_arg == 0 && ! (SVN_PATH_IS_EMPTY(base)))
{
if (SVN_PATH_IS_EMPTY(base))
memcpy(p, SVN_EMPTY_PATH, len = saved_lengths[0]);
else
memcpy(p, base, len = saved_lengths[0]);
p += len;
}
nargs = 0;
va_start(va, base);
while ((s = va_arg(va, const char *)) != NULL)
{
if (SVN_PATH_IS_EMPTY(s))
continue;
if (++nargs < base_arg)
continue;
if (nargs < MAX_SAVED_LENGTHS)
len = saved_lengths[nargs];
else
len = strlen(s);
if (p != dirent &&
( ! (nargs - 1 == base_arg) || add_separator))
*p++ = '/';
memcpy(p, s, len);
p += len;
}
va_end(va);
*p = '\0';
assert((apr_size_t)(p - dirent) == total_len);
return dirent;
}
char *
svn_dirent_dirname(const char *dirent, apr_pool_t *pool)
{
apr_size_t len = strlen(dirent);
assert(svn_dirent_is_canonical(dirent, pool));
if (svn_dirent_is_root(dirent, len))
return apr_pstrmemdup(pool, dirent, len);
else
return apr_pstrmemdup(pool, dirent, dirent_previous_segment(dirent, len));
}
char *
svn_uri_dirname(const char *uri, apr_pool_t *pool)
{
apr_size_t len = strlen(uri);
assert(svn_uri_is_canonical(uri, pool));
if (svn_uri_is_root(uri, len))
return apr_pstrmemdup(pool, uri, len);
else
return apr_pstrmemdup(pool, uri, uri_previous_segment(uri, len));
}
char *
svn_dirent_get_longest_ancestor(const char *dirent1,
const char *dirent2,
apr_pool_t *pool)
{
return apr_pstrndup(pool, dirent1,
get_longest_ancestor_length(type_dirent, dirent1,
dirent2, pool));
}
char *
svn_uri_get_longest_ancestor(const char *uri1,
const char *uri2,
apr_pool_t *pool)
{
svn_boolean_t uri1_is_url, uri2_is_url;
uri1_is_url = svn_path_is_url(uri1);
uri2_is_url = svn_path_is_url(uri2);
if (uri1_is_url && uri2_is_url)
{
apr_size_t uri_ancestor_len;
apr_size_t i = 0;
while (1)
{
if (uri1[i] != uri2[i])
return apr_pmemdup(pool, SVN_EMPTY_PATH,
sizeof(SVN_EMPTY_PATH));
if (uri1[i] == ':')
break;
assert((uri1[i] != '\0') && (uri2[i] != '\0'));
i++;
}
i += 3;
uri_ancestor_len = get_longest_ancestor_length(type_uri, uri1 + i,
uri2 + i, pool);
if (uri_ancestor_len == 0 ||
(uri_ancestor_len == 1 && (uri1 + i)[0] == '/'))
return apr_pmemdup(pool, SVN_EMPTY_PATH, sizeof(SVN_EMPTY_PATH));
else
return apr_pstrndup(pool, uri1, uri_ancestor_len + i);
}
else if ((! uri1_is_url) && (! uri2_is_url))
{
return apr_pstrndup(pool, uri1,
get_longest_ancestor_length(type_uri, uri1, uri2,
pool));
}
else
{
return apr_pmemdup(pool, SVN_EMPTY_PATH, sizeof(SVN_EMPTY_PATH));
}
}
const char *
svn_dirent_is_child(const char *dirent1,
const char *dirent2,
apr_pool_t *pool)
{
return is_child(type_dirent, dirent1, dirent2, pool);
}
const char *
svn_uri_is_child(const char *uri1,
const char *uri2,
apr_pool_t *pool)
{
return is_child(type_uri, uri1, uri2, pool);
}
svn_boolean_t
svn_dirent_is_ancestor(const char *dirent1, const char *dirent2)
{
return is_ancestor(type_dirent, dirent1, dirent2);
}
svn_boolean_t
svn_uri_is_ancestor(const char *uri1, const char *uri2)
{
return is_ancestor(type_uri, uri1, uri2);
}
svn_boolean_t
svn_dirent_is_absolute(const char *dirent)
{
if (! dirent)
return FALSE;
if (dirent[0] == '/')
return TRUE;
#if defined(WIN32) || defined(__CYGWIN__)
if (((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
(dirent[0] >= 'a' && dirent[0] <= 'z')) &&
(dirent[1] == ':'))
return TRUE;
#endif
return FALSE;
}
svn_boolean_t
svn_uri_is_absolute(const char *uri)
{
if (uri && uri[0] == '/')
return TRUE;
return svn_path_is_url(uri);
}
svn_error_t *
svn_dirent_get_absolute(const char **pabsolute,
const char *relative,
apr_pool_t *pool)
{
char *buffer;
apr_status_t apr_err;
const char *path_apr;
SVN_ERR(svn_path_cstring_from_utf8(&path_apr, relative, pool));
apr_err = apr_filepath_merge(&buffer, NULL,
path_apr,
APR_FILEPATH_NOTRELATIVE,
pool);
if (apr_err)
return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
_("Couldn't determine absolute path of '%s'"),
svn_path_local_style(relative, pool));
SVN_ERR(svn_path_cstring_to_utf8(pabsolute, buffer, pool));
*pabsolute = svn_dirent_canonicalize(*pabsolute, pool);
return SVN_NO_ERROR;
}
const char *
svn_uri_canonicalize(const char *uri, apr_pool_t *pool)
{
return canonicalize(type_uri, uri, pool);;
}
const char *
svn_dirent_canonicalize(const char *dirent, apr_pool_t *pool)
{
const char *dst = canonicalize(type_dirent, dirent, pool);;
#if defined(WIN32) || defined(__CYGWIN__)
if (((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
(dirent[0] >= 'a' && dirent[0] <= 'z')) &&
dirent[1] == ':' && dirent[2] == '/' &&
dst[3] == '\0')
{
char *dst_slash = apr_pcalloc(pool, 4);
dst_slash[0] = dirent[0];
dst_slash[1] = ':';
dst_slash[2] = '/';
dst_slash[3] = '\0';
return dst_slash;
}
#endif
return dst;
}
svn_boolean_t
svn_dirent_is_canonical(const char *dirent, apr_pool_t *pool)
{
return (strcmp(dirent, svn_dirent_canonicalize(dirent, pool)) == 0);
}
svn_boolean_t
svn_uri_is_canonical(const char *uri, apr_pool_t *pool)
{
const char *ptr = uri, *seg = uri;
if (*uri == '\0')
return TRUE;
if (*ptr != '/')
{
while (*ptr && (*ptr != '/') && (*ptr != ':'))
ptr++;
if (*ptr == ':' && *(ptr+1) == '/' && *(ptr+2) == '/')
{
ptr = uri;
while (*ptr != ':')
{
if (*ptr >= 'A' && *ptr <= 'Z')
return FALSE;
ptr++;
}
ptr += 3;
seg = ptr;
while (*ptr && (*ptr != '/') && (*ptr != '@'))
ptr++;
if (! *ptr)
return TRUE;
if (*ptr == '@')
seg = ptr + 1;
ptr = seg;
while (*ptr && *ptr != '/')
{
if (*ptr >= 'A' && *ptr <= 'Z')
return FALSE;
ptr++;
}
}
else
{
while (*ptr && *ptr != '/')
ptr++;
}
}
#if defined(WIN32) || defined(__CYGWIN__)
if (*ptr == '/')
{
if (strncmp(uri, "file:", 5) == 0 &&
! (*(ptr+1) >= 'A' && *(ptr+1) <= 'Z') &&
*(ptr+2) == ':')
return FALSE;
}
#endif
while(1)
{
int seglen = ptr - seg;
if (seglen == 1 && *seg == '.')
return FALSE;
if (*ptr == '/' && *(ptr+1) == '/')
return FALSE;
if (! *ptr && *(ptr - 1) == '/' && ptr - 1 != uri)
return FALSE;
if (! *ptr)
break;
if (*ptr == '/')
ptr++;
seg = ptr;
while (*ptr && (*ptr != '/'))
ptr++;
}
return TRUE;
}