1 /** @file fileutils.cc
2  *  @brief File and path manipulation routines.
3  */
4 /* Copyright (C) 2008 Lemur Consulting Ltd
5  * Copyright (C) 2008,2009,2010 Olly Betts
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
20  */
21 
22 #include <config.h>
23 
24 #include "fileutils.h"
25 
26 #include <cstring>
27 #include <string>
28 
29 using namespace std;
30 
31 #ifdef __WIN32__
32 /// Return true iff a path starts with a drive letter.
33 static bool
has_drive(const string & path)34 has_drive(const string &path)
35 {
36     return (path.size() >= 2 && path[1] == ':');
37 }
38 
39 /// Return true iff path is a UNCW path.
40 static bool
uncw_path(const string & path)41 uncw_path(const string & path)
42 {
43     return (path.size() >= 4 && memcmp(path.data(), "\\\\?\\", 4) == 0);
44 }
45 
slash(char ch)46 inline bool slash(char ch)
47 {
48     return ch == '/' || ch == '\\';
49 }
50 #endif
51 
52 void
resolve_relative_path(string & path,const string & base)53 resolve_relative_path(string & path, const string & base)
54 {
55 #ifndef __WIN32__
56     if (path.empty() || path[0] != '/') {
57 	// path is relative.
58 	string::size_type last_slash = base.rfind('/');
59 	if (last_slash != string::npos)
60 	    path.insert(0, base, 0, last_slash + 1);
61     }
62 #else
63     // Microsoft Windows paths may begin with a drive letter but still be
64     // relative within that drive.
65     bool drive = has_drive(path);
66     string::size_type p = (drive ? 2 : 0);
67     bool absolute = (p != path.size() && slash(path[p]));
68 
69     if (absolute) {
70 	// If path is absolute and has a drive specifier, just return it.
71 	if (drive)
72 	    return;
73 
74 	// If base has a drive specifier prepend that to path.
75 	if (has_drive(base)) {
76 	    path.insert(0, base, 0, 2);
77 	    return;
78 	}
79 
80 	// If base has a UNC (\\SERVER\\VOLUME) or \\?\ prefix, prepend that
81 	// to path.
82 	if (uncw_path(base)) {
83 	    string::size_type sl = 0;
84 	    if (base.size() >= 7 && memcmp(base.data() + 5, ":\\", 2) == 0) {
85 		// "\\?\X:\"
86 		sl = 6;
87 	    } else if (base.size() >= 8 &&
88 		       memcmp(base.data() + 4, "UNC\\", 4) == 0) {
89 		// "\\?\UNC\server\volume\"
90 		sl = base.find('\\', 8);
91 		if (sl != string::npos)
92 		    sl = base.find('\\', sl + 1);
93 	    }
94 	    if (sl) {
95 		// With the \\?\ prefix, '/' isn't recognised so change it
96 		// to '\' in path.
97 		string::iterator i;
98 		for (i = path.begin(); i != path.end(); ++i) {
99 		    if (*i == '/')
100 			*i = '\\';
101 		}
102 		path.insert(0, base, 0, sl);
103 	    }
104 	} else if (base.size() >= 5 && slash(base[0]) && slash(base[1])) {
105 	    // Handle UNC base.
106 	    string::size_type sl = base.find_first_of("/\\", 2);
107 	    if (sl != string::npos) {
108 		sl = base.find_first_of("/\\", sl + 1);
109 		path.insert(0, base, 0, sl);
110 	    }
111 	}
112 	return;
113     }
114 
115     // path is relative, so if it has no drive specifier or the same drive
116     // specifier as base, then we want to qualify it using base.
117     bool base_drive = has_drive(base);
118     if (!drive || (base_drive && (path[0] | 32) == (base[0] | 32))) {
119 	string::size_type last_slash = base.find_last_of("/\\");
120 	if (last_slash == string::npos && !drive && base_drive)
121 	    last_slash = 1;
122 	if (last_slash != string::npos) {
123 	    string::size_type b = (drive && base_drive ? 2 : 0);
124 	    if (uncw_path(base)) {
125 		// With the \\?\ prefix, '/' isn't recognised so change it
126 		// to '\' in path.
127 		string::iterator i;
128 		for (i = path.begin(); i != path.end(); ++i) {
129 		    if (*i == '/')
130 			*i = '\\';
131 		}
132 	    }
133 	    path.insert(b, base, b, last_slash + 1 - b);
134 	}
135     }
136 #endif
137 }
138