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