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