2 * dirent_uri.c: a library to manipulate URIs and directory entries.
\r
4 * ====================================================================
\r
5 * Copyright (c) 2008-2009 CollabNet. All rights reserved.
\r
7 * This software is licensed as described in the file COPYING, which
\r
8 * you should have received as part of this distribution. The terms
\r
9 * are also available at http://subversion.tigris.org/license-1.html.
\r
10 * If newer versions of this license are posted there, you may use a
\r
11 * newer version instead, at your option.
\r
13 * This software consists of voluntary contributions made by many
\r
14 * individuals. For exact contribution history, see the revision
\r
15 * history and logs, available at http://subversion.tigris.org/.
\r
16 * ====================================================================
\r
25 #include <apr_uri.h>
\r
27 //#include "svn_private_config.h"
\r
28 #include "svn_string.h"
\r
29 #include "svn_dirent_uri.h"
\r
30 #include "svn_path.h"
\r
32 //#include "private_uri.h"
\r
33 #define SVN_PATH_LOCAL_SEPARATOR '\\'
\r
35 svn_uri_canonicalize(const char *uri, apr_pool_t *pool);
\r
37 /* The canonical empty path. Can this be changed? Well, change the empty
\r
38 test below and the path library will work, not so sure about the fs/wc
\r
40 #define SVN_EMPTY_PATH ""
\r
42 /* TRUE if s is the canonical empty path, FALSE otherwise */
\r
43 #define SVN_PATH_IS_EMPTY(s) ((s)[0] == '\0')
\r
45 /* TRUE if s,n is the platform's empty path ("."), FALSE otherwise. Can
\r
46 this be changed? Well, the path library will work, not so sure about
\r
48 #define SVN_PATH_IS_PLATFORM_EMPTY(s,n) ((n) == 1 && (s)[0] == '.')
\r
50 /* Path type definition. Used only by internal functions. */
\r
57 /**** Internal implementation functions *****/
\r
59 /* Return an internal-style new path based on PATH, allocated in POOL.
\r
60 * Pass type_uri for TYPE if PATH is a uri and type_dirent if PATH
\r
61 * is a regular path.
\r
63 * "Internal-style" means that separators are all '/', and the new
\r
64 * path is canonicalized.
\r
67 internal_style(path_type_t type, const char *path, apr_pool_t *pool)
\r
69 #if '/' != SVN_PATH_LOCAL_SEPARATOR
\r
71 char *p = apr_pstrdup(pool, path);
\r
74 /* Convert all local-style separators to the canonical ones. */
\r
75 for (; *p != '\0'; ++p)
\r
76 if (*p == SVN_PATH_LOCAL_SEPARATOR)
\r
81 return type == type_uri ? svn_uri_canonicalize(path, pool)
\r
82 : svn_dirent_canonicalize(path, pool);
\r
83 /* FIXME: Should also remove trailing /.'s, if the style says so. */
\r
86 /* Return a local-style new path based on PATH, allocated in POOL.
\r
87 * Pass type_uri for TYPE if PATH is a uri and type_dirent if PATH
\r
88 * is a regular path.
\r
90 * "Local-style" means a path that looks like what users are
\r
91 * accustomed to seeing, including native separators. The new path
\r
92 * will still be canonicalized.
\r
95 local_style(path_type_t type, const char *path, apr_pool_t *pool)
\r
97 path = type == type_uri ? svn_uri_canonicalize(path, pool)
\r
98 : svn_dirent_canonicalize(path, pool);
\r
99 /* FIXME: Should also remove trailing /.'s, if the style says so. */
\r
101 /* Internally, Subversion represents the current directory with the
\r
102 empty string. But users like to see "." . */
\r
103 if (SVN_PATH_IS_EMPTY(path))
\r
106 /* If PATH is a URL, the "local style" is the same as the input. */
\r
107 if (type == type_uri && svn_path_is_url(path))
\r
108 return apr_pstrdup(pool, path);
\r
110 #if '/' != SVN_PATH_LOCAL_SEPARATOR
\r
112 char *p = apr_pstrdup(pool, path);
\r
115 /* Convert all canonical separators to the local-style ones. */
\r
116 for (; *p != '\0'; ++p)
\r
118 *p = SVN_PATH_LOCAL_SEPARATOR;
\r
125 /* Locale insensitive tolower() for converting parts of dirents and urls
\r
126 while canonicalizing */
\r
128 canonicalize_to_lower(char c)
\r
130 if (c < 'A' || c > 'Z')
\r
133 return c - 'A' + 'a';
\r
135 #if defined(WIN32) || defined(__CYGWIN__)
\r
136 /* Locale insensitive toupper() for converting parts of dirents and urls
\r
137 while canonicalizing */
\r
139 canonicalize_to_upper(char c)
\r
141 if (c < 'a' || c > 'z')
\r
144 return c - 'a' + 'A';
\r
148 /* Return the length of substring necessary to encompass the entire
\r
149 * previous dirent segment in DIRENT, which should be a LEN byte string.
\r
151 * A trailing slash will not be included in the returned length except
\r
152 * in the case in which DIRENT is absolute and there are no more
\r
153 * previous segments.
\r
156 dirent_previous_segment(const char *dirent,
\r
163 while (len > 0 && dirent[len] != '/'
\r
164 #if defined(WIN32) || defined(__CYGWIN__)
\r
165 && dirent[len] != ':'
\r
166 #endif /* WIN32 or Cygwin */
\r
170 /* check if the remaining segment including trailing '/' is a root dirent */
\r
171 if (svn_dirent_is_root(dirent, len + 1))
\r
177 /* Return the length of substring necessary to encompass the entire
\r
178 * previous uri segment in URI, which should be a LEN byte string.
\r
180 * A trailing slash will not be included in the returned length except
\r
181 * in the case in which URI is absolute and there are no more
\r
182 * previous segments.
\r
185 uri_previous_segment(const char *uri,
\r
188 /* ### Still the old path segment code, should start checking scheme specific format */
\r
193 while (len > 0 && uri[len] != '/')
\r
196 /* check if the remaining segment including trailing '/' is a root dirent */
\r
197 if (svn_uri_is_root(uri, len + 1))
\r
203 /* Return the canonicalized version of PATH, allocated in POOL.
\r
204 * Pass type_uri for TYPE if PATH is a uri and type_dirent if PATH
\r
205 * is a regular path.
\r
207 static const char *
\r
208 canonicalize(path_type_t type, const char *path, apr_pool_t *pool)
\r
213 apr_size_t schemelen = 0;
\r
214 apr_size_t canon_segments = 0;
\r
215 svn_boolean_t url = FALSE;
\r
217 /* "" is already canonical, so just return it; note that later code
\r
218 depends on path not being zero-length. */
\r
219 if (SVN_PATH_IS_EMPTY(path))
\r
222 dst = canon = apr_pcalloc(pool, strlen(path) + 1);
\r
224 /* Try to parse the path as an URI. */
\r
228 if (type == type_uri && *src != '/')
\r
230 while (*src && (*src != '/') && (*src != ':'))
\r
233 if (*src == ':' && *(src+1) == '/' && *(src+2) == '/')
\r
239 /* Found a scheme, convert to lowercase and copy to dst. */
\r
241 while (*src != ':')
\r
243 *(dst++) = canonicalize_to_lower((*src++));
\r
252 /* This might be the hostname */
\r
254 while (*src && (*src != '/') && (*src != '@'))
\r
259 /* Copy the username & password. */
\r
260 seglen = src - seg + 1;
\r
261 memcpy(dst, seg, seglen);
\r
268 /* Found a hostname, convert to lowercase and copy to dst. */
\r
269 while (*src && (*src != '/'))
\r
270 *(dst++) = canonicalize_to_lower((*src++));
\r
272 /* Copy trailing slash, or null-terminator. */
\r
275 /* Move src and dst forward only if we are not
\r
276 * at null-terminator yet. */
\r
283 canon_segments = 1;
\r
290 /* If this is an absolute path, then just copy over the initial
\r
291 separator character. */
\r
294 *(dst++) = *(src++);
\r
296 #if defined(WIN32) || defined(__CYGWIN__)
\r
297 /* On Windows permit two leading separator characters which means an
\r
299 if ((type == type_dirent) && *src == '/')
\r
300 *(dst++) = *(src++);
\r
301 #endif /* WIN32 or Cygwin */
\r
307 /* Parse each segment, find the closing '/' */
\r
308 const char *next = src;
\r
309 while (*next && (*next != '/'))
\r
312 seglen = next - src;
\r
314 if (seglen == 0 || (seglen == 1 && src[0] == '.'))
\r
316 /* Noop segment, so do nothing. */
\r
318 #if defined(WIN32) || defined(__CYGWIN__)
\r
319 /* If this is the first path segment of a file:// URI and it contains a
\r
320 windows drive letter, convert the drive letter to upper case. */
\r
321 else if (url && canon_segments == 1 && seglen == 2 &&
\r
322 (strncmp(canon, "file:", 5) == 0) &&
\r
323 src[0] >= 'a' && src[0] <= 'z' && src[1] == ':')
\r
325 *(dst++) = canonicalize_to_upper(src[0]);
\r
331 #endif /* WIN32 or Cygwin */
\r
334 /* An actual segment, append it to the destination path */
\r
337 memcpy(dst, src, seglen);
\r
342 /* Skip over trailing slash to the next segment. */
\r
348 /* Remove the trailing slash if there was at least one
\r
349 * canonical segment and the last segment ends with a slash.
\r
351 * But keep in mind that, for URLs, the scheme counts as a
\r
352 * canonical segment -- so if path is ONLY a scheme (such
\r
353 * as "https://") we should NOT remove the trailing slash. */
\r
354 if ((canon_segments > 0 && *(dst - 1) == '/')
\r
355 && ! (url && path[schemelen] == '\0'))
\r
362 #if defined(WIN32) || defined(__CYGWIN__)
\r
363 /* Skip leading double slashes when there are less than 2
\r
364 * canon segments. UNC paths *MUST* have two segments. */
\r
365 if ((type == type_dirent) && canon[0] == '/' && canon[1] == '/')
\r
367 if (canon_segments < 2)
\r
371 /* Now we're sure this is a valid UNC path, convert the server name
\r
372 (the first path segment) to lowercase as Windows treats it as case
\r
374 Note: normally the share name is treated as case insensitive too,
\r
375 but it seems to be possible to configure Samba to treat those as
\r
376 case sensitive, so better leave that alone. */
\r
378 while (*dst && *dst != '/')
\r
379 *(dst++) = canonicalize_to_lower(*dst);
\r
382 #endif /* WIN32 or Cygwin */
\r
387 /* Return the string length of the longest common ancestor of PATH1 and PATH2.
\r
388 * Pass type_uri for TYPE if PATH1 and PATH2 are URIs, and type_dirent if
\r
389 * PATH1 and PATH2 are regular paths.
\r
391 * If the two paths do not share a common ancestor, return 0.
\r
393 * New strings are allocated in POOL.
\r
396 get_longest_ancestor_length(path_type_t types,
\r
401 apr_size_t path1_len, path2_len;
\r
403 apr_size_t last_dirsep = 0;
\r
404 #if defined(WIN32) || defined(__CYGWIN__)
\r
405 svn_boolean_t unc = FALSE;
\r
408 path1_len = strlen(path1);
\r
409 path2_len = strlen(path2);
\r
411 if (SVN_PATH_IS_EMPTY(path1) || SVN_PATH_IS_EMPTY(path2))
\r
414 while (path1[i] == path2[i])
\r
416 /* Keep track of the last directory separator we hit. */
\r
417 if (path1[i] == '/')
\r
422 /* If we get to the end of either path, break out. */
\r
423 if ((i == path1_len) || (i == path2_len))
\r
427 /* two special cases:
\r
428 1. '/' is the longest common ancestor of '/' and '/foo' */
\r
429 if (i == 1 && path1[0] == '/' && path2[0] == '/')
\r
431 /* 2. '' is the longest common ancestor of any non-matching
\r
432 * strings 'foo' and 'bar' */
\r
433 if (types == type_dirent && i == 0)
\r
436 /* Handle some windows specific cases */
\r
437 #if defined(WIN32) || defined(__CYGWIN__)
\r
438 if (types == type_dirent)
\r
440 /* don't count the '//' from UNC paths */
\r
441 if (last_dirsep == 1 && path1[0] == '/' && path1[1] == '/')
\r
447 /* X:/ and X:/foo */
\r
448 if (i == 3 && path1[2] == '/' && path1[1] == ':')
\r
451 /* Cannot use SVN_ERR_ASSERT here, so we'll have to crash, sorry.
\r
452 * Note that this assertion triggers only if the code above has
\r
453 * been broken. The code below relies on this assertion, because
\r
454 * it uses [i - 1] as index. */
\r
458 if ((path1[i - 1] == ':' && path2[i] == '/') ||
\r
459 (path2[i - 1] == ':' && path1[i] == '/'))
\r
462 if (path1[i - 1] == ':' || path2[i - 1] == ':')
\r
465 #endif /* WIN32 or Cygwin */
\r
467 /* last_dirsep is now the offset of the last directory separator we
\r
468 crossed before reaching a non-matching byte. i is the offset of
\r
469 that non-matching byte, and is guaranteed to be <= the length of
\r
470 whichever path is shorter.
\r
471 If one of the paths is the common part return that. */
\r
472 if (((i == path1_len) && (path2[i] == '/'))
\r
473 || ((i == path2_len) && (path1[i] == '/'))
\r
474 || ((i == path1_len) && (i == path2_len)))
\r
478 /* Nothing in common but the root folder '/' or 'X:/' for Windows
\r
480 #if defined(WIN32) || defined(__CYGWIN__)
\r
483 /* X:/foo and X:/bar returns X:/ */
\r
484 if ((types == type_dirent) &&
\r
485 last_dirsep == 2 && path1[1] == ':' && path1[2] == '/'
\r
486 && path2[1] == ':' && path2[2] == '/')
\r
489 if (last_dirsep == 0 && path1[0] == '/' && path2[0] == '/')
\r
491 #if defined(WIN32) || defined(__CYGWIN__)
\r
496 return last_dirsep;
\r
499 /* Determine whether PATH2 is a child of PATH1.
\r
501 * PATH2 is a child of PATH1 if
\r
502 * 1) PATH1 is empty, and PATH2 is not empty and not an absolute path.
\r
504 * 2) PATH2 is has n components, PATH1 has x < n components,
\r
505 * and PATH1 matches PATH2 in all its x components.
\r
506 * Components are separated by a slash, '/'.
\r
508 * Pass type_uri for TYPE if PATH1 and PATH2 are URIs, and type_dirent if
\r
509 * PATH1 and PATH2 are regular paths.
\r
511 * If PATH2 is not a child of PATH1, return NULL.
\r
513 * If PATH2 is a child of PATH1, and POOL is not NULL, allocate a copy
\r
514 * of the child part of PATH2 in POOL and return a pointer to the
\r
515 * newly allocated child part.
\r
517 * If PATH2 is a child of PATH1, and POOL is NULL, return a pointer
\r
518 * pointing to the child part of PATH2.
\r
520 static const char *
\r
521 is_child(path_type_t type, const char *path1, const char *path2,
\r
526 /* Allow "" and "foo" or "H:foo" to be parent/child */
\r
527 if (SVN_PATH_IS_EMPTY(path1)) /* "" is the parent */
\r
529 if (SVN_PATH_IS_EMPTY(path2)) /* "" not a child */
\r
532 /* check if this is an absolute path */
\r
533 if ((type == type_uri && svn_uri_is_absolute(path2)) ||
\r
534 (type == type_dirent && svn_dirent_is_absolute(path2)))
\r
537 /* everything else is child */
\r
538 return pool ? apr_pstrdup(pool, path2) : path2;
\r
541 /* Reach the end of at least one of the paths. How should we handle
\r
542 things like path1:"foo///bar" and path2:"foo/bar/baz"? It doesn't
\r
543 appear to arise in the current Subversion code, it's not clear to me
\r
544 if they should be parent/child or not. */
\r
545 /* Hmmm... aren't paths assumed to be canonical in this function?
\r
546 * How can "foo///bar" even happen if the paths are canonical? */
\r
547 for (i = 0; path1[i] && path2[i]; i++)
\r
548 if (path1[i] != path2[i])
\r
551 /* FIXME: This comment does not really match
\r
552 * the checks made in the code it refers to: */
\r
553 /* There are two cases that are parent/child
\r
554 ... path1[i] == '\0'
\r
555 .../foo path2[i] == '/'
\r
558 /foo path2[i] != '/'
\r
560 Other root paths (like X:/) fall under the former case:
\r
561 X:/ path1[i] == '\0'
\r
562 X:/foo path2[i] != '/'
\r
564 Check for '//' to avoid matching '/' and '//srv'.
\r
566 if (path1[i] == '\0' && path2[i])
\r
568 if (path1[i - 1] == '/'
\r
569 #if defined(WIN32) || defined(__CYGWIN__)
\r
570 || ((type == type_dirent) && path1[i - 1] == ':')
\r
571 #endif /* WIN32 or Cygwin */
\r
574 if (path2[i] == '/')
\r
583 return pool ? apr_pstrdup(pool, path2 + i) : path2 + i;
\r
585 else if (path2[i] == '/')
\r
591 return pool ? apr_pstrdup(pool, path2 + i + 1) : path2 + i + 1;
\r
600 /* Otherwise, path2 isn't a child. */
\r
604 /* FIXME: no doc string */
\r
605 static svn_boolean_t
\r
606 is_ancestor(path_type_t type, const char *path1, const char *path2)
\r
608 apr_size_t path1_len;
\r
610 /* If path1 is empty and path2 is not absolute, then path1 is an ancestor. */
\r
611 if (SVN_PATH_IS_EMPTY(path1))
\r
613 return type == type_uri ? ! svn_uri_is_absolute(path2)
\r
614 : ! svn_dirent_is_absolute(path2);
\r
617 /* If path1 is a prefix of path2, then:
\r
618 - If path1 ends in a path separator,
\r
619 - If the paths are of the same length
\r
621 - path2 starts a new path component after the common prefix,
\r
622 then path1 is an ancestor. */
\r
623 path1_len = strlen(path1);
\r
624 if (strncmp(path1, path2, path1_len) == 0)
\r
625 return path1[path1_len - 1] == '/'
\r
626 #if defined(WIN32) || defined(__CYGWIN__)
\r
627 || ((type == type_dirent) && path1[path1_len - 1] == ':')
\r
628 #endif /* WIN32 or Cygwin */
\r
629 || (path2[path1_len] == '/' || path2[path1_len] == '\0');
\r
635 /**** Public API functions ****/
\r
638 svn_dirent_internal_style(const char *dirent, apr_pool_t *pool)
\r
640 return internal_style(type_dirent, dirent, pool);
\r
644 svn_dirent_local_style(const char *dirent, apr_pool_t *pool)
\r
646 return local_style(type_dirent, dirent, pool);
\r
650 svn_uri_internal_style(const char *uri, apr_pool_t *pool)
\r
652 return internal_style(type_uri, uri, pool);
\r
656 svn_uri_local_style(const char *uri, apr_pool_t *pool)
\r
658 return local_style(type_uri, uri, pool);
\r
661 /* We decided against using apr_filepath_root here because of the negative
\r
662 performance impact (creating a pool and converting strings ). */
\r
664 svn_dirent_is_root(const char *dirent, apr_size_t len)
\r
666 /* directory is root if it's equal to '/' */
\r
667 if (len == 1 && dirent[0] == '/')
\r
670 #if defined(WIN32) || defined(__CYGWIN__)
\r
671 /* On Windows and Cygwin, 'H:' or 'H:/' (where 'H' is any letter)
\r
672 are also root directories */
\r
673 if ((len == 2 || len == 3) &&
\r
674 (dirent[1] == ':') &&
\r
675 ((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
\r
676 (dirent[0] >= 'a' && dirent[0] <= 'z')) &&
\r
677 (len == 2 || (dirent[2] == '/' && len == 3)))
\r
680 /* On Windows and Cygwin, both //drive and //server/share are root
\r
682 if (len >= 2 && dirent[0] == '/' && dirent[1] == '/'
\r
683 && dirent[len - 1] != '/')
\r
687 for (i = len; i >= 2; i--)
\r
689 if (dirent[i] == '/')
\r
696 return (segments <= 1);
\r
698 #endif /* WIN32 or Cygwin */
\r
704 svn_uri_is_root(const char *uri, apr_size_t len)
\r
706 /* directory is root if it's equal to '/' */
\r
707 if (len == 1 && uri[0] == '/')
\r
713 char *svn_dirent_join(const char *base,
\r
714 const char *component,
\r
717 apr_size_t blen = strlen(base);
\r
718 apr_size_t clen = strlen(component);
\r
722 assert(svn_dirent_is_canonical(base, pool));
\r
723 assert(svn_dirent_is_canonical(component, pool));
\r
725 /* If the component is absolute, then return it. */
\r
726 if (svn_dirent_is_absolute(component))
\r
727 return apr_pmemdup(pool, component, clen + 1);
\r
729 /* If either is empty return the other */
\r
730 if (SVN_PATH_IS_EMPTY(base))
\r
731 return apr_pmemdup(pool, component, clen + 1);
\r
732 if (SVN_PATH_IS_EMPTY(component))
\r
733 return apr_pmemdup(pool, base, blen + 1);
\r
735 /* if last character of base is already a separator, don't add a '/' */
\r
737 if (base[blen - 1] == '/'
\r
738 #if defined(WIN32) || defined(__CYGWIN__)
\r
739 || base[blen - 1] == ':'
\r
740 #endif /* WIN32 or Cygwin */
\r
744 /* Construct the new, combined dirent. */
\r
745 dirent = apr_palloc(pool, blen + add_separator + clen + 1);
\r
746 memcpy(dirent, base, blen);
\r
748 dirent[blen] = '/';
\r
749 memcpy(dirent + blen + add_separator, component, clen + 1);
\r
754 char *svn_dirent_join_many(apr_pool_t *pool, const char *base, ...)
\r
756 #define MAX_SAVED_LENGTHS 10
\r
757 apr_size_t saved_lengths[MAX_SAVED_LENGTHS];
\r
758 apr_size_t total_len;
\r
768 total_len = strlen(base);
\r
770 assert(svn_dirent_is_canonical(base, pool));
\r
772 /* if last character of base is already a separator, don't add a '/' */
\r
775 || base[total_len - 1] == '/'
\r
776 #if defined(WIN32) || defined(__CYGWIN__)
\r
777 || base[total_len - 1] == ':'
\r
778 #endif /* WIN32 or Cygwin */
\r
782 saved_lengths[0] = total_len;
\r
784 /* Compute the length of the resulting string. */
\r
787 va_start(va, base);
\r
788 while ((s = va_arg(va, const char *)) != NULL)
\r
792 assert(svn_dirent_is_canonical(s, pool));
\r
794 if (SVN_PATH_IS_EMPTY(s))
\r
797 if (nargs++ < MAX_SAVED_LENGTHS)
\r
798 saved_lengths[nargs] = len;
\r
800 if (svn_dirent_is_absolute(s))
\r
802 /* an absolute dirent. skip all components to this point and reset
\r
803 the total length. */
\r
807 if (s[len - 1] == '/'
\r
808 #if defined(WIN32) || defined(__CYGWIN__)
\r
809 || s[len - 1] == ':'
\r
810 #endif /* WIN32 or Cygwin */
\r
814 else if (nargs == base_arg + 1)
\r
816 total_len += add_separator + len;
\r
820 total_len += 1 + len;
\r
825 /* base == "/" and no further components. just return that. */
\r
826 if (add_separator == 0 && total_len == 1)
\r
827 return apr_pmemdup(pool, "/", 2);
\r
829 /* we got the total size. allocate it, with room for a NULL character. */
\r
830 dirent = p = apr_palloc(pool, total_len + 1);
\r
832 /* if we aren't supposed to skip forward to an absolute component, and if
\r
833 this is not an empty base that we are skipping, then copy the base
\r
834 into the output. */
\r
835 if (base_arg == 0 && ! (SVN_PATH_IS_EMPTY(base)))
\r
837 if (SVN_PATH_IS_EMPTY(base))
\r
838 memcpy(p, SVN_EMPTY_PATH, len = saved_lengths[0]);
\r
840 memcpy(p, base, len = saved_lengths[0]);
\r
845 va_start(va, base);
\r
846 while ((s = va_arg(va, const char *)) != NULL)
\r
848 if (SVN_PATH_IS_EMPTY(s))
\r
851 if (++nargs < base_arg)
\r
854 if (nargs < MAX_SAVED_LENGTHS)
\r
855 len = saved_lengths[nargs];
\r
859 /* insert a separator if we aren't copying in the first component
\r
860 (which can happen when base_arg is set). also, don't put in a slash
\r
861 if the prior character is a slash (occurs when prior component
\r
864 ( ! (nargs - 1 == base_arg) || add_separator))
\r
867 /* copy the new component and advance the pointer */
\r
874 assert((apr_size_t)(p - dirent) == total_len);
\r
880 svn_dirent_dirname(const char *dirent, apr_pool_t *pool)
\r
882 apr_size_t len = strlen(dirent);
\r
884 assert(svn_dirent_is_canonical(dirent, pool));
\r
886 if (svn_dirent_is_root(dirent, len))
\r
887 return apr_pstrmemdup(pool, dirent, len);
\r
889 return apr_pstrmemdup(pool, dirent, dirent_previous_segment(dirent, len));
\r
893 svn_uri_dirname(const char *uri, apr_pool_t *pool)
\r
895 apr_size_t len = strlen(uri);
\r
897 assert(svn_uri_is_canonical(uri, pool));
\r
899 if (svn_uri_is_root(uri, len))
\r
900 return apr_pstrmemdup(pool, uri, len);
\r
902 return apr_pstrmemdup(pool, uri, uri_previous_segment(uri, len));
\r
906 svn_dirent_get_longest_ancestor(const char *dirent1,
\r
907 const char *dirent2,
\r
910 return apr_pstrndup(pool, dirent1,
\r
911 get_longest_ancestor_length(type_dirent, dirent1,
\r
916 svn_uri_get_longest_ancestor(const char *uri1,
\r
920 svn_boolean_t uri1_is_url, uri2_is_url;
\r
921 uri1_is_url = svn_path_is_url(uri1);
\r
922 uri2_is_url = svn_path_is_url(uri2);
\r
924 if (uri1_is_url && uri2_is_url)
\r
926 apr_size_t uri_ancestor_len;
\r
932 /* No shared protocol => no common prefix */
\r
933 if (uri1[i] != uri2[i])
\r
934 return apr_pmemdup(pool, SVN_EMPTY_PATH,
\r
935 sizeof(SVN_EMPTY_PATH));
\r
937 if (uri1[i] == ':')
\r
940 /* They're both URLs, so EOS can't come before ':' */
\r
941 assert((uri1[i] != '\0') && (uri2[i] != '\0'));
\r
946 i += 3; /* Advance past '://' */
\r
948 uri_ancestor_len = get_longest_ancestor_length(type_uri, uri1 + i,
\r
951 if (uri_ancestor_len == 0 ||
\r
952 (uri_ancestor_len == 1 && (uri1 + i)[0] == '/'))
\r
953 return apr_pmemdup(pool, SVN_EMPTY_PATH, sizeof(SVN_EMPTY_PATH));
\r
955 return apr_pstrndup(pool, uri1, uri_ancestor_len + i);
\r
958 else if ((! uri1_is_url) && (! uri2_is_url))
\r
960 return apr_pstrndup(pool, uri1,
\r
961 get_longest_ancestor_length(type_uri, uri1, uri2,
\r
967 /* A URL and a non-URL => no common prefix */
\r
968 return apr_pmemdup(pool, SVN_EMPTY_PATH, sizeof(SVN_EMPTY_PATH));
\r
973 svn_dirent_is_child(const char *dirent1,
\r
974 const char *dirent2,
\r
977 return is_child(type_dirent, dirent1, dirent2, pool);
\r
981 svn_uri_is_child(const char *uri1,
\r
985 return is_child(type_uri, uri1, uri2, pool);
\r
989 svn_dirent_is_ancestor(const char *dirent1, const char *dirent2)
\r
991 return is_ancestor(type_dirent, dirent1, dirent2);
\r
995 svn_uri_is_ancestor(const char *uri1, const char *uri2)
\r
997 return is_ancestor(type_uri, uri1, uri2);
\r
1001 svn_dirent_is_absolute(const char *dirent)
\r
1006 /* dirent is absolute if it starts with '/' */
\r
1007 if (dirent[0] == '/')
\r
1010 /* On Windows, dirent is also absolute when it starts with 'H:' or 'H:/'
\r
1011 where 'H' is any letter. */
\r
1012 #if defined(WIN32) || defined(__CYGWIN__)
\r
1013 if (((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
\r
1014 (dirent[0] >= 'a' && dirent[0] <= 'z')) &&
\r
1015 (dirent[1] == ':'))
\r
1017 #endif /* WIN32 or Cygwin */
\r
1023 svn_uri_is_absolute(const char *uri)
\r
1025 /* uri is absolute if it starts with '/' */
\r
1026 if (uri && uri[0] == '/')
\r
1029 /* URLs are absolute. */
\r
1030 return svn_path_is_url(uri);
\r
1034 svn_dirent_get_absolute(const char **pabsolute,
\r
1035 const char *relative,
\r
1039 apr_status_t apr_err;
\r
1040 const char *path_apr;
\r
1042 /* Merge the current working directory with the relative dirent. */
\r
1043 SVN_ERR(svn_path_cstring_from_utf8(&path_apr, relative, pool));
\r
1045 apr_err = apr_filepath_merge(&buffer, NULL,
\r
1047 APR_FILEPATH_NOTRELATIVE,
\r
1050 return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
\r
1051 _("Couldn't determine absolute path of '%s'"),
\r
1052 svn_path_local_style(relative, pool));
\r
1054 SVN_ERR(svn_path_cstring_to_utf8(pabsolute, buffer, pool));
\r
1055 *pabsolute = svn_dirent_canonicalize(*pabsolute, pool);
\r
1056 return SVN_NO_ERROR;
\r
1060 svn_uri_canonicalize(const char *uri, apr_pool_t *pool)
\r
1062 return canonicalize(type_uri, uri, pool);;
\r
1066 svn_dirent_canonicalize(const char *dirent, apr_pool_t *pool)
\r
1068 const char *dst = canonicalize(type_dirent, dirent, pool);;
\r
1070 #if defined(WIN32) || defined(__CYGWIN__)
\r
1071 /* Handle a specific case on Windows where path == "X:/". Here we have to
\r
1072 append the final '/', as svn_path_canonicalize will chop this of. */
\r
1073 if (((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
\r
1074 (dirent[0] >= 'a' && dirent[0] <= 'z')) &&
\r
1075 dirent[1] == ':' && dirent[2] == '/' &&
\r
1078 char *dst_slash = apr_pcalloc(pool, 4);
\r
1079 dst_slash[0] = dirent[0];
\r
1080 dst_slash[1] = ':';
\r
1081 dst_slash[2] = '/';
\r
1082 dst_slash[3] = '\0';
\r
1086 #endif /* WIN32 or Cygwin */
\r
1092 svn_dirent_is_canonical(const char *dirent, apr_pool_t *pool)
\r
1094 return (strcmp(dirent, svn_dirent_canonicalize(dirent, pool)) == 0);
\r
1098 svn_uri_is_canonical(const char *uri, apr_pool_t *pool)
\r
1100 const char *ptr = uri, *seg = uri;
\r
1102 /* URI is canonical if it has:
\r
1103 * - no '.' segments
\r
1104 * - no closing '/', unless for the root path '/' itself
\r
1106 * - lowercase URL scheme
\r
1107 * - lowercase URL hostname
\r
1113 /* Maybe parse hostname and scheme. */
\r
1116 while (*ptr && (*ptr != '/') && (*ptr != ':'))
\r
1119 if (*ptr == ':' && *(ptr+1) == '/' && *(ptr+2) == '/')
\r
1121 /* Found a scheme, check that it's all lowercase. */
\r
1123 while (*ptr != ':')
\r
1125 if (*ptr >= 'A' && *ptr <= 'Z')
\r
1132 /* This might be the hostname */
\r
1134 while (*ptr && (*ptr != '/') && (*ptr != '@'))
\r
1143 /* Found a hostname, check that it's all lowercase. */
\r
1145 while (*ptr && *ptr != '/')
\r
1147 if (*ptr >= 'A' && *ptr <= 'Z')
\r
1154 /* Didn't find a scheme; finish the segment. */
\r
1155 while (*ptr && *ptr != '/')
\r
1160 #if defined(WIN32) || defined(__CYGWIN__)
\r
1163 /* If this is a file url, ptr now points to the third '/' in
\r
1164 file:///C:/path. Check that if we have such a URL the drive
\r
1165 letter is in uppercase. */
\r
1166 if (strncmp(uri, "file:", 5) == 0 &&
\r
1167 ! (*(ptr+1) >= 'A' && *(ptr+1) <= 'Z') &&
\r
1171 #endif /* WIN32 or Cygwin */
\r
1173 /* Now validate the rest of the URI. */
\r
1176 int seglen = ptr - seg;
\r
1178 if (seglen == 1 && *seg == '.')
\r
1179 return FALSE; /* /./ */
\r
1181 if (*ptr == '/' && *(ptr+1) == '/')
\r
1182 return FALSE; /* // */
\r
1184 if (! *ptr && *(ptr - 1) == '/' && ptr - 1 != uri)
\r
1185 return FALSE; /* foo/ */
\r
1194 while (*ptr && (*ptr != '/'))
\r