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