Skip to content

Commit f9d88e4

Browse files
koumame
authored andcommitted
Fix a bug that invalid document declaration may be generated
HackerOne: HO-1104077 It's caused by quote character. Reported by Juho Nurminen. Thanks!!!
1 parent f7bab89 commit f9d88e4

File tree

2 files changed

+155
-35
lines changed

2 files changed

+155
-35
lines changed

lib/rexml/doctype.rb

Lines changed: 50 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,44 @@
77
require_relative 'xmltokens'
88

99
module REXML
10+
class ReferenceWriter
11+
def initialize(id_type,
12+
public_id_literal,
13+
system_literal,
14+
context=nil)
15+
@id_type = id_type
16+
@public_id_literal = public_id_literal
17+
@system_literal = system_literal
18+
if context and context[:prologue_quote] == :apostrophe
19+
@default_quote = "'"
20+
else
21+
@default_quote = "\""
22+
end
23+
end
24+
25+
def write(output)
26+
output << " #{@id_type}"
27+
if @public_id_literal
28+
if @public_id_literal.include?("'")
29+
quote = "\""
30+
else
31+
quote = @default_quote
32+
end
33+
output << " #{quote}#{@public_id_literal}#{quote}"
34+
end
35+
if @system_literal
36+
if @system_literal.include?("'")
37+
quote = "\""
38+
elsif @system_literal.include?("\"")
39+
quote = "'"
40+
else
41+
quote = @default_quote
42+
end
43+
output << " #{quote}#{@system_literal}#{quote}"
44+
end
45+
end
46+
end
47+
1048
# Represents an XML DOCTYPE declaration; that is, the contents of <!DOCTYPE
1149
# ... >. DOCTYPES can be used to declare the DTD of a document, as well as
1250
# being used to declare entities used in the document.
@@ -110,19 +148,17 @@ def clone
110148
# Ignored
111149
def write( output, indent=0, transitive=false, ie_hack=false )
112150
f = REXML::Formatters::Default.new
113-
c = context
114-
if c and c[:prologue_quote] == :apostrophe
115-
quote = "'"
116-
else
117-
quote = "\""
118-
end
119151
indent( output, indent )
120152
output << START
121153
output << ' '
122154
output << @name
123-
output << " #{@external_id}" if @external_id
124-
output << " #{quote}#{@long_name}#{quote}" if @long_name
125-
output << " #{quote}#{@uri}#{quote}" if @uri
155+
if @external_id
156+
reference_writer = ReferenceWriter.new(@external_id,
157+
@long_name,
158+
@uri,
159+
context)
160+
reference_writer.write(output)
161+
end
126162
unless @children.empty?
127163
output << ' ['
128164
@children.each { |child|
@@ -252,32 +288,11 @@ def initialize name, middle, pub, sys
252288
end
253289

254290
def to_s
255-
c = nil
256-
c = parent.context if parent
257-
if c and c[:prologue_quote] == :apostrophe
258-
default_quote = "'"
259-
else
260-
default_quote = "\""
261-
end
262-
notation = "<!NOTATION #{@name} #{@middle}"
263-
if @public
264-
if @public.include?("'")
265-
quote = "\""
266-
else
267-
quote = default_quote
268-
end
269-
notation << " #{quote}#{@public}#{quote}"
270-
end
271-
if @system
272-
if @system.include?("'")
273-
quote = "\""
274-
elsif @system.include?("\"")
275-
quote = "'"
276-
else
277-
quote = default_quote
278-
end
279-
notation << " #{quote}#{@system}#{quote}"
280-
end
291+
context = nil
292+
context = parent.context if parent
293+
notation = "<!NOTATION #{@name}"
294+
reference_writer = ReferenceWriter.new(@middle, @public, @system, context)
295+
reference_writer.write(notation)
281296
notation << ">"
282297
notation
283298
end

test/test_doctype.rb

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,111 @@ def test_notations
7777
end
7878
end
7979

80+
class TestDocType < Test::Unit::TestCase
81+
class TestExternalID < self
82+
class TestSystem < self
83+
class TestSystemLiteral < self
84+
def test_to_s
85+
doctype = REXML::DocType.new(["root", "SYSTEM", nil, "root.dtd"])
86+
assert_equal("<!DOCTYPE root SYSTEM \"root.dtd\">",
87+
doctype.to_s)
88+
end
89+
90+
def test_to_s_apostrophe
91+
doctype = REXML::DocType.new(["root", "SYSTEM", nil, "root.dtd"])
92+
doc = REXML::Document.new
93+
doc << doctype
94+
doctype.parent.context[:prologue_quote] = :apostrophe
95+
assert_equal("<!DOCTYPE root SYSTEM 'root.dtd'>",
96+
doctype.to_s)
97+
end
98+
99+
def test_to_s_single_quote_apostrophe
100+
doctype = REXML::DocType.new(["root", "SYSTEM", nil, "root'.dtd"])
101+
doc = REXML::Document.new
102+
doc << doctype
103+
# This isn't used.
104+
doctype.parent.context[:prologue_quote] = :apostrophe
105+
assert_equal("<!DOCTYPE root SYSTEM \"root'.dtd\">",
106+
doctype.to_s)
107+
end
108+
109+
def test_to_s_double_quote
110+
doctype = REXML::DocType.new(["root", "SYSTEM", nil, "root\".dtd"])
111+
doc = REXML::Document.new
112+
doc << doctype
113+
# This isn't used.
114+
doctype.parent.context[:prologue_quote] = :apostrophe
115+
assert_equal("<!DOCTYPE root SYSTEM 'root\".dtd'>",
116+
doctype.to_s)
117+
end
118+
end
119+
end
120+
121+
class TestPublic < self
122+
class TestPublicIDLiteral < self
123+
def test_to_s
124+
doctype = REXML::DocType.new(["root", "PUBLIC", "pub", "root.dtd"])
125+
assert_equal("<!DOCTYPE root PUBLIC \"pub\" \"root.dtd\">",
126+
doctype.to_s)
127+
end
128+
129+
def test_to_s_apostrophe
130+
doctype = REXML::DocType.new(["root", "PUBLIC", "pub", "root.dtd"])
131+
doc = REXML::Document.new
132+
doc << doctype
133+
doctype.parent.context[:prologue_quote] = :apostrophe
134+
assert_equal("<!DOCTYPE root PUBLIC 'pub' 'root.dtd'>",
135+
doctype.to_s)
136+
end
137+
138+
def test_to_s_apostrophe_include_apostrophe
139+
doctype = REXML::DocType.new(["root", "PUBLIC", "pub'", "root.dtd"])
140+
doc = REXML::Document.new
141+
doc << doctype
142+
# This isn't used.
143+
doctype.parent.context[:prologue_quote] = :apostrophe
144+
assert_equal("<!DOCTYPE root PUBLIC \"pub'\" 'root.dtd'>",
145+
doctype.to_s)
146+
end
147+
end
148+
149+
class TestSystemLiteral < self
150+
def test_to_s
151+
doctype = REXML::DocType.new(["root", "PUBLIC", "pub", "root.dtd"])
152+
assert_equal("<!DOCTYPE root PUBLIC \"pub\" \"root.dtd\">",
153+
doctype.to_s)
154+
end
155+
156+
def test_to_s_apostrophe
157+
doctype = REXML::DocType.new(["root", "PUBLIC", "pub", "root.dtd"])
158+
doc = REXML::Document.new
159+
doc << doctype
160+
doctype.parent.context[:prologue_quote] = :apostrophe
161+
assert_equal("<!DOCTYPE root PUBLIC 'pub' 'root.dtd'>",
162+
doctype.to_s)
163+
end
164+
165+
def test_to_s_apostrophe_include_apostrophe
166+
doctype = REXML::DocType.new(["root", "PUBLIC", "pub", "root'.dtd"])
167+
doc = REXML::Document.new
168+
doc << doctype
169+
# This isn't used.
170+
doctype.parent.context[:prologue_quote] = :apostrophe
171+
assert_equal("<!DOCTYPE root PUBLIC 'pub' \"root'.dtd\">",
172+
doctype.to_s)
173+
end
174+
175+
def test_to_s_double_quote
176+
doctype = REXML::DocType.new(["root", "PUBLIC", "pub", "root\".dtd"])
177+
assert_equal("<!DOCTYPE root PUBLIC \"pub\" 'root\".dtd'>",
178+
doctype.to_s)
179+
end
180+
end
181+
end
182+
end
183+
end
184+
80185
class TestNotationDeclPublic < Test::Unit::TestCase
81186
def setup
82187
@name = "vrml"

0 commit comments

Comments
 (0)