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