1251881Speter/*
2251881Speter * paths.c:   a path manipulation library using svn_stringbuf_t
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
29251881Speter#include <apr_file_info.h>
30251881Speter#include <apr_lib.h>
31251881Speter#include <apr_uri.h>
32251881Speter
33251881Speter#include "svn_string.h"
34251881Speter#include "svn_dirent_uri.h"
35251881Speter#include "svn_path.h"
36251881Speter#include "svn_private_config.h"         /* for SVN_PATH_LOCAL_SEPARATOR */
37251881Speter#include "svn_utf.h"
38251881Speter#include "svn_io.h"                     /* for svn_io_stat() */
39251881Speter#include "svn_ctype.h"
40251881Speter
41251881Speter#include "dirent_uri.h"
42251881Speter
43251881Speter
44251881Speter/* The canonical empty path.  Can this be changed?  Well, change the empty
45251881Speter   test below and the path library will work, not so sure about the fs/wc
46251881Speter   libraries. */
47251881Speter#define SVN_EMPTY_PATH ""
48251881Speter
49251881Speter/* TRUE if s is the canonical empty path, FALSE otherwise */
50251881Speter#define SVN_PATH_IS_EMPTY(s) ((s)[0] == '\0')
51251881Speter
52251881Speter/* TRUE if s,n is the platform's empty path ("."), FALSE otherwise. Can
53251881Speter   this be changed?  Well, the path library will work, not so sure about
54251881Speter   the OS! */
55251881Speter#define SVN_PATH_IS_PLATFORM_EMPTY(s,n) ((n) == 1 && (s)[0] == '.')
56251881Speter
57251881Speter
58251881Speter
59251881Speter
60251881Speter#ifndef NDEBUG
61251881Speter/* This function is an approximation of svn_path_is_canonical.
62251881Speter * It is supposed to be used in functions that do not have access
63251881Speter * to a pool, but still want to assert that a path is canonical.
64251881Speter *
65251881Speter * PATH with length LEN is assumed to be canonical if it isn't
66251881Speter * the platform's empty path (see definition of SVN_PATH_IS_PLATFORM_EMPTY),
67251881Speter * and does not contain "/./", and any one of the following
68251881Speter * conditions is also met:
69251881Speter *
70251881Speter *  1. PATH has zero length
71251881Speter *  2. PATH is the root directory (what exactly a root directory is
72251881Speter *                                depends on the platform)
73251881Speter *  3. PATH is not a root directory and does not end with '/'
74251881Speter *
75251881Speter * If possible, please use svn_path_is_canonical instead.
76251881Speter */
77251881Speterstatic svn_boolean_t
78251881Speteris_canonical(const char *path,
79251881Speter             apr_size_t len)
80251881Speter{
81251881Speter  return (! SVN_PATH_IS_PLATFORM_EMPTY(path, len)
82251881Speter          && strstr(path, "/./") == NULL
83251881Speter          && (len == 0
84251881Speter              || (len == 1 && path[0] == '/')
85251881Speter              || (path[len-1] != '/')
86251881Speter#if defined(WIN32) || defined(__CYGWIN__)
87251881Speter              || svn_dirent_is_root(path, len)
88251881Speter#endif
89251881Speter              ));
90251881Speter}
91251881Speter#endif
92251881Speter
93251881Speter
94251881Speter/* functionality of svn_path_is_canonical but without the deprecation */
95251881Speterstatic svn_boolean_t
96251881Spetersvn_path_is_canonical_internal(const char *path, apr_pool_t *pool)
97251881Speter{
98251881Speter  return svn_uri_is_canonical(path, pool) ||
99251881Speter      svn_dirent_is_canonical(path, pool) ||
100251881Speter      svn_relpath_is_canonical(path);
101251881Speter}
102251881Speter
103251881Spetersvn_boolean_t
104251881Spetersvn_path_is_canonical(const char *path, apr_pool_t *pool)
105251881Speter{
106251881Speter  return svn_path_is_canonical_internal(path, pool);
107251881Speter}
108251881Speter
109251881Speter/* functionality of svn_path_join but without the deprecation */
110251881Speterstatic char *
111251881Spetersvn_path_join_internal(const char *base,
112251881Speter                       const char *component,
113251881Speter                       apr_pool_t *pool)
114251881Speter{
115251881Speter  apr_size_t blen = strlen(base);
116251881Speter  apr_size_t clen = strlen(component);
117251881Speter  char *path;
118251881Speter
119251881Speter  assert(svn_path_is_canonical_internal(base, pool));
120251881Speter  assert(svn_path_is_canonical_internal(component, pool));
121251881Speter
122251881Speter  /* If the component is absolute, then return it.  */
123251881Speter  if (*component == '/')
124251881Speter    return apr_pmemdup(pool, component, clen + 1);
125251881Speter
126251881Speter  /* If either is empty return the other */
127251881Speter  if (SVN_PATH_IS_EMPTY(base))
128251881Speter    return apr_pmemdup(pool, component, clen + 1);
129251881Speter  if (SVN_PATH_IS_EMPTY(component))
130251881Speter    return apr_pmemdup(pool, base, blen + 1);
131251881Speter
132251881Speter  if (blen == 1 && base[0] == '/')
133251881Speter    blen = 0; /* Ignore base, just return separator + component */
134251881Speter
135251881Speter  /* Construct the new, combined path. */
136251881Speter  path = apr_palloc(pool, blen + 1 + clen + 1);
137251881Speter  memcpy(path, base, blen);
138251881Speter  path[blen] = '/';
139251881Speter  memcpy(path + blen + 1, component, clen + 1);
140251881Speter
141251881Speter  return path;
142251881Speter}
143251881Speter
144251881Speterchar *svn_path_join(const char *base,
145251881Speter                    const char *component,
146251881Speter                    apr_pool_t *pool)
147251881Speter{
148251881Speter  return svn_path_join_internal(base, component, pool);
149251881Speter}
150251881Speter
151251881Speterchar *svn_path_join_many(apr_pool_t *pool, const char *base, ...)
152251881Speter{
153251881Speter#define MAX_SAVED_LENGTHS 10
154251881Speter  apr_size_t saved_lengths[MAX_SAVED_LENGTHS];
155251881Speter  apr_size_t total_len;
156251881Speter  int nargs;
157251881Speter  va_list va;
158251881Speter  const char *s;
159251881Speter  apr_size_t len;
160251881Speter  char *path;
161251881Speter  char *p;
162251881Speter  svn_boolean_t base_is_empty = FALSE, base_is_root = FALSE;
163251881Speter  int base_arg = 0;
164251881Speter
165251881Speter  total_len = strlen(base);
166251881Speter
167251881Speter  assert(svn_path_is_canonical_internal(base, pool));
168251881Speter
169251881Speter  if (total_len == 1 && *base == '/')
170251881Speter    base_is_root = TRUE;
171251881Speter  else if (SVN_PATH_IS_EMPTY(base))
172251881Speter    {
173251881Speter      total_len = sizeof(SVN_EMPTY_PATH) - 1;
174251881Speter      base_is_empty = TRUE;
175251881Speter    }
176251881Speter
177251881Speter  saved_lengths[0] = total_len;
178251881Speter
179251881Speter  /* Compute the length of the resulting string. */
180251881Speter
181251881Speter  nargs = 0;
182251881Speter  va_start(va, base);
183251881Speter  while ((s = va_arg(va, const char *)) != NULL)
184251881Speter    {
185251881Speter      len = strlen(s);
186251881Speter
187251881Speter      assert(svn_path_is_canonical_internal(s, pool));
188251881Speter
189251881Speter      if (SVN_PATH_IS_EMPTY(s))
190251881Speter        continue;
191251881Speter
192251881Speter      if (nargs++ < MAX_SAVED_LENGTHS)
193251881Speter        saved_lengths[nargs] = len;
194251881Speter
195251881Speter      if (*s == '/')
196251881Speter        {
197251881Speter          /* an absolute path. skip all components to this point and reset
198251881Speter             the total length. */
199251881Speter          total_len = len;
200251881Speter          base_arg = nargs;
201251881Speter          base_is_root = len == 1;
202251881Speter          base_is_empty = FALSE;
203251881Speter        }
204251881Speter      else if (nargs == base_arg
205251881Speter               || (nargs == base_arg + 1 && base_is_root)
206251881Speter               || base_is_empty)
207251881Speter        {
208251881Speter          /* if we have skipped everything up to this arg, then the base
209251881Speter             and all prior components are empty. just set the length to
210251881Speter             this component; do not add a separator.  If the base is empty
211251881Speter             we can now ignore it. */
212251881Speter          if (base_is_empty)
213251881Speter            {
214251881Speter              base_is_empty = FALSE;
215251881Speter              total_len = 0;
216251881Speter            }
217251881Speter          total_len += len;
218251881Speter        }
219251881Speter      else
220251881Speter        {
221251881Speter          total_len += 1 + len;
222251881Speter        }
223251881Speter    }
224251881Speter  va_end(va);
225251881Speter
226251881Speter  /* base == "/" and no further components. just return that. */
227251881Speter  if (base_is_root && total_len == 1)
228251881Speter    return apr_pmemdup(pool, "/", 2);
229251881Speter
230251881Speter  /* we got the total size. allocate it, with room for a NULL character. */
231251881Speter  path = p = apr_palloc(pool, total_len + 1);
232251881Speter
233251881Speter  /* if we aren't supposed to skip forward to an absolute component, and if
234251881Speter     this is not an empty base that we are skipping, then copy the base
235251881Speter     into the output. */
236251881Speter  if (base_arg == 0 && ! (SVN_PATH_IS_EMPTY(base) && ! base_is_empty))
237251881Speter    {
238251881Speter      if (SVN_PATH_IS_EMPTY(base))
239251881Speter        memcpy(p, SVN_EMPTY_PATH, len = saved_lengths[0]);
240251881Speter      else
241251881Speter        memcpy(p, base, len = saved_lengths[0]);
242251881Speter      p += len;
243251881Speter    }
244251881Speter
245251881Speter  nargs = 0;
246251881Speter  va_start(va, base);
247251881Speter  while ((s = va_arg(va, const char *)) != NULL)
248251881Speter    {
249251881Speter      if (SVN_PATH_IS_EMPTY(s))
250251881Speter        continue;
251251881Speter
252251881Speter      if (++nargs < base_arg)
253251881Speter        continue;
254251881Speter
255251881Speter      if (nargs < MAX_SAVED_LENGTHS)
256251881Speter        len = saved_lengths[nargs];
257251881Speter      else
258251881Speter        len = strlen(s);
259251881Speter
260251881Speter      /* insert a separator if we aren't copying in the first component
261251881Speter         (which can happen when base_arg is set). also, don't put in a slash
262251881Speter         if the prior character is a slash (occurs when prior component
263251881Speter         is "/"). */
264251881Speter      if (p != path && p[-1] != '/')
265251881Speter        *p++ = '/';
266251881Speter
267251881Speter      /* copy the new component and advance the pointer */
268251881Speter      memcpy(p, s, len);
269251881Speter      p += len;
270251881Speter    }
271251881Speter  va_end(va);
272251881Speter
273251881Speter  *p = '\0';
274251881Speter  assert((apr_size_t)(p - path) == total_len);
275251881Speter
276251881Speter  return path;
277251881Speter}
278251881Speter
279251881Speter
280251881Speter
281251881Speterapr_size_t
282251881Spetersvn_path_component_count(const char *path)
283251881Speter{
284251881Speter  apr_size_t count = 0;
285251881Speter
286251881Speter  assert(is_canonical(path, strlen(path)));
287251881Speter
288251881Speter  while (*path)
289251881Speter    {
290251881Speter      const char *start;
291251881Speter
292251881Speter      while (*path == '/')
293251881Speter        ++path;
294251881Speter
295251881Speter      start = path;
296251881Speter
297251881Speter      while (*path && *path != '/')
298251881Speter        ++path;
299251881Speter
300251881Speter      if (path != start)
301251881Speter        ++count;
302251881Speter    }
303251881Speter
304251881Speter  return count;
305251881Speter}
306251881Speter
307251881Speter
308251881Speter/* Return the length of substring necessary to encompass the entire
309251881Speter * previous path segment in PATH, which should be a LEN byte string.
310251881Speter *
311251881Speter * A trailing slash will not be included in the returned length except
312251881Speter * in the case in which PATH is absolute and there are no more
313251881Speter * previous segments.
314251881Speter */
315251881Speterstatic apr_size_t
316251881Speterprevious_segment(const char *path,
317251881Speter                 apr_size_t len)
318251881Speter{
319251881Speter  if (len == 0)
320251881Speter    return 0;
321251881Speter
322251881Speter  while (len > 0 && path[--len] != '/')
323251881Speter    ;
324251881Speter
325251881Speter  if (len == 0 && path[0] == '/')
326251881Speter    return 1;
327251881Speter  else
328251881Speter    return len;
329251881Speter}
330251881Speter
331251881Speter
332251881Spetervoid
333251881Spetersvn_path_add_component(svn_stringbuf_t *path,
334251881Speter                       const char *component)
335251881Speter{
336251881Speter  apr_size_t len = strlen(component);
337251881Speter
338251881Speter  assert(is_canonical(path->data, path->len));
339251881Speter  assert(is_canonical(component, strlen(component)));
340251881Speter
341251881Speter  /* Append a dir separator, but only if this path is neither empty
342251881Speter     nor consists of a single dir separator already. */
343251881Speter  if ((! SVN_PATH_IS_EMPTY(path->data))
344251881Speter      && (! ((path->len == 1) && (*(path->data) == '/'))))
345251881Speter    {
346251881Speter      char dirsep = '/';
347251881Speter      svn_stringbuf_appendbytes(path, &dirsep, sizeof(dirsep));
348251881Speter    }
349251881Speter
350251881Speter  svn_stringbuf_appendbytes(path, component, len);
351251881Speter}
352251881Speter
353251881Speter
354251881Spetervoid
355251881Spetersvn_path_remove_component(svn_stringbuf_t *path)
356251881Speter{
357251881Speter  assert(is_canonical(path->data, path->len));
358251881Speter
359251881Speter  path->len = previous_segment(path->data, path->len);
360251881Speter  path->data[path->len] = '\0';
361251881Speter}
362251881Speter
363251881Speter
364251881Spetervoid
365251881Spetersvn_path_remove_components(svn_stringbuf_t *path, apr_size_t n)
366251881Speter{
367251881Speter  while (n > 0)
368251881Speter    {
369251881Speter      svn_path_remove_component(path);
370251881Speter      n--;
371251881Speter    }
372251881Speter}
373251881Speter
374251881Speter
375251881Speterchar *
376251881Spetersvn_path_dirname(const char *path, apr_pool_t *pool)
377251881Speter{
378251881Speter  apr_size_t len = strlen(path);
379251881Speter
380251881Speter  assert(svn_path_is_canonical_internal(path, pool));
381251881Speter
382251881Speter  return apr_pstrmemdup(pool, path, previous_segment(path, len));
383251881Speter}
384251881Speter
385251881Speter
386251881Speterchar *
387251881Spetersvn_path_basename(const char *path, apr_pool_t *pool)
388251881Speter{
389251881Speter  apr_size_t len = strlen(path);
390251881Speter  apr_size_t start;
391251881Speter
392251881Speter  assert(svn_path_is_canonical_internal(path, pool));
393251881Speter
394251881Speter  if (len == 1 && path[0] == '/')
395251881Speter    start = 0;
396251881Speter  else
397251881Speter    {
398251881Speter      start = len;
399251881Speter      while (start > 0 && path[start - 1] != '/')
400251881Speter        --start;
401251881Speter    }
402251881Speter
403251881Speter  return apr_pstrmemdup(pool, path + start, len - start);
404251881Speter}
405251881Speter
406251881Speterint
407251881Spetersvn_path_is_empty(const char *path)
408251881Speter{
409251881Speter  assert(is_canonical(path, strlen(path)));
410251881Speter
411251881Speter  if (SVN_PATH_IS_EMPTY(path))
412251881Speter    return 1;
413251881Speter
414251881Speter  return 0;
415251881Speter}
416251881Speter
417251881Speterint
418251881Spetersvn_path_compare_paths(const char *path1,
419251881Speter                       const char *path2)
420251881Speter{
421251881Speter  apr_size_t path1_len = strlen(path1);
422251881Speter  apr_size_t path2_len = strlen(path2);
423251881Speter  apr_size_t min_len = ((path1_len < path2_len) ? path1_len : path2_len);
424251881Speter  apr_size_t i = 0;
425251881Speter
426251881Speter  assert(is_canonical(path1, path1_len));
427251881Speter  assert(is_canonical(path2, path2_len));
428251881Speter
429251881Speter  /* Skip past common prefix. */
430251881Speter  while (i < min_len && path1[i] == path2[i])
431251881Speter    ++i;
432251881Speter
433251881Speter  /* Are the paths exactly the same? */
434251881Speter  if ((path1_len == path2_len) && (i >= min_len))
435251881Speter    return 0;
436251881Speter
437251881Speter  /* Children of paths are greater than their parents, but less than
438251881Speter     greater siblings of their parents. */
439251881Speter  if ((path1[i] == '/') && (path2[i] == 0))
440251881Speter    return 1;
441251881Speter  if ((path2[i] == '/') && (path1[i] == 0))
442251881Speter    return -1;
443251881Speter  if (path1[i] == '/')
444251881Speter    return -1;
445251881Speter  if (path2[i] == '/')
446251881Speter    return 1;
447251881Speter
448251881Speter  /* Common prefix was skipped above, next character is compared to
449251881Speter     determine order.  We need to use an unsigned comparison, though,
450251881Speter     so a "next character" of NULL (0x00) sorts numerically
451251881Speter     smallest. */
452251881Speter  return (unsigned char)(path1[i]) < (unsigned char)(path2[i]) ? -1 : 1;
453251881Speter}
454251881Speter
455251881Speter/* Return the string length of the longest common ancestor of PATH1 and PATH2.
456251881Speter *
457251881Speter * This function handles everything except the URL-handling logic
458251881Speter * of svn_path_get_longest_ancestor, and assumes that PATH1 and
459251881Speter * PATH2 are *not* URLs.
460251881Speter *
461251881Speter * If the two paths do not share a common ancestor, return 0.
462251881Speter *
463251881Speter * New strings are allocated in POOL.
464251881Speter */
465251881Speterstatic apr_size_t
466251881Speterget_path_ancestor_length(const char *path1,
467251881Speter                         const char *path2,
468251881Speter                         apr_pool_t *pool)
469251881Speter{
470251881Speter  apr_size_t path1_len, path2_len;
471251881Speter  apr_size_t i = 0;
472251881Speter  apr_size_t last_dirsep = 0;
473251881Speter
474251881Speter  path1_len = strlen(path1);
475251881Speter  path2_len = strlen(path2);
476251881Speter
477251881Speter  if (SVN_PATH_IS_EMPTY(path1) || SVN_PATH_IS_EMPTY(path2))
478251881Speter    return 0;
479251881Speter
480251881Speter  while (path1[i] == path2[i])
481251881Speter    {
482251881Speter      /* Keep track of the last directory separator we hit. */
483251881Speter      if (path1[i] == '/')
484251881Speter        last_dirsep = i;
485251881Speter
486251881Speter      i++;
487251881Speter
488251881Speter      /* If we get to the end of either path, break out. */
489251881Speter      if ((i == path1_len) || (i == path2_len))
490251881Speter        break;
491251881Speter    }
492251881Speter
493251881Speter  /* two special cases:
494251881Speter     1. '/' is the longest common ancestor of '/' and '/foo'
495251881Speter     2. '/' is the longest common ancestor of '/rif' and '/raf' */
496251881Speter  if (i == 1 && path1[0] == '/' && path2[0] == '/')
497251881Speter    return 1;
498251881Speter
499251881Speter  /* last_dirsep is now the offset of the last directory separator we
500251881Speter     crossed before reaching a non-matching byte.  i is the offset of
501251881Speter     that non-matching byte. */
502251881Speter  if (((i == path1_len) && (path2[i] == '/'))
503251881Speter           || ((i == path2_len) && (path1[i] == '/'))
504251881Speter           || ((i == path1_len) && (i == path2_len)))
505251881Speter    return i;
506251881Speter  else
507251881Speter    if (last_dirsep == 0 && path1[0] == '/' && path2[0] == '/')
508251881Speter      return 1;
509362181Sdim  return last_dirsep;
510251881Speter}
511251881Speter
512251881Speter
513251881Speterchar *
514251881Spetersvn_path_get_longest_ancestor(const char *path1,
515251881Speter                              const char *path2,
516251881Speter                              apr_pool_t *pool)
517251881Speter{
518251881Speter  svn_boolean_t path1_is_url = svn_path_is_url(path1);
519251881Speter  svn_boolean_t path2_is_url = svn_path_is_url(path2);
520251881Speter
521251881Speter  /* Are we messing with URLs?  If we have a mix of URLs and non-URLs,
522251881Speter     there's nothing common between them.  */
523251881Speter  if (path1_is_url && path2_is_url)
524251881Speter    {
525251881Speter      return svn_uri_get_longest_ancestor(path1, path2, pool);
526251881Speter    }
527251881Speter  else if ((! path1_is_url) && (! path2_is_url))
528251881Speter    {
529251881Speter      return apr_pstrndup(pool, path1,
530251881Speter                          get_path_ancestor_length(path1, path2, pool));
531251881Speter    }
532251881Speter  else
533251881Speter    {
534251881Speter      /* A URL and a non-URL => no common prefix */
535251881Speter      return apr_pmemdup(pool, SVN_EMPTY_PATH, sizeof(SVN_EMPTY_PATH));
536251881Speter    }
537251881Speter}
538251881Speter
539251881Speterconst char *
540251881Spetersvn_path_is_child(const char *path1,
541251881Speter                  const char *path2,
542251881Speter                  apr_pool_t *pool)
543251881Speter{
544251881Speter  apr_size_t i;
545251881Speter
546251881Speter  /* assert (is_canonical (path1, strlen (path1)));  ### Expensive strlen */
547251881Speter  /* assert (is_canonical (path2, strlen (path2)));  ### Expensive strlen */
548251881Speter
549251881Speter  /* Allow "" and "foo" to be parent/child */
550251881Speter  if (SVN_PATH_IS_EMPTY(path1))               /* "" is the parent  */
551251881Speter    {
552251881Speter      if (SVN_PATH_IS_EMPTY(path2)            /* "" not a child    */
553251881Speter          || path2[0] == '/')                  /* "/foo" not a child */
554251881Speter        return NULL;
555251881Speter      else
556251881Speter        /* everything else is child */
557251881Speter        return pool ? apr_pstrdup(pool, path2) : path2;
558251881Speter    }
559251881Speter
560251881Speter  /* Reach the end of at least one of the paths.  How should we handle
561251881Speter     things like path1:"foo///bar" and path2:"foo/bar/baz"?  It doesn't
562251881Speter     appear to arise in the current Subversion code, it's not clear to me
563251881Speter     if they should be parent/child or not. */
564251881Speter  for (i = 0; path1[i] && path2[i]; i++)
565251881Speter    if (path1[i] != path2[i])
566251881Speter      return NULL;
567251881Speter
568251881Speter  /* There are two cases that are parent/child
569251881Speter          ...      path1[i] == '\0'
570251881Speter          .../foo  path2[i] == '/'
571251881Speter      or
572251881Speter          /        path1[i] == '\0'
573251881Speter          /foo     path2[i] != '/'
574251881Speter  */
575251881Speter  if (path1[i] == '\0' && path2[i])
576251881Speter    {
577251881Speter      if (path2[i] == '/')
578251881Speter        return pool ? apr_pstrdup(pool, path2 + i + 1) : path2 + i + 1;
579251881Speter      else if (i == 1 && path1[0] == '/')
580251881Speter        return pool ? apr_pstrdup(pool, path2 + 1) : path2 + 1;
581251881Speter    }
582251881Speter
583251881Speter  /* Otherwise, path2 isn't a child. */
584251881Speter  return NULL;
585251881Speter}
586251881Speter
587251881Speter
588251881Spetersvn_boolean_t
589251881Spetersvn_path_is_ancestor(const char *path1, const char *path2)
590251881Speter{
591251881Speter  apr_size_t path1_len = strlen(path1);
592251881Speter
593251881Speter  /* If path1 is empty and path2 is not absoulte, then path1 is an ancestor. */
594251881Speter  if (SVN_PATH_IS_EMPTY(path1))
595251881Speter    return *path2 != '/';
596251881Speter
597251881Speter  /* If path1 is a prefix of path2, then:
598251881Speter     - If path1 ends in a path separator,
599251881Speter     - If the paths are of the same length
600251881Speter     OR
601251881Speter     - path2 starts a new path component after the common prefix,
602251881Speter     then path1 is an ancestor. */
603251881Speter  if (strncmp(path1, path2, path1_len) == 0)
604251881Speter    return path1[path1_len - 1] == '/'
605251881Speter      || (path2[path1_len] == '/' || path2[path1_len] == '\0');
606251881Speter
607251881Speter  return FALSE;
608251881Speter}
609251881Speter
610251881Speter
611251881Speterapr_array_header_t *
612251881Spetersvn_path_decompose(const char *path,
613251881Speter                   apr_pool_t *pool)
614251881Speter{
615251881Speter  apr_size_t i, oldi;
616251881Speter
617251881Speter  apr_array_header_t *components =
618251881Speter    apr_array_make(pool, 1, sizeof(const char *));
619251881Speter
620251881Speter  assert(svn_path_is_canonical_internal(path, pool));
621251881Speter
622251881Speter  if (SVN_PATH_IS_EMPTY(path))
623251881Speter    return components;  /* ### Should we return a "" component? */
624251881Speter
625251881Speter  /* If PATH is absolute, store the '/' as the first component. */
626251881Speter  i = oldi = 0;
627251881Speter  if (path[i] == '/')
628251881Speter    {
629251881Speter      char dirsep = '/';
630251881Speter
631251881Speter      APR_ARRAY_PUSH(components, const char *)
632251881Speter        = apr_pstrmemdup(pool, &dirsep, sizeof(dirsep));
633251881Speter
634251881Speter      i++;
635251881Speter      oldi++;
636251881Speter      if (path[i] == '\0') /* path is a single '/' */
637251881Speter        return components;
638251881Speter    }
639251881Speter
640251881Speter  do
641251881Speter    {
642251881Speter      if ((path[i] == '/') || (path[i] == '\0'))
643251881Speter        {
644251881Speter          if (SVN_PATH_IS_PLATFORM_EMPTY(path + oldi, i - oldi))
645251881Speter            APR_ARRAY_PUSH(components, const char *) = SVN_EMPTY_PATH;
646251881Speter          else
647251881Speter            APR_ARRAY_PUSH(components, const char *)
648251881Speter              = apr_pstrmemdup(pool, path + oldi, i - oldi);
649251881Speter
650251881Speter          i++;
651251881Speter          oldi = i;  /* skipping past the dirsep */
652251881Speter          continue;
653251881Speter        }
654251881Speter      i++;
655251881Speter    }
656251881Speter  while (path[i-1]);
657251881Speter
658251881Speter  return components;
659251881Speter}
660251881Speter
661251881Speter
662251881Speterconst char *
663251881Spetersvn_path_compose(const apr_array_header_t *components,
664251881Speter                 apr_pool_t *pool)
665251881Speter{
666251881Speter  apr_size_t *lengths = apr_palloc(pool, components->nelts*sizeof(*lengths));
667251881Speter  apr_size_t max_length = components->nelts;
668251881Speter  char *path;
669251881Speter  char *p;
670251881Speter  int i;
671251881Speter
672251881Speter  /* Get the length of each component so a total length can be
673251881Speter     calculated. */
674251881Speter  for (i = 0; i < components->nelts; ++i)
675251881Speter    {
676251881Speter      apr_size_t l = strlen(APR_ARRAY_IDX(components, i, const char *));
677251881Speter      lengths[i] = l;
678251881Speter      max_length += l;
679251881Speter    }
680251881Speter
681251881Speter  path = apr_palloc(pool, max_length + 1);
682251881Speter  p = path;
683251881Speter
684251881Speter  for (i = 0; i < components->nelts; ++i)
685251881Speter    {
686251881Speter      /* Append a '/' to the path.  Handle the case with an absolute
687251881Speter         path where a '/' appears in the first component.  Only append
688251881Speter         a '/' if the component is the second component that does not
689251881Speter         follow a "/" first component; or it is the third or later
690251881Speter         component. */
691251881Speter      if (i > 1 ||
692251881Speter          (i == 1 && strcmp("/", APR_ARRAY_IDX(components,
693251881Speter                                               0,
694251881Speter                                               const char *)) != 0))
695251881Speter        {
696251881Speter          *p++ = '/';
697251881Speter        }
698251881Speter
699251881Speter      memcpy(p, APR_ARRAY_IDX(components, i, const char *), lengths[i]);
700251881Speter      p += lengths[i];
701251881Speter    }
702251881Speter
703251881Speter  *p = '\0';
704251881Speter
705251881Speter  return path;
706251881Speter}
707251881Speter
708251881Speter
709251881Spetersvn_boolean_t
710251881Spetersvn_path_is_single_path_component(const char *name)
711251881Speter{
712251881Speter  assert(is_canonical(name, strlen(name)));
713251881Speter
714251881Speter  /* Can't be empty or `..'  */
715251881Speter  if (SVN_PATH_IS_EMPTY(name)
716251881Speter      || (name[0] == '.' && name[1] == '.' && name[2] == '\0'))
717251881Speter    return FALSE;
718251881Speter
719251881Speter  /* Slashes are bad, m'kay... */
720251881Speter  if (strchr(name, '/') != NULL)
721251881Speter    return FALSE;
722251881Speter
723251881Speter  /* It is valid.  */
724251881Speter  return TRUE;
725251881Speter}
726251881Speter
727251881Speter
728251881Spetersvn_boolean_t
729251881Spetersvn_path_is_dotpath_present(const char *path)
730251881Speter{
731251881Speter  size_t len;
732251881Speter
733251881Speter  /* The empty string does not have a dotpath */
734251881Speter  if (path[0] == '\0')
735251881Speter    return FALSE;
736251881Speter
737251881Speter  /* Handle "." or a leading "./" */
738251881Speter  if (path[0] == '.' && (path[1] == '\0' || path[1] == '/'))
739251881Speter    return TRUE;
740251881Speter
741251881Speter  /* Paths of length 1 (at this point) have no dotpath present. */
742251881Speter  if (path[1] == '\0')
743251881Speter    return FALSE;
744251881Speter
745251881Speter  /* If any segment is "/./", then a dotpath is present. */
746251881Speter  if (strstr(path, "/./") != NULL)
747251881Speter    return TRUE;
748251881Speter
749251881Speter  /* Does the path end in "/." ? */
750251881Speter  len = strlen(path);
751251881Speter  return path[len - 2] == '/' && path[len - 1] == '.';
752251881Speter}
753251881Speter
754251881Spetersvn_boolean_t
755251881Spetersvn_path_is_backpath_present(const char *path)
756251881Speter{
757251881Speter  size_t len;
758251881Speter
759251881Speter  /* 0 and 1-length paths do not have a backpath */
760251881Speter  if (path[0] == '\0' || path[1] == '\0')
761251881Speter    return FALSE;
762251881Speter
763251881Speter  /* Handle ".." or a leading "../" */
764251881Speter  if (path[0] == '.' && path[1] == '.' && (path[2] == '\0' || path[2] == '/'))
765251881Speter    return TRUE;
766251881Speter
767251881Speter  /* Paths of length 2 (at this point) have no backpath present. */
768251881Speter  if (path[2] == '\0')
769251881Speter    return FALSE;
770251881Speter
771251881Speter  /* If any segment is "..", then a backpath is present. */
772251881Speter  if (strstr(path, "/../") != NULL)
773251881Speter    return TRUE;
774251881Speter
775251881Speter  /* Does the path end in "/.." ? */
776251881Speter  len = strlen(path);
777251881Speter  return path[len - 3] == '/' && path[len - 2] == '.' && path[len - 1] == '.';
778251881Speter}
779251881Speter
780251881Speter
781251881Speter/*** URI Stuff ***/
782251881Speter
783251881Speter/* Examine PATH as a potential URI, and return a substring of PATH
784251881Speter   that immediately follows the (scheme):// portion of the URI, or
785251881Speter   NULL if PATH doesn't appear to be a valid URI.  The returned value
786251881Speter   is not alloced -- it shares memory with PATH. */
787251881Speterstatic const char *
788251881Speterskip_uri_scheme(const char *path)
789251881Speter{
790251881Speter  apr_size_t j;
791251881Speter
792251881Speter  /* A scheme is terminated by a : and cannot contain any /'s. */
793251881Speter  for (j = 0; path[j] && path[j] != ':'; ++j)
794251881Speter    if (path[j] == '/')
795251881Speter      return NULL;
796251881Speter
797251881Speter  if (j > 0 && path[j] == ':' && path[j+1] == '/' && path[j+2] == '/')
798251881Speter    return path + j + 3;
799251881Speter
800251881Speter  return NULL;
801251881Speter}
802251881Speter
803251881Speter
804251881Spetersvn_boolean_t
805251881Spetersvn_path_is_url(const char *path)
806251881Speter{
807251881Speter  /* ### This function is reaaaaaaaaaaaaaally stupid right now.
808251881Speter     We're just going to look for:
809251881Speter
810251881Speter        (scheme)://(optional_stuff)
811251881Speter
812251881Speter     Where (scheme) has no ':' or '/' characters.
813251881Speter
814251881Speter     Someday it might be nice to have an actual URI parser here.
815251881Speter  */
816251881Speter  return skip_uri_scheme(path) != NULL;
817251881Speter}
818251881Speter
819251881Speter
820251881Speter
821251881Speter/* Here is the BNF for path components in a URI. "pchar" is a
822251881Speter   character in a path component.
823251881Speter
824251881Speter      pchar       = unreserved | escaped |
825251881Speter                    ":" | "@" | "&" | "=" | "+" | "$" | ","
826251881Speter      unreserved  = alphanum | mark
827251881Speter      mark        = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
828251881Speter
829251881Speter   Note that "escaped" doesn't really apply to what users can put in
830251881Speter   their paths, so that really means the set of characters is:
831251881Speter
832251881Speter      alphanum | mark | ":" | "@" | "&" | "=" | "+" | "$" | ","
833251881Speter*/
834251881Speterconst char svn_uri__char_validity[256] = {
835251881Speter  0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
836251881Speter  0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
837251881Speter  0, 1, 0, 0, 1, 0, 1, 1,   1, 1, 1, 1, 1, 1, 1, 1,
838251881Speter  1, 1, 1, 1, 1, 1, 1, 1,   1, 1, 1, 0, 0, 1, 0, 0,
839251881Speter
840251881Speter  /* 64 */
841251881Speter  1, 1, 1, 1, 1, 1, 1, 1,   1, 1, 1, 1, 1, 1, 1, 1,
842251881Speter  1, 1, 1, 1, 1, 1, 1, 1,   1, 1, 1, 0, 0, 0, 0, 1,
843251881Speter  0, 1, 1, 1, 1, 1, 1, 1,   1, 1, 1, 1, 1, 1, 1, 1,
844251881Speter  1, 1, 1, 1, 1, 1, 1, 1,   1, 1, 1, 0, 0, 0, 1, 0,
845251881Speter
846251881Speter  /* 128 */
847251881Speter  0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
848251881Speter  0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
849251881Speter  0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
850251881Speter  0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
851251881Speter
852251881Speter  /* 192 */
853251881Speter  0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
854251881Speter  0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
855251881Speter  0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
856251881Speter  0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
857251881Speter};
858251881Speter
859251881Speter
860251881Spetersvn_boolean_t
861251881Spetersvn_path_is_uri_safe(const char *path)
862251881Speter{
863251881Speter  apr_size_t i;
864251881Speter
865251881Speter  /* Skip the URI scheme. */
866251881Speter  path = skip_uri_scheme(path);
867251881Speter
868251881Speter  /* No scheme?  Get outta here. */
869251881Speter  if (! path)
870251881Speter    return FALSE;
871251881Speter
872251881Speter  /* Skip to the first slash that's after the URI scheme. */
873251881Speter  path = strchr(path, '/');
874251881Speter
875251881Speter  /* If there's no first slash, then there's only a host portion;
876251881Speter     therefore there couldn't be any uri-unsafe characters after the
877251881Speter     host... so return true. */
878251881Speter  if (path == NULL)
879251881Speter    return TRUE;
880251881Speter
881251881Speter  for (i = 0; path[i]; i++)
882251881Speter    {
883251881Speter      /* Allow '%XX' (where each X is a hex digit) */
884251881Speter      if (path[i] == '%')
885251881Speter        {
886251881Speter          if (svn_ctype_isxdigit(path[i + 1]) &&
887251881Speter              svn_ctype_isxdigit(path[i + 2]))
888251881Speter            {
889251881Speter              i += 2;
890251881Speter              continue;
891251881Speter            }
892251881Speter          return FALSE;
893251881Speter        }
894251881Speter      else if (! svn_uri__char_validity[((unsigned char)path[i])])
895251881Speter        {
896251881Speter          return FALSE;
897251881Speter        }
898251881Speter    }
899251881Speter
900251881Speter  return TRUE;
901251881Speter}
902251881Speter
903251881Speter
904251881Speter/* URI-encode each character c in PATH for which TABLE[c] is 0.
905251881Speter   If no encoding was needed, return PATH, else return a new string allocated
906251881Speter   in POOL. */
907251881Speterstatic const char *
908251881Speteruri_escape(const char *path, const char table[], apr_pool_t *pool)
909251881Speter{
910251881Speter  svn_stringbuf_t *retstr;
911251881Speter  apr_size_t i, copied = 0;
912251881Speter  int c;
913362181Sdim  apr_size_t len;
914362181Sdim  const char *p;
915251881Speter
916362181Sdim  /* To terminate our scanning loop, table[NUL] must report "invalid". */
917362181Sdim  assert(table[0] == 0);
918362181Sdim
919362181Sdim  /* Quick check: Does any character need escaping? */
920362181Sdim  for (p = path; table[(unsigned char)*p]; ++p)
921362181Sdim    {}
922362181Sdim
923362181Sdim  /* No char to escape before EOS? */
924362181Sdim  if (*p == '\0')
925362181Sdim    return path;
926362181Sdim
927362181Sdim  /* We need to escape at least one character. */
928362181Sdim  len = strlen(p) + (p - path);
929362181Sdim  retstr = svn_stringbuf_create_ensure(len, pool);
930362181Sdim  for (i = p - path; i < len; i++)
931251881Speter    {
932251881Speter      c = (unsigned char)path[i];
933251881Speter      if (table[c])
934251881Speter        continue;
935251881Speter
936251881Speter      /* If we got here, we're looking at a character that isn't
937251881Speter         supported by the (or at least, our) URI encoding scheme.  We
938251881Speter         need to escape this character.  */
939251881Speter
940251881Speter      /* First things first, copy all the good stuff that we haven't
941251881Speter         yet copied into our output buffer. */
942251881Speter      if (i - copied)
943251881Speter        svn_stringbuf_appendbytes(retstr, path + copied,
944251881Speter                                  i - copied);
945251881Speter
946251881Speter      /* Now, write in our escaped character, consisting of the
947251881Speter         '%' and two digits.  We cast the C to unsigned char here because
948251881Speter         the 'X' format character will be tempted to treat it as an unsigned
949251881Speter         int...which causes problem when messing with 0x80-0xFF chars.
950251881Speter         We also need space for a null as apr_snprintf will write one. */
951251881Speter      svn_stringbuf_ensure(retstr, retstr->len + 4);
952251881Speter      apr_snprintf(retstr->data + retstr->len, 4, "%%%02X", (unsigned char)c);
953251881Speter      retstr->len += 3;
954251881Speter
955251881Speter      /* Finally, update our copy counter. */
956251881Speter      copied = i + 1;
957251881Speter    }
958251881Speter
959251881Speter  /* Anything left to copy? */
960251881Speter  if (i - copied)
961251881Speter    svn_stringbuf_appendbytes(retstr, path + copied, i - copied);
962251881Speter
963251881Speter  /* retstr is null-terminated either by apr_snprintf or the svn_stringbuf
964251881Speter     functions. */
965251881Speter
966251881Speter  return retstr->data;
967251881Speter}
968251881Speter
969251881Speter
970251881Speterconst char *
971251881Spetersvn_path_uri_encode(const char *path, apr_pool_t *pool)
972251881Speter{
973251881Speter  const char *ret;
974251881Speter
975251881Speter  ret = uri_escape(path, svn_uri__char_validity, pool);
976251881Speter
977251881Speter  /* Our interface guarantees a copy. */
978251881Speter  if (ret == path)
979251881Speter    return apr_pstrdup(pool, path);
980251881Speter  else
981251881Speter    return ret;
982251881Speter}
983251881Speter
984251881Speterstatic const char iri_escape_chars[256] = {
985362181Sdim  0, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
986251881Speter  1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
987251881Speter  1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
988251881Speter  1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
989251881Speter  1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
990251881Speter  1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
991251881Speter  1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
992251881Speter  1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
993251881Speter
994251881Speter  /* 128 */
995251881Speter  0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
996251881Speter  0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
997251881Speter  0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
998251881Speter  0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
999251881Speter  0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
1000251881Speter  0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
1001251881Speter  0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
1002251881Speter  0, 0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0
1003251881Speter};
1004251881Speter
1005251881Speterconst char *
1006251881Spetersvn_path_uri_from_iri(const char *iri, apr_pool_t *pool)
1007251881Speter{
1008251881Speter  return uri_escape(iri, iri_escape_chars, pool);
1009251881Speter}
1010251881Speter
1011251881Speterstatic const char uri_autoescape_chars[256] = {
1012362181Sdim  0, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
1013251881Speter  1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
1014251881Speter  0, 1, 0, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
1015251881Speter  1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 0, 1, 0, 1,
1016251881Speter
1017251881Speter  /* 64 */
1018251881Speter  1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
1019251881Speter  1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 0, 1, 0, 1,
1020251881Speter  0, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
1021251881Speter  1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 0, 0, 0, 1, 1,
1022251881Speter
1023251881Speter  /* 128 */
1024251881Speter  1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
1025251881Speter  1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
1026251881Speter  1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
1027251881Speter  1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
1028251881Speter
1029251881Speter  /* 192 */
1030251881Speter  1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
1031251881Speter  1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
1032251881Speter  1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
1033251881Speter  1, 1, 1, 1, 1, 1, 1, 1,  1, 1, 1, 1, 1, 1, 1, 1,
1034251881Speter};
1035251881Speter
1036251881Speterconst char *
1037251881Spetersvn_path_uri_autoescape(const char *uri, apr_pool_t *pool)
1038251881Speter{
1039251881Speter  return uri_escape(uri, uri_autoescape_chars, pool);
1040251881Speter}
1041251881Speter
1042251881Speterconst char *
1043251881Spetersvn_path_uri_decode(const char *path, apr_pool_t *pool)
1044251881Speter{
1045251881Speter  svn_stringbuf_t *retstr;
1046251881Speter  apr_size_t i;
1047251881Speter  svn_boolean_t query_start = FALSE;
1048251881Speter
1049251881Speter  /* avoid repeated realloc */
1050251881Speter  retstr = svn_stringbuf_create_ensure(strlen(path) + 1, pool);
1051251881Speter
1052251881Speter  retstr->len = 0;
1053251881Speter  for (i = 0; path[i]; i++)
1054251881Speter    {
1055251881Speter      char c = path[i];
1056251881Speter
1057251881Speter      if (c == '?')
1058251881Speter        {
1059251881Speter          /* Mark the start of the query string, if it exists. */
1060251881Speter          query_start = TRUE;
1061251881Speter        }
1062251881Speter      else if (c == '+' && query_start)
1063251881Speter        {
1064251881Speter          /* Only do this if we are into the query string.
1065251881Speter           * RFC 2396, section 3.3  */
1066251881Speter          c = ' ';
1067251881Speter        }
1068251881Speter      else if (c == '%' && svn_ctype_isxdigit(path[i + 1])
1069251881Speter               && svn_ctype_isxdigit(path[i+2]))
1070251881Speter        {
1071251881Speter          char digitz[3];
1072251881Speter          digitz[0] = path[++i];
1073251881Speter          digitz[1] = path[++i];
1074251881Speter          digitz[2] = '\0';
1075251881Speter          c = (char)(strtol(digitz, NULL, 16));
1076251881Speter        }
1077251881Speter
1078251881Speter      retstr->data[retstr->len++] = c;
1079251881Speter    }
1080251881Speter
1081251881Speter  /* Null-terminate this bad-boy. */
1082251881Speter  retstr->data[retstr->len] = 0;
1083251881Speter
1084251881Speter  return retstr->data;
1085251881Speter}
1086251881Speter
1087251881Speter
1088251881Speterconst char *
1089251881Spetersvn_path_url_add_component2(const char *url,
1090251881Speter                            const char *component,
1091251881Speter                            apr_pool_t *pool)
1092251881Speter{
1093251881Speter  /* = svn_path_uri_encode() but without always copying */
1094251881Speter  component = uri_escape(component, svn_uri__char_validity, pool);
1095251881Speter
1096251881Speter  return svn_path_join_internal(url, component, pool);
1097251881Speter}
1098251881Speter
1099251881Spetersvn_error_t *
1100251881Spetersvn_path_get_absolute(const char **pabsolute,
1101251881Speter                      const char *relative,
1102251881Speter                      apr_pool_t *pool)
1103251881Speter{
1104251881Speter  if (svn_path_is_url(relative))
1105251881Speter    {
1106251881Speter      *pabsolute = apr_pstrdup(pool, relative);
1107251881Speter      return SVN_NO_ERROR;
1108251881Speter    }
1109251881Speter
1110251881Speter  return svn_dirent_get_absolute(pabsolute, relative, pool);
1111251881Speter}
1112251881Speter
1113251881Speter
1114251881Speter#if !defined(WIN32) && !defined(DARWIN)
1115251881Speter/** Get APR's internal path encoding. */
1116251881Speterstatic svn_error_t *
1117251881Speterget_path_encoding(svn_boolean_t *path_is_utf8, apr_pool_t *pool)
1118251881Speter{
1119251881Speter  apr_status_t apr_err;
1120251881Speter  int encoding_style;
1121251881Speter
1122251881Speter  apr_err = apr_filepath_encoding(&encoding_style, pool);
1123251881Speter  if (apr_err)
1124251881Speter    return svn_error_wrap_apr(apr_err,
1125251881Speter                              _("Can't determine the native path encoding"));
1126251881Speter
1127251881Speter  /* ### What to do about APR_FILEPATH_ENCODING_UNKNOWN?
1128251881Speter     Well, for now we'll just punt to the svn_utf_ functions;
1129251881Speter     those will at least do the ASCII-subset check. */
1130251881Speter  *path_is_utf8 = (encoding_style == APR_FILEPATH_ENCODING_UTF8);
1131251881Speter  return SVN_NO_ERROR;
1132251881Speter}
1133251881Speter#endif
1134251881Speter
1135251881Speter
1136251881Spetersvn_error_t *
1137251881Spetersvn_path_cstring_from_utf8(const char **path_apr,
1138251881Speter                           const char *path_utf8,
1139251881Speter                           apr_pool_t *pool)
1140251881Speter{
1141251881Speter#if !defined(WIN32) && !defined(DARWIN)
1142251881Speter  svn_boolean_t path_is_utf8;
1143251881Speter  SVN_ERR(get_path_encoding(&path_is_utf8, pool));
1144251881Speter  if (path_is_utf8)
1145251881Speter#endif
1146251881Speter    {
1147251881Speter      *path_apr = apr_pstrdup(pool, path_utf8);
1148251881Speter      return SVN_NO_ERROR;
1149251881Speter    }
1150251881Speter#if !defined(WIN32) && !defined(DARWIN)
1151251881Speter  else
1152251881Speter    return svn_utf_cstring_from_utf8(path_apr, path_utf8, pool);
1153251881Speter#endif
1154251881Speter}
1155251881Speter
1156251881Speter
1157251881Spetersvn_error_t *
1158251881Spetersvn_path_cstring_to_utf8(const char **path_utf8,
1159251881Speter                         const char *path_apr,
1160251881Speter                         apr_pool_t *pool)
1161251881Speter{
1162251881Speter#if !defined(WIN32) && !defined(DARWIN)
1163251881Speter  svn_boolean_t path_is_utf8;
1164251881Speter  SVN_ERR(get_path_encoding(&path_is_utf8, pool));
1165251881Speter  if (path_is_utf8)
1166251881Speter#endif
1167251881Speter    {
1168251881Speter      *path_utf8 = apr_pstrdup(pool, path_apr);
1169251881Speter      return SVN_NO_ERROR;
1170251881Speter    }
1171251881Speter#if !defined(WIN32) && !defined(DARWIN)
1172251881Speter  else
1173251881Speter    return svn_utf_cstring_to_utf8(path_utf8, path_apr, pool);
1174251881Speter#endif
1175251881Speter}
1176251881Speter
1177251881Speter
1178251881Speterconst char *
1179251881Spetersvn_path_illegal_path_escape(const char *path, apr_pool_t *pool)
1180251881Speter{
1181251881Speter  svn_stringbuf_t *retstr;
1182251881Speter  apr_size_t i, copied = 0;
1183251881Speter  int c;
1184251881Speter
1185251881Speter  /* At least one control character:
1186251881Speter      strlen - 1 (control) + \ + N + N + N + null . */
1187251881Speter  retstr = svn_stringbuf_create_ensure(strlen(path) + 4, pool);
1188251881Speter  for (i = 0; path[i]; i++)
1189251881Speter    {
1190251881Speter      c = (unsigned char)path[i];
1191251881Speter      if (! svn_ctype_iscntrl(c))
1192251881Speter        continue;
1193251881Speter
1194251881Speter      /* If we got here, we're looking at a character that isn't
1195251881Speter         supported by the (or at least, our) URI encoding scheme.  We
1196251881Speter         need to escape this character.  */
1197251881Speter
1198251881Speter      /* First things first, copy all the good stuff that we haven't
1199251881Speter         yet copied into our output buffer. */
1200251881Speter      if (i - copied)
1201251881Speter        svn_stringbuf_appendbytes(retstr, path + copied,
1202251881Speter                                  i - copied);
1203251881Speter
1204251881Speter      /* Make sure buffer is big enough for '\' 'N' 'N' 'N' (and NUL) */
1205251881Speter      svn_stringbuf_ensure(retstr, retstr->len + 5);
1206251881Speter      /*### The backslash separator doesn't work too great with Windows,
1207251881Speter         but it's what we'll use for consistency with invalid utf8
1208251881Speter         formatting (until someone has a better idea) */
1209251881Speter      apr_snprintf(retstr->data + retstr->len, 5, "\\%03o", (unsigned char)c);
1210251881Speter      retstr->len += 4;
1211251881Speter
1212251881Speter      /* Finally, update our copy counter. */
1213251881Speter      copied = i + 1;
1214251881Speter    }
1215251881Speter
1216251881Speter  /* If we didn't encode anything, we don't need to duplicate the string. */
1217251881Speter  if (retstr->len == 0)
1218251881Speter    return path;
1219251881Speter
1220251881Speter  /* Anything left to copy? */
1221251881Speter  if (i - copied)
1222251881Speter    svn_stringbuf_appendbytes(retstr, path + copied, i - copied);
1223251881Speter
1224251881Speter  /* retstr is null-terminated either by apr_snprintf or the svn_stringbuf
1225251881Speter     functions. */
1226251881Speter
1227251881Speter  return retstr->data;
1228251881Speter}
1229251881Speter
1230251881Spetersvn_error_t *
1231251881Spetersvn_path_check_valid(const char *path, apr_pool_t *pool)
1232251881Speter{
1233251881Speter  const char *c;
1234251881Speter
1235251881Speter  for (c = path; *c; c++)
1236251881Speter    {
1237251881Speter      if (svn_ctype_iscntrl(*c))
1238251881Speter        {
1239289180Speter          return svn_error_createf(SVN_ERR_FS_PATH_SYNTAX, NULL,
1240251881Speter             _("Invalid control character '0x%02x' in path '%s'"),
1241251881Speter             (unsigned char)*c,
1242251881Speter             svn_path_illegal_path_escape(svn_dirent_local_style(path, pool),
1243251881Speter                                          pool));
1244251881Speter        }
1245251881Speter    }
1246251881Speter
1247251881Speter  return SVN_NO_ERROR;
1248251881Speter}
1249251881Speter
1250251881Spetervoid
1251251881Spetersvn_path_splitext(const char **path_root,
1252251881Speter                  const char **path_ext,
1253251881Speter                  const char *path,
1254251881Speter                  apr_pool_t *pool)
1255251881Speter{
1256251881Speter  const char *last_dot, *last_slash;
1257251881Speter
1258251881Speter  /* Easy out -- why do all the work when there's no way to report it? */
1259251881Speter  if (! (path_root || path_ext))
1260251881Speter    return;
1261251881Speter
1262251881Speter  /* Do we even have a period in this thing?  And if so, is there
1263251881Speter     anything after it?  We look for the "rightmost" period in the
1264251881Speter     string. */
1265251881Speter  last_dot = strrchr(path, '.');
1266289180Speter  if (last_dot && (*(last_dot + 1) != '\0'))
1267251881Speter    {
1268251881Speter      /* If we have a period, we need to make sure it occurs in the
1269251881Speter         final path component -- that there's no path separator
1270251881Speter         between the last period and the end of the PATH -- otherwise,
1271251881Speter         it doesn't count.  Also, we want to make sure that our period
1272251881Speter         isn't the first character of the last component. */
1273251881Speter      last_slash = strrchr(path, '/');
1274251881Speter      if ((last_slash && (last_dot > (last_slash + 1)))
1275251881Speter          || ((! last_slash) && (last_dot > path)))
1276251881Speter        {
1277251881Speter          if (path_root)
1278251881Speter            *path_root = apr_pstrmemdup(pool, path,
1279251881Speter                                        (last_dot - path + 1) * sizeof(*path));
1280251881Speter          if (path_ext)
1281251881Speter            *path_ext = apr_pstrdup(pool, last_dot + 1);
1282251881Speter          return;
1283251881Speter        }
1284251881Speter    }
1285251881Speter  /* If we get here, we never found a suitable separator character, so
1286251881Speter     there's no split. */
1287251881Speter  if (path_root)
1288251881Speter    *path_root = apr_pstrdup(pool, path);
1289251881Speter  if (path_ext)
1290251881Speter    *path_ext = "";
1291251881Speter}
1292251881Speter
1293251881Speter
1294251881Speter/* Repository relative URLs (^/). */
1295251881Speter
1296251881Spetersvn_boolean_t
1297251881Spetersvn_path_is_repos_relative_url(const char *path)
1298251881Speter{
1299251881Speter  return (0 == strncmp("^/", path, 2));
1300251881Speter}
1301251881Speter
1302251881Spetersvn_error_t *
1303251881Spetersvn_path_resolve_repos_relative_url(const char **absolute_url,
1304251881Speter                                    const char *relative_url,
1305251881Speter                                    const char *repos_root_url,
1306251881Speter                                    apr_pool_t *pool)
1307251881Speter{
1308251881Speter  if (! svn_path_is_repos_relative_url(relative_url))
1309251881Speter    return svn_error_createf(SVN_ERR_BAD_URL, NULL,
1310251881Speter                             _("Improper relative URL '%s'"),
1311251881Speter                             relative_url);
1312251881Speter
1313289180Speter  /* No assumptions are made about the canonicalization of the input
1314251881Speter   * arguments, it is presumed that the output will be canonicalized after
1315251881Speter   * this function, which will remove any duplicate path separator.
1316251881Speter   */
1317251881Speter  *absolute_url = apr_pstrcat(pool, repos_root_url, relative_url + 1,
1318289180Speter                              SVN_VA_NULL);
1319251881Speter
1320251881Speter  return SVN_NO_ERROR;
1321251881Speter}
1322251881Speter
1323