dirent_uri.c revision 269833
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"
41269833Speter#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
1297251881Speterchar *
1298251881Spetersvn_uri_dirname(const char *uri, apr_pool_t *pool)
1299251881Speter{
1300251881Speter  apr_size_t len = strlen(uri);
1301251881Speter
1302251881Speter  assert(svn_uri_is_canonical(uri, pool));
1303251881Speter
1304251881Speter  if (svn_uri_is_root(uri, len))
1305251881Speter    return apr_pstrmemdup(pool, uri, len);
1306251881Speter  else
1307251881Speter    return apr_pstrmemdup(pool, uri, uri_previous_segment(uri, len));
1308251881Speter}
1309251881Speter
1310251881Speterconst char *
1311251881Spetersvn_uri_basename(const char *uri, apr_pool_t *pool)
1312251881Speter{
1313251881Speter  apr_size_t len = strlen(uri);
1314251881Speter  apr_size_t start;
1315251881Speter
1316251881Speter  assert(svn_uri_is_canonical(uri, NULL));
1317251881Speter
1318251881Speter  if (svn_uri_is_root(uri, len))
1319251881Speter    return "";
1320251881Speter
1321251881Speter  start = len;
1322251881Speter  while (start > 0 && uri[start - 1] != '/')
1323251881Speter    --start;
1324251881Speter
1325251881Speter  return svn_path_uri_decode(uri + start, pool);
1326251881Speter}
1327251881Speter
1328251881Spetervoid
1329251881Spetersvn_uri_split(const char **dirpath,
1330251881Speter              const char **base_name,
1331251881Speter              const char *uri,
1332251881Speter              apr_pool_t *pool)
1333251881Speter{
1334251881Speter  assert(dirpath != base_name);
1335251881Speter
1336251881Speter  if (dirpath)
1337251881Speter    *dirpath = svn_uri_dirname(uri, pool);
1338251881Speter
1339251881Speter  if (base_name)
1340251881Speter    *base_name = svn_uri_basename(uri, pool);
1341251881Speter}
1342251881Speter
1343251881Speterchar *
1344251881Spetersvn_dirent_get_longest_ancestor(const char *dirent1,
1345251881Speter                                const char *dirent2,
1346251881Speter                                apr_pool_t *pool)
1347251881Speter{
1348251881Speter  return apr_pstrndup(pool, dirent1,
1349251881Speter                      get_longest_ancestor_length(type_dirent, dirent1,
1350251881Speter                                                  dirent2, pool));
1351251881Speter}
1352251881Speter
1353251881Speterchar *
1354251881Spetersvn_relpath_get_longest_ancestor(const char *relpath1,
1355251881Speter                                 const char *relpath2,
1356251881Speter                                 apr_pool_t *pool)
1357251881Speter{
1358251881Speter  assert(relpath_is_canonical(relpath1));
1359251881Speter  assert(relpath_is_canonical(relpath2));
1360251881Speter
1361251881Speter  return apr_pstrndup(pool, relpath1,
1362251881Speter                      get_longest_ancestor_length(type_relpath, relpath1,
1363251881Speter                                                  relpath2, pool));
1364251881Speter}
1365251881Speter
1366251881Speterchar *
1367251881Spetersvn_uri_get_longest_ancestor(const char *uri1,
1368251881Speter                             const char *uri2,
1369251881Speter                             apr_pool_t *pool)
1370251881Speter{
1371251881Speter  apr_size_t uri_ancestor_len;
1372251881Speter  apr_size_t i = 0;
1373251881Speter
1374251881Speter  assert(svn_uri_is_canonical(uri1, NULL));
1375251881Speter  assert(svn_uri_is_canonical(uri2, NULL));
1376251881Speter
1377251881Speter  /* Find ':' */
1378251881Speter  while (1)
1379251881Speter    {
1380251881Speter      /* No shared protocol => no common prefix */
1381251881Speter      if (uri1[i] != uri2[i])
1382251881Speter        return apr_pmemdup(pool, SVN_EMPTY_PATH,
1383251881Speter                           sizeof(SVN_EMPTY_PATH));
1384251881Speter
1385251881Speter      if (uri1[i] == ':')
1386251881Speter        break;
1387251881Speter
1388251881Speter      /* They're both URLs, so EOS can't come before ':' */
1389251881Speter      assert((uri1[i] != '\0') && (uri2[i] != '\0'));
1390251881Speter
1391251881Speter      i++;
1392251881Speter    }
1393251881Speter
1394251881Speter  i += 3;  /* Advance past '://' */
1395251881Speter
1396251881Speter  uri_ancestor_len = get_longest_ancestor_length(type_uri, uri1 + i,
1397251881Speter                                                 uri2 + i, pool);
1398251881Speter
1399251881Speter  if (uri_ancestor_len == 0 ||
1400251881Speter      (uri_ancestor_len == 1 && (uri1 + i)[0] == '/'))
1401251881Speter    return apr_pmemdup(pool, SVN_EMPTY_PATH, sizeof(SVN_EMPTY_PATH));
1402251881Speter  else
1403251881Speter    return apr_pstrndup(pool, uri1, uri_ancestor_len + i);
1404251881Speter}
1405251881Speter
1406251881Speterconst char *
1407251881Spetersvn_dirent_is_child(const char *parent_dirent,
1408251881Speter                    const char *child_dirent,
1409251881Speter                    apr_pool_t *pool)
1410251881Speter{
1411251881Speter  return is_child(type_dirent, parent_dirent, child_dirent, pool);
1412251881Speter}
1413251881Speter
1414251881Speterconst char *
1415251881Spetersvn_dirent_skip_ancestor(const char *parent_dirent,
1416251881Speter                         const char *child_dirent)
1417251881Speter{
1418251881Speter  apr_size_t len = strlen(parent_dirent);
1419251881Speter  apr_size_t root_len;
1420251881Speter
1421251881Speter  if (0 != strncmp(parent_dirent, child_dirent, len))
1422251881Speter    return NULL; /* parent_dirent is no ancestor of child_dirent */
1423251881Speter
1424251881Speter  if (child_dirent[len] == 0)
1425251881Speter    return ""; /* parent_dirent == child_dirent */
1426251881Speter
1427251881Speter  /* Child == parent + more-characters */
1428251881Speter
1429251881Speter  root_len = dirent_root_length(child_dirent, strlen(child_dirent));
1430251881Speter  if (root_len > len)
1431251881Speter    /* Different root, e.g. ("" "/...") or ("//z" "//z/share") */
1432251881Speter    return NULL;
1433251881Speter
1434251881Speter  /* Now, child == [root-of-parent] + [rest-of-parent] + more-characters.
1435251881Speter   * It must be one of the following forms.
1436251881Speter   *
1437251881Speter   * rlen parent    child       bad?  rlen=len? c[len]=/?
1438251881Speter   *  0   ""        "foo"               *
1439251881Speter   *  0   "b"       "bad"         !
1440251881Speter   *  0   "b"       "b/foo"                       *
1441251881Speter   *  1   "/"       "/foo"              *
1442251881Speter   *  1   "/b"      "/bad"        !
1443251881Speter   *  1   "/b"      "/b/foo"                      *
1444251881Speter   *  2   "a:"      "a:foo"             *
1445251881Speter   *  2   "a:b"     "a:bad"       !
1446251881Speter   *  2   "a:b"     "a:b/foo"                     *
1447251881Speter   *  3   "a:/"     "a:/foo"            *
1448251881Speter   *  3   "a:/b"    "a:/bad"      !
1449251881Speter   *  3   "a:/b"    "a:/b/foo"                    *
1450251881Speter   *  5   "//s/s"   "//s/s/foo"         *         *
1451251881Speter   *  5   "//s/s/b" "//s/s/bad"   !
1452251881Speter   *  5   "//s/s/b" "//s/s/b/foo"                 *
1453251881Speter   */
1454251881Speter
1455251881Speter  if (child_dirent[len] == '/')
1456251881Speter    /* "parent|child" is one of:
1457251881Speter     * "[a:]b|/foo" "[a:]/b|/foo" "//s/s|/foo" "//s/s/b|/foo" */
1458251881Speter    return child_dirent + len + 1;
1459251881Speter
1460251881Speter  if (root_len == len)
1461251881Speter    /* "parent|child" is "|foo" "/|foo" "a:|foo" "a:/|foo" "//s/s|/foo" */
1462251881Speter    return child_dirent + len;
1463251881Speter
1464251881Speter  return NULL;
1465251881Speter}
1466251881Speter
1467251881Speterconst char *
1468251881Spetersvn_relpath_skip_ancestor(const char *parent_relpath,
1469251881Speter                          const char *child_relpath)
1470251881Speter{
1471251881Speter  apr_size_t len = strlen(parent_relpath);
1472251881Speter
1473251881Speter  assert(relpath_is_canonical(parent_relpath));
1474251881Speter  assert(relpath_is_canonical(child_relpath));
1475251881Speter
1476251881Speter  if (len == 0)
1477251881Speter    return child_relpath;
1478251881Speter
1479251881Speter  if (0 != strncmp(parent_relpath, child_relpath, len))
1480251881Speter    return NULL; /* parent_relpath is no ancestor of child_relpath */
1481251881Speter
1482251881Speter  if (child_relpath[len] == 0)
1483251881Speter    return ""; /* parent_relpath == child_relpath */
1484251881Speter
1485251881Speter  if (child_relpath[len] == '/')
1486251881Speter    return child_relpath + len + 1;
1487251881Speter
1488251881Speter  return NULL;
1489251881Speter}
1490251881Speter
1491251881Speter
1492251881Speter/* */
1493251881Speterstatic const char *
1494251881Speteruri_skip_ancestor(const char *parent_uri,
1495251881Speter                  const char *child_uri)
1496251881Speter{
1497251881Speter  apr_size_t len = strlen(parent_uri);
1498251881Speter
1499251881Speter  assert(svn_uri_is_canonical(parent_uri, NULL));
1500251881Speter  assert(svn_uri_is_canonical(child_uri, NULL));
1501251881Speter
1502251881Speter  if (0 != strncmp(parent_uri, child_uri, len))
1503251881Speter    return NULL; /* parent_uri is no ancestor of child_uri */
1504251881Speter
1505251881Speter  if (child_uri[len] == 0)
1506251881Speter    return ""; /* parent_uri == child_uri */
1507251881Speter
1508251881Speter  if (child_uri[len] == '/')
1509251881Speter    return child_uri + len + 1;
1510251881Speter
1511251881Speter  return NULL;
1512251881Speter}
1513251881Speter
1514251881Speterconst char *
1515251881Spetersvn_uri_skip_ancestor(const char *parent_uri,
1516251881Speter                      const char *child_uri,
1517251881Speter                      apr_pool_t *result_pool)
1518251881Speter{
1519251881Speter  const char *result = uri_skip_ancestor(parent_uri, child_uri);
1520251881Speter
1521251881Speter  return result ? svn_path_uri_decode(result, result_pool) : NULL;
1522251881Speter}
1523251881Speter
1524251881Spetersvn_boolean_t
1525251881Spetersvn_dirent_is_ancestor(const char *parent_dirent, const char *child_dirent)
1526251881Speter{
1527251881Speter  return svn_dirent_skip_ancestor(parent_dirent, child_dirent) != NULL;
1528251881Speter}
1529251881Speter
1530251881Spetersvn_boolean_t
1531251881Spetersvn_uri__is_ancestor(const char *parent_uri, const char *child_uri)
1532251881Speter{
1533251881Speter  return uri_skip_ancestor(parent_uri, child_uri) != NULL;
1534251881Speter}
1535251881Speter
1536251881Speter
1537251881Spetersvn_boolean_t
1538251881Spetersvn_dirent_is_absolute(const char *dirent)
1539251881Speter{
1540251881Speter  if (! dirent)
1541251881Speter    return FALSE;
1542251881Speter
1543251881Speter  /* dirent is absolute if it starts with '/' on non-Windows platforms
1544251881Speter     or with '//' on Windows platforms */
1545251881Speter  if (dirent[0] == '/'
1546251881Speter#ifdef SVN_USE_DOS_PATHS
1547251881Speter      && dirent[1] == '/' /* Single '/' depends on current drive */
1548251881Speter#endif
1549251881Speter      )
1550251881Speter    return TRUE;
1551251881Speter
1552251881Speter  /* On Windows, dirent is also absolute when it starts with 'H:/'
1553251881Speter     where 'H' is any letter. */
1554251881Speter#ifdef SVN_USE_DOS_PATHS
1555251881Speter  if (((dirent[0] >= 'A' && dirent[0] <= 'Z')) &&
1556251881Speter      (dirent[1] == ':') && (dirent[2] == '/'))
1557251881Speter     return TRUE;
1558251881Speter#endif /* SVN_USE_DOS_PATHS */
1559251881Speter
1560251881Speter  return FALSE;
1561251881Speter}
1562251881Speter
1563251881Spetersvn_error_t *
1564251881Spetersvn_dirent_get_absolute(const char **pabsolute,
1565251881Speter                        const char *relative,
1566251881Speter                        apr_pool_t *pool)
1567251881Speter{
1568251881Speter  char *buffer;
1569251881Speter  apr_status_t apr_err;
1570251881Speter  const char *path_apr;
1571251881Speter
1572251881Speter  SVN_ERR_ASSERT(! svn_path_is_url(relative));
1573251881Speter
1574251881Speter  /* Merge the current working directory with the relative dirent. */
1575251881Speter  SVN_ERR(svn_path_cstring_from_utf8(&path_apr, relative, pool));
1576251881Speter
1577251881Speter  apr_err = apr_filepath_merge(&buffer, NULL,
1578251881Speter                               path_apr,
1579251881Speter                               APR_FILEPATH_NOTRELATIVE,
1580251881Speter                               pool);
1581251881Speter  if (apr_err)
1582251881Speter    {
1583251881Speter      /* In some cases when the passed path or its ancestor(s) do not exist
1584251881Speter         or no longer exist apr returns an error.
1585251881Speter
1586251881Speter         In many of these cases we would like to return a path anyway, when the
1587251881Speter         passed path was already a safe absolute path. So check for that now to
1588251881Speter         avoid an error.
1589251881Speter
1590251881Speter         svn_dirent_is_absolute() doesn't perform the necessary checks to see
1591251881Speter         if the path doesn't need post processing to be in the canonical absolute
1592251881Speter         format.
1593251881Speter         */
1594251881Speter
1595251881Speter      if (svn_dirent_is_absolute(relative)
1596251881Speter          && svn_dirent_is_canonical(relative, pool)
1597251881Speter          && !svn_path_is_backpath_present(relative))
1598251881Speter        {
1599251881Speter          *pabsolute = apr_pstrdup(pool, relative);
1600251881Speter          return SVN_NO_ERROR;
1601251881Speter        }
1602251881Speter
1603251881Speter      return svn_error_createf(SVN_ERR_BAD_FILENAME,
1604251881Speter                               svn_error_create(apr_err, NULL, NULL),
1605251881Speter                               _("Couldn't determine absolute path of '%s'"),
1606251881Speter                               svn_dirent_local_style(relative, pool));
1607251881Speter    }
1608251881Speter
1609251881Speter  SVN_ERR(svn_path_cstring_to_utf8(pabsolute, buffer, pool));
1610251881Speter  *pabsolute = svn_dirent_canonicalize(*pabsolute, pool);
1611251881Speter  return SVN_NO_ERROR;
1612251881Speter}
1613251881Speter
1614251881Speterconst char *
1615251881Spetersvn_uri_canonicalize(const char *uri, apr_pool_t *pool)
1616251881Speter{
1617251881Speter  return canonicalize(type_uri, uri, pool);
1618251881Speter}
1619251881Speter
1620251881Speterconst char *
1621251881Spetersvn_relpath_canonicalize(const char *relpath, apr_pool_t *pool)
1622251881Speter{
1623251881Speter  return canonicalize(type_relpath, relpath, pool);
1624251881Speter}
1625251881Speter
1626251881Speterconst char *
1627251881Spetersvn_dirent_canonicalize(const char *dirent, apr_pool_t *pool)
1628251881Speter{
1629251881Speter  const char *dst = canonicalize(type_dirent, dirent, pool);
1630251881Speter
1631251881Speter#ifdef SVN_USE_DOS_PATHS
1632251881Speter  /* Handle a specific case on Windows where path == "X:/". Here we have to
1633251881Speter     append the final '/', as svn_path_canonicalize will chop this of. */
1634251881Speter  if (((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
1635251881Speter        (dirent[0] >= 'a' && dirent[0] <= 'z')) &&
1636251881Speter        dirent[1] == ':' && dirent[2] == '/' &&
1637251881Speter        dst[3] == '\0')
1638251881Speter    {
1639251881Speter      char *dst_slash = apr_pcalloc(pool, 4);
1640251881Speter      dst_slash[0] = canonicalize_to_upper(dirent[0]);
1641251881Speter      dst_slash[1] = ':';
1642251881Speter      dst_slash[2] = '/';
1643251881Speter      dst_slash[3] = '\0';
1644251881Speter
1645251881Speter      return dst_slash;
1646251881Speter    }
1647251881Speter#endif /* SVN_USE_DOS_PATHS */
1648251881Speter
1649251881Speter  return dst;
1650251881Speter}
1651251881Speter
1652251881Spetersvn_boolean_t
1653251881Spetersvn_dirent_is_canonical(const char *dirent, apr_pool_t *scratch_pool)
1654251881Speter{
1655251881Speter  const char *ptr = dirent;
1656251881Speter  if (*ptr == '/')
1657251881Speter    {
1658251881Speter      ptr++;
1659251881Speter#ifdef SVN_USE_DOS_PATHS
1660251881Speter      /* Check for UNC paths */
1661251881Speter      if (*ptr == '/')
1662251881Speter        {
1663251881Speter          /* TODO: Scan hostname and sharename and fall back to part code */
1664251881Speter
1665251881Speter          /* ### Fall back to old implementation */
1666251881Speter          return (strcmp(dirent, svn_dirent_canonicalize(dirent, scratch_pool))
1667251881Speter                  == 0);
1668251881Speter        }
1669251881Speter#endif /* SVN_USE_DOS_PATHS */
1670251881Speter    }
1671251881Speter#ifdef SVN_USE_DOS_PATHS
1672251881Speter  else if (((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= 'A' && *ptr <= 'Z')) &&
1673251881Speter           (ptr[1] == ':'))
1674251881Speter    {
1675251881Speter      /* The only canonical drive names are "A:"..."Z:", no lower case */
1676251881Speter      if (*ptr < 'A' || *ptr > 'Z')
1677251881Speter        return FALSE;
1678251881Speter
1679251881Speter      ptr += 2;
1680251881Speter
1681251881Speter      if (*ptr == '/')
1682251881Speter        ptr++;
1683251881Speter    }
1684251881Speter#endif /* SVN_USE_DOS_PATHS */
1685251881Speter
1686251881Speter  return relpath_is_canonical(ptr);
1687251881Speter}
1688251881Speter
1689251881Speterstatic svn_boolean_t
1690251881Speterrelpath_is_canonical(const char *relpath)
1691251881Speter{
1692251881Speter  const char *ptr = relpath, *seg = relpath;
1693251881Speter
1694251881Speter  /* RELPATH is canonical if it has:
1695251881Speter   *  - no '.' segments
1696251881Speter   *  - no start and closing '/'
1697251881Speter   *  - no '//'
1698251881Speter   */
1699251881Speter
1700251881Speter  if (*relpath == '\0')
1701251881Speter    return TRUE;
1702251881Speter
1703251881Speter  if (*ptr == '/')
1704251881Speter    return FALSE;
1705251881Speter
1706251881Speter  /* Now validate the rest of the path. */
1707251881Speter  while(1)
1708251881Speter    {
1709251881Speter      apr_size_t seglen = ptr - seg;
1710251881Speter
1711251881Speter      if (seglen == 1 && *seg == '.')
1712251881Speter        return FALSE;  /*  /./   */
1713251881Speter
1714251881Speter      if (*ptr == '/' && *(ptr+1) == '/')
1715251881Speter        return FALSE;  /*  //    */
1716251881Speter
1717251881Speter      if (! *ptr && *(ptr - 1) == '/')
1718251881Speter        return FALSE;  /* foo/  */
1719251881Speter
1720251881Speter      if (! *ptr)
1721251881Speter        break;
1722251881Speter
1723251881Speter      if (*ptr == '/')
1724251881Speter        ptr++;
1725251881Speter      seg = ptr;
1726251881Speter
1727251881Speter      while (*ptr && (*ptr != '/'))
1728251881Speter        ptr++;
1729251881Speter    }
1730251881Speter
1731251881Speter  return TRUE;
1732251881Speter}
1733251881Speter
1734251881Spetersvn_boolean_t
1735251881Spetersvn_relpath_is_canonical(const char *relpath)
1736251881Speter{
1737251881Speter  return relpath_is_canonical(relpath);
1738251881Speter}
1739251881Speter
1740251881Spetersvn_boolean_t
1741251881Spetersvn_uri_is_canonical(const char *uri, apr_pool_t *scratch_pool)
1742251881Speter{
1743251881Speter  const char *ptr = uri, *seg = uri;
1744251881Speter  const char *schema_data = NULL;
1745251881Speter
1746251881Speter  /* URI is canonical if it has:
1747251881Speter   *  - lowercase URL scheme
1748251881Speter   *  - lowercase URL hostname
1749251881Speter   *  - no '.' segments
1750251881Speter   *  - no closing '/'
1751251881Speter   *  - no '//'
1752251881Speter   *  - uppercase hex-encoded pair digits ("%AB", not "%ab")
1753251881Speter   */
1754251881Speter
1755251881Speter  if (*uri == '\0')
1756251881Speter    return FALSE;
1757251881Speter
1758251881Speter  if (! svn_path_is_url(uri))
1759251881Speter    return FALSE;
1760251881Speter
1761251881Speter  /* Skip the scheme. */
1762251881Speter  while (*ptr && (*ptr != '/') && (*ptr != ':'))
1763251881Speter    ptr++;
1764251881Speter
1765251881Speter  /* No scheme?  No good. */
1766251881Speter  if (! (*ptr == ':' && *(ptr+1) == '/' && *(ptr+2) == '/'))
1767251881Speter    return FALSE;
1768251881Speter
1769251881Speter  /* Found a scheme, check that it's all lowercase. */
1770251881Speter  ptr = uri;
1771251881Speter  while (*ptr != ':')
1772251881Speter    {
1773251881Speter      if (*ptr >= 'A' && *ptr <= 'Z')
1774251881Speter        return FALSE;
1775251881Speter      ptr++;
1776251881Speter    }
1777251881Speter  /* Skip :// */
1778251881Speter  ptr += 3;
1779251881Speter
1780251881Speter  /* Scheme only?  That works. */
1781251881Speter  if (! *ptr)
1782251881Speter    return TRUE;
1783251881Speter
1784251881Speter  /* This might be the hostname */
1785251881Speter  seg = ptr;
1786251881Speter  while (*ptr && (*ptr != '/') && (*ptr != '@'))
1787251881Speter    ptr++;
1788251881Speter
1789251881Speter  if (*ptr == '@')
1790251881Speter    seg = ptr + 1;
1791251881Speter
1792251881Speter  /* Found a hostname, check that it's all lowercase. */
1793251881Speter  ptr = seg;
1794251881Speter
1795251881Speter  if (*ptr == '[')
1796251881Speter    {
1797251881Speter      ptr++;
1798251881Speter      while (*ptr == ':'
1799251881Speter             || (*ptr >= '0' && *ptr <= '9')
1800251881Speter             || (*ptr >= 'a' && *ptr <= 'f'))
1801251881Speter        {
1802251881Speter          ptr++;
1803251881Speter        }
1804251881Speter
1805251881Speter      if (*ptr != ']')
1806251881Speter        return FALSE;
1807251881Speter      ptr++;
1808251881Speter    }
1809251881Speter  else
1810251881Speter    while (*ptr && *ptr != '/' && *ptr != ':')
1811251881Speter      {
1812251881Speter        if (*ptr >= 'A' && *ptr <= 'Z')
1813251881Speter          return FALSE;
1814251881Speter        ptr++;
1815251881Speter      }
1816251881Speter
1817251881Speter  /* Found a portnumber */
1818251881Speter  if (*ptr == ':')
1819251881Speter    {
1820251881Speter      apr_int64_t port = 0;
1821251881Speter
1822251881Speter      ptr++;
1823251881Speter      schema_data = ptr;
1824251881Speter
1825251881Speter      while (*ptr >= '0' && *ptr <= '9')
1826251881Speter        {
1827251881Speter          port = 10 * port + (*ptr - '0');
1828251881Speter          ptr++;
1829251881Speter        }
1830251881Speter
1831251881Speter      if (ptr == schema_data)
1832251881Speter        return FALSE; /* Fail on "http://host:" */
1833251881Speter
1834251881Speter      if (*ptr && *ptr != '/')
1835251881Speter        return FALSE; /* Not a port number */
1836251881Speter
1837251881Speter      if (port == 80 && strncmp(uri, "http:", 5) == 0)
1838251881Speter        return FALSE;
1839251881Speter      else if (port == 443 && strncmp(uri, "https:", 6) == 0)
1840251881Speter        return FALSE;
1841251881Speter      else if (port == 3690 && strncmp(uri, "svn:", 4) == 0)
1842251881Speter        return FALSE;
1843251881Speter    }
1844251881Speter
1845251881Speter  schema_data = ptr;
1846251881Speter
1847251881Speter#ifdef SVN_USE_DOS_PATHS
1848251881Speter  if (schema_data && *ptr == '/')
1849251881Speter    {
1850251881Speter      /* If this is a file url, ptr now points to the third '/' in
1851251881Speter         file:///C:/path. Check that if we have such a URL the drive
1852251881Speter         letter is in uppercase. */
1853251881Speter      if (strncmp(uri, "file:", 5) == 0 &&
1854251881Speter          ! (*(ptr+1) >= 'A' && *(ptr+1) <= 'Z') &&
1855251881Speter          *(ptr+2) == ':')
1856251881Speter        return FALSE;
1857251881Speter    }
1858251881Speter#endif /* SVN_USE_DOS_PATHS */
1859251881Speter
1860251881Speter  /* Now validate the rest of the URI. */
1861257936Speter  seg = ptr;
1862257936Speter  while (*ptr && (*ptr != '/'))
1863257936Speter    ptr++;
1864251881Speter  while(1)
1865251881Speter    {
1866251881Speter      apr_size_t seglen = ptr - seg;
1867251881Speter
1868251881Speter      if (seglen == 1 && *seg == '.')
1869251881Speter        return FALSE;  /*  /./   */
1870251881Speter
1871251881Speter      if (*ptr == '/' && *(ptr+1) == '/')
1872251881Speter        return FALSE;  /*  //    */
1873251881Speter
1874251881Speter      if (! *ptr && *(ptr - 1) == '/' && ptr - 1 != uri)
1875251881Speter        return FALSE;  /* foo/  */
1876251881Speter
1877251881Speter      if (! *ptr)
1878251881Speter        break;
1879251881Speter
1880251881Speter      if (*ptr == '/')
1881251881Speter        ptr++;
1882257936Speter
1883251881Speter      seg = ptr;
1884251881Speter      while (*ptr && (*ptr != '/'))
1885251881Speter        ptr++;
1886251881Speter    }
1887251881Speter
1888251881Speter  ptr = schema_data;
1889251881Speter
1890251881Speter  while (*ptr)
1891251881Speter    {
1892251881Speter      if (*ptr == '%')
1893251881Speter        {
1894251881Speter          char digitz[3];
1895251881Speter          int val;
1896251881Speter
1897251881Speter          /* Can't usesvn_ctype_isxdigit() because lower case letters are
1898251881Speter             not in our canonical format */
1899251881Speter          if (((*(ptr+1) < '0' || *(ptr+1) > '9'))
1900251881Speter              && (*(ptr+1) < 'A' || *(ptr+1) > 'F'))
1901251881Speter            return FALSE;
1902251881Speter          else if (((*(ptr+2) < '0' || *(ptr+2) > '9'))
1903251881Speter                   && (*(ptr+2) < 'A' || *(ptr+2) > 'F'))
1904251881Speter            return FALSE;
1905251881Speter
1906251881Speter          digitz[0] = *(++ptr);
1907251881Speter          digitz[1] = *(++ptr);
1908251881Speter          digitz[2] = '\0';
1909251881Speter          val = (int)strtol(digitz, NULL, 16);
1910251881Speter
1911251881Speter          if (svn_uri__char_validity[val])
1912251881Speter            return FALSE; /* Should not have been escaped */
1913251881Speter        }
1914251881Speter      else if (*ptr != '/' && !svn_uri__char_validity[(unsigned char)*ptr])
1915251881Speter        return FALSE; /* Character should have been escaped */
1916251881Speter      ptr++;
1917251881Speter    }
1918251881Speter
1919251881Speter  return TRUE;
1920251881Speter}
1921251881Speter
1922251881Spetersvn_error_t *
1923251881Spetersvn_dirent_condense_targets(const char **pcommon,
1924251881Speter                            apr_array_header_t **pcondensed_targets,
1925251881Speter                            const apr_array_header_t *targets,
1926251881Speter                            svn_boolean_t remove_redundancies,
1927251881Speter                            apr_pool_t *result_pool,
1928251881Speter                            apr_pool_t *scratch_pool)
1929251881Speter{
1930251881Speter  int i, num_condensed = targets->nelts;
1931251881Speter  svn_boolean_t *removed;
1932251881Speter  apr_array_header_t *abs_targets;
1933251881Speter
1934251881Speter  /* Early exit when there's no data to work on. */
1935251881Speter  if (targets->nelts <= 0)
1936251881Speter    {
1937251881Speter      *pcommon = NULL;
1938251881Speter      if (pcondensed_targets)
1939251881Speter        *pcondensed_targets = NULL;
1940251881Speter      return SVN_NO_ERROR;
1941251881Speter    }
1942251881Speter
1943251881Speter  /* Get the absolute path of the first target. */
1944251881Speter  SVN_ERR(svn_dirent_get_absolute(pcommon,
1945251881Speter                                  APR_ARRAY_IDX(targets, 0, const char *),
1946251881Speter                                  scratch_pool));
1947251881Speter
1948251881Speter  /* Early exit when there's only one dirent to work on. */
1949251881Speter  if (targets->nelts == 1)
1950251881Speter    {
1951251881Speter      *pcommon = apr_pstrdup(result_pool, *pcommon);
1952251881Speter      if (pcondensed_targets)
1953251881Speter        *pcondensed_targets = apr_array_make(result_pool, 0,
1954251881Speter                                             sizeof(const char *));
1955251881Speter      return SVN_NO_ERROR;
1956251881Speter    }
1957251881Speter
1958251881Speter  /* Copy the targets array, but with absolute dirents instead of
1959251881Speter     relative.  Also, find the pcommon argument by finding what is
1960251881Speter     common in all of the absolute dirents. NOTE: This is not as
1961251881Speter     efficient as it could be.  The calculation of the basedir could
1962251881Speter     be done in the loop below, which would save some calls to
1963251881Speter     svn_dirent_get_longest_ancestor.  I decided to do it this way
1964251881Speter     because I thought it would be simpler, since this way, we don't
1965251881Speter     even do the loop if we don't need to condense the targets. */
1966251881Speter
1967251881Speter  removed = apr_pcalloc(scratch_pool, (targets->nelts *
1968251881Speter                                          sizeof(svn_boolean_t)));
1969251881Speter  abs_targets = apr_array_make(scratch_pool, targets->nelts,
1970251881Speter                               sizeof(const char *));
1971251881Speter
1972251881Speter  APR_ARRAY_PUSH(abs_targets, const char *) = *pcommon;
1973251881Speter
1974251881Speter  for (i = 1; i < targets->nelts; ++i)
1975251881Speter    {
1976251881Speter      const char *rel = APR_ARRAY_IDX(targets, i, const char *);
1977251881Speter      const char *absolute;
1978251881Speter      SVN_ERR(svn_dirent_get_absolute(&absolute, rel, scratch_pool));
1979251881Speter      APR_ARRAY_PUSH(abs_targets, const char *) = absolute;
1980251881Speter      *pcommon = svn_dirent_get_longest_ancestor(*pcommon, absolute,
1981251881Speter                                                 scratch_pool);
1982251881Speter    }
1983251881Speter
1984251881Speter  *pcommon = apr_pstrdup(result_pool, *pcommon);
1985251881Speter
1986251881Speter  if (pcondensed_targets != NULL)
1987251881Speter    {
1988251881Speter      size_t basedir_len;
1989251881Speter
1990251881Speter      if (remove_redundancies)
1991251881Speter        {
1992251881Speter          /* Find the common part of each pair of targets.  If
1993251881Speter             common part is equal to one of the dirents, the other
1994251881Speter             is a child of it, and can be removed.  If a target is
1995251881Speter             equal to *pcommon, it can also be removed. */
1996251881Speter
1997251881Speter          /* First pass: when one non-removed target is a child of
1998251881Speter             another non-removed target, remove the child. */
1999251881Speter          for (i = 0; i < abs_targets->nelts; ++i)
2000251881Speter            {
2001251881Speter              int j;
2002251881Speter
2003251881Speter              if (removed[i])
2004251881Speter                continue;
2005251881Speter
2006251881Speter              for (j = i + 1; j < abs_targets->nelts; ++j)
2007251881Speter                {
2008251881Speter                  const char *abs_targets_i;
2009251881Speter                  const char *abs_targets_j;
2010251881Speter                  const char *ancestor;
2011251881Speter
2012251881Speter                  if (removed[j])
2013251881Speter                    continue;
2014251881Speter
2015251881Speter                  abs_targets_i = APR_ARRAY_IDX(abs_targets, i, const char *);
2016251881Speter                  abs_targets_j = APR_ARRAY_IDX(abs_targets, j, const char *);
2017251881Speter
2018251881Speter                  ancestor = svn_dirent_get_longest_ancestor
2019251881Speter                    (abs_targets_i, abs_targets_j, scratch_pool);
2020251881Speter
2021251881Speter                  if (*ancestor == '\0')
2022251881Speter                    continue;
2023251881Speter
2024251881Speter                  if (strcmp(ancestor, abs_targets_i) == 0)
2025251881Speter                    {
2026251881Speter                      removed[j] = TRUE;
2027251881Speter                      num_condensed--;
2028251881Speter                    }
2029251881Speter                  else if (strcmp(ancestor, abs_targets_j) == 0)
2030251881Speter                    {
2031251881Speter                      removed[i] = TRUE;
2032251881Speter                      num_condensed--;
2033251881Speter                    }
2034251881Speter                }
2035251881Speter            }
2036251881Speter
2037251881Speter          /* Second pass: when a target is the same as *pcommon,
2038251881Speter             remove the target. */
2039251881Speter          for (i = 0; i < abs_targets->nelts; ++i)
2040251881Speter            {
2041251881Speter              const char *abs_targets_i = APR_ARRAY_IDX(abs_targets, i,
2042251881Speter                                                        const char *);
2043251881Speter
2044251881Speter              if ((strcmp(abs_targets_i, *pcommon) == 0) && (! removed[i]))
2045251881Speter                {
2046251881Speter                  removed[i] = TRUE;
2047251881Speter                  num_condensed--;
2048251881Speter                }
2049251881Speter            }
2050251881Speter        }
2051251881Speter
2052251881Speter      /* Now create the return array, and copy the non-removed items */
2053251881Speter      basedir_len = strlen(*pcommon);
2054251881Speter      *pcondensed_targets = apr_array_make(result_pool, num_condensed,
2055251881Speter                                           sizeof(const char *));
2056251881Speter
2057251881Speter      for (i = 0; i < abs_targets->nelts; ++i)
2058251881Speter        {
2059251881Speter          const char *rel_item = APR_ARRAY_IDX(abs_targets, i, const char *);
2060251881Speter
2061251881Speter          /* Skip this if it's been removed. */
2062251881Speter          if (removed[i])
2063251881Speter            continue;
2064251881Speter
2065251881Speter          /* If a common prefix was found, condensed_targets are given
2066251881Speter             relative to that prefix.  */
2067251881Speter          if (basedir_len > 0)
2068251881Speter            {
2069251881Speter              /* Only advance our pointer past a dirent separator if
2070251881Speter                 REL_ITEM isn't the same as *PCOMMON.
2071251881Speter
2072251881Speter                 If *PCOMMON is a root dirent, basedir_len will already
2073251881Speter                 include the closing '/', so never advance the pointer
2074251881Speter                 here.
2075251881Speter                 */
2076251881Speter              rel_item += basedir_len;
2077251881Speter              if (rel_item[0] &&
2078251881Speter                  ! svn_dirent_is_root(*pcommon, basedir_len))
2079251881Speter                rel_item++;
2080251881Speter            }
2081251881Speter
2082251881Speter          APR_ARRAY_PUSH(*pcondensed_targets, const char *)
2083251881Speter            = apr_pstrdup(result_pool, rel_item);
2084251881Speter        }
2085251881Speter    }
2086251881Speter
2087251881Speter  return SVN_NO_ERROR;
2088251881Speter}
2089251881Speter
2090251881Spetersvn_error_t *
2091251881Spetersvn_uri_condense_targets(const char **pcommon,
2092251881Speter                         apr_array_header_t **pcondensed_targets,
2093251881Speter                         const apr_array_header_t *targets,
2094251881Speter                         svn_boolean_t remove_redundancies,
2095251881Speter                         apr_pool_t *result_pool,
2096251881Speter                         apr_pool_t *scratch_pool)
2097251881Speter{
2098251881Speter  int i, num_condensed = targets->nelts;
2099251881Speter  apr_array_header_t *uri_targets;
2100251881Speter  svn_boolean_t *removed;
2101251881Speter
2102251881Speter  /* Early exit when there's no data to work on. */
2103251881Speter  if (targets->nelts <= 0)
2104251881Speter    {
2105251881Speter      *pcommon = NULL;
2106251881Speter      if (pcondensed_targets)
2107251881Speter        *pcondensed_targets = NULL;
2108251881Speter      return SVN_NO_ERROR;
2109251881Speter    }
2110251881Speter
2111251881Speter  *pcommon = svn_uri_canonicalize(APR_ARRAY_IDX(targets, 0, const char *),
2112251881Speter                                  scratch_pool);
2113251881Speter
2114251881Speter  /* Early exit when there's only one uri to work on. */
2115251881Speter  if (targets->nelts == 1)
2116251881Speter    {
2117251881Speter      *pcommon = apr_pstrdup(result_pool, *pcommon);
2118251881Speter      if (pcondensed_targets)
2119251881Speter        *pcondensed_targets = apr_array_make(result_pool, 0,
2120251881Speter                                             sizeof(const char *));
2121251881Speter      return SVN_NO_ERROR;
2122251881Speter    }
2123251881Speter
2124251881Speter  /* Find the pcommon argument by finding what is common in all of the
2125251881Speter     uris. NOTE: This is not as efficient as it could be.  The calculation
2126251881Speter     of the basedir could be done in the loop below, which would
2127251881Speter     save some calls to svn_uri_get_longest_ancestor.  I decided to do it
2128251881Speter     this way because I thought it would be simpler, since this way, we don't
2129251881Speter     even do the loop if we don't need to condense the targets. */
2130251881Speter
2131251881Speter  removed = apr_pcalloc(scratch_pool, (targets->nelts *
2132251881Speter                                          sizeof(svn_boolean_t)));
2133251881Speter  uri_targets = apr_array_make(scratch_pool, targets->nelts,
2134251881Speter                               sizeof(const char *));
2135251881Speter
2136251881Speter  APR_ARRAY_PUSH(uri_targets, const char *) = *pcommon;
2137251881Speter
2138251881Speter  for (i = 1; i < targets->nelts; ++i)
2139251881Speter    {
2140251881Speter      const char *uri = svn_uri_canonicalize(
2141251881Speter                           APR_ARRAY_IDX(targets, i, const char *),
2142251881Speter                           scratch_pool);
2143251881Speter      APR_ARRAY_PUSH(uri_targets, const char *) = uri;
2144251881Speter
2145251881Speter      /* If the commonmost ancestor so far is empty, there's no point
2146251881Speter         in continuing to search for a common ancestor at all.  But
2147251881Speter         we'll keep looping for the sake of canonicalizing the
2148251881Speter         targets, I suppose.  */
2149251881Speter      if (**pcommon != '\0')
2150251881Speter        *pcommon = svn_uri_get_longest_ancestor(*pcommon, uri,
2151251881Speter                                                scratch_pool);
2152251881Speter    }
2153251881Speter
2154251881Speter  *pcommon = apr_pstrdup(result_pool, *pcommon);
2155251881Speter
2156251881Speter  if (pcondensed_targets != NULL)
2157251881Speter    {
2158251881Speter      size_t basedir_len;
2159251881Speter
2160251881Speter      if (remove_redundancies)
2161251881Speter        {
2162251881Speter          /* Find the common part of each pair of targets.  If
2163251881Speter             common part is equal to one of the dirents, the other
2164251881Speter             is a child of it, and can be removed.  If a target is
2165251881Speter             equal to *pcommon, it can also be removed. */
2166251881Speter
2167251881Speter          /* First pass: when one non-removed target is a child of
2168251881Speter             another non-removed target, remove the child. */
2169251881Speter          for (i = 0; i < uri_targets->nelts; ++i)
2170251881Speter            {
2171251881Speter              int j;
2172251881Speter
2173251881Speter              if (removed[i])
2174251881Speter                continue;
2175251881Speter
2176251881Speter              for (j = i + 1; j < uri_targets->nelts; ++j)
2177251881Speter                {
2178251881Speter                  const char *uri_i;
2179251881Speter                  const char *uri_j;
2180251881Speter                  const char *ancestor;
2181251881Speter
2182251881Speter                  if (removed[j])
2183251881Speter                    continue;
2184251881Speter
2185251881Speter                  uri_i = APR_ARRAY_IDX(uri_targets, i, const char *);
2186251881Speter                  uri_j = APR_ARRAY_IDX(uri_targets, j, const char *);
2187251881Speter
2188251881Speter                  ancestor = svn_uri_get_longest_ancestor(uri_i,
2189251881Speter                                                          uri_j,
2190251881Speter                                                          scratch_pool);
2191251881Speter
2192251881Speter                  if (*ancestor == '\0')
2193251881Speter                    continue;
2194251881Speter
2195251881Speter                  if (strcmp(ancestor, uri_i) == 0)
2196251881Speter                    {
2197251881Speter                      removed[j] = TRUE;
2198251881Speter                      num_condensed--;
2199251881Speter                    }
2200251881Speter                  else if (strcmp(ancestor, uri_j) == 0)
2201251881Speter                    {
2202251881Speter                      removed[i] = TRUE;
2203251881Speter                      num_condensed--;
2204251881Speter                    }
2205251881Speter                }
2206251881Speter            }
2207251881Speter
2208251881Speter          /* Second pass: when a target is the same as *pcommon,
2209251881Speter             remove the target. */
2210251881Speter          for (i = 0; i < uri_targets->nelts; ++i)
2211251881Speter            {
2212251881Speter              const char *uri_targets_i = APR_ARRAY_IDX(uri_targets, i,
2213251881Speter                                                        const char *);
2214251881Speter
2215251881Speter              if ((strcmp(uri_targets_i, *pcommon) == 0) && (! removed[i]))
2216251881Speter                {
2217251881Speter                  removed[i] = TRUE;
2218251881Speter                  num_condensed--;
2219251881Speter                }
2220251881Speter            }
2221251881Speter        }
2222251881Speter
2223251881Speter      /* Now create the return array, and copy the non-removed items */
2224251881Speter      basedir_len = strlen(*pcommon);
2225251881Speter      *pcondensed_targets = apr_array_make(result_pool, num_condensed,
2226251881Speter                                           sizeof(const char *));
2227251881Speter
2228251881Speter      for (i = 0; i < uri_targets->nelts; ++i)
2229251881Speter        {
2230251881Speter          const char *rel_item = APR_ARRAY_IDX(uri_targets, i, const char *);
2231251881Speter
2232251881Speter          /* Skip this if it's been removed. */
2233251881Speter          if (removed[i])
2234251881Speter            continue;
2235251881Speter
2236251881Speter          /* If a common prefix was found, condensed_targets are given
2237251881Speter             relative to that prefix.  */
2238251881Speter          if (basedir_len > 0)
2239251881Speter            {
2240251881Speter              /* Only advance our pointer past a dirent separator if
2241251881Speter                 REL_ITEM isn't the same as *PCOMMON.
2242251881Speter
2243251881Speter                 If *PCOMMON is a root dirent, basedir_len will already
2244251881Speter                 include the closing '/', so never advance the pointer
2245251881Speter                 here.
2246251881Speter                 */
2247251881Speter              rel_item += basedir_len;
2248251881Speter              if ((rel_item[0] == '/') ||
2249251881Speter                  (rel_item[0] && !svn_uri_is_root(*pcommon, basedir_len)))
2250251881Speter                {
2251251881Speter                  rel_item++;
2252251881Speter                }
2253251881Speter            }
2254251881Speter
2255251881Speter          APR_ARRAY_PUSH(*pcondensed_targets, const char *)
2256251881Speter            = svn_path_uri_decode(rel_item, result_pool);
2257251881Speter        }
2258251881Speter    }
2259251881Speter
2260251881Speter  return SVN_NO_ERROR;
2261251881Speter}
2262251881Speter
2263251881Spetersvn_error_t *
2264251881Spetersvn_dirent_is_under_root(svn_boolean_t *under_root,
2265251881Speter                         const char **result_path,
2266251881Speter                         const char *base_path,
2267251881Speter                         const char *path,
2268251881Speter                         apr_pool_t *result_pool)
2269251881Speter{
2270251881Speter  apr_status_t status;
2271251881Speter  char *full_path;
2272251881Speter
2273251881Speter  *under_root = FALSE;
2274251881Speter  if (result_path)
2275251881Speter    *result_path = NULL;
2276251881Speter
2277251881Speter  status = apr_filepath_merge(&full_path,
2278251881Speter                              base_path,
2279251881Speter                              path,
2280251881Speter                              APR_FILEPATH_NOTABOVEROOT
2281251881Speter                              | APR_FILEPATH_SECUREROOTTEST,
2282251881Speter                              result_pool);
2283251881Speter
2284251881Speter  if (status == APR_SUCCESS)
2285251881Speter    {
2286251881Speter      if (result_path)
2287251881Speter        *result_path = svn_dirent_canonicalize(full_path, result_pool);
2288251881Speter      *under_root = TRUE;
2289251881Speter      return SVN_NO_ERROR;
2290251881Speter    }
2291251881Speter  else if (status == APR_EABOVEROOT)
2292251881Speter    {
2293251881Speter      *under_root = FALSE;
2294251881Speter      return SVN_NO_ERROR;
2295251881Speter    }
2296251881Speter
2297251881Speter  return svn_error_wrap_apr(status, NULL);
2298251881Speter}
2299251881Speter
2300251881Spetersvn_error_t *
2301251881Spetersvn_uri_get_dirent_from_file_url(const char **dirent,
2302251881Speter                                 const char *url,
2303251881Speter                                 apr_pool_t *pool)
2304251881Speter{
2305251881Speter  const char *hostname, *path;
2306251881Speter
2307251881Speter  SVN_ERR_ASSERT(svn_uri_is_canonical(url, pool));
2308251881Speter
2309251881Speter  /* Verify that the URL is well-formed (loosely) */
2310251881Speter
2311251881Speter  /* First, check for the "file://" prefix. */
2312251881Speter  if (strncmp(url, "file://", 7) != 0)
2313251881Speter    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2314251881Speter                             _("Local URL '%s' does not contain 'file://' "
2315251881Speter                               "prefix"), url);
2316251881Speter
2317251881Speter  /* Find the HOSTNAME portion and the PATH portion of the URL.  The host
2318251881Speter     name is between the "file://" prefix and the next occurence of '/'.  We
2319251881Speter     are considering everything from that '/' until the end of the URL to be
2320251881Speter     the absolute path portion of the URL.
2321251881Speter     If we got just "file://", treat it the same as "file:///". */
2322251881Speter  hostname = url + 7;
2323251881Speter  path = strchr(hostname, '/');
2324251881Speter  if (path)
2325251881Speter    hostname = apr_pstrmemdup(pool, hostname, path - hostname);
2326251881Speter  else
2327251881Speter    path = "/";
2328251881Speter
2329251881Speter  /* URI-decode HOSTNAME, and set it to NULL if it is "" or "localhost". */
2330251881Speter  if (*hostname == '\0')
2331251881Speter    hostname = NULL;
2332251881Speter  else
2333251881Speter    {
2334251881Speter      hostname = svn_path_uri_decode(hostname, pool);
2335251881Speter      if (strcmp(hostname, "localhost") == 0)
2336251881Speter        hostname = NULL;
2337251881Speter    }
2338251881Speter
2339251881Speter  /* Duplicate the URL, starting at the top of the path.
2340251881Speter     At the same time, we URI-decode the path. */
2341251881Speter#ifdef SVN_USE_DOS_PATHS
2342251881Speter  /* On Windows, we'll typically have to skip the leading / if the
2343251881Speter     path starts with a drive letter.  Like most Web browsers, We
2344251881Speter     support two variants of this scheme:
2345251881Speter
2346251881Speter         file:///X:/path    and
2347251881Speter         file:///X|/path
2348251881Speter
2349251881Speter    Note that, at least on WinNT and above,  file:////./X:/path  will
2350251881Speter    also work, so we must make sure the transformation doesn't break
2351251881Speter    that, and  file:///path  (that looks within the current drive
2352251881Speter    only) should also keep working.
2353251881Speter    If we got a non-empty hostname other than localhost, we convert this
2354251881Speter    into an UNC path.  In this case, we obviously don't strip the slash
2355251881Speter    even if the path looks like it starts with a drive letter.
2356251881Speter  */
2357251881Speter  {
2358251881Speter    static const char valid_drive_letters[] =
2359251881Speter      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2360251881Speter    /* Casting away const! */
2361251881Speter    char *dup_path = (char *)svn_path_uri_decode(path, pool);
2362251881Speter
2363251881Speter    /* This check assumes ':' and '|' are already decoded! */
2364251881Speter    if (!hostname && dup_path[1] && strchr(valid_drive_letters, dup_path[1])
2365251881Speter        && (dup_path[2] == ':' || dup_path[2] == '|'))
2366251881Speter      {
2367251881Speter        /* Skip the leading slash. */
2368251881Speter        ++dup_path;
2369251881Speter
2370251881Speter        if (dup_path[1] == '|')
2371251881Speter          dup_path[1] = ':';
2372251881Speter
2373251881Speter        if (dup_path[2] == '/' || dup_path[2] == '\0')
2374251881Speter          {
2375251881Speter            if (dup_path[2] == '\0')
2376251881Speter              {
2377251881Speter                /* A valid dirent for the driveroot must be like "C:/" instead of
2378251881Speter                   just "C:" or svn_dirent_join() will use the current directory
2379251881Speter                   on the drive instead */
2380251881Speter                char *new_path = apr_pcalloc(pool, 4);
2381251881Speter                new_path[0] = dup_path[0];
2382251881Speter                new_path[1] = ':';
2383251881Speter                new_path[2] = '/';
2384251881Speter                new_path[3] = '\0';
2385251881Speter                dup_path = new_path;
2386251881Speter              }
2387251881Speter          }
2388251881Speter      }
2389251881Speter    if (hostname)
2390251881Speter      {
2391251881Speter        if (dup_path[0] == '/' && dup_path[1] == '\0')
2392251881Speter          return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2393251881Speter                                   _("Local URL '%s' contains only a hostname, "
2394251881Speter                                     "no path"), url);
2395251881Speter
2396251881Speter        /* We still know that the path starts with a slash. */
2397251881Speter        *dirent = apr_pstrcat(pool, "//", hostname, dup_path, NULL);
2398251881Speter      }
2399251881Speter    else
2400251881Speter      *dirent = dup_path;
2401251881Speter  }
2402251881Speter#else /* !SVN_USE_DOS_PATHS */
2403251881Speter  /* Currently, the only hostnames we are allowing on non-Win32 platforms
2404251881Speter     are the empty string and 'localhost'. */
2405251881Speter  if (hostname)
2406251881Speter    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2407251881Speter                             _("Local URL '%s' contains unsupported hostname"),
2408251881Speter                             url);
2409251881Speter
2410251881Speter  *dirent = svn_path_uri_decode(path, pool);
2411251881Speter#endif /* SVN_USE_DOS_PATHS */
2412251881Speter  return SVN_NO_ERROR;
2413251881Speter}
2414251881Speter
2415251881Spetersvn_error_t *
2416251881Spetersvn_uri_get_file_url_from_dirent(const char **url,
2417251881Speter                                 const char *dirent,
2418251881Speter                                 apr_pool_t *pool)
2419251881Speter{
2420251881Speter  assert(svn_dirent_is_canonical(dirent, pool));
2421251881Speter
2422251881Speter  SVN_ERR(svn_dirent_get_absolute(&dirent, dirent, pool));
2423251881Speter
2424251881Speter  dirent = svn_path_uri_encode(dirent, pool);
2425251881Speter
2426251881Speter#ifndef SVN_USE_DOS_PATHS
2427251881Speter  if (dirent[0] == '/' && dirent[1] == '\0')
2428251881Speter    dirent = NULL; /* "file://" is the canonical form of "file:///" */
2429251881Speter
2430251881Speter  *url = apr_pstrcat(pool, "file://", dirent, (char *)NULL);
2431251881Speter#else
2432251881Speter  if (dirent[0] == '/')
2433251881Speter    {
2434251881Speter      /* Handle UNC paths //server/share -> file://server/share */
2435251881Speter      assert(dirent[1] == '/'); /* Expect UNC, not non-absolute */
2436251881Speter
2437251881Speter      *url = apr_pstrcat(pool, "file:", dirent, NULL);
2438251881Speter    }
2439251881Speter  else
2440251881Speter    {
2441251881Speter      char *uri = apr_pstrcat(pool, "file:///", dirent, NULL);
2442251881Speter      apr_size_t len = 8 /* strlen("file:///") */ + strlen(dirent);
2443251881Speter
2444251881Speter      /* "C:/" is a canonical dirent on Windows,
2445251881Speter         but "file:///C:/" is not a canonical uri */
2446251881Speter      if (uri[len-1] == '/')
2447251881Speter        uri[len-1] = '\0';
2448251881Speter
2449251881Speter      *url = uri;
2450251881Speter    }
2451251881Speter#endif
2452251881Speter
2453251881Speter  return SVN_NO_ERROR;
2454251881Speter}
2455251881Speter
2456251881Speter
2457251881Speter
2458251881Speter/* -------------- The fspath API (see private/svn_fspath.h) -------------- */
2459251881Speter
2460251881Spetersvn_boolean_t
2461251881Spetersvn_fspath__is_canonical(const char *fspath)
2462251881Speter{
2463251881Speter  return fspath[0] == '/' && relpath_is_canonical(fspath + 1);
2464251881Speter}
2465251881Speter
2466251881Speter
2467251881Speterconst char *
2468251881Spetersvn_fspath__canonicalize(const char *fspath,
2469251881Speter                         apr_pool_t *pool)
2470251881Speter{
2471251881Speter  if ((fspath[0] == '/') && (fspath[1] == '\0'))
2472251881Speter    return "/";
2473251881Speter
2474251881Speter  return apr_pstrcat(pool, "/", svn_relpath_canonicalize(fspath, pool),
2475251881Speter                     (char *)NULL);
2476251881Speter}
2477251881Speter
2478251881Speter
2479251881Spetersvn_boolean_t
2480251881Spetersvn_fspath__is_root(const char *fspath, apr_size_t len)
2481251881Speter{
2482251881Speter  /* directory is root if it's equal to '/' */
2483251881Speter  return (len == 1 && fspath[0] == '/');
2484251881Speter}
2485251881Speter
2486251881Speter
2487251881Speterconst char *
2488251881Spetersvn_fspath__skip_ancestor(const char *parent_fspath,
2489251881Speter                          const char *child_fspath)
2490251881Speter{
2491251881Speter  assert(svn_fspath__is_canonical(parent_fspath));
2492251881Speter  assert(svn_fspath__is_canonical(child_fspath));
2493251881Speter
2494251881Speter  return svn_relpath_skip_ancestor(parent_fspath + 1, child_fspath + 1);
2495251881Speter}
2496251881Speter
2497251881Speter
2498251881Speterconst char *
2499251881Spetersvn_fspath__dirname(const char *fspath,
2500251881Speter                    apr_pool_t *pool)
2501251881Speter{
2502251881Speter  assert(svn_fspath__is_canonical(fspath));
2503251881Speter
2504251881Speter  if (fspath[0] == '/' && fspath[1] == '\0')
2505251881Speter    return apr_pstrdup(pool, fspath);
2506251881Speter  else
2507251881Speter    return apr_pstrcat(pool, "/", svn_relpath_dirname(fspath + 1, pool),
2508251881Speter                       (char *)NULL);
2509251881Speter}
2510251881Speter
2511251881Speter
2512251881Speterconst char *
2513251881Spetersvn_fspath__basename(const char *fspath,
2514251881Speter                     apr_pool_t *pool)
2515251881Speter{
2516251881Speter  const char *result;
2517251881Speter  assert(svn_fspath__is_canonical(fspath));
2518251881Speter
2519251881Speter  result = svn_relpath_basename(fspath + 1, pool);
2520251881Speter
2521251881Speter  assert(strchr(result, '/') == NULL);
2522251881Speter  return result;
2523251881Speter}
2524251881Speter
2525251881Spetervoid
2526251881Spetersvn_fspath__split(const char **dirpath,
2527251881Speter                  const char **base_name,
2528251881Speter                  const char *fspath,
2529251881Speter                  apr_pool_t *result_pool)
2530251881Speter{
2531251881Speter  assert(dirpath != base_name);
2532251881Speter
2533251881Speter  if (dirpath)
2534251881Speter    *dirpath = svn_fspath__dirname(fspath, result_pool);
2535251881Speter
2536251881Speter  if (base_name)
2537251881Speter    *base_name = svn_fspath__basename(fspath, result_pool);
2538251881Speter}
2539251881Speter
2540251881Speterchar *
2541251881Spetersvn_fspath__join(const char *fspath,
2542251881Speter                 const char *relpath,
2543251881Speter                 apr_pool_t *result_pool)
2544251881Speter{
2545251881Speter  char *result;
2546251881Speter  assert(svn_fspath__is_canonical(fspath));
2547251881Speter  assert(svn_relpath_is_canonical(relpath));
2548251881Speter
2549251881Speter  if (relpath[0] == '\0')
2550251881Speter    result = apr_pstrdup(result_pool, fspath);
2551251881Speter  else if (fspath[1] == '\0')
2552251881Speter    result = apr_pstrcat(result_pool, "/", relpath, (char *)NULL);
2553251881Speter  else
2554251881Speter    result = apr_pstrcat(result_pool, fspath, "/", relpath, (char *)NULL);
2555251881Speter
2556251881Speter  assert(svn_fspath__is_canonical(result));
2557251881Speter  return result;
2558251881Speter}
2559251881Speter
2560251881Speterchar *
2561251881Spetersvn_fspath__get_longest_ancestor(const char *fspath1,
2562251881Speter                                 const char *fspath2,
2563251881Speter                                 apr_pool_t *result_pool)
2564251881Speter{
2565251881Speter  char *result;
2566251881Speter  assert(svn_fspath__is_canonical(fspath1));
2567251881Speter  assert(svn_fspath__is_canonical(fspath2));
2568251881Speter
2569251881Speter  result = apr_pstrcat(result_pool, "/",
2570251881Speter                       svn_relpath_get_longest_ancestor(fspath1 + 1,
2571251881Speter                                                        fspath2 + 1,
2572251881Speter                                                        result_pool),
2573251881Speter                       (char *)NULL);
2574251881Speter
2575251881Speter  assert(svn_fspath__is_canonical(result));
2576251881Speter  return result;
2577251881Speter}
2578251881Speter
2579251881Speter
2580251881Speter
2581251881Speter
2582251881Speter/* -------------- The urlpath API (see private/svn_fspath.h) ------------- */
2583251881Speter
2584251881Speterconst char *
2585251881Spetersvn_urlpath__canonicalize(const char *uri,
2586251881Speter                          apr_pool_t *pool)
2587251881Speter{
2588251881Speter  if (svn_path_is_url(uri))
2589251881Speter    {
2590251881Speter      uri = svn_uri_canonicalize(uri, pool);
2591251881Speter    }
2592251881Speter  else
2593251881Speter    {
2594251881Speter      uri = svn_fspath__canonicalize(uri, pool);
2595251881Speter      /* Do a little dance to normalize hex encoding. */
2596251881Speter      uri = svn_path_uri_decode(uri, pool);
2597251881Speter      uri = svn_path_uri_encode(uri, pool);
2598251881Speter    }
2599251881Speter  return uri;
2600251881Speter}
2601269833Speter
2602269833Speter
2603269833Speter/* -------------- The cert API (see private/svn_cert.h) ------------- */
2604269833Speter
2605269833Spetersvn_boolean_t
2606269833Spetersvn_cert__match_dns_identity(svn_string_t *pattern, svn_string_t *hostname)
2607269833Speter{
2608269833Speter  apr_size_t pattern_pos = 0, hostname_pos = 0;
2609269833Speter
2610269833Speter  /* support leading wildcards that composed of the only character in the
2611269833Speter   * left-most label. */
2612269833Speter  if (pattern->len >= 2 &&
2613269833Speter      pattern->data[pattern_pos] == '*' &&
2614269833Speter      pattern->data[pattern_pos + 1] == '.')
2615269833Speter    {
2616269833Speter      while (hostname_pos < hostname->len &&
2617269833Speter             hostname->data[hostname_pos] != '.')
2618269833Speter        {
2619269833Speter          hostname_pos++;
2620269833Speter        }
2621269833Speter      /* Assume that the wildcard must match something.  Rule 2 says
2622269833Speter       * that *.example.com should not match example.com.  If the wildcard
2623269833Speter       * ends up not matching anything then it matches .example.com which
2624269833Speter       * seems to be essentially the same as just example.com */
2625269833Speter      if (hostname_pos == 0)
2626269833Speter        return FALSE;
2627269833Speter
2628269833Speter      pattern_pos++;
2629269833Speter    }
2630269833Speter
2631269833Speter  while (pattern_pos < pattern->len && hostname_pos < hostname->len)
2632269833Speter    {
2633269833Speter      char pattern_c = pattern->data[pattern_pos];
2634269833Speter      char hostname_c = hostname->data[hostname_pos];
2635269833Speter
2636269833Speter      /* fold case as described in RFC 4343.
2637269833Speter       * Note: We actually convert to lowercase, since our URI
2638269833Speter       * canonicalization code converts to lowercase and generally
2639269833Speter       * most certs are issued with lowercase DNS names, meaning
2640269833Speter       * this avoids the fold operation in most cases.  The RFC
2641269833Speter       * suggests the opposite transformation, but doesn't require
2642269833Speter       * any specific implementation in any case.  It is critical
2643269833Speter       * that this folding be locale independent so you can't use
2644269833Speter       * tolower(). */
2645269833Speter      pattern_c = canonicalize_to_lower(pattern_c);
2646269833Speter      hostname_c = canonicalize_to_lower(hostname_c);
2647269833Speter
2648269833Speter      if (pattern_c != hostname_c)
2649269833Speter        {
2650269833Speter          /* doesn't match */
2651269833Speter          return FALSE;
2652269833Speter        }
2653269833Speter      else
2654269833Speter        {
2655269833Speter          /* characters match so skip both */
2656269833Speter          pattern_pos++;
2657269833Speter          hostname_pos++;
2658269833Speter        }
2659269833Speter    }
2660269833Speter
2661269833Speter  /* ignore a trailing period on the hostname since this has no effect on the
2662269833Speter   * security of the matching.  See the following for the long explanation as
2663269833Speter   * to why:
2664269833Speter   * https://bugzilla.mozilla.org/show_bug.cgi?id=134402#c28
2665269833Speter   */
2666269833Speter  if (pattern_pos == pattern->len &&
2667269833Speter      hostname_pos == hostname->len - 1 &&
2668269833Speter      hostname->data[hostname_pos] == '.')
2669269833Speter    hostname_pos++;
2670269833Speter
2671269833Speter  if (pattern_pos != pattern->len || hostname_pos != hostname->len)
2672269833Speter    {
2673269833Speter      /* end didn't match */
2674269833Speter      return FALSE;
2675269833Speter    }
2676269833Speter
2677269833Speter  return TRUE;
2678269833Speter}
2679