From f9d88e4948b4a43294c25dc0edb16815bd9d8618 Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Tue, 23 Feb 2021 16:11:04 +0900 Subject: [PATCH] Fix a bug that invalid document declaration may be generated HackerOne: HO-1104077 It's caused by quote character. Reported by Juho Nurminen. Thanks!!! --- lib/rexml/doctype.rb | 85 ++++++++++++++++++++--------------- test/test_doctype.rb | 105 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+), 35 deletions(-) diff --git a/lib/rexml/doctype.rb b/lib/rexml/doctype.rb index 3e86cccc..f3590484 100644 --- a/lib/rexml/doctype.rb +++ b/lib/rexml/doctype.rb @@ -7,6 +7,44 @@ require_relative 'xmltokens' module REXML + class ReferenceWriter + def initialize(id_type, + public_id_literal, + system_literal, + context=nil) + @id_type = id_type + @public_id_literal = public_id_literal + @system_literal = system_literal + if context and context[:prologue_quote] == :apostrophe + @default_quote = "'" + else + @default_quote = "\"" + end + end + + def write(output) + output << " #{@id_type}" + if @public_id_literal + if @public_id_literal.include?("'") + quote = "\"" + else + quote = @default_quote + end + output << " #{quote}#{@public_id_literal}#{quote}" + end + if @system_literal + if @system_literal.include?("'") + quote = "\"" + elsif @system_literal.include?("\"") + quote = "'" + else + quote = @default_quote + end + output << " #{quote}#{@system_literal}#{quote}" + end + end + end + # Represents an XML DOCTYPE declaration; that is, the contents of . DOCTYPES can be used to declare the DTD of a document, as well as # being used to declare entities used in the document. @@ -110,19 +148,17 @@ def clone # Ignored def write( output, indent=0, transitive=false, ie_hack=false ) f = REXML::Formatters::Default.new - c = context - if c and c[:prologue_quote] == :apostrophe - quote = "'" - else - quote = "\"" - end indent( output, indent ) output << START output << ' ' output << @name - output << " #{@external_id}" if @external_id - output << " #{quote}#{@long_name}#{quote}" if @long_name - output << " #{quote}#{@uri}#{quote}" if @uri + if @external_id + reference_writer = ReferenceWriter.new(@external_id, + @long_name, + @uri, + context) + reference_writer.write(output) + end unless @children.empty? output << ' [' @children.each { |child| @@ -252,32 +288,11 @@ def initialize name, middle, pub, sys end def to_s - c = nil - c = parent.context if parent - if c and c[:prologue_quote] == :apostrophe - default_quote = "'" - else - default_quote = "\"" - end - notation = "" notation end diff --git a/test/test_doctype.rb b/test/test_doctype.rb index 14284c94..b20d30ae 100644 --- a/test/test_doctype.rb +++ b/test/test_doctype.rb @@ -77,6 +77,111 @@ def test_notations end end + class TestDocType < Test::Unit::TestCase + class TestExternalID < self + class TestSystem < self + class TestSystemLiteral < self + def test_to_s + doctype = REXML::DocType.new(["root", "SYSTEM", nil, "root.dtd"]) + assert_equal("", + doctype.to_s) + end + + def test_to_s_apostrophe + doctype = REXML::DocType.new(["root", "SYSTEM", nil, "root.dtd"]) + doc = REXML::Document.new + doc << doctype + doctype.parent.context[:prologue_quote] = :apostrophe + assert_equal("", + doctype.to_s) + end + + def test_to_s_single_quote_apostrophe + doctype = REXML::DocType.new(["root", "SYSTEM", nil, "root'.dtd"]) + doc = REXML::Document.new + doc << doctype + # This isn't used. + doctype.parent.context[:prologue_quote] = :apostrophe + assert_equal("", + doctype.to_s) + end + + def test_to_s_double_quote + doctype = REXML::DocType.new(["root", "SYSTEM", nil, "root\".dtd"]) + doc = REXML::Document.new + doc << doctype + # This isn't used. + doctype.parent.context[:prologue_quote] = :apostrophe + assert_equal("", + doctype.to_s) + end + end + end + + class TestPublic < self + class TestPublicIDLiteral < self + def test_to_s + doctype = REXML::DocType.new(["root", "PUBLIC", "pub", "root.dtd"]) + assert_equal("", + doctype.to_s) + end + + def test_to_s_apostrophe + doctype = REXML::DocType.new(["root", "PUBLIC", "pub", "root.dtd"]) + doc = REXML::Document.new + doc << doctype + doctype.parent.context[:prologue_quote] = :apostrophe + assert_equal("", + doctype.to_s) + end + + def test_to_s_apostrophe_include_apostrophe + doctype = REXML::DocType.new(["root", "PUBLIC", "pub'", "root.dtd"]) + doc = REXML::Document.new + doc << doctype + # This isn't used. + doctype.parent.context[:prologue_quote] = :apostrophe + assert_equal("", + doctype.to_s) + end + end + + class TestSystemLiteral < self + def test_to_s + doctype = REXML::DocType.new(["root", "PUBLIC", "pub", "root.dtd"]) + assert_equal("", + doctype.to_s) + end + + def test_to_s_apostrophe + doctype = REXML::DocType.new(["root", "PUBLIC", "pub", "root.dtd"]) + doc = REXML::Document.new + doc << doctype + doctype.parent.context[:prologue_quote] = :apostrophe + assert_equal("", + doctype.to_s) + end + + def test_to_s_apostrophe_include_apostrophe + doctype = REXML::DocType.new(["root", "PUBLIC", "pub", "root'.dtd"]) + doc = REXML::Document.new + doc << doctype + # This isn't used. + doctype.parent.context[:prologue_quote] = :apostrophe + assert_equal("", + doctype.to_s) + end + + def test_to_s_double_quote + doctype = REXML::DocType.new(["root", "PUBLIC", "pub", "root\".dtd"]) + assert_equal("", + doctype.to_s) + end + end + end + end + end + class TestNotationDeclPublic < Test::Unit::TestCase def setup @name = "vrml"