https://git.reactos.org/?p=reactos.git;a=commitdiff;h=2e4c8c019ebe5d78dbcf4…
commit 2e4c8c019ebe5d78dbcf4397ffa0caa4eabb5d0d
Author:     Hermès Bélusca-Maïto <hermes.belusca-maito(a)reactos.org>
AuthorDate: Wed Jul 22 01:16:53 2020 +0200
Commit:     Hermès Bélusca-Maïto <hermes.belusca-maito(a)reactos.org>
CommitDate: Wed Aug 19 20:36:01 2020 +0200
    [CMD] GOTO: Fix label parsing.
    We note two things, when CMD searches for the corresponding label in the
    batch file:
    - the first character of the line is always ignored, unless it's a colon;
    - the escape caret ^ is supported and interpreted.
    Fixes some cmd_winetests.
---
 base/shell/cmd/batch.c  |  4 +-
 base/shell/cmd/cmd.h    |  4 ++
 base/shell/cmd/goto.c   | 97 ++++++++++++++++++++++++++++++++++---------------
 base/shell/cmd/parser.c |  3 --
 4 files changed, 74 insertions(+), 34 deletions(-)
diff --git a/base/shell/cmd/batch.c b/base/shell/cmd/batch.c
index a5c31aece09..184911c60a6 100644
--- a/base/shell/cmd/batch.c
+++ b/base/shell/cmd/batch.c
@@ -131,7 +131,7 @@ static LPTSTR BatchParams(LPTSTR s1, LPTSTR s2)
         BOOL inquotes = FALSE;
         /* Find next parameter */
-        while (_istspace(*s2) || (*s2 && _tcschr(_T(",;="), *s2)))
+        while (_istspace(*s2) || (*s2 && _tcschr(STANDARD_SEPS, *s2)))
             s2++;
         if (!*s2)
             break;
@@ -139,7 +139,7 @@ static LPTSTR BatchParams(LPTSTR s1, LPTSTR s2)
         /* Copy it */
         do
         {
-            if (!inquotes && (_istspace(*s2) || _tcschr(_T(",;="),
*s2)))
+            if (!inquotes && (_istspace(*s2) || _tcschr(STANDARD_SEPS, *s2)))
                 break;
             inquotes ^= (*s2 == _T('"'));
             *s1++ = *s2++;
diff --git a/base/shell/cmd/cmd.h b/base/shell/cmd/cmd.h
index a6e780bdc6e..f547389024e 100644
--- a/base/shell/cmd/cmd.h
+++ b/base/shell/cmd/cmd.h
@@ -301,6 +301,10 @@ INT cmd_move (LPTSTR);
 INT CommandMsgbox (LPTSTR);
 /* Prototypes from PARSER.C */
+
+/* These three characters act like spaces to the parser in most contexts */
+#define STANDARD_SEPS _T(",;=")
+
 enum { C_COMMAND, C_QUIET, C_BLOCK, C_MULTI, C_OR, C_AND, C_PIPE, C_IF, C_FOR };
 typedef struct _PARSED_COMMAND
 {
diff --git a/base/shell/cmd/goto.c b/base/shell/cmd/goto.c
index 5831b25e22f..5174b62417f 100644
--- a/base/shell/cmd/goto.c
+++ b/base/shell/cmd/goto.c
@@ -35,7 +35,7 @@
  */
 INT cmd_goto(LPTSTR param)
 {
-    LPTSTR tmp, tmp2;
+    LPTSTR label, tmp;
     DWORD dwCurrPos;
     BOOL bRetry;
@@ -59,11 +59,9 @@ INT cmd_goto(LPTSTR param)
         return 1;
     }
-    /* Terminate label at first space char */
-    tmp = param + 1;
-    while (!_istcntrl(*tmp) && !_istspace(*tmp) && (*tmp !=
_T(':')))
-        ++tmp;
-    *tmp = _T('\0');
+    /* Strip leading whitespace */
+    while (_istspace(*param))
+        ++param;
     /* Support jumping to the end of the file, only if extensions are enabled */
     if (bEnableExtensions &&
@@ -78,6 +76,23 @@ INT cmd_goto(LPTSTR param)
         return 0;
     }
+    /* Skip the first colon or plus sign */
+    if (*param == _T(':') || *param == _T('+'))
+        ++param;
+    /* Terminate the label at the first delimiter character */
+    tmp = param;
+    while (!_istcntrl(*tmp) && !_istspace(*tmp) &&
+           !_tcschr(_T(":+"), *tmp) && !_tcschr(STANDARD_SEPS, *tmp) /*
&&
+           !_tcschr(_T("&|<>"), *tmp) */)
+    {
+        ++tmp;
+    }
+    *tmp = _T('\0');
+
+    /* If we don't have any label, bail out */
+    if (!*param)
+        goto NotFound;
+
     /*
      * Search the next label starting our position, until the end of the file.
      * If none has been found, restart at the beginning of the file, and continue
@@ -89,36 +104,59 @@ INT cmd_goto(LPTSTR param)
 retry:
     while (BatchGetString(textline, ARRAYSIZE(textline)))
     {
-        INT pos;
-        INT_PTR size;
-
         if (bRetry && (bc->mempos >= dwCurrPos))
             break;
-        /* Strip out any trailing spaces or control chars */
-        tmp = textline + _tcslen(textline) - 1;
-        while (tmp > textline && (_istcntrl(*tmp) || _istspace(*tmp) || (*tmp
== _T(':'))))
-            --tmp;
-        *(tmp + 1) = _T('\0');
+#if 0
+        /* If this is not a label, continue searching */
+        if (!_tcschr(textline, _T(':')))
+            continue;
+#endif
+
+        label = textline;
+
+        /* A bug in Windows' CMD makes it always ignore the
+         * first character of the line, unless it's a colon. */
+        if (*label != _T(':'))
+            ++label;
+
+        /* Strip any leading whitespace */
+        while (_istspace(*label))
+            ++label;
+
+        /* If this is not a label, continue searching */
+        if (*label != _T(':'))
+            continue;
+
+        /* Skip the first colon or plus sign */
+#if 0
+        if (*label == _T(':') || *label == _T('+'))
+            ++label;
+#endif
+        ++label;
+        /* Strip any whitespace between the colon and the label */
+        while (_istspace(*label))
+            ++label;
+        /* Terminate the label at the first delimiter character */
+        tmp = label;
+        while (!_istcntrl(*tmp) && !_istspace(*tmp) &&
+               !_tcschr(_T(":+"), *tmp) && !_tcschr(STANDARD_SEPS,
*tmp) &&
+               !_tcschr(_T("&|<>"), *tmp))
+        {
+            /* Support the escape caret */
+            if (*tmp == _T('^'))
+            {
+                /* Move the buffer back one character */
+                memmove(tmp, tmp + 1, (_tcslen(tmp + 1) + 1) * sizeof(TCHAR));
+                /* We will ignore the new character */
+            }
-        /* Then leading spaces... */
-        tmp = textline;
-        while (_istspace(*tmp))
             ++tmp;
-
-        /* All space after leading space terminate the string */
-        size = _tcslen(tmp) - 1;
-        pos = 0;
-        while (tmp + pos < tmp + size)
-        {
-            if (_istspace(tmp[pos]))
-                tmp[pos]=_T('\0');
-            ++pos;
         }
+        *tmp = _T('\0');
-        tmp2 = param;
-        /* Use whole label name */
-        if ((*tmp == _T(':')) && ((_tcsicmp(++tmp, param) == 0) ||
(_tcsicmp(tmp, ++tmp2) == 0)))
+        /* Jump if the labels are identical */
+        if (_tcsicmp(label, param) == 0)
         {
             /* Do not process any more parts of a compound command */
             bc->current = NULL;
@@ -132,6 +170,7 @@ retry:
         goto retry;
     }
+NotFound:
     ConErrResPrintf(STRING_GOTO_ERROR2, param);
     ExitBatch();
     return 1;
diff --git a/base/shell/cmd/parser.c b/base/shell/cmd/parser.c
index e25b63aa91b..20310c77830 100644
--- a/base/shell/cmd/parser.c
+++ b/base/shell/cmd/parser.c
@@ -39,9 +39,6 @@ static const TCHAR *const IfOperatorString[] =
 #define IF_MAX_COMPARISON IF_NEQ
 };
-/* These three characters act like spaces to the parser in most contexts */
-#define STANDARD_SEPS _T(",;=")
-
 static BOOL IsSeparator(TCHAR Char)
 {
     return _istspace(Char) || (Char && _tcschr(STANDARD_SEPS, Char));