1 /**************************************************************************
2 *   Copyright (C) 2005-2020 by Oleksandr Shneyder                         *
3 *                              <o.shneyder@phoca-gmbh.de>                 *
4 *   Copyright (C) 2016-2020 by Mihai Moldovan <ionic@ionic.de>            *
5 *                                                                         *
6 *   This program is free software; you can redistribute it and/or modify  *
7 *   it under the terms of the GNU General Public License as published by  *
8 *   the Free Software Foundation; either version 2 of the License, or     *
9 *   (at your option) any later version.                                   *
10 *   This program 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 program.  If not, see <https://www.gnu.org/licenses/>. *
17 ***************************************************************************/
18 
19 #include <vector>
20 #include <algorithm>
21 #include <QString>
22 #include <QDir>
23 #include <QMessageBox>
24 #include <QFont>
25 #include <QFontInfo>
26 #include <QObject>
27 #include <QStringList>
28 
29 #include "x2goutils.h"
30 #include "onmainwindow.h"
31 #include "x2gologdebug.h"
32 
expandHome(QString path)33 QString expandHome (QString path) {
34   path = path.trimmed ();
35   if (path.startsWith ("~/") || path.startsWith ("~\\")) {
36       path = path.replace (QString ("~"), QDir::homePath ());
37   }
38   return path;
39 }
40 
fixup_resource_URIs(const QString & res_path)41 QString fixup_resource_URIs (const QString &res_path) {
42   QString ret (res_path);
43 
44   if (!(res_path.isEmpty ())) {
45     if (ret.at (1) != '/') {
46       ret.insert (1, '/');
47     }
48   }
49 
50   return (ret);
51 }
52 
wrap_legacy_resource_URIs(const QString & res_path)53 QString wrap_legacy_resource_URIs (const QString &res_path) {
54   QString ret (res_path);
55 
56   /*
57    * Skip empty or non-resource URIs.
58    * Assume all paths not starting with a colon are
59    * absolute, non-resource URIs.
60    */
61   if ((!(res_path.isEmpty ())) && (res_path.startsWith (':'))) {
62     std::vector<QString> legacy_locations;
63     legacy_locations.push_back (QString (":/icons/"));
64     legacy_locations.push_back (QString (":/png/"));
65     legacy_locations.push_back (QString (":/svg/"));
66 
67     ret = fixup_resource_URIs (ret);
68 
69     bool detected = false;
70 
71     /* This would be so much easier with C++ and lambdas... */
72     std::vector<QString>::const_iterator it = legacy_locations.begin ();
73     while (it != legacy_locations.end ()) {
74       if (ret.startsWith (*(it++))) {
75         detected = true;
76         break;
77       }
78     }
79 
80     if (detected)
81       ret.insert (1, QString ("/img"));
82   }
83 
84   return (ret);
85 }
86 
convert_to_rich_text(const QString & text,bool force)87 QString convert_to_rich_text (const QString &text, bool force) {
88   QString fixup_text (text);
89   fixup_text.replace ("\n", "\n<br />\n");
90 
91   if (force) {
92     // This is a workaround for a bug in Qt. Even though we set Qt::RichText as the text format
93     // later on, the informative text is not recognized as rich text, UNLESS a HTML tag
94     // is used ON THE VERY FIRST LINE.
95     // Make sure, that there always is one...
96     fixup_text.prepend ("<b></b>");
97   }
98 
99   return (fixup_text);
100 }
101 
show_RichText_Generic_MsgBox(QMessageBox::Icon icon,const QString & main_text,const QString & informative_text,bool app_modal)102 void show_RichText_Generic_MsgBox (QMessageBox::Icon icon, const QString &main_text, const QString &informative_text, bool app_modal) {
103   QString fixup_main_text (convert_to_rich_text (main_text));
104   QString fixup_informative_text (convert_to_rich_text (informative_text, true));
105 
106   QMessageBox msg_box (icon, QString ("X2Go Client"), fixup_main_text, QMessageBox::Ok);
107 
108   msg_box.setTextFormat (Qt::RichText);
109   msg_box.setInformativeText (fixup_informative_text);
110 
111   if (app_modal) {
112     msg_box.setWindowModality (Qt::ApplicationModal);
113   }
114   else {
115     msg_box.setWindowModality (Qt::WindowModal);
116   }
117 
118   msg_box.exec ();
119 }
120 
show_RichText_WarningMsgBox(const QString & main_text,const QString & informative_text,bool app_modal)121 void show_RichText_WarningMsgBox (const QString &main_text, const QString &informative_text, bool app_modal) {
122   show_RichText_Generic_MsgBox (QMessageBox::Warning, main_text, informative_text, app_modal);
123 }
124 
show_RichText_ErrorMsgBox(const QString & main_text,const QString & informative_text,bool app_modal)125 void show_RichText_ErrorMsgBox (const QString &main_text, const QString &informative_text, bool app_modal) {
126   show_RichText_Generic_MsgBox (QMessageBox::Critical, main_text, informative_text, app_modal);
127 }
128 
git_changelog_extract_commit_sha(const QString & gitlog)129 QString git_changelog_extract_commit_sha (const QString &gitlog) {
130   QString ret = "";
131 
132   /*
133    * Do a poor man's split.
134    * We know that a newline character should be somewhere at the beginning of the string.
135    * We don't need to have Qt split the string up completely as we only care about
136    * a substring: from start to the first newline character.
137    */
138   std::ptrdiff_t pos = gitlog.indexOf ("\n");
139 
140   if (0 < pos) {
141     ret = gitlog.left (pos + 1);
142 
143     x2goDebug << "First line of git changelog: " << ret;
144 
145     pos = ret.lastIndexOf (")");
146 
147     if (0 < pos) {
148       std::ptrdiff_t pos_paren_start = ret.lastIndexOf ("(");
149 
150       if ((0 < pos_paren_start) && (pos_paren_start < pos)) {
151         ret = ret.mid (pos_paren_start + 1, pos - pos_paren_start - 1);
152       }
153       else {
154         // Either starting parenthesis not found or starting parenthesis comes first.
155         ret = "";
156       }
157     }
158     else {
159       // End parenthesis not found.
160       ret = "";
161     }
162   }
163 
164   return (ret);
165 }
166 
font_is_monospaced(const QFont & font)167 bool font_is_monospaced (const QFont &font) {
168   const QFontInfo font_info (font);
169   return (font_info.fixedPitch ());
170 }
171 
172 #ifdef Q_OS_DARWIN
show_XQuartz_not_found_error()173 void show_XQuartz_not_found_error () {
174   show_XQuartz_generic_error (QObject::tr ("X2Go Client could not find any suitable X11 server."),
175                               QString ());
176 }
177 
show_XQuartz_start_error()178 void show_XQuartz_start_error () {
179   show_XQuartz_generic_error (QObject::tr ("X2Go Client could not start X11 server."),
180                               QObject::tr ("X2Go Client requires XQuartz to be installed.\n\n"
181                                            "If XQuartz is already installed on your system,\n"
182                                            "please select the correct path in the now upcoming dialog.\n"
183                                            "Refer to the end of this message for path examples,\n"
184                                            "in case you do not know the exact location yourself.\n\n"
185                                            "Should you have <b>not</b> installed XQuartz yet, please\n"
186                                            "follow the outlined steps:\n\n"));
187 }
188 
show_XQuartz_generic_error(const QString & main_error,const QString & additional_info)189 void show_XQuartz_generic_error (const QString &main_error, const QString &additional_info) {
190   show_RichText_WarningMsgBox (main_error,
191                                additional_info +
192                                QObject::tr ("MacPorts users, please install either the port <b>xorg-server</b>\n"
193                                             "or the port <b>xorg-server-devel</b>.\n"
194                                             "Upon successful installation, please follow the instructions printed\n"
195                                             "by the port utility to autostart/load the server.\n\n"
196 
197                                             "All other users, please obtain and install XQuartz from:\n"
198 
199                                             "<center><a href=\"https://www.xquartz.org/\">"
200                                                 "https://www.xquartz.org/"
201                                             "</a></center>\n\n"
202 
203                                             "Afterwards, restart X2Go Client and select the correct path\n"
204                                             "to the X11 application in the general X2Go Client settings.\n"
205                                             "This will most likely be\n"
206                                             "<center><b>/Applications/MacPorts/X11.app</b></center>\n"
207                                             "or\n"
208                                             "<center><b>/Applications/Utilities/XQuartz.app</b></center>"));
209 }
210 #endif /* defined (Q_OS_DARWIN) */
211 
212 #ifdef Q_OS_UNIX
add_to_path(const QString & orig_path,const QStringList & add,const bool back)213 QString add_to_path (const QString &orig_path, const QStringList &add, const bool back) {
214   QString ret = orig_path;
215   std::vector<bool> found;
216 
217   QStringList orig_path_list = orig_path.split (":");
218 
219   x2goDebug << "path value at beginning: " << orig_path;
220 
221   /*
222    * Clean up add list. We want to make sure no entry ends in a slash
223    * and skip empty entries.
224    */
225   QStringList tmp_clean_add;
226   for (int i = 0; i < add.size (); ++i) {
227     if (!(add[i].isEmpty ())) {
228       if (add[i].right (1) == "/") {
229         QString tmp_elem = add[i];
230         tmp_elem.chop (1);
231 
232         if (!(tmp_elem.isEmpty ())) {
233           tmp_clean_add.append (tmp_elem);
234         }
235       }
236       else {
237         tmp_clean_add.append (add[i]);
238       }
239     }
240   }
241 
242   x2goDebug << "tmp_clean_add: " << tmp_clean_add;
243 
244   /* Nothing to add, really... */
245   if (tmp_clean_add.isEmpty ()) {
246     return (ret);
247   }
248 
249   /* Create unique array. */
250   QStringList clean_add;
251   {
252     QStringList::const_iterator begin = tmp_clean_add.constBegin (),
253                                 end = tmp_clean_add.constEnd ();
254     for (QStringList::const_iterator cit = begin; cit != end; ++cit) {
255       bool tmp_found = false;
256 
257       for (QStringList::const_iterator cit2 = cit + 1; cit2 != end; ++cit2) {
258         if (*cit == *cit2) {
259           tmp_found = true;
260           break;
261         }
262       }
263 
264       if (!tmp_found) {
265         clean_add.append (*cit);
266       }
267     }
268   }
269 
270   x2goDebug << "clean_add: " << clean_add;
271 
272   /* Nothing to add. */
273   if (clean_add.isEmpty ()) {
274     return (ret);
275   }
276 
277   found.resize (clean_add.size (), false);
278 
279   for (int i = 0; i < orig_path_list.length (); ++i) {
280     for (int y = 0; y < clean_add.size (); ++y) {
281       if (!found[y]) {
282         if ((orig_path_list[i] == QString (clean_add[y])) || (orig_path_list[i] == QString (clean_add[y] + '/'))) {
283           found[y] = true;
284           break;
285         }
286       }
287     }
288   }
289 
290   for (std::size_t i = 0; i < found.size (); ++i) {
291     x2goDebug << "found entry i (" << i << ") in orig_path_list (" << clean_add[i] << "): " << found[i];
292   }
293 
294   if (back) {
295     for (int i = 0; i < clean_add.size (); ++i) {
296       if (!found[i]) {
297         ret.append (QString (":" + clean_add[i]));
298       }
299     }
300   }
301   else {
302     for (int i = (clean_add.size () - 1); i >= 0; --i) {
303       if (!found[i]) {
304         ret.prepend (QString (clean_add[i] + ":"));
305       }
306     }
307   }
308 
309   x2goDebug << "return value at end: " << ret;
310 
311   return (ret);
312 }
313 
find_binary(const QString & path,const QString & binary_name)314 QString find_binary (const QString &path, const QString &binary_name) {
315   QString ret = "";
316 
317   if (!(binary_name.isEmpty ())) {
318     QString cur_path = "";
319     QString tmp_path = path;
320 
321     if (path.isEmpty ()) {
322       tmp_path = "./";
323     }
324 
325     QStringList path_list = tmp_path.split (":");
326 
327     for (QStringList::const_iterator const_it = path_list.constBegin (); const_it != path_list.constEnd (); ++const_it) {
328       cur_path = *const_it;
329 
330       if (cur_path.isEmpty ()) {
331         cur_path = "./";
332       }
333 
334       cur_path = QDir (cur_path).absolutePath ();
335 
336       cur_path += "/" + binary_name;
337 
338       QFileInfo tmp_file_info = QFileInfo (cur_path);
339 
340       if ((tmp_file_info.exists ()) && (tmp_file_info.isExecutable ())) {
341         ret = tmp_file_info.canonicalFilePath ();
342         break;
343       }
344       else {
345         x2goDebug << "Binary at " << cur_path << " either does not exist (" << !(tmp_file_info.exists ())
346                   << ") or is not executable (" << tmp_file_info.isExecutable () << ")";
347       }
348     }
349   }
350 
351   return (ret);
352 }
353 #endif /* defined (Q_OS_UNIX) */
354