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

Bug#1032903: marked as done (unblock: liferea/1.14.1-1)



Your message dated Mon, 13 Mar 2023 21:18:22 +0000
with message-id <E1pbpYk-00Br9b-69@respighi.debian.org>
and subject line unblock liferea
has caused the Debian Bug report #1032903,
regarding unblock: liferea/1.14.1-1
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
1032903: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1032903
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
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;
 }

--- End Message ---
--- Begin Message ---
Unblocked.

--- End Message ---

Reply to: