Skip to main content

safe_chains/cst/
mod.rs

1pub(crate) mod check;
2#[cfg(test)]
3mod display;
4mod eval;
5mod parse;
6#[cfg(test)]
7mod proptests;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct Script(pub Vec<Stmt>);
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Stmt {
14    pub pipeline: Pipeline,
15    pub op: Option<ListOp>,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum ListOp {
20    And,
21    Or,
22    Semi,
23    Amp,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct Pipeline {
28    pub bang: bool,
29    pub commands: Vec<Cmd>,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum Cmd {
34    Simple(SimpleCmd),
35    Subshell {
36        body: Script,
37        redirs: Vec<Redir>,
38    },
39    BraceGroup {
40        body: Script,
41        redirs: Vec<Redir>,
42    },
43    For {
44        var: String,
45        items: Vec<Word>,
46        body: Script,
47    },
48    While {
49        cond: Script,
50        body: Script,
51    },
52    Until {
53        cond: Script,
54        body: Script,
55    },
56    If {
57        branches: Vec<Branch>,
58        else_body: Option<Script>,
59    },
60    DoubleBracket {
61        words: Vec<Word>,
62        redirs: Vec<Redir>,
63    },
64}
65
66#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct Branch {
68    pub cond: Script,
69    pub body: Script,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct SimpleCmd {
74    pub env: Vec<(String, Word)>,
75    pub words: Vec<Word>,
76    pub redirs: Vec<Redir>,
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct Word(pub Vec<WordPart>);
81
82#[derive(Debug, Clone, PartialEq, Eq)]
83pub enum WordPart {
84    Lit(String),
85    Escape(char),
86    SQuote(String),
87    DQuote(Word),
88    CmdSub(Script),
89    ProcSub(Script),
90    Backtick(String),
91    Arith(String),
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
95pub enum Redir {
96    Write {
97        fd: u32,
98        target: Word,
99        append: bool,
100    },
101    Read {
102        fd: u32,
103        target: Word,
104    },
105    HereStr(Word),
106    HereDoc {
107        delimiter: String,
108        strip_tabs: bool,
109    },
110    DupFd {
111        src: u32,
112        dst: String,
113    },
114}
115
116pub use check::{command_verdict, is_safe_command, is_safe_pipeline};
117pub use parse::parse;
118
119impl Word {
120    pub fn eval(&self) -> String {
121        eval::eval_word(self)
122    }
123
124    pub fn literal(s: &str) -> Self {
125        Word(vec![WordPart::Lit(s.to_string())])
126    }
127
128    pub fn normalize(&self) -> Self {
129        let mut parts = Vec::new();
130        for part in &self.0 {
131            let part = match part {
132                WordPart::DQuote(inner) => WordPart::DQuote(inner.normalize()),
133                WordPart::CmdSub(s) => WordPart::CmdSub(s.normalize()),
134                WordPart::ProcSub(s) => WordPart::ProcSub(s.normalize()),
135                other => other.clone(),
136            };
137            if let WordPart::Lit(s) = &part
138                && let Some(WordPart::Lit(prev)) = parts.last_mut()
139            {
140                prev.push_str(s);
141                continue;
142            }
143            parts.push(part);
144        }
145        Word(parts)
146    }
147}
148
149impl Script {
150    pub fn is_empty(&self) -> bool {
151        self.0.is_empty()
152    }
153
154    pub fn normalize(&self) -> Self {
155        Script(
156            self.0
157                .iter()
158                .map(|stmt| Stmt {
159                    pipeline: stmt.pipeline.normalize(),
160                    op: stmt.op,
161                })
162                .collect(),
163        )
164    }
165
166    pub fn normalize_as_body(&self) -> Self {
167        let mut s = self.normalize();
168        if let Some(last) = s.0.last_mut()
169            && last.op.is_none()
170        {
171            last.op = Some(ListOp::Semi);
172        }
173        s
174    }
175}
176
177impl Pipeline {
178    fn normalize(&self) -> Self {
179        Pipeline {
180            bang: self.bang,
181            commands: self.commands.iter().map(|c| c.normalize()).collect(),
182        }
183    }
184}
185
186impl Cmd {
187    fn normalize(&self) -> Self {
188        match self {
189            Cmd::Simple(s) => Cmd::Simple(s.normalize()),
190            Cmd::Subshell { body, redirs } => Cmd::Subshell {
191                body: body.normalize(),
192                redirs: normalize_redirs(redirs),
193            },
194            Cmd::BraceGroup { body, redirs } => Cmd::BraceGroup {
195                body: body.normalize_as_body(),
196                redirs: normalize_redirs(redirs),
197            },
198            Cmd::For { var, items, body } => Cmd::For {
199                var: var.clone(),
200                items: items.iter().map(|w| w.normalize()).collect(),
201                body: body.normalize_as_body(),
202            },
203            Cmd::While { cond, body } => Cmd::While {
204                cond: cond.normalize_as_body(),
205                body: body.normalize_as_body(),
206            },
207            Cmd::Until { cond, body } => Cmd::Until {
208                cond: cond.normalize_as_body(),
209                body: body.normalize_as_body(),
210            },
211            Cmd::If { branches, else_body } => Cmd::If {
212                branches: branches
213                    .iter()
214                    .map(|b| Branch {
215                        cond: b.cond.normalize_as_body(),
216                        body: b.body.normalize_as_body(),
217                    })
218                    .collect(),
219                else_body: else_body.as_ref().map(|e| e.normalize_as_body()),
220            },
221            Cmd::DoubleBracket { words, redirs } => Cmd::DoubleBracket {
222                words: words.iter().map(|w| w.normalize()).collect(),
223                redirs: normalize_redirs(redirs),
224            },
225        }
226    }
227}
228
229impl SimpleCmd {
230    fn normalize(&self) -> Self {
231        SimpleCmd {
232            env: self
233                .env
234                .iter()
235                .map(|(k, v)| (k.clone(), v.normalize()))
236                .collect(),
237            words: self.words.iter().map(|w| w.normalize()).collect(),
238            redirs: normalize_redirs(&self.redirs),
239        }
240    }
241}
242
243fn normalize_redirs(redirs: &[Redir]) -> Vec<Redir> {
244    redirs
245        .iter()
246        .map(|r| match r {
247            Redir::Write { fd, target, append } => Redir::Write {
248                fd: *fd,
249                target: target.normalize(),
250                append: *append,
251            },
252            Redir::Read { fd, target } => Redir::Read {
253                fd: *fd,
254                target: target.normalize(),
255            },
256            Redir::HereStr(w) => Redir::HereStr(w.normalize()),
257            Redir::HereDoc { .. } | Redir::DupFd { .. } => r.clone(),
258        })
259        .collect()
260}