rxerces 0.6.1 → 0.7.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.
data/spec/xpath_spec.rb CHANGED
@@ -28,6 +28,7 @@ RSpec.describe "XPath support" do
28
28
  end
29
29
 
30
30
  let(:doc) { RXerces::XML::Document.parse(xml) }
31
+ let(:xalan_installed) { have_library('xalan-c') }
31
32
 
32
33
  describe "Document XPath queries" do
33
34
  it "finds all book elements" do
@@ -108,8 +109,28 @@ RSpec.describe "XPath support" do
108
109
  it "raises error for invalid XPath" do
109
110
  expect {
110
111
  doc.xpath('//[invalid')
112
+ }.to raise_error(ArgumentError, /XPath expression has unbalanced/)
113
+ end
114
+
115
+ it "raises error for malformed XPath expressions" do
116
+ expect {
117
+ doc.xpath('///')
118
+ }.to raise_error(RuntimeError, /XPath error/)
119
+ end
120
+
121
+ it "raises error for XPath with unsupported features" do
122
+ skip "Xalan installed, skipping" if xalan_installed
123
+ expect {
124
+ doc.xpath('//book[substring-before(@category, "c")]')
111
125
  }.to raise_error(RuntimeError, /XPath error/)
112
126
  end
127
+
128
+ it "handles very long XPath expressions" do
129
+ skip "Xalan installed, skipping" if xalan_installed
130
+ long_xpath = '/' + ('child::' * 100) + 'library'
131
+ result = doc.xpath(long_xpath)
132
+ expect(result).to be_a(RXerces::XML::NodeSet)
133
+ end
113
134
  end
114
135
 
115
136
  describe "Nokogiri compatibility" do
@@ -141,24 +162,7 @@ RSpec.describe "XPath support" do
141
162
  end
142
163
  end
143
164
 
144
- describe "XPath 1.0 compliance with Xalan" do
145
- # Check if Xalan support is compiled in
146
- xalan_available = begin
147
- # Try a feature that only works with Xalan (attribute predicates)
148
- test_xml = '<root><item id="1">A</item><item id="2">B</item></root>'
149
- test_doc = RXerces::XML::Document.parse(test_xml)
150
- result = test_doc.xpath('//item[@id="1"]')
151
- result.length == 1
152
- rescue
153
- false
154
- end
155
-
156
- before(:all) do
157
- unless xalan_available
158
- skip "Xalan-C not available - XPath 1.0 features require Xalan-C library"
159
- end
160
- end
161
-
165
+ describe "XPath 1.0 compliance with Xalan", xalan: true do
162
166
  describe "Attribute predicates" do
163
167
  it "finds elements by attribute value" do
164
168
  book = doc.xpath('//book[@id="1"]')
@@ -395,4 +399,288 @@ RSpec.describe "XPath support" do
395
399
  end
396
400
  end
397
401
  end
402
+
403
+ describe "XPath Injection Prevention" do
404
+ let(:simple_xml) do
405
+ <<-XML
406
+ <users>
407
+ <user id="1">
408
+ <name>Alice</name>
409
+ <password>secret123</password>
410
+ </user>
411
+ <user id="2">
412
+ <name>Bob</name>
413
+ <password>admin456</password>
414
+ </user>
415
+ </users>
416
+ XML
417
+ end
418
+
419
+ let(:doc) { RXerces::XML::Document.parse(simple_xml) }
420
+
421
+ describe "validates empty XPath expressions" do
422
+ it "rejects empty string" do
423
+ expect {
424
+ doc.xpath('')
425
+ }.to raise_error(ArgumentError, /cannot be empty/)
426
+ end
427
+ end
428
+
429
+ describe "validates quote balancing" do
430
+ it "rejects unbalanced single quotes" do
431
+ expect {
432
+ doc.xpath("//user[text()='Alice]")
433
+ }.to raise_error(ArgumentError, /unbalanced quotes/)
434
+ end
435
+
436
+ it "rejects unbalanced double quotes" do
437
+ expect {
438
+ doc.xpath('//user[text()="Alice]')
439
+ }.to raise_error(ArgumentError, /unbalanced quotes/)
440
+ end
441
+
442
+ it "allows properly balanced quotes" do
443
+ expect {
444
+ doc.xpath("//user")
445
+ }.not_to raise_error
446
+ end
447
+
448
+ it "allows mixed balanced quotes" do
449
+ expect {
450
+ doc.xpath('//user')
451
+ }.not_to raise_error
452
+ end
453
+ end
454
+
455
+ describe "prevents XPath injection attacks" do
456
+ it "rejects OR-based injection with numeric equality" do
457
+ expect {
458
+ doc.xpath("//user[@name='Alice' or 1=1]")
459
+ }.to raise_error(ArgumentError, /suspicious injection pattern/)
460
+ end
461
+
462
+ it "rejects OR-based injection with string equality" do
463
+ expect {
464
+ doc.xpath("//user[@name='Alice' or 'a'='a']")
465
+ }.to raise_error(ArgumentError, /suspicious injection pattern/)
466
+ end
467
+
468
+ it "rejects OR-based injection with double quotes" do
469
+ expect {
470
+ doc.xpath('//user[@name="Alice" or "1"="1"]')
471
+ }.to raise_error(ArgumentError, /suspicious injection pattern/)
472
+ end
473
+
474
+ it "rejects OR-based injection with true() function" do
475
+ expect {
476
+ doc.xpath("//user[@name='Alice' or true()]")
477
+ }.to raise_error(ArgumentError, /suspicious injection pattern/)
478
+ end
479
+
480
+ it "rejects AND-based injection with false condition" do
481
+ expect {
482
+ doc.xpath("//user[@name='Alice' and 1=0]")
483
+ }.to raise_error(ArgumentError, /suspicious injection pattern/)
484
+ end
485
+
486
+ it "rejects AND-based injection with false() function" do
487
+ expect {
488
+ doc.xpath("//user[@name='Alice' and false()]")
489
+ }.to raise_error(ArgumentError, /suspicious injection pattern/)
490
+ end
491
+
492
+ it "is case-insensitive for injection patterns" do
493
+ expect {
494
+ doc.xpath("//user[@name='Alice' OR 1=1]")
495
+ }.to raise_error(ArgumentError, /suspicious injection pattern/)
496
+ end
497
+ end
498
+
499
+ describe "prevents dangerous function calls" do
500
+ it "rejects document() function" do
501
+ expect {
502
+ doc.xpath("document('file.xml')//user")
503
+ }.to raise_error(ArgumentError, /dangerous function/)
504
+ end
505
+
506
+ it "rejects doc() function" do
507
+ expect {
508
+ doc.xpath("doc('file.xml')//user")
509
+ }.to raise_error(ArgumentError, /dangerous function/)
510
+ end
511
+
512
+ it "rejects collection() function" do
513
+ expect {
514
+ doc.xpath("collection('files')//user")
515
+ }.to raise_error(ArgumentError, /dangerous function/)
516
+ end
517
+
518
+ it "rejects unparsed-text() function" do
519
+ expect {
520
+ doc.xpath("unparsed-text('/etc/passwd')")
521
+ }.to raise_error(ArgumentError, /dangerous function/)
522
+ end
523
+
524
+ it "rejects system-property() function" do
525
+ expect {
526
+ doc.xpath("system-property('java.version')")
527
+ }.to raise_error(ArgumentError, /dangerous function/)
528
+ end
529
+
530
+ it "rejects environment-variable() function" do
531
+ expect {
532
+ doc.xpath("environment-variable('PATH')")
533
+ }.to raise_error(ArgumentError, /dangerous function/)
534
+ end
535
+ end
536
+
537
+ describe "prevents encoded character attacks" do
538
+ it "rejects numeric character references" do
539
+ expect {
540
+ doc.xpath("//user[@name='&#65;lice']")
541
+ }.to raise_error(ArgumentError, /encoded characters/)
542
+ end
543
+
544
+ it "rejects hexadecimal character references" do
545
+ expect {
546
+ doc.xpath("//user[@name='&#x41;lice']")
547
+ }.to raise_error(ArgumentError, /encoded characters/)
548
+ end
549
+
550
+ it "rejects double-encoded entity references" do
551
+ expect {
552
+ doc.xpath("//user[@name='&amp;#65;lice']")
553
+ }.to raise_error(ArgumentError, /encoded characters/)
554
+ end
555
+
556
+ it "allows legitimate ampersand usage in text" do
557
+ # Should not raise - legitimate use of & in string literals
558
+ xml = "<root><item name='Q&amp;A'>text</item></root>"
559
+ test_doc = RXerces::XML::Document.parse(xml)
560
+ expect { test_doc.xpath("//item") }.not_to raise_error
561
+ end
562
+ end
563
+
564
+ describe "prevents comment-based attacks" do
565
+ it "rejects XPath comments" do
566
+ expect {
567
+ doc.xpath("//user(: comment :)[@id='1']")
568
+ }.to raise_error(ArgumentError, /comment patterns/)
569
+ end
570
+
571
+ it "rejects partial comment syntax" do
572
+ expect {
573
+ doc.xpath("//user(:[@id='1']")
574
+ }.to raise_error(ArgumentError, /comment patterns/)
575
+ end
576
+ end
577
+
578
+ describe "prevents null byte injection" do
579
+ it "rejects expressions with null bytes" do
580
+ xpath_with_null = "//user" + "\x00" + "[@id='1']"
581
+ expect {
582
+ doc.xpath(xpath_with_null)
583
+ }.to raise_error(ArgumentError, /null byte/)
584
+ end
585
+ end
586
+
587
+ describe "prevents excessive nesting attacks (DoS)" do
588
+ it "rejects deeply nested brackets" do
589
+ nested = "//user" + ("[." * 101) + ("]" * 101)
590
+ expect {
591
+ doc.xpath(nested)
592
+ }.to raise_error(ArgumentError, /excessive nesting/)
593
+ end
594
+
595
+ it "rejects deeply nested parentheses" do
596
+ nested = "count(" * 101 + "//user" + ")" * 101
597
+ expect {
598
+ doc.xpath(nested)
599
+ }.to raise_error(ArgumentError, /excessive nesting/)
600
+ end
601
+
602
+ it "rejects unbalanced opening brackets" do
603
+ expect {
604
+ doc.xpath("//user[[[@id='1']")
605
+ }.to raise_error(ArgumentError, /unbalanced/)
606
+ end
607
+
608
+ it "rejects unbalanced closing brackets" do
609
+ expect {
610
+ doc.xpath("//user[@id='1']]")
611
+ }.to raise_error(ArgumentError, /unbalanced/)
612
+ end
613
+
614
+ it "rejects unbalanced parentheses" do
615
+ expect {
616
+ doc.xpath("count(//user")
617
+ }.to raise_error(ArgumentError, /unbalanced/)
618
+ end
619
+
620
+ it "allows reasonable nesting depth" do
621
+ nested = "//users/user/name"
622
+ expect {
623
+ doc.xpath(nested)
624
+ }.not_to raise_error
625
+ end
626
+ end
627
+
628
+ describe "prevents DoS via excessive length" do
629
+ it "rejects excessively long XPath expressions" do
630
+ # Create an expression over 10000 characters
631
+ long_part = "/user" * 2001 # Each part is ~5 chars, 2001*5 > 10000
632
+ long_xpath = "//users" + long_part
633
+ expect {
634
+ doc.xpath(long_xpath)
635
+ }.to raise_error(ArgumentError, /too long/)
636
+ end
637
+
638
+ it "allows reasonably long expressions" do
639
+ reasonable = "//users/user/name"
640
+ expect {
641
+ doc.xpath(reasonable)
642
+ }.not_to raise_error
643
+ end
644
+ end
645
+
646
+ describe "allows safe XPath expressions" do
647
+ it "allows simple path expressions" do
648
+ expect {
649
+ result = doc.xpath('//user')
650
+ expect(result.length).to eq(2)
651
+ }.not_to raise_error
652
+ end
653
+
654
+ it "allows descendant paths" do
655
+ expect {
656
+ result = doc.xpath('//name')
657
+ expect(result.length).to eq(2)
658
+ }.not_to raise_error
659
+ end
660
+
661
+ it "allows child paths" do
662
+ expect {
663
+ result = doc.xpath('/users/user')
664
+ expect(result.length).to eq(2)
665
+ }.not_to raise_error
666
+ end
667
+ end
668
+
669
+ describe "validates node XPath queries with injection prevention" do
670
+ it "prevents injection in node context" do
671
+ root = doc.root
672
+ expect {
673
+ root.xpath("//user[@name='Alice' or 1=1]")
674
+ }.to raise_error(ArgumentError, /suspicious injection pattern/)
675
+ end
676
+
677
+ it "allows safe node queries" do
678
+ root = doc.root
679
+ expect {
680
+ result = root.xpath('.//user')
681
+ expect(result.length).to eq(2)
682
+ }.not_to raise_error
683
+ end
684
+ end
685
+ end
398
686
  end
@@ -0,0 +1,20 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>CFBundleDevelopmentRegion</key>
6
+ <string>English</string>
7
+ <key>CFBundleIdentifier</key>
8
+ <string>com.apple.xcode.dsym.rxerces.bundle</string>
9
+ <key>CFBundleInfoDictionaryVersion</key>
10
+ <string>6.0</string>
11
+ <key>CFBundlePackageType</key>
12
+ <string>dSYM</string>
13
+ <key>CFBundleSignature</key>
14
+ <string>????</string>
15
+ <key>CFBundleShortVersionString</key>
16
+ <string>1.0</string>
17
+ <key>CFBundleVersion</key>
18
+ <string>1</string>
19
+ </dict>
20
+ </plist>
@@ -0,0 +1,5 @@
1
+ ---
2
+ triple: 'arm64-apple-darwin'
3
+ binary-path: rxerces.bundle
4
+ relocations: []
5
+ ...
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rxerces
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel J. Berger
@@ -78,6 +78,20 @@ dependencies:
78
78
  - - "~>"
79
79
  - !ruby/object:Gem::Version
80
80
  version: '3.12'
81
+ - !ruby/object:Gem::Dependency
82
+ name: mkmf-lite
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: 0.7.5
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: 0.7.5
81
95
  description: |2
82
96
  A Ruby XML library with Nokogiri-compatible API, powered by Xerces-C
83
97
  instead of libxml2. It also optionally uses Xalan for Xpath 1.0 compliance.
@@ -99,12 +113,17 @@ files:
99
113
  - benchmarks/serialization_benchmark.rb
100
114
  - benchmarks/traversal_benchmark.rb
101
115
  - benchmarks/xpath_benchmark.rb
116
+ - benchmarks/xpath_validation_cache_benchmark.rb
117
+ - benchmarks/xpath_validation_micro_benchmark.rb
102
118
  - certs/djberg96_pub.pem
119
+ - e
103
120
  - examples/basic_usage.rb
104
121
  - examples/schema_example.rb
105
122
  - examples/simple_example.rb
106
123
  - examples/xpath_example.rb
107
124
  - ext/rxerces/extconf.rb
125
+ - ext/rxerces/rxerces.bundle.dSYM/Contents/Info.plist
126
+ - ext/rxerces/rxerces.bundle.dSYM/Contents/Resources/Relocations/aarch64/rxerces.bundle.yml
108
127
  - ext/rxerces/rxerces.cpp
109
128
  - ext/rxerces/rxerces.h
110
129
  - lib/rxerces.rb
@@ -120,9 +139,10 @@ files:
120
139
  - spec/rxerces_spec.rb
121
140
  - spec/schema_spec.rb
122
141
  - spec/spec_helper.rb
142
+ - spec/xpath_cache_spec.rb
123
143
  - spec/xpath_spec.rb
124
- - tmp/arm64-darwin24/rxerces/3.4.7/rxerces.bundle.dSYM/Contents/Info.plist
125
- - tmp/arm64-darwin24/rxerces/3.4.7/rxerces.bundle.dSYM/Contents/Resources/Relocations/aarch64/rxerces.bundle.yml
144
+ - tmp/arm64-darwin24/rxerces/3.4.8/rxerces.bundle.dSYM/Contents/Info.plist
145
+ - tmp/arm64-darwin24/rxerces/3.4.8/rxerces.bundle.dSYM/Contents/Resources/Relocations/aarch64/rxerces.bundle.yml
126
146
  homepage: http://github.com/djberg96/rxerces
127
147
  licenses:
128
148
  - MIT
@@ -150,7 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
150
170
  - !ruby/object:Gem::Version
151
171
  version: '0'
152
172
  requirements: []
153
- rubygems_version: 3.7.2
173
+ rubygems_version: 3.6.9
154
174
  specification_version: 4
155
175
  summary: Nokogiri-compatible XML library using Xerces-C
156
176
  test_files:
@@ -161,4 +181,5 @@ test_files:
161
181
  - spec/nokogiri_compatibility_spec.rb
162
182
  - spec/rxerces_spec.rb
163
183
  - spec/schema_spec.rb
184
+ - spec/xpath_cache_spec.rb
164
185
  - spec/xpath_spec.rb
metadata.gz.sig CHANGED
Binary file