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"
40251881Speter#include "private/svn_fspath.h"
41251881Speter
42251881Speter/* The canonical empty path.  Can this be changed?  Well, change the empty
43251881Speter   test below and the path library will work, not so sure about the fs/wc
44251881Speter   libraries. */
45251881Speter#define SVN_EMPTY_PATH ""
46251881Speter
47251881Speter/* TRUE if s is the canonical empty path, FALSE otherwise */
48251881Speter#define SVN_PATH_IS_EMPTY(s) ((s)[0] == '\0')
49251881Speter
50251881Speter/* TRUE if s,n is the platform's empty path ("."), FALSE otherwise. Can
51251881Speter   this be changed?  Well, the path library will work, not so sure about
52251881Speter   the OS! */
53251881Speter#define SVN_PATH_IS_PLATFORM_EMPTY(s,n) ((n) == 1 && (s)[0] == '.')
54251881Speter
55251881Speter/* This check must match the check on top of dirent_uri-tests.c and
56251881Speter   path-tests.c */
57251881Speter#if defined(WIN32) || defined(__CYGWIN__) || defined(__OS2__)
58251881Speter#define SVN_USE_DOS_PATHS
59251881Speter#endif
60251881Speter
61251881Speter/* Path type definition. Used only by internal functions. */
62251881Spetertypedef enum path_type_t {
63251881Speter  type_uri,
64251881Speter  type_dirent,
65251881Speter  type_relpath
66251881Speter} path_type_t;
67251881Speter
68251881Speter
69251881Speter/**** Forward declarations *****/
70251881Speter
71251881Speterstatic svn_boolean_t
72251881Speterrelpath_is_canonical(const char *relpath);
73251881Speter
74251881Speter
75251881Speter/**** Internal implementation functions *****/
76251881Speter
77251881Speter/* Return an internal-style new path based on PATH, allocated in POOL.
78251881Speter *
79251881Speter * "Internal-style" means that separators are all '/'.
80251881Speter */
81251881Speterstatic const char *
82251881Speterinternal_style(const char *path, apr_pool_t *pool)
83251881Speter{
84251881Speter#if '/' != SVN_PATH_LOCAL_SEPARATOR
85251881Speter    {
86251881Speter      char *p = apr_pstrdup(pool, path);
87251881Speter      path = p;
88251881Speter
89251881Speter      /* Convert all local-style separators to the canonical ones. */
90251881Speter      for (; *p != '\0'; ++p)
91251881Speter        if (*p == SVN_PATH_LOCAL_SEPARATOR)
92251881Speter          *p = '/';
93251881Speter    }
94251881Speter#endif
95251881Speter
96251881Speter  return path;
97251881Speter}
98251881Speter
99251881Speter/* Locale insensitive tolower() for converting parts of dirents and urls
100251881Speter   while canonicalizing */
101251881Speterstatic char
102251881Spetercanonicalize_to_lower(char c)
103251881Speter{
104251881Speter  if (c < 'A' || c > 'Z')
105251881Speter    return c;
106251881Speter  else
107251881Speter    return (char)(c - 'A' + 'a');
108251881Speter}
109251881Speter
110251881Speter/* Locale insensitive toupper() for converting parts of dirents and urls
111251881Speter   while canonicalizing */
112251881Speterstatic char
113251881Spetercanonicalize_to_upper(char c)
114251881Speter{
115251881Speter  if (c < 'a' || c > 'z')
116251881Speter    return c;
117251881Speter  else
118251881Speter    return (char)(c - 'a' + 'A');
119251881Speter}
120251881Speter
121251881Speter/* Calculates the length of the dirent absolute or non absolute root in
122251881Speter   DIRENT, return 0 if dirent is not rooted  */
123251881Speterstatic apr_size_t
124251881Speterdirent_root_length(const char *dirent, apr_size_t len)
125251881Speter{
126251881Speter#ifdef SVN_USE_DOS_PATHS
127251881Speter  if (len >= 2 && dirent[1] == ':' &&
128251881Speter      ((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
129251881Speter       (dirent[0] >= 'a' && dirent[0] <= 'z')))
130251881Speter    {
131251881Speter      return (len > 2 && dirent[2] == '/') ? 3 : 2;
132251881Speter    }
133251881Speter
134251881Speter  if (len > 2 && dirent[0] == '/' && dirent[1] == '/')
135251881Speter    {
136251881Speter      apr_size_t i = 2;
137251881Speter
138251881Speter      while (i < len && dirent[i] != '/')
139251881Speter        i++;
140251881Speter
141251881Speter      if (i == len)
142251881Speter        return len; /* Cygwin drive alias, invalid path on WIN32 */
143251881Speter
144251881Speter      i++; /* Skip '/' */
145251881Speter
146251881Speter      while (i < len && dirent[i] != '/')
147251881Speter        i++;
148251881Speter
149251881Speter      return i;
150251881Speter    }
151251881Speter#endif /* SVN_USE_DOS_PATHS */
152251881Speter  if (len >= 1 && dirent[0] == '/')
153251881Speter    return 1;
154251881Speter
155251881Speter  return 0;
156251881Speter}
157251881Speter
158251881Speter
159251881Speter/* Return the length of substring necessary to encompass the entire
160251881Speter * previous dirent segment in DIRENT, which should be a LEN byte string.
161251881Speter *
162251881Speter * A trailing slash will not be included in the returned length except
163251881Speter * in the case in which DIRENT is absolute and there are no more
164251881Speter * previous segments.
165251881Speter */
166251881Speterstatic apr_size_t
167251881Speterdirent_previous_segment(const char *dirent,
168251881Speter                        apr_size_t len)
169251881Speter{
170251881Speter  if (len == 0)
171251881Speter    return 0;
172251881Speter
173251881Speter  --len;
174251881Speter  while (len > 0 && dirent[len] != '/'
175251881Speter#ifdef SVN_USE_DOS_PATHS
176251881Speter                 && (dirent[len] != ':' || len != 1)
177251881Speter#endif /* SVN_USE_DOS_PATHS */
178251881Speter        )
179251881Speter    --len;
180251881Speter
181251881Speter  /* check if the remaining segment including trailing '/' is a root dirent */
182251881Speter  if (dirent_root_length(dirent, len+1) == len + 1)
183251881Speter    return len + 1;
184251881Speter  else
185251881Speter    return len;
186251881Speter}
187251881Speter
188251881Speter/* Calculates the length occupied by the schema defined root of URI */
189251881Speterstatic apr_size_t
190251881Speteruri_schema_root_length(const char *uri, apr_size_t len)
191251881Speter{
192251881Speter  apr_size_t i;
193251881Speter
194251881Speter  for (i = 0; i < len; i++)
195251881Speter    {
196251881Speter      if (uri[i] == '/')
197251881Speter        {
198251881Speter          if (i > 0 && uri[i-1] == ':' && i < len-1 && uri[i+1] == '/')
199251881Speter            {
200251881Speter              /* We have an absolute uri */
201251881Speter              if (i == 5 && strncmp("file", uri, 4) == 0)
202251881Speter                return 7; /* file:// */
203251881Speter              else
204251881Speter                {
205251881Speter                  for (i += 2; i < len; i++)
206251881Speter                    if (uri[i] == '/')
207251881Speter                      return i;
208251881Speter
209251881Speter                  return len; /* Only a hostname is found */
210251881Speter                }
211251881Speter            }
212251881Speter          else
213251881Speter            return 0;
214251881Speter        }
215251881Speter    }
216251881Speter
217251881Speter  return 0;
218251881Speter}
219251881Speter
220251881Speter/* Returns TRUE if svn_dirent_is_absolute(dirent) or when dirent has
221251881Speter   a non absolute root. (E.g. '/' or 'F:' on Windows) */
222251881Speterstatic svn_boolean_t
223251881Speterdirent_is_rooted(const char *dirent)
224251881Speter{
225251881Speter  if (! dirent)
226251881Speter    return FALSE;
227251881Speter
228251881Speter  /* Root on all systems */
229251881Speter  if (dirent[0] == '/')
230251881Speter    return TRUE;
231251881Speter
232251881Speter  /* On Windows, dirent is also absolute when it starts with 'H:' or 'H:/'
233251881Speter     where 'H' is any letter. */
234251881Speter#ifdef SVN_USE_DOS_PATHS
235251881Speter  if (((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
236251881Speter       (dirent[0] >= 'a' && dirent[0] <= 'z')) &&
237251881Speter      (dirent[1] == ':'))
238251881Speter     return TRUE;
239251881Speter#endif /* SVN_USE_DOS_PATHS */
240251881Speter
241251881Speter  return FALSE;
242251881Speter}
243251881Speter
244251881Speter/* Return the length of substring necessary to encompass the entire
245251881Speter * previous relpath segment in RELPATH, which should be a LEN byte string.
246251881Speter *
247251881Speter * A trailing slash will not be included in the returned length.
248251881Speter */
249251881Speterstatic apr_size_t
250251881Speterrelpath_previous_segment(const char *relpath,
251251881Speter                         apr_size_t len)
252251881Speter{
253251881Speter  if (len == 0)
254251881Speter    return 0;
255251881Speter
256251881Speter  --len;
257251881Speter  while (len > 0 && relpath[len] != '/')
258251881Speter    --len;
259251881Speter
260251881Speter  return len;
261251881Speter}
262251881Speter
263251881Speter/* Return the length of substring necessary to encompass the entire
264251881Speter * previous uri segment in URI, which should be a LEN byte string.
265251881Speter *
266251881Speter * A trailing slash will not be included in the returned length except
267251881Speter * in the case in which URI is absolute and there are no more
268251881Speter * previous segments.
269251881Speter */
270251881Speterstatic apr_size_t
271251881Speteruri_previous_segment(const char *uri,
272251881Speter                     apr_size_t len)
273251881Speter{
274251881Speter  apr_size_t root_length;
275251881Speter  apr_size_t i = len;
276251881Speter  if (len == 0)
277251881Speter    return 0;
278251881Speter
279251881Speter  root_length = uri_schema_root_length(uri, len);
280251881Speter
281251881Speter  --i;
282251881Speter  while (len > root_length && uri[i] != '/')
283251881Speter    --i;
284251881Speter
285251881Speter  if (i == 0 && len > 1 && *uri == '/')
286251881Speter    return 1;
287251881Speter
288251881Speter  return i;
289251881Speter}
290251881Speter
291251881Speter/* Return the canonicalized version of PATH, of type TYPE, allocated in
292251881Speter * POOL.
293251881Speter */
294251881Speterstatic const char *
295251881Spetercanonicalize(path_type_t type, const char *path, apr_pool_t *pool)
296251881Speter{
297251881Speter  char *canon, *dst;
298251881Speter  const char *src;
299251881Speter  apr_size_t seglen;
300251881Speter  apr_size_t schemelen = 0;
301251881Speter  apr_size_t canon_segments = 0;
302251881Speter  svn_boolean_t url = FALSE;
303251881Speter  char *schema_data = NULL;
304251881Speter
305251881Speter  /* "" is already canonical, so just return it; note that later code
306251881Speter     depends on path not being zero-length.  */
307251881Speter  if (SVN_PATH_IS_EMPTY(path))
308251881Speter    {
309251881Speter      assert(type != type_uri);
310251881Speter      return "";
311251881Speter    }
312251881Speter
313251881Speter  dst = canon = apr_pcalloc(pool, strlen(path) + 1);
314251881Speter
315251881Speter  /* If this is supposed to be an URI, it should start with
316251881Speter     "scheme://".  We'll copy the scheme, host name, etc. to DST and
317251881Speter     set URL = TRUE. */
318251881Speter  src = path;
319251881Speter  if (type == type_uri)
320251881Speter    {
321251881Speter      assert(*src != '/');
322251881Speter
323251881Speter      while (*src && (*src != '/') && (*src != ':'))
324251881Speter        src++;
325251881Speter
326251881Speter      if (*src == ':' && *(src+1) == '/' && *(src+2) == '/')
327251881Speter        {
328251881Speter          const char *seg;
329251881Speter
330251881Speter          url = TRUE;
331251881Speter
332251881Speter          /* Found a scheme, convert to lowercase and copy to dst. */
333251881Speter          src = path;
334251881Speter          while (*src != ':')
335251881Speter            {
336251881Speter              *(dst++) = canonicalize_to_lower((*src++));
337251881Speter              schemelen++;
338251881Speter            }
339251881Speter          *(dst++) = ':';
340251881Speter          *(dst++) = '/';
341251881Speter          *(dst++) = '/';
342251881Speter          src += 3;
343251881Speter          schemelen += 3;
344251881Speter
345251881Speter          /* This might be the hostname */
346251881Speter          seg = src;
347251881Speter          while (*src && (*src != '/') && (*src != '@'))
348251881Speter            src++;
349251881Speter
350251881Speter          if (*src == '@')
351251881Speter            {
352251881Speter              /* Copy the username & password. */
353251881Speter              seglen = src - seg + 1;
354251881Speter              memcpy(dst, seg, seglen);
355251881Speter              dst += seglen;
356251881Speter              src++;
357251881Speter            }
358251881Speter          else
359251881Speter            src = seg;
360251881Speter
361251881Speter          /* Found a hostname, convert to lowercase and copy to dst. */
362251881Speter          if (*src == '[')
363251881Speter            {
364251881Speter             *(dst++) = *(src++); /* Copy '[' */
365251881Speter
366251881Speter              while (*src == ':'
367251881Speter                     || (*src >= '0' && (*src <= '9'))
368251881Speter                     || (*src >= 'a' && (*src <= 'f'))
369251881Speter                     || (*src >= 'A' && (*src <= 'F')))
370251881Speter                {
371251881Speter                  *(dst++) = canonicalize_to_lower((*src++));
372251881Speter                }
373251881Speter
374251881Speter              if (*src == ']')
375251881Speter                *(dst++) = *(src++); /* Copy ']' */
376251881Speter            }
377251881Speter          else
378251881Speter            while (*src && (*src != '/') && (*src != ':'))
379251881Speter              *(dst++) = canonicalize_to_lower((*src++));
380251881Speter
381251881Speter          if (*src == ':')
382251881Speter            {
383251881Speter              /* We probably have a port number: Is it a default portnumber
384251881Speter                 which doesn't belong in a canonical url? */
385251881Speter              if (src[1] == '8' && src[2] == '0'
386251881Speter                  && (src[3]== '/'|| !src[3])
387251881Speter                  && !strncmp(canon, "http:", 5))
388251881Speter                {
389251881Speter                  src += 3;
390251881Speter                }
391251881Speter              else if (src[1] == '4' && src[2] == '4' && src[3] == '3'
392251881Speter                       && (src[4]== '/'|| !src[4])
393251881Speter                       && !strncmp(canon, "https:", 6))
394251881Speter                {
395251881Speter                  src += 4;
396251881Speter                }
397251881Speter              else if (src[1] == '3' && src[2] == '6'
398251881Speter                       && src[3] == '9' && src[4] == '0'
399251881Speter                       && (src[5]== '/'|| !src[5])
400251881Speter                       && !strncmp(canon, "svn:", 4))
401251881Speter                {
402251881Speter                  src += 5;
403251881Speter                }
404251881Speter              else if (src[1] == '/' || !src[1])
405251881Speter                {
406251881Speter                  src += 1;
407251881Speter                }
408251881Speter
409251881Speter              while (*src && (*src != '/'))
410251881Speter                *(dst++) = canonicalize_to_lower((*src++));
411251881Speter            }
412251881Speter
413251881Speter          /* Copy trailing slash, or null-terminator. */
414251881Speter          *(dst) = *(src);
415251881Speter
416251881Speter          /* Move src and dst forward only if we are not
417251881Speter           * at null-terminator yet. */
418251881Speter          if (*src)
419251881Speter            {
420251881Speter              src++;
421251881Speter              dst++;
422251881Speter              schema_data = dst;
423251881Speter            }
424251881Speter
425251881Speter          canon_segments = 1;
426251881Speter        }
427251881Speter    }
428251881Speter
429251881Speter  /* Copy to DST any separator or drive letter that must come before the
430251881Speter     first regular path segment. */
431251881Speter  if (! url && type != type_relpath)
432251881Speter    {
433251881Speter      src = path;
434251881Speter      /* If this is an absolute path, then just copy over the initial
435251881Speter         separator character. */
436251881Speter      if (*src == '/')
437251881Speter        {
438251881Speter          *(dst++) = *(src++);
439251881Speter
440251881Speter#ifdef SVN_USE_DOS_PATHS
441251881Speter          /* On Windows permit two leading separator characters which means an
442251881Speter           * UNC path. */
443251881Speter          if ((type == type_dirent) && *src == '/')
444251881Speter            *(dst++) = *(src++);
445251881Speter#endif /* SVN_USE_DOS_PATHS */
446251881Speter        }
447251881Speter#ifdef SVN_USE_DOS_PATHS
448251881Speter      /* On Windows the first segment can be a drive letter, which we normalize
449251881Speter         to upper case. */
450251881Speter      else if (type == type_dirent &&
451251881Speter               ((*src >= 'a' && *src <= 'z') ||
452251881Speter                (*src >= 'A' && *src <= 'Z')) &&
453251881Speter               (src[1] == ':'))
454251881Speter        {
455251881Speter          *(dst++) = canonicalize_to_upper(*(src++));
456251881Speter          /* Leave the ':' to be processed as (or as part of) a path segment
457251881Speter             by the following code block, so we need not care whether it has
458251881Speter             a slash after it. */
459251881Speter        }
460251881Speter#endif /* SVN_USE_DOS_PATHS */
461251881Speter    }
462251881Speter
463251881Speter  while (*src)
464251881Speter    {
465251881Speter      /* Parse each segment, finding the closing '/' (which might look
466251881Speter         like '%2F' for URIs).  */
467251881Speter      const char *next = src;
468251881Speter      apr_size_t slash_len = 0;
469251881Speter
470251881Speter      while (*next
471251881Speter             && (next[0] != '/')
472251881Speter             && (! (type == type_uri && next[0] == '%' && next[1] == '2' &&
473251881Speter                    canonicalize_to_upper(next[2]) == 'F')))
474251881Speter        {
475251881Speter          ++next;
476251881Speter        }
477251881Speter
478251881Speter      /* Record how long our "slash" is. */
479251881Speter      if (next[0] == '/')
480251881Speter        slash_len = 1;
481251881Speter      else if (type == type_uri && next[0] == '%')
482251881Speter        slash_len = 3;
483251881Speter
484251881Speter      seglen = next - src;
485251881Speter
486251881Speter      if (seglen == 0
487251881Speter          || (seglen == 1 && src[0] == '.')
488251881Speter          || (type == type_uri && seglen == 3 && src[0] == '%' && src[1] == '2'
489251881Speter              && canonicalize_to_upper(src[2]) == 'E'))
490251881Speter        {
491251881Speter          /* Empty or noop segment, so do nothing.  (For URIs, '%2E'
492251881Speter             is equivalent to '.').  */
493251881Speter        }
494251881Speter#ifdef SVN_USE_DOS_PATHS
495251881Speter      /* If this is the first path segment of a file:// URI and it contains a
496251881Speter         windows drive letter, convert the drive letter to upper case. */
497251881Speter      else if (url && canon_segments == 1 && seglen == 2 &&
498251881Speter               (strncmp(canon, "file:", 5) == 0) &&
499251881Speter               src[0] >= 'a' && src[0] <= 'z' && src[1] == ':')
500251881Speter        {
501251881Speter          *(dst++) = canonicalize_to_upper(src[0]);
502251881Speter          *(dst++) = ':';
503251881Speter          if (*next)
504251881Speter            *(dst++) = *next;
505251881Speter          canon_segments++;
506251881Speter        }
507251881Speter#endif /* SVN_USE_DOS_PATHS */
508251881Speter      else
509251881Speter        {
510251881Speter          /* An actual segment, append it to the destination path */
511251881Speter          memcpy(dst, src, seglen);
512251881Speter          dst += seglen;
513251881Speter          if (slash_len)
514251881Speter            *(dst++) = '/';
515251881Speter          canon_segments++;
516251881Speter        }
517251881Speter
518251881Speter      /* Skip over trailing slash to the next segment. */
519251881Speter      src = next + slash_len;
520251881Speter    }
521251881Speter
522251881Speter  /* Remove the trailing slash if there was at least one
523251881Speter   * canonical segment and the last segment ends with a slash.
524251881Speter   *
525251881Speter   * But keep in mind that, for URLs, the scheme counts as a
526251881Speter   * canonical segment -- so if path is ONLY a scheme (such
527251881Speter   * as "https://") we should NOT remove the trailing slash. */
528251881Speter  if ((canon_segments > 0 && *(dst - 1) == '/')
529251881Speter      && ! (url && path[schemelen] == '\0'))
530251881Speter    {
531251881Speter      dst --;
532251881Speter    }
533251881Speter
534251881Speter  *dst = '\0';
535251881Speter
536251881Speter#ifdef SVN_USE_DOS_PATHS
537251881Speter  /* Skip leading double slashes when there are less than 2
538251881Speter   * canon segments. UNC paths *MUST* have two segments. */
539251881Speter  if ((type == type_dirent) && canon[0] == '/' && canon[1] == '/')
540251881Speter    {
541251881Speter      if (canon_segments < 2)
542251881Speter        return canon + 1;
543251881Speter      else
544251881Speter        {
545251881Speter          /* Now we're sure this is a valid UNC path, convert the server name
546251881Speter             (the first path segment) to lowercase as Windows treats it as case
547251881Speter             insensitive.
548251881Speter             Note: normally the share name is treated as case insensitive too,
549251881Speter             but it seems to be possible to configure Samba to treat those as
550251881Speter             case sensitive, so better leave that alone. */
551251881Speter          for (dst = canon + 2; *dst && *dst != '/'; dst++)
552251881Speter            *dst = canonicalize_to_lower(*dst);
553251881Speter        }
554251881Speter    }
555251881Speter#endif /* SVN_USE_DOS_PATHS */
556251881Speter
557251881Speter  /* Check the normalization of characters in a uri */
558251881Speter  if (schema_data)
559251881Speter    {
560251881Speter      int need_extra = 0;
561251881Speter      src = schema_data;
562251881Speter
563251881Speter      while (*src)
564251881Speter        {
565251881Speter          switch (*src)
566251881Speter            {
567251881Speter              case '/':
568251881Speter                break;
569251881Speter              case '%':
570251881Speter                if (!svn_ctype_isxdigit(*(src+1)) ||
571251881Speter                    !svn_ctype_isxdigit(*(src+2)))
572251881Speter                  need_extra += 2;
573251881Speter                else
574251881Speter                  src += 2;
575251881Speter                break;
576251881Speter              default:
577251881Speter                if (!svn_uri__char_validity[(unsigned char)*src])
578251881Speter                  need_extra += 2;
579251881Speter                break;
580251881Speter            }
581251881Speter          src++;
582251881Speter        }
583251881Speter
584251881Speter      if (need_extra > 0)
585251881Speter        {
586251881Speter          apr_size_t pre_schema_size = (apr_size_t)(schema_data - canon);
587251881Speter
588251881Speter          dst = apr_palloc(pool, (apr_size_t)(src - canon) + need_extra + 1);
589251881Speter          memcpy(dst, canon, pre_schema_size);
590251881Speter          canon = dst;
591251881Speter
592251881Speter          dst += pre_schema_size;
593251881Speter        }
594251881Speter      else
595251881Speter        dst = schema_data;
596251881Speter
597251881Speter      src = schema_data;
598251881Speter
599251881Speter      while (*src)
600251881Speter        {
601251881Speter          switch (*src)
602251881Speter            {
603251881Speter              case '/':
604251881Speter                *(dst++) = '/';
605251881Speter                break;
606251881Speter              case '%':
607251881Speter                if (!svn_ctype_isxdigit(*(src+1)) ||
608251881Speter                    !svn_ctype_isxdigit(*(src+2)))
609251881Speter                  {
610251881Speter                    *(dst++) = '%';
611251881Speter                    *(dst++) = '2';
612251881Speter                    *(dst++) = '5';
613251881Speter                  }
614251881Speter                else
615251881Speter                  {
616251881Speter                    char digitz[3];
617251881Speter                    int val;
618251881Speter
619251881Speter                    digitz[0] = *(++src);
620251881Speter                    digitz[1] = *(++src);
621251881Speter                    digitz[2] = 0;
622251881Speter
623251881Speter                    val = (int)strtol(digitz, NULL, 16);
624251881Speter
625251881Speter                    if (svn_uri__char_validity[(unsigned char)val])
626251881Speter                      *(dst++) = (char)val;
627251881Speter                    else
628251881Speter                      {
629251881Speter                        *(dst++) = '%';
630251881Speter                        *(dst++) = canonicalize_to_upper(digitz[0]);
631251881Speter                        *(dst++) = canonicalize_to_upper(digitz[1]);
632251881Speter                      }
633251881Speter                  }
634251881Speter                break;
635251881Speter              default:
636251881Speter                if (!svn_uri__char_validity[(unsigned char)*src])
637251881Speter                  {
638251881Speter                    apr_snprintf(dst, 4, "%%%02X", (unsigned char)*src);
639251881Speter                    dst += 3;
640251881Speter                  }
641251881Speter                else
642251881Speter                  *(dst++) = *src;
643251881Speter                break;
644251881Speter            }
645251881Speter          src++;
646251881Speter        }
647251881Speter      *dst = '\0';
648251881Speter    }
649251881Speter
650251881Speter  return canon;
651251881Speter}
652251881Speter
653251881Speter/* Return the string length of the longest common ancestor of PATH1 and PATH2.
654251881Speter * Pass type_uri for TYPE if PATH1 and PATH2 are URIs, and type_dirent if
655251881Speter * PATH1 and PATH2 are regular paths.
656251881Speter *
657251881Speter * If the two paths do not share a common ancestor, return 0.
658251881Speter *
659251881Speter * New strings are allocated in POOL.
660251881Speter */
661251881Speterstatic apr_size_t
662251881Speterget_longest_ancestor_length(path_type_t types,
663251881Speter                            const char *path1,
664251881Speter                            const char *path2,
665251881Speter                            apr_pool_t *pool)
666251881Speter{
667251881Speter  apr_size_t path1_len, path2_len;
668251881Speter  apr_size_t i = 0;
669251881Speter  apr_size_t last_dirsep = 0;
670251881Speter#ifdef SVN_USE_DOS_PATHS
671251881Speter  svn_boolean_t unc = FALSE;
672251881Speter#endif
673251881Speter
674251881Speter  path1_len = strlen(path1);
675251881Speter  path2_len = strlen(path2);
676251881Speter
677251881Speter  if (SVN_PATH_IS_EMPTY(path1) || SVN_PATH_IS_EMPTY(path2))
678251881Speter    return 0;
679251881Speter
680251881Speter  while (path1[i] == path2[i])
681251881Speter    {
682251881Speter      /* Keep track of the last directory separator we hit. */
683251881Speter      if (path1[i] == '/')
684251881Speter        last_dirsep = i;
685251881Speter
686251881Speter      i++;
687251881Speter
688251881Speter      /* If we get to the end of either path, break out. */
689251881Speter      if ((i == path1_len) || (i == path2_len))
690251881Speter        break;
691251881Speter    }
692251881Speter
693251881Speter  /* two special cases:
694251881Speter     1. '/' is the longest common ancestor of '/' and '/foo' */
695251881Speter  if (i == 1 && path1[0] == '/' && path2[0] == '/')
696251881Speter    return 1;
697251881Speter  /* 2. '' is the longest common ancestor of any non-matching
698251881Speter   * strings 'foo' and 'bar' */
699251881Speter  if (types == type_dirent && i == 0)
700251881Speter    return 0;
701251881Speter
702251881Speter  /* Handle some windows specific cases */
703251881Speter#ifdef SVN_USE_DOS_PATHS
704251881Speter  if (types == type_dirent)
705251881Speter    {
706251881Speter      /* don't count the '//' from UNC paths */
707251881Speter      if (last_dirsep == 1 && path1[0] == '/' && path1[1] == '/')
708251881Speter        {
709251881Speter          last_dirsep = 0;
710251881Speter          unc = TRUE;
711251881Speter        }
712251881Speter
713251881Speter      /* X:/ and X:/foo */
714251881Speter      if (i == 3 && path1[2] == '/' && path1[1] == ':')
715251881Speter        return i;
716251881Speter
717251881Speter      /* Cannot use SVN_ERR_ASSERT here, so we'll have to crash, sorry.
718251881Speter       * Note that this assertion triggers only if the code above has
719251881Speter       * been broken. The code below relies on this assertion, because
720251881Speter       * it uses [i - 1] as index. */
721251881Speter      assert(i > 0);
722251881Speter
723251881Speter      /* X: and X:/ */
724251881Speter      if ((path1[i - 1] == ':' && path2[i] == '/') ||
725251881Speter          (path2[i - 1] == ':' && path1[i] == '/'))
726251881Speter          return 0;
727251881Speter      /* X: and X:foo */
728251881Speter      if (path1[i - 1] == ':' || path2[i - 1] == ':')
729251881Speter          return i;
730251881Speter    }
731251881Speter#endif /* SVN_USE_DOS_PATHS */
732251881Speter
733251881Speter  /* last_dirsep is now the offset of the last directory separator we
734251881Speter     crossed before reaching a non-matching byte.  i is the offset of
735251881Speter     that non-matching byte, and is guaranteed to be <= the length of
736251881Speter     whichever path is shorter.
737251881Speter     If one of the paths is the common part return that. */
738251881Speter  if (((i == path1_len) && (path2[i] == '/'))
739251881Speter           || ((i == path2_len) && (path1[i] == '/'))
740251881Speter           || ((i == path1_len) && (i == path2_len)))
741251881Speter    return i;
742251881Speter  else
743251881Speter    {
744251881Speter      /* Nothing in common but the root folder '/' or 'X:/' for Windows
745251881Speter         dirents. */
746251881Speter#ifdef SVN_USE_DOS_PATHS
747251881Speter      if (! unc)
748251881Speter        {
749251881Speter          /* X:/foo and X:/bar returns X:/ */
750251881Speter          if ((types == type_dirent) &&
751251881Speter              last_dirsep == 2 && path1[1] == ':' && path1[2] == '/'
752251881Speter                               && path2[1] == ':' && path2[2] == '/')
753251881Speter            return 3;
754251881Speter#endif /* SVN_USE_DOS_PATHS */
755251881Speter          if (last_dirsep == 0 && path1[0] == '/' && path2[0] == '/')
756251881Speter            return 1;
757251881Speter#ifdef SVN_USE_DOS_PATHS
758251881Speter        }
759251881Speter#endif
760251881Speter    }
761251881Speter
762251881Speter  return last_dirsep;
763251881Speter}
764251881Speter
765251881Speter/* Determine whether PATH2 is a child of PATH1.
766251881Speter *
767251881Speter * PATH2 is a child of PATH1 if
768251881Speter * 1) PATH1 is empty, and PATH2 is not empty and not an absolute path.
769251881Speter * or
770251881Speter * 2) PATH2 is has n components, PATH1 has x < n components,
771251881Speter *    and PATH1 matches PATH2 in all its x components.
772251881Speter *    Components are separated by a slash, '/'.
773251881Speter *
774251881Speter * Pass type_uri for TYPE if PATH1 and PATH2 are URIs, and type_dirent if
775251881Speter * PATH1 and PATH2 are regular paths.
776251881Speter *
777251881Speter * If PATH2 is not a child of PATH1, return NULL.
778251881Speter *
779251881Speter * If PATH2 is a child of PATH1, and POOL is not NULL, allocate a copy
780251881Speter * of the child part of PATH2 in POOL and return a pointer to the
781251881Speter * newly allocated child part.
782251881Speter *
783251881Speter * If PATH2 is a child of PATH1, and POOL is NULL, return a pointer
784251881Speter * pointing to the child part of PATH2.
785251881Speter * */
786251881Speterstatic const char *
787251881Speteris_child(path_type_t type, const char *path1, const char *path2,
788251881Speter         apr_pool_t *pool)
789251881Speter{
790251881Speter  apr_size_t i;
791251881Speter
792251881Speter  /* Allow "" and "foo" or "H:foo" to be parent/child */
793251881Speter  if (SVN_PATH_IS_EMPTY(path1))               /* "" is the parent  */
794251881Speter    {
795251881Speter      if (SVN_PATH_IS_EMPTY(path2))            /* "" not a child    */
796251881Speter        return NULL;
797251881Speter
798251881Speter      /* check if this is an absolute path */
799251881Speter      if ((type == type_uri) ||
800251881Speter          (type == type_dirent && dirent_is_rooted(path2)))
801251881Speter        return NULL;
802251881Speter      else
803251881Speter        /* everything else is child */
804251881Speter        return pool ? apr_pstrdup(pool, path2) : path2;
805251881Speter    }
806251881Speter
807251881Speter  /* Reach the end of at least one of the paths.  How should we handle
808251881Speter     things like path1:"foo///bar" and path2:"foo/bar/baz"?  It doesn't
809251881Speter     appear to arise in the current Subversion code, it's not clear to me
810251881Speter     if they should be parent/child or not. */
811251881Speter  /* Hmmm... aren't paths assumed to be canonical in this function?
812251881Speter   * How can "foo///bar" even happen if the paths are canonical? */
813251881Speter  for (i = 0; path1[i] && path2[i]; i++)
814251881Speter    if (path1[i] != path2[i])
815251881Speter      return NULL;
816251881Speter
817251881Speter  /* FIXME: This comment does not really match
818251881Speter   * the checks made in the code it refers to: */
819251881Speter  /* There are two cases that are parent/child
820251881Speter          ...      path1[i] == '\0'
821251881Speter          .../foo  path2[i] == '/'
822251881Speter      or
823251881Speter          /        path1[i] == '\0'
824251881Speter          /foo     path2[i] != '/'
825251881Speter
826251881Speter     Other root paths (like X:/) fall under the former case:
827251881Speter          X:/        path1[i] == '\0'
828251881Speter          X:/foo     path2[i] != '/'
829251881Speter
830251881Speter     Check for '//' to avoid matching '/' and '//srv'.
831251881Speter  */
832251881Speter  if (path1[i] == '\0' && path2[i])
833251881Speter    {
834251881Speter      if (path1[i - 1] == '/'
835251881Speter#ifdef SVN_USE_DOS_PATHS
836251881Speter          || ((type == type_dirent) && path1[i - 1] == ':')
837251881Speter#endif
838251881Speter           )
839251881Speter        {
840251881Speter          if (path2[i] == '/')
841251881Speter            /* .../
842251881Speter             * ..../
843251881Speter             *     i   */
844251881Speter            return NULL;
845251881Speter          else
846251881Speter            /* .../
847251881Speter             * .../foo
848251881Speter             *     i    */
849251881Speter            return pool ? apr_pstrdup(pool, path2 + i) : path2 + i;
850251881Speter        }
851251881Speter      else if (path2[i] == '/')
852251881Speter        {
853251881Speter          if (path2[i + 1])
854251881Speter            /* ...
855251881Speter             * .../foo
856251881Speter             *    i    */
857251881Speter            return pool ? apr_pstrdup(pool, path2 + i + 1) : path2 + i + 1;
858251881Speter          else
859251881Speter            /* ...
860251881Speter             * .../
861251881Speter             *    i    */
862251881Speter            return NULL;
863251881Speter        }
864251881Speter    }
865251881Speter
866251881Speter  /* Otherwise, path2 isn't a child. */
867251881Speter  return NULL;
868251881Speter}
869251881Speter
870251881Speter
871251881Speter/**** Public API functions ****/
872251881Speter
873251881Speterconst char *
874251881Spetersvn_dirent_internal_style(const char *dirent, apr_pool_t *pool)
875251881Speter{
876251881Speter  return svn_dirent_canonicalize(internal_style(dirent, pool), pool);
877251881Speter}
878251881Speter
879251881Speterconst char *
880251881Spetersvn_dirent_local_style(const char *dirent, apr_pool_t *pool)
881251881Speter{
882251881Speter  /* Internally, Subversion represents the current directory with the
883251881Speter     empty string.  But users like to see "." . */
884251881Speter  if (SVN_PATH_IS_EMPTY(dirent))
885251881Speter    return ".";
886251881Speter
887251881Speter#if '/' != SVN_PATH_LOCAL_SEPARATOR
888251881Speter    {
889251881Speter      char *p = apr_pstrdup(pool, dirent);
890251881Speter      dirent = p;
891251881Speter
892251881Speter      /* Convert all canonical separators to the local-style ones. */
893251881Speter      for (; *p != '\0'; ++p)
894251881Speter        if (*p == '/')
895251881Speter          *p = SVN_PATH_LOCAL_SEPARATOR;
896251881Speter    }
897251881Speter#endif
898251881Speter
899251881Speter  return dirent;
900251881Speter}
901251881Speter
902251881Speterconst char *
903251881Spetersvn_relpath__internal_style(const char *relpath,
904251881Speter                            apr_pool_t *pool)
905251881Speter{
906251881Speter  return svn_relpath_canonicalize(internal_style(relpath, pool), pool);
907251881Speter}
908251881Speter
909251881Speter
910251881Speter/* We decided against using apr_filepath_root here because of the negative
911251881Speter   performance impact (creating a pool and converting strings ). */
912251881Spetersvn_boolean_t
913251881Spetersvn_dirent_is_root(const char *dirent, apr_size_t len)
914251881Speter{
915251881Speter#ifdef SVN_USE_DOS_PATHS
916251881Speter  /* On Windows and Cygwin, 'H:' or 'H:/' (where 'H' is any letter)
917251881Speter     are also root directories */
918251881Speter  if ((len == 2 || ((len == 3) && (dirent[2] == '/'))) &&
919251881Speter      (dirent[1] == ':') &&
920251881Speter      ((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
921251881Speter       (dirent[0] >= 'a' && dirent[0] <= 'z')))
922251881Speter    return TRUE;
923251881Speter
924251881Speter  /* On Windows and Cygwin //server/share is a root directory,
925251881Speter     and on Cygwin //drive is a drive alias */
926251881Speter  if (len >= 2 && dirent[0] == '/' && dirent[1] == '/'
927251881Speter      && dirent[len - 1] != '/')
928251881Speter    {
929251881Speter      int segments = 0;
930251881Speter      apr_size_t i;
931251881Speter      for (i = len; i >= 2; i--)
932251881Speter        {
933251881Speter          if (dirent[i] == '/')
934251881Speter            {
935251881Speter              segments ++;
936251881Speter              if (segments > 1)
937251881Speter                return FALSE;
938251881Speter            }
939251881Speter        }
940251881Speter#ifdef __CYGWIN__
941251881Speter      return (segments <= 1);
942251881Speter#else
943251881Speter      return (segments == 1); /* //drive is invalid on plain Windows */
944251881Speter#endif
945251881Speter    }
946251881Speter#endif
947251881Speter
948251881Speter  /* directory is root if it's equal to '/' */
949251881Speter  if (len == 1 && dirent[0] == '/')
950251881Speter    return TRUE;
951251881Speter
952251881Speter  return FALSE;
953251881Speter}
954251881Speter
955251881Spetersvn_boolean_t
956251881Spetersvn_uri_is_root(const char *uri, apr_size_t len)
957251881Speter{
958251881Speter  assert(svn_uri_is_canonical(uri, NULL));
959251881Speter  return (len == uri_schema_root_length(uri, len));
960251881Speter}
961251881Speter
962251881Speterchar *svn_dirent_join(const char *base,
963251881Speter                      const char *component,
964251881Speter                      apr_pool_t *pool)
965251881Speter{
966251881Speter  apr_size_t blen = strlen(base);
967251881Speter  apr_size_t clen = strlen(component);
968251881Speter  char *dirent;
969251881Speter  int add_separator;
970251881Speter
971251881Speter  assert(svn_dirent_is_canonical(base, pool));
972251881Speter  assert(svn_dirent_is_canonical(component, pool));
973251881Speter
974251881Speter  /* If the component is absolute, then return it.  */
975251881Speter  if (svn_dirent_is_absolute(component))
976251881Speter    return apr_pmemdup(pool, component, clen + 1);
977251881Speter
978251881Speter  /* If either is empty return the other */
979251881Speter  if (SVN_PATH_IS_EMPTY(base))
980251881Speter    return apr_pmemdup(pool, component, clen + 1);
981251881Speter  if (SVN_PATH_IS_EMPTY(component))
982251881Speter    return apr_pmemdup(pool, base, blen + 1);
983251881Speter
984251881Speter#ifdef SVN_USE_DOS_PATHS
985251881Speter  if (component[0] == '/')
986251881Speter    {
987251881Speter      /* '/' is drive relative on Windows, not absolute like on Posix */
988251881Speter      if (dirent_is_rooted(base))
989251881Speter        {
990251881Speter          /* Join component without '/' to root-of(base) */
991251881Speter          blen = dirent_root_length(base, blen);
992251881Speter          component++;
993251881Speter          clen--;
994251881Speter
995251881Speter          if (blen == 2 && base[1] == ':') /* "C:" case */
996251881Speter            {
997251881Speter              char *root = apr_pmemdup(pool, base, 3);
998251881Speter              root[2] = '/'; /* We don't need the final '\0' */
999251881Speter
1000251881Speter              base = root;
1001251881Speter              blen = 3;
1002251881Speter            }
1003251881Speter
1004251881Speter          if (clen == 0)
1005251881Speter            return apr_pstrndup(pool, base, blen);
1006251881Speter        }
1007251881Speter      else
1008251881Speter        return apr_pmemdup(pool, component, clen + 1);
1009251881Speter    }
1010251881Speter  else if (dirent_is_rooted(component))
1011251881Speter    return apr_pmemdup(pool, component, clen + 1);
1012251881Speter#endif /* SVN_USE_DOS_PATHS */
1013251881Speter
1014251881Speter  /* if last character of base is already a separator, don't add a '/' */
1015251881Speter  add_separator = 1;
1016251881Speter  if (base[blen - 1] == '/'
1017251881Speter#ifdef SVN_USE_DOS_PATHS
1018251881Speter       || base[blen - 1] == ':'
1019251881Speter#endif
1020251881Speter        )
1021251881Speter          add_separator = 0;
1022251881Speter
1023251881Speter  /* Construct the new, combined dirent. */
1024251881Speter  dirent = apr_palloc(pool, blen + add_separator + clen + 1);
1025251881Speter  memcpy(dirent, base, blen);
1026251881Speter  if (add_separator)
1027251881Speter    dirent[blen] = '/';
1028251881Speter  memcpy(dirent + blen + add_separator, component, clen + 1);
1029251881Speter
1030251881Speter  return dirent;
1031251881Speter}
1032251881Speter
1033251881Speterchar *svn_dirent_join_many(apr_pool_t *pool, const char *base, ...)
1034251881Speter{
1035251881Speter#define MAX_SAVED_LENGTHS 10
1036251881Speter  apr_size_t saved_lengths[MAX_SAVED_LENGTHS];
1037251881Speter  apr_size_t total_len;
1038251881Speter  int nargs;
1039251881Speter  va_list va;
1040251881Speter  const char *s;
1041251881Speter  apr_size_t len;
1042251881Speter  char *dirent;
1043251881Speter  char *p;
1044251881Speter  int add_separator;
1045251881Speter  int base_arg = 0;
1046251881Speter
1047251881Speter  total_len = strlen(base);
1048251881Speter
1049251881Speter  assert(svn_dirent_is_canonical(base, pool));
1050251881Speter
1051251881Speter  /* if last character of base is already a separator, don't add a '/' */
1052251881Speter  add_separator = 1;
1053251881Speter  if (total_len == 0
1054251881Speter       || base[total_len - 1] == '/'
1055251881Speter#ifdef SVN_USE_DOS_PATHS
1056251881Speter       || base[total_len - 1] == ':'
1057251881Speter#endif
1058251881Speter        )
1059251881Speter          add_separator = 0;
1060251881Speter
1061251881Speter  saved_lengths[0] = total_len;
1062251881Speter
1063251881Speter  /* Compute the length of the resulting string. */
1064251881Speter
1065251881Speter  nargs = 0;
1066251881Speter  va_start(va, base);
1067251881Speter  while ((s = va_arg(va, const char *)) != NULL)
1068251881Speter    {
1069251881Speter      len = strlen(s);
1070251881Speter
1071251881Speter      assert(svn_dirent_is_canonical(s, pool));
1072251881Speter
1073251881Speter      if (SVN_PATH_IS_EMPTY(s))
1074251881Speter        continue;
1075251881Speter
1076251881Speter      if (nargs++ < MAX_SAVED_LENGTHS)
1077251881Speter        saved_lengths[nargs] = len;
1078251881Speter
1079251881Speter      if (dirent_is_rooted(s))
1080251881Speter        {
1081251881Speter          total_len = len;
1082251881Speter          base_arg = nargs;
1083251881Speter
1084251881Speter#ifdef SVN_USE_DOS_PATHS
1085251881Speter          if (!svn_dirent_is_absolute(s)) /* Handle non absolute roots */
1086251881Speter            {
1087251881Speter              /* Set new base and skip the current argument */
1088251881Speter              base = s = svn_dirent_join(base, s, pool);
1089251881Speter              base_arg++;
1090251881Speter              saved_lengths[0] = total_len = len = strlen(s);
1091251881Speter            }
1092251881Speter          else
1093251881Speter#endif /* SVN_USE_DOS_PATHS */
1094251881Speter            {
1095251881Speter              base = ""; /* Don't add base */
1096251881Speter              saved_lengths[0] = 0;
1097251881Speter            }
1098251881Speter
1099251881Speter          add_separator = 1;
1100251881Speter          if (s[len - 1] == '/'
1101251881Speter#ifdef SVN_USE_DOS_PATHS
1102251881Speter             || s[len - 1] == ':'
1103251881Speter#endif
1104251881Speter              )
1105251881Speter             add_separator = 0;
1106251881Speter        }
1107251881Speter      else if (nargs <= base_arg + 1)
1108251881Speter        {
1109251881Speter          total_len += add_separator + len;
1110251881Speter        }
1111251881Speter      else
1112251881Speter        {
1113251881Speter          total_len += 1 + len;
1114251881Speter        }
1115251881Speter    }
1116251881Speter  va_end(va);
1117251881Speter
1118251881Speter  /* base == "/" and no further components. just return that. */
1119251881Speter  if (add_separator == 0 && total_len == 1)
1120251881Speter    return apr_pmemdup(pool, "/", 2);
1121251881Speter
1122251881Speter  /* we got the total size. allocate it, with room for a NULL character. */
1123251881Speter  dirent = p = apr_palloc(pool, total_len + 1);
1124251881Speter
1125251881Speter  /* if we aren't supposed to skip forward to an absolute component, and if
1126251881Speter     this is not an empty base that we are skipping, then copy the base
1127251881Speter     into the output. */
1128251881Speter  if (! SVN_PATH_IS_EMPTY(base))
1129251881Speter    {
1130251881Speter      memcpy(p, base, len = saved_lengths[0]);
1131251881Speter      p += len;
1132251881Speter    }
1133251881Speter
1134251881Speter  nargs = 0;
1135251881Speter  va_start(va, base);
1136251881Speter  while ((s = va_arg(va, const char *)) != NULL)
1137251881Speter    {
1138251881Speter      if (SVN_PATH_IS_EMPTY(s))
1139251881Speter        continue;
1140251881Speter
1141251881Speter      if (++nargs < base_arg)
1142251881Speter        continue;
1143251881Speter
1144251881Speter      if (nargs < MAX_SAVED_LENGTHS)
1145251881Speter        len = saved_lengths[nargs];
1146251881Speter      else
1147251881Speter        len = strlen(s);
1148251881Speter
1149251881Speter      /* insert a separator if we aren't copying in the first component
1150251881Speter         (which can happen when base_arg is set). also, don't put in a slash
1151251881Speter         if the prior character is a slash (occurs when prior component
1152251881Speter         is "/"). */
1153251881Speter      if (p != dirent &&
1154251881Speter          ( ! (nargs - 1 <= base_arg) || add_separator))
1155251881Speter        *p++ = '/';
1156251881Speter
1157251881Speter      /* copy the new component and advance the pointer */
1158251881Speter      memcpy(p, s, len);
1159251881Speter      p += len;
1160251881Speter    }
1161251881Speter  va_end(va);
1162251881Speter
1163251881Speter  *p = '\0';
1164251881Speter  assert((apr_size_t)(p - dirent) == total_len);
1165251881Speter
1166251881Speter  return dirent;
1167251881Speter}
1168251881Speter
1169251881Speterchar *
1170251881Spetersvn_relpath_join(const char *base,
1171251881Speter                 const char *component,
1172251881Speter                 apr_pool_t *pool)
1173251881Speter{
1174251881Speter  apr_size_t blen = strlen(base);
1175251881Speter  apr_size_t clen = strlen(component);
1176251881Speter  char *path;
1177251881Speter
1178251881Speter  assert(relpath_is_canonical(base));
1179251881Speter  assert(relpath_is_canonical(component));
1180251881Speter
1181251881Speter  /* If either is empty return the other */
1182251881Speter  if (blen == 0)
1183251881Speter    return apr_pmemdup(pool, component, clen + 1);
1184251881Speter  if (clen == 0)
1185251881Speter    return apr_pmemdup(pool, base, blen + 1);
1186251881Speter
1187251881Speter  path = apr_palloc(pool, blen + 1 + clen + 1);
1188251881Speter  memcpy(path, base, blen);
1189251881Speter  path[blen] = '/';
1190251881Speter  memcpy(path + blen + 1, component, clen + 1);
1191251881Speter
1192251881Speter  return path;
1193251881Speter}
1194251881Speter
1195251881Speterchar *
1196251881Spetersvn_dirent_dirname(const char *dirent, apr_pool_t *pool)
1197251881Speter{
1198251881Speter  apr_size_t len = strlen(dirent);
1199251881Speter
1200251881Speter  assert(svn_dirent_is_canonical(dirent, pool));
1201251881Speter
1202251881Speter  if (len == dirent_root_length(dirent, len))
1203251881Speter    return apr_pstrmemdup(pool, dirent, len);
1204251881Speter  else
1205251881Speter    return apr_pstrmemdup(pool, dirent, dirent_previous_segment(dirent, len));
1206251881Speter}
1207251881Speter
1208251881Speterconst char *
1209251881Spetersvn_dirent_basename(const char *dirent, apr_pool_t *pool)
1210251881Speter{
1211251881Speter  apr_size_t len = strlen(dirent);
1212251881Speter  apr_size_t start;
1213251881Speter
1214251881Speter  assert(!pool || svn_dirent_is_canonical(dirent, pool));
1215251881Speter
1216251881Speter  if (svn_dirent_is_root(dirent, len))
1217251881Speter    return "";
1218251881Speter  else
1219251881Speter    {
1220251881Speter      start = len;
1221251881Speter      while (start > 0 && dirent[start - 1] != '/'
1222251881Speter#ifdef SVN_USE_DOS_PATHS
1223251881Speter             && dirent[start - 1] != ':'
1224251881Speter#endif
1225251881Speter            )
1226251881Speter        --start;
1227251881Speter    }
1228251881Speter
1229251881Speter  if (pool)
1230251881Speter    return apr_pstrmemdup(pool, dirent + start, len - start);
1231251881Speter  else
1232251881Speter    return dirent + start;
1233251881Speter}
1234251881Speter
1235251881Spetervoid
1236251881Spetersvn_dirent_split(const char **dirpath,
1237251881Speter                 const char **base_name,
1238251881Speter                 const char *dirent,
1239251881Speter                 apr_pool_t *pool)
1240251881Speter{
1241251881Speter  assert(dirpath != base_name);
1242251881Speter
1243251881Speter  if (dirpath)
1244251881Speter    *dirpath = svn_dirent_dirname(dirent, pool);
1245251881Speter
1246251881Speter  if (base_name)
1247251881Speter    *base_name = svn_dirent_basename(dirent, pool);
1248251881Speter}
1249251881Speter
1250251881Speterchar *
1251251881Spetersvn_relpath_dirname(const char *relpath,
1252251881Speter                    apr_pool_t *pool)
1253251881Speter{
1254251881Speter  apr_size_t len = strlen(relpath);
1255251881Speter
1256251881Speter  assert(relpath_is_canonical(relpath));
1257251881Speter
1258251881Speter  return apr_pstrmemdup(pool, relpath,
1259251881Speter                        relpath_previous_segment(relpath, len));
1260251881Speter}
1261251881Speter
1262251881Speterconst char *
1263251881Spetersvn_relpath_basename(const char *relpath,
1264251881Speter                     apr_pool_t *pool)
1265251881Speter{
1266251881Speter  apr_size_t len = strlen(relpath);
1267251881Speter  apr_size_t start;
1268251881Speter
1269251881Speter  assert(relpath_is_canonical(relpath));
1270251881Speter
1271251881Speter  start = len;
1272251881Speter  while (start > 0 && relpath[start - 1] != '/')
1273251881Speter    --start;
1274251881Speter
1275251881Speter  if (pool)
1276251881Speter    return apr_pstrmemdup(pool, relpath + start, len - start);
1277251881Speter  else
1278251881Speter    return relpath + start;
1279251881Speter}
1280251881Speter
1281251881Spetervoid
1282251881Spetersvn_relpath_split(const char **dirpath,
1283251881Speter                  const char **base_name,
1284251881Speter                  const char *relpath,
1285251881Speter                  apr_pool_t *pool)
1286251881Speter{
1287251881Speter  assert(dirpath != base_name);
1288251881Speter
1289251881Speter  if (dirpath)
1290251881Speter    *dirpath = svn_relpath_dirname(relpath, pool);
1291251881Speter
1292251881Speter  if (base_name)
1293251881Speter    *base_name = svn_relpath_basename(relpath, pool);
1294251881Speter}
1295251881Speter
1296251881Speterchar *
1297251881Spetersvn_uri_dirname(const char *uri, apr_pool_t *pool)
1298251881Speter{
1299251881Speter  apr_size_t len = strlen(uri);
1300251881Speter
1301251881Speter  assert(svn_uri_is_canonical(uri, pool));
1302251881Speter
1303251881Speter  if (svn_uri_is_root(uri, len))
1304251881Speter    return apr_pstrmemdup(pool, uri, len);
1305251881Speter  else
1306251881Speter    return apr_pstrmemdup(pool, uri, uri_previous_segment(uri, len));
1307251881Speter}
1308251881Speter
1309251881Speterconst char *
1310251881Spetersvn_uri_basename(const char *uri, apr_pool_t *pool)
1311251881Speter{
1312251881Speter  apr_size_t len = strlen(uri);
1313251881Speter  apr_size_t start;
1314251881Speter
1315251881Speter  assert(svn_uri_is_canonical(uri, NULL));
1316251881Speter
1317251881Speter  if (svn_uri_is_root(uri, len))
1318251881Speter    return "";
1319251881Speter
1320251881Speter  start = len;
1321251881Speter  while (start > 0 && uri[start - 1] != '/')
1322251881Speter    --start;
1323251881Speter
1324251881Speter  return svn_path_uri_decode(uri + start, pool);
1325251881Speter}
1326251881Speter
1327251881Spetervoid
1328251881Spetersvn_uri_split(const char **dirpath,
1329251881Speter              const char **base_name,
1330251881Speter              const char *uri,
1331251881Speter              apr_pool_t *pool)
1332251881Speter{
1333251881Speter  assert(dirpath != base_name);
1334251881Speter
1335251881Speter  if (dirpath)
1336251881Speter    *dirpath = svn_uri_dirname(uri, pool);
1337251881Speter
1338251881Speter  if (base_name)
1339251881Speter    *base_name = svn_uri_basename(uri, pool);
1340251881Speter}
1341251881Speter
1342251881Speterchar *
1343251881Spetersvn_dirent_get_longest_ancestor(const char *dirent1,
1344251881Speter                                const char *dirent2,
1345251881Speter                                apr_pool_t *pool)
1346251881Speter{
1347251881Speter  return apr_pstrndup(pool, dirent1,
1348251881Speter                      get_longest_ancestor_length(type_dirent, dirent1,
1349251881Speter                                                  dirent2, pool));
1350251881Speter}
1351251881Speter
1352251881Speterchar *
1353251881Spetersvn_relpath_get_longest_ancestor(const char *relpath1,
1354251881Speter                                 const char *relpath2,
1355251881Speter                                 apr_pool_t *pool)
1356251881Speter{
1357251881Speter  assert(relpath_is_canonical(relpath1));
1358251881Speter  assert(relpath_is_canonical(relpath2));
1359251881Speter
1360251881Speter  return apr_pstrndup(pool, relpath1,
1361251881Speter                      get_longest_ancestor_length(type_relpath, relpath1,
1362251881Speter                                                  relpath2, pool));
1363251881Speter}
1364251881Speter
1365251881Speterchar *
1366251881Spetersvn_uri_get_longest_ancestor(const char *uri1,
1367251881Speter                             const char *uri2,
1368251881Speter                             apr_pool_t *pool)
1369251881Speter{
1370251881Speter  apr_size_t uri_ancestor_len;
1371251881Speter  apr_size_t i = 0;
1372251881Speter
1373251881Speter  assert(svn_uri_is_canonical(uri1, NULL));
1374251881Speter  assert(svn_uri_is_canonical(uri2, NULL));
1375251881Speter
1376251881Speter  /* Find ':' */
1377251881Speter  while (1)
1378251881Speter    {
1379251881Speter      /* No shared protocol => no common prefix */
1380251881Speter      if (uri1[i] != uri2[i])
1381251881Speter        return apr_pmemdup(pool, SVN_EMPTY_PATH,
1382251881Speter                           sizeof(SVN_EMPTY_PATH));
1383251881Speter
1384251881Speter      if (uri1[i] == ':')
1385251881Speter        break;
1386251881Speter
1387251881Speter      /* They're both URLs, so EOS can't come before ':' */
1388251881Speter      assert((uri1[i] != '\0') && (uri2[i] != '\0'));
1389251881Speter
1390251881Speter      i++;
1391251881Speter    }
1392251881Speter
1393251881Speter  i += 3;  /* Advance past '://' */
1394251881Speter
1395251881Speter  uri_ancestor_len = get_longest_ancestor_length(type_uri, uri1 + i,
1396251881Speter                                                 uri2 + i, pool);
1397251881Speter
1398251881Speter  if (uri_ancestor_len == 0 ||
1399251881Speter      (uri_ancestor_len == 1 && (uri1 + i)[0] == '/'))
1400251881Speter    return apr_pmemdup(pool, SVN_EMPTY_PATH, sizeof(SVN_EMPTY_PATH));
1401251881Speter  else
1402251881Speter    return apr_pstrndup(pool, uri1, uri_ancestor_len + i);
1403251881Speter}
1404251881Speter
1405251881Speterconst char *
1406251881Spetersvn_dirent_is_child(const char *parent_dirent,
1407251881Speter                    const char *child_dirent,
1408251881Speter                    apr_pool_t *pool)
1409251881Speter{
1410251881Speter  return is_child(type_dirent, parent_dirent, child_dirent, pool);
1411251881Speter}
1412251881Speter
1413251881Speterconst char *
1414251881Spetersvn_dirent_skip_ancestor(const char *parent_dirent,
1415251881Speter                         const char *child_dirent)
1416251881Speter{
1417251881Speter  apr_size_t len = strlen(parent_dirent);
1418251881Speter  apr_size_t root_len;
1419251881Speter
1420251881Speter  if (0 != strncmp(parent_dirent, child_dirent, len))
1421251881Speter    return NULL; /* parent_dirent is no ancestor of child_dirent */
1422251881Speter
1423251881Speter  if (child_dirent[len] == 0)
1424251881Speter    return ""; /* parent_dirent == child_dirent */
1425251881Speter
1426251881Speter  /* Child == parent + more-characters */
1427251881Speter
1428251881Speter  root_len = dirent_root_length(child_dirent, strlen(child_dirent));
1429251881Speter  if (root_len > len)
1430251881Speter    /* Different root, e.g. ("" "/...") or ("//z" "//z/share") */
1431251881Speter    return NULL;
1432251881Speter
1433251881Speter  /* Now, child == [root-of-parent] + [rest-of-parent] + more-characters.
1434251881Speter   * It must be one of the following forms.
1435251881Speter   *
1436251881Speter   * rlen parent    child       bad?  rlen=len? c[len]=/?
1437251881Speter   *  0   ""        "foo"               *
1438251881Speter   *  0   "b"       "bad"         !
1439251881Speter   *  0   "b"       "b/foo"                       *
1440251881Speter   *  1   "/"       "/foo"              *
1441251881Speter   *  1   "/b"      "/bad"        !
1442251881Speter   *  1   "/b"      "/b/foo"                      *
1443251881Speter   *  2   "a:"      "a:foo"             *
1444251881Speter   *  2   "a:b"     "a:bad"       !
1445251881Speter   *  2   "a:b"     "a:b/foo"                     *
1446251881Speter   *  3   "a:/"     "a:/foo"            *
1447251881Speter   *  3   "a:/b"    "a:/bad"      !
1448251881Speter   *  3   "a:/b"    "a:/b/foo"                    *
1449251881Speter   *  5   "//s/s"   "//s/s/foo"         *         *
1450251881Speter   *  5   "//s/s/b" "//s/s/bad"   !
1451251881Speter   *  5   "//s/s/b" "//s/s/b/foo"                 *
1452251881Speter   */
1453251881Speter
1454251881Speter  if (child_dirent[len] == '/')
1455251881Speter    /* "parent|child" is one of:
1456251881Speter     * "[a:]b|/foo" "[a:]/b|/foo" "//s/s|/foo" "//s/s/b|/foo" */
1457251881Speter    return child_dirent + len + 1;
1458251881Speter
1459251881Speter  if (root_len == len)
1460251881Speter    /* "parent|child" is "|foo" "/|foo" "a:|foo" "a:/|foo" "//s/s|/foo" */
1461251881Speter    return child_dirent + len;
1462251881Speter
1463251881Speter  return NULL;
1464251881Speter}
1465251881Speter
1466251881Speterconst char *
1467251881Spetersvn_relpath_skip_ancestor(const char *parent_relpath,
1468251881Speter                          const char *child_relpath)
1469251881Speter{
1470251881Speter  apr_size_t len = strlen(parent_relpath);
1471251881Speter
1472251881Speter  assert(relpath_is_canonical(parent_relpath));
1473251881Speter  assert(relpath_is_canonical(child_relpath));
1474251881Speter
1475251881Speter  if (len == 0)
1476251881Speter    return child_relpath;
1477251881Speter
1478251881Speter  if (0 != strncmp(parent_relpath, child_relpath, len))
1479251881Speter    return NULL; /* parent_relpath is no ancestor of child_relpath */
1480251881Speter
1481251881Speter  if (child_relpath[len] == 0)
1482251881Speter    return ""; /* parent_relpath == child_relpath */
1483251881Speter
1484251881Speter  if (child_relpath[len] == '/')
1485251881Speter    return child_relpath + len + 1;
1486251881Speter
1487251881Speter  return NULL;
1488251881Speter}
1489251881Speter
1490251881Speter
1491251881Speter/* */
1492251881Speterstatic const char *
1493251881Speteruri_skip_ancestor(const char *parent_uri,
1494251881Speter                  const char *child_uri)
1495251881Speter{
1496251881Speter  apr_size_t len = strlen(parent_uri);
1497251881Speter
1498251881Speter  assert(svn_uri_is_canonical(parent_uri, NULL));
1499251881Speter  assert(svn_uri_is_canonical(child_uri, NULL));
1500251881Speter
1501251881Speter  if (0 != strncmp(parent_uri, child_uri, len))
1502251881Speter    return NULL; /* parent_uri is no ancestor of child_uri */
1503251881Speter
1504251881Speter  if (child_uri[len] == 0)
1505251881Speter    return ""; /* parent_uri == child_uri */
1506251881Speter
1507251881Speter  if (child_uri[len] == '/')
1508251881Speter    return child_uri + len + 1;
1509251881Speter
1510251881Speter  return NULL;
1511251881Speter}
1512251881Speter
1513251881Speterconst char *
1514251881Spetersvn_uri_skip_ancestor(const char *parent_uri,
1515251881Speter                      const char *child_uri,
1516251881Speter                      apr_pool_t *result_pool)
1517251881Speter{
1518251881Speter  const char *result = uri_skip_ancestor(parent_uri, child_uri);
1519251881Speter
1520251881Speter  return result ? svn_path_uri_decode(result, result_pool) : NULL;
1521251881Speter}
1522251881Speter
1523251881Spetersvn_boolean_t
1524251881Spetersvn_dirent_is_ancestor(const char *parent_dirent, const char *child_dirent)
1525251881Speter{
1526251881Speter  return svn_dirent_skip_ancestor(parent_dirent, child_dirent) != NULL;
1527251881Speter}
1528251881Speter
1529251881Spetersvn_boolean_t
1530251881Spetersvn_uri__is_ancestor(const char *parent_uri, const char *child_uri)
1531251881Speter{
1532251881Speter  return uri_skip_ancestor(parent_uri, child_uri) != NULL;
1533251881Speter}
1534251881Speter
1535251881Speter
1536251881Spetersvn_boolean_t
1537251881Spetersvn_dirent_is_absolute(const char *dirent)
1538251881Speter{
1539251881Speter  if (! dirent)
1540251881Speter    return FALSE;
1541251881Speter
1542251881Speter  /* dirent is absolute if it starts with '/' on non-Windows platforms
1543251881Speter     or with '//' on Windows platforms */
1544251881Speter  if (dirent[0] == '/'
1545251881Speter#ifdef SVN_USE_DOS_PATHS
1546251881Speter      && dirent[1] == '/' /* Single '/' depends on current drive */
1547251881Speter#endif
1548251881Speter      )
1549251881Speter    return TRUE;
1550251881Speter
1551251881Speter  /* On Windows, dirent is also absolute when it starts with 'H:/'
1552251881Speter     where 'H' is any letter. */
1553251881Speter#ifdef SVN_USE_DOS_PATHS
1554251881Speter  if (((dirent[0] >= 'A' && dirent[0] <= 'Z')) &&
1555251881Speter      (dirent[1] == ':') && (dirent[2] == '/'))
1556251881Speter     return TRUE;
1557251881Speter#endif /* SVN_USE_DOS_PATHS */
1558251881Speter
1559251881Speter  return FALSE;
1560251881Speter}
1561251881Speter
1562251881Spetersvn_error_t *
1563251881Spetersvn_dirent_get_absolute(const char **pabsolute,
1564251881Speter                        const char *relative,
1565251881Speter                        apr_pool_t *pool)
1566251881Speter{
1567251881Speter  char *buffer;
1568251881Speter  apr_status_t apr_err;
1569251881Speter  const char *path_apr;
1570251881Speter
1571251881Speter  SVN_ERR_ASSERT(! svn_path_is_url(relative));
1572251881Speter
1573251881Speter  /* Merge the current working directory with the relative dirent. */
1574251881Speter  SVN_ERR(svn_path_cstring_from_utf8(&path_apr, relative, pool));
1575251881Speter
1576251881Speter  apr_err = apr_filepath_merge(&buffer, NULL,
1577251881Speter                               path_apr,
1578251881Speter                               APR_FILEPATH_NOTRELATIVE,
1579251881Speter                               pool);
1580251881Speter  if (apr_err)
1581251881Speter    {
1582251881Speter      /* In some cases when the passed path or its ancestor(s) do not exist
1583251881Speter         or no longer exist apr returns an error.
1584251881Speter
1585251881Speter         In many of these cases we would like to return a path anyway, when the
1586251881Speter         passed path was already a safe absolute path. So check for that now to
1587251881Speter         avoid an error.
1588251881Speter
1589251881Speter         svn_dirent_is_absolute() doesn't perform the necessary checks to see
1590251881Speter         if the path doesn't need post processing to be in the canonical absolute
1591251881Speter         format.
1592251881Speter         */
1593251881Speter
1594251881Speter      if (svn_dirent_is_absolute(relative)
1595251881Speter          && svn_dirent_is_canonical(relative, pool)
1596251881Speter          && !svn_path_is_backpath_present(relative))
1597251881Speter        {
1598251881Speter          *pabsolute = apr_pstrdup(pool, relative);
1599251881Speter          return SVN_NO_ERROR;
1600251881Speter        }
1601251881Speter
1602251881Speter      return svn_error_createf(SVN_ERR_BAD_FILENAME,
1603251881Speter                               svn_error_create(apr_err, NULL, NULL),
1604251881Speter                               _("Couldn't determine absolute path of '%s'"),
1605251881Speter                               svn_dirent_local_style(relative, pool));
1606251881Speter    }
1607251881Speter
1608251881Speter  SVN_ERR(svn_path_cstring_to_utf8(pabsolute, buffer, pool));
1609251881Speter  *pabsolute = svn_dirent_canonicalize(*pabsolute, pool);
1610251881Speter  return SVN_NO_ERROR;
1611251881Speter}
1612251881Speter
1613251881Speterconst char *
1614251881Spetersvn_uri_canonicalize(const char *uri, apr_pool_t *pool)
1615251881Speter{
1616251881Speter  return canonicalize(type_uri, uri, pool);
1617251881Speter}
1618251881Speter
1619251881Speterconst char *
1620251881Spetersvn_relpath_canonicalize(const char *relpath, apr_pool_t *pool)
1621251881Speter{
1622251881Speter  return canonicalize(type_relpath, relpath, pool);
1623251881Speter}
1624251881Speter
1625251881Speterconst char *
1626251881Spetersvn_dirent_canonicalize(const char *dirent, apr_pool_t *pool)
1627251881Speter{
1628251881Speter  const char *dst = canonicalize(type_dirent, dirent, pool);
1629251881Speter
1630251881Speter#ifdef SVN_USE_DOS_PATHS
1631251881Speter  /* Handle a specific case on Windows where path == "X:/". Here we have to
1632251881Speter     append the final '/', as svn_path_canonicalize will chop this of. */
1633251881Speter  if (((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
1634251881Speter        (dirent[0] >= 'a' && dirent[0] <= 'z')) &&
1635251881Speter        dirent[1] == ':' && dirent[2] == '/' &&
1636251881Speter        dst[3] == '\0')
1637251881Speter    {
1638251881Speter      char *dst_slash = apr_pcalloc(pool, 4);
1639251881Speter      dst_slash[0] = canonicalize_to_upper(dirent[0]);
1640251881Speter      dst_slash[1] = ':';
1641251881Speter      dst_slash[2] = '/';
1642251881Speter      dst_slash[3] = '\0';
1643251881Speter
1644251881Speter      return dst_slash;
1645251881Speter    }
1646251881Speter#endif /* SVN_USE_DOS_PATHS */
1647251881Speter
1648251881Speter  return dst;
1649251881Speter}
1650251881Speter
1651251881Spetersvn_boolean_t
1652251881Spetersvn_dirent_is_canonical(const char *dirent, apr_pool_t *scratch_pool)
1653251881Speter{
1654251881Speter  const char *ptr = dirent;
1655251881Speter  if (*ptr == '/')
1656251881Speter    {
1657251881Speter      ptr++;
1658251881Speter#ifdef SVN_USE_DOS_PATHS
1659251881Speter      /* Check for UNC paths */
1660251881Speter      if (*ptr == '/')
1661251881Speter        {
1662251881Speter          /* TODO: Scan hostname and sharename and fall back to part code */
1663251881Speter
1664251881Speter          /* ### Fall back to old implementation */
1665251881Speter          return (strcmp(dirent, svn_dirent_canonicalize(dirent, scratch_pool))
1666251881Speter                  == 0);
1667251881Speter        }
1668251881Speter#endif /* SVN_USE_DOS_PATHS */
1669251881Speter    }
1670251881Speter#ifdef SVN_USE_DOS_PATHS
1671251881Speter  else if (((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= 'A' && *ptr <= 'Z')) &&
1672251881Speter           (ptr[1] == ':'))
1673251881Speter    {
1674251881Speter      /* The only canonical drive names are "A:"..."Z:", no lower case */
1675251881Speter      if (*ptr < 'A' || *ptr > 'Z')
1676251881Speter        return FALSE;
1677251881Speter
1678251881Speter      ptr += 2;
1679251881Speter
1680251881Speter      if (*ptr == '/')
1681251881Speter        ptr++;
1682251881Speter    }
1683251881Speter#endif /* SVN_USE_DOS_PATHS */
1684251881Speter
1685251881Speter  return relpath_is_canonical(ptr);
1686251881Speter}
1687251881Speter
1688251881Speterstatic svn_boolean_t
1689251881Speterrelpath_is_canonical(const char *relpath)
1690251881Speter{
1691251881Speter  const char *ptr = relpath, *seg = relpath;
1692251881Speter
1693251881Speter  /* RELPATH is canonical if it has:
1694251881Speter   *  - no '.' segments
1695251881Speter   *  - no start and closing '/'
1696251881Speter   *  - no '//'
1697251881Speter   */
1698251881Speter
1699251881Speter  if (*relpath == '\0')
1700251881Speter    return TRUE;
1701251881Speter
1702251881Speter  if (*ptr == '/')
1703251881Speter    return FALSE;
1704251881Speter
1705251881Speter  /* Now validate the rest of the path. */
1706251881Speter  while(1)
1707251881Speter    {
1708251881Speter      apr_size_t seglen = ptr - seg;
1709251881Speter
1710251881Speter      if (seglen == 1 && *seg == '.')
1711251881Speter        return FALSE;  /*  /./   */
1712251881Speter
1713251881Speter      if (*ptr == '/' && *(ptr+1) == '/')
1714251881Speter        return FALSE;  /*  //    */
1715251881Speter
1716251881Speter      if (! *ptr && *(ptr - 1) == '/')
1717251881Speter        return FALSE;  /* foo/  */
1718251881Speter
1719251881Speter      if (! *ptr)
1720251881Speter        break;
1721251881Speter
1722251881Speter      if (*ptr == '/')
1723251881Speter        ptr++;
1724251881Speter      seg = ptr;
1725251881Speter
1726251881Speter      while (*ptr && (*ptr != '/'))
1727251881Speter        ptr++;
1728251881Speter    }
1729251881Speter
1730251881Speter  return TRUE;
1731251881Speter}
1732251881Speter
1733251881Spetersvn_boolean_t
1734251881Spetersvn_relpath_is_canonical(const char *relpath)
1735251881Speter{
1736251881Speter  return relpath_is_canonical(relpath);
1737251881Speter}
1738251881Speter
1739251881Spetersvn_boolean_t
1740251881Spetersvn_uri_is_canonical(const char *uri, apr_pool_t *scratch_pool)
1741251881Speter{
1742251881Speter  const char *ptr = uri, *seg = uri;
1743251881Speter  const char *schema_data = NULL;
1744251881Speter
1745251881Speter  /* URI is canonical if it has:
1746251881Speter   *  - lowercase URL scheme
1747251881Speter   *  - lowercase URL hostname
1748251881Speter   *  - no '.' segments
1749251881Speter   *  - no closing '/'
1750251881Speter   *  - no '//'
1751251881Speter   *  - uppercase hex-encoded pair digits ("%AB", not "%ab")
1752251881Speter   */
1753251881Speter
1754251881Speter  if (*uri == '\0')
1755251881Speter    return FALSE;
1756251881Speter
1757251881Speter  if (! svn_path_is_url(uri))
1758251881Speter    return FALSE;
1759251881Speter
1760251881Speter  /* Skip the scheme. */
1761251881Speter  while (*ptr && (*ptr != '/') && (*ptr != ':'))
1762251881Speter    ptr++;
1763251881Speter
1764251881Speter  /* No scheme?  No good. */
1765251881Speter  if (! (*ptr == ':' && *(ptr+1) == '/' && *(ptr+2) == '/'))
1766251881Speter    return FALSE;
1767251881Speter
1768251881Speter  /* Found a scheme, check that it's all lowercase. */
1769251881Speter  ptr = uri;
1770251881Speter  while (*ptr != ':')
1771251881Speter    {
1772251881Speter      if (*ptr >= 'A' && *ptr <= 'Z')
1773251881Speter        return FALSE;
1774251881Speter      ptr++;
1775251881Speter    }
1776251881Speter  /* Skip :// */
1777251881Speter  ptr += 3;
1778251881Speter
1779251881Speter  /* Scheme only?  That works. */
1780251881Speter  if (! *ptr)
1781251881Speter    return TRUE;
1782251881Speter
1783251881Speter  /* This might be the hostname */
1784251881Speter  seg = ptr;
1785251881Speter  while (*ptr && (*ptr != '/') && (*ptr != '@'))
1786251881Speter    ptr++;
1787251881Speter
1788251881Speter  if (*ptr == '@')
1789251881Speter    seg = ptr + 1;
1790251881Speter
1791251881Speter  /* Found a hostname, check that it's all lowercase. */
1792251881Speter  ptr = seg;
1793251881Speter
1794251881Speter  if (*ptr == '[')
1795251881Speter    {
1796251881Speter      ptr++;
1797251881Speter      while (*ptr == ':'
1798251881Speter             || (*ptr >= '0' && *ptr <= '9')
1799251881Speter             || (*ptr >= 'a' && *ptr <= 'f'))
1800251881Speter        {
1801251881Speter          ptr++;
1802251881Speter        }
1803251881Speter
1804251881Speter      if (*ptr != ']')
1805251881Speter        return FALSE;
1806251881Speter      ptr++;
1807251881Speter    }
1808251881Speter  else
1809251881Speter    while (*ptr && *ptr != '/' && *ptr != ':')
1810251881Speter      {
1811251881Speter        if (*ptr >= 'A' && *ptr <= 'Z')
1812251881Speter          return FALSE;
1813251881Speter        ptr++;
1814251881Speter      }
1815251881Speter
1816251881Speter  /* Found a portnumber */
1817251881Speter  if (*ptr == ':')
1818251881Speter    {
1819251881Speter      apr_int64_t port = 0;
1820251881Speter
1821251881Speter      ptr++;
1822251881Speter      schema_data = ptr;
1823251881Speter
1824251881Speter      while (*ptr >= '0' && *ptr <= '9')
1825251881Speter        {
1826251881Speter          port = 10 * port + (*ptr - '0');
1827251881Speter          ptr++;
1828251881Speter        }
1829251881Speter
1830251881Speter      if (ptr == schema_data)
1831251881Speter        return FALSE; /* Fail on "http://host:" */
1832251881Speter
1833251881Speter      if (*ptr && *ptr != '/')
1834251881Speter        return FALSE; /* Not a port number */
1835251881Speter
1836251881Speter      if (port == 80 && strncmp(uri, "http:", 5) == 0)
1837251881Speter        return FALSE;
1838251881Speter      else if (port == 443 && strncmp(uri, "https:", 6) == 0)
1839251881Speter        return FALSE;
1840251881Speter      else if (port == 3690 && strncmp(uri, "svn:", 4) == 0)
1841251881Speter        return FALSE;
1842251881Speter    }
1843251881Speter
1844251881Speter  schema_data = ptr;
1845251881Speter
1846251881Speter#ifdef SVN_USE_DOS_PATHS
1847251881Speter  if (schema_data && *ptr == '/')
1848251881Speter    {
1849251881Speter      /* If this is a file url, ptr now points to the third '/' in
1850251881Speter         file:///C:/path. Check that if we have such a URL the drive
1851251881Speter         letter is in uppercase. */
1852251881Speter      if (strncmp(uri, "file:", 5) == 0 &&
1853251881Speter          ! (*(ptr+1) >= 'A' && *(ptr+1) <= 'Z') &&
1854251881Speter          *(ptr+2) == ':')
1855251881Speter        return FALSE;
1856251881Speter    }
1857251881Speter#endif /* SVN_USE_DOS_PATHS */
1858251881Speter
1859251881Speter  /* Now validate the rest of the URI. */
1860251881Speter  while(1)
1861251881Speter    {
1862251881Speter      apr_size_t seglen = ptr - seg;
1863251881Speter
1864251881Speter      if (seglen == 1 && *seg == '.')
1865251881Speter        return FALSE;  /*  /./   */
1866251881Speter
1867251881Speter      if (*ptr == '/' && *(ptr+1) == '/')
1868251881Speter        return FALSE;  /*  //    */
1869251881Speter
1870251881Speter      if (! *ptr && *(ptr - 1) == '/' && ptr - 1 != uri)
1871251881Speter        return FALSE;  /* foo/  */
1872251881Speter
1873251881Speter      if (! *ptr)
1874251881Speter        break;
1875251881Speter
1876251881Speter      if (*ptr == '/')
1877251881Speter        ptr++;
1878251881Speter      seg = ptr;
1879251881Speter
1880251881Speter
1881251881Speter      while (*ptr && (*ptr != '/'))
1882251881Speter        ptr++;
1883251881Speter    }
1884251881Speter
1885251881Speter  ptr = schema_data;
1886251881Speter
1887251881Speter  while (*ptr)
1888251881Speter    {
1889251881Speter      if (*ptr == '%')
1890251881Speter        {
1891251881Speter          char digitz[3];
1892251881Speter          int val;
1893251881Speter
1894251881Speter          /* Can't usesvn_ctype_isxdigit() because lower case letters are
1895251881Speter             not in our canonical format */
1896251881Speter          if (((*(ptr+1) < '0' || *(ptr+1) > '9'))
1897251881Speter              && (*(ptr+1) < 'A' || *(ptr+1) > 'F'))
1898251881Speter            return FALSE;
1899251881Speter          else if (((*(ptr+2) < '0' || *(ptr+2) > '9'))
1900251881Speter                   && (*(ptr+2) < 'A' || *(ptr+2) > 'F'))
1901251881Speter            return FALSE;
1902251881Speter
1903251881Speter          digitz[0] = *(++ptr);
1904251881Speter          digitz[1] = *(++ptr);
1905251881Speter          digitz[2] = '\0';
1906251881Speter          val = (int)strtol(digitz, NULL, 16);
1907251881Speter
1908251881Speter          if (svn_uri__char_validity[val])
1909251881Speter            return FALSE; /* Should not have been escaped */
1910251881Speter        }
1911251881Speter      else if (*ptr != '/' && !svn_uri__char_validity[(unsigned char)*ptr])
1912251881Speter        return FALSE; /* Character should have been escaped */
1913251881Speter      ptr++;
1914251881Speter    }
1915251881Speter
1916251881Speter  return TRUE;
1917251881Speter}
1918251881Speter
1919251881Spetersvn_error_t *
1920251881Spetersvn_dirent_condense_targets(const char **pcommon,
1921251881Speter                            apr_array_header_t **pcondensed_targets,
1922251881Speter                            const apr_array_header_t *targets,
1923251881Speter                            svn_boolean_t remove_redundancies,
1924251881Speter                            apr_pool_t *result_pool,
1925251881Speter                            apr_pool_t *scratch_pool)
1926251881Speter{
1927251881Speter  int i, num_condensed = targets->nelts;
1928251881Speter  svn_boolean_t *removed;
1929251881Speter  apr_array_header_t *abs_targets;
1930251881Speter
1931251881Speter  /* Early exit when there's no data to work on. */
1932251881Speter  if (targets->nelts <= 0)
1933251881Speter    {
1934251881Speter      *pcommon = NULL;
1935251881Speter      if (pcondensed_targets)
1936251881Speter        *pcondensed_targets = NULL;
1937251881Speter      return SVN_NO_ERROR;
1938251881Speter    }
1939251881Speter
1940251881Speter  /* Get the absolute path of the first target. */
1941251881Speter  SVN_ERR(svn_dirent_get_absolute(pcommon,
1942251881Speter                                  APR_ARRAY_IDX(targets, 0, const char *),
1943251881Speter                                  scratch_pool));
1944251881Speter
1945251881Speter  /* Early exit when there's only one dirent to work on. */
1946251881Speter  if (targets->nelts == 1)
1947251881Speter    {
1948251881Speter      *pcommon = apr_pstrdup(result_pool, *pcommon);
1949251881Speter      if (pcondensed_targets)
1950251881Speter        *pcondensed_targets = apr_array_make(result_pool, 0,
1951251881Speter                                             sizeof(const char *));
1952251881Speter      return SVN_NO_ERROR;
1953251881Speter    }
1954251881Speter
1955251881Speter  /* Copy the targets array, but with absolute dirents instead of
1956251881Speter     relative.  Also, find the pcommon argument by finding what is
1957251881Speter     common in all of the absolute dirents. NOTE: This is not as
1958251881Speter     efficient as it could be.  The calculation of the basedir could
1959251881Speter     be done in the loop below, which would save some calls to
1960251881Speter     svn_dirent_get_longest_ancestor.  I decided to do it this way
1961251881Speter     because I thought it would be simpler, since this way, we don't
1962251881Speter     even do the loop if we don't need to condense the targets. */
1963251881Speter
1964251881Speter  removed = apr_pcalloc(scratch_pool, (targets->nelts *
1965251881Speter                                          sizeof(svn_boolean_t)));
1966251881Speter  abs_targets = apr_array_make(scratch_pool, targets->nelts,
1967251881Speter                               sizeof(const char *));
1968251881Speter
1969251881Speter  APR_ARRAY_PUSH(abs_targets, const char *) = *pcommon;
1970251881Speter
1971251881Speter  for (i = 1; i < targets->nelts; ++i)
1972251881Speter    {
1973251881Speter      const char *rel = APR_ARRAY_IDX(targets, i, const char *);
1974251881Speter      const char *absolute;
1975251881Speter      SVN_ERR(svn_dirent_get_absolute(&absolute, rel, scratch_pool));
1976251881Speter      APR_ARRAY_PUSH(abs_targets, const char *) = absolute;
1977251881Speter      *pcommon = svn_dirent_get_longest_ancestor(*pcommon, absolute,
1978251881Speter                                                 scratch_pool);
1979251881Speter    }
1980251881Speter
1981251881Speter  *pcommon = apr_pstrdup(result_pool, *pcommon);
1982251881Speter
1983251881Speter  if (pcondensed_targets != NULL)
1984251881Speter    {
1985251881Speter      size_t basedir_len;
1986251881Speter
1987251881Speter      if (remove_redundancies)
1988251881Speter        {
1989251881Speter          /* Find the common part of each pair of targets.  If
1990251881Speter             common part is equal to one of the dirents, the other
1991251881Speter             is a child of it, and can be removed.  If a target is
1992251881Speter             equal to *pcommon, it can also be removed. */
1993251881Speter
1994251881Speter          /* First pass: when one non-removed target is a child of
1995251881Speter             another non-removed target, remove the child. */
1996251881Speter          for (i = 0; i < abs_targets->nelts; ++i)
1997251881Speter            {
1998251881Speter              int j;
1999251881Speter
2000251881Speter              if (removed[i])
2001251881Speter                continue;
2002251881Speter
2003251881Speter              for (j = i + 1; j < abs_targets->nelts; ++j)
2004251881Speter                {
2005251881Speter                  const char *abs_targets_i;
2006251881Speter                  const char *abs_targets_j;
2007251881Speter                  const char *ancestor;
2008251881Speter
2009251881Speter                  if (removed[j])
2010251881Speter                    continue;
2011251881Speter
2012251881Speter                  abs_targets_i = APR_ARRAY_IDX(abs_targets, i, const char *);
2013251881Speter                  abs_targets_j = APR_ARRAY_IDX(abs_targets, j, const char *);
2014251881Speter
2015251881Speter                  ancestor = svn_dirent_get_longest_ancestor
2016251881Speter                    (abs_targets_i, abs_targets_j, scratch_pool);
2017251881Speter
2018251881Speter                  if (*ancestor == '\0')
2019251881Speter                    continue;
2020251881Speter
2021251881Speter                  if (strcmp(ancestor, abs_targets_i) == 0)
2022251881Speter                    {
2023251881Speter                      removed[j] = TRUE;
2024251881Speter                      num_condensed--;
2025251881Speter                    }
2026251881Speter                  else if (strcmp(ancestor, abs_targets_j) == 0)
2027251881Speter                    {
2028251881Speter                      removed[i] = TRUE;
2029251881Speter                      num_condensed--;
2030251881Speter                    }
2031251881Speter                }
2032251881Speter            }
2033251881Speter
2034251881Speter          /* Second pass: when a target is the same as *pcommon,
2035251881Speter             remove the target. */
2036251881Speter          for (i = 0; i < abs_targets->nelts; ++i)
2037251881Speter            {
2038251881Speter              const char *abs_targets_i = APR_ARRAY_IDX(abs_targets, i,
2039251881Speter                                                        const char *);
2040251881Speter
2041251881Speter              if ((strcmp(abs_targets_i, *pcommon) == 0) && (! removed[i]))
2042251881Speter                {
2043251881Speter                  removed[i] = TRUE;
2044251881Speter                  num_condensed--;
2045251881Speter                }
2046251881Speter            }
2047251881Speter        }
2048251881Speter
2049251881Speter      /* Now create the return array, and copy the non-removed items */
2050251881Speter      basedir_len = strlen(*pcommon);
2051251881Speter      *pcondensed_targets = apr_array_make(result_pool, num_condensed,
2052251881Speter                                           sizeof(const char *));
2053251881Speter
2054251881Speter      for (i = 0; i < abs_targets->nelts; ++i)
2055251881Speter        {
2056251881Speter          const char *rel_item = APR_ARRAY_IDX(abs_targets, i, const char *);
2057251881Speter
2058251881Speter          /* Skip this if it's been removed. */
2059251881Speter          if (removed[i])
2060251881Speter            continue;
2061251881Speter
2062251881Speter          /* If a common prefix was found, condensed_targets are given
2063251881Speter             relative to that prefix.  */
2064251881Speter          if (basedir_len > 0)
2065251881Speter            {
2066251881Speter              /* Only advance our pointer past a dirent separator if
2067251881Speter                 REL_ITEM isn't the same as *PCOMMON.
2068251881Speter
2069251881Speter                 If *PCOMMON is a root dirent, basedir_len will already
2070251881Speter                 include the closing '/', so never advance the pointer
2071251881Speter                 here.
2072251881Speter                 */
2073251881Speter              rel_item += basedir_len;
2074251881Speter              if (rel_item[0] &&
2075251881Speter                  ! svn_dirent_is_root(*pcommon, basedir_len))
2076251881Speter                rel_item++;
2077251881Speter            }
2078251881Speter
2079251881Speter          APR_ARRAY_PUSH(*pcondensed_targets, const char *)
2080251881Speter            = apr_pstrdup(result_pool, rel_item);
2081251881Speter        }
2082251881Speter    }
2083251881Speter
2084251881Speter  return SVN_NO_ERROR;
2085251881Speter}
2086251881Speter
2087251881Spetersvn_error_t *
2088251881Spetersvn_uri_condense_targets(const char **pcommon,
2089251881Speter                         apr_array_header_t **pcondensed_targets,
2090251881Speter                         const apr_array_header_t *targets,
2091251881Speter                         svn_boolean_t remove_redundancies,
2092251881Speter                         apr_pool_t *result_pool,
2093251881Speter                         apr_pool_t *scratch_pool)
2094251881Speter{
2095251881Speter  int i, num_condensed = targets->nelts;
2096251881Speter  apr_array_header_t *uri_targets;
2097251881Speter  svn_boolean_t *removed;
2098251881Speter
2099251881Speter  /* Early exit when there's no data to work on. */
2100251881Speter  if (targets->nelts <= 0)
2101251881Speter    {
2102251881Speter      *pcommon = NULL;
2103251881Speter      if (pcondensed_targets)
2104251881Speter        *pcondensed_targets = NULL;
2105251881Speter      return SVN_NO_ERROR;
2106251881Speter    }
2107251881Speter
2108251881Speter  *pcommon = svn_uri_canonicalize(APR_ARRAY_IDX(targets, 0, const char *),
2109251881Speter                                  scratch_pool);
2110251881Speter
2111251881Speter  /* Early exit when there's only one uri to work on. */
2112251881Speter  if (targets->nelts == 1)
2113251881Speter    {
2114251881Speter      *pcommon = apr_pstrdup(result_pool, *pcommon);
2115251881Speter      if (pcondensed_targets)
2116251881Speter        *pcondensed_targets = apr_array_make(result_pool, 0,
2117251881Speter                                             sizeof(const char *));
2118251881Speter      return SVN_NO_ERROR;
2119251881Speter    }
2120251881Speter
2121251881Speter  /* Find the pcommon argument by finding what is common in all of the
2122251881Speter     uris. NOTE: This is not as efficient as it could be.  The calculation
2123251881Speter     of the basedir could be done in the loop below, which would
2124251881Speter     save some calls to svn_uri_get_longest_ancestor.  I decided to do it
2125251881Speter     this way because I thought it would be simpler, since this way, we don't
2126251881Speter     even do the loop if we don't need to condense the targets. */
2127251881Speter
2128251881Speter  removed = apr_pcalloc(scratch_pool, (targets->nelts *
2129251881Speter                                          sizeof(svn_boolean_t)));
2130251881Speter  uri_targets = apr_array_make(scratch_pool, targets->nelts,
2131251881Speter                               sizeof(const char *));
2132251881Speter
2133251881Speter  APR_ARRAY_PUSH(uri_targets, const char *) = *pcommon;
2134251881Speter
2135251881Speter  for (i = 1; i < targets->nelts; ++i)
2136251881Speter    {
2137251881Speter      const char *uri = svn_uri_canonicalize(
2138251881Speter                           APR_ARRAY_IDX(targets, i, const char *),
2139251881Speter                           scratch_pool);
2140251881Speter      APR_ARRAY_PUSH(uri_targets, const char *) = uri;
2141251881Speter
2142251881Speter      /* If the commonmost ancestor so far is empty, there's no point
2143251881Speter         in continuing to search for a common ancestor at all.  But
2144251881Speter         we'll keep looping for the sake of canonicalizing the
2145251881Speter         targets, I suppose.  */
2146251881Speter      if (**pcommon != '\0')
2147251881Speter        *pcommon = svn_uri_get_longest_ancestor(*pcommon, uri,
2148251881Speter                                                scratch_pool);
2149251881Speter    }
2150251881Speter
2151251881Speter  *pcommon = apr_pstrdup(result_pool, *pcommon);
2152251881Speter
2153251881Speter  if (pcondensed_targets != NULL)
2154251881Speter    {
2155251881Speter      size_t basedir_len;
2156251881Speter
2157251881Speter      if (remove_redundancies)
2158251881Speter        {
2159251881Speter          /* Find the common part of each pair of targets.  If
2160251881Speter             common part is equal to one of the dirents, the other
2161251881Speter             is a child of it, and can be removed.  If a target is
2162251881Speter             equal to *pcommon, it can also be removed. */
2163251881Speter
2164251881Speter          /* First pass: when one non-removed target is a child of
2165251881Speter             another non-removed target, remove the child. */
2166251881Speter          for (i = 0; i < uri_targets->nelts; ++i)
2167251881Speter            {
2168251881Speter              int j;
2169251881Speter
2170251881Speter              if (removed[i])
2171251881Speter                continue;
2172251881Speter
2173251881Speter              for (j = i + 1; j < uri_targets->nelts; ++j)
2174251881Speter                {
2175251881Speter                  const char *uri_i;
2176251881Speter                  const char *uri_j;
2177251881Speter                  const char *ancestor;
2178251881Speter
2179251881Speter                  if (removed[j])
2180251881Speter                    continue;
2181251881Speter
2182251881Speter                  uri_i = APR_ARRAY_IDX(uri_targets, i, const char *);
2183251881Speter                  uri_j = APR_ARRAY_IDX(uri_targets, j, const char *);
2184251881Speter
2185251881Speter                  ancestor = svn_uri_get_longest_ancestor(uri_i,
2186251881Speter                                                          uri_j,
2187251881Speter                                                          scratch_pool);
2188251881Speter
2189251881Speter                  if (*ancestor == '\0')
2190251881Speter                    continue;
2191251881Speter
2192251881Speter                  if (strcmp(ancestor, uri_i) == 0)
2193251881Speter                    {
2194251881Speter                      removed[j] = TRUE;
2195251881Speter                      num_condensed--;
2196251881Speter                    }
2197251881Speter                  else if (strcmp(ancestor, uri_j) == 0)
2198251881Speter                    {
2199251881Speter                      removed[i] = TRUE;
2200251881Speter                      num_condensed--;
2201251881Speter                    }
2202251881Speter                }
2203251881Speter            }
2204251881Speter
2205251881Speter          /* Second pass: when a target is the same as *pcommon,
2206251881Speter             remove the target. */
2207251881Speter          for (i = 0; i < uri_targets->nelts; ++i)
2208251881Speter            {
2209251881Speter              const char *uri_targets_i = APR_ARRAY_IDX(uri_targets, i,
2210251881Speter                                                        const char *);
2211251881Speter
2212251881Speter              if ((strcmp(uri_targets_i, *pcommon) == 0) && (! removed[i]))
2213251881Speter                {
2214251881Speter                  removed[i] = TRUE;
2215251881Speter                  num_condensed--;
2216251881Speter                }
2217251881Speter            }
2218251881Speter        }
2219251881Speter
2220251881Speter      /* Now create the return array, and copy the non-removed items */
2221251881Speter      basedir_len = strlen(*pcommon);
2222251881Speter      *pcondensed_targets = apr_array_make(result_pool, num_condensed,
2223251881Speter                                           sizeof(const char *));
2224251881Speter
2225251881Speter      for (i = 0; i < uri_targets->nelts; ++i)
2226251881Speter        {
2227251881Speter          const char *rel_item = APR_ARRAY_IDX(uri_targets, i, const char *);
2228251881Speter
2229251881Speter          /* Skip this if it's been removed. */
2230251881Speter          if (removed[i])
2231251881Speter            continue;
2232251881Speter
2233251881Speter          /* If a common prefix was found, condensed_targets are given
2234251881Speter             relative to that prefix.  */
2235251881Speter          if (basedir_len > 0)
2236251881Speter            {
2237251881Speter              /* Only advance our pointer past a dirent separator if
2238251881Speter                 REL_ITEM isn't the same as *PCOMMON.
2239251881Speter
2240251881Speter                 If *PCOMMON is a root dirent, basedir_len will already
2241251881Speter                 include the closing '/', so never advance the pointer
2242251881Speter                 here.
2243251881Speter                 */
2244251881Speter              rel_item += basedir_len;
2245251881Speter              if ((rel_item[0] == '/') ||
2246251881Speter                  (rel_item[0] && !svn_uri_is_root(*pcommon, basedir_len)))
2247251881Speter                {
2248251881Speter                  rel_item++;
2249251881Speter                }
2250251881Speter            }
2251251881Speter
2252251881Speter          APR_ARRAY_PUSH(*pcondensed_targets, const char *)
2253251881Speter            = svn_path_uri_decode(rel_item, result_pool);
2254251881Speter        }
2255251881Speter    }
2256251881Speter
2257251881Speter  return SVN_NO_ERROR;
2258251881Speter}
2259251881Speter
2260251881Spetersvn_error_t *
2261251881Spetersvn_dirent_is_under_root(svn_boolean_t *under_root,
2262251881Speter                         const char **result_path,
2263251881Speter                         const char *base_path,
2264251881Speter                         const char *path,
2265251881Speter                         apr_pool_t *result_pool)
2266251881Speter{
2267251881Speter  apr_status_t status;
2268251881Speter  char *full_path;
2269251881Speter
2270251881Speter  *under_root = FALSE;
2271251881Speter  if (result_path)
2272251881Speter    *result_path = NULL;
2273251881Speter
2274251881Speter  status = apr_filepath_merge(&full_path,
2275251881Speter                              base_path,
2276251881Speter                              path,
2277251881Speter                              APR_FILEPATH_NOTABOVEROOT
2278251881Speter                              | APR_FILEPATH_SECUREROOTTEST,
2279251881Speter                              result_pool);
2280251881Speter
2281251881Speter  if (status == APR_SUCCESS)
2282251881Speter    {
2283251881Speter      if (result_path)
2284251881Speter        *result_path = svn_dirent_canonicalize(full_path, result_pool);
2285251881Speter      *under_root = TRUE;
2286251881Speter      return SVN_NO_ERROR;
2287251881Speter    }
2288251881Speter  else if (status == APR_EABOVEROOT)
2289251881Speter    {
2290251881Speter      *under_root = FALSE;
2291251881Speter      return SVN_NO_ERROR;
2292251881Speter    }
2293251881Speter
2294251881Speter  return svn_error_wrap_apr(status, NULL);
2295251881Speter}
2296251881Speter
2297251881Spetersvn_error_t *
2298251881Spetersvn_uri_get_dirent_from_file_url(const char **dirent,
2299251881Speter                                 const char *url,
2300251881Speter                                 apr_pool_t *pool)
2301251881Speter{
2302251881Speter  const char *hostname, *path;
2303251881Speter
2304251881Speter  SVN_ERR_ASSERT(svn_uri_is_canonical(url, pool));
2305251881Speter
2306251881Speter  /* Verify that the URL is well-formed (loosely) */
2307251881Speter
2308251881Speter  /* First, check for the "file://" prefix. */
2309251881Speter  if (strncmp(url, "file://", 7) != 0)
2310251881Speter    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2311251881Speter                             _("Local URL '%s' does not contain 'file://' "
2312251881Speter                               "prefix"), url);
2313251881Speter
2314251881Speter  /* Find the HOSTNAME portion and the PATH portion of the URL.  The host
2315251881Speter     name is between the "file://" prefix and the next occurence of '/'.  We
2316251881Speter     are considering everything from that '/' until the end of the URL to be
2317251881Speter     the absolute path portion of the URL.
2318251881Speter     If we got just "file://", treat it the same as "file:///". */
2319251881Speter  hostname = url + 7;
2320251881Speter  path = strchr(hostname, '/');
2321251881Speter  if (path)
2322251881Speter    hostname = apr_pstrmemdup(pool, hostname, path - hostname);
2323251881Speter  else
2324251881Speter    path = "/";
2325251881Speter
2326251881Speter  /* URI-decode HOSTNAME, and set it to NULL if it is "" or "localhost". */
2327251881Speter  if (*hostname == '\0')
2328251881Speter    hostname = NULL;
2329251881Speter  else
2330251881Speter    {
2331251881Speter      hostname = svn_path_uri_decode(hostname, pool);
2332251881Speter      if (strcmp(hostname, "localhost") == 0)
2333251881Speter        hostname = NULL;
2334251881Speter    }
2335251881Speter
2336251881Speter  /* Duplicate the URL, starting at the top of the path.
2337251881Speter     At the same time, we URI-decode the path. */
2338251881Speter#ifdef SVN_USE_DOS_PATHS
2339251881Speter  /* On Windows, we'll typically have to skip the leading / if the
2340251881Speter     path starts with a drive letter.  Like most Web browsers, We
2341251881Speter     support two variants of this scheme:
2342251881Speter
2343251881Speter         file:///X:/path    and
2344251881Speter         file:///X|/path
2345251881Speter
2346251881Speter    Note that, at least on WinNT and above,  file:////./X:/path  will
2347251881Speter    also work, so we must make sure the transformation doesn't break
2348251881Speter    that, and  file:///path  (that looks within the current drive
2349251881Speter    only) should also keep working.
2350251881Speter    If we got a non-empty hostname other than localhost, we convert this
2351251881Speter    into an UNC path.  In this case, we obviously don't strip the slash
2352251881Speter    even if the path looks like it starts with a drive letter.
2353251881Speter  */
2354251881Speter  {
2355251881Speter    static const char valid_drive_letters[] =
2356251881Speter      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2357251881Speter    /* Casting away const! */
2358251881Speter    char *dup_path = (char *)svn_path_uri_decode(path, pool);
2359251881Speter
2360251881Speter    /* This check assumes ':' and '|' are already decoded! */
2361251881Speter    if (!hostname && dup_path[1] && strchr(valid_drive_letters, dup_path[1])
2362251881Speter        && (dup_path[2] == ':' || dup_path[2] == '|'))
2363251881Speter      {
2364251881Speter        /* Skip the leading slash. */
2365251881Speter        ++dup_path;
2366251881Speter
2367251881Speter        if (dup_path[1] == '|')
2368251881Speter          dup_path[1] = ':';
2369251881Speter
2370251881Speter        if (dup_path[2] == '/' || dup_path[2] == '\0')
2371251881Speter          {
2372251881Speter            if (dup_path[2] == '\0')
2373251881Speter              {
2374251881Speter                /* A valid dirent for the driveroot must be like "C:/" instead of
2375251881Speter                   just "C:" or svn_dirent_join() will use the current directory
2376251881Speter                   on the drive instead */
2377251881Speter                char *new_path = apr_pcalloc(pool, 4);
2378251881Speter                new_path[0] = dup_path[0];
2379251881Speter                new_path[1] = ':';
2380251881Speter                new_path[2] = '/';
2381251881Speter                new_path[3] = '\0';
2382251881Speter                dup_path = new_path;
2383251881Speter              }
2384251881Speter          }
2385251881Speter      }
2386251881Speter    if (hostname)
2387251881Speter      {
2388251881Speter        if (dup_path[0] == '/' && dup_path[1] == '\0')
2389251881Speter          return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2390251881Speter                                   _("Local URL '%s' contains only a hostname, "
2391251881Speter                                     "no path"), url);
2392251881Speter
2393251881Speter        /* We still know that the path starts with a slash. */
2394251881Speter        *dirent = apr_pstrcat(pool, "//", hostname, dup_path, NULL);
2395251881Speter      }
2396251881Speter    else
2397251881Speter      *dirent = dup_path;
2398251881Speter  }
2399251881Speter#else /* !SVN_USE_DOS_PATHS */
2400251881Speter  /* Currently, the only hostnames we are allowing on non-Win32 platforms
2401251881Speter     are the empty string and 'localhost'. */
2402251881Speter  if (hostname)
2403251881Speter    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2404251881Speter                             _("Local URL '%s' contains unsupported hostname"),
2405251881Speter                             url);
2406251881Speter
2407251881Speter  *dirent = svn_path_uri_decode(path, pool);
2408251881Speter#endif /* SVN_USE_DOS_PATHS */
2409251881Speter  return SVN_NO_ERROR;
2410251881Speter}
2411251881Speter
2412251881Spetersvn_error_t *
2413251881Spetersvn_uri_get_file_url_from_dirent(const char **url,
2414251881Speter                                 const char *dirent,
2415251881Speter                                 apr_pool_t *pool)
2416251881Speter{
2417251881Speter  assert(svn_dirent_is_canonical(dirent, pool));
2418251881Speter
2419251881Speter  SVN_ERR(svn_dirent_get_absolute(&dirent, dirent, pool));
2420251881Speter
2421251881Speter  dirent = svn_path_uri_encode(dirent, pool);
2422251881Speter
2423251881Speter#ifndef SVN_USE_DOS_PATHS
2424251881Speter  if (dirent[0] == '/' && dirent[1] == '\0')
2425251881Speter    dirent = NULL; /* "file://" is the canonical form of "file:///" */
2426251881Speter
2427251881Speter  *url = apr_pstrcat(pool, "file://", dirent, (char *)NULL);
2428251881Speter#else
2429251881Speter  if (dirent[0] == '/')
2430251881Speter    {
2431251881Speter      /* Handle UNC paths //server/share -> file://server/share */
2432251881Speter      assert(dirent[1] == '/'); /* Expect UNC, not non-absolute */
2433251881Speter
2434251881Speter      *url = apr_pstrcat(pool, "file:", dirent, NULL);
2435251881Speter    }
2436251881Speter  else
2437251881Speter    {
2438251881Speter      char *uri = apr_pstrcat(pool, "file:///", dirent, NULL);
2439251881Speter      apr_size_t len = 8 /* strlen("file:///") */ + strlen(dirent);
2440251881Speter
2441251881Speter      /* "C:/" is a canonical dirent on Windows,
2442251881Speter         but "file:///C:/" is not a canonical uri */
2443251881Speter      if (uri[len-1] == '/')
2444251881Speter        uri[len-1] = '\0';
2445251881Speter
2446251881Speter      *url = uri;
2447251881Speter    }
2448251881Speter#endif
2449251881Speter
2450251881Speter  return SVN_NO_ERROR;
2451251881Speter}
2452251881Speter
2453251881Speter
2454251881Speter
2455251881Speter/* -------------- The fspath API (see private/svn_fspath.h) -------------- */
2456251881Speter
2457251881Spetersvn_boolean_t
2458251881Spetersvn_fspath__is_canonical(const char *fspath)
2459251881Speter{
2460251881Speter  return fspath[0] == '/' && relpath_is_canonical(fspath + 1);
2461251881Speter}
2462251881Speter
2463251881Speter
2464251881Speterconst char *
2465251881Spetersvn_fspath__canonicalize(const char *fspath,
2466251881Speter                         apr_pool_t *pool)
2467251881Speter{
2468251881Speter  if ((fspath[0] == '/') && (fspath[1] == '\0'))
2469251881Speter    return "/";
2470251881Speter
2471251881Speter  return apr_pstrcat(pool, "/", svn_relpath_canonicalize(fspath, pool),
2472251881Speter                     (char *)NULL);
2473251881Speter}
2474251881Speter
2475251881Speter
2476251881Spetersvn_boolean_t
2477251881Spetersvn_fspath__is_root(const char *fspath, apr_size_t len)
2478251881Speter{
2479251881Speter  /* directory is root if it's equal to '/' */
2480251881Speter  return (len == 1 && fspath[0] == '/');
2481251881Speter}
2482251881Speter
2483251881Speter
2484251881Speterconst char *
2485251881Spetersvn_fspath__skip_ancestor(const char *parent_fspath,
2486251881Speter                          const char *child_fspath)
2487251881Speter{
2488251881Speter  assert(svn_fspath__is_canonical(parent_fspath));
2489251881Speter  assert(svn_fspath__is_canonical(child_fspath));
2490251881Speter
2491251881Speter  return svn_relpath_skip_ancestor(parent_fspath + 1, child_fspath + 1);
2492251881Speter}
2493251881Speter
2494251881Speter
2495251881Speterconst char *
2496251881Spetersvn_fspath__dirname(const char *fspath,
2497251881Speter                    apr_pool_t *pool)
2498251881Speter{
2499251881Speter  assert(svn_fspath__is_canonical(fspath));
2500251881Speter
2501251881Speter  if (fspath[0] == '/' && fspath[1] == '\0')
2502251881Speter    return apr_pstrdup(pool, fspath);
2503251881Speter  else
2504251881Speter    return apr_pstrcat(pool, "/", svn_relpath_dirname(fspath + 1, pool),
2505251881Speter                       (char *)NULL);
2506251881Speter}
2507251881Speter
2508251881Speter
2509251881Speterconst char *
2510251881Spetersvn_fspath__basename(const char *fspath,
2511251881Speter                     apr_pool_t *pool)
2512251881Speter{
2513251881Speter  const char *result;
2514251881Speter  assert(svn_fspath__is_canonical(fspath));
2515251881Speter
2516251881Speter  result = svn_relpath_basename(fspath + 1, pool);
2517251881Speter
2518251881Speter  assert(strchr(result, '/') == NULL);
2519251881Speter  return result;
2520251881Speter}
2521251881Speter
2522251881Spetervoid
2523251881Spetersvn_fspath__split(const char **dirpath,
2524251881Speter                  const char **base_name,
2525251881Speter                  const char *fspath,
2526251881Speter                  apr_pool_t *result_pool)
2527251881Speter{
2528251881Speter  assert(dirpath != base_name);
2529251881Speter
2530251881Speter  if (dirpath)
2531251881Speter    *dirpath = svn_fspath__dirname(fspath, result_pool);
2532251881Speter
2533251881Speter  if (base_name)
2534251881Speter    *base_name = svn_fspath__basename(fspath, result_pool);
2535251881Speter}
2536251881Speter
2537251881Speterchar *
2538251881Spetersvn_fspath__join(const char *fspath,
2539251881Speter                 const char *relpath,
2540251881Speter                 apr_pool_t *result_pool)
2541251881Speter{
2542251881Speter  char *result;
2543251881Speter  assert(svn_fspath__is_canonical(fspath));
2544251881Speter  assert(svn_relpath_is_canonical(relpath));
2545251881Speter
2546251881Speter  if (relpath[0] == '\0')
2547251881Speter    result = apr_pstrdup(result_pool, fspath);
2548251881Speter  else if (fspath[1] == '\0')
2549251881Speter    result = apr_pstrcat(result_pool, "/", relpath, (char *)NULL);
2550251881Speter  else
2551251881Speter    result = apr_pstrcat(result_pool, fspath, "/", relpath, (char *)NULL);
2552251881Speter
2553251881Speter  assert(svn_fspath__is_canonical(result));
2554251881Speter  return result;
2555251881Speter}
2556251881Speter
2557251881Speterchar *
2558251881Spetersvn_fspath__get_longest_ancestor(const char *fspath1,
2559251881Speter                                 const char *fspath2,
2560251881Speter                                 apr_pool_t *result_pool)
2561251881Speter{
2562251881Speter  char *result;
2563251881Speter  assert(svn_fspath__is_canonical(fspath1));
2564251881Speter  assert(svn_fspath__is_canonical(fspath2));
2565251881Speter
2566251881Speter  result = apr_pstrcat(result_pool, "/",
2567251881Speter                       svn_relpath_get_longest_ancestor(fspath1 + 1,
2568251881Speter                                                        fspath2 + 1,
2569251881Speter                                                        result_pool),
2570251881Speter                       (char *)NULL);
2571251881Speter
2572251881Speter  assert(svn_fspath__is_canonical(result));
2573251881Speter  return result;
2574251881Speter}
2575251881Speter
2576251881Speter
2577251881Speter
2578251881Speter
2579251881Speter/* -------------- The urlpath API (see private/svn_fspath.h) ------------- */
2580251881Speter
2581251881Speterconst char *
2582251881Spetersvn_urlpath__canonicalize(const char *uri,
2583251881Speter                          apr_pool_t *pool)
2584251881Speter{
2585251881Speter  if (svn_path_is_url(uri))
2586251881Speter    {
2587251881Speter      uri = svn_uri_canonicalize(uri, pool);
2588251881Speter    }
2589251881Speter  else
2590251881Speter    {
2591251881Speter      uri = svn_fspath__canonicalize(uri, pool);
2592251881Speter      /* Do a little dance to normalize hex encoding. */
2593251881Speter      uri = svn_path_uri_decode(uri, pool);
2594251881Speter      uri = svn_path_uri_encode(uri, pool);
2595251881Speter    }
2596251881Speter  return uri;
2597251881Speter}
2598