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