diff --git a/CHANGELOG.md b/CHANGELOG.md index dcb26061b8..328a5e8cbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,8 @@ end ### Fixed * [JRuby] Fix NPE in Schema parsing when an imported resource doesn't have a `systemId`. [[#2296](https://github.com/sparklemotion/nokogiri/issues/2296)] (Thanks, [@pepijnve](https://github.com/pepijnve)!) +* [JRuby] Fix `Schema#validate` to only return the most recent Document's errors. Previously, if multiple documents were validated, this method returned the accumulated errors of all previous documents. [#1282] +* [JRuby] Fix `Schema#validate` to not clobber the `@errors` instance variable. [#1282] ## 1.12.3 / 2021-08-10 diff --git a/ext/java/nokogiri/XmlSchema.java b/ext/java/nokogiri/XmlSchema.java index ff8e38fe70..7084332bc2 100644 --- a/ext/java/nokogiri/XmlSchema.java +++ b/ext/java/nokogiri/XmlSchema.java @@ -212,7 +212,7 @@ public class XmlSchema extends RubyObject IRubyObject validate_document_or_file(ThreadContext context, XmlDocument xmlDocument) { - RubyArray errors = (RubyArray) this.getInstanceVariable("@errors"); + RubyArray errors = (RubyArray)context.runtime.newEmptyArray(); ErrorHandler errorHandler = new SchemaErrorHandler(context.runtime, errors); setErrorHandler(errorHandler); diff --git a/ext/nokogiri/xml_relax_ng.c b/ext/nokogiri/xml_relax_ng.c index e55f54e4e7..8aef5b73ac 100644 --- a/ext/nokogiri/xml_relax_ng.c +++ b/ext/nokogiri/xml_relax_ng.c @@ -10,14 +10,9 @@ dealloc(xmlRelaxNGPtr schema) NOKOGIRI_DEBUG_END(schema); } -/* - * call-seq: - * validate_document(document) - * - * Validate a Nokogiri::XML::Document against this RelaxNG schema. - */ + static VALUE -validate_document(VALUE self, VALUE document) +rb_xml_relax_ng_validate_document(VALUE self, VALUE document) { xmlDocPtr doc; xmlRelaxNGPtr schema; @@ -51,14 +46,25 @@ validate_document(VALUE self, VALUE document) return errors; } + /* - * call-seq: - * read_memory(string) + * :call-seq: + * read_memory(input) → Nokogiri::XML::RelaxNG + * read_memory(input, parse_options) → Nokogiri::XML::RelaxNG + * + * Parse a RELAX NG schema definition and create a new Schema object. * - * Create a new RelaxNG from the contents of +string+ + * 💡 Note that the limitation of this method relative to RelaxNG.new is that +input+ must be type + * String, whereas RelaxNG.new also supports IO types. + * + * [Parameters] + * - +input+ (String) RELAX NG schema definition + * - +parse_options+ (Nokogiri::XML::ParseOptions) Defaults to ParseOptions::DEFAULT_SCHEMA + * + * [Returns] Nokogiri::XML::RelaxNG */ static VALUE -read_memory(int argc, VALUE *argv, VALUE klass) +rb_xml_relax_ng_s_read_memory(int argc, VALUE *argv, VALUE klass) { VALUE content; VALUE parse_options; @@ -109,14 +115,25 @@ read_memory(int argc, VALUE *argv, VALUE klass) return rb_schema; } + /* - * call-seq: - * from_document(doc) + * :call-seq: + * from_document(document) → Nokogiri::XML::RelaxNG + * from_document(document, parse_options) → Nokogiri::XML::RelaxNG + * + * Create a Schema from an already-parsed RELAX NG schema definition document. + * + * [Parameters] + * - +document+ (XML::Document) A XML::Document object representing the parsed RELAX NG + * - +parse_options+ (Nokogiri::XML::ParseOptions) ⚠ Unused + * + * [Returns] Nokogiri::XML::RelaxNG * - * Create a new RelaxNG schema from the Nokogiri::XML::Document +doc+ + * ⚠ +parse_options+ is currently unused by this method and is present only as a placeholder for + * future functionality. */ static VALUE -from_document(int argc, VALUE *argv, VALUE klass) +rb_xml_relax_ng_s_from_document(int argc, VALUE *argv, VALUE klass) { VALUE document; VALUE parse_options; @@ -178,8 +195,8 @@ noko_init_xml_relax_ng() assert(cNokogiriXmlSchema); cNokogiriXmlRelaxNG = rb_define_class_under(mNokogiriXml, "RelaxNG", cNokogiriXmlSchema); - rb_define_singleton_method(cNokogiriXmlRelaxNG, "read_memory", read_memory, -1); - rb_define_singleton_method(cNokogiriXmlRelaxNG, "from_document", from_document, -1); + rb_define_singleton_method(cNokogiriXmlRelaxNG, "read_memory", rb_xml_relax_ng_s_read_memory, -1); + rb_define_singleton_method(cNokogiriXmlRelaxNG, "from_document", rb_xml_relax_ng_s_from_document, -1); - rb_define_private_method(cNokogiriXmlRelaxNG, "validate_document", validate_document, 1); + rb_define_private_method(cNokogiriXmlRelaxNG, "validate_document", rb_xml_relax_ng_validate_document, 1); } diff --git a/ext/nokogiri/xml_schema.c b/ext/nokogiri/xml_schema.c index a8175e6da3..6f619f4d29 100644 --- a/ext/nokogiri/xml_schema.c +++ b/ext/nokogiri/xml_schema.c @@ -10,14 +10,8 @@ dealloc(xmlSchemaPtr schema) NOKOGIRI_DEBUG_END(schema); } -/* - * call-seq: - * validate_document(document) - * - * Validate a Nokogiri::XML::Document against this Schema. - */ static VALUE -validate_document(VALUE self, VALUE document) +rb_xml_schema_validate_document(VALUE self, VALUE document) { xmlDocPtr doc; xmlSchemaPtr schema; @@ -51,14 +45,8 @@ validate_document(VALUE self, VALUE document) return errors; } -/* - * call-seq: - * validate_file(filename) - * - * Validate a file against this Schema. - */ static VALUE -validate_file(VALUE self, VALUE rb_filename) +rb_xml_schema_validate_file(VALUE self, VALUE rb_filename) { xmlSchemaPtr schema; xmlSchemaValidCtxtPtr valid_ctxt; @@ -93,13 +81,24 @@ validate_file(VALUE self, VALUE rb_filename) } /* - * call-seq: - * read_memory(string) + * :call-seq: + * read_memory(input) → Nokogiri::XML::Schema + * read_memory(input, parse_options) → Nokogiri::XML::Schema + * + * Parse an XSD schema definition and create a new Schema object. + * + * 💡 Note that the limitation of this method relative to Schema.new is that +input+ must be type + * String, whereas Schema.new also supports IO types. + * + * [parameters] + * - +input+ (String) XSD schema definition + * - +parse_options+ (Nokogiri::XML::ParseOptions) + * Defaults to Nokogiri::XML::ParseOptions::DEFAULT_SCHEMA * - * Create a new Schema from the contents of +string+ + * [Returns] Nokogiri::XML::Schema */ static VALUE -read_memory(int argc, VALUE *argv, VALUE klass) +rb_xml_schema_s_read_memory(int argc, VALUE *argv, VALUE klass) { VALUE content; VALUE parse_options; @@ -162,6 +161,7 @@ read_memory(int argc, VALUE *argv, VALUE klass) return rb_schema; } + /* Schema creation will remove and deallocate "blank" nodes. * If those blank nodes have been exposed to Ruby, they could get freed * out from under the VALUE pointer. This function checks to see if any of @@ -188,14 +188,23 @@ has_blank_nodes_p(VALUE cache) return 0; } + /* - * call-seq: - * from_document(doc) + * :call-seq: + * from_document(document) → Nokogiri::XML::Schema + * from_document(document, parse_options) → Nokogiri::XML::Schema + * + * Create a Schema from an already-parsed XSD schema definition document. * - * Create a new Schema from the Nokogiri::XML::Document +doc+ + * [Parameters] + * - +document+ (XML::Document) A document object representing the parsed XSD + * - +parse_options+ (Nokogiri::XML::ParseOptions) + * Defaults to Nokogiri::XML::ParseOptions::DEFAULT_SCHEMA + * + * [Returns] Nokogiri::XML::Schema */ static VALUE -from_document(int argc, VALUE *argv, VALUE klass) +rb_xml_schema_s_from_document(int argc, VALUE *argv, VALUE klass) { VALUE document; VALUE parse_options; @@ -206,7 +215,7 @@ from_document(int argc, VALUE *argv, VALUE klass) VALUE errors; VALUE rb_schema; int scanned_args = 0; - xmlExternalEntityLoader old_loader = 0; + xmlExternalEntityLoader saved_loader = 0; scanned_args = rb_scan_args(argc, argv, "11", &document, &parse_options); @@ -236,14 +245,14 @@ from_document(int argc, VALUE *argv, VALUE klass) #endif if (parse_options_int & XML_PARSE_NONET) { - old_loader = xmlGetExternalEntityLoader(); + saved_loader = xmlGetExternalEntityLoader(); xmlSetExternalEntityLoader(xmlNoNetExternalEntityLoader); } schema = xmlSchemaParse(ctx); - if (old_loader) { - xmlSetExternalEntityLoader(old_loader); + if (saved_loader) { + xmlSetExternalEntityLoader(saved_loader); } xmlSetStructuredErrorFunc(NULL, NULL); @@ -269,6 +278,7 @@ from_document(int argc, VALUE *argv, VALUE klass) return Qnil; } + void noko_init_xml_schema() { @@ -276,9 +286,9 @@ noko_init_xml_schema() rb_undef_alloc_func(cNokogiriXmlSchema); - rb_define_singleton_method(cNokogiriXmlSchema, "read_memory", read_memory, -1); - rb_define_singleton_method(cNokogiriXmlSchema, "from_document", from_document, -1); + rb_define_singleton_method(cNokogiriXmlSchema, "read_memory", rb_xml_schema_s_read_memory, -1); + rb_define_singleton_method(cNokogiriXmlSchema, "from_document", rb_xml_schema_s_from_document, -1); - rb_define_private_method(cNokogiriXmlSchema, "validate_document", validate_document, 1); - rb_define_private_method(cNokogiriXmlSchema, "validate_file", validate_file, 1); + rb_define_private_method(cNokogiriXmlSchema, "validate_document", rb_xml_schema_validate_document, 1); + rb_define_private_method(cNokogiriXmlSchema, "validate_file", rb_xml_schema_validate_file, 1); } diff --git a/lib/nokogiri/xml/relax_ng.rb b/lib/nokogiri/xml/relax_ng.rb index 6d01ba01e5..38910a3d93 100644 --- a/lib/nokogiri/xml/relax_ng.rb +++ b/lib/nokogiri/xml/relax_ng.rb @@ -1,38 +1,66 @@ +# coding: utf-8 # frozen_string_literal: true module Nokogiri module XML class << self - ### - # Create a new Nokogiri::XML::RelaxNG document from +string_or_io+. - # See Nokogiri::XML::RelaxNG for an example. - def RelaxNG(string_or_io, options = ParseOptions::DEFAULT_SCHEMA) - RelaxNG.new(string_or_io, options) + # + # :call-seq: + # RelaxNg(input) → Nokogiri::XML::RelaxNG + # RelaxNg(input, parse_options) → Nokogiri::XML::RelaxNG + # + # Parse a RELAX NG schema definition and create a new Schema object. This is a convenience + # method for Nokogiri::XML::RelaxNG.new + # + # See related: Nokogiri::XML::RelaxNG.new + # + # [Parameters] + # - +input+ (String, IO) RELAX NG schema definition + # - +parse_options+ (Nokogiri::XML::ParseOptions) + # Defaults to ParseOptions::DEFAULT_SCHEMA + # + # [Returns] Nokogiri::XML::RelaxNG + # + def RelaxNG(input, parse_options = ParseOptions::DEFAULT_SCHEMA) + RelaxNG.new(input, parse_options) end end - ### - # Nokogiri::XML::RelaxNG is used for validating XML against a - # RelaxNG schema. + # Nokogiri::XML::RelaxNG is used for validating XML against a RELAX NG schema definition. # - # == Synopsis + # *Example:* Determine whether an XML document is valid. # - # Validate an XML document against a RelaxNG schema. Loop over the errors - # that are returned and print them out: + # schema = Nokogiri::XML::RelaxNG(File.read(RELAX_NG_FILE)) + # doc = Nokogiri::XML(File.read(XML_FILE)) + # schema.valid?(doc) # Boolean # - # schema = Nokogiri::XML::RelaxNG(File.open(ADDRESS_SCHEMA_FILE)) - # doc = Nokogiri::XML(File.open(ADDRESS_XML_FILE)) + # *Example:* Validate an XML document against a RelaxNG schema, and capture any errors that are found. # - # schema.validate(doc).each do |error| - # puts error.message - # end + # schema = Nokogiri::XML::RelaxNG(File.open(RELAX_NG_FILE)) + # doc = Nokogiri::XML(File.open(XML_FILE)) + # errors = schema.validate(doc) # Array # - # The list of errors are Nokogiri::XML::SyntaxError objects. - # - # NOTE: RelaxNG input is always treated as TRUSTED documents, meaning that they will cause the - # underlying parsing libraries to access network resources. This is counter to Nokogiri's - # "untrusted by default" security policy, but is a limitation of the underlying libraries. + # ⚠ RELAX NG input is always treated as *trusted*, meaning that the underlying parsing libraries + # *will access network resources*. This is counter to Nokogiri's "untrusted by default" security + # policy, but is an unfortunate limitation of the underlying libraries. Please do not use this + # class for untrusted schema documents. class RelaxNG < Nokogiri::XML::Schema + # :call-seq: + # new(input) → Nokogiri::XML::RelaxNG + # new(input, parse_options) → Nokogiri::XML::RelaxNG + # + # Parse a RELAX NG schema definition and create a new Schema object. + # + # [Parameters] + # - +input+ (String, IO) RELAX NG schema definition + # - +parse_options+ (Nokogiri::XML::ParseOptions) + # Defaults to ParseOptions::DEFAULT_SCHEMA + # + # [Returns] Nokogiri::XML::RelaxNG + # + def self.new(input, parse_options = ParseOptions::DEFAULT_SCHEMA) + from_document(Nokogiri::XML(input), parse_options) + end end end end diff --git a/lib/nokogiri/xml/schema.rb b/lib/nokogiri/xml/schema.rb index 497fc51532..b500e579d6 100644 --- a/lib/nokogiri/xml/schema.rb +++ b/lib/nokogiri/xml/schema.rb @@ -1,72 +1,134 @@ +# coding: utf-8 # frozen_string_literal: true module Nokogiri module XML class << self - ### - # Create a new Nokogiri::XML::Schema object using a +string_or_io+ - # object. - def Schema(string_or_io, options = ParseOptions::DEFAULT_SCHEMA) - Schema.new(string_or_io, options) + # + # :call-seq: + # Schema(input) → Nokogiri::XML::Schema + # Schema(input, parse_options) → Nokogiri::XML::Schema + # + # Parse an XSD schema definition and create a new {Schema} object. This is a convenience + # method for Nokogiri::XML::Schema.new + # + # See related: Nokogiri::XML::Schema.new + # + # [Parameters] + # - +input+ (String, IO) XSD schema definition + # - +parse_options+ (Nokogiri::XML::ParseOptions) + # [Returns] Nokogiri::XML::Schema + # + def Schema(input, parse_options = ParseOptions::DEFAULT_SCHEMA) + Schema.new(input, parse_options) end end - ### - # Nokogiri::XML::Schema is used for validating XML against a schema - # (usually from an xsd file). + # Nokogiri::XML::Schema is used for validating XML against an XSD schema definition. # - # == Synopsis + # *Example:* Determine whether an XML document is valid. # - # Validate an XML document against a Schema. Loop over the errors that - # are returned and print them out: + # schema = Nokogiri::XML::Schema(File.read(XSD_FILE)) + # doc = Nokogiri::XML(File.read(XML_FILE)) + # schema.valid?(doc) # Boolean # - # xsd = Nokogiri::XML::Schema(File.read(PO_SCHEMA_FILE)) - # doc = Nokogiri::XML(File.read(PO_XML_FILE)) + # *Example:* Validate an XML document against a Schema, and capture any errors that are found. # - # xsd.validate(doc).each do |error| - # puts error.message - # end + # schema = Nokogiri::XML::Schema(File.read(XSD_FILE)) + # doc = Nokogiri::XML(File.read(XML_FILE)) + # errors = schema.validate(doc) # Array # - # The list of errors are Nokogiri::XML::SyntaxError objects. + # ⚠ As of v1.11.0, Schema treats inputs as *untrusted* by default, and so external entities are + # not resolved from the network (+http://+ or +ftp://+). When parsing a trusted document, the + # caller may turn off the +NONET+ option via the ParseOptions to (re-)enable external entity + # resolution over a network connection. # - # NOTE: As of v1.11.0, Schema treats inputs as UNTRUSTED by default, and so external entities - # are not resolved from the network (`http://` or `ftp://`). Previously, parsing treated - # documents as "trusted" by default which was counter to Nokogiri's "untrusted by default" - # security policy. If a document is trusted, then the caller may turn off the NONET option via - # the ParseOptions to re-enable external entity resolution over a network connection. + # Previously, documents were "trusted" by default during schema parsing which was counter to + # Nokogiri's "untrusted by default" security policy. class Schema - # Errors while parsing the schema file + # The errors found while parsing the XSD + # + # [Returns] Array attr_accessor :errors - # The Nokogiri::XML::ParseOptions used to parse the schema + + # The options used to parse the schema + # + # [Returns] Nokogiri::XML::ParseOptions attr_accessor :parse_options - ### - # Create a new Nokogiri::XML::Schema object using a +string_or_io+ - # object. - def self.new(string_or_io, options = ParseOptions::DEFAULT_SCHEMA) - from_document(Nokogiri::XML(string_or_io), options) + # :call-seq: + # new(input) → Nokogiri::XML::Schema + # new(input, parse_options) → Nokogiri::XML::Schema + # + # Parse an XSD schema definition and create a new Nokogiri::XML:Schema object. + # + # [Parameters] + # - +input+ (String, IO) XSD schema definition + # - +parse_options+ (Nokogiri::XML::ParseOptions) + # Defaults to Nokogiri::XML::ParseOptions::DEFAULT_SCHEMA + # + # [Returns] Nokogiri::XML::Schema + # + def self.new(input, parse_options = ParseOptions::DEFAULT_SCHEMA) + from_document(Nokogiri::XML(input), parse_options) end - ### - # Validate +thing+ against this schema. +thing+ can be a - # Nokogiri::XML::Document object, or a filename. An Array of - # Nokogiri::XML::SyntaxError objects found while validating the - # +thing+ is returned. - def validate(thing) - if thing.is_a?(Nokogiri::XML::Document) - validate_document(thing) - elsif File.file?(thing) - validate_file(thing) + # + # :call-seq: validate(input) → Array + # + # Validate +input+ and return any errors that are found. + # + # [Parameters] + # - +input+ (Nokogiri::XML::Document, String) + # + # A parsed document, or a string containing a local filename. + # + # [Returns] Array + # + # *Example:* Validate an existing Document +document+, and capture any errors that are found. + # + # schema = Nokogiri::XML::Schema(File.read(XSD_FILE)) + # errors = schema.validate(document) + # + # *Example:* Validate an XML document on disk, and capture any errors that are found. + # + # schema = Nokogiri::XML::Schema(File.read(XSD_FILE)) + # errors = schema.validate("/path/to/file.xml") + # + def validate(input) + if input.is_a?(Nokogiri::XML::Document) + validate_document(input) + elsif File.file?(input) + validate_file(input) else - raise ArgumentError, "Must provide Nokogiri::Xml::Document or the name of an existing file" + raise ArgumentError, "Must provide Nokogiri::XML::Document or the name of an existing file" end end - ### - # Returns true if +thing+ is a valid Nokogiri::XML::Document or - # file. - def valid?(thing) - validate(thing).empty? + # + # :call-seq: valid?(input) → Boolean + # + # Validate +input+ and return a Boolean indicating whether the document is valid + # + # [Parameters] + # - +input+ (Nokogiri::XML::Document, String) + # + # A parsed document, or a string containing a local filename. + # + # [Returns] Boolean + # + # *Example:* Validate an existing XML::Document +document+ + # + # schema = Nokogiri::XML::Schema(File.read(XSD_FILE)) + # return unless schema.valid?(document) + # + # *Example:* Validate an XML document on disk + # + # schema = Nokogiri::XML::Schema(File.read(XSD_FILE)) + # return unless schema.valid?("/path/to/file.xml") + # + def valid?(input) + validate(input).empty? end end end diff --git a/test/xml/test_relax_ng.rb b/test/xml/test_relax_ng.rb index c33116ffee..f99886b6ad 100644 --- a/test/xml/test_relax_ng.rb +++ b/test/xml/test_relax_ng.rb @@ -50,20 +50,21 @@ def test_from_document_with_parse_options schema = Nokogiri::XML::RelaxNG.from_document(Nokogiri::XML::Document.parse(File.read(ADDRESS_SCHEMA_FILE))) assert_equal(Nokogiri::XML::ParseOptions::DEFAULT_SCHEMA, schema.parse_options) - schema = Nokogiri::XML::RelaxNG.from_document(Nokogiri::XML::Document.parse(File.read(ADDRESS_SCHEMA_FILE)), - Nokogiri::XML::ParseOptions.new.recover) + schema = Nokogiri::XML::RelaxNG.from_document( + Nokogiri::XML::Document.parse(File.read(ADDRESS_SCHEMA_FILE)), + Nokogiri::XML::ParseOptions.new.recover + ) assert_equal(Nokogiri::XML::ParseOptions.new.recover, schema.parse_options) end def test_read_memory_with_parse_options - # https://github.com/sparklemotion/nokogiri/issues/2115 - skip_unless_libxml2 - schema = Nokogiri::XML::RelaxNG.read_memory(File.read(ADDRESS_SCHEMA_FILE)) assert_equal(Nokogiri::XML::ParseOptions::DEFAULT_SCHEMA, schema.parse_options) - schema = Nokogiri::XML::RelaxNG.read_memory(File.read(ADDRESS_SCHEMA_FILE), - Nokogiri::XML::ParseOptions.new.recover) + schema = Nokogiri::XML::RelaxNG.read_memory( + File.read(ADDRESS_SCHEMA_FILE), + Nokogiri::XML::ParseOptions.new.recover + ) assert_equal(Nokogiri::XML::ParseOptions.new.recover, schema.parse_options) end diff --git a/test/xml/test_schema.rb b/test/xml/test_schema.rb index d2e28b14cd..3f6e498968 100644 --- a/test/xml/test_schema.rb +++ b/test/xml/test_schema.rb @@ -104,7 +104,7 @@ def test_schema_validates_with_relative_paths def test_parse_with_memory assert_instance_of(Nokogiri::XML::Schema, @xsd) - assert_equal(0, @xsd.errors.length) + assert_empty(@xsd.errors) end def test_new @@ -150,7 +150,7 @@ def test_parse_with_io File.open(PO_SCHEMA_FILE, "rb") do |f| assert(xsd = Nokogiri::XML::Schema(f)) end - assert_equal(0, xsd.errors.length) + assert_empty(xsd.errors) end def test_parse_with_errors @@ -163,12 +163,12 @@ def test_parse_with_errors def test_validate_document doc = Nokogiri::XML(File.read(PO_XML_FILE)) assert(errors = @xsd.validate(doc)) - assert_equal(0, errors.length) + assert_empty(errors) end def test_validate_file assert(errors = @xsd.validate(PO_XML_FILE)) - assert_equal(0, errors.length) + assert_empty(errors) end def test_validate_invalid_document @@ -231,6 +231,47 @@ def test_xsd_import_with_no_systemid Nokogiri::XML::Schema(xsd) # assert_nothing_raised end + describe "Schema#validate" do + let(:xsd) do + <<~EOF + + + + + EOF + end + + let(:good_xml) { %() } + let(:bad_xml) { %() } + + it "does not clobber @errors" do + schema = Nokogiri::XML::Schema.new(xsd) + bad_doc = Nokogiri::XML(bad_xml) + + # assert on setup + assert_empty(schema.errors) + refute_empty(schema.validate(bad_doc)) + + # this is the bit under test + assert_empty(schema.errors) + end + + it "returns only the most recent document's errors" do + # https://github.com/sparklemotion/nokogiri/issues/1282 + schema = Nokogiri::XML::Schema.new(xsd) + good_doc = Nokogiri::XML(good_xml) + bad_doc = Nokogiri::XML(bad_xml) + + # assert on setup + assert_empty(schema.validate(good_doc)) + refute_empty(schema.validate(bad_doc)) + + # this is the bit under test + assert_empty(schema.validate(good_doc)) + end + end + describe "CVE-2020-26247" do # https://github.com/sparklemotion/nokogiri/security/advisories/GHSA-vr8q-g5c7-m54m let(:schema) do @@ -270,17 +311,19 @@ def test_xsd_import_with_no_systemid it "XML::Schema parsing attempts to access external DTDs" do doc = Nokogiri::XML::Schema.new(schema, Nokogiri::XML::ParseOptions.new.nononet) errors = doc.errors.map(&:to_s) - assert_equal(0, errors.grep(/ERROR: Attempt to load network entity/).length, + assert_empty(errors.grep(/ERROR: Attempt to load network entity/), "Should not see xmlIO.c:xmlNoNetExternalEntityLoader() raising XML_IO_NETWORK_ATTEMPT") - assert_equal(1, errors.grep(/WARNING: failed to load HTTP resource|WARNING: failed to load external entity/).length) + assert_equal(1, + errors.grep(/WARNING: failed to load HTTP resource|WARNING: failed to load external entity/).length) end it "XML::Schema parsing of memory attempts to access external DTDs" do doc = Nokogiri::XML::Schema.read_memory(schema, Nokogiri::XML::ParseOptions.new.nononet) errors = doc.errors.map(&:to_s) - assert_equal(0, errors.grep(/ERROR: Attempt to load network entity/).length, + assert_empty(errors.grep(/ERROR: Attempt to load network entity/), "Should not see xmlIO.c:xmlNoNetExternalEntityLoader() raising XML_IO_NETWORK_ATTEMPT") - assert_equal(1, errors.grep(/WARNING: failed to load HTTP resource|WARNING: failed to load external entity/).length) + assert_equal(1, + errors.grep(/WARNING: failed to load HTTP resource|WARNING: failed to load external entity/).length) end end end @@ -289,28 +332,28 @@ def test_xsd_import_with_no_systemid describe "with default parse options" do it "XML::Schema parsing does not attempt to access external DTDs" do doc = Nokogiri::XML::Schema.new(schema) - assert_equal 1, doc.errors.map(&:to_s).grep(/WARNING: Attempt to load network entity/).length + assert_equal(1, doc.errors.map(&:to_s).grep(/WARNING: Attempt to load network entity/).length) end it "XML::Schema parsing of memory does not attempt to access external DTDs" do doc = Nokogiri::XML::Schema.read_memory(schema) - assert_equal 1, doc.errors.map(&:to_s).grep(/WARNING: Attempt to load network entity/).length + assert_equal(1, doc.errors.map(&:to_s).grep(/WARNING: Attempt to load network entity/).length) end end describe "with NONET turned off" do it "XML::Schema parsing attempts to access external DTDs" do doc = Nokogiri::XML::Schema.new(schema, Nokogiri::XML::ParseOptions.new.nononet) - assert_equal 0, doc.errors.map(&:to_s).grep(/WARNING: Attempt to load network entity/).length + assert_empty(doc.errors.map(&:to_s).grep(/WARNING: Attempt to load network entity/)) end it "XML::Schema parsing of memory attempts to access external DTDs" do doc = Nokogiri::XML::Schema.read_memory(schema, Nokogiri::XML::ParseOptions.new.nononet) - assert_equal 0, doc.errors.map(&:to_s).grep(/WARNING: Attempt to load network entity/).length + assert_empty(doc.errors.map(&:to_s).grep(/WARNING: Attempt to load network entity/)) end end end - end + end # "CVE-2020-26247" end end end