1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | |
5 | #include <stdio.h> |
6 | #include "consoleargs.h" |
7 | #include <strsafe.h> |
8 | |
9 | typedef unsigned char byte; |
10 | |
11 | size_t SafeStrCopy( _In_ LPCWSTR wszSrc, _In_ size_t cchSrc, _Out_ LPWSTR wszDest, _In_ size_t cchDest) |
12 | { |
13 | if (cchSrc == (size_t)-1) |
14 | cchSrc = wcslen(wszSrc); |
15 | |
16 | if (cchSrc >= cchDest) { |
17 | SetLastError(ERROR_FILENAME_EXCED_RANGE); |
18 | return 0; |
19 | } |
20 | |
21 | if (FAILED(StringCchCopyNW( wszDest, cchDest, wszSrc, cchSrc))) { |
22 | SetLastError(ERROR_FILENAME_EXCED_RANGE); |
23 | return 0; |
24 | } |
25 | return cchSrc; |
26 | } |
27 | |
28 | size_t SafeStrLower( _In_ LPCWSTR wszSrc, _In_ size_t cchSrc, _Out_ LPWSTR wszDest, _In_ size_t cchDest) |
29 | { |
30 | if (cchSrc == (size_t)-1) |
31 | cchSrc = wcslen(wszSrc); |
32 | |
33 | if (cchSrc >= cchDest) { |
34 | SetLastError(ERROR_FILENAME_EXCED_RANGE); |
35 | return 0; |
36 | } |
37 | |
38 | SafeStrCopy(wszSrc, cchSrc, wszDest, cchDest); |
39 | _wcslwr_s((WCHAR*)wszDest, cchDest); |
40 | return wcslen(wszDest); |
41 | } |
42 | |
43 | inline int HexValue (WCHAR c) |
44 | { |
45 | return (c >= '0' && c <= '9') ? c - '0' : (c & 0xdf) - 'A' + 10; |
46 | } |
47 | |
48 | #ifndef PLATFORM_UNIX |
49 | // Get canonical file path from a user specified path. wszSrcfileName can include relative paths, etc. |
50 | // Much of this function was taken from csc.exe. |
51 | DWORD GetCanonFilePath(_In_z_ LPCWSTR wszSrcFileName, _Out_z_cap_(cchDestFileName) LPWSTR wszDestFileName, _In_ DWORD cchDestFileName, _In_ bool fPreserveSrcCasing) |
52 | { |
53 | DWORD full_len; |
54 | WCHAR * full_path = new WCHAR[cchDestFileName]; // an intermediate buffer |
55 | WCHAR * temp_path = new WCHAR[cchDestFileName]; // Used if FindFile fails |
56 | WCHAR * full_cur; |
57 | WCHAR * out_cur; |
58 | WCHAR * out_end; |
59 | bool hasDrive = false; |
60 | |
61 | memset(full_path, 0, cchDestFileName * sizeof(WCHAR)); |
62 | out_cur = wszDestFileName; |
63 | out_end = out_cur + cchDestFileName; |
64 | if (wszSrcFileName != wszDestFileName) |
65 | *out_cur = L'\0'; |
66 | full_cur = full_path; |
67 | |
68 | // Replace '\\' with single backslashes in paths, because W_GetFullPathName fails to do this on win9x. |
69 | size_t i = 0; |
70 | size_t j = 0; |
71 | size_t length = wcslen(wszSrcFileName); |
72 | while (j<length) |
73 | { |
74 | // UNC paths start with '\\' so skip the first character if it is a backslash. |
75 | if (j!= 0 && wszSrcFileName[j] == '\\' && wszSrcFileName[j+1] == '\\') |
76 | j++; |
77 | else |
78 | temp_path[i++] = wszSrcFileName[j++]; |
79 | if (i >= cchDestFileName) { |
80 | SetLastError(ERROR_FILENAME_EXCED_RANGE); |
81 | goto FAIL; |
82 | } |
83 | } |
84 | temp_path[i] = L'\0'; |
85 | |
86 | full_len = GetFullPathNameW(temp_path, cchDestFileName, full_path, NULL); |
87 | if (wszSrcFileName == wszDestFileName) |
88 | wszDestFileName[cchDestFileName-1] = L'\0'; |
89 | if (full_len == 0) { |
90 | goto FAIL; |
91 | } else if (full_len >= cchDestFileName) { |
92 | SetLastError(ERROR_FILENAME_EXCED_RANGE); |
93 | goto FAIL; |
94 | } |
95 | |
96 | // Allow only 1 ':' for drives and no long paths with "\\?\" |
97 | if (((full_path[0] >= L'a' && full_path[0] <= L'z') || |
98 | (full_path[0] >= L'A' && full_path[0] <= L'Z')) && |
99 | full_path[1] == L':') |
100 | hasDrive = true; |
101 | |
102 | // We don't allow colons (except after the drive letter) |
103 | // long paths beginning with "\\?\" |
104 | // devices beginning with "\\.\" |
105 | // or wildcards |
106 | // or characters 0-31 |
107 | if (wcschr( full_path + (hasDrive ? 2 : 0), W(':')) != NULL || |
108 | wcsncmp( full_path, W("\\\\?\\" ), 4) == 0 || |
109 | wcsncmp( full_path, W("\\\\.\\" ), 4) == 0 || |
110 | wcspbrk(full_path, W("?*\x1\x2\x3\x4\x5\x6\x7\x8\x9" ) |
111 | W("\xA\xB\xC\xD\xE\xF\x10\x11\x12\x13\x14\x15" ) |
112 | W("\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\0" )) != NULL) { |
113 | SetLastError(ERROR_INVALID_NAME); |
114 | goto FAIL; |
115 | } |
116 | |
117 | |
118 | if (hasDrive) { |
119 | size_t len = SafeStrLower( full_path, 3, out_cur, out_end - out_cur); |
120 | if (len == 0) |
121 | goto FAIL; |
122 | |
123 | full_cur += 3; |
124 | out_cur += len; |
125 | |
126 | } else if (full_path[0] == L'\\' && full_path[1] == L'\\') { |
127 | // Must be a UNC pathname, so lower-case the server and share |
128 | // since there's no known way to get the 'correct casing' |
129 | WCHAR * slash = wcschr(full_path + 2, L'\\'); |
130 | // slash should now point to the backslash between the server and share |
131 | if (slash == NULL || slash == full_path + 2) { |
132 | SetLastError(ERROR_INVALID_NAME); |
133 | goto FAIL; |
134 | } |
135 | |
136 | slash = wcschr(slash + 1, L'\\'); |
137 | if (slash == NULL) { |
138 | slash = full_path + wcslen(full_path); |
139 | } else if (slash[-1] == L'\\') { |
140 | // An empty share-name? |
141 | SetLastError(ERROR_INVALID_NAME); |
142 | goto FAIL; |
143 | } else |
144 | slash++; |
145 | // slash should now point to char after the slash after the share name |
146 | // or the end of the sharename if there's no trailing slash |
147 | |
148 | size_t len = SafeStrLower( full_path, slash - full_path, out_cur, out_end - out_cur); |
149 | if (len == 0) |
150 | goto FAIL; |
151 | |
152 | full_cur = slash; |
153 | out_cur += len; |
154 | |
155 | } else { |
156 | // Not a drive-leter path or a UNC path, so assume it's invalid |
157 | SetLastError(ERROR_INVALID_NAME); |
158 | goto FAIL; |
159 | } |
160 | |
161 | // We either have a lower-cased drive letter or a UNC name |
162 | // with it's trailing slash |
163 | // out_cur points to the trailing NULL |
164 | // full_cur points to the character after the slash |
165 | |
166 | // Now iterate over each element of the path and attempt to canonicalize it |
167 | // It's possible for this loop to never run |
168 | // (for strings like "C:\" or "\\unc\share" or "\\unc\share2\") |
169 | while (*full_cur) { |
170 | WIN32_FIND_DATAW find_data; |
171 | bool hasSlash = true; |
172 | WCHAR * slash = wcschr(full_cur, '\\'); |
173 | if (slash == NULL) { |
174 | // This means we're on the last element of the path |
175 | // so work with everything left in the string |
176 | hasSlash = false; |
177 | slash = full_cur + wcslen(full_cur); |
178 | } |
179 | |
180 | // Check to make sure we have enough room for the next part of the path |
181 | if (out_cur + (slash - full_cur) >= out_end) { |
182 | SetLastError(ERROR_FILENAME_EXCED_RANGE); |
183 | goto FAIL; |
184 | } |
185 | |
186 | // Copy over the next path part into the output buffer |
187 | // so we can run FindFile to get the correct casing/long filename |
188 | memcpy(out_cur, full_cur, (BYTE*)slash - (BYTE*)full_cur); |
189 | out_cur[slash - full_cur] = L'\0'; |
190 | HANDLE hFind = FindFirstFileW(wszDestFileName, &find_data); |
191 | if (hFind == INVALID_HANDLE_VALUE) { |
192 | size_t temp_len; |
193 | |
194 | // We coundn't find the file, the general causes are the file doesn't exist |
195 | // or we don't have access to it. Either way we still try to get a canonical filename |
196 | // but preserve the passed in casing for the filename |
197 | |
198 | if (!hasSlash && fPreserveSrcCasing) { |
199 | // This is the last component in the filename, we should preserve the user's input text |
200 | // even if we can't find it |
201 | out_cur += slash - full_cur; |
202 | full_cur = slash; |
203 | break; |
204 | } |
205 | |
206 | // This will succeed even if we don't have access to the file |
207 | // (And on NT4 if the filename is already in 8.3 form) |
208 | temp_len = GetShortPathNameW(wszDestFileName, temp_path, cchDestFileName); |
209 | if (temp_len == 0) { |
210 | // GetShortPathName failed, we have no other way of figuring out the |
211 | // The filename, so just lowercase it so it hashes in a case-insensitive manner |
212 | |
213 | if (!hasSlash) { |
214 | // If it doesn't have a slash, then it must be the last part of the filename, |
215 | // so don't lowercase it, preserve whatever casing the user gave |
216 | temp_len = SafeStrCopy( full_cur, slash - full_cur, out_cur, out_end - out_cur); |
217 | } else { |
218 | temp_len = SafeStrLower( full_cur, slash - full_cur, out_cur, out_end - out_cur); |
219 | } |
220 | if (temp_len == 0) |
221 | goto FAIL; |
222 | |
223 | full_cur = slash; |
224 | out_cur += temp_len; |
225 | |
226 | } else if (temp_len >= cchDestFileName) { |
227 | // The short filename is longer than the whole thing? |
228 | // This shouldn't ever happen, right? |
229 | SetLastError(ERROR_FILENAME_EXCED_RANGE); |
230 | goto FAIL; |
231 | } else { |
232 | // GetShortPathName succeeded with a path that is less than BUFFER_LEN |
233 | // find the last slash and copy it. (We don't want to copy previous |
234 | // path components that we've already 'resolved') |
235 | // However, GetShortPathName doesn't always correct the casing |
236 | // so as a safe-guard, lower-case it (unless it's the last filename) |
237 | WCHAR * temp_slash = wcsrchr(temp_path, L'\\'); |
238 | |
239 | temp_slash++; |
240 | size_t len = 0; |
241 | if (!hasSlash) { |
242 | len = SafeStrCopy( temp_slash, -1, out_cur, out_end - out_cur); |
243 | } else { |
244 | len = SafeStrLower( temp_slash, -1, out_cur, out_end - out_cur); |
245 | } |
246 | if (len == 0) |
247 | goto FAIL; |
248 | |
249 | full_cur = slash; |
250 | out_cur += len; |
251 | |
252 | } |
253 | } else { |
254 | // Copy over the properly cased long filename |
255 | FindClose(hFind); |
256 | size_t name_len = wcslen(find_data.cFileName); |
257 | if (out_cur + name_len + (hasSlash ? 1 : 0) >= out_end) { |
258 | SetLastError(ERROR_FILENAME_EXCED_RANGE); |
259 | goto FAIL; |
260 | } |
261 | |
262 | // out_cur already has the filename with the input casing, so we can just leave it alone |
263 | // if this is not a directory name and the caller asked to perserve the casing |
264 | if (hasSlash || !fPreserveSrcCasing) { |
265 | memcpy(out_cur, find_data.cFileName, name_len * sizeof(WCHAR)); |
266 | } |
267 | else if (name_len != (slash - full_cur) || _wcsnicmp(find_data.cFileName, full_cur, name_len) != 0) { |
268 | // The user asked us to preserve the casing of the filename |
269 | // and the filename is different by more than just casing so report |
270 | // an error indicating we can't create the file |
271 | SetLastError(ERROR_FILE_EXISTS); |
272 | goto FAIL; |
273 | } |
274 | |
275 | out_cur += name_len; |
276 | full_cur = slash; |
277 | } |
278 | |
279 | if (hasSlash) { |
280 | if (out_cur + 1 >= out_end) { |
281 | SetLastError(ERROR_FILENAME_EXCED_RANGE); |
282 | goto FAIL; |
283 | } |
284 | full_cur++; |
285 | *out_cur++ = L'\\'; |
286 | } |
287 | *out_cur = '\0'; |
288 | } |
289 | |
290 | return (DWORD)(out_cur - wszDestFileName); |
291 | |
292 | FAIL: |
293 | if (full_path) |
294 | { |
295 | delete [] full_path; |
296 | } |
297 | if (temp_path) |
298 | { |
299 | delete [] temp_path; |
300 | } |
301 | return 0; |
302 | } |
303 | #endif // !PLATFORM_UNIX |
304 | |
305 | bool FreeString(LPCWSTR szText) |
306 | { |
307 | if (szText) |
308 | delete [] (const_cast<LPWSTR>(szText)); |
309 | return true; |
310 | } |
311 | |
312 | bool IsWhitespace(WCHAR c) |
313 | { |
314 | return c == L' ' || c == L'\t' || c == L'\n' || c == L'\r'; |
315 | } |
316 | |
317 | void ConsoleArgs::CleanUpArgs() |
318 | { |
319 | while (m_listArgs) |
320 | { |
321 | WStrList * next = m_listArgs->next; |
322 | if (m_listArgs->arg) |
323 | delete [] m_listArgs->arg; |
324 | delete m_listArgs; |
325 | m_listArgs = next; |
326 | } |
327 | |
328 | if (m_rgArgs) |
329 | delete[] m_rgArgs; |
330 | |
331 | m_rgArgs = NULL; |
332 | |
333 | if(m_lastErrorMessage) |
334 | { |
335 | delete[] m_lastErrorMessage; |
336 | } |
337 | } |
338 | |
339 | bool ConsoleArgs::GetFullFileName(LPCWSTR szSource, __out_ecount(cchFilenameBuffer) LPWSTR filenameBuffer, DWORD cchFilenameBuffer, bool fOutputFilename) |
340 | { |
341 | #ifdef PLATFORM_UNIX |
342 | WCHAR tempBuffer[MAX_LONGPATH]; |
343 | memset(filenameBuffer, 0, cchFilenameBuffer * sizeof(WCHAR)); |
344 | if (!PathCanonicalizeW(tempBuffer, szSource) || |
345 | StringCchCopyW(filenameBuffer, cchFilenameBuffer, tempBuffer) != S_OK) |
346 | #else |
347 | if (0 == GetCanonFilePath( szSource, filenameBuffer, cchFilenameBuffer, fOutputFilename)) |
348 | #endif |
349 | { |
350 | if (filenameBuffer[0] == L'\0') |
351 | { |
352 | // This could easily fail because of an overflow, but that's OK |
353 | // we only want what will fit in the output buffer so we can print |
354 | // a good error message |
355 | StringCchCopyW(filenameBuffer, cchFilenameBuffer - 4, szSource); |
356 | // Don't cat on the ..., only stick it in the last 4 characters |
357 | // to indicate truncation (if the string is short than this it just won't print) |
358 | StringCchCopyW(filenameBuffer + cchFilenameBuffer - 4, 4, W("..." )); |
359 | } |
360 | return false; |
361 | } |
362 | return true; |
363 | } |
364 | |
365 | // |
366 | // Clear previous error message if any and set the new one by copying into m_lastErrorMessage. |
367 | // We are responsible for freeing the memory destruction. |
368 | // |
369 | void ConsoleArgs::SetErrorMessage(__in LPCWSTR pwzMessage) |
370 | { |
371 | if (m_lastErrorMessage != nullptr) |
372 | { |
373 | delete[] m_lastErrorMessage; |
374 | } |
375 | m_errorOccurred = true; |
376 | m_lastErrorMessage = new WCHAR[wcslen(pwzMessage) + 1]; |
377 | if (m_lastErrorMessage == nullptr) |
378 | { |
379 | // |
380 | // Out of memory allocating error string |
381 | // |
382 | m_lastErrorMessage = kOutOfMemory; |
383 | return; |
384 | } |
385 | |
386 | wcscpy_s((LPWSTR)m_lastErrorMessage, wcslen(pwzMessage) + 1, pwzMessage); |
387 | } |
388 | |
389 | // |
390 | // Create a simple leaf tree node with the given text |
391 | // |
392 | b_tree * ConsoleArgs::MakeLeaf(LPCWSTR text) |
393 | { |
394 | b_tree * t = NULL; |
395 | size_t name_len = wcslen(text) + 1; |
396 | LPWSTR szCopy = new WCHAR[name_len]; |
397 | |
398 | if (!szCopy) |
399 | { |
400 | return NULL; |
401 | } |
402 | |
403 | HRESULT hr; |
404 | hr = StringCchCopyW (szCopy, name_len, text); |
405 | |
406 | t = new b_tree(szCopy); |
407 | if (!t) |
408 | { |
409 | delete [] szCopy; |
410 | return NULL; |
411 | } |
412 | return t; |
413 | } |
414 | |
415 | // |
416 | // Free the memory allocated by the tree (recursive) |
417 | // |
418 | void ConsoleArgs::CleanupTree(b_tree *root) |
419 | { |
420 | if (root == NULL) |
421 | return ; |
422 | root->InOrderWalk(FreeString); |
423 | delete root; |
424 | } |
425 | |
426 | // |
427 | // Search the binary tree and add the given string |
428 | // return true if it was added or false if it already |
429 | // exists |
430 | // |
431 | HRESULT ConsoleArgs::TreeAdd(b_tree **root, LPCWSTR add |
432 | ) |
433 | { |
434 | // Special case - init the tree if it |
435 | // doesn't already exist |
436 | if (*root == NULL) |
437 | { |
438 | *root = MakeLeaf(add |
439 | ); |
440 | return *root == NULL ? E_OUTOFMEMORY : S_OK; |
441 | } |
442 | |
443 | size_t name_len = wcslen(add |
444 | ) + 1; |
445 | LPWSTR szCopy = new WCHAR[name_len]; |
446 | |
447 | if (!szCopy) |
448 | { |
449 | return NULL; |
450 | } |
451 | |
452 | HRESULT hr = StringCchCopyW (szCopy, name_len, add |
453 | ); |
454 | // otherwise, just let the template do the work |
455 | hr = (*root)->Add(szCopy, _wcsicmp); |
456 | |
457 | if (hr != S_OK) // S_FALSE means it already existed |
458 | delete [] szCopy; |
459 | |
460 | return hr; |
461 | } |
462 | |
463 | // |
464 | // Parse the text into a list of argument |
465 | // return the total count |
466 | // and set 'args' to point to the last list element's 'next' |
467 | // This function assumes the text is NULL terminated |
468 | // |
469 | void ConsoleArgs::TextToArgs(LPCWSTR szText, WStrList ** listReplace) |
470 | { |
471 | WStrList **argLast; |
472 | const WCHAR *pCur; |
473 | size_t iSlash; |
474 | int iCount; |
475 | |
476 | argLast = listReplace; |
477 | pCur = szText; |
478 | iCount = 0; |
479 | |
480 | // Guaranteed that all tokens are no bigger than the entire file. |
481 | LPWSTR szTemp = new WCHAR[wcslen(szText) + 1]; |
482 | if (!szTemp) |
483 | { |
484 | return ; |
485 | } |
486 | while (*pCur != '\0') |
487 | { |
488 | WCHAR *pPut, *pFirst, *pLast; |
489 | WCHAR chIllegal; |
490 | |
491 | LEADINGWHITE: |
492 | while (IsWhitespace( *pCur) && *pCur != '\0') |
493 | pCur++; |
494 | |
495 | if (*pCur == '\0') |
496 | break; |
497 | else if (*pCur == L'#') |
498 | { |
499 | while ( *pCur != '\0' && *pCur != '\n') |
500 | pCur++; // Skip to end of line |
501 | goto LEADINGWHITE; |
502 | } |
503 | |
504 | // The treatment of quote marks is a bit different than the standard |
505 | // treatment. We only remove quote marks at the very beginning and end of the |
506 | // string. We still consider interior quotemarks for space ignoring purposes. |
507 | // All the below are considered a single argument: |
508 | // "foo bar" -> foo bar |
509 | // "foo bar";"baz" -> "foo bar";"baz" |
510 | // fo"o ba"r -> fo"o ba"r |
511 | // |
512 | // Additionally, in order to allow multi-line arguments we allow a ^ at the |
513 | // end of a line to specify "invisible space". A character sequence matching |
514 | // "\^(\r\n|\r|\n)[ \t]*" will be completely ignored (whether inside a quoted |
515 | // string or not). The below transformations occur (and represent a single |
516 | // argument): |
517 | // "foo ^ |
518 | // bar" -> foo bar |
519 | // foo;^ |
520 | // bar -> foo;bar |
521 | // Notes: |
522 | // 1. Comments are not recognized in a multi-line argument |
523 | // 2. A caret escapes only one new-line followed by an arbitrary number of |
524 | // tabs or blanks. |
525 | // The following will be parsed as the names suggest, into several different |
526 | // arguments: |
527 | // /option1 ^ |
528 | // val1_1;^ |
529 | // val1_2;^ |
530 | // val1_3;^ |
531 | // |
532 | // /option2 |
533 | // /opt^ |
534 | // ion3 -> /option1 val1_1;val1_2;val1_3; /option2 /option3 |
535 | int cQuotes = 0; |
536 | pPut = pFirst = szTemp; |
537 | chIllegal = 0; |
538 | while ((!IsWhitespace( *pCur) || !!(cQuotes & 1)) && *pCur != '\0') |
539 | { |
540 | switch (*pCur) |
541 | { |
542 | // All this weird slash stuff follows the standard argument processing routines |
543 | case L'\\': |
544 | iSlash = 0; |
545 | // Copy and advance while counting slashes |
546 | while (*pCur == L'\\') |
547 | { |
548 | *pPut++ = *pCur++; |
549 | iSlash++; |
550 | } |
551 | |
552 | // Slashes not followed by a quote character don't matter now |
553 | if (*pCur != L'\"') |
554 | break; |
555 | |
556 | // If there's an odd count of slashes, it's escaping the quote |
557 | // Otherwise the quote is a quote |
558 | if ((iSlash & 1) == 0) |
559 | { |
560 | ++cQuotes; |
561 | } |
562 | *pPut++ = *pCur++; |
563 | break; |
564 | |
565 | case L'\"': |
566 | ++cQuotes; |
567 | *pPut++ = *pCur++; |
568 | break; |
569 | |
570 | case L'^': |
571 | // ignore this sequence: \^[\r\n|\r|\n]( \t)* |
572 | if (pCur[1] == L'\r' || pCur[1] == L'\n') |
573 | { |
574 | if (pCur[1] == L'\r' && pCur[2] == L'\n') |
575 | pCur += 3; |
576 | else |
577 | pCur += 2; |
578 | |
579 | while (*pCur == L' ' || *pCur == L'\t') |
580 | ++pCur; |
581 | } |
582 | else |
583 | { |
584 | *pPut++ = *pCur++; // Copy the caret and advance |
585 | } |
586 | break; |
587 | |
588 | case L'\x01': |
589 | case L'\x02': |
590 | case L'\x03': |
591 | case L'\x04': |
592 | case L'\x05': |
593 | case L'\x06': |
594 | case L'\x07': |
595 | case L'\x08': |
596 | case L'\x09': |
597 | case L'\x0A': |
598 | case L'\x0B': |
599 | case L'\x0C': |
600 | case L'\x0D': |
601 | case L'\x0E': |
602 | case L'\x0F': |
603 | case L'\x10': |
604 | case L'\x11': |
605 | case L'\x12': |
606 | case L'\x13': |
607 | case L'\x14': |
608 | case L'\x15': |
609 | case L'\x16': |
610 | case L'\x17': |
611 | case L'\x18': |
612 | case L'\x19': |
613 | case L'\x1A': |
614 | case L'\x1B': |
615 | case L'\x1C': |
616 | case L'\x1D': |
617 | case L'\x1E': |
618 | case L'\x1F': |
619 | case L'|': |
620 | // Save the first legal character and skip over them |
621 | if (chIllegal == 0) |
622 | chIllegal = *pCur; |
623 | pCur++; |
624 | break; |
625 | |
626 | default: |
627 | *pPut++ = *pCur++; // Copy the char and advance |
628 | break; |
629 | } |
630 | } |
631 | |
632 | pLast = pPut; |
633 | *pPut++ = '\0'; |
634 | |
635 | // If the string is surrounded by quotes, with no interior quotes, remove them. |
636 | if (cQuotes == 2 && *pFirst == L'\"' && *(pLast - 1) == L'\"') |
637 | { |
638 | ++pFirst; |
639 | --pLast; |
640 | *pLast = L'\0'; |
641 | } |
642 | |
643 | if (chIllegal != 0) |
644 | { |
645 | SetErrorMessage(W("Illegal option character." )); |
646 | break; |
647 | } |
648 | |
649 | size_t cchLen = pLast - pFirst + 1; |
650 | WCHAR * szArgCopy = new WCHAR[cchLen]; |
651 | if (!szArgCopy || FAILED(StringCchCopyW(szArgCopy, cchLen, pFirst))) |
652 | { |
653 | SetErrorMessage(W("Out of memory." )); |
654 | break; |
655 | } |
656 | WStrList * listArgNew = new WStrList( szArgCopy, (*argLast)); |
657 | if (!listArgNew) |
658 | { |
659 | SetErrorMessage(W("Out of memory." )); |
660 | break; |
661 | } |
662 | |
663 | *argLast = listArgNew; |
664 | argLast = &listArgNew->next; |
665 | } |
666 | |
667 | delete[] szTemp; |
668 | |
669 | } |
670 | |
671 | // |
672 | // Pass in the command line args, argc and argv |
673 | // |
674 | // We expand any response files that may be contained in the args and return a new |
675 | // set of args, pargc2 and pppargv2 that contain the full flat command line. |
676 | // |
677 | bool ConsoleArgs::ExpandResponseFiles(__in int argc, __deref_in_ecount(argc) const LPCWSTR * argv, int * pargc2, __deref_out_ecount(*pargc2) LPWSTR ** pppargv2) |
678 | { |
679 | *pargc2 = 0; |
680 | *pppargv2 = NULL; |
681 | WStrList **argLast = &m_listArgs; |
682 | while (argc > 0) |
683 | { |
684 | // Make a copy of the original var args so we can just delete[] everything |
685 | // once parsing is done: original args and new args from response files |
686 | // mixed in amongst the originals. |
687 | LPWSTR copyArg = new WCHAR[wcslen(argv[0]) + 1]; |
688 | if (!copyArg) |
689 | { |
690 | SetErrorMessage(W("Out of memory." )); |
691 | return false; |
692 | } |
693 | wcscpy_s(copyArg, wcslen(argv[0]) + 1, argv[0]); |
694 | |
695 | WStrList * listArgNew = new WStrList(copyArg, (*argLast)); |
696 | if (!listArgNew) |
697 | { |
698 | SetErrorMessage(W("Out of memory." )); |
699 | return false; |
700 | } |
701 | |
702 | *argLast = listArgNew; |
703 | argLast = &listArgNew->next; |
704 | |
705 | argc--; |
706 | argv++; |
707 | } |
708 | |
709 | // Process Response Files |
710 | ProcessResponseArgs(); |
711 | if (m_errorOccurred) |
712 | return false; |
713 | |
714 | // Now convert to an argc/argv form for remaining processing. |
715 | int newArgc = 0; |
716 | for (WStrList * listCurArg = m_listArgs; listCurArg != NULL; listCurArg = listCurArg->next) |
717 | { |
718 | if (listCurArg->arg) |
719 | ++newArgc; |
720 | } |
721 | |
722 | m_rgArgs = new LPWSTR[newArgc]; |
723 | if (!m_rgArgs) |
724 | { |
725 | SetErrorMessage(W("Out of memory." )); |
726 | return false; |
727 | } |
728 | int i = 0; |
729 | for (WStrList * listCurArg = m_listArgs; listCurArg != NULL; listCurArg = listCurArg->next) |
730 | { |
731 | if (listCurArg->arg) |
732 | { |
733 | LPWSTR newString = new WCHAR[wcslen(listCurArg->arg) + 1]; |
734 | wcscpy_s(newString, wcslen(listCurArg->arg) + 1, listCurArg->arg); |
735 | m_rgArgs[i++] = newString; |
736 | } |
737 | } |
738 | |
739 | *pargc2 = newArgc; |
740 | *pppargv2 = m_rgArgs; |
741 | return !m_errorOccurred; |
742 | } |
743 | |
744 | // |
745 | // Read file to end, converting to unicode |
746 | // ppwzTextBuffer is allocated. Caller is responsible for freeing |
747 | // |
748 | bool ConsoleArgs::ReadTextFile(LPCWSTR pwzFilename, __deref_out LPWSTR *ppwzTextBuffer) |
749 | { |
750 | bool success = false; |
751 | char *bufA = nullptr; |
752 | WCHAR *bufW = nullptr; |
753 | |
754 | HANDLE hFile = CreateFile(pwzFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); |
755 | if (hFile == INVALID_HANDLE_VALUE) |
756 | { |
757 | SetErrorMessage(W("Cannot open response file." )); |
758 | goto ErrExit; |
759 | } |
760 | |
761 | { |
762 | DWORD size = GetFileSize(hFile, NULL); |
763 | bufA = new char[size]; |
764 | |
765 | if (!bufA) |
766 | { |
767 | SetErrorMessage(W("Out of memory" )); |
768 | goto ErrExit; |
769 | } |
770 | DWORD numRead = 0; |
771 | if (!ReadFile(hFile, bufA, size, &numRead, NULL) || numRead != size) |
772 | { |
773 | SetErrorMessage(W("Failure reading response file." )); |
774 | goto ErrExit; |
775 | } |
776 | |
777 | char *postByteOrderMarks = bufA; |
778 | |
779 | // |
780 | // If there are Byte Order Marks, skip them make sure they are ones that don't |
781 | // require us to handle the wrong endianness |
782 | // |
783 | |
784 | byte byte0 = (byte)bufA[0]; |
785 | byte byte1 = (byte)bufA[1]; |
786 | byte byte2 = (byte)bufA[2]; |
787 | byte byte3 = (byte)bufA[3]; |
788 | |
789 | bool alreadyUtf16 = false; |
790 | |
791 | if (byte0 == 0xEF && byte1 == 0xBB && byte2 == 0xBF) |
792 | { |
793 | postByteOrderMarks += 3; |
794 | size -= 3; |
795 | } |
796 | else if (byte0 == 0xFF && byte1 == 0xFE) |
797 | { |
798 | postByteOrderMarks += 2; |
799 | size -= 2; |
800 | alreadyUtf16 = true; |
801 | } |
802 | else if (byte0 == 0xFE && byte1 == 0xFF) |
803 | { |
804 | SetErrorMessage(W("Invalid response file format. Use little endian encoding with Unicode" )); |
805 | goto ErrExit; |
806 | } |
807 | else if ((byte0 == 0xFF && byte1 == 0xFE && byte2 == 0x00 && byte3 == 0x00) || |
808 | (byte0 == 0x00 && byte1 == 0x00 && byte2 == 0xFE && byte3 == 0xFF)) |
809 | { |
810 | SetErrorMessage(W("Invalid response file format. Use ANSI, UTF-8, or UTF-16" )); |
811 | goto ErrExit; |
812 | } |
813 | |
814 | if (alreadyUtf16) |
815 | { |
816 | // |
817 | // File is already formatted as UTF-16; just copy the bytes into the output buffer |
818 | // |
819 | int requiredSize = size + 2; // space for 2 nullptr bytes |
820 | |
821 | // Sanity check - requiredSize better be an even number since we're dealing with UTF-16 |
822 | if (requiredSize % 2 != 0) |
823 | { |
824 | SetErrorMessage(W("Response file corrupt. Expected UTF-16 encoding but we had an odd number of bytes" )); |
825 | goto ErrExit; |
826 | } |
827 | |
828 | requiredSize /= 2; |
829 | |
830 | bufW = new WCHAR[requiredSize]; |
831 | if (!bufW) |
832 | { |
833 | SetErrorMessage(W("Out of memory" )); |
834 | goto ErrExit; |
835 | } |
836 | |
837 | memcpy(bufW, postByteOrderMarks, size); |
838 | bufW[requiredSize - 1] = L'\0'; |
839 | } |
840 | else |
841 | { |
842 | // |
843 | // File is formated as ANSI or UTF-8 and needs converting to UTF-16 |
844 | // |
845 | int requiredSize = MultiByteToWideChar(CP_UTF8, 0, postByteOrderMarks, size, nullptr, 0); |
846 | bufW = new WCHAR[requiredSize + 1]; |
847 | if (!bufW) |
848 | { |
849 | SetErrorMessage(W("Out of memory" )); |
850 | goto ErrExit; |
851 | } |
852 | |
853 | if (!MultiByteToWideChar(CP_UTF8, 0, postByteOrderMarks, size, bufW, requiredSize)) |
854 | { |
855 | SetErrorMessage(W("Failure reading response file." )); |
856 | goto ErrExit; |
857 | } |
858 | |
859 | bufW[requiredSize] = L'\0'; |
860 | } |
861 | |
862 | *ppwzTextBuffer = bufW; |
863 | |
864 | success = true; |
865 | } |
866 | |
867 | ErrExit: |
868 | if (bufA) |
869 | { |
870 | delete[] bufA; |
871 | } |
872 | CloseHandle(hFile); |
873 | return success; |
874 | } |
875 | |
876 | /* |
877 | * Process Response files on the command line |
878 | */ |
879 | void ConsoleArgs::ProcessResponseArgs() |
880 | { |
881 | HRESULT hr; |
882 | b_tree *response_files = NULL; |
883 | |
884 | WCHAR szFilename[MAX_LONGPATH]; |
885 | |
886 | for (WStrList * listCurArg = m_listArgs; |
887 | listCurArg != NULL && !m_errorOccurred; |
888 | listCurArg = listCurArg->next) |
889 | { |
890 | WCHAR * szArg = listCurArg->arg; |
891 | |
892 | // Skip everything except Response files |
893 | if (szArg == NULL || szArg[0] != '@') |
894 | continue; |
895 | |
896 | if (wcslen(szArg) == 1) |
897 | { |
898 | SetErrorMessage(W("No response file specified" )); |
899 | goto CONTINUE; |
900 | } |
901 | |
902 | // Check for duplicates |
903 | if (!GetFullFileName(&szArg[1], szFilename, MAX_LONGPATH, false)) |
904 | continue; |
905 | |
906 | |
907 | hr = TreeAdd(&response_files, szFilename); |
908 | if (hr == E_OUTOFMEMORY) |
909 | { |
910 | SetErrorMessage(W("Out of memory." )); |
911 | goto CONTINUE; |
912 | } |
913 | else if (hr == S_FALSE) |
914 | { |
915 | SetErrorMessage(W("Duplicate response file." )); |
916 | goto CONTINUE; |
917 | } |
918 | |
919 | { |
920 | LPWSTR pwzFileBuffer; |
921 | pwzFileBuffer = nullptr; |
922 | if (!ReadTextFile(szFilename, &pwzFileBuffer)) |
923 | { |
924 | goto CONTINUE; |
925 | } |
926 | |
927 | LPWSTR szActualText = nullptr; |
928 | #ifdef PLATFORM_UNIX |
929 | szActualText = pwzFileBuffer; |
930 | #else |
931 | DWORD dwNumChars = ExpandEnvironmentStrings(pwzFileBuffer, NULL, 0); |
932 | LPWSTR szExpandedBuffer = new WCHAR[dwNumChars]; |
933 | if (szExpandedBuffer != nullptr) |
934 | { |
935 | DWORD dwRetVal = ExpandEnvironmentStrings(pwzFileBuffer, szExpandedBuffer, dwNumChars); |
936 | |
937 | if (dwRetVal != 0) |
938 | { |
939 | szActualText = szExpandedBuffer; |
940 | } |
941 | else |
942 | { |
943 | // Expand failed |
944 | |
945 | } |
946 | } |
947 | #endif |
948 | |
949 | TextToArgs(szActualText, &listCurArg->next); |
950 | } |
951 | |
952 | CONTINUE: // remove the response file argument, and continue to the next. |
953 | listCurArg->arg = NULL; |
954 | } |
955 | |
956 | CleanupTree(response_files); |
957 | } |
958 | |
959 | |