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

commit cb2a9c31a6c8c207f22852c68fc1464dd3b52080
Author:     Hermès Bélusca-Maïto <[email protected]>
AuthorDate: Sat Jul 4 17:40:58 2020 +0200
Commit:     Hermès Bélusca-Maïto <[email protected]>
CommitDate: Sat Sep 19 19:44:54 2020 +0200

    [CMD] Some fixes for getting the enhanced '%~XXX' batch/FOR variables.
    CORE-11857 CORE-13736
    
    It will be followed with a separate fix for the FOR-loop code.
    Fixes some cmd_winetests.
    
    A NULL pointer can be returned for a valid existing batch/FOR variable,
    in which case the enhanced-variable getter should return an empty string.
    This situation can happen e.g. when forcing a FOR-loop to tokenize a
    text line with not enough tokens in it.
---
 base/shell/cmd/batch.c |  26 +++++---
 base/shell/cmd/batch.h |   7 ++-
 base/shell/cmd/cmd.c   | 164 +++++++++++++++++++++++++++++++++++--------------
 base/shell/cmd/cmd.h   |   7 ++-
 4 files changed, 146 insertions(+), 58 deletions(-)

diff --git a/base/shell/cmd/batch.c b/base/shell/cmd/batch.c
index a7dfa2fd11a..e9a1e5aa76e 100644
--- a/base/shell/cmd/batch.c
+++ b/base/shell/cmd/batch.c
@@ -74,29 +74,35 @@ TCHAR textline[BATCH_BUFFSIZE];
 /*
  * Returns a pointer to the n'th parameter of the current batch file.
  * If no such parameter exists returns pointer to empty string.
- * If no batch file is current, returns NULL
- *
+ * If no batch file is current, returns NULL.
  */
-LPTSTR FindArg(TCHAR Char, BOOL *IsParam0)
+BOOL
+FindArg(
+    IN TCHAR Char,
+    OUT PCTSTR* ArgPtr,
+    OUT BOOL* IsParam0)
 {
-    LPTSTR pp;
+    PCTSTR pp;
     INT n = Char - _T('0');
 
-    TRACE ("FindArg: (%d)\n", n);
+    TRACE("FindArg: (%d)\n", n);
+
+    *ArgPtr = NULL;
 
     if (n < 0 || n > 9)
-        return NULL;
+        return FALSE;
 
     n = bc->shiftlevel[n];
     *IsParam0 = (n == 0);
     pp = bc->params;
 
-    /* Step up the strings till we reach the end */
-    /* or the one we want */
+    /* Step up the strings till we reach
+     * the end or the one we want. */
     while (*pp && n--)
-        pp += _tcslen (pp) + 1;
+        pp += _tcslen(pp) + 1;
 
-    return pp;
+    *ArgPtr = pp;
+    return TRUE;
 }
 
 
diff --git a/base/shell/cmd/batch.h b/base/shell/cmd/batch.h
index de425ee2f11..d40b640e421 100644
--- a/base/shell/cmd/batch.h
+++ b/base/shell/cmd/batch.h
@@ -44,7 +44,12 @@ extern BOOL bEcho;       /* The echo flag */
 extern TCHAR textline[BATCH_BUFFSIZE]; /* Buffer for reading Batch file lines 
*/
 
 
-LPTSTR FindArg(TCHAR, BOOL *);
+BOOL
+FindArg(
+    IN TCHAR Char,
+    OUT PCTSTR* ArgPtr,
+    OUT BOOL* IsParam0);
+
 VOID   ExitBatch(VOID);
 VOID   ExitAllBatches(VOID);
 INT    Batch(LPTSTR, LPTSTR, LPTSTR, PARSED_COMMAND *);
diff --git a/base/shell/cmd/cmd.c b/base/shell/cmd/cmd.c
index caa60a466fe..c4d41d0f247 100644
--- a/base/shell/cmd/cmd.c
+++ b/base/shell/cmd/cmd.c
@@ -964,8 +964,10 @@ GetEnvVarOrSpecial(LPCTSTR varName)
 }
 
 /* Handle the %~var syntax */
-static LPTSTR
-GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, BOOL *))
+static PCTSTR
+GetEnhancedVar(
+    IN OUT PCTSTR* pFormat,
+    IN BOOL (*GetVar)(TCHAR, PCTSTR*, BOOL*))
 {
     static const TCHAR ModifierTable[] = _T("dpnxfsatz");
     enum {
@@ -980,28 +982,68 @@ GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, 
BOOL *))
         M_SIZE  = 256, /* Z: file size */
     } Modifiers = 0;
 
-    TCHAR *Format, *FormatEnd;
-    TCHAR *PathVarName = NULL;
-    LPTSTR Variable;
-    TCHAR *VarEnd;
+    PCTSTR Format, FormatEnd;
+    PCTSTR PathVarName = NULL;
+    PCTSTR Variable;
+    PCTSTR VarEnd;
     BOOL VariableIsParam0;
     TCHAR FullPath[MAX_PATH];
-    TCHAR FixedPath[MAX_PATH], *Filename, *Extension;
+    TCHAR FixedPath[MAX_PATH];
+    PTCHAR Filename, Extension;
     HANDLE hFind;
     WIN32_FIND_DATA w32fd;
-    TCHAR *In, *Out;
+    PTCHAR In, Out;
 
     static TCHAR Result[CMDLINE_LENGTH];
 
-    /* There is ambiguity between modifier characters and FOR variables;
-     * the rule that cmd uses is to pick the longest possible match.
-     * For example, if there is a %n variable, then out of %~anxnd,
-     * %~anxn will be substituted rather than just %~an. */
+     /* Check whether the current character is a recognized variable.
+      * If it is not, then restore the previous one: there is indeed
+      * ambiguity between modifier characters and FOR variables;
+      * the rule that CMD uses is to pick the longest possible match.
+      * This case can happen if we have a FOR-variable specification
+      * of the following form:
+      *
+      *   %~<modifiers><actual FOR variable character><other characters>
+      *
+      * where the FOR variable character is also a similar to a modifier,
+      * but should not be interpreted as is, and the following other
+      * characters are not part of the possible modifier characters, and
+      * are unrelated to the FOR variable (they can be part of a command).
+      * For example, if there is a %n variable, then out of %~anxnd,
+      * %~anxn will be substituted rather than just %~an.
+      *
+      * In the following examples, all characters 'd','p','n','x' are valid 
modifiers.
+      *
+      * 1. In this example, the FOR variable character is 'x' and the actual
+      *    modifiers are 'dpn'. Parsing will first determine that 'dpnx'
+      *    are modifiers, with the possible (last) valid variable being 'x',
+      *    and will stop at the letter 'g'. Since 'g' is not a valid
+      *    variable, then the actual variable is the lattest one 'x',
+      *    and the modifiers are then actually 'dpn'.
+      *    The FOR-loop will then display the %x variable formatted with 'dpn'
+      *    and will append any other characters following, 'g'.
+      *
+      *  C:\Temp>for %x in (foo.exe bar.txt) do @echo %~dpnxg
+      *  C:\Temp\foog
+      *  C:\Temp\barg
+      *
+      *
+      * 2. In this second example, the FOR variable character is 'g' and
+      *    the actual modifiers are 'dpnx'. Parsing will determine also that
+      *    the possible (last) valid variable could be 'x', but since it's
+      *    not present in the FOR-variables list, it won't be the case.
+      *    This means that the actual FOR variable character must follow,
+      *    in this case, 'g'.
+      *
+      *  C:\Temp>for %g in (foo.exe bar.txt) do @echo %~dpnxg
+      *  C:\Temp\foo.exe
+      *  C:\Temp\bar.txt
+      */
 
     /* First, go through as many modifier characters as possible */
     FormatEnd = Format = *pFormat;
     while (*FormatEnd && _tcschr(ModifierTable, _totlower(*FormatEnd)))
-        FormatEnd++;
+        ++FormatEnd;
 
     if (*FormatEnd == _T('$'))
     {
@@ -1012,48 +1054,52 @@ GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, 
BOOL *))
             return NULL;
 
         /* Must be immediately followed by the variable */
-        Variable = GetVar(*++FormatEnd, &VariableIsParam0);
-        if (!Variable)
+        if (!GetVar(*++FormatEnd, &Variable, &VariableIsParam0))
             return NULL;
     }
     else
     {
         /* Backtrack if necessary to get a variable name match */
-        while (!(Variable = GetVar(*FormatEnd, &VariableIsParam0)))
+        while (!GetVar(*FormatEnd, &Variable, &VariableIsParam0))
         {
             if (FormatEnd == Format)
                 return NULL;
-            FormatEnd--;
+            --FormatEnd;
         }
     }
 
-    for (; Format < FormatEnd && *Format != _T('$'); Format++)
-        Modifiers |= 1 << (_tcschr(ModifierTable, _totlower(*Format)) - 
ModifierTable);
-
     *pFormat = FormatEnd + 1;
 
+    /* If the variable is empty, return an empty string */
+    if (!Variable || !*Variable)
+        return _T("");
+
     /* Exclude the leading and trailing quotes */
     VarEnd = &Variable[_tcslen(Variable)];
     if (*Variable == _T('"'))
     {
-        Variable++;
+        ++Variable;
         if (VarEnd > Variable && VarEnd[-1] == _T('"'))
-            VarEnd--;
+            --VarEnd;
     }
 
-    if ((char *)VarEnd - (char *)Variable >= sizeof Result)
+    if ((ULONG_PTR)VarEnd - (ULONG_PTR)Variable >= sizeof(Result))
         return _T("");
-    memcpy(Result, Variable, (char *)VarEnd - (char *)Variable);
+    memcpy(Result, Variable, (ULONG_PTR)VarEnd - (ULONG_PTR)Variable);
     Result[VarEnd - Variable] = _T('\0');
 
+    /* Now determine the actual modifiers */
+    for (; Format < FormatEnd && *Format != _T('$'); ++Format)
+        Modifiers |= 1 << (_tcschr(ModifierTable, _totlower(*Format)) - 
ModifierTable);
+
     if (PathVarName)
     {
         /* $PATH: syntax - search the directories listed in the
          * specified environment variable for the file */
-        LPTSTR PathVar;
-        FormatEnd[-1] = _T('\0');
+        PTSTR PathVar;
+        ((PTSTR)FormatEnd)[-1] = _T('\0'); // FIXME: HACK!
         PathVar = GetEnvVar(PathVarName);
-        FormatEnd[-1] = _T(':');
+        ((PTSTR)FormatEnd)[-1] = _T(':');
         if (!PathVar ||
             !SearchPath(PathVar, Result, NULL, ARRAYSIZE(FullPath), FullPath, 
NULL))
         {
@@ -1070,6 +1116,7 @@ GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, 
BOOL *))
         /* Special case: If the variable is %0 and modifier characters are 
present,
          * use the batch file's path (which includes the .bat/.cmd extension)
          * rather than the actual %0 variable (which might not). */
+        ASSERT(bc);
         _tcscpy(FullPath, bc->BatchFilePath);
     }
     else
@@ -1103,7 +1150,7 @@ GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, 
BOOL *))
         /* If it doesn't exist, just leave the name as it was given */
         if (hFind != INVALID_HANDLE_VALUE)
         {
-            LPTSTR FixedComponent = w32fd.cFileName;
+            PTSTR FixedComponent = w32fd.cFileName;
             if (*w32fd.cAlternateFileName &&
                 ((Modifiers & M_SHORT) || !_tcsicmp(In, 
w32fd.cAlternateFileName)))
             {
@@ -1194,7 +1241,7 @@ GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, 
BOOL *))
     }
     if (Modifiers & (M_PATH | M_FULL))
     {
-        memcpy(Out, &FixedPath[2], (char *)Filename - (char *)&FixedPath[2]);
+        memcpy(Out, &FixedPath[2], (ULONG_PTR)Filename - 
(ULONG_PTR)&FixedPath[2]);
         Out += Filename - &FixedPath[2];
     }
     if (Modifiers & (M_NAME | M_FULL))
@@ -1217,18 +1264,20 @@ GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, 
BOOL *))
     return Result;
 }
 
-static LPCTSTR
-GetBatchVar(TCHAR *varName, UINT *varNameLen)
+static PCTSTR
+GetBatchVar(
+    IN PCTSTR varName,
+    OUT PUINT varNameLen)
 {
-    LPCTSTR ret;
-    TCHAR *varNameEnd;
-    BOOL dummy;
+    PCTSTR ret;
+    PCTSTR varNameEnd;
 
     *varNameLen = 1;
 
-    switch ( *varName )
+    switch (*varName)
     {
     case _T('~'):
+    {
         varNameEnd = varName + 1;
         ret = GetEnhancedVar(&varNameEnd, FindArg);
         if (!ret)
@@ -1238,6 +1287,7 @@ GetBatchVar(TCHAR *varName, UINT *varNameLen)
         }
         *varNameLen = varNameEnd - varName;
         return ret;
+    }
 
     case _T('0'):
     case _T('1'):
@@ -1249,7 +1299,13 @@ GetBatchVar(TCHAR *varName, UINT *varNameLen)
     case _T('7'):
     case _T('8'):
     case _T('9'):
-        return FindArg(*varName, &dummy);
+    {
+        BOOL dummy;
+        if (!FindArg(*varName, &ret, &dummy))
+            return NULL;
+        else
+            return ret;
+    }
 
     case _T('*'):
         /* Copy over the raw params (not including the batch file name) */
@@ -1423,37 +1479,53 @@ too_long:
 }
 
 /* Search the list of FOR contexts for a variable */
-static LPTSTR
-FindForVar(TCHAR Var, BOOL *IsParam0)
+static BOOL
+FindForVar(
+    IN TCHAR Var,
+    OUT PCTSTR* VarPtr,
+    OUT BOOL* IsParam0)
 {
     PFOR_CONTEXT Ctx;
 
+    *VarPtr = NULL;
     *IsParam0 = FALSE;
+
     for (Ctx = fc; Ctx != NULL; Ctx = Ctx->prev)
     {
         if ((UINT)(Var - Ctx->firstvar) < Ctx->varcount)
-            return Ctx->values[Var - Ctx->firstvar];
+        {
+            *VarPtr = Ctx->values[Var - Ctx->firstvar];
+            return TRUE;
+        }
     }
-    return NULL;
+    return FALSE;
 }
 
 BOOL
-SubstituteForVars(TCHAR *Src, TCHAR *Dest)
+SubstituteForVars(
+    IN PCTSTR Src,
+    OUT PTSTR Dest)
 {
-    TCHAR *DestEnd = &Dest[CMDLINE_LENGTH - 1];
+    PTCHAR DestEnd = &Dest[CMDLINE_LENGTH - 1];
     while (*Src)
     {
         if (Src[0] == _T('%'))
         {
             BOOL Dummy;
-            LPTSTR End = &Src[2];
-            LPTSTR Value = NULL;
+            PCTSTR End = &Src[2];
+            PCTSTR Value = NULL;
 
             if (Src[1] == _T('~'))
                 Value = GetEnhancedVar(&End, FindForVar);
 
-            if (!Value)
-                Value = FindForVar(Src[1], &Dummy);
+            if (!Value && Src[1])
+            {
+                if (FindForVar(Src[1], &Value, &Dummy) && !Value)
+                {
+                    /* The variable is empty, return an empty string */
+                    Value = _T("");
+                }
+            }
 
             if (Value)
             {
diff --git a/base/shell/cmd/cmd.h b/base/shell/cmd/cmd.h
index b3c1c0f1d9d..ac2d7fddd8c 100644
--- a/base/shell/cmd/cmd.h
+++ b/base/shell/cmd/cmd.h
@@ -96,7 +96,12 @@ LPCTSTR GetEnvVarOrSpecial ( LPCTSTR varName );
 VOID AddBreakHandler (VOID);
 VOID RemoveBreakHandler (VOID);
 BOOL SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim);
-BOOL SubstituteForVars(TCHAR *Src, TCHAR *Dest);
+
+BOOL
+SubstituteForVars(
+    IN PCTSTR Src,
+    OUT PTSTR Dest);
+
 LPTSTR DoDelayedExpansion(LPTSTR Line);
 INT DoCommand(LPTSTR first, LPTSTR rest, struct _PARSED_COMMAND *Cmd);
 BOOL ReadLine(TCHAR *commandline, BOOL bMore);

Reply via email to