OSDN Git Service

TortoiseMerge Basic Support Git patch created by format patch
[tortoisegit/TortoiseGitJp.git] / src / TortoiseMerge / libsvn_diff / dirent_uri.c
1 /*\r
2  * dirent_uri.c:   a library to manipulate URIs and directory entries.\r
3  *\r
4  * ====================================================================\r
5  * Copyright (c) 2008-2009 CollabNet.  All rights reserved.\r
6  *\r
7  * This software is licensed as described in the file COPYING, which\r
8  * you should have received as part of this distribution.  The terms\r
9  * are also available at http://subversion.tigris.org/license-1.html.\r
10  * If newer versions of this license are posted there, you may use a\r
11  * newer version instead, at your option.\r
12  *\r
13  * This software consists of voluntary contributions made by many\r
14  * individuals.  For exact contribution history, see the revision\r
15  * history and logs, available at http://subversion.tigris.org/.\r
16  * ====================================================================\r
17  */\r
18 \r
19 \r
20 \f\r
21 #include <string.h>\r
22 #include <assert.h>\r
23 #include <ctype.h>\r
24 \r
25 #include <apr_uri.h>\r
26 \r
27 //#include "svn_private_config.h"\r
28 #include "svn_string.h"\r
29 #include "svn_dirent_uri.h"\r
30 #include "svn_path.h"\r
31 \r
32 //#include "private_uri.h"\r
33 #define SVN_PATH_LOCAL_SEPARATOR '\\'\r
34 const char *\r
35 svn_uri_canonicalize(const char *uri, apr_pool_t *pool);\r
36 \r
37 /* The canonical empty path.  Can this be changed?  Well, change the empty\r
38    test below and the path library will work, not so sure about the fs/wc\r
39    libraries. */\r
40 #define SVN_EMPTY_PATH ""\r
41 \r
42 /* TRUE if s is the canonical empty path, FALSE otherwise */\r
43 #define SVN_PATH_IS_EMPTY(s) ((s)[0] == '\0')\r
44 \r
45 /* TRUE if s,n is the platform's empty path ("."), FALSE otherwise. Can\r
46    this be changed?  Well, the path library will work, not so sure about\r
47    the OS! */\r
48 #define SVN_PATH_IS_PLATFORM_EMPTY(s,n) ((n) == 1 && (s)[0] == '.')\r
49 \r
50 /* Path type definition. Used only by internal functions. */\r
51 typedef enum {\r
52   type_uri,\r
53   type_dirent\r
54 } path_type_t;\r
55 \r
56 \f\r
57 /**** Internal implementation functions *****/\r
58 \r
59 /* Return an internal-style new path based on PATH, allocated in POOL.\r
60  * Pass type_uri for TYPE if PATH is a uri and type_dirent if PATH\r
61  * is a regular path.\r
62  *\r
63  * "Internal-style" means that separators are all '/', and the new\r
64  * path is canonicalized.\r
65  */\r
66 static const char *\r
67 internal_style(path_type_t type, const char *path, apr_pool_t *pool)\r
68 {\r
69 #if '/' != SVN_PATH_LOCAL_SEPARATOR\r
70     {\r
71       char *p = apr_pstrdup(pool, path);\r
72       path = p;\r
73 \r
74       /* Convert all local-style separators to the canonical ones. */\r
75       for (; *p != '\0'; ++p)\r
76         if (*p == SVN_PATH_LOCAL_SEPARATOR)\r
77           *p = '/';\r
78     }\r
79 #endif\r
80 \r
81   return type == type_uri ? svn_uri_canonicalize(path, pool)\r
82                           : svn_dirent_canonicalize(path, pool);\r
83   /* FIXME: Should also remove trailing /.'s, if the style says so. */\r
84 }\r
85 \r
86 /* Return a local-style new path based on PATH, allocated in POOL.\r
87  * Pass type_uri for TYPE if PATH is a uri and type_dirent if PATH\r
88  * is a regular path.\r
89  *\r
90  * "Local-style" means a path that looks like what users are\r
91  * accustomed to seeing, including native separators.  The new path\r
92  * will still be canonicalized.\r
93  */\r
94 static const char *\r
95 local_style(path_type_t type, const char *path, apr_pool_t *pool)\r
96 {\r
97   path = type == type_uri ? svn_uri_canonicalize(path, pool)\r
98                           : svn_dirent_canonicalize(path, pool);\r
99   /* FIXME: Should also remove trailing /.'s, if the style says so. */\r
100 \r
101   /* Internally, Subversion represents the current directory with the\r
102      empty string.  But users like to see "." . */\r
103   if (SVN_PATH_IS_EMPTY(path))\r
104     return ".";\r
105 \r
106   /* If PATH is a URL, the "local style" is the same as the input. */\r
107   if (type == type_uri && svn_path_is_url(path))\r
108     return apr_pstrdup(pool, path);\r
109 \r
110 #if '/' != SVN_PATH_LOCAL_SEPARATOR\r
111     {\r
112       char *p = apr_pstrdup(pool, path);\r
113       path = p;\r
114 \r
115       /* Convert all canonical separators to the local-style ones. */\r
116       for (; *p != '\0'; ++p)\r
117         if (*p == '/')\r
118           *p = SVN_PATH_LOCAL_SEPARATOR;\r
119     }\r
120 #endif\r
121 \r
122   return path;\r
123 }\r
124 \r
125 /* Locale insensitive tolower() for converting parts of dirents and urls\r
126    while canonicalizing */\r
127 static char\r
128 canonicalize_to_lower(char c)\r
129 {\r
130   if (c < 'A' || c > 'Z')\r
131     return c;\r
132   else\r
133     return c - 'A' + 'a';\r
134 }\r
135 #if defined(WIN32) || defined(__CYGWIN__)\r
136 /* Locale insensitive toupper() for converting parts of dirents and urls\r
137    while canonicalizing */\r
138 static char\r
139 canonicalize_to_upper(char c)\r
140 {\r
141   if (c < 'a' || c > 'z')\r
142     return c;\r
143   else\r
144     return c - 'a' + 'A';\r
145 }\r
146 #endif\r
147 \r
148 /* Return the length of substring necessary to encompass the entire\r
149  * previous dirent segment in DIRENT, which should be a LEN byte string.\r
150  *\r
151  * A trailing slash will not be included in the returned length except\r
152  * in the case in which DIRENT is absolute and there are no more\r
153  * previous segments.\r
154  */\r
155 static apr_size_t\r
156 dirent_previous_segment(const char *dirent,\r
157                         apr_size_t len)\r
158 {\r
159   if (len == 0)\r
160     return 0;\r
161 \r
162   --len;\r
163   while (len > 0 && dirent[len] != '/'\r
164 #if defined(WIN32) || defined(__CYGWIN__)\r
165                  && dirent[len] != ':'\r
166 #endif /* WIN32 or Cygwin */\r
167         )\r
168     --len;\r
169 \r
170   /* check if the remaining segment including trailing '/' is a root dirent */\r
171   if (svn_dirent_is_root(dirent, len + 1))\r
172     return len + 1;\r
173   else\r
174     return len;\r
175 }\r
176 \r
177 /* Return the length of substring necessary to encompass the entire\r
178  * previous uri segment in URI, which should be a LEN byte string.\r
179  *\r
180  * A trailing slash will not be included in the returned length except\r
181  * in the case in which URI is absolute and there are no more\r
182  * previous segments.\r
183  */\r
184 static apr_size_t\r
185 uri_previous_segment(const char *uri,\r
186                      apr_size_t len)\r
187 {\r
188   /* ### Still the old path segment code, should start checking scheme specific format */\r
189   if (len == 0)\r
190     return 0;\r
191 \r
192   --len;\r
193   while (len > 0 && uri[len] != '/')\r
194     --len;\r
195 \r
196   /* check if the remaining segment including trailing '/' is a root dirent */\r
197   if (svn_uri_is_root(uri, len + 1))\r
198     return len + 1;\r
199   else\r
200     return len;\r
201 }\r
202 \r
203 /* Return the canonicalized version of PATH, allocated in POOL.\r
204  * Pass type_uri for TYPE if PATH is a uri and type_dirent if PATH\r
205  * is a regular path.\r
206  */\r
207 static const char *\r
208 canonicalize(path_type_t type, const char *path, apr_pool_t *pool)\r
209 {\r
210   char *canon, *dst;\r
211   const char *src;\r
212   apr_size_t seglen;\r
213   apr_size_t schemelen = 0;\r
214   apr_size_t canon_segments = 0;\r
215   svn_boolean_t url = FALSE;\r
216 \r
217   /* "" is already canonical, so just return it; note that later code\r
218      depends on path not being zero-length.  */\r
219   if (SVN_PATH_IS_EMPTY(path))\r
220     return path;\r
221 \r
222   dst = canon = apr_pcalloc(pool, strlen(path) + 1);\r
223 \r
224   /* Try to parse the path as an URI. */\r
225   url = FALSE;\r
226   src = path;\r
227 \r
228   if (type == type_uri && *src != '/')\r
229     {\r
230       while (*src && (*src != '/') && (*src != ':'))\r
231         src++;\r
232 \r
233       if (*src == ':' && *(src+1) == '/' && *(src+2) == '/')\r
234         {\r
235           const char *seg;\r
236 \r
237           url = TRUE;\r
238 \r
239           /* Found a scheme, convert to lowercase and copy to dst. */\r
240           src = path;\r
241           while (*src != ':')\r
242             {\r
243               *(dst++) = canonicalize_to_lower((*src++));\r
244               schemelen++;\r
245             }\r
246           *(dst++) = ':';\r
247           *(dst++) = '/';\r
248           *(dst++) = '/';\r
249           src += 3;\r
250           schemelen += 3;\r
251 \r
252           /* This might be the hostname */\r
253           seg = src;\r
254           while (*src && (*src != '/') && (*src != '@'))\r
255             src++;\r
256 \r
257           if (*src == '@')\r
258             {\r
259               /* Copy the username & password. */\r
260               seglen = src - seg + 1;\r
261               memcpy(dst, seg, seglen);\r
262               dst += seglen;\r
263               src++;\r
264             }\r
265           else\r
266             src = seg;\r
267 \r
268           /* Found a hostname, convert to lowercase and copy to dst. */\r
269           while (*src && (*src != '/'))\r
270             *(dst++) = canonicalize_to_lower((*src++));\r
271 \r
272           /* Copy trailing slash, or null-terminator. */\r
273           *(dst) = *(src);\r
274 \r
275           /* Move src and dst forward only if we are not\r
276            * at null-terminator yet. */\r
277           if (*src)\r
278             {\r
279               src++;\r
280               dst++;\r
281             }\r
282 \r
283           canon_segments = 1;\r
284         }\r
285     }\r
286 \r
287   if (! url)\r
288     {\r
289       src = path;\r
290       /* If this is an absolute path, then just copy over the initial\r
291          separator character. */\r
292       if (*src == '/')\r
293         {\r
294           *(dst++) = *(src++);\r
295 \r
296 #if defined(WIN32) || defined(__CYGWIN__)\r
297           /* On Windows permit two leading separator characters which means an\r
298            * UNC path. */\r
299           if ((type == type_dirent) && *src == '/')\r
300             *(dst++) = *(src++);\r
301 #endif /* WIN32 or Cygwin */\r
302         }\r
303     }\r
304 \r
305   while (*src)\r
306     {\r
307       /* Parse each segment, find the closing '/' */\r
308       const char *next = src;\r
309       while (*next && (*next != '/'))\r
310         ++next;\r
311 \r
312       seglen = next - src;\r
313 \r
314       if (seglen == 0 || (seglen == 1 && src[0] == '.'))\r
315         {\r
316           /* Noop segment, so do nothing. */\r
317         }\r
318 #if defined(WIN32) || defined(__CYGWIN__)\r
319       /* If this is the first path segment of a file:// URI and it contains a\r
320          windows drive letter, convert the drive letter to upper case. */\r
321       else if (url && canon_segments == 1 && seglen == 2 &&\r
322                (strncmp(canon, "file:", 5) == 0) &&\r
323                src[0] >= 'a' && src[0] <= 'z' && src[1] == ':')\r
324         {\r
325           *(dst++) = canonicalize_to_upper(src[0]);\r
326           *(dst++) = ':';\r
327           if (*next)\r
328             *(dst++) = *next;\r
329           canon_segments++;\r
330         }\r
331 #endif /* WIN32 or Cygwin */\r
332       else\r
333         {\r
334           /* An actual segment, append it to the destination path */\r
335           if (*next)\r
336             seglen++;\r
337           memcpy(dst, src, seglen);\r
338           dst += seglen;\r
339           canon_segments++;\r
340         }\r
341 \r
342       /* Skip over trailing slash to the next segment. */\r
343       src = next;\r
344       if (*src)\r
345         src++;\r
346     }\r
347 \r
348   /* Remove the trailing slash if there was at least one\r
349    * canonical segment and the last segment ends with a slash.\r
350    *\r
351    * But keep in mind that, for URLs, the scheme counts as a\r
352    * canonical segment -- so if path is ONLY a scheme (such\r
353    * as "https://") we should NOT remove the trailing slash. */\r
354   if ((canon_segments > 0 && *(dst - 1) == '/')\r
355       && ! (url && path[schemelen] == '\0'))\r
356     {\r
357       dst --;\r
358     }\r
359 \r
360   *dst = '\0';\r
361 \r
362 #if defined(WIN32) || defined(__CYGWIN__)\r
363   /* Skip leading double slashes when there are less than 2\r
364    * canon segments. UNC paths *MUST* have two segments. */\r
365   if ((type == type_dirent) && canon[0] == '/' && canon[1] == '/')\r
366     {\r
367       if (canon_segments < 2)\r
368         return canon + 1;\r
369       else\r
370         {\r
371           /* Now we're sure this is a valid UNC path, convert the server name\r
372              (the first path segment) to lowercase as Windows treats it as case\r
373              insensitive.\r
374              Note: normally the share name is treated as case insensitive too,\r
375              but it seems to be possible to configure Samba to treat those as\r
376              case sensitive, so better leave that alone. */\r
377           dst = canon + 2;\r
378           while (*dst && *dst != '/')\r
379             *(dst++) = canonicalize_to_lower(*dst);\r
380         }\r
381     }\r
382 #endif /* WIN32 or Cygwin */\r
383 \r
384   return canon;\r
385 }\r
386 \r
387 /* Return the string length of the longest common ancestor of PATH1 and PATH2.\r
388  * Pass type_uri for TYPE if PATH1 and PATH2 are URIs, and type_dirent if\r
389  * PATH1 and PATH2 are regular paths.\r
390  *\r
391  * If the two paths do not share a common ancestor, return 0.\r
392  *\r
393  * New strings are allocated in POOL.\r
394  */\r
395 static apr_size_t\r
396 get_longest_ancestor_length(path_type_t types,\r
397                             const char *path1,\r
398                             const char *path2,\r
399                             apr_pool_t *pool)\r
400 {\r
401   apr_size_t path1_len, path2_len;\r
402   apr_size_t i = 0;\r
403   apr_size_t last_dirsep = 0;\r
404 #if defined(WIN32) || defined(__CYGWIN__)\r
405   svn_boolean_t unc = FALSE;\r
406 #endif\r
407 \r
408   path1_len = strlen(path1);\r
409   path2_len = strlen(path2);\r
410 \r
411   if (SVN_PATH_IS_EMPTY(path1) || SVN_PATH_IS_EMPTY(path2))\r
412     return 0;\r
413 \r
414   while (path1[i] == path2[i])\r
415     {\r
416       /* Keep track of the last directory separator we hit. */\r
417       if (path1[i] == '/')\r
418         last_dirsep = i;\r
419 \r
420       i++;\r
421 \r
422       /* If we get to the end of either path, break out. */\r
423       if ((i == path1_len) || (i == path2_len))\r
424         break;\r
425     }\r
426 \r
427   /* two special cases:\r
428      1. '/' is the longest common ancestor of '/' and '/foo' */\r
429   if (i == 1 && path1[0] == '/' && path2[0] == '/')\r
430     return 1;\r
431   /* 2. '' is the longest common ancestor of any non-matching\r
432    * strings 'foo' and 'bar' */\r
433   if (types == type_dirent && i == 0)\r
434     return 0;\r
435 \r
436   /* Handle some windows specific cases */\r
437 #if defined(WIN32) || defined(__CYGWIN__)\r
438   if (types == type_dirent)\r
439     {\r
440       /* don't count the '//' from UNC paths */\r
441       if (last_dirsep == 1 && path1[0] == '/' && path1[1] == '/')\r
442         {\r
443           last_dirsep = 0;\r
444           unc = TRUE;\r
445         }\r
446 \r
447       /* X:/ and X:/foo */\r
448       if (i == 3 && path1[2] == '/' && path1[1] == ':')\r
449         return i;\r
450 \r
451       /* Cannot use SVN_ERR_ASSERT here, so we'll have to crash, sorry.\r
452        * Note that this assertion triggers only if the code above has\r
453        * been broken. The code below relies on this assertion, because\r
454        * it uses [i - 1] as index. */\r
455       assert(i > 0);\r
456 \r
457       /* X: and X:/ */\r
458       if ((path1[i - 1] == ':' && path2[i] == '/') ||\r
459           (path2[i - 1] == ':' && path1[i] == '/'))\r
460           return 0;\r
461       /* X: and X:foo */\r
462       if (path1[i - 1] == ':' || path2[i - 1] == ':')\r
463           return i;\r
464     }\r
465 #endif /* WIN32 or Cygwin */\r
466 \r
467   /* last_dirsep is now the offset of the last directory separator we\r
468      crossed before reaching a non-matching byte.  i is the offset of\r
469      that non-matching byte, and is guaranteed to be <= the length of\r
470      whichever path is shorter.\r
471      If one of the paths is the common part return that. */\r
472   if (((i == path1_len) && (path2[i] == '/'))\r
473            || ((i == path2_len) && (path1[i] == '/'))\r
474            || ((i == path1_len) && (i == path2_len)))\r
475     return i;\r
476   else\r
477     {\r
478       /* Nothing in common but the root folder '/' or 'X:/' for Windows\r
479          dirents. */\r
480 #if defined(WIN32) || defined(__CYGWIN__)\r
481       if (! unc)\r
482         {\r
483           /* X:/foo and X:/bar returns X:/ */\r
484           if ((types == type_dirent) &&\r
485               last_dirsep == 2 && path1[1] == ':' && path1[2] == '/'\r
486                                && path2[1] == ':' && path2[2] == '/')\r
487             return 3;\r
488 #endif\r
489           if (last_dirsep == 0 && path1[0] == '/' && path2[0] == '/')\r
490             return 1;\r
491 #if defined(WIN32) || defined(__CYGWIN__)\r
492         }\r
493 #endif\r
494     }\r
495 \r
496   return last_dirsep;\r
497 }\r
498 \r
499 /* Determine whether PATH2 is a child of PATH1.\r
500  *\r
501  * PATH2 is a child of PATH1 if\r
502  * 1) PATH1 is empty, and PATH2 is not empty and not an absolute path.\r
503  * or\r
504  * 2) PATH2 is has n components, PATH1 has x < n components,\r
505  *    and PATH1 matches PATH2 in all its x components.\r
506  *    Components are separated by a slash, '/'.\r
507  *\r
508  * Pass type_uri for TYPE if PATH1 and PATH2 are URIs, and type_dirent if\r
509  * PATH1 and PATH2 are regular paths.\r
510  *\r
511  * If PATH2 is not a child of PATH1, return NULL.\r
512  *\r
513  * If PATH2 is a child of PATH1, and POOL is not NULL, allocate a copy\r
514  * of the child part of PATH2 in POOL and return a pointer to the\r
515  * newly allocated child part.\r
516  *\r
517  * If PATH2 is a child of PATH1, and POOL is NULL, return a pointer\r
518  * pointing to the child part of PATH2.\r
519  * */\r
520 static const char *\r
521 is_child(path_type_t type, const char *path1, const char *path2,\r
522          apr_pool_t *pool)\r
523 {\r
524   apr_size_t i;\r
525 \r
526   /* Allow "" and "foo" or "H:foo" to be parent/child */\r
527   if (SVN_PATH_IS_EMPTY(path1))               /* "" is the parent  */\r
528     {\r
529       if (SVN_PATH_IS_EMPTY(path2))            /* "" not a child    */\r
530         return NULL;\r
531 \r
532       /* check if this is an absolute path */\r
533       if ((type == type_uri && svn_uri_is_absolute(path2)) ||\r
534           (type == type_dirent && svn_dirent_is_absolute(path2)))\r
535         return NULL;\r
536       else\r
537         /* everything else is child */\r
538         return pool ? apr_pstrdup(pool, path2) : path2;\r
539     }\r
540 \r
541   /* Reach the end of at least one of the paths.  How should we handle\r
542      things like path1:"foo///bar" and path2:"foo/bar/baz"?  It doesn't\r
543      appear to arise in the current Subversion code, it's not clear to me\r
544      if they should be parent/child or not. */\r
545   /* Hmmm... aren't paths assumed to be canonical in this function?\r
546    * How can "foo///bar" even happen if the paths are canonical? */\r
547   for (i = 0; path1[i] && path2[i]; i++)\r
548     if (path1[i] != path2[i])\r
549       return NULL;\r
550 \r
551   /* FIXME: This comment does not really match\r
552    * the checks made in the code it refers to: */\r
553   /* There are two cases that are parent/child\r
554           ...      path1[i] == '\0'\r
555           .../foo  path2[i] == '/'\r
556       or\r
557           /        path1[i] == '\0'\r
558           /foo     path2[i] != '/'\r
559 \r
560      Other root paths (like X:/) fall under the former case:\r
561           X:/        path1[i] == '\0'\r
562           X:/foo     path2[i] != '/'\r
563 \r
564      Check for '//' to avoid matching '/' and '//srv'.\r
565   */\r
566   if (path1[i] == '\0' && path2[i])\r
567     {\r
568       if (path1[i - 1] == '/'\r
569 #if defined(WIN32) || defined(__CYGWIN__)\r
570           || ((type == type_dirent) && path1[i - 1] == ':')\r
571 #endif /* WIN32 or Cygwin */\r
572            )\r
573         {\r
574           if (path2[i] == '/')\r
575             /* .../\r
576              * ..../\r
577              *     i   */\r
578             return NULL;\r
579           else\r
580             /* .../\r
581              * .../foo\r
582              *     i    */\r
583             return pool ? apr_pstrdup(pool, path2 + i) : path2 + i;\r
584         }\r
585       else if (path2[i] == '/')\r
586         {\r
587           if (path2[i + 1])\r
588             /* ...\r
589              * .../foo\r
590              *    i    */\r
591             return pool ? apr_pstrdup(pool, path2 + i + 1) : path2 + i + 1;\r
592           else\r
593             /* ...\r
594              * .../\r
595              *    i    */\r
596             return NULL;\r
597         }\r
598     }\r
599 \r
600   /* Otherwise, path2 isn't a child. */\r
601   return NULL;\r
602 }\r
603 \r
604 /* FIXME: no doc string */\r
605 static svn_boolean_t\r
606 is_ancestor(path_type_t type, const char *path1, const char *path2)\r
607 {\r
608   apr_size_t path1_len;\r
609 \r
610   /* If path1 is empty and path2 is not absolute, then path1 is an ancestor. */\r
611   if (SVN_PATH_IS_EMPTY(path1))\r
612     {\r
613       return type == type_uri ? ! svn_uri_is_absolute(path2)\r
614                               : ! svn_dirent_is_absolute(path2);\r
615     }\r
616 \r
617   /* If path1 is a prefix of path2, then:\r
618      - If path1 ends in a path separator,\r
619      - If the paths are of the same length\r
620      OR\r
621      - path2 starts a new path component after the common prefix,\r
622      then path1 is an ancestor. */\r
623   path1_len = strlen(path1);\r
624   if (strncmp(path1, path2, path1_len) == 0)\r
625     return path1[path1_len - 1] == '/'\r
626 #if defined(WIN32) || defined(__CYGWIN__)\r
627       || ((type == type_dirent) && path1[path1_len - 1] == ':')\r
628 #endif /* WIN32 or Cygwin */\r
629       || (path2[path1_len] == '/' || path2[path1_len] == '\0');\r
630 \r
631   return FALSE;\r
632 }\r
633 \r
634 \f\r
635 /**** Public API functions ****/\r
636 \r
637 const char *\r
638 svn_dirent_internal_style(const char *dirent, apr_pool_t *pool)\r
639 {\r
640   return internal_style(type_dirent, dirent, pool);\r
641 }\r
642 \r
643 const char *\r
644 svn_dirent_local_style(const char *dirent, apr_pool_t *pool)\r
645 {\r
646   return local_style(type_dirent, dirent, pool);\r
647 }\r
648 \r
649 const char *\r
650 svn_uri_internal_style(const char *uri, apr_pool_t *pool)\r
651 {\r
652   return internal_style(type_uri, uri, pool);\r
653 }\r
654 \r
655 const char *\r
656 svn_uri_local_style(const char *uri, apr_pool_t *pool)\r
657 {\r
658   return local_style(type_uri, uri, pool);\r
659 }\r
660 \r
661 /* We decided against using apr_filepath_root here because of the negative\r
662    performance impact (creating a pool and converting strings ). */\r
663 svn_boolean_t\r
664 svn_dirent_is_root(const char *dirent, apr_size_t len)\r
665 {\r
666   /* directory is root if it's equal to '/' */\r
667   if (len == 1 && dirent[0] == '/')\r
668     return TRUE;\r
669 \r
670 #if defined(WIN32) || defined(__CYGWIN__)\r
671   /* On Windows and Cygwin, 'H:' or 'H:/' (where 'H' is any letter)\r
672      are also root directories */\r
673   if ((len == 2 || len == 3) &&\r
674       (dirent[1] == ':') &&\r
675       ((dirent[0] >= 'A' && dirent[0] <= 'Z') ||\r
676        (dirent[0] >= 'a' && dirent[0] <= 'z')) &&\r
677       (len == 2 || (dirent[2] == '/' && len == 3)))\r
678     return TRUE;\r
679 \r
680   /* On Windows and Cygwin, both //drive and //server/share are root\r
681      directories */\r
682   if (len >= 2 && dirent[0] == '/' && dirent[1] == '/'\r
683       && dirent[len - 1] != '/')\r
684     {\r
685       int segments = 0;\r
686       int i;\r
687       for (i = len; i >= 2; i--)\r
688         {\r
689           if (dirent[i] == '/')\r
690             {\r
691               segments ++;\r
692               if (segments > 1)\r
693                 return FALSE;\r
694             }\r
695         }\r
696       return (segments <= 1);\r
697     }\r
698 #endif /* WIN32 or Cygwin */\r
699 \r
700   return FALSE;\r
701 }\r
702 \r
703 svn_boolean_t\r
704 svn_uri_is_root(const char *uri, apr_size_t len)\r
705 {\r
706   /* directory is root if it's equal to '/' */\r
707   if (len == 1 && uri[0] == '/')\r
708     return TRUE;\r
709 \r
710   return FALSE;\r
711 }\r
712 \r
713 char *svn_dirent_join(const char *base,\r
714                       const char *component,\r
715                       apr_pool_t *pool)\r
716 {\r
717   apr_size_t blen = strlen(base);\r
718   apr_size_t clen = strlen(component);\r
719   char *dirent;\r
720   int add_separator;\r
721 \r
722   assert(svn_dirent_is_canonical(base, pool));\r
723   assert(svn_dirent_is_canonical(component, pool));\r
724 \r
725   /* If the component is absolute, then return it.  */\r
726   if (svn_dirent_is_absolute(component))\r
727     return apr_pmemdup(pool, component, clen + 1);\r
728 \r
729   /* If either is empty return the other */\r
730   if (SVN_PATH_IS_EMPTY(base))\r
731     return apr_pmemdup(pool, component, clen + 1);\r
732   if (SVN_PATH_IS_EMPTY(component))\r
733     return apr_pmemdup(pool, base, blen + 1);\r
734 \r
735   /* if last character of base is already a separator, don't add a '/' */\r
736   add_separator = 1;\r
737   if (base[blen - 1] == '/'\r
738 #if defined(WIN32) || defined(__CYGWIN__)\r
739        || base[blen - 1] == ':'\r
740 #endif /* WIN32 or Cygwin */\r
741         )\r
742           add_separator = 0;\r
743 \r
744   /* Construct the new, combined dirent. */\r
745   dirent = apr_palloc(pool, blen + add_separator + clen + 1);\r
746   memcpy(dirent, base, blen);\r
747   if (add_separator)\r
748     dirent[blen] = '/';\r
749   memcpy(dirent + blen + add_separator, component, clen + 1);\r
750 \r
751   return dirent;\r
752 }\r
753 \r
754 char *svn_dirent_join_many(apr_pool_t *pool, const char *base, ...)\r
755 {\r
756 #define MAX_SAVED_LENGTHS 10\r
757   apr_size_t saved_lengths[MAX_SAVED_LENGTHS];\r
758   apr_size_t total_len;\r
759   int nargs;\r
760   va_list va;\r
761   const char *s;\r
762   apr_size_t len;\r
763   char *dirent;\r
764   char *p;\r
765   int add_separator;\r
766   int base_arg = 0;\r
767 \r
768   total_len = strlen(base);\r
769 \r
770   assert(svn_dirent_is_canonical(base, pool));\r
771 \r
772   /* if last character of base is already a separator, don't add a '/' */\r
773   add_separator = 1;\r
774   if (total_len == 0\r
775        || base[total_len - 1] == '/'\r
776 #if defined(WIN32) || defined(__CYGWIN__)\r
777        || base[total_len - 1] == ':'\r
778 #endif /* WIN32 or Cygwin */\r
779         )\r
780           add_separator = 0;\r
781 \r
782   saved_lengths[0] = total_len;\r
783 \r
784   /* Compute the length of the resulting string. */\r
785 \r
786   nargs = 0;\r
787   va_start(va, base);\r
788   while ((s = va_arg(va, const char *)) != NULL)\r
789     {\r
790       len = strlen(s);\r
791 \r
792       assert(svn_dirent_is_canonical(s, pool));\r
793 \r
794       if (SVN_PATH_IS_EMPTY(s))\r
795         continue;\r
796 \r
797       if (nargs++ < MAX_SAVED_LENGTHS)\r
798         saved_lengths[nargs] = len;\r
799 \r
800       if (svn_dirent_is_absolute(s))\r
801         {\r
802           /* an absolute dirent. skip all components to this point and reset\r
803              the total length. */\r
804           total_len = len;\r
805           base_arg = nargs;\r
806           add_separator = 1;\r
807           if (s[len - 1] == '/'\r
808         #if defined(WIN32) || defined(__CYGWIN__)\r
809                || s[len - 1] == ':'\r
810         #endif /* WIN32 or Cygwin */\r
811                 )\r
812                   add_separator = 0;\r
813         }\r
814       else if (nargs == base_arg + 1)\r
815         {\r
816           total_len += add_separator + len;\r
817         }\r
818       else\r
819         {\r
820           total_len += 1 + len;\r
821         }\r
822     }\r
823   va_end(va);\r
824 \r
825   /* base == "/" and no further components. just return that. */\r
826   if (add_separator == 0 && total_len == 1)\r
827     return apr_pmemdup(pool, "/", 2);\r
828 \r
829   /* we got the total size. allocate it, with room for a NULL character. */\r
830   dirent = p = apr_palloc(pool, total_len + 1);\r
831 \r
832   /* if we aren't supposed to skip forward to an absolute component, and if\r
833      this is not an empty base that we are skipping, then copy the base\r
834      into the output. */\r
835   if (base_arg == 0 && ! (SVN_PATH_IS_EMPTY(base)))\r
836     {\r
837       if (SVN_PATH_IS_EMPTY(base))\r
838         memcpy(p, SVN_EMPTY_PATH, len = saved_lengths[0]);\r
839       else\r
840         memcpy(p, base, len = saved_lengths[0]);\r
841       p += len;\r
842     }\r
843 \r
844   nargs = 0;\r
845   va_start(va, base);\r
846   while ((s = va_arg(va, const char *)) != NULL)\r
847     {\r
848       if (SVN_PATH_IS_EMPTY(s))\r
849         continue;\r
850 \r
851       if (++nargs < base_arg)\r
852         continue;\r
853 \r
854       if (nargs < MAX_SAVED_LENGTHS)\r
855         len = saved_lengths[nargs];\r
856       else\r
857         len = strlen(s);\r
858 \r
859       /* insert a separator if we aren't copying in the first component\r
860          (which can happen when base_arg is set). also, don't put in a slash\r
861          if the prior character is a slash (occurs when prior component\r
862          is "/"). */\r
863       if (p != dirent &&\r
864           ( ! (nargs - 1 == base_arg) || add_separator))\r
865         *p++ = '/';\r
866 \r
867       /* copy the new component and advance the pointer */\r
868       memcpy(p, s, len);\r
869       p += len;\r
870     }\r
871   va_end(va);\r
872 \r
873   *p = '\0';\r
874   assert((apr_size_t)(p - dirent) == total_len);\r
875 \r
876   return dirent;\r
877 }\r
878 \r
879 char *\r
880 svn_dirent_dirname(const char *dirent, apr_pool_t *pool)\r
881 {\r
882   apr_size_t len = strlen(dirent);\r
883 \r
884   assert(svn_dirent_is_canonical(dirent, pool));\r
885 \r
886   if (svn_dirent_is_root(dirent, len))\r
887     return apr_pstrmemdup(pool, dirent, len);\r
888   else\r
889     return apr_pstrmemdup(pool, dirent, dirent_previous_segment(dirent, len));\r
890 }\r
891 \r
892 char *\r
893 svn_uri_dirname(const char *uri, apr_pool_t *pool)\r
894 {\r
895   apr_size_t len = strlen(uri);\r
896 \r
897   assert(svn_uri_is_canonical(uri, pool));\r
898 \r
899   if (svn_uri_is_root(uri, len))\r
900     return apr_pstrmemdup(pool, uri, len);\r
901   else\r
902     return apr_pstrmemdup(pool, uri, uri_previous_segment(uri, len));\r
903 }\r
904 \r
905 char *\r
906 svn_dirent_get_longest_ancestor(const char *dirent1,\r
907                                 const char *dirent2,\r
908                                 apr_pool_t *pool)\r
909 {\r
910   return apr_pstrndup(pool, dirent1,\r
911                       get_longest_ancestor_length(type_dirent, dirent1,\r
912                                                   dirent2, pool));\r
913 }\r
914 \r
915 char *\r
916 svn_uri_get_longest_ancestor(const char *uri1,\r
917                              const char *uri2,\r
918                              apr_pool_t *pool)\r
919 {\r
920   svn_boolean_t uri1_is_url, uri2_is_url;\r
921   uri1_is_url = svn_path_is_url(uri1);\r
922   uri2_is_url = svn_path_is_url(uri2);\r
923 \r
924   if (uri1_is_url && uri2_is_url)\r
925     {\r
926       apr_size_t uri_ancestor_len;\r
927       apr_size_t i = 0;\r
928 \r
929       /* Find ':' */\r
930       while (1)\r
931         {\r
932           /* No shared protocol => no common prefix */\r
933           if (uri1[i] != uri2[i])\r
934             return apr_pmemdup(pool, SVN_EMPTY_PATH,\r
935                                sizeof(SVN_EMPTY_PATH));\r
936 \r
937           if (uri1[i] == ':')\r
938             break;\r
939 \r
940           /* They're both URLs, so EOS can't come before ':' */\r
941           assert((uri1[i] != '\0') && (uri2[i] != '\0'));\r
942 \r
943           i++;\r
944         }\r
945 \r
946       i += 3;  /* Advance past '://' */\r
947 \r
948       uri_ancestor_len = get_longest_ancestor_length(type_uri, uri1 + i,\r
949                                                      uri2 + i, pool);\r
950 \r
951       if (uri_ancestor_len == 0 ||\r
952           (uri_ancestor_len == 1 && (uri1 + i)[0] == '/'))\r
953         return apr_pmemdup(pool, SVN_EMPTY_PATH, sizeof(SVN_EMPTY_PATH));\r
954       else\r
955         return apr_pstrndup(pool, uri1, uri_ancestor_len + i);\r
956     }\r
957 \r
958   else if ((! uri1_is_url) && (! uri2_is_url))\r
959     {\r
960       return apr_pstrndup(pool, uri1,\r
961                           get_longest_ancestor_length(type_uri, uri1, uri2,\r
962                                                       pool));\r
963     }\r
964 \r
965   else\r
966     {\r
967       /* A URL and a non-URL => no common prefix */\r
968       return apr_pmemdup(pool, SVN_EMPTY_PATH, sizeof(SVN_EMPTY_PATH));\r
969     }\r
970 }\r
971 \r
972 const char *\r
973 svn_dirent_is_child(const char *dirent1,\r
974                     const char *dirent2,\r
975                     apr_pool_t *pool)\r
976 {\r
977   return is_child(type_dirent, dirent1, dirent2, pool);\r
978 }\r
979 \r
980 const char *\r
981 svn_uri_is_child(const char *uri1,\r
982                  const char *uri2,\r
983                  apr_pool_t *pool)\r
984 {\r
985   return is_child(type_uri, uri1, uri2, pool);\r
986 }\r
987 \r
988 svn_boolean_t\r
989 svn_dirent_is_ancestor(const char *dirent1, const char *dirent2)\r
990 {\r
991   return is_ancestor(type_dirent, dirent1, dirent2);\r
992 }\r
993 \r
994 svn_boolean_t\r
995 svn_uri_is_ancestor(const char *uri1, const char *uri2)\r
996 {\r
997   return is_ancestor(type_uri, uri1, uri2);\r
998 }\r
999 \r
1000 svn_boolean_t\r
1001 svn_dirent_is_absolute(const char *dirent)\r
1002 {\r
1003   if (! dirent)\r
1004     return FALSE;\r
1005 \r
1006   /* dirent is absolute if it starts with '/' */\r
1007   if (dirent[0] == '/')\r
1008     return TRUE;\r
1009 \r
1010   /* On Windows, dirent is also absolute when it starts with 'H:' or 'H:/'\r
1011      where 'H' is any letter. */\r
1012 #if defined(WIN32) || defined(__CYGWIN__)\r
1013   if (((dirent[0] >= 'A' && dirent[0] <= 'Z') ||\r
1014        (dirent[0] >= 'a' && dirent[0] <= 'z')) &&\r
1015       (dirent[1] == ':'))\r
1016      return TRUE;\r
1017 #endif /* WIN32 or Cygwin */\r
1018 \r
1019   return FALSE;\r
1020 }\r
1021 \r
1022 svn_boolean_t\r
1023 svn_uri_is_absolute(const char *uri)\r
1024 {\r
1025   /* uri is absolute if it starts with '/' */\r
1026   if (uri && uri[0] == '/')\r
1027     return TRUE;\r
1028 \r
1029   /* URLs are absolute. */\r
1030   return svn_path_is_url(uri);\r
1031 }\r
1032 \r
1033 svn_error_t *\r
1034 svn_dirent_get_absolute(const char **pabsolute,\r
1035                         const char *relative,\r
1036                         apr_pool_t *pool)\r
1037 {\r
1038   char *buffer;\r
1039   apr_status_t apr_err;\r
1040   const char *path_apr;\r
1041 \r
1042   /* Merge the current working directory with the relative dirent. */\r
1043   SVN_ERR(svn_path_cstring_from_utf8(&path_apr, relative, pool));\r
1044 \r
1045   apr_err = apr_filepath_merge(&buffer, NULL,\r
1046                                path_apr,\r
1047                                APR_FILEPATH_NOTRELATIVE,\r
1048                                pool);\r
1049   if (apr_err)\r
1050     return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,\r
1051                              _("Couldn't determine absolute path of '%s'"),\r
1052                              svn_path_local_style(relative, pool));\r
1053 \r
1054   SVN_ERR(svn_path_cstring_to_utf8(pabsolute, buffer, pool));\r
1055   *pabsolute = svn_dirent_canonicalize(*pabsolute, pool);\r
1056   return SVN_NO_ERROR;\r
1057 }\r
1058 \r
1059 const char *\r
1060 svn_uri_canonicalize(const char *uri, apr_pool_t *pool)\r
1061 {\r
1062   return canonicalize(type_uri, uri, pool);;\r
1063 }\r
1064 \r
1065 const char *\r
1066 svn_dirent_canonicalize(const char *dirent, apr_pool_t *pool)\r
1067 {\r
1068   const char *dst = canonicalize(type_dirent, dirent, pool);;\r
1069 \r
1070 #if defined(WIN32) || defined(__CYGWIN__)\r
1071   /* Handle a specific case on Windows where path == "X:/". Here we have to\r
1072      append the final '/', as svn_path_canonicalize will chop this of. */\r
1073   if (((dirent[0] >= 'A' && dirent[0] <= 'Z') ||\r
1074         (dirent[0] >= 'a' && dirent[0] <= 'z')) &&\r
1075         dirent[1] == ':' && dirent[2] == '/' &&\r
1076         dst[3] == '\0')\r
1077     {\r
1078       char *dst_slash = apr_pcalloc(pool, 4);\r
1079       dst_slash[0] =  dirent[0];\r
1080       dst_slash[1] = ':';\r
1081       dst_slash[2] = '/';\r
1082       dst_slash[3] = '\0';\r
1083 \r
1084       return dst_slash;\r
1085     }\r
1086 #endif /* WIN32 or Cygwin */\r
1087 \r
1088   return dst;\r
1089 }\r
1090 \r
1091 svn_boolean_t\r
1092 svn_dirent_is_canonical(const char *dirent, apr_pool_t *pool)\r
1093 {\r
1094   return (strcmp(dirent, svn_dirent_canonicalize(dirent, pool)) == 0);\r
1095 }\r
1096 \r
1097 svn_boolean_t\r
1098 svn_uri_is_canonical(const char *uri, apr_pool_t *pool)\r
1099 {\r
1100   const char *ptr = uri, *seg = uri;\r
1101 \r
1102   /* URI is canonical if it has:\r
1103    *  - no '.' segments\r
1104    *  - no closing '/', unless for the root path '/' itself\r
1105    *  - no '//'\r
1106    *  - lowercase URL scheme\r
1107    *  - lowercase URL hostname\r
1108    */\r
1109 \r
1110   if (*uri == '\0')\r
1111     return TRUE;\r
1112 \r
1113   /* Maybe parse hostname and scheme. */\r
1114   if (*ptr != '/')\r
1115     {\r
1116       while (*ptr && (*ptr != '/') && (*ptr != ':'))\r
1117         ptr++;\r
1118 \r
1119       if (*ptr == ':' && *(ptr+1) == '/' && *(ptr+2) == '/')\r
1120         {\r
1121           /* Found a scheme, check that it's all lowercase. */\r
1122           ptr = uri;\r
1123           while (*ptr != ':')\r
1124             {\r
1125               if (*ptr >= 'A' && *ptr <= 'Z')\r
1126                 return FALSE;\r
1127               ptr++;\r
1128             }\r
1129           /* Skip :// */\r
1130           ptr += 3;\r
1131 \r
1132           /* This might be the hostname */\r
1133           seg = ptr;\r
1134           while (*ptr && (*ptr != '/') && (*ptr != '@'))\r
1135             ptr++;\r
1136 \r
1137           if (! *ptr)\r
1138             return TRUE;\r
1139 \r
1140           if (*ptr == '@')\r
1141             seg = ptr + 1;\r
1142 \r
1143           /* Found a hostname, check that it's all lowercase. */\r
1144           ptr = seg;\r
1145           while (*ptr && *ptr != '/')\r
1146             {\r
1147               if (*ptr >= 'A' && *ptr <= 'Z')\r
1148                 return FALSE;\r
1149               ptr++;\r
1150             }\r
1151         }\r
1152       else\r
1153         {\r
1154           /* Didn't find a scheme; finish the segment. */\r
1155           while (*ptr && *ptr != '/')\r
1156             ptr++;\r
1157         }\r
1158     }\r
1159 \r
1160 #if defined(WIN32) || defined(__CYGWIN__)\r
1161   if (*ptr == '/')\r
1162     {\r
1163       /* If this is a file url, ptr now points to the third '/' in\r
1164          file:///C:/path. Check that if we have such a URL the drive\r
1165          letter is in uppercase. */\r
1166       if (strncmp(uri, "file:", 5) == 0 &&\r
1167           ! (*(ptr+1) >= 'A' && *(ptr+1) <= 'Z') &&\r
1168           *(ptr+2) == ':')\r
1169         return FALSE;\r
1170     }\r
1171 #endif /* WIN32 or Cygwin */\r
1172 \r
1173   /* Now validate the rest of the URI. */\r
1174   while(1)\r
1175     {\r
1176       int seglen = ptr - seg;\r
1177 \r
1178       if (seglen == 1 && *seg == '.')\r
1179         return FALSE;  /*  /./   */\r
1180 \r
1181       if (*ptr == '/' && *(ptr+1) == '/')\r
1182         return FALSE;  /*  //    */\r
1183 \r
1184       if (! *ptr && *(ptr - 1) == '/' && ptr - 1 != uri)\r
1185         return FALSE;  /* foo/  */\r
1186 \r
1187       if (! *ptr)\r
1188         break;\r
1189 \r
1190       if (*ptr == '/')\r
1191         ptr++;\r
1192       seg = ptr;\r
1193 \r
1194       while (*ptr && (*ptr != '/'))\r
1195         ptr++;\r
1196     }\r
1197 \r
1198   return TRUE;\r
1199 }\r