1pub(crate) mod check;
2mod display;
3pub(crate) mod eval;
4mod explain;
5mod budget;
6mod parse;
7mod reserved;
8#[cfg(test)]
9mod proptests;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct Script(pub Vec<Stmt>);
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct Stmt {
16 pub pipeline: Pipeline,
17 pub op: Option<ListOp>,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum ListOp {
22 And,
23 Or,
24 Semi,
25 Amp,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct Pipeline {
30 pub bang: bool,
31 pub commands: Vec<Cmd>,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub enum Cmd {
36 Simple(SimpleCmd),
37 Subshell {
38 body: Script,
39 redirs: Vec<Redir>,
40 },
41 BraceGroup {
42 body: Script,
43 redirs: Vec<Redir>,
44 },
45 For {
46 var: String,
47 items: Vec<Word>,
48 body: Script,
49 redirs: Vec<Redir>,
50 },
51 While {
52 cond: Script,
53 body: Script,
54 redirs: Vec<Redir>,
55 },
56 Until {
57 cond: Script,
58 body: Script,
59 redirs: Vec<Redir>,
60 },
61 If {
62 branches: Vec<Branch>,
63 else_body: Option<Script>,
64 redirs: Vec<Redir>,
65 },
66 DoubleBracket {
67 words: Vec<Word>,
68 redirs: Vec<Redir>,
69 },
70 Case {
74 subject: Word,
75 arms: Vec<CaseArm>,
76 redirs: Vec<Redir>,
77 },
78 FunctionDef {
82 name: String,
83 body: Script,
84 },
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct Branch {
89 pub cond: Script,
90 pub body: Script,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct CaseArm {
97 pub patterns: Vec<Word>,
98 pub body: Script,
99}
100
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub struct SimpleCmd {
103 pub env: Vec<(String, Word)>,
104 pub words: Vec<Word>,
105 pub redirs: Vec<Redir>,
106}
107
108#[derive(Debug, Clone, PartialEq, Eq)]
109pub struct Word(pub Vec<WordPart>);
110
111#[derive(Debug, Clone, PartialEq, Eq)]
112pub enum WordPart {
113 Lit(String),
114 Escape(char),
115 SQuote(String),
116 DQuote(Word),
117 CmdSub(Script),
118 ProcSub(Script),
119 Backtick(String),
120 Arith(Word),
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub enum WriteMode {
133 Truncate,
135 Append,
137 TruncateBoth,
141 AppendBoth,
143 Clobber,
145}
146
147#[derive(Debug, Clone, PartialEq, Eq)]
148pub enum Redir {
149 Write {
150 fd: u32,
151 target: Word,
152 mode: WriteMode,
153 },
154 Read {
155 fd: u32,
156 target: Word,
157 },
158 ReadWrite {
162 fd: u32,
163 target: Word,
164 },
165 HereStr(Word),
166 HereDoc {
167 delimiter: String,
168 strip_tabs: bool,
169 body: Word,
174 },
175 DupFd {
176 src: u32,
177 dst: String,
178 },
179}
180
181pub use check::{command_verdict, is_safe_command, is_safe_pipeline};
182pub use explain::{Explanation, SegmentReport, explain, explain_with_coverage};
183pub(crate) use explain::denied_inner_words;
184pub use parse::parse;
185
186impl Word {
187 pub fn eval(&self) -> String {
188 eval::eval_word(self)
189 }
190
191 pub fn expand(&self) -> Vec<String> {
195 eval::expand_word(self)
196 }
197
198 pub fn literal(s: &str) -> Self {
199 Word(vec![WordPart::Lit(s.to_string())])
200 }
201
202 pub fn normalize(&self) -> Self {
203 let mut parts = Vec::new();
204 for part in &self.0 {
205 let part = match part {
206 WordPart::DQuote(inner) => WordPart::DQuote(inner.normalize()),
207 WordPart::CmdSub(s) => WordPart::CmdSub(s.normalize()),
208 WordPart::ProcSub(s) => WordPart::ProcSub(s.normalize()),
209 other => other.clone(),
210 };
211 if let WordPart::Lit(s) = &part
212 && let Some(WordPart::Lit(prev)) = parts.last_mut()
213 {
214 prev.push_str(s);
215 continue;
216 }
217 parts.push(part);
218 }
219 Word(parts)
220 }
221}
222
223impl Script {
224 pub fn is_empty(&self) -> bool {
225 self.0.is_empty()
226 }
227
228 pub fn normalize(&self) -> Self {
229 Script(
230 self.0
231 .iter()
232 .map(|stmt| Stmt {
233 pipeline: stmt.pipeline.normalize(),
234 op: stmt.op,
235 })
236 .collect(),
237 )
238 }
239
240 pub fn normalize_as_body(&self) -> Self {
241 let mut s = self.normalize();
242 if let Some(last) = s.0.last_mut()
243 && last.op.is_none()
244 {
245 last.op = Some(ListOp::Semi);
246 }
247 s
248 }
249}
250
251impl Pipeline {
252 fn normalize(&self) -> Self {
253 Pipeline {
254 bang: self.bang,
255 commands: self.commands.iter().map(|c| c.normalize()).collect(),
256 }
257 }
258}
259
260impl Cmd {
261 fn normalize(&self) -> Self {
262 match self {
263 Cmd::Simple(s) => Cmd::Simple(s.normalize()),
264 Cmd::Subshell { body, redirs } => Cmd::Subshell {
265 body: body.normalize(),
266 redirs: normalize_redirs(redirs),
267 },
268 Cmd::BraceGroup { body, redirs } => Cmd::BraceGroup {
269 body: body.normalize_as_body(),
270 redirs: normalize_redirs(redirs),
271 },
272 Cmd::For { var, items, body, redirs } => Cmd::For {
273 var: var.clone(),
274 items: items.iter().map(|w| w.normalize()).collect(),
275 body: body.normalize_as_body(),
276 redirs: normalize_redirs(redirs),
277 },
278 Cmd::While { cond, body, redirs } => Cmd::While {
279 cond: cond.normalize_as_body(),
280 body: body.normalize_as_body(),
281 redirs: normalize_redirs(redirs),
282 },
283 Cmd::Until { cond, body, redirs } => Cmd::Until {
284 cond: cond.normalize_as_body(),
285 body: body.normalize_as_body(),
286 redirs: normalize_redirs(redirs),
287 },
288 Cmd::If { branches, else_body, redirs } => Cmd::If {
289 branches: branches
290 .iter()
291 .map(|b| Branch {
292 cond: b.cond.normalize_as_body(),
293 body: b.body.normalize_as_body(),
294 })
295 .collect(),
296 else_body: else_body.as_ref().map(|e| e.normalize_as_body()),
297 redirs: normalize_redirs(redirs),
298 },
299 Cmd::DoubleBracket { words, redirs } => Cmd::DoubleBracket {
300 words: words.iter().map(|w| w.normalize()).collect(),
301 redirs: normalize_redirs(redirs),
302 },
303 Cmd::Case { subject, arms, redirs } => Cmd::Case {
304 subject: subject.normalize(),
305 arms: arms
306 .iter()
307 .map(|a| CaseArm {
308 patterns: a.patterns.iter().map(|w| w.normalize()).collect(),
309 body: a.body.normalize_as_body(),
310 })
311 .collect(),
312 redirs: normalize_redirs(redirs),
313 },
314 Cmd::FunctionDef { name, body } => Cmd::FunctionDef {
315 name: name.clone(),
316 body: body.normalize_as_body(),
317 },
318 }
319 }
320}
321
322impl SimpleCmd {
323 fn normalize(&self) -> Self {
324 SimpleCmd {
325 env: self
326 .env
327 .iter()
328 .map(|(k, v)| (k.clone(), v.normalize()))
329 .collect(),
330 words: self.words.iter().map(|w| w.normalize()).collect(),
331 redirs: normalize_redirs(&self.redirs),
332 }
333 }
334}
335
336fn normalize_redirs(redirs: &[Redir]) -> Vec<Redir> {
337 redirs
338 .iter()
339 .map(|r| match r {
340 Redir::Write { fd, target, mode } => Redir::Write {
341 fd: *fd,
342 target: target.normalize(),
343 mode: *mode,
344 },
345 Redir::Read { fd, target } => Redir::Read {
346 fd: *fd,
347 target: target.normalize(),
348 },
349 Redir::ReadWrite { fd, target } => Redir::ReadWrite {
350 fd: *fd,
351 target: target.normalize(),
352 },
353 Redir::HereStr(w) => Redir::HereStr(w.normalize()),
354 Redir::HereDoc { .. } | Redir::DupFd { .. } => r.clone(),
355 })
356 .collect()
357}