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 +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile +6 -6
- data/Rakefile +1 -1
- data/lib/feedjira/feed_entry_utilities.rb +1 -2
- data/lib/feedjira/feed_utilities.rb +1 -1
- data/lib/feedjira/parser/atom_google_alerts.rb +1 -1
- data/lib/feedjira/parser/itunes_rss.rb +2 -1
- data/lib/feedjira/rss_entry_utilities.rb +1 -2
- data/lib/feedjira/sax_nokogiri_handler_patch.rb +24 -0
- data/lib/feedjira/version.rb +1 -1
- data/lib/feedjira.rb +3 -1
- data/spec/feedjira/parser/atom_spec.rb +2 -2
- data/spec/feedjira/parser/itunes_rss_spec.rb +4 -0
- data/spec/feedjira/preprocessor_spec.rb +2 -2
- data/spec/feedjira/sax_nokogiri_handler_patch_spec.rb +65 -0
- data/spec/feedjira_spec.rb +6 -0
- data/spec/sample_feeds/AtomWithITunesInCDATA.xml +17 -0
- data/spec/sample_feeds.rb +2 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3f87fde64ce148ec9ebbd1da344070d1e9bb381904b377e030d9aae56b602aa3
|
|
4
|
+
data.tar.gz: 82282b9e5c0b03acd93b5a7e11f697793ef1d2bd34014178fcde7f632d3259ec
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 83b305a7cd33379b384ef8fe60928ba02cf7f64b0f49793d989c2a37a61ebf4a70d424b15709bd4b441de1b6bb2e170db1b646e590998bd31279b599d238fbc1
|
|
7
|
+
data.tar.gz: ae3c9076b1ae95dd943be0e4903ceaade5c955dde3a8af6bb45b8da9e94390b5a0a4607e469537a090246762febd4dcb68da8c56659cacf198da8db79af4a585
|
data/.rubocop.yml
CHANGED
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.
|
|
8
|
-
gem "pry", "~> 0.
|
|
7
|
+
gem "faraday", "~> 2.15.0"
|
|
8
|
+
gem "pry", "~> 0.16.0"
|
|
9
9
|
gem "rspec", "~> 3.13.0"
|
|
10
|
-
gem "rubocop", "~> 1.
|
|
11
|
-
gem "rubocop-performance", "~> 1.
|
|
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.
|
|
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.
|
|
20
|
+
gem "rake", "~> 13.4.2"
|
|
21
21
|
end
|
data/Rakefile
CHANGED
|
@@ -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.
|
|
@@ -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)
|
|
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
|
-
|
|
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
|
+
# "&", 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)
|
data/lib/feedjira/version.rb
CHANGED
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
|
|
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
|
|
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
|
|
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
|
|
21
|
-
expect(escaped_parts[16]).to
|
|
20
|
+
expect(escaped_parts[10]).to include("<i>dogs</i>") # title
|
|
21
|
+
expect(escaped_parts[16]).to include("<b>XHTML</b>") # summary
|
|
22
22
|
expect(escaped_parts[26]).to match(/<p>$/) # 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 & b < c ’"))
|
|
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&b=2&c=&#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=&"))
|
|
64
|
+
end
|
|
65
|
+
end
|
data/spec/feedjira_spec.rb
CHANGED
|
@@ -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><rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"></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.
|
|
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:
|
|
232
|
+
rubygems_version: 4.0.20
|
|
230
233
|
specification_version: 4
|
|
231
234
|
summary: A feed parsing library
|
|
232
235
|
test_files: []
|