Merge pull request #2599 from unxed/iterm_fix
[far2l.git] / utils / src / Environment.cpp
blob3faaa61f18a9e798aa55fe309985a09f26205e9f
1 #include <stdlib.h>
2 #include <unistd.h>
3 #include <string.h>
4 #include "utils.h"
6 namespace Environment
9 static char *GetHostNameCached()
11 static char s_out[0x100] = {0};
12 if (!s_out[0]) {
13 gethostname(&s_out[1], sizeof(s_out) - 1);
14 s_out[0] = 1;
16 return &s_out[1];
19 const char *GetVariable(const char *name)
21 const char *out = getenv(name);
22 if (out) {
23 return out;
26 if (strcmp(name, "HOSTNAME") == 0) {
27 return GetHostNameCached();
30 return nullptr;
33 static void ReplaceSubstringAt(std::string &s, size_t start, size_t &edge, const char *value)
35 size_t value_len = strlen(value);
37 s.replace(start, edge - start, value, value_len);
38 if (value_len < (edge - start)) {
39 edge-= ((edge - start) - value_len);
41 } else {
42 edge+= (value_len - (edge - start));
46 static void ReplaceSubstringAt(std::string &s, size_t start, size_t &edge, const std::string &value)
48 ReplaceSubstringAt(s, start, edge, value.c_str());
51 static void ReplaceSubstringAt(std::string &s, size_t start, size_t &edge, char value)
53 s.replace(start, edge - start, 1, value);
54 if (1 < (edge - start)) {
55 edge-= ((edge - start) - 1);
57 } else {
58 edge+= (1 - (edge - start));
62 static bool ReplaceVariableAt(std::string &s,
63 size_t start, size_t &edge, const std::string &env, bool empty_if_missing)
65 bool out = true;
66 const char *value = GetVariable(env.c_str());
67 if (!value) {
68 if (!empty_if_missing) {
69 return false;
71 out = false;
72 value = "";
74 ReplaceSubstringAt(s, start, edge, value);
75 return out;
78 static bool RunCommand(std::string &s,
79 size_t start, size_t &edge, std::string cmd, bool empty_if_missing)
81 if (!ExpandString(cmd, empty_if_missing, false))
82 return false;
84 std::string result;
85 bool out = POpen(result, cmd.c_str());
86 if (!out)
87 return empty_if_missing;
89 StrTrim(result, "\r\n");
90 // expand resulting stuff without commands execution for security
91 if (!ExpandString(result, empty_if_missing, false))
92 return false;
94 ReplaceSubstringAt(s, start, edge, result);
95 return true;
98 static void UnescapeCLikeSequence(std::string &s, size_t &i)
100 // check {s[i - 1]=\, s[i]=..} for sequence encoded by EscapeLikeInC and reverse that encoding
102 ++i; // adjust i cuz ReplaceSubstringAt needs past-substring index
104 if (i < s.size()) switch (s[i - 1]) {
105 /// first check for trivial single-character sequences
106 case 'a': ReplaceSubstringAt(s, i - 2, i, '\a'); break;
107 case 'b': ReplaceSubstringAt(s, i - 2, i, '\b'); break;
108 case 'e': ReplaceSubstringAt(s, i - 2, i, '\e'); break;
109 case 'f': ReplaceSubstringAt(s, i - 2, i, '\f'); break;
110 case 'n': ReplaceSubstringAt(s, i - 2, i, '\n'); break;
111 case 'r': ReplaceSubstringAt(s, i - 2, i, '\r'); break;
112 case 't': ReplaceSubstringAt(s, i - 2, i, '\t'); break;
113 case 'v': ReplaceSubstringAt(s, i - 2, i, '\v'); break;
114 case '\\': ReplaceSubstringAt(s, i - 2, i, '\\'); break;
115 case '\'': ReplaceSubstringAt(s, i - 2, i, '\''); break;
116 case '\"': ReplaceSubstringAt(s, i - 2, i, '\"'); break;
117 case '?': ReplaceSubstringAt(s, i - 2, i, '?'); break;
119 /// now check for multi-character codes
120 // \x## where ## is a hexadecimal char code
121 case 'x': if (i + 1 < s.size()) {
122 unsigned long code = strtol(s.substr(i, 2).c_str(), nullptr, 16);
123 i+= 2;
124 ReplaceSubstringAt(s, i - 4, i, StrPrintf("%c", (char)(unsigned char)code));
125 } break;
127 // \u#### where #### is a hexadecimal UTF16 code
128 case 'u': if (i + 3 < s.size()) {
129 unsigned long code = strtol(s.substr(i, 4).c_str(), nullptr, 16);
130 i+= 4;
131 ReplaceSubstringAt(s, i - 6, i, StrPrintf("%lc", (wchar_t)code));
132 } break;
134 // \U######## where ######## is a hexadecimal UTF32 code
135 case 'U': if (i + 7 < s.size()) {
136 unsigned long code = strtol(s.substr(i, 8).c_str(), nullptr, 16);
137 i+= 8;
138 ReplaceSubstringAt(s, i - 10, i, StrPrintf("%lc", (wchar_t)code));
139 } break;
141 // \### where ### is a octal char code
142 case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7':
143 if (i + 1 < s.size()) {
144 unsigned long code = strtol(s.substr(i - 1, 3).c_str(), nullptr, 8);
145 i+= 2;
146 ReplaceSubstringAt(s, i - 4, i, StrPrintf("%c", (char)(unsigned char)code));
147 } break;
149 --i; // adjust i back
152 void UnescapeCLikeSequences(std::string &s)
154 if (s.size() > 1) {
155 for (size_t i = s.size() - 1; i > 0; --i) {
156 if (s[i - 1] == '\\') {
157 size_t j = i;
158 UnescapeCLikeSequence(s, j);
164 // nasty and allmighty function that actually implements ExpandString and ParseCommandLine
165 static bool ExpandStringOrParseCommandLine(std::string &s, Arguments *args, bool empty_if_missing, bool allow_exec_cmd)
167 // Input example: ~/${EXPANDEDFOO}/$EXPANDEDBAR/unexpanded/part
168 if (s.empty()) {
169 return true;
172 bool out = true;
173 // std::string saved_s = s;
174 enum {
175 ENV_NONE,
176 ENV_SIMPLE,
177 ENV_CURLED,
178 ENV_COMMAND
179 } env_state = ENV_NONE;
181 Quoting quot = QUOT_NONE;
183 bool escaping_state = false;
184 bool token_splitter = true;
185 size_t i, orig_i, env_start;
187 for (i = orig_i = env_start = 0; i <= s.size(); ++i, ++orig_i) {
188 if (i != s.size() && args && env_state == ENV_NONE && !escaping_state) {
189 // Check for operation and if so - grab it into dedicated token. Examples:
190 // foo&bar -> 'foo' '&' 'bar'
191 // foo & bar -> 'foo' '&' 'bar'
192 // foo& -> 'foo' '&'
193 // foo & -> 'foo' '&'
194 size_t binops_count = 0;
195 while (i + binops_count < s.size() && strchr("<>&|()", s[i + binops_count]) != nullptr) {
196 ++binops_count;
198 if (binops_count != 0) {
199 if (!token_splitter) {
200 args->back().len = i - args->back().begin;
201 args->back().orig_len = orig_i - args->back().orig_begin;
203 args->emplace_back(Argument{i, binops_count, orig_i, binops_count, QUOT_NONE});
204 token_splitter = true;
205 i+= (binops_count - 1);
206 orig_i+= (binops_count - 1);
207 continue;
211 if (i != s.size() && token_splitter && s[i] != ' ') {
212 token_splitter = false;
213 if (args) {
214 args->emplace_back(Argument{i, 0, orig_i, 0, QUOT_NONE});
216 if (s[i] == '~' && (i + 1 == s.size() || s[i + 1] == '/')) {
217 const std::string &home = GetMyHome();
218 if (!home.empty() || empty_if_missing) {
219 s.replace(i, 1, home);
220 i+= home.size();
221 i--;
226 if (env_state == ENV_SIMPLE) {
227 if (i == s.size() || (!isalnum(s[i]) && s[i] != '_')) {
228 if (!ReplaceVariableAt(s, env_start, i, s.substr(env_start + 1, i - env_start - 1), empty_if_missing)) {
229 out = false;
231 env_state = ENV_NONE;
235 if (i == s.size()) {
236 break;
239 if (env_state == ENV_CURLED && s[i] == '}') {
240 ++i;
241 if (!ReplaceVariableAt(s, env_start, i, s.substr(env_start + 2, i - env_start - 3), empty_if_missing)) {
242 out = false;
244 --i;
245 env_state = ENV_NONE;
247 } else if (env_state == ENV_COMMAND && s[i] == ')') {
248 ++i;
249 if (!allow_exec_cmd) {
250 if (!empty_if_missing) {
251 out = false;
253 } else if (!RunCommand(s, env_start, i, s.substr(env_start + 2, i - env_start - 3), empty_if_missing)) {
254 out = false;
256 --i;
257 env_state = ENV_NONE;
260 if (quot == QUOT_SINGLE) {
261 if (s[i] == '\'') {
262 quot = QUOT_NONE;
263 s.erase(i, 1);
264 --i;
267 } else if (escaping_state) {
268 if (quot == QUOT_DOLLAR_SINGLE) {
269 UnescapeCLikeSequence(s, i);
271 } else if (quot == QUOT_NONE || s[i] == '"' || s[i] == '$') {
272 s.erase(i - 1, 1);
273 --i;
275 escaping_state = false;
277 } else switch (s[i]) {
278 case ' ': if (quot == QUOT_NONE && !token_splitter) {
279 token_splitter = true;
280 if (args && !args->empty()) {
281 args->back().len = i - args->back().begin;
282 args->back().orig_len = orig_i - args->back().orig_begin;
284 } break;
286 case '\'': if (quot == QUOT_DOLLAR_SINGLE) {
287 quot = QUOT_NONE;
288 s.erase(i, 1);
289 --i;
291 } else if (quot != QUOT_DOUBLE && args) {
292 quot = QUOT_SINGLE;
293 s.erase(i, 1);
294 --i;
295 } break;
297 case '\"': if (args) {
298 quot = (quot == QUOT_DOUBLE) ? QUOT_NONE : QUOT_DOUBLE;
299 s.erase(i, 1);
300 --i;
301 } break;
303 case '\\': if (args || (i + 1 < s.size() && s[i + 1] == '$')) {
304 escaping_state = true;
305 } break;
307 case '$': if (env_state == ENV_NONE && i + 1 < s.size()) {
308 if (i + 2 < s.size() && s[i + 1] == '(') {
309 env_state = ENV_COMMAND;
310 env_start = i;
312 } else if (i + 2 < s.size() && s[i + 1] == '{') {
313 env_state = ENV_CURLED;
314 env_start = i;
316 } else if (isalpha(s[i + 1])) {
317 env_state = ENV_SIMPLE;
318 env_start = i;
320 } else if (s[i + 1] == '\'' && args) {
321 s.erase(i, 2);
322 i-= 2;
323 quot = QUOT_DOLLAR_SINGLE;
326 } break;
329 if (args && !args->empty()) { // quot != QUOT_NONE &&
330 args->back().quot = quot;
334 if (!token_splitter && args && !args->empty()) {
335 args->back().len = i - args->back().begin;
336 args->back().orig_len = orig_i - args->back().orig_begin;
339 // fprintf(stderr, "ExpandString('%s', %d): '%s' [%d]\n", saved_s.c_str(), empty_if_missing, s.c_str(), out);
340 return out;
344 bool ExpandString(std::string &s, bool empty_if_missing, bool allow_exec_cmd)
346 return ExpandStringOrParseCommandLine(s, nullptr, empty_if_missing, allow_exec_cmd);
349 bool ParseCommandLine(std::string &s, Arguments &args, bool empty_if_missing, bool allow_exec_cmd)
351 return ExpandStringOrParseCommandLine(s, &args, empty_if_missing, allow_exec_cmd);
356 ExplodeCommandLine::ExplodeCommandLine(const char *expression)
358 if (expression) {
359 Parse(expression);
363 ExplodeCommandLine::ExplodeCommandLine(const std::string &expression)
365 Parse(expression);
368 void ExplodeCommandLine::Parse(std::string expression)
370 Arguments args;
371 ExpandStringOrParseCommandLine(expression, &args, false, false);
372 for (const auto &token : args) {
373 emplace_back(expression.substr(token.begin, token.len));