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