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