1 /*============================================================================= 2 Copyright (c) 2002 2004 2006 Joel de Guzman 3 Copyright (c) 2004 Eric Niebler 4 Copyright (c) 2005 Thomas Guest 5 Copyright (c) 2013, 2017 Daniel James 6 7 Use, modification and distribution is subject to the Boost Software 8 License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at 9 http://www.boost.org/LICENSE_1_0.txt) 10 =============================================================================*/ 11 12 #include "path.hpp" 13 #include <cassert> 14 #include <boost/filesystem/operations.hpp> 15 #include <boost/range/algorithm/replace.hpp> 16 #include "for.hpp" 17 #include "glob.hpp" 18 #include "include_paths.hpp" 19 #include "state.hpp" 20 #include "utils.hpp" 21 22 #if QUICKBOOK_CYGWIN_PATHS 23 #include <sys/cygwin.h> 24 #include <boost/scoped_array.hpp> 25 #endif 26 27 namespace quickbook 28 { 29 // Not a general purpose normalization function, just 30 // from paths from the root directory. It strips the excess 31 // ".." parts from a path like: "x/../../y", leaving "y". remove_dots_from_path(fs::path const & path)32 std::vector<fs::path> remove_dots_from_path(fs::path const& path) 33 { 34 assert(!path.has_root_directory() && !path.has_root_name()); 35 36 std::vector<fs::path> parts; 37 38 QUICKBOOK_FOR (fs::path const& part, path) { 39 if (part.empty() || part == ".") { 40 } 41 else if (part == "..") { 42 if (!parts.empty()) parts.pop_back(); 43 } 44 else { 45 parts.push_back(part); 46 } 47 } 48 49 return parts; 50 } 51 52 // The relative path from base to path path_difference(fs::path const & base,fs::path const & path,bool is_file)53 fs::path path_difference( 54 fs::path const& base, fs::path const& path, bool is_file) 55 { 56 fs::path absolute_base = fs::absolute(base), 57 absolute_path = fs::absolute(path); 58 59 // Remove '.', '..' and empty parts from the remaining path 60 std::vector<fs::path> base_parts = remove_dots_from_path( 61 absolute_base.relative_path()), 62 path_parts = remove_dots_from_path( 63 absolute_path.relative_path()); 64 65 std::vector<fs::path>::iterator base_it = base_parts.begin(), 66 base_end = base_parts.end(), 67 path_it = path_parts.begin(), 68 path_end = path_parts.end(); 69 70 // Build up the two paths in these variables, checking for the first 71 // difference. 72 fs::path base_tmp = absolute_base.root_path(), 73 path_tmp = absolute_path.root_path(); 74 75 fs::path result; 76 77 // If they have different roots then there's no relative path so 78 // just build an absolute path. 79 if (!fs::equivalent(base_tmp, path_tmp)) { 80 result = path_tmp; 81 } 82 else { 83 // Find the point at which the paths differ 84 for (; base_it != base_end && path_it != path_end; 85 ++base_it, ++path_it) { 86 base_tmp /= *base_it; 87 path_tmp /= *path_it; 88 if (*base_it != *path_it) { 89 if (!fs::exists(base_tmp) || !fs::exists(path_tmp) || 90 !fs::equivalent(base_tmp, path_tmp)) { 91 break; 92 } 93 } 94 } 95 96 if (is_file && path_it == path_end && 97 path_it != path_parts.begin()) { 98 --path_it; 99 result = ".."; 100 } 101 else if (base_it == base_end && path_it == path_end) { 102 result = "."; 103 } 104 105 // Build a relative path to that point 106 for (; base_it != base_end; ++base_it) 107 result /= ".."; 108 } 109 110 // Build the rest of our path 111 for (; path_it != path_end; ++path_it) 112 result /= *path_it; 113 114 return result; 115 } 116 117 // Convert a Boost.Filesystem path to a URL. 118 // 119 // I'm really not sure about this, as the meaning of root_name and 120 // root_directory are only clear for windows. 121 // 122 // Some info on file URLs at: 123 // https://en.wikipedia.org/wiki/File_URI_scheme file_path_to_url_impl(fs::path const & x,bool is_dir)124 std::string file_path_to_url_impl(fs::path const& x, bool is_dir) 125 { 126 fs::path::const_iterator it = x.begin(), end = x.end(); 127 if (it == end) { 128 return is_dir ? "./" : ""; 129 } 130 131 std::string result; 132 bool sep = false; 133 std::string part; 134 if (x.has_root_name()) { 135 // Handle network address (e.g. \\example.com) 136 part = detail::path_to_generic(*it); 137 if (part.size() >= 2 && part[0] == '/' && part[1] == '/') { 138 result = "file:" + detail::escape_uri(part); 139 sep = true; 140 ++it; 141 if (it != end && *it == "/") { 142 result += "/"; 143 sep = false; 144 ++it; 145 } 146 } 147 else { 148 result = "file:///"; 149 } 150 151 // Handle windows root (e.g. c:) 152 if (it != end) { 153 part = detail::path_to_generic(*it); 154 if (part.size() >= 2 && part[part.size() - 1] == ':') { 155 result += 156 detail::escape_uri(part.substr(0, part.size() - 1)); 157 result += ':'; 158 sep = false; 159 ++it; 160 } 161 } 162 } 163 else if (x.has_root_directory()) { 164 result = "file://"; 165 sep = true; 166 } 167 else if (*it == ".") { 168 result = "."; 169 sep = true; 170 ++it; 171 } 172 173 for (; it != end; ++it) { 174 part = detail::path_to_generic(*it); 175 if (part == "/") { 176 result += "/"; 177 sep = false; 178 } 179 else if (part == ".") { 180 // If the path has a trailing slash, write it out, 181 // even if is_dir is false. 182 if (sep) { 183 result += "/"; 184 sep = false; 185 } 186 } 187 else { 188 if (sep) { 189 result += "/"; 190 } 191 result += detail::escape_uri(detail::path_to_generic(*it)); 192 sep = true; 193 } 194 } 195 196 if (is_dir && sep) { 197 result += "/"; 198 } 199 200 return result; 201 } 202 file_path_to_url(fs::path const & x)203 std::string file_path_to_url(fs::path const& x) 204 { 205 return file_path_to_url_impl(x, false); 206 } 207 dir_path_to_url(fs::path const & x)208 std::string dir_path_to_url(fs::path const& x) 209 { 210 return file_path_to_url_impl(x, true); 211 } 212 213 namespace detail 214 { 215 #if QUICKBOOK_WIDE_PATHS command_line_to_utf8(command_line_string const & x)216 std::string command_line_to_utf8(command_line_string const& x) 217 { 218 return to_utf8(x); 219 } 220 #else 221 std::string command_line_to_utf8(command_line_string const& x) 222 { 223 return x; 224 } 225 #endif 226 227 #if QUICKBOOK_WIDE_PATHS generic_to_path(quickbook::string_view x)228 fs::path generic_to_path(quickbook::string_view x) 229 { 230 return fs::path(from_utf8(x)); 231 } 232 path_to_generic(fs::path const & x)233 std::string path_to_generic(fs::path const& x) 234 { 235 return to_utf8(x.generic_wstring()); 236 } 237 #else generic_to_path(quickbook::string_view x)238 fs::path generic_to_path(quickbook::string_view x) 239 { 240 return fs::path(x.begin(), x.end()); 241 } 242 path_to_generic(fs::path const & x)243 std::string path_to_generic(fs::path const& x) 244 { 245 return x.generic_string(); 246 } 247 #endif 248 249 #if QUICKBOOK_CYGWIN_PATHS command_line_to_path(command_line_string const & path)250 fs::path command_line_to_path(command_line_string const& path) 251 { 252 cygwin_conv_path_t flags = CCP_POSIX_TO_WIN_W | CCP_RELATIVE; 253 254 ssize_t size = cygwin_conv_path(flags, path.c_str(), NULL, 0); 255 256 if (size < 0) 257 throw conversion_error( 258 "Error converting cygwin path to windows."); 259 260 boost::scoped_array<char> result(new char[size]); 261 void* ptr = result.get(); 262 263 if (cygwin_conv_path(flags, path.c_str(), ptr, size)) 264 throw conversion_error( 265 "Error converting cygwin path to windows."); 266 267 return fs::path(static_cast<wchar_t*>(ptr)); 268 } 269 path_to_stream(fs::path const & path)270 stream_string path_to_stream(fs::path const& path) 271 { 272 cygwin_conv_path_t flags = CCP_WIN_W_TO_POSIX | CCP_RELATIVE; 273 274 ssize_t size = 275 cygwin_conv_path(flags, path.native().c_str(), NULL, 0); 276 277 if (size < 0) 278 throw conversion_error( 279 "Error converting windows path to cygwin."); 280 281 boost::scoped_array<char> result(new char[size]); 282 283 if (cygwin_conv_path( 284 flags, path.native().c_str(), result.get(), size)) 285 throw conversion_error( 286 "Error converting windows path to cygwin."); 287 288 return std::string(result.get()); 289 } 290 #else command_line_to_path(command_line_string const & path)291 fs::path command_line_to_path(command_line_string const& path) 292 { 293 return fs::path(path); 294 } 295 296 #if QUICKBOOK_WIDE_PATHS && !QUICKBOOK_WIDE_STREAMS path_to_stream(fs::path const & path)297 stream_string path_to_stream(fs::path const& path) 298 { 299 return path.string(); 300 } 301 #else path_to_stream(fs::path const & path)302 stream_string path_to_stream(fs::path const& path) 303 { 304 return path.native(); 305 } 306 #endif 307 308 #endif // QUICKBOOK_CYGWIN_PATHS 309 310 enum path_or_url_type 311 { 312 path_or_url_empty = 0, 313 path_or_url_path, 314 path_or_url_url 315 }; 316 path_or_url()317 path_or_url::path_or_url() : type_(path_or_url_empty) {} 318 path_or_url(path_or_url const & x)319 path_or_url::path_or_url(path_or_url const& x) 320 : type_(x.type_), path_(x.path_), url_(x.url_) 321 { 322 } 323 path_or_url(command_line_string const & x)324 path_or_url::path_or_url(command_line_string const& x) 325 { 326 auto rep = command_line_to_utf8(x); 327 auto it = rep.begin(), end = rep.end(); 328 std::size_t count = 0; 329 while (it != end && 330 ((*it >= 'a' && *it <= 'z') || (*it >= 'A' && *it <= 'Z') || 331 *it == '+' || *it == '-' || *it == '.')) { 332 ++it; 333 ++count; 334 } 335 336 if (it != end && *it == ':' && count > 1) { 337 type_ = path_or_url_url; 338 } 339 else { 340 type_ = path_or_url_path; 341 } 342 343 switch (type_) { 344 case path_or_url_empty: 345 break; 346 case path_or_url_path: 347 path_ = command_line_to_path(x); 348 break; 349 case path_or_url_url: 350 url_ = rep; 351 break; 352 default: 353 assert(false); 354 } 355 } 356 operator =(path_or_url const & x)357 path_or_url& path_or_url::operator=(path_or_url const& x) 358 { 359 type_ = x.type_; 360 path_ = x.path_; 361 url_ = x.url_; 362 return *this; 363 } 364 operator =(command_line_string const & x)365 path_or_url& path_or_url::operator=(command_line_string const& x) 366 { 367 path_or_url tmp(x); 368 swap(tmp); 369 return *this; 370 } 371 swap(path_or_url & x)372 void path_or_url::swap(path_or_url& x) 373 { 374 std::swap(type_, x.type_); 375 std::swap(path_, x.path_); 376 std::swap(url_, x.url_); 377 } 378 url(string_view x)379 path_or_url path_or_url::url(string_view x) 380 { 381 path_or_url r; 382 r.type_ = path_or_url_url; 383 r.url_.assign(x.begin(), x.end()); 384 return r; 385 } 386 path(boost::filesystem::path const & x)387 path_or_url path_or_url::path(boost::filesystem::path const& x) 388 { 389 path_or_url r; 390 r.type_ = path_or_url_path; 391 r.path_ = x; 392 return r; 393 } 394 operator bool() const395 path_or_url::operator bool() const 396 { 397 return type_ != path_or_url_empty; 398 } 399 is_path() const400 bool path_or_url::is_path() const { return type_ == path_or_url_path; } 401 is_url() const402 bool path_or_url::is_url() const { return type_ == path_or_url_url; } 403 get_path() const404 boost::filesystem::path const& path_or_url::get_path() const 405 { 406 assert(is_path()); 407 return path_; 408 } 409 get_url() const410 std::string const& path_or_url::get_url() const 411 { 412 assert(is_url()); 413 return url_; 414 } 415 operator /(string_view x) const416 path_or_url path_or_url::operator/(string_view x) const 417 { 418 path_or_url r; 419 r.type_ = type_; 420 421 switch (type_) { 422 case path_or_url_empty: 423 assert(false); 424 break; 425 case path_or_url_path: 426 r.path_ = path_ / x.to_s(); 427 break; 428 case path_or_url_url: { 429 r.url_ = url_; 430 auto pos = r.url_.rfind('/'); 431 if (pos == std::string::npos) { 432 pos = r.url_.rfind(':'); 433 } 434 if (pos != std::string::npos) { 435 r.url_.resize(pos + 1); 436 } 437 else { 438 // Error? Empty string? 439 r.url_ = "/"; 440 } 441 r.url_ += x; 442 break; 443 } 444 default: 445 assert(false); 446 } 447 return r; 448 } 449 } 450 } 451