1 /**
2  * Copyright (c) 2015, Timothy Stack
3  *
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * * Redistributions of source code must retain the above copyright notice, this
10  * list of conditions and the following disclaimer.
11  * * Redistributions in binary form must reproduce the above copyright notice,
12  * this list of conditions and the following disclaimer in the documentation
13  * and/or other materials provided with the distribution.
14  * * Neither the name of Timothy Stack nor the names of its contributors
15  * may be used to endorse or promote products derived from this software
16  * without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  *
29  * @file shlex.hh
30  */
31 
32 #ifndef LNAV_SHLEX_HH_H
33 #define LNAV_SHLEX_HH_H
34 
35 #include <pwd.h>
36 
37 #include <map>
38 #include <vector>
39 #include <string>
40 
41 #include "base/opt_util.hh"
42 #include "pcrepp/pcrepp.hh"
43 
44 enum class shlex_token_t {
45     ST_ERROR,
46     ST_WHITESPACE,
47     ST_ESCAPE,
48     ST_DOUBLE_QUOTE_START,
49     ST_DOUBLE_QUOTE_END,
50     ST_SINGLE_QUOTE_START,
51     ST_SINGLE_QUOTE_END,
52     ST_VARIABLE_REF,
53     ST_QUOTED_VARIABLE_REF,
54     ST_TILDE,
55 };
56 
57 class scoped_resolver {
58 public:
scoped_resolver(std::initializer_list<std::map<std::string,std::string> * > l)59     scoped_resolver(std::initializer_list<std::map<std::string, std::string> *> l) {
60         this->sr_stack.insert(this->sr_stack.end(), l.begin(), l.end());
61     };
62 
63     typedef std::map<std::string, std::string>::const_iterator const_iterator;
64 
find(const std::string & str) const65     const_iterator find(const std::string &str) const {
66         const_iterator retval;
67 
68         for (auto scope : this->sr_stack) {
69             if ((retval = scope->find(str)) != scope->end()) {
70                 return retval;
71             }
72         }
73 
74         return this->end();
75     };
76 
end() const77     const_iterator end() const {
78         return this->sr_stack.back()->end();
79     }
80 
81     std::vector<const std::map<std::string, std::string> *> sr_stack;
82 };
83 
84 class shlex {
85 public:
shlex(const char * str,size_t len)86     shlex(const char *str, size_t len)
87             : s_str(str),
88               s_len(len) {
89     };
90 
shlex(const string_fragment & sf)91     explicit shlex(const string_fragment &sf)
92         : s_str(sf.data()), s_len(sf.length()) {
93     }
94 
shlex(const std::string & str)95     explicit shlex(const std::string &str)
96             : s_str(str.c_str()),
97               s_len(str.size()) {
98     };
99 
with_ignore_quotes(bool val)100     shlex &with_ignore_quotes(bool val) {
101         this->s_ignore_quotes = val;
102         return *this;
103     }
104 
105     bool tokenize(pcre_context::capture_t &cap_out, shlex_token_t &token_out);
106 
107     template <typename Resolver = scoped_resolver>
eval(std::string & result,const Resolver & vars)108     bool eval(std::string &result, const Resolver &vars) {
109         result.clear();
110 
111         pcre_context::capture_t cap;
112         shlex_token_t token;
113         int last_index = 0;
114 
115         while (this->tokenize(cap, token)) {
116             result.append(&this->s_str[last_index], cap.c_begin - last_index);
117             switch (token) {
118                 case shlex_token_t::ST_ERROR:
119                     return false;
120                 case shlex_token_t::ST_ESCAPE:
121                     result.append(1, this->s_str[cap.c_begin + 1]);
122                     break;
123                 case shlex_token_t::ST_WHITESPACE:
124                     result.append(&this->s_str[cap.c_begin], cap.length());
125                     break;
126                 case shlex_token_t::ST_VARIABLE_REF:
127                 case shlex_token_t::ST_QUOTED_VARIABLE_REF: {
128                     int extra = token == shlex_token_t::ST_VARIABLE_REF ? 0 : 1;
129                     std::string var_name(&this->s_str[cap.c_begin + 1 + extra], cap.length() - 1 - extra * 2);
130                     std::map<std::string, std::string>::const_iterator local_var;
131                     const char *var_value = getenv(var_name.c_str());
132 
133                     if ((local_var = vars.find(var_name)) != vars.end()) {
134                         result.append(local_var->second);
135                     }
136                     else if (var_value != nullptr) {
137                         result.append(var_value);
138                     }
139                     break;
140                 }
141                 case shlex_token_t::ST_TILDE:
142                     this->resolve_home_dir(result, cap);
143                     break;
144                 case shlex_token_t::ST_DOUBLE_QUOTE_START:
145                 case shlex_token_t::ST_DOUBLE_QUOTE_END:
146                     result.append("\"");
147                     break;
148                 case shlex_token_t::ST_SINGLE_QUOTE_START:
149                 case shlex_token_t::ST_SINGLE_QUOTE_END:
150                     result.append("'");
151                     break;
152                 default:
153                     break;
154             }
155             last_index = cap.c_end;
156         }
157 
158         result.append(&this->s_str[last_index], this->s_len - last_index);
159 
160         return true;
161     };
162 
163     template <typename Resolver>
split(std::vector<std::string> & result,const Resolver & vars)164     bool split(std::vector<std::string> &result, const Resolver &vars) {
165         result.clear();
166 
167         pcre_context::capture_t cap;
168         shlex_token_t token;
169         int last_index = 0;
170         bool start_new = true;
171 
172         while (isspace(this->s_str[this->s_index])) {
173             this->s_index += 1;
174         }
175         while (this->tokenize(cap, token)) {
176             if (start_new) {
177                 result.emplace_back("");
178                 start_new = false;
179             }
180             result.back().append(&this->s_str[last_index], cap.c_begin - last_index);
181             switch (token) {
182                 case shlex_token_t::ST_ERROR:
183                     return false;
184                 case shlex_token_t::ST_ESCAPE:
185                     result.back().append(1, this->s_str[cap.c_begin + 1]);
186                     break;
187                 case shlex_token_t::ST_WHITESPACE:
188                     start_new = true;
189                     break;
190                 case shlex_token_t::ST_VARIABLE_REF:
191                 case shlex_token_t::ST_QUOTED_VARIABLE_REF: {
192                     int extra = token == shlex_token_t::ST_VARIABLE_REF ? 0 : 1;
193                     std::string var_name(&this->s_str[cap.c_begin + 1 + extra], cap.length() - 1 - extra * 2);
194                     std::map<std::string, std::string>::const_iterator local_var;
195                     const char *var_value = getenv(var_name.c_str());
196 
197                     if ((local_var = vars.find(var_name)) != vars.end()) {
198                         result.back().append(local_var->second);
199                     }
200                     else if (var_value != nullptr) {
201                         result.back().append(var_value);
202                     }
203                     break;
204                 }
205                 case shlex_token_t::ST_TILDE:
206                     this->resolve_home_dir(result.back(), cap);
207                     break;
208                 default:
209                     break;
210             }
211             last_index = cap.c_end;
212         }
213 
214         if (last_index < this->s_len) {
215             if (start_new || result.empty()) {
216                 result.emplace_back("");
217             }
218             result.back().append(&this->s_str[last_index], this->s_len - last_index);
219         }
220 
221         return true;
222     }
223 
reset()224     void reset() {
225         this->s_index = 0;
226         this->s_state = state_t::STATE_NORMAL;
227     };
228 
229     void scan_variable_ref(pcre_context::capture_t &cap_out, shlex_token_t &token_out);
230 
231     void resolve_home_dir(std::string& result, const pcre_context::capture_t cap) const;
232 
233     enum class state_t {
234         STATE_NORMAL,
235         STATE_IN_DOUBLE_QUOTE,
236         STATE_IN_SINGLE_QUOTE,
237     };
238 
239     const char *s_str;
240     ssize_t s_len;
241     bool s_ignore_quotes{false};
242     ssize_t s_index{0};
243     state_t s_state{state_t::STATE_NORMAL};
244 };
245 
246 #endif //LNAV_SHLEX_HH_H
247