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