BASH PATCH REPORT ================= Bash-Release: 3.2 Patch-ID: bash32-014 Bug-Reported-by: Brett Stahlman Bug-Reference-ID: <000701c72d29$a227e0e0$5ec7cf47@computerroom> Bug-Reference-URL: http://lists.gnu.org/archive/html/bug-bash/2006-12/msg00065.html Bug-Description: Bash mishandles word splitting under certain circumstances when IFS is null (IFS=). Constructs affected include ${param/pat/sub} and others when expanding arrays (array[@]). Patch: *** ../bash-3.2-patched/array.c Wed Jun 1 16:39:22 2005 --- bash-3.2/array.c Mon Jan 15 22:58:00 2007 *************** *** 121,125 **** } - #ifdef INCLUDE_UNUSED /* * Make and return a new array composed of the elements in array A from --- 121,124 ---- *************** *** 142,146 **** n = array_create_element (element_index(p), element_value(p)); ADD_BEFORE(a->head, n); ! mi = element_index(ae); } a->num_elements = i; --- 141,145 ---- n = array_create_element (element_index(p), element_value(p)); ADD_BEFORE(a->head, n); ! mi = element_index(n); } a->num_elements = i; *************** *** 148,152 **** return a; } - #endif /* --- 147,150 ---- *************** *** 301,304 **** --- 299,319 ---- } + ARRAY * + array_quote_escapes(array) + ARRAY *array; + { + ARRAY_ELEMENT *a; + char *t; + + if (array == 0 || array_head(array) == 0 || array_empty(array)) + return (ARRAY *)NULL; + for (a = element_forw(array->head); a != array->head; a = element_forw(a)) { + t = quote_escapes (a->value); + FREE(a->value); + a->value = t; + } + return array; + } + /* * Return a string whose elements are the members of array A beginning at *************** *** 312,318 **** int starsub, quoted; { ARRAY_ELEMENT *h, *p; arrayind_t i; ! char *ifs, sep[2]; p = a ? array_head (a) : 0; --- 327,334 ---- int starsub, quoted; { + ARRAY *a2; ARRAY_ELEMENT *h, *p; arrayind_t i; ! char *ifs, sep[2], *t; p = a ? array_head (a) : 0; *************** *** 337,340 **** --- 353,363 ---- ; + a2 = array_slice(a, h, p); + + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + array_quote(a2); + else + array_quote_escapes(a2); + if (starsub && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) { ifs = getifs(); *************** *** 344,348 **** sep[1] = '\0'; ! return (array_to_string_internal (h, p, sep, quoted)); } --- 367,374 ---- sep[1] = '\0'; ! t = array_to_string (a2, sep, 0); ! array_dispose(a2); ! ! return t; } *************** *** 368,372 **** if (mflags & MATCH_QUOTED) ! array_quote (a2); if (mflags & MATCH_STARSUB) { ifs = getifs(); --- 394,400 ---- if (mflags & MATCH_QUOTED) ! array_quote(a2); ! else ! array_quote_escapes(a2); if (mflags & MATCH_STARSUB) { ifs = getifs(); *** ../bash-3.2-patched/array.h Sun Jun 1 15:50:30 2003 --- bash-3.2/array.h Mon Jan 15 22:35:35 2007 *************** *** 56,59 **** --- 56,60 ---- extern int array_shift_element __P((ARRAY *, char *)); extern ARRAY *array_quote __P((ARRAY *)); + extern ARRAY *array_quote_escapes __P((ARRAY *)); extern char *array_subrange __P((ARRAY *, arrayind_t, arrayind_t, int, int)); *** ../bash-3.2-patched/subst.c Fri Mar 2 16:20:50 2007 --- bash-3.2/subst.c Tue Mar 6 11:40:55 2007 *************** *** 1888,1892 **** --- 1889,1899 ---- #endif + /* XXX -- why call quote_list if ifs == 0? we can get away without doing + it now that quote_escapes quotes spaces */ + #if 0 tlist = ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (ifs && *ifs == 0)) + #else + tlist = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + #endif ? quote_list (list) : list_quote_escapes (list); *************** *** 2922,2926 **** /* Quote escape characters in string s, but no other characters. This is used to protect CTLESC and CTLNUL in variable values from the rest of ! the word expansion process after the variable is expanded. */ char * quote_escapes (string) --- 2935,2944 ---- /* Quote escape characters in string s, but no other characters. This is used to protect CTLESC and CTLNUL in variable values from the rest of ! the word expansion process after the variable is expanded. If IFS is ! null, we quote spaces as well, just in case we split on spaces later ! (in the case of unquoted $@, we will eventually attempt to split the ! entire word on spaces). Corresponding code exists in dequote_escapes. ! Even if we don't end up splitting on spaces, quoting spaces is not a ! problem. */ char * quote_escapes (string) *************** *** 2930,2933 **** --- 2948,2952 ---- size_t slen; char *result, *send; + int quote_spaces; DECLARE_MBSTATE; *************** *** 2935,2938 **** --- 2954,2958 ---- send = string + slen; + quote_spaces = (ifs_value && *ifs_value == 0); t = result = (char *)xmalloc ((slen * 2) + 1); s = string; *************** *** 2940,2944 **** while (*s) { ! if (*s == CTLESC || *s == CTLNUL) *t++ = CTLESC; COPY_CHAR_P (t, s, send); --- 2960,2964 ---- while (*s) { ! if (*s == CTLESC || *s == CTLNUL || (quote_spaces && *s == ' ')) *t++ = CTLESC; COPY_CHAR_P (t, s, send); *************** *** 2982,2985 **** --- 3002,3006 ---- size_t slen; char *result, *send; + int quote_spaces; DECLARE_MBSTATE; *************** *** 2996,3002 **** return (strcpy (result, s)); while (*s) { ! if (*s == CTLESC && (s[1] == CTLESC || s[1] == CTLNUL)) { s++; --- 3017,3024 ---- return (strcpy (result, s)); + quote_spaces = (ifs_value && *ifs_value == 0); while (*s) { ! if (*s == CTLESC && (s[1] == CTLESC || s[1] == CTLNUL || (quote_spaces && s[1] == ' '))) { s++; *************** *** 4462,4466 **** RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size, DEFAULT_ARRAY_SIZE); ! if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || c == CTLESC || c == CTLNUL) istring[istring_index++] = CTLESC; --- 4498,4510 ---- RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size, DEFAULT_ARRAY_SIZE); ! /* This is essentially quote_string inline */ ! if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) /* || c == CTLESC || c == CTLNUL */) ! istring[istring_index++] = CTLESC; ! /* Escape CTLESC and CTLNUL in the output to protect those characters ! from the rest of the word expansions (word splitting and globbing.) ! This is essentially quote_escapes inline. */ ! else if (c == CTLESC) ! istring[istring_index++] = CTLESC; ! else if (c == CTLNUL || (c == ' ' && (ifs_value && *ifs_value == 0))) istring[istring_index++] = CTLESC; *************** *** 5552,5555 **** --- 5610,5616 ---- rely on array_subrange to understand how to deal with them). */ tt = array_subrange (array_cell (v), e1, e2, starsub, quoted); + #if 0 + /* array_subrange now calls array_quote_escapes as appropriate, so the + caller no longer needs to. */ if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0) { *************** *** 5558,5561 **** --- 5619,5623 ---- } else + #endif temp = tt; break; *************** *** 5808,5811 **** --- 5870,5876 ---- case VT_ARRAYVAR: temp = array_patsub (array_cell (v), p, rep, mflags); + #if 0 + /* Don't need to do this anymore; array_patsub calls array_quote_escapes + as appropriate before adding the space separators. */ if (temp && (mflags & MATCH_QUOTED) == 0) { *************** *** 5814,5817 **** --- 5879,5883 ---- temp = tt; } + #endif break; #endif *** ../bash-3.2/patchlevel.h Thu Apr 13 08:31:04 2006 --- bash-3.2/patchlevel.h Mon Oct 16 14:22:54 2006 *************** *** 26,30 **** looks for to find the patch level (for the sccs version string). */ ! #define PATCHLEVEL 13 #endif /* _PATCHLEVEL_H_ */ --- 26,30 ---- looks for to find the patch level (for the sccs version string). */ ! #define PATCHLEVEL 14 #endif /* _PATCHLEVEL_H_ */