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