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