lutaml-model 0.8.62 → 0.8.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93221695e02a0814183bb7935d242d416e7b5fc98fdae729ae7b7a548276fa1a
4
- data.tar.gz: a8c6631206d1ed3dc635012999f11a2deb9f29409a441a4b85e8d47dd7f2fa9c
3
+ metadata.gz: e60ce4a1bedec13a3b778b0afea4c9be5c4779f721916bfafeb151e3bd8f8c1e
4
+ data.tar.gz: b6f519fabe6c9dc9c0c5edeb871d6afcc23d6439eff433b15b74b532b1a633d7
5
5
  SHA512:
6
- metadata.gz: db7ee4ad6d440102cbf7c2c8452b20844c1ddb251a44f86d44f0655e14f359eb1e1fa17debc2f0857485c34b09c9199c6921c31a55b1910aa103ac0fd5e11ff8
7
- data.tar.gz: c8a43921457c96865466b5cb0111ccd4e6aba04585bb6e398bc7ad6f808a2009f2bbffa48e8dc1348ba10937776c96c0707249ab54d0bc018d900c4799eb98e3
6
+ metadata.gz: 646c4cfdfb4dc395f5f339332eb214b0d4cba7bf31710884357e5d8519db8825e0a8e937824f268bac8642ea5d23a7fee75686988b9d3a54923f508e50aaa3c0
7
+ data.tar.gz: d738e9c618790d6575691acc7349991f1b4f28986947e75a3dc40a883978cbc3ce7f6ef28021b7c85f822037c6c63137ed22f1930fe6aba36dd0b266f84a451b
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.62"
5
+ VERSION = "0.8.63"
6
6
  end
7
7
  end
@@ -20,6 +20,7 @@ module Lutaml
20
20
  raw_xml = xml
21
21
  xml = normalize_xml_for_parse(xml)
22
22
  parsed = parse_with_moxml(xml, parse_encoding)
23
+ assert_no_truncated_recovered_parse!(parsed)
23
24
  root_element = parsed.root
24
25
 
25
26
  raise_empty_document_error if root_element.nil?
@@ -30,6 +31,25 @@ module Lutaml
30
31
  new(root, parse_encoding, **parse_document_options(raw_xml))
31
32
  end
32
33
 
34
+ # Recover-mode parsing must never masquerade input truncation as
35
+ # success.
36
+ #
37
+ # libxml2 (the Nokogiri backend) caps its input buffer at 10 MB;
38
+ # longer documents trip a fatal "Resource limit exceeded: Buffer
39
+ # size limit exceeded, try XML_PARSE_HUGE" error mid-input. In
40
+ # recover mode Nokogiri records that error on the document and
41
+ # returns the partial tree, so every node past the limit silently
42
+ # vanishes (lutaml-model#871). A resource-limit fatal means the
43
+ # engine stopped early with input left — refuse to deserialize the
44
+ # partial document instead of dropping trailing content without a
45
+ # trace.
46
+ #
47
+ # Other recover-mode fatals (e.g. an XML declaration after leading
48
+ # whitespace) do not drop content and keep the long-standing
49
+ # recover behavior; only the resource-limit family is truncating.
50
+ TRUNCATION_FATAL_MARKER =
51
+ /Resource limit exceeded|Buffer size limit exceeded/
52
+
33
53
  private
34
54
 
35
55
  def normalize_xml_for_parse(xml)
@@ -62,6 +82,23 @@ module Lutaml
62
82
  end
63
83
  end
64
84
 
85
+ def assert_no_truncated_recovered_parse!(parsed)
86
+ errors = parsed.parse_errors
87
+ return if errors.nil? || errors.empty?
88
+
89
+ fatal = errors.find do |message|
90
+ message.match?(TRUNCATION_FATAL_MARKER)
91
+ end
92
+ return if fatal.nil?
93
+
94
+ raise Lutaml::Model::InvalidFormatError.new(
95
+ :xml,
96
+ "the XML engine reported a fatal resource limit and recovered " \
97
+ "with a truncated document; refusing to deserialize " \
98
+ "partial input (#{fatal})",
99
+ )
100
+ end
101
+
65
102
  def parse_document_options(xml)
66
103
  {
67
104
  doctype: extract_doctype_from_xml(xml),
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "nokogiri"
5
+ require "lutaml/model"
6
+ require "lutaml/xml"
7
+ require "lutaml/xml/adapter/nokogiri_adapter"
8
+
9
+ # Regression tests for lutaml-model#871.
10
+ #
11
+ # libxml2 (the Nokogiri backend) caps its input buffer at 10 MB. Parsing a
12
+ # longer document in recover mode records a FATAL "Resource limit exceeded:
13
+ # Buffer size limit exceeded, try XML_PARSE_HUGE" error on the document and
14
+ # returns the partial tree, so every node past the cap silently vanishes.
15
+ # XmlParser now refuses documents whose parse_errors carry a fatal error
16
+ # instead of deserializing the truncated input.
17
+ RSpec.describe "recovered fatal parse errors" do
18
+ let(:annex_class) do
19
+ Class.new(Lutaml::Model::Serializable) do
20
+ attribute :id, :string
21
+
22
+ xml do
23
+ element "annex"
24
+ map_attribute "id", to: :id
25
+ end
26
+ end
27
+ end
28
+
29
+ let(:root_class) do
30
+ annex = annex_class
31
+
32
+ Class.new(Lutaml::Model::Serializable) do
33
+ attribute :annexes, annex, collection: true
34
+
35
+ xml do
36
+ element "metanorma"
37
+ map_element "annex", to: :annexes
38
+ end
39
+ end
40
+ end
41
+
42
+ def build_document(chunk_count:, chunk_size:, with_tail: false)
43
+ filler = "A" * chunk_size
44
+ document = +"<metanorma>"
45
+ chunk_count.times do |index|
46
+ document << %(<annex id="annex#{index}" data="#{filler}"/>)
47
+ end
48
+ document << %(<annex id="annex-tail-marker"/>) if with_tail
49
+ document << "</metanorma>"
50
+ document
51
+ end
52
+
53
+ # Six 1.7 MB attribute values push the cumulative input past libxml2's
54
+ # 10 MB buffer cap mid-document, mirroring the issue's failing manifest.
55
+ def over_cap_document
56
+ build_document(chunk_count: 6, chunk_size: 1_700_000, with_tail: true)
57
+ end
58
+
59
+ it "hits the libxml2 buffer cap in recover mode past 10 MB" do
60
+ errors = Nokogiri::XML(over_cap_document) { |config| config.recover.nonet }.errors
61
+
62
+ expect(errors.map(&:message).join("\n")).to match(/\bFATAL:/)
63
+ end
64
+
65
+ it "raises instead of silently truncating on the nokogiri path" do
66
+ Lutaml::Model::Config.with_adapter(xml: :nokogiri) do
67
+ expect { root_class.from_xml(over_cap_document) }
68
+ .to raise_error(Lutaml::Model::InvalidFormatError, /resource limit.*truncated/m)
69
+ end
70
+ end
71
+
72
+ it "raises from the adapter layer directly" do
73
+ Lutaml::Model::Config.with_adapter(xml: :nokogiri) do
74
+ expect { Lutaml::Xml::Adapter::NokogiriAdapter.parse(over_cap_document) }
75
+ .to raise_error(Lutaml::Model::InvalidFormatError, /XML_PARSE_HUGE/)
76
+ end
77
+ end
78
+
79
+ it "keeps parsing documents whose fatals do not drop content" do
80
+ xml = " <?xml version=\"1.0\"?>\n<metanorma><annex id=\"a\"/></metanorma>"
81
+
82
+ Lutaml::Model::Config.with_adapter(xml: :nokogiri) do
83
+ model = root_class.from_xml(xml)
84
+
85
+ expect(model.annexes.map(&:id)).to eq(["a"])
86
+ end
87
+ end
88
+
89
+ it "still parses smaller documents without any fatal-error handling" do
90
+ Lutaml::Model::Config.with_adapter(xml: :nokogiri) do
91
+ model = root_class.from_xml(
92
+ build_document(chunk_count: 2, chunk_size: 500_000, with_tail: true),
93
+ )
94
+
95
+ expect(model.annexes.map(&:id))
96
+ .to eq(["annex0", "annex1", "annex-tail-marker"])
97
+ end
98
+ end
99
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.62
4
+ version: 0.8.63
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -1902,6 +1902,7 @@ files:
1902
1902
  - spec/lutaml/xml/adapter/oga_adapter_spec.rb
1903
1903
  - spec/lutaml/xml/adapter/order_spec.rb
1904
1904
  - spec/lutaml/xml/adapter/ox_adapter_spec.rb
1905
+ - spec/lutaml/xml/adapter/recovered_fatal_parse_spec.rb
1905
1906
  - spec/lutaml/xml/adapter/rexml_adapter_spec.rb
1906
1907
  - spec/lutaml/xml/adapter/xml_namespace_spec.rb
1907
1908
  - spec/lutaml/xml/adapter_loader_spec.rb