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;
69constexpr const char* kBrokerScheme =
"ms-appx-web";
74 std::condition_variable cv;
77 std::string redirectUrl;
78 std::string errorMessage;
95class AuthWindow :
public QWebEngineView
99 explicit AuthWindow(Session& owner);
102 void closeEvent(QCloseEvent* event)
override;
105 void onNavigationRequested(QWebEngineNavigationRequest& request);
106 void onLoadFinished(
bool ok);
118 bool navigate(
const std::string& title,
const std::string& url,
const std::string& redirectUri,
119 uint32_t timeoutMs, std::string& redirectUrl, std::string& error)
121 PendingResult pending;
123 std::lock_guard<std::mutex> lock(stateMtx);
126 error =
"navigate_already_in_progress";
133 const QString qtitle = QString::fromStdString(title);
134 const QUrl qurl(QString::fromStdString(url));
135 QMetaObject::invokeMethod(
137 [
this, qtitle, qurl]()
140 window =
new AuthWindow(*
this);
141 window->setWindowTitle(qtitle);
142 window->resize(800, 600);
145 window->activateWindow();
146 window->setUrl(qurl);
148 Qt::QueuedConnection);
150 const uint32_t timeout = timeoutMs ? timeoutMs : kDefaultTimeoutMs;
151 bool signalled =
false;
153 std::unique_lock<std::mutex> lock(pending.mtx);
154 signalled = pending.cv.wait_for(lock, std::chrono::milliseconds(timeout),
155 [&] {
return pending.done; });
159 std::lock_guard<std::mutex> lock(stateMtx);
162 if (current == &pending)
169 QMetaObject::invokeMethod(
176 Qt::QueuedConnection);
186 error = pending.errorMessage.empty() ?
"user_cancelled" : pending.errorMessage;
190 redirectUrl = pending.redirectUrl;
196 std::lock_guard<std::mutex> lock(stateMtx);
197 finishCurrentLocked(
false,
"",
"user_cancelled");
203 std::lock_guard<std::mutex> lock(stateMtx);
204 finishCurrentLocked(
false,
"",
"shutting_down");
210 QMetaObject::invokeMethod(
218 Qt::QueuedConnection);
224 bool onNavigate(
const std::string& uri)
226 std::lock_guard<std::mutex> lock(stateMtx);
227 if (!current || !watcher.matches(uri))
231 if (watcher.hasError(uri, err))
232 finishCurrentLocked(
false,
"", err);
234 finishCurrentLocked(
true, uri,
"");
239 void onWindowClosed()
241 std::lock_guard<std::mutex> lock(stateMtx);
242 finishCurrentLocked(
false,
"",
"user_cancelled");
247 void finishCurrentLocked(
bool ok,
const std::string& redirectUrl,
const std::string& err)
252 std::lock_guard<std::mutex> lock(current->mtx);
253 current->done =
true;
255 current->redirectUrl = redirectUrl;
256 current->errorMessage = err;
258 current->cv.notify_all();
263 PendingResult* current =
nullptr;
265 AuthWindow* window =
nullptr;
268AuthWindow::AuthWindow(Session& owner) : session(owner)
270 connect(page(), &QWebEnginePage::navigationRequested,
this, &AuthWindow::onNavigationRequested);
271 connect(
this, &QWebEngineView::loadFinished,
this, &AuthWindow::onLoadFinished);
274void AuthWindow::onNavigationRequested(QWebEngineNavigationRequest& request)
276 if (session.onNavigate(request.url().toString().toStdString()))
280void AuthWindow::onLoadFinished(
bool ok)
282 std::cout <<
"[qt-aad-helper] loaded " << url().toString().toStdString()
283 << (ok ?
"" :
" (failed)") << std::endl;
286void AuthWindow::closeEvent(QCloseEvent* event)
288 session.onWindowClosed();
289 QWebEngineView::closeEvent(event);
297class BrokerSchemeHandler :
public QWebEngineUrlSchemeHandler
300 explicit BrokerSchemeHandler(Session& owner) : session(owner)
304 void requestStarted(QWebEngineUrlRequestJob* job)
override
306 session.onNavigate(job->requestUrl().toString().toStdString());
307 job->fail(QWebEngineUrlRequestJob::RequestAborted);
321 HANDLE g_cmdIn =
nullptr;
322 HANDLE g_cmdOut =
nullptr;
324 bool writeLine(
const std::string& line)
326 std::string data = line;
330 while (written < data.size())
333 if (!WriteFile(g_cmdOut, data.data() + written,
334 static_cast<DWORD
>(data.size() - written), &dwWritten,
nullptr) ||
337 written += dwWritten;
343 bool extractLine(std::string& buf, std::string& line)
345 const auto pos = buf.find(
'\n');
346 if (pos == std::string::npos)
348 line = buf.substr(0, pos);
349 buf.erase(0, pos + 1);
353 bool readLine(std::string& buf, std::string& line)
355 if (extractLine(buf, line))
362 if (!ReadFile(g_cmdIn, chunk,
sizeof(chunk), &dwRead,
nullptr) || (dwRead == 0))
364 buf.append(chunk, dwRead);
365 if (extractLine(buf, line))
370 void sendLine(
const std::string& line)
372 (void)writeLine(line);
375 void sendJson(
const QJsonObject& obj)
377 const QJsonDocument doc(obj);
378 sendLine(doc.toJson(QJsonDocument::Compact).toStdString());
381 void sendHelloResult(int64_t
id)
384 result[
"protocol_version"] = 1;
385 result[
"helper"] = QStringLiteral(
"freerdp-qt-aad-helper/1.0");
388 obj[
"jsonrpc"] = QStringLiteral(
"2.0");
389 obj[
"id"] =
static_cast<double>(id);
390 obj[
"result"] = result;
394 void sendNavigateResult(int64_t
id,
const std::string& redirectUrl)
397 result[
"status"] = QStringLiteral(
"ok");
398 result[
"redirect_url"] = QString::fromStdString(redirectUrl);
401 obj[
"jsonrpc"] = QStringLiteral(
"2.0");
402 obj[
"id"] =
static_cast<double>(id);
403 obj[
"result"] = result;
407 void sendNullResult(int64_t
id)
410 obj[
"jsonrpc"] = QStringLiteral(
"2.0");
411 obj[
"id"] =
static_cast<double>(id);
412 obj[
"result"] = QJsonValue();
416 void sendError(int64_t
id,
int code,
const std::string& message)
419 error[
"code"] = code;
420 error[
"message"] = QString::fromStdString(message);
423 obj[
"jsonrpc"] = QStringLiteral(
"2.0");
424 obj[
"id"] =
static_cast<double>(id);
425 obj[
"error"] = error;
429 std::string getStringField(
const QJsonObject& obj,
const char* name)
431 const QJsonValue v = obj.value(QLatin1String(name));
432 return v.isString() ? v.toString().toStdString() : std::string();
435 uint32_t getUintField(
const QJsonObject& obj,
const char* name, uint32_t def)
437 const QJsonValue v = obj.value(QLatin1String(name));
438 return v.isDouble() ?
static_cast<uint32_t
>(v.toDouble()) : def;
444 void readerLoop(Session& session)
448 while (readLine(buf, line))
453 const QJsonDocument doc = QJsonDocument::fromJson(QByteArray::fromStdString(line));
456 const QJsonObject msg = doc.object();
458 const QJsonValue idValue = msg.value(QLatin1String(
"id"));
459 const bool hasId = idValue.isDouble();
460 const int64_t
id = hasId ?
static_cast<int64_t
>(idValue.toDouble()) : 0;
461 const std::string method = getStringField(msg,
"method");
462 const QJsonObject params = msg.value(QLatin1String(
"params")).toObject();
464 if (method ==
"hello")
468 else if (method ==
"navigate")
470 const std::string title = getStringField(params,
"title");
471 const std::string url = getStringField(params,
"url");
472 const std::string redirectUri = getStringField(params,
"redirect_uri");
473 const uint32_t timeoutMs = getUintField(params,
"timeout_ms", kDefaultTimeoutMs);
475 std::string redirectUrl;
477 if (session.navigate(title, url, redirectUri, timeoutMs, redirectUrl, error))
478 sendNavigateResult(
id, redirectUrl);
480 sendError(
id, 1, error);
482 else if (method ==
"cancel")
486 else if (method ==
"shutdown")
490 else if (method ==
"exit")
501int main(
int argc,
char* argv[])
503 std::string cmdInArg;
504 std::string cmdOutArg;
505 for (
int i = 1; i < argc; i++)
507 const std::string arg = argv[i];
508 if (arg.rfind(
"--cmdInFd=", 0) == 0)
510 else if (arg.rfind(
"--cmdOutFd=", 0) == 0)
514 if (cmdInArg.empty() || cmdOutArg.empty())
516 std::cerr <<
"usage: " << (argc > 0 ? argv[0] :
"freerdp-qt-aad-helper")
517 <<
" --cmdInFd=<handle> --cmdOutFd=<handle>" << std::endl;
521 g_cmdIn = winpr_importHandleFromString(cmdInArg.c_str(),
"--cmdInFd={}");
522 g_cmdOut = winpr_importHandleFromString(cmdOutArg.c_str(),
"--cmdOutFd={}");
523 if (!g_cmdIn || (g_cmdIn == INVALID_HANDLE_VALUE) || !g_cmdOut ||
524 (g_cmdOut == INVALID_HANDLE_VALUE))
526 std::cerr <<
"failed to import the cmdIn/cmdOut channel handles" << std::endl;
536 QWebEngineUrlScheme brokerScheme(kBrokerScheme);
537 brokerScheme.setSyntax(QWebEngineUrlScheme::Syntax::Host);
538 brokerScheme.setFlags(QWebEngineUrlScheme::CorsEnabled);
539 QWebEngineUrlScheme::registerScheme(brokerScheme);
541 QApplication app(argc, argv);
550 app.setQuitOnLastWindowClosed(
false);
553 BrokerSchemeHandler brokerHandler(session);
554 QWebEngineProfile::defaultProfile()->installUrlSchemeHandler(QByteArray(kBrokerScheme),
557 std::thread reader(readerLoop, std::ref(session));
559 const int rc = QApplication::exec();
562 (void)CloseHandle(g_cmdIn);
563 (void)CloseHandle(g_cmdOut);