FreeRDP
Loading...
Searching...
No Matches
webview-aad-helper/main.cpp
1
26#include <webview.h>
27
28#if defined(__linux__)
29#include <gtk/gtk.h>
30#endif
31
32#include <atomic>
33#include <chrono>
34#include <condition_variable>
35#include <cstdint>
36#include <iostream>
37#include <mutex>
38#include <string>
39#include <thread>
40
41#include <winpr/file.h>
42#include <winpr/handle.h>
43#include <winpr/json.h>
44
45#include "redirect_watcher.hpp"
46
47namespace
48{
49
50 constexpr uint32_t kDefaultTimeoutMs = 180000;
51
52 struct PendingResult
53 {
54 std::mutex mtx;
55 std::condition_variable cv;
56 bool done = false;
57 bool ok = false;
58 std::string redirectUrl;
59 std::string errorMessage;
60 };
61
69 class Session
70 {
71 public:
72 Session() : w(false, nullptr)
73 {
74 w.add_navigation_listener(&Session::onNavigateStatic, this);
75 }
76
77 void run()
78 {
79 w.run();
80 }
81
82 bool navigate(const std::string& title, const std::string& url,
83 const std::string& redirectUri, uint32_t timeoutMs,
84 const std::string& userAgent, std::string& redirectUrl, std::string& error)
85 {
86 PendingResult pending;
87 {
88 std::lock_guard<std::mutex> lock(stateMtx);
89 if (current)
90 {
91 error = "navigate_already_in_progress";
92 return false;
93 }
94 current = &pending;
95 watcher = RedirectWatcher(redirectUri);
96 }
97
98 w.dispatch(
99 [this, title, url, userAgent]()
100 {
101#if defined(__linux__)
102 /* the window may have been hidden (see below) by a previous navigate() on
103 * this same reused webview - bring it back before showing new content. */
104 auto handle = w.window();
105 if (handle.ok())
106 gtk_widget_show(GTK_WIDGET(handle.value()));
107#endif
108 w.set_title(title);
109 w.set_size(800, 600, WEBVIEW_HINT_NONE);
110#if defined(__linux__)
111 {
112 auto widget = w.widget();
113 if (widget.ok())
114 {
115 WebKitSettings* settings =
116 webkit_web_view_get_settings(WEBKIT_WEB_VIEW(widget.value()));
117 /* NULL restores WebKit's default UA, so an omitted/empty
118 * user_agent undoes an earlier override - the webview is reused
119 * across navigates within one helper process. */
120 if (settings)
121 webkit_settings_set_user_agent(
122 settings, userAgent.empty() ? nullptr : userAgent.c_str());
123 }
124 }
125#endif
126 w.navigate(url);
127 });
128
129 const uint32_t timeout = timeoutMs ? timeoutMs : kDefaultTimeoutMs;
130 bool signalled = false;
131 {
132 std::unique_lock<std::mutex> lock(pending.mtx);
133 signalled = pending.cv.wait_for(lock, std::chrono::milliseconds(timeout),
134 [&] { return pending.done; });
135 }
136
137 {
138 std::lock_guard<std::mutex> lock(stateMtx);
139 /* still ours: nobody finished it (timeout) - detach the now-invalid pointer to the
140 * stack-local `pending` before it goes out of scope */
141 if (current == &pending)
142 current = nullptr;
143 }
144
145 /* the popup has served its purpose once this navigate() is done, whatever the
146 * outcome - hide it now rather than leaving it sitting on screen until the next
147 * navigate() (if any) reuses and re-shows it. webview has no portable hide(), so on
148 * platforms without a native-handle-based fallback below, blanking the content is the
149 * best available approximation. */
150 w.dispatch(
151 [this]()
152 {
153#if defined(__linux__)
154 auto handle = w.window();
155 if (handle.ok())
156 gtk_widget_hide(GTK_WIDGET(handle.value()));
157 else
158 w.navigate("about:blank");
159#else
160 w.navigate("about:blank");
161#endif
162 });
163
164 if (!signalled)
165 {
166 error = "timeout";
167 return false;
168 }
169
170 if (!pending.ok)
171 {
172 error = pending.errorMessage.empty() ? "user_cancelled" : pending.errorMessage;
173 return false;
174 }
175
176 redirectUrl = pending.redirectUrl;
177 return true;
178 }
179
180 void cancel()
181 {
182 std::lock_guard<std::mutex> lock(stateMtx);
183 finishCurrentLocked(false, "", "user_cancelled");
184 }
185
186 void quit()
187 {
188 {
189 std::lock_guard<std::mutex> lock(stateMtx);
190 finishCurrentLocked(false, "", "shutting_down");
191 }
192 w.dispatch([this]() { w.terminate(); });
193 }
194
195 private:
196 static void onNavigateStatic(webview_t wv, const char* uri, webview_navigation_event_t type,
197 void* arg)
198 {
199 (void)wv;
200 if (type != WEBVIEW_LOAD_FINISHED)
201 return;
202 static_cast<Session*>(arg)->onNavigate(uri);
203 }
204
205 void onNavigate(const std::string& uri)
206 {
207 std::lock_guard<std::mutex> lock(stateMtx);
208 if (!current || !watcher.matches(uri))
209 return;
210
211 std::string err;
212 if (watcher.hasError(uri, err))
213 finishCurrentLocked(false, "", err);
214 else
215 finishCurrentLocked(true, uri, "");
216 }
217
218 /* caller must hold stateMtx */
219 void finishCurrentLocked(bool ok, const std::string& redirectUrl, const std::string& err)
220 {
221 if (!current)
222 return;
223 {
224 std::lock_guard<std::mutex> lock(current->mtx);
225 current->done = true;
226 current->ok = ok;
227 current->redirectUrl = redirectUrl;
228 current->errorMessage = err;
229 }
230 current->cv.notify_all();
231 current = nullptr;
232 }
233
234 webview::webview w;
235 std::mutex stateMtx;
236 PendingResult* current = nullptr;
237 RedirectWatcher watcher{ std::string() };
238 };
239
240 /* the JSON-RPC channel: handles imported from the --cmdInFd=/--cmdOutFd= command line arguments
241 * (see main()), not stdin/stdout - so this helper's own stdio stays free for its normal
242 * diagnostic output instead of colliding with the protocol. */
243 HANDLE g_cmdIn = nullptr;
244 HANDLE g_cmdOut = nullptr;
245
246 bool writeLine(const std::string& line)
247 {
248 std::string data = line;
249 data += "\n";
250
251 size_t written = 0;
252 while (written < data.size())
253 {
254 DWORD dwWritten = 0;
255 if (!WriteFile(g_cmdOut, data.data() + written,
256 static_cast<DWORD>(data.size() - written), &dwWritten, nullptr) ||
257 (dwWritten == 0))
258 return false;
259 written += dwWritten;
260 }
261 return true;
262 }
263
264 /* extracts one '\n'-terminated line already buffered in `buf`, if any */
265 bool extractLine(std::string& buf, std::string& line)
266 {
267 const auto pos = buf.find('\n');
268 if (pos == std::string::npos)
269 return false;
270 line = buf.substr(0, pos);
271 buf.erase(0, pos + 1);
272 return true;
273 }
274
275 bool readLine(std::string& buf, std::string& line)
276 {
277 if (extractLine(buf, line))
278 return true;
279
280 while (true)
281 {
282 char chunk[4096];
283 DWORD dwRead = 0;
284 if (!ReadFile(g_cmdIn, chunk, sizeof(chunk), &dwRead, nullptr) || (dwRead == 0))
285 return false;
286 buf.append(chunk, dwRead);
287 if (extractLine(buf, line))
288 return true;
289 }
290 }
291
292 void sendLine(const std::string& line)
293 {
294 (void)writeLine(line);
295 }
296
297 void sendJson(WINPR_JSON* obj)
298 {
299 char* str = WINPR_JSON_PrintUnformatted(obj);
300 if (str)
301 {
302 sendLine(str);
303 free(str);
304 }
306 }
307
308 void sendHelloResult(int64_t id)
309 {
310 WINPR_JSON* obj = WINPR_JSON_CreateObject();
311 bool ok = WINPR_JSON_AddStringToObject(obj, "jsonrpc", "2.0") &&
312 WINPR_JSON_AddNumberToObject(obj, "id", static_cast<double>(id));
313 WINPR_JSON* result = ok ? WINPR_JSON_AddObjectToObject(obj, "result") : nullptr;
314 if (result)
315 {
316 bool unused =
317 WINPR_JSON_AddIntegerToObject(result, "protocol_version", 1) &&
318 WINPR_JSON_AddStringToObject(result, "helper", "freerdp-webview-aad-helper/1.0");
319 (void)unused;
320 }
321 sendJson(obj);
322 }
323
324 void sendNavigateResult(int64_t id, const std::string& redirectUrl)
325 {
326 WINPR_JSON* obj = WINPR_JSON_CreateObject();
327 bool ok = WINPR_JSON_AddStringToObject(obj, "jsonrpc", "2.0") &&
328 WINPR_JSON_AddNumberToObject(obj, "id", static_cast<double>(id));
329 WINPR_JSON* result = ok ? WINPR_JSON_AddObjectToObject(obj, "result") : nullptr;
330 if (result)
331 {
332 bool unused = WINPR_JSON_AddStringToObject(result, "status", "ok") &&
333 WINPR_JSON_AddStringToObject(result, "redirect_url", redirectUrl.c_str());
334 (void)unused;
335 }
336 sendJson(obj);
337 }
338
339 void sendNullResult(int64_t id)
340 {
341 WINPR_JSON* obj = WINPR_JSON_CreateObject();
342 bool ok = WINPR_JSON_AddStringToObject(obj, "jsonrpc", "2.0") &&
343 WINPR_JSON_AddNumberToObject(obj, "id", static_cast<double>(id)) &&
344 WINPR_JSON_AddNullToObject(obj, "result");
345 (void)ok;
346 sendJson(obj);
347 }
348
349 void sendError(int64_t id, int code, const std::string& message)
350 {
351 WINPR_JSON* obj = WINPR_JSON_CreateObject();
352 bool ok = WINPR_JSON_AddStringToObject(obj, "jsonrpc", "2.0") &&
353 WINPR_JSON_AddNumberToObject(obj, "id", static_cast<double>(id));
354 WINPR_JSON* error = ok ? WINPR_JSON_AddObjectToObject(obj, "error") : nullptr;
355 if (error)
356 {
357 bool unused = WINPR_JSON_AddIntegerToObject(error, "code", code) &&
358 WINPR_JSON_AddStringToObject(error, "message", message.c_str());
359 (void)unused;
360 }
361 sendJson(obj);
362 }
363
364 std::string getStringField(WINPR_JSON* obj, const char* name)
365 {
366 if (!obj)
367 return {};
368 WINPR_JSON* item = WINPR_JSON_GetObjectItemCaseSensitive(obj, name);
369 if (!item || !WINPR_JSON_IsString(item))
370 return {};
371 const char* v = WINPR_JSON_GetStringValue(item);
372 return v ? v : "";
373 }
374
375 uint32_t getUintField(WINPR_JSON* obj, const char* name, uint32_t def)
376 {
377 if (!obj)
378 return def;
379 WINPR_JSON* item = WINPR_JSON_GetObjectItemCaseSensitive(obj, name);
380 if (!item || !WINPR_JSON_IsNumber(item))
381 return def;
382 return static_cast<uint32_t>(WINPR_JSON_GetNumberValue(item));
383 }
384
388 void readerLoop(Session& session)
389 {
390 std::string buf;
391 std::string line;
392 while (readLine(buf, line))
393 {
394 if (line.empty())
395 continue;
396
397 WINPR_JSON* msg = WINPR_JSON_Parse(line.c_str());
398 if (!msg)
399 continue;
400
401 WINPR_JSON* idItem = WINPR_JSON_GetObjectItemCaseSensitive(msg, "id");
402 const bool hasId = idItem && WINPR_JSON_IsNumber(idItem);
403 const int64_t id = hasId ? static_cast<int64_t>(WINPR_JSON_GetNumberValue(idItem)) : 0;
404 const std::string method = getStringField(msg, "method");
405 WINPR_JSON* params = WINPR_JSON_GetObjectItemCaseSensitive(msg, "params");
406
407 if (method == "hello")
408 {
409 sendHelloResult(id);
410 }
411 else if (method == "navigate")
412 {
413 const std::string title = getStringField(params, "title");
414 const std::string url = getStringField(params, "url");
415 const std::string redirectUri = getStringField(params, "redirect_uri");
416 const std::string userAgent = getStringField(params, "user_agent");
417 const uint32_t timeoutMs = getUintField(params, "timeout_ms", kDefaultTimeoutMs);
418
419 std::string redirectUrl;
420 std::string error;
421 if (session.navigate(title, url, redirectUri, timeoutMs, userAgent, redirectUrl,
422 error))
423 sendNavigateResult(id, redirectUrl);
424 else
425 sendError(id, 1, error);
426 }
427 else if (method == "cancel")
428 {
429 session.cancel();
430 }
431 else if (method == "shutdown")
432 {
433 sendNullResult(id);
434 }
435 else if (method == "exit")
436 {
438 break;
439 }
440
442 }
443
444 session.quit();
445 }
446
447} // namespace
448
449int main(int argc, char* argv[])
450{
451 std::string cmdInArg;
452 std::string cmdOutArg;
453 for (int i = 1; i < argc; i++)
454 {
455 const std::string arg = argv[i];
456 if (arg.rfind("--cmdInFd=", 0) == 0)
457 cmdInArg = arg;
458 else if (arg.rfind("--cmdOutFd=", 0) == 0)
459 cmdOutArg = arg;
460 }
461
462 if (cmdInArg.empty() || cmdOutArg.empty())
463 {
464 std::cerr << "usage: " << (argc > 0 ? argv[0] : "freerdp-webview-aad-helper")
465 << " --cmdInFd=<handle> --cmdOutFd=<handle>" << std::endl;
466 return 1;
467 }
468
469 g_cmdIn = winpr_importHandleFromString(cmdInArg.c_str(), "--cmdInFd={}");
470 g_cmdOut = winpr_importHandleFromString(cmdOutArg.c_str(), "--cmdOutFd={}");
471 if (!g_cmdIn || (g_cmdIn == INVALID_HANDLE_VALUE) || !g_cmdOut ||
472 (g_cmdOut == INVALID_HANDLE_VALUE))
473 {
474 std::cerr << "failed to import the cmdIn/cmdOut channel handles" << std::endl;
475 return 1;
476 }
477
478 Session session;
479 std::thread reader(readerLoop, std::ref(session));
480
481 session.run();
482 reader.join();
483
484 (void)CloseHandle(g_cmdIn);
485 (void)CloseHandle(g_cmdOut);
486 return 0;
487}
WINPR_ATTR_NODISCARD WINPR_API WINPR_JSON * WINPR_JSON_AddObjectToObject(WINPR_JSON *object, const char *name)
WINPR_JSON_AddObjectToObject.
Definition c-json.c:274
WINPR_ATTR_NODISCARD WINPR_API WINPR_JSON * WINPR_JSON_CreateObject(void)
WINPR_JSON_CreateObject.
Definition c-json.c:232
WINPR_ATTR_NODISCARD WINPR_API BOOL WINPR_JSON_IsNumber(const WINPR_JSON *item)
Check if JSON item is of type Number.
Definition c-json.c:177
WINPR_ATTR_NODISCARD WINPR_API WINPR_JSON * WINPR_JSON_AddIntegerToObject(WINPR_JSON *object, const char *name, int64_t number)
WINPR_JSON_AddIntegerToObject.
Definition c-json.c:262
WINPR_ATTR_NODISCARD WINPR_API WINPR_JSON * WINPR_JSON_GetObjectItemCaseSensitive(const WINPR_JSON *object, const char *string)
Same as WINPR_JSON_GetObjectItem but with case sensitive matching.
Definition c-json.c:127
WINPR_ATTR_NODISCARD WINPR_API WINPR_JSON * WINPR_JSON_AddNumberToObject(WINPR_JSON *object, const char *name, double number)
WINPR_JSON_AddNumberToObject.
Definition c-json.c:257
WINPR_ATTR_NODISCARD WINPR_API BOOL WINPR_JSON_IsString(const WINPR_JSON *item)
Check if JSON item is of type String.
Definition c-json.c:182
WINPR_API char * WINPR_JSON_PrintUnformatted(WINPR_JSON *item)
Serialize a JSON instance to string without formatting for human readable formatted output see WINPR_...
Definition c-json.c:311
WINPR_ATTR_NODISCARD WINPR_API double WINPR_JSON_GetNumberValue(const WINPR_JSON *item)
Return the Number value of a JSON item.
Definition c-json.c:147
WINPR_ATTR_NODISCARD WINPR_API WINPR_JSON * WINPR_JSON_AddNullToObject(WINPR_JSON *object, const char *name)
WINPR_JSON_AddNullToObject.
Definition c-json.c:237
WINPR_ATTR_NODISCARD WINPR_API WINPR_JSON * WINPR_JSON_AddStringToObject(WINPR_JSON *object, const char *name, const char *string)
WINPR_JSON_AddStringToObject.
Definition c-json.c:269
WINPR_API void WINPR_JSON_Delete(WINPR_JSON *item)
Delete a WinPR JSON wrapper object.
Definition c-json.c:103
WINPR_ATTR_NODISCARD WINPR_API const char * WINPR_JSON_GetStringValue(WINPR_JSON *item)
Return the String value of a JSON item.
Definition c-json.c:142
WINPR_API WINPR_JSON * WINPR_JSON_Parse(const char *value)
Parse a '\0' terminated JSON string.
Definition c-json.c:93