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