1"""`WhitespaceChecker`."""
2
3import logging
4
5from suricata_check.checkers.interface import CheckerInterface
6from suricata_check.utils.checker import (
7 is_rule_option_equal_to_regex,
8)
9from suricata_check.utils.checker_typing import ISSUES_TYPE, Issue, Rule
10from suricata_check.utils.regex import (
11 HEADER_REGEX,
12)
13from suricata_check.utils.regex_provider import get_regex_provider
14
15_regex_provider = get_regex_provider()
16
17# Regular expressions are placed here such that they are compiled only once.
18# This has a significant impact on the performance.
19REGEX_S100 = _regex_provider.compile(
20 rf"^(\s*#)?\s*{HEADER_REGEX.pattern}\s*\( .*\)\s*(#.*)?$",
21)
22REGEX_S101 = _regex_provider.compile(
23 rf"^(\s*#)?\s*{HEADER_REGEX.pattern}\s*\(.* \)\s*(#.*)?$",
24)
25REGEX_S102 = _regex_provider.compile(
26 rf"^(\s*#)?\s*{HEADER_REGEX.pattern}\s*\(.+ :.+\)\s*(#.*)?$",
27)
28REGEX_S103 = _regex_provider.compile(
29 rf"^(\s*#)?\s*{HEADER_REGEX.pattern}\s*\(.+: .+\)\s*(#.*)?$",
30)
31REGEX_S104 = _regex_provider.compile(
32 rf"^(\s*#)?\s*{HEADER_REGEX.pattern}\s*\(.+ ;.+\)\s*(#.*)?$",
33)
34REGEX_S105 = _regex_provider.compile(
35 rf"^(\s*#)?\s*{HEADER_REGEX.pattern}\s*\(.+; \s+.+\)\s*(#.*)?$",
36)
37REGEX_S106 = _regex_provider.compile(r'^".*\|.* .*\|.*"$')
38REGEX_S110 = _regex_provider.compile(
39 rf"^(\s*#)?\s*{HEADER_REGEX.pattern}\s*\(.+;(?! ).+\)\s*(#.*)?$",
40)
41REGEX_S111 = _regex_provider.compile(r'^".*\|.*[a-fA-F0-9]{4}.*\|.*"$')
42REGEX_S120 = _regex_provider.compile(
43 r'^"([^\|]*|(\|[\sa-zA-Z0-9]*\|))*(\\?[\x3a\x3b\x20\x27\x7b\x5c\x2f\x60\x24\x28\x29]+|\\[\x22\x7c]+)([^\|]*|(\|[\sa-zA-Z0-9]*\|))*"$',
44)
45REGEX_S121 = _regex_provider.compile(
46 r"^\"/.*(\\?[\x3a\x3b\x20\x22\x27\x2f\x60]+|\\[\x7b\x5c\x7c\x24\x28\x29]+).*/[ism]*\"$",
47)
48REGEX_S122 = _regex_provider.compile(r'^".*\\.*"$')
49REGEX_S123 = _regex_provider.compile(
50 r'^".*(?!\\(a|c[0-127]|e|f|n|r|t|0[0-9]{2}|[0-9]{3}|0\{[0-9]{3}\}|x[0-9a-f]{2}|x[0-9a-f]{3}|u[0-9a-f]{4}|d|D|h|H|s|S|v|V|w|W))(\\.).*"$'
51)
52
53
[docs]
54class WhitespaceChecker(CheckerInterface):
55 """The `WhitespaceChecker` contains several checks based on the Suricata Style guide relating to formatting rules.
56
57 Codes S100-S109 report on unneccessary whitespace that should be removed.
58
59 Codes S110-S119 report on missing whitespace that should be added.
60
61 Codes S120-S129 report on non-standard escaping of special characters.
62 """
63
64 codes = {
65 "S100": {"severity": logging.INFO},
66 "S101": {"severity": logging.INFO},
67 "S102": {"severity": logging.INFO},
68 "S103": {"severity": logging.INFO},
69 "S104": {"severity": logging.INFO},
70 "S105": {"severity": logging.INFO},
71 "S106": {"severity": logging.INFO},
72 "S110": {"severity": logging.INFO},
73 "S111": {"severity": logging.INFO},
74 "S120": {"severity": logging.INFO},
75 "S121": {"severity": logging.INFO},
76 "S122": {"severity": logging.INFO},
77 "S123": {"severity": logging.INFO},
78 }
79
80 def _check_rule( # noqa: C901, PLR0912
81 self: "WhitespaceChecker",
82 rule: Rule,
83 ) -> ISSUES_TYPE:
84 issues: ISSUES_TYPE = []
85
86 if (
87 REGEX_S100.match(
88 rule.raw.strip(),
89 )
90 is not None
91 ):
92 issues.append(
93 Issue(
94 code="S100",
95 message="""The rule contains unneccessary whitespace after opening the rule body with.
96Consider removing the unneccessary whitespace.""",
97 ),
98 )
99
100 if (
101 REGEX_S101.match(
102 rule.raw.strip(),
103 )
104 is not None
105 ):
106 issues.append(
107 Issue(
108 code="S101",
109 message="""The rule contains unneccessary whitespace before closing the rule body with.
110Consider removing the unneccessary whitespace.""",
111 ),
112 )
113
114 if (
115 REGEX_S102.match(
116 rule.raw.strip(),
117 )
118 is not None
119 ):
120 issues.append(
121 Issue(
122 code="S102",
123 message="""The rule contains unneccessary whitespace before the colon (:) after an option name.
124Consider removing the unneccessary whitespace.""",
125 ),
126 )
127
128 if (
129 REGEX_S103.match(
130 rule.raw.strip(),
131 )
132 is not None
133 ):
134 issues.append(
135 Issue(
136 code="S103",
137 message="""The rule contains unneccessary whitespace before the colon (:) after an option name.
138Consider removing the unneccessary whitespace.""",
139 ),
140 )
141
142 if (
143 REGEX_S104.match(
144 rule.raw.strip(),
145 )
146 is not None
147 ):
148 issues.append(
149 Issue(
150 code="S104",
151 message="""The rule contains unneccessary whitespace before the semicolon (;) after an option value.
152Consider removing the unneccessary whitespace.""",
153 ),
154 )
155
156 if (
157 REGEX_S105.match(
158 rule.raw.strip(),
159 )
160 is not None
161 ):
162 issues.append(
163 Issue(
164 code="S105",
165 message="""The rule contains more than one space between options after an option value.
166Consider replacing the unneccessary whitespace by a single space.""",
167 ),
168 )
169
170 if is_rule_option_equal_to_regex(
171 rule,
172 "content",
173 REGEX_S106,
174 ):
175 issues.append(
176 Issue(
177 code="S106",
178 message="""The rule contains more than one space between bytes in content.
179Consider replacing the unneccessary whitespace by a single space.""",
180 ),
181 )
182
183 if (
184 REGEX_S110.match(
185 rule.raw.strip(),
186 )
187 is not None
188 ):
189 issues.append(
190 Issue(
191 code="S110",
192 message="""The rule does not contain a space between the end of after an option value.
193Consider adding a single space.""",
194 ),
195 )
196
197 if is_rule_option_equal_to_regex(
198 rule,
199 "content",
200 REGEX_S111,
201 ):
202 issues.append(
203 Issue(
204 code="S111",
205 message="""The rule contains more than no spaces between bytes in content.
206Consider replacing adding a single space.""",
207 ),
208 )
209
210 if is_rule_option_equal_to_regex(
211 rule,
212 "content",
213 REGEX_S120,
214 ):
215 issues.append(
216 Issue(
217 code="S120",
218 message="""The rule did not escape \
219(\\x3a\\x3b\\x20\\x22\\x27\\x7b\\x7c\\x5c\\x2f\\x60\\x24\\x28\\x29) in a content field.
220Consider using hex encoding instead.""",
221 ),
222 )
223
224 if is_rule_option_equal_to_regex(
225 rule,
226 "pcre",
227 REGEX_S121,
228 ):
229 issues.append(
230 Issue(
231 code="S121",
232 message="""The rule did escape \
233(\\x3a\\x3b\\x20\\x22\\x27\\x7b\\x7c\\x5c\\x2f\\x60\\x24\\x28\\x29) in a pcre field.
234Consider using hex encoding instead.""",
235 ),
236 )
237
238 if is_rule_option_equal_to_regex(rule, "content", REGEX_S122):
239 issues.append(
240 Issue(
241 code="S122",
242 message="""The rule escaped special characters using a blackslash (\\) in a content field.
243Consider using hex encoding instead.""",
244 ),
245 )
246
247 if is_rule_option_equal_to_regex(rule, "pcre", REGEX_S123):
248 issues.append(
249 Issue(
250 code="S123",
251 message="""The rule escaped special characters using a blackslash (\\) in a pcre field.
252Consider using hex encoding instead.""",
253 ),
254 )
255
256 return issues