diff --git a/CHANGELOG.md b/CHANGELOG.md index c613e9554ea..c0c4cbed34e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Nokogiri Changelog +## 1.10.4 / 2019-08-07 + +### Security + +#### Address CVE-2019-5477 (#1915) + +A command injection vulnerability in Nokogiri v1.10.3 and earlier allows commands to be executed in a subprocess by Ruby's `Kernel.open` method. Processes are vulnerable only if the undocumented method `Nokogiri::CSS::Tokenizer#load_file` is being passed untrusted user input. + +This vulnerability appears in code generated by the Rexical gem versions v1.0.6 and earlier. Rexical is used by Nokogiri to generate lexical scanner code for parsing CSS queries. The underlying vulnerability was addressed in Rexical v1.0.7 and Nokogiri upgraded to this version of Rexical in Nokogiri v1.10.4. + +This CVE's public notice is https://github.com/sparklemotion/nokogiri/issues/1915 + + ## 1.10.3 / 2019-04-22 ### Security Notes diff --git a/Gemfile b/Gemfile index 0fbae5913b5..a036c766b46 100644 --- a/Gemfile +++ b/Gemfile @@ -17,6 +17,7 @@ gem "rake", "~>12.0", :group => [:development, :test] gem "rake-compiler", "~>1.0.3", :group => [:development, :test] gem "rake-compiler-dock", "~>0.7.0", :group => [:development, :test] gem "rexical", "~>1.0.5", :group => [:development, :test] +gem "rubocop", "~>0.73", :group => [:development, :test] gem "simplecov", "~>0.16", :group => [:development, :test] gem "rdoc", ">=4.0", "<7", :group => [:development, :test] gem "hoe", "~>3.17", :group => [:development, :test] diff --git a/Rakefile b/Rakefile index 9bdbccb554e..7d274b2dedc 100644 --- a/Rakefile +++ b/Rakefile @@ -143,6 +143,7 @@ HOE = Hoe.spec 'nokogiri' do ["rake-compiler", "~> 1.0.3"], ["rake-compiler-dock", "~> 0.7.0"], ["rexical", "~> 1.0.5"], + ["rubocop", "~> 0.73"], ["simplecov", "~> 0.16"], ] @@ -275,6 +276,11 @@ task :java_debug do end Rake::Task[:test].prerequisites << :java_debug +task :rubocop_security do + sh "rubocop lib --only Security" +end +Rake::Task[:test].prerequisites << :rubocop_security + if Hoe.plugins.include?(:debugging) ['valgrind', 'valgrind:mem', 'valgrind:mem0'].each do |task_name| Rake::Task["test:#{task_name}"].prerequisites << :compile diff --git a/lib/nokogiri/css/tokenizer.rb b/lib/nokogiri/css/tokenizer.rb index fcb7a299420..b5884c40e21 100644 --- a/lib/nokogiri/css/tokenizer.rb +++ b/lib/nokogiri/css/tokenizer.rb @@ -1,151 +1,152 @@ #-- # DO NOT MODIFY!!!! -# This file is automatically generated by rex 1.0.5 +# This file is automatically generated by rex 1.0.7 # from lexical definition file "lib/nokogiri/css/tokenizer.rex". #++ module Nokogiri module CSS class Tokenizer # :nodoc: - require 'strscan' + require 'strscan' - class ScanError < StandardError ; end + class ScanError < StandardError ; end - attr_reader :lineno - attr_reader :filename - attr_accessor :state + attr_reader :lineno + attr_reader :filename + attr_accessor :state - def scan_setup(str) - @ss = StringScanner.new(str) - @lineno = 1 - @state = nil - end + def scan_setup(str) + @ss = StringScanner.new(str) + @lineno = 1 + @state = nil + end - def action - yield - end + def action + yield + end - def scan_str(str) - scan_setup(str) - do_parse - end - alias :scan :scan_str + def scan_str(str) + scan_setup(str) + do_parse + end + alias :scan :scan_str - def load_file( filename ) - @filename = filename - open(filename, "r") do |f| - scan_setup(f.read) - end - end + def load_file( filename ) + @filename = filename + File.open(filename, "r") do |f| + scan_setup(f.read) + end + end - def scan_file( filename ) - load_file(filename) - do_parse - end + def scan_file( filename ) + load_file(filename) + do_parse + end - def next_token - return if @ss.eos? - - # skips empty actions - until token = _next_token or @ss.eos?; end - token - end + def next_token + return if @ss.eos? - def _next_token - text = @ss.peek(1) - @lineno += 1 if text == "\n" - token = case @state - when nil - case - when (text = @ss.scan(/has\([\s]*/)) - action { [:HAS, text] } + # skips empty actions + until token = _next_token or @ss.eos?; end + token + end - when (text = @ss.scan(/[-@]?([_A-Za-z]|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])([_A-Za-z0-9-]|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*\([\s]*/)) - action { [:FUNCTION, text] } + def _next_token + text = @ss.peek(1) + @lineno += 1 if text == "\n" + token = case @state + when nil + case + when (text = @ss.scan(/has\([\s]*/)) + action { [:HAS, text] } - when (text = @ss.scan(/[-@]?([_A-Za-z]|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])([_A-Za-z0-9-]|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*/)) - action { [:IDENT, text] } + when (text = @ss.scan(/[-@]?([_A-Za-z]|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])([_A-Za-z0-9-]|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*\([\s]*/)) + action { [:FUNCTION, text] } - when (text = @ss.scan(/\#([_A-Za-z0-9-]|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])+/)) - action { [:HASH, text] } + when (text = @ss.scan(/[-@]?([_A-Za-z]|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])([_A-Za-z0-9-]|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*/)) + action { [:IDENT, text] } - when (text = @ss.scan(/[\s]*~=[\s]*/)) - action { [:INCLUDES, text] } + when (text = @ss.scan(/\#([_A-Za-z0-9-]|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])+/)) + action { [:HASH, text] } - when (text = @ss.scan(/[\s]*\|=[\s]*/)) - action { [:DASHMATCH, text] } + when (text = @ss.scan(/[\s]*~=[\s]*/)) + action { [:INCLUDES, text] } - when (text = @ss.scan(/[\s]*\^=[\s]*/)) - action { [:PREFIXMATCH, text] } + when (text = @ss.scan(/[\s]*\|=[\s]*/)) + action { [:DASHMATCH, text] } - when (text = @ss.scan(/[\s]*\$=[\s]*/)) - action { [:SUFFIXMATCH, text] } + when (text = @ss.scan(/[\s]*\^=[\s]*/)) + action { [:PREFIXMATCH, text] } - when (text = @ss.scan(/[\s]*\*=[\s]*/)) - action { [:SUBSTRINGMATCH, text] } + when (text = @ss.scan(/[\s]*\$=[\s]*/)) + action { [:SUFFIXMATCH, text] } - when (text = @ss.scan(/[\s]*!=[\s]*/)) - action { [:NOT_EQUAL, text] } + when (text = @ss.scan(/[\s]*\*=[\s]*/)) + action { [:SUBSTRINGMATCH, text] } - when (text = @ss.scan(/[\s]*=[\s]*/)) - action { [:EQUAL, text] } + when (text = @ss.scan(/[\s]*!=[\s]*/)) + action { [:NOT_EQUAL, text] } - when (text = @ss.scan(/[\s]*\)/)) - action { [:RPAREN, text] } + when (text = @ss.scan(/[\s]*=[\s]*/)) + action { [:EQUAL, text] } - when (text = @ss.scan(/\[[\s]*/)) - action { [:LSQUARE, text] } + when (text = @ss.scan(/[\s]*\)/)) + action { [:RPAREN, text] } - when (text = @ss.scan(/[\s]*\]/)) - action { [:RSQUARE, text] } + when (text = @ss.scan(/\[[\s]*/)) + action { [:LSQUARE, text] } - when (text = @ss.scan(/[\s]*\+[\s]*/)) - action { [:PLUS, text] } + when (text = @ss.scan(/[\s]*\]/)) + action { [:RSQUARE, text] } - when (text = @ss.scan(/[\s]*>[\s]*/)) - action { [:GREATER, text] } + when (text = @ss.scan(/[\s]*\+[\s]*/)) + action { [:PLUS, text] } - when (text = @ss.scan(/[\s]*,[\s]*/)) - action { [:COMMA, text] } + when (text = @ss.scan(/[\s]*>[\s]*/)) + action { [:GREATER, text] } - when (text = @ss.scan(/[\s]*~[\s]*/)) - action { [:TILDE, text] } + when (text = @ss.scan(/[\s]*,[\s]*/)) + action { [:COMMA, text] } - when (text = @ss.scan(/\:not\([\s]*/)) - action { [:NOT, text] } + when (text = @ss.scan(/[\s]*~[\s]*/)) + action { [:TILDE, text] } - when (text = @ss.scan(/-?([0-9]+|[0-9]*\.[0-9]+)/)) - action { [:NUMBER, text] } + when (text = @ss.scan(/\:not\([\s]*/)) + action { [:NOT, text] } - when (text = @ss.scan(/[\s]*\/\/[\s]*/)) - action { [:DOUBLESLASH, text] } + when (text = @ss.scan(/-?([0-9]+|[0-9]*\.[0-9]+)/)) + action { [:NUMBER, text] } - when (text = @ss.scan(/[\s]*\/[\s]*/)) - action { [:SLASH, text] } + when (text = @ss.scan(/[\s]*\/\/[\s]*/)) + action { [:DOUBLESLASH, text] } - when (text = @ss.scan(/U\+[0-9a-f?]{1,6}(-[0-9a-f]{1,6})?/)) - action {[:UNICODE_RANGE, text] } + when (text = @ss.scan(/[\s]*\/[\s]*/)) + action { [:SLASH, text] } - when (text = @ss.scan(/[\s]+/)) - action { [:S, text] } + when (text = @ss.scan(/U\+[0-9a-f?]{1,6}(-[0-9a-f]{1,6})?/)) + action {[:UNICODE_RANGE, text] } - when (text = @ss.scan(/"([^\n\r\f"]|\n|\r\n|\r|\f|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*(? 'UTF-8') do |xml| # ... # end - def initialize options = {}, root = nil, &block - + def initialize(options = {}, root = nil, &block) if root - @doc = root.document + @doc = root.document @parent = root else - namespace = self.class.name.split('::') - namespace[-1] = 'Document' - @doc = eval(namespace.join('::')).new - @parent = @doc + klassname = "::" + (self.class.name.split("::")[0..-2] + ["Document"]).join("::") + klass = begin + Object.const_get(klassname) + rescue NameError + Nokogiri::XML::Document + end + @parent = @doc = klass.new end - @context = nil - @arity = nil - @ns = nil + @context = nil + @arity = nil + @ns = nil - options.each do |k,v| + options.each do |k, v| @doc.send(:"#{k}=", v) end @@ -287,7 +289,7 @@ def initialize options = {}, root = nil, &block @arity = block.arity if @arity <= 0 - @context = eval('self', block.binding) + @context = eval("self", block.binding) instance_eval(&block) else yield self @@ -298,26 +300,26 @@ def initialize options = {}, root = nil, &block ### # Create a Text Node with content of +string+ - def text string + def text(string) insert @doc.create_text_node(string) end ### # Create a CDATA Node with content of +string+ - def cdata string + def cdata(string) insert doc.create_cdata(string) end ### # Create a Comment Node with content of +string+ - def comment string + def comment(string) insert doc.create_comment(string) end ### # Build a tag that is associated with namespace +ns+. Raises an # ArgumentError if +ns+ has not been defined higher in the tree. - def [] ns + def [](ns) if @parent != @doc @ns = @parent.namespace_definitions.find { |x| x.prefix == ns.to_s } end @@ -348,15 +350,15 @@ def to_xml(*args) ### # Append the given raw XML +string+ to the document - def << string + def <<(string) @doc.fragment(string).children.each { |x| insert(x) } end - def method_missing method, *args, &block # :nodoc: + def method_missing(method, *args, &block) # :nodoc: if @context && @context.respond_to?(method) @context.send(method, *args, &block) else - node = @doc.create_element(method.to_s.sub(/[_!]$/, ''),*args) { |n| + node = @doc.create_element(method.to_s.sub(/[_!]$/, ""), *args) { |n| # Set up the namespace if @ns.is_a? Nokogiri::XML::Namespace n.namespace = @ns @@ -377,13 +379,14 @@ def method_missing method, *args, &block # :nodoc: end private + ### # Insert +node+ as a child of the current Node def insert(node, &block) node = @parent.add_child(node) if block_given? old_parent = @parent - @parent = node + @parent = node @arity ||= block.arity if @arity <= 0 instance_eval(&block) @@ -396,16 +399,16 @@ def insert(node, &block) end class NodeBuilder # :nodoc: - def initialize node, doc_builder + def initialize(node, doc_builder) @node = node @doc_builder = doc_builder end - def []= k, v + def []=(k, v) @node[k] = v end - def [] k + def [](k) @node[k] end @@ -413,19 +416,19 @@ def method_missing(method, *args, &block) opts = args.last.is_a?(Hash) ? args.pop : {} case method.to_s when /^(.*)!$/ - @node['id'] = $1 + @node["id"] = $1 @node.content = args.first if args.first when /^(.*)=/ @node[$1] = args.first else - @node['class'] = - ((@node['class'] || '').split(/\s/) + [method.to_s]).join(' ') + @node["class"] = + ((@node["class"] || "").split(/\s/) + [method.to_s]).join(" ") @node.content = args.first if args.first end # Assign any extra options - opts.each do |k,v| - @node[k.to_s] = ((@node[k.to_s] || '').split(/\s/) + [v]).join(' ') + opts.each do |k, v| + @node[k.to_s] = ((@node[k.to_s] || "").split(/\s/) + [v]).join(" ") end if block_given? diff --git a/test/xml/test_builder.rb b/test/xml/test_builder.rb index f755d4c7b88..aaa18ecb9dd 100644 --- a/test/xml/test_builder.rb +++ b/test/xml/test_builder.rb @@ -10,7 +10,7 @@ def test_attribute_sensitivity x.tag "hello", "abcDef" => "world" }.to_xml doc = Nokogiri.XML xml - assert_equal 'world', doc.root['abcDef'] + assert_equal "world", doc.root["abcDef"] end def test_builder_multiple_nodes @@ -21,7 +21,6 @@ def test_builder_multiple_nodes end end - def test_builder_with_utf8_text text = "test οΊ΅ " doc = Nokogiri::XML::Builder.new(:encoding => "UTF-8") { |xml| xml.test text }.doc @@ -33,8 +32,8 @@ def test_builder_escape x.condition "value < 1", :attr => "value < 1" }.to_xml doc = Nokogiri.XML xml - assert_equal 'value < 1', doc.root['attr'] - assert_equal 'value < 1', doc.root.content + assert_equal "value < 1", doc.root["attr"] + assert_equal "value < 1", doc.root.content end def test_builder_namespace @@ -44,10 +43,10 @@ def test_builder_namespace end }.doc - b = doc.at('b') + b = doc.at("b") assert b - assert_equal({"xmlns:a"=>"x", "xmlns:b"=>"y"}, b.namespaces) - assert_equal({"xmlns:b"=>"y"}, namespaces_defined_on(b)) + assert_equal({ "xmlns:a" => "x", "xmlns:b" => "y" }, b.namespaces) + assert_equal({ "xmlns:b" => "y" }, namespaces_defined_on(b)) end def test_builder_namespace_part_deux @@ -57,10 +56,10 @@ def test_builder_namespace_part_deux end }.doc - b = doc.at('b') + b = doc.at("b") assert b - assert_equal({"xmlns:a"=>"x", "xmlns:b"=>"y", "xmlns:c"=>"z"}, b.namespaces) - assert_equal({"xmlns:a"=>"x", "xmlns:c"=>"z"}, namespaces_defined_on(b)) + assert_equal({ "xmlns:a" => "x", "xmlns:b" => "y", "xmlns:c" => "z" }, b.namespaces) + assert_equal({ "xmlns:a" => "x", "xmlns:c" => "z" }, namespaces_defined_on(b)) end def test_builder_with_unlink @@ -75,58 +74,58 @@ def test_builder_with_unlink def test_with_root doc = Nokogiri::XML(File.read(XML_FILE)) - Nokogiri::XML::Builder.with(doc.at('employee')) do |xml| + Nokogiri::XML::Builder.with(doc.at("employee")) do |xml| xml.foo end - assert_equal 1, doc.xpath('//employee/foo').length + assert_equal 1, doc.xpath("//employee/foo").length end def test_root_namespace_default_decl - b = Nokogiri::XML::Builder.new { |xml| xml.root(:xmlns => 'one:two') } + b = Nokogiri::XML::Builder.new { |xml| xml.root(:xmlns => "one:two") } doc = b.doc - assert_equal 'one:two', doc.root.namespace.href - assert_equal({ 'xmlns' => 'one:two' }, doc.root.namespaces) + assert_equal "one:two", doc.root.namespace.href + assert_equal({ "xmlns" => "one:two" }, doc.root.namespaces) end def test_root_namespace_multi_decl b = Nokogiri::XML::Builder.new { |xml| - xml.root(:xmlns => 'one:two', 'xmlns:foo' => 'bar') do + xml.root(:xmlns => "one:two", "xmlns:foo" => "bar") do xml.hello end } doc = b.doc - assert_equal 'one:two', doc.root.namespace.href - assert_equal({ 'xmlns' => 'one:two', 'xmlns:foo' => 'bar' }, doc.root.namespaces) + assert_equal "one:two", doc.root.namespace.href + assert_equal({ "xmlns" => "one:two", "xmlns:foo" => "bar" }, doc.root.namespaces) - assert_equal 'one:two', doc.at('hello').namespace.href + assert_equal "one:two", doc.at("hello").namespace.href end def test_non_root_namespace b = Nokogiri::XML::Builder.new { |xml| - xml.root { xml.hello(:xmlns => 'one') } + xml.root { xml.hello(:xmlns => "one") } } - assert_equal 'one', b.doc.at('hello', 'xmlns' => 'one').namespace.href + assert_equal "one", b.doc.at("hello", "xmlns" => "one").namespace.href end def test_specify_namespace b = Nokogiri::XML::Builder.new { |xml| - xml.root('xmlns:foo' => 'bar') do + xml.root("xmlns:foo" => "bar") do xml[:foo].bar - xml['foo'].baz + xml["foo"].baz end } doc = b.doc - assert_equal 'bar', doc.at('foo|bar', 'foo' => 'bar').namespace.href - assert_equal 'bar', doc.at('foo|baz', 'foo' => 'bar').namespace.href + assert_equal "bar", doc.at("foo|bar", "foo" => "bar").namespace.href + assert_equal "bar", doc.at("foo|baz", "foo" => "bar").namespace.href end def test_dtd_in_builder_output builder = Nokogiri::XML::Builder.new do |xml| xml.doc.create_internal_subset( - 'html', - "-//W3C//DTD HTML 4.01 Transitional//EN", - "http://www.w3.org/TR/html4/loose.dtd" - ) + "html", + "-//W3C//DTD HTML 4.01 Transitional//EN", + "http://www.w3.org/TR/html4/loose.dtd" + ) xml.root do xml.foo end @@ -137,19 +136,19 @@ def test_dtd_in_builder_output def test_specify_namespace_nested b = Nokogiri::XML::Builder.new { |xml| - xml.root('xmlns:foo' => 'bar') do + xml.root("xmlns:foo" => "bar") do xml.yay do xml[:foo].bar xml.yikes do - xml['foo'].baz + xml["foo"].baz end end end } doc = b.doc - assert_equal 'bar', doc.at('foo|bar', 'foo' => 'bar').namespace.href - assert_equal 'bar', doc.at('foo|baz', 'foo' => 'bar').namespace.href + assert_equal "bar", doc.at("foo|bar", "foo" => "bar").namespace.href + assert_equal "bar", doc.at("foo|baz", "foo" => "bar").namespace.href end def test_specified_namespace_postdeclared @@ -158,12 +157,12 @@ def test_specified_namespace_postdeclared xml[:foo].b("xmlns:foo" => "bar") end }.doc - a = doc.at('a') + a = doc.at("a") assert_equal({}, a.namespaces) - b = doc.at_xpath('//foo:b', {:foo=>'bar'}) + b = doc.at_xpath("//foo:b", { :foo => "bar" }) assert b - assert_equal({"xmlns:foo"=>"bar"}, b.namespaces) + assert_equal({ "xmlns:foo" => "bar" }, b.namespaces) assert_equal("b", b.name) assert_equal("bar", b.namespace.href) end @@ -179,38 +178,38 @@ def test_specified_namespace_undeclared end def test_set_encoding - builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml| + builder = Nokogiri::XML::Builder.new(:encoding => "UTF-8") do |xml| xml.root do - xml.bar 'blah' + xml.bar "blah" end end - assert_match 'UTF-8', builder.to_xml + assert_match "UTF-8", builder.to_xml end def test_bang_and_underscore_is_escaped builder = Nokogiri::XML::Builder.new do |xml| xml.root do - xml.p_('adsfadsf') - xml.p!('adsfadsf') + xml.p_("adsfadsf") + xml.p!("adsfadsf") end end - assert_equal 2, builder.doc.xpath('//p').length + assert_equal 2, builder.doc.xpath("//p").length end def test_square_brackets_set_attributes builder = Nokogiri::XML::Builder.new do |xml| xml.root do foo = xml.foo - foo['id'] = 'hello' - assert_equal 'hello', foo['id'] + foo["id"] = "hello" + assert_equal "hello", foo["id"] end end assert_equal 1, builder.doc.xpath('//foo[@id = "hello"]').length end def test_nested_local_variable - @ivar = 'hello' - local_var = 'hello world' + @ivar = "hello" + local_var = "hello world" builder = Nokogiri::XML::Builder.new do |xml| xml.root do xml.foo local_var @@ -221,40 +220,40 @@ def test_nested_local_variable end end - assert_equal 'hello world', builder.doc.at('//root/foo').content - assert_equal 'hello', builder.doc.at('//root/bar').content - assert_equal 'hello', builder.doc.at('baz').content + assert_equal "hello world", builder.doc.at("//root/foo").content + assert_equal "hello", builder.doc.at("//root/bar").content + assert_equal "hello", builder.doc.at("baz").content end def test_raw_append builder = Nokogiri::XML::Builder.new do |xml| xml.root do - xml << 'hello' + xml << "hello" end end - assert_equal 'hello', builder.doc.at('/root').content + assert_equal "hello", builder.doc.at("/root").content end def test_raw_append_with_instance_eval builder = Nokogiri::XML::Builder.new do root do - self << 'hello' + self << "hello" end end - assert_equal 'hello', builder.doc.at('/root').content + assert_equal "hello", builder.doc.at("/root").content end def test_raw_xml_append builder = Nokogiri::XML::Builder.new do |xml| xml.root do - xml << '' + xml << "" end end - assert_equal ["aaa"], builder.doc.at_css("root").children.collect(&:name) - assert_equal ["bbb","ccc"], builder.doc.at_css("aaa").children.collect(&:name) + assert_equal ["aaa"], builder.doc.at_css("root").children.collect(&:name) + assert_equal ["bbb", "ccc"], builder.doc.at_css("aaa").children.collect(&:name) end def test_raw_xml_append_with_namespaces @@ -264,10 +263,10 @@ def test_raw_xml_append_with_namespaces end end.doc - el = doc.at 'Element' + el = doc.at "Element" assert_not_nil el - assert_equal 'y', el.namespace.href + assert_equal "y", el.namespace.href assert_nil el.namespace.prefix attr = el.attributes["bar"] @@ -283,7 +282,7 @@ def test_cdata } end assert_equal("", - builder.to_xml.gsub(/\n/, "")) + builder.to_xml.gsub(/\n/, "")) end def test_comment @@ -302,7 +301,7 @@ def test_builder_no_block cdata string } assert_equal("", - builder.to_xml.gsub(/\n/, '')) + builder.to_xml.gsub(/\n/, "")) end def test_builder_can_inherit_parent_namespace @@ -314,28 +313,28 @@ def test_builder_can_inherit_parent_namespace } } doc = builder.doc - ['product', 'products'].each do |n| - assert_equal doc.at_xpath("//*[local-name() = '#{n}']").namespace.href, 'foo' + ["product", "products"].each do |n| + assert_equal doc.at_xpath("//*[local-name() = '#{n}']").namespace.href, "foo" end end def test_builder_can_handle_namespace_override builder = Nokogiri::XML::Builder.new - builder.products('xmlns:foo' => 'bar') { - builder.product('xmlns:foo' => 'baz') + builder.products("xmlns:foo" => "bar") { + builder.product("xmlns:foo" => "baz") } doc = builder.doc - assert_equal doc.at_xpath("//*[local-name() = 'product']").namespaces['xmlns:foo'], 'baz' - assert_equal doc.at_xpath("//*[local-name() = 'products']").namespaces['xmlns:foo'], 'bar' + assert_equal doc.at_xpath("//*[local-name() = 'product']").namespaces["xmlns:foo"], "baz" + assert_equal doc.at_xpath("//*[local-name() = 'products']").namespaces["xmlns:foo"], "bar" assert_nil doc.at_xpath("//*[local-name() = 'products']").namespace end def test_builder_reuses_namespaces # see https://github.com/sparklemotion/nokogiri/issues/1810 for memory leak report builder = Nokogiri::XML::Builder.new - builder.send "envelope", {'xmlns' => 'http://schemas.xmlsoap.org/soap/envelope/'} do - builder.send "package", {'xmlns' => 'http://schemas.xmlsoap.org/soap/envelope/'} + builder.send "envelope", { "xmlns" => "http://schemas.xmlsoap.org/soap/envelope/" } do + builder.send "package", { "xmlns" => "http://schemas.xmlsoap.org/soap/envelope/" } end envelope = builder.doc.at_css("envelope") package = builder.doc.at_css("package") @@ -343,11 +342,26 @@ def test_builder_reuses_namespaces assert_equal envelope.namespace.object_id, package.namespace.object_id end + def test_builder_uses_proper_document_class + xml_builder = Nokogiri::XML::Builder.new + assert_instance_of Nokogiri::XML::Document, xml_builder.doc + + html_builder = Nokogiri::HTML::Builder.new + assert_instance_of Nokogiri::HTML::Document, html_builder.doc + + foo_builder = ThisIsATestBuilder.new + assert_instance_of Nokogiri::XML::Document, foo_builder.doc + end + private def namespaces_defined_on(node) - Hash[*node.namespace_definitions.collect{|n| ["xmlns:" + n.prefix, n.href]}.flatten] + Hash[*node.namespace_definitions.collect { |n| ["xmlns:" + n.prefix, n.href] }.flatten] end end end end + +class ThisIsATestBuilder < Nokogiri::XML::Builder + # this exists for the test_builder_uses_proper_document_class and should be empty +end