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
|
@@ -125,6 +125,186 @@ RSpec.shared_examples "xml adapter" do
|
|
|
125
125
|
value = attr.respond_to?(:to_xml) ? attr&.to_xml : attr.to_s
|
|
126
126
|
expect(value).to match(/< > & ("|") ('|')/)
|
|
127
127
|
end
|
|
128
|
+
|
|
129
|
+
it "keeps attributes that share a local name across namespaces (issues #94/#95)" do
|
|
130
|
+
collision = described_class.parse(
|
|
131
|
+
'<probe xmlns:xmi="http://www.example.com/xmi" ' \
|
|
132
|
+
'xmi:type="uml:Parameter" type="EAnone_void"/>',
|
|
133
|
+
).native
|
|
134
|
+
probe = described_class.root(collision)
|
|
135
|
+
|
|
136
|
+
attrs = described_class.attributes(probe)
|
|
137
|
+
expect(attrs.size).to eq(2)
|
|
138
|
+
expect(attrs.map(&:value).sort).to eq(["EAnone_void", "uml:Parameter"])
|
|
139
|
+
|
|
140
|
+
reversed = described_class.parse(
|
|
141
|
+
'<probe type="EAnone_void" ' \
|
|
142
|
+
'xmlns:xmi="http://www.example.com/xmi" xmi:type="uml:Parameter"/>',
|
|
143
|
+
).native
|
|
144
|
+
expect(described_class.attributes(described_class.root(reversed)).size).to eq(2)
|
|
145
|
+
|
|
146
|
+
# Bare name resolves to the namespace-less attribute; the
|
|
147
|
+
# prefixed name to the namespaced one — on every adapter.
|
|
148
|
+
expect(described_class.get_attribute_value(probe, "type"))
|
|
149
|
+
.to eq("EAnone_void")
|
|
150
|
+
expect(described_class.get_attribute_value(probe, "xmi:type"))
|
|
151
|
+
.to eq("uml:Parameter")
|
|
152
|
+
expect(described_class.get_attribute_value(probe, "bogus:missing"))
|
|
153
|
+
.to be_nil
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
describe "mutation return contract" do
|
|
158
|
+
let(:doc) { described_class.parse(xml).native }
|
|
159
|
+
let(:element) { described_class.children(described_class.root(doc)).first }
|
|
160
|
+
|
|
161
|
+
it "returns the native to track from set_attribute_name" do
|
|
162
|
+
native = described_class.attributes(element).first
|
|
163
|
+
tracked = described_class.set_attribute_name(native, "renamed")
|
|
164
|
+
expect(tracked).to be_a(native.class)
|
|
165
|
+
expect(described_class.attribute_name(tracked)).to eq("renamed")
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it "returns the native to track from set_namespace on an attribute" do
|
|
169
|
+
native = described_class.attributes(element).first
|
|
170
|
+
ns = described_class.namespace_definitions(
|
|
171
|
+
described_class.root(doc),
|
|
172
|
+
).last
|
|
173
|
+
tracked = described_class.set_namespace(native, ns)
|
|
174
|
+
expect(tracked).to be_a(native.class)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
it "returns the native to track from set_attribute_value" do
|
|
178
|
+
native = described_class.attributes(element).first
|
|
179
|
+
tracked = described_class.set_attribute_value(native, "updated")
|
|
180
|
+
expect(tracked).to be_a(native.class)
|
|
181
|
+
expect(tracked.value).to eq("updated")
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
describe "tree mutation" do
|
|
186
|
+
let(:doc) { described_class.parse(xml).native }
|
|
187
|
+
let(:root) { described_class.root(doc) }
|
|
188
|
+
|
|
189
|
+
def names_of(node)
|
|
190
|
+
described_class.children(node)
|
|
191
|
+
.map { |child| described_class.node_name(child) }
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it "inserts previous and next siblings" do
|
|
195
|
+
target = described_class.children(root)[1]
|
|
196
|
+
described_class.add_previous_sibling(
|
|
197
|
+
target, described_class.create_element("before")
|
|
198
|
+
)
|
|
199
|
+
described_class.add_next_sibling(
|
|
200
|
+
target, described_class.create_element("after")
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
expect(names_of(root)).to eq(%w[child before child after special])
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
it "removes nodes from the tree" do
|
|
207
|
+
described_class.remove(described_class.children(root)[1])
|
|
208
|
+
expect(names_of(root)).to eq(%w[child special])
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
it "replaces a node in place" do
|
|
212
|
+
described_class.replace(
|
|
213
|
+
described_class.children(root)[1],
|
|
214
|
+
described_class.create_element("replacement"),
|
|
215
|
+
)
|
|
216
|
+
expect(names_of(root)).to eq(%w[child replacement special])
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
it "replaces all children" do
|
|
220
|
+
described_class.replace_children(
|
|
221
|
+
root, [described_class.create_element("only")]
|
|
222
|
+
)
|
|
223
|
+
expect(names_of(root)).to eq(%w[only])
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
describe "content accessors" do
|
|
228
|
+
let(:doc) { described_class.parse(xml).native }
|
|
229
|
+
let(:root) { described_class.root(doc) }
|
|
230
|
+
|
|
231
|
+
it "reads inner text from direct text children" do
|
|
232
|
+
child = described_class.children(root)[0]
|
|
233
|
+
expect(described_class.inner_text(child)).to eq("Text")
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
it "writes text content" do
|
|
237
|
+
element = described_class.create_element("scratch")
|
|
238
|
+
described_class.set_text_content(element, "written")
|
|
239
|
+
expect(described_class.inner_text(element)).to eq("written")
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
it "reads and writes comment content" do
|
|
243
|
+
comment = described_class.create_comment("note")
|
|
244
|
+
expect(described_class.comment_content(comment)).to eq("note")
|
|
245
|
+
described_class.set_comment_content(comment, "changed")
|
|
246
|
+
expect(described_class.comment_content(comment)).to eq("changed")
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
it "reads and writes CDATA content" do
|
|
250
|
+
cdata = described_class.create_cdata("<raw>")
|
|
251
|
+
expect(described_class.cdata_content(cdata)).to eq("<raw>")
|
|
252
|
+
described_class.set_cdata_content(cdata, "<new>")
|
|
253
|
+
expect(described_class.cdata_content(cdata)).to eq("<new>")
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
describe "auxiliary node creation" do
|
|
258
|
+
it "creates doctypes with external identifiers" do
|
|
259
|
+
doctype = described_class.create_doctype("cfg", "pub", "sys")
|
|
260
|
+
expect(described_class.node_type(doctype)).to eq(:doctype)
|
|
261
|
+
expect(described_class.doctype_name(doctype)).to eq("cfg")
|
|
262
|
+
expect(described_class.doctype_external_id(doctype)).to eq("pub")
|
|
263
|
+
expect(described_class.doctype_system_id(doctype)).to eq("sys")
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
it "creates declarations with version, encoding and standalone" do
|
|
267
|
+
declaration = described_class.create_declaration("1.0", "UTF-8", "yes")
|
|
268
|
+
expect(described_class.declaration_attribute(declaration, "version"))
|
|
269
|
+
.to eq("1.0")
|
|
270
|
+
expect(described_class.declaration_attribute(declaration, "standalone"))
|
|
271
|
+
.to eq("yes")
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
it "creates entity references by name" do
|
|
275
|
+
reference = described_class.create_entity_reference("copy")
|
|
276
|
+
expect(described_class.node_type(reference)).to eq(:entity_reference)
|
|
277
|
+
expect(described_class.entity_reference_name(reference)).to eq("copy")
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
it "duplicates nodes with their names" do
|
|
281
|
+
element = described_class.create_element("original")
|
|
282
|
+
duplicate = described_class.duplicate_node(element)
|
|
283
|
+
expect(described_class.node_name(duplicate)).to eq("original")
|
|
284
|
+
expect(duplicate).not_to equal(element)
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
describe "in-scope namespaces" do
|
|
289
|
+
let(:doc) { described_class.parse(xml).native }
|
|
290
|
+
let(:special) { described_class.children(described_class.root(doc)).last }
|
|
291
|
+
|
|
292
|
+
it "returns the ancestor-declared prefixes" do
|
|
293
|
+
prefixes = described_class.in_scope_namespaces(special)
|
|
294
|
+
.map { |ns| described_class.namespace_prefix(ns) }
|
|
295
|
+
# The default declaration is reported as a nil prefix everywhere
|
|
296
|
+
# except Ox, which spells it "xmlns"
|
|
297
|
+
expect(prefixes).to include("x")
|
|
298
|
+
expect(prefixes.size).to eq(2)
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
describe "line numbers" do
|
|
303
|
+
it "returns a 1-based position or nil" do
|
|
304
|
+
doc = described_class.parse(xml).native
|
|
305
|
+
line = described_class.line_number(described_class.root(doc))
|
|
306
|
+
expect(line.nil? || (line.is_a?(Integer) && line >= 1)).to be(true)
|
|
307
|
+
end
|
|
128
308
|
end
|
|
129
309
|
|
|
130
310
|
describe "namespaces" do
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Moxml::AttributeResolver do
|
|
6
|
+
let(:context) { Moxml.new }
|
|
7
|
+
let(:doc) do
|
|
8
|
+
context.parse(<<~XML)
|
|
9
|
+
<root xmlns:p="http://x.org" xmlns:q="http://x.org">
|
|
10
|
+
<e q:type="namespaced" type="bare"/>
|
|
11
|
+
</root>
|
|
12
|
+
XML
|
|
13
|
+
end
|
|
14
|
+
let(:element) { doc.root.children.find(&:element?) }
|
|
15
|
+
|
|
16
|
+
describe ".resolve" do
|
|
17
|
+
it "matches bare names against no-namespace attributes only" do
|
|
18
|
+
expect(described_class.resolve(element, "type")&.value).to eq("bare")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "matches prefixed names by expanded name" do
|
|
22
|
+
expect(described_class.resolve(element, "p:type")&.value)
|
|
23
|
+
.to eq("namespaced")
|
|
24
|
+
expect(described_class.resolve(element, "q:type")&.value)
|
|
25
|
+
.to eq("namespaced")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "returns nil for undeclared prefixes and declarations" do
|
|
29
|
+
expect(described_class.resolve(element, "z:type")).to be_nil
|
|
30
|
+
expect(described_class.resolve(element, "xmlns:q")).to be_nil
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe ".assign" do
|
|
35
|
+
it "delegates xmlns declarations to the adapter" do
|
|
36
|
+
described_class.assign(element, "xmlns:r", "http://r.org")
|
|
37
|
+
expect(doc.to_xml).to include('xmlns:r="http://r.org"')
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "replaces the matching attribute in place" do
|
|
41
|
+
described_class.assign(element, "p:type", "rewritten")
|
|
42
|
+
expect(element["q:type"]).to eq("rewritten")
|
|
43
|
+
expect(element.attributes.size).to eq(2)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "stores undeclared prefixes raw until they come into scope" do
|
|
47
|
+
described_class.assign(element, "z:type", "value")
|
|
48
|
+
expect(element["z:type"]).to be_nil
|
|
49
|
+
|
|
50
|
+
element.add_namespace("z", "http://z.org")
|
|
51
|
+
expect(element["z:type"]).to eq("value")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
describe ".remove" do
|
|
56
|
+
it "removes by expanded name and returns the attribute" do
|
|
57
|
+
removed = described_class.remove(element, "p:type")
|
|
58
|
+
expect(removed.value).to eq("namespaced")
|
|
59
|
+
expect(element["q:type"]).to be_nil
|
|
60
|
+
expect(element["type"]).to eq("bare")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "returns nil when nothing matches" do
|
|
64
|
+
expect(described_class.remove(element, "z:type")).to be_nil
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe ".attribute_test?" do
|
|
69
|
+
let(:attributes) { element.attributes }
|
|
70
|
+
|
|
71
|
+
it "applies Namespaces 1.0 §5.2 to bare names" do
|
|
72
|
+
bare = attributes.find { |a| a.value == "bare" }
|
|
73
|
+
namespaced = attributes.find { |a| a.value == "namespaced" }
|
|
74
|
+
expect(described_class.attribute_test?(element, bare, nil, "type"))
|
|
75
|
+
.to be(true)
|
|
76
|
+
expect(described_class.attribute_test?(element, namespaced, nil, "type"))
|
|
77
|
+
.to be(false)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "matches prefixed tests by URI and accepts any local for nil" do
|
|
81
|
+
namespaced = attributes.find { |a| a.value == "namespaced" }
|
|
82
|
+
expect(described_class.attribute_test?(element, namespaced, "p", "type"))
|
|
83
|
+
.to be(true)
|
|
84
|
+
expect(described_class.attribute_test?(element, namespaced, "p", nil))
|
|
85
|
+
.to be(true)
|
|
86
|
+
expect(described_class.attribute_test?(element, namespaced, "z", "type"))
|
|
87
|
+
.to be(false)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "treats :any as any namespace including none" do
|
|
91
|
+
bare = attributes.find { |a| a.value == "bare" }
|
|
92
|
+
expect(described_class.attribute_test?(element, bare, :any, "type"))
|
|
93
|
+
.to be(true)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
describe ".attribute_test_uri?" do
|
|
98
|
+
it "matches the compile-time URI regardless of document prefixes" do
|
|
99
|
+
namespaced = element.attributes.find { |a| a.value == "namespaced" }
|
|
100
|
+
expect(described_class.attribute_test_uri?(
|
|
101
|
+
element, namespaced, "http://x.org", "type"
|
|
102
|
+
)).to be(true)
|
|
103
|
+
expect(described_class.attribute_test_uri?(
|
|
104
|
+
element, namespaced, "http://other.org", "type"
|
|
105
|
+
)).to be(false)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "moxml/c14n"
|
|
5
|
+
|
|
6
|
+
RSpec.describe "Moxml::C14n API" do
|
|
7
|
+
let(:ctx) { Moxml.new(:nokogiri) }
|
|
8
|
+
|
|
9
|
+
describe ".canonicalize (algorithm selector)" do
|
|
10
|
+
it "defaults to inclusive C14N 1.0" do
|
|
11
|
+
doc = ctx.parse("<root xmlns:foo='urn:foo'><foo:a/></root>")
|
|
12
|
+
default_result = Moxml::C14n.canonicalize(doc.root)
|
|
13
|
+
incl10_result = Moxml::C14n.canonicalize(doc.root, algorithm: :inclusive10)
|
|
14
|
+
expect(default_result).to eq(incl10_result)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "raises ArgumentError for unknown algorithm" do
|
|
18
|
+
expect do
|
|
19
|
+
Moxml::C14n.canonicalize("<x/>", algorithm: :bogus)
|
|
20
|
+
end.to raise_error(ArgumentError, /unknown C14N algorithm/)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "accepts inclusive_11 algorithm" do
|
|
24
|
+
doc = ctx.parse("<root/>")
|
|
25
|
+
result = Moxml::C14n.canonicalize(doc.root, algorithm: :inclusive11)
|
|
26
|
+
expect(result).to eq("<root></root>")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "accepts exclusive_10 algorithm" do
|
|
30
|
+
doc = ctx.parse("<root xmlns:foo='urn:foo'><foo:a/></root>")
|
|
31
|
+
result = Moxml::C14n.canonicalize(doc.root, algorithm: :exclusive10)
|
|
32
|
+
# Exclusive renders ns where visibly used (on foo:a), not on root
|
|
33
|
+
expect(result).to include('<foo:a xmlns:foo="urn:foo">')
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe ".equivalent?" do
|
|
38
|
+
it "returns true for byte-identical inputs" do
|
|
39
|
+
expect(Moxml::C14n).to be_equivalent("<root>x</root>", "<root>x</root>")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "returns true when only attribute whitespace differs" do
|
|
43
|
+
# Whitespace inside the tag is not significant — both parse identically.
|
|
44
|
+
a = %(<root attr="value"/>)
|
|
45
|
+
b = %(<root attr="value" />)
|
|
46
|
+
expect(Moxml::C14n).to be_equivalent(a, b)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "returns false when text whitespace differs (text IS significant)" do
|
|
50
|
+
a = "<root><child>a</child> </root>"
|
|
51
|
+
b = "<root> <child>a</child></root>"
|
|
52
|
+
expect(Moxml::C14n).not_to be_equivalent(a, b)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "returns true when only attribute order differs" do
|
|
56
|
+
a = %(<root a="1" b="2"/>)
|
|
57
|
+
b = %(<root b="2" a="1"/>)
|
|
58
|
+
expect(Moxml::C14n).to be_equivalent(a, b)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "returns true when only namespace prefix differs" do
|
|
62
|
+
a = "<root xmlns:foo='urn:foo'><foo:a/></root>"
|
|
63
|
+
b = "<root xmlns:bar='urn:foo'><bar:a/></root>"
|
|
64
|
+
# Inclusive canonical form: prefix is part of qname → not equivalent
|
|
65
|
+
expect(Moxml::C14n).not_to be_equivalent(a, b)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "returns false for different content" do
|
|
69
|
+
expect(Moxml::C14n).not_to be_equivalent("<root>a</root>", "<root>b</root>")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "respects algorithm argument" do
|
|
73
|
+
a = "<root xmlns:foo='urn:foo'><foo:a/></root>"
|
|
74
|
+
b = "<root><a xmlns:foo='urn:foo'/></root>"
|
|
75
|
+
# Inclusive: ns on root vs on child → different
|
|
76
|
+
expect(Moxml::C14n).not_to be_equivalent(a, b, algorithm: :inclusive10)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "accepts Moxml::Node inputs" do
|
|
80
|
+
doc1 = ctx.parse("<root/>")
|
|
81
|
+
doc2 = ctx.parse("<root></root>")
|
|
82
|
+
expect(Moxml::C14n).to be_equivalent(doc1.root, doc2.root)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
describe "convenience methods" do
|
|
87
|
+
it ".canonicalize_inclusive10 matches default" do
|
|
88
|
+
doc = ctx.parse("<root/>")
|
|
89
|
+
expect(Moxml::C14n.canonicalize_inclusive10(doc.root))
|
|
90
|
+
.to eq(Moxml::C14n.canonicalize(doc.root))
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it ".canonicalize_inclusive11 produces output" do
|
|
94
|
+
doc = ctx.parse("<root/>")
|
|
95
|
+
expect(Moxml::C14n.canonicalize_inclusive11(doc.root)).to eq("<root></root>")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it ".canonicalize_exclusive accepts inclusive_namespaces" do
|
|
99
|
+
doc = ctx.parse("<root xmlns:foo='urn:foo'/>")
|
|
100
|
+
result = Moxml::C14n.canonicalize_exclusive(doc.root, inclusive_namespaces: ["foo"])
|
|
101
|
+
# Even though foo is not visibly used, inclusive list forces render
|
|
102
|
+
expect(result).to include('xmlns:foo="urn:foo"')
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
describe "escape helpers (backward compat)" do
|
|
107
|
+
it ".escape_text escapes & < >" do
|
|
108
|
+
expect(Moxml::C14n.escape_text("a & b < c > d"))
|
|
109
|
+
.to eq("a & b < c > d")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it ".escape_attribute escapes quotes and whitespace" do
|
|
113
|
+
expect(Moxml::C14n.escape_attribute(%(a"b\tc\nd)))
|
|
114
|
+
.to eq(%(a"b	c
d))
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "moxml/signature"
|
|
5
|
+
|
|
6
|
+
RSpec.describe "Moxml C14N engine" do
|
|
7
|
+
describe Moxml::C14n::Exclusive do
|
|
8
|
+
let(:ctx) { Moxml.new(:nokogiri) }
|
|
9
|
+
let(:canon) { described_class.new }
|
|
10
|
+
|
|
11
|
+
it "renders a simple element without namespaces" do
|
|
12
|
+
doc = ctx.parse("<root>hello</root>")
|
|
13
|
+
expect(canon.canonicalize(doc.root)).to eq("<root>hello</root>")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "preserves nested element structure" do
|
|
17
|
+
doc = ctx.parse("<root><child>text</child></root>")
|
|
18
|
+
expect(canon.canonicalize(doc.root)).to eq("<root><child>text</child></root>")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "renders declared namespaces" do
|
|
22
|
+
doc = ctx.parse(<<~XML.strip)
|
|
23
|
+
<root xmlns:foo="http://example.com/foo"><foo:child>x</foo:child></root>
|
|
24
|
+
XML
|
|
25
|
+
result = canon.canonicalize(doc.root)
|
|
26
|
+
# Exclusive C14N renders the namespace on the element where it's visibly
|
|
27
|
+
# used (foo:child), not on ancestors that don't use it.
|
|
28
|
+
expect(result).to include('<foo:child xmlns:foo="http://example.com/foo">')
|
|
29
|
+
expect(result).to include("</foo:child>")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "excludes ancestor-inherited namespaces when not visibly used (exclusive)" do
|
|
33
|
+
doc = ctx.parse(<<~XML.strip)
|
|
34
|
+
<outer xmlns:unused="http://example.com/unused"><inner>text</inner></outer>
|
|
35
|
+
XML
|
|
36
|
+
inner = doc.at_xpath("//inner")
|
|
37
|
+
result = canon.canonicalize(inner)
|
|
38
|
+
expect(result).to eq("<inner>text</inner>")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "escapes special characters in text content" do
|
|
42
|
+
doc = ctx.parse("<root>a<b>c&d</root>")
|
|
43
|
+
expect(canon.canonicalize(doc.root)).to eq("<root>a<b>c&d</root>")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "escapes special characters in attribute values" do
|
|
47
|
+
doc = ctx.parse(%(<root attr="a&b"c"/>))
|
|
48
|
+
expect(canon.canonicalize(doc.root))
|
|
49
|
+
.to include(%(attr="a&b"c"))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "sorts attributes by namespace URI then local name" do
|
|
53
|
+
doc = ctx.parse(%(<root b="2" a="1" c="3"/>))
|
|
54
|
+
result = canon.canonicalize(doc.root)
|
|
55
|
+
expect(result).to include('a="1" b="2" c="3"')
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "omits comments by default" do
|
|
59
|
+
doc = ctx.parse("<root>a<!-- comment -->b</root>")
|
|
60
|
+
expect(canon.canonicalize(doc.root)).to eq("<root>ab</root>")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "includes comments when with_comments is true" do
|
|
64
|
+
doc = ctx.parse("<root>a<!-- comment -->b</root>")
|
|
65
|
+
expect(canon.canonicalize(doc.root, with_comments: true))
|
|
66
|
+
.to eq("<root>a<!-- comment -->b</root>")
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe ".escape_text" do
|
|
71
|
+
it "escapes &, <, >" do
|
|
72
|
+
expect(Moxml::C14n.escape_text("a & b < c > d"))
|
|
73
|
+
.to eq("a & b < c > d")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "escapes bare CR as 
" do
|
|
77
|
+
expect(Moxml::C14n.escape_text("a\rb"))
|
|
78
|
+
.to eq("a
b")
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
describe ".escape_attribute" do
|
|
83
|
+
it "escapes quotes and tabs/newlines" do
|
|
84
|
+
expect(Moxml::C14n.escape_attribute(%(a"b\tc\nd)))
|
|
85
|
+
.to eq(%(a"b	c
d))
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "moxml/c14n"
|
|
5
|
+
|
|
6
|
+
# Edge cases for comment and processing-instruction handling per
|
|
7
|
+
# W3C C14N 1.0 §2.6, §2.7.
|
|
8
|
+
RSpec.describe "C14N comments and PIs" do
|
|
9
|
+
let(:ctx) { Moxml.new(:nokogiri) }
|
|
10
|
+
|
|
11
|
+
describe "comments inside elements" do
|
|
12
|
+
it "omits comments by default" do
|
|
13
|
+
doc = ctx.parse("<root>a<!-- c -->b</root>")
|
|
14
|
+
expect(Moxml::C14n.canonicalize(doc.root)).to eq("<root>ab</root>")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "includes comments when with_comments: true" do
|
|
18
|
+
doc = ctx.parse("<root>a<!-- c -->b</root>")
|
|
19
|
+
expect(Moxml::C14n.canonicalize(doc.root, with_comments: true))
|
|
20
|
+
.to eq("<root>a<!-- c -->b</root>")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "preserves comment content verbatim" do
|
|
24
|
+
doc = ctx.parse("<root><!-- & <stuff> --></root>")
|
|
25
|
+
expect(Moxml::C14n.canonicalize(doc.root, with_comments: true))
|
|
26
|
+
.to eq("<root><!-- & <stuff> --></root>")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe "processing instructions" do
|
|
31
|
+
it "always renders PIs (not affected by with_comments)" do
|
|
32
|
+
doc = ctx.parse("<root><?pi data?>x</root>")
|
|
33
|
+
expect(Moxml::C14n.canonicalize(doc.root))
|
|
34
|
+
.to eq("<root><?pi data?>x</root>")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "renders PI without data when data is empty" do
|
|
38
|
+
doc = ctx.parse("<root><?pi?>x</root>")
|
|
39
|
+
expect(Moxml::C14n.canonicalize(doc.root))
|
|
40
|
+
.to eq("<root><?pi?>x</root>")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "preserves PI target and data verbatim" do
|
|
44
|
+
doc = ctx.parse(%(<root><?xml-stylesheet href="a.css" type="text/css"?>x</root>))
|
|
45
|
+
expect(Moxml::C14n.canonicalize(doc.root))
|
|
46
|
+
.to include(%(<?xml-stylesheet href="a.css" type="text/css"?>))
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe "PIs at document level" do
|
|
51
|
+
# TODO.c14n/09: Document-level PIs (outside the document element) are
|
|
52
|
+
# not yet rendered correctly. The moxml PI parser mis-parses document-level
|
|
53
|
+
# PIs, and the DataModel.from_document path drops them. Fixing requires
|
|
54
|
+
# adapter-level work and is tracked separately.
|
|
55
|
+
it "renders PIs outside the document element (KNOWN LIMITATION)" do
|
|
56
|
+
skip "document-level PI rendering not yet implemented"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe "comments at document level" do
|
|
61
|
+
it "renders document-level comments when with_comments: true (KNOWN LIMITATION)" do
|
|
62
|
+
skip "document-level comment rendering not yet implemented"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "moxml/signature"
|
|
5
|
+
|
|
6
|
+
RSpec.describe Moxml::C14n::Inclusive10 do
|
|
7
|
+
let(:ctx) { Moxml.new(:nokogiri) }
|
|
8
|
+
let(:canon) { described_class.new }
|
|
9
|
+
|
|
10
|
+
it "renders a simple element" do
|
|
11
|
+
doc = ctx.parse("<root>hello</root>")
|
|
12
|
+
expect(canon.canonicalize(doc.root)).to eq("<root>hello</root>")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "attracts ancestor namespaces at the apex (the key inclusive behavior)" do
|
|
16
|
+
xml = <<~XML.strip
|
|
17
|
+
<outer xmlns:foo="http://example.com/foo" xmlns:bar="http://example.com/bar">
|
|
18
|
+
<inner x="1"><foo:child>hello</foo:child></inner>
|
|
19
|
+
</outer>
|
|
20
|
+
XML
|
|
21
|
+
doc = ctx.parse(xml)
|
|
22
|
+
inner = doc.at_xpath("//*[local-name()='inner']")
|
|
23
|
+
result = canon.canonicalize(inner)
|
|
24
|
+
# Both inherited namespaces are rendered on <inner>, even though
|
|
25
|
+
# <inner> itself does not visibly use them. This is the inclusive
|
|
26
|
+
# "ancestor attraction" behavior.
|
|
27
|
+
expect(result).to include('xmlns:bar="http://example.com/bar"')
|
|
28
|
+
expect(result).to include('xmlns:foo="http://example.com/foo"')
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "renders each namespace declaration only once per chain" do
|
|
32
|
+
xml = <<~XML.strip
|
|
33
|
+
<root xmlns:a="http://example.com/a">
|
|
34
|
+
<child xmlns:a="http://example.com/a"><grand/></child>
|
|
35
|
+
</root>
|
|
36
|
+
XML
|
|
37
|
+
doc = ctx.parse(xml)
|
|
38
|
+
result = canon.canonicalize(doc.root)
|
|
39
|
+
expect(result.scan('xmlns:a="http://example.com/a"').count).to eq(1)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "re-renders a namespace when re-declared with a different URI" do
|
|
43
|
+
xml = <<~XML.strip
|
|
44
|
+
<root xmlns:a="http://example.com/a">
|
|
45
|
+
<child xmlns:a="http://example.com/other"><grand/></child>
|
|
46
|
+
</root>
|
|
47
|
+
XML
|
|
48
|
+
doc = ctx.parse(xml)
|
|
49
|
+
result = canon.canonicalize(doc.root)
|
|
50
|
+
expect(result.scan("xmlns:a=").count).to eq(2)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "renders the default namespace at apex when inherited" do
|
|
54
|
+
xml = '<outer xmlns="http://example.com"><inner/></outer>'
|
|
55
|
+
doc = ctx.parse(xml)
|
|
56
|
+
inner = doc.at_xpath("//*[local-name()='inner']")
|
|
57
|
+
result = canon.canonicalize(inner)
|
|
58
|
+
expect(result).to include('xmlns="http://example.com"')
|
|
59
|
+
end
|
|
60
|
+
end
|