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