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