]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_subr/dirent_uri.c
MFC r257129,257936,258084,258569,258602,262250,262251
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_subr / dirent_uri.c
1 /*
2  * dirent_uri.c:   a library to manipulate URIs and directory entries.
3  *
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
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
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
20  *    under the License.
21  * ====================================================================
22  */
23
24
25 \f
26 #include <string.h>
27 #include <assert.h>
28 #include <ctype.h>
29
30 #include <apr_uri.h>
31 #include <apr_lib.h>
32
33 #include "svn_private_config.h"
34 #include "svn_string.h"
35 #include "svn_dirent_uri.h"
36 #include "svn_path.h"
37 #include "svn_ctype.h"
38
39 #include "dirent_uri.h"
40 #include "private/svn_fspath.h"
41
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
44    libraries. */
45 #define SVN_EMPTY_PATH ""
46
47 /* TRUE if s is the canonical empty path, FALSE otherwise */
48 #define SVN_PATH_IS_EMPTY(s) ((s)[0] == '\0')
49
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
52    the OS! */
53 #define SVN_PATH_IS_PLATFORM_EMPTY(s,n) ((n) == 1 && (s)[0] == '.')
54
55 /* This check must match the check on top of dirent_uri-tests.c and
56    path-tests.c */
57 #if defined(WIN32) || defined(__CYGWIN__) || defined(__OS2__)
58 #define SVN_USE_DOS_PATHS
59 #endif
60
61 /* Path type definition. Used only by internal functions. */
62 typedef enum path_type_t {
63   type_uri,
64   type_dirent,
65   type_relpath
66 } path_type_t;
67
68
69 /**** Forward declarations *****/
70
71 static svn_boolean_t
72 relpath_is_canonical(const char *relpath);
73
74
75 /**** Internal implementation functions *****/
76
77 /* Return an internal-style new path based on PATH, allocated in POOL.
78  *
79  * "Internal-style" means that separators are all '/'.
80  */
81 static const char *
82 internal_style(const char *path, apr_pool_t *pool)
83 {
84 #if '/' != SVN_PATH_LOCAL_SEPARATOR
85     {
86       char *p = apr_pstrdup(pool, path);
87       path = p;
88
89       /* Convert all local-style separators to the canonical ones. */
90       for (; *p != '\0'; ++p)
91         if (*p == SVN_PATH_LOCAL_SEPARATOR)
92           *p = '/';
93     }
94 #endif
95
96   return path;
97 }
98
99 /* Locale insensitive tolower() for converting parts of dirents and urls
100    while canonicalizing */
101 static char
102 canonicalize_to_lower(char c)
103 {
104   if (c < 'A' || c > 'Z')
105     return c;
106   else
107     return (char)(c - 'A' + 'a');
108 }
109
110 /* Locale insensitive toupper() for converting parts of dirents and urls
111    while canonicalizing */
112 static char
113 canonicalize_to_upper(char c)
114 {
115   if (c < 'a' || c > 'z')
116     return c;
117   else
118     return (char)(c - 'a' + 'A');
119 }
120
121 /* Calculates the length of the dirent absolute or non absolute root in
122    DIRENT, return 0 if dirent is not rooted  */
123 static apr_size_t
124 dirent_root_length(const char *dirent, apr_size_t len)
125 {
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')))
130     {
131       return (len > 2 && dirent[2] == '/') ? 3 : 2;
132     }
133
134   if (len > 2 && dirent[0] == '/' && dirent[1] == '/')
135     {
136       apr_size_t i = 2;
137
138       while (i < len && dirent[i] != '/')
139         i++;
140
141       if (i == len)
142         return len; /* Cygwin drive alias, invalid path on WIN32 */
143
144       i++; /* Skip '/' */
145
146       while (i < len && dirent[i] != '/')
147         i++;
148
149       return i;
150     }
151 #endif /* SVN_USE_DOS_PATHS */
152   if (len >= 1 && dirent[0] == '/')
153     return 1;
154
155   return 0;
156 }
157
158
159 /* Return the length of substring necessary to encompass the entire
160  * previous dirent segment in DIRENT, which should be a LEN byte string.
161  *
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
164  * previous segments.
165  */
166 static apr_size_t
167 dirent_previous_segment(const char *dirent,
168                         apr_size_t len)
169 {
170   if (len == 0)
171     return 0;
172
173   --len;
174   while (len > 0 && dirent[len] != '/'
175 #ifdef SVN_USE_DOS_PATHS
176                  && (dirent[len] != ':' || len != 1)
177 #endif /* SVN_USE_DOS_PATHS */
178         )
179     --len;
180
181   /* check if the remaining segment including trailing '/' is a root dirent */
182   if (dirent_root_length(dirent, len+1) == len + 1)
183     return len + 1;
184   else
185     return len;
186 }
187
188 /* Calculates the length occupied by the schema defined root of URI */
189 static apr_size_t
190 uri_schema_root_length(const char *uri, apr_size_t len)
191 {
192   apr_size_t i;
193
194   for (i = 0; i < len; i++)
195     {
196       if (uri[i] == '/')
197         {
198           if (i > 0 && uri[i-1] == ':' && i < len-1 && uri[i+1] == '/')
199             {
200               /* We have an absolute uri */
201               if (i == 5 && strncmp("file", uri, 4) == 0)
202                 return 7; /* file:// */
203               else
204                 {
205                   for (i += 2; i < len; i++)
206                     if (uri[i] == '/')
207                       return i;
208
209                   return len; /* Only a hostname is found */
210                 }
211             }
212           else
213             return 0;
214         }
215     }
216
217   return 0;
218 }
219
220 /* Returns TRUE if svn_dirent_is_absolute(dirent) or when dirent has
221    a non absolute root. (E.g. '/' or 'F:' on Windows) */
222 static svn_boolean_t
223 dirent_is_rooted(const char *dirent)
224 {
225   if (! dirent)
226     return FALSE;
227
228   /* Root on all systems */
229   if (dirent[0] == '/')
230     return TRUE;
231
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')) &&
237       (dirent[1] == ':'))
238      return TRUE;
239 #endif /* SVN_USE_DOS_PATHS */
240
241   return FALSE;
242 }
243
244 /* Return the length of substring necessary to encompass the entire
245  * previous relpath segment in RELPATH, which should be a LEN byte string.
246  *
247  * A trailing slash will not be included in the returned length.
248  */
249 static apr_size_t
250 relpath_previous_segment(const char *relpath,
251                          apr_size_t len)
252 {
253   if (len == 0)
254     return 0;
255
256   --len;
257   while (len > 0 && relpath[len] != '/')
258     --len;
259
260   return len;
261 }
262
263 /* Return the length of substring necessary to encompass the entire
264  * previous uri segment in URI, which should be a LEN byte string.
265  *
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
268  * previous segments.
269  */
270 static apr_size_t
271 uri_previous_segment(const char *uri,
272                      apr_size_t len)
273 {
274   apr_size_t root_length;
275   apr_size_t i = len;
276   if (len == 0)
277     return 0;
278
279   root_length = uri_schema_root_length(uri, len);
280
281   --i;
282   while (len > root_length && uri[i] != '/')
283     --i;
284
285   if (i == 0 && len > 1 && *uri == '/')
286     return 1;
287
288   return i;
289 }
290
291 /* Return the canonicalized version of PATH, of type TYPE, allocated in
292  * POOL.
293  */
294 static const char *
295 canonicalize(path_type_t type, const char *path, apr_pool_t *pool)
296 {
297   char *canon, *dst;
298   const char *src;
299   apr_size_t seglen;
300   apr_size_t schemelen = 0;
301   apr_size_t canon_segments = 0;
302   svn_boolean_t url = FALSE;
303   char *schema_data = NULL;
304
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))
308     {
309       assert(type != type_uri);
310       return "";
311     }
312
313   dst = canon = apr_pcalloc(pool, strlen(path) + 1);
314
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
317      set URL = TRUE. */
318   src = path;
319   if (type == type_uri)
320     {
321       assert(*src != '/');
322
323       while (*src && (*src != '/') && (*src != ':'))
324         src++;
325
326       if (*src == ':' && *(src+1) == '/' && *(src+2) == '/')
327         {
328           const char *seg;
329
330           url = TRUE;
331
332           /* Found a scheme, convert to lowercase and copy to dst. */
333           src = path;
334           while (*src != ':')
335             {
336               *(dst++) = canonicalize_to_lower((*src++));
337               schemelen++;
338             }
339           *(dst++) = ':';
340           *(dst++) = '/';
341           *(dst++) = '/';
342           src += 3;
343           schemelen += 3;
344
345           /* This might be the hostname */
346           seg = src;
347           while (*src && (*src != '/') && (*src != '@'))
348             src++;
349
350           if (*src == '@')
351             {
352               /* Copy the username & password. */
353               seglen = src - seg + 1;
354               memcpy(dst, seg, seglen);
355               dst += seglen;
356               src++;
357             }
358           else
359             src = seg;
360
361           /* Found a hostname, convert to lowercase and copy to dst. */
362           if (*src == '[')
363             {
364              *(dst++) = *(src++); /* Copy '[' */
365
366               while (*src == ':'
367                      || (*src >= '0' && (*src <= '9'))
368                      || (*src >= 'a' && (*src <= 'f'))
369                      || (*src >= 'A' && (*src <= 'F')))
370                 {
371                   *(dst++) = canonicalize_to_lower((*src++));
372                 }
373
374               if (*src == ']')
375                 *(dst++) = *(src++); /* Copy ']' */
376             }
377           else
378             while (*src && (*src != '/') && (*src != ':'))
379               *(dst++) = canonicalize_to_lower((*src++));
380
381           if (*src == ':')
382             {
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))
388                 {
389                   src += 3;
390                 }
391               else if (src[1] == '4' && src[2] == '4' && src[3] == '3'
392                        && (src[4]== '/'|| !src[4])
393                        && !strncmp(canon, "https:", 6))
394                 {
395                   src += 4;
396                 }
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))
401                 {
402                   src += 5;
403                 }
404               else if (src[1] == '/' || !src[1])
405                 {
406                   src += 1;
407                 }
408
409               while (*src && (*src != '/'))
410                 *(dst++) = canonicalize_to_lower((*src++));
411             }
412
413           /* Copy trailing slash, or null-terminator. */
414           *(dst) = *(src);
415
416           /* Move src and dst forward only if we are not
417            * at null-terminator yet. */
418           if (*src)
419             {
420               src++;
421               dst++;
422               schema_data = dst;
423             }
424
425           canon_segments = 1;
426         }
427     }
428
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)
432     {
433       src = path;
434       /* If this is an absolute path, then just copy over the initial
435          separator character. */
436       if (*src == '/')
437         {
438           *(dst++) = *(src++);
439
440 #ifdef SVN_USE_DOS_PATHS
441           /* On Windows permit two leading separator characters which means an
442            * UNC path. */
443           if ((type == type_dirent) && *src == '/')
444             *(dst++) = *(src++);
445 #endif /* SVN_USE_DOS_PATHS */
446         }
447 #ifdef SVN_USE_DOS_PATHS
448       /* On Windows the first segment can be a drive letter, which we normalize
449          to upper case. */
450       else if (type == type_dirent &&
451                ((*src >= 'a' && *src <= 'z') ||
452                 (*src >= 'A' && *src <= 'Z')) &&
453                (src[1] == ':'))
454         {
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
458              a slash after it. */
459         }
460 #endif /* SVN_USE_DOS_PATHS */
461     }
462
463   while (*src)
464     {
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;
469
470       while (*next
471              && (next[0] != '/')
472              && (! (type == type_uri && next[0] == '%' && next[1] == '2' &&
473                     canonicalize_to_upper(next[2]) == 'F')))
474         {
475           ++next;
476         }
477
478       /* Record how long our "slash" is. */
479       if (next[0] == '/')
480         slash_len = 1;
481       else if (type == type_uri && next[0] == '%')
482         slash_len = 3;
483
484       seglen = next - src;
485
486       if (seglen == 0
487           || (seglen == 1 && src[0] == '.')
488           || (type == type_uri && seglen == 3 && src[0] == '%' && src[1] == '2'
489               && canonicalize_to_upper(src[2]) == 'E'))
490         {
491           /* Empty or noop segment, so do nothing.  (For URIs, '%2E'
492              is equivalent to '.').  */
493         }
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] == ':')
500         {
501           *(dst++) = canonicalize_to_upper(src[0]);
502           *(dst++) = ':';
503           if (*next)
504             *(dst++) = *next;
505           canon_segments++;
506         }
507 #endif /* SVN_USE_DOS_PATHS */
508       else
509         {
510           /* An actual segment, append it to the destination path */
511           memcpy(dst, src, seglen);
512           dst += seglen;
513           if (slash_len)
514             *(dst++) = '/';
515           canon_segments++;
516         }
517
518       /* Skip over trailing slash to the next segment. */
519       src = next + slash_len;
520     }
521
522   /* Remove the trailing slash if there was at least one
523    * canonical segment and the last segment ends with a slash.
524    *
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'))
530     {
531       dst --;
532     }
533
534   *dst = '\0';
535
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] == '/')
540     {
541       if (canon_segments < 2)
542         return canon + 1;
543       else
544         {
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
547              insensitive.
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);
553         }
554     }
555 #endif /* SVN_USE_DOS_PATHS */
556
557   /* Check the normalization of characters in a uri */
558   if (schema_data)
559     {
560       int need_extra = 0;
561       src = schema_data;
562
563       while (*src)
564         {
565           switch (*src)
566             {
567               case '/':
568                 break;
569               case '%':
570                 if (!svn_ctype_isxdigit(*(src+1)) ||
571                     !svn_ctype_isxdigit(*(src+2)))
572                   need_extra += 2;
573                 else
574                   src += 2;
575                 break;
576               default:
577                 if (!svn_uri__char_validity[(unsigned char)*src])
578                   need_extra += 2;
579                 break;
580             }
581           src++;
582         }
583
584       if (need_extra > 0)
585         {
586           apr_size_t pre_schema_size = (apr_size_t)(schema_data - canon);
587
588           dst = apr_palloc(pool, (apr_size_t)(src - canon) + need_extra + 1);
589           memcpy(dst, canon, pre_schema_size);
590           canon = dst;
591
592           dst += pre_schema_size;
593         }
594       else
595         dst = schema_data;
596
597       src = schema_data;
598
599       while (*src)
600         {
601           switch (*src)
602             {
603               case '/':
604                 *(dst++) = '/';
605                 break;
606               case '%':
607                 if (!svn_ctype_isxdigit(*(src+1)) ||
608                     !svn_ctype_isxdigit(*(src+2)))
609                   {
610                     *(dst++) = '%';
611                     *(dst++) = '2';
612                     *(dst++) = '5';
613                   }
614                 else
615                   {
616                     char digitz[3];
617                     int val;
618
619                     digitz[0] = *(++src);
620                     digitz[1] = *(++src);
621                     digitz[2] = 0;
622
623                     val = (int)strtol(digitz, NULL, 16);
624
625                     if (svn_uri__char_validity[(unsigned char)val])
626                       *(dst++) = (char)val;
627                     else
628                       {
629                         *(dst++) = '%';
630                         *(dst++) = canonicalize_to_upper(digitz[0]);
631                         *(dst++) = canonicalize_to_upper(digitz[1]);
632                       }
633                   }
634                 break;
635               default:
636                 if (!svn_uri__char_validity[(unsigned char)*src])
637                   {
638                     apr_snprintf(dst, 4, "%%%02X", (unsigned char)*src);
639                     dst += 3;
640                   }
641                 else
642                   *(dst++) = *src;
643                 break;
644             }
645           src++;
646         }
647       *dst = '\0';
648     }
649
650   return canon;
651 }
652
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.
656  *
657  * If the two paths do not share a common ancestor, return 0.
658  *
659  * New strings are allocated in POOL.
660  */
661 static apr_size_t
662 get_longest_ancestor_length(path_type_t types,
663                             const char *path1,
664                             const char *path2,
665                             apr_pool_t *pool)
666 {
667   apr_size_t path1_len, path2_len;
668   apr_size_t i = 0;
669   apr_size_t last_dirsep = 0;
670 #ifdef SVN_USE_DOS_PATHS
671   svn_boolean_t unc = FALSE;
672 #endif
673
674   path1_len = strlen(path1);
675   path2_len = strlen(path2);
676
677   if (SVN_PATH_IS_EMPTY(path1) || SVN_PATH_IS_EMPTY(path2))
678     return 0;
679
680   while (path1[i] == path2[i])
681     {
682       /* Keep track of the last directory separator we hit. */
683       if (path1[i] == '/')
684         last_dirsep = i;
685
686       i++;
687
688       /* If we get to the end of either path, break out. */
689       if ((i == path1_len) || (i == path2_len))
690         break;
691     }
692
693   /* two special cases:
694      1. '/' is the longest common ancestor of '/' and '/foo' */
695   if (i == 1 && path1[0] == '/' && path2[0] == '/')
696     return 1;
697   /* 2. '' is the longest common ancestor of any non-matching
698    * strings 'foo' and 'bar' */
699   if (types == type_dirent && i == 0)
700     return 0;
701
702   /* Handle some windows specific cases */
703 #ifdef SVN_USE_DOS_PATHS
704   if (types == type_dirent)
705     {
706       /* don't count the '//' from UNC paths */
707       if (last_dirsep == 1 && path1[0] == '/' && path1[1] == '/')
708         {
709           last_dirsep = 0;
710           unc = TRUE;
711         }
712
713       /* X:/ and X:/foo */
714       if (i == 3 && path1[2] == '/' && path1[1] == ':')
715         return i;
716
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. */
721       assert(i > 0);
722
723       /* X: and X:/ */
724       if ((path1[i - 1] == ':' && path2[i] == '/') ||
725           (path2[i - 1] == ':' && path1[i] == '/'))
726           return 0;
727       /* X: and X:foo */
728       if (path1[i - 1] == ':' || path2[i - 1] == ':')
729           return i;
730     }
731 #endif /* SVN_USE_DOS_PATHS */
732
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)))
741     return i;
742   else
743     {
744       /* Nothing in common but the root folder '/' or 'X:/' for Windows
745          dirents. */
746 #ifdef SVN_USE_DOS_PATHS
747       if (! unc)
748         {
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] == '/')
753             return 3;
754 #endif /* SVN_USE_DOS_PATHS */
755           if (last_dirsep == 0 && path1[0] == '/' && path2[0] == '/')
756             return 1;
757 #ifdef SVN_USE_DOS_PATHS
758         }
759 #endif
760     }
761
762   return last_dirsep;
763 }
764
765 /* Determine whether PATH2 is a child of PATH1.
766  *
767  * PATH2 is a child of PATH1 if
768  * 1) PATH1 is empty, and PATH2 is not empty and not an absolute path.
769  * or
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, '/'.
773  *
774  * Pass type_uri for TYPE if PATH1 and PATH2 are URIs, and type_dirent if
775  * PATH1 and PATH2 are regular paths.
776  *
777  * If PATH2 is not a child of PATH1, return NULL.
778  *
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.
782  *
783  * If PATH2 is a child of PATH1, and POOL is NULL, return a pointer
784  * pointing to the child part of PATH2.
785  * */
786 static const char *
787 is_child(path_type_t type, const char *path1, const char *path2,
788          apr_pool_t *pool)
789 {
790   apr_size_t i;
791
792   /* Allow "" and "foo" or "H:foo" to be parent/child */
793   if (SVN_PATH_IS_EMPTY(path1))               /* "" is the parent  */
794     {
795       if (SVN_PATH_IS_EMPTY(path2))            /* "" not a child    */
796         return NULL;
797
798       /* check if this is an absolute path */
799       if ((type == type_uri) ||
800           (type == type_dirent && dirent_is_rooted(path2)))
801         return NULL;
802       else
803         /* everything else is child */
804         return pool ? apr_pstrdup(pool, path2) : path2;
805     }
806
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])
815       return NULL;
816
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
820           ...      path1[i] == '\0'
821           .../foo  path2[i] == '/'
822       or
823           /        path1[i] == '\0'
824           /foo     path2[i] != '/'
825
826      Other root paths (like X:/) fall under the former case:
827           X:/        path1[i] == '\0'
828           X:/foo     path2[i] != '/'
829
830      Check for '//' to avoid matching '/' and '//srv'.
831   */
832   if (path1[i] == '\0' && path2[i])
833     {
834       if (path1[i - 1] == '/'
835 #ifdef SVN_USE_DOS_PATHS
836           || ((type == type_dirent) && path1[i - 1] == ':')
837 #endif
838            )
839         {
840           if (path2[i] == '/')
841             /* .../
842              * ..../
843              *     i   */
844             return NULL;
845           else
846             /* .../
847              * .../foo
848              *     i    */
849             return pool ? apr_pstrdup(pool, path2 + i) : path2 + i;
850         }
851       else if (path2[i] == '/')
852         {
853           if (path2[i + 1])
854             /* ...
855              * .../foo
856              *    i    */
857             return pool ? apr_pstrdup(pool, path2 + i + 1) : path2 + i + 1;
858           else
859             /* ...
860              * .../
861              *    i    */
862             return NULL;
863         }
864     }
865
866   /* Otherwise, path2 isn't a child. */
867   return NULL;
868 }
869
870 \f
871 /**** Public API functions ****/
872
873 const char *
874 svn_dirent_internal_style(const char *dirent, apr_pool_t *pool)
875 {
876   return svn_dirent_canonicalize(internal_style(dirent, pool), pool);
877 }
878
879 const char *
880 svn_dirent_local_style(const char *dirent, apr_pool_t *pool)
881 {
882   /* Internally, Subversion represents the current directory with the
883      empty string.  But users like to see "." . */
884   if (SVN_PATH_IS_EMPTY(dirent))
885     return ".";
886
887 #if '/' != SVN_PATH_LOCAL_SEPARATOR
888     {
889       char *p = apr_pstrdup(pool, dirent);
890       dirent = p;
891
892       /* Convert all canonical separators to the local-style ones. */
893       for (; *p != '\0'; ++p)
894         if (*p == '/')
895           *p = SVN_PATH_LOCAL_SEPARATOR;
896     }
897 #endif
898
899   return dirent;
900 }
901
902 const char *
903 svn_relpath__internal_style(const char *relpath,
904                             apr_pool_t *pool)
905 {
906   return svn_relpath_canonicalize(internal_style(relpath, pool), pool);
907 }
908
909
910 /* We decided against using apr_filepath_root here because of the negative
911    performance impact (creating a pool and converting strings ). */
912 svn_boolean_t
913 svn_dirent_is_root(const char *dirent, apr_size_t len)
914 {
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')))
922     return TRUE;
923
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] != '/')
928     {
929       int segments = 0;
930       apr_size_t i;
931       for (i = len; i >= 2; i--)
932         {
933           if (dirent[i] == '/')
934             {
935               segments ++;
936               if (segments > 1)
937                 return FALSE;
938             }
939         }
940 #ifdef __CYGWIN__
941       return (segments <= 1);
942 #else
943       return (segments == 1); /* //drive is invalid on plain Windows */
944 #endif
945     }
946 #endif
947
948   /* directory is root if it's equal to '/' */
949   if (len == 1 && dirent[0] == '/')
950     return TRUE;
951
952   return FALSE;
953 }
954
955 svn_boolean_t
956 svn_uri_is_root(const char *uri, apr_size_t len)
957 {
958   assert(svn_uri_is_canonical(uri, NULL));
959   return (len == uri_schema_root_length(uri, len));
960 }
961
962 char *svn_dirent_join(const char *base,
963                       const char *component,
964                       apr_pool_t *pool)
965 {
966   apr_size_t blen = strlen(base);
967   apr_size_t clen = strlen(component);
968   char *dirent;
969   int add_separator;
970
971   assert(svn_dirent_is_canonical(base, pool));
972   assert(svn_dirent_is_canonical(component, pool));
973
974   /* If the component is absolute, then return it.  */
975   if (svn_dirent_is_absolute(component))
976     return apr_pmemdup(pool, component, clen + 1);
977
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);
983
984 #ifdef SVN_USE_DOS_PATHS
985   if (component[0] == '/')
986     {
987       /* '/' is drive relative on Windows, not absolute like on Posix */
988       if (dirent_is_rooted(base))
989         {
990           /* Join component without '/' to root-of(base) */
991           blen = dirent_root_length(base, blen);
992           component++;
993           clen--;
994
995           if (blen == 2 && base[1] == ':') /* "C:" case */
996             {
997               char *root = apr_pmemdup(pool, base, 3);
998               root[2] = '/'; /* We don't need the final '\0' */
999
1000               base = root;
1001               blen = 3;
1002             }
1003
1004           if (clen == 0)
1005             return apr_pstrndup(pool, base, blen);
1006         }
1007       else
1008         return apr_pmemdup(pool, component, clen + 1);
1009     }
1010   else if (dirent_is_rooted(component))
1011     return apr_pmemdup(pool, component, clen + 1);
1012 #endif /* SVN_USE_DOS_PATHS */
1013
1014   /* if last character of base is already a separator, don't add a '/' */
1015   add_separator = 1;
1016   if (base[blen - 1] == '/'
1017 #ifdef SVN_USE_DOS_PATHS
1018        || base[blen - 1] == ':'
1019 #endif
1020         )
1021           add_separator = 0;
1022
1023   /* Construct the new, combined dirent. */
1024   dirent = apr_palloc(pool, blen + add_separator + clen + 1);
1025   memcpy(dirent, base, blen);
1026   if (add_separator)
1027     dirent[blen] = '/';
1028   memcpy(dirent + blen + add_separator, component, clen + 1);
1029
1030   return dirent;
1031 }
1032
1033 char *svn_dirent_join_many(apr_pool_t *pool, const char *base, ...)
1034 {
1035 #define MAX_SAVED_LENGTHS 10
1036   apr_size_t saved_lengths[MAX_SAVED_LENGTHS];
1037   apr_size_t total_len;
1038   int nargs;
1039   va_list va;
1040   const char *s;
1041   apr_size_t len;
1042   char *dirent;
1043   char *p;
1044   int add_separator;
1045   int base_arg = 0;
1046
1047   total_len = strlen(base);
1048
1049   assert(svn_dirent_is_canonical(base, pool));
1050
1051   /* if last character of base is already a separator, don't add a '/' */
1052   add_separator = 1;
1053   if (total_len == 0
1054        || base[total_len - 1] == '/'
1055 #ifdef SVN_USE_DOS_PATHS
1056        || base[total_len - 1] == ':'
1057 #endif
1058         )
1059           add_separator = 0;
1060
1061   saved_lengths[0] = total_len;
1062
1063   /* Compute the length of the resulting string. */
1064
1065   nargs = 0;
1066   va_start(va, base);
1067   while ((s = va_arg(va, const char *)) != NULL)
1068     {
1069       len = strlen(s);
1070
1071       assert(svn_dirent_is_canonical(s, pool));
1072
1073       if (SVN_PATH_IS_EMPTY(s))
1074         continue;
1075
1076       if (nargs++ < MAX_SAVED_LENGTHS)
1077         saved_lengths[nargs] = len;
1078
1079       if (dirent_is_rooted(s))
1080         {
1081           total_len = len;
1082           base_arg = nargs;
1083
1084 #ifdef SVN_USE_DOS_PATHS
1085           if (!svn_dirent_is_absolute(s)) /* Handle non absolute roots */
1086             {
1087               /* Set new base and skip the current argument */
1088               base = s = svn_dirent_join(base, s, pool);
1089               base_arg++;
1090               saved_lengths[0] = total_len = len = strlen(s);
1091             }
1092           else
1093 #endif /* SVN_USE_DOS_PATHS */
1094             {
1095               base = ""; /* Don't add base */
1096               saved_lengths[0] = 0;
1097             }
1098
1099           add_separator = 1;
1100           if (s[len - 1] == '/'
1101 #ifdef SVN_USE_DOS_PATHS
1102              || s[len - 1] == ':'
1103 #endif
1104               )
1105              add_separator = 0;
1106         }
1107       else if (nargs <= base_arg + 1)
1108         {
1109           total_len += add_separator + len;
1110         }
1111       else
1112         {
1113           total_len += 1 + len;
1114         }
1115     }
1116   va_end(va);
1117
1118   /* base == "/" and no further components. just return that. */
1119   if (add_separator == 0 && total_len == 1)
1120     return apr_pmemdup(pool, "/", 2);
1121
1122   /* we got the total size. allocate it, with room for a NULL character. */
1123   dirent = p = apr_palloc(pool, total_len + 1);
1124
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
1127      into the output. */
1128   if (! SVN_PATH_IS_EMPTY(base))
1129     {
1130       memcpy(p, base, len = saved_lengths[0]);
1131       p += len;
1132     }
1133
1134   nargs = 0;
1135   va_start(va, base);
1136   while ((s = va_arg(va, const char *)) != NULL)
1137     {
1138       if (SVN_PATH_IS_EMPTY(s))
1139         continue;
1140
1141       if (++nargs < base_arg)
1142         continue;
1143
1144       if (nargs < MAX_SAVED_LENGTHS)
1145         len = saved_lengths[nargs];
1146       else
1147         len = strlen(s);
1148
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
1152          is "/"). */
1153       if (p != dirent &&
1154           ( ! (nargs - 1 <= base_arg) || add_separator))
1155         *p++ = '/';
1156
1157       /* copy the new component and advance the pointer */
1158       memcpy(p, s, len);
1159       p += len;
1160     }
1161   va_end(va);
1162
1163   *p = '\0';
1164   assert((apr_size_t)(p - dirent) == total_len);
1165
1166   return dirent;
1167 }
1168
1169 char *
1170 svn_relpath_join(const char *base,
1171                  const char *component,
1172                  apr_pool_t *pool)
1173 {
1174   apr_size_t blen = strlen(base);
1175   apr_size_t clen = strlen(component);
1176   char *path;
1177
1178   assert(relpath_is_canonical(base));
1179   assert(relpath_is_canonical(component));
1180
1181   /* If either is empty return the other */
1182   if (blen == 0)
1183     return apr_pmemdup(pool, component, clen + 1);
1184   if (clen == 0)
1185     return apr_pmemdup(pool, base, blen + 1);
1186
1187   path = apr_palloc(pool, blen + 1 + clen + 1);
1188   memcpy(path, base, blen);
1189   path[blen] = '/';
1190   memcpy(path + blen + 1, component, clen + 1);
1191
1192   return path;
1193 }
1194
1195 char *
1196 svn_dirent_dirname(const char *dirent, apr_pool_t *pool)
1197 {
1198   apr_size_t len = strlen(dirent);
1199
1200   assert(svn_dirent_is_canonical(dirent, pool));
1201
1202   if (len == dirent_root_length(dirent, len))
1203     return apr_pstrmemdup(pool, dirent, len);
1204   else
1205     return apr_pstrmemdup(pool, dirent, dirent_previous_segment(dirent, len));
1206 }
1207
1208 const char *
1209 svn_dirent_basename(const char *dirent, apr_pool_t *pool)
1210 {
1211   apr_size_t len = strlen(dirent);
1212   apr_size_t start;
1213
1214   assert(!pool || svn_dirent_is_canonical(dirent, pool));
1215
1216   if (svn_dirent_is_root(dirent, len))
1217     return "";
1218   else
1219     {
1220       start = len;
1221       while (start > 0 && dirent[start - 1] != '/'
1222 #ifdef SVN_USE_DOS_PATHS
1223              && dirent[start - 1] != ':'
1224 #endif
1225             )
1226         --start;
1227     }
1228
1229   if (pool)
1230     return apr_pstrmemdup(pool, dirent + start, len - start);
1231   else
1232     return dirent + start;
1233 }
1234
1235 void
1236 svn_dirent_split(const char **dirpath,
1237                  const char **base_name,
1238                  const char *dirent,
1239                  apr_pool_t *pool)
1240 {
1241   assert(dirpath != base_name);
1242
1243   if (dirpath)
1244     *dirpath = svn_dirent_dirname(dirent, pool);
1245
1246   if (base_name)
1247     *base_name = svn_dirent_basename(dirent, pool);
1248 }
1249
1250 char *
1251 svn_relpath_dirname(const char *relpath,
1252                     apr_pool_t *pool)
1253 {
1254   apr_size_t len = strlen(relpath);
1255
1256   assert(relpath_is_canonical(relpath));
1257
1258   return apr_pstrmemdup(pool, relpath,
1259                         relpath_previous_segment(relpath, len));
1260 }
1261
1262 const char *
1263 svn_relpath_basename(const char *relpath,
1264                      apr_pool_t *pool)
1265 {
1266   apr_size_t len = strlen(relpath);
1267   apr_size_t start;
1268
1269   assert(relpath_is_canonical(relpath));
1270
1271   start = len;
1272   while (start > 0 && relpath[start - 1] != '/')
1273     --start;
1274
1275   if (pool)
1276     return apr_pstrmemdup(pool, relpath + start, len - start);
1277   else
1278     return relpath + start;
1279 }
1280
1281 void
1282 svn_relpath_split(const char **dirpath,
1283                   const char **base_name,
1284                   const char *relpath,
1285                   apr_pool_t *pool)
1286 {
1287   assert(dirpath != base_name);
1288
1289   if (dirpath)
1290     *dirpath = svn_relpath_dirname(relpath, pool);
1291
1292   if (base_name)
1293     *base_name = svn_relpath_basename(relpath, pool);
1294 }
1295
1296 char *
1297 svn_uri_dirname(const char *uri, apr_pool_t *pool)
1298 {
1299   apr_size_t len = strlen(uri);
1300
1301   assert(svn_uri_is_canonical(uri, pool));
1302
1303   if (svn_uri_is_root(uri, len))
1304     return apr_pstrmemdup(pool, uri, len);
1305   else
1306     return apr_pstrmemdup(pool, uri, uri_previous_segment(uri, len));
1307 }
1308
1309 const char *
1310 svn_uri_basename(const char *uri, apr_pool_t *pool)
1311 {
1312   apr_size_t len = strlen(uri);
1313   apr_size_t start;
1314
1315   assert(svn_uri_is_canonical(uri, NULL));
1316
1317   if (svn_uri_is_root(uri, len))
1318     return "";
1319
1320   start = len;
1321   while (start > 0 && uri[start - 1] != '/')
1322     --start;
1323
1324   return svn_path_uri_decode(uri + start, pool);
1325 }
1326
1327 void
1328 svn_uri_split(const char **dirpath,
1329               const char **base_name,
1330               const char *uri,
1331               apr_pool_t *pool)
1332 {
1333   assert(dirpath != base_name);
1334
1335   if (dirpath)
1336     *dirpath = svn_uri_dirname(uri, pool);
1337
1338   if (base_name)
1339     *base_name = svn_uri_basename(uri, pool);
1340 }
1341
1342 char *
1343 svn_dirent_get_longest_ancestor(const char *dirent1,
1344                                 const char *dirent2,
1345                                 apr_pool_t *pool)
1346 {
1347   return apr_pstrndup(pool, dirent1,
1348                       get_longest_ancestor_length(type_dirent, dirent1,
1349                                                   dirent2, pool));
1350 }
1351
1352 char *
1353 svn_relpath_get_longest_ancestor(const char *relpath1,
1354                                  const char *relpath2,
1355                                  apr_pool_t *pool)
1356 {
1357   assert(relpath_is_canonical(relpath1));
1358   assert(relpath_is_canonical(relpath2));
1359
1360   return apr_pstrndup(pool, relpath1,
1361                       get_longest_ancestor_length(type_relpath, relpath1,
1362                                                   relpath2, pool));
1363 }
1364
1365 char *
1366 svn_uri_get_longest_ancestor(const char *uri1,
1367                              const char *uri2,
1368                              apr_pool_t *pool)
1369 {
1370   apr_size_t uri_ancestor_len;
1371   apr_size_t i = 0;
1372
1373   assert(svn_uri_is_canonical(uri1, NULL));
1374   assert(svn_uri_is_canonical(uri2, NULL));
1375
1376   /* Find ':' */
1377   while (1)
1378     {
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));
1383
1384       if (uri1[i] == ':')
1385         break;
1386
1387       /* They're both URLs, so EOS can't come before ':' */
1388       assert((uri1[i] != '\0') && (uri2[i] != '\0'));
1389
1390       i++;
1391     }
1392
1393   i += 3;  /* Advance past '://' */
1394
1395   uri_ancestor_len = get_longest_ancestor_length(type_uri, uri1 + i,
1396                                                  uri2 + i, pool);
1397
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));
1401   else
1402     return apr_pstrndup(pool, uri1, uri_ancestor_len + i);
1403 }
1404
1405 const char *
1406 svn_dirent_is_child(const char *parent_dirent,
1407                     const char *child_dirent,
1408                     apr_pool_t *pool)
1409 {
1410   return is_child(type_dirent, parent_dirent, child_dirent, pool);
1411 }
1412
1413 const char *
1414 svn_dirent_skip_ancestor(const char *parent_dirent,
1415                          const char *child_dirent)
1416 {
1417   apr_size_t len = strlen(parent_dirent);
1418   apr_size_t root_len;
1419
1420   if (0 != strncmp(parent_dirent, child_dirent, len))
1421     return NULL; /* parent_dirent is no ancestor of child_dirent */
1422
1423   if (child_dirent[len] == 0)
1424     return ""; /* parent_dirent == child_dirent */
1425
1426   /* Child == parent + more-characters */
1427
1428   root_len = dirent_root_length(child_dirent, strlen(child_dirent));
1429   if (root_len > len)
1430     /* Different root, e.g. ("" "/...") or ("//z" "//z/share") */
1431     return NULL;
1432
1433   /* Now, child == [root-of-parent] + [rest-of-parent] + more-characters.
1434    * It must be one of the following forms.
1435    *
1436    * rlen parent    child       bad?  rlen=len? c[len]=/?
1437    *  0   ""        "foo"               *
1438    *  0   "b"       "bad"         !
1439    *  0   "b"       "b/foo"                       *
1440    *  1   "/"       "/foo"              *
1441    *  1   "/b"      "/bad"        !
1442    *  1   "/b"      "/b/foo"                      *
1443    *  2   "a:"      "a:foo"             *
1444    *  2   "a:b"     "a:bad"       !
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"                 *
1452    */
1453
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;
1458
1459   if (root_len == len)
1460     /* "parent|child" is "|foo" "/|foo" "a:|foo" "a:/|foo" "//s/s|/foo" */
1461     return child_dirent + len;
1462
1463   return NULL;
1464 }
1465
1466 const char *
1467 svn_relpath_skip_ancestor(const char *parent_relpath,
1468                           const char *child_relpath)
1469 {
1470   apr_size_t len = strlen(parent_relpath);
1471
1472   assert(relpath_is_canonical(parent_relpath));
1473   assert(relpath_is_canonical(child_relpath));
1474
1475   if (len == 0)
1476     return child_relpath;
1477
1478   if (0 != strncmp(parent_relpath, child_relpath, len))
1479     return NULL; /* parent_relpath is no ancestor of child_relpath */
1480
1481   if (child_relpath[len] == 0)
1482     return ""; /* parent_relpath == child_relpath */
1483
1484   if (child_relpath[len] == '/')
1485     return child_relpath + len + 1;
1486
1487   return NULL;
1488 }
1489
1490
1491 /* */
1492 static const char *
1493 uri_skip_ancestor(const char *parent_uri,
1494                   const char *child_uri)
1495 {
1496   apr_size_t len = strlen(parent_uri);
1497
1498   assert(svn_uri_is_canonical(parent_uri, NULL));
1499   assert(svn_uri_is_canonical(child_uri, NULL));
1500
1501   if (0 != strncmp(parent_uri, child_uri, len))
1502     return NULL; /* parent_uri is no ancestor of child_uri */
1503
1504   if (child_uri[len] == 0)
1505     return ""; /* parent_uri == child_uri */
1506
1507   if (child_uri[len] == '/')
1508     return child_uri + len + 1;
1509
1510   return NULL;
1511 }
1512
1513 const char *
1514 svn_uri_skip_ancestor(const char *parent_uri,
1515                       const char *child_uri,
1516                       apr_pool_t *result_pool)
1517 {
1518   const char *result = uri_skip_ancestor(parent_uri, child_uri);
1519
1520   return result ? svn_path_uri_decode(result, result_pool) : NULL;
1521 }
1522
1523 svn_boolean_t
1524 svn_dirent_is_ancestor(const char *parent_dirent, const char *child_dirent)
1525 {
1526   return svn_dirent_skip_ancestor(parent_dirent, child_dirent) != NULL;
1527 }
1528
1529 svn_boolean_t
1530 svn_uri__is_ancestor(const char *parent_uri, const char *child_uri)
1531 {
1532   return uri_skip_ancestor(parent_uri, child_uri) != NULL;
1533 }
1534
1535
1536 svn_boolean_t
1537 svn_dirent_is_absolute(const char *dirent)
1538 {
1539   if (! dirent)
1540     return FALSE;
1541
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 */
1547 #endif
1548       )
1549     return TRUE;
1550
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] == '/'))
1556      return TRUE;
1557 #endif /* SVN_USE_DOS_PATHS */
1558
1559   return FALSE;
1560 }
1561
1562 svn_error_t *
1563 svn_dirent_get_absolute(const char **pabsolute,
1564                         const char *relative,
1565                         apr_pool_t *pool)
1566 {
1567   char *buffer;
1568   apr_status_t apr_err;
1569   const char *path_apr;
1570
1571   SVN_ERR_ASSERT(! svn_path_is_url(relative));
1572
1573   /* Merge the current working directory with the relative dirent. */
1574   SVN_ERR(svn_path_cstring_from_utf8(&path_apr, relative, pool));
1575
1576   apr_err = apr_filepath_merge(&buffer, NULL,
1577                                path_apr,
1578                                APR_FILEPATH_NOTRELATIVE,
1579                                pool);
1580   if (apr_err)
1581     {
1582       /* In some cases when the passed path or its ancestor(s) do not exist
1583          or no longer exist apr returns an error.
1584
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
1587          avoid an error.
1588
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
1591          format.
1592          */
1593
1594       if (svn_dirent_is_absolute(relative)
1595           && svn_dirent_is_canonical(relative, pool)
1596           && !svn_path_is_backpath_present(relative))
1597         {
1598           *pabsolute = apr_pstrdup(pool, relative);
1599           return SVN_NO_ERROR;
1600         }
1601
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));
1606     }
1607
1608   SVN_ERR(svn_path_cstring_to_utf8(pabsolute, buffer, pool));
1609   *pabsolute = svn_dirent_canonicalize(*pabsolute, pool);
1610   return SVN_NO_ERROR;
1611 }
1612
1613 const char *
1614 svn_uri_canonicalize(const char *uri, apr_pool_t *pool)
1615 {
1616   return canonicalize(type_uri, uri, pool);
1617 }
1618
1619 const char *
1620 svn_relpath_canonicalize(const char *relpath, apr_pool_t *pool)
1621 {
1622   return canonicalize(type_relpath, relpath, pool);
1623 }
1624
1625 const char *
1626 svn_dirent_canonicalize(const char *dirent, apr_pool_t *pool)
1627 {
1628   const char *dst = canonicalize(type_dirent, dirent, pool);
1629
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] == '/' &&
1636         dst[3] == '\0')
1637     {
1638       char *dst_slash = apr_pcalloc(pool, 4);
1639       dst_slash[0] = canonicalize_to_upper(dirent[0]);
1640       dst_slash[1] = ':';
1641       dst_slash[2] = '/';
1642       dst_slash[3] = '\0';
1643
1644       return dst_slash;
1645     }
1646 #endif /* SVN_USE_DOS_PATHS */
1647
1648   return dst;
1649 }
1650
1651 svn_boolean_t
1652 svn_dirent_is_canonical(const char *dirent, apr_pool_t *scratch_pool)
1653 {
1654   const char *ptr = dirent;
1655   if (*ptr == '/')
1656     {
1657       ptr++;
1658 #ifdef SVN_USE_DOS_PATHS
1659       /* Check for UNC paths */
1660       if (*ptr == '/')
1661         {
1662           /* TODO: Scan hostname and sharename and fall back to part code */
1663
1664           /* ### Fall back to old implementation */
1665           return (strcmp(dirent, svn_dirent_canonicalize(dirent, scratch_pool))
1666                   == 0);
1667         }
1668 #endif /* SVN_USE_DOS_PATHS */
1669     }
1670 #ifdef SVN_USE_DOS_PATHS
1671   else if (((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= 'A' && *ptr <= 'Z')) &&
1672            (ptr[1] == ':'))
1673     {
1674       /* The only canonical drive names are "A:"..."Z:", no lower case */
1675       if (*ptr < 'A' || *ptr > 'Z')
1676         return FALSE;
1677
1678       ptr += 2;
1679
1680       if (*ptr == '/')
1681         ptr++;
1682     }
1683 #endif /* SVN_USE_DOS_PATHS */
1684
1685   return relpath_is_canonical(ptr);
1686 }
1687
1688 static svn_boolean_t
1689 relpath_is_canonical(const char *relpath)
1690 {
1691   const char *ptr = relpath, *seg = relpath;
1692
1693   /* RELPATH is canonical if it has:
1694    *  - no '.' segments
1695    *  - no start and closing '/'
1696    *  - no '//'
1697    */
1698
1699   if (*relpath == '\0')
1700     return TRUE;
1701
1702   if (*ptr == '/')
1703     return FALSE;
1704
1705   /* Now validate the rest of the path. */
1706   while(1)
1707     {
1708       apr_size_t seglen = ptr - seg;
1709
1710       if (seglen == 1 && *seg == '.')
1711         return FALSE;  /*  /./   */
1712
1713       if (*ptr == '/' && *(ptr+1) == '/')
1714         return FALSE;  /*  //    */
1715
1716       if (! *ptr && *(ptr - 1) == '/')
1717         return FALSE;  /* foo/  */
1718
1719       if (! *ptr)
1720         break;
1721
1722       if (*ptr == '/')
1723         ptr++;
1724       seg = ptr;
1725
1726       while (*ptr && (*ptr != '/'))
1727         ptr++;
1728     }
1729
1730   return TRUE;
1731 }
1732
1733 svn_boolean_t
1734 svn_relpath_is_canonical(const char *relpath)
1735 {
1736   return relpath_is_canonical(relpath);
1737 }
1738
1739 svn_boolean_t
1740 svn_uri_is_canonical(const char *uri, apr_pool_t *scratch_pool)
1741 {
1742   const char *ptr = uri, *seg = uri;
1743   const char *schema_data = NULL;
1744
1745   /* URI is canonical if it has:
1746    *  - lowercase URL scheme
1747    *  - lowercase URL hostname
1748    *  - no '.' segments
1749    *  - no closing '/'
1750    *  - no '//'
1751    *  - uppercase hex-encoded pair digits ("%AB", not "%ab")
1752    */
1753
1754   if (*uri == '\0')
1755     return FALSE;
1756
1757   if (! svn_path_is_url(uri))
1758     return FALSE;
1759
1760   /* Skip the scheme. */
1761   while (*ptr && (*ptr != '/') && (*ptr != ':'))
1762     ptr++;
1763
1764   /* No scheme?  No good. */
1765   if (! (*ptr == ':' && *(ptr+1) == '/' && *(ptr+2) == '/'))
1766     return FALSE;
1767
1768   /* Found a scheme, check that it's all lowercase. */
1769   ptr = uri;
1770   while (*ptr != ':')
1771     {
1772       if (*ptr >= 'A' && *ptr <= 'Z')
1773         return FALSE;
1774       ptr++;
1775     }
1776   /* Skip :// */
1777   ptr += 3;
1778
1779   /* Scheme only?  That works. */
1780   if (! *ptr)
1781     return TRUE;
1782
1783   /* This might be the hostname */
1784   seg = ptr;
1785   while (*ptr && (*ptr != '/') && (*ptr != '@'))
1786     ptr++;
1787
1788   if (*ptr == '@')
1789     seg = ptr + 1;
1790
1791   /* Found a hostname, check that it's all lowercase. */
1792   ptr = seg;
1793
1794   if (*ptr == '[')
1795     {
1796       ptr++;
1797       while (*ptr == ':'
1798              || (*ptr >= '0' && *ptr <= '9')
1799              || (*ptr >= 'a' && *ptr <= 'f'))
1800         {
1801           ptr++;
1802         }
1803
1804       if (*ptr != ']')
1805         return FALSE;
1806       ptr++;
1807     }
1808   else
1809     while (*ptr && *ptr != '/' && *ptr != ':')
1810       {
1811         if (*ptr >= 'A' && *ptr <= 'Z')
1812           return FALSE;
1813         ptr++;
1814       }
1815
1816   /* Found a portnumber */
1817   if (*ptr == ':')
1818     {
1819       apr_int64_t port = 0;
1820
1821       ptr++;
1822       schema_data = ptr;
1823
1824       while (*ptr >= '0' && *ptr <= '9')
1825         {
1826           port = 10 * port + (*ptr - '0');
1827           ptr++;
1828         }
1829
1830       if (ptr == schema_data)
1831         return FALSE; /* Fail on "http://host:" */
1832
1833       if (*ptr && *ptr != '/')
1834         return FALSE; /* Not a port number */
1835
1836       if (port == 80 && strncmp(uri, "http:", 5) == 0)
1837         return FALSE;
1838       else if (port == 443 && strncmp(uri, "https:", 6) == 0)
1839         return FALSE;
1840       else if (port == 3690 && strncmp(uri, "svn:", 4) == 0)
1841         return FALSE;
1842     }
1843
1844   schema_data = ptr;
1845
1846 #ifdef SVN_USE_DOS_PATHS
1847   if (schema_data && *ptr == '/')
1848     {
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') &&
1854           *(ptr+2) == ':')
1855         return FALSE;
1856     }
1857 #endif /* SVN_USE_DOS_PATHS */
1858
1859   /* Now validate the rest of the URI. */
1860   seg = ptr;
1861   while (*ptr && (*ptr != '/'))
1862     ptr++;
1863   while(1)
1864     {
1865       apr_size_t seglen = ptr - seg;
1866
1867       if (seglen == 1 && *seg == '.')
1868         return FALSE;  /*  /./   */
1869
1870       if (*ptr == '/' && *(ptr+1) == '/')
1871         return FALSE;  /*  //    */
1872
1873       if (! *ptr && *(ptr - 1) == '/' && ptr - 1 != uri)
1874         return FALSE;  /* foo/  */
1875
1876       if (! *ptr)
1877         break;
1878
1879       if (*ptr == '/')
1880         ptr++;
1881
1882       seg = ptr;
1883       while (*ptr && (*ptr != '/'))
1884         ptr++;
1885     }
1886
1887   ptr = schema_data;
1888
1889   while (*ptr)
1890     {
1891       if (*ptr == '%')
1892         {
1893           char digitz[3];
1894           int val;
1895
1896           /* Can't usesvn_ctype_isxdigit() because lower case letters are
1897              not in our canonical format */
1898           if (((*(ptr+1) < '0' || *(ptr+1) > '9'))
1899               && (*(ptr+1) < 'A' || *(ptr+1) > 'F'))
1900             return FALSE;
1901           else if (((*(ptr+2) < '0' || *(ptr+2) > '9'))
1902                    && (*(ptr+2) < 'A' || *(ptr+2) > 'F'))
1903             return FALSE;
1904
1905           digitz[0] = *(++ptr);
1906           digitz[1] = *(++ptr);
1907           digitz[2] = '\0';
1908           val = (int)strtol(digitz, NULL, 16);
1909
1910           if (svn_uri__char_validity[val])
1911             return FALSE; /* Should not have been escaped */
1912         }
1913       else if (*ptr != '/' && !svn_uri__char_validity[(unsigned char)*ptr])
1914         return FALSE; /* Character should have been escaped */
1915       ptr++;
1916     }
1917
1918   return TRUE;
1919 }
1920
1921 svn_error_t *
1922 svn_dirent_condense_targets(const char **pcommon,
1923                             apr_array_header_t **pcondensed_targets,
1924                             const apr_array_header_t *targets,
1925                             svn_boolean_t remove_redundancies,
1926                             apr_pool_t *result_pool,
1927                             apr_pool_t *scratch_pool)
1928 {
1929   int i, num_condensed = targets->nelts;
1930   svn_boolean_t *removed;
1931   apr_array_header_t *abs_targets;
1932
1933   /* Early exit when there's no data to work on. */
1934   if (targets->nelts <= 0)
1935     {
1936       *pcommon = NULL;
1937       if (pcondensed_targets)
1938         *pcondensed_targets = NULL;
1939       return SVN_NO_ERROR;
1940     }
1941
1942   /* Get the absolute path of the first target. */
1943   SVN_ERR(svn_dirent_get_absolute(pcommon,
1944                                   APR_ARRAY_IDX(targets, 0, const char *),
1945                                   scratch_pool));
1946
1947   /* Early exit when there's only one dirent to work on. */
1948   if (targets->nelts == 1)
1949     {
1950       *pcommon = apr_pstrdup(result_pool, *pcommon);
1951       if (pcondensed_targets)
1952         *pcondensed_targets = apr_array_make(result_pool, 0,
1953                                              sizeof(const char *));
1954       return SVN_NO_ERROR;
1955     }
1956
1957   /* Copy the targets array, but with absolute dirents instead of
1958      relative.  Also, find the pcommon argument by finding what is
1959      common in all of the absolute dirents. NOTE: This is not as
1960      efficient as it could be.  The calculation of the basedir could
1961      be done in the loop below, which would save some calls to
1962      svn_dirent_get_longest_ancestor.  I decided to do it this way
1963      because I thought it would be simpler, since this way, we don't
1964      even do the loop if we don't need to condense the targets. */
1965
1966   removed = apr_pcalloc(scratch_pool, (targets->nelts *
1967                                           sizeof(svn_boolean_t)));
1968   abs_targets = apr_array_make(scratch_pool, targets->nelts,
1969                                sizeof(const char *));
1970
1971   APR_ARRAY_PUSH(abs_targets, const char *) = *pcommon;
1972
1973   for (i = 1; i < targets->nelts; ++i)
1974     {
1975       const char *rel = APR_ARRAY_IDX(targets, i, const char *);
1976       const char *absolute;
1977       SVN_ERR(svn_dirent_get_absolute(&absolute, rel, scratch_pool));
1978       APR_ARRAY_PUSH(abs_targets, const char *) = absolute;
1979       *pcommon = svn_dirent_get_longest_ancestor(*pcommon, absolute,
1980                                                  scratch_pool);
1981     }
1982
1983   *pcommon = apr_pstrdup(result_pool, *pcommon);
1984
1985   if (pcondensed_targets != NULL)
1986     {
1987       size_t basedir_len;
1988
1989       if (remove_redundancies)
1990         {
1991           /* Find the common part of each pair of targets.  If
1992              common part is equal to one of the dirents, the other
1993              is a child of it, and can be removed.  If a target is
1994              equal to *pcommon, it can also be removed. */
1995
1996           /* First pass: when one non-removed target is a child of
1997              another non-removed target, remove the child. */
1998           for (i = 0; i < abs_targets->nelts; ++i)
1999             {
2000               int j;
2001
2002               if (removed[i])
2003                 continue;
2004
2005               for (j = i + 1; j < abs_targets->nelts; ++j)
2006                 {
2007                   const char *abs_targets_i;
2008                   const char *abs_targets_j;
2009                   const char *ancestor;
2010
2011                   if (removed[j])
2012                     continue;
2013
2014                   abs_targets_i = APR_ARRAY_IDX(abs_targets, i, const char *);
2015                   abs_targets_j = APR_ARRAY_IDX(abs_targets, j, const char *);
2016
2017                   ancestor = svn_dirent_get_longest_ancestor
2018                     (abs_targets_i, abs_targets_j, scratch_pool);
2019
2020                   if (*ancestor == '\0')
2021                     continue;
2022
2023                   if (strcmp(ancestor, abs_targets_i) == 0)
2024                     {
2025                       removed[j] = TRUE;
2026                       num_condensed--;
2027                     }
2028                   else if (strcmp(ancestor, abs_targets_j) == 0)
2029                     {
2030                       removed[i] = TRUE;
2031                       num_condensed--;
2032                     }
2033                 }
2034             }
2035
2036           /* Second pass: when a target is the same as *pcommon,
2037              remove the target. */
2038           for (i = 0; i < abs_targets->nelts; ++i)
2039             {
2040               const char *abs_targets_i = APR_ARRAY_IDX(abs_targets, i,
2041                                                         const char *);
2042
2043               if ((strcmp(abs_targets_i, *pcommon) == 0) && (! removed[i]))
2044                 {
2045                   removed[i] = TRUE;
2046                   num_condensed--;
2047                 }
2048             }
2049         }
2050
2051       /* Now create the return array, and copy the non-removed items */
2052       basedir_len = strlen(*pcommon);
2053       *pcondensed_targets = apr_array_make(result_pool, num_condensed,
2054                                            sizeof(const char *));
2055
2056       for (i = 0; i < abs_targets->nelts; ++i)
2057         {
2058           const char *rel_item = APR_ARRAY_IDX(abs_targets, i, const char *);
2059
2060           /* Skip this if it's been removed. */
2061           if (removed[i])
2062             continue;
2063
2064           /* If a common prefix was found, condensed_targets are given
2065              relative to that prefix.  */
2066           if (basedir_len > 0)
2067             {
2068               /* Only advance our pointer past a dirent separator if
2069                  REL_ITEM isn't the same as *PCOMMON.
2070
2071                  If *PCOMMON is a root dirent, basedir_len will already
2072                  include the closing '/', so never advance the pointer
2073                  here.
2074                  */
2075               rel_item += basedir_len;
2076               if (rel_item[0] &&
2077                   ! svn_dirent_is_root(*pcommon, basedir_len))
2078                 rel_item++;
2079             }
2080
2081           APR_ARRAY_PUSH(*pcondensed_targets, const char *)
2082             = apr_pstrdup(result_pool, rel_item);
2083         }
2084     }
2085
2086   return SVN_NO_ERROR;
2087 }
2088
2089 svn_error_t *
2090 svn_uri_condense_targets(const char **pcommon,
2091                          apr_array_header_t **pcondensed_targets,
2092                          const apr_array_header_t *targets,
2093                          svn_boolean_t remove_redundancies,
2094                          apr_pool_t *result_pool,
2095                          apr_pool_t *scratch_pool)
2096 {
2097   int i, num_condensed = targets->nelts;
2098   apr_array_header_t *uri_targets;
2099   svn_boolean_t *removed;
2100
2101   /* Early exit when there's no data to work on. */
2102   if (targets->nelts <= 0)
2103     {
2104       *pcommon = NULL;
2105       if (pcondensed_targets)
2106         *pcondensed_targets = NULL;
2107       return SVN_NO_ERROR;
2108     }
2109
2110   *pcommon = svn_uri_canonicalize(APR_ARRAY_IDX(targets, 0, const char *),
2111                                   scratch_pool);
2112
2113   /* Early exit when there's only one uri to work on. */
2114   if (targets->nelts == 1)
2115     {
2116       *pcommon = apr_pstrdup(result_pool, *pcommon);
2117       if (pcondensed_targets)
2118         *pcondensed_targets = apr_array_make(result_pool, 0,
2119                                              sizeof(const char *));
2120       return SVN_NO_ERROR;
2121     }
2122
2123   /* Find the pcommon argument by finding what is common in all of the
2124      uris. NOTE: This is not as efficient as it could be.  The calculation
2125      of the basedir could be done in the loop below, which would
2126      save some calls to svn_uri_get_longest_ancestor.  I decided to do it
2127      this way because I thought it would be simpler, since this way, we don't
2128      even do the loop if we don't need to condense the targets. */
2129
2130   removed = apr_pcalloc(scratch_pool, (targets->nelts *
2131                                           sizeof(svn_boolean_t)));
2132   uri_targets = apr_array_make(scratch_pool, targets->nelts,
2133                                sizeof(const char *));
2134
2135   APR_ARRAY_PUSH(uri_targets, const char *) = *pcommon;
2136
2137   for (i = 1; i < targets->nelts; ++i)
2138     {
2139       const char *uri = svn_uri_canonicalize(
2140                            APR_ARRAY_IDX(targets, i, const char *),
2141                            scratch_pool);
2142       APR_ARRAY_PUSH(uri_targets, const char *) = uri;
2143
2144       /* If the commonmost ancestor so far is empty, there's no point
2145          in continuing to search for a common ancestor at all.  But
2146          we'll keep looping for the sake of canonicalizing the
2147          targets, I suppose.  */
2148       if (**pcommon != '\0')
2149         *pcommon = svn_uri_get_longest_ancestor(*pcommon, uri,
2150                                                 scratch_pool);
2151     }
2152
2153   *pcommon = apr_pstrdup(result_pool, *pcommon);
2154
2155   if (pcondensed_targets != NULL)
2156     {
2157       size_t basedir_len;
2158
2159       if (remove_redundancies)
2160         {
2161           /* Find the common part of each pair of targets.  If
2162              common part is equal to one of the dirents, the other
2163              is a child of it, and can be removed.  If a target is
2164              equal to *pcommon, it can also be removed. */
2165
2166           /* First pass: when one non-removed target is a child of
2167              another non-removed target, remove the child. */
2168           for (i = 0; i < uri_targets->nelts; ++i)
2169             {
2170               int j;
2171
2172               if (removed[i])
2173                 continue;
2174
2175               for (j = i + 1; j < uri_targets->nelts; ++j)
2176                 {
2177                   const char *uri_i;
2178                   const char *uri_j;
2179                   const char *ancestor;
2180
2181                   if (removed[j])
2182                     continue;
2183
2184                   uri_i = APR_ARRAY_IDX(uri_targets, i, const char *);
2185                   uri_j = APR_ARRAY_IDX(uri_targets, j, const char *);
2186
2187                   ancestor = svn_uri_get_longest_ancestor(uri_i,
2188                                                           uri_j,
2189                                                           scratch_pool);
2190
2191                   if (*ancestor == '\0')
2192                     continue;
2193
2194                   if (strcmp(ancestor, uri_i) == 0)
2195                     {
2196                       removed[j] = TRUE;
2197                       num_condensed--;
2198                     }
2199                   else if (strcmp(ancestor, uri_j) == 0)
2200                     {
2201                       removed[i] = TRUE;
2202                       num_condensed--;
2203                     }
2204                 }
2205             }
2206
2207           /* Second pass: when a target is the same as *pcommon,
2208              remove the target. */
2209           for (i = 0; i < uri_targets->nelts; ++i)
2210             {
2211               const char *uri_targets_i = APR_ARRAY_IDX(uri_targets, i,
2212                                                         const char *);
2213
2214               if ((strcmp(uri_targets_i, *pcommon) == 0) && (! removed[i]))
2215                 {
2216                   removed[i] = TRUE;
2217                   num_condensed--;
2218                 }
2219             }
2220         }
2221
2222       /* Now create the return array, and copy the non-removed items */
2223       basedir_len = strlen(*pcommon);
2224       *pcondensed_targets = apr_array_make(result_pool, num_condensed,
2225                                            sizeof(const char *));
2226
2227       for (i = 0; i < uri_targets->nelts; ++i)
2228         {
2229           const char *rel_item = APR_ARRAY_IDX(uri_targets, i, const char *);
2230
2231           /* Skip this if it's been removed. */
2232           if (removed[i])
2233             continue;
2234
2235           /* If a common prefix was found, condensed_targets are given
2236              relative to that prefix.  */
2237           if (basedir_len > 0)
2238             {
2239               /* Only advance our pointer past a dirent separator if
2240                  REL_ITEM isn't the same as *PCOMMON.
2241
2242                  If *PCOMMON is a root dirent, basedir_len will already
2243                  include the closing '/', so never advance the pointer
2244                  here.
2245                  */
2246               rel_item += basedir_len;
2247               if ((rel_item[0] == '/') ||
2248                   (rel_item[0] && !svn_uri_is_root(*pcommon, basedir_len)))
2249                 {
2250                   rel_item++;
2251                 }
2252             }
2253
2254           APR_ARRAY_PUSH(*pcondensed_targets, const char *)
2255             = svn_path_uri_decode(rel_item, result_pool);
2256         }
2257     }
2258
2259   return SVN_NO_ERROR;
2260 }
2261
2262 svn_error_t *
2263 svn_dirent_is_under_root(svn_boolean_t *under_root,
2264                          const char **result_path,
2265                          const char *base_path,
2266                          const char *path,
2267                          apr_pool_t *result_pool)
2268 {
2269   apr_status_t status;
2270   char *full_path;
2271
2272   *under_root = FALSE;
2273   if (result_path)
2274     *result_path = NULL;
2275
2276   status = apr_filepath_merge(&full_path,
2277                               base_path,
2278                               path,
2279                               APR_FILEPATH_NOTABOVEROOT
2280                               | APR_FILEPATH_SECUREROOTTEST,
2281                               result_pool);
2282
2283   if (status == APR_SUCCESS)
2284     {
2285       if (result_path)
2286         *result_path = svn_dirent_canonicalize(full_path, result_pool);
2287       *under_root = TRUE;
2288       return SVN_NO_ERROR;
2289     }
2290   else if (status == APR_EABOVEROOT)
2291     {
2292       *under_root = FALSE;
2293       return SVN_NO_ERROR;
2294     }
2295
2296   return svn_error_wrap_apr(status, NULL);
2297 }
2298
2299 svn_error_t *
2300 svn_uri_get_dirent_from_file_url(const char **dirent,
2301                                  const char *url,
2302                                  apr_pool_t *pool)
2303 {
2304   const char *hostname, *path;
2305
2306   SVN_ERR_ASSERT(svn_uri_is_canonical(url, pool));
2307
2308   /* Verify that the URL is well-formed (loosely) */
2309
2310   /* First, check for the "file://" prefix. */
2311   if (strncmp(url, "file://", 7) != 0)
2312     return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2313                              _("Local URL '%s' does not contain 'file://' "
2314                                "prefix"), url);
2315
2316   /* Find the HOSTNAME portion and the PATH portion of the URL.  The host
2317      name is between the "file://" prefix and the next occurence of '/'.  We
2318      are considering everything from that '/' until the end of the URL to be
2319      the absolute path portion of the URL.
2320      If we got just "file://", treat it the same as "file:///". */
2321   hostname = url + 7;
2322   path = strchr(hostname, '/');
2323   if (path)
2324     hostname = apr_pstrmemdup(pool, hostname, path - hostname);
2325   else
2326     path = "/";
2327
2328   /* URI-decode HOSTNAME, and set it to NULL if it is "" or "localhost". */
2329   if (*hostname == '\0')
2330     hostname = NULL;
2331   else
2332     {
2333       hostname = svn_path_uri_decode(hostname, pool);
2334       if (strcmp(hostname, "localhost") == 0)
2335         hostname = NULL;
2336     }
2337
2338   /* Duplicate the URL, starting at the top of the path.
2339      At the same time, we URI-decode the path. */
2340 #ifdef SVN_USE_DOS_PATHS
2341   /* On Windows, we'll typically have to skip the leading / if the
2342      path starts with a drive letter.  Like most Web browsers, We
2343      support two variants of this scheme:
2344
2345          file:///X:/path    and
2346          file:///X|/path
2347
2348     Note that, at least on WinNT and above,  file:////./X:/path  will
2349     also work, so we must make sure the transformation doesn't break
2350     that, and  file:///path  (that looks within the current drive
2351     only) should also keep working.
2352     If we got a non-empty hostname other than localhost, we convert this
2353     into an UNC path.  In this case, we obviously don't strip the slash
2354     even if the path looks like it starts with a drive letter.
2355   */
2356   {
2357     static const char valid_drive_letters[] =
2358       "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2359     /* Casting away const! */
2360     char *dup_path = (char *)svn_path_uri_decode(path, pool);
2361
2362     /* This check assumes ':' and '|' are already decoded! */
2363     if (!hostname && dup_path[1] && strchr(valid_drive_letters, dup_path[1])
2364         && (dup_path[2] == ':' || dup_path[2] == '|'))
2365       {
2366         /* Skip the leading slash. */
2367         ++dup_path;
2368
2369         if (dup_path[1] == '|')
2370           dup_path[1] = ':';
2371
2372         if (dup_path[2] == '/' || dup_path[2] == '\0')
2373           {
2374             if (dup_path[2] == '\0')
2375               {
2376                 /* A valid dirent for the driveroot must be like "C:/" instead of
2377                    just "C:" or svn_dirent_join() will use the current directory
2378                    on the drive instead */
2379                 char *new_path = apr_pcalloc(pool, 4);
2380                 new_path[0] = dup_path[0];
2381                 new_path[1] = ':';
2382                 new_path[2] = '/';
2383                 new_path[3] = '\0';
2384                 dup_path = new_path;
2385               }
2386           }
2387       }
2388     if (hostname)
2389       {
2390         if (dup_path[0] == '/' && dup_path[1] == '\0')
2391           return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2392                                    _("Local URL '%s' contains only a hostname, "
2393                                      "no path"), url);
2394
2395         /* We still know that the path starts with a slash. */
2396         *dirent = apr_pstrcat(pool, "//", hostname, dup_path, NULL);
2397       }
2398     else
2399       *dirent = dup_path;
2400   }
2401 #else /* !SVN_USE_DOS_PATHS */
2402   /* Currently, the only hostnames we are allowing on non-Win32 platforms
2403      are the empty string and 'localhost'. */
2404   if (hostname)
2405     return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2406                              _("Local URL '%s' contains unsupported hostname"),
2407                              url);
2408
2409   *dirent = svn_path_uri_decode(path, pool);
2410 #endif /* SVN_USE_DOS_PATHS */
2411   return SVN_NO_ERROR;
2412 }
2413
2414 svn_error_t *
2415 svn_uri_get_file_url_from_dirent(const char **url,
2416                                  const char *dirent,
2417                                  apr_pool_t *pool)
2418 {
2419   assert(svn_dirent_is_canonical(dirent, pool));
2420
2421   SVN_ERR(svn_dirent_get_absolute(&dirent, dirent, pool));
2422
2423   dirent = svn_path_uri_encode(dirent, pool);
2424
2425 #ifndef SVN_USE_DOS_PATHS
2426   if (dirent[0] == '/' && dirent[1] == '\0')
2427     dirent = NULL; /* "file://" is the canonical form of "file:///" */
2428
2429   *url = apr_pstrcat(pool, "file://", dirent, (char *)NULL);
2430 #else
2431   if (dirent[0] == '/')
2432     {
2433       /* Handle UNC paths //server/share -> file://server/share */
2434       assert(dirent[1] == '/'); /* Expect UNC, not non-absolute */
2435
2436       *url = apr_pstrcat(pool, "file:", dirent, NULL);
2437     }
2438   else
2439     {
2440       char *uri = apr_pstrcat(pool, "file:///", dirent, NULL);
2441       apr_size_t len = 8 /* strlen("file:///") */ + strlen(dirent);
2442
2443       /* "C:/" is a canonical dirent on Windows,
2444          but "file:///C:/" is not a canonical uri */
2445       if (uri[len-1] == '/')
2446         uri[len-1] = '\0';
2447
2448       *url = uri;
2449     }
2450 #endif
2451
2452   return SVN_NO_ERROR;
2453 }
2454
2455
2456 \f
2457 /* -------------- The fspath API (see private/svn_fspath.h) -------------- */
2458
2459 svn_boolean_t
2460 svn_fspath__is_canonical(const char *fspath)
2461 {
2462   return fspath[0] == '/' && relpath_is_canonical(fspath + 1);
2463 }
2464
2465
2466 const char *
2467 svn_fspath__canonicalize(const char *fspath,
2468                          apr_pool_t *pool)
2469 {
2470   if ((fspath[0] == '/') && (fspath[1] == '\0'))
2471     return "/";
2472
2473   return apr_pstrcat(pool, "/", svn_relpath_canonicalize(fspath, pool),
2474                      (char *)NULL);
2475 }
2476
2477
2478 svn_boolean_t
2479 svn_fspath__is_root(const char *fspath, apr_size_t len)
2480 {
2481   /* directory is root if it's equal to '/' */
2482   return (len == 1 && fspath[0] == '/');
2483 }
2484
2485
2486 const char *
2487 svn_fspath__skip_ancestor(const char *parent_fspath,
2488                           const char *child_fspath)
2489 {
2490   assert(svn_fspath__is_canonical(parent_fspath));
2491   assert(svn_fspath__is_canonical(child_fspath));
2492
2493   return svn_relpath_skip_ancestor(parent_fspath + 1, child_fspath + 1);
2494 }
2495
2496
2497 const char *
2498 svn_fspath__dirname(const char *fspath,
2499                     apr_pool_t *pool)
2500 {
2501   assert(svn_fspath__is_canonical(fspath));
2502
2503   if (fspath[0] == '/' && fspath[1] == '\0')
2504     return apr_pstrdup(pool, fspath);
2505   else
2506     return apr_pstrcat(pool, "/", svn_relpath_dirname(fspath + 1, pool),
2507                        (char *)NULL);
2508 }
2509
2510
2511 const char *
2512 svn_fspath__basename(const char *fspath,
2513                      apr_pool_t *pool)
2514 {
2515   const char *result;
2516   assert(svn_fspath__is_canonical(fspath));
2517
2518   result = svn_relpath_basename(fspath + 1, pool);
2519
2520   assert(strchr(result, '/') == NULL);
2521   return result;
2522 }
2523
2524 void
2525 svn_fspath__split(const char **dirpath,
2526                   const char **base_name,
2527                   const char *fspath,
2528                   apr_pool_t *result_pool)
2529 {
2530   assert(dirpath != base_name);
2531
2532   if (dirpath)
2533     *dirpath = svn_fspath__dirname(fspath, result_pool);
2534
2535   if (base_name)
2536     *base_name = svn_fspath__basename(fspath, result_pool);
2537 }
2538
2539 char *
2540 svn_fspath__join(const char *fspath,
2541                  const char *relpath,
2542                  apr_pool_t *result_pool)
2543 {
2544   char *result;
2545   assert(svn_fspath__is_canonical(fspath));
2546   assert(svn_relpath_is_canonical(relpath));
2547
2548   if (relpath[0] == '\0')
2549     result = apr_pstrdup(result_pool, fspath);
2550   else if (fspath[1] == '\0')
2551     result = apr_pstrcat(result_pool, "/", relpath, (char *)NULL);
2552   else
2553     result = apr_pstrcat(result_pool, fspath, "/", relpath, (char *)NULL);
2554
2555   assert(svn_fspath__is_canonical(result));
2556   return result;
2557 }
2558
2559 char *
2560 svn_fspath__get_longest_ancestor(const char *fspath1,
2561                                  const char *fspath2,
2562                                  apr_pool_t *result_pool)
2563 {
2564   char *result;
2565   assert(svn_fspath__is_canonical(fspath1));
2566   assert(svn_fspath__is_canonical(fspath2));
2567
2568   result = apr_pstrcat(result_pool, "/",
2569                        svn_relpath_get_longest_ancestor(fspath1 + 1,
2570                                                         fspath2 + 1,
2571                                                         result_pool),
2572                        (char *)NULL);
2573
2574   assert(svn_fspath__is_canonical(result));
2575   return result;
2576 }
2577
2578
2579
2580 \f
2581 /* -------------- The urlpath API (see private/svn_fspath.h) ------------- */
2582
2583 const char *
2584 svn_urlpath__canonicalize(const char *uri,
2585                           apr_pool_t *pool)
2586 {
2587   if (svn_path_is_url(uri))
2588     {
2589       uri = svn_uri_canonicalize(uri, pool);
2590     }
2591   else
2592     {
2593       uri = svn_fspath__canonicalize(uri, pool);
2594       /* Do a little dance to normalize hex encoding. */
2595       uri = svn_path_uri_decode(uri, pool);
2596       uri = svn_path_uri_encode(uri, pool);
2597     }
2598   return uri;
2599 }