+/* A simplified version of extract_dollar_brace_string that exists to handle
+ $'...' and $"..." quoting in here-documents, since the here-document read
+ path doesn't. It's separate because we don't want to mess with the fast
+ common path. We already know we're going to allocate and return a new
+ string and quoted == Q_HERE_DOCUMENT. We might be able to cut it down
+ some more, but extracting strings and adding them as we go adds complexity.
+ This needs to match the logic in parse.y:parse_matched_pair so we get
+ consistent behavior between here-documents and double-quoted strings. */
+static char *
+extract_heredoc_dolbrace_string (string, sindex, quoted, flags)
+ char *string;
+ int *sindex, quoted, flags;
+{
+ register int i, c;
+ size_t slen, tlen, result_index, result_size;
+ int pass_character, nesting_level, si, dolbrace_state;
+ char *result, *t, *send;
+ DECLARE_MBSTATE;
+
+ pass_character = 0;
+ nesting_level = 1;
+ slen = strlen (string + *sindex) + *sindex;
+ send = string + slen;
+
+ result_size = slen;
+ result_index = 0;
+ result = xmalloc (result_size + 1);
+
+ /* This function isn't called if this condition is not true initially. */
+ dolbrace_state = DOLBRACE_QUOTE;
+
+ i = *sindex;
+ while (c = string[i])
+ {
+ if (pass_character)
+ {
+ pass_character = 0;
+ RESIZE_MALLOCED_BUFFER (result, result_index, locale_mb_cur_max + 1, result_size, 64);
+ COPY_CHAR_I (result, result_index, string, send, i);
+ continue;
+ }
+
+ /* CTLESCs and backslashes quote the next character. */
+ if (c == CTLESC || c == '\\')
+ {
+ pass_character++;
+ RESIZE_MALLOCED_BUFFER (result, result_index, 2, result_size, 64);
+ result[result_index++] = c;
+ i++;
+ continue;
+ }
+
+ /* The entire reason we have this separate function right here. */
+ if (c == '$' && string[i+1] == '\'')
+ {
+ char *ttrans;
+ int ttranslen;
+
+ if ((posixly_correct || extended_quote == 0) && dolbrace_state != DOLBRACE_QUOTE && dolbrace_state != DOLBRACE_QUOTE2)
+ {
+ RESIZE_MALLOCED_BUFFER (result, result_index, 3, result_size, 64);
+ result[result_index++] = '$';
+ result[result_index++] = '\'';
+ i += 2;
+ continue;
+ }
+
+ si = i + 2;
+ t = string_extract_single_quoted (string, &si, 1); /* XXX */
+ CHECK_STRING_OVERRUN (i, si, slen, c);
+
+ tlen = si - i - 2; /* -2 since si is one after the close quote */
+ ttrans = ansiexpand (t, 0, tlen, &ttranslen);
+ free (t);
+
+ /* needed to correctly quote any embedded single quotes. */
+ if (dolbrace_state == DOLBRACE_QUOTE || dolbrace_state == DOLBRACE_QUOTE2)
+ {
+ t = sh_single_quote (ttrans);
+ tlen = strlen (t);
+ free (ttrans);
+ }
+ else if (extended_quote) /* dolbrace_state == DOLBRACE_PARAM */
+ {
+ /* This matches what parse.y:parse_matched_pair() does */
+ t = ttrans;
+ tlen = strlen (t);
+ }
+
+ RESIZE_MALLOCED_BUFFER (result, result_index, tlen + 1, result_size, 64);
+ strncpy (result + result_index, t, tlen);
+ result_index += tlen;
+ free (t);
+ i = si;
+ continue;
+ }
+
+#if defined (TRANSLATABLE_STRINGS)
+ if (c == '$' && string[i+1] == '"')
+ {
+ char *ttrans;
+ int ttranslen;
+
+ si = i + 2;
+ t = string_extract_double_quoted (string, &si, flags); /* XXX */
+ CHECK_STRING_OVERRUN (i, si, slen, c);
+
+ tlen = si - i - 2; /* -2 since si is one after the close quote */
+ ttrans = locale_expand (t, 0, tlen, line_number, &ttranslen);
+ free (t);
+
+ t = singlequote_translations ? sh_single_quote (ttrans) : sh_mkdoublequoted (ttrans, ttranslen, 0);
+ tlen = strlen (t);
+ free (ttrans);
+
+ RESIZE_MALLOCED_BUFFER (result, result_index, tlen + 1, result_size, 64);
+ strncpy (result + result_index, t, tlen);
+ result_index += tlen;
+ free (t);
+ i = si;
+ continue;
+ }
+#endif /* TRANSLATABLE_STRINGS */
+
+ if (c == '$' && string[i+1] == LBRACE)
+ {
+ nesting_level++;
+ RESIZE_MALLOCED_BUFFER (result, result_index, 3, result_size, 64);
+ result[result_index++] = c;
+ result[result_index++] = string[i+1];
+ i += 2;
+ if (dolbrace_state == DOLBRACE_QUOTE || dolbrace_state == DOLBRACE_QUOTE2 || dolbrace_state == DOLBRACE_WORD)
+ dolbrace_state = DOLBRACE_PARAM;
+ continue;
+ }
+
+ if (c == RBRACE)
+ {
+ nesting_level--;
+ if (nesting_level == 0)
+ break;
+ RESIZE_MALLOCED_BUFFER (result, result_index, 2, result_size, 64);
+ result[result_index++] = c;
+ i++;
+ continue;
+ }
+
+ /* Pass the contents of old-style command substitutions through
+ verbatim. */
+ if (c == '`')
+ {
+ si = i + 1;
+ t = string_extract (string, &si, "`", flags); /* already know (flags & SX_NOALLOC) == 0) */
+ CHECK_STRING_OVERRUN (i, si, slen, c);
+
+ tlen = si - i - 1;
+ RESIZE_MALLOCED_BUFFER (result, result_index, tlen + 3, result_size, 64);
+ result[result_index++] = c;
+ strncpy (result + result_index, t, tlen);
+ result_index += tlen;
+ result[result_index++] = string[si];
+ free (t);
+ i = si + 1;
+ continue;
+ }
+
+ /* Pass the contents of new-style command substitutions and
+ arithmetic substitutions through verbatim. */
+ if (string[i] == '$' && string[i+1] == LPAREN)
+ {
+ si = i + 2;
+ t = extract_command_subst (string, &si, flags);
+ CHECK_STRING_OVERRUN (i, si, slen, c);
+
+ tlen = si - i - 2;
+ RESIZE_MALLOCED_BUFFER (result, result_index, tlen + 4, result_size, 64);
+ result[result_index++] = c;
+ result[result_index++] = LPAREN;
+ strncpy (result + result_index, t, tlen);
+ result_index += tlen;
+ result[result_index++] = string[si];
+ free (t);
+ i = si + 1;
+ continue;
+ }
+
+#if defined (PROCESS_SUBSTITUTION)
+ /* Technically this should only work at the start of a word */
+ if ((string[i] == '<' || string[i] == '>') && string[i+1] == LPAREN)
+ {
+ si = i + 2;
+ t = extract_process_subst (string, (string[i] == '<' ? "<(" : ">)"), &si, flags);
+ CHECK_STRING_OVERRUN (i, si, slen, c);
+
+ tlen = si - i - 2;
+ RESIZE_MALLOCED_BUFFER (result, result_index, tlen + 4, result_size, 64);
+ result[result_index++] = c;
+ result[result_index++] = LPAREN;
+ strncpy (result + result_index, t, tlen);
+ result_index += tlen;
+ result[result_index++] = string[si];
+ free (t);
+ i = si + 1;
+ continue;
+ }
+#endif
+
+ if (c == '\'' && posixly_correct && shell_compatibility_level > 42 && dolbrace_state != DOLBRACE_QUOTE)
+ {
+ COPY_CHAR_I (result, result_index, string, send, i);
+ continue;
+ }
+
+ /* Pass the contents of single and double-quoted strings through verbatim. */
+ if (c == '"' || c == '\'')
+ {
+ si = i + 1;
+ if (c == '"')
+ t = string_extract_double_quoted (string, &si, flags);
+ else
+ t = string_extract_single_quoted (string, &si, 0);
+ CHECK_STRING_OVERRUN (i, si, slen, c);
+
+ tlen = si - i - 2; /* -2 since si is one after the close quote */
+ RESIZE_MALLOCED_BUFFER (result, result_index, tlen + 3, result_size, 64);
+ result[result_index++] = c;
+ strncpy (result + result_index, t, tlen);
+ result_index += tlen;
+ result[result_index++] = string[si - 1];
+ free (t);
+ i = si;
+ continue;
+ }
+
+ /* copy this character, which was not special. */
+ COPY_CHAR_I (result, result_index, string, send, i);
+
+ /* This logic must agree with parse.y:parse_matched_pair, since they
+ share the same defines. */
+ if (dolbrace_state == DOLBRACE_PARAM && c == '%' && (i - *sindex) > 1)
+ dolbrace_state = DOLBRACE_QUOTE;
+ else if (dolbrace_state == DOLBRACE_PARAM && c == '#' && (i - *sindex) > 1)
+ dolbrace_state = DOLBRACE_QUOTE;
+ else if (dolbrace_state == DOLBRACE_PARAM && c == '/' && (i - *sindex) > 1)
+ dolbrace_state = DOLBRACE_QUOTE2; /* XXX */
+ else if (dolbrace_state == DOLBRACE_PARAM && c == '^' && (i - *sindex) > 1)
+ dolbrace_state = DOLBRACE_QUOTE;
+ else if (dolbrace_state == DOLBRACE_PARAM && c == ',' && (i - *sindex) > 1)
+ dolbrace_state = DOLBRACE_QUOTE;
+ /* This is intended to handle all of the [:]op expansions and the substring/
+ length/pattern removal/pattern substitution expansions. */
+ else if (dolbrace_state == DOLBRACE_PARAM && strchr ("#%^,~:-=?+/", c) != 0)
+ dolbrace_state = DOLBRACE_OP;
+ else if (dolbrace_state == DOLBRACE_OP && strchr ("#%^,~:-=?+/", c) == 0)
+ dolbrace_state = DOLBRACE_WORD;
+ }
+
+ if (c == 0 && nesting_level)
+ {
+ free (result);
+ if (no_longjmp_on_fatal_error == 0)
+ { /* { */
+ last_command_exit_value = EXECUTION_FAILURE;
+ report_error (_("bad substitution: no closing `%s' in %s"), "}", string);
+ exp_jump_to_top_level (DISCARD);
+ }
+ else
+ {
+ *sindex = i;
+ return ((char *)NULL);
+ }
+ }
+
+ *sindex = i;
+ result[result_index] = '\0';
+
+ return (result);
+}
+
+#define PARAMEXPNEST_MAX 32 // for now
+static int dbstate[PARAMEXPNEST_MAX];
+