https://git.reactos.org/?p=reactos.git;a=commitdiff;h=cdc8e45b4828b53039646…
commit cdc8e45b4828b53039646d4c9f50241cfb5d46ee
Author: Hermès Bélusca-Maïto <hermes.belusca-maito(a)reactos.org>
AuthorDate: Thu Jun 4 02:56:18 2020 +0200
Commit: Hermès Bélusca-Maïto <hermes.belusca-maito(a)reactos.org>
CommitDate: Sat Sep 19 19:44:55 2020 +0200
[CMD] Fix delayed expansion of variables.
CORE-13682
- Split SubstituteVars() into its main loop and a helper SubstituteVar()
that just substitutes only one variable.
- Use this new helper as the basis of the proper implementation of the
delayed expansion of variables.
- Fix a bug introduced in commit 495c82cc, when GetBatchVar() fails.
---
base/shell/cmd/cmd.c | 255 ++++++++++++++++++++++++++++++++++++++++-----------
base/shell/cmd/cmd.h | 21 ++++-
2 files changed, 220 insertions(+), 56 deletions(-)
diff --git a/base/shell/cmd/cmd.c b/base/shell/cmd/cmd.c
index c4d41d0f247..774bc4782bc 100644
--- a/base/shell/cmd/cmd.c
+++ b/base/shell/cmd/cmd.c
@@ -1318,32 +1318,52 @@ GetBatchVar(
}
BOOL
-SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim)
+SubstituteVar(
+ IN PCTSTR Src,
+ OUT size_t* SrcIncLen, // VarNameLen
+ OUT PTCHAR Dest,
+ IN PTCHAR DestEnd,
+ OUT size_t* DestIncLen,
+ IN TCHAR Delim)
{
-#define APPEND(From, Length) { \
+#define APPEND(From, Length) \
+do { \
if (Dest + (Length) > DestEnd) \
goto too_long; \
- memcpy(Dest, From, (Length) * sizeof(TCHAR)); \
- Dest += Length; }
-#define APPEND1(Char) { \
+ memcpy(Dest, (From), (Length) * sizeof(TCHAR)); \
+ Dest += (Length); \
+} while (0)
+
+#define APPEND1(Char) \
+do { \
if (Dest >= DestEnd) \
goto too_long; \
- *Dest++ = Char; }
+ *Dest++ = (Char); \
+} while (0)
- TCHAR *DestEnd = Dest + CMDLINE_LENGTH - 1;
- const TCHAR *Var;
- int VarLength;
- TCHAR *SubstStart;
+ PCTSTR Var;
+ PCTSTR Start, End, SubstStart;
TCHAR EndChr;
- while (*Src)
- {
- if (*Src != Delim)
- {
- APPEND1(*Src++)
- continue;
- }
+ size_t VarLength;
+
+ Start = Src;
+ End = Dest;
+ *SrcIncLen = 0;
+ *DestIncLen = 0;
+
+ if (!Delim)
+ return FALSE;
+ if (*Src != Delim)
+ return FALSE;
- Src++;
+ ++Src;
+
+ /* If we are already at the end of the string, fail the substitution */
+ SubstStart = Src;
+ if (!*Src || *Src == _T('\r') || *Src == _T('\n'))
+ goto bad_subst;
+
+ {
if (bc && Delim == _T('%'))
{
UINT NameLen;
@@ -1353,15 +1373,15 @@ SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim)
/* Return the partially-parsed command to be
* echoed for error diagnostics purposes. */
APPEND1(Delim);
- APPEND(Src, DestEnd-Dest);
+ APPEND(Src, _tcslen(Src) + 1);
return FALSE;
}
if (Var != NULL)
{
VarLength = _tcslen(Var);
- APPEND(Var, VarLength)
+ APPEND(Var, VarLength);
Src += NameLen;
- continue;
+ goto success;
}
}
@@ -1369,22 +1389,25 @@ SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim)
* end the name and begin the optional modifier, but not if it
* is immediately followed by the delimiter (%VAR:%). */
SubstStart = Src;
- while (*Src != Delim && !(*Src == _T(':') && Src[1] !=
Delim))
+ while (*Src && *Src != Delim && !(*Src == _T(':')
&& Src[1] != Delim))
{
- if (!*Src)
- goto bad_subst;
- Src++;
+ ++Src;
}
+ /* If we are either at the end of the string, or the delimiter
+ * has been repeated more than once, fail the substitution. */
+ if (!*Src || Src == SubstStart)
+ goto bad_subst;
EndChr = *Src;
- *Src = _T('\0');
+ *(PTSTR)Src = _T('\0'); // FIXME: HACK!
Var = GetEnvVarOrSpecial(SubstStart);
- *Src++ = EndChr;
+ *(PTSTR)Src++ = EndChr;
if (Var == NULL)
{
- /* In a batch file, %NONEXISTENT% "expands" to an empty string */
+ /* In a batch context, %NONEXISTENT% "expands" to an
+ * empty string, otherwise fail the substitution. */
if (bc)
- continue;
+ goto success;
goto bad_subst;
}
VarLength = _tcslen(Var);
@@ -1392,21 +1415,22 @@ SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim)
if (EndChr == Delim)
{
/* %VAR% - use as-is */
- APPEND(Var, VarLength)
+ APPEND(Var, VarLength);
}
else if (*Src == _T('~'))
{
/* %VAR:~[start][,length]% - substring
- * Negative values are offsets from the end */
- int Start = _tcstol(Src + 1, &Src, 0);
- int End = VarLength;
+ * Negative values are offsets from the end.
+ */
+ size_t Start = _tcstol(Src + 1, (PTSTR*)&Src, 0);
+ size_t End = VarLength;
if (Start < 0)
Start += VarLength;
Start = max(Start, 0);
Start = min(Start, VarLength);
if (*Src == _T(','))
{
- End = _tcstol(Src + 1, &Src, 0);
+ End = _tcstol(Src + 1, (PTSTR*)&Src, 0);
End += (End < 0) ? VarLength : Start;
End = max(End, Start);
End = min(End, VarLength);
@@ -1417,13 +1441,14 @@ SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim)
}
else
{
- /* %VAR:old=new% - replace all occurrences of old with new
- * %VAR:*old=new% - replace first occurrence only,
- * and remove everything before it */
- TCHAR *Old, *New;
- DWORD OldLength, NewLength;
+ /* %VAR:old=new% - Replace all occurrences of old with new.
+ * %VAR:*old=new% - Replace first occurrence only,
+ * and remove everything before it.
+ */
+ PCTSTR Old, New;
+ size_t OldLength, NewLength;
BOOL Star = FALSE;
- int LastMatch = 0, i = 0;
+ size_t LastMatch = 0, i = 0;
if (*Src == _T('*'))
{
@@ -1431,7 +1456,7 @@ SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim)
Src++;
}
- /* the string to replace may contain the delimiter */
+ /* The string to replace may contain the delimiter */
Src = _tcschr(Old = Src, _T('='));
if (Src == NULL)
goto bad_subst;
@@ -1449,8 +1474,8 @@ SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim)
if (_tcsnicmp(&Var[i], Old, OldLength) == 0)
{
if (!Star)
- APPEND(&Var[LastMatch], i - LastMatch)
- APPEND(New, NewLength)
+ APPEND(&Var[LastMatch], i - LastMatch);
+ APPEND(New, NewLength);
i += OldLength;
LastMatch = i;
if (Star)
@@ -1459,21 +1484,87 @@ SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim)
}
i++;
}
- APPEND(&Var[LastMatch], VarLength - LastMatch)
+ APPEND(&Var[LastMatch], VarLength - LastMatch);
}
- continue;
+
+ goto success;
bad_subst:
Src = SubstStart;
+ /* Only if no batch context active do we echo the delimiter */
if (!bc)
- APPEND1(Delim)
+ APPEND1(Delim);
}
- *Dest = _T('\0');
+
+success:
+ *SrcIncLen = (Src - Start);
+ *DestIncLen = (Dest - End);
return TRUE;
+
too_long:
ConOutResPrintf(STRING_ALIAS_ERROR);
nErrorLevel = 9023;
return FALSE;
+
+#undef APPEND
+#undef APPEND1
+}
+
+BOOL
+SubstituteVars(
+ IN PCTSTR Src,
+ OUT PTSTR Dest,
+ IN TCHAR Delim)
+{
+#define APPEND(From, Length) \
+do { \
+ if (Dest + (Length) > DestEnd) \
+ goto too_long; \
+ memcpy(Dest, (From), (Length) * sizeof(TCHAR)); \
+ Dest += (Length); \
+} while (0)
+
+#define APPEND1(Char) \
+do { \
+ if (Dest >= DestEnd) \
+ goto too_long; \
+ *Dest++ = (Char); \
+} while (0)
+
+ PTCHAR DestEnd = Dest + CMDLINE_LENGTH - 1;
+ PCTSTR End;
+ size_t SrcIncLen, DestIncLen;
+
+ while (*Src /* && (Dest < DestEnd) */)
+ {
+ if (*Src != Delim)
+ {
+ End = _tcschr(Src, Delim);
+ if (End == NULL)
+ End = Src + _tcslen(Src);
+ APPEND(Src, End - Src);
+ Src = End;
+ continue;
+ }
+
+ if (!SubstituteVar(Src, &SrcIncLen, Dest, DestEnd, &DestIncLen, Delim))
+ {
+ return FALSE;
+ }
+ else
+ {
+ Src += SrcIncLen;
+ Dest += DestIncLen;
+ }
+ }
+ APPEND1(_T('\0'));
+ return TRUE;
+
+too_long:
+ ConOutResPrintf(STRING_ALIAS_ERROR);
+ nErrorLevel = 9023;
+ return FALSE;
+
#undef APPEND
#undef APPEND1
}
@@ -1545,11 +1636,15 @@ SubstituteForVars(
return TRUE;
}
-LPTSTR
-DoDelayedExpansion(LPTSTR Line)
+PTSTR
+DoDelayedExpansion(
+ IN PCTSTR Line)
{
TCHAR Buf1[CMDLINE_LENGTH];
TCHAR Buf2[CMDLINE_LENGTH];
+ PTCHAR Src, Dst;
+ PTCHAR DestEnd = Buf2 + CMDLINE_LENGTH - 1;
+ size_t SrcIncLen, DestIncLen;
/* First, substitute FOR variables */
if (!SubstituteForVars(Line, Buf1))
@@ -1558,12 +1653,64 @@ DoDelayedExpansion(LPTSTR Line)
if (!bDelayedExpansion || !_tcschr(Buf1, _T('!')))
return cmd_dup(Buf1);
- /* FIXME: Delayed substitutions actually aren't quite the same as
- * immediate substitutions. In particular, it's possible to escape
- * the exclamation point using ^. */
- if (!SubstituteVars(Buf1, Buf2, _T('!')))
- return NULL;
+ /*
+ * Delayed substitutions are not actually completely the same as
+ * immediate substitutions. In particular, it is possible to escape
+ * the exclamation point using the escape caret.
+ */
+
+ /*
+ * Perform delayed expansion: expand variables around '!',
+ * and reparse escape carets.
+ */
+
+#define APPEND1(Char) \
+do { \
+ if (Dst >= DestEnd) \
+ goto too_long; \
+ *Dst++ = (Char); \
+} while (0)
+
+ Src = Buf1;
+ Dst = Buf2;
+ while (*Src && (Src < &Buf1[CMDLINE_LENGTH]))
+ {
+ if (*Src == _T('^'))
+ {
+ ++Src;
+ if (!*Src || !(Src < &Buf1[CMDLINE_LENGTH]))
+ break;
+
+ APPEND1(*Src++);
+ }
+ else if (*Src == _T('!'))
+ {
+ if (!SubstituteVar(Src, &SrcIncLen, Dst, DestEnd, &DestIncLen,
_T('!')))
+ {
+ return NULL; // Got an error during parsing.
+ }
+ else
+ {
+ Src += SrcIncLen;
+ Dst += DestIncLen;
+ }
+ }
+ else
+ {
+ APPEND1(*Src++);
+ }
+ continue;
+ }
+ APPEND1(_T('\0'));
+
return cmd_dup(Buf2);
+
+too_long:
+ ConOutResPrintf(STRING_ALIAS_ERROR);
+ nErrorLevel = 9023;
+ return NULL;
+
+#undef APPEND1
}
diff --git a/base/shell/cmd/cmd.h b/base/shell/cmd/cmd.h
index ac2d7fddd8c..93fefe4f4c6 100644
--- a/base/shell/cmd/cmd.h
+++ b/base/shell/cmd/cmd.h
@@ -95,14 +95,31 @@ ExecuteCommandWithEcho(
LPCTSTR GetEnvVarOrSpecial ( LPCTSTR varName );
VOID AddBreakHandler (VOID);
VOID RemoveBreakHandler (VOID);
-BOOL SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim);
+
+BOOL
+SubstituteVar(
+ IN PCTSTR Src,
+ OUT size_t* SrcIncLen, // VarNameLen
+ OUT PTCHAR Dest,
+ IN PTCHAR DestEnd,
+ OUT size_t* DestIncLen,
+ IN TCHAR Delim);
+
+BOOL
+SubstituteVars(
+ IN PCTSTR Src,
+ OUT PTSTR Dest,
+ IN TCHAR Delim);
BOOL
SubstituteForVars(
IN PCTSTR Src,
OUT PTSTR Dest);
-LPTSTR DoDelayedExpansion(LPTSTR Line);
+PTSTR
+DoDelayedExpansion(
+ IN PCTSTR Line);
+
INT DoCommand(LPTSTR first, LPTSTR rest, struct _PARSED_COMMAND *Cmd);
BOOL ReadLine(TCHAR *commandline, BOOL bMore);