feedjira 4.0.2 → 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: cb65081b36daffe6b574820cdfba4b0e444854e8e72b3b6f6f82f2efc80befd2
4
- data.tar.gz: 14d58fb668a54606e0b03e0f8f9a6748681a562902955120937f0cb6cd62c0b7
3
+ metadata.gz: 3f87fde64ce148ec9ebbd1da344070d1e9bb381904b377e030d9aae56b602aa3
4
+ data.tar.gz: 82282b9e5c0b03acd93b5a7e11f697793ef1d2bd34014178fcde7f632d3259ec
5
5
  SHA512:
6
- metadata.gz: 39be37b94a2c07856bee078fa641015ce78c19336797dbf1403d731db27b6c2ab44ad834e2b691c63b9d473462e1794b228e949e7b87d7647a04c198ab514274
7
- data.tar.gz: 0c9237598fd8d7cea5be2201169925d30983298b02612d2812fd1ab196337be4b5a5054eb5f58430b8a4f3bfe6465f4a8018064539bcb93779d12802d9371248
6
+ metadata.gz: 83b305a7cd33379b384ef8fe60928ba02cf7f64b0f49793d989c2a37a61ebf4a70d424b15709bd4b441de1b6bb2e170db1b646e590998bd31279b599d238fbc1
7
+ data.tar.gz: ae3c9076b1ae95dd943be0e4903ceaade5c955dde3a8af6bb45b8da9e94390b5a0a4607e469537a090246762febd4dcb68da8c56659cacf198da8db79af4a585
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
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
+
3
8
  ## 4.0.2
4
9
 
5
10
  * Fix ITunesRSS.able_to_parse? false positive on CDATA content (#570)
data/Gemfile CHANGED
@@ -4,18 +4,18 @@ source "https://rubygems.org/"
4
4
 
5
5
  gemspec
6
6
 
7
- gem "faraday", "~> 2.14.0"
7
+ gem "faraday", "~> 2.15.0"
8
8
  gem "pry", "~> 0.16.0"
9
9
  gem "rspec", "~> 3.13.0"
10
- gem "rubocop", "~> 1.86.0"
11
- gem "rubocop-performance", "~> 1.26.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.9.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
 
@@ -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
+ # "&", so we turn it back into "&".
18
+ def start_element(name, attrs = [])
19
+ super(name, attrs.map { |key, value| [key, value.gsub("&", "&")] })
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.2"
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"
@@ -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
 
@@ -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
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.2
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
@@ -227,7 +229,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
227
229
  - !ruby/object:Gem::Version
228
230
  version: '0'
229
231
  requirements: []
230
- rubygems_version: 4.0.10
232
+ rubygems_version: 4.0.20
231
233
  specification_version: 4
232
234
  summary: A feed parsing library
233
235
  test_files: []