1251881Speter/* 2251881Speter * paths.c: a path manipulation library using svn_stringbuf_t 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 29251881Speter#include <apr_file_info.h> 30251881Speter#include <apr_lib.h> 31251881Speter#include <apr_uri.h> 32251881Speter 33251881Speter#include "svn_string.h" 34251881Speter#include "svn_dirent_uri.h" 35251881Speter#include "svn_path.h" 36251881Speter#include "svn_private_config.h" /* for SVN_PATH_LOCAL_SEPARATOR */ 37251881Speter#include "svn_utf.h" 38251881Speter#include "svn_io.h" /* for svn_io_stat() */ 39251881Speter#include "svn_ctype.h" 40251881Speter 41251881Speter#include "dirent_uri.h" 42251881Speter 43251881Speter 44251881Speter/* The canonical empty path. Can this be changed? Well, change the empty 45251881Speter test below and the path library will work, not so sure about the fs/wc 46251881Speter libraries. */ 47251881Speter#define SVN_EMPTY_PATH "" 48251881Speter 49251881Speter/* TRUE if s is the canonical empty path, FALSE otherwise */ 50251881Speter#define SVN_PATH_IS_EMPTY(s) ((s)[0] == '\0') 51251881Speter 52251881Speter/* TRUE if s,n is the platform's empty path ("."), FALSE otherwise. Can 53251881Speter this be changed? Well, the path library will work, not so sure about 54251881Speter the OS! */ 55251881Speter#define SVN_PATH_IS_PLATFORM_EMPTY(s,n) ((n) == 1 && (s)[0] == '.') 56251881Speter 57251881Speter 58251881Speter 59251881Speter 60251881Speter#ifndef NDEBUG 61251881Speter/* This function is an approximation of svn_path_is_canonical. 62251881Speter * It is supposed to be used in functions that do not have access 63251881Speter * to a pool, but still want to assert that a path is canonical. 64251881Speter * 65251881Speter * PATH with length LEN is assumed to be canonical if it isn't 66251881Speter * the platform's empty path (see definition of SVN_PATH_IS_PLATFORM_EMPTY), 67251881Speter * and does not contain "/./", and any one of the following 68251881Speter * conditions is also met: 69251881Speter * 70251881Speter * 1. PATH has zero length 71251881Speter * 2. PATH is the root directory (what exactly a root directory is 72251881Speter * depends on the platform) 73251881Speter * 3. PATH is not a root directory and does not end with '/' 74251881Speter * 75251881Speter * If possible, please use svn_path_is_canonical instead. 76251881Speter */ 77251881Speterstatic svn_boolean_t 78251881Speteris_canonical(const char *path, 79251881Speter apr_size_t len) 80251881Speter{ 81251881Speter return (! SVN_PATH_IS_PLATFORM_EMPTY(path, len) 82251881Speter && strstr(path, "/./") == NULL 83251881Speter && (len == 0 84251881Speter || (len == 1 && path[0] == '/') 85251881Speter || (path[len-1] != '/') 86251881Speter#if defined(WIN32) || defined(__CYGWIN__) 87251881Speter || svn_dirent_is_root(path, len) 88251881Speter#endif 89251881Speter )); 90251881Speter} 91251881Speter#endif 92251881Speter 93251881Speter 94251881Speter/* functionality of svn_path_is_canonical but without the deprecation */ 95251881Speterstatic svn_boolean_t 96251881Spetersvn_path_is_canonical_internal(const char *path, apr_pool_t *pool) 97251881Speter{ 98251881Speter return svn_uri_is_canonical(path, pool) || 99251881Speter svn_dirent_is_canonical(path, pool) || 100251881Speter svn_relpath_is_canonical(path); 101251881Speter} 102251881Speter 103251881Spetersvn_boolean_t 104251881Spetersvn_path_is_canonical(const char *path, apr_pool_t *pool) 105251881Speter{ 106251881Speter return svn_path_is_canonical_internal(path, pool); 107251881Speter} 108251881Speter 109251881Speter/* functionality of svn_path_join but without the deprecation */ 110251881Speterstatic char * 111251881Spetersvn_path_join_internal(const char *base, 112251881Speter const char *component, 113251881Speter apr_pool_t *pool) 114251881Speter{ 115251881Speter apr_size_t blen = strlen(base); 116251881Speter apr_size_t clen = strlen(component); 117251881Speter char *path; 118251881Speter 119251881Speter assert(svn_path_is_canonical_internal(base, pool)); 120251881Speter assert(svn_path_is_canonical_internal(component, pool)); 121251881Speter 122251881Speter /* If the component is absolute, then return it. */ 123251881Speter if (*component == '/') 124251881Speter return apr_pmemdup(pool, component, clen + 1); 125251881Speter 126251881Speter /* If either is empty return the other */ 127251881Speter if (SVN_PATH_IS_EMPTY(base)) 128251881Speter return apr_pmemdup(pool, component, clen + 1); 129251881Speter if (SVN_PATH_IS_EMPTY(component)) 130251881Speter return apr_pmemdup(pool, base, blen + 1); 131251881Speter 132251881Speter if (blen == 1 && base[0] == '/') 133251881Speter blen = 0; /* Ignore base, just return separator + component */ 134251881Speter 135251881Speter /* Construct the new, combined path. */ 136251881Speter path = apr_palloc(pool, blen + 1 + clen + 1); 137251881Speter memcpy(path, base, blen); 138251881Speter path[blen] = '/'; 139251881Speter memcpy(path + blen + 1, component, clen + 1); 140251881Speter 141251881Speter return path; 142251881Speter} 143251881Speter 144251881Speterchar *svn_path_join(const char *base, 145251881Speter const char *component, 146251881Speter apr_pool_t *pool) 147251881Speter{ 148251881Speter return svn_path_join_internal(base, component, pool); 149251881Speter} 150251881Speter 151251881Speterchar *svn_path_join_many(apr_pool_t *pool, const char *base, ...) 152251881Speter{ 153251881Speter#define MAX_SAVED_LENGTHS 10 154251881Speter apr_size_t saved_lengths[MAX_SAVED_LENGTHS]; 155251881Speter apr_size_t total_len; 156251881Speter int nargs; 157251881Speter va_list va; 158251881Speter const char *s; 159251881Speter apr_size_t len; 160251881Speter char *path; 161251881Speter char *p; 162251881Speter svn_boolean_t base_is_empty = FALSE, base_is_root = FALSE; 163251881Speter int base_arg = 0; 164251881Speter 165251881Speter total_len = strlen(base); 166251881Speter 167251881Speter assert(svn_path_is_canonical_internal(base, pool)); 168251881Speter 169251881Speter if (total_len == 1 && *base == '/') 170251881Speter base_is_root = TRUE; 171251881Speter else if (SVN_PATH_IS_EMPTY(base)) 172251881Speter { 173251881Speter total_len = sizeof(SVN_EMPTY_PATH) - 1; 174251881Speter base_is_empty = TRUE; 175251881Speter } 176251881Speter 177251881Speter saved_lengths[0] = total_len; 178251881Speter 179251881Speter /* Compute the length of the resulting string. */ 180251881Speter 181251881Speter nargs = 0; 182251881Speter va_start(va, base); 183251881Speter while ((s = va_arg(va, const char *)) != NULL) 184251881Speter { 185251881Speter len = strlen(s); 186251881Speter 187251881Speter assert(svn_path_is_canonical_internal(s, pool)); 188251881Speter 189251881Speter if (SVN_PATH_IS_EMPTY(s)) 190251881Speter continue; 191251881Speter 192251881Speter if (nargs++ < MAX_SAVED_LENGTHS) 193251881Speter saved_lengths[nargs] = len; 194251881Speter 195251881Speter if (*s == '/') 196251881Speter { 197251881Speter /* an absolute path. skip all components to this point and reset 198251881Speter the total length. */ 199251881Speter total_len = len; 200251881Speter base_arg = nargs; 201251881Speter base_is_root = len == 1; 202251881Speter base_is_empty = FALSE; 203251881Speter } 204251881Speter else if (nargs == base_arg 205251881Speter || (nargs == base_arg + 1 && base_is_root) 206251881Speter || base_is_empty) 207251881Speter { 208251881Speter /* if we have skipped everything up to this arg, then the base 209251881Speter and all prior components are empty. just set the length to 210251881Speter this component; do not add a separator. If the base is empty 211251881Speter we can now ignore it. */ 212251881Speter if (base_is_empty) 213251881Speter { 214251881Speter base_is_empty = FALSE; 215251881Speter total_len = 0; 216251881Speter } 217251881Speter total_len += len; 218251881Speter } 219251881Speter else 220251881Speter { 221251881Speter total_len += 1 + len; 222251881Speter } 223251881Speter } 224251881Speter va_end(va); 225251881Speter 226251881Speter /* base == "/" and no further components. just return that. */ 227251881Speter if (base_is_root && total_len == 1) 228251881Speter return apr_pmemdup(pool, "/", 2); 229251881Speter 230251881Speter /* we got the total size. allocate it, with room for a NULL character. */ 231251881Speter path = p = apr_palloc(pool, total_len + 1); 232251881Speter 233251881Speter /* if we aren't supposed to skip forward to an absolute component, and if 234251881Speter this is not an empty base that we are skipping, then copy the base 235251881Speter into the output. */ 236251881Speter if (base_arg == 0 && ! (SVN_PATH_IS_EMPTY(base) && ! base_is_empty)) 237251881Speter { 238251881Speter if (SVN_PATH_IS_EMPTY(base)) 239251881Speter memcpy(p, SVN_EMPTY_PATH, len = saved_lengths[0]); 240251881Speter else 241251881Speter memcpy(p, base, len = saved_lengths[0]); 242251881Speter p += len; 243251881Speter } 244251881Speter 245251881Speter nargs = 0; 246251881Speter va_start(va, base); 247251881Speter while ((s = va_arg(va, const char *)) != NULL) 248251881Speter { 249251881Speter if (SVN_PATH_IS_EMPTY(s)) 250251881Speter continue; 251251881Speter 252251881Speter if (++nargs < base_arg) 253251881Speter continue; 254251881Speter 255251881Speter if (nargs < MAX_SAVED_LENGTHS) 256251881Speter len = saved_lengths[nargs]; 257251881Speter else 258251881Speter len = strlen(s); 259251881Speter 260251881Speter /* insert a separator if we aren't copying in the first component 261251881Speter (which can happen when base_arg is set). also, don't put in a slash 262251881Speter if the prior character is a slash (occurs when prior component 263251881Speter is "/"). */ 264251881Speter if (p != path && p[-1] != '/') 265251881Speter *p++ = '/'; 266251881Speter 267251881Speter /* copy the new component and advance the pointer */ 268251881Speter memcpy(p, s, len); 269251881Speter p += len; 270251881Speter } 271251881Speter va_end(va); 272251881Speter 273251881Speter *p = '\0'; 274251881Speter assert((apr_size_t)(p - path) == total_len); 275251881Speter 276251881Speter return path; 277251881Speter} 278251881Speter 279251881Speter 280251881Speter 281251881Speterapr_size_t 282251881Spetersvn_path_component_count(const char *path) 283251881Speter{ 284251881Speter apr_size_t count = 0; 285251881Speter 286251881Speter assert(is_canonical(path, strlen(path))); 287251881Speter 288251881Speter while (*path) 289251881Speter { 290251881Speter const char *start; 291251881Speter 292251881Speter while (*path == '/') 293251881Speter ++path; 294251881Speter 295251881Speter start = path; 296251881Speter 297251881Speter while (*path && *path != '/') 298251881Speter ++path; 299251881Speter 300251881Speter if (path != start) 301251881Speter ++count; 302251881Speter } 303251881Speter 304251881Speter return count; 305251881Speter} 306251881Speter 307251881Speter 308251881Speter/* Return the length of substring necessary to encompass the entire 309251881Speter * previous path segment in PATH, which should be a LEN byte string. 310251881Speter * 311251881Speter * A trailing slash will not be included in the returned length except 312251881Speter * in the case in which PATH is absolute and there are no more 313251881Speter * previous segments. 314251881Speter */ 315251881Speterstatic apr_size_t 316251881Speterprevious_segment(const char *path, 317251881Speter apr_size_t len) 318251881Speter{ 319251881Speter if (len == 0) 320251881Speter return 0; 321251881Speter 322251881Speter while (len > 0 && path[--len] != '/') 323251881Speter ; 324251881Speter 325251881Speter if (len == 0 && path[0] == '/') 326251881Speter return 1; 327251881Speter else 328251881Speter return len; 329251881Speter} 330251881Speter 331251881Speter 332251881Spetervoid 333251881Spetersvn_path_add_component(svn_stringbuf_t *path, 334251881Speter const char *component) 335251881Speter{ 336251881Speter apr_size_t len = strlen(component); 337251881Speter 338251881Speter assert(is_canonical(path->data, path->len)); 339251881Speter assert(is_canonical(component, strlen(component))); 340251881Speter 341251881Speter /* Append a dir separator, but only if this path is neither empty 342251881Speter nor consists of a single dir separator already. */ 343251881Speter if ((! SVN_PATH_IS_EMPTY(path->data)) 344251881Speter && (! ((path->len == 1) && (*(path->data) == '/')))) 345251881Speter { 346251881Speter char dirsep = '/'; 347251881Speter svn_stringbuf_appendbytes(path, &dirsep, sizeof(dirsep)); 348251881Speter } 349251881Speter 350251881Speter svn_stringbuf_appendbytes(path, component, len); 351251881Speter} 352251881Speter 353251881Speter 354251881Spetervoid 355251881Spetersvn_path_remove_component(svn_stringbuf_t *path) 356251881Speter{ 357251881Speter assert(is_canonical(path->data, path->len)); 358251881Speter 359251881Speter path->len = previous_segment(path->data, path->len); 360251881Speter path->data[path->len] = '\0'; 361251881Speter} 362251881Speter 363251881Speter 364251881Spetervoid 365251881Spetersvn_path_remove_components(svn_stringbuf_t *path, apr_size_t n) 366251881Speter{ 367251881Speter while (n > 0) 368251881Speter { 369251881Speter svn_path_remove_component(path); 370251881Speter n--; 371251881Speter } 372251881Speter} 373251881Speter 374251881Speter 375251881Speterchar * 376251881Spetersvn_path_dirname(const char *path, apr_pool_t *pool) 377251881Speter{ 378251881Speter apr_size_t len = strlen(path); 379251881Speter 380251881Speter assert(svn_path_is_canonical_internal(path, pool)); 381251881Speter 382251881Speter return apr_pstrmemdup(pool, path, previous_segment(path, len)); 383251881Speter} 384251881Speter 385251881Speter 386251881Speterchar * 387251881Spetersvn_path_basename(const char *path, apr_pool_t *pool) 388251881Speter{ 389251881Speter apr_size_t len = strlen(path); 390251881Speter apr_size_t start; 391251881Speter 392251881Speter assert(svn_path_is_canonical_internal(path, pool)); 393251881Speter 394251881Speter if (len == 1 && path[0] == '/') 395251881Speter start = 0; 396251881Speter else 397251881Speter { 398251881Speter start = len; 399251881Speter while (start > 0 && path[start - 1] != '/') 400251881Speter --start; 401251881Speter } 402251881Speter 403251881Speter return apr_pstrmemdup(pool, path + start, len - start); 404251881Speter} 405251881Speter 406251881Speterint 407251881Spetersvn_path_is_empty(const char *path) 408251881Speter{ 409251881Speter assert(is_canonical(path, strlen(path))); 410251881Speter 411251881Speter if (SVN_PATH_IS_EMPTY(path)) 412251881Speter return 1; 413251881Speter 414251881Speter return 0; 415251881Speter} 416251881Speter 417251881Speterint 418251881Spetersvn_path_compare_paths(const char *path1, 419251881Speter const char *path2) 420251881Speter{ 421251881Speter apr_size_t path1_len = strlen(path1); 422251881Speter apr_size_t path2_len = strlen(path2); 423251881Speter apr_size_t min_len = ((path1_len < path2_len) ? path1_len : path2_len); 424251881Speter apr_size_t i = 0; 425251881Speter 426251881Speter assert(is_canonical(path1, path1_len)); 427251881Speter assert(is_canonical(path2, path2_len)); 428251881Speter 429251881Speter /* Skip past common prefix. */ 430251881Speter while (i < min_len && path1[i] == path2[i]) 431251881Speter ++i; 432251881Speter 433251881Speter /* Are the paths exactly the same? */ 434251881Speter if ((path1_len == path2_len) && (i >= min_len)) 435251881Speter return 0; 436251881Speter 437251881Speter /* Children of paths are greater than their parents, but less than 438251881Speter greater siblings of their parents. */ 439251881Speter if ((path1[i] == '/') && (path2[i] == 0)) 440251881Speter return 1; 441251881Speter if ((path2[i] == '/') && (path1[i] == 0)) 442251881Speter return -1; 443251881Speter if (path1[i] == '/') 444251881Speter return -1; 445251881Speter if (path2[i] == '/') 446251881Speter return 1; 447251881Speter 448251881Speter /* Common prefix was skipped above, next character is compared to 449251881Speter determine order. We need to use an unsigned comparison, though, 450251881Speter so a "next character" of NULL (0x00) sorts numerically 451251881Speter smallest. */ 452251881Speter return (unsigned char)(path1[i]) < (unsigned char)(path2[i]) ? -1 : 1; 453251881Speter} 454251881Speter 455251881Speter/* Return the string length of the longest common ancestor of PATH1 and PATH2. 456251881Speter * 457251881Speter * This function handles everything except the URL-handling logic 458251881Speter * of svn_path_get_longest_ancestor, and assumes that PATH1 and 459251881Speter * PATH2 are *not* URLs. 460251881Speter * 461251881Speter * If the two paths do not share a common ancestor, return 0. 462251881Speter * 463251881Speter * New strings are allocated in POOL. 464251881Speter */ 465251881Speterstatic apr_size_t 466251881Speterget_path_ancestor_length(const char *path1, 467251881Speter const char *path2, 468251881Speter apr_pool_t *pool) 469251881Speter{ 470251881Speter apr_size_t path1_len, path2_len; 471251881Speter apr_size_t i = 0; 472251881Speter apr_size_t last_dirsep = 0; 473251881Speter 474251881Speter path1_len = strlen(path1); 475251881Speter path2_len = strlen(path2); 476251881Speter 477251881Speter if (SVN_PATH_IS_EMPTY(path1) || SVN_PATH_IS_EMPTY(path2)) 478251881Speter return 0; 479251881Speter 480251881Speter while (path1[i] == path2[i]) 481251881Speter { 482251881Speter /* Keep track of the last directory separator we hit. */ 483251881Speter if (path1[i] == '/') 484251881Speter last_dirsep = i; 485251881Speter 486251881Speter i++; 487251881Speter 488251881Speter /* If we get to the end of either path, break out. */ 489251881Speter if ((i == path1_len) || (i == path2_len)) 490251881Speter break; 491251881Speter } 492251881Speter 493251881Speter /* two special cases: 494251881Speter 1. '/' is the longest common ancestor of '/' and '/foo' 495251881Speter 2. '/' is the longest common ancestor of '/rif' and '/raf' */ 496251881Speter if (i == 1 && path1[0] == '/' && path2[0] == '/') 497251881Speter return 1; 498251881Speter 499251881Speter /* last_dirsep is now the offset of the last directory separator we 500251881Speter crossed before reaching a non-matching byte. i is the offset of 501251881Speter that non-matching byte. */ 502251881Speter if (((i == path1_len) && (path2[i] == '/')) 503251881Speter || ((i == path2_len) && (path1[i] == '/')) 504251881Speter || ((i == path1_len) && (i == path2_len))) 505251881Speter return i; 506251881Speter else 507251881Speter if (last_dirsep == 0 && path1[0] == '/' && path2[0] == '/') 508251881Speter return 1; 509251881Speter return last_dirsep; 510251881Speter} 511251881Speter 512251881Speter 513251881Speterchar * 514251881Spetersvn_path_get_longest_ancestor(const char *path1, 515251881Speter const char *path2, 516251881Speter apr_pool_t *pool) 517251881Speter{ 518251881Speter svn_boolean_t path1_is_url = svn_path_is_url(path1); 519251881Speter svn_boolean_t path2_is_url = svn_path_is_url(path2); 520251881Speter 521251881Speter /* Are we messing with URLs? If we have a mix of URLs and non-URLs, 522251881Speter there's nothing common between them. */ 523251881Speter if (path1_is_url && path2_is_url) 524251881Speter { 525251881Speter return svn_uri_get_longest_ancestor(path1, path2, pool); 526251881Speter } 527251881Speter else if ((! path1_is_url) && (! path2_is_url)) 528251881Speter { 529251881Speter return apr_pstrndup(pool, path1, 530251881Speter get_path_ancestor_length(path1, path2, pool)); 531251881Speter } 532251881Speter else 533251881Speter { 534251881Speter /* A URL and a non-URL => no common prefix */ 535251881Speter return apr_pmemdup(pool, SVN_EMPTY_PATH, sizeof(SVN_EMPTY_PATH)); 536251881Speter } 537251881Speter} 538251881Speter 539251881Speterconst char * 540251881Spetersvn_path_is_child(const char *path1, 541251881Speter const char *path2, 542251881Speter apr_pool_t *pool) 543251881Speter{ 544251881Speter apr_size_t i; 545251881Speter 546251881Speter /* assert (is_canonical (path1, strlen (path1))); ### Expensive strlen */ 547251881Speter /* assert (is_canonical (path2, strlen (path2))); ### Expensive strlen */ 548251881Speter 549251881Speter /* Allow "" and "foo" to be parent/child */ 550251881Speter if (SVN_PATH_IS_EMPTY(path1)) /* "" is the parent */ 551251881Speter { 552251881Speter if (SVN_PATH_IS_EMPTY(path2) /* "" not a child */ 553251881Speter || path2[0] == '/') /* "/foo" not a child */ 554251881Speter return NULL; 555251881Speter else 556251881Speter /* everything else is child */ 557251881Speter return pool ? apr_pstrdup(pool, path2) : path2; 558251881Speter } 559251881Speter 560251881Speter /* Reach the end of at least one of the paths. How should we handle 561251881Speter things like path1:"foo///bar" and path2:"foo/bar/baz"? It doesn't 562251881Speter appear to arise in the current Subversion code, it's not clear to me 563251881Speter if they should be parent/child or not. */ 564251881Speter for (i = 0; path1[i] && path2[i]; i++) 565251881Speter if (path1[i] != path2[i]) 566251881Speter return NULL; 567251881Speter 568251881Speter /* There are two cases that are parent/child 569251881Speter ... path1[i] == '\0' 570251881Speter .../foo path2[i] == '/' 571251881Speter or 572251881Speter / path1[i] == '\0' 573251881Speter /foo path2[i] != '/' 574251881Speter */ 575251881Speter if (path1[i] == '\0' && path2[i]) 576251881Speter { 577251881Speter if (path2[i] == '/') 578251881Speter return pool ? apr_pstrdup(pool, path2 + i + 1) : path2 + i + 1; 579251881Speter else if (i == 1 && path1[0] == '/') 580251881Speter return pool ? apr_pstrdup(pool, path2 + 1) : path2 + 1; 581251881Speter } 582251881Speter 583251881Speter /* Otherwise, path2 isn't a child. */ 584251881Speter return NULL; 585251881Speter} 586251881Speter 587251881Speter 588251881Spetersvn_boolean_t 589251881Spetersvn_path_is_ancestor(const char *path1, const char *path2) 590251881Speter{ 591251881Speter apr_size_t path1_len = strlen(path1); 592251881Speter 593251881Speter /* If path1 is empty and path2 is not absoulte, then path1 is an ancestor. */ 594251881Speter if (SVN_PATH_IS_EMPTY(path1)) 595251881Speter return *path2 != '/'; 596251881Speter 597251881Speter /* If path1 is a prefix of path2, then: 598251881Speter - If path1 ends in a path separator, 599251881Speter - If the paths are of the same length 600251881Speter OR 601251881Speter - path2 starts a new path component after the common prefix, 602251881Speter then path1 is an ancestor. */ 603251881Speter if (strncmp(path1, path2, path1_len) == 0) 604251881Speter return path1[path1_len - 1] == '/' 605251881Speter || (path2[path1_len] == '/' || path2[path1_len] == '\0'); 606251881Speter 607251881Speter return FALSE; 608251881Speter} 609251881Speter 610251881Speter 611251881Speterapr_array_header_t * 612251881Spetersvn_path_decompose(const char *path, 613251881Speter apr_pool_t *pool) 614251881Speter{ 615251881Speter apr_size_t i, oldi; 616251881Speter 617251881Speter apr_array_header_t *components = 618251881Speter apr_array_make(pool, 1, sizeof(const char *)); 619251881Speter 620251881Speter assert(svn_path_is_canonical_internal(path, pool)); 621251881Speter 622251881Speter if (SVN_PATH_IS_EMPTY(path)) 623251881Speter return components; /* ### Should we return a "" component? */ 624251881Speter 625251881Speter /* If PATH is absolute, store the '/' as the first component. */ 626251881Speter i = oldi = 0; 627251881Speter if (path[i] == '/') 628251881Speter { 629251881Speter char dirsep = '/'; 630251881Speter 631251881Speter APR_ARRAY_PUSH(components, const char *) 632251881Speter = apr_pstrmemdup(pool, &dirsep, sizeof(dirsep)); 633251881Speter 634251881Speter i++; 635251881Speter oldi++; 636251881Speter if (path[i] == '\0') /* path is a single '/' */ 637251881Speter return components; 638251881Speter } 639251881Speter 640251881Speter do 641251881Speter { 642251881Speter if ((path[i] == '/') || (path[i] == '\0')) 643251881Speter { 644251881Speter if (SVN_PATH_IS_PLATFORM_EMPTY(path + oldi, i - oldi)) 645251881Speter APR_ARRAY_PUSH(components, const char *) = SVN_EMPTY_PATH; 646251881Speter else 647251881Speter APR_ARRAY_PUSH(components, const char *) 648251881Speter = apr_pstrmemdup(pool, path + oldi, i - oldi); 649251881Speter 650251881Speter i++; 651251881Speter oldi = i; /* skipping past the dirsep */ 652251881Speter continue; 653251881Speter } 654251881Speter i++; 655251881Speter } 656251881Speter while (path[i-1]); 657251881Speter 658251881Speter return components; 659251881Speter} 660251881Speter 661251881Speter 662251881Speterconst char * 663251881Spetersvn_path_compose(const apr_array_header_t *components, 664251881Speter apr_pool_t *pool) 665251881Speter{ 666251881Speter apr_size_t *lengths = apr_palloc(pool, components->nelts*sizeof(*lengths)); 667251881Speter apr_size_t max_length = components->nelts; 668251881Speter char *path; 669251881Speter char *p; 670251881Speter int i; 671251881Speter 672251881Speter /* Get the length of each component so a total length can be 673251881Speter calculated. */ 674251881Speter for (i = 0; i < components->nelts; ++i) 675251881Speter { 676251881Speter apr_size_t l = strlen(APR_ARRAY_IDX(components, i, const char *)); 677251881Speter lengths[i] = l; 678251881Speter max_length += l; 679251881Speter } 680251881Speter 681251881Speter path = apr_palloc(pool, max_length + 1); 682251881Speter p = path; 683251881Speter 684251881Speter for (i = 0; i < components->nelts; ++i) 685251881Speter { 686251881Speter /* Append a '/' to the path. Handle the case with an absolute 687251881Speter path where a '/' appears in the first component. Only append 688251881Speter a '/' if the component is the second component that does not 689251881Speter follow a "/" first component; or it is the third or later 690251881Speter component. */ 691251881Speter if (i > 1 || 692251881Speter (i == 1 && strcmp("/", APR_ARRAY_IDX(components, 693251881Speter 0, 694251881Speter const char *)) != 0)) 695251881Speter { 696251881Speter *p++ = '/'; 697251881Speter } 698251881Speter 699251881Speter memcpy(p, APR_ARRAY_IDX(components, i, const char *), lengths[i]); 700251881Speter p += lengths[i]; 701251881Speter } 702251881Speter 703251881Speter *p = '\0'; 704251881Speter 705251881Speter return path; 706251881Speter} 707251881Speter 708251881Speter 709251881Spetersvn_boolean_t 710251881Spetersvn_path_is_single_path_component(const char *name) 711251881Speter{ 712251881Speter assert(is_canonical(name, strlen(name))); 713251881Speter 714251881Speter /* Can't be empty or `..' */ 715251881Speter if (SVN_PATH_IS_EMPTY(name) 716251881Speter || (name[0] == '.' && name[1] == '.' && name[2] == '\0')) 717251881Speter return FALSE; 718251881Speter 719251881Speter /* Slashes are bad, m'kay... */ 720251881Speter if (strchr(name, '/') != NULL) 721251881Speter return FALSE; 722251881Speter 723251881Speter /* It is valid. */ 724251881Speter return TRUE; 725251881Speter} 726251881Speter 727251881Speter 728251881Spetersvn_boolean_t 729251881Spetersvn_path_is_dotpath_present(const char *path) 730251881Speter{ 731251881Speter size_t len; 732251881Speter 733251881Speter /* The empty string does not have a dotpath */ 734251881Speter if (path[0] == '\0') 735251881Speter return FALSE; 736251881Speter 737251881Speter /* Handle "." or a leading "./" */ 738251881Speter if (path[0] == '.' && (path[1] == '\0' || path[1] == '/')) 739251881Speter return TRUE; 740251881Speter 741251881Speter /* Paths of length 1 (at this point) have no dotpath present. */ 742251881Speter if (path[1] == '\0') 743251881Speter return FALSE; 744251881Speter 745251881Speter /* If any segment is "/./", then a dotpath is present. */ 746251881Speter if (strstr(path, "/./") != NULL) 747251881Speter return TRUE; 748251881Speter 749251881Speter /* Does the path end in "/." ? */ 750251881Speter len = strlen(path); 751251881Speter return path[len - 2] == '/' && path[len - 1] == '.'; 752251881Speter} 753251881Speter 754251881Spetersvn_boolean_t 755251881Spetersvn_path_is_backpath_present(const char *path) 756251881Speter{ 757251881Speter size_t len; 758251881Speter 759251881Speter /* 0 and 1-length paths do not have a backpath */ 760251881Speter if (path[0] == '\0' || path[1] == '\0') 761251881Speter return FALSE; 762251881Speter 763251881Speter /* Handle ".." or a leading "../" */ 764251881Speter if (path[0] == '.' && path[1] == '.' && (path[2] == '\0' || path[2] == '/')) 765251881Speter return TRUE; 766251881Speter 767251881Speter /* Paths of length 2 (at this point) have no backpath present. */ 768251881Speter if (path[2] == '\0') 769251881Speter return FALSE; 770251881Speter 771251881Speter /* If any segment is "..", then a backpath is present. */ 772251881Speter if (strstr(path, "/../") != NULL) 773251881Speter return TRUE; 774251881Speter 775251881Speter /* Does the path end in "/.." ? */ 776251881Speter len = strlen(path); 777251881Speter return path[len - 3] == '/' && path[len - 2] == '.' && path[len - 1] == '.'; 778251881Speter} 779251881Speter 780251881Speter 781251881Speter/*** URI Stuff ***/ 782251881Speter 783251881Speter/* Examine PATH as a potential URI, and return a substring of PATH 784251881Speter that immediately follows the (scheme):// portion of the URI, or 785251881Speter NULL if PATH doesn't appear to be a valid URI. The returned value 786251881Speter is not alloced -- it shares memory with PATH. */ 787251881Speterstatic const char * 788251881Speterskip_uri_scheme(const char *path) 789251881Speter{ 790251881Speter apr_size_t j; 791251881Speter 792251881Speter /* A scheme is terminated by a : and cannot contain any /'s. */ 793251881Speter for (j = 0; path[j] && path[j] != ':'; ++j) 794251881Speter if (path[j] == '/') 795251881Speter return NULL; 796251881Speter 797251881Speter if (j > 0 && path[j] == ':' && path[j+1] == '/' && path[j+2] == '/') 798251881Speter return path + j + 3; 799251881Speter 800251881Speter return NULL; 801251881Speter} 802251881Speter 803251881Speter 804251881Spetersvn_boolean_t 805251881Spetersvn_path_is_url(const char *path) 806251881Speter{ 807251881Speter /* ### This function is reaaaaaaaaaaaaaally stupid right now. 808251881Speter We're just going to look for: 809251881Speter 810251881Speter (scheme)://(optional_stuff) 811251881Speter 812251881Speter Where (scheme) has no ':' or '/' characters. 813251881Speter 814251881Speter Someday it might be nice to have an actual URI parser here. 815251881Speter */ 816251881Speter return skip_uri_scheme(path) != NULL; 817251881Speter} 818251881Speter 819251881Speter 820251881Speter 821251881Speter/* Here is the BNF for path components in a URI. "pchar" is a 822251881Speter character in a path component. 823251881Speter 824251881Speter pchar = unreserved | escaped | 825251881Speter ":" | "@" | "&" | "=" | "+" | "$" | "," 826251881Speter unreserved = alphanum | mark 827251881Speter mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" 828251881Speter 829251881Speter Note that "escaped" doesn't really apply to what users can put in 830251881Speter their paths, so that really means the set of characters is: 831251881Speter 832251881Speter alphanum | mark | ":" | "@" | "&" | "=" | "+" | "$" | "," 833251881Speter*/ 834251881Speterconst char svn_uri__char_validity[256] = { 835251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 836251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 837251881Speter 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 838251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 839251881Speter 840251881Speter /* 64 */ 841251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 842251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 843251881Speter 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 844251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 845251881Speter 846251881Speter /* 128 */ 847251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 848251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 849251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 850251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 851251881Speter 852251881Speter /* 192 */ 853251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 854251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 855251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 856251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 857251881Speter}; 858251881Speter 859251881Speter 860251881Spetersvn_boolean_t 861251881Spetersvn_path_is_uri_safe(const char *path) 862251881Speter{ 863251881Speter apr_size_t i; 864251881Speter 865251881Speter /* Skip the URI scheme. */ 866251881Speter path = skip_uri_scheme(path); 867251881Speter 868251881Speter /* No scheme? Get outta here. */ 869251881Speter if (! path) 870251881Speter return FALSE; 871251881Speter 872251881Speter /* Skip to the first slash that's after the URI scheme. */ 873251881Speter path = strchr(path, '/'); 874251881Speter 875251881Speter /* If there's no first slash, then there's only a host portion; 876251881Speter therefore there couldn't be any uri-unsafe characters after the 877251881Speter host... so return true. */ 878251881Speter if (path == NULL) 879251881Speter return TRUE; 880251881Speter 881251881Speter for (i = 0; path[i]; i++) 882251881Speter { 883251881Speter /* Allow '%XX' (where each X is a hex digit) */ 884251881Speter if (path[i] == '%') 885251881Speter { 886251881Speter if (svn_ctype_isxdigit(path[i + 1]) && 887251881Speter svn_ctype_isxdigit(path[i + 2])) 888251881Speter { 889251881Speter i += 2; 890251881Speter continue; 891251881Speter } 892251881Speter return FALSE; 893251881Speter } 894251881Speter else if (! svn_uri__char_validity[((unsigned char)path[i])]) 895251881Speter { 896251881Speter return FALSE; 897251881Speter } 898251881Speter } 899251881Speter 900251881Speter return TRUE; 901251881Speter} 902251881Speter 903251881Speter 904251881Speter/* URI-encode each character c in PATH for which TABLE[c] is 0. 905251881Speter If no encoding was needed, return PATH, else return a new string allocated 906251881Speter in POOL. */ 907251881Speterstatic const char * 908251881Speteruri_escape(const char *path, const char table[], apr_pool_t *pool) 909251881Speter{ 910251881Speter svn_stringbuf_t *retstr; 911251881Speter apr_size_t i, copied = 0; 912251881Speter int c; 913251881Speter 914251881Speter retstr = svn_stringbuf_create_ensure(strlen(path), pool); 915251881Speter for (i = 0; path[i]; i++) 916251881Speter { 917251881Speter c = (unsigned char)path[i]; 918251881Speter if (table[c]) 919251881Speter continue; 920251881Speter 921251881Speter /* If we got here, we're looking at a character that isn't 922251881Speter supported by the (or at least, our) URI encoding scheme. We 923251881Speter need to escape this character. */ 924251881Speter 925251881Speter /* First things first, copy all the good stuff that we haven't 926251881Speter yet copied into our output buffer. */ 927251881Speter if (i - copied) 928251881Speter svn_stringbuf_appendbytes(retstr, path + copied, 929251881Speter i - copied); 930251881Speter 931251881Speter /* Now, write in our escaped character, consisting of the 932251881Speter '%' and two digits. We cast the C to unsigned char here because 933251881Speter the 'X' format character will be tempted to treat it as an unsigned 934251881Speter int...which causes problem when messing with 0x80-0xFF chars. 935251881Speter We also need space for a null as apr_snprintf will write one. */ 936251881Speter svn_stringbuf_ensure(retstr, retstr->len + 4); 937251881Speter apr_snprintf(retstr->data + retstr->len, 4, "%%%02X", (unsigned char)c); 938251881Speter retstr->len += 3; 939251881Speter 940251881Speter /* Finally, update our copy counter. */ 941251881Speter copied = i + 1; 942251881Speter } 943251881Speter 944251881Speter /* If we didn't encode anything, we don't need to duplicate the string. */ 945251881Speter if (retstr->len == 0) 946251881Speter return path; 947251881Speter 948251881Speter /* Anything left to copy? */ 949251881Speter if (i - copied) 950251881Speter svn_stringbuf_appendbytes(retstr, path + copied, i - copied); 951251881Speter 952251881Speter /* retstr is null-terminated either by apr_snprintf or the svn_stringbuf 953251881Speter functions. */ 954251881Speter 955251881Speter return retstr->data; 956251881Speter} 957251881Speter 958251881Speter 959251881Speterconst char * 960251881Spetersvn_path_uri_encode(const char *path, apr_pool_t *pool) 961251881Speter{ 962251881Speter const char *ret; 963251881Speter 964251881Speter ret = uri_escape(path, svn_uri__char_validity, pool); 965251881Speter 966251881Speter /* Our interface guarantees a copy. */ 967251881Speter if (ret == path) 968251881Speter return apr_pstrdup(pool, path); 969251881Speter else 970251881Speter return ret; 971251881Speter} 972251881Speter 973251881Speterstatic const char iri_escape_chars[256] = { 974251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 975251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 976251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 977251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 978251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 979251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 980251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 981251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 982251881Speter 983251881Speter /* 128 */ 984251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 985251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 986251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 987251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 988251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 989251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 990251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 991251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 992251881Speter}; 993251881Speter 994251881Speterconst char * 995251881Spetersvn_path_uri_from_iri(const char *iri, apr_pool_t *pool) 996251881Speter{ 997251881Speter return uri_escape(iri, iri_escape_chars, pool); 998251881Speter} 999251881Speter 1000251881Speterstatic const char uri_autoescape_chars[256] = { 1001251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1002251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1003251881Speter 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1004251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1005251881Speter 1006251881Speter /* 64 */ 1007251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1008251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1009251881Speter 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1010251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1011251881Speter 1012251881Speter /* 128 */ 1013251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1014251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1015251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1016251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1017251881Speter 1018251881Speter /* 192 */ 1019251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1020251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1021251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1022251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1023251881Speter}; 1024251881Speter 1025251881Speterconst char * 1026251881Spetersvn_path_uri_autoescape(const char *uri, apr_pool_t *pool) 1027251881Speter{ 1028251881Speter return uri_escape(uri, uri_autoescape_chars, pool); 1029251881Speter} 1030251881Speter 1031251881Speterconst char * 1032251881Spetersvn_path_uri_decode(const char *path, apr_pool_t *pool) 1033251881Speter{ 1034251881Speter svn_stringbuf_t *retstr; 1035251881Speter apr_size_t i; 1036251881Speter svn_boolean_t query_start = FALSE; 1037251881Speter 1038251881Speter /* avoid repeated realloc */ 1039251881Speter retstr = svn_stringbuf_create_ensure(strlen(path) + 1, pool); 1040251881Speter 1041251881Speter retstr->len = 0; 1042251881Speter for (i = 0; path[i]; i++) 1043251881Speter { 1044251881Speter char c = path[i]; 1045251881Speter 1046251881Speter if (c == '?') 1047251881Speter { 1048251881Speter /* Mark the start of the query string, if it exists. */ 1049251881Speter query_start = TRUE; 1050251881Speter } 1051251881Speter else if (c == '+' && query_start) 1052251881Speter { 1053251881Speter /* Only do this if we are into the query string. 1054251881Speter * RFC 2396, section 3.3 */ 1055251881Speter c = ' '; 1056251881Speter } 1057251881Speter else if (c == '%' && svn_ctype_isxdigit(path[i + 1]) 1058251881Speter && svn_ctype_isxdigit(path[i+2])) 1059251881Speter { 1060251881Speter char digitz[3]; 1061251881Speter digitz[0] = path[++i]; 1062251881Speter digitz[1] = path[++i]; 1063251881Speter digitz[2] = '\0'; 1064251881Speter c = (char)(strtol(digitz, NULL, 16)); 1065251881Speter } 1066251881Speter 1067251881Speter retstr->data[retstr->len++] = c; 1068251881Speter } 1069251881Speter 1070251881Speter /* Null-terminate this bad-boy. */ 1071251881Speter retstr->data[retstr->len] = 0; 1072251881Speter 1073251881Speter return retstr->data; 1074251881Speter} 1075251881Speter 1076251881Speter 1077251881Speterconst char * 1078251881Spetersvn_path_url_add_component2(const char *url, 1079251881Speter const char *component, 1080251881Speter apr_pool_t *pool) 1081251881Speter{ 1082251881Speter /* = svn_path_uri_encode() but without always copying */ 1083251881Speter component = uri_escape(component, svn_uri__char_validity, pool); 1084251881Speter 1085251881Speter return svn_path_join_internal(url, component, pool); 1086251881Speter} 1087251881Speter 1088251881Spetersvn_error_t * 1089251881Spetersvn_path_get_absolute(const char **pabsolute, 1090251881Speter const char *relative, 1091251881Speter apr_pool_t *pool) 1092251881Speter{ 1093251881Speter if (svn_path_is_url(relative)) 1094251881Speter { 1095251881Speter *pabsolute = apr_pstrdup(pool, relative); 1096251881Speter return SVN_NO_ERROR; 1097251881Speter } 1098251881Speter 1099251881Speter return svn_dirent_get_absolute(pabsolute, relative, pool); 1100251881Speter} 1101251881Speter 1102251881Speter 1103251881Speter#if !defined(WIN32) && !defined(DARWIN) 1104251881Speter/** Get APR's internal path encoding. */ 1105251881Speterstatic svn_error_t * 1106251881Speterget_path_encoding(svn_boolean_t *path_is_utf8, apr_pool_t *pool) 1107251881Speter{ 1108251881Speter apr_status_t apr_err; 1109251881Speter int encoding_style; 1110251881Speter 1111251881Speter apr_err = apr_filepath_encoding(&encoding_style, pool); 1112251881Speter if (apr_err) 1113251881Speter return svn_error_wrap_apr(apr_err, 1114251881Speter _("Can't determine the native path encoding")); 1115251881Speter 1116251881Speter /* ### What to do about APR_FILEPATH_ENCODING_UNKNOWN? 1117251881Speter Well, for now we'll just punt to the svn_utf_ functions; 1118251881Speter those will at least do the ASCII-subset check. */ 1119251881Speter *path_is_utf8 = (encoding_style == APR_FILEPATH_ENCODING_UTF8); 1120251881Speter return SVN_NO_ERROR; 1121251881Speter} 1122251881Speter#endif 1123251881Speter 1124251881Speter 1125251881Spetersvn_error_t * 1126251881Spetersvn_path_cstring_from_utf8(const char **path_apr, 1127251881Speter const char *path_utf8, 1128251881Speter apr_pool_t *pool) 1129251881Speter{ 1130251881Speter#if !defined(WIN32) && !defined(DARWIN) 1131251881Speter svn_boolean_t path_is_utf8; 1132251881Speter SVN_ERR(get_path_encoding(&path_is_utf8, pool)); 1133251881Speter if (path_is_utf8) 1134251881Speter#endif 1135251881Speter { 1136251881Speter *path_apr = apr_pstrdup(pool, path_utf8); 1137251881Speter return SVN_NO_ERROR; 1138251881Speter } 1139251881Speter#if !defined(WIN32) && !defined(DARWIN) 1140251881Speter else 1141251881Speter return svn_utf_cstring_from_utf8(path_apr, path_utf8, pool); 1142251881Speter#endif 1143251881Speter} 1144251881Speter 1145251881Speter 1146251881Spetersvn_error_t * 1147251881Spetersvn_path_cstring_to_utf8(const char **path_utf8, 1148251881Speter const char *path_apr, 1149251881Speter apr_pool_t *pool) 1150251881Speter{ 1151251881Speter#if !defined(WIN32) && !defined(DARWIN) 1152251881Speter svn_boolean_t path_is_utf8; 1153251881Speter SVN_ERR(get_path_encoding(&path_is_utf8, pool)); 1154251881Speter if (path_is_utf8) 1155251881Speter#endif 1156251881Speter { 1157251881Speter *path_utf8 = apr_pstrdup(pool, path_apr); 1158251881Speter return SVN_NO_ERROR; 1159251881Speter } 1160251881Speter#if !defined(WIN32) && !defined(DARWIN) 1161251881Speter else 1162251881Speter return svn_utf_cstring_to_utf8(path_utf8, path_apr, pool); 1163251881Speter#endif 1164251881Speter} 1165251881Speter 1166251881Speter 1167251881Speter/* Return a copy of PATH, allocated from POOL, for which control 1168251881Speter characters have been escaped using the form \NNN (where NNN is the 1169251881Speter octal representation of the byte's ordinal value). */ 1170251881Speterconst char * 1171251881Spetersvn_path_illegal_path_escape(const char *path, apr_pool_t *pool) 1172251881Speter{ 1173251881Speter svn_stringbuf_t *retstr; 1174251881Speter apr_size_t i, copied = 0; 1175251881Speter int c; 1176251881Speter 1177251881Speter /* At least one control character: 1178251881Speter strlen - 1 (control) + \ + N + N + N + null . */ 1179251881Speter retstr = svn_stringbuf_create_ensure(strlen(path) + 4, pool); 1180251881Speter for (i = 0; path[i]; i++) 1181251881Speter { 1182251881Speter c = (unsigned char)path[i]; 1183251881Speter if (! svn_ctype_iscntrl(c)) 1184251881Speter continue; 1185251881Speter 1186251881Speter /* If we got here, we're looking at a character that isn't 1187251881Speter supported by the (or at least, our) URI encoding scheme. We 1188251881Speter need to escape this character. */ 1189251881Speter 1190251881Speter /* First things first, copy all the good stuff that we haven't 1191251881Speter yet copied into our output buffer. */ 1192251881Speter if (i - copied) 1193251881Speter svn_stringbuf_appendbytes(retstr, path + copied, 1194251881Speter i - copied); 1195251881Speter 1196251881Speter /* Make sure buffer is big enough for '\' 'N' 'N' 'N' (and NUL) */ 1197251881Speter svn_stringbuf_ensure(retstr, retstr->len + 5); 1198251881Speter /*### The backslash separator doesn't work too great with Windows, 1199251881Speter but it's what we'll use for consistency with invalid utf8 1200251881Speter formatting (until someone has a better idea) */ 1201251881Speter apr_snprintf(retstr->data + retstr->len, 5, "\\%03o", (unsigned char)c); 1202251881Speter retstr->len += 4; 1203251881Speter 1204251881Speter /* Finally, update our copy counter. */ 1205251881Speter copied = i + 1; 1206251881Speter } 1207251881Speter 1208251881Speter /* If we didn't encode anything, we don't need to duplicate the string. */ 1209251881Speter if (retstr->len == 0) 1210251881Speter return path; 1211251881Speter 1212251881Speter /* Anything left to copy? */ 1213251881Speter if (i - copied) 1214251881Speter svn_stringbuf_appendbytes(retstr, path + copied, i - copied); 1215251881Speter 1216251881Speter /* retstr is null-terminated either by apr_snprintf or the svn_stringbuf 1217251881Speter functions. */ 1218251881Speter 1219251881Speter return retstr->data; 1220251881Speter} 1221251881Speter 1222251881Spetersvn_error_t * 1223251881Spetersvn_path_check_valid(const char *path, apr_pool_t *pool) 1224251881Speter{ 1225251881Speter const char *c; 1226251881Speter 1227251881Speter for (c = path; *c; c++) 1228251881Speter { 1229251881Speter if (svn_ctype_iscntrl(*c)) 1230251881Speter { 1231251881Speter return svn_error_createf 1232251881Speter (SVN_ERR_FS_PATH_SYNTAX, NULL, 1233251881Speter _("Invalid control character '0x%02x' in path '%s'"), 1234251881Speter (unsigned char)*c, 1235251881Speter svn_path_illegal_path_escape(svn_dirent_local_style(path, pool), 1236251881Speter pool)); 1237251881Speter } 1238251881Speter } 1239251881Speter 1240251881Speter return SVN_NO_ERROR; 1241251881Speter} 1242251881Speter 1243251881Spetervoid 1244251881Spetersvn_path_splitext(const char **path_root, 1245251881Speter const char **path_ext, 1246251881Speter const char *path, 1247251881Speter apr_pool_t *pool) 1248251881Speter{ 1249251881Speter const char *last_dot, *last_slash; 1250251881Speter 1251251881Speter /* Easy out -- why do all the work when there's no way to report it? */ 1252251881Speter if (! (path_root || path_ext)) 1253251881Speter return; 1254251881Speter 1255251881Speter /* Do we even have a period in this thing? And if so, is there 1256251881Speter anything after it? We look for the "rightmost" period in the 1257251881Speter string. */ 1258251881Speter last_dot = strrchr(path, '.'); 1259251881Speter if (last_dot && (last_dot + 1 != '\0')) 1260251881Speter { 1261251881Speter /* If we have a period, we need to make sure it occurs in the 1262251881Speter final path component -- that there's no path separator 1263251881Speter between the last period and the end of the PATH -- otherwise, 1264251881Speter it doesn't count. Also, we want to make sure that our period 1265251881Speter isn't the first character of the last component. */ 1266251881Speter last_slash = strrchr(path, '/'); 1267251881Speter if ((last_slash && (last_dot > (last_slash + 1))) 1268251881Speter || ((! last_slash) && (last_dot > path))) 1269251881Speter { 1270251881Speter if (path_root) 1271251881Speter *path_root = apr_pstrmemdup(pool, path, 1272251881Speter (last_dot - path + 1) * sizeof(*path)); 1273251881Speter if (path_ext) 1274251881Speter *path_ext = apr_pstrdup(pool, last_dot + 1); 1275251881Speter return; 1276251881Speter } 1277251881Speter } 1278251881Speter /* If we get here, we never found a suitable separator character, so 1279251881Speter there's no split. */ 1280251881Speter if (path_root) 1281251881Speter *path_root = apr_pstrdup(pool, path); 1282251881Speter if (path_ext) 1283251881Speter *path_ext = ""; 1284251881Speter} 1285251881Speter 1286251881Speter 1287251881Speter/* Repository relative URLs (^/). */ 1288251881Speter 1289251881Spetersvn_boolean_t 1290251881Spetersvn_path_is_repos_relative_url(const char *path) 1291251881Speter{ 1292251881Speter return (0 == strncmp("^/", path, 2)); 1293251881Speter} 1294251881Speter 1295251881Spetersvn_error_t * 1296251881Spetersvn_path_resolve_repos_relative_url(const char **absolute_url, 1297251881Speter const char *relative_url, 1298251881Speter const char *repos_root_url, 1299251881Speter apr_pool_t *pool) 1300251881Speter{ 1301251881Speter if (! svn_path_is_repos_relative_url(relative_url)) 1302251881Speter return svn_error_createf(SVN_ERR_BAD_URL, NULL, 1303251881Speter _("Improper relative URL '%s'"), 1304251881Speter relative_url); 1305251881Speter 1306251881Speter /* No assumptions are made about the canonicalization of the inut 1307251881Speter * arguments, it is presumed that the output will be canonicalized after 1308251881Speter * this function, which will remove any duplicate path separator. 1309251881Speter */ 1310251881Speter *absolute_url = apr_pstrcat(pool, repos_root_url, relative_url + 1, 1311251881Speter (char *)NULL); 1312251881Speter 1313251881Speter return SVN_NO_ERROR; 1314251881Speter} 1315251881Speter 1316