1 // qsamplerUtilities.cpp
2 //
3 /****************************************************************************
4    Copyright (C) 2004-2020, rncbc aka Rui Nuno Capela. All rights reserved.
5    Copyright (C) 2007, 2008 Christian Schoenebeck
6 
7    This program is free software; you can redistribute it and/or
8    modify it under the terms of the GNU General Public License
9    as published by the Free Software Foundation; either version 2
10    of the License, or (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 along
18    with this program; if not, write to the Free Software Foundation, Inc.,
19    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 
21 *****************************************************************************/
22 
23 #include "qsamplerUtilities.h"
24 
25 #include "qsamplerOptions.h"
26 #include "qsamplerMainForm.h"
27 
28 #include <QRegularExpression>
29 
30 
31 using namespace QSampler;
32 
33 namespace qsamplerUtilities {
34 
_hexToNumber(char hex_digit)35 static int _hexToNumber ( char hex_digit )
36 {
37 	switch (hex_digit) {
38 		case '0': return 0;
39 		case '1': return 1;
40 		case '2': return 2;
41 		case '3': return 3;
42 		case '4': return 4;
43 		case '5': return 5;
44 		case '6': return 6;
45 		case '7': return 7;
46 		case '8': return 8;
47 		case '9': return 9;
48 
49 		case 'a': return 10;
50 		case 'b': return 11;
51 		case 'c': return 12;
52 		case 'd': return 13;
53 		case 'e': return 14;
54 		case 'f': return 15;
55 
56 		case 'A': return 10;
57 		case 'B': return 11;
58 		case 'C': return 12;
59 		case 'D': return 13;
60 		case 'E': return 14;
61 		case 'F': return 15;
62 
63 		default:  return 0;
64 	}
65 }
66 
_hexsToNumber(char hex0,char hex1)67 static int _hexsToNumber ( char hex0, char hex1 )
68 {
69 	return _hexToNumber(hex1) * 16 + _hexToNumber(hex0);
70 }
71 
_isHex(char hex_digit)72 static bool _isHex ( char hex_digit )
73 {
74 	return _hexToNumber ( hex_digit ) || hex_digit == '0';
75 }
76 
77 // returns true if the connected LSCP server supports escape sequences
_remoteSupportsEscapeSequences(void)78 static bool _remoteSupportsEscapeSequences (void)
79 {
80 	const lscpVersion_t version = getRemoteLscpVersion();
81 	// LSCP v1.2 or younger required
82 	return (version.major > 1 || (version.major == 1 && version.minor >= 2));
83 }
84 
85 
86 // converts the given file path into a path as expected by LSCP 1.2
lscpEscapePath(const QString & sPath)87 QByteArray lscpEscapePath ( const QString& sPath )
88 {
89 	QByteArray path = sPath.toUtf8();
90 	if (!_remoteSupportsEscapeSequences()) return path;
91 
92         const char pathSeparator = '/';
93 	int path_len = path.length();
94 	char buf[5];
95 
96 	// Trying single pass to avoid redundant checks on extra run
97 	for ( int i = 0; i < path_len; i++ ) {
98 		// translate POSIX escape sequences
99 		if (path[i] == '%') {
100 			// replace POSIX path escape sequences (%HH) by LSCP escape sequences (\xHH)
101 			// TODO: missing code for other systems like Windows
102 			if (_isHex(path[i+1]) && _isHex(path[i+2])) {
103 				path.replace (i, 1, "\\x");
104 				path_len++;
105 				i += 3;
106 				continue;
107 			}
108 			// replace POSIX path escape sequence (%%) by its raw character
109 			if (path[i+1] == '%') {
110 				path.remove (i, 1);
111 				path_len--;
112 				continue;
113 			}
114 			continue;
115 		}
116 		// replace all non-basic characters by LSCP escape sequences
117 		//
118 		// match all non-alphanumerics
119 		// (we could exclude much more characters here, but that way
120 		// we're sure it just works^TM)
121 		const char c = path[i];
122 		if (
123 			!(c >= '0' && c <= '9') &&
124 			!(c >= 'a' && c <= 'z') &&
125 			!(c >= 'A' && c <= 'Z') &&
126 		#if defined(__WIN32__) || defined(_WIN32) || defined(WIN32)
127 			!(c == ':') &&
128 		#endif
129 			!(c == pathSeparator)
130 		) {
131 			// convertion
132 			::snprintf(buf, sizeof(buf), "\\x%02x", static_cast<unsigned char>(c));
133 			path = path.replace(i, 1, buf);
134 			path_len += 3;
135 			i += 3;
136 		}
137 	}
138 
139 	return path;
140 }
141 
142 
143 // converts a path returned by a LSCP command (and may contain escape
144 // sequences) into the appropriate POSIX path
lscpEscapedPathToPosix(const char * sPath)145 QString lscpEscapedPathToPosix ( const char* sPath )
146 {
147 	if (!_remoteSupportsEscapeSequences()) return QString(sPath);
148 
149 	QByteArray path(sPath);
150 	int path_len = path.length();
151 
152 	char cAscii[2] = "\0";
153 	for ( int i = 0; i < path_len; i++) {
154 		// first escape all percent ('%') characters for POSIX
155 		if (path[i] == '%') {
156 			path.insert(i, '%');
157 			path_len++;
158 			i++;
159 			continue;
160 		}
161 		// resolve LSCP hex escape sequences (\xHH)
162 		if (path[i] == '\\' && path[i+1] == 'x' && _isHex(path[i+2]) && _isHex(path[i+3])) {
163 			const QByteArray sHex = path.mid(i + 2, 2).toLower();
164 			// the slash has to be escaped for POSIX as well
165 			if (sHex == "2f") {
166 				path.replace(i, 4, "%2f");
167 			// all other characters we simply decode
168 			} else {
169 				cAscii[0] = _hexsToNumber(sHex[1], sHex[0]);
170 				path.replace(i, 4, cAscii);
171 			}
172 			path_len -= 3;
173 			continue;
174 		}
175 	}
176 
177 	return QString(path);
178 }
179 
180 
181 // converts the given text as expected by LSCP 1.2
182 // (that is by encoding special characters with LSCP escape sequences)
lscpEscapeText(const QString & sText)183 QByteArray lscpEscapeText ( const QString& sText )
184 {
185 	QByteArray text = sText.toUtf8();
186 	if (!_remoteSupportsEscapeSequences()) return text;
187 
188 	int text_len = text.length();
189 	char buf[5];
190 
191 	// replace all non-basic characters by LSCP escape sequences
192 	for (int i = 0; i < text_len; ++i) {
193 		// match all non-alphanumerics
194 		// (we could exclude much more characters here, but that way
195 		// we're sure it just works^TM)
196 		const char c = text[i];
197 		if (
198 			!(c >= '0' && c <= '9') &&
199 			!(c >= 'a' && c <= 'z') &&
200 			!(c >= 'A' && c <= 'Z')
201 		) {
202 			// convert the non-basic character into a LSCP escape sequence
203 			::snprintf(buf, sizeof(buf), "\\x%02x", static_cast<unsigned char>(c));
204 			text.replace(i, 1, buf);
205 			text_len += 3;
206 			i += 3;
207 		}
208 	}
209 
210 	return text;
211 }
212 
213 
214 // converts a text returned by a LSCP command and may contain escape
215 // sequences) into raw text, that is with all escape sequences decoded
lscpEscapedTextToRaw(const char * sText)216 QString lscpEscapedTextToRaw ( const char* sText )
217 {
218 	if (!_remoteSupportsEscapeSequences()) return QString(sText);
219 
220 	QByteArray text(sText);
221 	int text_len = text.length();
222 	char sHex[2], cAscii[2] = "\0";
223 
224 	// resolve LSCP hex escape sequences (\xHH)
225 	for (int i = 0; i < text_len; i++) {
226 		if (text[i] != '\\' || text[i+1] != 'x') continue;
227 
228 		sHex[0] = text[i+2], sHex[1] = text[i+3];
229 		if (_isHex(sHex[0]) && _isHex(sHex[1])) {
230 			cAscii[0] = _hexsToNumber(sHex[1], sHex[0]);
231 			text.replace(i, 4, cAscii);
232 			text_len -= 3;
233 		}
234 	}
235 
236 	return QString(text);
237 }
238 
getRemoteLscpVersion(void)239 lscpVersion_t getRemoteLscpVersion (void)
240 {
241     lscpVersion_t result = { 0, 0 };
242 
243     MainForm* pMainForm = MainForm::getInstance();
244     if (pMainForm == nullptr)
245         return result;
246     if (pMainForm->client() == nullptr)
247         return result;
248 
249     lscp_server_info_t* pServerInfo =
250         ::lscp_get_server_info(pMainForm->client());
251     if (pServerInfo && pServerInfo->protocol_version)
252         ::sscanf(pServerInfo->protocol_version, "%d.%d",
253             &result.major, &result.minor);
254 
255     return result;
256 }
257 
258 } // namespace qsamplerUtilities
259 
260 
261 // end of qsamplerUtilities.cpp
262