+/*
+ * Undo escaping of glob sequences in place. Used to undo extra escaping
+ * applied in makeargv() when the string is destined for a function that
+ * does not glob it.
+ */
+static void
+undo_glob_escape(char *s)
+{
+ size_t i, j;
+
+ for (i = j = 0;;) {
+ if (s[i] == '\0') {
+ s[j] = '\0';
+ return;
+ }
+ if (s[i] != '\\') {
+ s[j++] = s[i++];
+ continue;
+ }
+ /* s[i] == '\\' */
+ ++i;
+ switch (s[i]) {
+ case '?':
+ case '[':
+ case '*':
+ case '\\':
+ s[j++] = s[i++];
+ break;
+ case '\0':
+ s[j++] = '\\';
+ s[j] = '\0';
+ return;
+ default:
+ s[j++] = '\\';
+ s[j++] = s[i++];
+ break;
+ }
+ }
+}
+
+/*
+ * Split a string into an argument vector using sh(1)-style quoting,
+ * comment and escaping rules, but with some tweaks to handle glob(3)
+ * wildcards.
+ * Returns NULL on error or a NULL-terminated array of arguments.
+ */
+#define MAXARGS 128
+#define MAXARGLEN 8192
+static char **
+makeargv(const char *arg, int *argcp)
+{
+ int argc, quot;
+ size_t i, j;
+ static char argvs[MAXARGLEN];
+ static char *argv[MAXARGS + 1];
+ enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
+
+ *argcp = argc = 0;
+ if (strlen(arg) > sizeof(argvs) - 1) {
+ args_too_longs:
+ error("string too long");
+ return NULL;
+ }
+ state = MA_START;
+ i = j = 0;
+ for (;;) {
+ if (isspace(arg[i])) {
+ if (state == MA_UNQUOTED) {
+ /* Terminate current argument */
+ argvs[j++] = '\0';
+ argc++;
+ state = MA_START;
+ } else if (state != MA_START)
+ argvs[j++] = arg[i];
+ } else if (arg[i] == '"' || arg[i] == '\'') {
+ q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
+ if (state == MA_START) {
+ argv[argc] = argvs + j;
+ state = q;
+ } else if (state == MA_UNQUOTED)
+ state = q;
+ else if (state == q)
+ state = MA_UNQUOTED;
+ else
+ argvs[j++] = arg[i];
+ } else if (arg[i] == '\\') {
+ if (state == MA_SQUOTE || state == MA_DQUOTE) {
+ quot = state == MA_SQUOTE ? '\'' : '"';
+ /* Unescape quote we are in */
+ /* XXX support \n and friends? */
+ if (arg[i + 1] == quot) {
+ i++;
+ argvs[j++] = arg[i];
+ } else if (arg[i + 1] == '?' ||
+ arg[i + 1] == '[' || arg[i + 1] == '*') {
+ /*
+ * Special case for sftp: append
+ * double-escaped glob sequence -
+ * glob will undo one level of
+ * escaping. NB. string can grow here.
+ */
+ if (j >= sizeof(argvs) - 5)
+ goto args_too_longs;
+ argvs[j++] = '\\';
+ argvs[j++] = arg[i++];
+ argvs[j++] = '\\';
+ argvs[j++] = arg[i];
+ } else {
+ argvs[j++] = arg[i++];
+ argvs[j++] = arg[i];
+ }
+ } else {
+ if (state == MA_START) {
+ argv[argc] = argvs + j;
+ state = MA_UNQUOTED;
+ }
+ if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
+ arg[i + 1] == '*' || arg[i + 1] == '\\') {
+ /*
+ * Special case for sftp: append
+ * escaped glob sequence -
+ * glob will undo one level of
+ * escaping.
+ */
+ argvs[j++] = arg[i++];
+ argvs[j++] = arg[i];
+ } else {
+ /* Unescape everything */
+ /* XXX support \n and friends? */
+ i++;
+ argvs[j++] = arg[i];
+ }
+ }
+ } else if (arg[i] == '#') {
+ if (state == MA_SQUOTE || state == MA_DQUOTE)
+ argvs[j++] = arg[i];
+ else
+ goto string_done;
+ } else if (arg[i] == '\0') {
+ if (state == MA_SQUOTE || state == MA_DQUOTE) {
+ error("Unterminated quoted argument");
+ return NULL;
+ }
+ string_done:
+ if (state == MA_UNQUOTED) {
+ argvs[j++] = '\0';
+ argc++;
+ }
+ break;
+ } else {
+ if (state == MA_START) {
+ argv[argc] = argvs + j;
+ state = MA_UNQUOTED;
+ }
+ if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
+ (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
+ /*
+ * Special case for sftp: escape quoted
+ * glob(3) wildcards. NB. string can grow
+ * here.
+ */
+ if (j >= sizeof(argvs) - 3)
+ goto args_too_longs;
+ argvs[j++] = '\\';
+ argvs[j++] = arg[i];
+ } else
+ argvs[j++] = arg[i];
+ }
+ i++;
+ }
+ *argcp = argc;
+ return argv;
+}
+