PLearn 0.1
|
00001 // -*- C++ -*- 00002 00003 // PPath.cc 00004 // 00005 // Copyright (C) 2005 Pascal Vincent 00006 // 00007 // Redistribution and use in source and binary forms, with or without 00008 // modification, are permitted provided that the following conditions are met: 00009 // 00010 // 1. Redistributions of source code must retain the above copyright 00011 // notice, this list of conditions and the following disclaimer. 00012 // 00013 // 2. Redistributions in binary form must reproduce the above copyright 00014 // notice, this list of conditions and the following disclaimer in the 00015 // documentation and/or other materials provided with the distribution. 00016 // 00017 // 3. The name of the authors may not be used to endorse or promote 00018 // products derived from this software without specific prior written 00019 // permission. 00020 // 00021 // THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 00022 // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 00023 // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 00024 // NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 00025 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 00026 // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 00027 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 00028 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 00029 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 00030 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 00031 // 00032 // This file is part of the PLearn library. For more information on the PLearn 00033 // library, go to the PLearn Web site at www.plearn.org 00034 00035 /* ******************************************************* 00036 * $Id: PPath.cc 9774 2008-12-11 21:06:26Z nouiz $ 00037 ******************************************************* */ 00038 00039 // Authors: Christian Dorion 00040 00043 // #define PL_LOG_MODULE_NAME "PPath" 00044 00045 #include <ctype.h> 00046 #include <nspr/prenv.h> 00047 00048 #include "PPath.h" 00049 #include "PStream.h" 00050 #include "openFile.h" 00051 #include "pl_log.h" 00052 #include "fileutils.h" 00053 #include <plearn/base/stringutils.h> 00054 00056 // DOS SETTINGS 00057 #if defined(WIN32) && !defined(__CYGWIN__) 00058 00059 #include <direct.h> 00060 #define SYS_GETCWD _getcwd 00061 00063 // POSIX SETTINGS 00064 #else 00065 00066 #include <unistd.h> 00067 #define SYS_GETCWD ::getcwd 00068 00069 #endif 00070 00071 #define PPATH_SLASH '/' // The canonical slash. 00072 00073 namespace PLearn { 00074 using namespace std; 00075 00077 // Stringutils.h ////////////////////////////// 00078 bool startsWith(const string& str, const char& c) 00079 { 00080 if (str.empty()) 00081 return false; 00082 return str[0] == c; 00083 } 00084 00085 bool endsWith (const string& str, const char& c) 00086 { 00087 if (str.empty()) 00088 return false; 00089 return str[str.length()-1] == c; 00090 } 00091 00092 bool startsWith(const string& str, const string& s) 00093 { 00094 if ( s.length() > str.length() ) 00095 return false; 00096 return str.substr(0, s.length()) == s; 00097 } 00098 00099 bool endsWith (const string& str, const string& s) 00100 { 00101 if ( s.length() > str.length() ) 00102 return false; 00103 return str.substr(str.length()-s.length()) == s; 00104 } 00106 00107 00109 // DOS SETTINGS 00110 #if defined(WIN32) 00111 string PPath::forbidden_chars() 00112 { 00113 return string(); 00114 } 00115 00116 const string& PPath::_slash() 00117 { 00118 static string s = "\\"; 00119 return s; 00120 } 00121 00122 char PPath::_slash_char() { return '\\'; } 00123 00125 // POSIX SETTINGS 00126 #else 00127 // Even if the posix standard allows backslashes in file paths, 00128 // PLearn users should never use those since PLearn aims at full and 00129 // easy portability. 00130 // 00131 // Other chars may be forbidden soon. 00132 string PPath::forbidden_chars() 00133 { 00134 return "\\"; 00135 } 00136 00137 const string& PPath::_slash() 00138 { 00139 static string s = "/"; 00140 return s; 00141 } 00142 00143 char PPath::_slash_char() { return '/'; } 00144 00145 #endif 00146 00147 00149 // In the scope of the PLearn namespace 00150 PStream& operator<<(PStream& out, const PPath& path) 00151 { 00152 switch (out.outmode) { 00153 case PStream::raw_ascii: 00154 case PStream::pretty_ascii: 00155 { 00156 out << path.c_str(); 00157 break; 00158 } 00159 case PStream::plearn_ascii: 00160 case PStream::plearn_binary: 00161 { 00162 out << path.canonical(); 00163 break; 00164 } 00165 default: 00166 PLERROR("This PStream mode is not supported for PPath"); 00167 } 00168 return out; 00169 } 00170 00171 PStream& operator>>(PStream& in, PPath& path) 00172 { 00173 string spath; 00174 in >> spath; 00175 switch (in.inmode) { 00176 case PStream::raw_ascii: 00177 case PStream::pretty_ascii: 00178 case PStream::plearn_ascii: 00179 case PStream::plearn_binary: 00180 { 00181 path = PPath(spath); 00182 break; 00183 } 00184 default: 00185 PLERROR("In operator>> - This PStream mode is not supported for PPath"); 00186 } 00187 return in; 00188 } 00189 00191 // Static PPath methods 00192 00194 // home // 00196 PPath PPath::home() 00197 { 00198 // Supply a default value so PLearn does not crash. 00199 // when $HOME isn't defined. 00200 #ifdef WIN32 00201 #define PL_DEFAULT_HOME PPath("C:\\") 00202 #else 00203 #define PL_DEFAULT_HOME PPath("/") 00204 #endif 00205 return PPath::getenv("HOME", PL_DEFAULT_HOME); 00206 } 00207 00209 // getcwd // 00211 PPath PPath::getcwd() 00212 { 00213 // TODO Use a NSPR function when there is one: 00214 // https://bugzilla.mozilla.org/show_bug.cgi?id=280953 00215 char buf[2000]; 00216 if (!SYS_GETCWD(buf, 2000)) 00217 // Error while reading the current directory. One should probably use a 00218 // larger buffer, but it is even easier to crash. 00219 PLERROR("In PPath::getcwd - Could not obtain the current working " 00220 "directory, a larger buffer may be necessary"); 00221 return PPath(buf); 00222 } 00223 00225 // getenv // 00227 PPath PPath::getenv(const string& var, const PPath& default_) 00228 { 00229 char* env_var = PR_GetEnv(var.c_str()); 00230 if ( env_var ) 00231 return PPath( env_var ); 00232 return default_; 00233 } 00234 00236 // metaprotocolToMetapath // 00238 00239 // Static map that stores the binding metaprotocol <-> metapath. 00240 // It is embedded within a function to prevent potential compiler issues during 00241 // static initialization (e.g. under Windows with gcc 3.4.4). 00242 map<string, PPath>& metaprotocol_to_metapath() { 00243 static map<string, PPath> metaprotocol_to_metapath; 00244 return metaprotocol_to_metapath; 00245 } 00246 00247 const map<string, PPath>& PPath::metaprotocolToMetapath() 00248 { 00249 static bool mappings; 00250 00251 if ( !mappings ) 00252 { 00253 // Avoiding infinite loop. 00254 mappings = true; 00255 00256 PPath plearn_configs = PPath::getenv( "PLEARN_CONFIGS", 00257 PPath::home() / ".plearn" ); 00258 00259 PPath config_file_path = plearn_configs / "ppath.config"; 00260 00261 if (isfile(config_file_path)) 00262 { 00263 PStream ppath_config = openFile(config_file_path, 00264 PStream::plearn_ascii); 00265 00266 string next_metaprotocol; 00267 PPath next_metapath; 00268 while (ppath_config.good()) { 00269 ppath_config >> next_metaprotocol >> next_metapath; 00270 if (next_metaprotocol.empty()){ 00271 if (ppath_config.good()) 00272 PLERROR("In PPath::metaprotocolToMetapath - Error while parsing PPath config file (%s): read " 00273 "a blank line before reaching the end of the file", 00274 config_file_path.errorDisplay().c_str()); 00275 else 00276 // Nothing left to read. 00277 break; 00278 } 00279 // Make sure we managed to read the metapath associated with the metaprotocol. 00280 if (next_metapath.empty()) 00281 PLERROR("In PPath::metaprotocolToMetapath - Error in PPath config file (%s): could not read the " 00282 "path associated with '%s'", 00283 config_file_path.errorDisplay().c_str(), next_metaprotocol.c_str()); 00284 00285 // For the sake of simplicity, we do not allow a metapath to end with 00286 // a slash unless it is a root directory. 00287 next_metapath.removeTrailingSlash(); 00288 00289 if (!addMetaprotocolBinding(next_metaprotocol, next_metapath)) 00290 PLWARNING("In PPath::metaprotocolToMetapath - Metaprotocol" 00291 " '%s' is being redefined, please check your " 00292 "ppath.config (%s)", 00293 next_metaprotocol.c_str(), 00294 config_file_path.errorDisplay().c_str()); 00295 } 00296 } 00297 else 00298 { 00299 if (PR_GetEnv("HOME")) 00300 { 00301 // Default PPath settings. Defined only if the HOME environment 00302 // variable exists. 00303 metaprotocol_to_metapath()["HOME"] = "${HOME}"; 00304 metaprotocol_to_metapath()["PLEARNDIR"] = "HOME:PLearn"; 00305 metaprotocol_to_metapath()["PLEARN_LIBDIR"] = "PLEARNDIR:external_libs"; 00306 } 00307 } 00308 } 00309 00310 return metaprotocol_to_metapath(); 00311 } 00312 00314 // addMetaprotocolBinding // 00316 bool PPath::addMetaprotocolBinding(const string& metaprotocol, 00317 const PPath& metapath, 00318 bool force) 00319 { 00320 const map<string, PPath>& bindings = metaprotocolToMetapath(); 00321 bool already_here = bindings.find(metaprotocol) != bindings.end(); 00322 if (!already_here || force) 00323 metaprotocol_to_metapath()[metaprotocol] = metapath; 00324 return !already_here; 00325 } 00326 00327 00328 // This method MUST NOT return a path since it would lead to an infinite 00329 // loop of constructors. 00330 string PPath::expandEnvVariables(const string& path) 00331 { 00332 string expanded = path; 00333 size_t begvar = expanded.find( "${" ); 00334 size_t endvar = expanded.find( "}" ); 00335 00336 while ( begvar != npos && endvar != npos ) 00337 { 00338 size_t start = begvar+2; 00339 size_t len = endvar - start; 00340 string envvar = expanded.substr(start, len); 00341 PPath envpath = PPath::getenv(envvar); 00342 00343 if ( envpath == "" ) 00344 PLERROR( "Unknown environment variable %s in %s.", 00345 envvar.c_str(), path.c_str() ); 00346 00347 expanded.replace(begvar, len+3, envpath); 00348 00349 // Look for other environment variables 00350 begvar = expanded.find( "${" ); 00351 endvar = expanded.find( "}" ); 00352 } 00353 00354 // This method MUST NOT return a path since it would lead to an infinite 00355 // loop of ctors. 00356 return expanded; 00357 } 00358 00359 00361 // setCanonicalInErrors // 00363 bool PPath::canonical_in_errors = false; 00364 00365 void PPath::setCanonicalInErrors(bool canonical) 00366 { 00367 PPath::canonical_in_errors = canonical; 00368 } 00369 00370 #if defined(__CYGWIN__) 00371 00372 extern "C" void cygwin_conv_to_win32_path(const char *path, 00373 char *win32_path); 00374 #endif 00375 00377 // PPath methods 00378 00379 PPath::PPath(const char* path) 00380 : _protocol("") 00381 { 00382 // MODULE_LOG << "PPath(const char* path = " << path << ")" << endl; 00383 operator=( PPath(string(path)) ); 00384 } 00385 00386 // The canonical path always contains '/' delimiters to seperate 00387 // subdirectories. Under windows, the internal representation will 00388 // however keep the '\' version. Under Unix, the following simply copy the 00389 // path value in the current instance. 00390 PPath::PPath(const string& path_) 00391 : _protocol("") 00392 { 00393 // MODULE_LOG << "PPath(const string& path_ = " << path_ << ")" << endl; 00394 00395 // Empty path. 00396 if ( path_.empty() ) 00397 return; 00398 const string* the_path = &path_; 00399 #if defined(__CYGWIN__) || defined(_MINGW_) 00400 #ifdef __CYGWIN__ 00401 char buf[3000]; 00402 #endif 00403 string new_path; 00404 // This is a hack to try to get the right DOS path from Cygwin. 00405 // Because Cygwin has its own translation rules, not necessarily compatible 00406 // with the PPath ones, we ask it to translate the path iff it starts with 00407 // a UNIX '/' character. TODO We will need a better home-made function 00408 // to translate paths safely. 00409 if (startsWith(*the_path, '/')) { 00410 #if defined(__CYGWIN__) 00411 cygwin_conv_to_win32_path(the_path->c_str(), buf); 00412 new_path = string(buf); 00413 #elif defined(_MINGW_) 00414 // We need to convert the path by ourselves. 00415 if (!startsWith(*the_path, "/cygdrive/")) { 00416 PLWARNING("Path '%s' is expected to start with '/cygdrive/'", 00417 the_path->c_str()); 00418 new_path = *the_path; 00419 } else { 00420 // Remove '/cygdrive'. 00421 new_path = the_path->substr(9); 00422 // Copy drive letter from second to first position. 00423 new_path[0] = new_path[1]; 00424 // Add ':' after drive letter. 00425 new_path[1] = ':'; 00426 // Replace '/' by '\'. 00427 for (string::size_type i = 0; i < new_path.size(); i++) 00428 if (new_path[i] == '/') 00429 new_path[i] = '\\'; 00430 } 00431 #endif 00432 the_path = &new_path; 00433 } 00434 #endif 00435 00436 // The path_ argument may contain environment variables that must be 00437 // expanded prior to any other processing. 00438 string internal = expandEnvVariables( *the_path ); 00439 00440 // The PPath internal string is set here 00441 string::operator= ( internal ); 00442 resolveSlashChars ( ); 00443 expandMetaprotocols ( ); 00444 resolveDots ( ); 00445 parseProtocol ( ); 00446 // pout << "Creating PPath from '" << *the_path << "' --> '" << string(*this) 00447 // << "'" << endl; 00448 } 00449 00451 // resolveSlashChars // 00453 void PPath::resolveSlashChars( ) 00454 { 00455 size_t plen = length(); 00456 string resolved; 00457 resolved.reserve( plen ); 00458 00459 bool last_is_slash = false; 00460 for ( size_t ch = 0; ch < plen; ch++ ) 00461 { 00462 char char_ch = operator[](ch); 00463 if ( forbidden_chars().find(char_ch) != npos ) 00464 PLERROR( "PPath '%s' cannot contain character '%c' (or any of \"%s\").", 00465 c_str(), char_ch, forbidden_chars().c_str() ); 00466 00467 // Convert the canonic representation '/' to the appropriate 00468 // representation given the system (see slash_char instanciation in 00469 // PPath.cc). Multiple slash chars are removed. 00470 if ( char_ch == PPATH_SLASH || char_ch == _slash_char() ) 00471 { 00472 if( !last_is_slash ) { // Prevents duplicated slash characters. 00473 resolved += _slash_char(); 00474 last_is_slash = true; 00475 } 00476 } 00477 else { 00478 resolved += char_ch; 00479 last_is_slash = false; 00480 } 00481 } 00482 00483 string::operator=(resolved); 00484 } 00485 00487 // expandMetaprotocols // 00489 void PPath::expandMetaprotocols() 00490 { 00491 size_t endmeta = find(':'); 00492 if ( endmeta != npos ) 00493 { 00494 string meta = substr(0, endmeta); 00495 map<string, PPath>::const_iterator it = metaprotocolToMetapath().find(meta); 00496 00497 PPath metapath; 00498 if ( it != metaprotocolToMetapath().end() ) 00499 metapath = it->second; 00500 else 00501 metapath = getenv(meta); 00502 00503 if ( !metapath.isEmpty() ) 00504 { 00505 string after_colon = endmeta == length()-1 ? "" : substr(endmeta+1); 00506 *this = metapath / after_colon; 00507 } 00508 } 00509 } 00510 00512 // resolveSingleDots // 00514 void PPath::resolveSingleDots() { 00515 static string ds, sd; 00516 static bool initialized = false; 00517 if (!initialized) { 00518 initialized = true; 00519 ds = "." + _slash(); // Posix: "./" DOS: ".\" 00520 sd = _slash() + "."; // Posix: "/." DOS: "\." 00521 } 00522 // Examples with single dots. 00523 // ./foo -> foo 00524 // ./ -> ./ 00525 // . -> . 00526 // /. -> / 00527 // /./ -> / 00528 // /./foo -> /foo 00529 // foo/. -> foo 00530 // foo/./ -> foo/ 00531 // foo/./bar -> foo/bar 00532 // foo/.bar -> foo/.bar 00533 00534 // First deal with "/.": 00535 // - when it is followed by a slash, remove it 00536 // - when it ends the path, remove the dot, and remove the slash unless 00537 // the resulting directory is a root directory 00538 size_t pos_sd = find(sd); 00539 size_t next; 00540 while (pos_sd != npos) { 00541 if (pos_sd + 2 < size()) { 00542 if (operator[](pos_sd + 2) == _slash_char()) { 00543 // It is followed by a slash. 00544 replace(pos_sd, 2, ""); // Remove '/.' 00545 next = pos_sd; 00546 } 00547 else 00548 next = pos_sd + 2; // We ignore this one (e.g. "/.plearn"). 00549 } else { 00550 // It ends the path. 00551 resize(size() - 1); // Remove '.' 00552 if (!isRoot()) 00553 resize(size() - 1); // Remove '/' 00554 next = size(); // We reached the end. 00555 } 00556 pos_sd = find(sd, next); 00557 } 00558 00559 // Now deals with "./". Because we have removed the "/." before, we cannot 00560 // have "/./" nor "./.". Thus the only case we have to consider is when it 00561 // starts the path (and this can happen only once), in which case we remove 00562 // it iff there is something that follows. 00563 size_t pos_ds = find(ds); 00564 if (pos_ds == 0 && pos_ds + 2 < size()) 00565 replace(0, 2, ""); 00566 } 00567 00569 // resolveDoubleDots // 00571 void PPath::resolveDoubleDots() { 00572 static string sdd; 00573 static bool initialized = false; 00574 if (!initialized) { 00575 initialized = true; 00576 sdd = _slash() + ".."; // Posix: "/.." DOS: "\.." 00577 } 00578 // Examples with double dots. 00579 // "/.." -> PLERROR 00580 // "/../foo" -> PLERROR 00581 // "../foo" -> "../foo" 00582 // "foo/.." -> "." 00583 // "foo/../" -> "./" 00584 // "/foo/.." -> "/" 00585 // "/foo/../" -> "/" 00586 // "foo/../bar" -> "./bar" (call resolveSingleDots() after) 00587 // "/foo/../bar" -> "/bar" 00588 // "/..foo" -> "/..foo" 00589 // "foo../" -> "foo../" 00590 // "../../../foo"-> "../../../foo" 00591 // "foo/../../.."-> "foo/../../.." 00592 00593 // We only care about "/.." when it is followed by a slash or it ends the path. 00594 // The path made of the substring until the '/' must not be a root directory. 00595 size_t pos_sdd = find(sdd); 00596 size_t next; 00597 while (pos_sdd != npos) { 00598 if (pos_sdd + 3 < size() && operator[](pos_sdd + 3) != _slash_char()) { 00599 // Something like "/..foo", that we ignore. 00600 next = pos_sdd + 4; 00601 } else { 00602 // Look for the slash just before. 00603 size_t pos_previous_slash = pos_sdd == 0 ? npos 00604 : rfind(_slash_char(), pos_sdd - 1); 00605 if (pos_previous_slash == npos) { 00606 // We need to make sure we are not trying to go up on a root 00607 // directory. 00608 if (PPath(substr(0, pos_sdd + 1)).isRoot()) 00609 // Long single-line error message to ensure tests pass. 00610 PLERROR("In PPath::resolveDots - '%s' is invalid", errorDisplay().c_str()); 00611 if ( (pos_sdd == 2 && substr(0,2) == "..") 00612 || (pos_sdd == 1 && operator[](0) == '.')) 00613 // We are in the case "../.." or "./.." 00614 next = pos_sdd + 3; 00615 else { 00616 // We are in the case "foo/.." 00617 replace(0, pos_sdd + 3, "."); 00618 next = 1; 00619 } 00620 } else { 00621 // There was a slash: "/xxx/..". If "xxx" == "..", we do nothing, 00622 // otherwise we get rid of it. 00623 if (substr(pos_previous_slash+1, 2) == "..") 00624 next = pos_sdd + 3; 00625 else { 00626 // We must be careful with the special case "/foo/..", where we 00627 // need to ensure we keep a final slash. 00628 if ( pos_sdd + 3 == size() // Ends with "/.." 00629 && ( pos_previous_slash == 0 00630 || rfind(_slash_char(), pos_previous_slash-1) == npos) // "xxx/foo/.." 00631 && PPath(substr(0, pos_previous_slash+1)).isRoot()) // "xxx/" is root 00632 replace(pos_previous_slash + 1, pos_sdd-pos_previous_slash+2, ""); 00633 else 00634 replace(pos_previous_slash, pos_sdd-pos_previous_slash+3, ""); 00635 next = pos_previous_slash; 00636 } 00637 } 00638 } 00639 pos_sdd = find(sdd, next); 00640 } 00641 // At this point, we may have introduced an extra single dot ("./xxx"). 00642 resolveSingleDots(); 00643 } 00644 00646 // resolveDots // 00648 void PPath::resolveDots() 00649 { 00650 // First remove temporarily the protocol. 00651 if (!_protocol.empty()) 00652 replace(0, _protocol.size() + 1, ""); 00653 00654 resolveSingleDots(); 00655 resolveDoubleDots(); 00656 00657 // Put back the protocol. 00658 if (!_protocol.empty()) 00659 insert(0, _protocol + ":"); 00660 } 00661 00663 // parseProtocol // 00665 void PPath::parseProtocol() 00666 { 00667 size_t endpr = find(':'); 00668 00669 // No specified protocol 00670 if ( endpr == npos ) 00671 { 00672 // Even if the default protocol is considered to be the file protocol, 00673 // the _protocol member keeps the exact protocol value in the 00674 // string. The protocol() method, however, returns FILE_PROTOCOL if the 00675 // protocol was not specified. 00676 _protocol = ""; 00677 } 00678 00679 // The substring preceeding the ':' delimiter COULD be the protocol. 00680 else 00681 { 00682 _protocol = substr(0, endpr); 00683 00684 if ( _protocol == FILE_PROTOCOL || 00685 _protocol == HTTP_PROTOCOL || 00686 _protocol == FTP_PROTOCOL ) 00687 { 00688 // Make sure we do not define a protocol for a relative path. 00689 PPath check_filepath_validity = removeProtocol(); 00690 if ( !check_filepath_validity.isEmpty() && 00691 !check_filepath_validity.isAbsPath() ) 00692 PLERROR("A PPath should not specify a protocol " 00693 "for a relative path (in %s).", c_str()); 00694 } 00695 00696 // Nothing prevents a file from containing a ':' char. Under dos, for 00697 // instance, a letter preceeding ':' represents the drive. Hence, if 00698 // we do not recognize the protocol, we assume the ':' was part of 00699 // the file name. 00700 else 00701 _protocol = ""; 00702 } 00703 } 00704 00706 // absolute // 00708 PPath PPath::absolute(bool add_protocol) const 00709 { 00710 if (!add_protocol && protocol() != FILE_PROTOCOL) 00711 PLERROR("In PPath::absolute - The absolute() method is only meant for " 00712 "the FILE_PROTOCOL protocol when 'add_protocol' is false"); 00713 00714 PPath abspath; 00715 00716 // An empty path remains empty when converted to absolute. 00717 if ( isEmpty() || isAbsPath() ) 00718 abspath = PPath( *this ); 00719 00720 // This is necessarily a file protocol (because other protocols require 00721 // an absolute path). 00722 // ===> we concatenate the current working directory of the process. 00723 else 00724 { 00725 PLASSERT( _protocol.empty() ); 00726 abspath = PPath::getcwd() / *this; 00727 } 00728 00729 // Remove useless trailing slash. 00730 abspath.removeTrailingSlash(); 00731 // Add / remove protocol if required. Note that we cannot add a protocol to 00732 // an empty PPath (as an empty PPath is considered relative). 00733 if (add_protocol && !abspath.isEmpty()) 00734 abspath = abspath.addProtocol(); 00735 else 00736 // There can be a protocol in abspath only if there is one in *this. 00737 if (!_protocol.empty()) 00738 abspath = abspath.removeProtocol(); 00739 00740 return abspath; 00741 } 00742 00744 // canonical // 00746 string PPath::canonical() const 00747 { 00748 // An empty path does not need to be modified. 00749 if (isEmpty()) 00750 return *this; 00751 00752 // We have to replace any special path by its canonic equivalent. 00753 // Note that the protocol is kept. This means in particular that 00754 // if ppath = "/foo/bar" and the metaprotocol FOO maps to "file:/foo", 00755 // then the canonical form of ppath will still be "/foo/bar", and not 00756 // "FOO:bar" (we may want to change this behavior in the future). 00757 00758 string canonic_path = *this; 00759 EXTREME_LOG << plhead("canonic_path: "+canonic_path) << endl; 00760 00761 map<string, PPath>::const_iterator it = metaprotocolToMetapath().begin(); 00762 map<string, PPath>::const_iterator end = metaprotocolToMetapath().end(); 00763 00764 string metaprotocol; 00765 string metapath; // Used to store the longest metapath found so far. 00766 while ( it != end ) 00767 { 00768 const string& candidate = it->second; 00769 if ( candidate.length() < metapath.length() ) 00770 { 00771 // The candidate is shorter, we are not interested. 00772 EXTREME_LOG << "Shorter:\n\t" 00773 << it->first << " -> " << candidate.c_str() << endl; 00774 ++it; 00775 continue; 00776 } 00777 if ( !startsWith(canonic_path, candidate) ) { 00778 // No match. 00779 EXTREME_LOG << "No match:\n\t" 00780 << it->first << " -> " << candidate.c_str() << endl; 00781 ++it; 00782 continue; 00783 } 00784 00785 size_t endpath = candidate.length(); 00786 00787 // The current candidate is only a subtring of the canonic path. 00788 // Ex: 00789 // /home/dorionc/hey 00790 // in 00791 // /home/dorionc/heyYou. 00792 // Note that if the canonic path is a root directory, it may end 00793 // with a slash, in which case this cannot happen. 00794 if ( endpath != canonic_path.length() && 00795 canonic_path[endpath] != _slash_char() && 00796 !endsWith(candidate, _slash_char()) ) 00797 { 00798 EXTREME_LOG << "Substring:\n\t" 00799 << it->first << " -> " << it->second.c_str() << endl; 00800 ++it; 00801 continue; 00802 } 00803 00804 // The current candidate is indeed a subpath of canonic_path. 00805 metaprotocol = it->first; 00806 metapath = candidate; 00807 EXTREME_LOG << "Kept:\n\t" 00808 << it->first << " -> " << candidate.c_str() << endl; 00809 ++it; // We iterate to find the longest matching candidate. 00810 } 00811 00812 // If any metapath was found, it must be replaced by its metaprotocol 00813 // equivalent. 00814 if ( metaprotocol.length() > 0 ) { 00815 canonic_path.replace( 0, metapath.length(), metaprotocol+':' ); 00816 // Remove the slash just after the ':' if there is something following. 00817 size_t after_colon = metaprotocol.size() + 1; 00818 if (canonic_path.size() > after_colon + 1 && 00819 canonic_path[after_colon] == _slash_char()) 00820 canonic_path.erase(after_colon, 1); 00821 } 00822 00823 // If necessary, convert slash characters to the canonical slash. 00824 if (_slash_char() != PPATH_SLASH) { 00825 size_t slash_pos = 0; 00826 while ((slash_pos = canonic_path.find(_slash_char(), slash_pos)) != npos) 00827 canonic_path[slash_pos] = PPATH_SLASH; 00828 } 00829 00830 return canonic_path; 00831 } 00832 00834 // errorDisplay // 00836 string PPath::errorDisplay() const 00837 { 00838 if (PPath::canonical_in_errors) 00839 return this->canonical(); 00840 else 00841 return this->absolute(); 00842 } 00843 00845 // addProtocol // 00847 PPath PPath::addProtocol() const 00848 { 00849 if ( _protocol.empty()) { 00850 if (!isAbsPath()) 00851 // Ugly single-line error message to ensure tests pass. 00852 PLERROR("In PPath::addProtocol - A protocol can only be added to an absolute path, and '%s' is relative", errorDisplay().c_str()); 00853 return ( PPath(string(FILE_PROTOCOL) + ':' + string(*this)) ); 00854 } 00855 return PPath( *this ); 00856 } 00857 00859 // removeProtocol // 00861 PPath PPath::removeProtocol() const 00862 { 00863 if ( _protocol.length()==0 ) 00864 return PPath(*this); 00865 PPath no_protocol; 00866 // Avoid a call to the PPath constructor from a string. 00867 no_protocol.assign(substr(_protocol.length()+1)); 00868 return no_protocol; 00869 } 00870 00872 // operator/ // 00874 PPath PPath::operator/(const PPath& other) const 00875 { 00876 return ( PPath(*this) /= other ); 00877 } 00878 00880 // operator/= // 00882 PPath& PPath::operator/=(const PPath& other) 00883 { 00884 // MODULE_LOG << this->c_str() << " /= " << other << endl; 00885 00886 if (other.isAbsPath()) 00887 PLERROR("In PPath::operator/= - The concatenated path (%s) cannot be absolute", 00888 other.c_str()); 00889 // Add a slash if needed. 00890 // Note that 'other' cannot start with a slash, otherwise it would be an 00891 // absolute directory. 00892 if ( !isEmpty () && 00893 !endsWith (*this, _slash_char()) ) 00894 string::operator+=(_slash_char()); 00895 string::operator+=(other); 00896 00897 resolveDots ( ); 00898 return *this; 00899 } 00900 00902 // operator== // 00904 bool PPath::operator== (const string& other) const 00905 { 00906 // MODULE_LOG << this->c_str() << " == " << other << " (string)"<< endl; 00907 if ( other.empty() ) 00908 return isEmpty(); 00909 if ( isEmpty() ) 00910 return false; // since 'other' is not 00911 return operator==( PPath(other) ); 00912 } 00913 00914 bool PPath::operator==(const PPath& other) const 00915 { 00916 // MODULE_LOG << this->c_str() << " == " << other << " (PPath)"<< endl; 00917 00918 // If they are stricly equal there is no need to go further. 00919 // Otherwise they must point to the same absolute file or directory. 00920 // Note that the absolute() method already removes the trailing slash. 00921 return ( !strcmp(c_str(), other.c_str()) 00922 || !strcmp(absolute(true).c_str(), other.absolute(true).c_str())); 00923 } 00924 00926 // up // 00928 PPath PPath::up() const 00929 { 00930 if (isEmpty() || isRoot()) 00931 // Note that the following line has more than 80 characters, but it is 00932 // the simplest way to avoid issues in tests, as the line number of 00933 // this error is displayed, and it may be ambiguous if it spans 00934 // multiple lines. 00935 PLERROR("In PPath::up - Cannot go up on directory '%s'", errorDisplay().c_str()); 00936 return *this / ".."; 00937 } 00938 00940 // dirname // 00942 PPath PPath::dirname() const 00943 { 00944 if (isEmpty() || isRoot()) 00945 return PPath(*this); 00946 size_t slash_pos = rfind(_slash_char()); 00947 if ( slash_pos == npos ){ 00948 if (_protocol.empty()) 00949 return "."; 00950 else 00951 return _protocol + ":."; 00952 } 00953 PPath result = substr(0, slash_pos + 1); 00954 // Remove trailing slash if it is not a root directory. 00955 result.removeTrailingSlash(); 00956 return result; 00957 } 00958 00960 // basename // 00962 PPath PPath::basename() const 00963 { 00964 size_t slash_pos = rfind(_slash_char()); 00965 if ( slash_pos == npos ) 00966 return PPath(*this); 00967 return substr(slash_pos+1); 00968 } 00969 00971 // hostname // 00973 string PPath::hostname() const 00974 { 00975 if (!isAbsPath()) 00976 PLERROR("In PPath::hostname - Can only be used with an absolute path"); 00977 if (isRoot()) 00978 PLERROR("In PPath::hostname - The path cannot be a root directory"); 00979 int i = 0; 00980 PPath paths[2]; 00981 paths[0] = *this; 00982 paths[1] = paths[0].up(); 00983 while (!paths[1 - i].isRoot()) { 00984 i = 1 - i; 00985 paths[1 - i] = paths[i].up(); 00986 } 00987 paths[i].removeTrailingSlash(); 00988 return paths[i].basename(); 00989 } 00990 00992 // extension // 00994 string PPath::extension(bool with_dot) const 00995 { 00996 PPath base = basename(); 00997 size_t begext = base.rfind('.'); 00998 if ( begext == npos || // Not found. 00999 begext == base.length()-1 ) // Filename ending with a dot. 01000 return ""; 01001 return with_dot ? base.substr(begext) : base.substr(begext+1); 01002 } 01003 01005 // no_extension // 01007 PPath PPath::no_extension() const 01008 { 01009 size_t ext_size = extension().size(); 01010 string copy(*this); 01011 if (ext_size > 0) 01012 copy.resize(size() - ext_size - 1); // Remove the extension and the dot. 01013 return copy; 01014 } 01015 01016 // // Returns a ppath shorter than 256 character and exempt of any of the 01017 // // following chars: "*?'\"${}[]@ ,()" --- replaced by underscores. 01018 // PPath PPath::makeFileNameValid(const PPath& path) const 01019 // { 01020 // PPath valid = path; 01021 // PPath dirname = path.dirname(); 01022 // PPath filename = path.basename(); 01023 // 01024 // if ( filename.length() > 256 ) 01025 // { 01026 // string ext = filename.extension(); 01027 // 01028 // int kept_length = 256-ext.length()-12; 01029 // string filename_kept = filename.substr( 0, kept_length ); 01030 // 01031 // int rest_length = filename.length() - kept_length - ext.length(); 01032 // string rest = filename.substr( kept_length, rest_length ); 01033 // 01034 // int j= 0; 01035 // do 01036 // { 01037 // unsigned int n= j++; 01038 // for(unsigned int i= 0; i < rest_length; ++i) 01039 // { 01040 // int m=0; 01041 // switch(i%4) 01042 // { 01043 // case 3: m= 1; break; 01044 // case 2: m= 256; break; 01045 // case 1: m= 65536; break; 01046 // case 0: m= 256*65536; break; 01047 // } 01048 // n+= m*(unsigned char)rest[i]; 01049 // } 01050 // 01051 // 01052 // valid = dirname / filename_noext + "-" + tostring(n) + ext; 01053 // } 01054 // while( pathexists( valid ) ); 01055 // 01056 // PLWARNING("makeFilenameValid: Filename '%s' changed to '%s'.", 01057 // path.c_str(), (dirname + filename_wo_ext + ext).c_str()); 01058 // valid = dirname + filename_wo_ext + ext; 01059 // } 01060 // 01061 // // replace illegal characters 01062 // char illegal[]="*?'\"${}[]@ ,()"; 01063 // for(int i=0;i<(signed)valid.size();i++) 01064 // for(int j=0;j<15;j++) 01065 // if(valid[i]==illegal[j]) 01066 // valid[i]='_'; 01067 // return valid; 01068 // } 01069 01070 01071 #if defined(WIN32) 01072 PPath PPath::drive() const 01073 { 01074 if ( find(':') == 1 && isalpha( c_str()[0] ) ) 01075 return PPath(substr(0, 2)); 01076 return PPath(""); 01077 } 01078 01079 bool PPath::isabs() const 01080 { 01081 // Note that a Win32 path is considered absolute if starting with a '\' 01082 // character: this is so that the path resulting from removing the protocol 01083 // in a ftp path for instance is still considered as absolute. 01084 return !drive().isEmpty() || isHttpPath() || isFtpPath() || 01085 _protocol == FILE_PROTOCOL || startsWith(c_str(), _slash_char()); 01086 } 01087 01088 #else 01089 PPath PPath::drive() const 01090 { 01091 return PPath(""); 01092 } 01093 01094 bool PPath::isabs() const 01095 { 01096 return startsWith(c_str(), _slash_char()) || isHttpPath() || isFtpPath() || _protocol == FILE_PROTOCOL; 01097 } 01098 // TODO What about file:foo/bar ? 01099 01100 #endif 01101 01103 // isRoot // 01105 bool PPath::isRoot() const 01106 { 01107 if (!isAbsPath()) 01108 return false; 01109 PPath no_prot = removeProtocol(); 01110 string drv = no_prot.drive(); 01111 return string(no_prot) == drv + _slash(); 01112 } 01113 01114 01116 // parseUrlParameters // 01118 void PPath::parseUrlParameters(PPath& base_path, map<string, string>& parameters) const 01119 { 01120 size_t pos = rfind('?'); 01121 if (pos == string::npos) { 01122 base_path = *this; 01123 return; 01124 } 01125 size_t check = rfind('?', pos - 1); 01126 if (check != string::npos) 01127 PLERROR("In PPath::parseUrlParameters - There can be only one '?' in a PPath"); 01128 base_path = substr(0, pos); 01129 string rest = substr(pos + 1); 01130 vector<string> pairs = PLearn::split(rest, '&'); 01131 string equal = "="; 01132 string name, value; 01133 vector<string>::const_iterator it = pairs.begin(); 01134 for (; it != pairs.end(); it++) { 01135 PLearn::split_on_first(*it, equal, name, value); 01136 if (!name.empty()) { 01137 if (value.empty()) 01138 PLERROR("In PPath::parseUrlParameters - The parameter %s has no value", 01139 name.c_str()); 01140 parameters[name] = value; 01141 } else if (!value.empty()) 01142 PLERROR("In PPath::parseUrlParameters - The value %s has no parameter name", 01143 value.c_str()); 01144 } 01145 } 01146 01148 // removeTrailingSlash // 01150 void PPath::removeTrailingSlash() { 01151 if (isEmpty() || (*this)[length() - 1] != _slash_char() || isRoot()) 01152 return; 01153 resize(length() - 1); 01154 } 01155 01156 } // end of namespace PLearn 01157 01158 01159 /* 01160 Local Variables: 01161 mode:c++ 01162 c-basic-offset:4 01163 c-file-style:"stroustrup" 01164 c-file-offsets:((innamespace . 0)(inline-open . 0)) 01165 indent-tabs-mode:nil 01166 fill-column:79 01167 End: 01168 */ 01169 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=79 :