Skip to content

Commit

Permalink
Add local entity expansion limit methods (#202)
Browse files Browse the repository at this point in the history
GitHub: fix GH-192

Add local entity expansion limit methods.

- `REXML::Document#entity_expansion_limit=`
- `REXML::Document#entity_expansion_text_limit=`
- `REXML::Parsers::SAX2Parser#entity_expansion_limit=`
- `REXML::Parsers::SAX2Parser#entity_expansion_text_limit=`
- `REXML::Parsers::StreamParser#entity_expansion_limit=`
- `REXML::Parsers::StreamParser#entity_expansion_text_limit=`
- `REXML::Parsers::PullParser#entity_expansion_limit=`
- `REXML::Parsers::PullParser#entity_expansion_text_limit=`

---------

Co-authored-by: Sutou Kouhei <kou@clear-code.com>
  • Loading branch information
naitoh and kou committed Aug 26, 2024
1 parent 1c694d1 commit caec187
Show file tree
Hide file tree
Showing 12 changed files with 83 additions and 88 deletions.
5 changes: 3 additions & 2 deletions lib/rexml/attribute.rb
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,9 @@ def to_s
# have been expanded to their values
def value
return @unnormalized if @unnormalized
@unnormalized = Text::unnormalize( @normalized, doctype )
@unnormalized

@unnormalized = Text::unnormalize(@normalized, doctype,
entity_expansion_text_limit: @element&.document&.entity_expansion_text_limit)
end

# The normalized value of this attribute. That is, the attribute with
Expand Down
6 changes: 5 additions & 1 deletion lib/rexml/document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ class Document < Element
#
def initialize( source = nil, context = {} )
@entity_expansion_count = 0
@entity_expansion_limit = Security.entity_expansion_limit
@entity_expansion_text_limit = Security.entity_expansion_text_limit
super()
@context = context
return if source.nil?
Expand Down Expand Up @@ -431,10 +433,12 @@ def Document::entity_expansion_text_limit
end

attr_reader :entity_expansion_count
attr_writer :entity_expansion_limit
attr_accessor :entity_expansion_text_limit

def record_entity_expansion
@entity_expansion_count += 1
if @entity_expansion_count > Security.entity_expansion_limit
if @entity_expansion_count > @entity_expansion_limit
raise "number of entity expansions exceeded, processing aborted."
end
end
Expand Down
7 changes: 5 additions & 2 deletions lib/rexml/entity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,12 @@ def Entity::matches? string
# Evaluates to the unnormalized value of this entity; that is, replacing
# &ent; entities.
def unnormalized
document.record_entity_expansion unless document.nil?
document&.record_entity_expansion

return nil if @value.nil?
@unnormalized = Text::unnormalize(@value, parent)

@unnormalized = Text::unnormalize(@value, parent,
entity_expansion_text_limit: document&.entity_expansion_text_limit)
end

#once :unnormalized
Expand Down
8 changes: 6 additions & 2 deletions lib/rexml/parsers/baseparser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ def initialize( source )
@listeners = []
@prefixes = Set.new
@entity_expansion_count = 0
@entity_expansion_limit = Security.entity_expansion_limit
@entity_expansion_text_limit = Security.entity_expansion_text_limit
end

def add_listener( listener )
Expand All @@ -172,6 +174,8 @@ def add_listener( listener )

attr_reader :source
attr_reader :entity_expansion_count
attr_writer :entity_expansion_limit
attr_writer :entity_expansion_text_limit

def stream=( source )
@source = SourceFactory.create_from( source )
Expand Down Expand Up @@ -585,7 +589,7 @@ def unnormalize( string, entities=nil, filter=nil )
end
re = Private::DEFAULT_ENTITIES_PATTERNS[entity_reference] || /&#{entity_reference};/
rv.gsub!( re, entity_value )
if rv.bytesize > Security.entity_expansion_text_limit
if rv.bytesize > @entity_expansion_text_limit
raise "entity expansion has grown too large"
end
else
Expand Down Expand Up @@ -627,7 +631,7 @@ def pop_namespaces_restore

def record_entity_expansion(delta=1)
@entity_expansion_count += delta
if @entity_expansion_count > Security.entity_expansion_limit
if @entity_expansion_count > @entity_expansion_limit
raise "number of entity expansions exceeded, processing aborted."
end
end
Expand Down
8 changes: 8 additions & 0 deletions lib/rexml/parsers/pullparser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ def entity_expansion_count
@parser.entity_expansion_count
end

def entity_expansion_limit=( limit )
@parser.entity_expansion_limit = limit
end

def entity_expansion_text_limit=( limit )
@parser.entity_expansion_text_limit = limit
end

def each
while has_next?
yield self.pull
Expand Down
8 changes: 8 additions & 0 deletions lib/rexml/parsers/sax2parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ def entity_expansion_count
@parser.entity_expansion_count
end

def entity_expansion_limit=( limit )
@parser.entity_expansion_limit = limit
end

def entity_expansion_text_limit=( limit )
@parser.entity_expansion_text_limit = limit
end

def add_listener( listener )
@parser.add_listener( listener )
end
Expand Down
8 changes: 8 additions & 0 deletions lib/rexml/parsers/streamparser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ def entity_expansion_count
@parser.entity_expansion_count
end

def entity_expansion_limit=( limit )
@parser.entity_expansion_limit = limit
end

def entity_expansion_text_limit=( limit )
@parser.entity_expansion_text_limit = limit
end

def parse
# entity string
while true
Expand Down
8 changes: 5 additions & 3 deletions lib/rexml/text.rb
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,8 @@ def inspect
# u = Text.new( "sean russell", false, nil, true )
# u.value #-> "sean russell"
def value
@unnormalized ||= Text::unnormalize( @string, doctype )
@unnormalized ||= Text::unnormalize(@string, doctype,
entity_expansion_text_limit: document&.entity_expansion_text_limit)
end

# Sets the contents of this text node. This expects the text to be
Expand Down Expand Up @@ -411,11 +412,12 @@ def Text::normalize( input, doctype=nil, entity_filter=nil )
end

# Unescapes all possible entities
def Text::unnormalize( string, doctype=nil, filter=nil, illegal=nil )
def Text::unnormalize( string, doctype=nil, filter=nil, illegal=nil, entity_expansion_text_limit: nil )
entity_expansion_text_limit ||= Security.entity_expansion_text_limit
sum = 0
string.gsub( /\r\n?/, "\n" ).gsub( REFERENCE ) {
s = Text.expand($&, doctype, filter)
if sum + s.bytesize > Security.entity_expansion_text_limit
if sum + s.bytesize > entity_expansion_text_limit
raise "entity expansion has grown too large"
else
sum += s.bytesize
Expand Down
22 changes: 5 additions & 17 deletions test/test_document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,6 @@ def test_new
end

class EntityExpansionLimitTest < Test::Unit::TestCase
def setup
@default_entity_expansion_limit = REXML::Security.entity_expansion_limit
@default_entity_expansion_text_limit = REXML::Security.entity_expansion_text_limit
end

def teardown
REXML::Security.entity_expansion_limit = @default_entity_expansion_limit
REXML::Security.entity_expansion_text_limit = @default_entity_expansion_text_limit
end

class GeneralEntityTest < self
def test_have_value
xml = <<XML
Expand All @@ -64,9 +54,8 @@ def test_have_value
doc.root.children.first.value
end

REXML::Security.entity_expansion_limit = 100
assert_equal(100, REXML::Security.entity_expansion_limit)
doc = REXML::Document.new(xml)
doc.entity_expansion_limit = 100
assert_raise(RuntimeError.new("number of entity expansions exceeded, processing aborted.")) do
doc.root.children.first.value
end
Expand Down Expand Up @@ -95,9 +84,8 @@ def test_empty_value
doc.root.children.first.value
end

REXML::Security.entity_expansion_limit = 100
assert_equal(100, REXML::Security.entity_expansion_limit)
doc = REXML::Document.new(xml)
doc.entity_expansion_limit = 100
assert_raise(RuntimeError.new("number of entity expansions exceeded, processing aborted.")) do
doc.root.children.first.value
end
Expand All @@ -118,12 +106,12 @@ def test_with_default_entity
</member>
XML

REXML::Security.entity_expansion_limit = 4
doc = REXML::Document.new(xml)
doc.entity_expansion_limit = 4
assert_equal("\na\na a\n<\n", doc.root.children.first.value)

REXML::Security.entity_expansion_limit = 3
doc = REXML::Document.new(xml)
doc.entity_expansion_limit = 3
assert_raise(RuntimeError.new("number of entity expansions exceeded, processing aborted.")) do
doc.root.children.first.value
end
Expand All @@ -142,8 +130,8 @@ def test_entity_expansion_text_limit
<member>&a;</member>
XML

REXML::Security.entity_expansion_text_limit = 90
doc = REXML::Document.new(xml)
doc.entity_expansion_text_limit = 90
assert_equal(90, doc.root.children.first.value.bytesize)
end
end
Expand Down
27 changes: 8 additions & 19 deletions test/test_pullparser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -157,16 +157,6 @@ def test_peek
end

class EntityExpansionLimitTest < Test::Unit::TestCase
def setup
@default_entity_expansion_limit = REXML::Security.entity_expansion_limit
@default_entity_expansion_text_limit = REXML::Security.entity_expansion_text_limit
end

def teardown
REXML::Security.entity_expansion_limit = @default_entity_expansion_limit
REXML::Security.entity_expansion_text_limit = @default_entity_expansion_text_limit
end

class GeneralEntityTest < self
def test_have_value
source = <<-XML
Expand Down Expand Up @@ -206,22 +196,21 @@ def test_empty_value
</member>
XML

REXML::Security.entity_expansion_limit = 100000
parser = REXML::Parsers::PullParser.new(source)
parser.entity_expansion_limit = 100000
while parser.has_next?
parser.pull
end
assert_equal(11111, parser.entity_expansion_count)

REXML::Security.entity_expansion_limit = @default_entity_expansion_limit
parser = REXML::Parsers::PullParser.new(source)
assert_raise(RuntimeError.new("number of entity expansions exceeded, processing aborted.")) do
while parser.has_next?
parser.pull
end
end
assert do
parser.entity_expansion_count > @default_entity_expansion_limit
parser.entity_expansion_count > REXML::Security.entity_expansion_limit
end
end

Expand All @@ -239,14 +228,14 @@ def test_with_default_entity
</member>
XML

REXML::Security.entity_expansion_limit = 4
parser = REXML::Parsers::PullParser.new(source)
parser.entity_expansion_limit = 4
while parser.has_next?
parser.pull
end

REXML::Security.entity_expansion_limit = 3
parser = REXML::Parsers::PullParser.new(source)
parser.entity_expansion_limit = 3
assert_raise(RuntimeError.new("number of entity expansions exceeded, processing aborted.")) do
while parser.has_next?
parser.pull
Expand All @@ -255,7 +244,7 @@ def test_with_default_entity
end

def test_with_only_default_entities
member_value = "&lt;p&gt;#{'A' * @default_entity_expansion_text_limit}&lt;/p&gt;"
member_value = "&lt;p&gt;#{'A' * REXML::Security.entity_expansion_text_limit}&lt;/p&gt;"
source = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<member>
Expand All @@ -276,11 +265,11 @@ def test_with_only_default_entities
end
end

expected_value = "<p>#{'A' * @default_entity_expansion_text_limit}</p>"
expected_value = "<p>#{'A' * REXML::Security.entity_expansion_text_limit}</p>"
assert_equal(expected_value, events['member'].strip)
assert_equal(0, parser.entity_expansion_count)
assert do
events['member'].bytesize > @default_entity_expansion_text_limit
events['member'].bytesize > REXML::Security.entity_expansion_text_limit
end
end

Expand All @@ -296,8 +285,8 @@ def test_entity_expansion_text_limit
<member>&a;</member>
XML

REXML::Security.entity_expansion_text_limit = 90
parser = REXML::Parsers::PullParser.new(source)
parser.entity_expansion_text_limit = 90
events = {}
element_name = ''
while parser.has_next?
Expand Down
27 changes: 8 additions & 19 deletions test/test_sax.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,6 @@ def test_sax2
end

class EntityExpansionLimitTest < Test::Unit::TestCase
def setup
@default_entity_expansion_limit = REXML::Security.entity_expansion_limit
@default_entity_expansion_text_limit = REXML::Security.entity_expansion_text_limit
end

def teardown
REXML::Security.entity_expansion_limit = @default_entity_expansion_limit
REXML::Security.entity_expansion_text_limit = @default_entity_expansion_text_limit
end

class GeneralEntityTest < self
def test_have_value
source = <<-XML
Expand Down Expand Up @@ -147,18 +137,17 @@ def test_empty_value
</member>
XML

REXML::Security.entity_expansion_limit = 100000
sax = REXML::Parsers::SAX2Parser.new(source)
sax.entity_expansion_limit = 100000
sax.parse
assert_equal(11111, sax.entity_expansion_count)

REXML::Security.entity_expansion_limit = @default_entity_expansion_limit
sax = REXML::Parsers::SAX2Parser.new(source)
assert_raise(RuntimeError.new("number of entity expansions exceeded, processing aborted.")) do
sax.parse
end
assert do
sax.entity_expansion_count > @default_entity_expansion_limit
sax.entity_expansion_count > REXML::Security.entity_expansion_limit
end
end

Expand All @@ -176,19 +165,19 @@ def test_with_default_entity
</member>
XML

REXML::Security.entity_expansion_limit = 4
sax = REXML::Parsers::SAX2Parser.new(source)
sax.entity_expansion_limit = 4
sax.parse

REXML::Security.entity_expansion_limit = 3
sax = REXML::Parsers::SAX2Parser.new(source)
sax.entity_expansion_limit = 3
assert_raise(RuntimeError.new("number of entity expansions exceeded, processing aborted.")) do
sax.parse
end
end

def test_with_only_default_entities
member_value = "&lt;p&gt;#{'A' * @default_entity_expansion_text_limit}&lt;/p&gt;"
member_value = "&lt;p&gt;#{'A' * REXML::Security.entity_expansion_text_limit}&lt;/p&gt;"
source = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<member>
Expand All @@ -203,11 +192,11 @@ def test_with_only_default_entities
end
sax.parse

expected_value = "<p>#{'A' * @default_entity_expansion_text_limit}</p>"
expected_value = "<p>#{'A' * REXML::Security.entity_expansion_text_limit}</p>"
assert_equal(expected_value, text_value.strip)
assert_equal(0, sax.entity_expansion_count)
assert do
text_value.bytesize > @default_entity_expansion_text_limit
text_value.bytesize > REXML::Security.entity_expansion_text_limit
end
end

Expand All @@ -223,8 +212,8 @@ def test_entity_expansion_text_limit
<member>&a;</member>
XML

REXML::Security.entity_expansion_text_limit = 90
sax = REXML::Parsers::SAX2Parser.new(source)
sax.entity_expansion_text_limit = 90
text_size = nil
sax.listen(:characters, ["member"]) do |text|
text_size = text.size
Expand Down
Loading

0 comments on commit caec187

Please sign in to comment.