Coverage Report

Created: 2025-06-13 06:55

/src/glib/gio/gnetworkmonitorportal.c
Line
Count
Source (jump to first uncovered line)
1
/* GIO - GLib Input, Output and Streaming Library
2
 *
3
 * Copyright 2016 Red Hat, Inc.
4
 *
5
 * SPDX-License-Identifier: LGPL-2.1-or-later
6
 *
7
 * This library is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU Lesser General Public
9
 * License as published by the Free Software Foundation; either
10
 * version 2.1 of the License, or (at your option) any later version.
11
 *
12
 * This library is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 * Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General
18
 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
19
 */
20
21
#include "config.h"
22
23
#include "gnetworkmonitorportal.h"
24
#include "ginitable.h"
25
#include "giomodule-priv.h"
26
#include "xdp-dbus.h"
27
#include "gportalsupport.h"
28
29
static GInitableIface *initable_parent_iface;
30
static void g_network_monitor_portal_iface_init (GNetworkMonitorInterface *iface);
31
static void g_network_monitor_portal_initable_iface_init (GInitableIface *iface);
32
33
enum
34
{
35
  PROP_0,
36
  PROP_NETWORK_AVAILABLE,
37
  PROP_NETWORK_METERED,
38
  PROP_CONNECTIVITY
39
};
40
41
struct _GNetworkMonitorPortalPrivate
42
{
43
  GDBusProxy *proxy;
44
  gboolean has_network;
45
46
  gboolean available;
47
  gboolean metered;
48
  GNetworkConnectivity connectivity;
49
};
50
51
G_DEFINE_TYPE_WITH_CODE (GNetworkMonitorPortal, g_network_monitor_portal, G_TYPE_NETWORK_MONITOR_BASE,
52
                         G_ADD_PRIVATE (GNetworkMonitorPortal)
53
                         G_IMPLEMENT_INTERFACE (G_TYPE_NETWORK_MONITOR,
54
                                                g_network_monitor_portal_iface_init)
55
                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
56
                                                g_network_monitor_portal_initable_iface_init)
57
                         _g_io_modules_ensure_extension_points_registered ();
58
                         g_io_extension_point_implement (G_NETWORK_MONITOR_EXTENSION_POINT_NAME,
59
                                                         g_define_type_id,
60
                                                         "portal",
61
                                                         40))
62
63
static void
64
g_network_monitor_portal_init (GNetworkMonitorPortal *nm)
65
0
{
66
0
  nm->priv = g_network_monitor_portal_get_instance_private (nm);
67
0
}
68
69
static void
70
g_network_monitor_portal_get_property (GObject    *object,
71
                                       guint       prop_id,
72
                                       GValue     *value,
73
                                       GParamSpec *pspec)
74
0
{
75
0
  GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (object);
76
77
0
  switch (prop_id)
78
0
    {
79
0
    case PROP_NETWORK_AVAILABLE:
80
0
      g_value_set_boolean (value, nm->priv->available);
81
0
      break;
82
83
0
    case PROP_NETWORK_METERED:
84
0
      g_value_set_boolean (value, nm->priv->metered);
85
0
      break;
86
87
0
    case PROP_CONNECTIVITY:
88
0
      g_value_set_enum (value, nm->priv->connectivity);
89
0
      break;
90
91
0
    default:
92
0
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
93
0
      break;
94
0
    }
95
0
}
96
97
static gboolean
98
is_valid_connectivity (guint32 value)
99
0
{
100
0
  GEnumValue *enum_value;
101
0
  GEnumClass *enum_klass;
102
103
0
  enum_klass = g_type_class_ref (G_TYPE_NETWORK_CONNECTIVITY);
104
0
  enum_value = g_enum_get_value (enum_klass, value);
105
106
0
  g_type_class_unref (enum_klass);
107
108
0
  return enum_value != NULL;
109
0
}
110
111
static void
112
got_available (GObject *source,
113
               GAsyncResult *res,
114
               gpointer data)
115
0
{
116
0
  GDBusProxy *proxy = G_DBUS_PROXY (source);
117
0
  GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (data);
118
0
  GError *error = NULL;
119
0
  GVariant *ret;
120
0
  gboolean available;
121
  
122
0
  ret = g_dbus_proxy_call_finish (proxy, res, &error);
123
0
  if (ret == NULL)
124
0
    {
125
0
      if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
126
0
        {
127
0
          g_warning ("%s", error->message);
128
0
          g_clear_error (&error);
129
0
          return;
130
0
        }
131
132
0
      g_clear_error (&error);
133
134
      /* Fall back to version 1 */
135
0
      ret = g_dbus_proxy_get_cached_property (nm->priv->proxy, "available");
136
0
      if (ret == NULL)
137
0
        {
138
0
          g_warning ("Failed to get the '%s' property", "available");
139
0
          return;
140
0
        }
141
142
0
      available = g_variant_get_boolean (ret);
143
0
      g_variant_unref (ret);
144
0
    }
145
0
  else
146
0
    {
147
0
      g_variant_get (ret, "(b)", &available);
148
0
      g_variant_unref (ret);
149
0
    }
150
151
0
  if (nm->priv->available != available)
152
0
    {
153
0
      nm->priv->available = available;
154
0
      g_object_notify (G_OBJECT (nm), "network-available");
155
0
      g_signal_emit_by_name (nm, "network-changed", available);
156
0
    }
157
0
}
158
159
static void
160
got_metered (GObject *source,
161
             GAsyncResult *res,
162
             gpointer data)
163
0
{
164
0
  GDBusProxy *proxy = G_DBUS_PROXY (source);
165
0
  GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (data);
166
0
  GError *error = NULL;
167
0
  GVariant *ret;
168
0
  gboolean metered;
169
  
170
0
  ret = g_dbus_proxy_call_finish (proxy, res, &error);
171
0
  if (ret == NULL)
172
0
    {
173
0
      if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
174
0
        {
175
0
          g_warning ("%s", error->message);
176
0
          g_clear_error (&error);
177
0
          return;
178
0
        }
179
180
0
      g_clear_error (&error);
181
182
      /* Fall back to version 1 */
183
0
      ret = g_dbus_proxy_get_cached_property (nm->priv->proxy, "metered");
184
0
      if (ret == NULL)
185
0
        {
186
0
          g_warning ("Failed to get the '%s' property", "metered");
187
0
          return;
188
0
        }
189
190
0
      metered = g_variant_get_boolean (ret);
191
0
      g_variant_unref (ret);
192
0
    }
193
0
  else
194
0
    {
195
0
      g_variant_get (ret, "(b)", &metered);
196
0
      g_variant_unref (ret);
197
0
    }
198
199
0
  if (nm->priv->metered != metered)
200
0
    {
201
0
      nm->priv->metered = metered;
202
0
      g_object_notify (G_OBJECT (nm), "network-metered");
203
0
      g_signal_emit_by_name (nm, "network-changed", nm->priv->available);
204
0
    }
205
0
}
206
207
static void
208
got_connectivity (GObject *source,
209
                  GAsyncResult *res,
210
                  gpointer data)
211
0
{
212
0
  GDBusProxy *proxy = G_DBUS_PROXY (source);
213
0
  GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (data);
214
0
  GError *error = NULL;
215
0
  GVariant *ret;
216
0
  GNetworkConnectivity connectivity;
217
  
218
0
  ret = g_dbus_proxy_call_finish (proxy, res, &error);
219
0
  if (ret == NULL)
220
0
    {
221
0
      if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
222
0
        {
223
0
          g_warning ("%s", error->message);
224
0
          g_clear_error (&error);
225
0
          return;
226
0
        }
227
228
0
      g_clear_error (&error);
229
230
      /* Fall back to version 1 */
231
0
      ret = g_dbus_proxy_get_cached_property (nm->priv->proxy, "connectivity");
232
0
      if (ret == NULL)
233
0
        {
234
0
          g_warning ("Failed to get the '%s' property", "connectivity");
235
0
          return;
236
0
        }
237
238
0
      connectivity = g_variant_get_uint32 (ret);
239
0
      g_variant_unref (ret);
240
0
    }
241
0
  else
242
0
    {
243
0
      g_variant_get (ret, "(u)", &connectivity);
244
0
      g_variant_unref (ret);
245
0
    }
246
247
0
  if (nm->priv->connectivity != connectivity &&
248
0
      is_valid_connectivity (connectivity))
249
0
    {
250
0
      nm->priv->connectivity = connectivity;
251
0
      g_object_notify (G_OBJECT (nm), "connectivity");
252
0
      g_signal_emit_by_name (nm, "network-changed", nm->priv->available);
253
0
    }
254
0
}
255
256
static void
257
got_status (GObject *source,
258
            GAsyncResult *res,
259
            gpointer data)
260
0
{
261
0
  GDBusProxy *proxy = G_DBUS_PROXY (source);
262
0
  GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (data);
263
0
  GError *error = NULL;
264
0
  GVariant *ret;
265
0
  GVariant *status;
266
0
  gboolean available;
267
0
  gboolean metered;
268
0
  GNetworkConnectivity connectivity;
269
270
0
  ret = g_dbus_proxy_call_finish (proxy, res, &error);
271
0
  if (ret == NULL)
272
0
    {
273
0
      if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
274
0
        {
275
          /* Fall back to version 2 */
276
0
          g_dbus_proxy_call (proxy, "GetConnectivity", NULL, 0, -1, NULL, got_connectivity, nm);
277
0
          g_dbus_proxy_call (proxy, "GetMetered", NULL, 0, -1, NULL, got_metered, nm);
278
0
          g_dbus_proxy_call (proxy, "GetAvailable", NULL, 0, -1, NULL, got_available, nm);
279
0
        }
280
0
      else
281
0
        g_warning ("%s", error->message);
282
283
0
      g_clear_error (&error);
284
0
      return;
285
0
    }
286
287
0
  g_variant_get (ret, "(@a{sv})", &status);
288
0
  g_variant_unref (ret);
289
290
0
  g_variant_lookup (status, "available", "b", &available);
291
0
  g_variant_lookup (status, "metered", "b", &metered);
292
0
  g_variant_lookup (status, "connectivity", "u", &connectivity);
293
0
  g_variant_unref (status);
294
295
0
  g_object_freeze_notify (G_OBJECT (nm));
296
297
0
  if (nm->priv->available != available)
298
0
    {
299
0
      nm->priv->available = available;
300
0
      g_object_notify (G_OBJECT (nm), "network-available");
301
0
    }
302
303
0
  if (nm->priv->metered != metered)
304
0
    {
305
0
      nm->priv->metered = metered;
306
0
      g_object_notify (G_OBJECT (nm), "network-metered");
307
0
    }
308
309
0
  if (nm->priv->connectivity != connectivity &&
310
0
      is_valid_connectivity (connectivity))
311
0
    {
312
0
      nm->priv->connectivity = connectivity;
313
0
      g_object_notify (G_OBJECT (nm), "connectivity");
314
0
    }
315
316
0
  g_object_thaw_notify (G_OBJECT (nm));
317
318
0
  g_signal_emit_by_name (nm, "network-changed", available);
319
0
}
320
321
static void
322
update_properties (GDBusProxy *proxy,
323
                   GNetworkMonitorPortal *nm)
324
0
{
325
  /* Try version 3 first */
326
0
  g_dbus_proxy_call (proxy, "GetStatus", NULL, 0, -1, NULL, got_status, nm);
327
0
}
328
329
static void
330
proxy_signal (GDBusProxy            *proxy,
331
              const char            *sender,
332
              const char            *signal,
333
              GVariant              *parameters,
334
              GNetworkMonitorPortal *nm)
335
0
{
336
0
  if (!nm->priv->has_network)
337
0
    return;
338
339
0
  if (strcmp (signal, "changed") != 0)
340
0
    return;
341
342
  /* Version 1 updates "available" with the "changed" signal */
343
0
  if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(b)")))
344
0
    {
345
0
      gboolean available;
346
347
0
      g_variant_get (parameters, "(b)", &available);
348
0
      if (nm->priv->available != available)
349
0
        {
350
0
          nm->priv->available = available;
351
0
          g_object_notify (G_OBJECT (nm), "available");
352
0
        }
353
0
      g_signal_emit_by_name (nm, "network-changed", available);
354
0
    }
355
0
  else
356
0
    {
357
0
      update_properties (proxy, nm);
358
0
    }
359
0
}
360
361
static void
362
proxy_properties_changed (GDBusProxy            *proxy,
363
                          GVariant              *changed,
364
                          GVariant              *invalidated,
365
                          GNetworkMonitorPortal *nm)
366
0
{
367
0
  gboolean should_emit_changed = FALSE;
368
0
  GVariant *ret;
369
370
0
  if (!nm->priv->has_network)
371
0
    return;
372
373
0
  ret = g_dbus_proxy_get_cached_property (proxy, "connectivity");
374
0
  if (ret)
375
0
    {
376
0
      GNetworkConnectivity connectivity = g_variant_get_uint32 (ret);
377
0
      if (nm->priv->connectivity != connectivity &&
378
0
          is_valid_connectivity (connectivity))
379
0
        {
380
0
          nm->priv->connectivity = connectivity;
381
0
          g_object_notify (G_OBJECT (nm), "connectivity");
382
0
          should_emit_changed = TRUE;
383
0
        }
384
0
      g_variant_unref (ret);
385
0
    }
386
387
0
  ret = g_dbus_proxy_get_cached_property (proxy, "metered");
388
0
  if (ret)
389
0
    {
390
0
      gboolean metered = g_variant_get_boolean (ret);
391
0
      if (nm->priv->metered != metered)
392
0
        {
393
0
          nm->priv->metered = metered;
394
0
          g_object_notify (G_OBJECT (nm), "network-metered");
395
0
          should_emit_changed = TRUE;
396
0
        }
397
0
      g_variant_unref (ret);
398
0
    }
399
400
0
  ret = g_dbus_proxy_get_cached_property (proxy, "available");
401
0
  if (ret)
402
0
    {
403
0
      gboolean available = g_variant_get_boolean (ret);
404
0
      if (nm->priv->available != available)
405
0
        {
406
0
          nm->priv->available = available;
407
0
          g_object_notify (G_OBJECT (nm), "network-available");
408
0
          should_emit_changed = TRUE;
409
0
        } 
410
0
      g_variant_unref (ret);
411
0
    }
412
413
0
  if (should_emit_changed)
414
0
    g_signal_emit_by_name (nm, "network-changed", nm->priv->available);
415
0
}
416
                           
417
static gboolean
418
g_network_monitor_portal_initable_init (GInitable     *initable,
419
                                        GCancellable  *cancellable,
420
                                        GError       **error)
421
0
{
422
0
  GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (initable);
423
0
  GDBusProxy *proxy;
424
0
  gchar *name_owner = NULL;
425
426
0
  nm->priv->available = FALSE;
427
0
  nm->priv->metered = FALSE;
428
0
  nm->priv->connectivity = G_NETWORK_CONNECTIVITY_LOCAL;
429
430
0
  if (!glib_should_use_portal ())
431
0
    {
432
0
      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Not using portals");
433
0
      return FALSE;
434
0
    }
435
436
0
  proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
437
0
                                         G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
438
0
                                         NULL,
439
0
                                         "org.freedesktop.portal.Desktop",
440
0
                                         "/org/freedesktop/portal/desktop",
441
0
                                         "org.freedesktop.portal.NetworkMonitor",
442
0
                                         cancellable,
443
0
                                         error);
444
0
  if (!proxy)
445
0
    return FALSE;
446
447
0
  name_owner = g_dbus_proxy_get_name_owner (proxy);
448
449
0
  if (!name_owner)
450
0
    {
451
0
      g_object_unref (proxy);
452
0
      g_set_error (error,
453
0
                   G_DBUS_ERROR,
454
0
                   G_DBUS_ERROR_NAME_HAS_NO_OWNER,
455
0
                   "Desktop portal not found");
456
0
      return FALSE;
457
0
    }
458
459
0
  g_free (name_owner);
460
461
0
  g_signal_connect (proxy, "g-signal", G_CALLBACK (proxy_signal), nm);
462
0
  g_signal_connect (proxy, "g-properties-changed", G_CALLBACK (proxy_properties_changed), nm);
463
464
0
  nm->priv->proxy = proxy;
465
0
  nm->priv->has_network = glib_network_available_in_sandbox ();
466
467
0
  if (!initable_parent_iface->init (initable, cancellable, error))
468
0
    return FALSE;
469
470
0
  if (nm->priv->has_network)
471
0
    update_properties (proxy, nm);
472
473
0
  return TRUE;
474
0
}
475
476
static void
477
g_network_monitor_portal_finalize (GObject *object)
478
0
{
479
0
  GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (object);
480
481
0
  g_clear_object (&nm->priv->proxy);
482
483
0
  G_OBJECT_CLASS (g_network_monitor_portal_parent_class)->finalize (object);
484
0
}
485
486
static void
487
g_network_monitor_portal_class_init (GNetworkMonitorPortalClass *class)
488
0
{
489
0
  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
490
491
0
  gobject_class->finalize  = g_network_monitor_portal_finalize;
492
0
  gobject_class->get_property = g_network_monitor_portal_get_property;
493
494
0
  g_object_class_override_property (gobject_class, PROP_NETWORK_AVAILABLE, "network-available");
495
0
  g_object_class_override_property (gobject_class, PROP_NETWORK_METERED, "network-metered");
496
0
  g_object_class_override_property (gobject_class, PROP_CONNECTIVITY, "connectivity");
497
0
}
498
499
static gboolean
500
g_network_monitor_portal_can_reach (GNetworkMonitor     *monitor,
501
                                    GSocketConnectable  *connectable,
502
                                    GCancellable        *cancellable,
503
                                    GError             **error)
504
0
{
505
0
  GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (monitor);
506
0
  GVariant *ret;
507
0
  GNetworkAddress *address;
508
0
  gboolean reachable = FALSE;
509
510
0
  if (!G_IS_NETWORK_ADDRESS (connectable))
511
0
    {
512
0
      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
513
0
                   "Can't handle this kind of GSocketConnectable (%s)",
514
0
                   G_OBJECT_TYPE_NAME (connectable));
515
0
      return FALSE;
516
0
    }
517
518
0
  address = G_NETWORK_ADDRESS (connectable);
519
520
0
  ret = g_dbus_proxy_call_sync (nm->priv->proxy,
521
0
                                "CanReach",
522
0
                                g_variant_new ("(su)",
523
0
                                               g_network_address_get_hostname (address),
524
0
                                               g_network_address_get_port (address)),
525
0
                                G_DBUS_CALL_FLAGS_NONE,
526
0
                                -1,
527
0
                                cancellable,
528
0
                                error);
529
  
530
0
  if (ret)
531
0
    {
532
0
      g_variant_get (ret, "(b)", &reachable);
533
0
      g_variant_unref (ret);
534
0
    }
535
536
0
  return reachable;
537
0
}
538
539
static void
540
can_reach_done (GObject      *source,
541
                GAsyncResult *result,
542
                gpointer      data)
543
0
{
544
0
  GTask *task = data;
545
0
  GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (g_task_get_source_object (task));
546
0
  GError *error = NULL;
547
0
  GVariant *ret;
548
0
  gboolean reachable;
549
550
0
  ret = g_dbus_proxy_call_finish (nm->priv->proxy, result, &error);
551
0
  if (ret == NULL)
552
0
    {
553
0
      g_task_return_error (task, error);
554
0
      g_object_unref (task);
555
0
      return;
556
0
    }
557
 
558
0
  g_variant_get (ret, "(b)", &reachable);
559
0
  g_variant_unref (ret);
560
561
0
  if (reachable)
562
0
    g_task_return_boolean (task, TRUE);
563
0
  else
564
0
    g_task_return_new_error (task,
565
0
                             G_IO_ERROR, G_IO_ERROR_HOST_UNREACHABLE,
566
0
                             "Can't reach host");
567
568
0
  g_object_unref (task);
569
0
}
570
571
static void
572
g_network_monitor_portal_can_reach_async (GNetworkMonitor     *monitor,
573
                                          GSocketConnectable  *connectable,
574
                                          GCancellable        *cancellable,
575
                                          GAsyncReadyCallback  callback,
576
                                          gpointer             data)
577
0
{
578
0
  GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (monitor);
579
0
  GTask *task;
580
0
  GNetworkAddress *address;
581
582
0
  task = g_task_new (monitor, cancellable, callback, data);
583
584
0
  if (!G_IS_NETWORK_ADDRESS (connectable))
585
0
    {
586
0
      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
587
0
                               "Can't handle this kind of GSocketConnectable (%s)",
588
0
                               G_OBJECT_TYPE_NAME (connectable));
589
0
      g_object_unref (task);
590
0
      return;
591
0
    }
592
593
0
  address = G_NETWORK_ADDRESS (connectable);
594
595
0
  g_dbus_proxy_call (nm->priv->proxy,
596
0
                     "CanReach",
597
0
                     g_variant_new ("(su)",
598
0
                                    g_network_address_get_hostname (address),
599
0
                                    g_network_address_get_port (address)),
600
0
                     G_DBUS_CALL_FLAGS_NONE,
601
0
                     -1,
602
0
                     cancellable,
603
0
                     can_reach_done,
604
0
                     task);
605
0
}
606
607
static gboolean
608
g_network_monitor_portal_can_reach_finish (GNetworkMonitor  *monitor,
609
                                           GAsyncResult     *result,
610
                                           GError          **error)
611
0
{
612
0
  return g_task_propagate_boolean (G_TASK (result), error);
613
0
}
614
615
static void
616
g_network_monitor_portal_iface_init (GNetworkMonitorInterface *monitor_iface)
617
0
{
618
0
  monitor_iface->can_reach = g_network_monitor_portal_can_reach;
619
0
  monitor_iface->can_reach_async = g_network_monitor_portal_can_reach_async;
620
0
  monitor_iface->can_reach_finish = g_network_monitor_portal_can_reach_finish;
621
0
}
622
623
static void
624
g_network_monitor_portal_initable_iface_init (GInitableIface *iface)
625
0
{
626
0
  initable_parent_iface = g_type_interface_peek_parent (iface);
627
628
0
  iface->init = g_network_monitor_portal_initable_init;
629
0
}