pySigma-backend-sqlite 0.1.2__tar.gz → 0.2.0__tar.gz

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.
@@ -1,19 +1,19 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pySigma-backend-sqlite
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: pySigma sqlite backend
5
5
  Home-page: https://github.com/SigmaHQ/pySigma-backend-sqlite
6
6
  License: LGPL-3.0-only
7
7
  Author: wagga
8
- Requires-Python: >=3.8,<4.0
8
+ Requires-Python: >=3.9,<4.0
9
9
  Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
10
10
  Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.8
12
11
  Classifier: Programming Language :: Python :: 3.9
13
12
  Classifier: Programming Language :: Python :: 3.10
14
13
  Classifier: Programming Language :: Python :: 3.11
15
14
  Classifier: Programming Language :: Python :: 3.12
16
- Requires-Dist: pysigma (>=0.11,<0.12)
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Requires-Dist: pysigma (>=0.11.17,<0.12.0)
17
17
  Project-URL: Repository, https://github.com/SigmaHQ/pySigma-backend-sqlite
18
18
  Description-Content-Type: text/markdown
19
19
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pySigma-backend-sqlite"
3
- version = "0.1.2"
3
+ version = "0.2.0"
4
4
  description = "pySigma sqlite backend"
5
5
  readme = "README.md"
6
6
  authors = ["wagga"]
@@ -11,8 +11,8 @@ packages = [
11
11
  ]
12
12
 
13
13
  [tool.poetry.dependencies]
14
- python = "^3.8"
15
- pysigma = "^0.11"
14
+ python = "^3.9"
15
+ pysigma = "^0.11.17"
16
16
 
17
17
  [tool.poetry.group.dev.dependencies]
18
18
  black = "^24.1"
@@ -36,12 +36,13 @@ authors = [
36
36
  {name = "wagga", email = "wagga"},
37
37
  ]
38
38
  license = {text = "LGPL-3.0-only"}
39
- requires-python = ">=3.8,<4.0"
39
+ requires-python = ">=3.9,<4.0"
40
40
  dependencies = [
41
- "pysigma>=0.11",
41
+ "pysigma>=0.11.17",
42
42
  ]
43
+
43
44
  name = "pySigma-backend-sqlite"
44
- version = "0.1.2"
45
+ version = "0.2.0"
45
46
  description = "pySigma sqlite backend"
46
47
 
47
48
  [project.urls]
@@ -74,23 +74,21 @@ class sqliteBackend(TextQueryBackend):
74
74
  True # Negate field_quote_pattern result. Field name is quoted if pattern doesn't matches if set to True (default).
75
75
  )
76
76
 
77
- ### Escaping
78
- # field_escape : ClassVar[str] = "\\" # Character to escape particular parts defined in field_escape_pattern.
79
- # field_escape_quote : ClassVar[bool] = True # Escape quote string defined in field_quote
80
- # field_escape_pattern : ClassVar[Pattern] = re.compile("\\s") # All matches of this pattern are prepended with the string contained in field_escape.
81
-
82
77
  ## Values
83
78
  str_quote: ClassVar[str] = (
84
79
  "'" # string quoting character (added as escaping character)
85
80
  )
86
- # str_quote_pattern: ClassVar[Pattern] = re.compile(r"^.*%{1,}.*|.*_{1,}.*$")
87
- # str_quote_pattern_negation: ClassVar[bool] = True
88
81
 
89
82
  escape_char: ClassVar[str] = (
90
83
  "\\" # Escaping character for special characters inside string
91
84
  )
92
85
  wildcard_multi: ClassVar[str] = "%" # Character used as multi-character wildcard
93
86
  wildcard_single: ClassVar[str] = "_" # Character used as single-character wildcard
87
+
88
+ # Special case for case sensitive string matching
89
+ wildcard_glob : ClassVar[str] = "*" # Character used as glob wildcard
90
+ wildcard_glob_single : ClassVar[str] = "?" # Character used as glob wildcard
91
+
94
92
  add_escaped: ClassVar[str] = (
95
93
  "\\" # Characters quoted in addition to wildcards and string quote
96
94
  )
@@ -110,6 +108,8 @@ class sqliteBackend(TextQueryBackend):
110
108
  "{field} LIKE '{value}' ESCAPE '\\'" # Special expression if wildcards can't be matched with the eq_token operator
111
109
  )
112
110
 
111
+ field_exists_expression: ClassVar[str] = "{field} = {field}"
112
+
113
113
  # Special expression if wildcards can't be matched with the eq_token operator
114
114
  wildcard_match_str_expression: ClassVar[str] = "{field} LIKE '{value}' ESCAPE '\\'"
115
115
  # wildcard_match_num_expression: ClassVar[str] = "{field} LIKE '%{value}%'"
@@ -134,6 +134,8 @@ class sqliteBackend(TextQueryBackend):
134
134
  # remove it from re_flags or don't define it to ensure proper error handling in case of appearance.
135
135
  # re_flags : Dict[SigmaRegularExpressionFlag, str] = {}
136
136
 
137
+ case_sensitive_match_expression: ClassVar[str] = "{field} GLOB {value} ESCAPE '\\'"
138
+
137
139
  # Numeric comparison operators
138
140
  compare_op_expression: ClassVar[str] = (
139
141
  "{field} {operator} {value}" # Compare operation query as format string with placeholders {field}, {operator} and {value}
@@ -199,16 +201,26 @@ class sqliteBackend(TextQueryBackend):
199
201
  table = "<TABLE_NAME>"
200
202
 
201
203
  def convert_value_str(
202
- self, s: SigmaString, state: ConversionState, no_quote: bool = False
204
+ self, s: SigmaString, state: ConversionState, no_quote: bool = False, glob_wildcards: bool = False
203
205
  ) -> str:
204
206
  """Convert a SigmaString into a plain string which can be used in query."""
205
- converted = s.convert(
206
- self.escape_char,
207
- self.wildcard_multi,
208
- self.wildcard_single,
209
- self.add_escaped,
210
- self.filter_chars,
211
- )
207
+
208
+ if glob_wildcards:
209
+ converted = s.convert(
210
+ escape_char=self.escape_char,
211
+ wildcard_multi=self.wildcard_glob,
212
+ wildcard_single=self.wildcard_glob_single,
213
+ add_escaped=self.add_escaped,
214
+ filter_chars=self.filter_chars,
215
+ )
216
+ else:
217
+ converted = s.convert(
218
+ escape_char=self.escape_char,
219
+ wildcard_multi=self.wildcard_multi,
220
+ wildcard_single=self.wildcard_single,
221
+ add_escaped=self.add_escaped,
222
+ filter_chars=self.filter_chars,
223
+ )
212
224
 
213
225
  converted = converted.replace(
214
226
  "'", "''"
@@ -286,6 +298,63 @@ class sqliteBackend(TextQueryBackend):
286
298
  "Field equals string value expressions with strings are not supported by the backend."
287
299
  )
288
300
 
301
+ def convert_condition_field_eq_val_str_case_sensitive(
302
+ self, cond: ConditionFieldEqualsValueExpression, state: ConversionState
303
+ ) -> Union[str, DeferredQueryExpression]:
304
+ """Conversion of case-sensitive field = string value expressions"""
305
+ try:
306
+ if ( # Check conditions for usage of 'startswith' operator
307
+ self.case_sensitive_startswith_expression
308
+ is not None # 'startswith' operator is defined in backend
309
+ and cond.value.endswith(SpecialChars.WILDCARD_MULTI) # String ends with wildcard
310
+ and (
311
+ self.case_sensitive_startswith_expression_allow_special
312
+ or not cond.value[:-1].contains_special()
313
+ ) # Remainder of string doesn't contains special characters or it's allowed
314
+ ):
315
+ expr = (
316
+ self.case_sensitive_startswith_expression
317
+ ) # If all conditions are fulfilled, use 'startswith' operator instead of equal token
318
+ value = cond.value[:-1]
319
+ elif ( # Same as above but for 'endswith' operator: string starts with wildcard and doesn't contains further special characters
320
+ self.case_sensitive_endswith_expression is not None
321
+ and cond.value.startswith(SpecialChars.WILDCARD_MULTI)
322
+ and (
323
+ self.case_sensitive_endswith_expression_allow_special
324
+ or not cond.value[1:].contains_special()
325
+ )
326
+ ):
327
+ expr = self.case_sensitive_endswith_expression
328
+ value = cond.value[1:]
329
+ elif ( # contains: string starts and ends with wildcard
330
+ self.case_sensitive_contains_expression is not None
331
+ and cond.value.startswith(SpecialChars.WILDCARD_MULTI)
332
+ and cond.value.endswith(SpecialChars.WILDCARD_MULTI)
333
+ and (
334
+ self.case_sensitive_contains_expression_allow_special
335
+ or not cond.value[1:-1].contains_special()
336
+ )
337
+ ):
338
+ expr = self.case_sensitive_contains_expression
339
+ value = cond.value[1:-1]
340
+ elif self.case_sensitive_match_expression is not None:
341
+ expr = self.case_sensitive_match_expression
342
+ value = cond.value
343
+ else:
344
+ raise NotImplementedError(
345
+ "Case-sensitive string matching is not supported by backend."
346
+ )
347
+
348
+ return expr.format(
349
+ field=self.escape_and_quote_field(cond.field),
350
+ value=self.convert_value_str(value, state, no_quote=False, glob_wildcards=True),
351
+ regex=self.convert_value_re(value.to_regex(self.add_escaped_re), state),
352
+ )
353
+ except TypeError: # pragma: no cover
354
+ raise NotImplementedError(
355
+ "Case-sensitive field equals string value expressions with strings are not supported by the backend."
356
+ )
357
+
289
358
  def convert_condition_field_eq_val_cidr(
290
359
  self, cond: ConditionFieldEqualsValueExpression, state: ConversionState
291
360
  ) -> Union[str, DeferredQueryExpression]: