1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
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, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 *
19 * In addition, as a special exception, the copyright holders give permission to
20 * link this program with the OpenSSL project's "OpenSSL" library (or with
21 * modified versions of it that use the same license as the "OpenSSL" library),
22 * and distribute the linked executables. You must obey the GNU General Public
23 * License in all respects for all of the code used other than "OpenSSL". If you
24 * modify file(s), you may extend this exception to your version of the file(s),
25 * but you are not obligated to do so. If you do not wish to do so, delete this
26 * exception statement from your version.
27 */
28
29 #include "misc.h"
30
31 #include <optional>
32
33 #ifdef Q_OS_WIN
34 #include <windows.h>
35 #include <powrprof.h>
36 #include <Shlobj.h>
37 #else
38 #include <sys/types.h>
39 #include <unistd.h>
40 #endif
41
42 #ifdef Q_OS_MACOS
43 #include <Carbon/Carbon.h>
44 #include <CoreServices/CoreServices.h>
45 #endif
46
47 #include <boost/version.hpp>
48 #include <libtorrent/version.hpp>
49 #include <openssl/crypto.h>
50 #include <openssl/opensslv.h>
51 #include <zlib.h>
52
53 #include <QCoreApplication>
54 #include <QMimeDatabase>
55 #include <QRegularExpression>
56 #include <QSet>
57 #include <QSysInfo>
58 #include <QVector>
59
60 #if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) && defined(QT_DBUS_LIB)
61 #include <QDBusInterface>
62 #endif
63
64 #include "base/types.h"
65 #include "base/unicodestrings.h"
66 #include "base/utils/fs.h"
67 #include "base/utils/string.h"
68
69 namespace
70 {
71 const struct { const char *source; const char *comment; } units[] =
72 {
73 QT_TRANSLATE_NOOP3("misc", "B", "bytes"),
74 QT_TRANSLATE_NOOP3("misc", "KiB", "kibibytes (1024 bytes)"),
75 QT_TRANSLATE_NOOP3("misc", "MiB", "mebibytes (1024 kibibytes)"),
76 QT_TRANSLATE_NOOP3("misc", "GiB", "gibibytes (1024 mibibytes)"),
77 QT_TRANSLATE_NOOP3("misc", "TiB", "tebibytes (1024 gibibytes)"),
78 QT_TRANSLATE_NOOP3("misc", "PiB", "pebibytes (1024 tebibytes)"),
79 QT_TRANSLATE_NOOP3("misc", "EiB", "exbibytes (1024 pebibytes)")
80 };
81
82 // return best userfriendly storage unit (B, KiB, MiB, GiB, TiB, ...)
83 // use Binary prefix standards from IEC 60027-2
84 // see http://en.wikipedia.org/wiki/Kilobyte
85 // value must be given in bytes
86 // to send numbers instead of strings with suffixes
87 struct SplitToFriendlyUnitResult
88 {
89 qreal value;
90 Utils::Misc::SizeUnit unit;
91 };
92
splitToFriendlyUnit(const qint64 bytes)93 std::optional<SplitToFriendlyUnitResult> splitToFriendlyUnit(const qint64 bytes)
94 {
95 if (bytes < 0)
96 return std::nullopt;
97
98 int i = 0;
99 auto value = static_cast<qreal>(bytes);
100
101 while ((value >= 1024) && (i < static_cast<int>(Utils::Misc::SizeUnit::ExbiByte)))
102 {
103 value /= 1024;
104 ++i;
105 }
106 return {{value, static_cast<Utils::Misc::SizeUnit>(i)}};
107 }
108 }
109
shutdownComputer(const ShutdownDialogAction & action)110 void Utils::Misc::shutdownComputer(const ShutdownDialogAction &action)
111 {
112 #if defined(Q_OS_WIN)
113 HANDLE hToken; // handle to process token
114 TOKEN_PRIVILEGES tkp; // pointer to token structure
115 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
116 return;
117 // Get the LUID for shutdown privilege.
118 LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,
119 &tkp.Privileges[0].Luid);
120
121 tkp.PrivilegeCount = 1; // one privilege to set
122 tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
123
124 // Get shutdown privilege for this process.
125
126 AdjustTokenPrivileges(hToken, FALSE, &tkp, 0,
127 (PTOKEN_PRIVILEGES) NULL, 0);
128
129 // Cannot test the return value of AdjustTokenPrivileges.
130
131 if (GetLastError() != ERROR_SUCCESS)
132 return;
133
134 if (action == ShutdownDialogAction::Suspend)
135 {
136 ::SetSuspendState(false, false, false);
137 }
138 else if (action == ShutdownDialogAction::Hibernate)
139 {
140 ::SetSuspendState(true, false, false);
141 }
142 else
143 {
144 const QString msg = QCoreApplication::translate("misc", "qBittorrent will shutdown the computer now because all downloads are complete.");
145 auto msgWchar = std::make_unique<wchar_t[]>(msg.length() + 1);
146 msg.toWCharArray(msgWchar.get());
147 ::InitiateSystemShutdownW(nullptr, msgWchar.get(), 10, true, false);
148 }
149
150 // Disable shutdown privilege.
151 tkp.Privileges[0].Attributes = 0;
152 AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES) NULL, 0);
153
154 #elif defined(Q_OS_MACOS)
155 AEEventID EventToSend;
156 if (action != ShutdownDialogAction::Shutdown)
157 EventToSend = kAESleep;
158 else
159 EventToSend = kAEShutDown;
160 AEAddressDesc targetDesc;
161 const ProcessSerialNumber kPSNOfSystemProcess = {0, kSystemProcess};
162 AppleEvent eventReply = {typeNull, NULL};
163 AppleEvent appleEventToSend = {typeNull, NULL};
164
165 OSStatus error = AECreateDesc(typeProcessSerialNumber, &kPSNOfSystemProcess,
166 sizeof(kPSNOfSystemProcess), &targetDesc);
167
168 if (error != noErr)
169 return;
170
171 error = AECreateAppleEvent(kCoreEventClass, EventToSend, &targetDesc,
172 kAutoGenerateReturnID, kAnyTransactionID, &appleEventToSend);
173
174 AEDisposeDesc(&targetDesc);
175 if (error != noErr)
176 return;
177
178 error = AESend(&appleEventToSend, &eventReply, kAENoReply,
179 kAENormalPriority, kAEDefaultTimeout, NULL, NULL);
180
181 AEDisposeDesc(&appleEventToSend);
182 if (error != noErr)
183 return;
184
185 AEDisposeDesc(&eventReply);
186
187 #elif (defined(Q_OS_UNIX) && defined(QT_DBUS_LIB))
188 // Use dbus to power off / suspend the system
189 if (action != ShutdownDialogAction::Shutdown)
190 {
191 // Some recent systems use systemd's logind
192 QDBusInterface login1Iface("org.freedesktop.login1", "/org/freedesktop/login1",
193 "org.freedesktop.login1.Manager", QDBusConnection::systemBus());
194 if (login1Iface.isValid())
195 {
196 if (action == ShutdownDialogAction::Suspend)
197 login1Iface.call("Suspend", false);
198 else
199 login1Iface.call("Hibernate", false);
200 return;
201 }
202 // Else, other recent systems use UPower
203 QDBusInterface upowerIface("org.freedesktop.UPower", "/org/freedesktop/UPower",
204 "org.freedesktop.UPower", QDBusConnection::systemBus());
205 if (upowerIface.isValid())
206 {
207 if (action == ShutdownDialogAction::Suspend)
208 upowerIface.call("Suspend");
209 else
210 upowerIface.call("Hibernate");
211 return;
212 }
213 // HAL (older systems)
214 QDBusInterface halIface("org.freedesktop.Hal", "/org/freedesktop/Hal/devices/computer",
215 "org.freedesktop.Hal.Device.SystemPowerManagement",
216 QDBusConnection::systemBus());
217 if (action == ShutdownDialogAction::Suspend)
218 halIface.call("Suspend", 5);
219 else
220 halIface.call("Hibernate");
221 }
222 else
223 {
224 // Some recent systems use systemd's logind
225 QDBusInterface login1Iface("org.freedesktop.login1", "/org/freedesktop/login1",
226 "org.freedesktop.login1.Manager", QDBusConnection::systemBus());
227 if (login1Iface.isValid())
228 {
229 login1Iface.call("PowerOff", false);
230 return;
231 }
232 // Else, other recent systems use ConsoleKit
233 QDBusInterface consolekitIface("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager",
234 "org.freedesktop.ConsoleKit.Manager", QDBusConnection::systemBus());
235 if (consolekitIface.isValid())
236 {
237 consolekitIface.call("Stop");
238 return;
239 }
240 // HAL (older systems)
241 QDBusInterface halIface("org.freedesktop.Hal", "/org/freedesktop/Hal/devices/computer",
242 "org.freedesktop.Hal.Device.SystemPowerManagement",
243 QDBusConnection::systemBus());
244 halIface.call("Shutdown");
245 }
246
247 #else
248 Q_UNUSED(action);
249 #endif
250 }
251
unitString(const SizeUnit unit,const bool isSpeed)252 QString Utils::Misc::unitString(const SizeUnit unit, const bool isSpeed)
253 {
254 const auto &unitString = units[static_cast<int>(unit)];
255 QString ret = QCoreApplication::translate("misc", unitString.source, unitString.comment);
256 if (isSpeed)
257 ret += QCoreApplication::translate("misc", "/s", "per second");
258 return ret;
259 }
260
friendlyUnit(const qint64 bytes,const bool isSpeed)261 QString Utils::Misc::friendlyUnit(const qint64 bytes, const bool isSpeed)
262 {
263 const std::optional<SplitToFriendlyUnitResult> result = splitToFriendlyUnit(bytes);
264 if (!result)
265 return QCoreApplication::translate("misc", "Unknown", "Unknown (size)");
266 return Utils::String::fromDouble(result->value, friendlyUnitPrecision(result->unit))
267 + QString::fromUtf8(C_NON_BREAKING_SPACE)
268 + unitString(result->unit, isSpeed);
269 }
270
friendlyUnitPrecision(const SizeUnit unit)271 int Utils::Misc::friendlyUnitPrecision(const SizeUnit unit)
272 {
273 // friendlyUnit's number of digits after the decimal point
274 switch (unit)
275 {
276 case SizeUnit::Byte:
277 return 0;
278 case SizeUnit::KibiByte:
279 case SizeUnit::MebiByte:
280 return 1;
281 case SizeUnit::GibiByte:
282 return 2;
283 default:
284 return 3;
285 }
286 }
287
sizeInBytes(qreal size,const Utils::Misc::SizeUnit unit)288 qlonglong Utils::Misc::sizeInBytes(qreal size, const Utils::Misc::SizeUnit unit)
289 {
290 for (int i = 0; i < static_cast<int>(unit); ++i)
291 size *= 1024;
292 return size;
293 }
294
isPreviewable(const QString & filename)295 bool Utils::Misc::isPreviewable(const QString &filename)
296 {
297 const QString mime = QMimeDatabase().mimeTypeForFile(filename, QMimeDatabase::MatchExtension).name();
298
299 if (mime.startsWith(QLatin1String("audio"), Qt::CaseInsensitive)
300 || mime.startsWith(QLatin1String("video"), Qt::CaseInsensitive))
301 {
302 return true;
303 }
304
305 const QSet<QString> multimediaExtensions =
306 {
307 "3GP",
308 "AAC",
309 "AC3",
310 "AIF",
311 "AIFC",
312 "AIFF",
313 "ASF",
314 "AU",
315 "AVI",
316 "FLAC",
317 "FLV",
318 "M3U",
319 "M4A",
320 "M4P",
321 "M4V",
322 "MID",
323 "MKV",
324 "MOV",
325 "MP2",
326 "MP3",
327 "MP4",
328 "MPC",
329 "MPE",
330 "MPEG",
331 "MPG",
332 "MPP",
333 "OGG",
334 "OGM",
335 "OGV",
336 "QT",
337 "RA",
338 "RAM",
339 "RM",
340 "RMV",
341 "RMVB",
342 "SWA",
343 "SWF",
344 "TS",
345 "VOB",
346 "WAV",
347 "WMA",
348 "WMV"
349 };
350 return multimediaExtensions.contains(Utils::Fs::fileExtension(filename).toUpper());
351 }
352
userFriendlyDuration(const qlonglong seconds,const qlonglong maxCap)353 QString Utils::Misc::userFriendlyDuration(const qlonglong seconds, const qlonglong maxCap)
354 {
355 if (seconds < 0)
356 return QString::fromUtf8(C_INFINITY);
357 if ((maxCap >= 0) && (seconds >= maxCap))
358 return QString::fromUtf8(C_INFINITY);
359
360 if (seconds == 0)
361 return "0";
362
363 if (seconds < 60)
364 return QCoreApplication::translate("misc", "< 1m", "< 1 minute");
365
366 qlonglong minutes = (seconds / 60);
367 if (minutes < 60)
368 return QCoreApplication::translate("misc", "%1m", "e.g: 10minutes").arg(QString::number(minutes));
369
370 qlonglong hours = (minutes / 60);
371 if (hours < 24)
372 {
373 minutes -= (hours * 60);
374 return QCoreApplication::translate("misc", "%1h %2m", "e.g: 3hours 5minutes").arg(QString::number(hours), QString::number(minutes));
375 }
376
377 qlonglong days = (hours / 24);
378 if (days < 365)
379 {
380 hours -= (days * 24);
381 return QCoreApplication::translate("misc", "%1d %2h", "e.g: 2days 10hours").arg(QString::number(days), QString::number(hours));
382 }
383
384 qlonglong years = (days / 365);
385 days -= (years * 365);
386 return QCoreApplication::translate("misc", "%1y %2d", "e.g: 2years 10days").arg(QString::number(years), QString::number(days));
387 }
388
getUserIDString()389 QString Utils::Misc::getUserIDString()
390 {
391 QString uid = "0";
392 #ifdef Q_OS_WIN
393 const int UNLEN = 256;
394 WCHAR buffer[UNLEN + 1] = {0};
395 DWORD buffer_len = sizeof(buffer) / sizeof(*buffer);
396 if (GetUserNameW(buffer, &buffer_len))
397 uid = QString::fromWCharArray(buffer);
398 #else
399 uid = QString::number(getuid());
400 #endif
401 return uid;
402 }
403
parseHtmlLinks(const QString & rawText)404 QString Utils::Misc::parseHtmlLinks(const QString &rawText)
405 {
406 QString result = rawText;
407 static const QRegularExpression reURL(
408 "(\\s|^)" // start with whitespace or beginning of line
409 "("
410 "(" // case 1 -- URL with scheme
411 "(http(s?))\\://" // start with scheme
412 "([a-zA-Z0-9_-]+\\.)+" // domainpart. at least one of these must exist
413 "([a-zA-Z0-9\\?%=&/_\\.:#;-]+)" // everything to 1st non-URI char, must be at least one char after the previous dot (cannot use ".*" because it can be too greedy)
414 ")"
415 "|"
416 "(" // case 2a -- no scheme, contains common TLD example.com
417 "([a-zA-Z0-9_-]+\\.)+" // domainpart. at least one of these must exist
418 "(?=" // must be followed by TLD
419 "AERO|aero|" // N.B. assertions are non-capturing
420 "ARPA|arpa|"
421 "ASIA|asia|"
422 "BIZ|biz|"
423 "CAT|cat|"
424 "COM|com|"
425 "COOP|coop|"
426 "EDU|edu|"
427 "GOV|gov|"
428 "INFO|info|"
429 "INT|int|"
430 "JOBS|jobs|"
431 "MIL|mil|"
432 "MOBI|mobi|"
433 "MUSEUM|museum|"
434 "NAME|name|"
435 "NET|net|"
436 "ORG|org|"
437 "PRO|pro|"
438 "RO|ro|"
439 "RU|ru|"
440 "TEL|tel|"
441 "TRAVEL|travel"
442 ")"
443 "([a-zA-Z0-9\\?%=&/_\\.:#;-]+)" // everything to 1st non-URI char, must be at least one char after the previous dot (cannot use ".*" because it can be too greedy)
444 ")"
445 "|"
446 "(" // case 2b no scheme, no TLD, must have at least 2 alphanum strings plus uncommon TLD string --> del.icio.us
447 "([a-zA-Z0-9_-]+\\.) {2,}" // 2 or more domainpart. --> del.icio.
448 "[a-zA-Z]{2,}" // one ab (2 char or longer) --> us
449 "([a-zA-Z0-9\\?%=&/_\\.:#;-]*)" // everything to 1st non-URI char, maybe nothing in case of del.icio.us/path
450 ")"
451 ")"
452 );
453
454 // Capture links
455 result.replace(reURL, "\\1<a href=\"\\2\">\\2</a>");
456
457 // Capture links without scheme
458 static const QRegularExpression reNoScheme("<a\\s+href=\"(?!https?)([a-zA-Z0-9\\?%=&/_\\.-:#]+)\\s*\">");
459 result.replace(reNoScheme, "<a href=\"http://\\1\">");
460
461 // to preserve plain text formatting
462 result = "<p style=\"white-space: pre-wrap;\">" + result + "</p>";
463 return result;
464 }
465
osName()466 QString Utils::Misc::osName()
467 {
468 // static initialization for usage in signal handler
469 static const QString name =
470 QString("%1 %2 %3")
471 .arg(QSysInfo::prettyProductName()
472 , QSysInfo::kernelVersion()
473 , QSysInfo::currentCpuArchitecture());
474 return name;
475 }
476
boostVersionString()477 QString Utils::Misc::boostVersionString()
478 {
479 // static initialization for usage in signal handler
480 static const QString ver = QString("%1.%2.%3")
481 .arg(QString::number(BOOST_VERSION / 100000)
482 , QString::number((BOOST_VERSION / 100) % 1000)
483 , QString::number(BOOST_VERSION % 100));
484 return ver;
485 }
486
libtorrentVersionString()487 QString Utils::Misc::libtorrentVersionString()
488 {
489 // static initialization for usage in signal handler
490 static const auto version {QString::fromLatin1(lt::version())};
491 return version;
492 }
493
opensslVersionString()494 QString Utils::Misc::opensslVersionString()
495 {
496 #if (OPENSSL_VERSION_NUMBER >= 0x1010000f)
497 static const auto version {QString::fromLatin1(OpenSSL_version(OPENSSL_VERSION))};
498 #else
499 static const auto version {QString::fromLatin1(SSLeay_version(SSLEAY_VERSION))};
500 #endif
501 return version.splitRef(' ', QString::SkipEmptyParts)[1].toString();
502 }
503
zlibVersionString()504 QString Utils::Misc::zlibVersionString()
505 {
506 // static initialization for usage in signal handler
507 static const auto version {QString::fromLatin1(zlibVersion())};
508 return version;
509 }
510
511 #ifdef Q_OS_WIN
windowsSystemPath()512 QString Utils::Misc::windowsSystemPath()
513 {
514 static const QString path = []() -> QString
515 {
516 WCHAR systemPath[MAX_PATH] = {0};
517 GetSystemDirectoryW(systemPath, sizeof(systemPath) / sizeof(WCHAR));
518 return QString::fromWCharArray(systemPath);
519 }();
520 return path;
521 }
522 #endif
523