1use std::ops::Range;
3
4use nom::branch::alt;
5use nom::bytes::complete::take_till1;
6use nom::character::complete::char;
7use nom::combinator::map;
8use nom::sequence::delimited;
9use nom::Parser;
10use nom::{combinator::cut, sequence::preceded};
11
12use crate::rule::Rule;
13
14use super::rule::rule;
15use super::{
16 nom_recipes::{ltrim, rtrim, textual_tag as ttag},
17 types::{Input, ParseResult},
18};
19
20#[derive(Clone, Debug, PartialEq)]
22pub struct YaraFile {
23 pub components: Vec<YaraFileComponent>,
30}
31
32#[derive(Clone, Debug, PartialEq)]
34pub enum YaraFileComponent {
35 Rule(Box<Rule>),
37 Import(Import),
39 Include(Include),
41}
42
43#[derive(Clone, Debug, PartialEq, Eq)]
45pub struct Import {
46 pub name: String,
48 pub span: Range<usize>,
50}
51
52#[derive(Clone, Debug, PartialEq, Eq)]
54pub struct Include {
55 pub path: String,
57 pub span: Range<usize>,
59}
60
61pub(crate) fn parse_yara_file(input: Input) -> ParseResult<YaraFile> {
68 let (mut input, ()) = ltrim(input)?;
69
70 let mut file = YaraFile {
71 components: Vec::new(),
72 };
73 while !input.is_empty() {
74 let (i, component) = alt((
75 map(include_file, YaraFileComponent::Include),
76 map(import, YaraFileComponent::Import),
77 map(rule, |r| YaraFileComponent::Rule(Box::new(r))),
78 ))
79 .parse(input)?;
80 file.components.push(component);
81 input = i;
82 }
83
84 Ok((input, file))
85}
86
87fn include_file(input: Input) -> ParseResult<Include> {
89 let start = input.pos();
90
91 let (input, path) = rtrim(preceded(
92 rtrim(ttag("include")),
93 cut(delimited(
94 char('"'),
95 map(take_till1(|c| c == '"'), |v: Input| v.to_string()),
96 char('"'),
97 )),
98 ))(input)?;
99
100 Ok((
101 input,
102 Include {
103 path,
104 span: input.get_span_from(start),
105 },
106 ))
107}
108
109fn import(input: Input) -> ParseResult<Import> {
111 let start = input.pos();
112
113 let (input, name) = rtrim(preceded(
114 rtrim(ttag("import")),
115 cut(delimited(
116 char('"'),
117 map(take_till1(|c| c == '"'), |v: Input| v.to_string()),
118 char('"'),
119 )),
120 ))(input)?;
121
122 Ok((
123 input,
124 Import {
125 name,
126 span: input.get_span_from(start),
127 },
128 ))
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::expression::{Expression, ExpressionKind};
135 use crate::test_helpers::{parse, parse_err, test_public_type};
136
137 #[test]
138 fn test_parse_yara_file() {
139 parse(
140 parse_yara_file,
141 " global rule c { condition: false }",
142 "",
143 YaraFile {
144 components: vec![YaraFileComponent::Rule(Box::new(Rule {
145 name: "c".to_owned(),
146 name_span: 14..15,
147 condition: Expression {
148 expr: ExpressionKind::Boolean(false),
149 span: 29..34,
150 },
151 tags: Vec::new(),
152 metadatas: Vec::new(),
153 variables: Vec::new(),
154 is_private: false,
155 is_global: true,
156 }))],
157 },
158 );
159
160 parse(
161 parse_yara_file,
162 r#" import "pe"
163 global rule c { condition: false }
164 import "foo"
165 import "quux"
166 rule d { condition: true }
167 "#,
168 "",
169 YaraFile {
170 components: vec![
171 YaraFileComponent::Import(Import {
172 name: "pe".to_owned(),
173 span: 1..12,
174 }),
175 YaraFileComponent::Rule(Box::new(Rule {
176 name: "c".to_owned(),
177 name_span: 41..42,
178 condition: Expression {
179 expr: ExpressionKind::Boolean(false),
180 span: 56..61,
181 },
182 tags: Vec::new(),
183 metadatas: Vec::new(),
184 variables: Vec::new(),
185 is_private: false,
186 is_global: true,
187 })),
188 YaraFileComponent::Import(Import {
189 name: "foo".to_owned(),
190 span: 80..92,
191 }),
192 YaraFileComponent::Import(Import {
193 name: "quux".to_owned(),
194 span: 109..122,
195 }),
196 YaraFileComponent::Rule(Box::new(Rule {
197 name: "d".to_owned(),
198 name_span: 144..145,
199 condition: Expression {
200 expr: ExpressionKind::Boolean(true),
201 span: 159..163,
202 },
203 tags: Vec::new(),
204 metadatas: Vec::new(),
205 variables: Vec::new(),
206 is_private: false,
207 is_global: false,
208 })),
209 ],
210 },
211 );
212 parse(parse_yara_file, "", "", YaraFile { components: vec![] });
213 parse(
214 parse_yara_file,
215 " /* removed */ ",
216 "",
217 YaraFile { components: vec![] },
218 );
219 parse(
220 parse_yara_file,
221 "include \"v\"\ninclude\"i\"",
222 "",
223 YaraFile {
224 components: vec![
225 YaraFileComponent::Include(Include {
226 path: "v".to_owned(),
227 span: 0..11,
228 }),
229 YaraFileComponent::Include(Include {
230 path: "i".to_owned(),
231 span: 12..22,
232 }),
233 ],
234 },
235 );
236
237 parse_err(parse_yara_file, "rule");
238 parse_err(parse_yara_file, "rule a { condition: true } b");
239 parse_err(parse_yara_file, " /*");
240 }
241
242 #[test]
243 fn test_public_types() {
244 test_public_type(
245 parse_yara_file(Input::new(
246 r#"
247import "a"
248include "b"
249
250rule a { condition: true }
251"#,
252 ))
253 .unwrap(),
254 );
255 }
256}