1 /***************************************************************************
2  *                                                                         *
3  *   Copyright (C) 2007-2017 Christian Schoenebeck                         *
4  *                                                                         *
5  *   This library is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This library is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this library; if not, write to the Free Software           *
17  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston,                 *
18  *   MA  02111-1307  USA                                                   *
19  ***************************************************************************/
20 
21 #include "Path.h"
22 
23 // for function hexsToNumber()
24 #include "global_private.h"
25 
26 #include <sstream>
27 
28 namespace LinuxSampler {
29 
Path()30 Path::Path() : drive(0), absolute(false) {
31 }
32 
Path(std::string path)33 Path::Path(std::string path) {
34     #if WIN32
35     *this = fromWindows(path);
36     #else
37     *this = fromPosix(path);
38     #endif
39 }
40 
appendNode(std::string Name)41 void Path::appendNode(std::string Name) {
42     if (!Name.size()) return;
43     elements.push_back(Name);
44 }
45 
setDrive(const char & Drive)46 void Path::setDrive(const char& Drive) {
47     drive = Drive;
48     absolute = true;
49 }
50 
toNativeFSPath() const51 std::string Path::toNativeFSPath() const {
52     #if WIN32
53     return toWindows();
54     #else
55     return toPosix();
56     #endif
57 }
58 
toPosix() const59 std::string Path::toPosix() const {
60     // POSIX paths consist of forward slash separators and requires forward
61     // slashes in path and file names to be encoded as "%2f", the file names
62     // "." and ".." have special meanings
63     // (see http://www.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_169
64     // and http://www.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_266 )
65     std::string result;
66     for (int iElement = 0; iElement < elements.size(); iElement++) {
67         // encode percent characters
68         std::string e = elements[iElement];
69         for (
70             int pos = (int)e.find("%"); pos != std::string::npos;
71             pos = (int)e.find("%", pos+2)
72         ) e.replace(pos/*offset*/, 1/*length*/, "%%"/*by*/);
73         // encode forward slashes
74         for (
75             int pos = (int)e.find("/"); pos != std::string::npos;
76             pos = (int)e.find("/", pos+3)
77         ) e.replace(pos/*offset*/, 1/*length*/, "%2f"/*by*/);
78         // append encoded node to full encoded path
79         result += "/" + e;
80     }
81     if (!result.size()) result = "/";
82     return result;
83 }
84 
toDbPath() const85 std::string Path::toDbPath() const {
86     std::string result;
87     for (int iElement = 0; iElement < elements.size(); iElement++) {
88         // replace all slashes with '\0'
89         std::string e = elements[iElement];
90         for (int i = 0; i < e.length(); i++) {
91             if (e.at(i) == '/') e.at(i) = '\0';
92         }
93         // append encoded node to full encoded path
94         result += "/" + e;
95     }
96     if (!result.size()) result = "/";
97     return result;
98 }
99 
toLscp() const100 std::string Path::toLscp() const {
101     std::string result;
102     #if WIN32
103     if(drive) {
104         result.assign(&drive,1);
105         result += ":";
106     }
107     #endif
108     for (int iElement = 0; iElement < elements.size(); iElement++) {
109         // replace "special characters" by LSCP escape sequences
110         std::string e = elements[iElement];
111         for (int i = 0; i < e.length(); i++) {
112             const char c = e.c_str()[i];
113             if (
114                 !(c >= '0' && c <= '9') &&
115                 !(c >= 'a' && c <= 'z') &&
116                 !(c >= 'A' && c <= 'Z') &&
117                 !(c == '!') && !(c == '#') && !(c == '$') && !(c == '%') &&
118                 !(c == '&') && !(c == '(') && !(c == ')') && !(c == '*') &&
119                 !(c == '+') && !(c == ',') && !(c == '-') && !(c == '.') &&
120                 !(c == ':') && !(c == ';') && !(c == '<') && !(c == '=') &&
121                 !(c == '>') && !(c == '?') && !(c == '@') && !(c == '[') &&
122                 !(c == ']') && !(c == '^') && !(c == '_') && !(c == '`') &&
123                 !(c == '{') && !(c == '|') && !(c == '}') && !(c == '~')
124             ) {
125                 // convert the "special" character into a "\xHH" LSCP escape sequence
126                 char buf[5];
127                 snprintf(buf, sizeof(buf), "\\x%02x", static_cast<unsigned char>(c));
128                 e.replace(i, 1, buf);
129                 i += 3;
130             }
131         }
132         // append encoded node to full encoded path
133         result += "/" + e;
134     }
135     if (!result.size()) result = "/";
136     return result;
137 }
138 
toWindows() const139 std::string Path::toWindows() const {
140     std::stringstream result;
141 	const char cDrive =
142 	    ((drive >= 'A' && drive <= 'Z') || (drive >= 'a' && drive <= 'z'))
143             ? drive : '?';
144     result << cDrive;
145     result << ':';
146     for (int iElement = 0; iElement < elements.size(); iElement++) {
147         // append encoded node to full encoded path
148         result << "\\" << elements[iElement];
149     }
150     if (elements.empty()) result << '\\';
151     return result.str();
152 }
153 
operator +(const Path & p)154 Path Path::operator+(const Path& p) {
155     Path result = *this;
156     for (int i = 0; i < p.elements.size(); i++)
157         result.elements.push_back(p.elements[i]);
158     return result;
159 }
160 
operator +(const Path * p)161 Path Path::operator+(const Path* p) {
162     return *this + *p;
163 }
164 
fromUnknownFS(std::string path)165 Path Path::fromUnknownFS(std::string path) {
166     bool hasDrive = false;
167     int nSlash = 0, nBackSlash = 0;
168 
169     if (path.length() >= 2)
170         hasDrive = (path[1] == ':');
171 
172     for (size_t i = 0; i < path.size(); ++i) {
173         if (path[i] == '/')  nSlash++;
174         if (path[i] == '\\') nBackSlash++;
175     }
176 
177     if (!hasDrive && nSlash > nBackSlash)
178         return Path::fromPosix(path);
179     else if (hasDrive || nBackSlash > nSlash)
180         return Path::fromWindows(path);
181     else
182         return Path(path); // expect local file system encoding
183 }
184 
fromPosix(std::string path)185 Path Path::fromPosix(std::string path) {
186     Path result;
187     // first split the nodes
188     {
189         int nodeEnd;
190         for (
191             int nodeBegin = (int)path.find_first_not_of('/');
192             nodeBegin != std::string::npos;
193             nodeBegin = (int)path.find_first_not_of('/', nodeEnd)
194         ) {
195             nodeEnd = (int)path.find_first_of('/', nodeBegin);
196             result.appendNode(
197                 (nodeEnd != std::string::npos) ?
198                     path.substr(nodeBegin, nodeEnd - nodeBegin) :
199                     path.substr(nodeBegin)
200             );
201         }
202     }
203     // resolve POSIX escape sequences in all nodes
204     for (int iNode = 0; iNode < result.elements.size(); iNode++) {
205         std::string& s = result.elements[iNode];
206         for (size_t pos = s.find('%'); pos < s.length(); pos = s.find('%', ++pos)) {
207             if (pos+1 >= s.length()) { // unexpected character
208                 //TODO: we might want to throw an exception here, for now we simply replace the character by '?'
209                 s.replace(pos, 1, "?");
210                 continue;
211             }
212             if (s.c_str()[pos+1] == '%') {
213                 s.replace(pos, 2, "%");
214                 continue;
215             }
216             if (pos+2 >= s.length()) {
217                 //TODO: we might want to throw an exception here, for now we simply replace the character by '?'
218                 s.replace(pos, 2, "?");
219                 continue;
220             }
221             // expecting a "%HH" sequence here, convert it into the respective character
222             const std::string sHex = s.substr(pos+1, 2);
223             char cAscii = hexsToNumber(sHex.c_str()[1], sHex.c_str()[0]);
224             char pcAscii[] = { cAscii, 0 };
225             s.replace(pos, 3, pcAscii);
226         }
227     }
228     // check whether given string reflects an absolute path
229     // (indicated by a forward slash as first character on POSIX)
230     result.absolute = !path.empty() && path[0] == '/';
231     return result;
232 }
233 
fromDbPath(std::string path)234 Path Path::fromDbPath(std::string path) {
235     Path result;
236     // first split the nodes
237     {
238         int nodeEnd;
239         for (
240             int nodeBegin = (int)path.find_first_not_of('/');
241             nodeBegin != std::string::npos;
242             nodeBegin = (int)path.find_first_not_of('/', nodeEnd)
243         ) {
244             nodeEnd = (int)path.find_first_of('/', nodeBegin);
245 
246             std::string s = (nodeEnd != std::string::npos) ?
247                 path.substr(nodeBegin, nodeEnd - nodeBegin) :
248                 path.substr(nodeBegin);
249 
250             for (int i = 0; i < s.length(); i++) if (s.at(i) == '\0') s.at(i) = '/';
251             result.appendNode(s);
252         }
253     }
254     return result;
255 }
256 
fromWindows(std::string path)257 Path Path::fromWindows(std::string path) {
258     Path result;
259 
260     int nodeEnd = 0;
261 
262     // first retrieve drive
263     if (path.size() >= 2 && path.c_str()[1] == ':') {
264         result.setDrive(path.c_str()[0]);
265         nodeEnd = 2;
266     }
267 
268     // split the nodes
269     {
270         for (
271             int nodeBegin = (int)path.find_first_not_of('\\', nodeEnd);
272             nodeBegin != std::string::npos;
273             nodeBegin = (int)path.find_first_not_of('\\', nodeEnd)
274         ) {
275             nodeEnd = (int)path.find_first_of('\\', nodeBegin);
276             result.appendNode(
277                 (nodeEnd != std::string::npos) ?
278                     path.substr(nodeBegin, nodeEnd - nodeBegin) :
279                     path.substr(nodeBegin)
280             );
281         }
282     }
283 
284     // check whether given string reflects an absolute path
285     // (indicated either by a backslash or drive at the beginning on Windows)
286     result.absolute = result.drive || (!path.empty() && path[0] == '\\');
287 
288     return result;
289 }
290 
getName(std::string path)291 std::string Path::getName(std::string path) {
292     Path p;
293     #if WIN32
294     p.fromWindows(path);
295     #else
296     p.fromPosix(path);
297     #endif
298 
299     return p.getName();
300 }
301 
getName() const302 std::string Path::getName() const {
303     if(elements.empty()) return "";
304     return elements[elements.size() - 1];
305 }
306 
getBaseName(std::string path)307 std::string Path::getBaseName(std::string path) {
308     Path p;
309     #if WIN32
310     p = fromWindows(path);
311     #else
312     p = fromPosix(path);
313     #endif
314 
315     return p.getBaseName();
316 }
317 
getBaseName() const318 std::string Path::getBaseName() const {
319     std::string name = getName();
320     size_t lastdot = name.find_last_of('.');
321     if(lastdot == std::string::npos) return name;
322     return name.substr(0, lastdot);
323 }
324 
stripLastName()325 std::string Path::stripLastName() {
326     if (elements.size() > 0) elements.pop_back();
327     #if WIN32
328     return toWindows();
329     #else
330     return toPosix();
331     #endif
332 }
333 
stripLastName(std::string path)334 std::string Path::stripLastName(std::string path) {
335     Path p;
336     #if WIN32
337     p = fromWindows(path);
338     #else
339     p = fromPosix(path);
340     #endif
341 
342     return p.stripLastName();
343 }
344 
isAbsolute() const345 bool Path::isAbsolute() const {
346     return absolute;
347 }
348 
nodes()349 std::vector<std::string>& Path::nodes() {
350     return elements;
351 }
352 
353 } // namespace LinuxSampler
354