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, std::string& redirectUrl,
84 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]()
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 w.navigate(url);
111 });
112
113 const uint32_t timeout = timeoutMs ? timeoutMs : kDefaultTimeoutMs;
114 bool signalled = false;
115 {
116 std::unique_lock<std::mutex> lock(pending.mtx);
117 signalled = pending.cv.wait_for(lock, std::chrono::milliseconds(timeout),
118 [&] { return pending.done; });
119 }
120
121 {
122 std::lock_guard<std::mutex> lock(stateMtx);
123 /* still ours: nobody finished it (timeout) - detach the now-invalid pointer to the
124 * stack-local `pending` before it goes out of scope */
125 if (current == &pending)
126 current = nullptr;
127 }
128
129 /* the popup has served its purpose once this navigate() is done, whatever the
130 * outcome - hide it now rather than leaving it sitting on screen until the next
131 * navigate() (if any) reuses and re-shows it. webview has no portable hide(), so on
132 * platforms without a native-handle-based fallback below, blanking the content is the
133 * best available approximation. */
134 w.dispatch(
135 [this]()
136 {
137#if defined(__linux__)
138 auto handle = w.window();
139 if (handle.ok())
140 gtk_widget_hide(GTK_WIDGET(handle.value()));
141 else
142 w.navigate("about:blank");
143#else
144 w.navigate("about:blank");
145#endif
146 });
147
148 if (!signalled)
149 {
150 error = "timeout";
151 return false;
152 }
153
154 if (!pending.ok)
155 {
156 error = pending.errorMessage.empty() ? "user_cancelled" : pending.errorMessage;
157 return false;
158 }
159
160 redirectUrl = pending.redirectUrl;
161 return true;
162 }
163
164 void cancel()
165 {
166 std::lock_guard<std::mutex> lock(stateMtx);
167 finishCurrentLocked(false, "", "user_cancelled");
168 }
169
170 void quit()
171 {
172 {
173 std::lock_guard<std::mutex> lock(stateMtx);
174 finishCurrentLocked(false, "", "shutting_down");
175 }
176 w.dispatch([this]() { w.terminate(); });
177 }
178
179 private:
180 static void onNavigateStatic(webview_t wv, const char* uri, webview_navigation_event_t type,
181 void* arg)
182 {
183 (void)wv;
184 if (type != WEBVIEW_LOAD_FINISHED)
185 return;
186 static_cast<Session*>(arg)->onNavigate(uri);
187 }
188
189 void onNavigate(const std::string& uri)
190 {
191 std::lock_guard<std::mutex> lock(stateMtx);
192 if (!current || !watcher.matches(uri))
193 return;
194
195 std::string err;
196 if (watcher.hasError(uri, err))
197 finishCurrentLocked(false, "", err);
198 else
199 finishCurrentLocked(true, uri, "");
200 }
201
202 /* caller must hold stateMtx */
203 void finishCurrentLocked(bool ok, const std::string& redirectUrl, const std::string& err)
204 {
205 if (!current)
206 return;
207 {
208 std::lock_guard<std::mutex> lock(current->mtx);
209 current->done = true;
210 current->ok = ok;
211 current->redirectUrl = redirectUrl;
212 current->errorMessage = err;
213 }
214 current->cv.notify_all();
215 current = nullptr;
216 }
217
218 webview::webview w;
219 std::mutex stateMtx;
220 PendingResult* current = nullptr;
221 RedirectWatcher watcher{ std::string() };
222 };
223
224 /* the JSON-RPC channel: handles imported from the --cmdInFd=/--cmdOutFd= command line arguments
225 * (see main()), not stdin/stdout - so this helper's own stdio stays free for its normal
226 * diagnostic output instead of colliding with the protocol. */
227 HANDLE g_cmdIn = nullptr;
228 HANDLE g_cmdOut = nullptr;
229
230 bool writeLine(const std::string& line)
231 {
232 std::string data = line;
233 data += "\n";
234
235 size_t written = 0;
236 while (written < data.size())
237 {
238 DWORD dwWritten = 0;
239 if (!WriteFile(g_cmdOut, data.data() + written,
240 static_cast<DWORD>(data.size() - written), &dwWritten, nullptr) ||
241 (dwWritten == 0))
242 return false;
243 written += dwWritten;
244 }
245 return true;
246 }
247
248 /* extracts one '\n'-terminated line already buffered in `buf`, if any */
249 bool extractLine(std::string& buf, std::string& line)
250 {
251 const auto pos = buf.find('\n');
252 if (pos == std::string::npos)
253 return false;
254 line = buf.substr(0, pos);
255 buf.erase(0, pos + 1);
256 return true;
257 }
258
259 bool readLine(std::string& buf, std::string& line)
260 {
261 if (extractLine(buf, line))
262 return true;
263
264 while (true)
265 {
266 char chunk[4096];
267 DWORD dwRead = 0;
268 if (!ReadFile(g_cmdIn, chunk, sizeof(chunk), &dwRead, nullptr) || (dwRead == 0))
269 return false;
270 buf.append(chunk, dwRead);
271 if (extractLine(buf, line))
272 return true;
273 }
274 }
275
276 void sendLine(const std::string& line)
277 {
278 (void)writeLine(line);
279 }
280
281 void sendJson(WINPR_JSON* obj)
282 {
283 char* str = WINPR_JSON_PrintUnformatted(obj);
284 if (str)
285 {
286 sendLine(str);
287 free(str);
288 }
290 }
291
292 void sendHelloResult(int64_t id)
293 {
294 WINPR_JSON* obj = WINPR_JSON_CreateObject();
295 bool ok = WINPR_JSON_AddStringToObject(obj, "jsonrpc", "2.0") &&
296 WINPR_JSON_AddNumberToObject(obj, "id", static_cast<double>(id));
297 WINPR_JSON* result = ok ? WINPR_JSON_AddObjectToObject(obj, "result") : nullptr;
298 if (result)
299 {
300 bool unused =
301 WINPR_JSON_AddIntegerToObject(result, "protocol_version", 1) &&
302 WINPR_JSON_AddStringToObject(result, "helper", "freerdp-webview-aad-helper/1.0");
303 (void)unused;
304 }
305 sendJson(obj);
306 }
307
308 void sendNavigateResult(int64_t id, const std::string& redirectUrl)
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 = WINPR_JSON_AddStringToObject(result, "status", "ok") &&
317 WINPR_JSON_AddStringToObject(result, "redirect_url", redirectUrl.c_str());
318 (void)unused;
319 }
320 sendJson(obj);
321 }
322
323 void sendNullResult(int64_t id)
324 {
325 WINPR_JSON* obj = WINPR_JSON_CreateObject();
326 bool ok = WINPR_JSON_AddStringToObject(obj, "jsonrpc", "2.0") &&
327 WINPR_JSON_AddNumberToObject(obj, "id", static_cast<double>(id)) &&
328 WINPR_JSON_AddNullToObject(obj, "result");
329 (void)ok;
330 sendJson(obj);
331 }
332
333 void sendError(int64_t id, int code, const std::string& message)
334 {
335 WINPR_JSON* obj = WINPR_JSON_CreateObject();
336 bool ok = WINPR_JSON_AddStringToObject(obj, "jsonrpc", "2.0") &&
337 WINPR_JSON_AddNumberToObject(obj, "id", static_cast<double>(id));
338 WINPR_JSON* error = ok ? WINPR_JSON_AddObjectToObject(obj, "error") : nullptr;
339 if (error)
340 {
341 bool unused = WINPR_JSON_AddIntegerToObject(error, "code", code) &&
342 WINPR_JSON_AddStringToObject(error, "message", message.c_str());
343 (void)unused;
344 }
345 sendJson(obj);
346 }
347
348 std::string getStringField(WINPR_JSON* obj, const char* name)
349 {
350 if (!obj)
351 return {};
352 WINPR_JSON* item = WINPR_JSON_GetObjectItemCaseSensitive(obj, name);
353 if (!item || !WINPR_JSON_IsString(item))
354 return {};
355 const char* v = WINPR_JSON_GetStringValue(item);
356 return v ? v : "";
357 }
358
359 uint32_t getUintField(WINPR_JSON* obj, const char* name, uint32_t def)
360 {
361 if (!obj)
362 return def;
363 WINPR_JSON* item = WINPR_JSON_GetObjectItemCaseSensitive(obj, name);
364 if (!item || !WINPR_JSON_IsNumber(item))
365 return def;
366 return static_cast<uint32_t>(WINPR_JSON_GetNumberValue(item));
367 }
368
372 void readerLoop(Session& session)
373 {
374 std::string buf;
375 std::string line;
376 while (readLine(buf, line))
377 {
378 if (line.empty())
379 continue;
380
381 WINPR_JSON* msg = WINPR_JSON_Parse(line.c_str());
382 if (!msg)
383 continue;
384
385 WINPR_JSON* idItem = WINPR_JSON_GetObjectItemCaseSensitive(msg, "id");
386 const bool hasId = idItem && WINPR_JSON_IsNumber(idItem);
387 const int64_t id = hasId ? static_cast<int64_t>(WINPR_JSON_GetNumberValue(idItem)) : 0;
388 const std::string method = getStringField(msg, "method");
389 WINPR_JSON* params = WINPR_JSON_GetObjectItemCaseSensitive(msg, "params");
390
391 if (method == "hello")
392 {
393 sendHelloResult(id);
394 }
395 else if (method == "navigate")
396 {
397 const std::string title = getStringField(params, "title");
398 const std::string url = getStringField(params, "url");
399 const std::string redirectUri = getStringField(params, "redirect_uri");
400 const uint32_t timeoutMs = getUintField(params, "timeout_ms", kDefaultTimeoutMs);
401
402 std::string redirectUrl;
403 std::string error;
404 if (session.navigate(title, url, redirectUri, timeoutMs, redirectUrl, error))
405 sendNavigateResult(id, redirectUrl);
406 else
407 sendError(id, 1, error);
408 }
409 else if (method == "cancel")
410 {
411 session.cancel();
412 }
413 else if (method == "shutdown")
414 {
415 sendNullResult(id);
416 }
417 else if (method == "exit")
418 {
420 break;
421 }
422
424 }
425
426 session.quit();
427 }
428
429} // namespace
430
431int main(int argc, char* argv[])
432{
433 std::string cmdInArg;
434 std::string cmdOutArg;
435 for (int i = 1; i < argc; i++)
436 {
437 const std::string arg = argv[i];
438 if (arg.rfind("--cmdInFd=", 0) == 0)
439 cmdInArg = arg;
440 else if (arg.rfind("--cmdOutFd=", 0) == 0)
441 cmdOutArg = arg;
442 }
443
444 if (cmdInArg.empty() || cmdOutArg.empty())
445 {
446 std::cerr << "usage: " << (argc > 0 ? argv[0] : "freerdp-webview-aad-helper")
447 << " --cmdInFd=<handle> --cmdOutFd=<handle>" << std::endl;
448 return 1;
449 }
450
451 g_cmdIn = winpr_importHandleFromString(cmdInArg.c_str(), "--cmdInFd={}");
452 g_cmdOut = winpr_importHandleFromString(cmdOutArg.c_str(), "--cmdOutFd={}");
453 if (!g_cmdIn || (g_cmdIn == INVALID_HANDLE_VALUE) || !g_cmdOut ||
454 (g_cmdOut == INVALID_HANDLE_VALUE))
455 {
456 std::cerr << "failed to import the cmdIn/cmdOut channel handles" << std::endl;
457 return 1;
458 }
459
460 Session session;
461 std::thread reader(readerLoop, std::ref(session));
462
463 session.run();
464 reader.join();
465
466 (void)CloseHandle(g_cmdIn);
467 (void)CloseHandle(g_cmdOut);
468 return 0;
469}
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