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