lutaml-model 0.8.62 → 0.8.64

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: 8a6392d2dcfd63cc0cb61e585a164c72e09438c25154651cf9ad935e32985429
4
+ data.tar.gz: 492ad5dd9799652c1dd6abc9e148edba5937f3a613e143bdf556f625d94851fc
5
5
  SHA512:
6
- metadata.gz: db7ee4ad6d440102cbf7c2c8452b20844c1ddb251a44f86d44f0655e14f359eb1e1fa17debc2f0857485c34b09c9199c6921c31a55b1910aa103ac0fd5e11ff8
7
- data.tar.gz: c8a43921457c96865466b5cb0111ccd4e6aba04585bb6e398bc7ad6f808a2009f2bbffa48e8dc1348ba10937776c96c0707249ab54d0bc018d900c4799eb98e3
6
+ metadata.gz: c14bf78b03a2cce38a13d7a7c09cc07a7cca4b1dbed59a46c0faa9a5c4c275c872dcbff23afae3f8b8518b4fa926ad64597f0f7d67a7567a711ea05d60f8754d
7
+ data.tar.gz: c8d8b9723545747d826a7c16bb3d11699963299b2a238b5c04e5a724b1de555463fb839689cef9e46e52137c92a1f6831bd50676097653d888a92c57d7255f29
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.62"
5
+ VERSION = "0.8.64"
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,31 @@ 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
+ # moxml releases before 0.5.84 parsed Nokogiri documents without
38
+ # XML_PARSE_HUGE, so libxml2 capped its input buffer at 10 MB;
39
+ # longer documents tripped a fatal "Resource limit exceeded:
40
+ # Buffer size limit exceeded, try XML_PARSE_HUGE" error mid-input.
41
+ # In recover mode Nokogiri records that error on the document and
42
+ # returns the partial tree, so every node past the limit silently
43
+ # vanishes (lutaml-model#871). A resource-limit fatal means the
44
+ # engine stopped early with input left — refuse to deserialize the
45
+ # partial document instead of dropping trailing content without a
46
+ # trace.
47
+ #
48
+ # moxml 0.5.84 and later always pass XML_PARSE_HUGE, so the
49
+ # Nokogiri path no longer truncates and this guard stays silent;
50
+ # it remains for older moxml releases and for any other adapter
51
+ # that reports the resource-limit family.
52
+ #
53
+ # Other recover-mode fatals (e.g. an XML declaration after leading
54
+ # whitespace) do not drop content and keep the long-standing
55
+ # recover behavior; only the resource-limit family is truncating.
56
+ TRUNCATION_FATAL_MARKER =
57
+ /Resource limit exceeded|Buffer size limit exceeded/
58
+
33
59
  private
34
60
 
35
61
  def normalize_xml_for_parse(xml)
@@ -62,6 +88,23 @@ module Lutaml
62
88
  end
63
89
  end
64
90
 
91
+ def assert_no_truncated_recovered_parse!(parsed)
92
+ errors = parsed.parse_errors
93
+ return if errors.nil? || errors.empty?
94
+
95
+ fatal = errors.find do |message|
96
+ message.match?(TRUNCATION_FATAL_MARKER)
97
+ end
98
+ return if fatal.nil?
99
+
100
+ raise Lutaml::Model::InvalidFormatError.new(
101
+ :xml,
102
+ "the XML engine reported a fatal resource limit and recovered " \
103
+ "with a truncated document; refusing to deserialize " \
104
+ "partial input (#{fatal})",
105
+ )
106
+ end
107
+
65
108
  def parse_document_options(xml)
66
109
  {
67
110
  doctype: extract_doctype_from_xml(xml),
data/lutaml-model.gemspec CHANGED
@@ -38,7 +38,10 @@ Gem::Specification.new do |spec|
38
38
  spec.add_dependency "canon"
39
39
  spec.add_dependency "concurrent-ruby"
40
40
  spec.add_dependency "liquid", ">= 4.0", "< 6.0"
41
- spec.add_dependency "moxml", "~> 0.5.56"
41
+ # 0.5.84 always passes XML_PARSE_HUGE to libxml2: very large documents
42
+ # parse completely instead of being silently truncated at the 10 MB
43
+ # input-buffer cap (lutaml-model#871).
44
+ spec.add_dependency "moxml", "~> 0.5.84"
42
45
  spec.add_dependency "ostruct"
43
46
  spec.add_dependency "rubyzip", "~> 3.4"
44
47
  spec.add_dependency "thor"
@@ -0,0 +1,105 @@
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 coverage for lutaml-model#871: very large documents must
10
+ # deserialize completely, with no silent truncation and no data loss.
11
+ #
12
+ # libxml2's default mode caps its input buffer at 10 MB and, in recover
13
+ # mode, silently drops every node past the cap. moxml 0.5.84 always
14
+ # passes XML_PARSE_HUGE, so the Nokogiri path now parses past the cap.
15
+ # XmlParser additionally refuses recovered parses that report the
16
+ # resource-limit fatal family, which still protects moxml releases that
17
+ # truncate.
18
+ RSpec.describe "parsing documents past libxml2's 10 MB buffer cap" do
19
+ let(:annex_class) do
20
+ Class.new(Lutaml::Model::Serializable) do
21
+ attribute :id, :string
22
+
23
+ xml do
24
+ element "annex"
25
+ map_attribute "id", to: :id
26
+ end
27
+ end
28
+ end
29
+
30
+ let(:root_class) do
31
+ annex = annex_class
32
+
33
+ Class.new(Lutaml::Model::Serializable) do
34
+ attribute :annexes, annex, collection: true
35
+
36
+ xml do
37
+ element "metanorma"
38
+ map_element "annex", to: :annexes
39
+ end
40
+ end
41
+ end
42
+
43
+ def build_document(chunk_count:, chunk_size:, with_tail: false)
44
+ filler = "A" * chunk_size
45
+ document = +"<metanorma>"
46
+ chunk_count.times do |index|
47
+ document << %(<annex id="annex#{index}" data="#{filler}"/>)
48
+ end
49
+ document << %(<annex id="annex-tail-marker"/>) if with_tail
50
+ document << "</metanorma>"
51
+ document
52
+ end
53
+
54
+ # Six 1.7 MB attribute values push the cumulative input past libxml2's
55
+ # 10 MB buffer cap mid-document, mirroring the issue's failing manifest.
56
+ def over_cap_document
57
+ build_document(chunk_count: 6, chunk_size: 1_700_000, with_tail: true)
58
+ end
59
+
60
+ it "confirms libxml2's default mode still trips the buffer cap past 10 MB" do
61
+ errors = Nokogiri::XML(over_cap_document) { |config| config.recover.nonet }.errors
62
+
63
+ expect(errors.map(&:message).join("\n")).to match(/\bFATAL:/)
64
+ end
65
+
66
+ it "parses over-cap documents completely on the nokogiri path" do
67
+ Lutaml::Model::Config.with_adapter(xml: :nokogiri) do
68
+ model = root_class.from_xml(over_cap_document)
69
+
70
+ expect(model.annexes.map(&:id)).to eq(
71
+ ["annex0", "annex1", "annex2", "annex3", "annex4", "annex5",
72
+ "annex-tail-marker"],
73
+ )
74
+ end
75
+ end
76
+
77
+ it "returns the complete tree from the adapter layer" do
78
+ Lutaml::Model::Config.with_adapter(xml: :nokogiri) do
79
+ document = Lutaml::Xml::Adapter::NokogiriAdapter.parse(over_cap_document)
80
+
81
+ expect(document.root.children.count).to eq(7)
82
+ end
83
+ end
84
+
85
+ it "keeps parsing documents whose fatals do not drop content" do
86
+ xml = " <?xml version=\"1.0\"?>\n<metanorma><annex id=\"a\"/></metanorma>"
87
+
88
+ Lutaml::Model::Config.with_adapter(xml: :nokogiri) do
89
+ model = root_class.from_xml(xml)
90
+
91
+ expect(model.annexes.map(&:id)).to eq(["a"])
92
+ end
93
+ end
94
+
95
+ it "keeps parsing smaller documents unchanged" do
96
+ Lutaml::Model::Config.with_adapter(xml: :nokogiri) do
97
+ model = root_class.from_xml(
98
+ build_document(chunk_count: 2, chunk_size: 500_000, with_tail: true),
99
+ )
100
+
101
+ expect(model.annexes.map(&:id))
102
+ .to eq(["annex0", "annex1", "annex-tail-marker"])
103
+ end
104
+ end
105
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
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.64
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-09-25 00:00:00.000000000 Z
11
+ date: 2026-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -92,14 +92,14 @@ dependencies:
92
92
  requirements:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
- version: 0.5.56
95
+ version: 0.5.84
96
96
  type: :runtime
97
97
  prerelease: false
98
98
  version_requirements: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
- version: 0.5.56
102
+ version: 0.5.84
103
103
  - !ruby/object:Gem::Dependency
104
104
  name: ostruct
105
105
  requirement: !ruby/object:Gem::Requirement
@@ -1901,6 +1901,7 @@ files:
1901
1901
  - spec/lutaml/xml/adapter/nokogiri_adapter_spec.rb
1902
1902
  - spec/lutaml/xml/adapter/oga_adapter_spec.rb
1903
1903
  - spec/lutaml/xml/adapter/order_spec.rb
1904
+ - spec/lutaml/xml/adapter/over_cap_parse_spec.rb
1904
1905
  - spec/lutaml/xml/adapter/ox_adapter_spec.rb
1905
1906
  - spec/lutaml/xml/adapter/rexml_adapter_spec.rb
1906
1907
  - spec/lutaml/xml/adapter/xml_namespace_spec.rb