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