dirent_uri.c revision 269833
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" 41269833Speter#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 1297251881Speterchar * 1298251881Spetersvn_uri_dirname(const char *uri, apr_pool_t *pool) 1299251881Speter{ 1300251881Speter apr_size_t len = strlen(uri); 1301251881Speter 1302251881Speter assert(svn_uri_is_canonical(uri, pool)); 1303251881Speter 1304251881Speter if (svn_uri_is_root(uri, len)) 1305251881Speter return apr_pstrmemdup(pool, uri, len); 1306251881Speter else 1307251881Speter return apr_pstrmemdup(pool, uri, uri_previous_segment(uri, len)); 1308251881Speter} 1309251881Speter 1310251881Speterconst char * 1311251881Spetersvn_uri_basename(const char *uri, apr_pool_t *pool) 1312251881Speter{ 1313251881Speter apr_size_t len = strlen(uri); 1314251881Speter apr_size_t start; 1315251881Speter 1316251881Speter assert(svn_uri_is_canonical(uri, NULL)); 1317251881Speter 1318251881Speter if (svn_uri_is_root(uri, len)) 1319251881Speter return ""; 1320251881Speter 1321251881Speter start = len; 1322251881Speter while (start > 0 && uri[start - 1] != '/') 1323251881Speter --start; 1324251881Speter 1325251881Speter return svn_path_uri_decode(uri + start, pool); 1326251881Speter} 1327251881Speter 1328251881Spetervoid 1329251881Spetersvn_uri_split(const char **dirpath, 1330251881Speter const char **base_name, 1331251881Speter const char *uri, 1332251881Speter apr_pool_t *pool) 1333251881Speter{ 1334251881Speter assert(dirpath != base_name); 1335251881Speter 1336251881Speter if (dirpath) 1337251881Speter *dirpath = svn_uri_dirname(uri, pool); 1338251881Speter 1339251881Speter if (base_name) 1340251881Speter *base_name = svn_uri_basename(uri, pool); 1341251881Speter} 1342251881Speter 1343251881Speterchar * 1344251881Spetersvn_dirent_get_longest_ancestor(const char *dirent1, 1345251881Speter const char *dirent2, 1346251881Speter apr_pool_t *pool) 1347251881Speter{ 1348251881Speter return apr_pstrndup(pool, dirent1, 1349251881Speter get_longest_ancestor_length(type_dirent, dirent1, 1350251881Speter dirent2, pool)); 1351251881Speter} 1352251881Speter 1353251881Speterchar * 1354251881Spetersvn_relpath_get_longest_ancestor(const char *relpath1, 1355251881Speter const char *relpath2, 1356251881Speter apr_pool_t *pool) 1357251881Speter{ 1358251881Speter assert(relpath_is_canonical(relpath1)); 1359251881Speter assert(relpath_is_canonical(relpath2)); 1360251881Speter 1361251881Speter return apr_pstrndup(pool, relpath1, 1362251881Speter get_longest_ancestor_length(type_relpath, relpath1, 1363251881Speter relpath2, pool)); 1364251881Speter} 1365251881Speter 1366251881Speterchar * 1367251881Spetersvn_uri_get_longest_ancestor(const char *uri1, 1368251881Speter const char *uri2, 1369251881Speter apr_pool_t *pool) 1370251881Speter{ 1371251881Speter apr_size_t uri_ancestor_len; 1372251881Speter apr_size_t i = 0; 1373251881Speter 1374251881Speter assert(svn_uri_is_canonical(uri1, NULL)); 1375251881Speter assert(svn_uri_is_canonical(uri2, NULL)); 1376251881Speter 1377251881Speter /* Find ':' */ 1378251881Speter while (1) 1379251881Speter { 1380251881Speter /* No shared protocol => no common prefix */ 1381251881Speter if (uri1[i] != uri2[i]) 1382251881Speter return apr_pmemdup(pool, SVN_EMPTY_PATH, 1383251881Speter sizeof(SVN_EMPTY_PATH)); 1384251881Speter 1385251881Speter if (uri1[i] == ':') 1386251881Speter break; 1387251881Speter 1388251881Speter /* They're both URLs, so EOS can't come before ':' */ 1389251881Speter assert((uri1[i] != '\0') && (uri2[i] != '\0')); 1390251881Speter 1391251881Speter i++; 1392251881Speter } 1393251881Speter 1394251881Speter i += 3; /* Advance past '://' */ 1395251881Speter 1396251881Speter uri_ancestor_len = get_longest_ancestor_length(type_uri, uri1 + i, 1397251881Speter uri2 + i, pool); 1398251881Speter 1399251881Speter if (uri_ancestor_len == 0 || 1400251881Speter (uri_ancestor_len == 1 && (uri1 + i)[0] == '/')) 1401251881Speter return apr_pmemdup(pool, SVN_EMPTY_PATH, sizeof(SVN_EMPTY_PATH)); 1402251881Speter else 1403251881Speter return apr_pstrndup(pool, uri1, uri_ancestor_len + i); 1404251881Speter} 1405251881Speter 1406251881Speterconst char * 1407251881Spetersvn_dirent_is_child(const char *parent_dirent, 1408251881Speter const char *child_dirent, 1409251881Speter apr_pool_t *pool) 1410251881Speter{ 1411251881Speter return is_child(type_dirent, parent_dirent, child_dirent, pool); 1412251881Speter} 1413251881Speter 1414251881Speterconst char * 1415251881Spetersvn_dirent_skip_ancestor(const char *parent_dirent, 1416251881Speter const char *child_dirent) 1417251881Speter{ 1418251881Speter apr_size_t len = strlen(parent_dirent); 1419251881Speter apr_size_t root_len; 1420251881Speter 1421251881Speter if (0 != strncmp(parent_dirent, child_dirent, len)) 1422251881Speter return NULL; /* parent_dirent is no ancestor of child_dirent */ 1423251881Speter 1424251881Speter if (child_dirent[len] == 0) 1425251881Speter return ""; /* parent_dirent == child_dirent */ 1426251881Speter 1427251881Speter /* Child == parent + more-characters */ 1428251881Speter 1429251881Speter root_len = dirent_root_length(child_dirent, strlen(child_dirent)); 1430251881Speter if (root_len > len) 1431251881Speter /* Different root, e.g. ("" "/...") or ("//z" "//z/share") */ 1432251881Speter return NULL; 1433251881Speter 1434251881Speter /* Now, child == [root-of-parent] + [rest-of-parent] + more-characters. 1435251881Speter * It must be one of the following forms. 1436251881Speter * 1437251881Speter * rlen parent child bad? rlen=len? c[len]=/? 1438251881Speter * 0 "" "foo" * 1439251881Speter * 0 "b" "bad" ! 1440251881Speter * 0 "b" "b/foo" * 1441251881Speter * 1 "/" "/foo" * 1442251881Speter * 1 "/b" "/bad" ! 1443251881Speter * 1 "/b" "/b/foo" * 1444251881Speter * 2 "a:" "a:foo" * 1445251881Speter * 2 "a:b" "a:bad" ! 1446251881Speter * 2 "a:b" "a:b/foo" * 1447251881Speter * 3 "a:/" "a:/foo" * 1448251881Speter * 3 "a:/b" "a:/bad" ! 1449251881Speter * 3 "a:/b" "a:/b/foo" * 1450251881Speter * 5 "//s/s" "//s/s/foo" * * 1451251881Speter * 5 "//s/s/b" "//s/s/bad" ! 1452251881Speter * 5 "//s/s/b" "//s/s/b/foo" * 1453251881Speter */ 1454251881Speter 1455251881Speter if (child_dirent[len] == '/') 1456251881Speter /* "parent|child" is one of: 1457251881Speter * "[a:]b|/foo" "[a:]/b|/foo" "//s/s|/foo" "//s/s/b|/foo" */ 1458251881Speter return child_dirent + len + 1; 1459251881Speter 1460251881Speter if (root_len == len) 1461251881Speter /* "parent|child" is "|foo" "/|foo" "a:|foo" "a:/|foo" "//s/s|/foo" */ 1462251881Speter return child_dirent + len; 1463251881Speter 1464251881Speter return NULL; 1465251881Speter} 1466251881Speter 1467251881Speterconst char * 1468251881Spetersvn_relpath_skip_ancestor(const char *parent_relpath, 1469251881Speter const char *child_relpath) 1470251881Speter{ 1471251881Speter apr_size_t len = strlen(parent_relpath); 1472251881Speter 1473251881Speter assert(relpath_is_canonical(parent_relpath)); 1474251881Speter assert(relpath_is_canonical(child_relpath)); 1475251881Speter 1476251881Speter if (len == 0) 1477251881Speter return child_relpath; 1478251881Speter 1479251881Speter if (0 != strncmp(parent_relpath, child_relpath, len)) 1480251881Speter return NULL; /* parent_relpath is no ancestor of child_relpath */ 1481251881Speter 1482251881Speter if (child_relpath[len] == 0) 1483251881Speter return ""; /* parent_relpath == child_relpath */ 1484251881Speter 1485251881Speter if (child_relpath[len] == '/') 1486251881Speter return child_relpath + len + 1; 1487251881Speter 1488251881Speter return NULL; 1489251881Speter} 1490251881Speter 1491251881Speter 1492251881Speter/* */ 1493251881Speterstatic const char * 1494251881Speteruri_skip_ancestor(const char *parent_uri, 1495251881Speter const char *child_uri) 1496251881Speter{ 1497251881Speter apr_size_t len = strlen(parent_uri); 1498251881Speter 1499251881Speter assert(svn_uri_is_canonical(parent_uri, NULL)); 1500251881Speter assert(svn_uri_is_canonical(child_uri, NULL)); 1501251881Speter 1502251881Speter if (0 != strncmp(parent_uri, child_uri, len)) 1503251881Speter return NULL; /* parent_uri is no ancestor of child_uri */ 1504251881Speter 1505251881Speter if (child_uri[len] == 0) 1506251881Speter return ""; /* parent_uri == child_uri */ 1507251881Speter 1508251881Speter if (child_uri[len] == '/') 1509251881Speter return child_uri + len + 1; 1510251881Speter 1511251881Speter return NULL; 1512251881Speter} 1513251881Speter 1514251881Speterconst char * 1515251881Spetersvn_uri_skip_ancestor(const char *parent_uri, 1516251881Speter const char *child_uri, 1517251881Speter apr_pool_t *result_pool) 1518251881Speter{ 1519251881Speter const char *result = uri_skip_ancestor(parent_uri, child_uri); 1520251881Speter 1521251881Speter return result ? svn_path_uri_decode(result, result_pool) : NULL; 1522251881Speter} 1523251881Speter 1524251881Spetersvn_boolean_t 1525251881Spetersvn_dirent_is_ancestor(const char *parent_dirent, const char *child_dirent) 1526251881Speter{ 1527251881Speter return svn_dirent_skip_ancestor(parent_dirent, child_dirent) != NULL; 1528251881Speter} 1529251881Speter 1530251881Spetersvn_boolean_t 1531251881Spetersvn_uri__is_ancestor(const char *parent_uri, const char *child_uri) 1532251881Speter{ 1533251881Speter return uri_skip_ancestor(parent_uri, child_uri) != NULL; 1534251881Speter} 1535251881Speter 1536251881Speter 1537251881Spetersvn_boolean_t 1538251881Spetersvn_dirent_is_absolute(const char *dirent) 1539251881Speter{ 1540251881Speter if (! dirent) 1541251881Speter return FALSE; 1542251881Speter 1543251881Speter /* dirent is absolute if it starts with '/' on non-Windows platforms 1544251881Speter or with '//' on Windows platforms */ 1545251881Speter if (dirent[0] == '/' 1546251881Speter#ifdef SVN_USE_DOS_PATHS 1547251881Speter && dirent[1] == '/' /* Single '/' depends on current drive */ 1548251881Speter#endif 1549251881Speter ) 1550251881Speter return TRUE; 1551251881Speter 1552251881Speter /* On Windows, dirent is also absolute when it starts with 'H:/' 1553251881Speter where 'H' is any letter. */ 1554251881Speter#ifdef SVN_USE_DOS_PATHS 1555251881Speter if (((dirent[0] >= 'A' && dirent[0] <= 'Z')) && 1556251881Speter (dirent[1] == ':') && (dirent[2] == '/')) 1557251881Speter return TRUE; 1558251881Speter#endif /* SVN_USE_DOS_PATHS */ 1559251881Speter 1560251881Speter return FALSE; 1561251881Speter} 1562251881Speter 1563251881Spetersvn_error_t * 1564251881Spetersvn_dirent_get_absolute(const char **pabsolute, 1565251881Speter const char *relative, 1566251881Speter apr_pool_t *pool) 1567251881Speter{ 1568251881Speter char *buffer; 1569251881Speter apr_status_t apr_err; 1570251881Speter const char *path_apr; 1571251881Speter 1572251881Speter SVN_ERR_ASSERT(! svn_path_is_url(relative)); 1573251881Speter 1574251881Speter /* Merge the current working directory with the relative dirent. */ 1575251881Speter SVN_ERR(svn_path_cstring_from_utf8(&path_apr, relative, pool)); 1576251881Speter 1577251881Speter apr_err = apr_filepath_merge(&buffer, NULL, 1578251881Speter path_apr, 1579251881Speter APR_FILEPATH_NOTRELATIVE, 1580251881Speter pool); 1581251881Speter if (apr_err) 1582251881Speter { 1583251881Speter /* In some cases when the passed path or its ancestor(s) do not exist 1584251881Speter or no longer exist apr returns an error. 1585251881Speter 1586251881Speter In many of these cases we would like to return a path anyway, when the 1587251881Speter passed path was already a safe absolute path. So check for that now to 1588251881Speter avoid an error. 1589251881Speter 1590251881Speter svn_dirent_is_absolute() doesn't perform the necessary checks to see 1591251881Speter if the path doesn't need post processing to be in the canonical absolute 1592251881Speter format. 1593251881Speter */ 1594251881Speter 1595251881Speter if (svn_dirent_is_absolute(relative) 1596251881Speter && svn_dirent_is_canonical(relative, pool) 1597251881Speter && !svn_path_is_backpath_present(relative)) 1598251881Speter { 1599251881Speter *pabsolute = apr_pstrdup(pool, relative); 1600251881Speter return SVN_NO_ERROR; 1601251881Speter } 1602251881Speter 1603251881Speter return svn_error_createf(SVN_ERR_BAD_FILENAME, 1604251881Speter svn_error_create(apr_err, NULL, NULL), 1605251881Speter _("Couldn't determine absolute path of '%s'"), 1606251881Speter svn_dirent_local_style(relative, pool)); 1607251881Speter } 1608251881Speter 1609251881Speter SVN_ERR(svn_path_cstring_to_utf8(pabsolute, buffer, pool)); 1610251881Speter *pabsolute = svn_dirent_canonicalize(*pabsolute, pool); 1611251881Speter return SVN_NO_ERROR; 1612251881Speter} 1613251881Speter 1614251881Speterconst char * 1615251881Spetersvn_uri_canonicalize(const char *uri, apr_pool_t *pool) 1616251881Speter{ 1617251881Speter return canonicalize(type_uri, uri, pool); 1618251881Speter} 1619251881Speter 1620251881Speterconst char * 1621251881Spetersvn_relpath_canonicalize(const char *relpath, apr_pool_t *pool) 1622251881Speter{ 1623251881Speter return canonicalize(type_relpath, relpath, pool); 1624251881Speter} 1625251881Speter 1626251881Speterconst char * 1627251881Spetersvn_dirent_canonicalize(const char *dirent, apr_pool_t *pool) 1628251881Speter{ 1629251881Speter const char *dst = canonicalize(type_dirent, dirent, pool); 1630251881Speter 1631251881Speter#ifdef SVN_USE_DOS_PATHS 1632251881Speter /* Handle a specific case on Windows where path == "X:/". Here we have to 1633251881Speter append the final '/', as svn_path_canonicalize will chop this of. */ 1634251881Speter if (((dirent[0] >= 'A' && dirent[0] <= 'Z') || 1635251881Speter (dirent[0] >= 'a' && dirent[0] <= 'z')) && 1636251881Speter dirent[1] == ':' && dirent[2] == '/' && 1637251881Speter dst[3] == '\0') 1638251881Speter { 1639251881Speter char *dst_slash = apr_pcalloc(pool, 4); 1640251881Speter dst_slash[0] = canonicalize_to_upper(dirent[0]); 1641251881Speter dst_slash[1] = ':'; 1642251881Speter dst_slash[2] = '/'; 1643251881Speter dst_slash[3] = '\0'; 1644251881Speter 1645251881Speter return dst_slash; 1646251881Speter } 1647251881Speter#endif /* SVN_USE_DOS_PATHS */ 1648251881Speter 1649251881Speter return dst; 1650251881Speter} 1651251881Speter 1652251881Spetersvn_boolean_t 1653251881Spetersvn_dirent_is_canonical(const char *dirent, apr_pool_t *scratch_pool) 1654251881Speter{ 1655251881Speter const char *ptr = dirent; 1656251881Speter if (*ptr == '/') 1657251881Speter { 1658251881Speter ptr++; 1659251881Speter#ifdef SVN_USE_DOS_PATHS 1660251881Speter /* Check for UNC paths */ 1661251881Speter if (*ptr == '/') 1662251881Speter { 1663251881Speter /* TODO: Scan hostname and sharename and fall back to part code */ 1664251881Speter 1665251881Speter /* ### Fall back to old implementation */ 1666251881Speter return (strcmp(dirent, svn_dirent_canonicalize(dirent, scratch_pool)) 1667251881Speter == 0); 1668251881Speter } 1669251881Speter#endif /* SVN_USE_DOS_PATHS */ 1670251881Speter } 1671251881Speter#ifdef SVN_USE_DOS_PATHS 1672251881Speter else if (((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= 'A' && *ptr <= 'Z')) && 1673251881Speter (ptr[1] == ':')) 1674251881Speter { 1675251881Speter /* The only canonical drive names are "A:"..."Z:", no lower case */ 1676251881Speter if (*ptr < 'A' || *ptr > 'Z') 1677251881Speter return FALSE; 1678251881Speter 1679251881Speter ptr += 2; 1680251881Speter 1681251881Speter if (*ptr == '/') 1682251881Speter ptr++; 1683251881Speter } 1684251881Speter#endif /* SVN_USE_DOS_PATHS */ 1685251881Speter 1686251881Speter return relpath_is_canonical(ptr); 1687251881Speter} 1688251881Speter 1689251881Speterstatic svn_boolean_t 1690251881Speterrelpath_is_canonical(const char *relpath) 1691251881Speter{ 1692251881Speter const char *ptr = relpath, *seg = relpath; 1693251881Speter 1694251881Speter /* RELPATH is canonical if it has: 1695251881Speter * - no '.' segments 1696251881Speter * - no start and closing '/' 1697251881Speter * - no '//' 1698251881Speter */ 1699251881Speter 1700251881Speter if (*relpath == '\0') 1701251881Speter return TRUE; 1702251881Speter 1703251881Speter if (*ptr == '/') 1704251881Speter return FALSE; 1705251881Speter 1706251881Speter /* Now validate the rest of the path. */ 1707251881Speter while(1) 1708251881Speter { 1709251881Speter apr_size_t seglen = ptr - seg; 1710251881Speter 1711251881Speter if (seglen == 1 && *seg == '.') 1712251881Speter return FALSE; /* /./ */ 1713251881Speter 1714251881Speter if (*ptr == '/' && *(ptr+1) == '/') 1715251881Speter return FALSE; /* // */ 1716251881Speter 1717251881Speter if (! *ptr && *(ptr - 1) == '/') 1718251881Speter return FALSE; /* foo/ */ 1719251881Speter 1720251881Speter if (! *ptr) 1721251881Speter break; 1722251881Speter 1723251881Speter if (*ptr == '/') 1724251881Speter ptr++; 1725251881Speter seg = ptr; 1726251881Speter 1727251881Speter while (*ptr && (*ptr != '/')) 1728251881Speter ptr++; 1729251881Speter } 1730251881Speter 1731251881Speter return TRUE; 1732251881Speter} 1733251881Speter 1734251881Spetersvn_boolean_t 1735251881Spetersvn_relpath_is_canonical(const char *relpath) 1736251881Speter{ 1737251881Speter return relpath_is_canonical(relpath); 1738251881Speter} 1739251881Speter 1740251881Spetersvn_boolean_t 1741251881Spetersvn_uri_is_canonical(const char *uri, apr_pool_t *scratch_pool) 1742251881Speter{ 1743251881Speter const char *ptr = uri, *seg = uri; 1744251881Speter const char *schema_data = NULL; 1745251881Speter 1746251881Speter /* URI is canonical if it has: 1747251881Speter * - lowercase URL scheme 1748251881Speter * - lowercase URL hostname 1749251881Speter * - no '.' segments 1750251881Speter * - no closing '/' 1751251881Speter * - no '//' 1752251881Speter * - uppercase hex-encoded pair digits ("%AB", not "%ab") 1753251881Speter */ 1754251881Speter 1755251881Speter if (*uri == '\0') 1756251881Speter return FALSE; 1757251881Speter 1758251881Speter if (! svn_path_is_url(uri)) 1759251881Speter return FALSE; 1760251881Speter 1761251881Speter /* Skip the scheme. */ 1762251881Speter while (*ptr && (*ptr != '/') && (*ptr != ':')) 1763251881Speter ptr++; 1764251881Speter 1765251881Speter /* No scheme? No good. */ 1766251881Speter if (! (*ptr == ':' && *(ptr+1) == '/' && *(ptr+2) == '/')) 1767251881Speter return FALSE; 1768251881Speter 1769251881Speter /* Found a scheme, check that it's all lowercase. */ 1770251881Speter ptr = uri; 1771251881Speter while (*ptr != ':') 1772251881Speter { 1773251881Speter if (*ptr >= 'A' && *ptr <= 'Z') 1774251881Speter return FALSE; 1775251881Speter ptr++; 1776251881Speter } 1777251881Speter /* Skip :// */ 1778251881Speter ptr += 3; 1779251881Speter 1780251881Speter /* Scheme only? That works. */ 1781251881Speter if (! *ptr) 1782251881Speter return TRUE; 1783251881Speter 1784251881Speter /* This might be the hostname */ 1785251881Speter seg = ptr; 1786251881Speter while (*ptr && (*ptr != '/') && (*ptr != '@')) 1787251881Speter ptr++; 1788251881Speter 1789251881Speter if (*ptr == '@') 1790251881Speter seg = ptr + 1; 1791251881Speter 1792251881Speter /* Found a hostname, check that it's all lowercase. */ 1793251881Speter ptr = seg; 1794251881Speter 1795251881Speter if (*ptr == '[') 1796251881Speter { 1797251881Speter ptr++; 1798251881Speter while (*ptr == ':' 1799251881Speter || (*ptr >= '0' && *ptr <= '9') 1800251881Speter || (*ptr >= 'a' && *ptr <= 'f')) 1801251881Speter { 1802251881Speter ptr++; 1803251881Speter } 1804251881Speter 1805251881Speter if (*ptr != ']') 1806251881Speter return FALSE; 1807251881Speter ptr++; 1808251881Speter } 1809251881Speter else 1810251881Speter while (*ptr && *ptr != '/' && *ptr != ':') 1811251881Speter { 1812251881Speter if (*ptr >= 'A' && *ptr <= 'Z') 1813251881Speter return FALSE; 1814251881Speter ptr++; 1815251881Speter } 1816251881Speter 1817251881Speter /* Found a portnumber */ 1818251881Speter if (*ptr == ':') 1819251881Speter { 1820251881Speter apr_int64_t port = 0; 1821251881Speter 1822251881Speter ptr++; 1823251881Speter schema_data = ptr; 1824251881Speter 1825251881Speter while (*ptr >= '0' && *ptr <= '9') 1826251881Speter { 1827251881Speter port = 10 * port + (*ptr - '0'); 1828251881Speter ptr++; 1829251881Speter } 1830251881Speter 1831251881Speter if (ptr == schema_data) 1832251881Speter return FALSE; /* Fail on "http://host:" */ 1833251881Speter 1834251881Speter if (*ptr && *ptr != '/') 1835251881Speter return FALSE; /* Not a port number */ 1836251881Speter 1837251881Speter if (port == 80 && strncmp(uri, "http:", 5) == 0) 1838251881Speter return FALSE; 1839251881Speter else if (port == 443 && strncmp(uri, "https:", 6) == 0) 1840251881Speter return FALSE; 1841251881Speter else if (port == 3690 && strncmp(uri, "svn:", 4) == 0) 1842251881Speter return FALSE; 1843251881Speter } 1844251881Speter 1845251881Speter schema_data = ptr; 1846251881Speter 1847251881Speter#ifdef SVN_USE_DOS_PATHS 1848251881Speter if (schema_data && *ptr == '/') 1849251881Speter { 1850251881Speter /* If this is a file url, ptr now points to the third '/' in 1851251881Speter file:///C:/path. Check that if we have such a URL the drive 1852251881Speter letter is in uppercase. */ 1853251881Speter if (strncmp(uri, "file:", 5) == 0 && 1854251881Speter ! (*(ptr+1) >= 'A' && *(ptr+1) <= 'Z') && 1855251881Speter *(ptr+2) == ':') 1856251881Speter return FALSE; 1857251881Speter } 1858251881Speter#endif /* SVN_USE_DOS_PATHS */ 1859251881Speter 1860251881Speter /* Now validate the rest of the URI. */ 1861257936Speter seg = ptr; 1862257936Speter while (*ptr && (*ptr != '/')) 1863257936Speter ptr++; 1864251881Speter while(1) 1865251881Speter { 1866251881Speter apr_size_t seglen = ptr - seg; 1867251881Speter 1868251881Speter if (seglen == 1 && *seg == '.') 1869251881Speter return FALSE; /* /./ */ 1870251881Speter 1871251881Speter if (*ptr == '/' && *(ptr+1) == '/') 1872251881Speter return FALSE; /* // */ 1873251881Speter 1874251881Speter if (! *ptr && *(ptr - 1) == '/' && ptr - 1 != uri) 1875251881Speter return FALSE; /* foo/ */ 1876251881Speter 1877251881Speter if (! *ptr) 1878251881Speter break; 1879251881Speter 1880251881Speter if (*ptr == '/') 1881251881Speter ptr++; 1882257936Speter 1883251881Speter seg = ptr; 1884251881Speter while (*ptr && (*ptr != '/')) 1885251881Speter ptr++; 1886251881Speter } 1887251881Speter 1888251881Speter ptr = schema_data; 1889251881Speter 1890251881Speter while (*ptr) 1891251881Speter { 1892251881Speter if (*ptr == '%') 1893251881Speter { 1894251881Speter char digitz[3]; 1895251881Speter int val; 1896251881Speter 1897251881Speter /* Can't usesvn_ctype_isxdigit() because lower case letters are 1898251881Speter not in our canonical format */ 1899251881Speter if (((*(ptr+1) < '0' || *(ptr+1) > '9')) 1900251881Speter && (*(ptr+1) < 'A' || *(ptr+1) > 'F')) 1901251881Speter return FALSE; 1902251881Speter else if (((*(ptr+2) < '0' || *(ptr+2) > '9')) 1903251881Speter && (*(ptr+2) < 'A' || *(ptr+2) > 'F')) 1904251881Speter return FALSE; 1905251881Speter 1906251881Speter digitz[0] = *(++ptr); 1907251881Speter digitz[1] = *(++ptr); 1908251881Speter digitz[2] = '\0'; 1909251881Speter val = (int)strtol(digitz, NULL, 16); 1910251881Speter 1911251881Speter if (svn_uri__char_validity[val]) 1912251881Speter return FALSE; /* Should not have been escaped */ 1913251881Speter } 1914251881Speter else if (*ptr != '/' && !svn_uri__char_validity[(unsigned char)*ptr]) 1915251881Speter return FALSE; /* Character should have been escaped */ 1916251881Speter ptr++; 1917251881Speter } 1918251881Speter 1919251881Speter return TRUE; 1920251881Speter} 1921251881Speter 1922251881Spetersvn_error_t * 1923251881Spetersvn_dirent_condense_targets(const char **pcommon, 1924251881Speter apr_array_header_t **pcondensed_targets, 1925251881Speter const apr_array_header_t *targets, 1926251881Speter svn_boolean_t remove_redundancies, 1927251881Speter apr_pool_t *result_pool, 1928251881Speter apr_pool_t *scratch_pool) 1929251881Speter{ 1930251881Speter int i, num_condensed = targets->nelts; 1931251881Speter svn_boolean_t *removed; 1932251881Speter apr_array_header_t *abs_targets; 1933251881Speter 1934251881Speter /* Early exit when there's no data to work on. */ 1935251881Speter if (targets->nelts <= 0) 1936251881Speter { 1937251881Speter *pcommon = NULL; 1938251881Speter if (pcondensed_targets) 1939251881Speter *pcondensed_targets = NULL; 1940251881Speter return SVN_NO_ERROR; 1941251881Speter } 1942251881Speter 1943251881Speter /* Get the absolute path of the first target. */ 1944251881Speter SVN_ERR(svn_dirent_get_absolute(pcommon, 1945251881Speter APR_ARRAY_IDX(targets, 0, const char *), 1946251881Speter scratch_pool)); 1947251881Speter 1948251881Speter /* Early exit when there's only one dirent to work on. */ 1949251881Speter if (targets->nelts == 1) 1950251881Speter { 1951251881Speter *pcommon = apr_pstrdup(result_pool, *pcommon); 1952251881Speter if (pcondensed_targets) 1953251881Speter *pcondensed_targets = apr_array_make(result_pool, 0, 1954251881Speter sizeof(const char *)); 1955251881Speter return SVN_NO_ERROR; 1956251881Speter } 1957251881Speter 1958251881Speter /* Copy the targets array, but with absolute dirents instead of 1959251881Speter relative. Also, find the pcommon argument by finding what is 1960251881Speter common in all of the absolute dirents. NOTE: This is not as 1961251881Speter efficient as it could be. The calculation of the basedir could 1962251881Speter be done in the loop below, which would save some calls to 1963251881Speter svn_dirent_get_longest_ancestor. I decided to do it this way 1964251881Speter because I thought it would be simpler, since this way, we don't 1965251881Speter even do the loop if we don't need to condense the targets. */ 1966251881Speter 1967251881Speter removed = apr_pcalloc(scratch_pool, (targets->nelts * 1968251881Speter sizeof(svn_boolean_t))); 1969251881Speter abs_targets = apr_array_make(scratch_pool, targets->nelts, 1970251881Speter sizeof(const char *)); 1971251881Speter 1972251881Speter APR_ARRAY_PUSH(abs_targets, const char *) = *pcommon; 1973251881Speter 1974251881Speter for (i = 1; i < targets->nelts; ++i) 1975251881Speter { 1976251881Speter const char *rel = APR_ARRAY_IDX(targets, i, const char *); 1977251881Speter const char *absolute; 1978251881Speter SVN_ERR(svn_dirent_get_absolute(&absolute, rel, scratch_pool)); 1979251881Speter APR_ARRAY_PUSH(abs_targets, const char *) = absolute; 1980251881Speter *pcommon = svn_dirent_get_longest_ancestor(*pcommon, absolute, 1981251881Speter scratch_pool); 1982251881Speter } 1983251881Speter 1984251881Speter *pcommon = apr_pstrdup(result_pool, *pcommon); 1985251881Speter 1986251881Speter if (pcondensed_targets != NULL) 1987251881Speter { 1988251881Speter size_t basedir_len; 1989251881Speter 1990251881Speter if (remove_redundancies) 1991251881Speter { 1992251881Speter /* Find the common part of each pair of targets. If 1993251881Speter common part is equal to one of the dirents, the other 1994251881Speter is a child of it, and can be removed. If a target is 1995251881Speter equal to *pcommon, it can also be removed. */ 1996251881Speter 1997251881Speter /* First pass: when one non-removed target is a child of 1998251881Speter another non-removed target, remove the child. */ 1999251881Speter for (i = 0; i < abs_targets->nelts; ++i) 2000251881Speter { 2001251881Speter int j; 2002251881Speter 2003251881Speter if (removed[i]) 2004251881Speter continue; 2005251881Speter 2006251881Speter for (j = i + 1; j < abs_targets->nelts; ++j) 2007251881Speter { 2008251881Speter const char *abs_targets_i; 2009251881Speter const char *abs_targets_j; 2010251881Speter const char *ancestor; 2011251881Speter 2012251881Speter if (removed[j]) 2013251881Speter continue; 2014251881Speter 2015251881Speter abs_targets_i = APR_ARRAY_IDX(abs_targets, i, const char *); 2016251881Speter abs_targets_j = APR_ARRAY_IDX(abs_targets, j, const char *); 2017251881Speter 2018251881Speter ancestor = svn_dirent_get_longest_ancestor 2019251881Speter (abs_targets_i, abs_targets_j, scratch_pool); 2020251881Speter 2021251881Speter if (*ancestor == '\0') 2022251881Speter continue; 2023251881Speter 2024251881Speter if (strcmp(ancestor, abs_targets_i) == 0) 2025251881Speter { 2026251881Speter removed[j] = TRUE; 2027251881Speter num_condensed--; 2028251881Speter } 2029251881Speter else if (strcmp(ancestor, abs_targets_j) == 0) 2030251881Speter { 2031251881Speter removed[i] = TRUE; 2032251881Speter num_condensed--; 2033251881Speter } 2034251881Speter } 2035251881Speter } 2036251881Speter 2037251881Speter /* Second pass: when a target is the same as *pcommon, 2038251881Speter remove the target. */ 2039251881Speter for (i = 0; i < abs_targets->nelts; ++i) 2040251881Speter { 2041251881Speter const char *abs_targets_i = APR_ARRAY_IDX(abs_targets, i, 2042251881Speter const char *); 2043251881Speter 2044251881Speter if ((strcmp(abs_targets_i, *pcommon) == 0) && (! removed[i])) 2045251881Speter { 2046251881Speter removed[i] = TRUE; 2047251881Speter num_condensed--; 2048251881Speter } 2049251881Speter } 2050251881Speter } 2051251881Speter 2052251881Speter /* Now create the return array, and copy the non-removed items */ 2053251881Speter basedir_len = strlen(*pcommon); 2054251881Speter *pcondensed_targets = apr_array_make(result_pool, num_condensed, 2055251881Speter sizeof(const char *)); 2056251881Speter 2057251881Speter for (i = 0; i < abs_targets->nelts; ++i) 2058251881Speter { 2059251881Speter const char *rel_item = APR_ARRAY_IDX(abs_targets, i, const char *); 2060251881Speter 2061251881Speter /* Skip this if it's been removed. */ 2062251881Speter if (removed[i]) 2063251881Speter continue; 2064251881Speter 2065251881Speter /* If a common prefix was found, condensed_targets are given 2066251881Speter relative to that prefix. */ 2067251881Speter if (basedir_len > 0) 2068251881Speter { 2069251881Speter /* Only advance our pointer past a dirent separator if 2070251881Speter REL_ITEM isn't the same as *PCOMMON. 2071251881Speter 2072251881Speter If *PCOMMON is a root dirent, basedir_len will already 2073251881Speter include the closing '/', so never advance the pointer 2074251881Speter here. 2075251881Speter */ 2076251881Speter rel_item += basedir_len; 2077251881Speter if (rel_item[0] && 2078251881Speter ! svn_dirent_is_root(*pcommon, basedir_len)) 2079251881Speter rel_item++; 2080251881Speter } 2081251881Speter 2082251881Speter APR_ARRAY_PUSH(*pcondensed_targets, const char *) 2083251881Speter = apr_pstrdup(result_pool, rel_item); 2084251881Speter } 2085251881Speter } 2086251881Speter 2087251881Speter return SVN_NO_ERROR; 2088251881Speter} 2089251881Speter 2090251881Spetersvn_error_t * 2091251881Spetersvn_uri_condense_targets(const char **pcommon, 2092251881Speter apr_array_header_t **pcondensed_targets, 2093251881Speter const apr_array_header_t *targets, 2094251881Speter svn_boolean_t remove_redundancies, 2095251881Speter apr_pool_t *result_pool, 2096251881Speter apr_pool_t *scratch_pool) 2097251881Speter{ 2098251881Speter int i, num_condensed = targets->nelts; 2099251881Speter apr_array_header_t *uri_targets; 2100251881Speter svn_boolean_t *removed; 2101251881Speter 2102251881Speter /* Early exit when there's no data to work on. */ 2103251881Speter if (targets->nelts <= 0) 2104251881Speter { 2105251881Speter *pcommon = NULL; 2106251881Speter if (pcondensed_targets) 2107251881Speter *pcondensed_targets = NULL; 2108251881Speter return SVN_NO_ERROR; 2109251881Speter } 2110251881Speter 2111251881Speter *pcommon = svn_uri_canonicalize(APR_ARRAY_IDX(targets, 0, const char *), 2112251881Speter scratch_pool); 2113251881Speter 2114251881Speter /* Early exit when there's only one uri to work on. */ 2115251881Speter if (targets->nelts == 1) 2116251881Speter { 2117251881Speter *pcommon = apr_pstrdup(result_pool, *pcommon); 2118251881Speter if (pcondensed_targets) 2119251881Speter *pcondensed_targets = apr_array_make(result_pool, 0, 2120251881Speter sizeof(const char *)); 2121251881Speter return SVN_NO_ERROR; 2122251881Speter } 2123251881Speter 2124251881Speter /* Find the pcommon argument by finding what is common in all of the 2125251881Speter uris. NOTE: This is not as efficient as it could be. The calculation 2126251881Speter of the basedir could be done in the loop below, which would 2127251881Speter save some calls to svn_uri_get_longest_ancestor. I decided to do it 2128251881Speter this way because I thought it would be simpler, since this way, we don't 2129251881Speter even do the loop if we don't need to condense the targets. */ 2130251881Speter 2131251881Speter removed = apr_pcalloc(scratch_pool, (targets->nelts * 2132251881Speter sizeof(svn_boolean_t))); 2133251881Speter uri_targets = apr_array_make(scratch_pool, targets->nelts, 2134251881Speter sizeof(const char *)); 2135251881Speter 2136251881Speter APR_ARRAY_PUSH(uri_targets, const char *) = *pcommon; 2137251881Speter 2138251881Speter for (i = 1; i < targets->nelts; ++i) 2139251881Speter { 2140251881Speter const char *uri = svn_uri_canonicalize( 2141251881Speter APR_ARRAY_IDX(targets, i, const char *), 2142251881Speter scratch_pool); 2143251881Speter APR_ARRAY_PUSH(uri_targets, const char *) = uri; 2144251881Speter 2145251881Speter /* If the commonmost ancestor so far is empty, there's no point 2146251881Speter in continuing to search for a common ancestor at all. But 2147251881Speter we'll keep looping for the sake of canonicalizing the 2148251881Speter targets, I suppose. */ 2149251881Speter if (**pcommon != '\0') 2150251881Speter *pcommon = svn_uri_get_longest_ancestor(*pcommon, uri, 2151251881Speter scratch_pool); 2152251881Speter } 2153251881Speter 2154251881Speter *pcommon = apr_pstrdup(result_pool, *pcommon); 2155251881Speter 2156251881Speter if (pcondensed_targets != NULL) 2157251881Speter { 2158251881Speter size_t basedir_len; 2159251881Speter 2160251881Speter if (remove_redundancies) 2161251881Speter { 2162251881Speter /* Find the common part of each pair of targets. If 2163251881Speter common part is equal to one of the dirents, the other 2164251881Speter is a child of it, and can be removed. If a target is 2165251881Speter equal to *pcommon, it can also be removed. */ 2166251881Speter 2167251881Speter /* First pass: when one non-removed target is a child of 2168251881Speter another non-removed target, remove the child. */ 2169251881Speter for (i = 0; i < uri_targets->nelts; ++i) 2170251881Speter { 2171251881Speter int j; 2172251881Speter 2173251881Speter if (removed[i]) 2174251881Speter continue; 2175251881Speter 2176251881Speter for (j = i + 1; j < uri_targets->nelts; ++j) 2177251881Speter { 2178251881Speter const char *uri_i; 2179251881Speter const char *uri_j; 2180251881Speter const char *ancestor; 2181251881Speter 2182251881Speter if (removed[j]) 2183251881Speter continue; 2184251881Speter 2185251881Speter uri_i = APR_ARRAY_IDX(uri_targets, i, const char *); 2186251881Speter uri_j = APR_ARRAY_IDX(uri_targets, j, const char *); 2187251881Speter 2188251881Speter ancestor = svn_uri_get_longest_ancestor(uri_i, 2189251881Speter uri_j, 2190251881Speter scratch_pool); 2191251881Speter 2192251881Speter if (*ancestor == '\0') 2193251881Speter continue; 2194251881Speter 2195251881Speter if (strcmp(ancestor, uri_i) == 0) 2196251881Speter { 2197251881Speter removed[j] = TRUE; 2198251881Speter num_condensed--; 2199251881Speter } 2200251881Speter else if (strcmp(ancestor, uri_j) == 0) 2201251881Speter { 2202251881Speter removed[i] = TRUE; 2203251881Speter num_condensed--; 2204251881Speter } 2205251881Speter } 2206251881Speter } 2207251881Speter 2208251881Speter /* Second pass: when a target is the same as *pcommon, 2209251881Speter remove the target. */ 2210251881Speter for (i = 0; i < uri_targets->nelts; ++i) 2211251881Speter { 2212251881Speter const char *uri_targets_i = APR_ARRAY_IDX(uri_targets, i, 2213251881Speter const char *); 2214251881Speter 2215251881Speter if ((strcmp(uri_targets_i, *pcommon) == 0) && (! removed[i])) 2216251881Speter { 2217251881Speter removed[i] = TRUE; 2218251881Speter num_condensed--; 2219251881Speter } 2220251881Speter } 2221251881Speter } 2222251881Speter 2223251881Speter /* Now create the return array, and copy the non-removed items */ 2224251881Speter basedir_len = strlen(*pcommon); 2225251881Speter *pcondensed_targets = apr_array_make(result_pool, num_condensed, 2226251881Speter sizeof(const char *)); 2227251881Speter 2228251881Speter for (i = 0; i < uri_targets->nelts; ++i) 2229251881Speter { 2230251881Speter const char *rel_item = APR_ARRAY_IDX(uri_targets, i, const char *); 2231251881Speter 2232251881Speter /* Skip this if it's been removed. */ 2233251881Speter if (removed[i]) 2234251881Speter continue; 2235251881Speter 2236251881Speter /* If a common prefix was found, condensed_targets are given 2237251881Speter relative to that prefix. */ 2238251881Speter if (basedir_len > 0) 2239251881Speter { 2240251881Speter /* Only advance our pointer past a dirent separator if 2241251881Speter REL_ITEM isn't the same as *PCOMMON. 2242251881Speter 2243251881Speter If *PCOMMON is a root dirent, basedir_len will already 2244251881Speter include the closing '/', so never advance the pointer 2245251881Speter here. 2246251881Speter */ 2247251881Speter rel_item += basedir_len; 2248251881Speter if ((rel_item[0] == '/') || 2249251881Speter (rel_item[0] && !svn_uri_is_root(*pcommon, basedir_len))) 2250251881Speter { 2251251881Speter rel_item++; 2252251881Speter } 2253251881Speter } 2254251881Speter 2255251881Speter APR_ARRAY_PUSH(*pcondensed_targets, const char *) 2256251881Speter = svn_path_uri_decode(rel_item, result_pool); 2257251881Speter } 2258251881Speter } 2259251881Speter 2260251881Speter return SVN_NO_ERROR; 2261251881Speter} 2262251881Speter 2263251881Spetersvn_error_t * 2264251881Spetersvn_dirent_is_under_root(svn_boolean_t *under_root, 2265251881Speter const char **result_path, 2266251881Speter const char *base_path, 2267251881Speter const char *path, 2268251881Speter apr_pool_t *result_pool) 2269251881Speter{ 2270251881Speter apr_status_t status; 2271251881Speter char *full_path; 2272251881Speter 2273251881Speter *under_root = FALSE; 2274251881Speter if (result_path) 2275251881Speter *result_path = NULL; 2276251881Speter 2277251881Speter status = apr_filepath_merge(&full_path, 2278251881Speter base_path, 2279251881Speter path, 2280251881Speter APR_FILEPATH_NOTABOVEROOT 2281251881Speter | APR_FILEPATH_SECUREROOTTEST, 2282251881Speter result_pool); 2283251881Speter 2284251881Speter if (status == APR_SUCCESS) 2285251881Speter { 2286251881Speter if (result_path) 2287251881Speter *result_path = svn_dirent_canonicalize(full_path, result_pool); 2288251881Speter *under_root = TRUE; 2289251881Speter return SVN_NO_ERROR; 2290251881Speter } 2291251881Speter else if (status == APR_EABOVEROOT) 2292251881Speter { 2293251881Speter *under_root = FALSE; 2294251881Speter return SVN_NO_ERROR; 2295251881Speter } 2296251881Speter 2297251881Speter return svn_error_wrap_apr(status, NULL); 2298251881Speter} 2299251881Speter 2300251881Spetersvn_error_t * 2301251881Spetersvn_uri_get_dirent_from_file_url(const char **dirent, 2302251881Speter const char *url, 2303251881Speter apr_pool_t *pool) 2304251881Speter{ 2305251881Speter const char *hostname, *path; 2306251881Speter 2307251881Speter SVN_ERR_ASSERT(svn_uri_is_canonical(url, pool)); 2308251881Speter 2309251881Speter /* Verify that the URL is well-formed (loosely) */ 2310251881Speter 2311251881Speter /* First, check for the "file://" prefix. */ 2312251881Speter if (strncmp(url, "file://", 7) != 0) 2313251881Speter return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 2314251881Speter _("Local URL '%s' does not contain 'file://' " 2315251881Speter "prefix"), url); 2316251881Speter 2317251881Speter /* Find the HOSTNAME portion and the PATH portion of the URL. The host 2318251881Speter name is between the "file://" prefix and the next occurence of '/'. We 2319251881Speter are considering everything from that '/' until the end of the URL to be 2320251881Speter the absolute path portion of the URL. 2321251881Speter If we got just "file://", treat it the same as "file:///". */ 2322251881Speter hostname = url + 7; 2323251881Speter path = strchr(hostname, '/'); 2324251881Speter if (path) 2325251881Speter hostname = apr_pstrmemdup(pool, hostname, path - hostname); 2326251881Speter else 2327251881Speter path = "/"; 2328251881Speter 2329251881Speter /* URI-decode HOSTNAME, and set it to NULL if it is "" or "localhost". */ 2330251881Speter if (*hostname == '\0') 2331251881Speter hostname = NULL; 2332251881Speter else 2333251881Speter { 2334251881Speter hostname = svn_path_uri_decode(hostname, pool); 2335251881Speter if (strcmp(hostname, "localhost") == 0) 2336251881Speter hostname = NULL; 2337251881Speter } 2338251881Speter 2339251881Speter /* Duplicate the URL, starting at the top of the path. 2340251881Speter At the same time, we URI-decode the path. */ 2341251881Speter#ifdef SVN_USE_DOS_PATHS 2342251881Speter /* On Windows, we'll typically have to skip the leading / if the 2343251881Speter path starts with a drive letter. Like most Web browsers, We 2344251881Speter support two variants of this scheme: 2345251881Speter 2346251881Speter file:///X:/path and 2347251881Speter file:///X|/path 2348251881Speter 2349251881Speter Note that, at least on WinNT and above, file:////./X:/path will 2350251881Speter also work, so we must make sure the transformation doesn't break 2351251881Speter that, and file:///path (that looks within the current drive 2352251881Speter only) should also keep working. 2353251881Speter If we got a non-empty hostname other than localhost, we convert this 2354251881Speter into an UNC path. In this case, we obviously don't strip the slash 2355251881Speter even if the path looks like it starts with a drive letter. 2356251881Speter */ 2357251881Speter { 2358251881Speter static const char valid_drive_letters[] = 2359251881Speter "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 2360251881Speter /* Casting away const! */ 2361251881Speter char *dup_path = (char *)svn_path_uri_decode(path, pool); 2362251881Speter 2363251881Speter /* This check assumes ':' and '|' are already decoded! */ 2364251881Speter if (!hostname && dup_path[1] && strchr(valid_drive_letters, dup_path[1]) 2365251881Speter && (dup_path[2] == ':' || dup_path[2] == '|')) 2366251881Speter { 2367251881Speter /* Skip the leading slash. */ 2368251881Speter ++dup_path; 2369251881Speter 2370251881Speter if (dup_path[1] == '|') 2371251881Speter dup_path[1] = ':'; 2372251881Speter 2373251881Speter if (dup_path[2] == '/' || dup_path[2] == '\0') 2374251881Speter { 2375251881Speter if (dup_path[2] == '\0') 2376251881Speter { 2377251881Speter /* A valid dirent for the driveroot must be like "C:/" instead of 2378251881Speter just "C:" or svn_dirent_join() will use the current directory 2379251881Speter on the drive instead */ 2380251881Speter char *new_path = apr_pcalloc(pool, 4); 2381251881Speter new_path[0] = dup_path[0]; 2382251881Speter new_path[1] = ':'; 2383251881Speter new_path[2] = '/'; 2384251881Speter new_path[3] = '\0'; 2385251881Speter dup_path = new_path; 2386251881Speter } 2387251881Speter } 2388251881Speter } 2389251881Speter if (hostname) 2390251881Speter { 2391251881Speter if (dup_path[0] == '/' && dup_path[1] == '\0') 2392251881Speter return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 2393251881Speter _("Local URL '%s' contains only a hostname, " 2394251881Speter "no path"), url); 2395251881Speter 2396251881Speter /* We still know that the path starts with a slash. */ 2397251881Speter *dirent = apr_pstrcat(pool, "//", hostname, dup_path, NULL); 2398251881Speter } 2399251881Speter else 2400251881Speter *dirent = dup_path; 2401251881Speter } 2402251881Speter#else /* !SVN_USE_DOS_PATHS */ 2403251881Speter /* Currently, the only hostnames we are allowing on non-Win32 platforms 2404251881Speter are the empty string and 'localhost'. */ 2405251881Speter if (hostname) 2406251881Speter return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 2407251881Speter _("Local URL '%s' contains unsupported hostname"), 2408251881Speter url); 2409251881Speter 2410251881Speter *dirent = svn_path_uri_decode(path, pool); 2411251881Speter#endif /* SVN_USE_DOS_PATHS */ 2412251881Speter return SVN_NO_ERROR; 2413251881Speter} 2414251881Speter 2415251881Spetersvn_error_t * 2416251881Spetersvn_uri_get_file_url_from_dirent(const char **url, 2417251881Speter const char *dirent, 2418251881Speter apr_pool_t *pool) 2419251881Speter{ 2420251881Speter assert(svn_dirent_is_canonical(dirent, pool)); 2421251881Speter 2422251881Speter SVN_ERR(svn_dirent_get_absolute(&dirent, dirent, pool)); 2423251881Speter 2424251881Speter dirent = svn_path_uri_encode(dirent, pool); 2425251881Speter 2426251881Speter#ifndef SVN_USE_DOS_PATHS 2427251881Speter if (dirent[0] == '/' && dirent[1] == '\0') 2428251881Speter dirent = NULL; /* "file://" is the canonical form of "file:///" */ 2429251881Speter 2430251881Speter *url = apr_pstrcat(pool, "file://", dirent, (char *)NULL); 2431251881Speter#else 2432251881Speter if (dirent[0] == '/') 2433251881Speter { 2434251881Speter /* Handle UNC paths //server/share -> file://server/share */ 2435251881Speter assert(dirent[1] == '/'); /* Expect UNC, not non-absolute */ 2436251881Speter 2437251881Speter *url = apr_pstrcat(pool, "file:", dirent, NULL); 2438251881Speter } 2439251881Speter else 2440251881Speter { 2441251881Speter char *uri = apr_pstrcat(pool, "file:///", dirent, NULL); 2442251881Speter apr_size_t len = 8 /* strlen("file:///") */ + strlen(dirent); 2443251881Speter 2444251881Speter /* "C:/" is a canonical dirent on Windows, 2445251881Speter but "file:///C:/" is not a canonical uri */ 2446251881Speter if (uri[len-1] == '/') 2447251881Speter uri[len-1] = '\0'; 2448251881Speter 2449251881Speter *url = uri; 2450251881Speter } 2451251881Speter#endif 2452251881Speter 2453251881Speter return SVN_NO_ERROR; 2454251881Speter} 2455251881Speter 2456251881Speter 2457251881Speter 2458251881Speter/* -------------- The fspath API (see private/svn_fspath.h) -------------- */ 2459251881Speter 2460251881Spetersvn_boolean_t 2461251881Spetersvn_fspath__is_canonical(const char *fspath) 2462251881Speter{ 2463251881Speter return fspath[0] == '/' && relpath_is_canonical(fspath + 1); 2464251881Speter} 2465251881Speter 2466251881Speter 2467251881Speterconst char * 2468251881Spetersvn_fspath__canonicalize(const char *fspath, 2469251881Speter apr_pool_t *pool) 2470251881Speter{ 2471251881Speter if ((fspath[0] == '/') && (fspath[1] == '\0')) 2472251881Speter return "/"; 2473251881Speter 2474251881Speter return apr_pstrcat(pool, "/", svn_relpath_canonicalize(fspath, pool), 2475251881Speter (char *)NULL); 2476251881Speter} 2477251881Speter 2478251881Speter 2479251881Spetersvn_boolean_t 2480251881Spetersvn_fspath__is_root(const char *fspath, apr_size_t len) 2481251881Speter{ 2482251881Speter /* directory is root if it's equal to '/' */ 2483251881Speter return (len == 1 && fspath[0] == '/'); 2484251881Speter} 2485251881Speter 2486251881Speter 2487251881Speterconst char * 2488251881Spetersvn_fspath__skip_ancestor(const char *parent_fspath, 2489251881Speter const char *child_fspath) 2490251881Speter{ 2491251881Speter assert(svn_fspath__is_canonical(parent_fspath)); 2492251881Speter assert(svn_fspath__is_canonical(child_fspath)); 2493251881Speter 2494251881Speter return svn_relpath_skip_ancestor(parent_fspath + 1, child_fspath + 1); 2495251881Speter} 2496251881Speter 2497251881Speter 2498251881Speterconst char * 2499251881Spetersvn_fspath__dirname(const char *fspath, 2500251881Speter apr_pool_t *pool) 2501251881Speter{ 2502251881Speter assert(svn_fspath__is_canonical(fspath)); 2503251881Speter 2504251881Speter if (fspath[0] == '/' && fspath[1] == '\0') 2505251881Speter return apr_pstrdup(pool, fspath); 2506251881Speter else 2507251881Speter return apr_pstrcat(pool, "/", svn_relpath_dirname(fspath + 1, pool), 2508251881Speter (char *)NULL); 2509251881Speter} 2510251881Speter 2511251881Speter 2512251881Speterconst char * 2513251881Spetersvn_fspath__basename(const char *fspath, 2514251881Speter apr_pool_t *pool) 2515251881Speter{ 2516251881Speter const char *result; 2517251881Speter assert(svn_fspath__is_canonical(fspath)); 2518251881Speter 2519251881Speter result = svn_relpath_basename(fspath + 1, pool); 2520251881Speter 2521251881Speter assert(strchr(result, '/') == NULL); 2522251881Speter return result; 2523251881Speter} 2524251881Speter 2525251881Spetervoid 2526251881Spetersvn_fspath__split(const char **dirpath, 2527251881Speter const char **base_name, 2528251881Speter const char *fspath, 2529251881Speter apr_pool_t *result_pool) 2530251881Speter{ 2531251881Speter assert(dirpath != base_name); 2532251881Speter 2533251881Speter if (dirpath) 2534251881Speter *dirpath = svn_fspath__dirname(fspath, result_pool); 2535251881Speter 2536251881Speter if (base_name) 2537251881Speter *base_name = svn_fspath__basename(fspath, result_pool); 2538251881Speter} 2539251881Speter 2540251881Speterchar * 2541251881Spetersvn_fspath__join(const char *fspath, 2542251881Speter const char *relpath, 2543251881Speter apr_pool_t *result_pool) 2544251881Speter{ 2545251881Speter char *result; 2546251881Speter assert(svn_fspath__is_canonical(fspath)); 2547251881Speter assert(svn_relpath_is_canonical(relpath)); 2548251881Speter 2549251881Speter if (relpath[0] == '\0') 2550251881Speter result = apr_pstrdup(result_pool, fspath); 2551251881Speter else if (fspath[1] == '\0') 2552251881Speter result = apr_pstrcat(result_pool, "/", relpath, (char *)NULL); 2553251881Speter else 2554251881Speter result = apr_pstrcat(result_pool, fspath, "/", relpath, (char *)NULL); 2555251881Speter 2556251881Speter assert(svn_fspath__is_canonical(result)); 2557251881Speter return result; 2558251881Speter} 2559251881Speter 2560251881Speterchar * 2561251881Spetersvn_fspath__get_longest_ancestor(const char *fspath1, 2562251881Speter const char *fspath2, 2563251881Speter apr_pool_t *result_pool) 2564251881Speter{ 2565251881Speter char *result; 2566251881Speter assert(svn_fspath__is_canonical(fspath1)); 2567251881Speter assert(svn_fspath__is_canonical(fspath2)); 2568251881Speter 2569251881Speter result = apr_pstrcat(result_pool, "/", 2570251881Speter svn_relpath_get_longest_ancestor(fspath1 + 1, 2571251881Speter fspath2 + 1, 2572251881Speter result_pool), 2573251881Speter (char *)NULL); 2574251881Speter 2575251881Speter assert(svn_fspath__is_canonical(result)); 2576251881Speter return result; 2577251881Speter} 2578251881Speter 2579251881Speter 2580251881Speter 2581251881Speter 2582251881Speter/* -------------- The urlpath API (see private/svn_fspath.h) ------------- */ 2583251881Speter 2584251881Speterconst char * 2585251881Spetersvn_urlpath__canonicalize(const char *uri, 2586251881Speter apr_pool_t *pool) 2587251881Speter{ 2588251881Speter if (svn_path_is_url(uri)) 2589251881Speter { 2590251881Speter uri = svn_uri_canonicalize(uri, pool); 2591251881Speter } 2592251881Speter else 2593251881Speter { 2594251881Speter uri = svn_fspath__canonicalize(uri, pool); 2595251881Speter /* Do a little dance to normalize hex encoding. */ 2596251881Speter uri = svn_path_uri_decode(uri, pool); 2597251881Speter uri = svn_path_uri_encode(uri, pool); 2598251881Speter } 2599251881Speter return uri; 2600251881Speter} 2601269833Speter 2602269833Speter 2603269833Speter/* -------------- The cert API (see private/svn_cert.h) ------------- */ 2604269833Speter 2605269833Spetersvn_boolean_t 2606269833Spetersvn_cert__match_dns_identity(svn_string_t *pattern, svn_string_t *hostname) 2607269833Speter{ 2608269833Speter apr_size_t pattern_pos = 0, hostname_pos = 0; 2609269833Speter 2610269833Speter /* support leading wildcards that composed of the only character in the 2611269833Speter * left-most label. */ 2612269833Speter if (pattern->len >= 2 && 2613269833Speter pattern->data[pattern_pos] == '*' && 2614269833Speter pattern->data[pattern_pos + 1] == '.') 2615269833Speter { 2616269833Speter while (hostname_pos < hostname->len && 2617269833Speter hostname->data[hostname_pos] != '.') 2618269833Speter { 2619269833Speter hostname_pos++; 2620269833Speter } 2621269833Speter /* Assume that the wildcard must match something. Rule 2 says 2622269833Speter * that *.example.com should not match example.com. If the wildcard 2623269833Speter * ends up not matching anything then it matches .example.com which 2624269833Speter * seems to be essentially the same as just example.com */ 2625269833Speter if (hostname_pos == 0) 2626269833Speter return FALSE; 2627269833Speter 2628269833Speter pattern_pos++; 2629269833Speter } 2630269833Speter 2631269833Speter while (pattern_pos < pattern->len && hostname_pos < hostname->len) 2632269833Speter { 2633269833Speter char pattern_c = pattern->data[pattern_pos]; 2634269833Speter char hostname_c = hostname->data[hostname_pos]; 2635269833Speter 2636269833Speter /* fold case as described in RFC 4343. 2637269833Speter * Note: We actually convert to lowercase, since our URI 2638269833Speter * canonicalization code converts to lowercase and generally 2639269833Speter * most certs are issued with lowercase DNS names, meaning 2640269833Speter * this avoids the fold operation in most cases. The RFC 2641269833Speter * suggests the opposite transformation, but doesn't require 2642269833Speter * any specific implementation in any case. It is critical 2643269833Speter * that this folding be locale independent so you can't use 2644269833Speter * tolower(). */ 2645269833Speter pattern_c = canonicalize_to_lower(pattern_c); 2646269833Speter hostname_c = canonicalize_to_lower(hostname_c); 2647269833Speter 2648269833Speter if (pattern_c != hostname_c) 2649269833Speter { 2650269833Speter /* doesn't match */ 2651269833Speter return FALSE; 2652269833Speter } 2653269833Speter else 2654269833Speter { 2655269833Speter /* characters match so skip both */ 2656269833Speter pattern_pos++; 2657269833Speter hostname_pos++; 2658269833Speter } 2659269833Speter } 2660269833Speter 2661269833Speter /* ignore a trailing period on the hostname since this has no effect on the 2662269833Speter * security of the matching. See the following for the long explanation as 2663269833Speter * to why: 2664269833Speter * https://bugzilla.mozilla.org/show_bug.cgi?id=134402#c28 2665269833Speter */ 2666269833Speter if (pattern_pos == pattern->len && 2667269833Speter hostname_pos == hostname->len - 1 && 2668269833Speter hostname->data[hostname_pos] == '.') 2669269833Speter hostname_pos++; 2670269833Speter 2671269833Speter if (pattern_pos != pattern->len || hostname_pos != hostname->len) 2672269833Speter { 2673269833Speter /* end didn't match */ 2674269833Speter return FALSE; 2675269833Speter } 2676269833Speter 2677269833Speter return TRUE; 2678269833Speter} 2679