1251881Speter/*
2251881Speter * dirent_uri.c:   a library to manipulate URIs and directory entries.
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter
26251881Speter#include <string.h>
27251881Speter#include <assert.h>
28251881Speter#include <ctype.h>
29251881Speter
30251881Speter#include <apr_uri.h>
31251881Speter#include <apr_lib.h>
32251881Speter
33251881Speter#include "svn_private_config.h"
34251881Speter#include "svn_string.h"
35251881Speter#include "svn_dirent_uri.h"
36251881Speter#include "svn_path.h"
37251881Speter#include "svn_ctype.h"
38251881Speter
39251881Speter#include "dirent_uri.h"
40362181Sdim#include "private/svn_dirent_uri_private.h"
41251881Speter#include "private/svn_fspath.h"
42269833Speter#include "private/svn_cert.h"
43251881Speter
44251881Speter/* The canonical empty path.  Can this be changed?  Well, change the empty
45251881Speter   test below and the path library will work, not so sure about the fs/wc
46251881Speter   libraries. */
47251881Speter#define SVN_EMPTY_PATH ""
48251881Speter
49251881Speter/* TRUE if s is the canonical empty path, FALSE otherwise */
50251881Speter#define SVN_PATH_IS_EMPTY(s) ((s)[0] == '\0')
51251881Speter
52251881Speter/* TRUE if s,n is the platform's empty path ("."), FALSE otherwise. Can
53251881Speter   this be changed?  Well, the path library will work, not so sure about
54251881Speter   the OS! */
55251881Speter#define SVN_PATH_IS_PLATFORM_EMPTY(s,n) ((n) == 1 && (s)[0] == '.')
56251881Speter
57251881Speter/* This check must match the check on top of dirent_uri-tests.c and
58251881Speter   path-tests.c */
59251881Speter#if defined(WIN32) || defined(__CYGWIN__) || defined(__OS2__)
60251881Speter#define SVN_USE_DOS_PATHS
61251881Speter#endif
62251881Speter
63251881Speter/* Path type definition. Used only by internal functions. */
64251881Spetertypedef enum path_type_t {
65251881Speter  type_uri,
66251881Speter  type_dirent,
67251881Speter  type_relpath
68251881Speter} path_type_t;
69251881Speter
70251881Speter
71251881Speter/**** Forward declarations *****/
72251881Speter
73251881Speterstatic svn_boolean_t
74251881Speterrelpath_is_canonical(const char *relpath);
75251881Speter
76251881Speter
77251881Speter/**** Internal implementation functions *****/
78251881Speter
79251881Speter/* Return an internal-style new path based on PATH, allocated in POOL.
80251881Speter *
81251881Speter * "Internal-style" means that separators are all '/'.
82251881Speter */
83251881Speterstatic const char *
84251881Speterinternal_style(const char *path, apr_pool_t *pool)
85251881Speter{
86251881Speter#if '/' != SVN_PATH_LOCAL_SEPARATOR
87251881Speter    {
88251881Speter      char *p = apr_pstrdup(pool, path);
89251881Speter      path = p;
90251881Speter
91251881Speter      /* Convert all local-style separators to the canonical ones. */
92251881Speter      for (; *p != '\0'; ++p)
93251881Speter        if (*p == SVN_PATH_LOCAL_SEPARATOR)
94251881Speter          *p = '/';
95251881Speter    }
96251881Speter#endif
97251881Speter
98251881Speter  return path;
99251881Speter}
100251881Speter
101251881Speter/* Locale insensitive tolower() for converting parts of dirents and urls
102251881Speter   while canonicalizing */
103251881Speterstatic char
104251881Spetercanonicalize_to_lower(char c)
105251881Speter{
106251881Speter  if (c < 'A' || c > 'Z')
107251881Speter    return c;
108251881Speter  else
109251881Speter    return (char)(c - 'A' + 'a');
110251881Speter}
111251881Speter
112251881Speter/* Locale insensitive toupper() for converting parts of dirents and urls
113251881Speter   while canonicalizing */
114251881Speterstatic char
115251881Spetercanonicalize_to_upper(char c)
116251881Speter{
117251881Speter  if (c < 'a' || c > 'z')
118251881Speter    return c;
119251881Speter  else
120251881Speter    return (char)(c - 'a' + 'A');
121251881Speter}
122251881Speter
123251881Speter/* Calculates the length of the dirent absolute or non absolute root in
124251881Speter   DIRENT, return 0 if dirent is not rooted  */
125251881Speterstatic apr_size_t
126251881Speterdirent_root_length(const char *dirent, apr_size_t len)
127251881Speter{
128251881Speter#ifdef SVN_USE_DOS_PATHS
129251881Speter  if (len >= 2 && dirent[1] == ':' &&
130251881Speter      ((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
131251881Speter       (dirent[0] >= 'a' && dirent[0] <= 'z')))
132251881Speter    {
133251881Speter      return (len > 2 && dirent[2] == '/') ? 3 : 2;
134251881Speter    }
135251881Speter
136251881Speter  if (len > 2 && dirent[0] == '/' && dirent[1] == '/')
137251881Speter    {
138251881Speter      apr_size_t i = 2;
139251881Speter
140251881Speter      while (i < len && dirent[i] != '/')
141251881Speter        i++;
142251881Speter
143251881Speter      if (i == len)
144251881Speter        return len; /* Cygwin drive alias, invalid path on WIN32 */
145251881Speter
146251881Speter      i++; /* Skip '/' */
147251881Speter
148251881Speter      while (i < len && dirent[i] != '/')
149251881Speter        i++;
150251881Speter
151251881Speter      return i;
152251881Speter    }
153251881Speter#endif /* SVN_USE_DOS_PATHS */
154251881Speter  if (len >= 1 && dirent[0] == '/')
155251881Speter    return 1;
156251881Speter
157251881Speter  return 0;
158251881Speter}
159251881Speter
160251881Speter
161251881Speter/* Return the length of substring necessary to encompass the entire
162251881Speter * previous dirent segment in DIRENT, which should be a LEN byte string.
163251881Speter *
164251881Speter * A trailing slash will not be included in the returned length except
165251881Speter * in the case in which DIRENT is absolute and there are no more
166251881Speter * previous segments.
167251881Speter */
168251881Speterstatic apr_size_t
169251881Speterdirent_previous_segment(const char *dirent,
170251881Speter                        apr_size_t len)
171251881Speter{
172251881Speter  if (len == 0)
173251881Speter    return 0;
174251881Speter
175251881Speter  --len;
176251881Speter  while (len > 0 && dirent[len] != '/'
177251881Speter#ifdef SVN_USE_DOS_PATHS
178251881Speter                 && (dirent[len] != ':' || len != 1)
179251881Speter#endif /* SVN_USE_DOS_PATHS */
180251881Speter        )
181251881Speter    --len;
182251881Speter
183251881Speter  /* check if the remaining segment including trailing '/' is a root dirent */
184251881Speter  if (dirent_root_length(dirent, len+1) == len + 1)
185251881Speter    return len + 1;
186251881Speter  else
187251881Speter    return len;
188251881Speter}
189251881Speter
190251881Speter/* Calculates the length occupied by the schema defined root of URI */
191251881Speterstatic apr_size_t
192251881Speteruri_schema_root_length(const char *uri, apr_size_t len)
193251881Speter{
194251881Speter  apr_size_t i;
195251881Speter
196251881Speter  for (i = 0; i < len; i++)
197251881Speter    {
198251881Speter      if (uri[i] == '/')
199251881Speter        {
200251881Speter          if (i > 0 && uri[i-1] == ':' && i < len-1 && uri[i+1] == '/')
201251881Speter            {
202251881Speter              /* We have an absolute uri */
203251881Speter              if (i == 5 && strncmp("file", uri, 4) == 0)
204251881Speter                return 7; /* file:// */
205251881Speter              else
206251881Speter                {
207251881Speter                  for (i += 2; i < len; i++)
208251881Speter                    if (uri[i] == '/')
209251881Speter                      return i;
210251881Speter
211251881Speter                  return len; /* Only a hostname is found */
212251881Speter                }
213251881Speter            }
214251881Speter          else
215251881Speter            return 0;
216251881Speter        }
217251881Speter    }
218251881Speter
219251881Speter  return 0;
220251881Speter}
221251881Speter
222251881Speter/* Returns TRUE if svn_dirent_is_absolute(dirent) or when dirent has
223251881Speter   a non absolute root. (E.g. '/' or 'F:' on Windows) */
224251881Speterstatic svn_boolean_t
225251881Speterdirent_is_rooted(const char *dirent)
226251881Speter{
227251881Speter  if (! dirent)
228251881Speter    return FALSE;
229251881Speter
230251881Speter  /* Root on all systems */
231251881Speter  if (dirent[0] == '/')
232251881Speter    return TRUE;
233251881Speter
234251881Speter  /* On Windows, dirent is also absolute when it starts with 'H:' or 'H:/'
235251881Speter     where 'H' is any letter. */
236251881Speter#ifdef SVN_USE_DOS_PATHS
237251881Speter  if (((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
238251881Speter       (dirent[0] >= 'a' && dirent[0] <= 'z')) &&
239251881Speter      (dirent[1] == ':'))
240251881Speter     return TRUE;
241251881Speter#endif /* SVN_USE_DOS_PATHS */
242251881Speter
243251881Speter  return FALSE;
244251881Speter}
245251881Speter
246251881Speter/* Return the length of substring necessary to encompass the entire
247251881Speter * previous relpath segment in RELPATH, which should be a LEN byte string.
248251881Speter *
249251881Speter * A trailing slash will not be included in the returned length.
250251881Speter */
251251881Speterstatic apr_size_t
252251881Speterrelpath_previous_segment(const char *relpath,
253251881Speter                         apr_size_t len)
254251881Speter{
255251881Speter  if (len == 0)
256251881Speter    return 0;
257251881Speter
258251881Speter  --len;
259251881Speter  while (len > 0 && relpath[len] != '/')
260251881Speter    --len;
261251881Speter
262251881Speter  return len;
263251881Speter}
264251881Speter
265251881Speter/* Return the length of substring necessary to encompass the entire
266251881Speter * previous uri segment in URI, which should be a LEN byte string.
267251881Speter *
268251881Speter * A trailing slash will not be included in the returned length except
269251881Speter * in the case in which URI is absolute and there are no more
270251881Speter * previous segments.
271251881Speter */
272251881Speterstatic apr_size_t
273251881Speteruri_previous_segment(const char *uri,
274251881Speter                     apr_size_t len)
275251881Speter{
276251881Speter  apr_size_t root_length;
277251881Speter  apr_size_t i = len;
278251881Speter  if (len == 0)
279251881Speter    return 0;
280251881Speter
281251881Speter  root_length = uri_schema_root_length(uri, len);
282251881Speter
283251881Speter  --i;
284251881Speter  while (len > root_length && uri[i] != '/')
285251881Speter    --i;
286251881Speter
287251881Speter  if (i == 0 && len > 1 && *uri == '/')
288251881Speter    return 1;
289251881Speter
290251881Speter  return i;
291251881Speter}
292251881Speter
293251881Speter/* Return the canonicalized version of PATH, of type TYPE, allocated in
294251881Speter * POOL.
295251881Speter */
296362181Sdimstatic svn_error_t *
297362181Sdimcanonicalize(const char **canonical_path,
298362181Sdim             path_type_t type, const char *path, apr_pool_t *pool)
299251881Speter{
300251881Speter  char *canon, *dst;
301251881Speter  const char *src;
302251881Speter  apr_size_t seglen;
303251881Speter  apr_size_t schemelen = 0;
304251881Speter  apr_size_t canon_segments = 0;
305251881Speter  svn_boolean_t url = FALSE;
306251881Speter  char *schema_data = NULL;
307251881Speter
308251881Speter  /* "" is already canonical, so just return it; note that later code
309251881Speter     depends on path not being zero-length.  */
310251881Speter  if (SVN_PATH_IS_EMPTY(path))
311251881Speter    {
312362181Sdim      *canonical_path = "";
313362181Sdim      if (type == type_uri)
314362181Sdim        return svn_error_create(SVN_ERR_CANONICALIZATION_FAILED, NULL,
315362181Sdim                                _("An empty URI can not be canonicalized"));
316362181Sdim      else
317362181Sdim        return SVN_NO_ERROR;
318251881Speter    }
319251881Speter
320251881Speter  dst = canon = apr_pcalloc(pool, strlen(path) + 1);
321251881Speter
322251881Speter  /* If this is supposed to be an URI, it should start with
323251881Speter     "scheme://".  We'll copy the scheme, host name, etc. to DST and
324251881Speter     set URL = TRUE. */
325251881Speter  src = path;
326251881Speter  if (type == type_uri)
327251881Speter    {
328362181Sdim      if (*src == '/')
329362181Sdim        {
330362181Sdim          *canonical_path = src;
331362181Sdim          return svn_error_create(SVN_ERR_CANONICALIZATION_FAILED, NULL,
332362181Sdim                                  _("A URI can not start with '/'"));
333362181Sdim        }
334251881Speter
335251881Speter      while (*src && (*src != '/') && (*src != ':'))
336251881Speter        src++;
337251881Speter
338251881Speter      if (*src == ':' && *(src+1) == '/' && *(src+2) == '/')
339251881Speter        {
340251881Speter          const char *seg;
341251881Speter
342251881Speter          url = TRUE;
343251881Speter
344251881Speter          /* Found a scheme, convert to lowercase and copy to dst. */
345251881Speter          src = path;
346251881Speter          while (*src != ':')
347251881Speter            {
348251881Speter              *(dst++) = canonicalize_to_lower((*src++));
349251881Speter              schemelen++;
350251881Speter            }
351251881Speter          *(dst++) = ':';
352251881Speter          *(dst++) = '/';
353251881Speter          *(dst++) = '/';
354251881Speter          src += 3;
355251881Speter          schemelen += 3;
356251881Speter
357251881Speter          /* This might be the hostname */
358251881Speter          seg = src;
359251881Speter          while (*src && (*src != '/') && (*src != '@'))
360251881Speter            src++;
361251881Speter
362251881Speter          if (*src == '@')
363251881Speter            {
364251881Speter              /* Copy the username & password. */
365251881Speter              seglen = src - seg + 1;
366251881Speter              memcpy(dst, seg, seglen);
367251881Speter              dst += seglen;
368251881Speter              src++;
369251881Speter            }
370251881Speter          else
371251881Speter            src = seg;
372251881Speter
373251881Speter          /* Found a hostname, convert to lowercase and copy to dst. */
374251881Speter          if (*src == '[')
375251881Speter            {
376251881Speter             *(dst++) = *(src++); /* Copy '[' */
377251881Speter
378251881Speter              while (*src == ':'
379251881Speter                     || (*src >= '0' && (*src <= '9'))
380251881Speter                     || (*src >= 'a' && (*src <= 'f'))
381251881Speter                     || (*src >= 'A' && (*src <= 'F')))
382251881Speter                {
383251881Speter                  *(dst++) = canonicalize_to_lower((*src++));
384251881Speter                }
385251881Speter
386251881Speter              if (*src == ']')
387251881Speter                *(dst++) = *(src++); /* Copy ']' */
388251881Speter            }
389251881Speter          else
390251881Speter            while (*src && (*src != '/') && (*src != ':'))
391251881Speter              *(dst++) = canonicalize_to_lower((*src++));
392251881Speter
393251881Speter          if (*src == ':')
394251881Speter            {
395251881Speter              /* We probably have a port number: Is it a default portnumber
396251881Speter                 which doesn't belong in a canonical url? */
397251881Speter              if (src[1] == '8' && src[2] == '0'
398251881Speter                  && (src[3]== '/'|| !src[3])
399251881Speter                  && !strncmp(canon, "http:", 5))
400251881Speter                {
401251881Speter                  src += 3;
402251881Speter                }
403251881Speter              else if (src[1] == '4' && src[2] == '4' && src[3] == '3'
404251881Speter                       && (src[4]== '/'|| !src[4])
405251881Speter                       && !strncmp(canon, "https:", 6))
406251881Speter                {
407251881Speter                  src += 4;
408251881Speter                }
409251881Speter              else if (src[1] == '3' && src[2] == '6'
410251881Speter                       && src[3] == '9' && src[4] == '0'
411251881Speter                       && (src[5]== '/'|| !src[5])
412251881Speter                       && !strncmp(canon, "svn:", 4))
413251881Speter                {
414251881Speter                  src += 5;
415251881Speter                }
416251881Speter              else if (src[1] == '/' || !src[1])
417251881Speter                {
418251881Speter                  src += 1;
419251881Speter                }
420251881Speter
421251881Speter              while (*src && (*src != '/'))
422251881Speter                *(dst++) = canonicalize_to_lower((*src++));
423251881Speter            }
424251881Speter
425251881Speter          /* Copy trailing slash, or null-terminator. */
426251881Speter          *(dst) = *(src);
427251881Speter
428251881Speter          /* Move src and dst forward only if we are not
429251881Speter           * at null-terminator yet. */
430251881Speter          if (*src)
431251881Speter            {
432251881Speter              src++;
433251881Speter              dst++;
434251881Speter              schema_data = dst;
435251881Speter            }
436251881Speter
437251881Speter          canon_segments = 1;
438251881Speter        }
439251881Speter    }
440251881Speter
441251881Speter  /* Copy to DST any separator or drive letter that must come before the
442251881Speter     first regular path segment. */
443251881Speter  if (! url && type != type_relpath)
444251881Speter    {
445251881Speter      src = path;
446251881Speter      /* If this is an absolute path, then just copy over the initial
447251881Speter         separator character. */
448251881Speter      if (*src == '/')
449251881Speter        {
450251881Speter          *(dst++) = *(src++);
451251881Speter
452251881Speter#ifdef SVN_USE_DOS_PATHS
453251881Speter          /* On Windows permit two leading separator characters which means an
454251881Speter           * UNC path. */
455251881Speter          if ((type == type_dirent) && *src == '/')
456251881Speter            *(dst++) = *(src++);
457251881Speter#endif /* SVN_USE_DOS_PATHS */
458251881Speter        }
459251881Speter#ifdef SVN_USE_DOS_PATHS
460251881Speter      /* On Windows the first segment can be a drive letter, which we normalize
461251881Speter         to upper case. */
462251881Speter      else if (type == type_dirent &&
463251881Speter               ((*src >= 'a' && *src <= 'z') ||
464251881Speter                (*src >= 'A' && *src <= 'Z')) &&
465251881Speter               (src[1] == ':'))
466251881Speter        {
467251881Speter          *(dst++) = canonicalize_to_upper(*(src++));
468251881Speter          /* Leave the ':' to be processed as (or as part of) a path segment
469251881Speter             by the following code block, so we need not care whether it has
470251881Speter             a slash after it. */
471251881Speter        }
472251881Speter#endif /* SVN_USE_DOS_PATHS */
473251881Speter    }
474251881Speter
475251881Speter  while (*src)
476251881Speter    {
477251881Speter      /* Parse each segment, finding the closing '/' (which might look
478251881Speter         like '%2F' for URIs).  */
479251881Speter      const char *next = src;
480251881Speter      apr_size_t slash_len = 0;
481251881Speter
482251881Speter      while (*next
483251881Speter             && (next[0] != '/')
484251881Speter             && (! (type == type_uri && next[0] == '%' && next[1] == '2' &&
485251881Speter                    canonicalize_to_upper(next[2]) == 'F')))
486251881Speter        {
487251881Speter          ++next;
488251881Speter        }
489251881Speter
490251881Speter      /* Record how long our "slash" is. */
491251881Speter      if (next[0] == '/')
492251881Speter        slash_len = 1;
493251881Speter      else if (type == type_uri && next[0] == '%')
494251881Speter        slash_len = 3;
495251881Speter
496251881Speter      seglen = next - src;
497251881Speter
498251881Speter      if (seglen == 0
499251881Speter          || (seglen == 1 && src[0] == '.')
500251881Speter          || (type == type_uri && seglen == 3 && src[0] == '%' && src[1] == '2'
501251881Speter              && canonicalize_to_upper(src[2]) == 'E'))
502251881Speter        {
503251881Speter          /* Empty or noop segment, so do nothing.  (For URIs, '%2E'
504251881Speter             is equivalent to '.').  */
505251881Speter        }
506251881Speter#ifdef SVN_USE_DOS_PATHS
507251881Speter      /* If this is the first path segment of a file:// URI and it contains a
508251881Speter         windows drive letter, convert the drive letter to upper case. */
509362181Sdim      else if (url && canon_segments == 1 && seglen >= 2 &&
510251881Speter               (strncmp(canon, "file:", 5) == 0) &&
511251881Speter               src[0] >= 'a' && src[0] <= 'z' && src[1] == ':')
512251881Speter        {
513251881Speter          *(dst++) = canonicalize_to_upper(src[0]);
514251881Speter          *(dst++) = ':';
515362181Sdim          if (seglen > 2) /* drive relative path */
516362181Sdim            {
517362181Sdim              memcpy(dst, src + 2, seglen - 2);
518362181Sdim              dst += seglen - 2;
519362181Sdim            }
520362181Sdim
521362181Sdim          if (slash_len)
522362181Sdim            *(dst++) = '/';
523251881Speter          canon_segments++;
524251881Speter        }
525251881Speter#endif /* SVN_USE_DOS_PATHS */
526251881Speter      else
527251881Speter        {
528251881Speter          /* An actual segment, append it to the destination path */
529251881Speter          memcpy(dst, src, seglen);
530251881Speter          dst += seglen;
531251881Speter          if (slash_len)
532251881Speter            *(dst++) = '/';
533251881Speter          canon_segments++;
534251881Speter        }
535251881Speter
536251881Speter      /* Skip over trailing slash to the next segment. */
537251881Speter      src = next + slash_len;
538251881Speter    }
539251881Speter
540251881Speter  /* Remove the trailing slash if there was at least one
541251881Speter   * canonical segment and the last segment ends with a slash.
542251881Speter   *
543251881Speter   * But keep in mind that, for URLs, the scheme counts as a
544251881Speter   * canonical segment -- so if path is ONLY a scheme (such
545251881Speter   * as "https://") we should NOT remove the trailing slash. */
546251881Speter  if ((canon_segments > 0 && *(dst - 1) == '/')
547251881Speter      && ! (url && path[schemelen] == '\0'))
548251881Speter    {
549251881Speter      dst --;
550251881Speter    }
551251881Speter
552251881Speter  *dst = '\0';
553251881Speter
554251881Speter#ifdef SVN_USE_DOS_PATHS
555251881Speter  /* Skip leading double slashes when there are less than 2
556251881Speter   * canon segments. UNC paths *MUST* have two segments. */
557251881Speter  if ((type == type_dirent) && canon[0] == '/' && canon[1] == '/')
558251881Speter    {
559251881Speter      if (canon_segments < 2)
560362181Sdim        {
561362181Sdim          *canonical_path = canon + 1;
562362181Sdim          return SVN_NO_ERROR;
563362181Sdim        }
564251881Speter      else
565251881Speter        {
566251881Speter          /* Now we're sure this is a valid UNC path, convert the server name
567251881Speter             (the first path segment) to lowercase as Windows treats it as case
568251881Speter             insensitive.
569251881Speter             Note: normally the share name is treated as case insensitive too,
570251881Speter             but it seems to be possible to configure Samba to treat those as
571251881Speter             case sensitive, so better leave that alone. */
572251881Speter          for (dst = canon + 2; *dst && *dst != '/'; dst++)
573251881Speter            *dst = canonicalize_to_lower(*dst);
574251881Speter        }
575251881Speter    }
576251881Speter#endif /* SVN_USE_DOS_PATHS */
577251881Speter
578251881Speter  /* Check the normalization of characters in a uri */
579251881Speter  if (schema_data)
580251881Speter    {
581251881Speter      int need_extra = 0;
582251881Speter      src = schema_data;
583251881Speter
584251881Speter      while (*src)
585251881Speter        {
586251881Speter          switch (*src)
587251881Speter            {
588251881Speter              case '/':
589251881Speter                break;
590251881Speter              case '%':
591251881Speter                if (!svn_ctype_isxdigit(*(src+1)) ||
592251881Speter                    !svn_ctype_isxdigit(*(src+2)))
593251881Speter                  need_extra += 2;
594251881Speter                else
595251881Speter                  src += 2;
596251881Speter                break;
597251881Speter              default:
598251881Speter                if (!svn_uri__char_validity[(unsigned char)*src])
599251881Speter                  need_extra += 2;
600251881Speter                break;
601251881Speter            }
602251881Speter          src++;
603251881Speter        }
604251881Speter
605251881Speter      if (need_extra > 0)
606251881Speter        {
607251881Speter          apr_size_t pre_schema_size = (apr_size_t)(schema_data - canon);
608251881Speter
609251881Speter          dst = apr_palloc(pool, (apr_size_t)(src - canon) + need_extra + 1);
610251881Speter          memcpy(dst, canon, pre_schema_size);
611251881Speter          canon = dst;
612251881Speter
613251881Speter          dst += pre_schema_size;
614251881Speter        }
615251881Speter      else
616251881Speter        dst = schema_data;
617251881Speter
618251881Speter      src = schema_data;
619251881Speter
620251881Speter      while (*src)
621251881Speter        {
622251881Speter          switch (*src)
623251881Speter            {
624251881Speter              case '/':
625251881Speter                *(dst++) = '/';
626251881Speter                break;
627251881Speter              case '%':
628251881Speter                if (!svn_ctype_isxdigit(*(src+1)) ||
629251881Speter                    !svn_ctype_isxdigit(*(src+2)))
630251881Speter                  {
631251881Speter                    *(dst++) = '%';
632251881Speter                    *(dst++) = '2';
633251881Speter                    *(dst++) = '5';
634251881Speter                  }
635251881Speter                else
636251881Speter                  {
637251881Speter                    char digitz[3];
638251881Speter                    int val;
639251881Speter
640251881Speter                    digitz[0] = *(++src);
641251881Speter                    digitz[1] = *(++src);
642251881Speter                    digitz[2] = 0;
643251881Speter
644251881Speter                    val = (int)strtol(digitz, NULL, 16);
645251881Speter
646251881Speter                    if (svn_uri__char_validity[(unsigned char)val])
647251881Speter                      *(dst++) = (char)val;
648251881Speter                    else
649251881Speter                      {
650251881Speter                        *(dst++) = '%';
651251881Speter                        *(dst++) = canonicalize_to_upper(digitz[0]);
652251881Speter                        *(dst++) = canonicalize_to_upper(digitz[1]);
653251881Speter                      }
654251881Speter                  }
655251881Speter                break;
656251881Speter              default:
657251881Speter                if (!svn_uri__char_validity[(unsigned char)*src])
658251881Speter                  {
659251881Speter                    apr_snprintf(dst, 4, "%%%02X", (unsigned char)*src);
660251881Speter                    dst += 3;
661251881Speter                  }
662251881Speter                else
663251881Speter                  *(dst++) = *src;
664251881Speter                break;
665251881Speter            }
666251881Speter          src++;
667251881Speter        }
668251881Speter      *dst = '\0';
669251881Speter    }
670251881Speter
671362181Sdim  *canonical_path = canon;
672362181Sdim  return SVN_NO_ERROR;
673251881Speter}
674251881Speter
675251881Speter/* Return the string length of the longest common ancestor of PATH1 and PATH2.
676251881Speter * Pass type_uri for TYPE if PATH1 and PATH2 are URIs, and type_dirent if
677251881Speter * PATH1 and PATH2 are regular paths.
678251881Speter *
679251881Speter * If the two paths do not share a common ancestor, return 0.
680251881Speter *
681251881Speter * New strings are allocated in POOL.
682251881Speter */
683251881Speterstatic apr_size_t
684251881Speterget_longest_ancestor_length(path_type_t types,
685251881Speter                            const char *path1,
686251881Speter                            const char *path2,
687251881Speter                            apr_pool_t *pool)
688251881Speter{
689251881Speter  apr_size_t path1_len, path2_len;
690251881Speter  apr_size_t i = 0;
691251881Speter  apr_size_t last_dirsep = 0;
692251881Speter#ifdef SVN_USE_DOS_PATHS
693251881Speter  svn_boolean_t unc = FALSE;
694251881Speter#endif
695251881Speter
696251881Speter  path1_len = strlen(path1);
697251881Speter  path2_len = strlen(path2);
698251881Speter
699251881Speter  if (SVN_PATH_IS_EMPTY(path1) || SVN_PATH_IS_EMPTY(path2))
700251881Speter    return 0;
701251881Speter
702251881Speter  while (path1[i] == path2[i])
703251881Speter    {
704251881Speter      /* Keep track of the last directory separator we hit. */
705251881Speter      if (path1[i] == '/')
706251881Speter        last_dirsep = i;
707251881Speter
708251881Speter      i++;
709251881Speter
710251881Speter      /* If we get to the end of either path, break out. */
711251881Speter      if ((i == path1_len) || (i == path2_len))
712251881Speter        break;
713251881Speter    }
714251881Speter
715251881Speter  /* two special cases:
716251881Speter     1. '/' is the longest common ancestor of '/' and '/foo' */
717251881Speter  if (i == 1 && path1[0] == '/' && path2[0] == '/')
718251881Speter    return 1;
719251881Speter  /* 2. '' is the longest common ancestor of any non-matching
720251881Speter   * strings 'foo' and 'bar' */
721251881Speter  if (types == type_dirent && i == 0)
722251881Speter    return 0;
723251881Speter
724251881Speter  /* Handle some windows specific cases */
725251881Speter#ifdef SVN_USE_DOS_PATHS
726251881Speter  if (types == type_dirent)
727251881Speter    {
728251881Speter      /* don't count the '//' from UNC paths */
729251881Speter      if (last_dirsep == 1 && path1[0] == '/' && path1[1] == '/')
730251881Speter        {
731251881Speter          last_dirsep = 0;
732251881Speter          unc = TRUE;
733251881Speter        }
734251881Speter
735251881Speter      /* X:/ and X:/foo */
736251881Speter      if (i == 3 && path1[2] == '/' && path1[1] == ':')
737251881Speter        return i;
738251881Speter
739251881Speter      /* Cannot use SVN_ERR_ASSERT here, so we'll have to crash, sorry.
740251881Speter       * Note that this assertion triggers only if the code above has
741251881Speter       * been broken. The code below relies on this assertion, because
742251881Speter       * it uses [i - 1] as index. */
743251881Speter      assert(i > 0);
744251881Speter
745251881Speter      /* X: and X:/ */
746251881Speter      if ((path1[i - 1] == ':' && path2[i] == '/') ||
747251881Speter          (path2[i - 1] == ':' && path1[i] == '/'))
748251881Speter          return 0;
749251881Speter      /* X: and X:foo */
750251881Speter      if (path1[i - 1] == ':' || path2[i - 1] == ':')
751251881Speter          return i;
752251881Speter    }
753251881Speter#endif /* SVN_USE_DOS_PATHS */
754251881Speter
755251881Speter  /* last_dirsep is now the offset of the last directory separator we
756251881Speter     crossed before reaching a non-matching byte.  i is the offset of
757251881Speter     that non-matching byte, and is guaranteed to be <= the length of
758251881Speter     whichever path is shorter.
759251881Speter     If one of the paths is the common part return that. */
760251881Speter  if (((i == path1_len) && (path2[i] == '/'))
761251881Speter           || ((i == path2_len) && (path1[i] == '/'))
762251881Speter           || ((i == path1_len) && (i == path2_len)))
763251881Speter    return i;
764251881Speter  else
765251881Speter    {
766251881Speter      /* Nothing in common but the root folder '/' or 'X:/' for Windows
767251881Speter         dirents. */
768251881Speter#ifdef SVN_USE_DOS_PATHS
769251881Speter      if (! unc)
770251881Speter        {
771251881Speter          /* X:/foo and X:/bar returns X:/ */
772251881Speter          if ((types == type_dirent) &&
773251881Speter              last_dirsep == 2 && path1[1] == ':' && path1[2] == '/'
774251881Speter                               && path2[1] == ':' && path2[2] == '/')
775251881Speter            return 3;
776251881Speter#endif /* SVN_USE_DOS_PATHS */
777251881Speter          if (last_dirsep == 0 && path1[0] == '/' && path2[0] == '/')
778251881Speter            return 1;
779251881Speter#ifdef SVN_USE_DOS_PATHS
780251881Speter        }
781251881Speter#endif
782251881Speter    }
783251881Speter
784251881Speter  return last_dirsep;
785251881Speter}
786251881Speter
787251881Speter/* Determine whether PATH2 is a child of PATH1.
788251881Speter *
789251881Speter * PATH2 is a child of PATH1 if
790251881Speter * 1) PATH1 is empty, and PATH2 is not empty and not an absolute path.
791251881Speter * or
792251881Speter * 2) PATH2 is has n components, PATH1 has x < n components,
793251881Speter *    and PATH1 matches PATH2 in all its x components.
794251881Speter *    Components are separated by a slash, '/'.
795251881Speter *
796251881Speter * Pass type_uri for TYPE if PATH1 and PATH2 are URIs, and type_dirent if
797251881Speter * PATH1 and PATH2 are regular paths.
798251881Speter *
799251881Speter * If PATH2 is not a child of PATH1, return NULL.
800251881Speter *
801251881Speter * If PATH2 is a child of PATH1, and POOL is not NULL, allocate a copy
802251881Speter * of the child part of PATH2 in POOL and return a pointer to the
803251881Speter * newly allocated child part.
804251881Speter *
805251881Speter * If PATH2 is a child of PATH1, and POOL is NULL, return a pointer
806251881Speter * pointing to the child part of PATH2.
807251881Speter * */
808251881Speterstatic const char *
809251881Speteris_child(path_type_t type, const char *path1, const char *path2,
810251881Speter         apr_pool_t *pool)
811251881Speter{
812251881Speter  apr_size_t i;
813251881Speter
814251881Speter  /* Allow "" and "foo" or "H:foo" to be parent/child */
815251881Speter  if (SVN_PATH_IS_EMPTY(path1))               /* "" is the parent  */
816251881Speter    {
817251881Speter      if (SVN_PATH_IS_EMPTY(path2))            /* "" not a child    */
818251881Speter        return NULL;
819251881Speter
820251881Speter      /* check if this is an absolute path */
821251881Speter      if ((type == type_uri) ||
822251881Speter          (type == type_dirent && dirent_is_rooted(path2)))
823251881Speter        return NULL;
824251881Speter      else
825251881Speter        /* everything else is child */
826251881Speter        return pool ? apr_pstrdup(pool, path2) : path2;
827251881Speter    }
828251881Speter
829251881Speter  /* Reach the end of at least one of the paths.  How should we handle
830251881Speter     things like path1:"foo///bar" and path2:"foo/bar/baz"?  It doesn't
831251881Speter     appear to arise in the current Subversion code, it's not clear to me
832251881Speter     if they should be parent/child or not. */
833251881Speter  /* Hmmm... aren't paths assumed to be canonical in this function?
834251881Speter   * How can "foo///bar" even happen if the paths are canonical? */
835251881Speter  for (i = 0; path1[i] && path2[i]; i++)
836251881Speter    if (path1[i] != path2[i])
837251881Speter      return NULL;
838251881Speter
839251881Speter  /* FIXME: This comment does not really match
840251881Speter   * the checks made in the code it refers to: */
841251881Speter  /* There are two cases that are parent/child
842251881Speter          ...      path1[i] == '\0'
843251881Speter          .../foo  path2[i] == '/'
844251881Speter      or
845251881Speter          /        path1[i] == '\0'
846251881Speter          /foo     path2[i] != '/'
847251881Speter
848251881Speter     Other root paths (like X:/) fall under the former case:
849251881Speter          X:/        path1[i] == '\0'
850251881Speter          X:/foo     path2[i] != '/'
851251881Speter
852251881Speter     Check for '//' to avoid matching '/' and '//srv'.
853251881Speter  */
854251881Speter  if (path1[i] == '\0' && path2[i])
855251881Speter    {
856251881Speter      if (path1[i - 1] == '/'
857251881Speter#ifdef SVN_USE_DOS_PATHS
858251881Speter          || ((type == type_dirent) && path1[i - 1] == ':')
859251881Speter#endif
860251881Speter           )
861251881Speter        {
862251881Speter          if (path2[i] == '/')
863251881Speter            /* .../
864251881Speter             * ..../
865251881Speter             *     i   */
866251881Speter            return NULL;
867251881Speter          else
868251881Speter            /* .../
869251881Speter             * .../foo
870251881Speter             *     i    */
871251881Speter            return pool ? apr_pstrdup(pool, path2 + i) : path2 + i;
872251881Speter        }
873251881Speter      else if (path2[i] == '/')
874251881Speter        {
875251881Speter          if (path2[i + 1])
876251881Speter            /* ...
877251881Speter             * .../foo
878251881Speter             *    i    */
879251881Speter            return pool ? apr_pstrdup(pool, path2 + i + 1) : path2 + i + 1;
880251881Speter          else
881251881Speter            /* ...
882251881Speter             * .../
883251881Speter             *    i    */
884251881Speter            return NULL;
885251881Speter        }
886251881Speter    }
887251881Speter
888251881Speter  /* Otherwise, path2 isn't a child. */
889251881Speter  return NULL;
890251881Speter}
891251881Speter
892251881Speter
893251881Speter/**** Public API functions ****/
894251881Speter
895251881Speterconst char *
896251881Spetersvn_dirent_internal_style(const char *dirent, apr_pool_t *pool)
897251881Speter{
898251881Speter  return svn_dirent_canonicalize(internal_style(dirent, pool), pool);
899251881Speter}
900251881Speter
901362181Sdimsvn_error_t *
902362181Sdimsvn_dirent_internal_style_safe(const char **internal_style_dirent,
903362181Sdim                               const char **non_canonical_result,
904362181Sdim                               const char *dirent,
905362181Sdim                               apr_pool_t *result_pool,
906362181Sdim                               apr_pool_t *scratch_pool)
907362181Sdim{
908362181Sdim  return svn_error_trace(
909362181Sdim      svn_dirent_canonicalize_safe(internal_style_dirent,
910362181Sdim                                   non_canonical_result,
911362181Sdim                                   internal_style(dirent, scratch_pool),
912362181Sdim                                   result_pool, scratch_pool));
913362181Sdim}
914362181Sdim
915251881Speterconst char *
916251881Spetersvn_dirent_local_style(const char *dirent, apr_pool_t *pool)
917251881Speter{
918251881Speter  /* Internally, Subversion represents the current directory with the
919251881Speter     empty string.  But users like to see "." . */
920251881Speter  if (SVN_PATH_IS_EMPTY(dirent))
921251881Speter    return ".";
922251881Speter
923251881Speter#if '/' != SVN_PATH_LOCAL_SEPARATOR
924251881Speter    {
925251881Speter      char *p = apr_pstrdup(pool, dirent);
926251881Speter      dirent = p;
927251881Speter
928251881Speter      /* Convert all canonical separators to the local-style ones. */
929251881Speter      for (; *p != '\0'; ++p)
930251881Speter        if (*p == '/')
931251881Speter          *p = SVN_PATH_LOCAL_SEPARATOR;
932251881Speter    }
933251881Speter#endif
934251881Speter
935251881Speter  return dirent;
936251881Speter}
937251881Speter
938362181Sdimsvn_error_t *
939362181Sdimsvn_relpath__make_internal(const char **internal_style_relpath,
940362181Sdim                           const char *relpath,
941362181Sdim                           apr_pool_t *result_pool,
942362181Sdim                           apr_pool_t *scratch_pool)
943251881Speter{
944362181Sdim  return svn_error_trace(
945362181Sdim      svn_relpath_canonicalize_safe(internal_style_relpath, NULL,
946362181Sdim                                    internal_style(relpath, scratch_pool),
947362181Sdim                                    result_pool, scratch_pool));
948251881Speter}
949251881Speter
950251881Speter/* We decided against using apr_filepath_root here because of the negative
951251881Speter   performance impact (creating a pool and converting strings ). */
952251881Spetersvn_boolean_t
953251881Spetersvn_dirent_is_root(const char *dirent, apr_size_t len)
954251881Speter{
955251881Speter#ifdef SVN_USE_DOS_PATHS
956251881Speter  /* On Windows and Cygwin, 'H:' or 'H:/' (where 'H' is any letter)
957251881Speter     are also root directories */
958251881Speter  if ((len == 2 || ((len == 3) && (dirent[2] == '/'))) &&
959251881Speter      (dirent[1] == ':') &&
960251881Speter      ((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
961251881Speter       (dirent[0] >= 'a' && dirent[0] <= 'z')))
962251881Speter    return TRUE;
963251881Speter
964251881Speter  /* On Windows and Cygwin //server/share is a root directory,
965251881Speter     and on Cygwin //drive is a drive alias */
966251881Speter  if (len >= 2 && dirent[0] == '/' && dirent[1] == '/'
967251881Speter      && dirent[len - 1] != '/')
968251881Speter    {
969251881Speter      int segments = 0;
970251881Speter      apr_size_t i;
971251881Speter      for (i = len; i >= 2; i--)
972251881Speter        {
973251881Speter          if (dirent[i] == '/')
974251881Speter            {
975251881Speter              segments ++;
976251881Speter              if (segments > 1)
977251881Speter                return FALSE;
978251881Speter            }
979251881Speter        }
980251881Speter#ifdef __CYGWIN__
981251881Speter      return (segments <= 1);
982251881Speter#else
983251881Speter      return (segments == 1); /* //drive is invalid on plain Windows */
984251881Speter#endif
985251881Speter    }
986251881Speter#endif
987251881Speter
988251881Speter  /* directory is root if it's equal to '/' */
989251881Speter  if (len == 1 && dirent[0] == '/')
990251881Speter    return TRUE;
991251881Speter
992251881Speter  return FALSE;
993251881Speter}
994251881Speter
995251881Spetersvn_boolean_t
996251881Spetersvn_uri_is_root(const char *uri, apr_size_t len)
997251881Speter{
998251881Speter  assert(svn_uri_is_canonical(uri, NULL));
999251881Speter  return (len == uri_schema_root_length(uri, len));
1000251881Speter}
1001251881Speter
1002251881Speterchar *svn_dirent_join(const char *base,
1003251881Speter                      const char *component,
1004251881Speter                      apr_pool_t *pool)
1005251881Speter{
1006251881Speter  apr_size_t blen = strlen(base);
1007251881Speter  apr_size_t clen = strlen(component);
1008251881Speter  char *dirent;
1009251881Speter  int add_separator;
1010251881Speter
1011251881Speter  assert(svn_dirent_is_canonical(base, pool));
1012251881Speter  assert(svn_dirent_is_canonical(component, pool));
1013251881Speter
1014251881Speter  /* If the component is absolute, then return it.  */
1015251881Speter  if (svn_dirent_is_absolute(component))
1016251881Speter    return apr_pmemdup(pool, component, clen + 1);
1017251881Speter
1018251881Speter  /* If either is empty return the other */
1019251881Speter  if (SVN_PATH_IS_EMPTY(base))
1020251881Speter    return apr_pmemdup(pool, component, clen + 1);
1021251881Speter  if (SVN_PATH_IS_EMPTY(component))
1022251881Speter    return apr_pmemdup(pool, base, blen + 1);
1023251881Speter
1024251881Speter#ifdef SVN_USE_DOS_PATHS
1025251881Speter  if (component[0] == '/')
1026251881Speter    {
1027251881Speter      /* '/' is drive relative on Windows, not absolute like on Posix */
1028251881Speter      if (dirent_is_rooted(base))
1029251881Speter        {
1030251881Speter          /* Join component without '/' to root-of(base) */
1031251881Speter          blen = dirent_root_length(base, blen);
1032251881Speter          component++;
1033251881Speter          clen--;
1034251881Speter
1035251881Speter          if (blen == 2 && base[1] == ':') /* "C:" case */
1036251881Speter            {
1037251881Speter              char *root = apr_pmemdup(pool, base, 3);
1038251881Speter              root[2] = '/'; /* We don't need the final '\0' */
1039251881Speter
1040251881Speter              base = root;
1041251881Speter              blen = 3;
1042251881Speter            }
1043251881Speter
1044251881Speter          if (clen == 0)
1045251881Speter            return apr_pstrndup(pool, base, blen);
1046251881Speter        }
1047251881Speter      else
1048251881Speter        return apr_pmemdup(pool, component, clen + 1);
1049251881Speter    }
1050251881Speter  else if (dirent_is_rooted(component))
1051251881Speter    return apr_pmemdup(pool, component, clen + 1);
1052251881Speter#endif /* SVN_USE_DOS_PATHS */
1053251881Speter
1054251881Speter  /* if last character of base is already a separator, don't add a '/' */
1055251881Speter  add_separator = 1;
1056251881Speter  if (base[blen - 1] == '/'
1057251881Speter#ifdef SVN_USE_DOS_PATHS
1058251881Speter       || base[blen - 1] == ':'
1059251881Speter#endif
1060251881Speter        )
1061251881Speter          add_separator = 0;
1062251881Speter
1063251881Speter  /* Construct the new, combined dirent. */
1064251881Speter  dirent = apr_palloc(pool, blen + add_separator + clen + 1);
1065251881Speter  memcpy(dirent, base, blen);
1066251881Speter  if (add_separator)
1067251881Speter    dirent[blen] = '/';
1068251881Speter  memcpy(dirent + blen + add_separator, component, clen + 1);
1069251881Speter
1070251881Speter  return dirent;
1071251881Speter}
1072251881Speter
1073251881Speterchar *svn_dirent_join_many(apr_pool_t *pool, const char *base, ...)
1074251881Speter{
1075251881Speter#define MAX_SAVED_LENGTHS 10
1076251881Speter  apr_size_t saved_lengths[MAX_SAVED_LENGTHS];
1077251881Speter  apr_size_t total_len;
1078251881Speter  int nargs;
1079251881Speter  va_list va;
1080251881Speter  const char *s;
1081251881Speter  apr_size_t len;
1082251881Speter  char *dirent;
1083251881Speter  char *p;
1084251881Speter  int add_separator;
1085251881Speter  int base_arg = 0;
1086251881Speter
1087251881Speter  total_len = strlen(base);
1088251881Speter
1089251881Speter  assert(svn_dirent_is_canonical(base, pool));
1090251881Speter
1091251881Speter  /* if last character of base is already a separator, don't add a '/' */
1092251881Speter  add_separator = 1;
1093251881Speter  if (total_len == 0
1094251881Speter       || base[total_len - 1] == '/'
1095251881Speter#ifdef SVN_USE_DOS_PATHS
1096251881Speter       || base[total_len - 1] == ':'
1097251881Speter#endif
1098251881Speter        )
1099251881Speter          add_separator = 0;
1100251881Speter
1101251881Speter  saved_lengths[0] = total_len;
1102251881Speter
1103251881Speter  /* Compute the length of the resulting string. */
1104251881Speter
1105251881Speter  nargs = 0;
1106251881Speter  va_start(va, base);
1107251881Speter  while ((s = va_arg(va, const char *)) != NULL)
1108251881Speter    {
1109251881Speter      len = strlen(s);
1110251881Speter
1111251881Speter      assert(svn_dirent_is_canonical(s, pool));
1112251881Speter
1113251881Speter      if (SVN_PATH_IS_EMPTY(s))
1114251881Speter        continue;
1115251881Speter
1116251881Speter      if (nargs++ < MAX_SAVED_LENGTHS)
1117251881Speter        saved_lengths[nargs] = len;
1118251881Speter
1119251881Speter      if (dirent_is_rooted(s))
1120251881Speter        {
1121251881Speter          total_len = len;
1122251881Speter          base_arg = nargs;
1123251881Speter
1124251881Speter#ifdef SVN_USE_DOS_PATHS
1125251881Speter          if (!svn_dirent_is_absolute(s)) /* Handle non absolute roots */
1126251881Speter            {
1127251881Speter              /* Set new base and skip the current argument */
1128251881Speter              base = s = svn_dirent_join(base, s, pool);
1129251881Speter              base_arg++;
1130251881Speter              saved_lengths[0] = total_len = len = strlen(s);
1131251881Speter            }
1132251881Speter          else
1133251881Speter#endif /* SVN_USE_DOS_PATHS */
1134251881Speter            {
1135251881Speter              base = ""; /* Don't add base */
1136251881Speter              saved_lengths[0] = 0;
1137251881Speter            }
1138251881Speter
1139251881Speter          add_separator = 1;
1140251881Speter          if (s[len - 1] == '/'
1141251881Speter#ifdef SVN_USE_DOS_PATHS
1142251881Speter             || s[len - 1] == ':'
1143251881Speter#endif
1144251881Speter              )
1145251881Speter             add_separator = 0;
1146251881Speter        }
1147251881Speter      else if (nargs <= base_arg + 1)
1148251881Speter        {
1149251881Speter          total_len += add_separator + len;
1150251881Speter        }
1151251881Speter      else
1152251881Speter        {
1153251881Speter          total_len += 1 + len;
1154251881Speter        }
1155251881Speter    }
1156251881Speter  va_end(va);
1157251881Speter
1158251881Speter  /* base == "/" and no further components. just return that. */
1159251881Speter  if (add_separator == 0 && total_len == 1)
1160251881Speter    return apr_pmemdup(pool, "/", 2);
1161251881Speter
1162251881Speter  /* we got the total size. allocate it, with room for a NULL character. */
1163251881Speter  dirent = p = apr_palloc(pool, total_len + 1);
1164251881Speter
1165251881Speter  /* if we aren't supposed to skip forward to an absolute component, and if
1166251881Speter     this is not an empty base that we are skipping, then copy the base
1167251881Speter     into the output. */
1168251881Speter  if (! SVN_PATH_IS_EMPTY(base))
1169251881Speter    {
1170251881Speter      memcpy(p, base, len = saved_lengths[0]);
1171251881Speter      p += len;
1172251881Speter    }
1173251881Speter
1174251881Speter  nargs = 0;
1175251881Speter  va_start(va, base);
1176251881Speter  while ((s = va_arg(va, const char *)) != NULL)
1177251881Speter    {
1178251881Speter      if (SVN_PATH_IS_EMPTY(s))
1179251881Speter        continue;
1180251881Speter
1181251881Speter      if (++nargs < base_arg)
1182251881Speter        continue;
1183251881Speter
1184251881Speter      if (nargs < MAX_SAVED_LENGTHS)
1185251881Speter        len = saved_lengths[nargs];
1186251881Speter      else
1187251881Speter        len = strlen(s);
1188251881Speter
1189251881Speter      /* insert a separator if we aren't copying in the first component
1190251881Speter         (which can happen when base_arg is set). also, don't put in a slash
1191251881Speter         if the prior character is a slash (occurs when prior component
1192251881Speter         is "/"). */
1193251881Speter      if (p != dirent &&
1194251881Speter          ( ! (nargs - 1 <= base_arg) || add_separator))
1195251881Speter        *p++ = '/';
1196251881Speter
1197251881Speter      /* copy the new component and advance the pointer */
1198251881Speter      memcpy(p, s, len);
1199251881Speter      p += len;
1200251881Speter    }
1201251881Speter  va_end(va);
1202251881Speter
1203251881Speter  *p = '\0';
1204251881Speter  assert((apr_size_t)(p - dirent) == total_len);
1205251881Speter
1206251881Speter  return dirent;
1207251881Speter}
1208251881Speter
1209251881Speterchar *
1210251881Spetersvn_relpath_join(const char *base,
1211251881Speter                 const char *component,
1212251881Speter                 apr_pool_t *pool)
1213251881Speter{
1214251881Speter  apr_size_t blen = strlen(base);
1215251881Speter  apr_size_t clen = strlen(component);
1216251881Speter  char *path;
1217251881Speter
1218251881Speter  assert(relpath_is_canonical(base));
1219251881Speter  assert(relpath_is_canonical(component));
1220251881Speter
1221251881Speter  /* If either is empty return the other */
1222251881Speter  if (blen == 0)
1223251881Speter    return apr_pmemdup(pool, component, clen + 1);
1224251881Speter  if (clen == 0)
1225251881Speter    return apr_pmemdup(pool, base, blen + 1);
1226251881Speter
1227251881Speter  path = apr_palloc(pool, blen + 1 + clen + 1);
1228251881Speter  memcpy(path, base, blen);
1229251881Speter  path[blen] = '/';
1230251881Speter  memcpy(path + blen + 1, component, clen + 1);
1231251881Speter
1232251881Speter  return path;
1233251881Speter}
1234251881Speter
1235251881Speterchar *
1236251881Spetersvn_dirent_dirname(const char *dirent, apr_pool_t *pool)
1237251881Speter{
1238251881Speter  apr_size_t len = strlen(dirent);
1239251881Speter
1240251881Speter  assert(svn_dirent_is_canonical(dirent, pool));
1241251881Speter
1242251881Speter  if (len == dirent_root_length(dirent, len))
1243251881Speter    return apr_pstrmemdup(pool, dirent, len);
1244251881Speter  else
1245251881Speter    return apr_pstrmemdup(pool, dirent, dirent_previous_segment(dirent, len));
1246251881Speter}
1247251881Speter
1248251881Speterconst char *
1249251881Spetersvn_dirent_basename(const char *dirent, apr_pool_t *pool)
1250251881Speter{
1251251881Speter  apr_size_t len = strlen(dirent);
1252251881Speter  apr_size_t start;
1253251881Speter
1254251881Speter  assert(!pool || svn_dirent_is_canonical(dirent, pool));
1255251881Speter
1256251881Speter  if (svn_dirent_is_root(dirent, len))
1257251881Speter    return "";
1258251881Speter  else
1259251881Speter    {
1260251881Speter      start = len;
1261251881Speter      while (start > 0 && dirent[start - 1] != '/'
1262251881Speter#ifdef SVN_USE_DOS_PATHS
1263251881Speter             && dirent[start - 1] != ':'
1264251881Speter#endif
1265251881Speter            )
1266251881Speter        --start;
1267251881Speter    }
1268251881Speter
1269251881Speter  if (pool)
1270251881Speter    return apr_pstrmemdup(pool, dirent + start, len - start);
1271251881Speter  else
1272251881Speter    return dirent + start;
1273251881Speter}
1274251881Speter
1275251881Spetervoid
1276251881Spetersvn_dirent_split(const char **dirpath,
1277251881Speter                 const char **base_name,
1278251881Speter                 const char *dirent,
1279251881Speter                 apr_pool_t *pool)
1280251881Speter{
1281251881Speter  assert(dirpath != base_name);
1282251881Speter
1283251881Speter  if (dirpath)
1284251881Speter    *dirpath = svn_dirent_dirname(dirent, pool);
1285251881Speter
1286251881Speter  if (base_name)
1287251881Speter    *base_name = svn_dirent_basename(dirent, pool);
1288251881Speter}
1289251881Speter
1290251881Speterchar *
1291251881Spetersvn_relpath_dirname(const char *relpath,
1292251881Speter                    apr_pool_t *pool)
1293251881Speter{
1294251881Speter  apr_size_t len = strlen(relpath);
1295251881Speter
1296251881Speter  assert(relpath_is_canonical(relpath));
1297251881Speter
1298251881Speter  return apr_pstrmemdup(pool, relpath,
1299251881Speter                        relpath_previous_segment(relpath, len));
1300251881Speter}
1301251881Speter
1302251881Speterconst char *
1303251881Spetersvn_relpath_basename(const char *relpath,
1304251881Speter                     apr_pool_t *pool)
1305251881Speter{
1306251881Speter  apr_size_t len = strlen(relpath);
1307251881Speter  apr_size_t start;
1308251881Speter
1309251881Speter  assert(relpath_is_canonical(relpath));
1310251881Speter
1311251881Speter  start = len;
1312251881Speter  while (start > 0 && relpath[start - 1] != '/')
1313251881Speter    --start;
1314251881Speter
1315251881Speter  if (pool)
1316251881Speter    return apr_pstrmemdup(pool, relpath + start, len - start);
1317251881Speter  else
1318251881Speter    return relpath + start;
1319251881Speter}
1320251881Speter
1321251881Spetervoid
1322251881Spetersvn_relpath_split(const char **dirpath,
1323251881Speter                  const char **base_name,
1324251881Speter                  const char *relpath,
1325251881Speter                  apr_pool_t *pool)
1326251881Speter{
1327251881Speter  assert(dirpath != base_name);
1328251881Speter
1329251881Speter  if (dirpath)
1330251881Speter    *dirpath = svn_relpath_dirname(relpath, pool);
1331251881Speter
1332251881Speter  if (base_name)
1333251881Speter    *base_name = svn_relpath_basename(relpath, pool);
1334251881Speter}
1335251881Speter
1336289180Speterconst char *
1337289180Spetersvn_relpath_prefix(const char *relpath,
1338289180Speter                   int max_components,
1339289180Speter                   apr_pool_t *result_pool)
1340289180Speter{
1341289180Speter  const char *end;
1342289180Speter  assert(relpath_is_canonical(relpath));
1343289180Speter
1344289180Speter  if (max_components <= 0)
1345289180Speter    return "";
1346289180Speter
1347289180Speter  for (end = relpath; *end; end++)
1348289180Speter    {
1349289180Speter      if (*end == '/')
1350289180Speter        {
1351289180Speter          if (!--max_components)
1352289180Speter            break;
1353289180Speter        }
1354289180Speter    }
1355289180Speter
1356289180Speter  return apr_pstrmemdup(result_pool, relpath, end-relpath);
1357289180Speter}
1358289180Speter
1359251881Speterchar *
1360251881Spetersvn_uri_dirname(const char *uri, apr_pool_t *pool)
1361251881Speter{
1362251881Speter  apr_size_t len = strlen(uri);
1363251881Speter
1364251881Speter  assert(svn_uri_is_canonical(uri, pool));
1365251881Speter
1366251881Speter  if (svn_uri_is_root(uri, len))
1367251881Speter    return apr_pstrmemdup(pool, uri, len);
1368251881Speter  else
1369251881Speter    return apr_pstrmemdup(pool, uri, uri_previous_segment(uri, len));
1370251881Speter}
1371251881Speter
1372251881Speterconst char *
1373251881Spetersvn_uri_basename(const char *uri, apr_pool_t *pool)
1374251881Speter{
1375251881Speter  apr_size_t len = strlen(uri);
1376251881Speter  apr_size_t start;
1377251881Speter
1378251881Speter  assert(svn_uri_is_canonical(uri, NULL));
1379251881Speter
1380251881Speter  if (svn_uri_is_root(uri, len))
1381251881Speter    return "";
1382251881Speter
1383251881Speter  start = len;
1384251881Speter  while (start > 0 && uri[start - 1] != '/')
1385251881Speter    --start;
1386251881Speter
1387251881Speter  return svn_path_uri_decode(uri + start, pool);
1388251881Speter}
1389251881Speter
1390251881Spetervoid
1391251881Spetersvn_uri_split(const char **dirpath,
1392251881Speter              const char **base_name,
1393251881Speter              const char *uri,
1394251881Speter              apr_pool_t *pool)
1395251881Speter{
1396251881Speter  assert(dirpath != base_name);
1397251881Speter
1398251881Speter  if (dirpath)
1399251881Speter    *dirpath = svn_uri_dirname(uri, pool);
1400251881Speter
1401251881Speter  if (base_name)
1402251881Speter    *base_name = svn_uri_basename(uri, pool);
1403251881Speter}
1404251881Speter
1405251881Speterchar *
1406251881Spetersvn_dirent_get_longest_ancestor(const char *dirent1,
1407251881Speter                                const char *dirent2,
1408251881Speter                                apr_pool_t *pool)
1409251881Speter{
1410251881Speter  return apr_pstrndup(pool, dirent1,
1411251881Speter                      get_longest_ancestor_length(type_dirent, dirent1,
1412251881Speter                                                  dirent2, pool));
1413251881Speter}
1414251881Speter
1415251881Speterchar *
1416251881Spetersvn_relpath_get_longest_ancestor(const char *relpath1,
1417251881Speter                                 const char *relpath2,
1418251881Speter                                 apr_pool_t *pool)
1419251881Speter{
1420251881Speter  assert(relpath_is_canonical(relpath1));
1421251881Speter  assert(relpath_is_canonical(relpath2));
1422251881Speter
1423251881Speter  return apr_pstrndup(pool, relpath1,
1424251881Speter                      get_longest_ancestor_length(type_relpath, relpath1,
1425251881Speter                                                  relpath2, pool));
1426251881Speter}
1427251881Speter
1428251881Speterchar *
1429251881Spetersvn_uri_get_longest_ancestor(const char *uri1,
1430251881Speter                             const char *uri2,
1431251881Speter                             apr_pool_t *pool)
1432251881Speter{
1433251881Speter  apr_size_t uri_ancestor_len;
1434251881Speter  apr_size_t i = 0;
1435251881Speter
1436251881Speter  assert(svn_uri_is_canonical(uri1, NULL));
1437251881Speter  assert(svn_uri_is_canonical(uri2, NULL));
1438251881Speter
1439251881Speter  /* Find ':' */
1440251881Speter  while (1)
1441251881Speter    {
1442251881Speter      /* No shared protocol => no common prefix */
1443251881Speter      if (uri1[i] != uri2[i])
1444251881Speter        return apr_pmemdup(pool, SVN_EMPTY_PATH,
1445251881Speter                           sizeof(SVN_EMPTY_PATH));
1446251881Speter
1447251881Speter      if (uri1[i] == ':')
1448251881Speter        break;
1449251881Speter
1450251881Speter      /* They're both URLs, so EOS can't come before ':' */
1451251881Speter      assert((uri1[i] != '\0') && (uri2[i] != '\0'));
1452251881Speter
1453251881Speter      i++;
1454251881Speter    }
1455251881Speter
1456251881Speter  i += 3;  /* Advance past '://' */
1457251881Speter
1458251881Speter  uri_ancestor_len = get_longest_ancestor_length(type_uri, uri1 + i,
1459251881Speter                                                 uri2 + i, pool);
1460251881Speter
1461251881Speter  if (uri_ancestor_len == 0 ||
1462251881Speter      (uri_ancestor_len == 1 && (uri1 + i)[0] == '/'))
1463251881Speter    return apr_pmemdup(pool, SVN_EMPTY_PATH, sizeof(SVN_EMPTY_PATH));
1464251881Speter  else
1465251881Speter    return apr_pstrndup(pool, uri1, uri_ancestor_len + i);
1466251881Speter}
1467251881Speter
1468251881Speterconst char *
1469251881Spetersvn_dirent_is_child(const char *parent_dirent,
1470251881Speter                    const char *child_dirent,
1471251881Speter                    apr_pool_t *pool)
1472251881Speter{
1473251881Speter  return is_child(type_dirent, parent_dirent, child_dirent, pool);
1474251881Speter}
1475251881Speter
1476251881Speterconst char *
1477251881Spetersvn_dirent_skip_ancestor(const char *parent_dirent,
1478251881Speter                         const char *child_dirent)
1479251881Speter{
1480251881Speter  apr_size_t len = strlen(parent_dirent);
1481251881Speter  apr_size_t root_len;
1482251881Speter
1483251881Speter  if (0 != strncmp(parent_dirent, child_dirent, len))
1484251881Speter    return NULL; /* parent_dirent is no ancestor of child_dirent */
1485251881Speter
1486251881Speter  if (child_dirent[len] == 0)
1487251881Speter    return ""; /* parent_dirent == child_dirent */
1488251881Speter
1489251881Speter  /* Child == parent + more-characters */
1490251881Speter
1491251881Speter  root_len = dirent_root_length(child_dirent, strlen(child_dirent));
1492251881Speter  if (root_len > len)
1493251881Speter    /* Different root, e.g. ("" "/...") or ("//z" "//z/share") */
1494251881Speter    return NULL;
1495251881Speter
1496251881Speter  /* Now, child == [root-of-parent] + [rest-of-parent] + more-characters.
1497251881Speter   * It must be one of the following forms.
1498251881Speter   *
1499251881Speter   * rlen parent    child       bad?  rlen=len? c[len]=/?
1500251881Speter   *  0   ""        "foo"               *
1501251881Speter   *  0   "b"       "bad"         !
1502251881Speter   *  0   "b"       "b/foo"                       *
1503251881Speter   *  1   "/"       "/foo"              *
1504251881Speter   *  1   "/b"      "/bad"        !
1505251881Speter   *  1   "/b"      "/b/foo"                      *
1506251881Speter   *  2   "a:"      "a:foo"             *
1507251881Speter   *  2   "a:b"     "a:bad"       !
1508251881Speter   *  2   "a:b"     "a:b/foo"                     *
1509251881Speter   *  3   "a:/"     "a:/foo"            *
1510251881Speter   *  3   "a:/b"    "a:/bad"      !
1511251881Speter   *  3   "a:/b"    "a:/b/foo"                    *
1512251881Speter   *  5   "//s/s"   "//s/s/foo"         *         *
1513251881Speter   *  5   "//s/s/b" "//s/s/bad"   !
1514251881Speter   *  5   "//s/s/b" "//s/s/b/foo"                 *
1515251881Speter   */
1516251881Speter
1517251881Speter  if (child_dirent[len] == '/')
1518251881Speter    /* "parent|child" is one of:
1519251881Speter     * "[a:]b|/foo" "[a:]/b|/foo" "//s/s|/foo" "//s/s/b|/foo" */
1520251881Speter    return child_dirent + len + 1;
1521251881Speter
1522251881Speter  if (root_len == len)
1523251881Speter    /* "parent|child" is "|foo" "/|foo" "a:|foo" "a:/|foo" "//s/s|/foo" */
1524251881Speter    return child_dirent + len;
1525251881Speter
1526251881Speter  return NULL;
1527251881Speter}
1528251881Speter
1529251881Speterconst char *
1530251881Spetersvn_relpath_skip_ancestor(const char *parent_relpath,
1531251881Speter                          const char *child_relpath)
1532251881Speter{
1533251881Speter  apr_size_t len = strlen(parent_relpath);
1534251881Speter
1535251881Speter  assert(relpath_is_canonical(parent_relpath));
1536251881Speter  assert(relpath_is_canonical(child_relpath));
1537251881Speter
1538251881Speter  if (len == 0)
1539251881Speter    return child_relpath;
1540251881Speter
1541251881Speter  if (0 != strncmp(parent_relpath, child_relpath, len))
1542251881Speter    return NULL; /* parent_relpath is no ancestor of child_relpath */
1543251881Speter
1544251881Speter  if (child_relpath[len] == 0)
1545251881Speter    return ""; /* parent_relpath == child_relpath */
1546251881Speter
1547251881Speter  if (child_relpath[len] == '/')
1548251881Speter    return child_relpath + len + 1;
1549251881Speter
1550251881Speter  return NULL;
1551251881Speter}
1552251881Speter
1553251881Speter
1554251881Speter/* */
1555251881Speterstatic const char *
1556251881Speteruri_skip_ancestor(const char *parent_uri,
1557251881Speter                  const char *child_uri)
1558251881Speter{
1559251881Speter  apr_size_t len = strlen(parent_uri);
1560251881Speter
1561251881Speter  assert(svn_uri_is_canonical(parent_uri, NULL));
1562251881Speter  assert(svn_uri_is_canonical(child_uri, NULL));
1563251881Speter
1564251881Speter  if (0 != strncmp(parent_uri, child_uri, len))
1565251881Speter    return NULL; /* parent_uri is no ancestor of child_uri */
1566251881Speter
1567251881Speter  if (child_uri[len] == 0)
1568251881Speter    return ""; /* parent_uri == child_uri */
1569251881Speter
1570251881Speter  if (child_uri[len] == '/')
1571251881Speter    return child_uri + len + 1;
1572251881Speter
1573251881Speter  return NULL;
1574251881Speter}
1575251881Speter
1576251881Speterconst char *
1577251881Spetersvn_uri_skip_ancestor(const char *parent_uri,
1578251881Speter                      const char *child_uri,
1579251881Speter                      apr_pool_t *result_pool)
1580251881Speter{
1581251881Speter  const char *result = uri_skip_ancestor(parent_uri, child_uri);
1582251881Speter
1583251881Speter  return result ? svn_path_uri_decode(result, result_pool) : NULL;
1584251881Speter}
1585251881Speter
1586251881Spetersvn_boolean_t
1587251881Spetersvn_dirent_is_ancestor(const char *parent_dirent, const char *child_dirent)
1588251881Speter{
1589251881Speter  return svn_dirent_skip_ancestor(parent_dirent, child_dirent) != NULL;
1590251881Speter}
1591251881Speter
1592251881Spetersvn_boolean_t
1593251881Spetersvn_uri__is_ancestor(const char *parent_uri, const char *child_uri)
1594251881Speter{
1595251881Speter  return uri_skip_ancestor(parent_uri, child_uri) != NULL;
1596251881Speter}
1597251881Speter
1598251881Speter
1599251881Spetersvn_boolean_t
1600251881Spetersvn_dirent_is_absolute(const char *dirent)
1601251881Speter{
1602251881Speter  if (! dirent)
1603251881Speter    return FALSE;
1604251881Speter
1605251881Speter  /* dirent is absolute if it starts with '/' on non-Windows platforms
1606251881Speter     or with '//' on Windows platforms */
1607251881Speter  if (dirent[0] == '/'
1608251881Speter#ifdef SVN_USE_DOS_PATHS
1609251881Speter      && dirent[1] == '/' /* Single '/' depends on current drive */
1610251881Speter#endif
1611251881Speter      )
1612251881Speter    return TRUE;
1613251881Speter
1614251881Speter  /* On Windows, dirent is also absolute when it starts with 'H:/'
1615251881Speter     where 'H' is any letter. */
1616251881Speter#ifdef SVN_USE_DOS_PATHS
1617251881Speter  if (((dirent[0] >= 'A' && dirent[0] <= 'Z')) &&
1618251881Speter      (dirent[1] == ':') && (dirent[2] == '/'))
1619251881Speter     return TRUE;
1620251881Speter#endif /* SVN_USE_DOS_PATHS */
1621251881Speter
1622251881Speter  return FALSE;
1623251881Speter}
1624251881Speter
1625251881Spetersvn_error_t *
1626251881Spetersvn_dirent_get_absolute(const char **pabsolute,
1627251881Speter                        const char *relative,
1628251881Speter                        apr_pool_t *pool)
1629251881Speter{
1630251881Speter  char *buffer;
1631251881Speter  apr_status_t apr_err;
1632251881Speter  const char *path_apr;
1633251881Speter
1634251881Speter  SVN_ERR_ASSERT(! svn_path_is_url(relative));
1635251881Speter
1636251881Speter  /* Merge the current working directory with the relative dirent. */
1637251881Speter  SVN_ERR(svn_path_cstring_from_utf8(&path_apr, relative, pool));
1638251881Speter
1639251881Speter  apr_err = apr_filepath_merge(&buffer, NULL,
1640251881Speter                               path_apr,
1641251881Speter                               APR_FILEPATH_NOTRELATIVE,
1642251881Speter                               pool);
1643251881Speter  if (apr_err)
1644251881Speter    {
1645251881Speter      /* In some cases when the passed path or its ancestor(s) do not exist
1646251881Speter         or no longer exist apr returns an error.
1647251881Speter
1648251881Speter         In many of these cases we would like to return a path anyway, when the
1649251881Speter         passed path was already a safe absolute path. So check for that now to
1650251881Speter         avoid an error.
1651251881Speter
1652251881Speter         svn_dirent_is_absolute() doesn't perform the necessary checks to see
1653251881Speter         if the path doesn't need post processing to be in the canonical absolute
1654251881Speter         format.
1655251881Speter         */
1656251881Speter
1657251881Speter      if (svn_dirent_is_absolute(relative)
1658251881Speter          && svn_dirent_is_canonical(relative, pool)
1659251881Speter          && !svn_path_is_backpath_present(relative))
1660251881Speter        {
1661251881Speter          *pabsolute = apr_pstrdup(pool, relative);
1662251881Speter          return SVN_NO_ERROR;
1663251881Speter        }
1664251881Speter
1665251881Speter      return svn_error_createf(SVN_ERR_BAD_FILENAME,
1666251881Speter                               svn_error_create(apr_err, NULL, NULL),
1667251881Speter                               _("Couldn't determine absolute path of '%s'"),
1668251881Speter                               svn_dirent_local_style(relative, pool));
1669251881Speter    }
1670251881Speter
1671251881Speter  SVN_ERR(svn_path_cstring_to_utf8(pabsolute, buffer, pool));
1672251881Speter  *pabsolute = svn_dirent_canonicalize(*pabsolute, pool);
1673251881Speter  return SVN_NO_ERROR;
1674251881Speter}
1675251881Speter
1676251881Speterconst char *
1677251881Spetersvn_uri_canonicalize(const char *uri, apr_pool_t *pool)
1678251881Speter{
1679362181Sdim  const char *result;
1680362181Sdim  svn_error_t *const err = canonicalize(&result, type_uri, uri, pool);
1681362181Sdim  if (err)
1682362181Sdim    {
1683362181Sdim      svn_error_clear(err);
1684362181Sdim      SVN_ERR_ASSERT_NO_RETURN(!"URI canonicalization failed");
1685362181Sdim    }
1686362181Sdim  return result;
1687251881Speter}
1688251881Speter
1689362181Sdimsvn_error_t *
1690362181Sdimsvn_uri_canonicalize_safe(const char **canonical_uri,
1691362181Sdim                          const char **non_canonical_result,
1692362181Sdim                          const char *uri,
1693362181Sdim                          apr_pool_t *result_pool,
1694362181Sdim                          apr_pool_t *scratch_pool)
1695362181Sdim{
1696362181Sdim  const char *result = NULL;
1697362181Sdim  SVN_ERR(canonicalize(&result, type_uri, uri, result_pool));
1698362181Sdim  if (!svn_uri_is_canonical(result, scratch_pool))
1699362181Sdim    {
1700362181Sdim      if (non_canonical_result)
1701362181Sdim        *non_canonical_result = result;
1702362181Sdim
1703362181Sdim      return svn_error_createf(
1704362181Sdim          SVN_ERR_CANONICALIZATION_FAILED, NULL,
1705362181Sdim          _("Could not canonicalize URI '%s'"
1706362181Sdim            " (the result '%s' is not canonical)"),
1707362181Sdim          uri, result);
1708362181Sdim    }
1709362181Sdim  *canonical_uri = result;
1710362181Sdim  return SVN_NO_ERROR;
1711362181Sdim}
1712362181Sdim
1713251881Speterconst char *
1714251881Spetersvn_relpath_canonicalize(const char *relpath, apr_pool_t *pool)
1715251881Speter{
1716362181Sdim  const char *result;
1717362181Sdim  svn_error_t *const err = canonicalize(&result, type_relpath, relpath, pool);
1718362181Sdim  if (err)
1719362181Sdim    {
1720362181Sdim      svn_error_clear(err);
1721362181Sdim      SVN_ERR_ASSERT_NO_RETURN(!"relpath canonicalization failed");
1722362181Sdim    }
1723362181Sdim  return result;
1724251881Speter}
1725251881Speter
1726362181Sdimsvn_error_t *
1727362181Sdimsvn_relpath_canonicalize_safe(const char **canonical_relpath,
1728362181Sdim                              const char **non_canonical_result,
1729362181Sdim                              const char *relpath,
1730362181Sdim                              apr_pool_t *result_pool,
1731362181Sdim                              apr_pool_t *scratch_pool)
1732251881Speter{
1733362181Sdim  const char *result = NULL;
1734362181Sdim  SVN_ERR(canonicalize(&result, type_relpath, relpath, result_pool));
1735362181Sdim  if (!svn_relpath_is_canonical(result))
1736362181Sdim    {
1737362181Sdim      if (non_canonical_result)
1738362181Sdim        *non_canonical_result = result;
1739251881Speter
1740362181Sdim      return svn_error_createf(
1741362181Sdim          SVN_ERR_CANONICALIZATION_FAILED, NULL,
1742362181Sdim          _("Could not canonicalize relpath '%s'"
1743362181Sdim            " (the result '%s' is not canonical)"),
1744362181Sdim          relpath, result);
1745362181Sdim    }
1746362181Sdim
1747362181Sdim  SVN_UNUSED(scratch_pool);
1748362181Sdim  *canonical_relpath = result;
1749362181Sdim  return SVN_NO_ERROR;
1750362181Sdim}
1751362181Sdim
1752362181Sdimstatic svn_error_t *
1753362181Sdimcanonicalize_dirent(const char **result, const char *dirent, apr_pool_t *pool)
1754362181Sdim{
1755362181Sdim  const char *dst;
1756362181Sdim  SVN_ERR(canonicalize(&dst, type_dirent, dirent, pool));
1757362181Sdim
1758251881Speter#ifdef SVN_USE_DOS_PATHS
1759251881Speter  /* Handle a specific case on Windows where path == "X:/". Here we have to
1760251881Speter     append the final '/', as svn_path_canonicalize will chop this of. */
1761251881Speter  if (((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
1762251881Speter        (dirent[0] >= 'a' && dirent[0] <= 'z')) &&
1763251881Speter        dirent[1] == ':' && dirent[2] == '/' &&
1764251881Speter        dst[3] == '\0')
1765251881Speter    {
1766251881Speter      char *dst_slash = apr_pcalloc(pool, 4);
1767251881Speter      dst_slash[0] = canonicalize_to_upper(dirent[0]);
1768251881Speter      dst_slash[1] = ':';
1769251881Speter      dst_slash[2] = '/';
1770251881Speter      dst_slash[3] = '\0';
1771251881Speter
1772362181Sdim      *result = dst_slash;
1773362181Sdim      return SVN_NO_ERROR;
1774251881Speter    }
1775251881Speter#endif /* SVN_USE_DOS_PATHS */
1776251881Speter
1777362181Sdim  *result = dst;
1778362181Sdim  return SVN_NO_ERROR;
1779251881Speter}
1780251881Speter
1781362181Sdimconst char *
1782362181Sdimsvn_dirent_canonicalize(const char *dirent, apr_pool_t *pool)
1783362181Sdim{
1784362181Sdim  const char *result;
1785362181Sdim  svn_error_t *const err = canonicalize_dirent(&result, dirent, pool);
1786362181Sdim  if (err)
1787362181Sdim    {
1788362181Sdim      svn_error_clear(err);
1789362181Sdim      SVN_ERR_ASSERT_NO_RETURN(!"dirent canonicalization failed");
1790362181Sdim    }
1791362181Sdim  return result;
1792362181Sdim}
1793362181Sdim
1794362181Sdimsvn_error_t *
1795362181Sdimsvn_dirent_canonicalize_safe(const char **canonical_dirent,
1796362181Sdim                             const char **non_canonical_result,
1797362181Sdim                             const char *dirent,
1798362181Sdim                             apr_pool_t *result_pool,
1799362181Sdim                             apr_pool_t *scratch_pool)
1800362181Sdim{
1801362181Sdim  const char *result = NULL;
1802362181Sdim  SVN_ERR(canonicalize_dirent(&result, dirent, result_pool));
1803362181Sdim  if (!svn_dirent_is_canonical(result, scratch_pool))
1804362181Sdim    {
1805362181Sdim      if (non_canonical_result)
1806362181Sdim        *non_canonical_result = result;
1807362181Sdim
1808362181Sdim      return svn_error_createf(
1809362181Sdim          SVN_ERR_CANONICALIZATION_FAILED, NULL,
1810362181Sdim          _("Could not canonicalize dirent '%s'"
1811362181Sdim            " (the result '%s' is not canonical)"),
1812362181Sdim          dirent, result);
1813362181Sdim    }
1814362181Sdim  *canonical_dirent = result;
1815362181Sdim  return SVN_NO_ERROR;
1816362181Sdim}
1817362181Sdim
1818251881Spetersvn_boolean_t
1819251881Spetersvn_dirent_is_canonical(const char *dirent, apr_pool_t *scratch_pool)
1820251881Speter{
1821251881Speter  const char *ptr = dirent;
1822251881Speter  if (*ptr == '/')
1823251881Speter    {
1824251881Speter      ptr++;
1825251881Speter#ifdef SVN_USE_DOS_PATHS
1826251881Speter      /* Check for UNC paths */
1827251881Speter      if (*ptr == '/')
1828251881Speter        {
1829251881Speter          /* TODO: Scan hostname and sharename and fall back to part code */
1830251881Speter
1831251881Speter          /* ### Fall back to old implementation */
1832251881Speter          return (strcmp(dirent, svn_dirent_canonicalize(dirent, scratch_pool))
1833251881Speter                  == 0);
1834251881Speter        }
1835251881Speter#endif /* SVN_USE_DOS_PATHS */
1836251881Speter    }
1837251881Speter#ifdef SVN_USE_DOS_PATHS
1838251881Speter  else if (((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= 'A' && *ptr <= 'Z')) &&
1839251881Speter           (ptr[1] == ':'))
1840251881Speter    {
1841251881Speter      /* The only canonical drive names are "A:"..."Z:", no lower case */
1842251881Speter      if (*ptr < 'A' || *ptr > 'Z')
1843251881Speter        return FALSE;
1844251881Speter
1845251881Speter      ptr += 2;
1846251881Speter
1847251881Speter      if (*ptr == '/')
1848251881Speter        ptr++;
1849251881Speter    }
1850251881Speter#endif /* SVN_USE_DOS_PATHS */
1851251881Speter
1852251881Speter  return relpath_is_canonical(ptr);
1853251881Speter}
1854251881Speter
1855251881Speterstatic svn_boolean_t
1856251881Speterrelpath_is_canonical(const char *relpath)
1857251881Speter{
1858289180Speter  const char *dot_pos, *ptr = relpath;
1859289180Speter  apr_size_t i, len;
1860289180Speter  unsigned pattern = 0;
1861251881Speter
1862251881Speter  /* RELPATH is canonical if it has:
1863251881Speter   *  - no '.' segments
1864251881Speter   *  - no start and closing '/'
1865251881Speter   *  - no '//'
1866251881Speter   */
1867251881Speter
1868289180Speter  /* invalid beginnings */
1869251881Speter  if (*ptr == '/')
1870251881Speter    return FALSE;
1871251881Speter
1872289180Speter  if (ptr[0] == '.' && (ptr[1] == '/' || ptr[1] == '\0'))
1873289180Speter    return FALSE;
1874251881Speter
1875289180Speter  /* valid special cases */
1876289180Speter  len = strlen(ptr);
1877289180Speter  if (len < 2)
1878289180Speter    return TRUE;
1879251881Speter
1880289180Speter  /* invalid endings */
1881289180Speter  if (ptr[len-1] == '/' || (ptr[len-1] == '.' && ptr[len-2] == '/'))
1882289180Speter    return FALSE;
1883251881Speter
1884289180Speter  /* '.' are rare. So, search for them globally. There will often be no
1885289180Speter   * more than one hit.  Also note that we already checked for invalid
1886289180Speter   * starts and endings, i.e. we only need to check for "/./"
1887289180Speter   */
1888289180Speter  for (dot_pos = memchr(ptr, '.', len);
1889289180Speter       dot_pos;
1890289180Speter       dot_pos = strchr(dot_pos+1, '.'))
1891289180Speter    if (dot_pos > ptr && dot_pos[-1] == '/' && dot_pos[1] == '/')
1892289180Speter      return FALSE;
1893251881Speter
1894289180Speter  /* Now validate the rest of the path. */
1895289180Speter  for (i = 0; i < len - 1; ++i)
1896289180Speter    {
1897289180Speter      pattern = ((pattern & 0xff) << 8) + (unsigned char)ptr[i];
1898289180Speter      if (pattern == 0x101 * (unsigned char)('/'))
1899289180Speter        return FALSE;
1900251881Speter    }
1901251881Speter
1902251881Speter  return TRUE;
1903251881Speter}
1904251881Speter
1905251881Spetersvn_boolean_t
1906251881Spetersvn_relpath_is_canonical(const char *relpath)
1907251881Speter{
1908251881Speter  return relpath_is_canonical(relpath);
1909251881Speter}
1910251881Speter
1911251881Spetersvn_boolean_t
1912251881Spetersvn_uri_is_canonical(const char *uri, apr_pool_t *scratch_pool)
1913251881Speter{
1914251881Speter  const char *ptr = uri, *seg = uri;
1915251881Speter  const char *schema_data = NULL;
1916251881Speter
1917251881Speter  /* URI is canonical if it has:
1918251881Speter   *  - lowercase URL scheme
1919251881Speter   *  - lowercase URL hostname
1920251881Speter   *  - no '.' segments
1921251881Speter   *  - no closing '/'
1922251881Speter   *  - no '//'
1923251881Speter   *  - uppercase hex-encoded pair digits ("%AB", not "%ab")
1924251881Speter   */
1925251881Speter
1926251881Speter  if (*uri == '\0')
1927251881Speter    return FALSE;
1928251881Speter
1929251881Speter  if (! svn_path_is_url(uri))
1930251881Speter    return FALSE;
1931251881Speter
1932251881Speter  /* Skip the scheme. */
1933251881Speter  while (*ptr && (*ptr != '/') && (*ptr != ':'))
1934251881Speter    ptr++;
1935251881Speter
1936251881Speter  /* No scheme?  No good. */
1937251881Speter  if (! (*ptr == ':' && *(ptr+1) == '/' && *(ptr+2) == '/'))
1938251881Speter    return FALSE;
1939251881Speter
1940251881Speter  /* Found a scheme, check that it's all lowercase. */
1941251881Speter  ptr = uri;
1942251881Speter  while (*ptr != ':')
1943251881Speter    {
1944251881Speter      if (*ptr >= 'A' && *ptr <= 'Z')
1945251881Speter        return FALSE;
1946251881Speter      ptr++;
1947251881Speter    }
1948251881Speter  /* Skip :// */
1949251881Speter  ptr += 3;
1950251881Speter
1951251881Speter  /* Scheme only?  That works. */
1952251881Speter  if (! *ptr)
1953251881Speter    return TRUE;
1954251881Speter
1955251881Speter  /* This might be the hostname */
1956251881Speter  seg = ptr;
1957251881Speter  while (*ptr && (*ptr != '/') && (*ptr != '@'))
1958251881Speter    ptr++;
1959251881Speter
1960251881Speter  if (*ptr == '@')
1961251881Speter    seg = ptr + 1;
1962251881Speter
1963251881Speter  /* Found a hostname, check that it's all lowercase. */
1964251881Speter  ptr = seg;
1965251881Speter
1966251881Speter  if (*ptr == '[')
1967251881Speter    {
1968251881Speter      ptr++;
1969251881Speter      while (*ptr == ':'
1970251881Speter             || (*ptr >= '0' && *ptr <= '9')
1971251881Speter             || (*ptr >= 'a' && *ptr <= 'f'))
1972251881Speter        {
1973251881Speter          ptr++;
1974251881Speter        }
1975251881Speter
1976251881Speter      if (*ptr != ']')
1977251881Speter        return FALSE;
1978251881Speter      ptr++;
1979251881Speter    }
1980251881Speter  else
1981251881Speter    while (*ptr && *ptr != '/' && *ptr != ':')
1982251881Speter      {
1983251881Speter        if (*ptr >= 'A' && *ptr <= 'Z')
1984251881Speter          return FALSE;
1985251881Speter        ptr++;
1986251881Speter      }
1987251881Speter
1988251881Speter  /* Found a portnumber */
1989251881Speter  if (*ptr == ':')
1990251881Speter    {
1991251881Speter      apr_int64_t port = 0;
1992251881Speter
1993251881Speter      ptr++;
1994251881Speter      schema_data = ptr;
1995251881Speter
1996251881Speter      while (*ptr >= '0' && *ptr <= '9')
1997251881Speter        {
1998251881Speter          port = 10 * port + (*ptr - '0');
1999251881Speter          ptr++;
2000251881Speter        }
2001251881Speter
2002362181Sdim      if (ptr == schema_data && (*ptr == '/' || *ptr == '\0'))
2003251881Speter        return FALSE; /* Fail on "http://host:" */
2004251881Speter
2005251881Speter      if (port == 80 && strncmp(uri, "http:", 5) == 0)
2006251881Speter        return FALSE;
2007251881Speter      else if (port == 443 && strncmp(uri, "https:", 6) == 0)
2008251881Speter        return FALSE;
2009251881Speter      else if (port == 3690 && strncmp(uri, "svn:", 4) == 0)
2010251881Speter        return FALSE;
2011362181Sdim
2012362181Sdim      while (*ptr && *ptr != '/')
2013362181Sdim        ++ptr; /* Allow "http://host:stuff" */
2014251881Speter    }
2015251881Speter
2016251881Speter  schema_data = ptr;
2017251881Speter
2018251881Speter#ifdef SVN_USE_DOS_PATHS
2019251881Speter  if (schema_data && *ptr == '/')
2020251881Speter    {
2021251881Speter      /* If this is a file url, ptr now points to the third '/' in
2022251881Speter         file:///C:/path. Check that if we have such a URL the drive
2023251881Speter         letter is in uppercase. */
2024251881Speter      if (strncmp(uri, "file:", 5) == 0 &&
2025251881Speter          ! (*(ptr+1) >= 'A' && *(ptr+1) <= 'Z') &&
2026251881Speter          *(ptr+2) == ':')
2027251881Speter        return FALSE;
2028251881Speter    }
2029251881Speter#endif /* SVN_USE_DOS_PATHS */
2030251881Speter
2031251881Speter  /* Now validate the rest of the URI. */
2032257936Speter  seg = ptr;
2033257936Speter  while (*ptr && (*ptr != '/'))
2034257936Speter    ptr++;
2035251881Speter  while(1)
2036251881Speter    {
2037251881Speter      apr_size_t seglen = ptr - seg;
2038251881Speter
2039251881Speter      if (seglen == 1 && *seg == '.')
2040251881Speter        return FALSE;  /*  /./   */
2041251881Speter
2042251881Speter      if (*ptr == '/' && *(ptr+1) == '/')
2043251881Speter        return FALSE;  /*  //    */
2044251881Speter
2045251881Speter      if (! *ptr && *(ptr - 1) == '/' && ptr - 1 != uri)
2046251881Speter        return FALSE;  /* foo/  */
2047251881Speter
2048251881Speter      if (! *ptr)
2049251881Speter        break;
2050251881Speter
2051251881Speter      if (*ptr == '/')
2052251881Speter        ptr++;
2053257936Speter
2054251881Speter      seg = ptr;
2055251881Speter      while (*ptr && (*ptr != '/'))
2056251881Speter        ptr++;
2057251881Speter    }
2058251881Speter
2059251881Speter  ptr = schema_data;
2060251881Speter
2061251881Speter  while (*ptr)
2062251881Speter    {
2063251881Speter      if (*ptr == '%')
2064251881Speter        {
2065251881Speter          char digitz[3];
2066251881Speter          int val;
2067251881Speter
2068251881Speter          /* Can't usesvn_ctype_isxdigit() because lower case letters are
2069251881Speter             not in our canonical format */
2070251881Speter          if (((*(ptr+1) < '0' || *(ptr+1) > '9'))
2071251881Speter              && (*(ptr+1) < 'A' || *(ptr+1) > 'F'))
2072251881Speter            return FALSE;
2073251881Speter          else if (((*(ptr+2) < '0' || *(ptr+2) > '9'))
2074251881Speter                   && (*(ptr+2) < 'A' || *(ptr+2) > 'F'))
2075251881Speter            return FALSE;
2076251881Speter
2077251881Speter          digitz[0] = *(++ptr);
2078251881Speter          digitz[1] = *(++ptr);
2079251881Speter          digitz[2] = '\0';
2080251881Speter          val = (int)strtol(digitz, NULL, 16);
2081251881Speter
2082251881Speter          if (svn_uri__char_validity[val])
2083251881Speter            return FALSE; /* Should not have been escaped */
2084251881Speter        }
2085251881Speter      else if (*ptr != '/' && !svn_uri__char_validity[(unsigned char)*ptr])
2086251881Speter        return FALSE; /* Character should have been escaped */
2087251881Speter      ptr++;
2088251881Speter    }
2089251881Speter
2090251881Speter  return TRUE;
2091251881Speter}
2092251881Speter
2093251881Spetersvn_error_t *
2094251881Spetersvn_dirent_condense_targets(const char **pcommon,
2095251881Speter                            apr_array_header_t **pcondensed_targets,
2096251881Speter                            const apr_array_header_t *targets,
2097251881Speter                            svn_boolean_t remove_redundancies,
2098251881Speter                            apr_pool_t *result_pool,
2099251881Speter                            apr_pool_t *scratch_pool)
2100251881Speter{
2101251881Speter  int i, num_condensed = targets->nelts;
2102251881Speter  svn_boolean_t *removed;
2103251881Speter  apr_array_header_t *abs_targets;
2104251881Speter
2105251881Speter  /* Early exit when there's no data to work on. */
2106251881Speter  if (targets->nelts <= 0)
2107251881Speter    {
2108251881Speter      *pcommon = NULL;
2109251881Speter      if (pcondensed_targets)
2110251881Speter        *pcondensed_targets = NULL;
2111251881Speter      return SVN_NO_ERROR;
2112251881Speter    }
2113251881Speter
2114251881Speter  /* Get the absolute path of the first target. */
2115251881Speter  SVN_ERR(svn_dirent_get_absolute(pcommon,
2116251881Speter                                  APR_ARRAY_IDX(targets, 0, const char *),
2117251881Speter                                  scratch_pool));
2118251881Speter
2119251881Speter  /* Early exit when there's only one dirent to work on. */
2120251881Speter  if (targets->nelts == 1)
2121251881Speter    {
2122251881Speter      *pcommon = apr_pstrdup(result_pool, *pcommon);
2123251881Speter      if (pcondensed_targets)
2124251881Speter        *pcondensed_targets = apr_array_make(result_pool, 0,
2125251881Speter                                             sizeof(const char *));
2126251881Speter      return SVN_NO_ERROR;
2127251881Speter    }
2128251881Speter
2129251881Speter  /* Copy the targets array, but with absolute dirents instead of
2130251881Speter     relative.  Also, find the pcommon argument by finding what is
2131251881Speter     common in all of the absolute dirents. NOTE: This is not as
2132251881Speter     efficient as it could be.  The calculation of the basedir could
2133251881Speter     be done in the loop below, which would save some calls to
2134251881Speter     svn_dirent_get_longest_ancestor.  I decided to do it this way
2135251881Speter     because I thought it would be simpler, since this way, we don't
2136251881Speter     even do the loop if we don't need to condense the targets. */
2137251881Speter
2138251881Speter  removed = apr_pcalloc(scratch_pool, (targets->nelts *
2139251881Speter                                          sizeof(svn_boolean_t)));
2140251881Speter  abs_targets = apr_array_make(scratch_pool, targets->nelts,
2141251881Speter                               sizeof(const char *));
2142251881Speter
2143251881Speter  APR_ARRAY_PUSH(abs_targets, const char *) = *pcommon;
2144251881Speter
2145251881Speter  for (i = 1; i < targets->nelts; ++i)
2146251881Speter    {
2147251881Speter      const char *rel = APR_ARRAY_IDX(targets, i, const char *);
2148251881Speter      const char *absolute;
2149251881Speter      SVN_ERR(svn_dirent_get_absolute(&absolute, rel, scratch_pool));
2150251881Speter      APR_ARRAY_PUSH(abs_targets, const char *) = absolute;
2151251881Speter      *pcommon = svn_dirent_get_longest_ancestor(*pcommon, absolute,
2152251881Speter                                                 scratch_pool);
2153251881Speter    }
2154251881Speter
2155251881Speter  *pcommon = apr_pstrdup(result_pool, *pcommon);
2156251881Speter
2157251881Speter  if (pcondensed_targets != NULL)
2158251881Speter    {
2159251881Speter      size_t basedir_len;
2160251881Speter
2161251881Speter      if (remove_redundancies)
2162251881Speter        {
2163251881Speter          /* Find the common part of each pair of targets.  If
2164251881Speter             common part is equal to one of the dirents, the other
2165251881Speter             is a child of it, and can be removed.  If a target is
2166251881Speter             equal to *pcommon, it can also be removed. */
2167251881Speter
2168251881Speter          /* First pass: when one non-removed target is a child of
2169251881Speter             another non-removed target, remove the child. */
2170251881Speter          for (i = 0; i < abs_targets->nelts; ++i)
2171251881Speter            {
2172251881Speter              int j;
2173251881Speter
2174251881Speter              if (removed[i])
2175251881Speter                continue;
2176251881Speter
2177251881Speter              for (j = i + 1; j < abs_targets->nelts; ++j)
2178251881Speter                {
2179251881Speter                  const char *abs_targets_i;
2180251881Speter                  const char *abs_targets_j;
2181251881Speter                  const char *ancestor;
2182251881Speter
2183251881Speter                  if (removed[j])
2184251881Speter                    continue;
2185251881Speter
2186251881Speter                  abs_targets_i = APR_ARRAY_IDX(abs_targets, i, const char *);
2187251881Speter                  abs_targets_j = APR_ARRAY_IDX(abs_targets, j, const char *);
2188251881Speter
2189251881Speter                  ancestor = svn_dirent_get_longest_ancestor
2190251881Speter                    (abs_targets_i, abs_targets_j, scratch_pool);
2191251881Speter
2192251881Speter                  if (*ancestor == '\0')
2193251881Speter                    continue;
2194251881Speter
2195251881Speter                  if (strcmp(ancestor, abs_targets_i) == 0)
2196251881Speter                    {
2197251881Speter                      removed[j] = TRUE;
2198251881Speter                      num_condensed--;
2199251881Speter                    }
2200251881Speter                  else if (strcmp(ancestor, abs_targets_j) == 0)
2201251881Speter                    {
2202251881Speter                      removed[i] = TRUE;
2203251881Speter                      num_condensed--;
2204251881Speter                    }
2205251881Speter                }
2206251881Speter            }
2207251881Speter
2208251881Speter          /* Second pass: when a target is the same as *pcommon,
2209251881Speter             remove the target. */
2210251881Speter          for (i = 0; i < abs_targets->nelts; ++i)
2211251881Speter            {
2212251881Speter              const char *abs_targets_i = APR_ARRAY_IDX(abs_targets, i,
2213251881Speter                                                        const char *);
2214251881Speter
2215251881Speter              if ((strcmp(abs_targets_i, *pcommon) == 0) && (! removed[i]))
2216251881Speter                {
2217251881Speter                  removed[i] = TRUE;
2218251881Speter                  num_condensed--;
2219251881Speter                }
2220251881Speter            }
2221251881Speter        }
2222251881Speter
2223251881Speter      /* Now create the return array, and copy the non-removed items */
2224251881Speter      basedir_len = strlen(*pcommon);
2225251881Speter      *pcondensed_targets = apr_array_make(result_pool, num_condensed,
2226251881Speter                                           sizeof(const char *));
2227251881Speter
2228251881Speter      for (i = 0; i < abs_targets->nelts; ++i)
2229251881Speter        {
2230251881Speter          const char *rel_item = APR_ARRAY_IDX(abs_targets, i, const char *);
2231251881Speter
2232251881Speter          /* Skip this if it's been removed. */
2233251881Speter          if (removed[i])
2234251881Speter            continue;
2235251881Speter
2236251881Speter          /* If a common prefix was found, condensed_targets are given
2237251881Speter             relative to that prefix.  */
2238251881Speter          if (basedir_len > 0)
2239251881Speter            {
2240251881Speter              /* Only advance our pointer past a dirent separator if
2241251881Speter                 REL_ITEM isn't the same as *PCOMMON.
2242251881Speter
2243251881Speter                 If *PCOMMON is a root dirent, basedir_len will already
2244251881Speter                 include the closing '/', so never advance the pointer
2245251881Speter                 here.
2246251881Speter                 */
2247251881Speter              rel_item += basedir_len;
2248251881Speter              if (rel_item[0] &&
2249251881Speter                  ! svn_dirent_is_root(*pcommon, basedir_len))
2250251881Speter                rel_item++;
2251251881Speter            }
2252251881Speter
2253251881Speter          APR_ARRAY_PUSH(*pcondensed_targets, const char *)
2254251881Speter            = apr_pstrdup(result_pool, rel_item);
2255251881Speter        }
2256251881Speter    }
2257251881Speter
2258251881Speter  return SVN_NO_ERROR;
2259251881Speter}
2260251881Speter
2261251881Spetersvn_error_t *
2262251881Spetersvn_uri_condense_targets(const char **pcommon,
2263251881Speter                         apr_array_header_t **pcondensed_targets,
2264251881Speter                         const apr_array_header_t *targets,
2265251881Speter                         svn_boolean_t remove_redundancies,
2266251881Speter                         apr_pool_t *result_pool,
2267251881Speter                         apr_pool_t *scratch_pool)
2268251881Speter{
2269251881Speter  int i, num_condensed = targets->nelts;
2270251881Speter  apr_array_header_t *uri_targets;
2271251881Speter  svn_boolean_t *removed;
2272251881Speter
2273251881Speter  /* Early exit when there's no data to work on. */
2274251881Speter  if (targets->nelts <= 0)
2275251881Speter    {
2276251881Speter      *pcommon = NULL;
2277251881Speter      if (pcondensed_targets)
2278251881Speter        *pcondensed_targets = NULL;
2279251881Speter      return SVN_NO_ERROR;
2280251881Speter    }
2281251881Speter
2282251881Speter  *pcommon = svn_uri_canonicalize(APR_ARRAY_IDX(targets, 0, const char *),
2283251881Speter                                  scratch_pool);
2284251881Speter
2285251881Speter  /* Early exit when there's only one uri to work on. */
2286251881Speter  if (targets->nelts == 1)
2287251881Speter    {
2288251881Speter      *pcommon = apr_pstrdup(result_pool, *pcommon);
2289251881Speter      if (pcondensed_targets)
2290251881Speter        *pcondensed_targets = apr_array_make(result_pool, 0,
2291251881Speter                                             sizeof(const char *));
2292251881Speter      return SVN_NO_ERROR;
2293251881Speter    }
2294251881Speter
2295251881Speter  /* Find the pcommon argument by finding what is common in all of the
2296251881Speter     uris. NOTE: This is not as efficient as it could be.  The calculation
2297251881Speter     of the basedir could be done in the loop below, which would
2298251881Speter     save some calls to svn_uri_get_longest_ancestor.  I decided to do it
2299251881Speter     this way because I thought it would be simpler, since this way, we don't
2300251881Speter     even do the loop if we don't need to condense the targets. */
2301251881Speter
2302251881Speter  removed = apr_pcalloc(scratch_pool, (targets->nelts *
2303251881Speter                                          sizeof(svn_boolean_t)));
2304251881Speter  uri_targets = apr_array_make(scratch_pool, targets->nelts,
2305251881Speter                               sizeof(const char *));
2306251881Speter
2307251881Speter  APR_ARRAY_PUSH(uri_targets, const char *) = *pcommon;
2308251881Speter
2309251881Speter  for (i = 1; i < targets->nelts; ++i)
2310251881Speter    {
2311251881Speter      const char *uri = svn_uri_canonicalize(
2312251881Speter                           APR_ARRAY_IDX(targets, i, const char *),
2313251881Speter                           scratch_pool);
2314251881Speter      APR_ARRAY_PUSH(uri_targets, const char *) = uri;
2315251881Speter
2316251881Speter      /* If the commonmost ancestor so far is empty, there's no point
2317251881Speter         in continuing to search for a common ancestor at all.  But
2318251881Speter         we'll keep looping for the sake of canonicalizing the
2319251881Speter         targets, I suppose.  */
2320251881Speter      if (**pcommon != '\0')
2321251881Speter        *pcommon = svn_uri_get_longest_ancestor(*pcommon, uri,
2322251881Speter                                                scratch_pool);
2323251881Speter    }
2324251881Speter
2325251881Speter  *pcommon = apr_pstrdup(result_pool, *pcommon);
2326251881Speter
2327251881Speter  if (pcondensed_targets != NULL)
2328251881Speter    {
2329251881Speter      size_t basedir_len;
2330251881Speter
2331251881Speter      if (remove_redundancies)
2332251881Speter        {
2333251881Speter          /* Find the common part of each pair of targets.  If
2334251881Speter             common part is equal to one of the dirents, the other
2335251881Speter             is a child of it, and can be removed.  If a target is
2336251881Speter             equal to *pcommon, it can also be removed. */
2337251881Speter
2338251881Speter          /* First pass: when one non-removed target is a child of
2339251881Speter             another non-removed target, remove the child. */
2340251881Speter          for (i = 0; i < uri_targets->nelts; ++i)
2341251881Speter            {
2342251881Speter              int j;
2343251881Speter
2344251881Speter              if (removed[i])
2345251881Speter                continue;
2346251881Speter
2347251881Speter              for (j = i + 1; j < uri_targets->nelts; ++j)
2348251881Speter                {
2349251881Speter                  const char *uri_i;
2350251881Speter                  const char *uri_j;
2351251881Speter                  const char *ancestor;
2352251881Speter
2353251881Speter                  if (removed[j])
2354251881Speter                    continue;
2355251881Speter
2356251881Speter                  uri_i = APR_ARRAY_IDX(uri_targets, i, const char *);
2357251881Speter                  uri_j = APR_ARRAY_IDX(uri_targets, j, const char *);
2358251881Speter
2359251881Speter                  ancestor = svn_uri_get_longest_ancestor(uri_i,
2360251881Speter                                                          uri_j,
2361251881Speter                                                          scratch_pool);
2362251881Speter
2363251881Speter                  if (*ancestor == '\0')
2364251881Speter                    continue;
2365251881Speter
2366251881Speter                  if (strcmp(ancestor, uri_i) == 0)
2367251881Speter                    {
2368251881Speter                      removed[j] = TRUE;
2369251881Speter                      num_condensed--;
2370251881Speter                    }
2371251881Speter                  else if (strcmp(ancestor, uri_j) == 0)
2372251881Speter                    {
2373251881Speter                      removed[i] = TRUE;
2374251881Speter                      num_condensed--;
2375251881Speter                    }
2376251881Speter                }
2377251881Speter            }
2378251881Speter
2379251881Speter          /* Second pass: when a target is the same as *pcommon,
2380251881Speter             remove the target. */
2381251881Speter          for (i = 0; i < uri_targets->nelts; ++i)
2382251881Speter            {
2383251881Speter              const char *uri_targets_i = APR_ARRAY_IDX(uri_targets, i,
2384251881Speter                                                        const char *);
2385251881Speter
2386251881Speter              if ((strcmp(uri_targets_i, *pcommon) == 0) && (! removed[i]))
2387251881Speter                {
2388251881Speter                  removed[i] = TRUE;
2389251881Speter                  num_condensed--;
2390251881Speter                }
2391251881Speter            }
2392251881Speter        }
2393251881Speter
2394251881Speter      /* Now create the return array, and copy the non-removed items */
2395251881Speter      basedir_len = strlen(*pcommon);
2396251881Speter      *pcondensed_targets = apr_array_make(result_pool, num_condensed,
2397251881Speter                                           sizeof(const char *));
2398251881Speter
2399251881Speter      for (i = 0; i < uri_targets->nelts; ++i)
2400251881Speter        {
2401251881Speter          const char *rel_item = APR_ARRAY_IDX(uri_targets, i, const char *);
2402251881Speter
2403251881Speter          /* Skip this if it's been removed. */
2404251881Speter          if (removed[i])
2405251881Speter            continue;
2406251881Speter
2407251881Speter          /* If a common prefix was found, condensed_targets are given
2408251881Speter             relative to that prefix.  */
2409251881Speter          if (basedir_len > 0)
2410251881Speter            {
2411251881Speter              /* Only advance our pointer past a dirent separator if
2412251881Speter                 REL_ITEM isn't the same as *PCOMMON.
2413251881Speter
2414251881Speter                 If *PCOMMON is a root dirent, basedir_len will already
2415251881Speter                 include the closing '/', so never advance the pointer
2416251881Speter                 here.
2417251881Speter                 */
2418251881Speter              rel_item += basedir_len;
2419251881Speter              if ((rel_item[0] == '/') ||
2420251881Speter                  (rel_item[0] && !svn_uri_is_root(*pcommon, basedir_len)))
2421251881Speter                {
2422251881Speter                  rel_item++;
2423251881Speter                }
2424251881Speter            }
2425251881Speter
2426251881Speter          APR_ARRAY_PUSH(*pcondensed_targets, const char *)
2427251881Speter            = svn_path_uri_decode(rel_item, result_pool);
2428251881Speter        }
2429251881Speter    }
2430251881Speter
2431251881Speter  return SVN_NO_ERROR;
2432251881Speter}
2433251881Speter
2434251881Spetersvn_error_t *
2435251881Spetersvn_dirent_is_under_root(svn_boolean_t *under_root,
2436251881Speter                         const char **result_path,
2437251881Speter                         const char *base_path,
2438251881Speter                         const char *path,
2439251881Speter                         apr_pool_t *result_pool)
2440251881Speter{
2441251881Speter  apr_status_t status;
2442251881Speter  char *full_path;
2443251881Speter
2444251881Speter  *under_root = FALSE;
2445251881Speter  if (result_path)
2446251881Speter    *result_path = NULL;
2447251881Speter
2448251881Speter  status = apr_filepath_merge(&full_path,
2449251881Speter                              base_path,
2450251881Speter                              path,
2451251881Speter                              APR_FILEPATH_NOTABOVEROOT
2452251881Speter                              | APR_FILEPATH_SECUREROOTTEST,
2453251881Speter                              result_pool);
2454251881Speter
2455251881Speter  if (status == APR_SUCCESS)
2456251881Speter    {
2457251881Speter      if (result_path)
2458251881Speter        *result_path = svn_dirent_canonicalize(full_path, result_pool);
2459251881Speter      *under_root = TRUE;
2460251881Speter      return SVN_NO_ERROR;
2461251881Speter    }
2462251881Speter  else if (status == APR_EABOVEROOT)
2463251881Speter    {
2464251881Speter      *under_root = FALSE;
2465251881Speter      return SVN_NO_ERROR;
2466251881Speter    }
2467251881Speter
2468251881Speter  return svn_error_wrap_apr(status, NULL);
2469251881Speter}
2470251881Speter
2471251881Spetersvn_error_t *
2472251881Spetersvn_uri_get_dirent_from_file_url(const char **dirent,
2473251881Speter                                 const char *url,
2474251881Speter                                 apr_pool_t *pool)
2475251881Speter{
2476251881Speter  const char *hostname, *path;
2477251881Speter
2478251881Speter  SVN_ERR_ASSERT(svn_uri_is_canonical(url, pool));
2479251881Speter
2480251881Speter  /* Verify that the URL is well-formed (loosely) */
2481251881Speter
2482251881Speter  /* First, check for the "file://" prefix. */
2483251881Speter  if (strncmp(url, "file://", 7) != 0)
2484251881Speter    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2485251881Speter                             _("Local URL '%s' does not contain 'file://' "
2486251881Speter                               "prefix"), url);
2487251881Speter
2488251881Speter  /* Find the HOSTNAME portion and the PATH portion of the URL.  The host
2489289180Speter     name is between the "file://" prefix and the next occurrence of '/'.  We
2490251881Speter     are considering everything from that '/' until the end of the URL to be
2491251881Speter     the absolute path portion of the URL.
2492251881Speter     If we got just "file://", treat it the same as "file:///". */
2493251881Speter  hostname = url + 7;
2494251881Speter  path = strchr(hostname, '/');
2495251881Speter  if (path)
2496251881Speter    hostname = apr_pstrmemdup(pool, hostname, path - hostname);
2497251881Speter  else
2498251881Speter    path = "/";
2499251881Speter
2500251881Speter  /* URI-decode HOSTNAME, and set it to NULL if it is "" or "localhost". */
2501251881Speter  if (*hostname == '\0')
2502251881Speter    hostname = NULL;
2503251881Speter  else
2504251881Speter    {
2505251881Speter      hostname = svn_path_uri_decode(hostname, pool);
2506251881Speter      if (strcmp(hostname, "localhost") == 0)
2507251881Speter        hostname = NULL;
2508251881Speter    }
2509251881Speter
2510251881Speter  /* Duplicate the URL, starting at the top of the path.
2511251881Speter     At the same time, we URI-decode the path. */
2512251881Speter#ifdef SVN_USE_DOS_PATHS
2513251881Speter  /* On Windows, we'll typically have to skip the leading / if the
2514251881Speter     path starts with a drive letter.  Like most Web browsers, We
2515251881Speter     support two variants of this scheme:
2516251881Speter
2517251881Speter         file:///X:/path    and
2518251881Speter         file:///X|/path
2519251881Speter
2520251881Speter    Note that, at least on WinNT and above,  file:////./X:/path  will
2521251881Speter    also work, so we must make sure the transformation doesn't break
2522251881Speter    that, and  file:///path  (that looks within the current drive
2523251881Speter    only) should also keep working.
2524251881Speter    If we got a non-empty hostname other than localhost, we convert this
2525251881Speter    into an UNC path.  In this case, we obviously don't strip the slash
2526251881Speter    even if the path looks like it starts with a drive letter.
2527251881Speter  */
2528251881Speter  {
2529251881Speter    static const char valid_drive_letters[] =
2530251881Speter      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2531251881Speter    /* Casting away const! */
2532251881Speter    char *dup_path = (char *)svn_path_uri_decode(path, pool);
2533251881Speter
2534251881Speter    /* This check assumes ':' and '|' are already decoded! */
2535251881Speter    if (!hostname && dup_path[1] && strchr(valid_drive_letters, dup_path[1])
2536251881Speter        && (dup_path[2] == ':' || dup_path[2] == '|'))
2537251881Speter      {
2538251881Speter        /* Skip the leading slash. */
2539251881Speter        ++dup_path;
2540251881Speter
2541251881Speter        if (dup_path[1] == '|')
2542251881Speter          dup_path[1] = ':';
2543251881Speter
2544362181Sdim        if (dup_path[2] == '/' || dup_path[2] == '\\' || dup_path[2] == '\0')
2545251881Speter          {
2546362181Sdim            /* Dirents have upper case drive letters in their canonical form */
2547362181Sdim            dup_path[0] = canonicalize_to_upper(dup_path[0]);
2548362181Sdim
2549251881Speter            if (dup_path[2] == '\0')
2550251881Speter              {
2551251881Speter                /* A valid dirent for the driveroot must be like "C:/" instead of
2552251881Speter                   just "C:" or svn_dirent_join() will use the current directory
2553251881Speter                   on the drive instead */
2554251881Speter                char *new_path = apr_pcalloc(pool, 4);
2555251881Speter                new_path[0] = dup_path[0];
2556251881Speter                new_path[1] = ':';
2557251881Speter                new_path[2] = '/';
2558251881Speter                new_path[3] = '\0';
2559251881Speter                dup_path = new_path;
2560251881Speter              }
2561362181Sdim            else
2562362181Sdim              dup_path[2] = '/'; /* Ensure not relative for '\' after drive! */
2563251881Speter          }
2564251881Speter      }
2565251881Speter    if (hostname)
2566251881Speter      {
2567251881Speter        if (dup_path[0] == '/' && dup_path[1] == '\0')
2568251881Speter          return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2569251881Speter                                   _("Local URL '%s' contains only a hostname, "
2570251881Speter                                     "no path"), url);
2571251881Speter
2572251881Speter        /* We still know that the path starts with a slash. */
2573289180Speter        *dirent = apr_pstrcat(pool, "//", hostname, dup_path, SVN_VA_NULL);
2574251881Speter      }
2575251881Speter    else
2576251881Speter      *dirent = dup_path;
2577251881Speter  }
2578251881Speter#else /* !SVN_USE_DOS_PATHS */
2579251881Speter  /* Currently, the only hostnames we are allowing on non-Win32 platforms
2580251881Speter     are the empty string and 'localhost'. */
2581251881Speter  if (hostname)
2582251881Speter    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2583251881Speter                             _("Local URL '%s' contains unsupported hostname"),
2584251881Speter                             url);
2585251881Speter
2586251881Speter  *dirent = svn_path_uri_decode(path, pool);
2587251881Speter#endif /* SVN_USE_DOS_PATHS */
2588251881Speter  return SVN_NO_ERROR;
2589251881Speter}
2590251881Speter
2591251881Spetersvn_error_t *
2592251881Spetersvn_uri_get_file_url_from_dirent(const char **url,
2593251881Speter                                 const char *dirent,
2594251881Speter                                 apr_pool_t *pool)
2595251881Speter{
2596251881Speter  assert(svn_dirent_is_canonical(dirent, pool));
2597251881Speter
2598251881Speter  SVN_ERR(svn_dirent_get_absolute(&dirent, dirent, pool));
2599251881Speter
2600251881Speter  dirent = svn_path_uri_encode(dirent, pool);
2601251881Speter
2602251881Speter#ifndef SVN_USE_DOS_PATHS
2603251881Speter  if (dirent[0] == '/' && dirent[1] == '\0')
2604251881Speter    dirent = NULL; /* "file://" is the canonical form of "file:///" */
2605251881Speter
2606289180Speter  *url = apr_pstrcat(pool, "file://", dirent, SVN_VA_NULL);
2607251881Speter#else
2608251881Speter  if (dirent[0] == '/')
2609251881Speter    {
2610251881Speter      /* Handle UNC paths //server/share -> file://server/share */
2611251881Speter      assert(dirent[1] == '/'); /* Expect UNC, not non-absolute */
2612251881Speter
2613289180Speter      *url = apr_pstrcat(pool, "file:", dirent, SVN_VA_NULL);
2614251881Speter    }
2615251881Speter  else
2616251881Speter    {
2617289180Speter      char *uri = apr_pstrcat(pool, "file:///", dirent, SVN_VA_NULL);
2618251881Speter      apr_size_t len = 8 /* strlen("file:///") */ + strlen(dirent);
2619251881Speter
2620251881Speter      /* "C:/" is a canonical dirent on Windows,
2621251881Speter         but "file:///C:/" is not a canonical uri */
2622251881Speter      if (uri[len-1] == '/')
2623251881Speter        uri[len-1] = '\0';
2624251881Speter
2625251881Speter      *url = uri;
2626251881Speter    }
2627251881Speter#endif
2628251881Speter
2629251881Speter  return SVN_NO_ERROR;
2630251881Speter}
2631251881Speter
2632251881Speter
2633251881Speter
2634251881Speter/* -------------- The fspath API (see private/svn_fspath.h) -------------- */
2635251881Speter
2636251881Spetersvn_boolean_t
2637251881Spetersvn_fspath__is_canonical(const char *fspath)
2638251881Speter{
2639251881Speter  return fspath[0] == '/' && relpath_is_canonical(fspath + 1);
2640251881Speter}
2641251881Speter
2642251881Speter
2643251881Speterconst char *
2644251881Spetersvn_fspath__canonicalize(const char *fspath,
2645251881Speter                         apr_pool_t *pool)
2646251881Speter{
2647251881Speter  if ((fspath[0] == '/') && (fspath[1] == '\0'))
2648251881Speter    return "/";
2649251881Speter
2650251881Speter  return apr_pstrcat(pool, "/", svn_relpath_canonicalize(fspath, pool),
2651289180Speter                     SVN_VA_NULL);
2652251881Speter}
2653251881Speter
2654251881Speter
2655251881Spetersvn_boolean_t
2656251881Spetersvn_fspath__is_root(const char *fspath, apr_size_t len)
2657251881Speter{
2658251881Speter  /* directory is root if it's equal to '/' */
2659251881Speter  return (len == 1 && fspath[0] == '/');
2660251881Speter}
2661251881Speter
2662251881Speter
2663251881Speterconst char *
2664251881Spetersvn_fspath__skip_ancestor(const char *parent_fspath,
2665251881Speter                          const char *child_fspath)
2666251881Speter{
2667251881Speter  assert(svn_fspath__is_canonical(parent_fspath));
2668251881Speter  assert(svn_fspath__is_canonical(child_fspath));
2669251881Speter
2670251881Speter  return svn_relpath_skip_ancestor(parent_fspath + 1, child_fspath + 1);
2671251881Speter}
2672251881Speter
2673251881Speter
2674251881Speterconst char *
2675251881Spetersvn_fspath__dirname(const char *fspath,
2676251881Speter                    apr_pool_t *pool)
2677251881Speter{
2678251881Speter  assert(svn_fspath__is_canonical(fspath));
2679251881Speter
2680251881Speter  if (fspath[0] == '/' && fspath[1] == '\0')
2681251881Speter    return apr_pstrdup(pool, fspath);
2682251881Speter  else
2683251881Speter    return apr_pstrcat(pool, "/", svn_relpath_dirname(fspath + 1, pool),
2684289180Speter                       SVN_VA_NULL);
2685251881Speter}
2686251881Speter
2687251881Speter
2688251881Speterconst char *
2689251881Spetersvn_fspath__basename(const char *fspath,
2690251881Speter                     apr_pool_t *pool)
2691251881Speter{
2692251881Speter  const char *result;
2693251881Speter  assert(svn_fspath__is_canonical(fspath));
2694251881Speter
2695251881Speter  result = svn_relpath_basename(fspath + 1, pool);
2696251881Speter
2697251881Speter  assert(strchr(result, '/') == NULL);
2698251881Speter  return result;
2699251881Speter}
2700251881Speter
2701251881Spetervoid
2702251881Spetersvn_fspath__split(const char **dirpath,
2703251881Speter                  const char **base_name,
2704251881Speter                  const char *fspath,
2705251881Speter                  apr_pool_t *result_pool)
2706251881Speter{
2707251881Speter  assert(dirpath != base_name);
2708251881Speter
2709251881Speter  if (dirpath)
2710251881Speter    *dirpath = svn_fspath__dirname(fspath, result_pool);
2711251881Speter
2712251881Speter  if (base_name)
2713251881Speter    *base_name = svn_fspath__basename(fspath, result_pool);
2714251881Speter}
2715251881Speter
2716251881Speterchar *
2717251881Spetersvn_fspath__join(const char *fspath,
2718251881Speter                 const char *relpath,
2719251881Speter                 apr_pool_t *result_pool)
2720251881Speter{
2721251881Speter  char *result;
2722251881Speter  assert(svn_fspath__is_canonical(fspath));
2723251881Speter  assert(svn_relpath_is_canonical(relpath));
2724251881Speter
2725251881Speter  if (relpath[0] == '\0')
2726251881Speter    result = apr_pstrdup(result_pool, fspath);
2727251881Speter  else if (fspath[1] == '\0')
2728289180Speter    result = apr_pstrcat(result_pool, "/", relpath, SVN_VA_NULL);
2729251881Speter  else
2730289180Speter    result = apr_pstrcat(result_pool, fspath, "/", relpath, SVN_VA_NULL);
2731251881Speter
2732251881Speter  assert(svn_fspath__is_canonical(result));
2733251881Speter  return result;
2734251881Speter}
2735251881Speter
2736251881Speterchar *
2737251881Spetersvn_fspath__get_longest_ancestor(const char *fspath1,
2738251881Speter                                 const char *fspath2,
2739251881Speter                                 apr_pool_t *result_pool)
2740251881Speter{
2741251881Speter  char *result;
2742251881Speter  assert(svn_fspath__is_canonical(fspath1));
2743251881Speter  assert(svn_fspath__is_canonical(fspath2));
2744251881Speter
2745251881Speter  result = apr_pstrcat(result_pool, "/",
2746251881Speter                       svn_relpath_get_longest_ancestor(fspath1 + 1,
2747251881Speter                                                        fspath2 + 1,
2748251881Speter                                                        result_pool),
2749289180Speter                       SVN_VA_NULL);
2750251881Speter
2751251881Speter  assert(svn_fspath__is_canonical(result));
2752251881Speter  return result;
2753251881Speter}
2754251881Speter
2755251881Speter
2756251881Speter
2757251881Speter
2758251881Speter/* -------------- The urlpath API (see private/svn_fspath.h) ------------- */
2759251881Speter
2760251881Speterconst char *
2761251881Spetersvn_urlpath__canonicalize(const char *uri,
2762251881Speter                          apr_pool_t *pool)
2763251881Speter{
2764251881Speter  if (svn_path_is_url(uri))
2765251881Speter    {
2766251881Speter      uri = svn_uri_canonicalize(uri, pool);
2767251881Speter    }
2768251881Speter  else
2769251881Speter    {
2770251881Speter      uri = svn_fspath__canonicalize(uri, pool);
2771251881Speter      /* Do a little dance to normalize hex encoding. */
2772251881Speter      uri = svn_path_uri_decode(uri, pool);
2773251881Speter      uri = svn_path_uri_encode(uri, pool);
2774251881Speter    }
2775251881Speter  return uri;
2776251881Speter}
2777269833Speter
2778269833Speter
2779269833Speter/* -------------- The cert API (see private/svn_cert.h) ------------- */
2780269833Speter
2781269833Spetersvn_boolean_t
2782269833Spetersvn_cert__match_dns_identity(svn_string_t *pattern, svn_string_t *hostname)
2783269833Speter{
2784269833Speter  apr_size_t pattern_pos = 0, hostname_pos = 0;
2785269833Speter
2786269833Speter  /* support leading wildcards that composed of the only character in the
2787269833Speter   * left-most label. */
2788269833Speter  if (pattern->len >= 2 &&
2789269833Speter      pattern->data[pattern_pos] == '*' &&
2790269833Speter      pattern->data[pattern_pos + 1] == '.')
2791269833Speter    {
2792269833Speter      while (hostname_pos < hostname->len &&
2793269833Speter             hostname->data[hostname_pos] != '.')
2794269833Speter        {
2795269833Speter          hostname_pos++;
2796269833Speter        }
2797269833Speter      /* Assume that the wildcard must match something.  Rule 2 says
2798269833Speter       * that *.example.com should not match example.com.  If the wildcard
2799269833Speter       * ends up not matching anything then it matches .example.com which
2800269833Speter       * seems to be essentially the same as just example.com */
2801269833Speter      if (hostname_pos == 0)
2802269833Speter        return FALSE;
2803269833Speter
2804269833Speter      pattern_pos++;
2805269833Speter    }
2806269833Speter
2807269833Speter  while (pattern_pos < pattern->len && hostname_pos < hostname->len)
2808269833Speter    {
2809269833Speter      char pattern_c = pattern->data[pattern_pos];
2810269833Speter      char hostname_c = hostname->data[hostname_pos];
2811269833Speter
2812269833Speter      /* fold case as described in RFC 4343.
2813269833Speter       * Note: We actually convert to lowercase, since our URI
2814269833Speter       * canonicalization code converts to lowercase and generally
2815269833Speter       * most certs are issued with lowercase DNS names, meaning
2816269833Speter       * this avoids the fold operation in most cases.  The RFC
2817269833Speter       * suggests the opposite transformation, but doesn't require
2818269833Speter       * any specific implementation in any case.  It is critical
2819269833Speter       * that this folding be locale independent so you can't use
2820269833Speter       * tolower(). */
2821269833Speter      pattern_c = canonicalize_to_lower(pattern_c);
2822269833Speter      hostname_c = canonicalize_to_lower(hostname_c);
2823269833Speter
2824269833Speter      if (pattern_c != hostname_c)
2825269833Speter        {
2826269833Speter          /* doesn't match */
2827269833Speter          return FALSE;
2828269833Speter        }
2829269833Speter      else
2830269833Speter        {
2831269833Speter          /* characters match so skip both */
2832269833Speter          pattern_pos++;
2833269833Speter          hostname_pos++;
2834269833Speter        }
2835269833Speter    }
2836269833Speter
2837269833Speter  /* ignore a trailing period on the hostname since this has no effect on the
2838269833Speter   * security of the matching.  See the following for the long explanation as
2839269833Speter   * to why:
2840269833Speter   * https://bugzilla.mozilla.org/show_bug.cgi?id=134402#c28
2841269833Speter   */
2842269833Speter  if (pattern_pos == pattern->len &&
2843269833Speter      hostname_pos == hostname->len - 1 &&
2844269833Speter      hostname->data[hostname_pos] == '.')
2845269833Speter    hostname_pos++;
2846269833Speter
2847269833Speter  if (pattern_pos != pattern->len || hostname_pos != hostname->len)
2848269833Speter    {
2849269833Speter      /* end didn't match */
2850269833Speter      return FALSE;
2851269833Speter    }
2852269833Speter
2853269833Speter  return TRUE;
2854269833Speter}
2855