Package: release.debian.org Severity: normal User: release.debian....@packages.debian.org Usertags: unblock X-Debbugs-Cc: life...@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.wind...@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.wind...@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-de...@lists.sourceforge.net]) +AC_INIT([liferea],[1.14.1],[liferea-de...@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 <elb...@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 <associati...@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.wind...@gmx.de> + * Copyright (C) 2008-2023 Lars Windolf <lars.wind...@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.wind...@gmx.de> + * Copyright (C) 2005-2023 Lars Windolf <lars.wind...@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.wind...@gmx.de> + * Copyright (C) 2003-2023 Lars Windolf <lars.wind...@gmx.de> * Copyright (C) 2004-2006 Nathan J. Conrad <t98...@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.wind...@gmx.de> + * Copyright (C) 2003-2023 Lars Windolf <lars.wind...@gmx.de> * Copyright (C) 2004-2006 Nathan J. Conrad <t98...@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.wind...@gmx.de> + * Copyright (C) 2014-2023 Lars Windolf <lars.wind...@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 >/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.wind...@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><div xmlns=\"http://www.w3.org/1999/xhtml\"><p>D</p></div></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; }