Skip to main content

safe_chains/cst/
mod.rs

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