Change URL seed error message
[qBittorrent.git] / src / base / utils / os.cpp
blob2f1c77adb0f02e0e49d8afc75a8cdd3d4a238702
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2023 Mike Tzou (Chocobo1)
4 * Copyright (C) 2014 sledgehammer999 <hammered999@gmail.com>
5 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
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.
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.
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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 * In addition, as a special exception, the copyright holders give permission to
22 * link this program with the OpenSSL project's "OpenSSL" library (or with
23 * modified versions of it that use the same license as the "OpenSSL" library),
24 * and distribute the linked executables. You must obey the GNU General Public
25 * License in all respects for all of the code used other than "OpenSSL". If you
26 * modify file(s), you may extend this exception to your version of the file(s),
27 * but you are not obligated to do so. If you do not wish to do so, delete this
28 * exception statement from your version.
31 #include "os.h"
33 #ifdef Q_OS_WIN
34 #include <algorithm>
36 #include <windows.h>
37 #include <powrprof.h>
38 #include <shlobj.h>
39 #endif // Q_OS_WIN
41 #ifdef Q_OS_MACOS
42 #include <Carbon/Carbon.h>
43 #include <CoreFoundation/CoreFoundation.h>
44 #include <CoreServices/CoreServices.h>
45 #endif // Q_OS_MACOS
47 #include <QScopeGuard>
49 #ifdef QBT_USES_DBUS
50 #include <QDBusInterface>
51 #endif // QBT_USES_DBUS
53 #ifdef Q_OS_WIN
54 #include <QCoreApplication>
55 #endif // Q_OS_WIN
57 #include "base/global.h"
58 #include "base/types.h"
60 void Utils::OS::shutdownComputer([[maybe_unused]] const ShutdownDialogAction &action)
62 #if defined(Q_OS_WIN)
63 HANDLE hToken; // handle to process token
64 TOKEN_PRIVILEGES tkp; // pointer to token structure
65 if (!::OpenProcessToken(::GetCurrentProcess(), (TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY), &hToken))
66 return;
67 // Get the LUID for shutdown privilege.
68 ::LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid);
70 tkp.PrivilegeCount = 1; // one privilege to set
71 tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
73 // Get shutdown privilege for this process.
75 ::AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, 0);
77 // Cannot test the return value of AdjustTokenPrivileges.
79 if (::GetLastError() != ERROR_SUCCESS)
80 return;
82 if (action == ShutdownDialogAction::Suspend)
84 ::SetSuspendState(FALSE, FALSE, FALSE);
86 else if (action == ShutdownDialogAction::Hibernate)
88 ::SetSuspendState(TRUE, FALSE, FALSE);
90 else
92 std::wstring msg = QCoreApplication::translate("misc"
93 , "qBittorrent will shutdown the computer now because all downloads are complete.").toStdWString();
94 ::InitiateSystemShutdownW(nullptr, msg.data(), 10, TRUE, FALSE);
97 // Disable shutdown privilege.
98 tkp.Privileges[0].Attributes = 0;
99 ::AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, 0);
101 #elif defined(Q_OS_MACOS)
102 AEEventID EventToSend;
103 if (action != ShutdownDialogAction::Shutdown)
104 EventToSend = kAESleep;
105 else
106 EventToSend = kAEShutDown;
107 AEAddressDesc targetDesc;
108 const ProcessSerialNumber kPSNOfSystemProcess = {0, kSystemProcess};
109 AppleEvent eventReply = {typeNull, NULL};
110 AppleEvent appleEventToSend = {typeNull, NULL};
112 OSStatus error = ::AECreateDesc(typeProcessSerialNumber, &kPSNOfSystemProcess
113 , sizeof(kPSNOfSystemProcess), &targetDesc);
115 if (error != noErr)
116 return;
118 error = ::AECreateAppleEvent(kCoreEventClass, EventToSend, &targetDesc, kAutoGenerateReturnID
119 , kAnyTransactionID, &appleEventToSend);
121 AEDisposeDesc(&targetDesc);
122 if (error != noErr)
123 return;
125 error = ::AESend(&appleEventToSend, &eventReply, kAENoReply, kAENormalPriority, kAEDefaultTimeout
126 , NULL, NULL);
128 ::AEDisposeDesc(&appleEventToSend);
129 if (error != noErr)
130 return;
132 ::AEDisposeDesc(&eventReply);
134 #elif defined(QBT_USES_DBUS)
135 // Use dbus to power off / suspend the system
136 if (action != ShutdownDialogAction::Shutdown)
138 // Some recent systems use systemd's logind
139 QDBusInterface login1Iface(u"org.freedesktop.login1"_s, u"/org/freedesktop/login1"_s,
140 u"org.freedesktop.login1.Manager"_s, QDBusConnection::systemBus());
141 if (login1Iface.isValid())
143 if (action == ShutdownDialogAction::Suspend)
144 login1Iface.call(u"Suspend"_s, false);
145 else
146 login1Iface.call(u"Hibernate"_s, false);
147 return;
149 // Else, other recent systems use UPower
150 QDBusInterface upowerIface(u"org.freedesktop.UPower"_s, u"/org/freedesktop/UPower"_s,
151 u"org.freedesktop.UPower"_s, QDBusConnection::systemBus());
152 if (upowerIface.isValid())
154 if (action == ShutdownDialogAction::Suspend)
155 upowerIface.call(u"Suspend"_s);
156 else
157 upowerIface.call(u"Hibernate"_s);
158 return;
160 // HAL (older systems)
161 QDBusInterface halIface(u"org.freedesktop.Hal"_s, u"/org/freedesktop/Hal/devices/computer"_s,
162 u"org.freedesktop.Hal.Device.SystemPowerManagement"_s,
163 QDBusConnection::systemBus());
164 if (action == ShutdownDialogAction::Suspend)
165 halIface.call(u"Suspend"_s, 5);
166 else
167 halIface.call(u"Hibernate"_s);
169 else
171 // Some recent systems use systemd's logind
172 QDBusInterface login1Iface(u"org.freedesktop.login1"_s, u"/org/freedesktop/login1"_s,
173 u"org.freedesktop.login1.Manager"_s, QDBusConnection::systemBus());
174 if (login1Iface.isValid())
176 login1Iface.call(u"PowerOff"_s, false);
177 return;
179 // Else, other recent systems use ConsoleKit
180 QDBusInterface consolekitIface(u"org.freedesktop.ConsoleKit"_s, u"/org/freedesktop/ConsoleKit/Manager"_s,
181 u"org.freedesktop.ConsoleKit.Manager"_s, QDBusConnection::systemBus());
182 if (consolekitIface.isValid())
184 consolekitIface.call(u"Stop"_s);
185 return;
187 // HAL (older systems)
188 QDBusInterface halIface(u"org.freedesktop.Hal"_s, u"/org/freedesktop/Hal/devices/computer"_s,
189 u"org.freedesktop.Hal.Device.SystemPowerManagement"_s,
190 QDBusConnection::systemBus());
191 halIface.call(u"Shutdown"_s);
193 #endif
196 #ifdef Q_OS_MACOS
197 namespace
199 const CFStringRef torrentExtension = CFSTR("torrent");
200 const CFStringRef magnetUrlScheme = CFSTR("magnet");
203 bool Utils::OS::isTorrentFileAssocSet()
205 bool isSet = false;
206 const CFStringRef torrentId = ::UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, torrentExtension, NULL);
207 if (torrentId != NULL)
209 const CFStringRef defaultHandlerId = ::LSCopyDefaultRoleHandlerForContentType(torrentId, kLSRolesViewer);
210 if (defaultHandlerId != NULL)
212 const CFStringRef myBundleId = ::CFBundleGetIdentifier(::CFBundleGetMainBundle());
213 if (myBundleId != NULL)
214 isSet = ::CFStringCompare(myBundleId, defaultHandlerId, 0) == kCFCompareEqualTo;
215 ::CFRelease(defaultHandlerId);
217 ::CFRelease(torrentId);
219 return isSet;
222 void Utils::OS::setTorrentFileAssoc()
224 if (isTorrentFileAssocSet())
225 return;
227 const CFStringRef torrentId = ::UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, torrentExtension, NULL);
228 if (torrentId != NULL)
230 const CFStringRef myBundleId = ::CFBundleGetIdentifier(::CFBundleGetMainBundle());
231 if (myBundleId != NULL)
232 ::LSSetDefaultRoleHandlerForContentType(torrentId, kLSRolesViewer, myBundleId);
233 ::CFRelease(torrentId);
237 bool Utils::OS::isMagnetLinkAssocSet()
239 bool isSet = false;
240 const CFStringRef defaultHandlerId = ::LSCopyDefaultHandlerForURLScheme(magnetUrlScheme);
241 if (defaultHandlerId != NULL)
243 const CFStringRef myBundleId = ::CFBundleGetIdentifier(::CFBundleGetMainBundle());
244 if (myBundleId != NULL)
245 isSet = ::CFStringCompare(myBundleId, defaultHandlerId, 0) == kCFCompareEqualTo;
246 ::CFRelease(defaultHandlerId);
248 return isSet;
251 void Utils::OS::setMagnetLinkAssoc()
253 if (isMagnetLinkAssocSet())
254 return;
256 const CFStringRef myBundleId = ::CFBundleGetIdentifier(::CFBundleGetMainBundle());
257 if (myBundleId != NULL)
258 ::LSSetDefaultHandlerForURLScheme(magnetUrlScheme, myBundleId);
260 #endif // Q_OS_MACOS
262 #ifdef Q_OS_WIN
263 Path Utils::OS::windowsSystemPath()
265 static const Path path = []() -> Path
267 WCHAR systemPath[MAX_PATH] = {0};
268 ::GetSystemDirectoryW(systemPath, sizeof(systemPath) / sizeof(WCHAR));
269 return Path(QString::fromWCharArray(systemPath));
270 }();
271 return path;
273 #endif // Q_OS_WIN
275 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
276 bool Utils::OS::applyMarkOfTheWeb(const Path &file, const QString &url)
278 // Trying to apply this to a non-existent file is unacceptable,
279 // as it may unexpectedly create such a file.
280 if (!file.exists())
281 return false;
283 Q_ASSERT(url.isEmpty() || url.startsWith(u"http:") || url.startsWith(u"https:"));
285 #ifdef Q_OS_MACOS
286 // References:
287 // https://searchfox.org/mozilla-central/rev/ffdc4971dc18e1141cb2a90c2b0b776365650270/xpcom/io/CocoaFileUtils.mm#230
288 // https://github.com/transmission/transmission/blob/f62f7427edb1fd5c430e0ef6956bbaa4f03ae597/macosx/Torrent.mm#L1945-L1955
290 const CFStringRef fileString = file.toString().toCFString();
291 [[maybe_unused]] const auto fileStringGuard = qScopeGuard([&fileString] { ::CFRelease(fileString); });
292 const CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault
293 , fileString, kCFURLPOSIXPathStyle, false);
294 [[maybe_unused]] const auto fileURLGuard = qScopeGuard([&fileURL] { ::CFRelease(fileURL); });
296 if (CFDictionaryRef currentProperties = nullptr;
297 ::CFURLCopyResourcePropertyForKey(fileURL, kCFURLQuarantinePropertiesKey, &currentProperties, NULL)
298 && currentProperties)
300 [[maybe_unused]] const auto currentPropertiesGuard = qScopeGuard([&currentProperties] { ::CFRelease(currentProperties); });
302 if (CFStringRef quarantineType = nullptr;
303 ::CFDictionaryGetValueIfPresent(currentProperties, kLSQuarantineTypeKey, reinterpret_cast<const void **>(&quarantineType))
304 && quarantineType)
306 if (::CFStringCompare(quarantineType, kLSQuarantineTypeOtherDownload, 0) == kCFCompareEqualTo)
307 return true;
311 CFMutableDictionaryRef properties = ::CFDictionaryCreateMutable(kCFAllocatorDefault, 0
312 , &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
313 if (!properties)
314 return false;
315 [[maybe_unused]] const auto propertiesGuard = qScopeGuard([&properties] { ::CFRelease(properties); });
317 ::CFDictionarySetValue(properties, kLSQuarantineTypeKey, kLSQuarantineTypeOtherDownload);
318 if (!url.isEmpty())
319 ::CFDictionarySetValue(properties, kLSQuarantineDataURLKey, url.toCFString());
321 const Boolean success = ::CFURLSetResourcePropertyForKey(fileURL, kCFURLQuarantinePropertiesKey
322 , properties, NULL);
323 return success;
324 #elif defined(Q_OS_WIN)
325 const QString zoneIDStream = file.toString() + u":Zone.Identifier";
327 HANDLE handle = ::CreateFileW(zoneIDStream.toStdWString().c_str(), (GENERIC_READ | GENERIC_WRITE)
328 , (FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE)
329 , nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
330 if (handle == INVALID_HANDLE_VALUE)
331 return false;
332 [[maybe_unused]] const auto handleGuard = qScopeGuard([&handle] { ::CloseHandle(handle); });
334 // 5.6.1 Zone.Identifier Stream Name
335 // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/6e3f7352-d11c-4d76-8c39-2516a9df36e8
336 const QString hostURL = !url.isEmpty() ? url : u"about:internet"_s;
337 const QByteArray zoneID = QByteArrayLiteral("[ZoneTransfer]\r\nZoneId=3\r\n")
338 + u"HostUrl=%1\r\n"_s.arg(hostURL).toUtf8();
340 if (LARGE_INTEGER streamSize = {0};
341 ::GetFileSizeEx(handle, &streamSize) && (streamSize.QuadPart > 0))
343 const DWORD expectedReadSize = std::min<LONGLONG>(streamSize.QuadPart, 1024);
344 QByteArray buf {expectedReadSize, '\0'};
346 if (DWORD actualReadSize = 0;
347 ::ReadFile(handle, buf.data(), expectedReadSize, &actualReadSize, nullptr) && (actualReadSize == expectedReadSize))
349 if (buf.startsWith("[ZoneTransfer]\r\n") && buf.contains("\r\nZoneId=3\r\n") && buf.contains("\r\nHostUrl="))
350 return true;
354 if (!::SetFilePointerEx(handle, {0}, nullptr, FILE_BEGIN))
355 return false;
356 if (!::SetEndOfFile(handle))
357 return false;
359 DWORD written = 0;
360 const BOOL writeResult = ::WriteFile(handle, zoneID.constData(), zoneID.size(), &written, nullptr);
361 return writeResult && (written == zoneID.size());
362 #endif
364 #endif // Q_OS_MACOS || Q_OS_WIN