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