dirent_uri.c revision 262253
1/* 2 * dirent_uri.c: a library to manipulate URIs and directory entries. 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24 25 26#include <string.h> 27#include <assert.h> 28#include <ctype.h> 29 30#include <apr_uri.h> 31#include <apr_lib.h> 32 33#include "svn_private_config.h" 34#include "svn_string.h" 35#include "svn_dirent_uri.h" 36#include "svn_path.h" 37#include "svn_ctype.h" 38 39#include "dirent_uri.h" 40#include "private/svn_fspath.h" 41 42/* The canonical empty path. Can this be changed? Well, change the empty 43 test below and the path library will work, not so sure about the fs/wc 44 libraries. */ 45#define SVN_EMPTY_PATH "" 46 47/* TRUE if s is the canonical empty path, FALSE otherwise */ 48#define SVN_PATH_IS_EMPTY(s) ((s)[0] == '\0') 49 50/* TRUE if s,n is the platform's empty path ("."), FALSE otherwise. Can 51 this be changed? Well, the path library will work, not so sure about 52 the OS! */ 53#define SVN_PATH_IS_PLATFORM_EMPTY(s,n) ((n) == 1 && (s)[0] == '.') 54 55/* This check must match the check on top of dirent_uri-tests.c and 56 path-tests.c */ 57#if defined(WIN32) || defined(__CYGWIN__) || defined(__OS2__) 58#define SVN_USE_DOS_PATHS 59#endif 60 61/* Path type definition. Used only by internal functions. */ 62typedef enum path_type_t { 63 type_uri, 64 type_dirent, 65 type_relpath 66} path_type_t; 67 68 69/**** Forward declarations *****/ 70 71static svn_boolean_t 72relpath_is_canonical(const char *relpath); 73 74 75/**** Internal implementation functions *****/ 76 77/* Return an internal-style new path based on PATH, allocated in POOL. 78 * 79 * "Internal-style" means that separators are all '/'. 80 */ 81static const char * 82internal_style(const char *path, apr_pool_t *pool) 83{ 84#if '/' != SVN_PATH_LOCAL_SEPARATOR 85 { 86 char *p = apr_pstrdup(pool, path); 87 path = p; 88 89 /* Convert all local-style separators to the canonical ones. */ 90 for (; *p != '\0'; ++p) 91 if (*p == SVN_PATH_LOCAL_SEPARATOR) 92 *p = '/'; 93 } 94#endif 95 96 return path; 97} 98 99/* Locale insensitive tolower() for converting parts of dirents and urls 100 while canonicalizing */ 101static char 102canonicalize_to_lower(char c) 103{ 104 if (c < 'A' || c > 'Z') 105 return c; 106 else 107 return (char)(c - 'A' + 'a'); 108} 109 110/* Locale insensitive toupper() for converting parts of dirents and urls 111 while canonicalizing */ 112static char 113canonicalize_to_upper(char c) 114{ 115 if (c < 'a' || c > 'z') 116 return c; 117 else 118 return (char)(c - 'a' + 'A'); 119} 120 121/* Calculates the length of the dirent absolute or non absolute root in 122 DIRENT, return 0 if dirent is not rooted */ 123static apr_size_t 124dirent_root_length(const char *dirent, apr_size_t len) 125{ 126#ifdef SVN_USE_DOS_PATHS 127 if (len >= 2 && dirent[1] == ':' && 128 ((dirent[0] >= 'A' && dirent[0] <= 'Z') || 129 (dirent[0] >= 'a' && dirent[0] <= 'z'))) 130 { 131 return (len > 2 && dirent[2] == '/') ? 3 : 2; 132 } 133 134 if (len > 2 && dirent[0] == '/' && dirent[1] == '/') 135 { 136 apr_size_t i = 2; 137 138 while (i < len && dirent[i] != '/') 139 i++; 140 141 if (i == len) 142 return len; /* Cygwin drive alias, invalid path on WIN32 */ 143 144 i++; /* Skip '/' */ 145 146 while (i < len && dirent[i] != '/') 147 i++; 148 149 return i; 150 } 151#endif /* SVN_USE_DOS_PATHS */ 152 if (len >= 1 && dirent[0] == '/') 153 return 1; 154 155 return 0; 156} 157 158 159/* Return the length of substring necessary to encompass the entire 160 * previous dirent segment in DIRENT, which should be a LEN byte string. 161 * 162 * A trailing slash will not be included in the returned length except 163 * in the case in which DIRENT is absolute and there are no more 164 * previous segments. 165 */ 166static apr_size_t 167dirent_previous_segment(const char *dirent, 168 apr_size_t len) 169{ 170 if (len == 0) 171 return 0; 172 173 --len; 174 while (len > 0 && dirent[len] != '/' 175#ifdef SVN_USE_DOS_PATHS 176 && (dirent[len] != ':' || len != 1) 177#endif /* SVN_USE_DOS_PATHS */ 178 ) 179 --len; 180 181 /* check if the remaining segment including trailing '/' is a root dirent */ 182 if (dirent_root_length(dirent, len+1) == len + 1) 183 return len + 1; 184 else 185 return len; 186} 187 188/* Calculates the length occupied by the schema defined root of URI */ 189static apr_size_t 190uri_schema_root_length(const char *uri, apr_size_t len) 191{ 192 apr_size_t i; 193 194 for (i = 0; i < len; i++) 195 { 196 if (uri[i] == '/') 197 { 198 if (i > 0 && uri[i-1] == ':' && i < len-1 && uri[i+1] == '/') 199 { 200 /* We have an absolute uri */ 201 if (i == 5 && strncmp("file", uri, 4) == 0) 202 return 7; /* file:// */ 203 else 204 { 205 for (i += 2; i < len; i++) 206 if (uri[i] == '/') 207 return i; 208 209 return len; /* Only a hostname is found */ 210 } 211 } 212 else 213 return 0; 214 } 215 } 216 217 return 0; 218} 219 220/* Returns TRUE if svn_dirent_is_absolute(dirent) or when dirent has 221 a non absolute root. (E.g. '/' or 'F:' on Windows) */ 222static svn_boolean_t 223dirent_is_rooted(const char *dirent) 224{ 225 if (! dirent) 226 return FALSE; 227 228 /* Root on all systems */ 229 if (dirent[0] == '/') 230 return TRUE; 231 232 /* On Windows, dirent is also absolute when it starts with 'H:' or 'H:/' 233 where 'H' is any letter. */ 234#ifdef SVN_USE_DOS_PATHS 235 if (((dirent[0] >= 'A' && dirent[0] <= 'Z') || 236 (dirent[0] >= 'a' && dirent[0] <= 'z')) && 237 (dirent[1] == ':')) 238 return TRUE; 239#endif /* SVN_USE_DOS_PATHS */ 240 241 return FALSE; 242} 243 244/* Return the length of substring necessary to encompass the entire 245 * previous relpath segment in RELPATH, which should be a LEN byte string. 246 * 247 * A trailing slash will not be included in the returned length. 248 */ 249static apr_size_t 250relpath_previous_segment(const char *relpath, 251 apr_size_t len) 252{ 253 if (len == 0) 254 return 0; 255 256 --len; 257 while (len > 0 && relpath[len] != '/') 258 --len; 259 260 return len; 261} 262 263/* Return the length of substring necessary to encompass the entire 264 * previous uri segment in URI, which should be a LEN byte string. 265 * 266 * A trailing slash will not be included in the returned length except 267 * in the case in which URI is absolute and there are no more 268 * previous segments. 269 */ 270static apr_size_t 271uri_previous_segment(const char *uri, 272 apr_size_t len) 273{ 274 apr_size_t root_length; 275 apr_size_t i = len; 276 if (len == 0) 277 return 0; 278 279 root_length = uri_schema_root_length(uri, len); 280 281 --i; 282 while (len > root_length && uri[i] != '/') 283 --i; 284 285 if (i == 0 && len > 1 && *uri == '/') 286 return 1; 287 288 return i; 289} 290 291/* Return the canonicalized version of PATH, of type TYPE, allocated in 292 * POOL. 293 */ 294static const char * 295canonicalize(path_type_t type, const char *path, apr_pool_t *pool) 296{ 297 char *canon, *dst; 298 const char *src; 299 apr_size_t seglen; 300 apr_size_t schemelen = 0; 301 apr_size_t canon_segments = 0; 302 svn_boolean_t url = FALSE; 303 char *schema_data = NULL; 304 305 /* "" is already canonical, so just return it; note that later code 306 depends on path not being zero-length. */ 307 if (SVN_PATH_IS_EMPTY(path)) 308 { 309 assert(type != type_uri); 310 return ""; 311 } 312 313 dst = canon = apr_pcalloc(pool, strlen(path) + 1); 314 315 /* If this is supposed to be an URI, it should start with 316 "scheme://". We'll copy the scheme, host name, etc. to DST and 317 set URL = TRUE. */ 318 src = path; 319 if (type == type_uri) 320 { 321 assert(*src != '/'); 322 323 while (*src && (*src != '/') && (*src != ':')) 324 src++; 325 326 if (*src == ':' && *(src+1) == '/' && *(src+2) == '/') 327 { 328 const char *seg; 329 330 url = TRUE; 331 332 /* Found a scheme, convert to lowercase and copy to dst. */ 333 src = path; 334 while (*src != ':') 335 { 336 *(dst++) = canonicalize_to_lower((*src++)); 337 schemelen++; 338 } 339 *(dst++) = ':'; 340 *(dst++) = '/'; 341 *(dst++) = '/'; 342 src += 3; 343 schemelen += 3; 344 345 /* This might be the hostname */ 346 seg = src; 347 while (*src && (*src != '/') && (*src != '@')) 348 src++; 349 350 if (*src == '@') 351 { 352 /* Copy the username & password. */ 353 seglen = src - seg + 1; 354 memcpy(dst, seg, seglen); 355 dst += seglen; 356 src++; 357 } 358 else 359 src = seg; 360 361 /* Found a hostname, convert to lowercase and copy to dst. */ 362 if (*src == '[') 363 { 364 *(dst++) = *(src++); /* Copy '[' */ 365 366 while (*src == ':' 367 || (*src >= '0' && (*src <= '9')) 368 || (*src >= 'a' && (*src <= 'f')) 369 || (*src >= 'A' && (*src <= 'F'))) 370 { 371 *(dst++) = canonicalize_to_lower((*src++)); 372 } 373 374 if (*src == ']') 375 *(dst++) = *(src++); /* Copy ']' */ 376 } 377 else 378 while (*src && (*src != '/') && (*src != ':')) 379 *(dst++) = canonicalize_to_lower((*src++)); 380 381 if (*src == ':') 382 { 383 /* We probably have a port number: Is it a default portnumber 384 which doesn't belong in a canonical url? */ 385 if (src[1] == '8' && src[2] == '0' 386 && (src[3]== '/'|| !src[3]) 387 && !strncmp(canon, "http:", 5)) 388 { 389 src += 3; 390 } 391 else if (src[1] == '4' && src[2] == '4' && src[3] == '3' 392 && (src[4]== '/'|| !src[4]) 393 && !strncmp(canon, "https:", 6)) 394 { 395 src += 4; 396 } 397 else if (src[1] == '3' && src[2] == '6' 398 && src[3] == '9' && src[4] == '0' 399 && (src[5]== '/'|| !src[5]) 400 && !strncmp(canon, "svn:", 4)) 401 { 402 src += 5; 403 } 404 else if (src[1] == '/' || !src[1]) 405 { 406 src += 1; 407 } 408 409 while (*src && (*src != '/')) 410 *(dst++) = canonicalize_to_lower((*src++)); 411 } 412 413 /* Copy trailing slash, or null-terminator. */ 414 *(dst) = *(src); 415 416 /* Move src and dst forward only if we are not 417 * at null-terminator yet. */ 418 if (*src) 419 { 420 src++; 421 dst++; 422 schema_data = dst; 423 } 424 425 canon_segments = 1; 426 } 427 } 428 429 /* Copy to DST any separator or drive letter that must come before the 430 first regular path segment. */ 431 if (! url && type != type_relpath) 432 { 433 src = path; 434 /* If this is an absolute path, then just copy over the initial 435 separator character. */ 436 if (*src == '/') 437 { 438 *(dst++) = *(src++); 439 440#ifdef SVN_USE_DOS_PATHS 441 /* On Windows permit two leading separator characters which means an 442 * UNC path. */ 443 if ((type == type_dirent) && *src == '/') 444 *(dst++) = *(src++); 445#endif /* SVN_USE_DOS_PATHS */ 446 } 447#ifdef SVN_USE_DOS_PATHS 448 /* On Windows the first segment can be a drive letter, which we normalize 449 to upper case. */ 450 else if (type == type_dirent && 451 ((*src >= 'a' && *src <= 'z') || 452 (*src >= 'A' && *src <= 'Z')) && 453 (src[1] == ':')) 454 { 455 *(dst++) = canonicalize_to_upper(*(src++)); 456 /* Leave the ':' to be processed as (or as part of) a path segment 457 by the following code block, so we need not care whether it has 458 a slash after it. */ 459 } 460#endif /* SVN_USE_DOS_PATHS */ 461 } 462 463 while (*src) 464 { 465 /* Parse each segment, finding the closing '/' (which might look 466 like '%2F' for URIs). */ 467 const char *next = src; 468 apr_size_t slash_len = 0; 469 470 while (*next 471 && (next[0] != '/') 472 && (! (type == type_uri && next[0] == '%' && next[1] == '2' && 473 canonicalize_to_upper(next[2]) == 'F'))) 474 { 475 ++next; 476 } 477 478 /* Record how long our "slash" is. */ 479 if (next[0] == '/') 480 slash_len = 1; 481 else if (type == type_uri && next[0] == '%') 482 slash_len = 3; 483 484 seglen = next - src; 485 486 if (seglen == 0 487 || (seglen == 1 && src[0] == '.') 488 || (type == type_uri && seglen == 3 && src[0] == '%' && src[1] == '2' 489 && canonicalize_to_upper(src[2]) == 'E')) 490 { 491 /* Empty or noop segment, so do nothing. (For URIs, '%2E' 492 is equivalent to '.'). */ 493 } 494#ifdef SVN_USE_DOS_PATHS 495 /* If this is the first path segment of a file:// URI and it contains a 496 windows drive letter, convert the drive letter to upper case. */ 497 else if (url && canon_segments == 1 && seglen == 2 && 498 (strncmp(canon, "file:", 5) == 0) && 499 src[0] >= 'a' && src[0] <= 'z' && src[1] == ':') 500 { 501 *(dst++) = canonicalize_to_upper(src[0]); 502 *(dst++) = ':'; 503 if (*next) 504 *(dst++) = *next; 505 canon_segments++; 506 } 507#endif /* SVN_USE_DOS_PATHS */ 508 else 509 { 510 /* An actual segment, append it to the destination path */ 511 memcpy(dst, src, seglen); 512 dst += seglen; 513 if (slash_len) 514 *(dst++) = '/'; 515 canon_segments++; 516 } 517 518 /* Skip over trailing slash to the next segment. */ 519 src = next + slash_len; 520 } 521 522 /* Remove the trailing slash if there was at least one 523 * canonical segment and the last segment ends with a slash. 524 * 525 * But keep in mind that, for URLs, the scheme counts as a 526 * canonical segment -- so if path is ONLY a scheme (such 527 * as "https://") we should NOT remove the trailing slash. */ 528 if ((canon_segments > 0 && *(dst - 1) == '/') 529 && ! (url && path[schemelen] == '\0')) 530 { 531 dst --; 532 } 533 534 *dst = '\0'; 535 536#ifdef SVN_USE_DOS_PATHS 537 /* Skip leading double slashes when there are less than 2 538 * canon segments. UNC paths *MUST* have two segments. */ 539 if ((type == type_dirent) && canon[0] == '/' && canon[1] == '/') 540 { 541 if (canon_segments < 2) 542 return canon + 1; 543 else 544 { 545 /* Now we're sure this is a valid UNC path, convert the server name 546 (the first path segment) to lowercase as Windows treats it as case 547 insensitive. 548 Note: normally the share name is treated as case insensitive too, 549 but it seems to be possible to configure Samba to treat those as 550 case sensitive, so better leave that alone. */ 551 for (dst = canon + 2; *dst && *dst != '/'; dst++) 552 *dst = canonicalize_to_lower(*dst); 553 } 554 } 555#endif /* SVN_USE_DOS_PATHS */ 556 557 /* Check the normalization of characters in a uri */ 558 if (schema_data) 559 { 560 int need_extra = 0; 561 src = schema_data; 562 563 while (*src) 564 { 565 switch (*src) 566 { 567 case '/': 568 break; 569 case '%': 570 if (!svn_ctype_isxdigit(*(src+1)) || 571 !svn_ctype_isxdigit(*(src+2))) 572 need_extra += 2; 573 else 574 src += 2; 575 break; 576 default: 577 if (!svn_uri__char_validity[(unsigned char)*src]) 578 need_extra += 2; 579 break; 580 } 581 src++; 582 } 583 584 if (need_extra > 0) 585 { 586 apr_size_t pre_schema_size = (apr_size_t)(schema_data - canon); 587 588 dst = apr_palloc(pool, (apr_size_t)(src - canon) + need_extra + 1); 589 memcpy(dst, canon, pre_schema_size); 590 canon = dst; 591 592 dst += pre_schema_size; 593 } 594 else 595 dst = schema_data; 596 597 src = schema_data; 598 599 while (*src) 600 { 601 switch (*src) 602 { 603 case '/': 604 *(dst++) = '/'; 605 break; 606 case '%': 607 if (!svn_ctype_isxdigit(*(src+1)) || 608 !svn_ctype_isxdigit(*(src+2))) 609 { 610 *(dst++) = '%'; 611 *(dst++) = '2'; 612 *(dst++) = '5'; 613 } 614 else 615 { 616 char digitz[3]; 617 int val; 618 619 digitz[0] = *(++src); 620 digitz[1] = *(++src); 621 digitz[2] = 0; 622 623 val = (int)strtol(digitz, NULL, 16); 624 625 if (svn_uri__char_validity[(unsigned char)val]) 626 *(dst++) = (char)val; 627 else 628 { 629 *(dst++) = '%'; 630 *(dst++) = canonicalize_to_upper(digitz[0]); 631 *(dst++) = canonicalize_to_upper(digitz[1]); 632 } 633 } 634 break; 635 default: 636 if (!svn_uri__char_validity[(unsigned char)*src]) 637 { 638 apr_snprintf(dst, 4, "%%%02X", (unsigned char)*src); 639 dst += 3; 640 } 641 else 642 *(dst++) = *src; 643 break; 644 } 645 src++; 646 } 647 *dst = '\0'; 648 } 649 650 return canon; 651} 652 653/* Return the string length of the longest common ancestor of PATH1 and PATH2. 654 * Pass type_uri for TYPE if PATH1 and PATH2 are URIs, and type_dirent if 655 * PATH1 and PATH2 are regular paths. 656 * 657 * If the two paths do not share a common ancestor, return 0. 658 * 659 * New strings are allocated in POOL. 660 */ 661static apr_size_t 662get_longest_ancestor_length(path_type_t types, 663 const char *path1, 664 const char *path2, 665 apr_pool_t *pool) 666{ 667 apr_size_t path1_len, path2_len; 668 apr_size_t i = 0; 669 apr_size_t last_dirsep = 0; 670#ifdef SVN_USE_DOS_PATHS 671 svn_boolean_t unc = FALSE; 672#endif 673 674 path1_len = strlen(path1); 675 path2_len = strlen(path2); 676 677 if (SVN_PATH_IS_EMPTY(path1) || SVN_PATH_IS_EMPTY(path2)) 678 return 0; 679 680 while (path1[i] == path2[i]) 681 { 682 /* Keep track of the last directory separator we hit. */ 683 if (path1[i] == '/') 684 last_dirsep = i; 685 686 i++; 687 688 /* If we get to the end of either path, break out. */ 689 if ((i == path1_len) || (i == path2_len)) 690 break; 691 } 692 693 /* two special cases: 694 1. '/' is the longest common ancestor of '/' and '/foo' */ 695 if (i == 1 && path1[0] == '/' && path2[0] == '/') 696 return 1; 697 /* 2. '' is the longest common ancestor of any non-matching 698 * strings 'foo' and 'bar' */ 699 if (types == type_dirent && i == 0) 700 return 0; 701 702 /* Handle some windows specific cases */ 703#ifdef SVN_USE_DOS_PATHS 704 if (types == type_dirent) 705 { 706 /* don't count the '//' from UNC paths */ 707 if (last_dirsep == 1 && path1[0] == '/' && path1[1] == '/') 708 { 709 last_dirsep = 0; 710 unc = TRUE; 711 } 712 713 /* X:/ and X:/foo */ 714 if (i == 3 && path1[2] == '/' && path1[1] == ':') 715 return i; 716 717 /* Cannot use SVN_ERR_ASSERT here, so we'll have to crash, sorry. 718 * Note that this assertion triggers only if the code above has 719 * been broken. The code below relies on this assertion, because 720 * it uses [i - 1] as index. */ 721 assert(i > 0); 722 723 /* X: and X:/ */ 724 if ((path1[i - 1] == ':' && path2[i] == '/') || 725 (path2[i - 1] == ':' && path1[i] == '/')) 726 return 0; 727 /* X: and X:foo */ 728 if (path1[i - 1] == ':' || path2[i - 1] == ':') 729 return i; 730 } 731#endif /* SVN_USE_DOS_PATHS */ 732 733 /* last_dirsep is now the offset of the last directory separator we 734 crossed before reaching a non-matching byte. i is the offset of 735 that non-matching byte, and is guaranteed to be <= the length of 736 whichever path is shorter. 737 If one of the paths is the common part return that. */ 738 if (((i == path1_len) && (path2[i] == '/')) 739 || ((i == path2_len) && (path1[i] == '/')) 740 || ((i == path1_len) && (i == path2_len))) 741 return i; 742 else 743 { 744 /* Nothing in common but the root folder '/' or 'X:/' for Windows 745 dirents. */ 746#ifdef SVN_USE_DOS_PATHS 747 if (! unc) 748 { 749 /* X:/foo and X:/bar returns X:/ */ 750 if ((types == type_dirent) && 751 last_dirsep == 2 && path1[1] == ':' && path1[2] == '/' 752 && path2[1] == ':' && path2[2] == '/') 753 return 3; 754#endif /* SVN_USE_DOS_PATHS */ 755 if (last_dirsep == 0 && path1[0] == '/' && path2[0] == '/') 756 return 1; 757#ifdef SVN_USE_DOS_PATHS 758 } 759#endif 760 } 761 762 return last_dirsep; 763} 764 765/* Determine whether PATH2 is a child of PATH1. 766 * 767 * PATH2 is a child of PATH1 if 768 * 1) PATH1 is empty, and PATH2 is not empty and not an absolute path. 769 * or 770 * 2) PATH2 is has n components, PATH1 has x < n components, 771 * and PATH1 matches PATH2 in all its x components. 772 * Components are separated by a slash, '/'. 773 * 774 * Pass type_uri for TYPE if PATH1 and PATH2 are URIs, and type_dirent if 775 * PATH1 and PATH2 are regular paths. 776 * 777 * If PATH2 is not a child of PATH1, return NULL. 778 * 779 * If PATH2 is a child of PATH1, and POOL is not NULL, allocate a copy 780 * of the child part of PATH2 in POOL and return a pointer to the 781 * newly allocated child part. 782 * 783 * If PATH2 is a child of PATH1, and POOL is NULL, return a pointer 784 * pointing to the child part of PATH2. 785 * */ 786static const char * 787is_child(path_type_t type, const char *path1, const char *path2, 788 apr_pool_t *pool) 789{ 790 apr_size_t i; 791 792 /* Allow "" and "foo" or "H:foo" to be parent/child */ 793 if (SVN_PATH_IS_EMPTY(path1)) /* "" is the parent */ 794 { 795 if (SVN_PATH_IS_EMPTY(path2)) /* "" not a child */ 796 return NULL; 797 798 /* check if this is an absolute path */ 799 if ((type == type_uri) || 800 (type == type_dirent && dirent_is_rooted(path2))) 801 return NULL; 802 else 803 /* everything else is child */ 804 return pool ? apr_pstrdup(pool, path2) : path2; 805 } 806 807 /* Reach the end of at least one of the paths. How should we handle 808 things like path1:"foo///bar" and path2:"foo/bar/baz"? It doesn't 809 appear to arise in the current Subversion code, it's not clear to me 810 if they should be parent/child or not. */ 811 /* Hmmm... aren't paths assumed to be canonical in this function? 812 * How can "foo///bar" even happen if the paths are canonical? */ 813 for (i = 0; path1[i] && path2[i]; i++) 814 if (path1[i] != path2[i]) 815 return NULL; 816 817 /* FIXME: This comment does not really match 818 * the checks made in the code it refers to: */ 819 /* There are two cases that are parent/child 820 ... path1[i] == '\0' 821 .../foo path2[i] == '/' 822 or 823 / path1[i] == '\0' 824 /foo path2[i] != '/' 825 826 Other root paths (like X:/) fall under the former case: 827 X:/ path1[i] == '\0' 828 X:/foo path2[i] != '/' 829 830 Check for '//' to avoid matching '/' and '//srv'. 831 */ 832 if (path1[i] == '\0' && path2[i]) 833 { 834 if (path1[i - 1] == '/' 835#ifdef SVN_USE_DOS_PATHS 836 || ((type == type_dirent) && path1[i - 1] == ':') 837#endif 838 ) 839 { 840 if (path2[i] == '/') 841 /* .../ 842 * ..../ 843 * i */ 844 return NULL; 845 else 846 /* .../ 847 * .../foo 848 * i */ 849 return pool ? apr_pstrdup(pool, path2 + i) : path2 + i; 850 } 851 else if (path2[i] == '/') 852 { 853 if (path2[i + 1]) 854 /* ... 855 * .../foo 856 * i */ 857 return pool ? apr_pstrdup(pool, path2 + i + 1) : path2 + i + 1; 858 else 859 /* ... 860 * .../ 861 * i */ 862 return NULL; 863 } 864 } 865 866 /* Otherwise, path2 isn't a child. */ 867 return NULL; 868} 869 870 871/**** Public API functions ****/ 872 873const char * 874svn_dirent_internal_style(const char *dirent, apr_pool_t *pool) 875{ 876 return svn_dirent_canonicalize(internal_style(dirent, pool), pool); 877} 878 879const char * 880svn_dirent_local_style(const char *dirent, apr_pool_t *pool) 881{ 882 /* Internally, Subversion represents the current directory with the 883 empty string. But users like to see "." . */ 884 if (SVN_PATH_IS_EMPTY(dirent)) 885 return "."; 886 887#if '/' != SVN_PATH_LOCAL_SEPARATOR 888 { 889 char *p = apr_pstrdup(pool, dirent); 890 dirent = p; 891 892 /* Convert all canonical separators to the local-style ones. */ 893 for (; *p != '\0'; ++p) 894 if (*p == '/') 895 *p = SVN_PATH_LOCAL_SEPARATOR; 896 } 897#endif 898 899 return dirent; 900} 901 902const char * 903svn_relpath__internal_style(const char *relpath, 904 apr_pool_t *pool) 905{ 906 return svn_relpath_canonicalize(internal_style(relpath, pool), pool); 907} 908 909 910/* We decided against using apr_filepath_root here because of the negative 911 performance impact (creating a pool and converting strings ). */ 912svn_boolean_t 913svn_dirent_is_root(const char *dirent, apr_size_t len) 914{ 915#ifdef SVN_USE_DOS_PATHS 916 /* On Windows and Cygwin, 'H:' or 'H:/' (where 'H' is any letter) 917 are also root directories */ 918 if ((len == 2 || ((len == 3) && (dirent[2] == '/'))) && 919 (dirent[1] == ':') && 920 ((dirent[0] >= 'A' && dirent[0] <= 'Z') || 921 (dirent[0] >= 'a' && dirent[0] <= 'z'))) 922 return TRUE; 923 924 /* On Windows and Cygwin //server/share is a root directory, 925 and on Cygwin //drive is a drive alias */ 926 if (len >= 2 && dirent[0] == '/' && dirent[1] == '/' 927 && dirent[len - 1] != '/') 928 { 929 int segments = 0; 930 apr_size_t i; 931 for (i = len; i >= 2; i--) 932 { 933 if (dirent[i] == '/') 934 { 935 segments ++; 936 if (segments > 1) 937 return FALSE; 938 } 939 } 940#ifdef __CYGWIN__ 941 return (segments <= 1); 942#else 943 return (segments == 1); /* //drive is invalid on plain Windows */ 944#endif 945 } 946#endif 947 948 /* directory is root if it's equal to '/' */ 949 if (len == 1 && dirent[0] == '/') 950 return TRUE; 951 952 return FALSE; 953} 954 955svn_boolean_t 956svn_uri_is_root(const char *uri, apr_size_t len) 957{ 958 assert(svn_uri_is_canonical(uri, NULL)); 959 return (len == uri_schema_root_length(uri, len)); 960} 961 962char *svn_dirent_join(const char *base, 963 const char *component, 964 apr_pool_t *pool) 965{ 966 apr_size_t blen = strlen(base); 967 apr_size_t clen = strlen(component); 968 char *dirent; 969 int add_separator; 970 971 assert(svn_dirent_is_canonical(base, pool)); 972 assert(svn_dirent_is_canonical(component, pool)); 973 974 /* If the component is absolute, then return it. */ 975 if (svn_dirent_is_absolute(component)) 976 return apr_pmemdup(pool, component, clen + 1); 977 978 /* If either is empty return the other */ 979 if (SVN_PATH_IS_EMPTY(base)) 980 return apr_pmemdup(pool, component, clen + 1); 981 if (SVN_PATH_IS_EMPTY(component)) 982 return apr_pmemdup(pool, base, blen + 1); 983 984#ifdef SVN_USE_DOS_PATHS 985 if (component[0] == '/') 986 { 987 /* '/' is drive relative on Windows, not absolute like on Posix */ 988 if (dirent_is_rooted(base)) 989 { 990 /* Join component without '/' to root-of(base) */ 991 blen = dirent_root_length(base, blen); 992 component++; 993 clen--; 994 995 if (blen == 2 && base[1] == ':') /* "C:" case */ 996 { 997 char *root = apr_pmemdup(pool, base, 3); 998 root[2] = '/'; /* We don't need the final '\0' */ 999 1000 base = root; 1001 blen = 3; 1002 } 1003 1004 if (clen == 0) 1005 return apr_pstrndup(pool, base, blen); 1006 } 1007 else 1008 return apr_pmemdup(pool, component, clen + 1); 1009 } 1010 else if (dirent_is_rooted(component)) 1011 return apr_pmemdup(pool, component, clen + 1); 1012#endif /* SVN_USE_DOS_PATHS */ 1013 1014 /* if last character of base is already a separator, don't add a '/' */ 1015 add_separator = 1; 1016 if (base[blen - 1] == '/' 1017#ifdef SVN_USE_DOS_PATHS 1018 || base[blen - 1] == ':' 1019#endif 1020 ) 1021 add_separator = 0; 1022 1023 /* Construct the new, combined dirent. */ 1024 dirent = apr_palloc(pool, blen + add_separator + clen + 1); 1025 memcpy(dirent, base, blen); 1026 if (add_separator) 1027 dirent[blen] = '/'; 1028 memcpy(dirent + blen + add_separator, component, clen + 1); 1029 1030 return dirent; 1031} 1032 1033char *svn_dirent_join_many(apr_pool_t *pool, const char *base, ...) 1034{ 1035#define MAX_SAVED_LENGTHS 10 1036 apr_size_t saved_lengths[MAX_SAVED_LENGTHS]; 1037 apr_size_t total_len; 1038 int nargs; 1039 va_list va; 1040 const char *s; 1041 apr_size_t len; 1042 char *dirent; 1043 char *p; 1044 int add_separator; 1045 int base_arg = 0; 1046 1047 total_len = strlen(base); 1048 1049 assert(svn_dirent_is_canonical(base, pool)); 1050 1051 /* if last character of base is already a separator, don't add a '/' */ 1052 add_separator = 1; 1053 if (total_len == 0 1054 || base[total_len - 1] == '/' 1055#ifdef SVN_USE_DOS_PATHS 1056 || base[total_len - 1] == ':' 1057#endif 1058 ) 1059 add_separator = 0; 1060 1061 saved_lengths[0] = total_len; 1062 1063 /* Compute the length of the resulting string. */ 1064 1065 nargs = 0; 1066 va_start(va, base); 1067 while ((s = va_arg(va, const char *)) != NULL) 1068 { 1069 len = strlen(s); 1070 1071 assert(svn_dirent_is_canonical(s, pool)); 1072 1073 if (SVN_PATH_IS_EMPTY(s)) 1074 continue; 1075 1076 if (nargs++ < MAX_SAVED_LENGTHS) 1077 saved_lengths[nargs] = len; 1078 1079 if (dirent_is_rooted(s)) 1080 { 1081 total_len = len; 1082 base_arg = nargs; 1083 1084#ifdef SVN_USE_DOS_PATHS 1085 if (!svn_dirent_is_absolute(s)) /* Handle non absolute roots */ 1086 { 1087 /* Set new base and skip the current argument */ 1088 base = s = svn_dirent_join(base, s, pool); 1089 base_arg++; 1090 saved_lengths[0] = total_len = len = strlen(s); 1091 } 1092 else 1093#endif /* SVN_USE_DOS_PATHS */ 1094 { 1095 base = ""; /* Don't add base */ 1096 saved_lengths[0] = 0; 1097 } 1098 1099 add_separator = 1; 1100 if (s[len - 1] == '/' 1101#ifdef SVN_USE_DOS_PATHS 1102 || s[len - 1] == ':' 1103#endif 1104 ) 1105 add_separator = 0; 1106 } 1107 else if (nargs <= base_arg + 1) 1108 { 1109 total_len += add_separator + len; 1110 } 1111 else 1112 { 1113 total_len += 1 + len; 1114 } 1115 } 1116 va_end(va); 1117 1118 /* base == "/" and no further components. just return that. */ 1119 if (add_separator == 0 && total_len == 1) 1120 return apr_pmemdup(pool, "/", 2); 1121 1122 /* we got the total size. allocate it, with room for a NULL character. */ 1123 dirent = p = apr_palloc(pool, total_len + 1); 1124 1125 /* if we aren't supposed to skip forward to an absolute component, and if 1126 this is not an empty base that we are skipping, then copy the base 1127 into the output. */ 1128 if (! SVN_PATH_IS_EMPTY(base)) 1129 { 1130 memcpy(p, base, len = saved_lengths[0]); 1131 p += len; 1132 } 1133 1134 nargs = 0; 1135 va_start(va, base); 1136 while ((s = va_arg(va, const char *)) != NULL) 1137 { 1138 if (SVN_PATH_IS_EMPTY(s)) 1139 continue; 1140 1141 if (++nargs < base_arg) 1142 continue; 1143 1144 if (nargs < MAX_SAVED_LENGTHS) 1145 len = saved_lengths[nargs]; 1146 else 1147 len = strlen(s); 1148 1149 /* insert a separator if we aren't copying in the first component 1150 (which can happen when base_arg is set). also, don't put in a slash 1151 if the prior character is a slash (occurs when prior component 1152 is "/"). */ 1153 if (p != dirent && 1154 ( ! (nargs - 1 <= base_arg) || add_separator)) 1155 *p++ = '/'; 1156 1157 /* copy the new component and advance the pointer */ 1158 memcpy(p, s, len); 1159 p += len; 1160 } 1161 va_end(va); 1162 1163 *p = '\0'; 1164 assert((apr_size_t)(p - dirent) == total_len); 1165 1166 return dirent; 1167} 1168 1169char * 1170svn_relpath_join(const char *base, 1171 const char *component, 1172 apr_pool_t *pool) 1173{ 1174 apr_size_t blen = strlen(base); 1175 apr_size_t clen = strlen(component); 1176 char *path; 1177 1178 assert(relpath_is_canonical(base)); 1179 assert(relpath_is_canonical(component)); 1180 1181 /* If either is empty return the other */ 1182 if (blen == 0) 1183 return apr_pmemdup(pool, component, clen + 1); 1184 if (clen == 0) 1185 return apr_pmemdup(pool, base, blen + 1); 1186 1187 path = apr_palloc(pool, blen + 1 + clen + 1); 1188 memcpy(path, base, blen); 1189 path[blen] = '/'; 1190 memcpy(path + blen + 1, component, clen + 1); 1191 1192 return path; 1193} 1194 1195char * 1196svn_dirent_dirname(const char *dirent, apr_pool_t *pool) 1197{ 1198 apr_size_t len = strlen(dirent); 1199 1200 assert(svn_dirent_is_canonical(dirent, pool)); 1201 1202 if (len == dirent_root_length(dirent, len)) 1203 return apr_pstrmemdup(pool, dirent, len); 1204 else 1205 return apr_pstrmemdup(pool, dirent, dirent_previous_segment(dirent, len)); 1206} 1207 1208const char * 1209svn_dirent_basename(const char *dirent, apr_pool_t *pool) 1210{ 1211 apr_size_t len = strlen(dirent); 1212 apr_size_t start; 1213 1214 assert(!pool || svn_dirent_is_canonical(dirent, pool)); 1215 1216 if (svn_dirent_is_root(dirent, len)) 1217 return ""; 1218 else 1219 { 1220 start = len; 1221 while (start > 0 && dirent[start - 1] != '/' 1222#ifdef SVN_USE_DOS_PATHS 1223 && dirent[start - 1] != ':' 1224#endif 1225 ) 1226 --start; 1227 } 1228 1229 if (pool) 1230 return apr_pstrmemdup(pool, dirent + start, len - start); 1231 else 1232 return dirent + start; 1233} 1234 1235void 1236svn_dirent_split(const char **dirpath, 1237 const char **base_name, 1238 const char *dirent, 1239 apr_pool_t *pool) 1240{ 1241 assert(dirpath != base_name); 1242 1243 if (dirpath) 1244 *dirpath = svn_dirent_dirname(dirent, pool); 1245 1246 if (base_name) 1247 *base_name = svn_dirent_basename(dirent, pool); 1248} 1249 1250char * 1251svn_relpath_dirname(const char *relpath, 1252 apr_pool_t *pool) 1253{ 1254 apr_size_t len = strlen(relpath); 1255 1256 assert(relpath_is_canonical(relpath)); 1257 1258 return apr_pstrmemdup(pool, relpath, 1259 relpath_previous_segment(relpath, len)); 1260} 1261 1262const char * 1263svn_relpath_basename(const char *relpath, 1264 apr_pool_t *pool) 1265{ 1266 apr_size_t len = strlen(relpath); 1267 apr_size_t start; 1268 1269 assert(relpath_is_canonical(relpath)); 1270 1271 start = len; 1272 while (start > 0 && relpath[start - 1] != '/') 1273 --start; 1274 1275 if (pool) 1276 return apr_pstrmemdup(pool, relpath + start, len - start); 1277 else 1278 return relpath + start; 1279} 1280 1281void 1282svn_relpath_split(const char **dirpath, 1283 const char **base_name, 1284 const char *relpath, 1285 apr_pool_t *pool) 1286{ 1287 assert(dirpath != base_name); 1288 1289 if (dirpath) 1290 *dirpath = svn_relpath_dirname(relpath, pool); 1291 1292 if (base_name) 1293 *base_name = svn_relpath_basename(relpath, pool); 1294} 1295 1296char * 1297svn_uri_dirname(const char *uri, apr_pool_t *pool) 1298{ 1299 apr_size_t len = strlen(uri); 1300 1301 assert(svn_uri_is_canonical(uri, pool)); 1302 1303 if (svn_uri_is_root(uri, len)) 1304 return apr_pstrmemdup(pool, uri, len); 1305 else 1306 return apr_pstrmemdup(pool, uri, uri_previous_segment(uri, len)); 1307} 1308 1309const char * 1310svn_uri_basename(const char *uri, apr_pool_t *pool) 1311{ 1312 apr_size_t len = strlen(uri); 1313 apr_size_t start; 1314 1315 assert(svn_uri_is_canonical(uri, NULL)); 1316 1317 if (svn_uri_is_root(uri, len)) 1318 return ""; 1319 1320 start = len; 1321 while (start > 0 && uri[start - 1] != '/') 1322 --start; 1323 1324 return svn_path_uri_decode(uri + start, pool); 1325} 1326 1327void 1328svn_uri_split(const char **dirpath, 1329 const char **base_name, 1330 const char *uri, 1331 apr_pool_t *pool) 1332{ 1333 assert(dirpath != base_name); 1334 1335 if (dirpath) 1336 *dirpath = svn_uri_dirname(uri, pool); 1337 1338 if (base_name) 1339 *base_name = svn_uri_basename(uri, pool); 1340} 1341 1342char * 1343svn_dirent_get_longest_ancestor(const char *dirent1, 1344 const char *dirent2, 1345 apr_pool_t *pool) 1346{ 1347 return apr_pstrndup(pool, dirent1, 1348 get_longest_ancestor_length(type_dirent, dirent1, 1349 dirent2, pool)); 1350} 1351 1352char * 1353svn_relpath_get_longest_ancestor(const char *relpath1, 1354 const char *relpath2, 1355 apr_pool_t *pool) 1356{ 1357 assert(relpath_is_canonical(relpath1)); 1358 assert(relpath_is_canonical(relpath2)); 1359 1360 return apr_pstrndup(pool, relpath1, 1361 get_longest_ancestor_length(type_relpath, relpath1, 1362 relpath2, pool)); 1363} 1364 1365char * 1366svn_uri_get_longest_ancestor(const char *uri1, 1367 const char *uri2, 1368 apr_pool_t *pool) 1369{ 1370 apr_size_t uri_ancestor_len; 1371 apr_size_t i = 0; 1372 1373 assert(svn_uri_is_canonical(uri1, NULL)); 1374 assert(svn_uri_is_canonical(uri2, NULL)); 1375 1376 /* Find ':' */ 1377 while (1) 1378 { 1379 /* No shared protocol => no common prefix */ 1380 if (uri1[i] != uri2[i]) 1381 return apr_pmemdup(pool, SVN_EMPTY_PATH, 1382 sizeof(SVN_EMPTY_PATH)); 1383 1384 if (uri1[i] == ':') 1385 break; 1386 1387 /* They're both URLs, so EOS can't come before ':' */ 1388 assert((uri1[i] != '\0') && (uri2[i] != '\0')); 1389 1390 i++; 1391 } 1392 1393 i += 3; /* Advance past '://' */ 1394 1395 uri_ancestor_len = get_longest_ancestor_length(type_uri, uri1 + i, 1396 uri2 + i, pool); 1397 1398 if (uri_ancestor_len == 0 || 1399 (uri_ancestor_len == 1 && (uri1 + i)[0] == '/')) 1400 return apr_pmemdup(pool, SVN_EMPTY_PATH, sizeof(SVN_EMPTY_PATH)); 1401 else 1402 return apr_pstrndup(pool, uri1, uri_ancestor_len + i); 1403} 1404 1405const char * 1406svn_dirent_is_child(const char *parent_dirent, 1407 const char *child_dirent, 1408 apr_pool_t *pool) 1409{ 1410 return is_child(type_dirent, parent_dirent, child_dirent, pool); 1411} 1412 1413const char * 1414svn_dirent_skip_ancestor(const char *parent_dirent, 1415 const char *child_dirent) 1416{ 1417 apr_size_t len = strlen(parent_dirent); 1418 apr_size_t root_len; 1419 1420 if (0 != strncmp(parent_dirent, child_dirent, len)) 1421 return NULL; /* parent_dirent is no ancestor of child_dirent */ 1422 1423 if (child_dirent[len] == 0) 1424 return ""; /* parent_dirent == child_dirent */ 1425 1426 /* Child == parent + more-characters */ 1427 1428 root_len = dirent_root_length(child_dirent, strlen(child_dirent)); 1429 if (root_len > len) 1430 /* Different root, e.g. ("" "/...") or ("//z" "//z/share") */ 1431 return NULL; 1432 1433 /* Now, child == [root-of-parent] + [rest-of-parent] + more-characters. 1434 * It must be one of the following forms. 1435 * 1436 * rlen parent child bad? rlen=len? c[len]=/? 1437 * 0 "" "foo" * 1438 * 0 "b" "bad" ! 1439 * 0 "b" "b/foo" * 1440 * 1 "/" "/foo" * 1441 * 1 "/b" "/bad" ! 1442 * 1 "/b" "/b/foo" * 1443 * 2 "a:" "a:foo" * 1444 * 2 "a:b" "a:bad" ! 1445 * 2 "a:b" "a:b/foo" * 1446 * 3 "a:/" "a:/foo" * 1447 * 3 "a:/b" "a:/bad" ! 1448 * 3 "a:/b" "a:/b/foo" * 1449 * 5 "//s/s" "//s/s/foo" * * 1450 * 5 "//s/s/b" "//s/s/bad" ! 1451 * 5 "//s/s/b" "//s/s/b/foo" * 1452 */ 1453 1454 if (child_dirent[len] == '/') 1455 /* "parent|child" is one of: 1456 * "[a:]b|/foo" "[a:]/b|/foo" "//s/s|/foo" "//s/s/b|/foo" */ 1457 return child_dirent + len + 1; 1458 1459 if (root_len == len) 1460 /* "parent|child" is "|foo" "/|foo" "a:|foo" "a:/|foo" "//s/s|/foo" */ 1461 return child_dirent + len; 1462 1463 return NULL; 1464} 1465 1466const char * 1467svn_relpath_skip_ancestor(const char *parent_relpath, 1468 const char *child_relpath) 1469{ 1470 apr_size_t len = strlen(parent_relpath); 1471 1472 assert(relpath_is_canonical(parent_relpath)); 1473 assert(relpath_is_canonical(child_relpath)); 1474 1475 if (len == 0) 1476 return child_relpath; 1477 1478 if (0 != strncmp(parent_relpath, child_relpath, len)) 1479 return NULL; /* parent_relpath is no ancestor of child_relpath */ 1480 1481 if (child_relpath[len] == 0) 1482 return ""; /* parent_relpath == child_relpath */ 1483 1484 if (child_relpath[len] == '/') 1485 return child_relpath + len + 1; 1486 1487 return NULL; 1488} 1489 1490 1491/* */ 1492static const char * 1493uri_skip_ancestor(const char *parent_uri, 1494 const char *child_uri) 1495{ 1496 apr_size_t len = strlen(parent_uri); 1497 1498 assert(svn_uri_is_canonical(parent_uri, NULL)); 1499 assert(svn_uri_is_canonical(child_uri, NULL)); 1500 1501 if (0 != strncmp(parent_uri, child_uri, len)) 1502 return NULL; /* parent_uri is no ancestor of child_uri */ 1503 1504 if (child_uri[len] == 0) 1505 return ""; /* parent_uri == child_uri */ 1506 1507 if (child_uri[len] == '/') 1508 return child_uri + len + 1; 1509 1510 return NULL; 1511} 1512 1513const char * 1514svn_uri_skip_ancestor(const char *parent_uri, 1515 const char *child_uri, 1516 apr_pool_t *result_pool) 1517{ 1518 const char *result = uri_skip_ancestor(parent_uri, child_uri); 1519 1520 return result ? svn_path_uri_decode(result, result_pool) : NULL; 1521} 1522 1523svn_boolean_t 1524svn_dirent_is_ancestor(const char *parent_dirent, const char *child_dirent) 1525{ 1526 return svn_dirent_skip_ancestor(parent_dirent, child_dirent) != NULL; 1527} 1528 1529svn_boolean_t 1530svn_uri__is_ancestor(const char *parent_uri, const char *child_uri) 1531{ 1532 return uri_skip_ancestor(parent_uri, child_uri) != NULL; 1533} 1534 1535 1536svn_boolean_t 1537svn_dirent_is_absolute(const char *dirent) 1538{ 1539 if (! dirent) 1540 return FALSE; 1541 1542 /* dirent is absolute if it starts with '/' on non-Windows platforms 1543 or with '//' on Windows platforms */ 1544 if (dirent[0] == '/' 1545#ifdef SVN_USE_DOS_PATHS 1546 && dirent[1] == '/' /* Single '/' depends on current drive */ 1547#endif 1548 ) 1549 return TRUE; 1550 1551 /* On Windows, dirent is also absolute when it starts with 'H:/' 1552 where 'H' is any letter. */ 1553#ifdef SVN_USE_DOS_PATHS 1554 if (((dirent[0] >= 'A' && dirent[0] <= 'Z')) && 1555 (dirent[1] == ':') && (dirent[2] == '/')) 1556 return TRUE; 1557#endif /* SVN_USE_DOS_PATHS */ 1558 1559 return FALSE; 1560} 1561 1562svn_error_t * 1563svn_dirent_get_absolute(const char **pabsolute, 1564 const char *relative, 1565 apr_pool_t *pool) 1566{ 1567 char *buffer; 1568 apr_status_t apr_err; 1569 const char *path_apr; 1570 1571 SVN_ERR_ASSERT(! svn_path_is_url(relative)); 1572 1573 /* Merge the current working directory with the relative dirent. */ 1574 SVN_ERR(svn_path_cstring_from_utf8(&path_apr, relative, pool)); 1575 1576 apr_err = apr_filepath_merge(&buffer, NULL, 1577 path_apr, 1578 APR_FILEPATH_NOTRELATIVE, 1579 pool); 1580 if (apr_err) 1581 { 1582 /* In some cases when the passed path or its ancestor(s) do not exist 1583 or no longer exist apr returns an error. 1584 1585 In many of these cases we would like to return a path anyway, when the 1586 passed path was already a safe absolute path. So check for that now to 1587 avoid an error. 1588 1589 svn_dirent_is_absolute() doesn't perform the necessary checks to see 1590 if the path doesn't need post processing to be in the canonical absolute 1591 format. 1592 */ 1593 1594 if (svn_dirent_is_absolute(relative) 1595 && svn_dirent_is_canonical(relative, pool) 1596 && !svn_path_is_backpath_present(relative)) 1597 { 1598 *pabsolute = apr_pstrdup(pool, relative); 1599 return SVN_NO_ERROR; 1600 } 1601 1602 return svn_error_createf(SVN_ERR_BAD_FILENAME, 1603 svn_error_create(apr_err, NULL, NULL), 1604 _("Couldn't determine absolute path of '%s'"), 1605 svn_dirent_local_style(relative, pool)); 1606 } 1607 1608 SVN_ERR(svn_path_cstring_to_utf8(pabsolute, buffer, pool)); 1609 *pabsolute = svn_dirent_canonicalize(*pabsolute, pool); 1610 return SVN_NO_ERROR; 1611} 1612 1613const char * 1614svn_uri_canonicalize(const char *uri, apr_pool_t *pool) 1615{ 1616 return canonicalize(type_uri, uri, pool); 1617} 1618 1619const char * 1620svn_relpath_canonicalize(const char *relpath, apr_pool_t *pool) 1621{ 1622 return canonicalize(type_relpath, relpath, pool); 1623} 1624 1625const char * 1626svn_dirent_canonicalize(const char *dirent, apr_pool_t *pool) 1627{ 1628 const char *dst = canonicalize(type_dirent, dirent, pool); 1629 1630#ifdef SVN_USE_DOS_PATHS 1631 /* Handle a specific case on Windows where path == "X:/". Here we have to 1632 append the final '/', as svn_path_canonicalize will chop this of. */ 1633 if (((dirent[0] >= 'A' && dirent[0] <= 'Z') || 1634 (dirent[0] >= 'a' && dirent[0] <= 'z')) && 1635 dirent[1] == ':' && dirent[2] == '/' && 1636 dst[3] == '\0') 1637 { 1638 char *dst_slash = apr_pcalloc(pool, 4); 1639 dst_slash[0] = canonicalize_to_upper(dirent[0]); 1640 dst_slash[1] = ':'; 1641 dst_slash[2] = '/'; 1642 dst_slash[3] = '\0'; 1643 1644 return dst_slash; 1645 } 1646#endif /* SVN_USE_DOS_PATHS */ 1647 1648 return dst; 1649} 1650 1651svn_boolean_t 1652svn_dirent_is_canonical(const char *dirent, apr_pool_t *scratch_pool) 1653{ 1654 const char *ptr = dirent; 1655 if (*ptr == '/') 1656 { 1657 ptr++; 1658#ifdef SVN_USE_DOS_PATHS 1659 /* Check for UNC paths */ 1660 if (*ptr == '/') 1661 { 1662 /* TODO: Scan hostname and sharename and fall back to part code */ 1663 1664 /* ### Fall back to old implementation */ 1665 return (strcmp(dirent, svn_dirent_canonicalize(dirent, scratch_pool)) 1666 == 0); 1667 } 1668#endif /* SVN_USE_DOS_PATHS */ 1669 } 1670#ifdef SVN_USE_DOS_PATHS 1671 else if (((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= 'A' && *ptr <= 'Z')) && 1672 (ptr[1] == ':')) 1673 { 1674 /* The only canonical drive names are "A:"..."Z:", no lower case */ 1675 if (*ptr < 'A' || *ptr > 'Z') 1676 return FALSE; 1677 1678 ptr += 2; 1679 1680 if (*ptr == '/') 1681 ptr++; 1682 } 1683#endif /* SVN_USE_DOS_PATHS */ 1684 1685 return relpath_is_canonical(ptr); 1686} 1687 1688static svn_boolean_t 1689relpath_is_canonical(const char *relpath) 1690{ 1691 const char *ptr = relpath, *seg = relpath; 1692 1693 /* RELPATH is canonical if it has: 1694 * - no '.' segments 1695 * - no start and closing '/' 1696 * - no '//' 1697 */ 1698 1699 if (*relpath == '\0') 1700 return TRUE; 1701 1702 if (*ptr == '/') 1703 return FALSE; 1704 1705 /* Now validate the rest of the path. */ 1706 while(1) 1707 { 1708 apr_size_t seglen = ptr - seg; 1709 1710 if (seglen == 1 && *seg == '.') 1711 return FALSE; /* /./ */ 1712 1713 if (*ptr == '/' && *(ptr+1) == '/') 1714 return FALSE; /* // */ 1715 1716 if (! *ptr && *(ptr - 1) == '/') 1717 return FALSE; /* foo/ */ 1718 1719 if (! *ptr) 1720 break; 1721 1722 if (*ptr == '/') 1723 ptr++; 1724 seg = ptr; 1725 1726 while (*ptr && (*ptr != '/')) 1727 ptr++; 1728 } 1729 1730 return TRUE; 1731} 1732 1733svn_boolean_t 1734svn_relpath_is_canonical(const char *relpath) 1735{ 1736 return relpath_is_canonical(relpath); 1737} 1738 1739svn_boolean_t 1740svn_uri_is_canonical(const char *uri, apr_pool_t *scratch_pool) 1741{ 1742 const char *ptr = uri, *seg = uri; 1743 const char *schema_data = NULL; 1744 1745 /* URI is canonical if it has: 1746 * - lowercase URL scheme 1747 * - lowercase URL hostname 1748 * - no '.' segments 1749 * - no closing '/' 1750 * - no '//' 1751 * - uppercase hex-encoded pair digits ("%AB", not "%ab") 1752 */ 1753 1754 if (*uri == '\0') 1755 return FALSE; 1756 1757 if (! svn_path_is_url(uri)) 1758 return FALSE; 1759 1760 /* Skip the scheme. */ 1761 while (*ptr && (*ptr != '/') && (*ptr != ':')) 1762 ptr++; 1763 1764 /* No scheme? No good. */ 1765 if (! (*ptr == ':' && *(ptr+1) == '/' && *(ptr+2) == '/')) 1766 return FALSE; 1767 1768 /* Found a scheme, check that it's all lowercase. */ 1769 ptr = uri; 1770 while (*ptr != ':') 1771 { 1772 if (*ptr >= 'A' && *ptr <= 'Z') 1773 return FALSE; 1774 ptr++; 1775 } 1776 /* Skip :// */ 1777 ptr += 3; 1778 1779 /* Scheme only? That works. */ 1780 if (! *ptr) 1781 return TRUE; 1782 1783 /* This might be the hostname */ 1784 seg = ptr; 1785 while (*ptr && (*ptr != '/') && (*ptr != '@')) 1786 ptr++; 1787 1788 if (*ptr == '@') 1789 seg = ptr + 1; 1790 1791 /* Found a hostname, check that it's all lowercase. */ 1792 ptr = seg; 1793 1794 if (*ptr == '[') 1795 { 1796 ptr++; 1797 while (*ptr == ':' 1798 || (*ptr >= '0' && *ptr <= '9') 1799 || (*ptr >= 'a' && *ptr <= 'f')) 1800 { 1801 ptr++; 1802 } 1803 1804 if (*ptr != ']') 1805 return FALSE; 1806 ptr++; 1807 } 1808 else 1809 while (*ptr && *ptr != '/' && *ptr != ':') 1810 { 1811 if (*ptr >= 'A' && *ptr <= 'Z') 1812 return FALSE; 1813 ptr++; 1814 } 1815 1816 /* Found a portnumber */ 1817 if (*ptr == ':') 1818 { 1819 apr_int64_t port = 0; 1820 1821 ptr++; 1822 schema_data = ptr; 1823 1824 while (*ptr >= '0' && *ptr <= '9') 1825 { 1826 port = 10 * port + (*ptr - '0'); 1827 ptr++; 1828 } 1829 1830 if (ptr == schema_data) 1831 return FALSE; /* Fail on "http://host:" */ 1832 1833 if (*ptr && *ptr != '/') 1834 return FALSE; /* Not a port number */ 1835 1836 if (port == 80 && strncmp(uri, "http:", 5) == 0) 1837 return FALSE; 1838 else if (port == 443 && strncmp(uri, "https:", 6) == 0) 1839 return FALSE; 1840 else if (port == 3690 && strncmp(uri, "svn:", 4) == 0) 1841 return FALSE; 1842 } 1843 1844 schema_data = ptr; 1845 1846#ifdef SVN_USE_DOS_PATHS 1847 if (schema_data && *ptr == '/') 1848 { 1849 /* If this is a file url, ptr now points to the third '/' in 1850 file:///C:/path. Check that if we have such a URL the drive 1851 letter is in uppercase. */ 1852 if (strncmp(uri, "file:", 5) == 0 && 1853 ! (*(ptr+1) >= 'A' && *(ptr+1) <= 'Z') && 1854 *(ptr+2) == ':') 1855 return FALSE; 1856 } 1857#endif /* SVN_USE_DOS_PATHS */ 1858 1859 /* Now validate the rest of the URI. */ 1860 seg = ptr; 1861 while (*ptr && (*ptr != '/')) 1862 ptr++; 1863 while(1) 1864 { 1865 apr_size_t seglen = ptr - seg; 1866 1867 if (seglen == 1 && *seg == '.') 1868 return FALSE; /* /./ */ 1869 1870 if (*ptr == '/' && *(ptr+1) == '/') 1871 return FALSE; /* // */ 1872 1873 if (! *ptr && *(ptr - 1) == '/' && ptr - 1 != uri) 1874 return FALSE; /* foo/ */ 1875 1876 if (! *ptr) 1877 break; 1878 1879 if (*ptr == '/') 1880 ptr++; 1881 1882 seg = ptr; 1883 while (*ptr && (*ptr != '/')) 1884 ptr++; 1885 } 1886 1887 ptr = schema_data; 1888 1889 while (*ptr) 1890 { 1891 if (*ptr == '%') 1892 { 1893 char digitz[3]; 1894 int val; 1895 1896 /* Can't usesvn_ctype_isxdigit() because lower case letters are 1897 not in our canonical format */ 1898 if (((*(ptr+1) < '0' || *(ptr+1) > '9')) 1899 && (*(ptr+1) < 'A' || *(ptr+1) > 'F')) 1900 return FALSE; 1901 else if (((*(ptr+2) < '0' || *(ptr+2) > '9')) 1902 && (*(ptr+2) < 'A' || *(ptr+2) > 'F')) 1903 return FALSE; 1904 1905 digitz[0] = *(++ptr); 1906 digitz[1] = *(++ptr); 1907 digitz[2] = '\0'; 1908 val = (int)strtol(digitz, NULL, 16); 1909 1910 if (svn_uri__char_validity[val]) 1911 return FALSE; /* Should not have been escaped */ 1912 } 1913 else if (*ptr != '/' && !svn_uri__char_validity[(unsigned char)*ptr]) 1914 return FALSE; /* Character should have been escaped */ 1915 ptr++; 1916 } 1917 1918 return TRUE; 1919} 1920 1921svn_error_t * 1922svn_dirent_condense_targets(const char **pcommon, 1923 apr_array_header_t **pcondensed_targets, 1924 const apr_array_header_t *targets, 1925 svn_boolean_t remove_redundancies, 1926 apr_pool_t *result_pool, 1927 apr_pool_t *scratch_pool) 1928{ 1929 int i, num_condensed = targets->nelts; 1930 svn_boolean_t *removed; 1931 apr_array_header_t *abs_targets; 1932 1933 /* Early exit when there's no data to work on. */ 1934 if (targets->nelts <= 0) 1935 { 1936 *pcommon = NULL; 1937 if (pcondensed_targets) 1938 *pcondensed_targets = NULL; 1939 return SVN_NO_ERROR; 1940 } 1941 1942 /* Get the absolute path of the first target. */ 1943 SVN_ERR(svn_dirent_get_absolute(pcommon, 1944 APR_ARRAY_IDX(targets, 0, const char *), 1945 scratch_pool)); 1946 1947 /* Early exit when there's only one dirent to work on. */ 1948 if (targets->nelts == 1) 1949 { 1950 *pcommon = apr_pstrdup(result_pool, *pcommon); 1951 if (pcondensed_targets) 1952 *pcondensed_targets = apr_array_make(result_pool, 0, 1953 sizeof(const char *)); 1954 return SVN_NO_ERROR; 1955 } 1956 1957 /* Copy the targets array, but with absolute dirents instead of 1958 relative. Also, find the pcommon argument by finding what is 1959 common in all of the absolute dirents. NOTE: This is not as 1960 efficient as it could be. The calculation of the basedir could 1961 be done in the loop below, which would save some calls to 1962 svn_dirent_get_longest_ancestor. I decided to do it this way 1963 because I thought it would be simpler, since this way, we don't 1964 even do the loop if we don't need to condense the targets. */ 1965 1966 removed = apr_pcalloc(scratch_pool, (targets->nelts * 1967 sizeof(svn_boolean_t))); 1968 abs_targets = apr_array_make(scratch_pool, targets->nelts, 1969 sizeof(const char *)); 1970 1971 APR_ARRAY_PUSH(abs_targets, const char *) = *pcommon; 1972 1973 for (i = 1; i < targets->nelts; ++i) 1974 { 1975 const char *rel = APR_ARRAY_IDX(targets, i, const char *); 1976 const char *absolute; 1977 SVN_ERR(svn_dirent_get_absolute(&absolute, rel, scratch_pool)); 1978 APR_ARRAY_PUSH(abs_targets, const char *) = absolute; 1979 *pcommon = svn_dirent_get_longest_ancestor(*pcommon, absolute, 1980 scratch_pool); 1981 } 1982 1983 *pcommon = apr_pstrdup(result_pool, *pcommon); 1984 1985 if (pcondensed_targets != NULL) 1986 { 1987 size_t basedir_len; 1988 1989 if (remove_redundancies) 1990 { 1991 /* Find the common part of each pair of targets. If 1992 common part is equal to one of the dirents, the other 1993 is a child of it, and can be removed. If a target is 1994 equal to *pcommon, it can also be removed. */ 1995 1996 /* First pass: when one non-removed target is a child of 1997 another non-removed target, remove the child. */ 1998 for (i = 0; i < abs_targets->nelts; ++i) 1999 { 2000 int j; 2001 2002 if (removed[i]) 2003 continue; 2004 2005 for (j = i + 1; j < abs_targets->nelts; ++j) 2006 { 2007 const char *abs_targets_i; 2008 const char *abs_targets_j; 2009 const char *ancestor; 2010 2011 if (removed[j]) 2012 continue; 2013 2014 abs_targets_i = APR_ARRAY_IDX(abs_targets, i, const char *); 2015 abs_targets_j = APR_ARRAY_IDX(abs_targets, j, const char *); 2016 2017 ancestor = svn_dirent_get_longest_ancestor 2018 (abs_targets_i, abs_targets_j, scratch_pool); 2019 2020 if (*ancestor == '\0') 2021 continue; 2022 2023 if (strcmp(ancestor, abs_targets_i) == 0) 2024 { 2025 removed[j] = TRUE; 2026 num_condensed--; 2027 } 2028 else if (strcmp(ancestor, abs_targets_j) == 0) 2029 { 2030 removed[i] = TRUE; 2031 num_condensed--; 2032 } 2033 } 2034 } 2035 2036 /* Second pass: when a target is the same as *pcommon, 2037 remove the target. */ 2038 for (i = 0; i < abs_targets->nelts; ++i) 2039 { 2040 const char *abs_targets_i = APR_ARRAY_IDX(abs_targets, i, 2041 const char *); 2042 2043 if ((strcmp(abs_targets_i, *pcommon) == 0) && (! removed[i])) 2044 { 2045 removed[i] = TRUE; 2046 num_condensed--; 2047 } 2048 } 2049 } 2050 2051 /* Now create the return array, and copy the non-removed items */ 2052 basedir_len = strlen(*pcommon); 2053 *pcondensed_targets = apr_array_make(result_pool, num_condensed, 2054 sizeof(const char *)); 2055 2056 for (i = 0; i < abs_targets->nelts; ++i) 2057 { 2058 const char *rel_item = APR_ARRAY_IDX(abs_targets, i, const char *); 2059 2060 /* Skip this if it's been removed. */ 2061 if (removed[i]) 2062 continue; 2063 2064 /* If a common prefix was found, condensed_targets are given 2065 relative to that prefix. */ 2066 if (basedir_len > 0) 2067 { 2068 /* Only advance our pointer past a dirent separator if 2069 REL_ITEM isn't the same as *PCOMMON. 2070 2071 If *PCOMMON is a root dirent, basedir_len will already 2072 include the closing '/', so never advance the pointer 2073 here. 2074 */ 2075 rel_item += basedir_len; 2076 if (rel_item[0] && 2077 ! svn_dirent_is_root(*pcommon, basedir_len)) 2078 rel_item++; 2079 } 2080 2081 APR_ARRAY_PUSH(*pcondensed_targets, const char *) 2082 = apr_pstrdup(result_pool, rel_item); 2083 } 2084 } 2085 2086 return SVN_NO_ERROR; 2087} 2088 2089svn_error_t * 2090svn_uri_condense_targets(const char **pcommon, 2091 apr_array_header_t **pcondensed_targets, 2092 const apr_array_header_t *targets, 2093 svn_boolean_t remove_redundancies, 2094 apr_pool_t *result_pool, 2095 apr_pool_t *scratch_pool) 2096{ 2097 int i, num_condensed = targets->nelts; 2098 apr_array_header_t *uri_targets; 2099 svn_boolean_t *removed; 2100 2101 /* Early exit when there's no data to work on. */ 2102 if (targets->nelts <= 0) 2103 { 2104 *pcommon = NULL; 2105 if (pcondensed_targets) 2106 *pcondensed_targets = NULL; 2107 return SVN_NO_ERROR; 2108 } 2109 2110 *pcommon = svn_uri_canonicalize(APR_ARRAY_IDX(targets, 0, const char *), 2111 scratch_pool); 2112 2113 /* Early exit when there's only one uri to work on. */ 2114 if (targets->nelts == 1) 2115 { 2116 *pcommon = apr_pstrdup(result_pool, *pcommon); 2117 if (pcondensed_targets) 2118 *pcondensed_targets = apr_array_make(result_pool, 0, 2119 sizeof(const char *)); 2120 return SVN_NO_ERROR; 2121 } 2122 2123 /* Find the pcommon argument by finding what is common in all of the 2124 uris. NOTE: This is not as efficient as it could be. The calculation 2125 of the basedir could be done in the loop below, which would 2126 save some calls to svn_uri_get_longest_ancestor. I decided to do it 2127 this way because I thought it would be simpler, since this way, we don't 2128 even do the loop if we don't need to condense the targets. */ 2129 2130 removed = apr_pcalloc(scratch_pool, (targets->nelts * 2131 sizeof(svn_boolean_t))); 2132 uri_targets = apr_array_make(scratch_pool, targets->nelts, 2133 sizeof(const char *)); 2134 2135 APR_ARRAY_PUSH(uri_targets, const char *) = *pcommon; 2136 2137 for (i = 1; i < targets->nelts; ++i) 2138 { 2139 const char *uri = svn_uri_canonicalize( 2140 APR_ARRAY_IDX(targets, i, const char *), 2141 scratch_pool); 2142 APR_ARRAY_PUSH(uri_targets, const char *) = uri; 2143 2144 /* If the commonmost ancestor so far is empty, there's no point 2145 in continuing to search for a common ancestor at all. But 2146 we'll keep looping for the sake of canonicalizing the 2147 targets, I suppose. */ 2148 if (**pcommon != '\0') 2149 *pcommon = svn_uri_get_longest_ancestor(*pcommon, uri, 2150 scratch_pool); 2151 } 2152 2153 *pcommon = apr_pstrdup(result_pool, *pcommon); 2154 2155 if (pcondensed_targets != NULL) 2156 { 2157 size_t basedir_len; 2158 2159 if (remove_redundancies) 2160 { 2161 /* Find the common part of each pair of targets. If 2162 common part is equal to one of the dirents, the other 2163 is a child of it, and can be removed. If a target is 2164 equal to *pcommon, it can also be removed. */ 2165 2166 /* First pass: when one non-removed target is a child of 2167 another non-removed target, remove the child. */ 2168 for (i = 0; i < uri_targets->nelts; ++i) 2169 { 2170 int j; 2171 2172 if (removed[i]) 2173 continue; 2174 2175 for (j = i + 1; j < uri_targets->nelts; ++j) 2176 { 2177 const char *uri_i; 2178 const char *uri_j; 2179 const char *ancestor; 2180 2181 if (removed[j]) 2182 continue; 2183 2184 uri_i = APR_ARRAY_IDX(uri_targets, i, const char *); 2185 uri_j = APR_ARRAY_IDX(uri_targets, j, const char *); 2186 2187 ancestor = svn_uri_get_longest_ancestor(uri_i, 2188 uri_j, 2189 scratch_pool); 2190 2191 if (*ancestor == '\0') 2192 continue; 2193 2194 if (strcmp(ancestor, uri_i) == 0) 2195 { 2196 removed[j] = TRUE; 2197 num_condensed--; 2198 } 2199 else if (strcmp(ancestor, uri_j) == 0) 2200 { 2201 removed[i] = TRUE; 2202 num_condensed--; 2203 } 2204 } 2205 } 2206 2207 /* Second pass: when a target is the same as *pcommon, 2208 remove the target. */ 2209 for (i = 0; i < uri_targets->nelts; ++i) 2210 { 2211 const char *uri_targets_i = APR_ARRAY_IDX(uri_targets, i, 2212 const char *); 2213 2214 if ((strcmp(uri_targets_i, *pcommon) == 0) && (! removed[i])) 2215 { 2216 removed[i] = TRUE; 2217 num_condensed--; 2218 } 2219 } 2220 } 2221 2222 /* Now create the return array, and copy the non-removed items */ 2223 basedir_len = strlen(*pcommon); 2224 *pcondensed_targets = apr_array_make(result_pool, num_condensed, 2225 sizeof(const char *)); 2226 2227 for (i = 0; i < uri_targets->nelts; ++i) 2228 { 2229 const char *rel_item = APR_ARRAY_IDX(uri_targets, i, const char *); 2230 2231 /* Skip this if it's been removed. */ 2232 if (removed[i]) 2233 continue; 2234 2235 /* If a common prefix was found, condensed_targets are given 2236 relative to that prefix. */ 2237 if (basedir_len > 0) 2238 { 2239 /* Only advance our pointer past a dirent separator if 2240 REL_ITEM isn't the same as *PCOMMON. 2241 2242 If *PCOMMON is a root dirent, basedir_len will already 2243 include the closing '/', so never advance the pointer 2244 here. 2245 */ 2246 rel_item += basedir_len; 2247 if ((rel_item[0] == '/') || 2248 (rel_item[0] && !svn_uri_is_root(*pcommon, basedir_len))) 2249 { 2250 rel_item++; 2251 } 2252 } 2253 2254 APR_ARRAY_PUSH(*pcondensed_targets, const char *) 2255 = svn_path_uri_decode(rel_item, result_pool); 2256 } 2257 } 2258 2259 return SVN_NO_ERROR; 2260} 2261 2262svn_error_t * 2263svn_dirent_is_under_root(svn_boolean_t *under_root, 2264 const char **result_path, 2265 const char *base_path, 2266 const char *path, 2267 apr_pool_t *result_pool) 2268{ 2269 apr_status_t status; 2270 char *full_path; 2271 2272 *under_root = FALSE; 2273 if (result_path) 2274 *result_path = NULL; 2275 2276 status = apr_filepath_merge(&full_path, 2277 base_path, 2278 path, 2279 APR_FILEPATH_NOTABOVEROOT 2280 | APR_FILEPATH_SECUREROOTTEST, 2281 result_pool); 2282 2283 if (status == APR_SUCCESS) 2284 { 2285 if (result_path) 2286 *result_path = svn_dirent_canonicalize(full_path, result_pool); 2287 *under_root = TRUE; 2288 return SVN_NO_ERROR; 2289 } 2290 else if (status == APR_EABOVEROOT) 2291 { 2292 *under_root = FALSE; 2293 return SVN_NO_ERROR; 2294 } 2295 2296 return svn_error_wrap_apr(status, NULL); 2297} 2298 2299svn_error_t * 2300svn_uri_get_dirent_from_file_url(const char **dirent, 2301 const char *url, 2302 apr_pool_t *pool) 2303{ 2304 const char *hostname, *path; 2305 2306 SVN_ERR_ASSERT(svn_uri_is_canonical(url, pool)); 2307 2308 /* Verify that the URL is well-formed (loosely) */ 2309 2310 /* First, check for the "file://" prefix. */ 2311 if (strncmp(url, "file://", 7) != 0) 2312 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 2313 _("Local URL '%s' does not contain 'file://' " 2314 "prefix"), url); 2315 2316 /* Find the HOSTNAME portion and the PATH portion of the URL. The host 2317 name is between the "file://" prefix and the next occurence of '/'. We 2318 are considering everything from that '/' until the end of the URL to be 2319 the absolute path portion of the URL. 2320 If we got just "file://", treat it the same as "file:///". */ 2321 hostname = url + 7; 2322 path = strchr(hostname, '/'); 2323 if (path) 2324 hostname = apr_pstrmemdup(pool, hostname, path - hostname); 2325 else 2326 path = "/"; 2327 2328 /* URI-decode HOSTNAME, and set it to NULL if it is "" or "localhost". */ 2329 if (*hostname == '\0') 2330 hostname = NULL; 2331 else 2332 { 2333 hostname = svn_path_uri_decode(hostname, pool); 2334 if (strcmp(hostname, "localhost") == 0) 2335 hostname = NULL; 2336 } 2337 2338 /* Duplicate the URL, starting at the top of the path. 2339 At the same time, we URI-decode the path. */ 2340#ifdef SVN_USE_DOS_PATHS 2341 /* On Windows, we'll typically have to skip the leading / if the 2342 path starts with a drive letter. Like most Web browsers, We 2343 support two variants of this scheme: 2344 2345 file:///X:/path and 2346 file:///X|/path 2347 2348 Note that, at least on WinNT and above, file:////./X:/path will 2349 also work, so we must make sure the transformation doesn't break 2350 that, and file:///path (that looks within the current drive 2351 only) should also keep working. 2352 If we got a non-empty hostname other than localhost, we convert this 2353 into an UNC path. In this case, we obviously don't strip the slash 2354 even if the path looks like it starts with a drive letter. 2355 */ 2356 { 2357 static const char valid_drive_letters[] = 2358 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 2359 /* Casting away const! */ 2360 char *dup_path = (char *)svn_path_uri_decode(path, pool); 2361 2362 /* This check assumes ':' and '|' are already decoded! */ 2363 if (!hostname && dup_path[1] && strchr(valid_drive_letters, dup_path[1]) 2364 && (dup_path[2] == ':' || dup_path[2] == '|')) 2365 { 2366 /* Skip the leading slash. */ 2367 ++dup_path; 2368 2369 if (dup_path[1] == '|') 2370 dup_path[1] = ':'; 2371 2372 if (dup_path[2] == '/' || dup_path[2] == '\0') 2373 { 2374 if (dup_path[2] == '\0') 2375 { 2376 /* A valid dirent for the driveroot must be like "C:/" instead of 2377 just "C:" or svn_dirent_join() will use the current directory 2378 on the drive instead */ 2379 char *new_path = apr_pcalloc(pool, 4); 2380 new_path[0] = dup_path[0]; 2381 new_path[1] = ':'; 2382 new_path[2] = '/'; 2383 new_path[3] = '\0'; 2384 dup_path = new_path; 2385 } 2386 } 2387 } 2388 if (hostname) 2389 { 2390 if (dup_path[0] == '/' && dup_path[1] == '\0') 2391 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 2392 _("Local URL '%s' contains only a hostname, " 2393 "no path"), url); 2394 2395 /* We still know that the path starts with a slash. */ 2396 *dirent = apr_pstrcat(pool, "//", hostname, dup_path, NULL); 2397 } 2398 else 2399 *dirent = dup_path; 2400 } 2401#else /* !SVN_USE_DOS_PATHS */ 2402 /* Currently, the only hostnames we are allowing on non-Win32 platforms 2403 are the empty string and 'localhost'. */ 2404 if (hostname) 2405 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 2406 _("Local URL '%s' contains unsupported hostname"), 2407 url); 2408 2409 *dirent = svn_path_uri_decode(path, pool); 2410#endif /* SVN_USE_DOS_PATHS */ 2411 return SVN_NO_ERROR; 2412} 2413 2414svn_error_t * 2415svn_uri_get_file_url_from_dirent(const char **url, 2416 const char *dirent, 2417 apr_pool_t *pool) 2418{ 2419 assert(svn_dirent_is_canonical(dirent, pool)); 2420 2421 SVN_ERR(svn_dirent_get_absolute(&dirent, dirent, pool)); 2422 2423 dirent = svn_path_uri_encode(dirent, pool); 2424 2425#ifndef SVN_USE_DOS_PATHS 2426 if (dirent[0] == '/' && dirent[1] == '\0') 2427 dirent = NULL; /* "file://" is the canonical form of "file:///" */ 2428 2429 *url = apr_pstrcat(pool, "file://", dirent, (char *)NULL); 2430#else 2431 if (dirent[0] == '/') 2432 { 2433 /* Handle UNC paths //server/share -> file://server/share */ 2434 assert(dirent[1] == '/'); /* Expect UNC, not non-absolute */ 2435 2436 *url = apr_pstrcat(pool, "file:", dirent, NULL); 2437 } 2438 else 2439 { 2440 char *uri = apr_pstrcat(pool, "file:///", dirent, NULL); 2441 apr_size_t len = 8 /* strlen("file:///") */ + strlen(dirent); 2442 2443 /* "C:/" is a canonical dirent on Windows, 2444 but "file:///C:/" is not a canonical uri */ 2445 if (uri[len-1] == '/') 2446 uri[len-1] = '\0'; 2447 2448 *url = uri; 2449 } 2450#endif 2451 2452 return SVN_NO_ERROR; 2453} 2454 2455 2456 2457/* -------------- The fspath API (see private/svn_fspath.h) -------------- */ 2458 2459svn_boolean_t 2460svn_fspath__is_canonical(const char *fspath) 2461{ 2462 return fspath[0] == '/' && relpath_is_canonical(fspath + 1); 2463} 2464 2465 2466const char * 2467svn_fspath__canonicalize(const char *fspath, 2468 apr_pool_t *pool) 2469{ 2470 if ((fspath[0] == '/') && (fspath[1] == '\0')) 2471 return "/"; 2472 2473 return apr_pstrcat(pool, "/", svn_relpath_canonicalize(fspath, pool), 2474 (char *)NULL); 2475} 2476 2477 2478svn_boolean_t 2479svn_fspath__is_root(const char *fspath, apr_size_t len) 2480{ 2481 /* directory is root if it's equal to '/' */ 2482 return (len == 1 && fspath[0] == '/'); 2483} 2484 2485 2486const char * 2487svn_fspath__skip_ancestor(const char *parent_fspath, 2488 const char *child_fspath) 2489{ 2490 assert(svn_fspath__is_canonical(parent_fspath)); 2491 assert(svn_fspath__is_canonical(child_fspath)); 2492 2493 return svn_relpath_skip_ancestor(parent_fspath + 1, child_fspath + 1); 2494} 2495 2496 2497const char * 2498svn_fspath__dirname(const char *fspath, 2499 apr_pool_t *pool) 2500{ 2501 assert(svn_fspath__is_canonical(fspath)); 2502 2503 if (fspath[0] == '/' && fspath[1] == '\0') 2504 return apr_pstrdup(pool, fspath); 2505 else 2506 return apr_pstrcat(pool, "/", svn_relpath_dirname(fspath + 1, pool), 2507 (char *)NULL); 2508} 2509 2510 2511const char * 2512svn_fspath__basename(const char *fspath, 2513 apr_pool_t *pool) 2514{ 2515 const char *result; 2516 assert(svn_fspath__is_canonical(fspath)); 2517 2518 result = svn_relpath_basename(fspath + 1, pool); 2519 2520 assert(strchr(result, '/') == NULL); 2521 return result; 2522} 2523 2524void 2525svn_fspath__split(const char **dirpath, 2526 const char **base_name, 2527 const char *fspath, 2528 apr_pool_t *result_pool) 2529{ 2530 assert(dirpath != base_name); 2531 2532 if (dirpath) 2533 *dirpath = svn_fspath__dirname(fspath, result_pool); 2534 2535 if (base_name) 2536 *base_name = svn_fspath__basename(fspath, result_pool); 2537} 2538 2539char * 2540svn_fspath__join(const char *fspath, 2541 const char *relpath, 2542 apr_pool_t *result_pool) 2543{ 2544 char *result; 2545 assert(svn_fspath__is_canonical(fspath)); 2546 assert(svn_relpath_is_canonical(relpath)); 2547 2548 if (relpath[0] == '\0') 2549 result = apr_pstrdup(result_pool, fspath); 2550 else if (fspath[1] == '\0') 2551 result = apr_pstrcat(result_pool, "/", relpath, (char *)NULL); 2552 else 2553 result = apr_pstrcat(result_pool, fspath, "/", relpath, (char *)NULL); 2554 2555 assert(svn_fspath__is_canonical(result)); 2556 return result; 2557} 2558 2559char * 2560svn_fspath__get_longest_ancestor(const char *fspath1, 2561 const char *fspath2, 2562 apr_pool_t *result_pool) 2563{ 2564 char *result; 2565 assert(svn_fspath__is_canonical(fspath1)); 2566 assert(svn_fspath__is_canonical(fspath2)); 2567 2568 result = apr_pstrcat(result_pool, "/", 2569 svn_relpath_get_longest_ancestor(fspath1 + 1, 2570 fspath2 + 1, 2571 result_pool), 2572 (char *)NULL); 2573 2574 assert(svn_fspath__is_canonical(result)); 2575 return result; 2576} 2577 2578 2579 2580 2581/* -------------- The urlpath API (see private/svn_fspath.h) ------------- */ 2582 2583const char * 2584svn_urlpath__canonicalize(const char *uri, 2585 apr_pool_t *pool) 2586{ 2587 if (svn_path_is_url(uri)) 2588 { 2589 uri = svn_uri_canonicalize(uri, pool); 2590 } 2591 else 2592 { 2593 uri = svn_fspath__canonicalize(uri, pool); 2594 /* Do a little dance to normalize hex encoding. */ 2595 uri = svn_path_uri_decode(uri, pool); 2596 uri = svn_path_uri_encode(uri, pool); 2597 } 2598 return uri; 2599} 2600