1 // String expansion functions. These functions perform several kinds of parameter expansion.
2 // IWYU pragma: no_include <cstddef>
3 #include "config.h"
4 
5 #include <errno.h>
6 #include <pwd.h>
7 #include <stdarg.h>
8 #include <stddef.h>
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <wctype.h>
12 
13 #include <cstring>
14 #include <cwchar>
15 
16 #ifdef SunOS
17 #include <procfs.h>
18 #endif
19 #ifdef __APPLE__
20 #include <sys/time.h>  // Required to build with old SDK versions
21 // proc.h needs to be included *after* time.h, this comment stops clang-format from reordering.
22 #include <sys/proc.h>
23 #else
24 #include <dirent.h>
25 #include <sys/stat.h>
26 #endif
27 
28 #include <algorithm>
29 #include <functional>
30 #include <map>
31 #include <memory>  // IWYU pragma: keep
32 #include <type_traits>
33 #include <utility>
34 #include <vector>
35 
36 #include "common.h"
37 #include "complete.h"
38 #include "env.h"
39 #include "exec.h"
40 #include "expand.h"
41 #include "fallback.h"  // IWYU pragma: keep
42 #include "history.h"
43 #include "iothread.h"
44 #include "parse_constants.h"
45 #include "parse_util.h"
46 #include "parser.h"
47 #include "path.h"
48 #include "proc.h"
49 #include "reader.h"
50 #include "util.h"
51 #include "wcstringutil.h"
52 #include "wildcard.h"
53 #include "wutil.h"  // IWYU pragma: keep
54 
55 /// Characters which make a string unclean if they are the first character of the string. See \c
56 /// expand_is_clean().
57 #define UNCLEAN_FIRST L"~%"
58 /// Unclean characters. See \c expand_is_clean().
59 #define UNCLEAN L"$*?\\\"'({})"
60 
61 static void remove_internal_separator(wcstring *s, bool conv);
62 
63 /// Test if the specified argument is clean, i.e. it does not contain any tokens which need to be
64 /// expanded or otherwise altered. Clean strings can be passed through expand_string and expand_one
65 /// without changing them. About two thirds of all strings are clean, so skipping expansion on them
66 /// actually does save a small amount of time, since it avoids multiple memory allocations during
67 /// the expansion process.
68 ///
69 /// \param in the string to test
expand_is_clean(const wcstring & in)70 static bool expand_is_clean(const wcstring &in) {
71     if (in.empty()) return true;
72 
73     // Test characters that have a special meaning in the first character position.
74     if (std::wcschr(UNCLEAN_FIRST, in.at(0)) != nullptr) return false;
75 
76     // Test characters that have a special meaning in any character position.
77     return in.find_first_of(UNCLEAN) == wcstring::npos;
78 }
79 
80 /// Append a syntax error to the given error list.
append_syntax_error(parse_error_list_t * errors,size_t source_start,const wchar_t * fmt,...)81 static void append_syntax_error(parse_error_list_t *errors, size_t source_start, const wchar_t *fmt,
82                                 ...) {
83     if (!errors) return;
84 
85     parse_error_t error;
86     error.source_start = source_start;
87     error.source_length = 0;
88     error.code = parse_error_syntax;
89 
90     va_list va;
91     va_start(va, fmt);
92     error.text = vformat_string(fmt, va);
93     va_end(va);
94 
95     errors->push_back(error);
96 }
97 
98 /// Append a cmdsub error to the given error list. But only do so if the error hasn't already been
99 /// recorded. This is needed because command substitution is a recursive process and some errors
100 /// could consequently be recorded more than once.
append_cmdsub_error(parse_error_list_t * errors,size_t source_start,const wchar_t * fmt,...)101 static void append_cmdsub_error(parse_error_list_t *errors, size_t source_start, const wchar_t *fmt,
102                                 ...) {
103     if (!errors) return;
104 
105     parse_error_t error;
106     error.source_start = source_start;
107     error.source_length = 0;
108     error.code = parse_error_cmdsubst;
109 
110     va_list va;
111     va_start(va, fmt);
112     error.text = vformat_string(fmt, va);
113     va_end(va);
114 
115     for (const auto &it : *errors) {
116         if (error.text == it.text) return;
117     }
118 
119     errors->push_back(error);
120 }
121 
122 /// Append an overflow error, when expansion produces too much data.
append_overflow_error(parse_error_list_t * errors,size_t source_start=SOURCE_LOCATION_UNKNOWN)123 static expand_result_t append_overflow_error(parse_error_list_t *errors,
124                                              size_t source_start = SOURCE_LOCATION_UNKNOWN) {
125     if (errors) {
126         parse_error_t error;
127         error.source_start = source_start;
128         error.source_length = 0;
129         error.code = parse_error_generic;
130         error.text = _(L"Expansion produced too many results");
131         errors->push_back(std::move(error));
132     }
133     return expand_result_t::make_error(STATUS_EXPAND_ERROR);
134 }
135 
136 /// Test if the specified string does not contain character which can not be used inside a quoted
137 /// string.
is_quotable(const wcstring & str)138 static bool is_quotable(const wcstring &str) {
139     return str.find_first_of(L"\n\t\r\b\x1B") == wcstring::npos;
140 }
141 
expand_escape_variable(const env_var_t & var)142 wcstring expand_escape_variable(const env_var_t &var) {
143     wcstring buff;
144     const wcstring_list_t &lst = var.as_list();
145 
146     for (size_t j = 0; j < lst.size(); j++) {
147         const wcstring &el = lst.at(j);
148         if (j) buff.append(L"  ");
149 
150         // We want to use quotes if we have more than one string, or the string contains a space.
151         bool prefer_quotes = lst.size() > 1 || el.find(L' ') != wcstring::npos;
152         if (prefer_quotes && is_quotable(el)) {
153             buff.append(L"'");
154             buff.append(el);
155             buff.append(L"'");
156         } else {
157             buff.append(escape_string(el, 1));
158         }
159     }
160 
161     return buff;
162 }
163 
expand_escape_string(const wcstring & el)164 wcstring expand_escape_string(const wcstring &el) {
165     wcstring buff;
166     bool prefer_quotes = el.find(L' ') != wcstring::npos;
167     if (prefer_quotes && is_quotable(el)) {
168         buff.append(L"'");
169         buff.append(el);
170         buff.append(L"'");
171     } else {
172         buff.append(escape_string(el, 1));
173     }
174     return buff;
175 }
176 
177 /// Parse an array slicing specification Returns 0 on success. If a parse error occurs, returns the
178 /// index of the bad token. Note that 0 can never be a bad index because the string always starts
179 /// with [.
parse_slice(const wchar_t * in,wchar_t ** end_ptr,std::vector<long> & idx,size_t array_size)180 static size_t parse_slice(const wchar_t *in, wchar_t **end_ptr, std::vector<long> &idx,
181                           size_t array_size) {
182     const long size = static_cast<long>(array_size);
183     size_t pos = 1;  // skip past the opening square brace
184 
185     int zero_index = -1;
186     bool literal_zero_index = true;
187 
188     while (true) {
189         while (iswspace(in[pos]) || (in[pos] == INTERNAL_SEPARATOR)) pos++;
190         if (in[pos] == L']') {
191             pos++;
192             break;
193         }
194 
195         // Explicitly refuse $foo[0] as valid syntax, regardless of whether or not we're going
196         // to show an error if the index ultimately evaluates to zero. This will help newcomers
197         // to fish avoid a common off-by-one error. See #4862.
198         if (literal_zero_index) {
199             if (in[pos] == L'0') {
200                 zero_index = pos;
201             } else {
202                 literal_zero_index = false;
203             }
204         }
205 
206         const wchar_t *end;
207         long tmp;
208         if (idx.empty() && in[pos] == L'.' && in[pos + 1] == L'.') {
209             // If we are at the first index expression, a missing start-index means the range starts
210             // at the first item.
211             tmp = 1;  // first index
212             end = &in[pos];
213         } else {
214             tmp = fish_wcstol(&in[pos], &end);
215             if (errno > 0) {
216                 // We don't test `*end` as is typically done because we expect it to not be the null
217                 // char. Ignore the case of errno==-1 because it means the end char wasn't the null
218                 // char.
219                 return pos;
220             }
221         }
222 
223         long i1 = tmp > -1 ? tmp : size + tmp + 1;
224         pos = end - in;
225         while (in[pos] == INTERNAL_SEPARATOR) pos++;
226         if (in[pos] == L'.' && in[pos + 1] == L'.') {
227             pos += 2;
228             while (in[pos] == INTERNAL_SEPARATOR) pos++;
229             while (iswspace(in[pos])) pos++;  // Allow the space in "[.. ]".
230 
231             long tmp1;
232             // If we are at the last index range expression  then a missing end-index means the
233             // range spans until the last item.
234             if (in[pos] == L']') {
235                 tmp1 = -1;  // last index
236                 end = &in[pos];
237             } else {
238                 tmp1 = fish_wcstol(&in[pos], &end);
239                 // Ignore the case of errno==-1 because it means the end char wasn't the null char.
240                 if (errno > 0) {
241                     return pos;
242                 }
243             }
244             pos = end - in;
245 
246             long i2 = tmp1 > -1 ? tmp1 : size + tmp1 + 1;
247             // Skip sequences that are entirely outside.
248             // This means "17..18" expands to nothing if there are less than 17 elements.
249             if (i1 > size && i2 > size) {
250                 continue;
251             }
252             short direction = i2 < i1 ? -1 : 1;
253             // If only the beginning is negative, always go reverse.
254             // If only the end, always go forward.
255             // Prevents `[x..-1]` from going reverse if less than x elements are there.
256             if ((tmp1 > -1) != (tmp > -1)) {
257                 direction = tmp1 > -1 ? -1 : 1;
258             } else {
259                 // Clamp to array size when not forcing direction
260                 // - otherwise "2..-1" clamps both to 1 and then becomes "1..1".
261                 i1 = i1 < size ? i1 : size;
262                 i2 = i2 < size ? i2 : size;
263             }
264             for (long jjj = i1; jjj * direction <= i2 * direction; jjj += direction) {
265                 // FLOGF(error, L"Expand range [subst]: %i\n", jjj);
266                 idx.push_back(jjj);
267             }
268             continue;
269         }
270 
271         literal_zero_index = literal_zero_index && tmp == 0;
272         idx.push_back(i1);
273     }
274 
275     if (literal_zero_index && zero_index != -1) {
276         return zero_index;
277     }
278 
279     if (end_ptr) {
280         *end_ptr = const_cast<wchar_t *>(in + pos);
281     }
282 
283     return 0;
284 }
285 
286 /// Expand all environment variables in the string *ptr.
287 ///
288 /// This function is slow, fragile and complicated. There are lots of little corner cases, like
289 /// $$foo should do a double expansion, $foo$bar should not double expand bar, etc.
290 ///
291 /// This function operates on strings backwards, starting at last_idx.
292 ///
293 /// Note: last_idx is considered to be where it previously finished procesisng. This means it
294 /// actually starts operating on last_idx-1. As such, to process a string fully, pass string.size()
295 /// as last_idx instead of string.size()-1.
296 ///
297 /// \return the result of expansion.
expand_variables(wcstring instr,completion_receiver_t * out,size_t last_idx,const environment_t & vars,parse_error_list_t * errors)298 static expand_result_t expand_variables(wcstring instr, completion_receiver_t *out, size_t last_idx,
299                                         const environment_t &vars, parse_error_list_t *errors) {
300     const size_t insize = instr.size();
301 
302     // last_idx may be 1 past the end of the string, but no further.
303     assert(last_idx <= insize && "Invalid last_idx");
304     if (last_idx == 0) {
305         if (!out->add(std::move(instr))) {
306             return append_overflow_error(errors);
307         }
308         return expand_result_t::ok;
309     }
310 
311     // Locate the last VARIABLE_EXPAND or VARIABLE_EXPAND_SINGLE
312     bool is_single = false;
313     size_t varexp_char_idx = last_idx;
314     while (varexp_char_idx--) {
315         const wchar_t c = instr.at(varexp_char_idx);
316         if (c == VARIABLE_EXPAND || c == VARIABLE_EXPAND_SINGLE) {
317             is_single = (c == VARIABLE_EXPAND_SINGLE);
318             break;
319         }
320     }
321     if (varexp_char_idx >= instr.size()) {
322         // No variable expand char, we're done.
323         if (!out->add(std::move(instr))) {
324             return append_overflow_error(errors);
325         }
326         return expand_result_t::ok;
327     }
328 
329     // Get the variable name.
330     const size_t var_name_start = varexp_char_idx + 1;
331     size_t var_name_stop = var_name_start;
332     while (var_name_stop < insize) {
333         const wchar_t nc = instr.at(var_name_stop);
334         if (nc == VARIABLE_EXPAND_EMPTY) {
335             var_name_stop++;
336             break;
337         }
338         if (!valid_var_name_char(nc)) break;
339         var_name_stop++;
340     }
341     assert(var_name_stop >= var_name_start && "Bogus variable name indexes");
342     const size_t var_name_len = var_name_stop - var_name_start;
343 
344     // It's an error if the name is empty.
345     if (var_name_len == 0) {
346         if (errors) {
347             parse_util_expand_variable_error(instr, 0 /* global_token_pos */, varexp_char_idx,
348                                              errors);
349         }
350         return expand_result_t::make_error(STATUS_EXPAND_ERROR);
351     }
352 
353     // Get the variable name as a string, then try to get the variable from env.
354     const wcstring var_name(instr, var_name_start, var_name_len);
355     // Do a dirty hack to make sliced history fast (#4650). We expand from either a variable, or a
356     // history_t. Note that "history" is read only in env.cpp so it's safe to special-case it in
357     // this way (it cannot be shadowed, etc).
358     std::shared_ptr<history_t> history{};
359     maybe_t<env_var_t> var{};
360     if (var_name == L"history") {
361         // Note reader_get_history may return null, if we are running non-interactively (e.g. from
362         // web_config).
363         if (is_main_thread()) {
364             history = history_t::with_name(history_session_id(env_stack_t::principal()));
365         }
366     } else if (var_name != wcstring{VARIABLE_EXPAND_EMPTY}) {
367         var = vars.get(var_name);
368     }
369 
370     // Parse out any following slice.
371     // Record the end of the variable name and any following slice.
372     size_t var_name_and_slice_stop = var_name_stop;
373     bool all_values = true;
374     const size_t slice_start = var_name_stop;
375     std::vector<long> var_idx_list;
376     if (slice_start < insize && instr.at(slice_start) == L'[') {
377         all_values = false;
378         const wchar_t *in = instr.c_str();
379         wchar_t *slice_end;
380         // If a variable is missing, behave as though we have one value, so that $var[1] always
381         // works.
382         size_t effective_val_count = 1;
383         if (var) {
384             effective_val_count = var->as_list().size();
385         } else if (history) {
386             effective_val_count = history->size();
387         }
388         size_t bad_pos =
389             parse_slice(in + slice_start, &slice_end, var_idx_list, effective_val_count);
390         if (bad_pos != 0) {
391             if (in[slice_start + bad_pos] == L'0') {
392                 append_syntax_error(errors, slice_start + bad_pos,
393                                     L"array indices start at 1, not 0.");
394             } else {
395                 append_syntax_error(errors, slice_start + bad_pos, L"Invalid index value");
396             }
397             return expand_result_t::make_error(STATUS_EXPAND_ERROR);
398         }
399         var_name_and_slice_stop = (slice_end - in);
400     }
401 
402     if (!var && !history) {
403         // Expanding a non-existent variable.
404         if (!is_single) {
405             // Normal expansions of missing variables successfully expand to nothing.
406             return expand_result_t::ok;
407         } else {
408             // Expansion to single argument.
409             // Replace the variable name and slice with VARIABLE_EXPAND_EMPTY.
410             wcstring res(instr, 0, varexp_char_idx);
411             if (!res.empty() && res.back() == VARIABLE_EXPAND_SINGLE) {
412                 res.push_back(VARIABLE_EXPAND_EMPTY);
413             }
414             res.append(instr, var_name_and_slice_stop, wcstring::npos);
415             return expand_variables(std::move(res), out, varexp_char_idx, vars, errors);
416         }
417     }
418 
419     // Ok, we have a variable or a history. Let's expand it.
420     // Start by respecting the sliced elements.
421     assert((var || history) && "Should have variable or history here");
422     wcstring_list_t var_item_list;
423     if (all_values) {
424         if (history) {
425             history->get_history(var_item_list);
426         } else {
427             var->to_list(var_item_list);
428         }
429     } else {
430         // We have to respect the slice.
431         if (history) {
432             // Ask history to map indexes to item strings.
433             // Note this may have missing entries for out-of-bounds.
434             auto item_map = history->items_at_indexes(var_idx_list);
435             for (long item_index : var_idx_list) {
436                 auto iter = item_map.find(item_index);
437                 if (iter != item_map.end()) {
438                     var_item_list.push_back(iter->second);
439                 }
440             }
441         } else {
442             const wcstring_list_t &all_var_items = var->as_list();
443             for (long item_index : var_idx_list) {
444                 // Check that we are within array bounds. If not, skip the element. Note:
445                 // Negative indices (`echo $foo[-1]`) are already converted to positive ones
446                 // here, So tmp < 1 means it's definitely not in.
447                 // Note we are 1-based.
448                 if (item_index >= 1 && size_t(item_index) <= all_var_items.size()) {
449                     var_item_list.push_back(all_var_items.at(item_index - 1));
450                 }
451             }
452         }
453     }
454 
455     if (is_single) {
456         // Quoted expansion. Here we expect the variable's delimiter.
457         // Note history always has a space delimiter.
458         wchar_t delimit = history ? L' ' : var->get_delimiter();
459         wcstring res(instr, 0, varexp_char_idx);
460         if (!res.empty()) {
461             if (res.back() != VARIABLE_EXPAND_SINGLE) {
462                 res.push_back(INTERNAL_SEPARATOR);
463             } else if (var_item_list.empty() || var_item_list.front().empty()) {
464                 // First expansion is empty, but we need to recursively expand.
465                 res.push_back(VARIABLE_EXPAND_EMPTY);
466             }
467         }
468 
469         // Append all entries in var_item_list, separated by the delimiter.
470         res.append(join_strings(var_item_list, delimit));
471         res.append(instr, var_name_and_slice_stop, wcstring::npos);
472         return expand_variables(std::move(res), out, varexp_char_idx, vars, errors);
473     } else {
474         // Normal cartesian-product expansion.
475         for (wcstring &item : var_item_list) {
476             if (varexp_char_idx == 0 && var_name_and_slice_stop == insize) {
477                 if (!out->add(std::move(item))) {
478                     return append_overflow_error(errors);
479                 }
480             } else {
481                 wcstring new_in(instr, 0, varexp_char_idx);
482                 if (!new_in.empty()) {
483                     if (new_in.back() != VARIABLE_EXPAND) {
484                         new_in.push_back(INTERNAL_SEPARATOR);
485                     } else if (item.empty()) {
486                         new_in.push_back(VARIABLE_EXPAND_EMPTY);
487                     }
488                 }
489                 new_in.append(item);
490                 new_in.append(instr, var_name_and_slice_stop, wcstring::npos);
491                 auto res = expand_variables(std::move(new_in), out, varexp_char_idx, vars, errors);
492                 if (res.result != expand_result_t::ok) {
493                     return res;
494                 }
495             }
496         }
497     }
498     return expand_result_t::ok;
499 }
500 
501 /// Perform brace expansion, placing the expanded strings into \p out.
expand_braces(wcstring && instr,expand_flags_t flags,completion_receiver_t * out,parse_error_list_t * errors)502 static expand_result_t expand_braces(wcstring &&instr, expand_flags_t flags,
503                                      completion_receiver_t *out, parse_error_list_t *errors) {
504     bool syntax_error = false;
505     int brace_count = 0;
506 
507     const wchar_t *brace_begin = nullptr, *brace_end = nullptr;
508     const wchar_t *last_sep = nullptr;
509 
510     const wchar_t *item_begin;
511     size_t length_preceding_braces, length_following_braces, tot_len;
512 
513     const wchar_t *const in = instr.c_str();
514 
515     // Locate the first non-nested brace pair.
516     for (const wchar_t *pos = in; (*pos) && !syntax_error; pos++) {
517         switch (*pos) {
518             case BRACE_BEGIN: {
519                 if (brace_count == 0) brace_begin = pos;
520                 brace_count++;
521                 break;
522             }
523             case BRACE_END: {
524                 brace_count--;
525                 if (brace_count < 0) {
526                     syntax_error = true;
527                 } else if (brace_count == 0) {
528                     brace_end = pos;
529                 }
530                 break;
531             }
532             case BRACE_SEP: {
533                 if (brace_count == 1) last_sep = pos;
534                 break;
535             }
536             default: {
537                 break;  // we ignore all other characters here
538             }
539         }
540     }
541 
542     if (brace_count > 0) {
543         if (!(flags & expand_flag::for_completions)) {
544             syntax_error = true;
545         } else {
546             // The user hasn't typed an end brace yet; make one up and append it, then expand
547             // that.
548             wcstring mod;
549             if (last_sep) {
550                 mod.append(in, brace_begin - in + 1);
551                 mod.append(last_sep + 1);
552                 mod.push_back(BRACE_END);
553             } else {
554                 mod.append(in);
555                 mod.push_back(BRACE_END);
556             }
557 
558             // Note: this code looks very fishy, apparently it has never worked.
559             return expand_braces(std::move(mod), expand_flag::skip_cmdsubst, out, errors);
560         }
561     }
562 
563     if (syntax_error) {
564         append_syntax_error(errors, SOURCE_LOCATION_UNKNOWN, _(L"Mismatched braces"));
565         return expand_result_t::make_error(STATUS_EXPAND_ERROR);
566     }
567 
568     if (brace_begin == nullptr) {
569         if (!out->add(std::move(instr))) {
570             return expand_result_t::error;
571         }
572         return expand_result_t::ok;
573     }
574 
575     length_preceding_braces = (brace_begin - in);
576     length_following_braces = instr.size() - (brace_end - in) - 1;
577     tot_len = length_preceding_braces + length_following_braces;
578     item_begin = brace_begin + 1;
579     for (const wchar_t *pos = (brace_begin + 1); true; pos++) {
580         if (brace_count == 0 && ((*pos == BRACE_SEP) || (pos == brace_end))) {
581             assert(pos >= item_begin);
582             size_t item_len = pos - item_begin;
583             wcstring item = wcstring(item_begin, item_len);
584             item = trim(item, (const wchar_t[]){BRACE_SPACE, L'\0'});
585             for (auto &c : item) {
586                 if (c == BRACE_SPACE) {
587                     c = ' ';
588                 }
589             }
590 
591             wcstring whole_item;
592             whole_item.reserve(tot_len + item_len + 2);
593             whole_item.append(in, length_preceding_braces);
594             whole_item.append(item.begin(), item.end());
595             whole_item.append(brace_end + 1);
596             expand_braces(std::move(whole_item), flags, out, errors);
597 
598             item_begin = pos + 1;
599             if (pos == brace_end) break;
600         }
601 
602         if (*pos == BRACE_BEGIN) {
603             brace_count++;
604         }
605 
606         if (*pos == BRACE_END) {
607             brace_count--;
608         }
609     }
610     return expand_result_t::ok;
611 }
612 
613 /// Expand a command substitution \p input, executing on \p ctx, and inserting the results into
614 /// \p out_list, or any errors into \p errors. \return an expand result.
expand_cmdsubst(wcstring input,const operation_context_t & ctx,completion_receiver_t * out,parse_error_list_t * errors)615 static expand_result_t expand_cmdsubst(wcstring input, const operation_context_t &ctx,
616                                        completion_receiver_t *out, parse_error_list_t *errors) {
617     assert(ctx.parser && "Cannot expand without a parser");
618     size_t cursor = 0;
619     size_t paren_begin = 0;
620     size_t paren_end = 0;
621     wcstring subcmd;
622 
623     switch (parse_util_locate_cmdsubst_range(input, &cursor, &subcmd, &paren_begin, &paren_end,
624                                              false)) {
625         case -1: {
626             append_syntax_error(errors, SOURCE_LOCATION_UNKNOWN, L"Mismatched parenthesis");
627             return expand_result_t::make_error(STATUS_EXPAND_ERROR);
628         }
629         case 0: {
630             if (!out->add(std::move(input))) {
631                 return append_overflow_error(errors);
632             }
633             return expand_result_t::ok;
634         }
635         case 1: {
636             break;
637         }
638         default: {
639             DIE("unhandled parse_ret value");
640         }
641     }
642 
643     wcstring_list_t sub_res;
644     int subshell_status = exec_subshell_for_expand(subcmd, *ctx.parser, ctx.job_group, sub_res);
645     if (subshell_status != 0) {
646         // TODO: Ad-hoc switch, how can we enumerate the possible errors more safely?
647         const wchar_t *err;
648         switch (subshell_status) {
649             case STATUS_READ_TOO_MUCH:
650                 err = L"Too much data emitted by command substitution so it was discarded";
651                 break;
652             case STATUS_CMD_ERROR:
653                 err = L"Too many active file descriptors";
654                 break;
655             case STATUS_CMD_UNKNOWN:
656                 err = L"Unknown command";
657                 break;
658             case STATUS_ILLEGAL_CMD:
659                 err = L"Commandname was invalid";
660                 break;
661             case STATUS_NOT_EXECUTABLE:
662                 err = L"Command not executable";
663                 break;
664             default:
665                 err = L"Unknown error while evaluating command substitution";
666                 break;
667         }
668         append_cmdsub_error(errors, paren_begin, _(err));
669         return expand_result_t::make_error(subshell_status);
670     }
671 
672     // Expand slices like (cat /var/words)[1]
673     size_t tail_begin = paren_end + 1;
674     if (tail_begin < input.size() && input.at(tail_begin) == L'[') {
675         const wchar_t *in = input.c_str();
676         std::vector<long> slice_idx;
677         const wchar_t *const slice_begin = in + tail_begin;
678         wchar_t *slice_end = nullptr;
679         size_t bad_pos = parse_slice(slice_begin, &slice_end, slice_idx, sub_res.size());
680         if (bad_pos != 0) {
681             if (slice_begin[bad_pos] == L'0') {
682                 append_syntax_error(errors, slice_begin - in + bad_pos,
683                                     L"array indices start at 1, not 0.");
684             } else {
685                 append_syntax_error(errors, slice_begin - in + bad_pos, L"Invalid index value");
686             }
687             return expand_result_t::make_error(STATUS_EXPAND_ERROR);
688         }
689 
690         wcstring_list_t sub_res2;
691         tail_begin = slice_end - in;
692         for (long idx : slice_idx) {
693             if (static_cast<size_t>(idx) > sub_res.size() || idx < 1) {
694                 continue;
695             }
696             // -1 to convert from 1-based slice index to C++ 0-based vector index.
697             sub_res2.push_back(sub_res.at(idx - 1));
698         }
699         sub_res = std::move(sub_res2);
700     }
701 
702     // Recursively call ourselves to expand any remaining command substitutions. The result of this
703     // recursive call using the tail of the string is inserted into the tail_expand array list
704     completion_receiver_t tail_expand_recv = out->subreceiver();
705     expand_cmdsubst(input.substr(tail_begin), ctx, &tail_expand_recv,
706                     errors);  // TODO: offset error locations
707     completion_list_t tail_expand = tail_expand_recv.take();
708 
709     // Combine the result of the current command substitution with the result of the recursive tail
710     // expansion.
711     for (const wcstring &sub_item : sub_res) {
712         wcstring sub_item2 = escape_string(sub_item, ESCAPE_ALL);
713         for (const completion_t &tail_item : tail_expand) {
714             wcstring whole_item;
715             whole_item.reserve(paren_begin + 1 + sub_item2.size() + 1 +
716                                tail_item.completion.size());
717             whole_item.append(input, 0, paren_begin);
718             whole_item.push_back(INTERNAL_SEPARATOR);
719             whole_item.append(sub_item2);
720             whole_item.push_back(INTERNAL_SEPARATOR);
721             whole_item.append(tail_item.completion);
722             if (!out->add(std::move(whole_item))) {
723                 return append_overflow_error(errors);
724             }
725         }
726     }
727 
728     return expand_result_t::ok;
729 }
730 
731 // Given that input[0] is HOME_DIRECTORY or tilde (ugh), return the user's name. Return the empty
732 // string if it is just a tilde. Also return by reference the index of the first character of the
733 // remaining part of the string (e.g. the subsequent slash).
get_home_directory_name(const wcstring & input,size_t * out_tail_idx)734 static wcstring get_home_directory_name(const wcstring &input, size_t *out_tail_idx) {
735     assert(input[0] == HOME_DIRECTORY || input[0] == L'~');
736 
737     auto pos = input.find_first_of(L'/');
738     // We get the position of the /, but we need to remove it as well.
739     if (pos != wcstring::npos) {
740         *out_tail_idx = pos;
741         pos -= 1;
742     } else {
743         *out_tail_idx = input.length();
744     }
745 
746     return input.substr(1, pos);
747 }
748 
749 /// Attempts tilde expansion of the string specified, modifying it in place.
expand_home_directory(wcstring & input,const environment_t & vars)750 static void expand_home_directory(wcstring &input, const environment_t &vars) {
751     if (!input.empty() && input.at(0) == HOME_DIRECTORY) {
752         size_t tail_idx;
753         wcstring username = get_home_directory_name(input, &tail_idx);
754 
755         maybe_t<wcstring> home;
756         if (username.empty()) {
757             // Current users home directory.
758             auto home_var = vars.get(L"HOME");
759             if (home_var.missing_or_empty()) {
760                 input.clear();
761                 return;
762             }
763             home = home_var->as_string();
764             tail_idx = 1;
765         } else {
766             // Some other user's home directory.
767             std::string name_cstr = wcs2string(username);
768             struct passwd userinfo;
769             struct passwd *result;
770             char buf[8192];
771             int retval = getpwnam_r(name_cstr.c_str(), &userinfo, buf, sizeof(buf), &result);
772             if (!retval && result) {
773                 home = str2wcstring(userinfo.pw_dir);
774             }
775         }
776 
777         maybe_t<wcstring> realhome;
778         if (home) realhome = normalize_path(*home);
779 
780         if (realhome) {
781             input.replace(input.begin(), input.begin() + tail_idx, *realhome);
782         } else {
783             input[0] = L'~';
784         }
785     }
786 }
787 
788 /// Expand the %self escape. Note this can only come at the beginning of the string.
expand_percent_self(wcstring & input)789 static void expand_percent_self(wcstring &input) {
790     if (!input.empty() && input.front() == PROCESS_EXPAND_SELF) {
791         input.replace(0, 1, to_string(getpid()));
792     }
793 }
794 
expand_tilde(wcstring & input,const environment_t & vars)795 void expand_tilde(wcstring &input, const environment_t &vars) {
796     // Avoid needless COW behavior by ensuring we use const at.
797     const wcstring &tmp = input;
798     if (!tmp.empty() && tmp.at(0) == L'~') {
799         input.at(0) = HOME_DIRECTORY;
800         expand_home_directory(input, vars);
801     }
802 }
803 
unexpand_tildes(const wcstring & input,const environment_t & vars,completion_list_t * completions)804 static void unexpand_tildes(const wcstring &input, const environment_t &vars,
805                             completion_list_t *completions) {
806     // If input begins with tilde, then try to replace the corresponding string in each completion
807     // with the tilde. If it does not, there's nothing to do.
808     if (input.empty() || input.at(0) != L'~') return;
809 
810     // We only operate on completions that replace their contents. If we don't have any, we're done.
811     // In particular, empty vectors are common.
812     bool has_candidate_completion = false;
813     for (const auto &completion : *completions) {
814         if (completion.flags & COMPLETE_REPLACES_TOKEN) {
815             has_candidate_completion = true;
816             break;
817         }
818     }
819     if (!has_candidate_completion) return;
820 
821     size_t tail_idx;
822     wcstring username_with_tilde = L"~";
823     username_with_tilde.append(get_home_directory_name(input, &tail_idx));
824 
825     // Expand username_with_tilde.
826     wcstring home = username_with_tilde;
827     expand_tilde(home, vars);
828 
829     // Now for each completion that starts with home, replace it with the username_with_tilde.
830     for (auto &comp : *completions) {
831         if ((comp.flags & COMPLETE_REPLACES_TOKEN) &&
832             string_prefixes_string(home, comp.completion)) {
833             comp.completion.replace(0, home.size(), username_with_tilde);
834 
835             // And mark that our tilde is literal, so it doesn't try to escape it.
836             comp.flags |= COMPLETE_DONT_ESCAPE_TILDES;
837         }
838     }
839 }
840 
841 // If the given path contains the user's home directory, replace that with a tilde. We don't try to
842 // be smart about case insensitivity, etc.
replace_home_directory_with_tilde(const wcstring & str,const environment_t & vars)843 wcstring replace_home_directory_with_tilde(const wcstring &str, const environment_t &vars) {
844     // Only absolute paths get this treatment.
845     wcstring result = str;
846     if (string_prefixes_string(L"/", result)) {
847         wcstring home_directory = L"~";
848         expand_tilde(home_directory, vars);
849         if (!string_suffixes_string(L"/", home_directory)) {
850             home_directory.push_back(L'/');
851         }
852 
853         // Now check if the home_directory prefixes the string.
854         if (string_prefixes_string(home_directory, result)) {
855             // Success
856             result.replace(0, home_directory.size(), L"~/");
857         }
858     }
859     return result;
860 }
861 
862 /// Remove any internal separators. Also optionally convert wildcard characters to regular
863 /// equivalents. This is done to support skip_wildcards.
remove_internal_separator(wcstring * str,bool conv)864 static void remove_internal_separator(wcstring *str, bool conv) {
865     // Remove all instances of INTERNAL_SEPARATOR.
866     str->erase(std::remove(str->begin(), str->end(), static_cast<wchar_t>(INTERNAL_SEPARATOR)),
867                str->end());
868 
869     // If conv is true, replace all instances of ANY_STRING with '*',
870     // ANY_STRING_RECURSIVE with '*'.
871     if (conv) {
872         for (auto &idx : *str) {
873             switch (idx) {
874                 case ANY_CHAR: {
875                     idx = L'?';
876                     break;
877                 }
878                 case ANY_STRING:
879                 case ANY_STRING_RECURSIVE: {
880                     idx = L'*';
881                     break;
882                 }
883                 default: {
884                     break;  // we ignore all other characters
885                 }
886             }
887         }
888     }
889 }
890 
891 namespace {
892 /// A type that knows how to perform expansions.
893 class expander_t {
894     /// Operation context for this expansion.
895     const operation_context_t &ctx;
896 
897     /// Flags to use during expansion.
898     const expand_flags_t flags;
899 
900     /// List to receive any errors generated during expansion, or null to ignore errors.
901     parse_error_list_t *const errors;
902 
903     /// An expansion stage is a member function pointer.
904     /// It accepts the input string (transferring ownership) and returns the list of output
905     /// completions by reference. It may return an error, which halts expansion.
906     using stage_t = expand_result_t (expander_t::*)(wcstring, completion_receiver_t *);
907 
908     expand_result_t stage_cmdsubst(wcstring input, completion_receiver_t *out);
909     expand_result_t stage_variables(wcstring input, completion_receiver_t *out);
910     expand_result_t stage_braces(wcstring input, completion_receiver_t *out);
911     expand_result_t stage_home_and_self(wcstring input, completion_receiver_t *out);
912     expand_result_t stage_wildcards(wcstring path_to_expand, completion_receiver_t *out);
913 
expander_t(const operation_context_t & ctx,expand_flags_t flags,parse_error_list_t * errors)914     expander_t(const operation_context_t &ctx, expand_flags_t flags, parse_error_list_t *errors)
915         : ctx(ctx), flags(flags), errors(errors) {}
916 
917    public:
918     static expand_result_t expand_string(wcstring input, completion_receiver_t *out_completions,
919                                          expand_flags_t flags, const operation_context_t &ctx,
920                                          parse_error_list_t *errors);
921 };
922 
stage_cmdsubst(wcstring input,completion_receiver_t * out)923 expand_result_t expander_t::stage_cmdsubst(wcstring input, completion_receiver_t *out) {
924     if (flags & expand_flag::skip_cmdsubst) {
925         size_t cur = 0, start = 0, end;
926         switch (parse_util_locate_cmdsubst_range(input, &cur, nullptr, &start, &end, true)) {
927             case 0:
928                 if (!out->add(std::move(input))) {
929                     return append_overflow_error(errors);
930                 }
931                 return expand_result_t::ok;
932             case 1:
933                 append_cmdsub_error(errors, start, L"Command substitutions not allowed");
934                 /* intentionally falls through */
935             case -1:
936             default:
937                 return expand_result_t::make_error(STATUS_EXPAND_ERROR);
938         }
939     } else {
940         assert(ctx.parser && "Must have a parser to expand command substitutions");
941         return expand_cmdsubst(std::move(input), ctx, out, errors);
942     }
943 }
944 
stage_variables(wcstring input,completion_receiver_t * out)945 expand_result_t expander_t::stage_variables(wcstring input, completion_receiver_t *out) {
946     // We accept incomplete strings here, since complete uses expand_string to expand incomplete
947     // strings from the commandline.
948     wcstring next;
949     unescape_string(input, &next, UNESCAPE_SPECIAL | UNESCAPE_INCOMPLETE);
950 
951     if (flags & expand_flag::skip_variables) {
952         for (auto &i : next) {
953             if (i == VARIABLE_EXPAND) {
954                 i = L'$';
955             }
956         }
957         if (!out->add(std::move(next))) {
958             return append_overflow_error(errors);
959         }
960         return expand_result_t::ok;
961     } else {
962         size_t size = next.size();
963         return expand_variables(std::move(next), out, size, ctx.vars, errors);
964     }
965 }
966 
stage_braces(wcstring input,completion_receiver_t * out)967 expand_result_t expander_t::stage_braces(wcstring input, completion_receiver_t *out) {
968     return expand_braces(std::move(input), flags, out, errors);
969 }
970 
stage_home_and_self(wcstring input,completion_receiver_t * out)971 expand_result_t expander_t::stage_home_and_self(wcstring input, completion_receiver_t *out) {
972     if (!(flags & expand_flag::skip_home_directories)) {
973         expand_home_directory(input, ctx.vars);
974     }
975     expand_percent_self(input);
976     if (!out->add(std::move(input))) {
977         return append_overflow_error(errors);
978     }
979     return expand_result_t::ok;
980 }
981 
stage_wildcards(wcstring path_to_expand,completion_receiver_t * out)982 expand_result_t expander_t::stage_wildcards(wcstring path_to_expand, completion_receiver_t *out) {
983     expand_result_t result = expand_result_t::ok;
984 
985     remove_internal_separator(&path_to_expand, flags & expand_flag::skip_wildcards);
986     const bool has_wildcard = wildcard_has(path_to_expand, true /* internal, i.e. ANY_STRING */);
987     const bool for_completions = flags & expand_flag::for_completions;
988     const bool skip_wildcards = flags & expand_flag::skip_wildcards;
989 
990     if (has_wildcard && (flags & expand_flag::executables_only)) {
991         // don't do wildcard expansion for executables, see issue #785
992     } else if ((for_completions && !skip_wildcards) || has_wildcard) {
993         // We either have a wildcard, or we don't have a wildcard but we're doing completion
994         // expansion (so we want to get the completion of a file path). Note that if
995         // skip_wildcards is set, we stomped wildcards in remove_internal_separator above, so
996         // there actually aren't any.
997         //
998         // So we're going to treat this input as a file path. Compute the "working directories",
999         // which may be CDPATH if the special flag is set.
1000         const wcstring working_dir = ctx.vars.get_pwd_slash();
1001         wcstring_list_t effective_working_dirs;
1002         bool for_cd = flags & expand_flag::special_for_cd;
1003         bool for_command = flags & expand_flag::special_for_command;
1004         if (!for_cd && !for_command) {
1005             // Common case.
1006             effective_working_dirs.push_back(working_dir);
1007         } else {
1008             // Either special_for_command or special_for_cd. We can handle these
1009             // mostly the same. There's the following differences:
1010             //
1011             // 1. An empty CDPATH should be treated as '.', but an empty PATH should be left empty
1012             // (no commands can be found). Also, an empty element in either is treated as '.' for
1013             // consistency with POSIX shells. Note that we rely on the latter by having called
1014             // `munge_colon_delimited_array()` for these special env vars. Thus we do not
1015             // special-case them here.
1016             //
1017             // 2. PATH is only "one level," while CDPATH is multiple levels. That is, input like
1018             // 'foo/bar' should resolve against CDPATH, but not PATH.
1019             //
1020             // In either case, we ignore the path if we start with ./ or /. Also ignore it if we are
1021             // doing command completion and we contain a slash, per IEEE 1003.1, chapter 8 under
1022             // PATH.
1023             if (string_prefixes_string(L"/", path_to_expand) ||
1024                 string_prefixes_string(L"./", path_to_expand) ||
1025                 string_prefixes_string(L"../", path_to_expand) ||
1026                 (for_command && path_to_expand.find(L'/') != wcstring::npos)) {
1027                 effective_working_dirs.push_back(working_dir);
1028             } else {
1029                 // Get the PATH/CDPATH and CWD. Perhaps these should be passed in. An empty CDPATH
1030                 // implies just the current directory, while an empty PATH is left empty.
1031                 wcstring_list_t paths;
1032                 if (auto paths_var = ctx.vars.get(for_cd ? L"CDPATH" : L"PATH")) {
1033                     paths = paths_var->as_list();
1034                 }
1035                 if (paths.empty()) {
1036                     paths.emplace_back(for_cd ? L"." : L"");
1037                 }
1038                 for (const wcstring &next_path : paths) {
1039                     effective_working_dirs.push_back(
1040                         path_apply_working_directory(next_path, working_dir));
1041                 }
1042             }
1043         }
1044 
1045         result = expand_result_t::wildcard_no_match;
1046         completion_receiver_t expanded_recv = out->subreceiver();
1047         for (const auto &effective_working_dir : effective_working_dirs) {
1048             wildcard_result_t expand_res = wildcard_expand_string(
1049                 path_to_expand, effective_working_dir, flags, ctx.cancel_checker, &expanded_recv);
1050             switch (expand_res) {
1051                 case wildcard_result_t::match:
1052                     result = expand_result_t::ok;
1053                     break;
1054                 case wildcard_result_t::no_match:
1055                     break;
1056                 case wildcard_result_t::overflow:
1057                     return append_overflow_error(errors);
1058                 case wildcard_result_t::cancel:
1059                     return expand_result_t::cancel;
1060             }
1061         }
1062 
1063         completion_list_t expanded = expanded_recv.take();
1064         std::sort(expanded.begin(), expanded.end(),
1065                   [&](const completion_t &a, const completion_t &b) {
1066                       return wcsfilecmp_glob(a.completion.c_str(), b.completion.c_str()) < 0;
1067                   });
1068         if (!out->add_list(std::move(expanded))) {
1069             result = expand_result_t::error;
1070         }
1071     } else {
1072         // Can't fully justify this check. I think it's that SKIP_WILDCARDS is used when completing
1073         // to mean don't do file expansions, so if we're not doing file expansions, just drop this
1074         // completion on the floor.
1075         if (!(flags & expand_flag::for_completions)) {
1076             if (!out->add(std::move(path_to_expand))) {
1077                 return append_overflow_error(errors);
1078             }
1079         }
1080     }
1081     return result;
1082 }
1083 
expand_string(wcstring input,completion_receiver_t * out_completions,expand_flags_t flags,const operation_context_t & ctx,parse_error_list_t * errors)1084 expand_result_t expander_t::expand_string(wcstring input, completion_receiver_t *out_completions,
1085                                           expand_flags_t flags, const operation_context_t &ctx,
1086                                           parse_error_list_t *errors) {
1087     assert(((flags & expand_flag::skip_cmdsubst) || ctx.parser) &&
1088            "Must have a parser if not skipping command substitutions");
1089     // Early out. If we're not completing, and there's no magic in the input, we're done.
1090     if (!(flags & expand_flag::for_completions) && expand_is_clean(input)) {
1091         if (!out_completions->add(std::move(input))) {
1092             return append_overflow_error(errors);
1093         }
1094         return expand_result_t::ok;
1095     }
1096 
1097     expander_t expand(ctx, flags, errors);
1098 
1099     // Our expansion stages.
1100     const stage_t stages[] = {&expander_t::stage_cmdsubst, &expander_t::stage_variables,
1101                               &expander_t::stage_braces, &expander_t::stage_home_and_self,
1102                               &expander_t::stage_wildcards};
1103 
1104     // Load up our single initial completion.
1105     completion_list_t completions;
1106     append_completion(&completions, input);
1107 
1108     completion_receiver_t output_storage = out_completions->subreceiver();
1109     expand_result_t total_result = expand_result_t::ok;
1110     for (stage_t stage : stages) {
1111         for (completion_t &comp : completions) {
1112             if (ctx.check_cancel()) {
1113                 total_result = expand_result_t::cancel;
1114                 break;
1115             }
1116             expand_result_t this_result =
1117                 (expand.*stage)(std::move(comp.completion), &output_storage);
1118             total_result = this_result;
1119             if (total_result == expand_result_t::error) {
1120                 break;
1121             }
1122         }
1123 
1124         // Output becomes our next stage's input.
1125         output_storage.swap(completions);
1126         output_storage.clear();
1127         if (total_result == expand_result_t::error) {
1128             break;
1129         }
1130     }
1131 
1132     // This is a little tricky: if one wildcard failed to match but we still got output, it
1133     // means that a previous expansion resulted in multiple strings. For example:
1134     //   set dirs ./a ./b
1135     //   echo $dirs/*.txt
1136     // Here if ./a/*.txt matches and ./b/*.txt does not, then we don't want to report a failed
1137     // wildcard. So swallow failed-wildcard errors if we got any output.
1138     if (total_result == expand_result_t::wildcard_no_match && !completions.empty()) {
1139         total_result = expand_result_t::ok;
1140     }
1141 
1142     if (total_result == expand_result_t::ok) {
1143         // Hack to un-expand tildes (see #647).
1144         if (!(flags & expand_flag::skip_home_directories)) {
1145             unexpand_tildes(input, ctx.vars, &completions);
1146         }
1147         if (!out_completions->add_list(std::move(completions))) {
1148             total_result = append_overflow_error(errors);
1149         }
1150     }
1151     return total_result;
1152 }
1153 }  // namespace
1154 
expand_string(wcstring input,completion_list_t * out_completions,expand_flags_t flags,const operation_context_t & ctx,parse_error_list_t * errors)1155 expand_result_t expand_string(wcstring input, completion_list_t *out_completions,
1156                               expand_flags_t flags, const operation_context_t &ctx,
1157                               parse_error_list_t *errors) {
1158     completion_receiver_t recv(std::move(*out_completions), ctx.expansion_limit);
1159     auto res = expand_string(std::move(input), &recv, flags, ctx, errors);
1160     *out_completions = recv.take();
1161     return res;
1162 }
1163 
expand_string(wcstring input,completion_receiver_t * out_completions,expand_flags_t flags,const operation_context_t & ctx,parse_error_list_t * errors)1164 expand_result_t expand_string(wcstring input, completion_receiver_t *out_completions,
1165                               expand_flags_t flags, const operation_context_t &ctx,
1166                               parse_error_list_t *errors) {
1167     return expander_t::expand_string(std::move(input), out_completions, flags, ctx, errors);
1168 }
1169 
expand_one(wcstring & string,expand_flags_t flags,const operation_context_t & ctx,parse_error_list_t * errors)1170 bool expand_one(wcstring &string, expand_flags_t flags, const operation_context_t &ctx,
1171                 parse_error_list_t *errors) {
1172     completion_list_t completions;
1173 
1174     if (!flags.get(expand_flag::for_completions) && expand_is_clean(string)) {
1175         return true;
1176     }
1177 
1178     if (expand_string(std::move(string), &completions, flags, ctx, errors) == expand_result_t::ok &&
1179         completions.size() == 1) {
1180         string = std::move(completions.at(0).completion);
1181         return true;
1182     }
1183     return false;
1184 }
1185 
expand_to_command_and_args(const wcstring & instr,const operation_context_t & ctx,wcstring * out_cmd,wcstring_list_t * out_args,parse_error_list_t * errors,bool skip_wildcards)1186 expand_result_t expand_to_command_and_args(const wcstring &instr, const operation_context_t &ctx,
1187                                            wcstring *out_cmd, wcstring_list_t *out_args,
1188                                            parse_error_list_t *errors, bool skip_wildcards) {
1189     // Fast path.
1190     if (expand_is_clean(instr)) {
1191         *out_cmd = instr;
1192         return expand_result_t::ok;
1193     }
1194 
1195     expand_flags_t eflags{expand_flag::skip_cmdsubst};
1196     if (skip_wildcards) {
1197         eflags.set(expand_flag::skip_wildcards);
1198     }
1199 
1200     completion_list_t completions;
1201     expand_result_t expand_err = expand_string(instr, &completions, eflags, ctx, errors);
1202     if (expand_err == expand_result_t::ok) {
1203         // The first completion is the command, any remaning are arguments.
1204         bool first = true;
1205         for (auto &comp : completions) {
1206             if (first) {
1207                 if (out_cmd) *out_cmd = std::move(comp.completion);
1208                 first = false;
1209             } else {
1210                 if (out_args) out_args->push_back(std::move(comp.completion));
1211             }
1212         }
1213     }
1214     return expand_err;
1215 }
1216 
1217 // https://github.com/fish-shell/fish-shell/issues/367
1218 //
1219 // With them the Seed of Wisdom did I sow,
1220 // And with my own hand labour'd it to grow:
1221 // And this was all the Harvest that I reap'd---
1222 // "I came like Water, and like Wind I go."
1223 
escape_single_quoted_hack_hack_hack_hack(const char * str)1224 static std::string escape_single_quoted_hack_hack_hack_hack(const char *str) {
1225     std::string result;
1226     size_t len = std::strlen(str);
1227     result.reserve(len + 2);
1228     result.push_back('\'');
1229     for (size_t i = 0; i < len; i++) {
1230         char c = str[i];
1231         // Escape backslashes and single quotes only.
1232         if (c == '\\' || c == '\'') result.push_back('\\');
1233         result.push_back(c);
1234     }
1235     result.push_back('\'');
1236     return result;
1237 }
1238 
fish_xdm_login_hack_hack_hack_hack(std::vector<std::string> * cmds,int argc,const char * const * argv)1239 bool fish_xdm_login_hack_hack_hack_hack(std::vector<std::string> *cmds, int argc,
1240                                         const char *const *argv) {
1241     if (!cmds || cmds->size() != 1) {
1242         return false;
1243     }
1244 
1245     bool result = false;
1246     const std::string &cmd = cmds->at(0);
1247     if (cmd == "exec \"${@}\"" || cmd == "exec \"$@\"") {
1248         // We're going to construct a new command that starts with exec, and then has the
1249         // remaining arguments escaped.
1250         std::string new_cmd = "exec";
1251         for (int i = 1; i < argc; i++) {
1252             const char *arg = argv[i];
1253             if (arg) {
1254                 new_cmd.push_back(' ');
1255                 new_cmd.append(escape_single_quoted_hack_hack_hack_hack(arg));
1256             }
1257         }
1258 
1259         cmds->at(0) = new_cmd;
1260         result = true;
1261     }
1262     return result;
1263 }
1264 
expand_abbreviation(const wcstring & src,const environment_t & vars)1265 maybe_t<wcstring> expand_abbreviation(const wcstring &src, const environment_t &vars) {
1266     if (src.empty()) return none();
1267 
1268     wcstring esc_src = escape_string(src, 0, STRING_STYLE_VAR);
1269     if (esc_src.empty()) {
1270         return none();
1271     }
1272     wcstring var_name = L"_fish_abbr_" + esc_src;
1273     if (auto var_value = vars.get(var_name)) {
1274         return var_value->as_string();
1275     }
1276     return none();
1277 }
1278 
get_abbreviations(const environment_t & vars)1279 std::map<wcstring, wcstring> get_abbreviations(const environment_t &vars) {
1280     const wcstring prefix = L"_fish_abbr_";
1281     auto names = vars.get_names(0);
1282     std::map<wcstring, wcstring> result;
1283     for (const wcstring &name : names) {
1284         if (string_prefixes_string(prefix, name)) {
1285             wcstring key;
1286             unescape_string(name.substr(prefix.size()), &key, UNESCAPE_DEFAULT, STRING_STYLE_VAR);
1287             result[key] = vars.get(name)->as_string();
1288         }
1289     }
1290     return result;
1291 }
1292