shdocvw: Support URLs passed by reference in WebBrowser_Navigate2.
[wine] / programs / cmd / wcmdmain.c
1 /*
2  * CMD - Wine-compatible command line interface.
3  *
4  * Copyright (C) 1999 - 2001 D A Pickles
5  * Copyright (C) 2007 J Edmeades
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21
22 /*
23  * FIXME:
24  * - Cannot handle parameters in quotes
25  * - Lots of functionality missing from builtins
26  */
27
28 #include "config.h"
29 #include "wcmd.h"
30 #include "wine/debug.h"
31
32 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
33
34 const WCHAR inbuilt[][10] = {
35         {'A','T','T','R','I','B','\0'},
36         {'C','A','L','L','\0'},
37         {'C','D','\0'},
38         {'C','H','D','I','R','\0'},
39         {'C','L','S','\0'},
40         {'C','O','P','Y','\0'},
41         {'C','T','T','Y','\0'},
42         {'D','A','T','E','\0'},
43         {'D','E','L','\0'},
44         {'D','I','R','\0'},
45         {'E','C','H','O','\0'},
46         {'E','R','A','S','E','\0'},
47         {'F','O','R','\0'},
48         {'G','O','T','O','\0'},
49         {'H','E','L','P','\0'},
50         {'I','F','\0'},
51         {'L','A','B','E','L','\0'},
52         {'M','D','\0'},
53         {'M','K','D','I','R','\0'},
54         {'M','O','V','E','\0'},
55         {'P','A','T','H','\0'},
56         {'P','A','U','S','E','\0'},
57         {'P','R','O','M','P','T','\0'},
58         {'R','E','M','\0'},
59         {'R','E','N','\0'},
60         {'R','E','N','A','M','E','\0'},
61         {'R','D','\0'},
62         {'R','M','D','I','R','\0'},
63         {'S','E','T','\0'},
64         {'S','H','I','F','T','\0'},
65         {'T','I','M','E','\0'},
66         {'T','I','T','L','E','\0'},
67         {'T','Y','P','E','\0'},
68         {'V','E','R','I','F','Y','\0'},
69         {'V','E','R','\0'},
70         {'V','O','L','\0'},
71         {'E','N','D','L','O','C','A','L','\0'},
72         {'S','E','T','L','O','C','A','L','\0'},
73         {'P','U','S','H','D','\0'},
74         {'P','O','P','D','\0'},
75         {'A','S','S','O','C','\0'},
76         {'C','O','L','O','R','\0'},
77         {'F','T','Y','P','E','\0'},
78         {'M','O','R','E','\0'},
79         {'E','X','I','T','\0'}
80 };
81
82 HINSTANCE hinst;
83 DWORD errorlevel;
84 int echo_mode = 1, verify_mode = 0, defaultColor = 7;
85 static int opt_c, opt_k, opt_s;
86 const WCHAR newline[] = {'\n','\0'};
87 static const WCHAR equalsW[] = {'=','\0'};
88 static const WCHAR closeBW[] = {')','\0'};
89 WCHAR anykey[100];
90 WCHAR version_string[100];
91 WCHAR quals[MAX_PATH], param1[MAXSTRING], param2[MAXSTRING];
92 BATCH_CONTEXT *context = NULL;
93 extern struct env_stack *pushd_directories;
94 static const WCHAR *pagedMessage = NULL;
95 static char  *output_bufA = NULL;
96 #define MAX_WRITECONSOLE_SIZE 65535
97 BOOL unicodePipes = FALSE;
98
99
100 /*******************************************************************
101  * WCMD_output_asis_len - send output to current standard output
102  *
103  * Output a formatted unicode string. Ideally this will go to the console
104  *  and hence required WriteConsoleW to output it, however if file i/o is
105  *  redirected, it needs to be WriteFile'd using OEM (not ANSI) format
106  */
107 static void WCMD_output_asis_len(const WCHAR *message, int len, HANDLE device) {
108
109     DWORD   nOut= 0;
110     DWORD   res = 0;
111
112     /* If nothing to write, return (MORE does this sometimes) */
113     if (!len) return;
114
115     /* Try to write as unicode assuming it is to a console */
116     res = WriteConsoleW(device, message, len, &nOut, NULL);
117
118     /* If writing to console fails, assume its file
119        i/o so convert to OEM codepage and output                  */
120     if (!res) {
121       BOOL usedDefaultChar = FALSE;
122       DWORD convertedChars;
123
124       if (!unicodePipes) {
125         /*
126          * Allocate buffer to use when writing to file. (Not freed, as one off)
127          */
128         if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
129                                                   MAX_WRITECONSOLE_SIZE);
130         if (!output_bufA) {
131           WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
132           return;
133         }
134
135         /* Convert to OEM, then output */
136         convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
137                             len, output_bufA, MAX_WRITECONSOLE_SIZE,
138                             "?", &usedDefaultChar);
139         WriteFile(device, output_bufA, convertedChars,
140                   &nOut, FALSE);
141       } else {
142         WriteFile(device, message, len*sizeof(WCHAR),
143                   &nOut, FALSE);
144       }
145     }
146     return;
147 }
148
149 /*******************************************************************
150  * WCMD_output - send output to current standard output device.
151  *
152  */
153
154 void WCMD_output (const WCHAR *format, ...) {
155
156   va_list ap;
157   WCHAR string[1024];
158   int ret;
159
160   va_start(ap,format);
161   ret = vsnprintfW(string, sizeof(string)/sizeof(WCHAR), format, ap);
162   if( ret >= (sizeof(string)/sizeof(WCHAR))) {
163        WINE_ERR("Output truncated in WCMD_output\n" );
164        ret = (sizeof(string)/sizeof(WCHAR)) - 1;
165        string[ret] = '\0';
166   }
167   va_end(ap);
168   WCMD_output_asis_len(string, ret, GetStdHandle(STD_OUTPUT_HANDLE));
169 }
170
171
172 static int line_count;
173 static int max_height;
174 static int max_width;
175 static BOOL paged_mode;
176 static int numChars;
177
178 void WCMD_enter_paged_mode(const WCHAR *msg)
179 {
180   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
181
182   if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
183     max_height = consoleInfo.dwSize.Y;
184     max_width  = consoleInfo.dwSize.X;
185   } else {
186     max_height = 25;
187     max_width  = 80;
188   }
189   paged_mode = TRUE;
190   line_count = 0;
191   numChars   = 0;
192   pagedMessage = (msg==NULL)? anykey : msg;
193 }
194
195 void WCMD_leave_paged_mode(void)
196 {
197   paged_mode = FALSE;
198   pagedMessage = NULL;
199 }
200
201 /***************************************************************************
202  * WCMD_Readfile
203  *
204  *      Read characters in from a console/file, returning result in Unicode
205  *      with signature identical to ReadFile
206  */
207 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars,
208                           LPDWORD charsRead, const LPOVERLAPPED unused) {
209
210     BOOL   res;
211
212     /* Try to read from console as Unicode */
213     res = ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
214
215     /* If reading from console has failed we assume its file
216        i/o so read in and convert from OEM codepage               */
217     if (!res) {
218
219         DWORD numRead;
220         /*
221          * Allocate buffer to use when reading from file. Not freed
222          */
223         if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
224                                                   MAX_WRITECONSOLE_SIZE);
225         if (!output_bufA) {
226           WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
227           return 0;
228         }
229
230         /* Read from file (assume OEM codepage) */
231         res = ReadFile(hIn, output_bufA, maxChars, &numRead, unused);
232
233         /* Convert from OEM */
234         *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, output_bufA, numRead,
235                          intoBuf, maxChars);
236
237     }
238     return res;
239 }
240
241 /*******************************************************************
242  * WCMD_output_asis - send output to current standard output device.
243  *        without formatting eg. when message contains '%'
244  */
245 void WCMD_output_asis (const WCHAR *message) {
246   DWORD count;
247   const WCHAR* ptr;
248   WCHAR string[1024];
249
250   if (paged_mode) {
251     do {
252       ptr = message;
253       while (*ptr && *ptr!='\n' && (numChars < max_width)) {
254         numChars++;
255         ptr++;
256       };
257       if (*ptr == '\n') ptr++;
258       WCMD_output_asis_len(message, (ptr) ? ptr - message : strlenW(message),
259                            GetStdHandle(STD_OUTPUT_HANDLE));
260       if (ptr) {
261         numChars = 0;
262         if (++line_count >= max_height - 1) {
263           line_count = 0;
264           WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage),
265                                GetStdHandle(STD_OUTPUT_HANDLE));
266           WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
267                          sizeof(string)/sizeof(WCHAR), &count, NULL);
268         }
269       }
270     } while (((message = ptr) != NULL) && (*ptr));
271   } else {
272     WCMD_output_asis_len(message, lstrlen(message),
273                          GetStdHandle(STD_OUTPUT_HANDLE));
274   }
275 }
276
277 /****************************************************************************
278  * WCMD_print_error
279  *
280  * Print the message for GetLastError
281  */
282
283 void WCMD_print_error (void) {
284   LPVOID lpMsgBuf;
285   DWORD error_code;
286   int status;
287
288   error_code = GetLastError ();
289   status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
290                           NULL, error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL);
291   if (!status) {
292     WINE_FIXME ("Cannot display message for error %d, status %d\n",
293                         error_code, GetLastError());
294     return;
295   }
296
297   WCMD_output_asis_len(lpMsgBuf, lstrlen(lpMsgBuf),
298                        GetStdHandle(STD_ERROR_HANDLE));
299   LocalFree (lpMsgBuf);
300   WCMD_output_asis_len (newline, lstrlen(newline),
301                         GetStdHandle(STD_ERROR_HANDLE));
302   return;
303 }
304
305 /******************************************************************************
306  * WCMD_show_prompt
307  *
308  *      Display the prompt on STDout
309  *
310  */
311
312 static void WCMD_show_prompt (void) {
313
314   int status;
315   WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
316   WCHAR *p, *q;
317   DWORD len;
318   static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
319
320   len = GetEnvironmentVariable (envPrompt, prompt_string,
321                                 sizeof(prompt_string)/sizeof(WCHAR));
322   if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
323     const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
324     strcpyW (prompt_string, dfltPrompt);
325   }
326   p = prompt_string;
327   q = out_string;
328   *q = '\0';
329   while (*p != '\0') {
330     if (*p != '$') {
331       *q++ = *p++;
332       *q = '\0';
333     }
334     else {
335       p++;
336       switch (toupper(*p)) {
337         case '$':
338           *q++ = '$';
339           break;
340         case 'A':
341           *q++ = '&';
342           break;
343         case 'B':
344           *q++ = '|';
345           break;
346         case 'C':
347           *q++ = '(';
348           break;
349         case 'D':
350           GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
351           while (*q) q++;
352           break;
353         case 'E':
354           *q++ = '\E';
355           break;
356         case 'F':
357           *q++ = ')';
358           break;
359         case 'G':
360           *q++ = '>';
361           break;
362         case 'H':
363           *q++ = '\b';
364           break;
365         case 'L':
366           *q++ = '<';
367           break;
368         case 'N':
369           status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
370           if (status) {
371             *q++ = curdir[0];
372           }
373           break;
374         case 'P':
375           status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
376           if (status) {
377             strcatW (q, curdir);
378             while (*q) q++;
379           }
380           break;
381         case 'Q':
382           *q++ = '=';
383           break;
384         case 'S':
385           *q++ = ' ';
386           break;
387         case 'T':
388           GetTimeFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
389           while (*q) q++;
390           break;
391         case 'V':
392           strcatW (q, version_string);
393           while (*q) q++;
394           break;
395         case '_':
396           *q++ = '\n';
397           break;
398         case '+':
399           if (pushd_directories) {
400             memset(q, '+', pushd_directories->u.stackdepth);
401             q = q + pushd_directories->u.stackdepth;
402           }
403           break;
404       }
405       p++;
406       *q = '\0';
407     }
408   }
409   WCMD_output_asis (out_string);
410 }
411
412
413 /*************************************************************************
414  * WCMD_strdupW
415  *    A wide version of strdup as its missing from unicode.h
416  */
417 WCHAR *WCMD_strdupW(WCHAR *input) {
418    int len=strlenW(input)+1;
419    WCHAR *result = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
420    memcpy(result, input, len * sizeof(WCHAR));
421    return result;
422 }
423
424 /*************************************************************************
425  * WCMD_strsubstW
426  *    Replaces a portion of a Unicode string with the specified string.
427  *    It's up to the caller to ensure there is enough space in the
428  *    destination buffer.
429  */
430 void WCMD_strsubstW(WCHAR* start, WCHAR* next, WCHAR* insert, int len) {
431
432    if (len < 0)
433       len=insert ? lstrlenW(insert) : 0;
434    if (start+len != next)
435        memmove(start+len, next, (strlenW(next) + 1) * sizeof(*next));
436    if (insert)
437        memcpy(start, insert, len * sizeof(*insert));
438 }
439
440 /***************************************************************************
441  * WCMD_strtrim_leading_spaces
442  *
443  *      Remove leading spaces from a string. Return a pointer to the first
444  *      non-space character. Does not modify the input string
445  */
446 WCHAR *WCMD_strtrim_leading_spaces (WCHAR *string) {
447
448   WCHAR *ptr;
449
450   ptr = string;
451   while (*ptr == ' ') ptr++;
452   return ptr;
453 }
454
455 /*************************************************************************
456  * WCMD_opt_s_strip_quotes
457  *
458  *      Remove first and last quote WCHARacters, preserving all other text
459  */
460 static void WCMD_opt_s_strip_quotes(WCHAR *cmd) {
461   WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
462   while((*dest=*src) != '\0') {
463       if (*src=='\"')
464           lastq=dest;
465       dest++, src++;
466   }
467   if (lastq) {
468       dest=lastq++;
469       while ((*dest++=*lastq++) != 0)
470           ;
471   }
472 }
473
474
475 /*************************************************************************
476  * WCMD_expand_envvar
477  *
478  *      Expands environment variables, allowing for WCHARacter substitution
479  */
480 static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR *forVar, WCHAR *forVal) {
481     WCHAR *endOfVar = NULL, *s;
482     WCHAR *colonpos = NULL;
483     WCHAR thisVar[MAXSTRING];
484     WCHAR thisVarContents[MAXSTRING];
485     WCHAR savedchar = 0x00;
486     int len;
487
488     static const WCHAR ErrorLvl[]  = {'E','R','R','O','R','L','E','V','E','L','\0'};
489     static const WCHAR ErrorLvlP[] = {'%','E','R','R','O','R','L','E','V','E','L','%','\0'};
490     static const WCHAR Date[]      = {'D','A','T','E','\0'};
491     static const WCHAR DateP[]     = {'%','D','A','T','E','%','\0'};
492     static const WCHAR Time[]      = {'T','I','M','E','\0'};
493     static const WCHAR TimeP[]     = {'%','T','I','M','E','%','\0'};
494     static const WCHAR Cd[]        = {'C','D','\0'};
495     static const WCHAR CdP[]       = {'%','C','D','%','\0'};
496     static const WCHAR Random[]    = {'R','A','N','D','O','M','\0'};
497     static const WCHAR RandomP[]   = {'%','R','A','N','D','O','M','%','\0'};
498     static const WCHAR Delims[]    = {'%',' ',':','\0'};
499
500     WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start),
501                wine_dbgstr_w(forVal), wine_dbgstr_w(forVar));
502
503     /* Find the end of the environment variable, and extract name */
504     endOfVar = strpbrkW(start+1, Delims);
505
506     if (endOfVar == NULL || *endOfVar==' ') {
507
508       /* In batch program, missing terminator for % and no following
509          ':' just removes the '%'                                   */
510       if (context) {
511         WCMD_strsubstW(start, start + 1, NULL, 0);
512         return start;
513       } else {
514
515         /* In command processing, just ignore it - allows command line
516            syntax like: for %i in (a.a) do echo %i                     */
517         return start+1;
518       }
519     }
520
521     /* If ':' found, process remaining up until '%' (or stop at ':' if
522        a missing '%' */
523     if (*endOfVar==':') {
524         WCHAR *endOfVar2 = strchrW(endOfVar+1, '%');
525         if (endOfVar2 != NULL) endOfVar = endOfVar2;
526     }
527
528     memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
529     thisVar[(endOfVar - start)+1] = 0x00;
530     colonpos = strchrW(thisVar+1, ':');
531
532     /* If there's complex substitution, just need %var% for now
533        to get the expanded data to play with                    */
534     if (colonpos) {
535         *colonpos = '%';
536         savedchar = *(colonpos+1);
537         *(colonpos+1) = 0x00;
538     }
539
540     WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
541
542     /* Expand to contents, if unchanged, return */
543     /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
544     /* override if existing env var called that name              */
545     if ((CompareString (LOCALE_USER_DEFAULT,
546                         NORM_IGNORECASE | SORT_STRINGSORT,
547                         thisVar, 12, ErrorLvlP, -1) == 2) &&
548                 (GetEnvironmentVariable(ErrorLvl, thisVarContents, 1) == 0) &&
549                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
550       static const WCHAR fmt[] = {'%','d','\0'};
551       wsprintf(thisVarContents, fmt, errorlevel);
552       len = strlenW(thisVarContents);
553
554     } else if ((CompareString (LOCALE_USER_DEFAULT,
555                                NORM_IGNORECASE | SORT_STRINGSORT,
556                                thisVar, 6, DateP, -1) == 2) &&
557                 (GetEnvironmentVariable(Date, thisVarContents, 1) == 0) &&
558                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
559
560       GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
561                     NULL, thisVarContents, MAXSTRING);
562       len = strlenW(thisVarContents);
563
564     } else if ((CompareString (LOCALE_USER_DEFAULT,
565                                NORM_IGNORECASE | SORT_STRINGSORT,
566                                thisVar, 6, TimeP, -1) == 2) &&
567                 (GetEnvironmentVariable(Time, thisVarContents, 1) == 0) &&
568                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
569       GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
570                         NULL, thisVarContents, MAXSTRING);
571       len = strlenW(thisVarContents);
572
573     } else if ((CompareString (LOCALE_USER_DEFAULT,
574                                NORM_IGNORECASE | SORT_STRINGSORT,
575                                thisVar, 4, CdP, -1) == 2) &&
576                 (GetEnvironmentVariable(Cd, thisVarContents, 1) == 0) &&
577                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
578       GetCurrentDirectory (MAXSTRING, thisVarContents);
579       len = strlenW(thisVarContents);
580
581     } else if ((CompareString (LOCALE_USER_DEFAULT,
582                                NORM_IGNORECASE | SORT_STRINGSORT,
583                                thisVar, 8, RandomP, -1) == 2) &&
584                 (GetEnvironmentVariable(Random, thisVarContents, 1) == 0) &&
585                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
586       static const WCHAR fmt[] = {'%','d','\0'};
587       wsprintf(thisVarContents, fmt, rand() % 32768);
588       len = strlenW(thisVarContents);
589
590     /* Look for a matching 'for' variable */
591     } else if (forVar &&
592                (CompareString (LOCALE_USER_DEFAULT,
593                                SORT_STRINGSORT,
594                                thisVar,
595                                (colonpos - thisVar) - 1,
596                                forVar, -1) == 2)) {
597       strcpyW(thisVarContents, forVal);
598       len = strlenW(thisVarContents);
599
600     } else {
601
602       len = ExpandEnvironmentStrings(thisVar, thisVarContents,
603                                sizeof(thisVarContents)/sizeof(WCHAR));
604     }
605
606     if (len == 0)
607       return endOfVar+1;
608
609     /* In a batch program, unknown env vars are replaced with nothing,
610          note syntax %garbage:1,3% results in anything after the ':'
611          except the %
612        From the command line, you just get back what you entered      */
613     if (lstrcmpiW(thisVar, thisVarContents) == 0) {
614
615       /* Restore the complex part after the compare */
616       if (colonpos) {
617         *colonpos = ':';
618         *(colonpos+1) = savedchar;
619       }
620
621       /* Command line - just ignore this */
622       if (context == NULL) return endOfVar+1;
623
624
625       /* Batch - replace unknown env var with nothing */
626       if (colonpos == NULL) {
627         WCMD_strsubstW(start, endOfVar + 1, NULL, 0);
628       } else {
629         len = strlenW(thisVar);
630         thisVar[len-1] = 0x00;
631         /* If %:...% supplied, : is retained */
632         if (colonpos == thisVar+1) {
633           WCMD_strsubstW(start, endOfVar + 1, colonpos, -1);
634         } else {
635           WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1);
636         }
637       }
638       return start;
639
640     }
641
642     /* See if we need to do complex substitution (any ':'s), if not
643        then our work here is done                                  */
644     if (colonpos == NULL) {
645       WCMD_strsubstW(start, endOfVar + 1, thisVarContents, -1);
646       return start;
647     }
648
649     /* Restore complex bit */
650     *colonpos = ':';
651     *(colonpos+1) = savedchar;
652
653     /*
654         Handle complex substitutions:
655            xxx=yyy    (replace xxx with yyy)
656            *xxx=yyy   (replace up to and including xxx with yyy)
657            ~x         (from x WCHARs in)
658            ~-x        (from x WCHARs from the end)
659            ~x,y       (from x WCHARs in for y WCHARacters)
660            ~x,-y      (from x WCHARs in until y WCHARacters from the end)
661      */
662
663     /* ~ is substring manipulation */
664     if (savedchar == '~') {
665
666       int   substrposition, substrlength = 0;
667       WCHAR *commapos = strchrW(colonpos+2, ',');
668       WCHAR *startCopy;
669
670       substrposition = atolW(colonpos+2);
671       if (commapos) substrlength = atolW(commapos+1);
672
673       /* Check bounds */
674       if (substrposition >= 0) {
675         startCopy = &thisVarContents[min(substrposition, len)];
676       } else {
677         startCopy = &thisVarContents[max(0, len+substrposition-1)];
678       }
679
680       if (commapos == NULL) {
681         /* Copy the lot */
682         WCMD_strsubstW(start, endOfVar + 1, startCopy, -1);
683       } else if (substrlength < 0) {
684
685         int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
686         if (copybytes > len) copybytes = len;
687         else if (copybytes < 0) copybytes = 0;
688         WCMD_strsubstW(start, endOfVar + 1, startCopy, copybytes);
689       } else {
690         WCMD_strsubstW(start, endOfVar + 1, startCopy, substrlength);
691       }
692
693       return start;
694
695     /* search and replace manipulation */
696     } else {
697       WCHAR *equalspos = strstrW(colonpos, equalsW);
698       WCHAR *replacewith = equalspos+1;
699       WCHAR *found       = NULL;
700       WCHAR *searchIn;
701       WCHAR *searchFor;
702
703       if (equalspos == NULL) return start+1;
704       s = WCMD_strdupW(endOfVar + 1);
705
706       /* Null terminate both strings */
707       thisVar[strlenW(thisVar)-1] = 0x00;
708       *equalspos = 0x00;
709
710       /* Since we need to be case insensitive, copy the 2 buffers */
711       searchIn  = WCMD_strdupW(thisVarContents);
712       CharUpperBuff(searchIn, strlenW(thisVarContents));
713       searchFor = WCMD_strdupW(colonpos+1);
714       CharUpperBuff(searchFor, strlenW(colonpos+1));
715
716       /* Handle wildcard case */
717       if (*(colonpos+1) == '*') {
718         /* Search for string to replace */
719         found = strstrW(searchIn, searchFor+1);
720
721         if (found) {
722           /* Do replacement */
723           strcpyW(start, replacewith);
724           strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
725           strcatW(start, s);
726         } else {
727           /* Copy as is */
728           strcpyW(start, thisVarContents);
729           strcatW(start, s);
730         }
731
732       } else {
733         /* Loop replacing all instances */
734         WCHAR *lastFound = searchIn;
735         WCHAR *outputposn = start;
736
737         *start = 0x00;
738         while ((found = strstrW(lastFound, searchFor))) {
739             lstrcpynW(outputposn,
740                     thisVarContents + (lastFound-searchIn),
741                     (found - lastFound)+1);
742             outputposn  = outputposn + (found - lastFound);
743             strcatW(outputposn, replacewith);
744             outputposn = outputposn + strlenW(replacewith);
745             lastFound = found + strlenW(searchFor);
746         }
747         strcatW(outputposn,
748                 thisVarContents + (lastFound-searchIn));
749         strcatW(outputposn, s);
750       }
751       HeapFree(GetProcessHeap(), 0, s);
752       HeapFree(GetProcessHeap(), 0, searchIn);
753       HeapFree(GetProcessHeap(), 0, searchFor);
754       return start;
755     }
756     return start+1;
757 }
758
759 /*****************************************************************************
760  * Expand the command. Native expands lines from batch programs as they are
761  * read in and not again, except for 'for' variable substitution.
762  * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
763  */
764 static void handleExpansion(WCHAR *cmd, BOOL justFors, WCHAR *forVariable, WCHAR *forValue) {
765
766   /* For commands in a context (batch program):                  */
767   /*   Expand environment variables in a batch file %{0-9} first */
768   /*     including support for any ~ modifiers                   */
769   /* Additionally:                                               */
770   /*   Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special  */
771   /*     names allowing environment variable overrides           */
772   /* NOTE: To support the %PATH:xxx% syntax, also perform        */
773   /*   manual expansion of environment variables here            */
774
775   WCHAR *p = cmd;
776   WCHAR *t;
777   int   i;
778
779   while ((p = strchrW(p, '%'))) {
780
781     WINE_TRACE("Translate command:%s %d (at: %s)\n",
782                    wine_dbgstr_w(cmd), justFors, wine_dbgstr_w(p));
783     i = *(p+1) - '0';
784
785     /* Don't touch %% unless its in Batch */
786     if (!justFors && *(p+1) == '%') {
787       if (context) {
788         WCMD_strsubstW(p, p+1, NULL, 0);
789       }
790       p+=1;
791
792     /* Replace %~ modifications if in batch program */
793     } else if (*(p+1) == '~') {
794       WCMD_HandleTildaModifiers(&p, forVariable, forValue, justFors);
795       p++;
796
797     /* Replace use of %0...%9 if in batch program*/
798     } else if (!justFors && context && (i >= 0) && (i <= 9)) {
799       t = WCMD_parameter (context -> command, i + context -> shift_count[i], NULL);
800       WCMD_strsubstW(p, p+2, t, -1);
801
802     /* Replace use of %* if in batch program*/
803     } else if (!justFors && context && *(p+1)=='*') {
804       WCHAR *startOfParms = NULL;
805       t = WCMD_parameter (context -> command, 1, &startOfParms);
806       if (startOfParms != NULL)
807         WCMD_strsubstW(p, p+2, startOfParms, -1);
808       else
809         WCMD_strsubstW(p, p+2, NULL, 0);
810
811     } else if (forVariable &&
812                (CompareString (LOCALE_USER_DEFAULT,
813                                SORT_STRINGSORT,
814                                p,
815                                strlenW(forVariable),
816                                forVariable, -1) == 2)) {
817       WCMD_strsubstW(p, p + strlenW(forVariable), forValue, -1);
818
819     } else if (!justFors) {
820       p = WCMD_expand_envvar(p, forVariable, forValue);
821
822     /* In a FOR loop, see if this is the variable to replace */
823     } else { /* Ignore %'s on second pass of batch program */
824       p++;
825     }
826   }
827
828   return;
829 }
830
831
832 /*******************************************************************
833  * WCMD_parse - parse a command into parameters and qualifiers.
834  *
835  *      On exit, all qualifiers are concatenated into q, the first string
836  *      not beginning with "/" is in p1 and the
837  *      second in p2. Any subsequent non-qualifier strings are lost.
838  *      Parameters in quotes are handled.
839  */
840 static void WCMD_parse (WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2)
841 {
842   int p = 0;
843
844   *q = *p1 = *p2 = '\0';
845   while (TRUE) {
846     switch (*s) {
847       case '/':
848         *q++ = *s++;
849         while ((*s != '\0') && (*s != ' ') && *s != '/') {
850           *q++ = toupperW (*s++);
851         }
852         *q = '\0';
853         break;
854       case ' ':
855       case '\t':
856         s++;
857         break;
858       case '"':
859         s++;
860         while ((*s != '\0') && (*s != '"')) {
861           if (p == 0) *p1++ = *s++;
862           else if (p == 1) *p2++ = *s++;
863           else s++;
864         }
865         if (p == 0) *p1 = '\0';
866         if (p == 1) *p2 = '\0';
867         p++;
868         if (*s == '"') s++;
869         break;
870       case '\0':
871         return;
872       default:
873         while ((*s != '\0') && (*s != ' ') && (*s != '\t')
874                && (*s != '=')  && (*s != ',') ) {
875           if (p == 0) *p1++ = *s++;
876           else if (p == 1) *p2++ = *s++;
877           else s++;
878         }
879         /* Skip concurrent parms */
880         while ((*s == ' ') || (*s == '\t') || (*s == '=')  || (*s == ',') ) s++;
881
882         if (p == 0) *p1 = '\0';
883         if (p == 1) *p2 = '\0';
884         p++;
885     }
886   }
887 }
888
889 static void init_msvcrt_io_block(STARTUPINFO* st)
890 {
891     STARTUPINFO st_p;
892     /* fetch the parent MSVCRT info block if any, so that the child can use the
893      * same handles as its grand-father
894      */
895     st_p.cb = sizeof(STARTUPINFO);
896     GetStartupInfo(&st_p);
897     st->cbReserved2 = st_p.cbReserved2;
898     st->lpReserved2 = st_p.lpReserved2;
899     if (st_p.cbReserved2 && st_p.lpReserved2)
900     {
901         /* Override the entries for fd 0,1,2 if we happened
902          * to change those std handles (this depends on the way wcmd sets
903          * it's new input & output handles)
904          */
905         size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
906         BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
907         if (ptr)
908         {
909             unsigned num = *(unsigned*)st_p.lpReserved2;
910             char* flags = (char*)(ptr + sizeof(unsigned));
911             HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
912
913             memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
914             st->cbReserved2 = sz;
915             st->lpReserved2 = ptr;
916
917 #define WX_OPEN 0x01    /* see dlls/msvcrt/file.c */
918             if (num <= 0 || (flags[0] & WX_OPEN))
919             {
920                 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
921                 flags[0] |= WX_OPEN;
922             }
923             if (num <= 1 || (flags[1] & WX_OPEN))
924             {
925                 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
926                 flags[1] |= WX_OPEN;
927             }
928             if (num <= 2 || (flags[2] & WX_OPEN))
929             {
930                 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
931                 flags[2] |= WX_OPEN;
932             }
933 #undef WX_OPEN
934         }
935     }
936 }
937
938 /******************************************************************************
939  * WCMD_run_program
940  *
941  *      Execute a command line as an external program. Must allow recursion.
942  *
943  *      Precedence:
944  *        Manual testing under windows shows PATHEXT plays a key part in this,
945  *      and the search algorithm and precedence appears to be as follows.
946  *
947  *      Search locations:
948  *        If directory supplied on command, just use that directory
949  *        If extension supplied on command, look for that explicit name first
950  *        Otherwise, search in each directory on the path
951  *      Precedence:
952  *        If extension supplied on command, look for that explicit name first
953  *        Then look for supplied name .* (even if extension supplied, so
954  *          'garbage.exe' will match 'garbage.exe.cmd')
955  *        If any found, cycle through PATHEXT looking for name.exe one by one
956  *      Launching
957  *        Once a match has been found, it is launched - Code currently uses
958  *          findexecutable to achieve this which is left untouched.
959  */
960
961 void WCMD_run_program (WCHAR *command, int called) {
962
963   WCHAR  temp[MAX_PATH];
964   WCHAR  pathtosearch[MAXSTRING];
965   WCHAR *pathposn;
966   WCHAR  stemofsearch[MAX_PATH];    /* maximum allowed executable name is
967                                        MAX_PATH, including null character */
968   WCHAR *lastSlash;
969   WCHAR  pathext[MAXSTRING];
970   BOOL  extensionsupplied = FALSE;
971   BOOL  launched = FALSE;
972   BOOL  status;
973   BOOL  assumeInternal = FALSE;
974   DWORD len;
975   static const WCHAR envPath[] = {'P','A','T','H','\0'};
976   static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
977   static const WCHAR delims[] = {'/','\\',':','\0'};
978
979   WCMD_parse (command, quals, param1, param2);  /* Quick way to get the filename */
980   if (!(*param1) && !(*param2))
981     return;
982
983   /* Calculate the search path and stem to search for */
984   if (strpbrkW (param1, delims) == NULL) {  /* No explicit path given, search path */
985     static const WCHAR curDir[] = {'.',';','\0'};
986     strcpyW(pathtosearch, curDir);
987     len = GetEnvironmentVariable (envPath, &pathtosearch[2], (sizeof(pathtosearch)/sizeof(WCHAR))-2);
988     if ((len == 0) || (len >= (sizeof(pathtosearch)/sizeof(WCHAR)) - 2)) {
989       static const WCHAR curDir[] = {'.','\0'};
990       strcpyW (pathtosearch, curDir);
991     }
992     if (strchrW(param1, '.') != NULL) extensionsupplied = TRUE;
993     if (strlenW(param1) >= MAX_PATH)
994     {
995         WCMD_output_asis(WCMD_LoadMessage(WCMD_LINETOOLONG));
996         return;
997     }
998
999     strcpyW(stemofsearch, param1);
1000
1001   } else {
1002
1003     /* Convert eg. ..\fred to include a directory by removing file part */
1004     GetFullPathName(param1, sizeof(pathtosearch)/sizeof(WCHAR), pathtosearch, NULL);
1005     lastSlash = strrchrW(pathtosearch, '\\');
1006     if (lastSlash && strchrW(lastSlash, '.') != NULL) extensionsupplied = TRUE;
1007     strcpyW(stemofsearch, lastSlash+1);
1008
1009     /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1010        c:\windows\a.bat syntax                                                 */
1011     if (lastSlash) *(lastSlash + 1) = 0x00;
1012   }
1013
1014   /* Now extract PATHEXT */
1015   len = GetEnvironmentVariable (envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
1016   if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
1017     static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
1018                                         '.','c','o','m',';',
1019                                         '.','c','m','d',';',
1020                                         '.','e','x','e','\0'};
1021     strcpyW (pathext, dfltPathExt);
1022   }
1023
1024   /* Loop through the search path, dir by dir */
1025   pathposn = pathtosearch;
1026   WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
1027              wine_dbgstr_w(stemofsearch));
1028   while (!launched && pathposn) {
1029
1030     WCHAR  thisDir[MAX_PATH] = {'\0'};
1031     WCHAR *pos               = NULL;
1032     BOOL  found             = FALSE;
1033     const WCHAR slashW[] = {'\\','\0'};
1034
1035     /* Work on the first directory on the search path */
1036     pos = strchrW(pathposn, ';');
1037     if (pos) {
1038       memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
1039       thisDir[(pos-pathposn)] = 0x00;
1040       pathposn = pos+1;
1041
1042     } else {
1043       strcpyW(thisDir, pathposn);
1044       pathposn = NULL;
1045     }
1046
1047     /* Since you can have eg. ..\.. on the path, need to expand
1048        to full information                                      */
1049     strcpyW(temp, thisDir);
1050     GetFullPathName(temp, MAX_PATH, thisDir, NULL);
1051
1052     /* 1. If extension supplied, see if that file exists */
1053     strcatW(thisDir, slashW);
1054     strcatW(thisDir, stemofsearch);
1055     pos = &thisDir[strlenW(thisDir)]; /* Pos = end of name */
1056
1057     /* 1. If extension supplied, see if that file exists */
1058     if (extensionsupplied) {
1059       if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
1060         found = TRUE;
1061       }
1062     }
1063
1064     /* 2. Any .* matches? */
1065     if (!found) {
1066       HANDLE          h;
1067       WIN32_FIND_DATA finddata;
1068       static const WCHAR allFiles[] = {'.','*','\0'};
1069
1070       strcatW(thisDir,allFiles);
1071       h = FindFirstFile(thisDir, &finddata);
1072       FindClose(h);
1073       if (h != INVALID_HANDLE_VALUE) {
1074
1075         WCHAR *thisExt = pathext;
1076
1077         /* 3. Yes - Try each path ext */
1078         while (thisExt) {
1079           WCHAR *nextExt = strchrW(thisExt, ';');
1080
1081           if (nextExt) {
1082             memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1083             pos[(nextExt-thisExt)] = 0x00;
1084             thisExt = nextExt+1;
1085           } else {
1086             strcpyW(pos, thisExt);
1087             thisExt = NULL;
1088           }
1089
1090           if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
1091             found = TRUE;
1092             thisExt = NULL;
1093           }
1094         }
1095       }
1096     }
1097
1098    /* Internal programs won't be picked up by this search, so even
1099       though not found, try one last createprocess and wait for it
1100       to complete.
1101       Note: Ideally we could tell between a console app (wait) and a
1102       windows app, but the API's for it fail in this case           */
1103     if (!found && pathposn == NULL) {
1104         WINE_TRACE("ASSUMING INTERNAL\n");
1105         assumeInternal = TRUE;
1106     } else {
1107         WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1108     }
1109
1110     /* Once found, launch it */
1111     if (found || assumeInternal) {
1112       STARTUPINFO st;
1113       PROCESS_INFORMATION pe;
1114       SHFILEINFO psfi;
1115       DWORD console;
1116       HINSTANCE hinst;
1117       WCHAR *ext = strrchrW( thisDir, '.' );
1118       static const WCHAR batExt[] = {'.','b','a','t','\0'};
1119       static const WCHAR cmdExt[] = {'.','c','m','d','\0'};
1120
1121       launched = TRUE;
1122
1123       /* Special case BAT and CMD */
1124       if (ext && !strcmpiW(ext, batExt)) {
1125         WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1126         return;
1127       } else if (ext && !strcmpiW(ext, cmdExt)) {
1128         WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1129         return;
1130       } else {
1131
1132         /* thisDir contains the file to be launched, but with what?
1133            eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1134         hinst = FindExecutable (thisDir, NULL, temp);
1135         if ((INT_PTR)hinst < 32)
1136           console = 0;
1137         else
1138           console = SHGetFileInfo (temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1139
1140         ZeroMemory (&st, sizeof(STARTUPINFO));
1141         st.cb = sizeof(STARTUPINFO);
1142         init_msvcrt_io_block(&st);
1143
1144         /* Launch the process and if a CUI wait on it to complete
1145            Note: Launching internal wine processes cannot specify a full path to exe */
1146         status = CreateProcess (assumeInternal?NULL : thisDir,
1147                                 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1148         if ((opt_c || opt_k) && !opt_s && !status
1149             && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1150           /* strip first and last quote WCHARacters and try again */
1151           WCMD_opt_s_strip_quotes(command);
1152           opt_s=1;
1153           WCMD_run_program(command, called);
1154           return;
1155         }
1156         if (!status) {
1157           WCMD_print_error ();
1158           /* If a command fails to launch, it sets errorlevel 9009 - which
1159              does not seem to have any associated constant definition     */
1160           errorlevel = 9009;
1161           return;
1162         }
1163         if (!assumeInternal && !console) errorlevel = 0;
1164         else
1165         {
1166             /* Always wait when called in a batch program context */
1167             if (assumeInternal || context || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1168             GetExitCodeProcess (pe.hProcess, &errorlevel);
1169             if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1170         }
1171         CloseHandle(pe.hProcess);
1172         CloseHandle(pe.hThread);
1173         return;
1174       }
1175     }
1176   }
1177
1178   /* Not found anywhere - give up */
1179   SetLastError(ERROR_FILE_NOT_FOUND);
1180   WCMD_print_error ();
1181
1182   /* If a command fails to launch, it sets errorlevel 9009 - which
1183      does not seem to have any associated constant definition     */
1184   errorlevel = 9009;
1185   return;
1186
1187 }
1188
1189 /*****************************************************************************
1190  * Process one command. If the command is EXIT this routine does not return.
1191  * We will recurse through here executing batch files.
1192  */
1193 void WCMD_execute (WCHAR *command, WCHAR *redirects,
1194                    WCHAR *forVariable, WCHAR *forValue,
1195                    CMD_LIST **cmdList)
1196 {
1197     WCHAR *cmd, *p, *redir;
1198     int status, i;
1199     DWORD count, creationDisposition;
1200     HANDLE h;
1201     WCHAR *whichcmd;
1202     SECURITY_ATTRIBUTES sa;
1203     WCHAR *new_cmd = NULL;
1204     WCHAR *new_redir = NULL;
1205     HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
1206                                 GetStdHandle (STD_OUTPUT_HANDLE),
1207                                 GetStdHandle (STD_ERROR_HANDLE)};
1208     DWORD  idx_stdhandles[3] = {STD_INPUT_HANDLE,
1209                                 STD_OUTPUT_HANDLE,
1210                                 STD_ERROR_HANDLE};
1211     BOOL piped = FALSE;
1212
1213     WINE_TRACE("command on entry:%s (%p), with '%s'='%s'\n",
1214                wine_dbgstr_w(command), cmdList,
1215                wine_dbgstr_w(forVariable), wine_dbgstr_w(forValue));
1216
1217     /* If the next command is a pipe then we implement pipes by redirecting
1218        the output from this command to a temp file and input into the
1219        next command from that temp file.
1220        FIXME: Use of named pipes would make more sense here as currently this
1221        process has to finish before the next one can start but this requires
1222        a change to not wait for the first app to finish but rather the pipe  */
1223     if (cmdList && (*cmdList)->nextcommand &&
1224         (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
1225
1226         WCHAR temp_path[MAX_PATH];
1227         static const WCHAR cmdW[]     = {'C','M','D','\0'};
1228
1229         /* Remember piping is in action */
1230         WINE_TRACE("Output needs to be piped\n");
1231         piped = TRUE;
1232
1233         /* Generate a unique temporary filename */
1234         GetTempPath (sizeof(temp_path)/sizeof(WCHAR), temp_path);
1235         GetTempFileName (temp_path, cmdW, 0, (*cmdList)->nextcommand->pipeFile);
1236         WINE_TRACE("Using temporary file of %s\n",
1237                    wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
1238     }
1239
1240     /* Move copy of the command onto the heap so it can be expanded */
1241     new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1242     if (!new_cmd)
1243     {
1244         WINE_ERR("Could not allocate memory for new_cmd\n");
1245         return;
1246     }
1247     strcpyW(new_cmd, command);
1248
1249     /* Move copy of the redirects onto the heap so it can be expanded */
1250     new_redir = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1251     if (!new_redir)
1252     {
1253         WINE_ERR("Could not allocate memory for new_redir\n");
1254         HeapFree( GetProcessHeap(), 0, new_cmd );
1255         return;
1256     }
1257
1258     /* If piped output, send stdout to the pipe by appending >filename to redirects */
1259     if (piped) {
1260         static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1261         wsprintf (new_redir, redirOut, redirects, (*cmdList)->nextcommand->pipeFile);
1262         WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
1263     } else {
1264         strcpyW(new_redir, redirects);
1265     }
1266
1267     /* Expand variables in command line mode only (batch mode will
1268        be expanded as the line is read in, except for 'for' loops) */
1269     handleExpansion(new_cmd, (context != NULL), forVariable, forValue);
1270     handleExpansion(new_redir, (context != NULL), forVariable, forValue);
1271     cmd = new_cmd;
1272
1273     /* Show prompt before batch line IF echo is on and in batch program */
1274     if (context && echo_mode && (cmd[0] != '@')) {
1275       WCMD_show_prompt();
1276       WCMD_output_asis ( cmd);
1277       WCMD_output_asis ( newline);
1278     }
1279
1280 /*
1281  *      Changing default drive has to be handled as a special case.
1282  */
1283
1284     if ((cmd[1] == ':') && IsCharAlpha (cmd[0]) && (strlenW(cmd) == 2)) {
1285       WCHAR envvar[5];
1286       WCHAR dir[MAX_PATH];
1287
1288       /* According to MSDN CreateProcess docs, special env vars record
1289          the current directory on each drive, in the form =C:
1290          so see if one specified, and if so go back to it             */
1291       strcpyW(envvar, equalsW);
1292       strcatW(envvar, cmd);
1293       if (GetEnvironmentVariable(envvar, dir, MAX_PATH) == 0) {
1294         static const WCHAR fmt[] = {'%','s','\\','\0'};
1295         wsprintf(cmd, fmt, cmd);
1296         WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
1297       }
1298       WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
1299       status = SetCurrentDirectory (cmd);
1300       if (!status) WCMD_print_error ();
1301       HeapFree( GetProcessHeap(), 0, cmd );
1302       HeapFree( GetProcessHeap(), 0, new_redir );
1303       return;
1304     }
1305
1306     sa.nLength = sizeof(sa);
1307     sa.lpSecurityDescriptor = NULL;
1308     sa.bInheritHandle = TRUE;
1309
1310 /*
1311  *      Redirect stdin, stdout and/or stderr if required.
1312  */
1313
1314     /* STDIN could come from a preceding pipe, so delete on close if it does */
1315     if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
1316         WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
1317         h = CreateFile ((*cmdList)->pipeFile, GENERIC_READ,
1318                   FILE_SHARE_READ, &sa, OPEN_EXISTING,
1319                   FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
1320         if (h == INVALID_HANDLE_VALUE) {
1321           WCMD_print_error ();
1322           HeapFree( GetProcessHeap(), 0, cmd );
1323           HeapFree( GetProcessHeap(), 0, new_redir );
1324           return;
1325         }
1326         SetStdHandle (STD_INPUT_HANDLE, h);
1327
1328         /* No need to remember the temporary name any longer once opened */
1329         (*cmdList)->pipeFile[0] = 0x00;
1330
1331     /* Otherwise STDIN could come from a '<' redirect */
1332     } else if ((p = strchrW(new_redir,'<')) != NULL) {
1333       h = CreateFile (WCMD_parameter (++p, 0, NULL), GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
1334                 FILE_ATTRIBUTE_NORMAL, NULL);
1335       if (h == INVALID_HANDLE_VALUE) {
1336         WCMD_print_error ();
1337         HeapFree( GetProcessHeap(), 0, cmd );
1338         HeapFree( GetProcessHeap(), 0, new_redir );
1339         return;
1340       }
1341       SetStdHandle (STD_INPUT_HANDLE, h);
1342     }
1343
1344     /* Scan the whole command looking for > and 2> */
1345     redir = new_redir;
1346     while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
1347       int handle = 0;
1348
1349       if (*(p-1)!='2') {
1350         handle = 1;
1351       } else {
1352         handle = 2;
1353       }
1354
1355       p++;
1356       if ('>' == *p) {
1357         creationDisposition = OPEN_ALWAYS;
1358         p++;
1359       }
1360       else {
1361         creationDisposition = CREATE_ALWAYS;
1362       }
1363
1364       /* Add support for 2>&1 */
1365       redir = p;
1366       if (*p == '&') {
1367         int idx = *(p+1) - '0';
1368
1369         if (DuplicateHandle(GetCurrentProcess(),
1370                         GetStdHandle(idx_stdhandles[idx]),
1371                         GetCurrentProcess(),
1372                         &h,
1373                         0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
1374           WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
1375         }
1376         WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
1377
1378       } else {
1379         WCHAR *param = WCMD_parameter (p, 0, NULL);
1380         h = CreateFile (param, GENERIC_WRITE, 0, &sa, creationDisposition,
1381                         FILE_ATTRIBUTE_NORMAL, NULL);
1382         if (h == INVALID_HANDLE_VALUE) {
1383           WCMD_print_error ();
1384           HeapFree( GetProcessHeap(), 0, cmd );
1385           HeapFree( GetProcessHeap(), 0, new_redir );
1386           return;
1387         }
1388         if (SetFilePointer (h, 0, NULL, FILE_END) ==
1389               INVALID_SET_FILE_POINTER) {
1390           WCMD_print_error ();
1391         }
1392         WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
1393       }
1394
1395       SetStdHandle (idx_stdhandles[handle], h);
1396     }
1397
1398 /*
1399  * Strip leading whitespaces, and a '@' if supplied
1400  */
1401     whichcmd = WCMD_strtrim_leading_spaces(cmd);
1402     WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
1403     if (whichcmd[0] == '@') whichcmd++;
1404
1405 /*
1406  *      Check if the command entered is internal. If it is, pass the rest of the
1407  *      line down to the command. If not try to run a program.
1408  */
1409
1410     count = 0;
1411     while (IsCharAlphaNumeric(whichcmd[count])) {
1412       count++;
1413     }
1414     for (i=0; i<=WCMD_EXIT; i++) {
1415       if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1416         whichcmd, count, inbuilt[i], -1) == 2) break;
1417     }
1418     p = WCMD_strtrim_leading_spaces (&whichcmd[count]);
1419     WCMD_parse (p, quals, param1, param2);
1420     WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1421
1422     switch (i) {
1423
1424       case WCMD_ATTRIB:
1425         WCMD_setshow_attrib ();
1426         break;
1427       case WCMD_CALL:
1428         WCMD_call (p);
1429         break;
1430       case WCMD_CD:
1431       case WCMD_CHDIR:
1432         WCMD_setshow_default (p);
1433         break;
1434       case WCMD_CLS:
1435         WCMD_clear_screen ();
1436         break;
1437       case WCMD_COPY:
1438         WCMD_copy ();
1439         break;
1440       case WCMD_CTTY:
1441         WCMD_change_tty ();
1442         break;
1443       case WCMD_DATE:
1444         WCMD_setshow_date ();
1445         break;
1446       case WCMD_DEL:
1447       case WCMD_ERASE:
1448         WCMD_delete (p, TRUE);
1449         break;
1450       case WCMD_DIR:
1451         WCMD_directory (p);
1452         break;
1453       case WCMD_ECHO:
1454         WCMD_echo(&whichcmd[count]);
1455         break;
1456       case WCMD_FOR:
1457         WCMD_for (p, cmdList);
1458         break;
1459       case WCMD_GOTO:
1460         WCMD_goto (cmdList);
1461         break;
1462       case WCMD_HELP:
1463         WCMD_give_help (p);
1464         break;
1465       case WCMD_IF:
1466         WCMD_if (p, cmdList);
1467         break;
1468       case WCMD_LABEL:
1469         WCMD_volume (1, p);
1470         break;
1471       case WCMD_MD:
1472       case WCMD_MKDIR:
1473         WCMD_create_dir ();
1474         break;
1475       case WCMD_MOVE:
1476         WCMD_move ();
1477         break;
1478       case WCMD_PATH:
1479         WCMD_setshow_path (p);
1480         break;
1481       case WCMD_PAUSE:
1482         WCMD_pause ();
1483         break;
1484       case WCMD_PROMPT:
1485         WCMD_setshow_prompt ();
1486         break;
1487       case WCMD_REM:
1488         break;
1489       case WCMD_REN:
1490       case WCMD_RENAME:
1491         WCMD_rename ();
1492         break;
1493       case WCMD_RD:
1494       case WCMD_RMDIR:
1495         WCMD_remove_dir (p);
1496         break;
1497       case WCMD_SETLOCAL:
1498         WCMD_setlocal(p);
1499         break;
1500       case WCMD_ENDLOCAL:
1501         WCMD_endlocal();
1502         break;
1503       case WCMD_SET:
1504         WCMD_setshow_env (p);
1505         break;
1506       case WCMD_SHIFT:
1507         WCMD_shift (p);
1508         break;
1509       case WCMD_TIME:
1510         WCMD_setshow_time ();
1511         break;
1512       case WCMD_TITLE:
1513         if (strlenW(&whichcmd[count]) > 0)
1514           WCMD_title(&whichcmd[count+1]);
1515         break;
1516       case WCMD_TYPE:
1517         WCMD_type (p);
1518         break;
1519       case WCMD_VER:
1520         WCMD_version ();
1521         break;
1522       case WCMD_VERIFY:
1523         WCMD_verify (p);
1524         break;
1525       case WCMD_VOL:
1526         WCMD_volume (0, p);
1527         break;
1528       case WCMD_PUSHD:
1529         WCMD_pushd(p);
1530         break;
1531       case WCMD_POPD:
1532         WCMD_popd();
1533         break;
1534       case WCMD_ASSOC:
1535         WCMD_assoc(p, TRUE);
1536         break;
1537       case WCMD_COLOR:
1538         WCMD_color();
1539         break;
1540       case WCMD_FTYPE:
1541         WCMD_assoc(p, FALSE);
1542         break;
1543       case WCMD_MORE:
1544         WCMD_more(p);
1545         break;
1546       case WCMD_EXIT:
1547         WCMD_exit (cmdList);
1548         break;
1549       default:
1550         WCMD_run_program (whichcmd, 0);
1551     }
1552     HeapFree( GetProcessHeap(), 0, cmd );
1553     HeapFree( GetProcessHeap(), 0, new_redir );
1554
1555     /* Restore old handles */
1556     for (i=0; i<3; i++) {
1557       if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
1558         CloseHandle (GetStdHandle (idx_stdhandles[i]));
1559         SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
1560       }
1561     }
1562 }
1563 /*************************************************************************
1564  * WCMD_LoadMessage
1565  *    Load a string from the resource file, handling any error
1566  *    Returns string retrieved from resource file
1567  */
1568 WCHAR *WCMD_LoadMessage(UINT id) {
1569     static WCHAR msg[2048];
1570     static const WCHAR failedMsg[]  = {'F','a','i','l','e','d','!','\0'};
1571
1572     if (!LoadString(GetModuleHandle(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1573        WINE_FIXME("LoadString failed with %d\n", GetLastError());
1574        strcpyW(msg, failedMsg);
1575     }
1576     return msg;
1577 }
1578
1579 /***************************************************************************
1580  * WCMD_DumpCommands
1581  *
1582  *      Dumps out the parsed command line to ensure syntax is correct
1583  */
1584 static void WCMD_DumpCommands(CMD_LIST *commands) {
1585     CMD_LIST *thisCmd = commands;
1586
1587     WINE_TRACE("Parsed line:\n");
1588     while (thisCmd != NULL) {
1589       WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1590                thisCmd,
1591                thisCmd->prevDelim,
1592                thisCmd->bracketDepth,
1593                thisCmd->nextcommand,
1594                wine_dbgstr_w(thisCmd->command),
1595                wine_dbgstr_w(thisCmd->redirects));
1596       thisCmd = thisCmd->nextcommand;
1597     }
1598 }
1599
1600 /***************************************************************************
1601  * WCMD_addCommand
1602  *
1603  *   Adds a command to the current command list
1604  */
1605 static void WCMD_addCommand(WCHAR *command, int *commandLen,
1606                      WCHAR *redirs,  int *redirLen,
1607                      WCHAR **copyTo, int **copyToLen,
1608                      CMD_DELIMITERS prevDelim, int curDepth,
1609                      CMD_LIST **lastEntry, CMD_LIST **output) {
1610
1611     CMD_LIST *thisEntry = NULL;
1612
1613     /* Allocate storage for command */
1614     thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
1615
1616     /* Copy in the command */
1617     if (command) {
1618         thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
1619                                       (*commandLen+1) * sizeof(WCHAR));
1620         memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1621         thisEntry->command[*commandLen] = 0x00;
1622
1623         /* Copy in the redirects */
1624         thisEntry->redirects = HeapAlloc(GetProcessHeap(), 0,
1625                                          (*redirLen+1) * sizeof(WCHAR));
1626         memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1627         thisEntry->redirects[*redirLen] = 0x00;
1628         thisEntry->pipeFile[0] = 0x00;
1629
1630         /* Reset the lengths */
1631         *commandLen   = 0;
1632         *redirLen     = 0;
1633         *copyToLen    = commandLen;
1634         *copyTo       = command;
1635
1636     } else {
1637         thisEntry->command = NULL;
1638     }
1639
1640     /* Fill in other fields */
1641     thisEntry->nextcommand = NULL;
1642     thisEntry->prevDelim = prevDelim;
1643     thisEntry->bracketDepth = curDepth;
1644     if (*lastEntry) {
1645         (*lastEntry)->nextcommand = thisEntry;
1646     } else {
1647         *output = thisEntry;
1648     }
1649     *lastEntry = thisEntry;
1650 }
1651
1652 /***************************************************************************
1653  * WCMD_ReadAndParseLine
1654  *
1655  *   Either uses supplied input or
1656  *     Reads a file from the handle, and then...
1657  *   Parse the text buffer, spliting into separate commands
1658  *     - unquoted && strings split 2 commands but the 2nd is flagged as
1659  *            following an &&
1660  *     - ( as the first character just ups the bracket depth
1661  *     - unquoted ) when bracket depth > 0 terminates a bracket and
1662  *            adds a CMD_LIST structure with null command
1663  *     - Anything else gets put into the command string (including
1664  *            redirects)
1665  */
1666 WCHAR *WCMD_ReadAndParseLine(WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom) {
1667
1668     WCHAR    *curPos;
1669     int       inQuotes = 0;
1670     WCHAR     curString[MAXSTRING];
1671     int       curStringLen = 0;
1672     WCHAR     curRedirs[MAXSTRING];
1673     int       curRedirsLen = 0;
1674     WCHAR    *curCopyTo;
1675     int      *curLen;
1676     int       curDepth = 0;
1677     CMD_LIST *lastEntry = NULL;
1678     CMD_DELIMITERS prevDelim = CMD_NONE;
1679     static WCHAR    *extraSpace = NULL;  /* Deliberately never freed */
1680     const WCHAR remCmd[] = {'r','e','m',' ','\0'};
1681     const WCHAR forCmd[] = {'f','o','r',' ','\0'};
1682     const WCHAR ifCmd[]  = {'i','f',' ','\0'};
1683     const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1684     BOOL      inRem = FALSE;
1685     BOOL      inFor = FALSE;
1686     BOOL      inIn  = FALSE;
1687     BOOL      inIf  = FALSE;
1688     BOOL      inElse= FALSE;
1689     BOOL      onlyWhiteSpace = FALSE;
1690     BOOL      lastWasWhiteSpace = FALSE;
1691     BOOL      lastWasDo   = FALSE;
1692     BOOL      lastWasIn   = FALSE;
1693     BOOL      lastWasElse = FALSE;
1694     BOOL      lastWasRedirect = TRUE;
1695
1696     /* Allocate working space for a command read from keyboard, file etc */
1697     if (!extraSpace)
1698       extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
1699     if (!extraSpace)
1700     {
1701         WINE_ERR("Could not allocate memory for extraSpace\n");
1702         return NULL;
1703     }
1704
1705     /* If initial command read in, use that, otherwise get input from handle */
1706     if (optionalcmd != NULL) {
1707         strcpyW(extraSpace, optionalcmd);
1708     } else if (readFrom == INVALID_HANDLE_VALUE) {
1709         WINE_FIXME("No command nor handle supplied\n");
1710     } else {
1711         if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) return NULL;
1712     }
1713     curPos = extraSpace;
1714
1715     /* Handle truncated input - issue warning */
1716     if (strlenW(extraSpace) == MAXSTRING -1) {
1717         WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1718         WCMD_output_asis(extraSpace);
1719         WCMD_output_asis(newline);
1720     }
1721
1722     /* Replace env vars if in a batch context */
1723     if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
1724
1725     /* Start with an empty string, copying to the command string */
1726     curStringLen = 0;
1727     curRedirsLen = 0;
1728     curCopyTo    = curString;
1729     curLen       = &curStringLen;
1730     lastWasRedirect = FALSE;  /* Required for eg spaces between > and filename */
1731
1732     /* Parse every character on the line being processed */
1733     while (*curPos != 0x00) {
1734
1735       WCHAR thisChar;
1736
1737       /* Debugging AID:
1738       WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1739                  lastWasWhiteSpace, onlyWhiteSpace);
1740       */
1741
1742       /* Certain commands need special handling */
1743       if (curStringLen == 0 && curCopyTo == curString) {
1744         const WCHAR forDO[]  = {'d','o',' ','\0'};
1745
1746         /* If command starts with 'rem', ignore any &&, ( etc */
1747         if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1748           curPos, 4, remCmd, -1) == 2) {
1749           inRem = TRUE;
1750
1751         /* If command starts with 'for', handle ('s mid line after IN or DO */
1752         } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1753           curPos, 4, forCmd, -1) == 2) {
1754           inFor = TRUE;
1755
1756         /* If command starts with 'if' or 'else', handle ('s mid line. We should ensure this
1757            is only true in the command portion of the IF statement, but this
1758            should suffice for now
1759             FIXME: Silly syntax like "if 1(==1( (
1760                                         echo they equal
1761                                       )" will be parsed wrong */
1762         } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1763           curPos, 3, ifCmd, -1) == 2) {
1764           inIf = TRUE;
1765
1766         } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1767           curPos, 5, ifElse, -1) == 2) {
1768           inElse = TRUE;
1769           lastWasElse = TRUE;
1770           onlyWhiteSpace = TRUE;
1771           memcpy(&curCopyTo[*curLen], curPos, 5*sizeof(WCHAR));
1772           (*curLen)+=5;
1773           curPos+=5;
1774           continue;
1775
1776         /* In a for loop, the DO command will follow a close bracket followed by
1777            whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1778            is then 0, and all whitespace is skipped                                */
1779         } else if (inFor &&
1780                    (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1781                     curPos, 3, forDO, -1) == 2)) {
1782           WINE_TRACE("Found DO\n");
1783           lastWasDo = TRUE;
1784           onlyWhiteSpace = TRUE;
1785           memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
1786           (*curLen)+=3;
1787           curPos+=3;
1788           continue;
1789         }
1790       } else if (curCopyTo == curString) {
1791
1792         /* Special handling for the 'FOR' command */
1793         if (inFor && lastWasWhiteSpace) {
1794           const WCHAR forIN[] = {'i','n',' ','\0'};
1795
1796           WINE_TRACE("Found 'FOR', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
1797
1798           if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1799               curPos, 3, forIN, -1) == 2) {
1800             WINE_TRACE("Found IN\n");
1801             lastWasIn = TRUE;
1802             onlyWhiteSpace = TRUE;
1803             memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
1804             (*curLen)+=3;
1805             curPos+=3;
1806             continue;
1807           }
1808         }
1809       }
1810
1811       /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
1812          so just use the default processing ie skip character specific
1813          matching below                                                    */
1814       if (!inRem) thisChar = *curPos;
1815       else        thisChar = 'X';  /* Character with no special processing */
1816
1817       lastWasWhiteSpace = FALSE; /* Will be reset below */
1818
1819       switch (thisChar) {
1820
1821       case '=': /* drop through - ignore token delimiters at the start of a command */
1822       case ',': /* drop through - ignore token delimiters at the start of a command */
1823       case '\t':/* drop through - ignore token delimiters at the start of a command */
1824       case ' ':
1825                 /* If a redirect in place, it ends here */
1826                 if (!inQuotes && !lastWasRedirect) {
1827
1828                   /* If finishing off a redirect, add a whitespace delimiter */
1829                   if (curCopyTo == curRedirs) {
1830                       curCopyTo[(*curLen)++] = ' ';
1831                   }
1832                   curCopyTo = curString;
1833                   curLen = &curStringLen;
1834                 }
1835                 if (*curLen > 0) {
1836                   curCopyTo[(*curLen)++] = *curPos;
1837                 }
1838
1839                 /* Remember just processed whitespace */
1840                 lastWasWhiteSpace = TRUE;
1841
1842                 break;
1843
1844       case '>': /* drop through - handle redirect chars the same */
1845       case '<':
1846                 /* Make a redirect start here */
1847                 if (!inQuotes) {
1848                   curCopyTo = curRedirs;
1849                   curLen = &curRedirsLen;
1850                   lastWasRedirect = TRUE;
1851                 }
1852
1853                 /* See if 1>, 2> etc, in which case we have some patching up
1854                    to do                                                     */
1855                 if (curPos != extraSpace &&
1856                     *(curPos-1)>='1' && *(curPos-1)<='9') {
1857
1858                     curStringLen--;
1859                     curString[curStringLen] = 0x00;
1860                     curCopyTo[(*curLen)++] = *(curPos-1);
1861                 }
1862
1863                 curCopyTo[(*curLen)++] = *curPos;
1864
1865                 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
1866                     do not process that ampersand as an AND operator */
1867                 if (thisChar == '>' && *(curPos+1) == '&') {
1868                     curCopyTo[(*curLen)++] = *(curPos+1);
1869                     curPos++;
1870                 }
1871                 break;
1872
1873       case '|': /* Pipe character only if not || */
1874                 if (!inQuotes) {
1875                   lastWasRedirect = FALSE;
1876
1877                   /* Add an entry to the command list */
1878                   if (curStringLen > 0) {
1879
1880                     /* Add the current command */
1881                     WCMD_addCommand(curString, &curStringLen,
1882                           curRedirs, &curRedirsLen,
1883                           &curCopyTo, &curLen,
1884                           prevDelim, curDepth,
1885                           &lastEntry, output);
1886
1887                   }
1888
1889                   if (*(curPos+1) == '|') {
1890                     curPos++; /* Skip other | */
1891                     prevDelim = CMD_ONFAILURE;
1892                   } else {
1893                     prevDelim = CMD_PIPE;
1894                   }
1895                 } else {
1896                   curCopyTo[(*curLen)++] = *curPos;
1897                 }
1898                 break;
1899
1900       case '"': if (inQuotes && *(curPos+1) == ' ') {
1901                     inQuotes--; /* An end quote must be proceeded by a space */
1902                 } else {
1903                     inQuotes++; /* Quotes within quotes are fun! */
1904                 }
1905                 curCopyTo[(*curLen)++] = *curPos;
1906                 lastWasRedirect = FALSE;
1907                 break;
1908
1909       case '(': /* If a '(' is the first non whitespace in a command portion
1910                    ie start of line or just after &&, then we read until an
1911                    unquoted ) is found                                       */
1912                 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
1913                            ", for(%d, In:%d, Do:%d)"
1914                            ", if(%d, else:%d, lwe:%d)\n",
1915                            *curLen, inQuotes,
1916                            onlyWhiteSpace,
1917                            inFor, lastWasIn, lastWasDo,
1918                            inIf, inElse, lastWasElse);
1919                 lastWasRedirect = FALSE;
1920
1921                 /* Ignore open brackets inside the for set */
1922                 if (*curLen == 0 && !inIn) {
1923                   curDepth++;
1924
1925                 /* If in quotes, ignore brackets */
1926                 } else if (inQuotes) {
1927                   curCopyTo[(*curLen)++] = *curPos;
1928
1929                 /* In a FOR loop, an unquoted '(' may occur straight after
1930                       IN or DO
1931                    In an IF statement just handle it regardless as we don't
1932                       parse the operands
1933                    In an ELSE statement, only allow it straight away after
1934                       the ELSE and whitespace
1935                  */
1936                 } else if (inIf ||
1937                            (inElse && lastWasElse && onlyWhiteSpace) ||
1938                            (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
1939
1940                    /* If entering into an 'IN', set inIn */
1941                   if (inFor && lastWasIn && onlyWhiteSpace) {
1942                     WINE_TRACE("Inside an IN\n");
1943                     inIn = TRUE;
1944                   }
1945
1946                   /* Add the current command */
1947                   WCMD_addCommand(curString, &curStringLen,
1948                                   curRedirs, &curRedirsLen,
1949                                   &curCopyTo, &curLen,
1950                                   prevDelim, curDepth,
1951                                   &lastEntry, output);
1952
1953                   curDepth++;
1954                 } else {
1955                   curCopyTo[(*curLen)++] = *curPos;
1956                 }
1957                 break;
1958
1959       case '&': if (!inQuotes) {
1960                   lastWasRedirect = FALSE;
1961
1962                   /* Add an entry to the command list */
1963                   if (curStringLen > 0) {
1964
1965                     /* Add the current command */
1966                     WCMD_addCommand(curString, &curStringLen,
1967                           curRedirs, &curRedirsLen,
1968                           &curCopyTo, &curLen,
1969                           prevDelim, curDepth,
1970                           &lastEntry, output);
1971
1972                   }
1973
1974                   if (*(curPos+1) == '&') {
1975                     curPos++; /* Skip other & */
1976                     prevDelim = CMD_ONSUCCESS;
1977                   } else {
1978                     prevDelim = CMD_NONE;
1979                   }
1980                 } else {
1981                   curCopyTo[(*curLen)++] = *curPos;
1982                 }
1983                 break;
1984
1985       case ')': if (!inQuotes && curDepth > 0) {
1986                   lastWasRedirect = FALSE;
1987
1988                   /* Add the current command if there is one */
1989                   if (curStringLen) {
1990
1991                       /* Add the current command */
1992                       WCMD_addCommand(curString, &curStringLen,
1993                             curRedirs, &curRedirsLen,
1994                             &curCopyTo, &curLen,
1995                             prevDelim, curDepth,
1996                             &lastEntry, output);
1997                   }
1998
1999                   /* Add an empty entry to the command list */
2000                   prevDelim = CMD_NONE;
2001                   WCMD_addCommand(NULL, &curStringLen,
2002                         curRedirs, &curRedirsLen,
2003                         &curCopyTo, &curLen,
2004                         prevDelim, curDepth,
2005                         &lastEntry, output);
2006                   curDepth--;
2007
2008                   /* Leave inIn if necessary */
2009                   if (inIn) inIn =  FALSE;
2010                 } else {
2011                   curCopyTo[(*curLen)++] = *curPos;
2012                 }
2013                 break;
2014       default:
2015                 lastWasRedirect = FALSE;
2016                 curCopyTo[(*curLen)++] = *curPos;
2017       }
2018
2019       curPos++;
2020
2021       /* At various times we need to know if we have only skipped whitespace,
2022          so reset this variable and then it will remain true until a non
2023          whitespace is found                                               */
2024       if ((thisChar != ' ') && (thisChar != '\n')) onlyWhiteSpace = FALSE;
2025
2026       /* Flag end of interest in FOR DO and IN parms once something has been processed */
2027       if (!lastWasWhiteSpace) {
2028         lastWasIn = lastWasDo = FALSE;
2029       }
2030
2031       /* If we have reached the end, add this command into the list */
2032       if (*curPos == 0x00 && *curLen > 0) {
2033
2034           /* Add an entry to the command list */
2035           WCMD_addCommand(curString, &curStringLen,
2036                 curRedirs, &curRedirsLen,
2037                 &curCopyTo, &curLen,
2038                 prevDelim, curDepth,
2039                 &lastEntry, output);
2040       }
2041
2042       /* If we have reached the end of the string, see if bracketing outstanding */
2043       if (*curPos == 0x00 && curDepth > 0 && readFrom != INVALID_HANDLE_VALUE) {
2044         inRem = FALSE;
2045         prevDelim = CMD_NONE;
2046         inQuotes = 0;
2047         memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2048
2049         /* Read more, skipping any blank lines */
2050         while (*extraSpace == 0x00) {
2051           if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2052           if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) break;
2053         }
2054         curPos = extraSpace;
2055         if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2056       }
2057     }
2058
2059     /* Dump out the parsed output */
2060     WCMD_DumpCommands(*output);
2061
2062     return extraSpace;
2063 }
2064
2065 /***************************************************************************
2066  * WCMD_process_commands
2067  *
2068  * Process all the commands read in so far
2069  */
2070 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2071                                 WCHAR *var, WCHAR *val) {
2072
2073     int bdepth = -1;
2074
2075     if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2076
2077     /* Loop through the commands, processing them one by one */
2078     while (thisCmd) {
2079
2080       CMD_LIST *origCmd = thisCmd;
2081
2082       /* If processing one bracket only, and we find the end bracket
2083          entry (or less), return                                    */
2084       if (oneBracket && !thisCmd->command &&
2085           bdepth <= thisCmd->bracketDepth) {
2086         WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2087                    thisCmd, thisCmd->nextcommand);
2088         return thisCmd->nextcommand;
2089       }
2090
2091       /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2092          about them and it will be handled in there)
2093          Also, skip over any batch labels (eg. :fred)          */
2094       if (thisCmd->command && thisCmd->command[0] != ':') {
2095         WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2096         WCMD_execute (thisCmd->command, thisCmd->redirects, var, val, &thisCmd);
2097       }
2098
2099       /* Step on unless the command itself already stepped on */
2100       if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2101     }
2102     return NULL;
2103 }
2104
2105 /***************************************************************************
2106  * WCMD_free_commands
2107  *
2108  * Frees the storage held for a parsed command line
2109  * - This is not done in the process_commands, as eventually the current
2110  *   pointer will be modified within the commands, and hence a single free
2111  *   routine is simpler
2112  */
2113 void WCMD_free_commands(CMD_LIST *cmds) {
2114
2115     /* Loop through the commands, freeing them one by one */
2116     while (cmds) {
2117       CMD_LIST *thisCmd = cmds;
2118       cmds = cmds->nextcommand;
2119       HeapFree(GetProcessHeap(), 0, thisCmd->command);
2120       HeapFree(GetProcessHeap(), 0, thisCmd);
2121     }
2122 }
2123
2124
2125 /*****************************************************************************
2126  * Main entry point. This is a console application so we have a main() not a
2127  * winmain().
2128  */
2129
2130 int wmain (int argc, WCHAR *argvW[])
2131 {
2132   int     args;
2133   WCHAR  *cmd   = NULL;
2134   WCHAR string[1024];
2135   WCHAR envvar[4];
2136   HANDLE h;
2137   int opt_q;
2138   int opt_t = 0;
2139   static const WCHAR autoexec[] = {'\\','a','u','t','o','e','x','e','c','.',
2140                                    'b','a','t','\0'};
2141   char ansiVersion[100];
2142   CMD_LIST *toExecute = NULL;         /* Commands left to be executed */
2143
2144   srand(time(NULL));
2145
2146   /* Pre initialize some messages */
2147   strcpy(ansiVersion, PACKAGE_VERSION);
2148   MultiByteToWideChar(CP_ACP, 0, ansiVersion, -1, string, 1024);
2149   wsprintf(version_string, WCMD_LoadMessage(WCMD_VERSION), string);
2150   strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
2151
2152   args  = argc;
2153   opt_c=opt_k=opt_q=opt_s=0;
2154   while (args > 0)
2155   {
2156       WCHAR c;
2157       WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW));
2158       if ((*argvW)[0]!='/' || (*argvW)[1]=='\0') {
2159           argvW++;
2160           args--;
2161           continue;
2162       }
2163
2164       c=(*argvW)[1];
2165       if (tolowerW(c)=='c') {
2166           opt_c=1;
2167       } else if (tolowerW(c)=='q') {
2168           opt_q=1;
2169       } else if (tolowerW(c)=='k') {
2170           opt_k=1;
2171       } else if (tolowerW(c)=='s') {
2172           opt_s=1;
2173       } else if (tolowerW(c)=='a') {
2174           unicodePipes=FALSE;
2175       } else if (tolowerW(c)=='u') {
2176           unicodePipes=TRUE;
2177       } else if (tolowerW(c)=='t' && (*argvW)[2]==':') {
2178           opt_t=strtoulW(&(*argvW)[3], NULL, 16);
2179       } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
2180           /* Ignored for compatibility with Windows */
2181       }
2182
2183       if ((*argvW)[2]==0) {
2184           argvW++;
2185           args--;
2186       }
2187       else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
2188       {
2189           *argvW+=2;
2190       }
2191
2192       if (opt_c || opt_k) /* break out of parsing immediately after c or k */
2193           break;
2194   }
2195
2196   if (opt_q) {
2197     const WCHAR eoff[] = {'O','F','F','\0'};
2198     WCMD_echo(eoff);
2199   }
2200
2201   if (opt_c || opt_k) {
2202       int     len,qcount;
2203       WCHAR** arg;
2204       int     argsLeft;
2205       WCHAR*  p;
2206
2207       /* opt_s left unflagged if the command starts with and contains exactly
2208        * one quoted string (exactly two quote characters). The quoted string
2209        * must be an executable name that has whitespace and must not have the
2210        * following characters: &<>()@^| */
2211
2212       /* Build the command to execute */
2213       len = 0;
2214       qcount = 0;
2215       argsLeft = args;
2216       for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2217       {
2218           int has_space,bcount;
2219           WCHAR* a;
2220
2221           has_space=0;
2222           bcount=0;
2223           a=*arg;
2224           if( !*a ) has_space=1;
2225           while (*a!='\0') {
2226               if (*a=='\\') {
2227                   bcount++;
2228               } else {
2229                   if (*a==' ' || *a=='\t') {
2230                       has_space=1;
2231                   } else if (*a=='"') {
2232                       /* doubling of '\' preceding a '"',
2233                        * plus escaping of said '"'
2234                        */
2235                       len+=2*bcount+1;
2236                       qcount++;
2237                   }
2238                   bcount=0;
2239               }
2240               a++;
2241           }
2242           len+=(a-*arg) + 1; /* for the separating space */
2243           if (has_space)
2244           {
2245               len+=2; /* for the quotes */
2246               qcount+=2;
2247           }
2248       }
2249
2250       if (qcount!=2)
2251           opt_s=1;
2252
2253       /* check argvW[0] for a space and invalid characters */
2254       if (!opt_s) {
2255           opt_s=1;
2256           p=*argvW;
2257           while (*p!='\0') {
2258               if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
2259                   || *p=='@' || *p=='^' || *p=='|') {
2260                   opt_s=1;
2261                   break;
2262               }
2263               if (*p==' ')
2264                   opt_s=0;
2265               p++;
2266           }
2267       }
2268
2269       cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
2270       if (!cmd)
2271           exit(1);
2272
2273       p = cmd;
2274       argsLeft = args;
2275       for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2276       {
2277           int has_space,has_quote;
2278           WCHAR* a;
2279
2280           /* Check for quotes and spaces in this argument */
2281           has_space=has_quote=0;
2282           a=*arg;
2283           if( !*a ) has_space=1;
2284           while (*a!='\0') {
2285               if (*a==' ' || *a=='\t') {
2286                   has_space=1;
2287                   if (has_quote)
2288                       break;
2289               } else if (*a=='"') {
2290                   has_quote=1;
2291                   if (has_space)
2292                       break;
2293               }
2294               a++;
2295           }
2296
2297           /* Now transfer it to the command line */
2298           if (has_space)
2299               *p++='"';
2300           if (has_quote) {
2301               int bcount;
2302               WCHAR* a;
2303
2304               bcount=0;
2305               a=*arg;
2306               while (*a!='\0') {
2307                   if (*a=='\\') {
2308                       *p++=*a;
2309                       bcount++;
2310                   } else {
2311                       if (*a=='"') {
2312                           int i;
2313
2314                           /* Double all the '\\' preceding this '"', plus one */
2315                           for (i=0;i<=bcount;i++)
2316                               *p++='\\';
2317                           *p++='"';
2318                       } else {
2319                           *p++=*a;
2320                       }
2321                       bcount=0;
2322                   }
2323                   a++;
2324               }
2325           } else {
2326               strcpyW(p,*arg);
2327               p+=strlenW(*arg);
2328           }
2329           if (has_space)
2330               *p++='"';
2331           *p++=' ';
2332       }
2333       if (p > cmd)
2334           p--;  /* remove last space */
2335       *p = '\0';
2336
2337       WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
2338
2339       /* strip first and last quote characters if opt_s; check for invalid
2340        * executable is done later */
2341       if (opt_s && *cmd=='\"')
2342           WCMD_opt_s_strip_quotes(cmd);
2343   }
2344
2345   if (opt_c) {
2346       /* If we do a "wcmd /c command", we don't want to allocate a new
2347        * console since the command returns immediately. Rather, we use
2348        * the currently allocated input and output handles. This allows
2349        * us to pipe to and read from the command interpreter.
2350        */
2351
2352       /* Parse the command string, without reading any more input */
2353       WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2354       WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2355       WCMD_free_commands(toExecute);
2356       toExecute = NULL;
2357
2358       HeapFree(GetProcessHeap(), 0, cmd);
2359       return errorlevel;
2360   }
2361
2362   SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
2363                  ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
2364   SetConsoleTitle(WCMD_LoadMessage(WCMD_CONSTITLE));
2365
2366   /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2367   if (opt_t) {
2368       if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
2369           defaultColor = opt_t & 0xFF;
2370           param1[0] = 0x00;
2371           WCMD_color();
2372       }
2373   } else {
2374       /* Check HKCU\Software\Microsoft\Command Processor
2375          Then  HKLM\Software\Microsoft\Command Processor
2376            for defaultcolour value
2377            Note  Can be supplied as DWORD or REG_SZ
2378            Note2 When supplied as REG_SZ it's in decimal!!! */
2379       HKEY key;
2380       DWORD type;
2381       DWORD value=0, size=4;
2382       static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
2383                                       'M','i','c','r','o','s','o','f','t','\\',
2384                                       'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
2385       static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
2386
2387       if (RegOpenKeyEx(HKEY_CURRENT_USER, regKeyW,
2388                        0, KEY_READ, &key) == ERROR_SUCCESS) {
2389           WCHAR  strvalue[4];
2390
2391           /* See if DWORD or REG_SZ */
2392           if (RegQueryValueEx(key, dfltColorW, NULL, &type,
2393                      NULL, NULL) == ERROR_SUCCESS) {
2394               if (type == REG_DWORD) {
2395                   size = sizeof(DWORD);
2396                   RegQueryValueEx(key, dfltColorW, NULL, NULL,
2397                                   (LPBYTE)&value, &size);
2398               } else if (type == REG_SZ) {
2399                   size = sizeof(strvalue)/sizeof(WCHAR);
2400                   RegQueryValueEx(key, dfltColorW, NULL, NULL,
2401                                   (LPBYTE)strvalue, &size);
2402                   value = strtoulW(strvalue, NULL, 10);
2403               }
2404           }
2405           RegCloseKey(key);
2406       }
2407
2408       if (value == 0 && RegOpenKeyEx(HKEY_LOCAL_MACHINE, regKeyW,
2409                        0, KEY_READ, &key) == ERROR_SUCCESS) {
2410           WCHAR  strvalue[4];
2411
2412           /* See if DWORD or REG_SZ */
2413           if (RegQueryValueEx(key, dfltColorW, NULL, &type,
2414                      NULL, NULL) == ERROR_SUCCESS) {
2415               if (type == REG_DWORD) {
2416                   size = sizeof(DWORD);
2417                   RegQueryValueEx(key, dfltColorW, NULL, NULL,
2418                                   (LPBYTE)&value, &size);
2419               } else if (type == REG_SZ) {
2420                   size = sizeof(strvalue)/sizeof(WCHAR);
2421                   RegQueryValueEx(key, dfltColorW, NULL, NULL,
2422                                   (LPBYTE)strvalue, &size);
2423                   value = strtoulW(strvalue, NULL, 10);
2424               }
2425           }
2426           RegCloseKey(key);
2427       }
2428
2429       /* If one found, set the screen to that colour */
2430       if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
2431           defaultColor = value & 0xFF;
2432           param1[0] = 0x00;
2433           WCMD_color();
2434       }
2435
2436   }
2437
2438   /* Save cwd into appropriate env var */
2439   GetCurrentDirectory(1024, string);
2440   if (IsCharAlpha(string[0]) && string[1] == ':') {
2441     static const WCHAR fmt[] = {'=','%','c',':','\0'};
2442     wsprintf(envvar, fmt, string[0]);
2443     SetEnvironmentVariable(envvar, string);
2444     WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
2445   }
2446
2447   if (opt_k) {
2448       /* Parse the command string, without reading any more input */
2449       WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2450       WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2451       WCMD_free_commands(toExecute);
2452       toExecute = NULL;
2453       HeapFree(GetProcessHeap(), 0, cmd);
2454   }
2455
2456 /*
2457  *      If there is an AUTOEXEC.BAT file, try to execute it.
2458  */
2459
2460   GetFullPathName (autoexec, sizeof(string)/sizeof(WCHAR), string, NULL);
2461   h = CreateFile (string, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2462   if (h != INVALID_HANDLE_VALUE) {
2463     CloseHandle (h);
2464 #if 0
2465     WCMD_batch (autoexec, autoexec, 0, NULL, INVALID_HANDLE_VALUE);
2466 #endif
2467   }
2468
2469 /*
2470  *      Loop forever getting commands and executing them.
2471  */
2472
2473   WCMD_version ();
2474   while (TRUE) {
2475
2476     /* Read until EOF (which for std input is never, but if redirect
2477        in place, may occur                                          */
2478     WCMD_show_prompt ();
2479     if (WCMD_ReadAndParseLine(NULL, &toExecute,
2480                               GetStdHandle(STD_INPUT_HANDLE)) == NULL)
2481       break;
2482     WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2483     WCMD_free_commands(toExecute);
2484     toExecute = NULL;
2485   }
2486   return 0;
2487 }