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