Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package newsboat for openSUSE:Factory checked in at 2026-11-09 16:07:43 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/newsboat (Old) and /work/SRC/openSUSE:Factory/.newsboat.new.3759290 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "newsboat" Mon Nov 9 16:07:43 2026 rev:5 rq:1396290 version:2.45.1 Changes: -------- --- /work/SRC/openSUSE:Factory/newsboat/newsboat.changes 2026-10-09 21:43:11.948812352 +0200 +++ /work/SRC/openSUSE:Factory/.newsboat.new.3759290/newsboat.changes 2026-11-09 16:08:47.336233999 +0100 @@ -1,0 +2,6 @@ +Mon Nov 9 09:41:09 UTC 2026 - Michael Vetter <[email protected]> + +- Update to 2.45.1: + * GHSA-78q5-mpjq-5q34: Remote code execution via manual OPML import (`--import-from-opml`) + +------------------------------------------------------------------- Old: ---- newsboat-2.45.tar.xz newsboat-2.45.tar.xz.asc New: ---- newsboat-2.45.1.tar.xz newsboat-2.45.1.tar.xz.asc ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ newsboat.spec ++++++ --- /var/tmp/diff_new_pack.DPR1Hm/_old 2026-11-09 16:08:50.202354057 +0100 +++ /var/tmp/diff_new_pack.DPR1Hm/_new 2026-11-09 16:08:50.203354099 +0100 @@ -17,7 +17,7 @@ Name: newsboat -Version: 2.45 +Version: 2.45.1 Release: 0 Summary: RSS/Atom Feed Reader for Text Terminals License: MIT ++++++ newsboat-2.45.tar.xz -> newsboat-2.45.1.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/newsboat-2.45/CHANGELOG.md new/newsboat-2.45.1/CHANGELOG.md --- old/newsboat-2.45/CHANGELOG.md 2026-10-04 12:16:42.000000000 +0200 +++ new/newsboat-2.45.1/CHANGELOG.md 2026-11-05 16:46:02.000000000 +0100 @@ -1,5 +1,33 @@ # Changes for Newsboat +## 2.45.1 - 2026-11-05 + +### Security + +- GHSA-78q5-mpjq-5q34: Remote code execution via manual OPML import (`--import-from-opml`) + + All Newsboat releases are affected. + + CVE ID pending. + + This is a more specific version of GHSA-4m8m-vj3m-hfxx: the vulnerability is + the same, but the attack vector is a bit more complex. + + When importing an OPML document, Newsboat doesn't sanitise newlines in the + data it reads. This data is then written into a plain-text file (the urls + file) which is then read back line-by-line. Specially crafted OPML documents + could utilise this to add more lines to the urls file and inject `exec:` and + `filter:` special URLs that contain shell commands to be executed when such + URL is reloaded. + + In order to resolve this, URLs are sanitized by percent-encoding them, while + newlines in tags are replaced by underscore character. + + Responsibly disclosed via email by @ghozt, who also provided the essence + of the fix. + + + ## 2.45 - 2026-10-04 This release was delayed by a week in order to work on security fixes. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/newsboat-2.45/Cargo.lock new/newsboat-2.45.1/Cargo.lock --- old/newsboat-2.45/Cargo.lock 2026-10-04 12:16:42.000000000 +0200 +++ new/newsboat-2.45.1/Cargo.lock 2026-11-05 16:46:02.000000000 +0100 @@ -901,7 +901,7 @@ [[package]] name = "libnewsboat" -version = "2.45.0" +version = "2.45.1" dependencies = [ "backtrace", "chrono", @@ -928,7 +928,7 @@ [[package]] name = "libnewsboat-ffi" -version = "2.45.0" +version = "2.45.1" dependencies = [ "cxx", "cxx-build", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/newsboat-2.45/README.md new/newsboat-2.45.1/README.md --- old/newsboat-2.45/README.md 2026-10-04 12:16:42.000000000 +0200 +++ new/newsboat-2.45.1/README.md 2026-11-05 16:46:02.000000000 +0100 @@ -31,18 +31,18 @@ * Powerful built-in HTML renderer — no need to start the web browser to view text-only entries * Send links and whole articles to third-party services using [bookmarking - scripts](https://newsboat.org/releases/2.45/docs/newsboat.html#_bookmarking) -* [Filter articles out](https://newsboat.org/releases/2.45/docs/newsboat.html#_killfiles) + scripts](https://newsboat.org/releases/2.45.1/docs/newsboat.html#_bookmarking) +* [Filter articles out](https://newsboat.org/releases/2.45.1/docs/newsboat.html#_killfiles) based on title, author, contents etc. -* [Aggregate articles](https://newsboat.org/releases/2.45/docs/newsboat.html#_query_feeds) +* [Aggregate articles](https://newsboat.org/releases/2.45.1/docs/newsboat.html#_query_feeds) into meta-feeds by arbitrary criteria -* [Apply transformations](https://newsboat.org/releases/2.45/docs/newsboat.html#_scripts_and_filters_snownews_extensions) +* [Apply transformations](https://newsboat.org/releases/2.45.1/docs/newsboat.html#_scripts_and_filters_snownews_extensions) to feeds before passing them into Newsboat * Integrates with services like The Old Reader, NewsBlur, FeedHQ - and [many more](https://newsboat.org/releases/2.45/docs/newsboat.html#_newsboat_as_a_client_for_newsreading_services) -* [Macros](https://newsboat.org/releases/2.45/docs/newsboat.html#_macro_support) + and [many more](https://newsboat.org/releases/2.45.1/docs/newsboat.html#_newsboat_as_a_client_for_newsreading_services) +* [Macros](https://newsboat.org/releases/2.45.1/docs/newsboat.html#_macro_support) to execute sequences of actions with just two keystrokes -* Rudimentary [podcast support](https://newsboat.org/releases/2.45/docs/newsboat.html#_podcast_support) +* Rudimentary [podcast support](https://newsboat.org/releases/2.45.1/docs/newsboat.html#_podcast_support) Downloading ----------- @@ -115,7 +115,7 @@ strict confinement; if we disabled it, the snap would be no better than a distribution's package; - [snap-browser]: https://newsboat.org/releases/2.45/docs/faq.html#_with_snap_i_cant_start_browserbookmarking_scriptexecfilterrun_program_from_macro + [snap-browser]: https://newsboat.org/releases/2.45.1/docs/faq.html#_with_snap_i_cant_start_browserbookmarking_scriptexecfilterrun_program_from_macro - [build from source with Docker](doc/docker.md). Note that the resulting binary might not run outside of that same Docker container if your system doesn't @@ -154,8 +154,8 @@ ------- * Check out our - [documentation](https://newsboat.org/releases/2.45/docs/newsboat.html) and - [FAQ](https://newsboat.org/releases/2.45/docs/faq.html) + [documentation](https://newsboat.org/releases/2.45.1/docs/newsboat.html) and + [FAQ](https://newsboat.org/releases/2.45.1/docs/faq.html) * Report security vulnerabilities to [email protected]. Please encrypt your emails to [OpenPGP key 4ED6CD61932B9EBE](https://newsboat.org/newsboat.pgp) if you can. * Report bugs and ask questions on diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/newsboat-2.45/include/utils.h new/newsboat-2.45.1/include/utils.h --- old/newsboat-2.45/include/utils.h 2026-10-04 12:16:42.000000000 +0200 +++ new/newsboat-2.45.1/include/utils.h 2026-11-05 16:46:02.000000000 +0100 @@ -35,6 +35,7 @@ std::string strip_comments(const std::string& line); bool contains_control_characters(std::string_view str); +std::string replace_newlines(std::string_view str, char replacement = '_'); std::string replace_control_characters(std::string_view str, char replacement = '_'); std::vector<std::string> tokenize(const std::string& str, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/newsboat-2.45/rust/libnewsboat/Cargo.toml new/newsboat-2.45.1/rust/libnewsboat/Cargo.toml --- old/newsboat-2.45/rust/libnewsboat/Cargo.toml 2026-10-04 12:16:42.000000000 +0200 +++ new/newsboat-2.45.1/rust/libnewsboat/Cargo.toml 2026-11-05 16:46:02.000000000 +0100 @@ -1,6 +1,6 @@ [package] name = "libnewsboat" -version = "2.45.0" +version = "2.45.1" authors = ["Alexander Batischev <[email protected]>"] edition = "2024" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/newsboat-2.45/rust/libnewsboat/proptest-regressions/utils.txt new/newsboat-2.45.1/rust/libnewsboat/proptest-regressions/utils.txt --- old/newsboat-2.45/rust/libnewsboat/proptest-regressions/utils.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/newsboat-2.45.1/rust/libnewsboat/proptest-regressions/utils.txt 2026-11-05 16:46:02.000000000 +0100 @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 0313ef86a079ca9b252b4812c078a62ba49035eea6e5ce77736ca7b2e1503b4d # shrinks to ref input = "\\" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/newsboat-2.45/rust/libnewsboat/src/utils.rs new/newsboat-2.45.1/rust/libnewsboat/src/utils.rs --- old/newsboat-2.45/rust/libnewsboat/src/utils.rs 2026-10-04 12:16:42.000000000 +0200 +++ new/newsboat-2.45.1/rust/libnewsboat/src/utils.rs 2026-11-05 16:46:02.000000000 +0100 @@ -171,7 +171,7 @@ } pub fn quote(input: &str) -> String { - let mut input = input.replace('\"', "\\\""); + let mut input = input.replace('\\', "\\\\").replace('\"', "\\\""); input.insert(0, '"'); input.push('"'); input @@ -1375,6 +1375,10 @@ assert_eq!(quote(""), "\"\""); assert_eq!(quote("Hello World!"), "\"Hello World!\""); assert_eq!(quote("\"Hello World!\""), "\"\\\"Hello World!\\\"\""); + assert_eq!( + quote(r#"Here's a stray backslash: \ <- Saw that?"#), + r#""Here's a stray backslash: \\ <- Saw that?""# + ); } #[test] @@ -2427,4 +2431,14 @@ ) ); } + + proptest::proptest! { + #[test] + fn t_quote_unquote(ref input in "\\PC*") { + let quoted = quote(input); + let unquoted = tokenize_quoted("ed, " \r\n\t"); + assert_eq!(unquoted.len(), 1); + assert_eq!(&unquoted[0], input); + } + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/newsboat-2.45/rust/libnewsboat-ffi/Cargo.toml new/newsboat-2.45.1/rust/libnewsboat-ffi/Cargo.toml --- old/newsboat-2.45/rust/libnewsboat-ffi/Cargo.toml 2026-10-04 12:16:42.000000000 +0200 +++ new/newsboat-2.45.1/rust/libnewsboat-ffi/Cargo.toml 2026-11-05 16:46:02.000000000 +0100 @@ -1,6 +1,6 @@ [package] name = "libnewsboat-ffi" -version = "2.45.0" +version = "2.45.1" authors = ["Alexander Batischev <[email protected]>"] edition = "2024" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/newsboat-2.45/src/fileurlwriter.cpp new/newsboat-2.45.1/src/fileurlwriter.cpp --- old/newsboat-2.45/src/fileurlwriter.cpp 2026-10-04 12:16:42.000000000 +0200 +++ new/newsboat-2.45.1/src/fileurlwriter.cpp 2026-11-05 16:46:02.000000000 +0100 @@ -21,9 +21,9 @@ } for (auto& feed_url : feed_urls) { - f << utils::quote_if_necessary(feed_url.url); + f << utils::quote_if_necessary(utils::sanitize_url(feed_url.url)); for (const auto& tag : feed_url.tags) { - f << " \"" << tag << "\""; + f << " " << utils::quote(utils::replace_newlines(tag)); } f << std::endl; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/newsboat-2.45/src/utils.cpp new/newsboat-2.45.1/src/utils.cpp --- old/newsboat-2.45/src/utils.cpp 2026-10-04 12:16:42.000000000 +0200 +++ new/newsboat-2.45.1/src/utils.cpp 2026-11-05 16:46:02.000000000 +0100 @@ -59,6 +59,17 @@ return false; } +std::string utils::replace_newlines(std::string_view str, char replacement) +{ + std::string result(str); + for (char& c : result) { + if (c == '\n') { + c = replacement; + } + } + return result; +} + std::string utils::replace_control_characters(std::string_view str, char replacement) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/newsboat-2.45/test/data/with_escaped_newline_in_nested_outlines.opml new/newsboat-2.45.1/test/data/with_escaped_newline_in_nested_outlines.opml --- old/newsboat-2.45/test/data/with_escaped_newline_in_nested_outlines.opml 1970-01-01 01:00:00.000000000 +0100 +++ new/newsboat-2.45.1/test/data/with_escaped_newline_in_nested_outlines.opml 2026-11-05 16:46:02.000000000 +0100 @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<opml version="1.0"> + <head> + <title>Example OPML with a hidden newline in the nested outlines</title> + </head> + + <body> + <outline text="one exec:echo${IFS}foobar"> + <outline text="three exec:echo${IFS}bazquux"> + <outline type="rss" + xmlUrl="http://example.org/feed2.xml" + text="Nothing special about this feed"/> + </outline> + </outline> + </body> +</opml> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/newsboat-2.45/test/data/with_escaped_newline_in_text_field.opml new/newsboat-2.45.1/test/data/with_escaped_newline_in_text_field.opml --- old/newsboat-2.45/test/data/with_escaped_newline_in_text_field.opml 1970-01-01 01:00:00.000000000 +0100 +++ new/newsboat-2.45.1/test/data/with_escaped_newline_in_text_field.opml 2026-11-05 16:46:02.000000000 +0100 @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<opml version="1.0"> + <head> + <title>Example OPML with a hidden newline in the `text` field</title> + </head> + + <body> + <outline type="rss" + xmlUrl="http://example.org/feed1.xml" + text="demo exec:echo${IFS}boo"/> + </body> +</opml> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/newsboat-2.45/test/data/with_escaped_newline_in_title_field.opml new/newsboat-2.45.1/test/data/with_escaped_newline_in_title_field.opml --- old/newsboat-2.45/test/data/with_escaped_newline_in_title_field.opml 1970-01-01 01:00:00.000000000 +0100 +++ new/newsboat-2.45.1/test/data/with_escaped_newline_in_title_field.opml 2026-11-05 16:46:02.000000000 +0100 @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<opml version="1.0"> + <head> + <title>Example OPML with a hidden newline in the `title` field</title> + </head> + + <body> + <outline type="rss" + xmlUrl="http://example.org/feed2.xml" + title="demo exec:echo${IFS}oops"/> + </body> +</opml> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/newsboat-2.45/test/data/with_escaped_newline_in_xmlurl_field.opml new/newsboat-2.45.1/test/data/with_escaped_newline_in_xmlurl_field.opml --- old/newsboat-2.45/test/data/with_escaped_newline_in_xmlurl_field.opml 1970-01-01 01:00:00.000000000 +0100 +++ new/newsboat-2.45.1/test/data/with_escaped_newline_in_xmlurl_field.opml 2026-11-05 16:46:02.000000000 +0100 @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<opml version="1.0"> + <head> + <title>Example OPML with a hidden newline in the `xmlUrl` field</title> + </head> + + <body> + <outline type="rss" + xmlUrl="http://example.org/feed1.xml exec:echo${IFS}boo" + text="A normal site"/> + </body> +</opml> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/newsboat-2.45/test/fileurlwriter.cpp new/newsboat-2.45.1/test/fileurlwriter.cpp --- old/newsboat-2.45/test/fileurlwriter.cpp 2026-10-04 12:16:42.000000000 +0200 +++ new/newsboat-2.45.1/test/fileurlwriter.cpp 2026-11-05 16:46:02.000000000 +0100 @@ -109,3 +109,132 @@ } } } + +TEST_CASE("FileUrlWriter percent-encodes URLs", "[FileUrlWriter]") +{ + test_helpers::TempFile file; + + const auto get_feed_origin = []() -> FeedOrigin { + static std::size_t file_origin; + FeedOrigin ret; + ret.file_origin = { file_origin++ }; + return ret; + }; + const std::vector<std::string> no_tags; + const std::vector<UrlReader::FeedUrl> urls { + { + "https://example.com/feed.xml\nexec:echo boo", + get_feed_origin(), + no_tags + }, + + { + "https://example.com/another_feed.xml\rexec:echo\tboo", + get_feed_origin(), + no_tags + } + }; + + FileUrlWriter::write_urls(urls, file.get_path()); + + const auto result = test_helpers::file_contents(file.get_path()); + const std::vector<std::string> expected_result { + R"("https://example.com/feed.xml%0Aexec:echo boo")", + R"(https://example.com/another_feed.xml%0Dexec:echo%09boo)", + "" + }; + REQUIRE(result == expected_result); +} + +TEST_CASE("FileUrlWriter replaces newlines in tags with underscore", + "[FileUrlWriter]") +{ + test_helpers::TempFile file; + + const auto get_feed_origin = []() -> FeedOrigin { + static std::size_t file_origin; + FeedOrigin ret; + ret.file_origin = { file_origin++ }; + return ret; + }; + const std::vector<std::string> no_tags; + const std::vector<UrlReader::FeedUrl> urls { + { + "https://example.org/feed2.xml", + get_feed_origin(), + { "sneaky tag\nexec:echo foo", "weird\tag" } + } + }; + + FileUrlWriter::write_urls(urls, file.get_path()); + + const auto result = test_helpers::file_contents(file.get_path()); + const std::vector<std::string> expected_result { + R"(https://example.org/feed2.xml "sneaky tag_exec:echo foo" "weird ag")", + "" + }; + REQUIRE(result == expected_result); +} + +TEST_CASE("FileUrlWriter puts tags in double quotes", "[FileUrlWriter]") +{ + test_helpers::TempFile file; + + const auto get_feed_origin = []() -> FeedOrigin { + static std::size_t file_origin; + FeedOrigin ret; + ret.file_origin = { file_origin++ }; + return ret; + }; + const std::vector<UrlReader::FeedUrl> urls { + { + "https://example.com/news.atom", + get_feed_origin(), + { "tag1", "tag two", "tag \"three\"" } + }, + + { + "https://example.com/feed.xml", + get_feed_origin(), + { "the quick", "brown fox \"jumps", "tag1", "the lazy", "tag two" } + }, + + { + "https://example.com/feed2.xml", + get_feed_origin(), + { "tricky tag\\", "that extends" } + } + }; + + FileUrlWriter::write_urls(urls, file.get_path()); + + FileUrlReader reader(file.get_path()); + reader.reload(); + + const auto into_map = [](const std::vector<UrlReader::FeedUrl>& urls) + -> std::map<std::string, std::vector<std::string>> { + std::map<std::string, std::vector<std::string>> ret; + for (const auto& url : urls) + { + ret[url.url] = url.tags; + } + return ret; + }; + const auto actual_urls = into_map(reader.get_urls()); + for (const auto& url : urls) { + auto it = actual_urls.find(url.url); + REQUIRE(it != std::end(actual_urls)); + REQUIRE(it->second == url.tags); + } + + const std::set<std::string> expected_alltags { + "tag1", "tag two", "tag \"three\"", + "the quick", "brown fox \"jumps", "the lazy", + "tricky tag\\", "that extends" + }; + const auto actual_alltags = reader.get_alltags(); + const std::set<std::string> actual_alltags_set { + actual_alltags.begin(), actual_alltags.end() + }; + REQUIRE(actual_alltags_set == expected_alltags); +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/newsboat-2.45/test/opml.cpp new/newsboat-2.45.1/test/opml.cpp --- old/newsboat-2.45/test/opml.cpp 2026-10-04 12:16:42.000000000 +0200 +++ new/newsboat-2.45.1/test/opml.cpp 2026-11-05 16:46:02.000000000 +0100 @@ -460,6 +460,101 @@ REQUIRE(urlcfg.get_alltags().empty()); } +TEST_CASE("import() replaces newlines with underscores in text attribute", "[Opml]") +{ + test_helpers::TempFile urlsFile; + + FileUrlReader urlcfg(urlsFile.get_path()); + urlcfg.reload(); + + const auto path = + "file:/"_path // `Filepath` will append an extra slash + .join(utils::getcwd()) + .join("data/with_escaped_newline_in_text_field.opml"_path); + REQUIRE_NOTHROW(opml::import(path, urlcfg)); + + urlcfg.reload(); + const auto urls = urlcfg.get_urls(); + REQUIRE(urls.size() == 1); + REQUIRE(urls[0].url == "http://example.org/feed1.xml"); + std::vector<std::string> expected_tags({ "~demo_exec:echo${IFS}boo" }); + REQUIRE(urls[0].tags == expected_tags); + REQUIRE(urlcfg.get_alltags().empty()); +} + +TEST_CASE("import() replaces newlines with underscores in title attribute", "[Opml]") +{ + test_helpers::TempFile urlsFile; + + FileUrlReader urlcfg(urlsFile.get_path()); + urlcfg.reload(); + + const auto path = + "file:/"_path // `Filepath` will append an extra slash + .join(utils::getcwd()) + .join("data/with_escaped_newline_in_title_field.opml"_path); + REQUIRE_NOTHROW(opml::import(path, urlcfg)); + + urlcfg.reload(); + const auto urls = urlcfg.get_urls(); + REQUIRE(urls.size() == 1); + REQUIRE(urls[0].url == "http://example.org/feed2.xml"); + std::vector<std::string> expected_tags({ "~demo_exec:echo${IFS}oops" }); + REQUIRE(urls[0].tags == expected_tags); + REQUIRE(urlcfg.get_alltags().empty()); +} + +TEST_CASE("import() percent-encodes the URL", "[Opml]") +{ + test_helpers::TempFile urlsFile; + + FileUrlReader urlcfg(urlsFile.get_path()); + urlcfg.reload(); + + const auto path = + "file:/"_path // `Filepath` will append an extra slash + .join(utils::getcwd()) + .join("data/with_escaped_newline_in_xmlurl_field.opml"_path); + REQUIRE_NOTHROW(opml::import(path, urlcfg)); + + urlcfg.reload(); + const auto urls = urlcfg.get_urls(); + REQUIRE(urls.size() == 1); + REQUIRE(urls[0].url == "http://example.org/feed1.xml%0Aexec:echo${IFS}boo"); + std::vector<std::string> expected_tags({ "~A normal site" }); + REQUIRE(urls[0].tags == expected_tags); + REQUIRE(urlcfg.get_alltags().empty()); +} + +TEST_CASE("import() replaces newlines with underscores in tags (nested OPML outlines)", + "[Opml]") +{ + test_helpers::TempFile urlsFile; + + FileUrlReader urlcfg(urlsFile.get_path()); + urlcfg.reload(); + + const auto path = + "file:/"_path // `Filepath` will append an extra slash + .join(utils::getcwd()) + .join("data/with_escaped_newline_in_nested_outlines.opml"_path); + REQUIRE_NOTHROW(opml::import(path, urlcfg)); + + urlcfg.reload(); + const auto urls = urlcfg.get_urls(); + REQUIRE(urls.size() == 1); + REQUIRE(urls[0].url == "http://example.org/feed2.xml"); + std::vector<std::string> expected_tags({ + "~Nothing special about this feed", + "one_exec:echo${IFS}foobar/three_exec:echo${IFS}bazquux" + }); + REQUIRE(urls[0].tags == expected_tags); + std::vector<std::string> expected_alltags({ + "one_exec:echo${IFS}foobar/three_exec:echo${IFS}bazquux" + }); + REQUIRE(urlcfg.get_alltags() == expected_alltags); +} + // falls back to "url" if "xmlUrl" is absent // skips an entry if xmlUrl/url is absent ++++++ vendor.tar.xz ++++++ /work/SRC/openSUSE:Factory/newsboat/vendor.tar.xz /work/SRC/openSUSE:Factory/.newsboat.new.3759290/vendor.tar.xz differ: char 15, line 1
