cmd.exe: Wildcards in directory names for CD.
[wine] / programs / cmd / wcmdmain.c
1 /*
2  * CMD - Wine-compatible command line interface.
3  *
4  * Copyright (C) 1999 - 2001 D A Pickles
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 /*
22  * FIXME:
23  * - Cannot handle parameters in quotes
24  * - Lots of functionality missing from builtins
25  */
26
27 #include "config.h"
28 #include "wcmd.h"
29 #include "wine/debug.h"
30
31 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
32
33 const char * const inbuilt[] = {"ATTRIB", "CALL", "CD", "CHDIR", "CLS", "COPY", "CTTY",
34                 "DATE", "DEL", "DIR", "ECHO", "ERASE", "FOR", "GOTO",
35                 "HELP", "IF", "LABEL", "MD", "MKDIR", "MOVE", "PATH", "PAUSE",
36                 "PROMPT", "REM", "REN", "RENAME", "RD", "RMDIR", "SET", "SHIFT",
37                 "TIME", "TITLE", "TYPE", "VERIFY", "VER", "VOL", 
38                 "ENDLOCAL", "SETLOCAL", "PUSHD", "POPD", "ASSOC", "COLOR", "EXIT" };
39
40 HINSTANCE hinst;
41 DWORD errorlevel;
42 int echo_mode = 1, verify_mode = 0, defaultColor = 7;
43 static int opt_c, opt_k, opt_s;
44 const char nyi[] = "Not Yet Implemented\n\n";
45 const char newline[] = "\n";
46 const char version_string[] = "CMD Version " PACKAGE_VERSION "\n\n";
47 const char anykey[] = "Press Return key to continue: ";
48 char quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
49 BATCH_CONTEXT *context = NULL;
50 extern struct env_stack *pushd_directories;
51
52 static char *WCMD_expand_envvar(char *start);
53
54 /*****************************************************************************
55  * Main entry point. This is a console application so we have a main() not a
56  * winmain().
57  */
58
59 int main (int argc, char *argv[])
60 {
61   char string[1024];
62   char* cmd=NULL;
63   DWORD count;
64   HANDLE h;
65   int opt_q;
66   int opt_t = 0;
67
68   opt_c=opt_k=opt_q=opt_s=0;
69   while (*argv!=NULL)
70   {
71       char c;
72       if ((*argv)[0]!='/' || (*argv)[1]=='\0') {
73           argv++;
74           continue;
75       }
76
77       c=(*argv)[1];
78       if (tolower(c)=='c') {
79           opt_c=1;
80       } else if (tolower(c)=='q') {
81           opt_q=1;
82       } else if (tolower(c)=='k') {
83           opt_k=1;
84       } else if (tolower(c)=='s') {
85           opt_s=1;
86       } else if (tolower(c)=='t' && (*argv)[2]==':') {
87           opt_t=strtoul(&(*argv)[3], NULL, 16);
88       } else if (tolower(c)=='x' || tolower(c)=='y') {
89           /* Ignored for compatibility with Windows */
90       }
91
92       if ((*argv)[2]==0)
93           argv++;
94       else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
95           *argv+=2;
96
97       if (opt_c || opt_k) /* break out of parsing immediately after c or k */
98           break;
99   }
100
101   if (opt_q) {
102     WCMD_echo("OFF");
103   }
104
105   if (opt_c || opt_k) {
106       int len,qcount;
107       char** arg;
108       char* p;
109
110       /* opt_s left unflagged if the command starts with and contains exactly
111        * one quoted string (exactly two quote characters). The quoted string
112        * must be an executable name that has whitespace and must not have the
113        * following characters: &<>()@^| */
114
115       /* Build the command to execute */
116       len = 0;
117       qcount = 0;
118       for (arg = argv; *arg; arg++)
119       {
120           int has_space,bcount;
121           char* a;
122
123           has_space=0;
124           bcount=0;
125           a=*arg;
126           if( !*a ) has_space=1;
127           while (*a!='\0') {
128               if (*a=='\\') {
129                   bcount++;
130               } else {
131                   if (*a==' ' || *a=='\t') {
132                       has_space=1;
133                   } else if (*a=='"') {
134                       /* doubling of '\' preceding a '"',
135                        * plus escaping of said '"'
136                        */
137                       len+=2*bcount+1;
138                       qcount++;
139                   }
140                   bcount=0;
141               }
142               a++;
143           }
144           len+=(a-*arg)+1 /* for the separating space */;
145           if (has_space)
146           {
147               len+=2; /* for the quotes */
148               qcount+=2;
149           }
150       }
151
152       if (qcount!=2)
153           opt_s=1;
154
155       /* check argv[0] for a space and invalid characters */
156       if (!opt_s) {
157           opt_s=1;
158           p=*argv;
159           while (*p!='\0') {
160               if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
161                   || *p=='@' || *p=='^' || *p=='|') {
162                   opt_s=1;
163                   break;
164               }
165               if (*p==' ')
166                   opt_s=0;
167               p++;
168           }
169       }
170
171       cmd = HeapAlloc(GetProcessHeap(), 0, len);
172       if (!cmd)
173           exit(1);
174
175       p = cmd;
176       for (arg = argv; *arg; arg++)
177       {
178           int has_space,has_quote;
179           char* a;
180
181           /* Check for quotes and spaces in this argument */
182           has_space=has_quote=0;
183           a=*arg;
184           if( !*a ) has_space=1;
185           while (*a!='\0') {
186               if (*a==' ' || *a=='\t') {
187                   has_space=1;
188                   if (has_quote)
189                       break;
190               } else if (*a=='"') {
191                   has_quote=1;
192                   if (has_space)
193                       break;
194               }
195               a++;
196           }
197
198           /* Now transfer it to the command line */
199           if (has_space)
200               *p++='"';
201           if (has_quote) {
202               int bcount;
203               char* a;
204
205               bcount=0;
206               a=*arg;
207               while (*a!='\0') {
208                   if (*a=='\\') {
209                       *p++=*a;
210                       bcount++;
211                   } else {
212                       if (*a=='"') {
213                           int i;
214
215                           /* Double all the '\\' preceding this '"', plus one */
216                           for (i=0;i<=bcount;i++)
217                               *p++='\\';
218                           *p++='"';
219                       } else {
220                           *p++=*a;
221                       }
222                       bcount=0;
223                   }
224                   a++;
225               }
226           } else {
227               strcpy(p,*arg);
228               p+=strlen(*arg);
229           }
230           if (has_space)
231               *p++='"';
232           *p++=' ';
233       }
234       if (p > cmd)
235           p--;  /* remove last space */
236       *p = '\0';
237
238       /* strip first and last quote characters if opt_s; check for invalid
239        * executable is done later */
240       if (opt_s && *cmd=='\"')
241           WCMD_opt_s_strip_quotes(cmd);
242   }
243
244   if (opt_c) {
245       /* If we do a "wcmd /c command", we don't want to allocate a new
246        * console since the command returns immediately. Rather, we use
247        * the currently allocated input and output handles. This allows
248        * us to pipe to and read from the command interpreter.
249        */
250       if (strchr(cmd,'|') != NULL)
251           WCMD_pipe(cmd);
252       else
253           WCMD_process_command(cmd);
254       HeapFree(GetProcessHeap(), 0, cmd);
255       return errorlevel;
256   }
257
258   SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
259                  ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
260   SetConsoleTitle("Wine Command Prompt");
261
262   /* Note: cmd.exe /c dir does not get a new color, /k dir does */
263   if (opt_t) {
264       if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
265           defaultColor = opt_t & 0xFF;
266           param1[0] = 0x00;
267           WCMD_color();
268       }
269   } else {
270       /* Check HKCU\Software\Microsoft\Command Processor
271          Then  HKLM\Software\Microsoft\Command Processor
272            for defaultcolour value
273            Note  Can be supplied as DWORD or REG_SZ
274            Note2 When supplied as REG_SZ it's in decimal!!! */
275       HKEY key;
276       DWORD type;
277       DWORD value=0, size=4;
278
279       if (RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Command Processor",
280                        0, KEY_READ, &key) == ERROR_SUCCESS) {
281           char  strvalue[4];
282
283           /* See if DWORD or REG_SZ */
284           if (RegQueryValueEx(key, "DefaultColor", NULL, &type,
285                      NULL, NULL) == ERROR_SUCCESS) {
286               if (type == REG_DWORD) {
287                   size = sizeof(DWORD);
288                   RegQueryValueEx(key, "DefaultColor", NULL, NULL,
289                                   (LPBYTE)&value, &size);
290               } else if (type == REG_SZ) {
291                   size = sizeof(strvalue);
292                   RegQueryValueEx(key, "DefaultColor", NULL, NULL,
293                                   (LPBYTE)strvalue, &size);
294                   value = strtoul(strvalue, NULL, 10);
295               }
296           }
297       }
298
299       if (value == 0 && RegOpenKeyEx(HKEY_LOCAL_MACHINE,
300                        "Software\\Microsoft\\Command Processor",
301                        0, KEY_READ, &key) == ERROR_SUCCESS) {
302           char  strvalue[4];
303
304           /* See if DWORD or REG_SZ */
305           if (RegQueryValueEx(key, "DefaultColor", NULL, &type,
306                      NULL, NULL) == ERROR_SUCCESS) {
307               if (type == REG_DWORD) {
308                   size = sizeof(DWORD);
309                   RegQueryValueEx(key, "DefaultColor", NULL, NULL,
310                                   (LPBYTE)&value, &size);
311               } else if (type == REG_SZ) {
312                   size = sizeof(strvalue);
313                   RegQueryValueEx(key, "DefaultColor", NULL, NULL,
314                                   (LPBYTE)strvalue, &size);
315                   value = strtoul(strvalue, NULL, 10);
316               }
317           }
318       }
319
320       /* If one found, set the screen to that colour */
321       if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
322           defaultColor = value & 0xFF;
323           param1[0] = 0x00;
324           WCMD_color();
325       }
326
327   }
328
329   if (opt_k) {
330       WCMD_process_command(cmd);
331       HeapFree(GetProcessHeap(), 0, cmd);
332   }
333
334 /*
335  *      If there is an AUTOEXEC.BAT file, try to execute it.
336  */
337
338   GetFullPathName ("\\autoexec.bat", sizeof(string), string, NULL);
339   h = CreateFile (string, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
340   if (h != INVALID_HANDLE_VALUE) {
341     CloseHandle (h);
342 #if 0
343     WCMD_batch ((char *)"\\autoexec.bat", (char *)"\\autoexec.bat", 0, NULL, INVALID_HANDLE_VALUE);
344 #endif
345   }
346
347 /*
348  *      Loop forever getting commands and executing them.
349  */
350
351   WCMD_version ();
352   while (TRUE) {
353     WCMD_show_prompt ();
354     ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
355     if (count > 1) {
356       string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
357       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
358       if (lstrlen (string) != 0) {
359         if (strchr(string,'|') != NULL) {
360           WCMD_pipe (string);
361         }
362         else {
363           WCMD_process_command (string);
364         }
365       }
366     }
367   }
368 }
369
370
371 /*****************************************************************************
372  * Process one command. If the command is EXIT this routine does not return.
373  * We will recurse through here executing batch files.
374  */
375
376
377 void WCMD_process_command (char *command)
378 {
379     char *cmd, *p, *s, *t;
380     int status, i;
381     DWORD count, creationDisposition;
382     HANDLE h;
383     char *whichcmd;
384     SECURITY_ATTRIBUTES sa;
385     char *new_cmd;
386     HANDLE old_stdin = INVALID_HANDLE_VALUE;
387     HANDLE old_stdout = INVALID_HANDLE_VALUE;
388
389     /* Move copy of the command onto the heap so it can be expanded */
390     new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING );
391     strcpy(new_cmd, command);
392
393     /* For commands in a context (batch program):                  */
394     /*   Expand environment variables in a batch file %{0-9} first */
395     /*     including support for any ~ modifiers                   */
396     /* Additionally:                                               */
397     /*   Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special  */
398     /*     names allowing environment variable overrides           */
399     /* NOTE: To support the %PATH:xxx% syntax, also perform        */
400     /*   manual expansion of environment variables here            */
401
402     p = new_cmd;
403     while ((p = strchr(p, '%'))) {
404       i = *(p+1) - '0';
405
406       /* Replace %~ modifications if in batch program */
407       if (context && *(p+1) == '~') {
408         WCMD_HandleTildaModifiers(&p, NULL);
409         p++;
410
411       /* Replace use of %0...%9 if in batch program*/
412       } else if (context && (i >= 0) && (i <= 9)) {
413         s = strdup (p+2);
414         t = WCMD_parameter (context -> command, i + context -> shift_count, NULL);
415         strcpy (p, t);
416         strcat (p, s);
417         free (s);
418
419       /* Replace use of %* if in batch program*/
420       } else if (context && *(p+1)=='*') {
421         char *startOfParms = NULL;
422         s = strdup (p+2);
423         t = WCMD_parameter (context -> command, 1, &startOfParms);
424         if (startOfParms != NULL) strcpy (p, startOfParms);
425         else *p = 0x00;
426         strcat (p, s);
427         free (s);
428
429       } else {
430         p = WCMD_expand_envvar(p);
431       }
432     }
433     cmd = new_cmd;
434
435     /* In a batch program, unknown variables are replace by nothing */
436     /* so remove any remaining %var%                                */
437     if (context) {
438       p = cmd;
439       while ((p = strchr(p, '%'))) {
440         s = strchr(p+1, '%');
441         if (!s) {
442           *p=0x00;
443         } else {
444           t = strdup(s+1);
445           strcpy(p, t);
446           free(t);
447         }
448       }
449
450       /* Show prompt before batch line IF echo is on and in batch program */
451       if (echo_mode && (cmd[0] != '@')) {
452         WCMD_show_prompt();
453         WCMD_output_asis ( cmd);
454         WCMD_output_asis ( "\n");
455       }
456     }
457
458 /*
459  *      Changing default drive has to be handled as a special case.
460  */
461
462     if ((cmd[1] == ':') && IsCharAlpha (cmd[0]) && (strlen(cmd) == 2)) {
463       status = SetCurrentDirectory (cmd);
464       if (!status) WCMD_print_error ();
465       HeapFree( GetProcessHeap(), 0, cmd );
466       return;
467     }
468
469     /* Don't issue newline WCMD_output (newline);           @JED*/
470
471     sa.nLength = sizeof(sa);
472     sa.lpSecurityDescriptor = NULL;
473     sa.bInheritHandle = TRUE;
474 /*
475  *      Redirect stdin and/or stdout if required.
476  */
477
478     if ((p = strchr(cmd,'<')) != NULL) {
479       h = CreateFile (WCMD_parameter (++p, 0, NULL), GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
480                 FILE_ATTRIBUTE_NORMAL, NULL);
481       if (h == INVALID_HANDLE_VALUE) {
482         WCMD_print_error ();
483         HeapFree( GetProcessHeap(), 0, cmd );
484         return;
485       }
486       old_stdin = GetStdHandle (STD_INPUT_HANDLE);
487       SetStdHandle (STD_INPUT_HANDLE, h);
488     }
489     if ((p = strchr(cmd,'>')) != NULL) {
490       *p++ = '\0';
491       if ('>' == *p) {
492         creationDisposition = OPEN_ALWAYS;
493         p++;
494       }
495       else {
496         creationDisposition = CREATE_ALWAYS;
497       }
498       h = CreateFile (WCMD_parameter (p, 0, NULL), GENERIC_WRITE, 0, &sa, creationDisposition,
499                 FILE_ATTRIBUTE_NORMAL, NULL);
500       if (h == INVALID_HANDLE_VALUE) {
501         WCMD_print_error ();
502         HeapFree( GetProcessHeap(), 0, cmd );
503         return;
504       }
505       if (SetFilePointer (h, 0, NULL, FILE_END) ==
506           INVALID_SET_FILE_POINTER) {
507         WCMD_print_error ();
508       }
509       old_stdout = GetStdHandle (STD_OUTPUT_HANDLE);
510       SetStdHandle (STD_OUTPUT_HANDLE, h);
511     }
512     if ((p = strchr(cmd,'<')) != NULL) *p = '\0';
513
514 /*
515  * Strip leading whitespaces, and a '@' if supplied
516  */
517     whichcmd = WCMD_strtrim_leading_spaces(cmd);
518     WINE_TRACE("Command: '%s'\n", cmd);
519     if (whichcmd[0] == '@') whichcmd++;
520
521 /*
522  *      Check if the command entered is internal. If it is, pass the rest of the
523  *      line down to the command. If not try to run a program.
524  */
525
526     count = 0;
527     while (IsCharAlphaNumeric(whichcmd[count])) {
528       count++;
529     }
530     for (i=0; i<=WCMD_EXIT; i++) {
531       if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
532           whichcmd, count, inbuilt[i], -1) == 2) break;
533     }
534     p = WCMD_strtrim_leading_spaces (&whichcmd[count]);
535     WCMD_parse (p, quals, param1, param2);
536     switch (i) {
537
538       case WCMD_ATTRIB:
539         WCMD_setshow_attrib ();
540         break;
541       case WCMD_CALL:
542         WCMD_call (p);
543         break;
544       case WCMD_CD:
545       case WCMD_CHDIR:
546         WCMD_setshow_default (p);
547         break;
548       case WCMD_CLS:
549         WCMD_clear_screen ();
550         break;
551       case WCMD_COPY:
552         WCMD_copy ();
553         break;
554       case WCMD_CTTY:
555         WCMD_change_tty ();
556         break;
557       case WCMD_DATE:
558         WCMD_setshow_date ();
559         break;
560       case WCMD_DEL:
561       case WCMD_ERASE:
562         WCMD_delete (p);
563         break;
564       case WCMD_DIR:
565         WCMD_directory ();
566         break;
567       case WCMD_ECHO:
568         WCMD_echo(&whichcmd[count]);
569         break;
570       case WCMD_FOR:
571         WCMD_for (p);
572         break;
573       case WCMD_GOTO:
574         WCMD_goto ();
575         break;
576       case WCMD_HELP:
577         WCMD_give_help (p);
578         break;
579       case WCMD_IF:
580         WCMD_if (p);
581         break;
582       case WCMD_LABEL:
583         WCMD_volume (1, p);
584         break;
585       case WCMD_MD:
586       case WCMD_MKDIR:
587         WCMD_create_dir ();
588         break;
589       case WCMD_MOVE:
590         WCMD_move ();
591         break;
592       case WCMD_PATH:
593         WCMD_setshow_path (p);
594         break;
595       case WCMD_PAUSE:
596         WCMD_pause ();
597         break;
598       case WCMD_PROMPT:
599         WCMD_setshow_prompt ();
600         break;
601       case WCMD_REM:
602         break;
603       case WCMD_REN:
604       case WCMD_RENAME:
605         WCMD_rename ();
606         break;
607       case WCMD_RD:
608       case WCMD_RMDIR:
609         WCMD_remove_dir (p);
610         break;
611       case WCMD_SETLOCAL:
612         WCMD_setlocal(p);
613         break;
614       case WCMD_ENDLOCAL:
615         WCMD_endlocal();
616         break;
617       case WCMD_SET:
618         WCMD_setshow_env (p);
619         break;
620       case WCMD_SHIFT:
621         WCMD_shift ();
622         break;
623       case WCMD_TIME:
624         WCMD_setshow_time ();
625         break;
626       case WCMD_TITLE:
627         if (strlen(&whichcmd[count]) > 0)
628           WCMD_title(&whichcmd[count+1]);
629         break;
630       case WCMD_TYPE:
631         WCMD_type ();
632         break;
633       case WCMD_VER:
634         WCMD_version ();
635         break;
636       case WCMD_VERIFY:
637         WCMD_verify (p);
638         break;
639       case WCMD_VOL:
640         WCMD_volume (0, p);
641         break;
642       case WCMD_PUSHD:
643         WCMD_pushd();
644         break;
645       case WCMD_POPD:
646         WCMD_popd();
647         break;
648       case WCMD_ASSOC:
649         WCMD_assoc(p);
650         break;
651       case WCMD_COLOR:
652         WCMD_color();
653         break;
654       case WCMD_EXIT:
655         WCMD_exit ();
656         break;
657       default:
658         WCMD_run_program (whichcmd, 0);
659     }
660     HeapFree( GetProcessHeap(), 0, cmd );
661     if (old_stdin != INVALID_HANDLE_VALUE) {
662       CloseHandle (GetStdHandle (STD_INPUT_HANDLE));
663       SetStdHandle (STD_INPUT_HANDLE, old_stdin);
664     }
665     if (old_stdout != INVALID_HANDLE_VALUE) {
666       CloseHandle (GetStdHandle (STD_OUTPUT_HANDLE));
667       SetStdHandle (STD_OUTPUT_HANDLE, old_stdout);
668     }
669 }
670
671 static void init_msvcrt_io_block(STARTUPINFO* st)
672 {
673     STARTUPINFO st_p;
674     /* fetch the parent MSVCRT info block if any, so that the child can use the
675      * same handles as its grand-father
676      */
677     st_p.cb = sizeof(STARTUPINFO);
678     GetStartupInfo(&st_p);
679     st->cbReserved2 = st_p.cbReserved2;
680     st->lpReserved2 = st_p.lpReserved2;
681     if (st_p.cbReserved2 && st_p.lpReserved2)
682     {
683         /* Override the entries for fd 0,1,2 if we happened
684          * to change those std handles (this depends on the way wcmd sets
685          * it's new input & output handles)
686          */
687         size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
688         BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
689         if (ptr)
690         {
691             unsigned num = *(unsigned*)st_p.lpReserved2;
692             char* flags = (char*)(ptr + sizeof(unsigned));
693             HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
694
695             memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
696             st->cbReserved2 = sz;
697             st->lpReserved2 = ptr;
698
699 #define WX_OPEN 0x01    /* see dlls/msvcrt/file.c */
700             if (num <= 0 || (flags[0] & WX_OPEN))
701             {
702                 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
703                 flags[0] |= WX_OPEN;
704             }
705             if (num <= 1 || (flags[1] & WX_OPEN))
706             {
707                 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
708                 flags[1] |= WX_OPEN;
709             }
710             if (num <= 2 || (flags[2] & WX_OPEN))
711             {
712                 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
713                 flags[2] |= WX_OPEN;
714             }
715 #undef WX_OPEN
716         }
717     }
718 }
719
720 /******************************************************************************
721  * WCMD_run_program
722  *
723  *      Execute a command line as an external program. Must allow recursion.
724  *
725  *      Precedence:
726  *        Manual testing under windows shows PATHEXT plays a key part in this,
727  *      and the search algorithm and precedence appears to be as follows.
728  *
729  *      Search locations:
730  *        If directory supplied on command, just use that directory
731  *        If extension supplied on command, look for that explicit name first
732  *        Otherwise, search in each directory on the path
733  *      Precedence:
734  *        If extension supplied on command, look for that explicit name first
735  *        Then look for supplied name .* (even if extension supplied, so
736  *          'garbage.exe' will match 'garbage.exe.cmd')
737  *        If any found, cycle through PATHEXT looking for name.exe one by one
738  *      Launching
739  *        Once a match has been found, it is launched - Code currently uses
740  *          findexecutable to acheive this which is left untouched.
741  */
742
743 void WCMD_run_program (char *command, int called) {
744
745   char  temp[MAX_PATH];
746   char  pathtosearch[MAX_PATH];
747   char *pathposn;
748   char  stemofsearch[MAX_PATH];
749   char *lastSlash;
750   char  pathext[MAXSTRING];
751   BOOL  extensionsupplied = FALSE;
752   BOOL  launched = FALSE;
753   BOOL  status;
754   DWORD len;
755
756
757   WCMD_parse (command, quals, param1, param2);  /* Quick way to get the filename */
758   if (!(*param1) && !(*param2))
759     return;
760
761   /* Calculate the search path and stem to search for */
762   if (strpbrk (param1, "/\\:") == NULL) {  /* No explicit path given, search path */
763     strcpy(pathtosearch,".;");
764     len = GetEnvironmentVariable ("PATH", &pathtosearch[2], sizeof(pathtosearch)-2);
765     if ((len == 0) || (len >= sizeof(pathtosearch) - 2)) {
766       lstrcpy (pathtosearch, ".");
767     }
768     if (strchr(param1, '.') != NULL) extensionsupplied = TRUE;
769     strcpy(stemofsearch, param1);
770
771   } else {
772
773     /* Convert eg. ..\fred to include a directory by removing file part */
774     GetFullPathName(param1, MAX_PATH, pathtosearch, NULL);
775     lastSlash = strrchr(pathtosearch, '\\');
776     if (lastSlash) *lastSlash = 0x00;
777     if (strchr(lastSlash, '.') != NULL) extensionsupplied = TRUE;
778     strcpy(stemofsearch, lastSlash+1);
779   }
780
781   /* Now extract PATHEXT */
782   len = GetEnvironmentVariable ("PATHEXT", pathext, sizeof(pathext));
783   if ((len == 0) || (len >= sizeof(pathext))) {
784     lstrcpy (pathext, ".bat;.com;.cmd;.exe");
785   }
786
787   /* Loop through the search path, dir by dir */
788   pathposn = pathtosearch;
789   while (!launched && pathposn) {
790
791     char  thisDir[MAX_PATH] = "";
792     char *pos               = NULL;
793     BOOL  found             = FALSE;
794
795     /* Work on the first directory on the search path */
796     pos = strchr(pathposn, ';');
797     if (pos) {
798       strncpy(thisDir, pathposn, (pos-pathposn));
799       thisDir[(pos-pathposn)] = 0x00;
800       pathposn = pos+1;
801
802     } else {
803       strcpy(thisDir, pathposn);
804       pathposn = NULL;
805     }
806
807     /* Since you can have eg. ..\.. on the path, need to expand
808        to full information                                      */
809     strcpy(temp, thisDir);
810     GetFullPathName(temp, MAX_PATH, thisDir, NULL);
811
812     /* 1. If extension supplied, see if that file exists */
813     strcat(thisDir, "\\");
814     strcat(thisDir, stemofsearch);
815     pos = &thisDir[strlen(thisDir)]; /* Pos = end of name */
816
817     if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
818       found = TRUE;
819     }
820
821     /* 2. Any .* matches? */
822     if (!found) {
823       HANDLE          h;
824       WIN32_FIND_DATA finddata;
825
826       strcat(thisDir,".*");
827       h = FindFirstFile(thisDir, &finddata);
828       FindClose(h);
829       if (h != INVALID_HANDLE_VALUE) {
830
831         char *thisExt = pathext;
832
833         /* 3. Yes - Try each path ext */
834         while (thisExt) {
835           char *nextExt = strchr(thisExt, ';');
836
837           if (nextExt) {
838             strncpy(pos, thisExt, (nextExt-thisExt));
839             pos[(nextExt-thisExt)] = 0x00;
840             thisExt = nextExt+1;
841           } else {
842             strcpy(pos, thisExt);
843             thisExt = NULL;
844           }
845
846           if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
847             found = TRUE;
848             thisExt = NULL;
849           }
850         }
851       }
852     }
853
854     /* Once found, launch it */
855     if (found) {
856       STARTUPINFO st;
857       PROCESS_INFORMATION pe;
858       SHFILEINFO psfi;
859       DWORD console;
860       HINSTANCE hinst;
861       char *ext = strrchr( thisDir, '.' );
862       launched = TRUE;
863
864       /* Special case BAT and CMD */
865       if (ext && !strcasecmp(ext, ".bat")) {
866         WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
867         return;
868       } else if (ext && !strcasecmp(ext, ".cmd")) {
869         WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
870         return;
871       } else {
872
873         /* thisDir contains the file to be launched, but with what?
874            eg. a.exe will require a.exe to be launched, a.html may be iexplore */
875         hinst = FindExecutable (param1, NULL, temp);
876         if ((INT_PTR)hinst < 32)
877           console = 0;
878         else
879           console = SHGetFileInfo (temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
880
881         ZeroMemory (&st, sizeof(STARTUPINFO));
882         st.cb = sizeof(STARTUPINFO);
883         init_msvcrt_io_block(&st);
884
885         /* Launch the process and if a CUI wait on it to complete */
886         status = CreateProcess (thisDir, command, NULL, NULL, TRUE,
887                                 0, NULL, NULL, &st, &pe);
888         if ((opt_c || opt_k) && !opt_s && !status
889             && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
890           /* strip first and last quote characters and try again */
891           WCMD_opt_s_strip_quotes(command);
892           opt_s=1;
893           WCMD_run_program(command, called);
894           return;
895         }
896         if (!status) {
897           WCMD_print_error ();
898           /* If a command fails to launch, it sets errorlevel 9009 - which
899              does not seem to have any associated constant definition     */
900           errorlevel = 9009;
901           return;
902         }
903         if (!console) errorlevel = 0;
904         else
905         {
906             if (!HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
907             GetExitCodeProcess (pe.hProcess, &errorlevel);
908             if (errorlevel == STILL_ACTIVE) errorlevel = 0;
909         }
910         CloseHandle(pe.hProcess);
911         CloseHandle(pe.hThread);
912         return;
913       }
914     }
915   }
916
917   /* Not found anywhere - give up */
918   SetLastError(ERROR_FILE_NOT_FOUND);
919   WCMD_print_error ();
920
921   /* If a command fails to launch, it sets errorlevel 9009 - which
922      does not seem to have any associated constant definition     */
923   errorlevel = 9009;
924   return;
925
926 }
927
928 /******************************************************************************
929  * WCMD_show_prompt
930  *
931  *      Display the prompt on STDout
932  *
933  */
934
935 void WCMD_show_prompt (void) {
936
937   int status;
938   char out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
939   char *p, *q;
940   DWORD len;
941
942   len = GetEnvironmentVariable ("PROMPT", prompt_string, sizeof(prompt_string));
943   if ((len == 0) || (len >= sizeof(prompt_string))) {
944     lstrcpy (prompt_string, "$P$G");
945   }
946   p = prompt_string;
947   q = out_string;
948   *q = '\0';
949   while (*p != '\0') {
950     if (*p != '$') {
951       *q++ = *p++;
952       *q = '\0';
953     }
954     else {
955       p++;
956       switch (toupper(*p)) {
957         case '$':
958           *q++ = '$';
959           break;
960         case 'A':
961           *q++ = '&';
962           break;
963         case 'B':
964           *q++ = '|';
965           break;
966         case 'C':
967           *q++ = '(';
968           break;
969         case 'D':
970           GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
971           while (*q) q++;
972           break;
973         case 'E':
974           *q++ = '\E';
975           break;
976         case 'F':
977           *q++ = ')';
978           break;
979         case 'G':
980           *q++ = '>';
981           break;
982         case 'H':
983           *q++ = '\b';
984           break;
985         case 'L':
986           *q++ = '<';
987           break;
988         case 'N':
989           status = GetCurrentDirectory (sizeof(curdir), curdir);
990           if (status) {
991             *q++ = curdir[0];
992           }
993           break;
994         case 'P':
995           status = GetCurrentDirectory (sizeof(curdir), curdir);
996           if (status) {
997             lstrcat (q, curdir);
998             while (*q) q++;
999           }
1000           break;
1001         case 'Q':
1002           *q++ = '=';
1003           break;
1004         case 'S':
1005           *q++ = ' ';
1006           break;
1007         case 'T':
1008           GetTimeFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
1009           while (*q) q++;
1010           break;
1011         case 'V':
1012           lstrcat (q, version_string);
1013           while (*q) q++;
1014           break;
1015         case '_':
1016           *q++ = '\n';
1017           break;
1018         case '+':
1019           if (pushd_directories) {
1020             memset(q, '+', pushd_directories->stackdepth);
1021             q = q + pushd_directories->stackdepth;
1022           }
1023           break;
1024       }
1025       p++;
1026       *q = '\0';
1027     }
1028   }
1029   WCMD_output_asis (out_string);
1030 }
1031
1032 /****************************************************************************
1033  * WCMD_print_error
1034  *
1035  * Print the message for GetLastError
1036  */
1037
1038 void WCMD_print_error (void) {
1039   LPVOID lpMsgBuf;
1040   DWORD error_code;
1041   int status;
1042
1043   error_code = GetLastError ();
1044   status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
1045                         NULL, error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL);
1046   if (!status) {
1047     WCMD_output ("FIXME: Cannot display message for error %d, status %d\n",
1048                         error_code, GetLastError());
1049     return;
1050   }
1051   WCMD_output_asis (lpMsgBuf);
1052   LocalFree ((HLOCAL)lpMsgBuf);
1053   WCMD_output_asis (newline);
1054   return;
1055 }
1056
1057 /*******************************************************************
1058  * WCMD_parse - parse a command into parameters and qualifiers.
1059  *
1060  *      On exit, all qualifiers are concatenated into q, the first string
1061  *      not beginning with "/" is in p1 and the
1062  *      second in p2. Any subsequent non-qualifier strings are lost.
1063  *      Parameters in quotes are handled.
1064  */
1065
1066 void WCMD_parse (char *s, char *q, char *p1, char *p2) {
1067
1068 int p = 0;
1069
1070   *q = *p1 = *p2 = '\0';
1071   while (TRUE) {
1072     switch (*s) {
1073       case '/':
1074         *q++ = *s++;
1075         while ((*s != '\0') && (*s != ' ') && *s != '/') {
1076           *q++ = toupper (*s++);
1077         }
1078         *q = '\0';
1079         break;
1080       case ' ':
1081       case '\t':
1082         s++;
1083         break;
1084       case '"':
1085         s++;
1086         while ((*s != '\0') && (*s != '"')) {
1087           if (p == 0) *p1++ = *s++;
1088           else if (p == 1) *p2++ = *s++;
1089           else s++;
1090         }
1091         if (p == 0) *p1 = '\0';
1092         if (p == 1) *p2 = '\0';
1093         p++;
1094         if (*s == '"') s++;
1095         break;
1096       case '\0':
1097         return;
1098       default:
1099         while ((*s != '\0') && (*s != ' ') && (*s != '\t')) {
1100           if (p == 0) *p1++ = *s++;
1101           else if (p == 1) *p2++ = *s++;
1102           else s++;
1103         }
1104         if (p == 0) *p1 = '\0';
1105         if (p == 1) *p2 = '\0';
1106         p++;
1107     }
1108   }
1109 }
1110
1111 /*******************************************************************
1112  * WCMD_output - send output to current standard output device.
1113  *
1114  */
1115
1116 void WCMD_output (const char *format, ...) {
1117
1118   va_list ap;
1119   char string[1024];
1120   int ret;
1121
1122   va_start(ap,format);
1123   ret = vsnprintf (string, sizeof( string), format, ap);
1124   va_end(ap);
1125   if( ret >= sizeof( string)) {
1126        WCMD_output_asis("ERR: output truncated in WCMD_output\n" );
1127        string[sizeof( string) -1] = '\0';
1128   }
1129   WCMD_output_asis(string);
1130 }
1131
1132
1133 static int line_count;
1134 static int max_height;
1135 static BOOL paged_mode;
1136
1137 void WCMD_enter_paged_mode(void)
1138 {
1139   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
1140
1141   if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo))
1142     max_height = consoleInfo.dwSize.Y;
1143   else
1144     max_height = 25;
1145   paged_mode = TRUE;
1146   line_count = 5; /* keep 5 lines from previous output */
1147 }
1148
1149 void WCMD_leave_paged_mode(void)
1150 {
1151   paged_mode = FALSE;
1152 }
1153
1154 /*******************************************************************
1155  * WCMD_output_asis - send output to current standard output device.
1156  *        without formatting eg. when message contains '%'
1157  */
1158
1159 void WCMD_output_asis (const char *message) {
1160   DWORD count;
1161   char* ptr;
1162   char string[1024];
1163
1164   if (paged_mode) {
1165     do {
1166       if ((ptr = strchr(message, '\n')) != NULL) ptr++;
1167       WriteFile (GetStdHandle(STD_OUTPUT_HANDLE), message, 
1168                  (ptr) ? ptr - message : lstrlen(message), &count, NULL);
1169       if (ptr) {
1170         if (++line_count >= max_height - 1) {
1171           line_count = 0;
1172           WCMD_output_asis (anykey);
1173           ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
1174         }
1175       }
1176     } while ((message = ptr) != NULL);
1177   } else {
1178       WriteFile (GetStdHandle(STD_OUTPUT_HANDLE), message, lstrlen(message), &count, NULL);
1179   }
1180 }
1181
1182
1183 /***************************************************************************
1184  * WCMD_strtrim_leading_spaces
1185  *
1186  *      Remove leading spaces from a string. Return a pointer to the first
1187  *      non-space character. Does not modify the input string
1188  */
1189
1190 char *WCMD_strtrim_leading_spaces (char *string) {
1191
1192   char *ptr;
1193
1194   ptr = string;
1195   while (*ptr == ' ') ptr++;
1196   return ptr;
1197 }
1198
1199 /*************************************************************************
1200  * WCMD_strtrim_trailing_spaces
1201  *
1202  *      Remove trailing spaces from a string. This routine modifies the input
1203  *      string by placing a null after the last non-space character
1204  */
1205
1206 void WCMD_strtrim_trailing_spaces (char *string) {
1207
1208   char *ptr;
1209
1210   ptr = string + lstrlen (string) - 1;
1211   while ((*ptr == ' ') && (ptr >= string)) {
1212     *ptr = '\0';
1213     ptr--;
1214   }
1215 }
1216
1217 /*************************************************************************
1218  * WCMD_opt_s_strip_quotes
1219  *
1220  *      Remove first and last quote characters, preserving all other text
1221  */
1222
1223 void WCMD_opt_s_strip_quotes(char *cmd) {
1224   char *src = cmd + 1, *dest = cmd, *lastq = NULL;
1225   while((*dest=*src) != '\0') {
1226       if (*src=='\"')
1227           lastq=dest;
1228       dest++, src++;
1229   }
1230   if (lastq) {
1231       dest=lastq++;
1232       while ((*dest++=*lastq++) != 0)
1233           ;
1234   }
1235 }
1236
1237 /*************************************************************************
1238  * WCMD_pipe
1239  *
1240  *      Handle pipes within a command - the DOS way using temporary files.
1241  */
1242
1243 void WCMD_pipe (char *command) {
1244
1245   char *p;
1246   char temp_path[MAX_PATH], temp_file[MAX_PATH], temp_file2[MAX_PATH], temp_cmd[1024];
1247
1248   GetTempPath (sizeof(temp_path), temp_path);
1249   GetTempFileName (temp_path, "CMD", 0, temp_file);
1250   p = strchr(command, '|');
1251   *p++ = '\0';
1252   wsprintf (temp_cmd, "%s > %s", command, temp_file);
1253   WCMD_process_command (temp_cmd);
1254   command = p;
1255   while ((p = strchr(command, '|'))) {
1256     *p++ = '\0';
1257     GetTempFileName (temp_path, "CMD", 0, temp_file2);
1258     wsprintf (temp_cmd, "%s < %s > %s", command, temp_file, temp_file2);
1259     WCMD_process_command (temp_cmd);
1260     DeleteFile (temp_file);
1261     lstrcpy (temp_file, temp_file2);
1262     command = p;
1263   }
1264   wsprintf (temp_cmd, "%s < %s", command, temp_file);
1265   WCMD_process_command (temp_cmd);
1266   DeleteFile (temp_file);
1267 }
1268
1269 /*************************************************************************
1270  * WCMD_expand_envvar
1271  *
1272  *      Expands environment variables, allowing for character substitution
1273  */
1274 static char *WCMD_expand_envvar(char *start) {
1275     char *endOfVar = NULL, *s;
1276     char *colonpos = NULL;
1277     char thisVar[MAXSTRING];
1278     char thisVarContents[MAXSTRING];
1279     char savedchar = 0x00;
1280     int len;
1281
1282     /* Find the end of the environment variable, and extract name */
1283     endOfVar = strchr(start+1, '%');
1284     if (endOfVar == NULL) {
1285       /* FIXME: Some special conditions here depending opn whether
1286          in batch, complex or not, and whether env var exists or not! */
1287       return start+1;
1288     }
1289     strncpy(thisVar, start, (endOfVar - start)+1);
1290     thisVar[(endOfVar - start)+1] = 0x00;
1291     colonpos = strchr(thisVar+1, ':');
1292
1293     /* If there's complex substitution, just need %var% for now
1294        to get the expanded data to play with                    */
1295     if (colonpos) {
1296         *colonpos = '%';
1297         savedchar = *(colonpos+1);
1298         *(colonpos+1) = 0x00;
1299     }
1300
1301     /* Expand to contents, if unchanged, return */
1302     /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
1303     /* override if existing env var called that name              */
1304     if ((CompareString (LOCALE_USER_DEFAULT,
1305                         NORM_IGNORECASE | SORT_STRINGSORT,
1306                         thisVar, 12, "%ERRORLEVEL%", -1) == 2) &&
1307                 (GetEnvironmentVariable("ERRORLEVEL", thisVarContents, 1) == 0) &&
1308                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1309       sprintf(thisVarContents, "%d", errorlevel);
1310       len = strlen(thisVarContents);
1311
1312     } else if ((CompareString (LOCALE_USER_DEFAULT,
1313                                NORM_IGNORECASE | SORT_STRINGSORT,
1314                                thisVar, 6, "%DATE%", -1) == 2) &&
1315                 (GetEnvironmentVariable("DATE", thisVarContents, 1) == 0) &&
1316                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1317
1318       GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
1319                     NULL, thisVarContents, MAXSTRING);
1320       len = strlen(thisVarContents);
1321
1322     } else if ((CompareString (LOCALE_USER_DEFAULT,
1323                                NORM_IGNORECASE | SORT_STRINGSORT,
1324                                thisVar, 6, "%TIME%", -1) == 2) &&
1325                 (GetEnvironmentVariable("TIME", thisVarContents, 1) == 0) &&
1326                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1327       GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
1328                         NULL, thisVarContents, MAXSTRING);
1329       len = strlen(thisVarContents);
1330
1331     } else if ((CompareString (LOCALE_USER_DEFAULT,
1332                                NORM_IGNORECASE | SORT_STRINGSORT,
1333                                thisVar, 4, "%CD%", -1) == 2) &&
1334                 (GetEnvironmentVariable("CD", thisVarContents, 1) == 0) &&
1335                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1336       GetCurrentDirectory (MAXSTRING, thisVarContents);
1337       len = strlen(thisVarContents);
1338
1339     } else if ((CompareString (LOCALE_USER_DEFAULT,
1340                                NORM_IGNORECASE | SORT_STRINGSORT,
1341                                thisVar, 8, "%RANDOM%", -1) == 2) &&
1342                 (GetEnvironmentVariable("RANDOM", thisVarContents, 1) == 0) &&
1343                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1344       sprintf(thisVarContents, "%d", rand() % 32768);
1345       len = strlen(thisVarContents);
1346
1347     } else {
1348
1349       len = ExpandEnvironmentStrings(thisVar, thisVarContents,
1350                                       sizeof(thisVarContents));
1351     }
1352
1353     if (len == 0)
1354       return endOfVar+1;
1355
1356     /* In a batch program, unknown env vars are replaced with nothing,
1357          note syntax %garbage:1,3% results in anything after the ':'
1358          except the %
1359        From the command line, you just get back what you entered      */
1360     if (lstrcmpi(thisVar, thisVarContents) == 0) {
1361
1362       /* Restore the complex part after the compare */
1363       if (colonpos) {
1364         *colonpos = ':';
1365         *(colonpos+1) = savedchar;
1366       }
1367
1368       s = strdup (endOfVar + 1);
1369
1370       /* Command line - just ignore this */
1371       if (context == NULL) return endOfVar+1;
1372
1373       /* Batch - replace unknown env var with nothing */
1374       if (colonpos == NULL) {
1375         strcpy (start, s);
1376
1377       } else {
1378         len = strlen(thisVar);
1379         thisVar[len-1] = 0x00;
1380         /* If %:...% supplied, : is retained */
1381         if (colonpos == thisVar+1) {
1382           strcpy (start, colonpos);
1383         } else {
1384           strcpy (start, colonpos+1);
1385         }
1386         strcat (start, s);
1387       }
1388       free (s);
1389       return start;
1390
1391     }
1392
1393     /* See if we need to do complex substitution (any ':'s), if not
1394        then our work here is done                                  */
1395     if (colonpos == NULL) {
1396       s = strdup (endOfVar + 1);
1397       strcpy (start, thisVarContents);
1398       strcat (start, s);
1399       free(s);
1400       return start;
1401     }
1402
1403     /* Restore complex bit */
1404     *colonpos = ':';
1405     *(colonpos+1) = savedchar;
1406
1407     /*
1408         Handle complex substitutions:
1409            xxx=yyy    (replace xxx with yyy)
1410            *xxx=yyy   (replace up to and including xxx with yyy)
1411            ~x         (from x chars in)
1412            ~-x        (from x chars from the end)
1413            ~x,y       (from x chars in for y characters)
1414            ~x,-y      (from x chars in until y characters from the end)
1415      */
1416
1417     /* ~ is substring manipulation */
1418     if (savedchar == '~') {
1419
1420       int   substrposition, substrlength;
1421       char *commapos = strchr(colonpos+2, ',');
1422       char *startCopy;
1423
1424       substrposition = atol(colonpos+2);
1425       if (commapos) substrlength = atol(commapos+1);
1426
1427       s = strdup (endOfVar + 1);
1428
1429       /* Check bounds */
1430       if (substrposition >= 0) {
1431         startCopy = &thisVarContents[min(substrposition, len)];
1432       } else {
1433         startCopy = &thisVarContents[max(0, len+substrposition-1)];
1434       }
1435
1436       if (commapos == NULL) {
1437         strcpy (start, startCopy); /* Copy the lot */
1438       } else if (substrlength < 0) {
1439
1440         int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
1441         if (copybytes > len) copybytes = len;
1442         else if (copybytes < 0) copybytes = 0;
1443         strncpy (start, startCopy, copybytes); /* Copy the lot */
1444         start[copybytes] = 0x00;
1445       } else {
1446         strncpy (start, startCopy, substrlength); /* Copy the lot */
1447         start[substrlength] = 0x00;
1448       }
1449
1450       strcat (start, s);
1451       free(s);
1452       return start;
1453
1454     /* search and replace manipulation */
1455     } else {
1456       char *equalspos = strstr(colonpos, "=");
1457       char *replacewith = equalspos+1;
1458       char *found       = NULL;
1459       char *searchIn;
1460       char *searchFor;
1461
1462       s = strdup (endOfVar + 1);
1463       if (equalspos == NULL) return start+1;
1464
1465       /* Null terminate both strings */
1466       thisVar[strlen(thisVar)-1] = 0x00;
1467       *equalspos = 0x00;
1468
1469       /* Since we need to be case insensitive, copy the 2 buffers */
1470       searchIn  = strdup(thisVarContents);
1471       CharUpperBuff(searchIn, strlen(thisVarContents));
1472       searchFor = strdup(colonpos+1);
1473       CharUpperBuff(searchFor, strlen(colonpos+1));
1474
1475
1476       /* Handle wildcard case */
1477       if (*(colonpos+1) == '*') {
1478         /* Search for string to replace */
1479         found = strstr(searchIn, searchFor+1);
1480
1481         if (found) {
1482           /* Do replacement */
1483           strcpy(start, replacewith);
1484           strcat(start, thisVarContents + (found-searchIn) + strlen(searchFor+1));
1485           strcat(start, s);
1486           free(s);
1487         } else {
1488           /* Copy as it */
1489           strcpy(start, thisVarContents);
1490           strcat(start, s);
1491         }
1492
1493       } else {
1494         /* Loop replacing all instances */
1495         char *lastFound = searchIn;
1496         char *outputposn = start;
1497
1498         *start = 0x00;
1499         while ((found = strstr(lastFound, searchFor))) {
1500             strncpy(outputposn,
1501                     thisVarContents + (lastFound-searchIn),
1502                     (found - lastFound));
1503             outputposn  = outputposn + (found - lastFound);
1504             *outputposn = 0x00;
1505             strcat(outputposn, replacewith);
1506             outputposn = outputposn + strlen(replacewith);
1507             lastFound = found + strlen(searchFor);
1508         }
1509         strcat(outputposn,
1510                 thisVarContents + (lastFound-searchIn));
1511         strcat(outputposn, s);
1512       }
1513       free(searchIn);
1514       free(searchFor);
1515       return start;
1516     }
1517     return start+1;
1518 }