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