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