https://git.reactos.org/?p=reactos.git;a=commitdiff;h=cdc8e45b4828b53039646d4c9f50241cfb5d46ee

commit cdc8e45b4828b53039646d4c9f50241cfb5d46ee
Author:     Hermès Bélusca-Maïto <[email protected]>
AuthorDate: Thu Jun 4 02:56:18 2020 +0200
Commit:     Hermès Bélusca-Maïto <[email protected]>
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);
 

Reply via email to