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
9typedef unsigned char byte;
10
11size_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
28size_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
43inline 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.
51DWORD 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
292FAIL:
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
305bool FreeString(LPCWSTR szText)
306{
307 if (szText)
308 delete [] (const_cast<LPWSTR>(szText));
309 return true;
310}
311
312bool IsWhitespace(WCHAR c)
313{
314 return c == L' ' || c == L'\t' || c == L'\n' || c == L'\r';
315}
316
317void 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
339bool 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//
369void 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//
392b_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//
418void 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//
431HRESULT 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//
469void 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
491LEADINGWHITE:
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//
677bool 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//
748bool 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
867ErrExit:
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 */
879void 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
952CONTINUE: // remove the response file argument, and continue to the next.
953 listCurArg->arg = NULL;
954 }
955
956 CleanupTree(response_files);
957}
958
959