/* bashline.c -- Bash's interface to the readline library. */ /* modified for use in Yafc by Martin Hedenfalk * this is not the complete bashline.c, see the bash source, * available at ftp://ftp.gnu.org/pub/ * * latest modified 1999-07-22, 2000-11-04 */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "syshdr.h" #include "gvars.h" #include "bashline.h" #include "strq.h" /* for tilde_expand_home */ #define COMPLETE_DQUOTE 1 #define COMPLETE_SQUOTE 2 #define COMPLETE_BSQUOTE 3 #ifdef HAVE_LIBREADLINE static int completion_quoting_style = COMPLETE_BSQUOTE; #endif /* A function to strip quotes that are not protected by backquotes. It allows single quotes to appear within double quotes, and vice versa. It should be smarter. */ char *bash_dequote_filename (const char *text, int quote_char) { char *ret, *r; const char *p; int l, quoted; l = strlen (text); ret = xmalloc (l + 1); for (quoted = quote_char, p = text, r = ret; p && *p; p++) { /* Allow backslash-quoted characters to pass through unscathed. */ if (*p == '\\') { *r++ = *++p; if (*p == '\0') break; continue; } /* Close quote. */ if (quoted && *p == quoted) { quoted = 0; continue; } /* Open quote. */ if (quoted == 0 && (*p == '\'' || *p == '"')) { quoted = *p; continue; } *r++ = *p; } *r = '\0'; return ret; } /* these should really be defined in readline/readline.h */ #ifndef SINGLE_MATCH # define SINGLE_MATCH 1 #endif #ifndef MULT_MATCH # define MULT_MATCH 2 #endif #ifdef HAVE_LIBREADLINE /* Quote a filename using double quotes, single quotes, or backslashes depending on the value of completion_quoting_style. If we're completing using backslashes, we need to quote some additional characters (those that readline treats as word breaks), so we call quote_word_break_chars on the result. */ char *bash_quote_filename (char *s, int rtype, char *qcp) { char *rtext, *mtext, *ret; int rlen, cs; rtext = (char *)NULL; /* If RTYPE == MULT_MATCH, it means that there is more than one match. In this case, we do not add the closing quote or attempt to perform tilde expansion. If RTYPE == SINGLE_MATCH, we try to perform tilde expansion, because single and double quotes inhibit tilde expansion by the shell. */ mtext = s; if (mtext[0] == '~' && rtype == SINGLE_MATCH) mtext = tilde_expand_home (s, gvLocalHomeDir); cs = completion_quoting_style; /* Might need to modify the default completion style based on *qcp, since it's set to any user-provided opening quote. */ if (*qcp == '"') cs = COMPLETE_DQUOTE; else if (*qcp == '\'') cs = COMPLETE_SQUOTE; #if defined (BANG_HISTORY) else if (*qcp == '\0' && history_expansion && cs == COMPLETE_DQUOTE && history_expansion_inhibited == 0 && strchr (mtext, '!')) cs = COMPLETE_BSQUOTE; if (*qcp == '"' && history_expansion && cs == COMPLETE_DQUOTE && history_expansion_inhibited == 0 && strchr (mtext, '!')) { cs = COMPLETE_BSQUOTE; *qcp = '\0'; } #endif switch (cs) { case COMPLETE_DQUOTE: rtext = bash_double_quote (mtext); break; case COMPLETE_SQUOTE: rtext = bash_single_quote (mtext); break; case COMPLETE_BSQUOTE: rtext = bash_backslash_quote (mtext); break; } if (mtext != s) free (mtext); /* We may need to quote additional characters: those that readline treats as word breaks that are not quoted by backslash_quote. */ if (rtext && cs == COMPLETE_BSQUOTE) { mtext = quote_word_break_chars (rtext); free (rtext); rtext = mtext; } /* Leave the opening quote intact. The readline completion code takes care of avoiding doubled opening quotes. */ rlen = strlen (rtext); ret = xmalloc (rlen + 1); strcpy (ret, rtext); /* If there are multiple matches, cut off the closing quote. */ if (rtype == MULT_MATCH && cs != COMPLETE_BSQUOTE) ret[rlen - 1] = '\0'; free (rtext); return ret; } #endif static inline int skip_single_quoted (char *string, int sind) { register int i; for (i = sind; string[i] && string[i] != '\''; i++) ; if (string[i]) i++; return i; } static inline int skip_double_quoted (char *string, int sind) { register int i; for (i = sind; string[i] && string[i] != '\"'; i++) ; if (string[i]) i++; return i; } /* Return 1 if the portion of STRING ending at EINDEX is quoted (there is an unclosed quoted string), or if the character at EINDEX is quoted by a backslash. */ int char_is_quoted (char *string, int eindex) { int i, pass_next, quoted; for (i = pass_next = quoted = 0; i <= eindex; i++) { if (pass_next) { pass_next = 0; if (i >= eindex) /* XXX was if (i >= eindex - 1) */ return 1; continue; } else if (string[i] == '\'' || string[i] == '"') { i = (string[i] == '\'') ? skip_single_quoted (string, ++i) : skip_double_quoted (string, ++i); if (i > eindex) return 1; i--; /* the skip functions increment past the closing quote. */ } else if (string[i] == '\\') { pass_next = 1; continue; } } return (0); } /* Quote STRING using double quotes. Return a new string. */ char *bash_double_quote (char *string) { register int c; char *result, *r, *s; result = (char *)xmalloc (3 + (2 * strlen (string))); r = result; *r++ = '"'; for (s = string; s && (c = *s); s++) { switch (c) { case '"': case '$': case '`': case '\\': *r++ = '\\'; default: *r++ = c; break; } } *r++ = '"'; *r = '\0'; return (result); } /* Return a new string which is the single-quoted version of STRING. Used by alias and trap, among others. */ char *bash_single_quote (char *string) { register int c; char *result, *r, *s; result = (char *)xmalloc (3 + (4 * strlen (string))); r = result; *r++ = '\''; for (s = string; s && (c = *s); s++) { *r++ = c; if (c == '\'') { *r++ = '\\'; /* insert escaped single quote */ *r++ = '\''; *r++ = '\''; /* start new quoted string */ } } *r++ = '\''; *r = '\0'; return (result); } /* Quote special characters in STRING using backslashes. Return a new string. */ char *bash_backslash_quote (char *string) { int c; char *result, *r, *s; result = xmalloc (2 * strlen (string) + 1); for (r = result, s = string; s && (c = *s); s++) { switch (c) { case ' ': case '\t': case '\n': /* IFS white space */ case '\'': case '"': case '\\': /* quoting chars */ case '|': case '&': case ';': /* shell metacharacters */ case '(': case ')': case '<': case '>': case '!': case '{': case '}': /* reserved words */ case '*': case '[': case '?': case ']': /* globbing chars */ case '^': case '$': case '`': /* expansion chars */ *r++ = '\\'; *r++ = c; break; case '#': /* comment char */ #if 0 case '~': /* tilde expansion */ #endif if (s == string) *r++ = '\\'; /* FALLTHROUGH */ default: *r++ = c; break; } } *r = '\0'; return (result); } #ifdef HAVE_LIBREADLINE /* Quote characters that the readline completion code would treat as word break characters with backslashes. Pass backslash-quoted characters through without examination. */ char *quote_word_break_chars (char *text) { char *ret, *r, *s; int l; l = strlen (text); ret = xmalloc ((2 * l) + 1); for (s = text, r = ret; *s; s++) { /* Pass backslash-quoted characters through, including the backslash. */ if (*s == '\\') { *r++ = '\\'; *r++ = *++s; if (*s == '\0') break; continue; } /* OK, we have an unquoted character. Check its presence in rl_completer_word_break_characters. */ if (strchr (rl_completer_word_break_characters, *s)) *r++ = '\\'; *r++ = *s; } *r = '\0'; return ret; } #endif