@@ -37,6 +37,7 @@ pub(crate) trait LinterInternal {
37
37
pub ( crate ) enum LinterResult {
38
38
Ok ,
39
39
Warn ( Warning ) ,
40
+ Warns ( Vec < Warning > ) ,
40
41
Err ( CompileError ) ,
41
42
}
42
43
@@ -114,6 +115,145 @@ impl LinterInternal for RuleName {
114
115
115
116
type Predicate < ' a > = dyn Fn ( & Meta ) -> bool + ' a ;
116
117
118
+ /// A linter that ensures tags meet specified requirements in either an allowed
119
+ /// list of tags or in a regex.
120
+ ///
121
+ /// ```
122
+ /// # use yara_x::Compiler;
123
+ /// use yara_x::linters;
124
+ /// let mut compiler = Compiler::new();
125
+ /// let warnings = compiler
126
+ /// .add_linter(linters::tags_allowed(vec!["foo".to_string(), "bar".to_string()]))
127
+ /// // This produces a warning because the rule tags are not from the
128
+ /// // allowed list
129
+ /// .add_source(r#"rule foo : test { strings: $foo = "foo" condition: $foo }"#)
130
+ /// .unwrap()
131
+ /// .warnings();
132
+ ///
133
+ /// assert_eq!(
134
+ /// warnings[0].to_string(),
135
+ /// r#"warning[unknown_tag]: tag not in allowed list
136
+ /// --> line:1:12
137
+ /// |
138
+ /// 1 | rule foo : test { strings: $foo = "foo" condition: $foo }
139
+ /// | ---- tag `test` not in allowed list
140
+ /// |
141
+ /// = note: allowed tags: foo, bar"#);
142
+ pub struct Tags {
143
+ allow_list : Vec < String > ,
144
+ regex : Option < String > ,
145
+ compiled_regex : Option < Regex > ,
146
+ error : bool ,
147
+ }
148
+
149
+ impl Tags {
150
+ /// A list of strings that tags for each rule must match one of.
151
+ pub ( crate ) fn from_list ( list : Vec < String > ) -> Self {
152
+ Self {
153
+ allow_list : list,
154
+ regex : None ,
155
+ compiled_regex : None ,
156
+ error : false ,
157
+ }
158
+ }
159
+
160
+ /// Regular expression that tags for each rule must match.
161
+ pub ( crate ) fn from_regex < R : Into < String > > (
162
+ regex : R ,
163
+ ) -> Result < Self , regex:: Error > {
164
+ let regex = regex. into ( ) ;
165
+ let compiled_regex = Some ( Regex :: new ( regex. as_str ( ) ) ?) ;
166
+ let tags = Self {
167
+ allow_list : Vec :: new ( ) ,
168
+ regex : Some ( regex) ,
169
+ compiled_regex,
170
+ error : false ,
171
+ } ;
172
+ Ok ( tags)
173
+ }
174
+
175
+ /// Specifies whether the linter should produce an error instead of a
176
+ /// warning.
177
+ ///
178
+ /// By default, the linter raises warnings about tags that don't match the
179
+ /// regular expression. This setting allows turning such warnings into
180
+ /// errors.
181
+ pub fn error ( mut self , yes : bool ) -> Self {
182
+ self . error = yes;
183
+ self
184
+ }
185
+ }
186
+
187
+ impl LinterInternal for Tags {
188
+ fn check (
189
+ & self ,
190
+ report_builder : & ReportBuilder ,
191
+ rule : & ast:: Rule ,
192
+ ) -> LinterResult {
193
+ if rule. tags . is_none ( ) {
194
+ return LinterResult :: Ok ;
195
+ }
196
+
197
+ let mut results: Vec < Warning > = Vec :: new ( ) ;
198
+ let tags = rule. tags . as_ref ( ) . unwrap ( ) ;
199
+ if !self . allow_list . is_empty ( ) {
200
+ for tag in tags. iter ( ) {
201
+ if !self . allow_list . contains ( & tag. name . to_string ( ) ) {
202
+ if self . error {
203
+ return LinterResult :: Err ( errors:: UnknownTag :: build (
204
+ report_builder,
205
+ tag. span ( ) . into ( ) ,
206
+ tag. name . to_string ( ) ,
207
+ Some ( format ! (
208
+ "allowed tags: {}" ,
209
+ self . allow_list. join( ", " )
210
+ ) ) ,
211
+ ) ) ;
212
+ } else {
213
+ results. push ( warnings:: UnknownTag :: build (
214
+ report_builder,
215
+ tag. span ( ) . into ( ) ,
216
+ tag. name . to_string ( ) ,
217
+ Some ( format ! (
218
+ "allowed tags: {}" ,
219
+ self . allow_list. join( ", " )
220
+ ) ) ,
221
+ ) ) ;
222
+ }
223
+ }
224
+ }
225
+ } else {
226
+ let compiled_regex = self . compiled_regex . as_ref ( ) . unwrap ( ) ;
227
+
228
+ for tag in tags. iter ( ) {
229
+ if !compiled_regex. is_match ( tag. name ) {
230
+ if self . error {
231
+ return LinterResult :: Err ( errors:: InvalidTag :: build (
232
+ report_builder,
233
+ tag. span ( ) . into ( ) ,
234
+ tag. name . to_string ( ) ,
235
+ self . regex . as_ref ( ) . unwrap ( ) . clone ( ) ,
236
+ ) ) ;
237
+ } else {
238
+ results. push ( warnings:: InvalidTag :: build (
239
+ report_builder,
240
+ tag. span ( ) . into ( ) ,
241
+ tag. name . to_string ( ) ,
242
+ self . regex . as_ref ( ) . unwrap ( ) . clone ( ) ,
243
+ ) ) ;
244
+ }
245
+ }
246
+ }
247
+ }
248
+
249
+ if results. is_empty ( ) {
250
+ LinterResult :: Ok
251
+ } else {
252
+ LinterResult :: Warns ( results)
253
+ }
254
+ }
255
+ }
256
+
117
257
/// A linter that validates metadata entries.
118
258
///
119
259
/// ```
@@ -286,6 +426,17 @@ impl LinterInternal for Metadata<'_> {
286
426
}
287
427
}
288
428
429
+ /// Creates a tag linter from a list of allowed tags.
430
+ pub fn tags_allowed ( list : Vec < String > ) -> Tags {
431
+ Tags :: from_list ( list)
432
+ }
433
+
434
+ /// Creates a tag linter that makes sure that each tag matches the given regular
435
+ /// expression.
436
+ pub fn tag_regex < R : Into < String > > ( regex : R ) -> Result < Tags , Error > {
437
+ Tags :: from_regex ( regex)
438
+ }
439
+
289
440
/// Creates a linter that validates metadata entries.
290
441
///
291
442
/// See [`Metadata`] for details.
0 commit comments