// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include <string.h>
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <uv.h>
#include "nvim/ascii.h"
#include "nvim/fileio.h"
#include "nvim/lib/kvec.h"
#include "nvim/log.h"
#include "nvim/event/loop.h"
#include "nvim/event/libuv_process.h"
#include "nvim/event/rstream.h"
#include "nvim/ex_cmds.h"
#include "nvim/misc1.h"
#include "nvim/os/shell.h"
#include "nvim/os/signal.h"
#include "nvim/path.h"
#include "nvim/types.h"
#include "nvim/main.h"
#include "nvim/vim.h"
#include "nvim/message.h"
#include "nvim/memory.h"
#include "nvim/ui.h"
#include "nvim/screen.h"
#include "nvim/memline.h"
#include "nvim/option_defs.h"
#include "nvim/charset.h"
#include "nvim/strings.h"
#define DYNAMIC_BUFFER_INIT { NULL, 0, 0 }
#define NS_1_SECOND 1000000000U // 1 second, in nanoseconds
#define OUT_DATA_THRESHOLD 1024 * 10U // 10KB, "a few screenfuls" of data.
#define SHELL_SPECIAL (char_u *)"\t \"&'$;<>()\\|"
typedef struct {
char *data;
size_t cap, len;
} DynamicBuffer;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/shell.c.generated.h"
#endif
static void save_patterns(int num_pat, char_u **pat, int *num_file,
char_u ***file)
{
*file = xmalloc((size_t)num_pat * sizeof(char_u *));
for (int i = 0; i < num_pat; i++) {
char_u *s = vim_strsave(pat[i]);
// Be compatible with expand_filename(): halve the number of
// backslashes.
backslash_halve(s);
(*file)[i] = s;
}
*num_file = num_pat;
}
static bool have_wildcard(int num, char_u **file)
{
for (int i = 0; i < num; i++) {
if (path_has_wildcard(file[i])) {
return true;
}
}
return false;
}
static bool have_dollars(int num, char_u **file)
{
for (int i = 0; i < num; i++) {
if (vim_strchr(file[i], '$') != NULL) {
return true;
}
}
return false;
}
/// Performs wildcard pattern matching using the shell.
///
/// @param num_pat is the number of input patterns.
/// @param pat is an array of pointers to input patterns.
/// @param[out] num_file is pointer to number of matched file names.
/// Set to the number of pointers in *file.
/// @param[out] file is pointer to array of pointers to matched file names.
/// Memory pointed to by the initial value of *file will
/// not be freed.
/// Set to NULL if FAIL is returned. Otherwise points to
/// allocated memory.
/// @param flags is a combination of EW_* flags used in
/// expand_wildcards().
/// If matching fails but EW_NOTFOUND is set in flags or
/// there are no wildcards, the patterns from pat are
/// copied into *file.
///
/// @returns OK for success or FAIL for error.
int os_expand_wildcards(int num_pat, char_u **pat, int *num_file,
char_u ***file, int flags)
FUNC_ATTR_NONNULL_ARG(3)
FUNC_ATTR_NONNULL_ARG(4)
{
int i;
size_t len;
char_u *p;
bool dir;
char_u *extra_shell_arg = NULL;
ShellOpts shellopts = kShellOptExpand | kShellOptSilent;
int j;
char_u *tempname;
char_u *command;
FILE *fd;
char_u *buffer;
#define STYLE_ECHO 0 // use "echo", the default
#define STYLE_GLOB 1 // use "glob", for csh
#define STYLE_VIMGLOB 2 // use "vimglob", for Posix sh
#define STYLE_PRINT 3 // use "print -N", for zsh
#define STYLE_BT 4 // `cmd` expansion, execute the pattern directly
int shell_style = STYLE_ECHO;
int check_spaces;
static bool did_find_nul = false;
bool ampersand = false;
// vimglob() function to define for Posix shell
static char *sh_vimglob_func =
"vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >";
bool is_fish_shell =
#if defined(UNIX)
STRNCMP(invocation_path_tail(p_sh, NULL), "fish", 4) == 0;
#else
false;
#endif
*num_file = 0; // default: no files found
*file = NULL;
// If there are no wildcards, just copy the names to allocated memory.
// Saves a lot of time, because we don't have to start a new shell.
if (!have_wildcard(num_pat, pat)) {
save_patterns(num_pat, pat, num_file, file);
return OK;
}
// Don't allow any shell command in the sandbox.
if (sandbox != 0 && check_secure()) {
return FAIL;
}
// Don't allow the use of backticks in secure.
if (secure) {
for (i = 0; i < num_pat; i++) {
if (vim_strchr(pat[i], '`') != NULL
&& (check_secure())) {
return FAIL;
}
}
}
// get a name for the temp file
if ((tempname = vim_tempname()) == NULL) {
EMSG(_(e_notmp));
return FAIL;
}
// Let the shell expand the patterns and write the result into the temp
// file.
// STYLE_BT: NL separated
// If expanding `cmd` execute it directly.
// STYLE_GLOB: NUL separated
// If we use *csh, "glob" will work better than "echo".
// STYLE_PRINT: NL or NUL separated
// If we use *zsh, "print -N" will work better than "glob".
// STYLE_VIMGLOB: NL separated
// If we use *sh*, we define "vimglob()".
// STYLE_ECHO: space separated.
// A shell we don't know, stay safe and use "echo".
if (num_pat == 1 && *pat[0] == '`'
&& (len = STRLEN(pat[0])) > 2
&& *(pat[0] + len - 1) == '`') {
shell_style = STYLE_BT;
} else if
|