202 def navigate(self, msg_id, params: dict) -> None:
203 with self.lock:
204 if self.shutting_down:
205 self.channel.send(make_error(msg_id, 1, "shutting_down"))
206 return
207 if self.current is not None:
208 self.channel.send(make_error(msg_id, 1, "navigate_already_in_progress"))
209 return
210 state = NavigateState(uuid.uuid4().hex[:12])
211 self.current = state
212
213 title = params.get("title", "")
214 url = params.get("url", "")
215 redirect_uri = params.get("redirect_uri", "")
216 timeout_ms = params.get("timeout_ms") or DEFAULT_TIMEOUT_MS
217
218 user_agent = params.get("user_agent", "")
219 if user_agent:
220 print("[xdgopen-aad-helper] warning: user_agent was configured, but this helper "
221 "opens the system browser and cannot override its User-Agent; ignoring "
222 "user_agent", file=sys.stderr)
223
224 if not redirect_uri.lower().startswith(BROKER_SCHEME):
225 with self.lock:
226 self.current = None
227 self.channel.send(make_error(msg_id, 1, "unsupported_redirect_scheme"))
228 return
229
230 effective_url, state.had_original_state = rewrite_state(url, state.freerdp_id)
231
232 runtime_dir = os.environ.get("XDG_RUNTIME_DIR", "/tmp")
233 sock_path = os.path.join(runtime_dir, f"freerdp_aad_{state.freerdp_id}.sock")
234 try:
235 os.unlink(sock_path)
236 except FileNotFoundError:
237 pass
238
239 server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
240 redirect_url = None
241 error = None
242 try:
243 server.bind(sock_path)
244 os.chmod(sock_path, 0o600)
245 server.listen(1)
246 server.settimeout(0.5)
247
248 print(f"[xdgopen-aad-helper] navigate: opening browser for {effective_url!r} "
249 f"(title={title!r}), waiting on {sock_path}", file=sys.stderr)
250 subprocess.Popen(["xdg-open", effective_url])
251
252 deadline = time.monotonic() + timeout_ms / 1000.0
253 while redirect_url is None and error is None:
254 if state.cancelled.is_set():
255 error = "user_cancelled"
256 break
257 with self.lock:
258 if self.shutting_down:
259 error = "shutting_down"
260 break
261 if time.monotonic() >= deadline:
262 error = "timeout"
263 break
264 try:
265 conn, _ = server.accept()
266 except socket.timeout:
267 continue
268 with conn:
269 conn.settimeout(2.0)
270 data = b""
271 try:
272 while True:
273 chunk = conn.recv(4096)
274 if not chunk:
275 break
276 data += chunk
277 except socket.timeout:
278 pass
279 redirect_url = data.decode(errors="replace").strip()
280 finally:
281 server.close()
282 try:
283 os.unlink(sock_path)
284 except FileNotFoundError:
285 pass
286 with self.lock:
287 self.current = None
288
289 if error:
290 self.channel.send(make_error(msg_id, 1, error))
291 return
292
293 redirect_url = restore_state(redirect_url, state.freerdp_id, state.had_original_state)
294
295 parsed = urllib.parse.urlparse(redirect_url)
296 qs = urllib.parse.parse_qs(parsed.query)
297 if "error" in qs:
298 message = qs["error"][0]
299 if "error_subcode" in qs:
300 message += ": " + qs["error_subcode"][0]
301 self.channel.send(make_error(msg_id, 1, message))
302 return
303
304 self.channel.send(make_result(msg_id, {"status": "ok", "redirect_url": redirect_url}))
305