26#include <QApplication>
28#include <QJsonDocument>
34#include <QWebEngineNavigationRequest>
35#include <QWebEnginePage>
36#include <QWebEngineProfile>
37#include <QWebEngineUrlRequestJob>
38#include <QWebEngineUrlScheme>
39#include <QWebEngineUrlSchemeHandler>
40#include <QWebEngineView>
43#include <condition_variable>
50#include <winpr/file.h>
51#include <winpr/handle.h>
53#include "redirect_watcher.hpp"
55constexpr uint32_t kDefaultTimeoutMs = 180000;
75constexpr const char* kBrokerScheme =
"ms-appx-web";
80 std::condition_variable cv;
83 std::string redirectUrl;
84 std::string errorMessage;
101class AuthWindow :
public QWebEngineView
105 explicit AuthWindow(Session& owner);
108 void closeEvent(QCloseEvent* event)
override;
111 void onNavigationRequested(QWebEngineNavigationRequest& request);
112 void onLoadFinished(
bool ok);
124 Session() : nativeUserAgent(QWebEngineProfile::defaultProfile()->httpUserAgent().toStdString())
128 bool navigate(
const std::string& title,
const std::string& url,
const std::string& redirectUri,
129 uint32_t timeoutMs,
const std::string& userAgent, std::string& redirectUrl,
132 PendingResult pending;
134 std::lock_guard<std::mutex> lock(stateMtx);
137 error =
"navigate_already_in_progress";
144 const QString qtitle = QString::fromStdString(title);
145 const QUrl qurl(QString::fromStdString(url));
146 QMetaObject::invokeMethod(
148 [
this, qtitle, qurl, userAgent]()
151 window =
new AuthWindow(*
this);
152 window->setWindowTitle(qtitle);
153 window->resize(800, 600);
156 window->activateWindow();
161 QString::fromStdString(userAgent.empty() ? nativeUserAgent : userAgent);
162 QWebEngineProfile::defaultProfile()->setHttpUserAgent(ua);
163 window->setUrl(qurl);
165 Qt::QueuedConnection);
167 const uint32_t timeout = timeoutMs ? timeoutMs : kDefaultTimeoutMs;
168 bool signalled =
false;
170 std::unique_lock<std::mutex> lock(pending.mtx);
171 signalled = pending.cv.wait_for(lock, std::chrono::milliseconds(timeout),
172 [&] {
return pending.done; });
176 std::lock_guard<std::mutex> lock(stateMtx);
179 if (current == &pending)
186 QMetaObject::invokeMethod(
193 Qt::QueuedConnection);
203 error = pending.errorMessage.empty() ?
"user_cancelled" : pending.errorMessage;
207 redirectUrl = pending.redirectUrl;
213 std::lock_guard<std::mutex> lock(stateMtx);
214 finishCurrentLocked(
false,
"",
"user_cancelled");
220 std::lock_guard<std::mutex> lock(stateMtx);
221 finishCurrentLocked(
false,
"",
"shutting_down");
227 QMetaObject::invokeMethod(
235 Qt::QueuedConnection);
241 bool onNavigate(
const std::string& uri)
243 std::lock_guard<std::mutex> lock(stateMtx);
244 if (!current || !watcher.matches(uri))
248 if (watcher.hasError(uri, err))
249 finishCurrentLocked(
false,
"", err);
251 finishCurrentLocked(
true, uri,
"");
256 void onWindowClosed()
258 std::lock_guard<std::mutex> lock(stateMtx);
259 finishCurrentLocked(
false,
"",
"user_cancelled");
264 void finishCurrentLocked(
bool ok,
const std::string& redirectUrl,
const std::string& err)
269 std::lock_guard<std::mutex> lock(current->mtx);
270 current->done =
true;
272 current->redirectUrl = redirectUrl;
273 current->errorMessage = err;
275 current->cv.notify_all();
280 PendingResult* current =
nullptr;
282 AuthWindow* window =
nullptr;
283 std::string nativeUserAgent;
286AuthWindow::AuthWindow(Session& owner) : session(owner)
288 connect(page(), &QWebEnginePage::navigationRequested,
this, &AuthWindow::onNavigationRequested);
289 connect(
this, &QWebEngineView::loadFinished,
this, &AuthWindow::onLoadFinished);
292void AuthWindow::onNavigationRequested(QWebEngineNavigationRequest& request)
294 if (session.onNavigate(request.url().toString().toStdString()))
298void AuthWindow::onLoadFinished(
bool ok)
300 std::cout <<
"[qt-aad-helper] loaded " << url().toString().toStdString()
301 << (ok ?
"" :
" (failed)") << std::endl;
304void AuthWindow::closeEvent(QCloseEvent* event)
306 session.onWindowClosed();
307 QWebEngineView::closeEvent(event);
315class BrokerSchemeHandler :
public QWebEngineUrlSchemeHandler
318 explicit BrokerSchemeHandler(Session& owner) : session(owner)
322 void requestStarted(QWebEngineUrlRequestJob* job)
override
324 session.onNavigate(job->requestUrl().toString().toStdString());
325 job->fail(QWebEngineUrlRequestJob::RequestAborted);
339 HANDLE g_cmdIn =
nullptr;
340 HANDLE g_cmdOut =
nullptr;
342 bool writeLine(
const std::string& line)
344 std::string data = line;
348 while (written < data.size())
351 if (!WriteFile(g_cmdOut, data.data() + written,
352 static_cast<DWORD
>(data.size() - written), &dwWritten,
nullptr) ||
355 written += dwWritten;
361 bool extractLine(std::string& buf, std::string& line)
363 const auto pos = buf.find(
'\n');
364 if (pos == std::string::npos)
366 line = buf.substr(0, pos);
367 buf.erase(0, pos + 1);
371 bool readLine(std::string& buf, std::string& line)
373 if (extractLine(buf, line))
380 if (!ReadFile(g_cmdIn, chunk,
sizeof(chunk), &dwRead,
nullptr) || (dwRead == 0))
382 buf.append(chunk, dwRead);
383 if (extractLine(buf, line))
388 void sendLine(
const std::string& line)
390 (void)writeLine(line);
393 void sendJson(
const QJsonObject& obj)
395 const QJsonDocument doc(obj);
396 sendLine(doc.toJson(QJsonDocument::Compact).toStdString());
399 void sendHelloResult(int64_t
id)
402 result[
"protocol_version"] = 1;
403 result[
"helper"] = QStringLiteral(
"freerdp-qt-aad-helper/1.0");
406 obj[
"jsonrpc"] = QStringLiteral(
"2.0");
407 obj[
"id"] =
static_cast<double>(id);
408 obj[
"result"] = result;
412 void sendNavigateResult(int64_t
id,
const std::string& redirectUrl)
415 result[
"status"] = QStringLiteral(
"ok");
416 result[
"redirect_url"] = QString::fromStdString(redirectUrl);
419 obj[
"jsonrpc"] = QStringLiteral(
"2.0");
420 obj[
"id"] =
static_cast<double>(id);
421 obj[
"result"] = result;
425 void sendNullResult(int64_t
id)
428 obj[
"jsonrpc"] = QStringLiteral(
"2.0");
429 obj[
"id"] =
static_cast<double>(id);
430 obj[
"result"] = QJsonValue();
434 void sendError(int64_t
id,
int code,
const std::string& message)
437 error[
"code"] = code;
438 error[
"message"] = QString::fromStdString(message);
441 obj[
"jsonrpc"] = QStringLiteral(
"2.0");
442 obj[
"id"] =
static_cast<double>(id);
443 obj[
"error"] = error;
447 std::string getStringField(
const QJsonObject& obj,
const char* name)
449 const QJsonValue v = obj.value(QLatin1String(name));
450 return v.isString() ? v.toString().toStdString() : std::string();
453 uint32_t getUintField(
const QJsonObject& obj,
const char* name, uint32_t def)
455 const QJsonValue v = obj.value(QLatin1String(name));
456 return v.isDouble() ?
static_cast<uint32_t
>(v.toDouble()) : def;
462 void readerLoop(Session& session)
466 while (readLine(buf, line))
471 const QJsonDocument doc = QJsonDocument::fromJson(QByteArray::fromStdString(line));
474 const QJsonObject msg = doc.object();
476 const QJsonValue idValue = msg.value(QLatin1String(
"id"));
477 const bool hasId = idValue.isDouble();
478 const int64_t
id = hasId ?
static_cast<int64_t
>(idValue.toDouble()) : 0;
479 const std::string method = getStringField(msg,
"method");
480 const QJsonObject params = msg.value(QLatin1String(
"params")).toObject();
482 if (method ==
"hello")
486 else if (method ==
"navigate")
488 const std::string title = getStringField(params,
"title");
489 const std::string url = getStringField(params,
"url");
490 const std::string redirectUri = getStringField(params,
"redirect_uri");
491 const std::string userAgent = getStringField(params,
"user_agent");
492 const uint32_t timeoutMs = getUintField(params,
"timeout_ms", kDefaultTimeoutMs);
494 std::string redirectUrl;
496 if (session.navigate(title, url, redirectUri, timeoutMs, userAgent, redirectUrl,
498 sendNavigateResult(
id, redirectUrl);
500 sendError(
id, 1, error);
502 else if (method ==
"cancel")
506 else if (method ==
"shutdown")
510 else if (method ==
"exit")
521int main(
int argc,
char* argv[])
523 std::string cmdInArg;
524 std::string cmdOutArg;
525 for (
int i = 1; i < argc; i++)
527 const std::string arg = argv[i];
528 if (arg.rfind(
"--cmdInFd=", 0) == 0)
530 else if (arg.rfind(
"--cmdOutFd=", 0) == 0)
534 if (cmdInArg.empty() || cmdOutArg.empty())
536 std::cerr <<
"usage: " << (argc > 0 ? argv[0] :
"freerdp-qt-aad-helper")
537 <<
" --cmdInFd=<handle> --cmdOutFd=<handle>" << std::endl;
541 g_cmdIn = winpr_importHandleFromString(cmdInArg.c_str(),
"--cmdInFd={}");
542 g_cmdOut = winpr_importHandleFromString(cmdOutArg.c_str(),
"--cmdOutFd={}");
543 if (!g_cmdIn || (g_cmdIn == INVALID_HANDLE_VALUE) || !g_cmdOut ||
544 (g_cmdOut == INVALID_HANDLE_VALUE))
546 std::cerr <<
"failed to import the cmdIn/cmdOut channel handles" << std::endl;
556 QWebEngineUrlScheme brokerScheme(kBrokerScheme);
557 brokerScheme.setSyntax(QWebEngineUrlScheme::Syntax::Host);
558 brokerScheme.setFlags(QWebEngineUrlScheme::CorsEnabled);
559 QWebEngineUrlScheme::registerScheme(brokerScheme);
561 QApplication app(argc, argv);
570 app.setQuitOnLastWindowClosed(
false);
573 BrokerSchemeHandler brokerHandler(session);
574 QWebEngineProfile::defaultProfile()->installUrlSchemeHandler(QByteArray(kBrokerScheme),
577 std::thread reader(readerLoop, std::ref(session));
579 const int rc = QApplication::exec();
582 (void)CloseHandle(g_cmdIn);
583 (void)CloseHandle(g_cmdOut);