[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1032903: unblock: liferea/1.14.1-1



Package: release.debian.org
Severity: normal
User: release.debian.org@packages.debian.org
Usertags: unblock
X-Debbugs-Cc: liferea@packages.debian.org
Control: affects -1 + src:liferea

Please unblock package liferea

[ Reason ]

A CVE was discovered in liferea and upstream quickly released a new
version including the fix. The new version also fixes a crash on
double free. Unfortunately it also included one more less important
improvement and an updated translation. Considering my options, I
decided it was best to upload the new version instead of only fixing
the CVE.

https://security-tracker.debian.org/tracker/CVE-2023-1350

[ Impact ]

The CVE is about a Remote Code Excecution of RSS feed information when
the user has opted-in to "Extract full content from HTML5 and Google
AMP". I believe that's pretty bad, but luckily it's not the default.

[ Tests ]

liferea doesn't have autopkgtests (yet), but I do activate the
upstream tests during build. Unfortunately, that currently fails
(because liferea isn't installed during build and if that's worked
around something fails due to being root; sorry, haven't fixed that
yet), but I ran the tests locally and then all regular tests pass. The
memtest fails in the same way as before. I also eat my own dogfood as
I'm a user of liferea and have the binaries installed since I built
them.

[ Risks ]

In the end, the changes are a bit more than trivial, but the delta in
this release is targetted to specific issues. I have a good relation
with upstream and he even supported me in the discussion with the
security team. Unfortunately, liferea isn't a leaf package as the
bfh-desktop (new in bookworm) and progress-linux-desktop (already in
bullseye) depend on it.

[ Checklist ]
  [x] all changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in testing

[ Other info ]

I recommend viewing the debdiff with the following filter to ignore
upstream workflow items, the translation update and additional test
cases added for the purpose of testing the fix:

filterdiff -x '*/.gitignore' -x '*/.github/workflows/cb.yml' -x '*/po/fr.po' -x '*/src/tests' liferea_1.14.1-1.debdiff

unblock liferea/1.14.1-1

Paul
diff -Nru liferea-1.14.0/ChangeLog liferea-1.14.1/ChangeLog
--- liferea-1.14.0/ChangeLog	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/ChangeLog	2023-03-12 21:00:51.000000000 +0100
@@ -1,3 +1,17 @@
+2023-03-12  Lars Windolf <lars.windolf@gmx.de>
+
+	Version 1.14.1
+
+	* Fixes CVE-2023-1350: RCE vulnerability on feed enrichment
+	  (patch by Alexander Erwin Ittner)
+
+	* Fixes #1200: Crash on double free
+	  (mozbugbox)
+
+	* Improve #1192 be reordering widget creation order
+	  (Lars Windolf)
+
+
 2023-01-10  Lars Windolf <lars.windolf@gmx.de>
 
 	Version 1.14.0
diff -Nru liferea-1.14.0/configure.ac liferea-1.14.1/configure.ac
--- liferea-1.14.0/configure.ac	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/configure.ac	2023-03-12 21:00:51.000000000 +0100
@@ -1,6 +1,6 @@
 dnl Process this file with autoconf to produce a configure script.
 
-AC_INIT([liferea],[1.14.0],[liferea-devel@lists.sourceforge.net])
+AC_INIT([liferea],[1.14.1],[liferea-devel@lists.sourceforge.net])
 AC_CANONICAL_HOST
 AC_CONFIG_SRCDIR([src/feedlist.c])
 
diff -Nru liferea-1.14.0/debian/changelog liferea-1.14.1/debian/changelog
--- liferea-1.14.0/debian/changelog	2023-01-15 21:14:44.000000000 +0100
+++ liferea-1.14.1/debian/changelog	2023-03-12 21:32:33.000000000 +0100
@@ -1,3 +1,12 @@
+liferea (1.14.1-1) unstable; urgency=medium
+
+  * New upstream version 1.14.1
+    Contains fix for CVE-2023-1350 which is a RCE when the option "Extract
+    full content from HTML5 and Google AMP" is enable on a feed (Closes:
+    #1032822)
+
+ -- Paul Gevers <elbrus@debian.org>  Sun, 12 Mar 2023 21:32:33 +0100
+
 liferea (1.14.0-1) unstable; urgency=medium
 
   * New upstream version 1.14.0
diff -Nru liferea-1.14.0/.github/workflows/cb.yml liferea-1.14.1/.github/workflows/cb.yml
--- liferea-1.14.0/.github/workflows/cb.yml	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/.github/workflows/cb.yml	2023-03-12 21:00:51.000000000 +0100
@@ -24,7 +24,7 @@
 
     - run: |
        sudo apt-get update -qq
-       sudo apt-get install -y -qq libxml2-dev libxslt1-dev libsqlite3-dev libwebkit2gtk-4.0-dev libjson-glib-dev libgirepository1.0-dev libpeas-dev gsettings-desktop-schemas-dev python3 libtool intltool valgrind libfribidi-dev gla11y
+       sudo apt-get install -y -qq libxml2-dev libxslt1-dev libsqlite3-dev libwebkit2gtk-4.0-dev libjson-glib-dev libgirepository1.0-dev libpeas-dev gsettings-desktop-schemas-dev python3 libtool intltool valgrind libfribidi-dev gla11y appstream-util desktop-file-utils
        mkdir inst
 
     - run: |
@@ -35,6 +35,8 @@
     - run: make && make install
     - run: sudo cp net.sf.liferea.gschema.xml /usr/share/glib-2.0/schemas
     - run: sudo /usr/bin/glib-compile-schemas /usr/share/glib-2.0/schemas/
-    - run: ls -l /usr/share/glib-2.0/schemas 
+    - run: ls -l /usr/share/glib-2.0/schemas
     - run: cd src/tests && make test
     - run: cd src/tests && ./memcheck.sh parse_xml parse_date
+    - run: desktop-file-validate net.sourceforge.liferea.desktop
+    - run: appstream-util validate net.sourceforge.liferea.appdata.xml
diff -Nru liferea-1.14.0/.gitignore liferea-1.14.1/.gitignore
--- liferea-1.14.0/.gitignore	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/.gitignore	2023-03-12 21:00:51.000000000 +0100
@@ -50,8 +50,9 @@
 src/Liferea-3.0.typelib
 src/tests/favicon
 src/tests/html_auto
-src/tests/parse_html
 src/tests/parse_date
+src/tests/parse_html
+src/tests/parse_rss
 src/tests/parse_xml
 src/tests/social
 xslt/*.xml
diff -Nru liferea-1.14.0/net.sourceforge.liferea.appdata.xml.in liferea-1.14.1/net.sourceforge.liferea.appdata.xml.in
--- liferea-1.14.0/net.sourceforge.liferea.appdata.xml.in	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/net.sourceforge.liferea.appdata.xml.in	2023-03-12 21:00:51.000000000 +0100
@@ -201,8 +201,6 @@
            Now Liferea will never allow the panes to be smaller than 5% in height or width
            regarding to there orientation. If a pane is smaller than 5% height/width it will be
            set to 30% width or 50% height on startup.
-
-           The intention here is that panes are never invisible after startup.
         </li>
         <li>
           Wait for network to be fully available before updating: sometimes when real internet
diff -Nru liferea-1.14.0/po/fr.po liferea-1.14.1/po/fr.po
--- liferea-1.14.0/po/fr.po	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/po/fr.po	2023-03-12 21:00:51.000000000 +0100
@@ -13,15 +13,15 @@
 "Project-Id-Version: Liferea 1.8\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2022-10-26 01:24+0200\n"
-"PO-Revision-Date: 2022-09-16 10:26+0200\n"
-"Last-Translator: Guillaume Bernard <associations@guillaume-bernard.fr>\n"
+"PO-Revision-Date: 2023-01-13 12:16+0100\n"
+"Last-Translator: Irénée Thirion <irenee.thirion@e.email>\n"
 "Language-Team: français <>\n"
 "Language: fr\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=(n > 1);\n"
-"X-Generator: Poedit 3.1.1\n"
+"X-Generator: Poedit 3.2.2\n"
 
 #: ../net.sourceforge.liferea.desktop.in.h:1 ../src/liferea_application.c:349
 #: ../glade/mainwindow.ui.h:1
@@ -439,18 +439,17 @@
 msgstr "La connexion a échoué !"
 
 #: ../src/fl_sources/google_source.c:404
-#, fuzzy
 msgid "Google Reader API"
-msgstr "Google Reader"
+msgstr "API Google Reader"
 
 #: ../src/fl_sources/google_source_feed.c:159
-#, fuzzy
 msgid "Could not parse JSON returned by Google Reader API!"
-msgstr "Impossible d’analyser le JSON envoyé par l’API Reedah !"
+msgstr ""
+"Impossible d’analyser le fichier JSON retourné par l’API Google Reader !"
 
 #: ../src/fl_sources/node_source.c:117
 msgid "Miniflux"
-msgstr ""
+msgstr "Miniflux"
 
 #: ../src/fl_sources/node_source.c:332
 msgid "No feed list source types found!"
@@ -717,30 +716,28 @@
 
 #. http 5xx server errors
 #: ../src/net.c:493
-#, fuzzy
 msgid "Internal Server Error"
-msgstr "Erreur du serveur"
+msgstr "Erreur interne du serveur"
 
 #: ../src/net.c:494
 msgid "Not Implemented"
-msgstr ""
+msgstr "Non implémenté"
 
 #: ../src/net.c:495
 msgid "Bad Gateway"
-msgstr ""
+msgstr "Mauvaise passerelle"
 
 #: ../src/net.c:496
-#, fuzzy
 msgid "Service Unavailable"
-msgstr "« %s » n’est pas disponible"
+msgstr "Service indisponible"
 
 #: ../src/net.c:497
 msgid "Gateway Timeout"
-msgstr ""
+msgstr "Délai d’attente de la passerelle écoulé"
 
 #: ../src/net.c:498
 msgid "HTTP Version Not Supported"
-msgstr ""
+msgstr "Version HTTP non prise en charge"
 
 #: ../src/net.c:503
 msgid "There was an internal error in the update process"
@@ -819,9 +816,8 @@
 msgstr "Le corps de l’élément"
 
 #: ../src/rule.c:277
-#, fuzzy
 msgid "Item author"
-msgstr "Le corps de l’élément"
+msgstr "L’auteur de l’élément"
 
 #: ../src/rule.c:278
 msgid "Read status"
@@ -1091,14 +1087,14 @@
 msgstr "Aucun élément n’a été sélectionné"
 
 #: ../src/ui/liferea_browser.c:482
-#, fuzzy
 msgid "Content download failed! Try disabling reader mode."
-msgstr "Impossible de télécharger le contenu."
+msgstr ""
+"Impossible de télécharger le contenu. Essayez de désactiver le mode lecture."
 
 #: ../src/ui/liferea_browser.c:495
-#, fuzzy
 msgid "Content extraction failed! Try disabling reader mode."
-msgstr "Impossible d’extraire le contenu."
+msgstr ""
+"Impossible d’extraire le contenu. Essayez de désactiver le mode lecture."
 
 #: ../src/ui/liferea_shell.c:409
 #, c-format
@@ -1325,9 +1321,8 @@
 msgstr "Programme"
 
 #: ../src/ui/rule_editor.c:257
-#, fuzzy
 msgid "Remove"
-msgstr "_Supprimer"
+msgstr "Supprimer"
 
 #: ../src/ui/search_dialog.c:106
 msgid "Saved Search"
@@ -1504,15 +1499,16 @@
 "de maintenant."
 
 #: ../glade/google_source.ui.h:1
-#, fuzzy
 msgid "Add Google Reader API Account"
-msgstr "Ajouter un compte Google Reader"
+msgstr "Ajouter un compte API Google Reader"
 
 #: ../glade/google_source.ui.h:2
 msgid ""
 "Please enter the details of the new Google Reader API compatible "
 "subscription."
 msgstr ""
+"Veuillez saisir les détails du nouvel abonnement compatible avec l’API "
+"Google Reader."
 
 #: ../glade/google_source.ui.h:3 ../glade/reedah_source.ui.h:3
 #: ../glade/theoldreader_source.ui.h:3 ../glade/ttrss_source.ui.h:4
@@ -1525,14 +1521,12 @@
 msgstr "Nom d’_utilisateur (e-mail)"
 
 #: ../glade/google_source.ui.h:5
-#, fuzzy
 msgid "_Server"
-msgstr "URL du _serveur"
+msgstr "_Serveur"
 
 #: ../glade/google_source.ui.h:6
-#, fuzzy
 msgid "_Name"
-msgstr "_Nom du flux"
+msgstr "_Nom"
 
 #: ../glade/liferea_menu.ui.h:1
 msgid "_Subscriptions"
@@ -1886,9 +1880,8 @@
 "recherche."
 
 #: ../glade/prefs.ui.h:22
-#, fuzzy
 msgid "Ask for confirmation when marking all items as read."
-msgstr "Demander confirmation pour marquer tous les éléments comme lus"
+msgstr "Demander confirmation pour marquer tous les éléments comme lus."
 
 #: ../glade/prefs.ui.h:23
 msgid "Web Integration"
diff -Nru liferea-1.14.0/src/common.c liferea-1.14.1/src/common.c
--- liferea-1.14.0/src/common.c	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/src/common.c	2023-03-12 21:00:51.000000000 +0100
@@ -138,7 +138,9 @@
 	g_assert (NULL != url);
 
 	/* xmlURIEscape returns NULL if spaces are in the URL,
-	   so we need to replace them first (see SF #2965158) */
+	   so we need to replace them first (see SF #2965158).
+	   TODO: perhaps replace xmlURIEscape with g_uri_escape_string ?
+	 */
 	tmp = (xmlChar *)common_strreplace (g_strdup ((gchar *)url), " ", "%20");
 	result = xmlURIEscape (tmp);
 	g_free (tmp);
diff -Nru liferea-1.14.0/src/feed.c liferea-1.14.1/src/feed.c
--- liferea-1.14.0/src/feed.c	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/src/feed.c	2023-03-12 21:00:51.000000000 +0100
@@ -460,7 +460,7 @@
 		NODE_CAPABILITY_EXPORT |
 		NODE_CAPABILITY_EXPORT_ITEMS,
 		"feed",		/* not used, feed format ids are used instead */
-		NULL,
+		ICON_DEFAULT,
 		feed_import,
 		feed_export,
 		feed_load,
@@ -472,7 +472,6 @@
 		feed_properties,
 		feed_free
 	};
-	nti.icon = icon_get (ICON_DEFAULT);
 
 	return &nti;
 }
diff -Nru liferea-1.14.0/src/feed_parser.h liferea-1.14.1/src/feed_parser.h
--- liferea-1.14.0/src/feed_parser.h	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/src/feed_parser.h	2023-03-12 21:00:51.000000000 +0100
@@ -1,7 +1,7 @@
 /**
  * @file feed_parser.h  parsing of different feed formats
  *
- * Copyright (C) 2008-2021 Lars Windolf <lars.windolf@gmx.de>
+ * Copyright (C) 2008-2023 Lars Windolf <lars.windolf@gmx.de>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -30,7 +30,7 @@
 	subscriptionPtr	subscription;		/**< the subscription the feed belongs to (optional) */
 	feedPtr		feed;			/**< the feed structure to fill */
 	GList		*items;			/**< the list of new items */
-	struct item	*item;			/**< the item currently parsed (or NULL) */
+	itemPtr		item;			/**< the item currently parsed (or NULL) */
 
 	GHashTable	*tmpdata;		/**< tmp data hash used during stateful parsing */
 
diff -Nru liferea-1.14.0/src/fl_sources/node_source.c liferea-1.14.1/src/fl_sources/node_source.c
--- liferea-1.14.0/src/fl_sources/node_source.c	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/src/fl_sources/node_source.c	2023-03-12 21:00:51.000000000 +0100
@@ -1,7 +1,7 @@
 /*
  * @file node_source.c  generic node source provider implementation
  *
- * Copyright (C) 2005-2022 Lars Windolf <lars.windolf@gmx.de>
+ * Copyright (C) 2005-2023 Lars Windolf <lars.windolf@gmx.de>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -622,7 +622,7 @@
 		/* derive the node source node type from the folder node type */
 		nodeType = (nodeTypePtr) g_new0 (struct nodeType, 1);
 		nodeType->id			= "source";
-		nodeType->icon			= icon_get (ICON_DEFAULT);
+		nodeType->icon			= ICON_DEFAULT;
 		nodeType->capabilities		= NODE_CAPABILITY_SHOW_UNREAD_COUNT |
 						  NODE_CAPABILITY_SHOW_ITEM_FAVICONS |
 						  NODE_CAPABILITY_UPDATE_CHILDS |
diff -Nru liferea-1.14.0/src/folder.c liferea-1.14.1/src/folder.c
--- liferea-1.14.0/src/folder.c	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/src/folder.c	2023-03-12 21:00:51.000000000 +0100
@@ -119,7 +119,7 @@
 		NODE_CAPABILITY_UPDATE_CHILDS |
 		NODE_CAPABILITY_EXPORT,
 		"folder",
-		NULL,
+		ICON_FOLDER,
 		folder_import,
 		folder_export,
 		folder_load,
@@ -131,7 +131,6 @@
 		feed_list_view_rename_node,
 		NULL
 	};
-	fnti.icon = icon_get (ICON_FOLDER);
 
 	return &fnti;
 }
@@ -150,7 +149,7 @@
 		NODE_CAPABILITY_UPDATE_CHILDS |
 		NODE_CAPABILITY_EXPORT,
 		"root",
-		NULL,		/* and no need for an icon */
+		0,		/* and no need for an icon */
 		folder_import,
 		folder_export,
 		folder_load,
diff -Nru liferea-1.14.0/src/html.c liferea-1.14.1/src/html.c
--- liferea-1.14.0/src/html.c	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/src/html.c	2023-03-12 21:00:51.000000000 +0100
@@ -221,7 +221,7 @@
 GSList *
 html_auto_discover_feed (const gchar* data, const gchar *defaultBaseUri)
 {
-	GSList		*iter, *links = NULL;
+	GSList		*iter, *links = NULL, *valid_links = NULL;
 	gchar		*baseUri = NULL;
 	xmlDocPtr	doc;
 	xmlNodePtr	node, root;
@@ -253,17 +253,25 @@
 	/* Turn relative URIs into absolute URIs */
 	iter = links;
 	while (iter) {
-		gchar *tmp = iter->data;
-		iter->data = common_build_url (tmp, baseUri);
-		g_free (tmp);
-		debug1 (DEBUG_UPDATE, "search result: %s", (gchar *)iter->data);
+		gchar *tmp = (gchar *)common_build_url (iter->data, baseUri);
+
+		/* We expect only relative URIs starting with '/' or absolute URIs starting with 'http://' or 'https://' */
+		if ('h' == tmp[0] || '/' == tmp[0]) {
+			debug1 (DEBUG_UPDATE, "search result: %s", (gchar *)iter->data);
+			valid_links = g_slist_append (valid_links, tmp);
+		} else {
+			debug1 (DEBUG_UPDATE, "html_auto_discover_feed: discarding invalid URL %s", tmp ? tmp : "NULL");
+			g_free (tmp);
+		}
+
 		iter = g_slist_next (iter);
 	}
+	g_slist_free_full (links, g_free);
 
 	g_free (baseUri);
 	xmlFreeDoc (doc);
 
-	return links;
+	return valid_links;
 }
 
 GSList *
diff -Nru liferea-1.14.0/src/item.c liferea-1.14.1/src/item.c
--- liferea-1.14.0/src/item.c	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/src/item.c	2023-03-12 21:00:51.000000000 +0100
@@ -1,7 +1,7 @@
 /**
  * @file item.c item handling
  *
- * Copyright (C) 2003-2021 Lars Windolf <lars.windolf@gmx.de>
+ * Copyright (C) 2003-2023 Lars Windolf <lars.windolf@gmx.de>
  * Copyright (C) 2004-2006 Nathan J. Conrad <t98502@users.sourceforge.net>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -34,27 +34,55 @@
 #include "render.h"
 #include "xml.h"
 
-itemPtr
-item_new (void)
+G_DEFINE_TYPE (LifereaItem, liferea_item, G_TYPE_OBJECT);
+
+static void
+liferea_item_finalize (GObject *object)
 {
-	itemPtr		item;
+	LifereaItem *item = LIFEREA_ITEM (object);
+
+	g_free (item->title);
+	g_free (item->source);
+	g_free (item->sourceId);
+	g_free (item->description);
+	g_free (item->commentFeedId);
+	g_free (item->nodeId);
+	g_free (item->parentNodeId);
 
-	item = g_new0 (struct item, 1);
+	g_assert (NULL == item->tmpdata);	/* should be free after rendering */
+	metadata_list_free (item->metadata);
+}
+
+static void
+liferea_item_class_init (LifereaItemClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->finalize = liferea_item_finalize;
+}
+
+static void
+liferea_item_init (LifereaItem *item)
+{
 	item->popupStatus = TRUE;
+}
 
-	return item;
+LifereaItem *
+item_new (void)
+{
+	return LIFEREA_ITEM (g_object_new (LIFEREA_ITEM_TYPE, NULL));
 }
 
-itemPtr
+LifereaItem *
 item_load (gulong id)
 {
 	return db_item_load (id);
 }
 
-itemPtr
-item_copy (itemPtr item)
+LifereaItem *
+item_copy (LifereaItem *item)
 {
-	itemPtr copy = item_new ();
+	LifereaItem *copy = item_new ();
 
 	item_set_title (copy, item->title);
 	item_set_source (copy, item->source);
@@ -84,7 +112,7 @@
 }
 
 void
-item_set_title (itemPtr item, const gchar * title)
+item_set_title (LifereaItem *item, const gchar * title)
 {
 	g_free (item->title);
 
@@ -95,7 +123,7 @@
 }
 
 void
-item_set_description (itemPtr item, const gchar *description)
+item_set_description (LifereaItem *item, const gchar *description)
 {
 	if (!description)
 		return;
@@ -109,39 +137,41 @@
 }
 
 void
-item_set_source (itemPtr item, const gchar * source)
+item_set_source (LifereaItem *item, const gchar * source)
 {
 	g_free (item->source);
-	if (source)
+
+	/* We expect only relative URIs starting with '/' or absolute URIs starting with 'http://' or 'https://' */
+	if (source && ('/' == source[0] || 'h' == source[0]))
 		item->source = g_strstrip (g_strdup (source));
 	else
 		item->source = NULL;
 }
 
 void
-item_set_id (itemPtr item, const gchar * id)
+item_set_id (LifereaItem *item, const gchar * id)
 {
 	g_free (item->sourceId);
 	item->sourceId = g_strdup (id);
 }
 
 void
-item_set_time (itemPtr item, gint64 time)
+item_set_time (LifereaItem *item, gint64 time)
 {
 	item->time = time;
 	if (item->time > 0)
 		item->validTime = TRUE;
 }
 
-const gchar *	item_get_id(itemPtr item) { return item->sourceId; }
-const gchar *	item_get_title(itemPtr item) {return item->title; }
-const gchar *	item_get_description(itemPtr item) { return item->description; }
-const gchar *	item_get_source(itemPtr item) { return item->source; }
+const gchar *	item_get_id(LifereaItem *item) { return item->sourceId; }
+const gchar *	item_get_title(LifereaItem *item) {return item->title; }
+const gchar *	item_get_description(LifereaItem *item) { return item->description; }
+const gchar *	item_get_source(LifereaItem *item) { return item->source; }
 
 static GRegex *whitespace_strip_re = NULL;
 
 gchar *
-item_get_teaser (itemPtr item)
+item_get_teaser (LifereaItem *item)
 {
 	gchar		*input, *tmpDesc;
 	gchar		*teaser = NULL;
@@ -176,7 +206,7 @@
 }
 
 gchar *
-item_make_link (itemPtr item)
+item_make_link (LifereaItem *item)
 {
 	const gchar	*src;
 	gchar		*link;
@@ -202,7 +232,7 @@
 }
 
 const gchar *
-item_get_author(itemPtr item)
+item_get_author(LifereaItem *item)
 {
 	gchar *author;
 
@@ -210,25 +240,8 @@
 	return author;
 }
 
-void
-item_unload (itemPtr item)
-{
-	g_free (item->title);
-	g_free (item->source);
-	g_free (item->sourceId);
-	g_free (item->description);
-	g_free (item->commentFeedId);
-	g_free (item->nodeId);
-	g_free (item->parentNodeId);
-
-	g_assert (NULL == item->tmpdata);	/* should be free after rendering */
-	metadata_list_free (item->metadata);
-
-	g_free (item);
-}
-
 const gchar *
-item_get_base_url (itemPtr item)
+item_get_base_url (LifereaItem *item)
 {
 	/* item->node is always the source node for the item
 	   never a search folder or folder */
@@ -236,7 +249,7 @@
 }
 
 void
-item_to_xml (itemPtr item, gpointer xmlNode)
+item_to_xml (LifereaItem *item, gpointer xmlNode)
 {
 	xmlNodePtr	parentNode = (xmlNodePtr)xmlNode;
 	xmlNodePtr	duplicatesNode;
@@ -293,7 +306,7 @@
 		duplicates = iter = db_item_get_duplicates(item->sourceId);
 		while (iter) {
 			gulong id = GPOINTER_TO_UINT (iter->data);
-			itemPtr duplicate = item_load (id);
+			LifereaItem * duplicate = item_load (id);
 			if (duplicate) {
 				nodePtr duplicateNode = node_from_id (duplicate->nodeId);
 				if (duplicateNode && (item->id != duplicate->id))
@@ -328,7 +341,7 @@
 }
 
 static const gchar *
-item_get_text_direction (itemPtr item)
+item_get_text_direction (LifereaItem *item)
 {
 	if (item_get_title (item))
 		return (common_get_text_direction (item_get_title (item)));
@@ -340,7 +353,7 @@
 }
 
 gchar *
-item_render (itemPtr item, guint viewMode)
+item_render (LifereaItem *item, guint viewMode)
 {
 	renderParamPtr	params;
 	gchar		*output = NULL, *baseUrl = NULL;
diff -Nru liferea-1.14.0/src/item.h liferea-1.14.1/src/item.h
--- liferea-1.14.0/src/item.h	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/src/item.h	2023-03-12 21:00:51.000000000 +0100
@@ -1,7 +1,7 @@
 /*
  * @file item.h item handling
  *
- * Copyright (C) 2003-2022 Lars Windolf <lars.windolf@gmx.de>
+ * Copyright (C) 2003-2023 Lars Windolf <lars.windolf@gmx.de>
  * Copyright (C) 2004-2006 Nathan J. Conrad <t98502@users.sourceforge.net>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -23,24 +23,27 @@
 #define _ITEM_H
 
 #include <glib.h>
+#include <glib-object.h>
 
-/* Currently Liferea knows only a single type of items used
-   for the itemset types feed, folder and search folder. So each
-   feed list type provider must provide it's data using the
-   item interface. */
-
-/* ------------------------------------------------------------ */
-/* item interface						*/
-/* ------------------------------------------------------------ */
+/* Each feed/subscription type provider must provide it's data using `Item` */
+
+G_BEGIN_DECLS
+
+#define LIFEREA_ITEM_TYPE	(liferea_item_get_type ())
+G_DECLARE_FINAL_TYPE (LifereaItem, liferea_item, LIFEREA, ITEM, GObject)
 
 /*
  * An item stores a particular entry in a feed or a search.
+ *
  *  Each item belongs to an item set. An itemset is a collection
  *  of items. There are different item set types (e.g. feed,
- *  folder,vfolder or plugin). Each item has a source node.
+ *  folder, search folder or plugin). Each item has a source node.
  *  The item set node and the item source node is different
- *  for folders and vfolders. */
-typedef struct item {
+ *  for folders and search folders.
+ */
+struct _LifereaItem {
+	GObject	parent_instance;
+
 	gulong		id;			/*<< internally unique item id */
 
 	/* those fields should not be accessed directly. Accessors are provided. */
@@ -75,7 +78,9 @@
 	/* remote states used during sync of remote accounts */
 	gboolean	remoteReadStatus;	/*<< TRUE if the remote copy of the item has been read */
 	gboolean	remoteFlagStatus;	/*<< TRUE if the remote copy of the item has been flagged */
-} *itemPtr;
+};
+
+typedef struct _LifereaItem *itemPtr;
 
 /**
  * item_new: (skip)
@@ -83,7 +88,7 @@
  *
  * Returns: (transfer full): the new structure
  */
-itemPtr 	item_new(void);
+LifereaItem *	item_new(void);
 
 /**
  * item_load: (skip)
@@ -95,7 +100,10 @@
  *
  * Returns: (transfer full) (nullable): item structure
  */
-itemPtr		item_load(gulong id);
+LifereaItem *	item_load(gulong id);
+
+// For legacy code let's keep item_unload()
+#define item_unload(a) g_object_unref(a)
 
 /**
  * item_copy: (skip)
@@ -107,7 +115,7 @@
  *
  * Returns: (transfer full): copy of the item.
  */
-itemPtr		item_copy(itemPtr item);
+LifereaItem *	item_copy(LifereaItem * item);
 
 /**
  * item_get_base_url: (skip)
@@ -117,27 +125,17 @@
  *
  * Returns: base URL
  */
-const gchar * item_get_base_url(itemPtr item);
-
-/**
- * item_unload: (skip)
- * @item:	the item to unload
- *
- * Free the memory used by an itempointer. The item needs to be
- * removed from the itemlist before calling this function.
- *
- */
-void	item_unload(itemPtr item);
+const gchar * item_get_base_url(LifereaItem *item);
 
 /* methods to access properties */
 /* Returns the id of item. */
-const gchar *	item_get_id(itemPtr item);
+const gchar *	item_get_id(LifereaItem *item);
 /* Returns the title of item. */
-const gchar *	item_get_title(itemPtr item);
+const gchar *	item_get_title(LifereaItem *item);
 /* Returns the description of item. */
-const gchar *	item_get_description(itemPtr item);
+const gchar *	item_get_description(LifereaItem *item);
 /* Returns the source of item. */
-const gchar *	item_get_source(itemPtr item);
+const gchar *	item_get_source(LifereaItem *item);
 
 /**
  * item_get_teaser: (skip)
@@ -147,7 +145,7 @@
  *
  * Returns: (transfer full): newly allocated string to be free'd using g_free() (or NULL)
  */
-gchar * item_get_teaser(itemPtr item);
+gchar * item_get_teaser(LifereaItem *item);
 
 /**
  * item_make_link: (skip)
@@ -157,7 +155,7 @@
  *
  * Returns: (transfer full): newly allocated URI to be free'd using g_free()
  */
-gchar *	item_make_link(itemPtr item);
+gchar *	item_make_link(LifereaItem *item);
 
 /**
  * item_get_author: (skip)
@@ -167,7 +165,7 @@
  *
  * Returns: pointer to string in GSList meta data
  */
-const gchar * item_get_author	(itemPtr item);
+const gchar * item_get_author(LifereaItem *item);
 
 /**
  * item_set_title: (skip)
@@ -176,7 +174,7 @@
  *
  * Sets the item title
  */
-void item_set_title(itemPtr item, const gchar * title);
+void item_set_title(LifereaItem *item, const gchar * title);
 
 /**
  * item_set_description: (skip)
@@ -187,7 +185,7 @@
  * will merge the new description against the old one deciding
  * on the best to keep.
  */
-void item_set_description (itemPtr item, const gchar *description);
+void item_set_description (LifereaItem *item, const gchar *description);
 
 /**
  * item_set_source: (skip)
@@ -196,7 +194,7 @@
  *
  * Sets the item source 
  */
-void item_set_source(itemPtr item, const gchar * source);
+void item_set_source(LifereaItem *item, const gchar * source);
 
 /**
  * item_set_id: (skip)
@@ -205,7 +203,7 @@
  *
  * Sets the item id 
  */
-void item_set_id (itemPtr item, const gchar * id);
+void item_set_id (LifereaItem *item, const gchar * id);
 
 /**
  * item_set_time: (skip)
@@ -215,7 +213,7 @@
  * Sets the item time. Always use this when a valid date was 
  * supplied for the item!
  */
-void item_set_time (itemPtr item, gint64 time);
+void item_set_time (LifereaItem *item, gint64 time);
 
 /**
  * item_to_xml: (skip)
@@ -225,7 +223,7 @@
  * Adds an XML node to the given item.
  *
  */
-void item_to_xml (itemPtr item, gpointer parentNode);
+void item_to_xml (LifereaItem *item, gpointer parentNode);
 
 /**
  * item_render: (skip)
@@ -236,6 +234,8 @@
  *
  * Returns XML string (to be free'd using g_free())
  */
-gchar * item_render (itemPtr item, guint viewMode);
+gchar * item_render (LifereaItem *item, guint viewMode);
+
+G_END_DECLS
 
-#endif
+#endif
\ No newline at end of file
diff -Nru liferea-1.14.0/src/itemlist.c liferea-1.14.1/src/itemlist.c
--- liferea-1.14.0/src/itemlist.c	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/src/itemlist.c	2023-03-12 21:00:51.000000000 +0100
@@ -237,9 +237,9 @@
 	if (itemlist->priv->deferredRemove) {
 		itemlist->priv->deferredRemove = FALSE;
 		itemlist_remove_item (item);
+	} else {
+		item_unload (item);
 	}
-
-	item_unload (item);
 }
 
 static void
@@ -499,16 +499,8 @@
 
 	while (iter) {
 		itemPtr item = (itemPtr) iter->data;
-
-		if (itemlist->priv->selectedId != item->id) {
-			/* don't call itemlist_remove_item() here, because it's to slow */
-			itemview_remove_item (item);
-			db_item_remove (item->id);
-		} else {
-			/* go the normal and selection-safe way to avoid disturbing the user */
-			itemlist_request_remove_item (item);
-		}
-		item_unload (item);
+		itemlist_request_remove_item (item);
+		db_item_remove (item->id);
 		iter = g_list_next (iter);
 	}
 
@@ -590,7 +582,7 @@
 	}
 
 	if (item)
-		item_unload (item);
+		g_object_unref (item);
 
 	debug_end_measurement (DEBUG_GUI, "itemlist selection");
 	debug_exit ("itemlist_selection_changed");
diff -Nru liferea-1.14.0/src/itemset.c liferea-1.14.1/src/itemset.c
--- liferea-1.14.0/src/itemset.c	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/src/itemset.c	2023-03-12 21:00:51.000000000 +0100
@@ -44,7 +44,7 @@
 		itemPtr item = item_load (GPOINTER_TO_UINT (iter->data));
 		if (item) {
 			(*callback) (item, userdata);
-			item_unload (item);
+			g_object_unref (item);
 		}
 		iter = g_list_next (iter);
 	}
diff -Nru liferea-1.14.0/src/newsbin.c liferea-1.14.1/src/newsbin.c
--- liferea-1.14.0/src/newsbin.c	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/src/newsbin.c	2023-03-12 21:00:51.000000000 +0100
@@ -217,7 +217,7 @@
 		                                  NODE_CAPABILITY_SHOW_ITEM_COUNT |
 		                                  NODE_CAPABILITY_EXPORT_ITEMS;
 		nodeType->id			= "newsbin";
-		nodeType->icon			= icon_get (ICON_NEWSBIN);
+		nodeType->icon			= ICON_NEWSBIN;
 		nodeType->load			= feed_get_node_type()->load;
 		nodeType->import		= newsbin_import;
 		nodeType->export		= newsbin_export;
diff -Nru liferea-1.14.0/src/node.c liferea-1.14.1/src/node.c
--- liferea-1.14.0/src/node.c	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/src/node.c	2023-03-12 21:00:51.000000000 +0100
@@ -43,6 +43,7 @@
 #include "date.h"
 #include "fl_sources/node_source.h"
 #include "ui/feed_list_view.h"
+#include "ui/icons.h"
 #include "ui/liferea_shell.h"
 
 static GHashTable *nodes = NULL;	/*<< node id -> node lookup table */
@@ -431,7 +432,7 @@
 node_get_icon (nodePtr node)
 {
 	if (!node->icon)
-		return (gpointer) NODE_TYPE(node)->icon;
+		return (gpointer) icon_get (NODE_TYPE(node)->icon);
 
 	return node->icon;
 }
diff -Nru liferea-1.14.0/src/node_type.h liferea-1.14.1/src/node_type.h
--- liferea-1.14.0/src/node_type.h	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/src/node_type.h	2023-03-12 21:00:51.000000000 +0100
@@ -54,7 +54,7 @@
 typedef struct nodeType {
 	gulong		capabilities;	/**< bitmask of node type capabilities */
 	const gchar	*id;		/**< type id (used for type attribute in OPML export) */
-	const GIcon	*icon;		/**< default icon for nodes of this type (if no favicon available) */
+	guint		icon;		/**< default icon for nodes of this type (if no favicon available) */
 	
 	/* For method documentation see the wrappers defined below! 
 	   All methods are mandatory for each node type. */
diff -Nru liferea-1.14.0/src/subscription.c liferea-1.14.1/src/subscription.c
--- liferea-1.14.0/src/subscription.c	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/src/subscription.c	2023-03-12 21:00:51.000000000 +0100
@@ -282,6 +282,7 @@
 			subscription->updateState,
 			subscription->updateOptions
 		);
+		update_request_allow_commands (request, TRUE);
 
 		if (subscription_get_filter (subscription))
 			request->filtercmd = g_strdup (subscription_get_filter (subscription));
diff -Nru liferea-1.14.0/src/tests/Makefile.am liferea-1.14.1/src/tests/Makefile.am
--- liferea-1.14.0/src/tests/Makefile.am	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/src/tests/Makefile.am	2023-03-12 21:00:51.000000000 +0100
@@ -2,7 +2,7 @@
 
 noinst_PROGRAMS = $(TEST_PROGS)
 
-TEST_PROGS = parse_html favicon parse_date parse_xml social
+TEST_PROGS = parse_html favicon parse_date parse_rss parse_xml social
 
 test: $(TEST_PROGS)
 	echo $(TEST_PROGS) |\
@@ -93,6 +93,9 @@
 parse_date_CFLAGS = $(AM_CPPFLAGS)
 parse_date_LDADD = $(favicon_LDADD)
 
+parse_rss_CFLAGS = $(AM_CPPFLAGS)
+parse_rss_LDADD = $(favicon_LDADD)
+
 parse_xml_CFLAGS = $(AM_CPPFLAGS)
 parse_xml_LDADD = $(favicon_LDADD)
 
diff -Nru liferea-1.14.0/src/tests/parse_html.c liferea-1.14.1/src/tests/parse_html.c
--- liferea-1.14.0/src/tests/parse_html.c	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/src/tests/parse_html.c	2023-03-12 21:00:51.000000000 +0100
@@ -1,7 +1,7 @@
 /**
- * @file html.c  Test cases for feed link auto discovery
+ * @file parse_html.c  Test cases for feed link auto discovery
  *
- * Copyright (C) 2014-2019 Lars Windolf <lars.windolf@gmx.de>
+ * Copyright (C) 2014-2023 Lars Windolf <lars.windolf@gmx.de>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -20,6 +20,7 @@
 
 #include <glib.h>
 
+#include "debug.h"
 #include "html.h"
 
 /* We need two groups of autodiscovery test cases, one for the tag soup fuzzy
@@ -115,6 +116,13 @@
 	NULL
 };
 
+// Injection via "|"" command must not result in command subscription
+gchar *tc_xml_rce[] = {
+	"<html><head><link rel=\"alternate\" type=\"application/rss+xml\" href=\"|date &gt;/tmp/bad-feed-discovery.txt\"></html>",
+	NULL,
+	NULL
+};
+
 /* HTML5 extraction test cases */
 
 gchar *tc_article[] = {
@@ -214,6 +222,9 @@
 {
 	g_test_init (&argc, &argv, NULL);
 
+	if (argv[1] && g_str_equal (argv[1], "--debug"))
+		set_debug_level (DEBUG_UPDATE | DEBUG_HTML | DEBUG_PARSING);
+
 	g_test_add_data_func ("/html/auto_discover_link_xml", &tc_xml, &tc_auto_discover_link);
 	g_test_add_data_func ("/html/auto_discover_link_xml_base_url", &tc_xml_base_url, &tc_auto_discover_link);
 	g_test_add_data_func ("/html/auto_discover_link_rss", &tc_rss, &tc_auto_discover_link);
@@ -225,6 +236,7 @@
 	g_test_add_data_func ("/html/auto_discover_link_xml_atom", &tc_xml_atom, &tc_auto_discover_link);
 	g_test_add_data_func ("/html/auto_discover_link_xml_atom2", &tc_xml_atom2, &tc_auto_discover_link);
 	g_test_add_data_func ("/html/auto_discover_link_xml_atom3", &tc_xml_atom3, &tc_auto_discover_link);
+	g_test_add_data_func ("/html/auto_discover_link_xml_rce", &tc_xml_rce, &tc_auto_discover_link);
 
 	g_test_add_data_func ("/html/html5_extract_article", &tc_article, &tc_get_article);
 	g_test_add_data_func ("/html/html5_extract_article_main", &tc_article_main, &tc_get_article);
diff -Nru liferea-1.14.0/src/tests/parse_rss.c liferea-1.14.1/src/tests/parse_rss.c
--- liferea-1.14.0/src/tests/parse_rss.c	1970-01-01 01:00:00.000000000 +0100
+++ liferea-1.14.1/src/tests/parse_rss.c	2023-03-12 21:00:51.000000000 +0100
@@ -0,0 +1,128 @@
+/**
+ * @file parse_rss.c  Test cases for RSS parsing
+ *
+ * Copyright (C) 2023 Lars Windolf <lars.windolf@gmx.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <glib.h>
+#include <string.h>
+
+#include "debug.h"
+#include "feed.h"
+#include "feed_parser.h"
+#include "item.h"
+#include "subscription.h"
+#include "xml.h"
+
+/* Format of test cases:
+
+   1.     feed XML string
+   2.     "true" for successfully parsed feed, "false" for unparseable
+   3.     number of items
+   4..n   string of XML serialized items
+ */
+
+gchar *tc_rss_feed1[] = {
+	"<rss version=\"2.0\"><channel><title>T</title><link>http://localhost</link><item><title>i1</title><link>http://localhost/item1.html</link><description>D</description></item><item><title>i2</title><link>https://localhost/item2.html</link></item></channel></rss>",
+	"true",
+	"2",
+	"<item><title>i1</title><description>&lt;div xmlns=\"http://www.w3.org/1999/xhtml\"&gt;&lt;p&gt;D&lt;/p&gt;&lt;/div&gt;</description><source>http://localhost/item1.html</source><nr>0</nr><readStatus>0</readStatus><updateStatus>0</updateStatus><mark>0</mark><time>1678397817</time><sourceId/><sourceNr>0</sourceNr><attributes/></item>",
+	"<item><title>i2</title><source>https://localhost/item2.html</source><nr>0</nr><readStatus>0</readStatus><updateStatus>0</updateStatus><mark>0</mark><time>1678397817</time><sourceId/><sourceNr>0</sourceNr><attributes/></item>",
+	NULL
+};
+
+/* Test case to prevent | command injection in item link which could trigger
+   a HTML5 extraction */
+gchar *tc_rss_feed2_rce[] = {
+	"<rss version=\"2.0\"><channel><title>T</title><item><title>i1</title><link>|date >/tmp/bad-item-link.txt</link></item></channel></rss>",
+	"true",
+	"1",
+	"<item><title>i1</title><nr>0</nr><readStatus>0</readStatus><updateStatus>0</updateStatus><mark>0</mark><time>1678397817</time><sourceId/><sourceNr>0</sourceNr><attributes/></item>",
+	NULL
+};
+
+static void
+tc_parse_feed (gconstpointer user_data)
+{
+	gchar			**tc = (gchar **)user_data;
+	nodePtr			node;
+	feedParserCtxtPtr 	ctxt;
+	int			i;
+	GList			*iter;
+
+	node = node_new (feed_get_node_type ());
+	node_set_data (node, feed_new ());
+ 	node_set_subscription (node, subscription_new (NULL, NULL, NULL));
+	ctxt = feed_parser_ctxt_new (node->subscription, tc[0], strlen(tc[0]));
+
+	g_assert_cmpstr (feed_parse (ctxt)?"true":"false", ==, tc[1]);
+	g_assert (g_list_length (ctxt->items) == atoi(tc[2]));
+
+	i = 2;
+	iter = ctxt->items;
+	while (tc[++i]) {
+		gchar		*buffer, *tmp, *tmp2;
+		gint		buffersize;
+		xmlDocPtr	doc = xmlNewDoc (BAD_CAST"1.0");
+		xmlNodePtr	rootNode = xmlNewDocNode (doc, NULL, BAD_CAST"result", NULL);
+
+		xmlDocSetRootElement (doc, rootNode);
+
+		// Force time and delete <timestr> to make result compareable
+		itemPtr item = (itemPtr)iter->data;
+		item->time = 1678397817;
+		item_to_xml (item, rootNode);
+
+		xmlNode *timestr = xpath_find (rootNode, "//timestr");
+		if (timestr) {
+			xmlUnlinkNode (timestr);
+			xmlFreeNode (timestr);
+		}
+		xmlDocDumpMemory(doc, (xmlChar **)&buffer, &buffersize);
+
+		/* strip boilerplate */
+		tmp = buffer;
+		if ((tmp = strstr (tmp, "<result>")))
+			tmp += 8;		
+		if ((tmp2 = strstr (tmp, "</result>")))
+			*tmp2 = 0;
+
+		g_assert_cmpstr (tc[i], ==, tmp);
+
+		xmlFreeDoc (doc);
+		xmlFree (buffer);
+
+		iter = g_list_next (iter);
+	}	
+
+	feed_parser_ctxt_free (ctxt);
+	node_free (node);
+}
+
+int
+main (int argc, char *argv[])
+{
+	g_test_init (&argc, &argv, NULL);
+
+	if (argv[1] && g_str_equal (argv[1], "--debug"))
+		set_debug_level (DEBUG_UPDATE | DEBUG_HTML | DEBUG_PARSING);
+
+	g_test_add_data_func ("/rss/feed1",	&tc_rss_feed1,		&tc_parse_feed);
+	g_test_add_data_func ("/rss/feed2_rce",	&tc_rss_feed2_rce,	&tc_parse_feed);
+
+	return g_test_run();
+}
diff -Nru liferea-1.14.0/src/ui/liferea_shell.c liferea-1.14.1/src/ui/liferea_shell.c
--- liferea-1.14.0/src/ui/liferea_shell.c	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/src/ui/liferea_shell.c	2023-03-12 21:00:51.000000000 +0100
@@ -1387,7 +1387,6 @@
 	liferea_shell_update_toolbar ();
 	liferea_shell_update_history_actions ();
 	liferea_shell_setup_URL_receiver ();
-	liferea_shell_restore_state (overrideWindowState);
 
 	gtk_widget_set_sensitive (GTK_WIDGET (shell->feedlistViewWidget), TRUE);
 
@@ -1407,6 +1406,7 @@
 	                  G_CALLBACK (liferea_shell_update_node_actions), NULL);
 
 	/* 11.) Restore latest layout and selection */
+	liferea_shell_restore_state (overrideWindowState);
 	conf_get_int_value (DEFAULT_VIEW_MODE, &mode);
 	itemview_set_layout (mode);
 
diff -Nru liferea-1.14.0/src/update.c liferea-1.14.1/src/update.c
--- liferea-1.14.0/src/update.c	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/src/update.c	2023-03-12 21:00:51.000000000 +0100
@@ -234,6 +234,13 @@
 	request->authValue = g_strdup (authValue);
 }
 
+void
+update_request_allow_commands (UpdateRequest *request, gboolean allowCommands)
+{
+	request->allowCommands = allowCommands;
+}
+
+
 /* update result object */
 
 updateResultPtr
@@ -672,8 +679,14 @@
 
 	/* everything starting with '|' is a local command */
 	if (*(job->request->source) == '|') {
-		debug1 (DEBUG_UPDATE, "Recognized local command: %s", job->request->source);
-		update_exec_cmd (job);
+		if (job->request->allowCommands) {
+			debug1 (DEBUG_UPDATE, "Recognized local command: %s", job->request->source);
+			update_exec_cmd (job);
+		} else {
+			debug1 (DEBUG_UPDATE, "Refusing to run local command from unexpected source: %s", job->request->source);
+			job->result->httpstatus = 403;  /* Forbidden. */
+			update_process_finished_job (job);
+		}
 		return;
 	}
 
diff -Nru liferea-1.14.0/src/update.h liferea-1.14.1/src/update.h
--- liferea-1.14.0/src/update.h	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/src/update.h	2023-03-12 21:00:51.000000000 +0100
@@ -103,6 +103,7 @@
 	updateOptionsPtr options;	/**< Update options for the request */
 	gchar		*filtercmd;	/**< Command will filter output of URL */
 	updateStatePtr	updateState;	/**< Update state of the requested object (etags, last modified...) */
+	gboolean	allowCommands;	/**< Allow this requests to run commands */
 };
 
 /** structure to store results of the processing of an update request */
@@ -229,6 +230,21 @@
 void update_request_set_auth_value (UpdateRequest *request, const gchar* authValue);
 
 /**
+ * Allows *this* request to run local commands.
+ *
+ * At first it may look this flag should be in updateOptions, but we can
+ * take a safer path: feed commands are restricted to a few use cases while
+ * options are propagated to downstream requests (feed enrichment, comments,
+ * etc.), so it is a good idea to prevent these from running commands in the
+ * local system via tricky URLs without needing to validate these options
+ * everywhere (which is error-prone).
+ *
+ * @param request      the update request
+ * @param can_run      TRUE if the request can run commands, FALSE otherwise.
+ */
+void update_request_allow_commands (UpdateRequest *request, gboolean allowCommands);
+
+/**
  * Creates a new update result for the given update request.
  *
  * @returns update result (to be free'd using update_result_free())
diff -Nru liferea-1.14.0/src/vfolder.c liferea-1.14.1/src/vfolder.c
--- liferea-1.14.0/src/vfolder.c	2023-01-10 21:12:42.000000000 +0100
+++ liferea-1.14.1/src/vfolder.c	2023-03-12 21:00:51.000000000 +0100
@@ -308,7 +308,7 @@
 		NODE_CAPABILITY_SHOW_UNREAD_COUNT |
 		NODE_CAPABILITY_EXPORT_ITEMS,
 		"vfolder",
-		NULL,
+		ICON_VFOLDER,
 		vfolder_import,
 		vfolder_export,
 		vfolder_load,
@@ -320,7 +320,6 @@
 		vfolder_properties,
 		vfolder_free
 	};
-	nti.icon = icon_get (ICON_VFOLDER);
 
 	return &nti;
 }

Reply to: