1use std::{
2 collections::{BTreeMap, BTreeSet},
3 fmt,
4 path::{Component, Path, PathBuf},
5};
6
7use crate::{ServerBlockKey, Token, lex, normalize_key};
8
9#[derive(Clone, Debug, Default, Eq, PartialEq)]
11pub struct ServerBlock {
12 keys: Vec<String>,
13 key_sources: Vec<Token>,
14 pub directives: BTreeMap<String, Vec<Token>>,
15}
16
17impl ServerBlock {
18 pub fn keys(&self) -> &[String] {
20 &self.keys
21 }
22
23 pub fn set_default_dns_port(&mut self, port: u16) {
25 for key in &mut self.keys {
26 let host = key.strip_prefix("dns://").unwrap_or(key);
27 if host.ends_with(':')
28 || host.contains("://")
29 || host
30 .rsplit_once(':')
31 .is_some_and(|(_, port)| port.parse::<i64>().is_ok())
32 {
33 continue;
34 }
35 key.push_str(&format!(":{port}"));
36 }
37 }
38
39 pub fn normalize_keys(&self) -> Result<Vec<ServerBlockKey>, Error> {
41 Ok(self
42 .normalized_key_sources()?
43 .into_iter()
44 .map(|(key, _)| key)
45 .collect())
46 }
47
48 pub(crate) fn normalized_key_sources(&self) -> Result<Vec<(ServerBlockKey, &Token)>, Error> {
49 let mut normalized = Vec::new();
50 for (key, source) in self.keys.iter().zip(&self.key_sources) {
51 normalized.extend(
52 normalize_key(key)
53 .map_err(|message| Error {
54 file: source.file.clone(),
55 line: source.line,
56 message,
57 })?
58 .into_iter()
59 .map(|key| (key, source)),
60 );
61 }
62 Ok(normalized)
63 }
64}
65
66#[derive(Clone, Debug, Eq, PartialEq)]
68pub struct Error {
69 pub file: String,
70 pub line: usize,
71 pub message: String,
72}
73
74impl fmt::Display for Error {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 write!(f, "{}:{}: {}", self.file, self.line, self.message)
77 }
78}
79
80impl std::error::Error for Error {}
81
82pub(crate) fn new_line(previous: &Token, current: &Token) -> bool {
83 previous.file != current.file
84 || previous.line + previous.text.matches('\n').count() < current.line
85}
86
87fn clean_path(path: &Path) -> PathBuf {
88 let mut clean = PathBuf::new();
89 for component in path.components() {
90 if component == Component::ParentDir {
91 clean.pop();
92 } else {
93 clean.push(component);
94 }
95 }
96 clean
97}
98
99pub fn parse(file: &str, input: &str) -> Result<Vec<ServerBlock>, Error> {
101 parse_with_env(file, input, |name| std::env::var(name).ok())
102}
103
104pub fn parse_with_env(
106 file: &str,
107 input: &str,
108 lookup: impl Fn(&str) -> Option<String>,
109) -> Result<Vec<ServerBlock>, Error> {
110 Parser {
111 tokens: lex(file, input),
112 pos: 0,
113 file,
114 lookup: &lookup,
115 snippets: BTreeMap::new(),
116 snippet_expansions: 0,
117 file_expansions: 0,
118 }
119 .parse_all()
120}
121
122struct Parser<'a> {
123 tokens: Vec<Token>,
124 pos: usize,
125 file: &'a str,
126 lookup: &'a dyn Fn(&str) -> Option<String>,
127 snippets: BTreeMap<String, Vec<Token>>,
128 snippet_expansions: usize,
129 file_expansions: usize,
130}
131
132impl Parser<'_> {
133 fn substitute(&self, text: &str) -> Result<String, Error> {
134 let mut result = text.to_owned();
135 for (start, end) in [("{%", "%}"), ("{$", "}")] {
136 let mut seen = BTreeSet::new();
137 while let Some(index) = result.find(start) {
138 let Some(end_offset) = result[index..].find(end) else {
139 break;
140 };
141 let end_index = index + end_offset;
142 if end_index <= index + start.len() {
143 break;
144 }
145 let name = &result[index + start.len()..end_index];
146 let value = (self.lookup)(name).unwrap_or_default();
147 let reference = &result[index..end_index + end.len()];
148 let occurrences = result.matches(reference).count();
150 let size = result.len() - occurrences * reference.len();
151 if seen.len() >= 10_000
152 || !seen.insert(result.clone())
153 || value
154 .len()
155 .checked_mul(occurrences)
156 .and_then(|n| n.checked_add(size))
157 .is_none_or(|n| n > 16 * 1024 * 1024)
158 {
159 return Err(self.error("environment expansion limit or cycle"));
160 }
161 result = result.replace(reference, &value);
162 }
163 }
164 Ok(result)
165 }
166 fn error(&self, message: &str) -> Error {
167 let token = self.tokens.get(self.pos).or_else(|| self.tokens.last());
168 Error {
169 file: token.map_or(self.file, |t| &t.file).into(),
170 line: token.map_or(1, |t| t.line),
171 message: message.into(),
172 }
173 }
174
175 fn parse_all(&mut self) -> Result<Vec<ServerBlock>, Error> {
176 let mut blocks = Vec::new();
177 while self.pos < self.tokens.len() {
178 let block = self.block()?;
179 if !block.keys.is_empty() {
180 blocks.push(block);
181 }
182 }
183 Ok(blocks)
184 }
185
186 fn block(&mut self) -> Result<ServerBlock, Error> {
187 let mut block = ServerBlock::default();
188 let mut expecting = false;
189 while self.pos < self.tokens.len() {
190 let text = self.substitute(&self.tokens[self.pos].text)?;
191 if text == "import"
192 && (self.pos == 0 || new_line(&self.tokens[self.pos - 1], &self.tokens[self.pos]))
193 {
194 self.import()?;
195 continue;
196 }
197 if text == "{" {
198 if expecting {
199 return Err(self.error("expected another Server Block Key"));
200 }
201 break;
202 }
203 if !text.is_empty() {
204 expecting = text.ends_with(',');
205 block
206 .keys
207 .push(text.strip_suffix(',').unwrap_or(&text).into());
208 block.key_sources.push(self.tokens[self.pos].clone());
209 }
210 self.pos += 1;
211 if self.pos == self.tokens.len() {
212 if expecting {
213 return Err(self.error("unexpected end of input"));
214 }
215 return Ok(block);
216 }
217 if !expecting && new_line(&self.tokens[self.pos - 1], &self.tokens[self.pos]) {
218 break;
219 }
220 }
221 if self.pos == self.tokens.len() {
222 return Ok(block);
223 }
224 if block.keys.len() == 1 && block.keys[0].starts_with('(') && block.keys[0].ends_with(')') {
225 let name = block.keys[0][1..block.keys[0].len() - 1].to_owned();
226 if self.tokens[self.pos].text != "{" {
227 return Err(self.error("expected snippet opening brace"));
228 }
229 if self.snippets.contains_key(&name) {
230 return Err(self.error("duplicate snippet"));
231 }
232 self.pos += 1;
233 let start = self.pos;
234 let mut nesting = 1;
235 while self.pos < self.tokens.len() && nesting > 0 {
236 match self.tokens[self.pos].text.as_str() {
237 "{" => nesting += 1,
238 "}" => nesting -= 1,
239 _ => {}
240 }
241 self.pos += 1;
242 }
243 if nesting != 0 {
244 return Err(self.error("expected snippet closing brace"));
245 }
246 let tokens = self.tokens[start..self.pos - 1].to_vec();
247 for (i, token) in tokens.iter().enumerate() {
248 if token.text == "import"
249 && (i == 0 || new_line(&tokens[i - 1], token))
250 && let Some(next) = tokens.get(i + 1)
251 && self.substitute(&next.text)? == name
252 {
253 return Err(self.error("recursive snippet"));
254 }
255 }
256 self.snippets.insert(name, tokens);
257 return Ok(ServerBlock::default());
258 }
259 let braced = self.tokens[self.pos].text == "{";
260 if braced {
261 self.pos += 1;
262 }
263 while self.pos < self.tokens.len() && self.tokens[self.pos].text != "}" {
264 if self.tokens[self.pos].text == "import" {
265 self.import()?;
266 continue;
267 }
268 self.directive(&mut block)?;
269 }
270 if braced {
271 if self.pos == self.tokens.len() {
272 return Err(self.error("expected closing brace"));
273 }
274 self.pos += 1;
275 } else if self.pos < self.tokens.len() {
276 self.pos += 1;
277 }
278 Ok(block)
279 }
280
281 fn directive(&mut self, block: &mut ServerBlock) -> Result<(), Error> {
282 let name = self.substitute(&self.tokens[self.pos].text)?;
283 let mut items = vec![self.tokens[self.pos].clone()];
284 self.pos += 1;
285 let mut nesting = 0usize;
286 while self.pos < self.tokens.len() {
287 let token = &self.tokens[self.pos];
288 if token.text == "{" {
289 nesting += 1;
290 } else if nesting == 0 && new_line(&self.tokens[self.pos - 1], token) {
291 break;
292 } else if token.text == "}" {
293 if nesting == 0 {
294 return Err(self.error("unexpected closing brace"));
295 }
296 nesting -= 1;
297 } else if token.text == "import" && new_line(&self.tokens[self.pos - 1], token) {
298 self.import()?;
299 continue;
300 }
301 let mut token = token.clone();
302 token.text = self.substitute(&token.text)?;
303 items.push(token);
304 self.pos += 1;
305 }
306 if nesting != 0 {
307 return Err(self.error("unexpected end of input"));
308 }
309 block.directives.entry(name).or_default().extend(items);
310 Ok(())
311 }
312
313 fn import(&mut self) -> Result<(), Error> {
314 let start = self.pos;
315 let Some(arg) = self.tokens.get(start + 1) else {
316 return Err(self.error("import requires an argument"));
317 };
318 if arg.file != self.tokens[start].file
319 || arg.line != self.tokens[start].line + self.tokens[start].text.matches('\n').count()
320 {
321 return Err(self.error("import requires an argument"));
322 }
323 let pattern = self.substitute(&arg.text)?;
324 if pattern.is_empty() {
325 return Err(self.error("import requires a non-empty path"));
326 }
327 if let Some(next) = self.tokens.get(start + 2)
328 && next.file == arg.file
329 && next.line == arg.line + arg.text.matches('\n').count()
330 {
331 return Err(self.error("import takes only one argument"));
332 }
333 let imported = if let Some(tokens) = self.snippets.get(&pattern) {
334 self.snippet_expansions += 1;
335 if self.snippet_expansions > 10_000 {
336 return Err(self.error("snippet import limit exceeded"));
337 }
338 tokens.clone()
339 } else {
340 self.file_expansions += 1;
341 if self.file_expansions > 100_000 {
342 return Err(self.error("file import limit exceeded"));
343 }
344 let base = Path::new(&self.tokens[start].file);
345 let base = if base.is_absolute() {
346 base.to_path_buf()
347 } else {
348 std::env::current_dir()
349 .map_err(|e| self.error(&e.to_string()))?
350 .join(base)
351 };
352 let base = clean_path(&base);
353 let path = if Path::new(&pattern).is_absolute() {
354 pattern.clone().into()
355 } else {
356 clean_path(&base.parent().unwrap_or(Path::new("/")).join(&pattern))
357 };
358 let glob_pattern = path.to_string_lossy();
359 if glob_pattern.matches('*').count() > 1
360 || glob_pattern.matches('?').count() > 1
361 || (glob_pattern.contains('[') && glob_pattern.contains(']'))
362 {
363 return Err(self.error("too many import wildcards"));
364 }
365 let mut translated = String::new();
366 let mut chars = glob_pattern.chars();
367 while let Some(ch) = chars.next() {
368 if ch == '\\' {
369 let Some(escaped) = chars.next().filter(|c| *c != '/') else {
370 return Err(self.error("invalid import pattern"));
371 };
372 translated.push_str(&glob::Pattern::escape(&escaped.to_string()));
373 } else {
374 translated.push(ch);
375 }
376 }
377 let paths = glob::glob(&translated).map_err(|e| self.error(&e.to_string()))?;
378 let mut imported = Vec::new();
379 let mut count = 0;
380 for path in paths.flatten() {
381 count += 1;
382 let contents = std::fs::read(&path).map_err(|e| self.error(&e.to_string()))?;
383 imported.extend(lex(
384 &clean_path(&path).to_string_lossy(),
385 &crate::lexer::decode(&contents),
386 ));
387 }
388 if count == 0 && !glob_pattern.contains(['*', '?', '[', ']']) {
389 return Err(self.error("import file not found"));
390 }
391 imported
392 };
393 self.tokens.splice(start..start + 2, imported);
394 Ok(())
395 }
396}