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; 509362181Sdim 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; 913362181Sdim apr_size_t len; 914362181Sdim const char *p; 915251881Speter 916362181Sdim /* To terminate our scanning loop, table[NUL] must report "invalid". */ 917362181Sdim assert(table[0] == 0); 918362181Sdim 919362181Sdim /* Quick check: Does any character need escaping? */ 920362181Sdim for (p = path; table[(unsigned char)*p]; ++p) 921362181Sdim {} 922362181Sdim 923362181Sdim /* No char to escape before EOS? */ 924362181Sdim if (*p == '\0') 925362181Sdim return path; 926362181Sdim 927362181Sdim /* We need to escape at least one character. */ 928362181Sdim len = strlen(p) + (p - path); 929362181Sdim retstr = svn_stringbuf_create_ensure(len, pool); 930362181Sdim for (i = p - path; i < len; i++) 931251881Speter { 932251881Speter c = (unsigned char)path[i]; 933251881Speter if (table[c]) 934251881Speter continue; 935251881Speter 936251881Speter /* If we got here, we're looking at a character that isn't 937251881Speter supported by the (or at least, our) URI encoding scheme. We 938251881Speter need to escape this character. */ 939251881Speter 940251881Speter /* First things first, copy all the good stuff that we haven't 941251881Speter yet copied into our output buffer. */ 942251881Speter if (i - copied) 943251881Speter svn_stringbuf_appendbytes(retstr, path + copied, 944251881Speter i - copied); 945251881Speter 946251881Speter /* Now, write in our escaped character, consisting of the 947251881Speter '%' and two digits. We cast the C to unsigned char here because 948251881Speter the 'X' format character will be tempted to treat it as an unsigned 949251881Speter int...which causes problem when messing with 0x80-0xFF chars. 950251881Speter We also need space for a null as apr_snprintf will write one. */ 951251881Speter svn_stringbuf_ensure(retstr, retstr->len + 4); 952251881Speter apr_snprintf(retstr->data + retstr->len, 4, "%%%02X", (unsigned char)c); 953251881Speter retstr->len += 3; 954251881Speter 955251881Speter /* Finally, update our copy counter. */ 956251881Speter copied = i + 1; 957251881Speter } 958251881Speter 959251881Speter /* Anything left to copy? */ 960251881Speter if (i - copied) 961251881Speter svn_stringbuf_appendbytes(retstr, path + copied, i - copied); 962251881Speter 963251881Speter /* retstr is null-terminated either by apr_snprintf or the svn_stringbuf 964251881Speter functions. */ 965251881Speter 966251881Speter return retstr->data; 967251881Speter} 968251881Speter 969251881Speter 970251881Speterconst char * 971251881Spetersvn_path_uri_encode(const char *path, apr_pool_t *pool) 972251881Speter{ 973251881Speter const char *ret; 974251881Speter 975251881Speter ret = uri_escape(path, svn_uri__char_validity, pool); 976251881Speter 977251881Speter /* Our interface guarantees a copy. */ 978251881Speter if (ret == path) 979251881Speter return apr_pstrdup(pool, path); 980251881Speter else 981251881Speter return ret; 982251881Speter} 983251881Speter 984251881Speterstatic const char iri_escape_chars[256] = { 985362181Sdim 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 986251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 987251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 988251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 989251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 990251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 991251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 992251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 993251881Speter 994251881Speter /* 128 */ 995251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 996251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 997251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 998251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 999251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1000251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1001251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1002251881Speter 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 1003251881Speter}; 1004251881Speter 1005251881Speterconst char * 1006251881Spetersvn_path_uri_from_iri(const char *iri, apr_pool_t *pool) 1007251881Speter{ 1008251881Speter return uri_escape(iri, iri_escape_chars, pool); 1009251881Speter} 1010251881Speter 1011251881Speterstatic const char uri_autoescape_chars[256] = { 1012362181Sdim 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1013251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1014251881Speter 0, 1, 0, 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, 0, 1, 0, 1, 1016251881Speter 1017251881Speter /* 64 */ 1018251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1019251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1020251881Speter 0, 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, 0, 0, 0, 1, 1, 1022251881Speter 1023251881Speter /* 128 */ 1024251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1025251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1026251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1027251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1028251881Speter 1029251881Speter /* 192 */ 1030251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1031251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1032251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1033251881Speter 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1034251881Speter}; 1035251881Speter 1036251881Speterconst char * 1037251881Spetersvn_path_uri_autoescape(const char *uri, apr_pool_t *pool) 1038251881Speter{ 1039251881Speter return uri_escape(uri, uri_autoescape_chars, pool); 1040251881Speter} 1041251881Speter 1042251881Speterconst char * 1043251881Spetersvn_path_uri_decode(const char *path, apr_pool_t *pool) 1044251881Speter{ 1045251881Speter svn_stringbuf_t *retstr; 1046251881Speter apr_size_t i; 1047251881Speter svn_boolean_t query_start = FALSE; 1048251881Speter 1049251881Speter /* avoid repeated realloc */ 1050251881Speter retstr = svn_stringbuf_create_ensure(strlen(path) + 1, pool); 1051251881Speter 1052251881Speter retstr->len = 0; 1053251881Speter for (i = 0; path[i]; i++) 1054251881Speter { 1055251881Speter char c = path[i]; 1056251881Speter 1057251881Speter if (c == '?') 1058251881Speter { 1059251881Speter /* Mark the start of the query string, if it exists. */ 1060251881Speter query_start = TRUE; 1061251881Speter } 1062251881Speter else if (c == '+' && query_start) 1063251881Speter { 1064251881Speter /* Only do this if we are into the query string. 1065251881Speter * RFC 2396, section 3.3 */ 1066251881Speter c = ' '; 1067251881Speter } 1068251881Speter else if (c == '%' && svn_ctype_isxdigit(path[i + 1]) 1069251881Speter && svn_ctype_isxdigit(path[i+2])) 1070251881Speter { 1071251881Speter char digitz[3]; 1072251881Speter digitz[0] = path[++i]; 1073251881Speter digitz[1] = path[++i]; 1074251881Speter digitz[2] = '\0'; 1075251881Speter c = (char)(strtol(digitz, NULL, 16)); 1076251881Speter } 1077251881Speter 1078251881Speter retstr->data[retstr->len++] = c; 1079251881Speter } 1080251881Speter 1081251881Speter /* Null-terminate this bad-boy. */ 1082251881Speter retstr->data[retstr->len] = 0; 1083251881Speter 1084251881Speter return retstr->data; 1085251881Speter} 1086251881Speter 1087251881Speter 1088251881Speterconst char * 1089251881Spetersvn_path_url_add_component2(const char *url, 1090251881Speter const char *component, 1091251881Speter apr_pool_t *pool) 1092251881Speter{ 1093251881Speter /* = svn_path_uri_encode() but without always copying */ 1094251881Speter component = uri_escape(component, svn_uri__char_validity, pool); 1095251881Speter 1096251881Speter return svn_path_join_internal(url, component, pool); 1097251881Speter} 1098251881Speter 1099251881Spetersvn_error_t * 1100251881Spetersvn_path_get_absolute(const char **pabsolute, 1101251881Speter const char *relative, 1102251881Speter apr_pool_t *pool) 1103251881Speter{ 1104251881Speter if (svn_path_is_url(relative)) 1105251881Speter { 1106251881Speter *pabsolute = apr_pstrdup(pool, relative); 1107251881Speter return SVN_NO_ERROR; 1108251881Speter } 1109251881Speter 1110251881Speter return svn_dirent_get_absolute(pabsolute, relative, pool); 1111251881Speter} 1112251881Speter 1113251881Speter 1114251881Speter#if !defined(WIN32) && !defined(DARWIN) 1115251881Speter/** Get APR's internal path encoding. */ 1116251881Speterstatic svn_error_t * 1117251881Speterget_path_encoding(svn_boolean_t *path_is_utf8, apr_pool_t *pool) 1118251881Speter{ 1119251881Speter apr_status_t apr_err; 1120251881Speter int encoding_style; 1121251881Speter 1122251881Speter apr_err = apr_filepath_encoding(&encoding_style, pool); 1123251881Speter if (apr_err) 1124251881Speter return svn_error_wrap_apr(apr_err, 1125251881Speter _("Can't determine the native path encoding")); 1126251881Speter 1127251881Speter /* ### What to do about APR_FILEPATH_ENCODING_UNKNOWN? 1128251881Speter Well, for now we'll just punt to the svn_utf_ functions; 1129251881Speter those will at least do the ASCII-subset check. */ 1130251881Speter *path_is_utf8 = (encoding_style == APR_FILEPATH_ENCODING_UTF8); 1131251881Speter return SVN_NO_ERROR; 1132251881Speter} 1133251881Speter#endif 1134251881Speter 1135251881Speter 1136251881Spetersvn_error_t * 1137251881Spetersvn_path_cstring_from_utf8(const char **path_apr, 1138251881Speter const char *path_utf8, 1139251881Speter apr_pool_t *pool) 1140251881Speter{ 1141251881Speter#if !defined(WIN32) && !defined(DARWIN) 1142251881Speter svn_boolean_t path_is_utf8; 1143251881Speter SVN_ERR(get_path_encoding(&path_is_utf8, pool)); 1144251881Speter if (path_is_utf8) 1145251881Speter#endif 1146251881Speter { 1147251881Speter *path_apr = apr_pstrdup(pool, path_utf8); 1148251881Speter return SVN_NO_ERROR; 1149251881Speter } 1150251881Speter#if !defined(WIN32) && !defined(DARWIN) 1151251881Speter else 1152251881Speter return svn_utf_cstring_from_utf8(path_apr, path_utf8, pool); 1153251881Speter#endif 1154251881Speter} 1155251881Speter 1156251881Speter 1157251881Spetersvn_error_t * 1158251881Spetersvn_path_cstring_to_utf8(const char **path_utf8, 1159251881Speter const char *path_apr, 1160251881Speter apr_pool_t *pool) 1161251881Speter{ 1162251881Speter#if !defined(WIN32) && !defined(DARWIN) 1163251881Speter svn_boolean_t path_is_utf8; 1164251881Speter SVN_ERR(get_path_encoding(&path_is_utf8, pool)); 1165251881Speter if (path_is_utf8) 1166251881Speter#endif 1167251881Speter { 1168251881Speter *path_utf8 = apr_pstrdup(pool, path_apr); 1169251881Speter return SVN_NO_ERROR; 1170251881Speter } 1171251881Speter#if !defined(WIN32) && !defined(DARWIN) 1172251881Speter else 1173251881Speter return svn_utf_cstring_to_utf8(path_utf8, path_apr, pool); 1174251881Speter#endif 1175251881Speter} 1176251881Speter 1177251881Speter 1178251881Speterconst char * 1179251881Spetersvn_path_illegal_path_escape(const char *path, apr_pool_t *pool) 1180251881Speter{ 1181251881Speter svn_stringbuf_t *retstr; 1182251881Speter apr_size_t i, copied = 0; 1183251881Speter int c; 1184251881Speter 1185251881Speter /* At least one control character: 1186251881Speter strlen - 1 (control) + \ + N + N + N + null . */ 1187251881Speter retstr = svn_stringbuf_create_ensure(strlen(path) + 4, pool); 1188251881Speter for (i = 0; path[i]; i++) 1189251881Speter { 1190251881Speter c = (unsigned char)path[i]; 1191251881Speter if (! svn_ctype_iscntrl(c)) 1192251881Speter continue; 1193251881Speter 1194251881Speter /* If we got here, we're looking at a character that isn't 1195251881Speter supported by the (or at least, our) URI encoding scheme. We 1196251881Speter need to escape this character. */ 1197251881Speter 1198251881Speter /* First things first, copy all the good stuff that we haven't 1199251881Speter yet copied into our output buffer. */ 1200251881Speter if (i - copied) 1201251881Speter svn_stringbuf_appendbytes(retstr, path + copied, 1202251881Speter i - copied); 1203251881Speter 1204251881Speter /* Make sure buffer is big enough for '\' 'N' 'N' 'N' (and NUL) */ 1205251881Speter svn_stringbuf_ensure(retstr, retstr->len + 5); 1206251881Speter /*### The backslash separator doesn't work too great with Windows, 1207251881Speter but it's what we'll use for consistency with invalid utf8 1208251881Speter formatting (until someone has a better idea) */ 1209251881Speter apr_snprintf(retstr->data + retstr->len, 5, "\\%03o", (unsigned char)c); 1210251881Speter retstr->len += 4; 1211251881Speter 1212251881Speter /* Finally, update our copy counter. */ 1213251881Speter copied = i + 1; 1214251881Speter } 1215251881Speter 1216251881Speter /* If we didn't encode anything, we don't need to duplicate the string. */ 1217251881Speter if (retstr->len == 0) 1218251881Speter return path; 1219251881Speter 1220251881Speter /* Anything left to copy? */ 1221251881Speter if (i - copied) 1222251881Speter svn_stringbuf_appendbytes(retstr, path + copied, i - copied); 1223251881Speter 1224251881Speter /* retstr is null-terminated either by apr_snprintf or the svn_stringbuf 1225251881Speter functions. */ 1226251881Speter 1227251881Speter return retstr->data; 1228251881Speter} 1229251881Speter 1230251881Spetersvn_error_t * 1231251881Spetersvn_path_check_valid(const char *path, apr_pool_t *pool) 1232251881Speter{ 1233251881Speter const char *c; 1234251881Speter 1235251881Speter for (c = path; *c; c++) 1236251881Speter { 1237251881Speter if (svn_ctype_iscntrl(*c)) 1238251881Speter { 1239289180Speter return svn_error_createf(SVN_ERR_FS_PATH_SYNTAX, NULL, 1240251881Speter _("Invalid control character '0x%02x' in path '%s'"), 1241251881Speter (unsigned char)*c, 1242251881Speter svn_path_illegal_path_escape(svn_dirent_local_style(path, pool), 1243251881Speter pool)); 1244251881Speter } 1245251881Speter } 1246251881Speter 1247251881Speter return SVN_NO_ERROR; 1248251881Speter} 1249251881Speter 1250251881Spetervoid 1251251881Spetersvn_path_splitext(const char **path_root, 1252251881Speter const char **path_ext, 1253251881Speter const char *path, 1254251881Speter apr_pool_t *pool) 1255251881Speter{ 1256251881Speter const char *last_dot, *last_slash; 1257251881Speter 1258251881Speter /* Easy out -- why do all the work when there's no way to report it? */ 1259251881Speter if (! (path_root || path_ext)) 1260251881Speter return; 1261251881Speter 1262251881Speter /* Do we even have a period in this thing? And if so, is there 1263251881Speter anything after it? We look for the "rightmost" period in the 1264251881Speter string. */ 1265251881Speter last_dot = strrchr(path, '.'); 1266289180Speter if (last_dot && (*(last_dot + 1) != '\0')) 1267251881Speter { 1268251881Speter /* If we have a period, we need to make sure it occurs in the 1269251881Speter final path component -- that there's no path separator 1270251881Speter between the last period and the end of the PATH -- otherwise, 1271251881Speter it doesn't count. Also, we want to make sure that our period 1272251881Speter isn't the first character of the last component. */ 1273251881Speter last_slash = strrchr(path, '/'); 1274251881Speter if ((last_slash && (last_dot > (last_slash + 1))) 1275251881Speter || ((! last_slash) && (last_dot > path))) 1276251881Speter { 1277251881Speter if (path_root) 1278251881Speter *path_root = apr_pstrmemdup(pool, path, 1279251881Speter (last_dot - path + 1) * sizeof(*path)); 1280251881Speter if (path_ext) 1281251881Speter *path_ext = apr_pstrdup(pool, last_dot + 1); 1282251881Speter return; 1283251881Speter } 1284251881Speter } 1285251881Speter /* If we get here, we never found a suitable separator character, so 1286251881Speter there's no split. */ 1287251881Speter if (path_root) 1288251881Speter *path_root = apr_pstrdup(pool, path); 1289251881Speter if (path_ext) 1290251881Speter *path_ext = ""; 1291251881Speter} 1292251881Speter 1293251881Speter 1294251881Speter/* Repository relative URLs (^/). */ 1295251881Speter 1296251881Spetersvn_boolean_t 1297251881Spetersvn_path_is_repos_relative_url(const char *path) 1298251881Speter{ 1299251881Speter return (0 == strncmp("^/", path, 2)); 1300251881Speter} 1301251881Speter 1302251881Spetersvn_error_t * 1303251881Spetersvn_path_resolve_repos_relative_url(const char **absolute_url, 1304251881Speter const char *relative_url, 1305251881Speter const char *repos_root_url, 1306251881Speter apr_pool_t *pool) 1307251881Speter{ 1308251881Speter if (! svn_path_is_repos_relative_url(relative_url)) 1309251881Speter return svn_error_createf(SVN_ERR_BAD_URL, NULL, 1310251881Speter _("Improper relative URL '%s'"), 1311251881Speter relative_url); 1312251881Speter 1313289180Speter /* No assumptions are made about the canonicalization of the input 1314251881Speter * arguments, it is presumed that the output will be canonicalized after 1315251881Speter * this function, which will remove any duplicate path separator. 1316251881Speter */ 1317251881Speter *absolute_url = apr_pstrcat(pool, repos_root_url, relative_url + 1, 1318289180Speter SVN_VA_NULL); 1319251881Speter 1320251881Speter return SVN_NO_ERROR; 1321251881Speter} 1322251881Speter 1323