2 * dirent_uri.c: a library to manipulate URIs and directory entries.
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
33 #include "svn_private_config.h"
34 #include "svn_string.h"
35 #include "svn_dirent_uri.h"
37 #include "svn_ctype.h"
39 #include "dirent_uri.h"
40 #include "private/svn_fspath.h"
42 /* The canonical empty path. Can this be changed? Well, change the empty
43 test below and the path library will work, not so sure about the fs/wc
45 #define SVN_EMPTY_PATH ""
47 /* TRUE if s is the canonical empty path, FALSE otherwise */
48 #define SVN_PATH_IS_EMPTY(s) ((s)[0] == '\0')
50 /* TRUE if s,n is the platform's empty path ("."), FALSE otherwise. Can
51 this be changed? Well, the path library will work, not so sure about
53 #define SVN_PATH_IS_PLATFORM_EMPTY(s,n) ((n) == 1 && (s)[0] == '.')
55 /* This check must match the check on top of dirent_uri-tests.c and
57 #if defined(WIN32) || defined(__CYGWIN__) || defined(__OS2__)
58 #define SVN_USE_DOS_PATHS
61 /* Path type definition. Used only by internal functions. */
62 typedef enum path_type_t {
69 /**** Forward declarations *****/
72 relpath_is_canonical(const char *relpath);
75 /**** Internal implementation functions *****/
77 /* Return an internal-style new path based on PATH, allocated in POOL.
79 * "Internal-style" means that separators are all '/'.
82 internal_style(const char *path, apr_pool_t *pool)
84 #if '/' != SVN_PATH_LOCAL_SEPARATOR
86 char *p = apr_pstrdup(pool, path);
89 /* Convert all local-style separators to the canonical ones. */
90 for (; *p != '\0'; ++p)
91 if (*p == SVN_PATH_LOCAL_SEPARATOR)
99 /* Locale insensitive tolower() for converting parts of dirents and urls
100 while canonicalizing */
102 canonicalize_to_lower(char c)
104 if (c < 'A' || c > 'Z')
107 return (char)(c - 'A' + 'a');
110 /* Locale insensitive toupper() for converting parts of dirents and urls
111 while canonicalizing */
113 canonicalize_to_upper(char c)
115 if (c < 'a' || c > 'z')
118 return (char)(c - 'a' + 'A');
121 /* Calculates the length of the dirent absolute or non absolute root in
122 DIRENT, return 0 if dirent is not rooted */
124 dirent_root_length(const char *dirent, apr_size_t len)
126 #ifdef SVN_USE_DOS_PATHS
127 if (len >= 2 && dirent[1] == ':' &&
128 ((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
129 (dirent[0] >= 'a' && dirent[0] <= 'z')))
131 return (len > 2 && dirent[2] == '/') ? 3 : 2;
134 if (len > 2 && dirent[0] == '/' && dirent[1] == '/')
138 while (i < len && dirent[i] != '/')
142 return len; /* Cygwin drive alias, invalid path on WIN32 */
146 while (i < len && dirent[i] != '/')
151 #endif /* SVN_USE_DOS_PATHS */
152 if (len >= 1 && dirent[0] == '/')
159 /* Return the length of substring necessary to encompass the entire
160 * previous dirent segment in DIRENT, which should be a LEN byte string.
162 * A trailing slash will not be included in the returned length except
163 * in the case in which DIRENT is absolute and there are no more
167 dirent_previous_segment(const char *dirent,
174 while (len > 0 && dirent[len] != '/'
175 #ifdef SVN_USE_DOS_PATHS
176 && (dirent[len] != ':' || len != 1)
177 #endif /* SVN_USE_DOS_PATHS */
181 /* check if the remaining segment including trailing '/' is a root dirent */
182 if (dirent_root_length(dirent, len+1) == len + 1)
188 /* Calculates the length occupied by the schema defined root of URI */
190 uri_schema_root_length(const char *uri, apr_size_t len)
194 for (i = 0; i < len; i++)
198 if (i > 0 && uri[i-1] == ':' && i < len-1 && uri[i+1] == '/')
200 /* We have an absolute uri */
201 if (i == 5 && strncmp("file", uri, 4) == 0)
202 return 7; /* file:// */
205 for (i += 2; i < len; i++)
209 return len; /* Only a hostname is found */
220 /* Returns TRUE if svn_dirent_is_absolute(dirent) or when dirent has
221 a non absolute root. (E.g. '/' or 'F:' on Windows) */
223 dirent_is_rooted(const char *dirent)
228 /* Root on all systems */
229 if (dirent[0] == '/')
232 /* On Windows, dirent is also absolute when it starts with 'H:' or 'H:/'
233 where 'H' is any letter. */
234 #ifdef SVN_USE_DOS_PATHS
235 if (((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
236 (dirent[0] >= 'a' && dirent[0] <= 'z')) &&
239 #endif /* SVN_USE_DOS_PATHS */
244 /* Return the length of substring necessary to encompass the entire
245 * previous relpath segment in RELPATH, which should be a LEN byte string.
247 * A trailing slash will not be included in the returned length.
250 relpath_previous_segment(const char *relpath,
257 while (len > 0 && relpath[len] != '/')
263 /* Return the length of substring necessary to encompass the entire
264 * previous uri segment in URI, which should be a LEN byte string.
266 * A trailing slash will not be included in the returned length except
267 * in the case in which URI is absolute and there are no more
271 uri_previous_segment(const char *uri,
274 apr_size_t root_length;
279 root_length = uri_schema_root_length(uri, len);
282 while (len > root_length && uri[i] != '/')
285 if (i == 0 && len > 1 && *uri == '/')
291 /* Return the canonicalized version of PATH, of type TYPE, allocated in
295 canonicalize(path_type_t type, const char *path, apr_pool_t *pool)
300 apr_size_t schemelen = 0;
301 apr_size_t canon_segments = 0;
302 svn_boolean_t url = FALSE;
303 char *schema_data = NULL;
305 /* "" is already canonical, so just return it; note that later code
306 depends on path not being zero-length. */
307 if (SVN_PATH_IS_EMPTY(path))
309 assert(type != type_uri);
313 dst = canon = apr_pcalloc(pool, strlen(path) + 1);
315 /* If this is supposed to be an URI, it should start with
316 "scheme://". We'll copy the scheme, host name, etc. to DST and
319 if (type == type_uri)
323 while (*src && (*src != '/') && (*src != ':'))
326 if (*src == ':' && *(src+1) == '/' && *(src+2) == '/')
332 /* Found a scheme, convert to lowercase and copy to dst. */
336 *(dst++) = canonicalize_to_lower((*src++));
345 /* This might be the hostname */
347 while (*src && (*src != '/') && (*src != '@'))
352 /* Copy the username & password. */
353 seglen = src - seg + 1;
354 memcpy(dst, seg, seglen);
361 /* Found a hostname, convert to lowercase and copy to dst. */
364 *(dst++) = *(src++); /* Copy '[' */
367 || (*src >= '0' && (*src <= '9'))
368 || (*src >= 'a' && (*src <= 'f'))
369 || (*src >= 'A' && (*src <= 'F')))
371 *(dst++) = canonicalize_to_lower((*src++));
375 *(dst++) = *(src++); /* Copy ']' */
378 while (*src && (*src != '/') && (*src != ':'))
379 *(dst++) = canonicalize_to_lower((*src++));
383 /* We probably have a port number: Is it a default portnumber
384 which doesn't belong in a canonical url? */
385 if (src[1] == '8' && src[2] == '0'
386 && (src[3]== '/'|| !src[3])
387 && !strncmp(canon, "http:", 5))
391 else if (src[1] == '4' && src[2] == '4' && src[3] == '3'
392 && (src[4]== '/'|| !src[4])
393 && !strncmp(canon, "https:", 6))
397 else if (src[1] == '3' && src[2] == '6'
398 && src[3] == '9' && src[4] == '0'
399 && (src[5]== '/'|| !src[5])
400 && !strncmp(canon, "svn:", 4))
404 else if (src[1] == '/' || !src[1])
409 while (*src && (*src != '/'))
410 *(dst++) = canonicalize_to_lower((*src++));
413 /* Copy trailing slash, or null-terminator. */
416 /* Move src and dst forward only if we are not
417 * at null-terminator yet. */
429 /* Copy to DST any separator or drive letter that must come before the
430 first regular path segment. */
431 if (! url && type != type_relpath)
434 /* If this is an absolute path, then just copy over the initial
435 separator character. */
440 #ifdef SVN_USE_DOS_PATHS
441 /* On Windows permit two leading separator characters which means an
443 if ((type == type_dirent) && *src == '/')
445 #endif /* SVN_USE_DOS_PATHS */
447 #ifdef SVN_USE_DOS_PATHS
448 /* On Windows the first segment can be a drive letter, which we normalize
450 else if (type == type_dirent &&
451 ((*src >= 'a' && *src <= 'z') ||
452 (*src >= 'A' && *src <= 'Z')) &&
455 *(dst++) = canonicalize_to_upper(*(src++));
456 /* Leave the ':' to be processed as (or as part of) a path segment
457 by the following code block, so we need not care whether it has
460 #endif /* SVN_USE_DOS_PATHS */
465 /* Parse each segment, finding the closing '/' (which might look
466 like '%2F' for URIs). */
467 const char *next = src;
468 apr_size_t slash_len = 0;
472 && (! (type == type_uri && next[0] == '%' && next[1] == '2' &&
473 canonicalize_to_upper(next[2]) == 'F')))
478 /* Record how long our "slash" is. */
481 else if (type == type_uri && next[0] == '%')
487 || (seglen == 1 && src[0] == '.')
488 || (type == type_uri && seglen == 3 && src[0] == '%' && src[1] == '2'
489 && canonicalize_to_upper(src[2]) == 'E'))
491 /* Empty or noop segment, so do nothing. (For URIs, '%2E'
492 is equivalent to '.'). */
494 #ifdef SVN_USE_DOS_PATHS
495 /* If this is the first path segment of a file:// URI and it contains a
496 windows drive letter, convert the drive letter to upper case. */
497 else if (url && canon_segments == 1 && seglen == 2 &&
498 (strncmp(canon, "file:", 5) == 0) &&
499 src[0] >= 'a' && src[0] <= 'z' && src[1] == ':')
501 *(dst++) = canonicalize_to_upper(src[0]);
507 #endif /* SVN_USE_DOS_PATHS */
510 /* An actual segment, append it to the destination path */
511 memcpy(dst, src, seglen);
518 /* Skip over trailing slash to the next segment. */
519 src = next + slash_len;
522 /* Remove the trailing slash if there was at least one
523 * canonical segment and the last segment ends with a slash.
525 * But keep in mind that, for URLs, the scheme counts as a
526 * canonical segment -- so if path is ONLY a scheme (such
527 * as "https://") we should NOT remove the trailing slash. */
528 if ((canon_segments > 0 && *(dst - 1) == '/')
529 && ! (url && path[schemelen] == '\0'))
536 #ifdef SVN_USE_DOS_PATHS
537 /* Skip leading double slashes when there are less than 2
538 * canon segments. UNC paths *MUST* have two segments. */
539 if ((type == type_dirent) && canon[0] == '/' && canon[1] == '/')
541 if (canon_segments < 2)
545 /* Now we're sure this is a valid UNC path, convert the server name
546 (the first path segment) to lowercase as Windows treats it as case
548 Note: normally the share name is treated as case insensitive too,
549 but it seems to be possible to configure Samba to treat those as
550 case sensitive, so better leave that alone. */
551 for (dst = canon + 2; *dst && *dst != '/'; dst++)
552 *dst = canonicalize_to_lower(*dst);
555 #endif /* SVN_USE_DOS_PATHS */
557 /* Check the normalization of characters in a uri */
570 if (!svn_ctype_isxdigit(*(src+1)) ||
571 !svn_ctype_isxdigit(*(src+2)))
577 if (!svn_uri__char_validity[(unsigned char)*src])
586 apr_size_t pre_schema_size = (apr_size_t)(schema_data - canon);
588 dst = apr_palloc(pool, (apr_size_t)(src - canon) + need_extra + 1);
589 memcpy(dst, canon, pre_schema_size);
592 dst += pre_schema_size;
607 if (!svn_ctype_isxdigit(*(src+1)) ||
608 !svn_ctype_isxdigit(*(src+2)))
619 digitz[0] = *(++src);
620 digitz[1] = *(++src);
623 val = (int)strtol(digitz, NULL, 16);
625 if (svn_uri__char_validity[(unsigned char)val])
626 *(dst++) = (char)val;
630 *(dst++) = canonicalize_to_upper(digitz[0]);
631 *(dst++) = canonicalize_to_upper(digitz[1]);
636 if (!svn_uri__char_validity[(unsigned char)*src])
638 apr_snprintf(dst, 4, "%%%02X", (unsigned char)*src);
653 /* Return the string length of the longest common ancestor of PATH1 and PATH2.
654 * Pass type_uri for TYPE if PATH1 and PATH2 are URIs, and type_dirent if
655 * PATH1 and PATH2 are regular paths.
657 * If the two paths do not share a common ancestor, return 0.
659 * New strings are allocated in POOL.
662 get_longest_ancestor_length(path_type_t types,
667 apr_size_t path1_len, path2_len;
669 apr_size_t last_dirsep = 0;
670 #ifdef SVN_USE_DOS_PATHS
671 svn_boolean_t unc = FALSE;
674 path1_len = strlen(path1);
675 path2_len = strlen(path2);
677 if (SVN_PATH_IS_EMPTY(path1) || SVN_PATH_IS_EMPTY(path2))
680 while (path1[i] == path2[i])
682 /* Keep track of the last directory separator we hit. */
688 /* If we get to the end of either path, break out. */
689 if ((i == path1_len) || (i == path2_len))
693 /* two special cases:
694 1. '/' is the longest common ancestor of '/' and '/foo' */
695 if (i == 1 && path1[0] == '/' && path2[0] == '/')
697 /* 2. '' is the longest common ancestor of any non-matching
698 * strings 'foo' and 'bar' */
699 if (types == type_dirent && i == 0)
702 /* Handle some windows specific cases */
703 #ifdef SVN_USE_DOS_PATHS
704 if (types == type_dirent)
706 /* don't count the '//' from UNC paths */
707 if (last_dirsep == 1 && path1[0] == '/' && path1[1] == '/')
714 if (i == 3 && path1[2] == '/' && path1[1] == ':')
717 /* Cannot use SVN_ERR_ASSERT here, so we'll have to crash, sorry.
718 * Note that this assertion triggers only if the code above has
719 * been broken. The code below relies on this assertion, because
720 * it uses [i - 1] as index. */
724 if ((path1[i - 1] == ':' && path2[i] == '/') ||
725 (path2[i - 1] == ':' && path1[i] == '/'))
728 if (path1[i - 1] == ':' || path2[i - 1] == ':')
731 #endif /* SVN_USE_DOS_PATHS */
733 /* last_dirsep is now the offset of the last directory separator we
734 crossed before reaching a non-matching byte. i is the offset of
735 that non-matching byte, and is guaranteed to be <= the length of
736 whichever path is shorter.
737 If one of the paths is the common part return that. */
738 if (((i == path1_len) && (path2[i] == '/'))
739 || ((i == path2_len) && (path1[i] == '/'))
740 || ((i == path1_len) && (i == path2_len)))
744 /* Nothing in common but the root folder '/' or 'X:/' for Windows
746 #ifdef SVN_USE_DOS_PATHS
749 /* X:/foo and X:/bar returns X:/ */
750 if ((types == type_dirent) &&
751 last_dirsep == 2 && path1[1] == ':' && path1[2] == '/'
752 && path2[1] == ':' && path2[2] == '/')
754 #endif /* SVN_USE_DOS_PATHS */
755 if (last_dirsep == 0 && path1[0] == '/' && path2[0] == '/')
757 #ifdef SVN_USE_DOS_PATHS
765 /* Determine whether PATH2 is a child of PATH1.
767 * PATH2 is a child of PATH1 if
768 * 1) PATH1 is empty, and PATH2 is not empty and not an absolute path.
770 * 2) PATH2 is has n components, PATH1 has x < n components,
771 * and PATH1 matches PATH2 in all its x components.
772 * Components are separated by a slash, '/'.
774 * Pass type_uri for TYPE if PATH1 and PATH2 are URIs, and type_dirent if
775 * PATH1 and PATH2 are regular paths.
777 * If PATH2 is not a child of PATH1, return NULL.
779 * If PATH2 is a child of PATH1, and POOL is not NULL, allocate a copy
780 * of the child part of PATH2 in POOL and return a pointer to the
781 * newly allocated child part.
783 * If PATH2 is a child of PATH1, and POOL is NULL, return a pointer
784 * pointing to the child part of PATH2.
787 is_child(path_type_t type, const char *path1, const char *path2,
792 /* Allow "" and "foo" or "H:foo" to be parent/child */
793 if (SVN_PATH_IS_EMPTY(path1)) /* "" is the parent */
795 if (SVN_PATH_IS_EMPTY(path2)) /* "" not a child */
798 /* check if this is an absolute path */
799 if ((type == type_uri) ||
800 (type == type_dirent && dirent_is_rooted(path2)))
803 /* everything else is child */
804 return pool ? apr_pstrdup(pool, path2) : path2;
807 /* Reach the end of at least one of the paths. How should we handle
808 things like path1:"foo///bar" and path2:"foo/bar/baz"? It doesn't
809 appear to arise in the current Subversion code, it's not clear to me
810 if they should be parent/child or not. */
811 /* Hmmm... aren't paths assumed to be canonical in this function?
812 * How can "foo///bar" even happen if the paths are canonical? */
813 for (i = 0; path1[i] && path2[i]; i++)
814 if (path1[i] != path2[i])
817 /* FIXME: This comment does not really match
818 * the checks made in the code it refers to: */
819 /* There are two cases that are parent/child
821 .../foo path2[i] == '/'
826 Other root paths (like X:/) fall under the former case:
828 X:/foo path2[i] != '/'
830 Check for '//' to avoid matching '/' and '//srv'.
832 if (path1[i] == '\0' && path2[i])
834 if (path1[i - 1] == '/'
835 #ifdef SVN_USE_DOS_PATHS
836 || ((type == type_dirent) && path1[i - 1] == ':')
849 return pool ? apr_pstrdup(pool, path2 + i) : path2 + i;
851 else if (path2[i] == '/')
857 return pool ? apr_pstrdup(pool, path2 + i + 1) : path2 + i + 1;
866 /* Otherwise, path2 isn't a child. */
871 /**** Public API functions ****/
874 svn_dirent_internal_style(const char *dirent, apr_pool_t *pool)
876 return svn_dirent_canonicalize(internal_style(dirent, pool), pool);
880 svn_dirent_local_style(const char *dirent, apr_pool_t *pool)
882 /* Internally, Subversion represents the current directory with the
883 empty string. But users like to see "." . */
884 if (SVN_PATH_IS_EMPTY(dirent))
887 #if '/' != SVN_PATH_LOCAL_SEPARATOR
889 char *p = apr_pstrdup(pool, dirent);
892 /* Convert all canonical separators to the local-style ones. */
893 for (; *p != '\0'; ++p)
895 *p = SVN_PATH_LOCAL_SEPARATOR;
903 svn_relpath__internal_style(const char *relpath,
906 return svn_relpath_canonicalize(internal_style(relpath, pool), pool);
910 /* We decided against using apr_filepath_root here because of the negative
911 performance impact (creating a pool and converting strings ). */
913 svn_dirent_is_root(const char *dirent, apr_size_t len)
915 #ifdef SVN_USE_DOS_PATHS
916 /* On Windows and Cygwin, 'H:' or 'H:/' (where 'H' is any letter)
917 are also root directories */
918 if ((len == 2 || ((len == 3) && (dirent[2] == '/'))) &&
919 (dirent[1] == ':') &&
920 ((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
921 (dirent[0] >= 'a' && dirent[0] <= 'z')))
924 /* On Windows and Cygwin //server/share is a root directory,
925 and on Cygwin //drive is a drive alias */
926 if (len >= 2 && dirent[0] == '/' && dirent[1] == '/'
927 && dirent[len - 1] != '/')
931 for (i = len; i >= 2; i--)
933 if (dirent[i] == '/')
941 return (segments <= 1);
943 return (segments == 1); /* //drive is invalid on plain Windows */
948 /* directory is root if it's equal to '/' */
949 if (len == 1 && dirent[0] == '/')
956 svn_uri_is_root(const char *uri, apr_size_t len)
958 assert(svn_uri_is_canonical(uri, NULL));
959 return (len == uri_schema_root_length(uri, len));
962 char *svn_dirent_join(const char *base,
963 const char *component,
966 apr_size_t blen = strlen(base);
967 apr_size_t clen = strlen(component);
971 assert(svn_dirent_is_canonical(base, pool));
972 assert(svn_dirent_is_canonical(component, pool));
974 /* If the component is absolute, then return it. */
975 if (svn_dirent_is_absolute(component))
976 return apr_pmemdup(pool, component, clen + 1);
978 /* If either is empty return the other */
979 if (SVN_PATH_IS_EMPTY(base))
980 return apr_pmemdup(pool, component, clen + 1);
981 if (SVN_PATH_IS_EMPTY(component))
982 return apr_pmemdup(pool, base, blen + 1);
984 #ifdef SVN_USE_DOS_PATHS
985 if (component[0] == '/')
987 /* '/' is drive relative on Windows, not absolute like on Posix */
988 if (dirent_is_rooted(base))
990 /* Join component without '/' to root-of(base) */
991 blen = dirent_root_length(base, blen);
995 if (blen == 2 && base[1] == ':') /* "C:" case */
997 char *root = apr_pmemdup(pool, base, 3);
998 root[2] = '/'; /* We don't need the final '\0' */
1005 return apr_pstrndup(pool, base, blen);
1008 return apr_pmemdup(pool, component, clen + 1);
1010 else if (dirent_is_rooted(component))
1011 return apr_pmemdup(pool, component, clen + 1);
1012 #endif /* SVN_USE_DOS_PATHS */
1014 /* if last character of base is already a separator, don't add a '/' */
1016 if (base[blen - 1] == '/'
1017 #ifdef SVN_USE_DOS_PATHS
1018 || base[blen - 1] == ':'
1023 /* Construct the new, combined dirent. */
1024 dirent = apr_palloc(pool, blen + add_separator + clen + 1);
1025 memcpy(dirent, base, blen);
1028 memcpy(dirent + blen + add_separator, component, clen + 1);
1033 char *svn_dirent_join_many(apr_pool_t *pool, const char *base, ...)
1035 #define MAX_SAVED_LENGTHS 10
1036 apr_size_t saved_lengths[MAX_SAVED_LENGTHS];
1037 apr_size_t total_len;
1047 total_len = strlen(base);
1049 assert(svn_dirent_is_canonical(base, pool));
1051 /* if last character of base is already a separator, don't add a '/' */
1054 || base[total_len - 1] == '/'
1055 #ifdef SVN_USE_DOS_PATHS
1056 || base[total_len - 1] == ':'
1061 saved_lengths[0] = total_len;
1063 /* Compute the length of the resulting string. */
1067 while ((s = va_arg(va, const char *)) != NULL)
1071 assert(svn_dirent_is_canonical(s, pool));
1073 if (SVN_PATH_IS_EMPTY(s))
1076 if (nargs++ < MAX_SAVED_LENGTHS)
1077 saved_lengths[nargs] = len;
1079 if (dirent_is_rooted(s))
1084 #ifdef SVN_USE_DOS_PATHS
1085 if (!svn_dirent_is_absolute(s)) /* Handle non absolute roots */
1087 /* Set new base and skip the current argument */
1088 base = s = svn_dirent_join(base, s, pool);
1090 saved_lengths[0] = total_len = len = strlen(s);
1093 #endif /* SVN_USE_DOS_PATHS */
1095 base = ""; /* Don't add base */
1096 saved_lengths[0] = 0;
1100 if (s[len - 1] == '/'
1101 #ifdef SVN_USE_DOS_PATHS
1102 || s[len - 1] == ':'
1107 else if (nargs <= base_arg + 1)
1109 total_len += add_separator + len;
1113 total_len += 1 + len;
1118 /* base == "/" and no further components. just return that. */
1119 if (add_separator == 0 && total_len == 1)
1120 return apr_pmemdup(pool, "/", 2);
1122 /* we got the total size. allocate it, with room for a NULL character. */
1123 dirent = p = apr_palloc(pool, total_len + 1);
1125 /* if we aren't supposed to skip forward to an absolute component, and if
1126 this is not an empty base that we are skipping, then copy the base
1128 if (! SVN_PATH_IS_EMPTY(base))
1130 memcpy(p, base, len = saved_lengths[0]);
1136 while ((s = va_arg(va, const char *)) != NULL)
1138 if (SVN_PATH_IS_EMPTY(s))
1141 if (++nargs < base_arg)
1144 if (nargs < MAX_SAVED_LENGTHS)
1145 len = saved_lengths[nargs];
1149 /* insert a separator if we aren't copying in the first component
1150 (which can happen when base_arg is set). also, don't put in a slash
1151 if the prior character is a slash (occurs when prior component
1154 ( ! (nargs - 1 <= base_arg) || add_separator))
1157 /* copy the new component and advance the pointer */
1164 assert((apr_size_t)(p - dirent) == total_len);
1170 svn_relpath_join(const char *base,
1171 const char *component,
1174 apr_size_t blen = strlen(base);
1175 apr_size_t clen = strlen(component);
1178 assert(relpath_is_canonical(base));
1179 assert(relpath_is_canonical(component));
1181 /* If either is empty return the other */
1183 return apr_pmemdup(pool, component, clen + 1);
1185 return apr_pmemdup(pool, base, blen + 1);
1187 path = apr_palloc(pool, blen + 1 + clen + 1);
1188 memcpy(path, base, blen);
1190 memcpy(path + blen + 1, component, clen + 1);
1196 svn_dirent_dirname(const char *dirent, apr_pool_t *pool)
1198 apr_size_t len = strlen(dirent);
1200 assert(svn_dirent_is_canonical(dirent, pool));
1202 if (len == dirent_root_length(dirent, len))
1203 return apr_pstrmemdup(pool, dirent, len);
1205 return apr_pstrmemdup(pool, dirent, dirent_previous_segment(dirent, len));
1209 svn_dirent_basename(const char *dirent, apr_pool_t *pool)
1211 apr_size_t len = strlen(dirent);
1214 assert(!pool || svn_dirent_is_canonical(dirent, pool));
1216 if (svn_dirent_is_root(dirent, len))
1221 while (start > 0 && dirent[start - 1] != '/'
1222 #ifdef SVN_USE_DOS_PATHS
1223 && dirent[start - 1] != ':'
1230 return apr_pstrmemdup(pool, dirent + start, len - start);
1232 return dirent + start;
1236 svn_dirent_split(const char **dirpath,
1237 const char **base_name,
1241 assert(dirpath != base_name);
1244 *dirpath = svn_dirent_dirname(dirent, pool);
1247 *base_name = svn_dirent_basename(dirent, pool);
1251 svn_relpath_dirname(const char *relpath,
1254 apr_size_t len = strlen(relpath);
1256 assert(relpath_is_canonical(relpath));
1258 return apr_pstrmemdup(pool, relpath,
1259 relpath_previous_segment(relpath, len));
1263 svn_relpath_basename(const char *relpath,
1266 apr_size_t len = strlen(relpath);
1269 assert(relpath_is_canonical(relpath));
1272 while (start > 0 && relpath[start - 1] != '/')
1276 return apr_pstrmemdup(pool, relpath + start, len - start);
1278 return relpath + start;
1282 svn_relpath_split(const char **dirpath,
1283 const char **base_name,
1284 const char *relpath,
1287 assert(dirpath != base_name);
1290 *dirpath = svn_relpath_dirname(relpath, pool);
1293 *base_name = svn_relpath_basename(relpath, pool);
1297 svn_uri_dirname(const char *uri, apr_pool_t *pool)
1299 apr_size_t len = strlen(uri);
1301 assert(svn_uri_is_canonical(uri, pool));
1303 if (svn_uri_is_root(uri, len))
1304 return apr_pstrmemdup(pool, uri, len);
1306 return apr_pstrmemdup(pool, uri, uri_previous_segment(uri, len));
1310 svn_uri_basename(const char *uri, apr_pool_t *pool)
1312 apr_size_t len = strlen(uri);
1315 assert(svn_uri_is_canonical(uri, NULL));
1317 if (svn_uri_is_root(uri, len))
1321 while (start > 0 && uri[start - 1] != '/')
1324 return svn_path_uri_decode(uri + start, pool);
1328 svn_uri_split(const char **dirpath,
1329 const char **base_name,
1333 assert(dirpath != base_name);
1336 *dirpath = svn_uri_dirname(uri, pool);
1339 *base_name = svn_uri_basename(uri, pool);
1343 svn_dirent_get_longest_ancestor(const char *dirent1,
1344 const char *dirent2,
1347 return apr_pstrndup(pool, dirent1,
1348 get_longest_ancestor_length(type_dirent, dirent1,
1353 svn_relpath_get_longest_ancestor(const char *relpath1,
1354 const char *relpath2,
1357 assert(relpath_is_canonical(relpath1));
1358 assert(relpath_is_canonical(relpath2));
1360 return apr_pstrndup(pool, relpath1,
1361 get_longest_ancestor_length(type_relpath, relpath1,
1366 svn_uri_get_longest_ancestor(const char *uri1,
1370 apr_size_t uri_ancestor_len;
1373 assert(svn_uri_is_canonical(uri1, NULL));
1374 assert(svn_uri_is_canonical(uri2, NULL));
1379 /* No shared protocol => no common prefix */
1380 if (uri1[i] != uri2[i])
1381 return apr_pmemdup(pool, SVN_EMPTY_PATH,
1382 sizeof(SVN_EMPTY_PATH));
1387 /* They're both URLs, so EOS can't come before ':' */
1388 assert((uri1[i] != '\0') && (uri2[i] != '\0'));
1393 i += 3; /* Advance past '://' */
1395 uri_ancestor_len = get_longest_ancestor_length(type_uri, uri1 + i,
1398 if (uri_ancestor_len == 0 ||
1399 (uri_ancestor_len == 1 && (uri1 + i)[0] == '/'))
1400 return apr_pmemdup(pool, SVN_EMPTY_PATH, sizeof(SVN_EMPTY_PATH));
1402 return apr_pstrndup(pool, uri1, uri_ancestor_len + i);
1406 svn_dirent_is_child(const char *parent_dirent,
1407 const char *child_dirent,
1410 return is_child(type_dirent, parent_dirent, child_dirent, pool);
1414 svn_dirent_skip_ancestor(const char *parent_dirent,
1415 const char *child_dirent)
1417 apr_size_t len = strlen(parent_dirent);
1418 apr_size_t root_len;
1420 if (0 != strncmp(parent_dirent, child_dirent, len))
1421 return NULL; /* parent_dirent is no ancestor of child_dirent */
1423 if (child_dirent[len] == 0)
1424 return ""; /* parent_dirent == child_dirent */
1426 /* Child == parent + more-characters */
1428 root_len = dirent_root_length(child_dirent, strlen(child_dirent));
1430 /* Different root, e.g. ("" "/...") or ("//z" "//z/share") */
1433 /* Now, child == [root-of-parent] + [rest-of-parent] + more-characters.
1434 * It must be one of the following forms.
1436 * rlen parent child bad? rlen=len? c[len]=/?
1445 * 2 "a:b" "a:b/foo" *
1446 * 3 "a:/" "a:/foo" *
1447 * 3 "a:/b" "a:/bad" !
1448 * 3 "a:/b" "a:/b/foo" *
1449 * 5 "//s/s" "//s/s/foo" * *
1450 * 5 "//s/s/b" "//s/s/bad" !
1451 * 5 "//s/s/b" "//s/s/b/foo" *
1454 if (child_dirent[len] == '/')
1455 /* "parent|child" is one of:
1456 * "[a:]b|/foo" "[a:]/b|/foo" "//s/s|/foo" "//s/s/b|/foo" */
1457 return child_dirent + len + 1;
1459 if (root_len == len)
1460 /* "parent|child" is "|foo" "/|foo" "a:|foo" "a:/|foo" "//s/s|/foo" */
1461 return child_dirent + len;
1467 svn_relpath_skip_ancestor(const char *parent_relpath,
1468 const char *child_relpath)
1470 apr_size_t len = strlen(parent_relpath);
1472 assert(relpath_is_canonical(parent_relpath));
1473 assert(relpath_is_canonical(child_relpath));
1476 return child_relpath;
1478 if (0 != strncmp(parent_relpath, child_relpath, len))
1479 return NULL; /* parent_relpath is no ancestor of child_relpath */
1481 if (child_relpath[len] == 0)
1482 return ""; /* parent_relpath == child_relpath */
1484 if (child_relpath[len] == '/')
1485 return child_relpath + len + 1;
1493 uri_skip_ancestor(const char *parent_uri,
1494 const char *child_uri)
1496 apr_size_t len = strlen(parent_uri);
1498 assert(svn_uri_is_canonical(parent_uri, NULL));
1499 assert(svn_uri_is_canonical(child_uri, NULL));
1501 if (0 != strncmp(parent_uri, child_uri, len))
1502 return NULL; /* parent_uri is no ancestor of child_uri */
1504 if (child_uri[len] == 0)
1505 return ""; /* parent_uri == child_uri */
1507 if (child_uri[len] == '/')
1508 return child_uri + len + 1;
1514 svn_uri_skip_ancestor(const char *parent_uri,
1515 const char *child_uri,
1516 apr_pool_t *result_pool)
1518 const char *result = uri_skip_ancestor(parent_uri, child_uri);
1520 return result ? svn_path_uri_decode(result, result_pool) : NULL;
1524 svn_dirent_is_ancestor(const char *parent_dirent, const char *child_dirent)
1526 return svn_dirent_skip_ancestor(parent_dirent, child_dirent) != NULL;
1530 svn_uri__is_ancestor(const char *parent_uri, const char *child_uri)
1532 return uri_skip_ancestor(parent_uri, child_uri) != NULL;
1537 svn_dirent_is_absolute(const char *dirent)
1542 /* dirent is absolute if it starts with '/' on non-Windows platforms
1543 or with '//' on Windows platforms */
1544 if (dirent[0] == '/'
1545 #ifdef SVN_USE_DOS_PATHS
1546 && dirent[1] == '/' /* Single '/' depends on current drive */
1551 /* On Windows, dirent is also absolute when it starts with 'H:/'
1552 where 'H' is any letter. */
1553 #ifdef SVN_USE_DOS_PATHS
1554 if (((dirent[0] >= 'A' && dirent[0] <= 'Z')) &&
1555 (dirent[1] == ':') && (dirent[2] == '/'))
1557 #endif /* SVN_USE_DOS_PATHS */
1563 svn_dirent_get_absolute(const char **pabsolute,
1564 const char *relative,
1568 apr_status_t apr_err;
1569 const char *path_apr;
1571 SVN_ERR_ASSERT(! svn_path_is_url(relative));
1573 /* Merge the current working directory with the relative dirent. */
1574 SVN_ERR(svn_path_cstring_from_utf8(&path_apr, relative, pool));
1576 apr_err = apr_filepath_merge(&buffer, NULL,
1578 APR_FILEPATH_NOTRELATIVE,
1582 /* In some cases when the passed path or its ancestor(s) do not exist
1583 or no longer exist apr returns an error.
1585 In many of these cases we would like to return a path anyway, when the
1586 passed path was already a safe absolute path. So check for that now to
1589 svn_dirent_is_absolute() doesn't perform the necessary checks to see
1590 if the path doesn't need post processing to be in the canonical absolute
1594 if (svn_dirent_is_absolute(relative)
1595 && svn_dirent_is_canonical(relative, pool)
1596 && !svn_path_is_backpath_present(relative))
1598 *pabsolute = apr_pstrdup(pool, relative);
1599 return SVN_NO_ERROR;
1602 return svn_error_createf(SVN_ERR_BAD_FILENAME,
1603 svn_error_create(apr_err, NULL, NULL),
1604 _("Couldn't determine absolute path of '%s'"),
1605 svn_dirent_local_style(relative, pool));
1608 SVN_ERR(svn_path_cstring_to_utf8(pabsolute, buffer, pool));
1609 *pabsolute = svn_dirent_canonicalize(*pabsolute, pool);
1610 return SVN_NO_ERROR;
1614 svn_uri_canonicalize(const char *uri, apr_pool_t *pool)
1616 return canonicalize(type_uri, uri, pool);
1620 svn_relpath_canonicalize(const char *relpath, apr_pool_t *pool)
1622 return canonicalize(type_relpath, relpath, pool);
1626 svn_dirent_canonicalize(const char *dirent, apr_pool_t *pool)
1628 const char *dst = canonicalize(type_dirent, dirent, pool);
1630 #ifdef SVN_USE_DOS_PATHS
1631 /* Handle a specific case on Windows where path == "X:/". Here we have to
1632 append the final '/', as svn_path_canonicalize will chop this of. */
1633 if (((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
1634 (dirent[0] >= 'a' && dirent[0] <= 'z')) &&
1635 dirent[1] == ':' && dirent[2] == '/' &&
1638 char *dst_slash = apr_pcalloc(pool, 4);
1639 dst_slash[0] = canonicalize_to_upper(dirent[0]);
1642 dst_slash[3] = '\0';
1646 #endif /* SVN_USE_DOS_PATHS */
1652 svn_dirent_is_canonical(const char *dirent, apr_pool_t *scratch_pool)
1654 const char *ptr = dirent;
1658 #ifdef SVN_USE_DOS_PATHS
1659 /* Check for UNC paths */
1662 /* TODO: Scan hostname and sharename and fall back to part code */
1664 /* ### Fall back to old implementation */
1665 return (strcmp(dirent, svn_dirent_canonicalize(dirent, scratch_pool))
1668 #endif /* SVN_USE_DOS_PATHS */
1670 #ifdef SVN_USE_DOS_PATHS
1671 else if (((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= 'A' && *ptr <= 'Z')) &&
1674 /* The only canonical drive names are "A:"..."Z:", no lower case */
1675 if (*ptr < 'A' || *ptr > 'Z')
1683 #endif /* SVN_USE_DOS_PATHS */
1685 return relpath_is_canonical(ptr);
1688 static svn_boolean_t
1689 relpath_is_canonical(const char *relpath)
1691 const char *ptr = relpath, *seg = relpath;
1693 /* RELPATH is canonical if it has:
1695 * - no start and closing '/'
1699 if (*relpath == '\0')
1705 /* Now validate the rest of the path. */
1708 apr_size_t seglen = ptr - seg;
1710 if (seglen == 1 && *seg == '.')
1711 return FALSE; /* /./ */
1713 if (*ptr == '/' && *(ptr+1) == '/')
1714 return FALSE; /* // */
1716 if (! *ptr && *(ptr - 1) == '/')
1717 return FALSE; /* foo/ */
1726 while (*ptr && (*ptr != '/'))
1734 svn_relpath_is_canonical(const char *relpath)
1736 return relpath_is_canonical(relpath);
1740 svn_uri_is_canonical(const char *uri, apr_pool_t *scratch_pool)
1742 const char *ptr = uri, *seg = uri;
1743 const char *schema_data = NULL;
1745 /* URI is canonical if it has:
1746 * - lowercase URL scheme
1747 * - lowercase URL hostname
1751 * - uppercase hex-encoded pair digits ("%AB", not "%ab")
1757 if (! svn_path_is_url(uri))
1760 /* Skip the scheme. */
1761 while (*ptr && (*ptr != '/') && (*ptr != ':'))
1764 /* No scheme? No good. */
1765 if (! (*ptr == ':' && *(ptr+1) == '/' && *(ptr+2) == '/'))
1768 /* Found a scheme, check that it's all lowercase. */
1772 if (*ptr >= 'A' && *ptr <= 'Z')
1779 /* Scheme only? That works. */
1783 /* This might be the hostname */
1785 while (*ptr && (*ptr != '/') && (*ptr != '@'))
1791 /* Found a hostname, check that it's all lowercase. */
1798 || (*ptr >= '0' && *ptr <= '9')
1799 || (*ptr >= 'a' && *ptr <= 'f'))
1809 while (*ptr && *ptr != '/' && *ptr != ':')
1811 if (*ptr >= 'A' && *ptr <= 'Z')
1816 /* Found a portnumber */
1819 apr_int64_t port = 0;
1824 while (*ptr >= '0' && *ptr <= '9')
1826 port = 10 * port + (*ptr - '0');
1830 if (ptr == schema_data)
1831 return FALSE; /* Fail on "http://host:" */
1833 if (*ptr && *ptr != '/')
1834 return FALSE; /* Not a port number */
1836 if (port == 80 && strncmp(uri, "http:", 5) == 0)
1838 else if (port == 443 && strncmp(uri, "https:", 6) == 0)
1840 else if (port == 3690 && strncmp(uri, "svn:", 4) == 0)
1846 #ifdef SVN_USE_DOS_PATHS
1847 if (schema_data && *ptr == '/')
1849 /* If this is a file url, ptr now points to the third '/' in
1850 file:///C:/path. Check that if we have such a URL the drive
1851 letter is in uppercase. */
1852 if (strncmp(uri, "file:", 5) == 0 &&
1853 ! (*(ptr+1) >= 'A' && *(ptr+1) <= 'Z') &&
1857 #endif /* SVN_USE_DOS_PATHS */
1859 /* Now validate the rest of the URI. */
1862 apr_size_t seglen = ptr - seg;
1864 if (seglen == 1 && *seg == '.')
1865 return FALSE; /* /./ */
1867 if (*ptr == '/' && *(ptr+1) == '/')
1868 return FALSE; /* // */
1870 if (! *ptr && *(ptr - 1) == '/' && ptr - 1 != uri)
1871 return FALSE; /* foo/ */
1881 while (*ptr && (*ptr != '/'))
1894 /* Can't usesvn_ctype_isxdigit() because lower case letters are
1895 not in our canonical format */
1896 if (((*(ptr+1) < '0' || *(ptr+1) > '9'))
1897 && (*(ptr+1) < 'A' || *(ptr+1) > 'F'))
1899 else if (((*(ptr+2) < '0' || *(ptr+2) > '9'))
1900 && (*(ptr+2) < 'A' || *(ptr+2) > 'F'))
1903 digitz[0] = *(++ptr);
1904 digitz[1] = *(++ptr);
1906 val = (int)strtol(digitz, NULL, 16);
1908 if (svn_uri__char_validity[val])
1909 return FALSE; /* Should not have been escaped */
1911 else if (*ptr != '/' && !svn_uri__char_validity[(unsigned char)*ptr])
1912 return FALSE; /* Character should have been escaped */
1920 svn_dirent_condense_targets(const char **pcommon,
1921 apr_array_header_t **pcondensed_targets,
1922 const apr_array_header_t *targets,
1923 svn_boolean_t remove_redundancies,
1924 apr_pool_t *result_pool,
1925 apr_pool_t *scratch_pool)
1927 int i, num_condensed = targets->nelts;
1928 svn_boolean_t *removed;
1929 apr_array_header_t *abs_targets;
1931 /* Early exit when there's no data to work on. */
1932 if (targets->nelts <= 0)
1935 if (pcondensed_targets)
1936 *pcondensed_targets = NULL;
1937 return SVN_NO_ERROR;
1940 /* Get the absolute path of the first target. */
1941 SVN_ERR(svn_dirent_get_absolute(pcommon,
1942 APR_ARRAY_IDX(targets, 0, const char *),
1945 /* Early exit when there's only one dirent to work on. */
1946 if (targets->nelts == 1)
1948 *pcommon = apr_pstrdup(result_pool, *pcommon);
1949 if (pcondensed_targets)
1950 *pcondensed_targets = apr_array_make(result_pool, 0,
1951 sizeof(const char *));
1952 return SVN_NO_ERROR;
1955 /* Copy the targets array, but with absolute dirents instead of
1956 relative. Also, find the pcommon argument by finding what is
1957 common in all of the absolute dirents. NOTE: This is not as
1958 efficient as it could be. The calculation of the basedir could
1959 be done in the loop below, which would save some calls to
1960 svn_dirent_get_longest_ancestor. I decided to do it this way
1961 because I thought it would be simpler, since this way, we don't
1962 even do the loop if we don't need to condense the targets. */
1964 removed = apr_pcalloc(scratch_pool, (targets->nelts *
1965 sizeof(svn_boolean_t)));
1966 abs_targets = apr_array_make(scratch_pool, targets->nelts,
1967 sizeof(const char *));
1969 APR_ARRAY_PUSH(abs_targets, const char *) = *pcommon;
1971 for (i = 1; i < targets->nelts; ++i)
1973 const char *rel = APR_ARRAY_IDX(targets, i, const char *);
1974 const char *absolute;
1975 SVN_ERR(svn_dirent_get_absolute(&absolute, rel, scratch_pool));
1976 APR_ARRAY_PUSH(abs_targets, const char *) = absolute;
1977 *pcommon = svn_dirent_get_longest_ancestor(*pcommon, absolute,
1981 *pcommon = apr_pstrdup(result_pool, *pcommon);
1983 if (pcondensed_targets != NULL)
1987 if (remove_redundancies)
1989 /* Find the common part of each pair of targets. If
1990 common part is equal to one of the dirents, the other
1991 is a child of it, and can be removed. If a target is
1992 equal to *pcommon, it can also be removed. */
1994 /* First pass: when one non-removed target is a child of
1995 another non-removed target, remove the child. */
1996 for (i = 0; i < abs_targets->nelts; ++i)
2003 for (j = i + 1; j < abs_targets->nelts; ++j)
2005 const char *abs_targets_i;
2006 const char *abs_targets_j;
2007 const char *ancestor;
2012 abs_targets_i = APR_ARRAY_IDX(abs_targets, i, const char *);
2013 abs_targets_j = APR_ARRAY_IDX(abs_targets, j, const char *);
2015 ancestor = svn_dirent_get_longest_ancestor
2016 (abs_targets_i, abs_targets_j, scratch_pool);
2018 if (*ancestor == '\0')
2021 if (strcmp(ancestor, abs_targets_i) == 0)
2026 else if (strcmp(ancestor, abs_targets_j) == 0)
2034 /* Second pass: when a target is the same as *pcommon,
2035 remove the target. */
2036 for (i = 0; i < abs_targets->nelts; ++i)
2038 const char *abs_targets_i = APR_ARRAY_IDX(abs_targets, i,
2041 if ((strcmp(abs_targets_i, *pcommon) == 0) && (! removed[i]))
2049 /* Now create the return array, and copy the non-removed items */
2050 basedir_len = strlen(*pcommon);
2051 *pcondensed_targets = apr_array_make(result_pool, num_condensed,
2052 sizeof(const char *));
2054 for (i = 0; i < abs_targets->nelts; ++i)
2056 const char *rel_item = APR_ARRAY_IDX(abs_targets, i, const char *);
2058 /* Skip this if it's been removed. */
2062 /* If a common prefix was found, condensed_targets are given
2063 relative to that prefix. */
2064 if (basedir_len > 0)
2066 /* Only advance our pointer past a dirent separator if
2067 REL_ITEM isn't the same as *PCOMMON.
2069 If *PCOMMON is a root dirent, basedir_len will already
2070 include the closing '/', so never advance the pointer
2073 rel_item += basedir_len;
2075 ! svn_dirent_is_root(*pcommon, basedir_len))
2079 APR_ARRAY_PUSH(*pcondensed_targets, const char *)
2080 = apr_pstrdup(result_pool, rel_item);
2084 return SVN_NO_ERROR;
2088 svn_uri_condense_targets(const char **pcommon,
2089 apr_array_header_t **pcondensed_targets,
2090 const apr_array_header_t *targets,
2091 svn_boolean_t remove_redundancies,
2092 apr_pool_t *result_pool,
2093 apr_pool_t *scratch_pool)
2095 int i, num_condensed = targets->nelts;
2096 apr_array_header_t *uri_targets;
2097 svn_boolean_t *removed;
2099 /* Early exit when there's no data to work on. */
2100 if (targets->nelts <= 0)
2103 if (pcondensed_targets)
2104 *pcondensed_targets = NULL;
2105 return SVN_NO_ERROR;
2108 *pcommon = svn_uri_canonicalize(APR_ARRAY_IDX(targets, 0, const char *),
2111 /* Early exit when there's only one uri to work on. */
2112 if (targets->nelts == 1)
2114 *pcommon = apr_pstrdup(result_pool, *pcommon);
2115 if (pcondensed_targets)
2116 *pcondensed_targets = apr_array_make(result_pool, 0,
2117 sizeof(const char *));
2118 return SVN_NO_ERROR;
2121 /* Find the pcommon argument by finding what is common in all of the
2122 uris. NOTE: This is not as efficient as it could be. The calculation
2123 of the basedir could be done in the loop below, which would
2124 save some calls to svn_uri_get_longest_ancestor. I decided to do it
2125 this way because I thought it would be simpler, since this way, we don't
2126 even do the loop if we don't need to condense the targets. */
2128 removed = apr_pcalloc(scratch_pool, (targets->nelts *
2129 sizeof(svn_boolean_t)));
2130 uri_targets = apr_array_make(scratch_pool, targets->nelts,
2131 sizeof(const char *));
2133 APR_ARRAY_PUSH(uri_targets, const char *) = *pcommon;
2135 for (i = 1; i < targets->nelts; ++i)
2137 const char *uri = svn_uri_canonicalize(
2138 APR_ARRAY_IDX(targets, i, const char *),
2140 APR_ARRAY_PUSH(uri_targets, const char *) = uri;
2142 /* If the commonmost ancestor so far is empty, there's no point
2143 in continuing to search for a common ancestor at all. But
2144 we'll keep looping for the sake of canonicalizing the
2145 targets, I suppose. */
2146 if (**pcommon != '\0')
2147 *pcommon = svn_uri_get_longest_ancestor(*pcommon, uri,
2151 *pcommon = apr_pstrdup(result_pool, *pcommon);
2153 if (pcondensed_targets != NULL)
2157 if (remove_redundancies)
2159 /* Find the common part of each pair of targets. If
2160 common part is equal to one of the dirents, the other
2161 is a child of it, and can be removed. If a target is
2162 equal to *pcommon, it can also be removed. */
2164 /* First pass: when one non-removed target is a child of
2165 another non-removed target, remove the child. */
2166 for (i = 0; i < uri_targets->nelts; ++i)
2173 for (j = i + 1; j < uri_targets->nelts; ++j)
2177 const char *ancestor;
2182 uri_i = APR_ARRAY_IDX(uri_targets, i, const char *);
2183 uri_j = APR_ARRAY_IDX(uri_targets, j, const char *);
2185 ancestor = svn_uri_get_longest_ancestor(uri_i,
2189 if (*ancestor == '\0')
2192 if (strcmp(ancestor, uri_i) == 0)
2197 else if (strcmp(ancestor, uri_j) == 0)
2205 /* Second pass: when a target is the same as *pcommon,
2206 remove the target. */
2207 for (i = 0; i < uri_targets->nelts; ++i)
2209 const char *uri_targets_i = APR_ARRAY_IDX(uri_targets, i,
2212 if ((strcmp(uri_targets_i, *pcommon) == 0) && (! removed[i]))
2220 /* Now create the return array, and copy the non-removed items */
2221 basedir_len = strlen(*pcommon);
2222 *pcondensed_targets = apr_array_make(result_pool, num_condensed,
2223 sizeof(const char *));
2225 for (i = 0; i < uri_targets->nelts; ++i)
2227 const char *rel_item = APR_ARRAY_IDX(uri_targets, i, const char *);
2229 /* Skip this if it's been removed. */
2233 /* If a common prefix was found, condensed_targets are given
2234 relative to that prefix. */
2235 if (basedir_len > 0)
2237 /* Only advance our pointer past a dirent separator if
2238 REL_ITEM isn't the same as *PCOMMON.
2240 If *PCOMMON is a root dirent, basedir_len will already
2241 include the closing '/', so never advance the pointer
2244 rel_item += basedir_len;
2245 if ((rel_item[0] == '/') ||
2246 (rel_item[0] && !svn_uri_is_root(*pcommon, basedir_len)))
2252 APR_ARRAY_PUSH(*pcondensed_targets, const char *)
2253 = svn_path_uri_decode(rel_item, result_pool);
2257 return SVN_NO_ERROR;
2261 svn_dirent_is_under_root(svn_boolean_t *under_root,
2262 const char **result_path,
2263 const char *base_path,
2265 apr_pool_t *result_pool)
2267 apr_status_t status;
2270 *under_root = FALSE;
2272 *result_path = NULL;
2274 status = apr_filepath_merge(&full_path,
2277 APR_FILEPATH_NOTABOVEROOT
2278 | APR_FILEPATH_SECUREROOTTEST,
2281 if (status == APR_SUCCESS)
2284 *result_path = svn_dirent_canonicalize(full_path, result_pool);
2286 return SVN_NO_ERROR;
2288 else if (status == APR_EABOVEROOT)
2290 *under_root = FALSE;
2291 return SVN_NO_ERROR;
2294 return svn_error_wrap_apr(status, NULL);
2298 svn_uri_get_dirent_from_file_url(const char **dirent,
2302 const char *hostname, *path;
2304 SVN_ERR_ASSERT(svn_uri_is_canonical(url, pool));
2306 /* Verify that the URL is well-formed (loosely) */
2308 /* First, check for the "file://" prefix. */
2309 if (strncmp(url, "file://", 7) != 0)
2310 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2311 _("Local URL '%s' does not contain 'file://' "
2314 /* Find the HOSTNAME portion and the PATH portion of the URL. The host
2315 name is between the "file://" prefix and the next occurence of '/'. We
2316 are considering everything from that '/' until the end of the URL to be
2317 the absolute path portion of the URL.
2318 If we got just "file://", treat it the same as "file:///". */
2320 path = strchr(hostname, '/');
2322 hostname = apr_pstrmemdup(pool, hostname, path - hostname);
2326 /* URI-decode HOSTNAME, and set it to NULL if it is "" or "localhost". */
2327 if (*hostname == '\0')
2331 hostname = svn_path_uri_decode(hostname, pool);
2332 if (strcmp(hostname, "localhost") == 0)
2336 /* Duplicate the URL, starting at the top of the path.
2337 At the same time, we URI-decode the path. */
2338 #ifdef SVN_USE_DOS_PATHS
2339 /* On Windows, we'll typically have to skip the leading / if the
2340 path starts with a drive letter. Like most Web browsers, We
2341 support two variants of this scheme:
2346 Note that, at least on WinNT and above, file:////./X:/path will
2347 also work, so we must make sure the transformation doesn't break
2348 that, and file:///path (that looks within the current drive
2349 only) should also keep working.
2350 If we got a non-empty hostname other than localhost, we convert this
2351 into an UNC path. In this case, we obviously don't strip the slash
2352 even if the path looks like it starts with a drive letter.
2355 static const char valid_drive_letters[] =
2356 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2357 /* Casting away const! */
2358 char *dup_path = (char *)svn_path_uri_decode(path, pool);
2360 /* This check assumes ':' and '|' are already decoded! */
2361 if (!hostname && dup_path[1] && strchr(valid_drive_letters, dup_path[1])
2362 && (dup_path[2] == ':' || dup_path[2] == '|'))
2364 /* Skip the leading slash. */
2367 if (dup_path[1] == '|')
2370 if (dup_path[2] == '/' || dup_path[2] == '\0')
2372 if (dup_path[2] == '\0')
2374 /* A valid dirent for the driveroot must be like "C:/" instead of
2375 just "C:" or svn_dirent_join() will use the current directory
2376 on the drive instead */
2377 char *new_path = apr_pcalloc(pool, 4);
2378 new_path[0] = dup_path[0];
2382 dup_path = new_path;
2388 if (dup_path[0] == '/' && dup_path[1] == '\0')
2389 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2390 _("Local URL '%s' contains only a hostname, "
2393 /* We still know that the path starts with a slash. */
2394 *dirent = apr_pstrcat(pool, "//", hostname, dup_path, NULL);
2399 #else /* !SVN_USE_DOS_PATHS */
2400 /* Currently, the only hostnames we are allowing on non-Win32 platforms
2401 are the empty string and 'localhost'. */
2403 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2404 _("Local URL '%s' contains unsupported hostname"),
2407 *dirent = svn_path_uri_decode(path, pool);
2408 #endif /* SVN_USE_DOS_PATHS */
2409 return SVN_NO_ERROR;
2413 svn_uri_get_file_url_from_dirent(const char **url,
2417 assert(svn_dirent_is_canonical(dirent, pool));
2419 SVN_ERR(svn_dirent_get_absolute(&dirent, dirent, pool));
2421 dirent = svn_path_uri_encode(dirent, pool);
2423 #ifndef SVN_USE_DOS_PATHS
2424 if (dirent[0] == '/' && dirent[1] == '\0')
2425 dirent = NULL; /* "file://" is the canonical form of "file:///" */
2427 *url = apr_pstrcat(pool, "file://", dirent, (char *)NULL);
2429 if (dirent[0] == '/')
2431 /* Handle UNC paths //server/share -> file://server/share */
2432 assert(dirent[1] == '/'); /* Expect UNC, not non-absolute */
2434 *url = apr_pstrcat(pool, "file:", dirent, NULL);
2438 char *uri = apr_pstrcat(pool, "file:///", dirent, NULL);
2439 apr_size_t len = 8 /* strlen("file:///") */ + strlen(dirent);
2441 /* "C:/" is a canonical dirent on Windows,
2442 but "file:///C:/" is not a canonical uri */
2443 if (uri[len-1] == '/')
2450 return SVN_NO_ERROR;
2455 /* -------------- The fspath API (see private/svn_fspath.h) -------------- */
2458 svn_fspath__is_canonical(const char *fspath)
2460 return fspath[0] == '/' && relpath_is_canonical(fspath + 1);
2465 svn_fspath__canonicalize(const char *fspath,
2468 if ((fspath[0] == '/') && (fspath[1] == '\0'))
2471 return apr_pstrcat(pool, "/", svn_relpath_canonicalize(fspath, pool),
2477 svn_fspath__is_root(const char *fspath, apr_size_t len)
2479 /* directory is root if it's equal to '/' */
2480 return (len == 1 && fspath[0] == '/');
2485 svn_fspath__skip_ancestor(const char *parent_fspath,
2486 const char *child_fspath)
2488 assert(svn_fspath__is_canonical(parent_fspath));
2489 assert(svn_fspath__is_canonical(child_fspath));
2491 return svn_relpath_skip_ancestor(parent_fspath + 1, child_fspath + 1);
2496 svn_fspath__dirname(const char *fspath,
2499 assert(svn_fspath__is_canonical(fspath));
2501 if (fspath[0] == '/' && fspath[1] == '\0')
2502 return apr_pstrdup(pool, fspath);
2504 return apr_pstrcat(pool, "/", svn_relpath_dirname(fspath + 1, pool),
2510 svn_fspath__basename(const char *fspath,
2514 assert(svn_fspath__is_canonical(fspath));
2516 result = svn_relpath_basename(fspath + 1, pool);
2518 assert(strchr(result, '/') == NULL);
2523 svn_fspath__split(const char **dirpath,
2524 const char **base_name,
2526 apr_pool_t *result_pool)
2528 assert(dirpath != base_name);
2531 *dirpath = svn_fspath__dirname(fspath, result_pool);
2534 *base_name = svn_fspath__basename(fspath, result_pool);
2538 svn_fspath__join(const char *fspath,
2539 const char *relpath,
2540 apr_pool_t *result_pool)
2543 assert(svn_fspath__is_canonical(fspath));
2544 assert(svn_relpath_is_canonical(relpath));
2546 if (relpath[0] == '\0')
2547 result = apr_pstrdup(result_pool, fspath);
2548 else if (fspath[1] == '\0')
2549 result = apr_pstrcat(result_pool, "/", relpath, (char *)NULL);
2551 result = apr_pstrcat(result_pool, fspath, "/", relpath, (char *)NULL);
2553 assert(svn_fspath__is_canonical(result));
2558 svn_fspath__get_longest_ancestor(const char *fspath1,
2559 const char *fspath2,
2560 apr_pool_t *result_pool)
2563 assert(svn_fspath__is_canonical(fspath1));
2564 assert(svn_fspath__is_canonical(fspath2));
2566 result = apr_pstrcat(result_pool, "/",
2567 svn_relpath_get_longest_ancestor(fspath1 + 1,
2572 assert(svn_fspath__is_canonical(result));
2579 /* -------------- The urlpath API (see private/svn_fspath.h) ------------- */
2582 svn_urlpath__canonicalize(const char *uri,
2585 if (svn_path_is_url(uri))
2587 uri = svn_uri_canonicalize(uri, pool);
2591 uri = svn_fspath__canonicalize(uri, pool);
2592 /* Do a little dance to normalize hex encoding. */
2593 uri = svn_path_uri_decode(uri, pool);
2594 uri = svn_path_uri_encode(uri, pool);