1 /*
2  * PROJECT:     ReactOS DosKey Command
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Provides history and command aliases management for
5  *              command-line programs.
6  * COPYRIGHT:   Copyright 2008 Christoph von Wittich
7  *              Copyright 2013-2017 Hermès Bélusca-Maïto
8  */
9 
10 #include <stdio.h>
11 #include <wchar.h>
12 #include <locale.h>
13 
14 #include <windef.h>
15 #include <winbase.h>
16 #include <winuser.h>
17 #include <wincon.h>
18 
19 /* Console API functions which are absent from wincon.h */
20 #define EXENAME_LENGTH (255 + 1)
21 
22 VOID
23 WINAPI
24 ExpungeConsoleCommandHistoryW(LPCWSTR lpExeName);
25 
26 DWORD
27 WINAPI
28 GetConsoleCommandHistoryW(LPWSTR lpHistory,
29                           DWORD cbHistory,
30                           LPCWSTR lpExeName);
31 
32 DWORD
33 WINAPI
34 GetConsoleCommandHistoryLengthW(LPCWSTR lpExeName);
35 
36 BOOL
37 WINAPI
38 SetConsoleNumberOfCommandsW(DWORD dwNumCommands,
39                             LPCWSTR lpExeName);
40 
41 #include "doskey.h"
42 
43 #define MAX_STRING 2000
44 WCHAR szStringBuf[MAX_STRING];
45 LPWSTR pszExeName = L"cmd.exe";
46 
SetInsert(DWORD dwFlag)47 static VOID SetInsert(DWORD dwFlag)
48 {
49     /*
50      * NOTE: Enabling the ENABLE_INSERT_MODE mode can also be done by calling
51      * kernel32:SetConsoleCommandHistoryMode(CONSOLE_OVERSTRIKE) (deprecated).
52      */
53     DWORD dwMode;
54     HANDLE hConsole = GetStdHandle(STD_INPUT_HANDLE);
55     GetConsoleMode(hConsole, &dwMode);
56     dwMode |= ENABLE_EXTENDED_FLAGS;
57     SetConsoleMode(hConsole, (dwMode & ~ENABLE_INSERT_MODE) | dwFlag);
58 }
59 
PrintHistory(VOID)60 static VOID PrintHistory(VOID)
61 {
62     DWORD Length = GetConsoleCommandHistoryLengthW(pszExeName);
63     PBYTE HistBuf;
64     WCHAR *Hist;
65     WCHAR *HistEnd;
66 
67     HistBuf = HeapAlloc(GetProcessHeap(),
68                         HEAP_ZERO_MEMORY,
69                         Length);
70     if (!HistBuf) return;
71     Hist = (WCHAR *)HistBuf;
72     HistEnd = (WCHAR *)&HistBuf[Length];
73 
74     if (GetConsoleCommandHistoryW(Hist, Length, pszExeName))
75     {
76         for (; Hist < HistEnd; Hist += wcslen(Hist) + 1)
77         {
78             wprintf(L"%s\n", Hist);
79         }
80     }
81 
82     HeapFree(GetProcessHeap(), 0, HistBuf);
83 }
84 
SetMacro(LPWSTR definition)85 static INT SetMacro(LPWSTR definition)
86 {
87     WCHAR *name, *nameend, *text, temp;
88 
89     name = definition;
90     while (*name == L' ')
91         name++;
92 
93     /* error if no '=' found */
94     if ((nameend = wcschr(name, L'=')) != NULL)
95     {
96         text = nameend + 1;
97         while (*text == L' ')
98             text++;
99 
100         while (nameend > name && nameend[-1] == L' ')
101             nameend--;
102 
103         /* Split rest into name and substitute */
104         temp = *nameend;
105         *nameend = L'\0';
106         /* Don't allow spaces in the name, since such a macro would be unusable */
107         if (!wcschr(name, L' ') && AddConsoleAliasW(name, text, pszExeName))
108             return 0;
109         *nameend = temp;
110     }
111 
112     LoadStringW(GetModuleHandle(NULL),
113                 IDS_INVALID_MACRO_DEF,
114                 szStringBuf,
115                 ARRAYSIZE(szStringBuf));
116     wprintf(szStringBuf, definition);
117     return 1;
118 }
119 
PrintMacros(LPWSTR pszExeName,LPWSTR Indent)120 static VOID PrintMacros(LPWSTR pszExeName, LPWSTR Indent)
121 {
122     DWORD Length = GetConsoleAliasesLengthW(pszExeName);
123     PBYTE AliasBuf;
124     WCHAR *Alias;
125     WCHAR *AliasEnd;
126 
127     AliasBuf = HeapAlloc(GetProcessHeap(),
128                          HEAP_ZERO_MEMORY,
129                          Length * sizeof(BYTE));
130     if (!AliasBuf) return;
131     Alias = (WCHAR *)AliasBuf;
132     AliasEnd = (WCHAR *)&AliasBuf[Length];
133 
134     if (GetConsoleAliasesW(Alias, Length * sizeof(BYTE), pszExeName))
135     {
136         for (; Alias < AliasEnd; Alias += wcslen(Alias) + 1)
137         {
138             wprintf(L"%s%s\n", Indent, Alias);
139         }
140     }
141 
142     HeapFree(GetProcessHeap(), 0, AliasBuf);
143 }
144 
PrintAllMacros(VOID)145 static VOID PrintAllMacros(VOID)
146 {
147     DWORD Length = GetConsoleAliasExesLength();
148     PBYTE ExeNameBuf;
149     WCHAR *ExeName;
150     WCHAR *ExeNameEnd;
151 
152     ExeNameBuf = HeapAlloc(GetProcessHeap(),
153                            HEAP_ZERO_MEMORY,
154                            Length * sizeof(BYTE));
155     if (!ExeNameBuf) return;
156     ExeName = (WCHAR *)ExeNameBuf;
157     ExeNameEnd = (WCHAR *)&ExeNameBuf[Length];
158 
159     if (GetConsoleAliasExesW(ExeName, Length * sizeof(BYTE)))
160     {
161         for (; ExeName < ExeNameEnd; ExeName += wcslen(ExeName) + 1)
162         {
163             wprintf(L"[%s]\n", ExeName);
164             PrintMacros(ExeName, L"    ");
165             wprintf(L"\n");
166         }
167     }
168 
169     HeapFree(GetProcessHeap(), 0, ExeNameBuf);
170 }
171 
172 /* Remove starting and ending quotes from a string, if present */
RemoveQuotes(LPWSTR str)173 static LPWSTR RemoveQuotes(LPWSTR str)
174 {
175     WCHAR *end;
176     if (*str == L'"' && *(end = str + wcslen(str) - 1) == L'"')
177     {
178         str++;
179         *end = L'\0';
180     }
181     return str;
182 }
183 
ReadFromFile(LPWSTR FileName)184 static VOID ReadFromFile(LPWSTR FileName)
185 {
186     FILE* fp;
187     WCHAR line[MAX_PATH];
188     WCHAR ExeNameBuffer[EXENAME_LENGTH];
189     LPWSTR pszOrgExeName = pszExeName;
190 
191     /* Open the file */
192     fp = _wfopen(FileName, L"rt");
193     if (!fp)
194     {
195         _wperror(FileName);
196         return;
197     }
198 
199     while (fgetws(line, ARRAYSIZE(line), fp) != NULL)
200     {
201         PWCHAR end;
202 
203         if (!*line)
204             continue;
205 
206         /* Remove trailing newline character */
207         end = &line[wcslen(line) - 1];
208         if (*end == L'\n')
209             *end = L'\0';
210 
211         if (!*line)
212             continue;
213 
214         /* Check for any section redefining the current executable name */
215         end = NULL;
216         if (*line == L'[')
217             end = wcschr(line, L']');
218 
219         if (end != NULL)
220         {
221             /* New section: change the current executable name */
222 
223             *end = L'\0'; // NULL-terminate it
224             pszExeName = RemoveQuotes(line + 1);
225             if (*pszExeName)
226             {
227                 /* Capture the new executable name and truncate it if needed */
228                 end = &pszExeName[wcslen(pszExeName)];
229                 if (end - pszExeName >= EXENAME_LENGTH)
230                     end = &pszExeName[EXENAME_LENGTH - 1];
231                 *end = L'\0'; // Truncate it
232                 wcscpy(ExeNameBuffer, pszExeName);
233                 pszExeName = ExeNameBuffer;
234             }
235             else
236             {
237                 /* Restore the original current executable name */
238                 pszExeName = pszOrgExeName;
239             }
240         }
241         else
242         {
243             /* Set the new macro for the current executable */
244             SetMacro(line);
245         }
246     }
247 
248     /* Restore the original current executable name if it has changed */
249     pszExeName = pszOrgExeName;
250 
251     /* Close the file and return */
252     fclose(fp);
253     return;
254 }
255 
256 /* Get the start and end of the next command-line argument. */
GetArg(WCHAR ** pStart,WCHAR ** pEnd)257 static BOOL GetArg(WCHAR **pStart, WCHAR **pEnd)
258 {
259     BOOL bInQuotes = FALSE;
260     WCHAR *p = *pEnd;
261     p += wcsspn(p, L" \t");
262     if (!*p)
263         return FALSE;
264     *pStart = p;
265     do
266     {
267         if (!bInQuotes && (*p == L' ' || *p == L'\t'))
268             break;
269         bInQuotes ^= (*p++ == L'"');
270     } while (*p);
271     *pEnd = p;
272     return TRUE;
273 }
274 
275 int
wmain(VOID)276 wmain(VOID)
277 {
278     LPWSTR pArgStart, pArgEnd;
279 
280     setlocale(LC_ALL, "");
281 
282     /* Get the full command line using GetCommandLine(). We can't just use argv,
283      * because then a parameter like "gotoroot=cd \" wouldn't be passed completely. */
284     pArgEnd = GetCommandLineW();
285 
286     /* Skip the application name */
287     GetArg(&pArgStart, &pArgEnd);
288 
289     while (GetArg(&pArgStart, &pArgEnd))
290     {
291         /* NULL-terminate this argument to make processing easier */
292         WCHAR tmp = *pArgEnd;
293         *pArgEnd = L'\0';
294 
295         if (!wcscmp(pArgStart, L"/?"))
296         {
297             LoadStringW(GetModuleHandle(NULL),
298                         IDS_HELP,
299                         szStringBuf,
300                         ARRAYSIZE(szStringBuf));
301             wprintf(szStringBuf);
302             break;
303         }
304         else if (!_wcsnicmp(pArgStart, L"/EXENAME=", 9))
305         {
306             pszExeName = RemoveQuotes(pArgStart + 9);
307         }
308         else if (!_wcsicmp(pArgStart, L"/H") ||
309                  !_wcsicmp(pArgStart, L"/HISTORY"))
310         {
311             PrintHistory();
312         }
313         else if (!_wcsnicmp(pArgStart, L"/LISTSIZE=", 10))
314         {
315             SetConsoleNumberOfCommandsW(_wtoi(pArgStart + 10), pszExeName);
316         }
317         else if (!_wcsicmp(pArgStart, L"/REINSTALL"))
318         {
319             ExpungeConsoleCommandHistoryW(pszExeName);
320         }
321         else if (!_wcsicmp(pArgStart, L"/INSERT"))
322         {
323             SetInsert(ENABLE_INSERT_MODE);
324         }
325         else if (!_wcsicmp(pArgStart, L"/OVERSTRIKE"))
326         {
327             SetInsert(0);
328         }
329         else if (!_wcsicmp(pArgStart, L"/M") ||
330                  !_wcsicmp(pArgStart, L"/MACROS"))
331         {
332             PrintMacros(pszExeName, L"");
333         }
334         else if (!_wcsnicmp(pArgStart, L"/M:",      3) ||
335                  !_wcsnicmp(pArgStart, L"/MACROS:", 8))
336         {
337             LPWSTR exe = RemoveQuotes(wcschr(pArgStart, L':') + 1);
338             if (!_wcsicmp(exe, L"ALL"))
339                 PrintAllMacros();
340             else
341                 PrintMacros(exe, L"");
342         }
343         else if (!_wcsnicmp(pArgStart, L"/MACROFILE=", 11))
344         {
345             ReadFromFile(RemoveQuotes(pArgStart + 11));
346         }
347         else
348         {
349             /* This is the beginning of a macro definition. It includes
350              * the entire remainder of the line, so first put back the
351              * character that we replaced with NULL. */
352             *pArgEnd = tmp;
353             return SetMacro(pArgStart);
354         }
355 
356         if (!tmp) break;
357         pArgEnd++;
358     }
359 
360     return 0;
361 }
362