-
-
-
-
-
-
-
+ def test_entity_expansion_text_limit
+ xml = <<-XML
+
+
+
+
+
+
]>
-
-EOF
+&a;
+ XML
- REXML::Document.new(xml)
- REXML::Security.entity_expansion_limit = 90
- assert_equal(90, REXML::Security.entity_expansion_limit)
- assert_raise(REXML::ParseException) do
- REXML::Document.new(xml)
- end
+ REXML::Security.entity_expansion_text_limit = 90
+ doc = REXML::Document.new(xml)
+ assert_equal(90, doc.root.children.first.value.bytesize)
end
end
end
@@ -202,16 +171,45 @@ def test_xml_declaration_standalone
assert_equal('no', doc.stand_alone?, bug2539)
end
- def test_gt_linear_performance
- seq = [10000, 50000, 100000, 150000, 200000]
- assert_linear_performance(seq) do |n|
- REXML::Document.new('" * n + '">')
+ def test_each_recursive
+ xml_source = <<~XML
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ XML
+
+ expected_names = %w[
+ root
+ 1_1 1_2 1_3
+ 2_1 2_2 2_3
+ ]
+
+ document = REXML::Document.new(xml_source)
+
+ # Node#each_recursive iterates elements only.
+ # This does not iterate XML declarations, comments, attributes, CDATA sections, etc.
+ actual_names = []
+ document.each_recursive do |element|
+ actual_names << element.attributes["name"]
end
+ assert_equal(expected_names, actual_names)
end
class WriteTest < Test::Unit::TestCase
def setup
- @document = REXML::Document.new(<<-EOX)
+ @document = REXML::Document.new(<<-EOX.chomp)
Hello world!
EOX
@@ -221,7 +219,7 @@ class ArgumentsTest < self
def test_output
output = ""
@document.write(output)
- assert_equal(<<-EOX, output)
+ assert_equal(<<-EOX.chomp, output)
Hello world!
EOX
@@ -244,7 +242,7 @@ def test_transitive
indent = 2
transitive = true
@document.write(output, indent, transitive)
- assert_equal(<<-EOX, output)
+ assert_equal(<<-EOX.chomp, output)
Hello world!
#{japanese_text}
EOX
@@ -284,7 +282,7 @@ class OptionsTest < self
def test_output
output = ""
@document.write(:output => output)
- assert_equal(<<-EOX, output)
+ assert_equal(<<-EOX.chomp, output)
Hello world!
EOX
@@ -304,7 +302,7 @@ def test_indent
def test_transitive
output = ""
@document.write(:output => output, :indent => 2, :transitive => true)
- assert_equal(<<-EOX, output)
+ assert_equal(<<-EOX.chomp, output)
Hello world! output, :encoding => encoding)
- assert_equal(<<-EOX.encode(encoding), output)
+ assert_equal(<<-EOX.chomp.encode(encoding), output)
#{japanese_text}
EOX
@@ -410,7 +408,7 @@ def test_utf_16
actual_xml = ""
document.write(actual_xml)
- expected_xml = <<-EOX.encode("UTF-16BE")
+ expected_xml = <<-EOX.chomp.encode("UTF-16BE")
\ufeff
Hello world!
EOX
diff --git a/test/test_entity.rb b/test/test_entity.rb
index a2b262f7..89f83894 100644
--- a/test/test_entity.rb
+++ b/test/test_entity.rb
@@ -59,8 +59,7 @@ def test_parse_entity
def test_constructor
one = [ %q{},
- %q{},
- %q{},
+ %q{},
'',
'' ]
source = %q{
-
-
+
',
+ "a",
+ "B",
+ "B",
+ "B",
+ ],
+ [
+ entity.to_s,
+ entity.name,
+ entity.value,
+ entity.normalized,
+ entity.unnormalized,
+ ])
+ end
+
+ def test_readers_without_reference
+ entity = REXML::Entity.new([:entitydecl, "a", "&b;"])
+ assert_equal([
+ '',
+ "a",
+ "&b;",
+ "&b;",
+ "&b;",
+ ],
+ [
+ entity.to_s,
+ entity.name,
+ entity.value,
+ entity.normalized,
+ entity.unnormalized,
+ ])
+ end
+
+ def test_readers_with_nested_references
+ doctype = REXML::DocType.new('root')
+ doctype.add(REXML::Entity.new([:entitydecl, "a", "&b;"]))
+ doctype.add(REXML::Entity.new([:entitydecl, "b", "X"]))
+ assert_equal([
+ "a",
+ "&b;",
+ "&b;",
+ "X",
+ "b",
+ "X",
+ "X",
+ "X",
+ ],
+ [
+ doctype.entities["a"].name,
+ doctype.entities["a"].value,
+ doctype.entities["a"].normalized,
+ doctype.entities["a"].unnormalized,
+ doctype.entities["b"].name,
+ doctype.entities["b"].value,
+ doctype.entities["b"].normalized,
+ doctype.entities["b"].unnormalized,
+ ])
+ end
+
+ def test_parameter_entity_reference_forbidden_by_internal_subset_in_parser
+ source = ' ]>'
+ parser = REXML::Parsers::BaseParser.new(source)
+ exception = assert_raise(REXML::ParseException) do
+ while parser.has_next?
+ parser.pull
+ end
+ end
+ assert_equal(<<-DETAIL, exception.to_s)
+Parameter entity references forbidden in internal subset: "%a;"
+Line: 1
+Position: 54
+Last 80 unconsumed characters:
+ DETAIL
+ end
+
def test_entity_string_limit
template = ' ]> $'
len = 5120 # 5k per entity
@@ -122,22 +198,6 @@ def test_entity_string_limit
end
end
- def test_entity_string_limit_for_parameter_entity
- template = ' ]>'
- len = 5120 # 5k per entity
- template.sub!(/\^/, "B" * len)
-
- # 10k is OK
- entities = '%a;' * 2 # 5k entity * 2 = 10k
- REXML::Document.new(template.sub(/\$/, entities))
-
- # above 10k explodes
- entities = '%a;' * 3 # 5k entity * 2 = 15k
- assert_raise(REXML::ParseException) do
- REXML::Document.new(template.sub(/\$/, entities))
- end
- end
-
def test_raw
source = '
@@ -161,7 +221,7 @@ def test_lazy_evaluation
def test_entity_replacement
source = %q{
- ]>
+ ]>
&WhatHeSaid;}
d = REXML::Document.new( source )
diff --git a/test/test_light.rb b/test/test_light.rb
index 54b2c52e..c556c978 100644
--- a/test/test_light.rb
+++ b/test/test_light.rb
@@ -62,7 +62,7 @@ def test_access_child_elements
assert_equal( 'c', a[1].name )
end
- def test_itterate_over_children
+ def test_iterate_over_children
foo = make_small_document
ctr = 0
foo[0].each { ctr += 1 }
diff --git a/test/test_pullparser.rb b/test/test_pullparser.rb
index 53a985ba..005a106a 100644
--- a/test/test_pullparser.rb
+++ b/test/test_pullparser.rb
@@ -62,6 +62,63 @@ def test_entity_replacement
end
end
+ def test_character_references
+ source = 'AB'
+ parser = REXML::Parsers::PullParser.new( source )
+
+ events = {}
+ element_name = ''
+ while parser.has_next?
+ event = parser.pull
+ case event.event_type
+ when :start_element
+ element_name = event[0]
+ when :text
+ events[element_name] = event[1]
+ end
+ end
+
+ assert_equal('A', events['a'])
+ assert_equal("B", events['b'])
+ end
+
+ def test_text_entity_references
+ source = '<P> <I> <B> Text </B> </I>'
+ parser = REXML::Parsers::PullParser.new( source )
+
+ events = []
+ while parser.has_next?
+ event = parser.pull
+ case event.event_type
+ when :text
+ events << event[1]
+ end
+ end
+
+ assert_equal([" Text "], events)
+ end
+
+ def test_text_content_with_line_breaks
+ source = "AB\nC\r\n"
+ parser = REXML::Parsers::PullParser.new( source )
+
+ events = {}
+ element_name = ''
+ while parser.has_next?
+ event = parser.pull
+ case event.event_type
+ when :start_element
+ element_name = event[0]
+ when :text
+ events[element_name] = event[1]
+ end
+ end
+
+ assert_equal('A', events['a'])
+ assert_equal("B\n", events['b'])
+ assert_equal("C\n", events['c'])
+ end
+
def test_peek_unshift
source = ""
REXML::Parsers::PullParser.new(source)
@@ -98,5 +155,163 @@ def test_peek
end
assert_equal( 0, names.length )
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
+
+
+
+
+
+
+]>
+
+&a;
+
+ XML
+
+ parser = REXML::Parsers::PullParser.new(source)
+ assert_raise(RuntimeError.new("entity expansion has grown too large")) do
+ while parser.has_next?
+ parser.pull
+ end
+ end
+ end
+
+ def test_empty_value
+ source = <<-XML
+
+
+
+
+
+
+]>
+
+&a;
+
+ XML
+
+ REXML::Security.entity_expansion_limit = 100000
+ parser = REXML::Parsers::PullParser.new(source)
+ 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
+ end
+ end
+
+ def test_with_default_entity
+ source = <<-XML
+
+
+
+]>
+
+&a;
+&a2;
+<
+
+ XML
+
+ REXML::Security.entity_expansion_limit = 4
+ parser = REXML::Parsers::PullParser.new(source)
+ while parser.has_next?
+ parser.pull
+ end
+
+ REXML::Security.entity_expansion_limit = 3
+ 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
+ end
+
+ def test_with_only_default_entities
+ member_value = "<p>#{'A' * @default_entity_expansion_text_limit}</p>"
+ source = <<-XML
+
+
+#{member_value}
+
+ XML
+
+ parser = REXML::Parsers::PullParser.new(source)
+ events = {}
+ element_name = ''
+ while parser.has_next?
+ event = parser.pull
+ case event.event_type
+ when :start_element
+ element_name = event[0]
+ when :text
+ events[element_name] = event[1]
+ end
+ end
+
+ expected_value = "
#{'A' * @default_entity_expansion_text_limit}
"
+ assert_equal(expected_value, events['member'].strip)
+ assert_equal(0, parser.entity_expansion_count)
+ assert do
+ events['member'].bytesize > @default_entity_expansion_text_limit
+ end
+ end
+
+ def test_entity_expansion_text_limit
+ source = <<-XML
+
+
+
+
+
+]>
+&a;
+ XML
+
+ REXML::Security.entity_expansion_text_limit = 90
+ parser = REXML::Parsers::PullParser.new(source)
+ events = {}
+ element_name = ''
+ while parser.has_next?
+ event = parser.pull
+ case event.event_type
+ when :start_element
+ element_name = event[0]
+ when :text
+ events[element_name] = event[1]
+ end
+ end
+ assert_equal(90, events['member'].size)
+ end
+ end
+ end
end
end
diff --git a/test/test_sax.rb b/test/test_sax.rb
index c2255bf3..ae17e364 100644
--- a/test/test_sax.rb
+++ b/test/test_sax.rb
@@ -31,6 +31,17 @@ def test_entity_replacement
assert_equal '--1234--', results[1]
end
+ def test_characters_predefined_entities
+ source = '<P> <I> <B> Text </B> </I>'
+
+ sax = Parsers::SAX2Parser.new( source )
+ results = []
+ sax.listen(:characters) {|x| results << x }
+ sax.parse
+
+ assert_equal([" Text "], results)
+ end
+
def test_sax2
File.open(fixture_path("documentation.xml")) do |f|
parser = Parsers::SAX2Parser.new( f )
@@ -88,6 +99,142 @@ def test_sax2
end
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
+
+
+
+
+
+
+]>
+
+&a;
+
+ XML
+
+ sax = REXML::Parsers::SAX2Parser.new(source)
+ assert_raise(RuntimeError.new("entity expansion has grown too large")) do
+ sax.parse
+ end
+ end
+
+ def test_empty_value
+ source = <<-XML
+
+
+
+
+
+
+]>
+
+&a;
+
+ XML
+
+ REXML::Security.entity_expansion_limit = 100000
+ sax = REXML::Parsers::SAX2Parser.new(source)
+ 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
+ end
+ end
+
+ def test_with_default_entity
+ source = <<-XML
+
+
+
+]>
+
+&a;
+&a2;
+<
+
+ XML
+
+ REXML::Security.entity_expansion_limit = 4
+ sax = REXML::Parsers::SAX2Parser.new(source)
+ sax.parse
+
+ REXML::Security.entity_expansion_limit = 3
+ sax = REXML::Parsers::SAX2Parser.new(source)
+ assert_raise(RuntimeError.new("number of entity expansions exceeded, processing aborted.")) do
+ sax.parse
+ end
+ end
+
+ def test_with_only_default_entities
+ member_value = "<p>#{'A' * @default_entity_expansion_text_limit}</p>"
+ source = <<-XML
+
+
+#{member_value}
+
+ XML
+
+ sax = REXML::Parsers::SAX2Parser.new(source)
+ text_value = nil
+ sax.listen(:characters, ["member"]) do |text|
+ text_value = text
+ end
+ sax.parse
+
+ expected_value = "
#{'A' * @default_entity_expansion_text_limit}
"
+ assert_equal(expected_value, text_value.strip)
+ assert_equal(0, sax.entity_expansion_count)
+ assert do
+ text_value.bytesize > @default_entity_expansion_text_limit
+ end
+ end
+
+ def test_entity_expansion_text_limit
+ source = <<-XML
+
+
+
+
+
+]>
+&a;
+ XML
+
+ REXML::Security.entity_expansion_text_limit = 90
+ sax = REXML::Parsers::SAX2Parser.new(source)
+ text_size = nil
+ sax.listen(:characters, ["member"]) do |text|
+ text_size = text.size
+ end
+ sax.parse
+ assert_equal(90, text_size)
+ end
+ end
+ end
+
# used by test_simple_doctype_listener
# submitted by Jeff Barczewski
class SimpleDoctypeListener
@@ -140,7 +287,7 @@ def test_simple_doctype_listener
# test doctype with missing name, should throw ParseException
# submitted by Jeff Barczewseki
- def test_doctype_with_mising_name_throws_exception
+ def test_doctype_with_missing_name_throws_exception
xml = <<~END
diff --git a/test/test_stream.rb b/test/test_stream.rb
index 545d5349..782066c2 100644
--- a/test/test_stream.rb
+++ b/test/test_stream.rb
@@ -87,8 +87,184 @@ def entity(content)
assert_equal(["ISOLat2"], listener.entities)
end
+
+ def test_entity_replacement
+ source = <<-XML
+
+
+
+]>&la;&lala;
+ XML
+
+ listener = MyListener.new
+ class << listener
+ attr_accessor :text_values
+ def text(text)
+ @text_values << text
+ end
+ end
+ listener.text_values = []
+ REXML::Document.parse_stream(source, listener)
+ assert_equal(["1234", "--1234--"], listener.text_values)
+ end
+
+ def test_characters_predefined_entities
+ source = '<P> <I> <B> Text </B> </I>'
+
+ listener = MyListener.new
+ class << listener
+ attr_accessor :text_value
+ def text(text)
+ @text_value << text
+ end
+ end
+ listener.text_value = ""
+ REXML::Document.parse_stream(source, listener)
+ assert_equal(" Text ", listener.text_value)
+ end
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
+
+ def test_have_value
+ source = <<-XML
+
+
+
+
+
+
+]>
+
+&a;
+
+ XML
+
+ assert_raise(RuntimeError.new("entity expansion has grown too large")) do
+ REXML::Document.parse_stream(source, MyListener.new)
+ end
+ end
+
+ def test_empty_value
+ source = <<-XML
+
+
+
+
+
+
+]>
+
+&a;
+
+ XML
+
+ listener = MyListener.new
+ REXML::Security.entity_expansion_limit = 100000
+ parser = REXML::Parsers::StreamParser.new( source, listener )
+ parser.parse
+ assert_equal(11111, parser.entity_expansion_count)
+
+ REXML::Security.entity_expansion_limit = @default_entity_expansion_limit
+ parser = REXML::Parsers::StreamParser.new( source, listener )
+ assert_raise(RuntimeError.new("number of entity expansions exceeded, processing aborted.")) do
+ parser.parse
+ end
+ assert do
+ parser.entity_expansion_count > @default_entity_expansion_limit
+ end
+ end
+
+ def test_with_default_entity
+ source = <<-XML
+
+
+
+]>
+
+&a;
+&a2;
+<
+
+ XML
+
+ listener = MyListener.new
+ REXML::Security.entity_expansion_limit = 4
+ REXML::Document.parse_stream(source, listener)
+
+ REXML::Security.entity_expansion_limit = 3
+ assert_raise(RuntimeError.new("number of entity expansions exceeded, processing aborted.")) do
+ REXML::Document.parse_stream(source, listener)
+ end
+ end
+
+ def test_with_only_default_entities
+ member_value = "<p>#{'A' * @default_entity_expansion_text_limit}</p>"
+ source = <<-XML
+
+
+#{member_value}
+
+ XML
+
+ listener = MyListener.new
+ class << listener
+ attr_accessor :text_value
+ def text(text)
+ @text_value << text
+ end
+ end
+ listener.text_value = ""
+ parser = REXML::Parsers::StreamParser.new( source, listener )
+ parser.parse
+
+ expected_value = "
#{'A' * @default_entity_expansion_text_limit}
"
+ assert_equal(expected_value, listener.text_value.strip)
+ assert_equal(0, parser.entity_expansion_count)
+ assert do
+ listener.text_value.bytesize > @default_entity_expansion_text_limit
+ end
+ end
+
+ def test_entity_expansion_text_limit
+ source = <<-XML
+
+
+
+
+
+]>
+&a;
+ XML
+
+ listener = MyListener.new
+ class << listener
+ attr_accessor :text_value
+ def text(text)
+ @text_value << text
+ end
+ end
+ listener.text_value = ""
+ REXML::Security.entity_expansion_text_limit = 90
+ REXML::Document.parse_stream(source, listener)
+
+ assert_equal(90, listener.text_value.size)
+ end
+ end
# For test_listener
class RequestReader
diff --git a/test/test_text_check.rb b/test/test_text_check.rb
new file mode 100644
index 00000000..11cf65a3
--- /dev/null
+++ b/test/test_text_check.rb
@@ -0,0 +1,121 @@
+# frozen_string_literal: false
+
+module REXMLTests
+ class TextCheckTester < Test::Unit::TestCase
+
+ def check(string)
+ REXML::Text.check(string, REXML::Text::NEEDS_A_SECOND_CHECK, nil)
+ end
+
+ def assert_check(string)
+ assert_nothing_raised { check(string) }
+ end
+
+ def assert_check_failed(string, illegal_part)
+ message = "Illegal character #{illegal_part.inspect} in raw string #{string.inspect}"
+ assert_raise(RuntimeError.new(message)) do
+ check(string)
+ end
+ end
+
+ class TestValid < self
+ def test_entity_name_start_char_colon
+ assert_check("&:;")
+ end
+
+ def test_entity_name_start_char_under_score
+ assert_check("&_;")
+ end
+
+ def test_entity_name_mix
+ assert_check("&A.b-0123;")
+ end
+
+ def test_character_reference_decimal
+ assert_check("¢")
+ end
+
+ def test_character_reference_hex
+ assert_check("")
+ end
+
+ def test_entity_name_non_ascii
+ # U+3042 HIRAGANA LETTER A
+ # U+3044 HIRAGANA LETTER I
+ assert_check("&\u3042\u3044;")
+ end
+
+ def test_normal_string
+ assert_check("foo")
+ end
+ end
+
+ class TestInvalid < self
+ def test_lt
+ assert_check_failed("<;", "<")
+ end
+
+ def test_lt_mix
+ assert_check_failed("ab 1]").size
assert_equal 3, REXML::XPath.match(doc, "//b[number(@id) >= 1]").size
assert_equal 1, REXML::XPath.match(doc, "//b[number(@id) <= 1]").size