sparkql 1.3.4 → 1.3.5

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: 995514ae40305cedb2116dafc6c98b8efb534ae3d2f31c9d48e86421e3f5c10c
4
- data.tar.gz: 6162d29fb4520941edb2b53742d966fdf373418b2745f6fd1d8c1fa79432b896
3
+ metadata.gz: ee4b94747c3d671e7ceac9675caaebaf3d8661a4a9316327719e7221f439b3bc
4
+ data.tar.gz: '09a7eddd5cd21a23d2083c8312f0a1bec48a0f21938c0871538cdb08ef52d1d3'
5
5
  SHA512:
6
- metadata.gz: c39c06894d6a7b298d4fafb3c578e6d203543c77c68ad0cf8acb9ec943c00c656e38529a7e6b2afe4cc3177b5a59b078c7ce4c193a08dea0f575bd62ee0495d7
7
- data.tar.gz: fbbba23b099146c85f3c15fb82ea3bbc93fe989b156752a80137a2212b2ed64f4cf50ecb99bf06b59914f1e1f8996a65155c9cde11c025702e6b8504b083cc93
6
+ metadata.gz: 67b098969f33bf5eaa19277d3d202687c8ceddf4183d4f54cb703cdb7b5ed3aff934a201a3e238226a27bfcecce9b2c58e31e2ce64f76fa0f03babf23b980291
7
+ data.tar.gz: 42db5ca0692fca37b627562870641af084f539c64909fb8858e8bc872d4371d365621fe502cbba8289dbd0ea47e13cdc80df24c83172ebc3d88a92ec1291e64c
data/CHANGELOG.md CHANGED
@@ -1,4 +1,7 @@
1
- v1.3.4, 20265-01-20
1
+ v1.3.5, 2026-04-30
2
+ * [IMPROVEMENT] add date_date() function
3
+
4
+ v1.3.4, 2026-01-20
2
5
  * [BUGFIX] Validate limit for days and weekdays
3
6
 
4
7
  v1.3.3, 2025-08-12
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.3.4
1
+ 1.3.5
@@ -651,6 +651,10 @@ module Sparkql
651
651
  }
652
652
  end
653
653
 
654
+ def date_date(value)
655
+ date_datetime(value)
656
+ end
657
+
654
658
  def time_datetime(datetime)
655
659
  {
656
660
  type: :time,
@@ -0,0 +1,150 @@
1
+ require 'test_helper'
2
+
3
+ # Security regression tests covering SQL injection attack vectors identified
4
+ # during audit. The parser is responsible for rejecting malformed input and
5
+ # producing type-safe output. Consumers of parser output must still use
6
+ # parameterized queries and quote field identifiers — see comments below.
7
+ class SecurityTest < Test::Unit::TestCase
8
+ include Sparkql
9
+
10
+ def setup
11
+ @parser = Parser.new
12
+ end
13
+
14
+ # --- String value injection ---
15
+
16
+ test 'rejects unclosed string literals' do
17
+ assert_parse_error "City Eq 'Fargo"
18
+ assert_parse_error "City Eq Fargo'"
19
+ end
20
+
21
+ test 'rejects double-quote SQL injection in string values' do
22
+ # SparkQL only recognises \' as an escape sequence inside strings.
23
+ # A bare '' does not escape the quote — the lexer matches 'test' and
24
+ # the leftover 'injection' token causes a parse error.
25
+ assert_parse_error "City Eq 'test''injection'"
26
+ end
27
+
28
+ test 'string with backslash-escaped quote parses and escapes safely' do
29
+ # \' is the valid SparkQL escape for a literal apostrophe. :value retains
30
+ # outer quotes; character_escape strips them and unescapes \' to '.
31
+ # The resulting value contains a single quote — consumers MUST use bind
32
+ # parameters, not string interpolation, when building SQL from this value.
33
+ expressions = @parser.parse("City Eq 'O\\'Brien'")
34
+ assert !@parser.errors?, @parser.errors.inspect
35
+ assert_equal "O'Brien", @parser.character_escape(expressions.first[:value])
36
+ end
37
+
38
+ test 'sql payload inside valid sparkql string is accepted but value is raw' do
39
+ # The lexer correctly accepts 'val\'; DROP TABLE t; --' as a character
40
+ # literal (\' is a valid escape sequence). character_escape returns the raw
41
+ # string including the injected SQL — safe only with parameterized queries.
42
+ expressions = @parser.parse("City Eq 'val\\'; DROP TABLE t; --'")
43
+ assert !@parser.errors?, @parser.errors.inspect
44
+ assert_equal "val'; DROP TABLE t; --", @parser.character_escape(expressions.first[:value])
45
+ end
46
+
47
+ test 'plain string :value retains outer quotes before character_escape' do
48
+ expressions = @parser.parse("City Eq 'Fargo'")
49
+ assert !@parser.errors?, @parser.errors.inspect
50
+ assert_equal "'Fargo'", expressions.first[:value]
51
+ assert_equal :character, expressions.first[:type]
52
+ assert_equal 'Fargo', @parser.character_escape(expressions.first[:value])
53
+ end
54
+
55
+ # --- Numeric/decimal injection ---
56
+
57
+ test 'rejects sql keywords appended after integer token' do
58
+ assert_parse_error "Price Eq 100; DROP TABLE listings"
59
+ assert_parse_error "Price Eq 100 UNION SELECT"
60
+ end
61
+
62
+ test 'integer values are coerced to integers preventing non-numeric injection' do
63
+ expressions = @parser.parse("Price Eq 100")
64
+ assert !@parser.errors?, @parser.errors.inspect
65
+ assert_equal '100', expressions.first[:value]
66
+ assert_equal :integer, expressions.first[:type]
67
+ end
68
+
69
+ test 'decimal values are stored as strings preventing non-numeric injection' do
70
+ expressions = @parser.parse("Price Eq 100.50")
71
+ assert !@parser.errors?, @parser.errors.inspect
72
+ assert_equal '100.50', expressions.first[:value]
73
+ assert_equal :decimal, expressions.first[:type]
74
+ end
75
+
76
+ # --- Operator injection ---
77
+
78
+ test 'rejects sql operators that are not in the sparkql whitelist' do
79
+ assert_parse_error "City LIKE '%Fargo%'"
80
+ assert_parse_error "Price > 100"
81
+ assert_parse_error "Price < 100"
82
+ assert_parse_error "Price != 100"
83
+ end
84
+
85
+ test 'rejects sql keywords used as conjunctions' do
86
+ # OR/AND lowercased is valid SparkQL, but 1=1 is not a valid expression.
87
+ # Uppercase SQL keywords like UNION are rejected entirely.
88
+ assert_parse_error "City Eq 'Fargo' UNION SELECT City FROM listings"
89
+ assert_parse_error "City Eq 'Fargo' OR 1=1"
90
+ assert_parse_error "City Eq 'Fargo' AND 1=1"
91
+ end
92
+
93
+ # --- Function name injection ---
94
+
95
+ test 'rejects function names not in the supported functions whitelist' do
96
+ assert_parse_error "City Eq unknownfn('Fargo')"
97
+ assert_parse_error "City Eq exec('xp_cmdshell')"
98
+ assert_parse_error "City Eq sleep(10)"
99
+ end
100
+
101
+ # --- Custom field name handling ---
102
+
103
+ test 'custom field regex rejects names starting with dollar sign or period' do
104
+ assert_parse_error '"$BadGroup"."Field" Eq 10'
105
+ assert_parse_error '"Group"."$BadField" Eq 10'
106
+ assert_parse_error '"Group".".BadField" Eq 10'
107
+ assert_parse_error '"Group.Bad"."Field" Eq 10'
108
+ assert_parse_error '"Group"."Field.Sub" Eq 10'
109
+ assert_parse_error '""."" Eq 10'
110
+ end
111
+
112
+ test 'valid custom field names parse and field is returned verbatim' do
113
+ filter = '"General Property Description"."Taxes" Lt 500.0'
114
+ expressions = @parser.parse(filter)
115
+ assert !@parser.errors?, @parser.errors.inspect
116
+ assert expressions.first[:custom_field]
117
+ # The field value is returned as-is with its surrounding double-quotes.
118
+ # Consumers MUST validate this against allowed metadata and/or quote it
119
+ # as a SQL identifier before using it in a query — never interpolate directly.
120
+ assert_equal '"General Property Description"."Taxes"', expressions.first[:field]
121
+ end
122
+
123
+ test 'custom field name containing sql-significant characters is returned verbatim' do
124
+ # SparkQL allows characters like ; and ' inside double-quoted field names.
125
+ # The CUSTOM_FIELD regex does not strip these — that is the consumer's job.
126
+ filter = %("It's a field"."Value" Eq 10)
127
+ expressions = @parser.parse(filter)
128
+ assert !@parser.errors?, @parser.errors.inspect
129
+ assert expressions.first[:custom_field]
130
+ assert expressions.first[:field].include?("It's a field"), \
131
+ "Expected field to contain the group name, got: #{expressions.first[:field].inspect}"
132
+ end
133
+
134
+ # --- Date/datetime injection ---
135
+
136
+ test 'valid dates parse to their string representation' do
137
+ expressions = @parser.parse("DateField Eq 2023-06-15")
138
+ assert !@parser.errors?, @parser.errors.inspect
139
+ assert_equal '2023-06-15', expressions.first[:value]
140
+ assert_equal :date, expressions.first[:type]
141
+ end
142
+
143
+ private
144
+
145
+ def assert_parse_error(filter)
146
+ parser = Parser.new
147
+ parser.parse(filter)
148
+ assert parser.errors?, "Expected parse error for: #{filter.inspect}"
149
+ end
150
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sparkql
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.4
4
+ version: 1.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wade McEwen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-01-20 00:00:00.000000000 Z
11
+ date: 2026-05-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: georuby
@@ -155,6 +155,7 @@ files:
155
155
  - test/unit/lexer_test.rb
156
156
  - test/unit/parser_compatability_test.rb
157
157
  - test/unit/parser_test.rb
158
+ - test/unit/security_test.rb
158
159
  homepage: ''
159
160
  licenses:
160
161
  - Apache 2.0