Control: tags -1 + patch

Hi Richard,

On Thu, 20 Jun 2024 17:45:26 +0300,
Jeremy BĂ­cha wrote:
> The Debian GNOME maintainers would like to stop building libsoup2.4.
> Therefore, please stop using libsoup2.4.

Please find attached a patch which I tested extensively:

  * I tried numerous downloads and cancelled them.
  * I wiped ~/.gbonds several times and triggered full downloads.
  * I simulated errors from soup_session_send_finish and
    json_parser_load_from_stream by introducing intentional typo in
    TREASURY_HOST and TREASURY_URL, respectively.
  * Ran the program under gdb and valgrind with GBONDS_DEBUG_UPDATE
    and G_DEBUG set, which revealed a few issues (fixed).

The main problem is easily reproducible with the version in unstable:
just interrupt the download by clicking the "Cancel" button and retry
again.  The download hangs and there's a GLib critical message logged.
There are also a few problems which stem from improper use of the
GCancellable object, insufficient guards in free_download_data and a
few memory leaks as well.

One addition unrelated to the libsoup migration is making the text in
the GtkProgressBar visible.

I made it as a separate patch so that it would be easier for you to
review the changes but if you prefer, I can merge it into
use-treasury-api.patch.

Let me know if I missed something.

As I've put significant efforts in this package by porting it from
BonoboUI to GTK 3 I'd be sad to see it removed from the archive.
>From 6e1426461d899c4aa4ebef9b3721a6bd391eec97 Mon Sep 17 00:00:00 2001
From: Yavor Doganov <[email protected]>
Date: Tue, 14 Oct 2025 15:36:10 +0300
Subject: [PATCH] Port to libsoup3 (Closes: #1073944).

---
 debian/control                |   2 +-
 debian/patches/libsoup3.patch | 307 ++++++++++++++++++++++++++++++++++
 debian/patches/series         |   1 +
 3 files changed, 309 insertions(+), 1 deletion(-)
 create mode 100644 debian/patches/libsoup3.patch

diff --git a/debian/control b/debian/control
index 564c8ba..49e7c04 100644
--- a/debian/control
+++ b/debian/control
@@ -6,7 +6,7 @@ Build-Depends: debhelper-compat (= 13),
                intltool,
                libgtk-3-dev,
                libjson-glib-dev,
-               libsoup2.4-dev,
+               libsoup-3.0-dev,
                libtool,
                libxml2-dev,
 Standards-Version: 4.6.0
diff --git a/debian/patches/libsoup3.patch b/debian/patches/libsoup3.patch
new file mode 100644
index 0000000..8ee94c5
--- /dev/null
+++ b/debian/patches/libsoup3.patch
@@ -0,0 +1,307 @@
+Description: Port to libsoup3.
+Bug-Debian: https://bugs.debian.org/1073944
+Author: Yavor Doganov <[email protected]>
+Forwarded: no
+Last-Update: 2025-10-14
+---
+
+--- gbonds.orig/configure.in
++++ gbonds/configure.in
+@@ -31,13 +31,10 @@
+                
+ PKG_CHECK_MODULES(GBONDS, gtk+-3.0 >= $GTK_REQUIRED \
+ json-glib-1.0
+-libsoup-2.4 >= 2.56
++libsoup-3.0
+ libxml-2.0 >= $LIBXML_REQUIRED \
+ )
+ 
+-GBONDS_CFLAGS="$GBONDS_CFLAGS -DSOUP_VERSION_MIN_REQUIRED=SOUP_VERSION_2_56"
+-GBONDS_CFLAGS="$GBONDS_CFLAGS -DSOUP_VERSION_MAX_ALLOWED=SOUP_VERSION_2_56"
+-
+ AC_SUBST(GBONDS_CFLAGS)
+ AC_SUBST(GBONDS_LIBS)
+ 
+--- gbonds.orig/src/update.c
++++ gbonds/src/update.c
+@@ -97,14 +97,15 @@
+ 
+ static void add_start_page( GtkAssistant *wdruid,
+                           GdkPixbuf *logo );
+-static void add_downloading_page( GtkAssistant *wdruid, GdkPixbuf *logo );
++static void add_downloading_page( GtkAssistant *wdruid, GdkPixbuf *logo,
++                                DownloadCallbackData *data );
+ static void prepare_downloading_page( GtkAssistant *druid, GtkWidget *page,
+                                     gpointer data );
+ 
+ static void download_page ( DownloadCallbackData *data);
+-static void download_cb ( SoupSession *session, SoupMessage *msg,
++static void download_cb ( GObject *source, GAsyncResult *res,
+                         gpointer user_data);
+-static void start_download ();
++static void start_download ( DownloadCallbackData *data );
+ 
+ static void download_done( DownloadCallbackData *data );
+ static void no_download( DownloadCallbackData *data );
+@@ -130,6 +131,7 @@
+ void
+ gb_update_druid (void)
+ {
++      DownloadCallbackData *data;
+       GdkPixbuf *logo;
+       gchar     *file;
+ 
+@@ -145,14 +147,18 @@
+                                    DRUID_WIDTH, DRUID_HEIGHT);
+ 
+       add_start_page (GTK_ASSISTANT (update_druid), logo);
+-      add_downloading_page (GTK_ASSISTANT (update_druid), logo);
++      data = g_new0 (DownloadCallbackData, 1);
++      add_downloading_page (GTK_ASSISTANT (update_druid), logo, data);
+       add_finish_page (GTK_ASSISTANT (update_druid), logo);
+       g_object_unref (logo);
+ 
++      if (!update_cancel_flag)
++              update_cancel_flag = g_cancellable_new ();
++
+       g_signal_connect (update_druid, "cancel",
+-                        G_CALLBACK (cancel_cb), NULL);
++                        G_CALLBACK (cancel_cb), data);
+       g_signal_connect (update_druid, "close",
+-                        G_CALLBACK (destroy_cb), NULL);
++                        G_CALLBACK (destroy_cb), data);
+ 
+       gtk_widget_show_all (update_druid);
+ 
+@@ -298,7 +304,8 @@
+ /* PRIVATE.  Create and add "downloading" page to druid.                    */
+ /*--------------------------------------------------------------------------*/
+ static void
+-add_downloading_page( GtkAssistant *wdruid, GdkPixbuf *logo )
++add_downloading_page( GtkAssistant *wdruid, GdkPixbuf *logo,
++                    DownloadCallbackData *data )
+ {
+       GtkWidget *wvbox, *wvbox2, *wvbox3, *wvbox4;
+       GtkWidget *whbox2, *whbox3, *whbox4;
+@@ -334,6 +341,7 @@
+       gtk_box_pack_start( GTK_BOX(whbox3), page_label, FALSE, TRUE, 0 );
+ 
+       page_progress = gtk_progress_bar_new();
++      gtk_progress_bar_set_show_text( GTK_PROGRESS_BAR(page_progress), TRUE );
+       gtk_progress_bar_set_text( GTK_PROGRESS_BAR(page_progress), "" );
+       gtk_box_pack_start( GTK_BOX(wvbox3), page_progress, FALSE, TRUE, 0 );
+ 
+@@ -342,7 +350,7 @@
+       gtk_assistant_set_page_type (wdruid, wvbox,
+                                    GTK_ASSISTANT_PAGE_CUSTOM);
+       g_signal_connect_after( G_OBJECT(wdruid), "prepare",
+-                              G_CALLBACK(prepare_downloading_page), NULL );
++                              G_CALLBACK(prepare_downloading_page), data );
+ 
+       gb_debug (DEBUG_UPDATE, "END");
+ }
+@@ -367,11 +375,11 @@
+ 
+       custom_widget = gtk_button_new_with_mnemonic (_("_Cancel"));
+       g_signal_connect (custom_widget, "clicked",
+-                        G_CALLBACK (cancel_cb), NULL);
++                        G_CALLBACK (cancel_cb), data);
+       gtk_widget_show (custom_widget);
+       gtk_assistant_add_action_widget (druid, custom_widget);
+ 
+-      start_download ();
++      start_download (data);
+ 
+       gb_debug (DEBUG_UPDATE, "END");
+ }
+@@ -416,10 +424,9 @@
+ /* PRIVATE.  Begin the asynchronous process of downloading data.            */
+ /*--------------------------------------------------------------------------*/
+ static void
+-start_download ()
++start_download (DownloadCallbackData *data)
+ {
+       gchar                *status_string;
+-      DownloadCallbackData *data;
+ 
+       gb_debug (DEBUG_UPDATE, "START");
+ 
+@@ -428,17 +435,11 @@
+       gtk_label_set_text (GTK_LABEL (status_label), status_string);
+       g_free (status_string);
+ 
+-      if (!update_cancel_flag)
+-              update_cancel_flag = g_cancellable_new ();
+-
+       update_progress_bar (GTK_PROGRESS_BAR (page_progress), 0, 0);
+ 
+-      data = g_new0 ( DownloadCallbackData, 1 );
+-      data->session = g_object_new (
+-              SOUP_TYPE_SESSION,
+-              SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_CONTENT_DECODER,
+-              SOUP_SESSION_USER_AGENT, PACKAGE_STRING,
+-              SOUP_SESSION_ACCEPT_LANGUAGE_AUTO, TRUE,
++      data->session = soup_session_new_with_options (
++              "user-agent", PACKAGE_STRING,
++              "accept-language-auto", TRUE,
+               NULL);
+       data->year = GB_YEAR(download_date);
+       data->month = GB_MONTH(download_date);
+@@ -471,7 +472,14 @@
+       gb_debug (DEBUG_UPDATE, "URI: %s", uri);
+ 
+       msg = soup_message_new (SOUP_METHOD_GET, uri);
+-      soup_session_queue_message (data->session, msg, download_cb, data);
++      /* Keep the session object alive in case the async operation
++         gets cancelled and it is freed in destroy_cb before
++         download_cb completes.  */
++      g_object_ref (data->session);
++      soup_session_send_async (data->session, msg, G_PRIORITY_DEFAULT,
++                               update_cancel_flag, download_cb, data);
++      g_free (uri);
++      g_object_unref (msg);
+ 
+       gb_debug (DEBUG_UPDATE, "END");
+ }
+@@ -663,9 +671,11 @@
+ /* PRIVATE.  Process one page of data from the API.                         */
+ /*--------------------------------------------------------------------------*/
+ static void
+-download_cb (SoupSession *session, SoupMessage *msg, gpointer user_data)
++download_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+ {
+       DownloadCallbackData *data = (DownloadCallbackData *)user_data;
++      GError               *err = NULL;
++      GInputStream         *body;
+       gboolean             result;
+       JsonNode             *root;
+       JsonObject           *root_object;
+@@ -674,35 +684,44 @@
+ 
+       gb_debug (DEBUG_UPDATE, "START");
+ 
+-      if (g_cancellable_is_cancelled (update_cancel_flag)) {
+-              free_download_data (data);
++      body = soup_session_send_finish (SOUP_SESSION (source), res, &err);
++      /* Drop the extra ref added in download_page.  */
++      g_object_unref (SOUP_SESSION (source));
++
++      if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
++              g_error_free (err);
+               gb_debug (DEBUG_UPDATE, "END -- CANCEL");
+               return;
+       }
+ 
+-      if (msg->status_code == SOUP_STATUS_SSL_FAILED) {
+-              GTlsCertificateFlags flags;
+-
+-              if (soup_message_get_https_status (msg, NULL, &flags))
+-                      io_error ("%d %s (0x%x)\n", msg->status_code,
+-                              msg->reason_phrase, flags);
+-              else
++      if (err) {
++              if (err->domain == G_TLS_ERROR)
+                       io_error ("%d %s (no handshake status)\n",
+-                              msg->status_code, msg->reason_phrase);
+-              gb_debug (DEBUG_UPDATE, "END -- ERROR");
+-              return;
+-      } else if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) {
+-              io_error ("%d %s\n", msg->status_code, msg->reason_phrase);
+-              gb_debug (DEBUG_UPDATE, "END -- ERROR");
+-              return;
++                                err->code, err->message);
++              else {
++                      io_error ("%d %s\n", err->code, err->message);
++                      g_error_free (err);
++                      gb_debug (DEBUG_UPDATE, "END -- ERROR");
++                      return;
++              }
+       }
+ 
+       if (!data->parser)
+               data->parser = json_parser_new ();
+-      result = json_parser_load_from_data (
+-              data->parser, msg->response_body->data, -1, NULL);
++      result = json_parser_load_from_stream (
++              data->parser, body, update_cancel_flag, &err);
++      g_input_stream_close (body, NULL, NULL);
++      g_object_unref (body);
++
++      if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
++              g_error_free (err);
++              gb_debug (DEBUG_UPDATE, "END -- CANCEL");
++              return;
++      }
+       if (!result) {
+-              io_error (_("Failed to parse JSON response."));
++              io_error (_("Failed to parse JSON response: %s."),
++                        err->message);
++              g_error_free (err);
+               gb_debug (DEBUG_UPDATE, "END -- FAILED TO PARSE");
+               return;
+       }
+@@ -912,8 +931,6 @@
+                                           custom_widget);
+       gtk_assistant_next_page (GTK_ASSISTANT (update_druid));
+ 
+-      free_download_data (data);
+-
+       gb_debug (DEBUG_UPDATE, "END");
+ }
+ 
+@@ -958,22 +975,24 @@
+                                           custom_widget);
+       gtk_assistant_next_page (GTK_ASSISTANT (update_druid));
+ 
+-      free_download_data (data);
+-
+       gb_debug (DEBUG_UPDATE, "END");
+ }
+ 
+ static void free_download_data( DownloadCallbackData *data )
+ {
+-      g_object_unref (data->session);
+-      g_object_unref (data->parser);
++      if (data->session)
++              g_object_unref (data->session);
++      if (data->parser)
++              g_object_unref (data->parser);
+       if (data->file) {
+               fclose (data->file);
+               unlink (data->tmp_filename);
+               g_free (data->tmp_filename);
+               g_free (data->filename);
+       }
+-      g_free (data->dir);
++      if (data->dir)
++              g_free (data->dir);
++
+       g_free (data);
+ }
+ 
+@@ -1010,10 +1029,12 @@
+ {
+       gb_debug (DEBUG_UPDATE, "START");
+ 
+-      if (update_cancel_flag)
+-              g_cancellable_cancel (update_cancel_flag);
++      if (update_cancel_flag) {
++              g_cancellable_cancel (update_cancel_flag);
++              g_clear_object (&update_cancel_flag);
++      }
+ 
+-      gtk_widget_destroy (update_druid);
++      destroy_cb (druid, data);
+ 
+       gb_debug (DEBUG_UPDATE, "END");
+ }
+@@ -1028,6 +1049,9 @@
+ {
+       gb_debug (DEBUG_UPDATE, "START");
+ 
++      if (update_cancel_flag)
++              g_clear_object (&update_cancel_flag);
++      free_download_data (data);
+       gtk_widget_destroy (update_druid);
+ 
+       gb_debug (DEBUG_UPDATE, "END");
diff --git a/debian/patches/series b/debian/patches/series
index 60888c2..f5d394c 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -17,3 +17,4 @@ gsettings-port
 no-rarian-compat
 extern-gb_prefs
 use-treasury-api.patch
+libsoup3.patch
-- 
2.51.0

Reply via email to