feedjira 4.0.1 → 4.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7c43633bb85f7aa16a1930ba863d404bee83de5365676c1df4600df5640d4d6
4
- data.tar.gz: e910be45a7e47e55cec3d4d09e219d6d245834da7360d22338c6380e1bad42ac
3
+ metadata.gz: 3f87fde64ce148ec9ebbd1da344070d1e9bb381904b377e030d9aae56b602aa3
4
+ data.tar.gz: 82282b9e5c0b03acd93b5a7e11f697793ef1d2bd34014178fcde7f632d3259ec
5
5
  SHA512:
6
- metadata.gz: a1fbf835d1ae58a7a542fc37362e7c8d1fa9ab5ec435075da34f7a386aac5d0a2acda1e8d24f4138b50ca410582e5fec48409ecf6bcdf93cc2a6f8f6dee55882
7
- data.tar.gz: 0ec37861fc3fac82ce93a41df4b84a3800a3a74090f17b5b6322e35c1189e63d83671d0deeca6847bb25803118f9e406c067b1e1363f863edce788b9b985130d
6
+ metadata.gz: 83b305a7cd33379b384ef8fe60928ba02cf7f64b0f49793d989c2a37a61ebf4a70d424b15709bd4b441de1b6bb2e170db1b646e590998bd31279b599d238fbc1
7
+ data.tar.gz: ae3c9076b1ae95dd943be0e4903ceaade5c955dde3a8af6bb45b8da9e94390b5a0a4607e469537a090246762febd4dcb68da8c56659cacf198da8db79af4a585
data/.rubocop.yml CHANGED
@@ -60,3 +60,6 @@ RSpec/RepeatedExample:
60
60
  Enabled: false
61
61
 
62
62
  Style/Copyright: { Enabled: false }
63
+
64
+ Style/HashLookupMethod:
65
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Feedjira Changelog
2
2
 
3
+ ## 4.0.3
4
+
5
+ * Security: stop loading external entities declared in a feed's DOCTYPE when
6
+ parsing with the Nokogiri handler (GHSA-frpm-47cm-rg8f)
7
+
8
+ ## 4.0.2
9
+
10
+ * Fix ITunesRSS.able_to_parse? false positive on CDATA content (#570)
11
+
3
12
  ## 4.0.1
4
13
 
5
14
  * Amend Atom parser able_to_parse regex to also match the HTTPS variant of the xmlns attribute value (#558)
data/Gemfile CHANGED
@@ -4,18 +4,18 @@ source "https://rubygems.org/"
4
4
 
5
5
  gemspec
6
6
 
7
- gem "faraday", "~> 2.13.4"
8
- gem "pry", "~> 0.15.0"
7
+ gem "faraday", "~> 2.15.0"
8
+ gem "pry", "~> 0.16.0"
9
9
  gem "rspec", "~> 3.13.0"
10
- gem "rubocop", "~> 1.80.0"
11
- gem "rubocop-performance", "~> 1.25.0"
10
+ gem "rubocop", "~> 1.92.0"
11
+ gem "rubocop-performance", "~> 1.27.0"
12
12
  gem "rubocop-rake", "~> 0.7.1"
13
- gem "rubocop-rspec", "~> 3.6.0"
13
+ gem "rubocop-rspec", "~> 3.10.2"
14
14
  gem "simplecov", "~> 0.22.0"
15
15
  gem "yard", "~> 0.9.34"
16
16
 
17
17
  group :test do
18
18
  gem "oga", "~> 3.4"
19
19
  gem "ox", "~> 2.14.17", platforms: %i[mri rbx]
20
- gem "rake", "~> 13.3.0"
20
+ gem "rake", "~> 13.4.2"
21
21
  end
data/Rakefile CHANGED
@@ -14,7 +14,7 @@ RuboCop::RakeTask.new(:rubocop) do |t|
14
14
  end
15
15
 
16
16
  YARD::Rake::YardocTask.new do |t|
17
- t.files = ["lib/**/*.rb", "-", "LICENSE"]
17
+ t.files = ["lib/**/*.rb", "-", "LICENSE"]
18
18
  end
19
19
 
20
20
  task default: %i[spec rubocop]
@@ -19,11 +19,10 @@ module Feedjira
19
19
  ##
20
20
  # Returns the id of the entry or its url if not id is present, as some
21
21
  # formats don't support it
22
- # rubocop:disable Naming/MemoizedInstanceVariableName
22
+ # rubocop:disable-next Naming/MemoizedInstanceVariableName
23
23
  def id
24
24
  @entry_id ||= @url
25
25
  end
26
- # rubocop:enable Naming/MemoizedInstanceVariableName
27
26
 
28
27
  ##
29
28
  # Writer for published. By default, we keep the "oldest" publish time found.
@@ -58,7 +58,7 @@ module Feedjira
58
58
 
59
59
  def update_from_feed(feed)
60
60
  self.new_entries += find_new_entries_for(feed)
61
- entries.unshift(*self.new_entries)
61
+ entries.unshift(*new_entries)
62
62
 
63
63
  @updated = false
64
64
 
@@ -15,7 +15,7 @@ module Feedjira
15
15
  elements :entry, as: :entries, class: AtomGoogleAlertsEntry
16
16
 
17
17
  def self.able_to_parse?(xml)
18
- Atom.able_to_parse?(xml) && (%r{<id>tag:google\.com,2005:[^<]+/com\.google/alerts/} === xml) # rubocop:disable Style/CaseEquality
18
+ Atom.able_to_parse?(xml) && (%r{<id>tag:google\.com,2005:[^<]+/com\.google/alerts/} === xml)
19
19
  end
20
20
 
21
21
  def self.preprocess(xml)
@@ -63,7 +63,8 @@ module Feedjira
63
63
  elements :item, as: :entries, class: ITunesRSSItem
64
64
 
65
65
  def self.able_to_parse?(xml)
66
- %r{xmlns:itunes\s?=\s?["']http://www\.itunes\.com/dtds/podcast-1\.0\.dtd["']}i =~ xml
66
+ xml_without_cdata = xml.gsub(/<!\[CDATA\[.*?\]\]>/m, "")
67
+ %r{xmlns:itunes\s?=\s?["']http://www\.itunes\.com/dtds/podcast-1\.0\.dtd["']}i =~ xml_without_cdata
67
68
  end
68
69
  end
69
70
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Feedjira
4
4
  module RSSEntryUtilities
5
- # rubocop:todo Metrics/MethodLength
5
+ # rubocop:todo-next Metrics/MethodLength
6
6
  def self.included(mod) # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
7
7
  mod.class_exec do
8
8
  element :title
@@ -40,7 +40,6 @@ module Feedjira
40
40
  elements :category, as: :categories
41
41
  end
42
42
  end
43
- # rubocop:enable Metrics/MethodLength
44
43
 
45
44
  def entry_id
46
45
  @entry_id&.guid
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sax-machine/handlers/sax_nokogiri_handler"
4
+
5
+ module Feedjira
6
+ # sax-machine's Nokogiri handler turns on entity replacement, which makes
7
+ # libxml2 load external entities declared in a feed's DOCTYPE (XXE). This
8
+ # parses with Nokogiri's default instead, which still decodes predefined
9
+ # entities, character references, and internal entities in text.
10
+ module SAXNokogiriHandlerPatch
11
+ # Parse without entity replacement so external entities are never loaded.
12
+ def sax_parse(xml_input)
13
+ ::Nokogiri::XML::SAX::Parser.new(self).parse(xml_input)
14
+ end
15
+
16
+ # Without entity replacement, libxml2 returns "&" in attribute values as
17
+ # "&#38;", so we turn it back into "&".
18
+ def start_element(name, attrs = [])
19
+ super(name, attrs.map { |key, value| [key, value.gsub("&#38;", "&")] })
20
+ end
21
+ end
22
+ end
23
+
24
+ ::SAXMachine::SAXNokogiriHandler.prepend(::Feedjira::SAXNokogiriHandlerPatch)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Feedjira
4
- VERSION = "4.0.1"
4
+ VERSION = "4.0.3"
5
5
  end
data/lib/feedjira.rb CHANGED
@@ -6,6 +6,7 @@ require "loofah"
6
6
  require "logger"
7
7
  require "json"
8
8
 
9
+ require_relative "feedjira/sax_nokogiri_handler_patch"
9
10
  require_relative "feedjira/util"
10
11
  require_relative "feedjira/util/parse_time"
11
12
  require_relative "feedjira/configuration"
@@ -44,7 +45,8 @@ require_relative "feedjira/parser/json_feed_item"
44
45
 
45
46
  # Feedjira
46
47
  module Feedjira
47
- NoParserAvailable = Class.new(StandardError)
48
+ class NoParserAvailable < StandardError
49
+ end
48
50
 
49
51
  extend Configuration
50
52
 
@@ -133,10 +133,10 @@ module Feedjira
133
133
  feed = Atom.parse sample_atom_xhtml_feed
134
134
  entry = feed.entries.first
135
135
 
136
- expect(entry.raw_title).to match(/<i/)
136
+ expect(entry.raw_title).to include("<i")
137
137
  expect(entry.title).to eq("Sentry Calming Collar for dogs")
138
138
  expect(entry.title_type).to eq("xhtml")
139
- expect(entry.summary).to match(/<b/)
139
+ expect(entry.summary).to include("<b")
140
140
  expect(entry.content).to match(/\A<p/)
141
141
  end
142
142
 
@@ -24,6 +24,10 @@ module Feedjira
24
24
  it "returns false for an rss feedburner feed" do
25
25
  expect(ITunesRSS).not_to be_able_to_parse(sample_rss_feed_burner_feed)
26
26
  end
27
+
28
+ it "returns false for an atom feed with xmlns:itunes inside CDATA" do
29
+ expect(ITunesRSS).not_to be_able_to_parse(sample_atom_with_itunes_in_cdata)
30
+ end
27
31
  end
28
32
 
29
33
  describe "parsing" do
@@ -17,8 +17,8 @@ describe Feedjira::Preprocessor do
17
17
  escaped = processor.to_xml
18
18
  escaped_parts = escaped.split "\n"
19
19
 
20
- expect(escaped_parts[10]).to match(%r{&lt;i&gt;dogs&lt;/i&gt;}) # title
21
- expect(escaped_parts[16]).to match(%r{&lt;b&gt;XHTML&lt;/b&gt;}) # summary
20
+ expect(escaped_parts[10]).to include("&lt;i&gt;dogs&lt;/i&gt;") # title
21
+ expect(escaped_parts[16]).to include("&lt;b&gt;XHTML&lt;/b&gt;") # summary
22
22
  expect(escaped_parts[26]).to match(/&lt;p&gt;$/) # content
23
23
  end
24
24
 
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "tempfile"
5
+
6
+ ::RSpec.describe(::Feedjira::SAXNokogiriHandlerPatch) do
7
+ around do |example|
8
+ original_handler = ::SAXMachine.handler
9
+ ::SAXMachine.handler = :nokogiri
10
+ example.run
11
+ ensure
12
+ ::SAXMachine.handler = original_handler
13
+ end
14
+
15
+ def rss_with_entry(title: "Title", link: "https://example.com", doctype: "")
16
+ <<~XML
17
+ <?xml version="1.0"?>
18
+ #{doctype}
19
+ <rss version="2.0">
20
+ <channel>
21
+ <title>Feed</title>
22
+ <item>
23
+ <title>#{title}</title>
24
+ <enclosure url="#{link}" type="audio/mpeg"/>
25
+ </item>
26
+ </channel>
27
+ </rss>
28
+ XML
29
+ end
30
+
31
+ it "does not load external entities" do
32
+ secret = ::Tempfile.new
33
+ secret.write("secret contents")
34
+ secret.flush
35
+ doctype = %(<!DOCTYPE rss [<!ENTITY file SYSTEM "file://#{secret.path}">]>)
36
+
37
+ feed = ::Feedjira.parse(rss_with_entry(title: "&file;", doctype:))
38
+
39
+ expect(feed.entries.first.title.to_s).not_to(include("secret contents"))
40
+ ensure
41
+ secret.close!
42
+ end
43
+
44
+ it "decodes predefined entities and character references in text" do
45
+ feed = ::Feedjira.parse(rss_with_entry(title: "a &amp; b &lt; c &#8217;"))
46
+
47
+ expect(feed.entries.first.title).to(eq("a & b < c ’"))
48
+ end
49
+
50
+ it "expands internal entities in text" do
51
+ doctype = '<!DOCTYPE rss [<!ENTITY co "Acme Inc">]>'
52
+
53
+ feed = ::Feedjira.parse(rss_with_entry(title: "by &co;", doctype:))
54
+
55
+ expect(feed.entries.first.title).to(eq("by Acme Inc"))
56
+ end
57
+
58
+ it "decodes ampersands in attribute values" do
59
+ xml = rss_with_entry(link: "https://example.com/?a=1&amp;b=2&amp;c=&amp;#38;")
60
+
61
+ feed = ::Feedjira.parse(xml, parser: ::Feedjira::Parser::ITunesRSS)
62
+
63
+ expect(feed.entries.first.enclosure_url).to(eq("https://example.com/?a=1&b=2&c=&#38;"))
64
+ end
65
+ end
@@ -155,6 +155,12 @@ RSpec.describe Feedjira do
155
155
  expect(actual_parser).to eq described_class::Parser::ITunesRSS
156
156
  end
157
157
 
158
+ it "with an atom feed containing xmlns:itunes in CDATA it returns the Atom parser" do
159
+ xml = sample_atom_with_itunes_in_cdata
160
+ actual_parser = described_class.parser_for_xml(xml)
161
+ expect(actual_parser).to eq described_class::Parser::Atom
162
+ end
163
+
158
164
  context "when parsers are configured" do
159
165
  it "does not use default parsers" do
160
166
  xml = "Atom asdf"
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" encoding="UTF-8" ?>
2
+ <feed xmlns="http://www.w3.org/2005/Atom">
3
+ <title>Test Blog</title>
4
+ <link rel="alternate" href="https://example.com/" />
5
+ <id>tag:example.com,2024:blog</id>
6
+ <updated>2024-01-01T00:00:00Z</updated>
7
+ <entry>
8
+ <title>How to set up a podcast RSS feed</title>
9
+ <link href="https://example.com/post/1" />
10
+ <id>tag:example.com,2024:post-1</id>
11
+ <published>2024-01-01T00:00:00Z</published>
12
+ <content type="html"><![CDATA[
13
+ <p>Here is an example RSS feed:</p>
14
+ <pre><code>&lt;rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"&gt;</code></pre>
15
+ ]]></content>
16
+ </entry>
17
+ </feed>
data/spec/sample_feeds.rb CHANGED
@@ -37,7 +37,8 @@ module SampleFeeds
37
37
  sample_invalid_date_format_feed: "InvalidDateFormat.xml",
38
38
  sample_rss_feed_permalinks: "Permalinks.xml",
39
39
  sample_rss_feed_with_a10_namespace: "a10.xml",
40
- sample_rss_feed_with_comments: "RSSWithComments.xml"
40
+ sample_rss_feed_with_comments: "RSSWithComments.xml",
41
+ sample_atom_with_itunes_in_cdata: "AtomWithITunesInCDATA.xml"
41
42
  }.freeze
42
43
 
43
44
  FEEDS.each do |method, filename|
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: feedjira
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.1
4
+ version: 4.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Hess
@@ -127,6 +127,7 @@ files:
127
127
  - lib/feedjira/parser/rss_image.rb
128
128
  - lib/feedjira/preprocessor.rb
129
129
  - lib/feedjira/rss_entry_utilities.rb
130
+ - lib/feedjira/sax_nokogiri_handler_patch.rb
130
131
  - lib/feedjira/util.rb
131
132
  - lib/feedjira/util/parse_time.rb
132
133
  - lib/feedjira/version.rb
@@ -157,6 +158,7 @@ files:
157
158
  - spec/feedjira/parser/rss_feed_burner_spec.rb
158
159
  - spec/feedjira/parser/rss_spec.rb
159
160
  - spec/feedjira/preprocessor_spec.rb
161
+ - spec/feedjira/sax_nokogiri_handler_patch_spec.rb
160
162
  - spec/feedjira/util/parse_time_spec.rb
161
163
  - spec/feedjira_spec.rb
162
164
  - spec/sample_feeds.rb
@@ -164,6 +166,7 @@ files:
164
166
  - spec/sample_feeds/AmazonWebServicesBlogFirstEntryContent.xml
165
167
  - spec/sample_feeds/AtomEscapedHTMLInPreTag.xml
166
168
  - spec/sample_feeds/AtomFeedWithSpacesAroundEquals.xml
169
+ - spec/sample_feeds/AtomWithITunesInCDATA.xml
167
170
  - spec/sample_feeds/CRE.xml
168
171
  - spec/sample_feeds/DuplicateContentAtomFeed.xml
169
172
  - spec/sample_feeds/FeedBurnerUrlNoAlternate.xml
@@ -226,7 +229,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
226
229
  - !ruby/object:Gem::Version
227
230
  version: '0'
228
231
  requirements: []
229
- rubygems_version: 3.6.9
232
+ rubygems_version: 4.0.20
230
233
  specification_version: 4
231
234
  summary: A feed parsing library
232
235
  test_files: []