9 static char *GetHostNameCached()
11 static char s_out
[0x100] = {0};
13 gethostname(&s_out
[1], sizeof(s_out
) - 1);
19 const char *GetVariable(const char *name
)
21 const char *out
= getenv(name
);
26 if (strcmp(name
, "HOSTNAME") == 0) {
27 return GetHostNameCached();
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
);
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);
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
)
66 const char *value
= GetVariable(env
.c_str());
68 if (!empty_if_missing
) {
74 ReplaceSubstringAt(s
, start
, edge
, value
);
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))
85 bool out
= POpen(result
, cmd
.c_str());
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))
94 ReplaceSubstringAt(s
, start
, edge
, result
);
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);
124 ReplaceSubstringAt(s
, i
- 4, i
, StrPrintf("%c", (char)(unsigned char)code
));
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);
131 ReplaceSubstringAt(s
, i
- 6, i
, StrPrintf("%lc", (wchar_t)code
));
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);
138 ReplaceSubstringAt(s
, i
- 10, i
, StrPrintf("%lc", (wchar_t)code
));
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);
146 ReplaceSubstringAt(s
, i
- 4, i
, StrPrintf("%c", (char)(unsigned char)code
));
149 --i
; // adjust i back
152 void UnescapeCLikeSequences(std::string
&s
)
155 for (size_t i
= s
.size() - 1; i
> 0; --i
) {
156 if (s
[i
- 1] == '\\') {
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
173 // std::string saved_s = s;
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'
193 // foo & -> 'foo' '&'
194 size_t binops_count
= 0;
195 while (i
+ binops_count
< s
.size() && strchr("<>&|()", s
[i
+ binops_count
]) != nullptr) {
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);
211 if (i
!= s
.size() && token_splitter
&& s
[i
] != ' ') {
212 token_splitter
= false;
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
);
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
)) {
231 env_state
= ENV_NONE
;
239 if (env_state
== ENV_CURLED
&& s
[i
] == '}') {
241 if (!ReplaceVariableAt(s
, env_start
, i
, s
.substr(env_start
+ 2, i
- env_start
- 3), empty_if_missing
)) {
245 env_state
= ENV_NONE
;
247 } else if (env_state
== ENV_COMMAND
&& s
[i
] == ')') {
249 if (!allow_exec_cmd
) {
250 if (!empty_if_missing
) {
253 } else if (!RunCommand(s
, env_start
, i
, s
.substr(env_start
+ 2, i
- env_start
- 3), empty_if_missing
)) {
257 env_state
= ENV_NONE
;
260 if (quot
== QUOT_SINGLE
) {
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
] == '$') {
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
;
286 case '\'': if (quot
== QUOT_DOLLAR_SINGLE
) {
291 } else if (quot
!= QUOT_DOUBLE
&& args
) {
297 case '\"': if (args
) {
298 quot
= (quot
== QUOT_DOUBLE
) ? QUOT_NONE
: QUOT_DOUBLE
;
303 case '\\': if (args
|| (i
+ 1 < s
.size() && s
[i
+ 1] == '$')) {
304 escaping_state
= true;
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
;
312 } else if (i
+ 2 < s
.size() && s
[i
+ 1] == '{') {
313 env_state
= ENV_CURLED
;
316 } else if (isalpha(s
[i
+ 1])) {
317 env_state
= ENV_SIMPLE
;
320 } else if (s
[i
+ 1] == '\'' && args
) {
323 quot
= QUOT_DOLLAR_SINGLE
;
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);
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
)
363 ExplodeCommandLine::ExplodeCommandLine(const std::string
&expression
)
368 void ExplodeCommandLine::Parse(std::string expression
)
371 ExpandStringOrParseCommandLine(expression
, &args
, false, false);
372 for (const auto &token
: args
) {
373 emplace_back(expression
.substr(token
.begin
, token
.len
));