Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwindowsservices.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:execute-external-code
4
6#include <QtCore/qt_windows.h>
7
8#include <QtCore/qurl.h>
9#include <QtCore/qdebug.h>
10#include <QtCore/qdir.h>
11#include <QtCore/qscopedpointer.h>
12#include <QtCore/qthread.h>
13
14#include <QtCore/private/qwinregistry_p.h>
15#include <QtCore/private/qfunctions_win_p.h>
16
17#include <shlobj.h>
18#include <shlwapi.h>
19#include <intshcut.h>
20
22
23using namespace Qt::StringLiterals;
24
26{
27public:
28 explicit QWindowsShellExecuteThread(const wchar_t *operation, const wchar_t *file,
29 const wchar_t *parameters)
30 : m_operation(operation)
31 , m_file(file)
32 , m_parameters(parameters) { }
33
35 {
36 QComHelper comHelper;
37 if (comHelper.isValid())
38 m_result = ShellExecute(nullptr, m_operation, m_file, m_parameters, nullptr,
39 SW_SHOWNORMAL);
40 }
41
42 HINSTANCE result() const { return m_result; }
43
44private:
45 HINSTANCE m_result = nullptr;
46 const wchar_t *m_operation;
47 const wchar_t *m_file;
48 const wchar_t *m_parameters;
49};
50
51static QString msgShellExecuteFailed(const QUrl &url, quintptr code)
52{
53 QString result;
54 QTextStream(&result) <<"ShellExecute '" << url.toString() << "' failed (error " << code << ").";
55 return result;
56}
57
58// Retrieve the web browser and open the URL. This should be used for URLs with
59// fragments which don't work when using ShellExecute() directly (QTBUG-14460,
60// QTBUG-55300).
61static bool openWebBrowser(const QUrl &url)
62{
63 WCHAR browserExecutable[MAX_PATH] = {};
64 const wchar_t operation[] = L"open";
65 DWORD browserExecutableSize = MAX_PATH;
66 if (FAILED(AssocQueryString(0, ASSOCSTR_EXECUTABLE, L"http", operation,
67 browserExecutable, &browserExecutableSize))) {
68 return false;
69 }
70 QString browser = QString::fromWCharArray(browserExecutable, browserExecutableSize - 1);
71 // Workaround for "old" MS Edge entries. Instead of LaunchWinApp.exe we can just use msedge.exe
72 if (browser.contains("LaunchWinApp.exe"_L1, Qt::CaseInsensitive))
73 browser = "msedge.exe"_L1;
74 const QString urlS = url.toString(QUrl::FullyEncoded);
75
76 // Run ShellExecute() in a thread since it may spin the event loop.
77 // Prevent it from interfering with processing of posted events (QTBUG-85676).
78 QWindowsShellExecuteThread thread(operation,
79 reinterpret_cast<const wchar_t *>(browser.utf16()),
80 reinterpret_cast<const wchar_t *>(urlS.utf16()));
81 thread.start();
82 thread.wait();
83
84 const auto result = reinterpret_cast<quintptr>(thread.result());
85 qCDebug(lcQpaServices) << urlS << QString::fromWCharArray(browserExecutable) << result;
86 // ShellExecute returns a value greater than 32 if successful
87 if (result <= 32) {
88 qCWarning(lcQpaServices, "%s", qPrintable(msgShellExecuteFailed(url, result)));
89 return false;
90 }
91 return true;
92}
93
94static inline bool shellExecute(const QUrl &url)
95{
96 const QString nativeFilePath = url.isLocalFile() && !url.hasFragment() && !url.hasQuery()
97 ? QDir::toNativeSeparators(url.toLocalFile())
98 : url.toString(QUrl::FullyEncoded);
99
100
101 // Run ShellExecute() in a thread since it may spin the event loop.
102 // Prevent it from interfering with processing of posted events (QTBUG-85676).
103 QWindowsShellExecuteThread thread(nullptr,
104 reinterpret_cast<const wchar_t *>(nativeFilePath.utf16()),
105 nullptr);
106 thread.start();
107 thread.wait();
108
109 const auto result = reinterpret_cast<quintptr>(thread.result());
110
111 // ShellExecute returns a value greater than 32 if successful
112 if (result <= 32) {
113 qCWarning(lcQpaServices, "%s", qPrintable(msgShellExecuteFailed(url, result)));
114 return false;
115 }
116 return true;
117}
118
119// Retrieve the commandline for the default mail client. It contains a
120// placeholder %1 for the URL. The default key used below is the
121// command line for the mailto: shell command.
122static inline QString mailCommand()
123{
124 enum { BufferSize = sizeof(wchar_t) * MAX_PATH };
125
126 const wchar_t mailUserKey[] = L"Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\mailto\\UserChoice";
127
128 // Check if user has set preference, otherwise use default.
129 QString keyName = QWinRegistryKey(HKEY_CURRENT_USER, mailUserKey)
130 .stringValue( L"Progid");
131 const auto mailto = keyName.isEmpty() ? "mailto"_L1 : QLatin1StringView();
132 keyName += mailto + "\\Shell\\Open\\Command"_L1;
133 qCDebug(lcQpaServices) << "keyName=" << keyName;
134 const QString command = QWinRegistryKey(HKEY_CLASSES_ROOT, keyName).stringValue(L"");
135 // QTBUG-57816: As of Windows 10, if there is no mail client installed, an entry like
136 // "rundll32.exe .. url.dll,MailToProtocolHandler %l" is returned. Launching it
137 // silently fails or brings up a broken dialog after a long time, so exclude it and
138 // fall back to ShellExecute() which brings up the URL association dialog.
139 if (command.isEmpty() || command.contains(u",MailToProtocolHandler"))
140 return QString();
141 wchar_t expandedCommand[MAX_PATH] = {0};
142 return ExpandEnvironmentStrings(reinterpret_cast<const wchar_t *>(command.utf16()),
143 expandedCommand, MAX_PATH)
144 ? QString::fromWCharArray(expandedCommand) : command;
145}
146
147static inline bool launchMail(const QUrl &url)
148{
149 QString command = mailCommand();
150 if (command.isEmpty()) {
151 qCWarning(lcQpaServices, "Cannot launch '%ls': There is no mail program installed.", qUtf16Printable(url.toString()));
152 return false;
153 }
154 // Fix mail launch if no param is expected in this command.
155 if (command.indexOf("%1"_L1) < 0) {
156 qWarning(lcQpaServices) << "The mail command lacks the '%1' parameter.";
157 return false;
158 }
159 //Make sure the path for the process is in quotes
160 const QChar doubleQuote = u'"';
161 if (!command.startsWith(doubleQuote)) {
162 const int exeIndex = command.indexOf(".exe "_L1, 0, Qt::CaseInsensitive);
163 if (exeIndex != -1) {
164 command.insert(exeIndex + 4, doubleQuote);
165 command.prepend(doubleQuote);
166 }
167 }
168 // Pass the url as the parameter. Should use QProcess::startDetached(),
169 // but that cannot handle a Windows command line [yet].
170 command.replace("%1"_L1, url.toString(QUrl::FullyEncoded));
171 qCDebug(lcQpaServices) << "Launching" << command;
172 //start the process
173 PROCESS_INFORMATION pi;
174 ZeroMemory(&pi, sizeof(pi));
175 STARTUPINFO si;
176 ZeroMemory(&si, sizeof(si));
177 si.cb = sizeof(si);
178 if (!CreateProcess(nullptr, reinterpret_cast<wchar_t *>(const_cast<ushort *>(command.utf16())),
179 nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) {
180 qErrnoWarning("Unable to launch '%ls'", qUtf16Printable(command));
181 return false;
182 }
183 CloseHandle(pi.hProcess);
184 CloseHandle(pi.hThread);
185 return true;
186}
187
188bool QWindowsServices::openUrl(const QUrl &url)
189{
190 const QString scheme = url.scheme();
191 if (scheme == u"mailto" && launchMail(url))
192 return true;
193 return url.isLocalFile() && url.hasFragment()
194 ? openWebBrowser(url) : shellExecute(url);
195}
196
197bool QWindowsServices::openDocument(const QUrl &url)
198{
199 return shellExecute(url);
200}
201
202QT_END_NAMESPACE
QWindowsShellExecuteThread(const wchar_t *operation, const wchar_t *file, const wchar_t *parameters)
Combined button and popup list for selecting options.
static bool launchMail(const QUrl &url)
static QString msgShellExecuteFailed(const QUrl &url, quintptr code)
static QString mailCommand()
static bool shellExecute(const QUrl &url)
static bool openWebBrowser(const QUrl &url)