moxml 0.1.25 → 0.2.0
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 +4 -4
- data/.github/workflows/release.yml +9 -0
- data/.github/workflows/windows.yml +62 -0
- data/Gemfile +3 -1
- data/README.adoc +133 -6
- data/docs/signature/algorithms.md +108 -0
- data/docs/signature/architecture.md +148 -0
- data/docs/signature/c14n.md +110 -0
- data/docs/signature/examples.md +109 -0
- data/docs/signature/flows.md +132 -0
- data/docs/signature/quick-reference.md +115 -0
- data/docs/signature/security.md +143 -0
- data/examples/signature/auto_key_extraction.rb +24 -0
- data/examples/signature/enveloped_rsa.rb +45 -0
- data/examples/signature/hmac.rb +33 -0
- data/lib/compat/opal/moxml_boot.rb +5 -0
- data/lib/moxml/adapter/base.rb +53 -49
- data/lib/moxml/adapter/customized_leptris/declaration.rb +32 -0
- data/lib/moxml/adapter/customized_leptris/doctype.rb +31 -0
- data/lib/moxml/adapter/customized_leptris/entity_reference.rb +12 -0
- data/lib/moxml/adapter/customized_leptris/text_segment.rb +26 -0
- data/lib/moxml/adapter/customized_leptris.rb +16 -0
- data/lib/moxml/adapter/customized_libxml/cdata.rb +3 -12
- data/lib/moxml/adapter/customized_libxml/comment.rb +2 -9
- data/lib/moxml/adapter/customized_libxml/declaration.rb +1 -10
- data/lib/moxml/adapter/customized_libxml/node.rb +8 -0
- data/lib/moxml/adapter/customized_libxml/processing_instruction.rb +2 -10
- data/lib/moxml/adapter/customized_libxml/text.rb +2 -9
- data/lib/moxml/adapter/customized_oga/entity_decoder.rb +37 -0
- data/lib/moxml/adapter/customized_oga/raw_value_override.rb +52 -0
- data/lib/moxml/adapter/customized_oga/xml_generator.rb +4 -34
- data/lib/moxml/adapter/customized_oga.rb +2 -0
- data/lib/moxml/adapter/customized_ox/entity_reference.rb +1 -17
- data/lib/moxml/adapter/customized_rexml/entity_reference.rb +1 -11
- data/lib/moxml/adapter/headed_ox.rb +9 -128
- data/lib/moxml/adapter/leptris.rb +965 -0
- data/lib/moxml/adapter/libxml.rb +131 -99
- data/lib/moxml/adapter/nokogiri.rb +29 -8
- data/lib/moxml/adapter/oga.rb +91 -54
- data/lib/moxml/adapter/ox.rb +93 -137
- data/lib/moxml/adapter/rexml.rb +61 -50
- data/lib/moxml/adapter.rb +2 -1
- data/lib/moxml/attribute.rb +8 -4
- data/lib/moxml/attribute_resolver.rb +189 -0
- data/lib/moxml/c14n/attribute_handler.rb +71 -0
- data/lib/moxml/c14n/character_encoder.rb +29 -0
- data/lib/moxml/c14n/data_model.rb +145 -0
- data/lib/moxml/c14n/exclusive.rb +188 -0
- data/lib/moxml/c14n/inclusive_10.rb +42 -0
- data/lib/moxml/c14n/inclusive_11.rb +21 -0
- data/lib/moxml/c14n/namespace_context.rb +68 -0
- data/lib/moxml/c14n/namespace_handler.rb +71 -0
- data/lib/moxml/c14n/node.rb +42 -0
- data/lib/moxml/c14n/nodes/attribute_node.rb +50 -0
- data/lib/moxml/c14n/nodes/comment_node.rb +29 -0
- data/lib/moxml/c14n/nodes/element_node.rb +62 -0
- data/lib/moxml/c14n/nodes/namespace_node.rb +41 -0
- data/lib/moxml/c14n/nodes/processing_instruction_node.rb +30 -0
- data/lib/moxml/c14n/nodes/root_node.rb +22 -0
- data/lib/moxml/c14n/nodes/text_node.rb +29 -0
- data/lib/moxml/c14n/nodes.rb +17 -0
- data/lib/moxml/c14n/processor.rb +118 -0
- data/lib/moxml/c14n/writer.rb +121 -0
- data/lib/moxml/c14n/xml_base_handler.rb +140 -0
- data/lib/moxml/c14n.rb +96 -0
- data/lib/moxml/config.rb +29 -3
- data/lib/moxml/context.rb +12 -0
- data/lib/moxml/element.rb +29 -12
- data/lib/moxml/entity/reference.rb +28 -0
- data/lib/moxml/entity.rb +125 -0
- data/lib/moxml/node.rb +90 -1
- data/lib/moxml/sax/namespace_splitter.rb +5 -2
- data/lib/moxml/signature/algorithms/base64_transform.rb +40 -0
- data/lib/moxml/signature/algorithms/canonicalization_base.rb +87 -0
- data/lib/moxml/signature/algorithms/digest_base.rb +51 -0
- data/lib/moxml/signature/algorithms/dsa_sha.rb +107 -0
- data/lib/moxml/signature/algorithms/ecdsa_sha.rb +125 -0
- data/lib/moxml/signature/algorithms/enveloped_signature_transform.rb +79 -0
- data/lib/moxml/signature/algorithms/exc_c14n_10.rb +23 -0
- data/lib/moxml/signature/algorithms/hmac_sha.rb +117 -0
- data/lib/moxml/signature/algorithms/inclusive_c14n_10.rb +21 -0
- data/lib/moxml/signature/algorithms/inclusive_c14n_11.rb +23 -0
- data/lib/moxml/signature/algorithms/rsa_pkcs1_sha.rb +56 -0
- data/lib/moxml/signature/algorithms/sha1.rb +13 -0
- data/lib/moxml/signature/algorithms/sha224.rb +13 -0
- data/lib/moxml/signature/algorithms/sha256.rb +13 -0
- data/lib/moxml/signature/algorithms/sha384.rb +13 -0
- data/lib/moxml/signature/algorithms/sha512.rb +13 -0
- data/lib/moxml/signature/algorithms/signature_method_base.rb +58 -0
- data/lib/moxml/signature/algorithms/transform_base.rb +40 -0
- data/lib/moxml/signature/algorithms.rb +113 -0
- data/lib/moxml/signature/errors.rb +99 -0
- data/lib/moxml/signature/key_extractor.rb +172 -0
- data/lib/moxml/signature/model/algorithm_method.rb +18 -0
- data/lib/moxml/signature/model/digest_method.rb +15 -0
- data/lib/moxml/signature/model/key/dsa_key_value.rb +28 -0
- data/lib/moxml/signature/model/key/ec_key_value.rb +22 -0
- data/lib/moxml/signature/model/key/rsa_key_value.rb +19 -0
- data/lib/moxml/signature/model/key/x509_data.rb +32 -0
- data/lib/moxml/signature/model/key/x509_digest.rb +19 -0
- data/lib/moxml/signature/model/key/x509_issuer_serial.rb +19 -0
- data/lib/moxml/signature/model/key_info.rb +21 -0
- data/lib/moxml/signature/model/key_value.rb +20 -0
- data/lib/moxml/signature/model/object_element.rb +18 -0
- data/lib/moxml/signature/model/reference.rb +22 -0
- data/lib/moxml/signature/model/signature.rb +21 -0
- data/lib/moxml/signature/model/signature_value.rb +16 -0
- data/lib/moxml/signature/model/signed_info.rb +20 -0
- data/lib/moxml/signature/model/transform.rb +16 -0
- data/lib/moxml/signature/model/transforms.rb +36 -0
- data/lib/moxml/signature/model.rb +35 -0
- data/lib/moxml/signature/parser.rb +270 -0
- data/lib/moxml/signature/reference_resolver.rb +79 -0
- data/lib/moxml/signature/reference_result.rb +22 -0
- data/lib/moxml/signature/serializer.rb +163 -0
- data/lib/moxml/signature/signer.rb +73 -0
- data/lib/moxml/signature/single_verification_result.rb +31 -0
- data/lib/moxml/signature/transform_pipeline.rb +106 -0
- data/lib/moxml/signature/verification_result.rb +27 -0
- data/lib/moxml/signature/verifier.rb +129 -0
- data/lib/moxml/signature.rb +94 -0
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xml_emitter.rb +71 -0
- data/lib/moxml/xpath/compiler.rb +60 -7
- data/lib/moxml/xpath/parser.rb +22 -15
- data/lib/moxml.rb +5 -0
- data/reference-docs/w3c-xmldsig-bestpractices.md +216 -0
- data/reference-docs/w3c-xmldsig-core.md +400 -0
- data/spec/examples/readme_examples_spec.rb +0 -4
- data/spec/examples/xpath_examples_spec.rb +0 -11
- data/spec/fixtures/xmldsig/keys/rsa_private.pem +28 -0
- data/spec/fixtures/xmldsig/keys/rsa_public.pem +9 -0
- data/spec/fixtures/xmldsig/keys/rsa_ref.pem +15 -0
- data/spec/fixtures/xmldsig/keys/rsa_ref.pub +6 -0
- data/spec/fixtures/xmldsig/sign2-doc.xml +6 -0
- data/spec/fixtures/xmldsig/sign2-result.xml +25 -0
- data/spec/fixtures/xmldsig/sign3-result.xml +39 -0
- data/spec/integration/all_adapters_spec.rb +17 -0
- data/spec/integration/sax_parity_spec.rb +58 -0
- data/spec/integration/shared_examples/edge_cases.rb +0 -10
- data/spec/integration/shared_examples/integration_workflows.rb +0 -10
- data/spec/integration/shared_examples/node_wrappers/attribute_behavior.rb +98 -0
- data/spec/integration/shared_examples/node_wrappers/namespace_behavior.rb +26 -0
- data/spec/integration/shared_examples/node_wrappers/node_behavior.rb +0 -8
- data/spec/moxml/adapter/leptris_spec.rb +75 -0
- data/spec/moxml/adapter/ox_spec.rb +20 -6
- data/spec/moxml/adapter/platform_spec.rb +15 -2
- data/spec/moxml/adapter/shared_examples/adapter_contract.rb +180 -0
- data/spec/moxml/attribute_resolver_spec.rb +108 -0
- data/spec/moxml/c14n/api_spec.rb +117 -0
- data/spec/moxml/c14n/c14n_spec.rb +88 -0
- data/spec/moxml/c14n/comments_pis_spec.rb +65 -0
- data/spec/moxml/c14n/inclusive10_spec.rb +60 -0
- data/spec/moxml/c14n/namespace_edge_cases_spec.rb +88 -0
- data/spec/moxml/c14n/xml_attributes_spec.rb +54 -0
- data/spec/moxml/doctype_spec.rb +1 -1
- data/spec/moxml/entity_spec.rb +76 -0
- data/spec/moxml/node_spec.rb +104 -0
- data/spec/moxml/sax_entity_parity_spec.rb +358 -0
- data/spec/moxml/signature/algorithms/base64_transform_spec.rb +26 -0
- data/spec/moxml/signature/algorithms/digest_base_spec.rb +72 -0
- data/spec/moxml/signature/algorithms/dsa_sha_spec.rb +46 -0
- data/spec/moxml/signature/algorithms/ecdsa_sha_spec.rb +72 -0
- data/spec/moxml/signature/algorithms/enveloped_signature_transform_spec.rb +40 -0
- data/spec/moxml/signature/algorithms/hmac_sha_spec.rb +71 -0
- data/spec/moxml/signature/algorithms/rsa_pkcs1_sha_spec.rb +55 -0
- data/spec/moxml/signature/algorithms_spec.rb +76 -0
- data/spec/moxml/signature/cross_verify_spec.rb +64 -0
- data/spec/moxml/signature/edge_cases_spec.rb +284 -0
- data/spec/moxml/signature/fixtures_spec.rb +80 -0
- data/spec/moxml/signature/key_extractor_spec.rb +96 -0
- data/spec/moxml/signature/model_spec.rb +54 -0
- data/spec/moxml/signature/round_trip_spec.rb +113 -0
- data/spec/moxml/xml_emitter_spec.rb +64 -0
- data/spec/moxml/xpath/axes_spec.rb +72 -0
- data/spec/moxml/xpath/parser_spec.rb +6 -0
- data/spec/moxml/xpath_capabilities_spec.rb +5 -3
- data/spec/performance/benchmark_spec.rb +13 -6
- data/spec/performance/thread_safety_spec.rb +0 -4
- data/spec/spec_helper.rb +5 -1
- metadata +130 -2
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
module C14n
|
|
5
|
+
# Exclusive XML Canonicalization 1.0 (https://www.w3.org/TR/xml-exc-c14n/)
|
|
6
|
+
#
|
|
7
|
+
# Renders only the namespaces visibly utilized by each element's
|
|
8
|
+
# qualified name and attributes (plus any in the inclusive prefix
|
|
9
|
+
# list), as opposed to inclusive C14N which renders every in-scope
|
|
10
|
+
# namespace on every element.
|
|
11
|
+
#
|
|
12
|
+
# Output is UTF-8 octets with no BOM.
|
|
13
|
+
class Exclusive
|
|
14
|
+
# `node`: a Moxml::Node — element, document, text, comment, or PI.
|
|
15
|
+
# `with_comments:`: include comment nodes in output.
|
|
16
|
+
# `inclusive_namespaces`: list of prefixes to always include even if
|
|
17
|
+
# not visibly utilized (InclusiveNamespacesPrefixList parameter).
|
|
18
|
+
def canonicalize(node, with_comments: false, inclusive_namespaces: [])
|
|
19
|
+
writer = Writer.new
|
|
20
|
+
context = NamespaceContext.new
|
|
21
|
+
render(node, writer, context, with_comments, inclusive_namespaces)
|
|
22
|
+
writer.output
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def render(node, writer, context, with_comments, inclusive)
|
|
28
|
+
case node_type(node)
|
|
29
|
+
when :document
|
|
30
|
+
node.children.each { |c| render(c, writer, context, with_comments, inclusive) }
|
|
31
|
+
when :element
|
|
32
|
+
render_element(node, writer, context, with_comments, inclusive)
|
|
33
|
+
when :text, :cdata
|
|
34
|
+
writer.text(node.content)
|
|
35
|
+
when :comment
|
|
36
|
+
writer.comment(node.content) if with_comments
|
|
37
|
+
when :processing_instruction
|
|
38
|
+
target, content = pi_target_and_content(node)
|
|
39
|
+
writer.processing_instruction(target, content)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def render_element(element, writer, context, with_comments, inclusive)
|
|
44
|
+
declared = bindings_declared_on(element)
|
|
45
|
+
in_scope = bindings_in_scope_on(element)
|
|
46
|
+
context.push(in_scope)
|
|
47
|
+
|
|
48
|
+
prefix = element_namespace_prefix(element)
|
|
49
|
+
local = element_name(element)
|
|
50
|
+
|
|
51
|
+
visible_prefixes = visibly_used_prefixes(element, prefix)
|
|
52
|
+
inclusive.each { |p| visible_prefixes.add(p) }
|
|
53
|
+
visible_prefixes.add("xml") if visibly_uses_xml(element)
|
|
54
|
+
|
|
55
|
+
namespaces_to_render = visible_prefixes.map do |p|
|
|
56
|
+
[p, context.uri_for(p)]
|
|
57
|
+
end.reject { |(_, uri)| uri.nil? }
|
|
58
|
+
|
|
59
|
+
writer.open_rendered_frame(element)
|
|
60
|
+
writer.open_tag(prefix, local)
|
|
61
|
+
render_namespaces(writer, element, namespaces_to_render)
|
|
62
|
+
render_attributes(writer, element)
|
|
63
|
+
writer.close_tag_open
|
|
64
|
+
|
|
65
|
+
# Switch context from in-scope (apex lookup) to declared-only so
|
|
66
|
+
# children build their own in-scope view by pushing their declared
|
|
67
|
+
# set on top of the parent's declared set.
|
|
68
|
+
context.pop
|
|
69
|
+
context.push(declared)
|
|
70
|
+
|
|
71
|
+
element.children.each { |c| render(c, writer, context, with_comments, inclusive) }
|
|
72
|
+
|
|
73
|
+
writer.close_tag(prefix, local)
|
|
74
|
+
writer.close_rendered_frame
|
|
75
|
+
context.pop
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def render_namespaces(writer, element, namespaces)
|
|
79
|
+
already_rendered = writer.rendered_namespaces_for(element)
|
|
80
|
+
sorted = namespaces
|
|
81
|
+
.reject { |(prefix, _)| already_rendered.include?(prefix) }
|
|
82
|
+
.sort_by { |(prefix, _)| prefix.to_s }
|
|
83
|
+
sorted.each do |(prefix, uri)|
|
|
84
|
+
writer.namespace(prefix, uri)
|
|
85
|
+
writer.mark_rendered(element, prefix, uri)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def render_attributes(writer, element)
|
|
90
|
+
attrs = collect_attributes(element)
|
|
91
|
+
sorted = attrs.sort_by { |a| [a[:ns_uri] || "", a[:local]] }
|
|
92
|
+
sorted.each { |a| writer.attribute(a[:expanded], a[:value]) }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def collect_attributes(element)
|
|
96
|
+
attrs = []
|
|
97
|
+
element.attributes.each do |attr|
|
|
98
|
+
ns = attr.namespace
|
|
99
|
+
attrs << {
|
|
100
|
+
ns_uri: ns&.uri,
|
|
101
|
+
ns_prefix: ns&.prefix,
|
|
102
|
+
local: attr.name,
|
|
103
|
+
expanded: attr_expanded_name(attr),
|
|
104
|
+
value: attr.value,
|
|
105
|
+
}
|
|
106
|
+
end
|
|
107
|
+
attrs
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def attr_expanded_name(attr)
|
|
111
|
+
ns = attr.namespace
|
|
112
|
+
prefix = ns&.prefix
|
|
113
|
+
if prefix && !prefix.empty?
|
|
114
|
+
"#{prefix}:#{attr.name}"
|
|
115
|
+
else
|
|
116
|
+
attr.name
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def visibly_used_prefixes(element, element_prefix)
|
|
121
|
+
set = Set.new
|
|
122
|
+
# The element's qualified name visibly uses its own namespace.
|
|
123
|
+
# Default namespace (prefix == "" or nil) is included so it gets
|
|
124
|
+
# rendered on the apex element when the apex uses default ns.
|
|
125
|
+
if element.namespace_uri
|
|
126
|
+
set.add(element_prefix || "")
|
|
127
|
+
end
|
|
128
|
+
element.attributes.each do |attr|
|
|
129
|
+
ns = attr.namespace
|
|
130
|
+
prefix = ns&.prefix
|
|
131
|
+
set.add(prefix || "") if ns&.uri
|
|
132
|
+
end
|
|
133
|
+
set
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def visibly_uses_xml(element)
|
|
137
|
+
element.attributes.any? { |a| a.namespace&.prefix == "xml" }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def bindings_declared_on(element)
|
|
141
|
+
bindings = {}
|
|
142
|
+
element.namespaces.each do |ns|
|
|
143
|
+
prefix = ns.prefix
|
|
144
|
+
prefix = "" if prefix.nil? || prefix == "xmlns"
|
|
145
|
+
bindings[prefix] = ns.uri
|
|
146
|
+
end
|
|
147
|
+
bindings
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# In-scope namespaces (declared + inherited from ancestors).
|
|
151
|
+
# Used at the apex to find URIs for visibly-used prefixes whose
|
|
152
|
+
# declarations live on ancestors.
|
|
153
|
+
def bindings_in_scope_on(element)
|
|
154
|
+
bindings = {}
|
|
155
|
+
element.in_scope_namespaces.each do |ns|
|
|
156
|
+
prefix = ns.prefix
|
|
157
|
+
prefix = "" if prefix.nil? || prefix == "xmlns"
|
|
158
|
+
bindings[prefix] = ns.uri
|
|
159
|
+
end
|
|
160
|
+
bindings
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def element_namespace_prefix(element)
|
|
164
|
+
ns = element.namespace
|
|
165
|
+
ns&.prefix
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def element_name(element)
|
|
169
|
+
element.name
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def pi_target_and_content(node)
|
|
173
|
+
[node.name, node.text]
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def node_type(node)
|
|
177
|
+
return :document if node.is_a?(::Moxml::Document)
|
|
178
|
+
return :element if node.is_a?(::Moxml::Element)
|
|
179
|
+
return :text if node.is_a?(::Moxml::Text)
|
|
180
|
+
return :cdata if node.is_a?(::Moxml::Cdata)
|
|
181
|
+
return :comment if node.is_a?(::Moxml::Comment)
|
|
182
|
+
return :processing_instruction if node.is_a?(::Moxml::ProcessingInstruction)
|
|
183
|
+
|
|
184
|
+
:unknown
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
module C14n
|
|
5
|
+
# Inclusive Canonical XML 1.0 (https://www.w3.org/TR/xml-c14n/)
|
|
6
|
+
#
|
|
7
|
+
# Unlike exclusive C14N, inclusive C14N "attracts" ancestor context:
|
|
8
|
+
# at the apex element, ALL in-scope namespaces are rendered, including
|
|
9
|
+
# those inherited from ancestors outside the canonicalization subset.
|
|
10
|
+
#
|
|
11
|
+
# xml:* inheritable attributes (xml:lang, xml:space) are also inherited
|
|
12
|
+
# from the nearest ancestor in which they are declared.
|
|
13
|
+
#
|
|
14
|
+
# Implementation: delegates to the canon-derived Processor pipeline
|
|
15
|
+
# (DataModel + NamespaceHandler + AttributeHandler + XmlBaseHandler).
|
|
16
|
+
class Inclusive10
|
|
17
|
+
# rubocop:disable Lint/UnusedMethodArgument -- signature must match Exclusive
|
|
18
|
+
def canonicalize(node_or_xml, with_comments: false, inclusive_namespaces: [])
|
|
19
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
|
20
|
+
root = coerce_to_data_model(node_or_xml)
|
|
21
|
+
Processor.new(with_comments: with_comments).process(root)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def coerce_to_data_model(node_or_xml)
|
|
27
|
+
return node_or_xml if node_or_xml.is_a?(Nodes::RootNode)
|
|
28
|
+
|
|
29
|
+
case node_or_xml
|
|
30
|
+
when ::Moxml::Document, ::Moxml::Node
|
|
31
|
+
DataModel.from_node(node_or_xml)
|
|
32
|
+
when String
|
|
33
|
+
DataModel.from_xml(node_or_xml)
|
|
34
|
+
else
|
|
35
|
+
raise ArgumentError,
|
|
36
|
+
"Inclusive10#canonicalize expects a Moxml::Node, " \
|
|
37
|
+
"Moxml::Document, or XML String; got #{node_or_xml.class}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
module C14n
|
|
5
|
+
# Canonical XML 1.1 (https://www.w3.org/TR/xml-c14n11/).
|
|
6
|
+
#
|
|
7
|
+
# In this implementation, Inclusive11 is a thin alias of Inclusive10.
|
|
8
|
+
# Both delegate to the canon-derived Processor. The W3C 1.1 additions
|
|
9
|
+
# (notations, entity references in DTD internal subset, XML 1.1 line
|
|
10
|
+
# ending) are rarely encountered in modern XML signature practice.
|
|
11
|
+
class Inclusive11
|
|
12
|
+
def canonicalize(node_or_xml, with_comments: false, inclusive_namespaces: [])
|
|
13
|
+
Inclusive10.new.canonicalize(
|
|
14
|
+
node_or_xml,
|
|
15
|
+
with_comments: with_comments,
|
|
16
|
+
inclusive_namespaces: inclusive_namespaces,
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
module C14n
|
|
5
|
+
# Tracks in-scope namespace URIs along the ancestor chain of the
|
|
6
|
+
# element currently being canonicalized. The canonicalizer pushes
|
|
7
|
+
# when entering an element and pops when leaving.
|
|
8
|
+
#
|
|
9
|
+
# `xml` prefix is always bound to the XML namespace per XML 1.0 spec.
|
|
10
|
+
class NamespaceContext
|
|
11
|
+
DEFAULT_XML_BINDING = { "xml" => C14n::XML_URI }.freeze
|
|
12
|
+
|
|
13
|
+
attr_reader :bindings
|
|
14
|
+
|
|
15
|
+
def initialize(initial = {})
|
|
16
|
+
@stack = []
|
|
17
|
+
@bindings = DEFAULT_XML_BINDING.dup
|
|
18
|
+
merge(initial)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def push(bindings_to_apply)
|
|
22
|
+
applied = {}
|
|
23
|
+
bindings_to_apply.each do |prefix, uri|
|
|
24
|
+
prev = @bindings[prefix]
|
|
25
|
+
applied[prefix] = prev
|
|
26
|
+
@bindings[prefix] = uri
|
|
27
|
+
end
|
|
28
|
+
@stack.push(applied)
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def pop
|
|
33
|
+
applied = @stack.pop
|
|
34
|
+
applied.each do |prefix, prev|
|
|
35
|
+
if prev.nil?
|
|
36
|
+
@bindings.delete(prefix)
|
|
37
|
+
@bindings[prefix] = C14n::XML_URI if prefix == "xml"
|
|
38
|
+
else
|
|
39
|
+
@bindings[prefix] = prev
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
self
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def uri_for(prefix)
|
|
46
|
+
@bindings[prefix]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def binding_for(prefix)
|
|
50
|
+
@bindings[prefix]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def include?(prefix)
|
|
54
|
+
@bindings.key?(prefix)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def to_h
|
|
58
|
+
@bindings.dup
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def merge(hash)
|
|
64
|
+
hash.each { |prefix, uri| @bindings[prefix] = uri }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
module C14n
|
|
5
|
+
# Namespace axis handler for inclusive C14N.
|
|
6
|
+
# Implements W3C C14N 1.0 §2.3 / 1.1 §2.3 namespace rendering rules
|
|
7
|
+
# for the document subset (full and partial).
|
|
8
|
+
class NamespaceHandler
|
|
9
|
+
attr_reader :encoder
|
|
10
|
+
|
|
11
|
+
def initialize(encoder)
|
|
12
|
+
@encoder = encoder
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def process_namespaces(element, output, parent_element = nil)
|
|
16
|
+
return unless element.in_node_set?
|
|
17
|
+
|
|
18
|
+
namespaces = element.sorted_namespace_nodes.select(&:in_node_set?)
|
|
19
|
+
|
|
20
|
+
if should_emit_empty_default_namespace?(element, namespaces, parent_element)
|
|
21
|
+
output << ' xmlns=""'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
namespaces.each do |ns|
|
|
25
|
+
next if should_skip_namespace?(ns, parent_element)
|
|
26
|
+
|
|
27
|
+
output << " "
|
|
28
|
+
output << (ns.default_namespace? ? "xmlns" : "xmlns:#{ns.prefix}")
|
|
29
|
+
output << '="'
|
|
30
|
+
output << encoder.encode_attribute(ns.uri)
|
|
31
|
+
output << '"'
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
# Emit xmlns="" if the element's nearest in-set ancestor had a non-empty
|
|
38
|
+
# default namespace and this element does not.
|
|
39
|
+
def should_emit_empty_default_namespace?(element, namespaces, parent_element)
|
|
40
|
+
return false unless element.in_node_set?
|
|
41
|
+
return false if namespaces.first&.default_namespace?
|
|
42
|
+
return false unless parent_element
|
|
43
|
+
|
|
44
|
+
parent_default_ns = parent_element.namespace_nodes.find do |ns|
|
|
45
|
+
ns.default_namespace? && ns.in_node_set?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
parent_default_ns && !parent_default_ns.uri.empty?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def should_skip_namespace?(namespace, parent_element)
|
|
52
|
+
# The xml namespace is implicit, never rendered.
|
|
53
|
+
return true if namespace.xml_namespace?
|
|
54
|
+
# Skip namespaces already declared (with same URI) by an ancestor.
|
|
55
|
+
return true if namespace_declared_by_ancestor?(namespace, parent_element)
|
|
56
|
+
|
|
57
|
+
false
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def namespace_declared_by_ancestor?(namespace, parent_element)
|
|
61
|
+
return false unless parent_element
|
|
62
|
+
|
|
63
|
+
parent_ns = parent_element.namespace_nodes.find do |candidate|
|
|
64
|
+
candidate.prefix == namespace.prefix && candidate.in_node_set?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
parent_ns && parent_ns.uri == namespace.uri
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
module C14n
|
|
5
|
+
# Base class for all C14N data-model nodes.
|
|
6
|
+
# Ported from canon ( lutaml/canon ) — keeps the mature node-set
|
|
7
|
+
# semantics needed for subset canonicalization (spec §3).
|
|
8
|
+
class Node
|
|
9
|
+
attr_reader :parent, :children
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@parent = nil
|
|
13
|
+
@children = []
|
|
14
|
+
@in_node_set = true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def add_child(child)
|
|
18
|
+
child.parent = self
|
|
19
|
+
@children << child
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def in_node_set?
|
|
23
|
+
@in_node_set
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def in_node_set=(value)
|
|
27
|
+
@in_node_set = value
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Return the text content of this node and all descendants.
|
|
31
|
+
# ElementNode concatenates children's text_content; other nodes
|
|
32
|
+
# (TextNode, CommentNode, etc.) return their value.
|
|
33
|
+
def text_content
|
|
34
|
+
children.map(&:text_content).join
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
protected
|
|
38
|
+
|
|
39
|
+
attr_writer :parent
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
module C14n
|
|
5
|
+
module Nodes
|
|
6
|
+
# Attribute node.
|
|
7
|
+
class AttributeNode < Node
|
|
8
|
+
attr_reader :name, :value, :namespace_uri, :prefix
|
|
9
|
+
|
|
10
|
+
def initialize(name:, value:, namespace_uri: nil, prefix: nil)
|
|
11
|
+
super()
|
|
12
|
+
@name = name
|
|
13
|
+
@value = value
|
|
14
|
+
@namespace_uri = namespace_uri
|
|
15
|
+
@prefix = prefix
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def node_type
|
|
19
|
+
:attribute
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def local_name
|
|
23
|
+
name
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def qname
|
|
27
|
+
prefix.nil? || prefix.empty? ? name : "#{prefix}:#{name}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# xml:* attributes (lang, space, base, id).
|
|
31
|
+
def xml_attribute?
|
|
32
|
+
namespace_uri == Moxml::C14n::XML_URI
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# xml:lang and xml:space are inheritable per C14N 1.1 §2.4.
|
|
36
|
+
def simple_inheritable?
|
|
37
|
+
xml_attribute? && %w[lang space].include?(name)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def xml_id?
|
|
41
|
+
xml_attribute? && name == "id"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def xml_base?
|
|
45
|
+
xml_attribute? && name == "base"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
module C14n
|
|
5
|
+
module Nodes
|
|
6
|
+
# Comment node.
|
|
7
|
+
class CommentNode < Node
|
|
8
|
+
attr_reader :value
|
|
9
|
+
|
|
10
|
+
def initialize(value:)
|
|
11
|
+
super()
|
|
12
|
+
@value = value
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def name
|
|
16
|
+
"comment"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def node_type
|
|
20
|
+
:comment
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def text_content
|
|
24
|
+
@value
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
module C14n
|
|
5
|
+
module Nodes
|
|
6
|
+
# Element node in the C14N data model.
|
|
7
|
+
class ElementNode < Node
|
|
8
|
+
attr_reader :name, :namespace_uri, :prefix, :namespace_nodes,
|
|
9
|
+
:attribute_nodes
|
|
10
|
+
|
|
11
|
+
def initialize(name:, namespace_uri: nil, prefix: nil)
|
|
12
|
+
super()
|
|
13
|
+
@name = name
|
|
14
|
+
@namespace_uri = namespace_uri
|
|
15
|
+
@prefix = prefix
|
|
16
|
+
@namespace_nodes = []
|
|
17
|
+
@attribute_nodes = []
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def node_type
|
|
21
|
+
:element
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def qname
|
|
25
|
+
prefix.nil? || prefix.empty? ? name : "#{prefix}:#{name}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def add_namespace(namespace_node)
|
|
29
|
+
namespace_node.parent = self
|
|
30
|
+
@namespace_nodes << namespace_node
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def add_attribute(attribute_node)
|
|
34
|
+
attribute_node.parent = self
|
|
35
|
+
@attribute_nodes << attribute_node
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Namespace nodes sorted lexicographically by local name (prefix).
|
|
39
|
+
# Per W3C C14N 1.0 §2.3 / 1.1 §2.3, default namespace sorts first.
|
|
40
|
+
def sorted_namespace_nodes
|
|
41
|
+
@namespace_nodes.sort_by { |ns| ns.local_name.to_s }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Attribute nodes sorted by namespace URI then local name
|
|
45
|
+
# (W3C C14N 1.0 §2.4 / 1.1 §2.4).
|
|
46
|
+
def sorted_attribute_nodes
|
|
47
|
+
@attribute_nodes.sort_by do |attr|
|
|
48
|
+
[attr.namespace_uri.to_s, attr.local_name]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def text_content
|
|
53
|
+
children.map(&:text_content).join
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def to_s
|
|
57
|
+
"<#{qname}>"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
module C14n
|
|
5
|
+
module Nodes
|
|
6
|
+
# Namespace node. Prefix is empty string for the default namespace.
|
|
7
|
+
class NamespaceNode < Node
|
|
8
|
+
attr_reader :prefix, :uri
|
|
9
|
+
|
|
10
|
+
def initialize(prefix:, uri:)
|
|
11
|
+
super()
|
|
12
|
+
@prefix = prefix
|
|
13
|
+
@uri = uri
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def name
|
|
17
|
+
prefix.to_s
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def node_type
|
|
21
|
+
:namespace
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Local name is the prefix (empty string for default namespace).
|
|
25
|
+
# Used by ElementNode#sorted_namespace_nodes for sort order.
|
|
26
|
+
def local_name
|
|
27
|
+
prefix.to_s
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def default_namespace?
|
|
31
|
+
prefix.nil? || prefix.empty?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# The `xml` namespace is implicit and never rendered.
|
|
35
|
+
def xml_namespace?
|
|
36
|
+
prefix == "xml" && uri == Moxml::C14n::XML_URI
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
module C14n
|
|
5
|
+
module Nodes
|
|
6
|
+
# Processing Instruction node.
|
|
7
|
+
class ProcessingInstructionNode < Node
|
|
8
|
+
attr_reader :target, :data
|
|
9
|
+
|
|
10
|
+
def initialize(target:, data: "")
|
|
11
|
+
super()
|
|
12
|
+
@target = target
|
|
13
|
+
@data = data
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def name
|
|
17
|
+
target
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def node_type
|
|
21
|
+
:processing_instruction
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def text_content
|
|
25
|
+
""
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
module C14n
|
|
5
|
+
module Nodes
|
|
6
|
+
# Root node representing the document root.
|
|
7
|
+
class RootNode < Node
|
|
8
|
+
def name
|
|
9
|
+
"#document"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def node_type
|
|
13
|
+
:root
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def children=(new_children)
|
|
17
|
+
@children = new_children
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
module C14n
|
|
5
|
+
module Nodes
|
|
6
|
+
# Text node. Stores the decoded value (entity refs resolved).
|
|
7
|
+
class TextNode < Node
|
|
8
|
+
attr_accessor :value
|
|
9
|
+
|
|
10
|
+
def initialize(value:)
|
|
11
|
+
super()
|
|
12
|
+
@value = value
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def name
|
|
16
|
+
"#text"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def node_type
|
|
20
|
+
:text
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def text_content
|
|
24
|
+
@value
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|