82616bda7ddede60b5db4a1f238f573f74c9c58f — Simon Ser 25 days ago 2a9e336
Don't perform pathname expansion on quoted strings

Closes: https://github.com/emersion/mrsh/issues/24
3 files changed, 93 insertions(+), 25 deletions(-)

M shell/task/simple_command.c
M shell/task/task.c
M shell/word.c
M shell/task/simple_command.c => shell/task/simple_command.c +6 -13
@@ 231,24 231,17 @@ }
  	assert(fields.len > 0);
  
- 	struct mrsh_array strs = {0};
- 	get_fields_str(&strs, &fields);
- 	for (size_t i = 0; i < fields.len; ++i) {
- 		mrsh_word_destroy(fields.data[i]);
- 	}
- 	mrsh_array_finish(&fields);
- 	fields = strs;
- 
  	if (ctx->state->options & MRSH_OPT_NOGLOB) {
- 		*args = fields;
+ 		get_fields_str(args, &fields);
  	} else {
  		expand_pathnames(args, &fields);
- 		for (size_t i = 0; i < fields.len; ++i) {
- 			free(fields.data[i]);
- 		}
- 		mrsh_array_finish(&fields);
  	}
  
+ 	for (size_t i = 0; i < fields.len; ++i) {
+ 		mrsh_word_destroy(fields.data[i]);
+ 	}
+ 	mrsh_array_finish(&fields);
+ 
  	assert(args->len > 0);
  	mrsh_array_add(args, NULL);
  }

M shell/task/task.c => shell/task/task.c +1 -6
@@ 132,18 132,13 @@ if (ret < 0) {
  			return ret;
  		}
- 		struct mrsh_word_string *ws = mrsh_word_get_string(*word_ptr);
- 
- 		mrsh_array_add(&word_fields, strdup(ws->str));
+ 		mrsh_array_add(&word_fields, *word_ptr);
  	}
  
  	struct mrsh_array expanded_fields = {0};
  	if (!expand_pathnames(&expanded_fields, &word_fields)) {
  		return TASK_STATUS_ERROR;
  	}
- 	for (size_t i = 0; i < word_fields.len; i++) {
- 		free(word_fields.data[i]);
- 	}
  	mrsh_array_finish(&word_fields);
  
  	int loop_ret = 0;

M shell/word.c => shell/word.c +86 -6
@@ 171,30 171,110 @@ }
  }
  
+ static bool is_pathname_metachar(char c) {
+ 	switch (c) {
+ 	case '*':
+ 	case '?':
+ 	case '[':
+ 	case ']':
+ 		return true;
+ 	default:
+ 		return false;
+ 	}
+ }
+ 
+ static bool needs_pathname_expansion(struct mrsh_word *word) {
+ 	switch (word->type) {
+ 	case MRSH_WORD_STRING:;
+ 		struct mrsh_word_string *ws = mrsh_word_get_string(word);
+ 		if (ws->single_quoted) {
+ 			return false;
+ 		}
+ 
+ 		size_t len = strlen(ws->str);
+ 		for (size_t i = 0; i < len; i++) {
+ 			if (is_pathname_metachar(ws->str[i])) {
+ 				return true;
+ 			}
+ 		}
+ 		return false;
+ 	case MRSH_WORD_LIST:;
+ 		struct mrsh_word_list *wl = mrsh_word_get_list(word);
+ 		if (wl->double_quoted) {
+ 			return false;
+ 		}
+ 
+ 		for (size_t i = 0; i < wl->children.len; i++) {
+ 			struct mrsh_word *child = wl->children.data[i];
+ 			if (needs_pathname_expansion(child)) {
+ 				return true;
+ 			}
+ 		}
+ 		return false;
+ 	default:
+ 		assert(false);
+ 	}
+ 
+ }
+ 
+ static void get_word_pathname_pattern(struct mrsh_buffer *buf,
+ 		struct mrsh_word *word, bool quoted) {
+ 	switch (word->type) {
+ 	case MRSH_WORD_STRING:;
+ 		struct mrsh_word_string *ws = mrsh_word_get_string(word);
+ 
+ 		size_t len = strlen(ws->str);
+ 		for (size_t i = 0; i < len; i++) {
+ 			char c = ws->str[i];
+ 			if (is_pathname_metachar(c) && (quoted || ws->single_quoted)) {
+ 				mrsh_buffer_append_char(buf, '\\');
+ 			}
+ 			mrsh_buffer_append_char(buf, c);
+ 		}
+ 		break;
+ 	case MRSH_WORD_LIST:;
+ 		struct mrsh_word_list *wl = mrsh_word_get_list(word);
+ 
+ 		for (size_t i = 0; i < wl->children.len; i++) {
+ 			struct mrsh_word *child = wl->children.data[i];
+ 			get_word_pathname_pattern(buf, child, quoted || wl->double_quoted);
+ 		}
+ 		break;
+ 	default:
+ 		assert(false);
+ 	}
+ }
+ 
  bool expand_pathnames(struct mrsh_array *expanded, struct mrsh_array *fields) {
- 	const char metachars[] = "*?[";
+ 	struct mrsh_buffer buf = {0};
  
  	for (size_t i = 0; i < fields->len; ++i) {
- 		const char *field = fields->data[i];
+ 		struct mrsh_word *field = fields->data[i];
  
- 		if (strpbrk(field, metachars) == NULL) {
- 			mrsh_array_add(expanded, strdup(field));
+ 		if (!needs_pathname_expansion(field)) {
+ 			mrsh_array_add(expanded, mrsh_word_str(field));
  			continue;
  		}
  
+ 		buf.len = 0;
+ 		get_word_pathname_pattern(&buf, field, false);
+ 		mrsh_buffer_append_char(&buf, '\0');
+ 
  		glob_t glob_buf;
- 		int ret = glob(field, GLOB_NOSORT | GLOB_NOCHECK, NULL, &glob_buf);
+ 		int ret = glob(buf.data, GLOB_NOSORT, NULL, &glob_buf);
  		if (ret == 0) {
  			for (size_t i = 0; i < glob_buf.gl_pathc; ++i) {
  				mrsh_array_add(expanded, strdup(glob_buf.gl_pathv[i]));
  			}
  			globfree(&glob_buf);
+ 		} else if (ret == GLOB_NOMATCH) {
+ 			mrsh_array_add(expanded, mrsh_word_str(field));
  		} else {
- 			assert(ret != GLOB_NOMATCH);
  			fprintf(stderr, "glob() failed\n");
  			return false;
  		}
  	}
  
+ 	mrsh_buffer_finish(&buf);
  	return true;
  }