Skip to main content

safe_chains/cst/
mod.rs

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