1 /*
2  * PROJECT:         ReactOS API tests
3  * LICENSE:         LGPLv2.1+ - See COPYING.LIB in the top level directory
4  * PURPOSE:         Test for ShellExecCmdLine
5  * PROGRAMMERS:     Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
6  */
7 #include "shelltest.h"
8 #include <shlwapi.h>
9 #include <strsafe.h>
10 #include <versionhelpers.h>
11 
12 #define NDEBUG
13 #include <debug.h>
14 #include <stdio.h>
15 
16 #ifndef SECL_NO_UI
17     #define SECL_NO_UI          0x2
18     #define SECL_LOG_USAGE      0x8
19     #define SECL_USE_IDLIST     0x10
20     #define SECL_ALLOW_NONEXE   0x20
21     #define SECL_RUNAS          0x40
22 #endif
23 
24 #define ShellExecCmdLine proxy_ShellExecCmdLine
25 
26 #define shell32_hInstance   GetModuleHandle(NULL)
27 #define IDS_FILE_NOT_FOUND  (-1)
28 
29 static const WCHAR wszOpen[] = L"open";
30 static const WCHAR wszExe[] = L".exe";
31 static const WCHAR wszCom[] = L".com";
32 
33 static __inline void __SHCloneStrW(WCHAR **target, const WCHAR *source)
34 {
35     *target = (WCHAR *)SHAlloc((lstrlenW(source) + 1) * sizeof(WCHAR) );
36     lstrcpyW(*target, source);
37 }
38 
39 // NOTE: You have to sync the following code to dll/win32/shell32/shlexec.cpp.
40 static LPCWSTR
41 SplitParams(LPCWSTR psz, LPWSTR pszArg0, size_t cchArg0)
42 {
43     LPCWSTR pch;
44     size_t ich = 0;
45     if (*psz == L'"')
46     {
47         // 1st argument is quoted. the string in quotes is quoted 1st argument.
48         // [pch] --> [pszArg0+ich]
49         for (pch = psz + 1; *pch && ich + 1 < cchArg0; ++ich, ++pch)
50         {
51             if (*pch == L'"' && pch[1] == L'"')
52             {
53                 // doubled double quotations found!
54                 pszArg0[ich] = L'"';
55             }
56             else if (*pch == L'"')
57             {
58                 // single double quotation found!
59                 ++pch;
60                 break;
61             }
62             else
63             {
64                 // otherwise
65                 pszArg0[ich] = *pch;
66             }
67         }
68     }
69     else
70     {
71         // 1st argument is unquoted. non-space sequence is 1st argument.
72         // [pch] --> [pszArg0+ich]
73         for (pch = psz; *pch && !iswspace(*pch) && ich + 1 < cchArg0; ++ich, ++pch)
74         {
75             pszArg0[ich] = *pch;
76         }
77     }
78     pszArg0[ich] = 0;
79 
80     // skip space
81     while (iswspace(*pch))
82         ++pch;
83 
84     return pch;
85 }
86 
87 HRESULT WINAPI ShellExecCmdLine(
88     HWND hwnd,
89     LPCWSTR pwszCommand,
90     LPCWSTR pwszStartDir,
91     int nShow,
92     LPVOID pUnused,
93     DWORD dwSeclFlags)
94 {
95     SHELLEXECUTEINFOW info;
96     DWORD dwSize, dwError, dwType, dwFlags = SEE_MASK_DOENVSUBST | SEE_MASK_NOASYNC;
97     LPCWSTR pszVerb = NULL;
98     WCHAR szFile[MAX_PATH], szFile2[MAX_PATH];
99     HRESULT hr;
100     LPCWSTR pchParams;
101     LPWSTR lpCommand = NULL;
102 
103     if (pwszCommand == NULL)
104         RaiseException(EXCEPTION_ACCESS_VIOLATION, EXCEPTION_NONCONTINUABLE,
105                        1, (ULONG_PTR*)pwszCommand);
106 
107     __SHCloneStrW(&lpCommand, pwszCommand);
108     StrTrimW(lpCommand, L" \t");
109 
110     if (dwSeclFlags & SECL_NO_UI)
111         dwFlags |= SEE_MASK_FLAG_NO_UI;
112     if (dwSeclFlags & SECL_LOG_USAGE)
113         dwFlags |= SEE_MASK_FLAG_LOG_USAGE;
114     if (dwSeclFlags & SECL_USE_IDLIST)
115         dwFlags |= SEE_MASK_INVOKEIDLIST;
116 
117     if (dwSeclFlags & SECL_RUNAS)
118     {
119         dwSize = 0;
120         hr = AssocQueryStringW(0, ASSOCSTR_COMMAND, lpCommand, L"RunAs", NULL, &dwSize);
121         if (SUCCEEDED(hr) && dwSize != 0)
122         {
123             pszVerb = L"runas";
124         }
125     }
126 
127     if (UrlIsFileUrlW(lpCommand))
128     {
129         StringCchCopyW(szFile, _countof(szFile), lpCommand);
130         pchParams = NULL;
131     }
132     else
133     {
134         pchParams = SplitParams(lpCommand, szFile, _countof(szFile));
135         if (szFile[0] != UNICODE_NULL && szFile[1] == L':' &&
136             szFile[2] == UNICODE_NULL)
137         {
138             PathAddBackslashW(szFile);
139         }
140 
141         WCHAR szCurDir[MAX_PATH];
142         GetCurrentDirectoryW(_countof(szCurDir), szCurDir);
143         if (pwszStartDir)
144         {
145             SetCurrentDirectoryW(pwszStartDir);
146         }
147 
148         if (PathIsRelativeW(szFile) &&
149             GetFullPathNameW(szFile, _countof(szFile2), szFile2, NULL) &&
150             PathFileExistsW(szFile2))
151         {
152             StringCchCopyW(szFile, _countof(szFile), szFile2);
153         }
154         else if (SearchPathW(NULL, szFile, NULL, _countof(szFile2), szFile2, NULL) ||
155                  SearchPathW(NULL, szFile, wszExe, _countof(szFile2), szFile2, NULL) ||
156                  SearchPathW(NULL, szFile, wszCom, _countof(szFile2), szFile2, NULL) ||
157                  SearchPathW(pwszStartDir, szFile, NULL, _countof(szFile2), szFile2, NULL) ||
158                  SearchPathW(pwszStartDir, szFile, wszExe, _countof(szFile2), szFile2, NULL) ||
159                  SearchPathW(pwszStartDir, szFile, wszCom, _countof(szFile2), szFile2, NULL))
160         {
161             StringCchCopyW(szFile, _countof(szFile), szFile2);
162         }
163         else if (SearchPathW(NULL, lpCommand, NULL, _countof(szFile2), szFile2, NULL) ||
164                  SearchPathW(NULL, lpCommand, wszExe, _countof(szFile2), szFile2, NULL) ||
165                  SearchPathW(NULL, lpCommand, wszCom, _countof(szFile2), szFile2, NULL) ||
166                  SearchPathW(pwszStartDir, lpCommand, NULL, _countof(szFile2), szFile2, NULL) ||
167                  SearchPathW(pwszStartDir, lpCommand, wszExe, _countof(szFile2), szFile2, NULL) ||
168                  SearchPathW(pwszStartDir, lpCommand, wszCom, _countof(szFile2), szFile2, NULL))
169         {
170             StringCchCopyW(szFile, _countof(szFile), szFile2);
171             pchParams = NULL;
172         }
173 
174         if (pwszStartDir)
175         {
176             SetCurrentDirectoryW(szCurDir);
177         }
178 
179         if (!(dwSeclFlags & SECL_ALLOW_NONEXE))
180         {
181             if (!GetBinaryTypeW(szFile, &dwType))
182             {
183                 SHFree(lpCommand);
184 
185                 if (!(dwSeclFlags & SECL_NO_UI))
186                 {
187                     WCHAR szText[128 + MAX_PATH], szFormat[128];
188                     LoadStringW(shell32_hInstance, IDS_FILE_NOT_FOUND, szFormat, _countof(szFormat));
189                     StringCchPrintfW(szText, _countof(szText), szFormat, szFile);
190                     MessageBoxW(hwnd, szText, NULL, MB_ICONERROR);
191                 }
192                 return CO_E_APPNOTFOUND;
193             }
194         }
195         else
196         {
197             if (GetFileAttributesW(szFile) == INVALID_FILE_ATTRIBUTES)
198             {
199                 SHFree(lpCommand);
200 
201                 if (!(dwSeclFlags & SECL_NO_UI))
202                 {
203                     WCHAR szText[128 + MAX_PATH], szFormat[128];
204                     LoadStringW(shell32_hInstance, IDS_FILE_NOT_FOUND, szFormat, _countof(szFormat));
205                     StringCchPrintfW(szText, _countof(szText), szFormat, szFile);
206                     MessageBoxW(hwnd, szText, NULL, MB_ICONERROR);
207                 }
208                 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
209             }
210         }
211     }
212 
213     ZeroMemory(&info, sizeof(info));
214     info.cbSize = sizeof(info);
215     info.fMask = dwFlags;
216     info.hwnd = hwnd;
217     info.lpVerb = pszVerb;
218     info.lpFile = szFile;
219     info.lpParameters = (pchParams && *pchParams) ? pchParams : NULL;
220     info.lpDirectory = pwszStartDir;
221     info.nShow = nShow;
222     if (ShellExecuteExW(&info))
223     {
224         if (info.lpIDList)
225             CoTaskMemFree(info.lpIDList);
226 
227         SHFree(lpCommand);
228 
229         return S_OK;
230     }
231 
232     dwError = GetLastError();
233 
234     SHFree(lpCommand);
235 
236     return HRESULT_FROM_WIN32(dwError);
237 }
238 
239 #undef ShellExecCmdLine
240 
241 typedef HRESULT (WINAPI *SHELLEXECCMDLINE)(HWND, LPCWSTR, LPCWSTR, INT, LPVOID, DWORD);
242 SHELLEXECCMDLINE g_pShellExecCmdLine = NULL;
243 
244 typedef struct TEST_ENTRY
245 {
246     INT lineno;
247     BOOL result;
248     BOOL bAllowNonExe;
249     LPCWSTR pwszCommand;
250     LPCWSTR pwszStartDir;
251 } TEST_ENTRY;
252 
253 static WCHAR s_sub_program[MAX_PATH];
254 static WCHAR s_win_test_exe[MAX_PATH];
255 static WCHAR s_sys_bat_file[MAX_PATH];
256 static WCHAR s_cur_dir[MAX_PATH];
257 
258 static BOOL
259 GetSubProgramPath(void)
260 {
261     GetModuleFileNameW(NULL, s_sub_program, _countof(s_sub_program));
262     PathRemoveFileSpecW(s_sub_program);
263     PathAppendW(s_sub_program, L"shell32_apitest_sub.exe");
264 
265     if (!PathFileExistsW(s_sub_program))
266     {
267         PathRemoveFileSpecW(s_sub_program);
268         PathAppendW(s_sub_program, L"testdata\\shell32_apitest_sub.exe");
269 
270         if (!PathFileExistsW(s_sub_program))
271         {
272             return FALSE;
273         }
274     }
275 
276     return TRUE;
277 }
278 
279 static const TEST_ENTRY s_entries_1[] =
280 {
281     // NULL
282     { __LINE__, 0xBADFACE, FALSE, NULL, NULL },
283     { __LINE__, 0xBADFACE, FALSE, NULL, L"." },
284     { __LINE__, 0xBADFACE, FALSE, NULL, L"system32" },
285     { __LINE__, 0xBADFACE, FALSE, NULL, L"C:\\Program Files" },
286     { __LINE__, 0xBADFACE, TRUE, NULL, NULL },
287     { __LINE__, 0xBADFACE, TRUE, NULL, L"." },
288     { __LINE__, 0xBADFACE, TRUE, NULL, L"system32" },
289     { __LINE__, 0xBADFACE, TRUE, NULL, L"C:\\Program Files" },
290     // notepad
291     { __LINE__, TRUE, FALSE, L"notepad", NULL },
292     { __LINE__, TRUE, FALSE, L"notepad", L"." },
293     { __LINE__, TRUE, FALSE, L"notepad", L"system32" },
294     { __LINE__, TRUE, FALSE, L"notepad", L"C:\\Program Files" },
295     { __LINE__, TRUE, FALSE, L"notepad \"Test File.txt\"", NULL },
296     { __LINE__, TRUE, FALSE, L"notepad \"Test File.txt\"", L"." },
297     { __LINE__, TRUE, TRUE, L"notepad", NULL },
298     { __LINE__, TRUE, TRUE, L"notepad", L"." },
299     { __LINE__, TRUE, TRUE, L"notepad", L"system32" },
300     { __LINE__, TRUE, TRUE, L"notepad", L"C:\\Program Files" },
301     { __LINE__, TRUE, TRUE, L"notepad \"Test File.txt\"", NULL },
302     { __LINE__, TRUE, TRUE, L"notepad \"Test File.txt\"", L"." },
303     // notepad.exe
304     { __LINE__, TRUE, FALSE, L"notepad.exe", NULL },
305     { __LINE__, TRUE, FALSE, L"notepad.exe", L"." },
306     { __LINE__, TRUE, FALSE, L"notepad.exe", L"system32" },
307     { __LINE__, TRUE, FALSE, L"notepad.exe", L"C:\\Program Files" },
308     { __LINE__, TRUE, FALSE, L"notepad.exe \"Test File.txt\"", NULL },
309     { __LINE__, TRUE, FALSE, L"notepad.exe \"Test File.txt\"", L"." },
310     { __LINE__, TRUE, TRUE, L"notepad.exe", NULL },
311     { __LINE__, TRUE, TRUE, L"notepad.exe", L"." },
312     { __LINE__, TRUE, TRUE, L"notepad.exe", L"system32" },
313     { __LINE__, TRUE, TRUE, L"notepad.exe", L"C:\\Program Files" },
314     { __LINE__, TRUE, TRUE, L"notepad.exe \"Test File.txt\"", NULL },
315     { __LINE__, TRUE, TRUE, L"notepad.exe \"Test File.txt\"", L"." },
316     // C:\notepad.exe
317     { __LINE__, FALSE, FALSE, L"C:\\notepad.exe", NULL },
318     { __LINE__, FALSE, FALSE, L"C:\\notepad.exe", L"." },
319     { __LINE__, FALSE, FALSE, L"C:\\notepad.exe", L"system32" },
320     { __LINE__, FALSE, FALSE, L"C:\\notepad.exe", L"C:\\Program Files" },
321     { __LINE__, FALSE, FALSE, L"C:\\notepad.exe \"Test File.txt\"", NULL },
322     { __LINE__, FALSE, FALSE, L"C:\\notepad.exe \"Test File.txt\"", L"." },
323     { __LINE__, FALSE, TRUE, L"C:\\notepad.exe", NULL },
324     { __LINE__, FALSE, TRUE, L"C:\\notepad.exe", L"." },
325     { __LINE__, FALSE, TRUE, L"C:\\notepad.exe", L"system32" },
326     { __LINE__, FALSE, TRUE, L"C:\\notepad.exe", L"C:\\Program Files" },
327     { __LINE__, FALSE, TRUE, L"C:\\notepad.exe \"Test File.txt\"", NULL },
328     { __LINE__, FALSE, TRUE, L"C:\\notepad.exe \"Test File.txt\"", L"." },
329     // "notepad"
330     { __LINE__, TRUE, FALSE, L"\"notepad\"", NULL },
331     { __LINE__, TRUE, FALSE, L"\"notepad\"", L"." },
332     { __LINE__, TRUE, FALSE, L"\"notepad\"", L"system32" },
333     { __LINE__, TRUE, FALSE, L"\"notepad\"", L"C:\\Program Files" },
334     { __LINE__, TRUE, FALSE, L"\"notepad\" \"Test File.txt\"", NULL },
335     { __LINE__, TRUE, FALSE, L"\"notepad\" \"Test File.txt\"", L"." },
336     { __LINE__, TRUE, TRUE, L"\"notepad\"", NULL },
337     { __LINE__, TRUE, TRUE, L"\"notepad\"", L"." },
338     { __LINE__, TRUE, TRUE, L"\"notepad\"", L"system32" },
339     { __LINE__, TRUE, TRUE, L"\"notepad\"", L"C:\\Program Files" },
340     { __LINE__, TRUE, TRUE, L"\"notepad\" \"Test File.txt\"", NULL },
341     { __LINE__, TRUE, TRUE, L"\"notepad\" \"Test File.txt\"", L"." },
342     // "notepad.exe"
343     { __LINE__, TRUE, FALSE, L"\"notepad.exe\"", NULL },
344     { __LINE__, TRUE, FALSE, L"\"notepad.exe\"", L"." },
345     { __LINE__, TRUE, FALSE, L"\"notepad.exe\"", L"system32" },
346     { __LINE__, TRUE, FALSE, L"\"notepad.exe\"", L"C:\\Program Files" },
347     { __LINE__, TRUE, FALSE, L"\"notepad.exe\" \"Test File.txt\"", NULL },
348     { __LINE__, TRUE, FALSE, L"\"notepad.exe\" \"Test File.txt\"", L"." },
349     { __LINE__, TRUE, TRUE, L"\"notepad.exe\"", NULL },
350     { __LINE__, TRUE, TRUE, L"\"notepad.exe\"", L"." },
351     { __LINE__, TRUE, TRUE, L"\"notepad.exe\"", L"system32" },
352     { __LINE__, TRUE, TRUE, L"\"notepad.exe\"", L"C:\\Program Files" },
353     { __LINE__, TRUE, TRUE, L"\"notepad.exe\" \"Test File.txt\"", NULL },
354     { __LINE__, TRUE, TRUE, L"\"notepad.exe\" \"Test File.txt\"", L"." },
355     // test program
356     { __LINE__, FALSE, FALSE, L"test program", NULL },
357     { __LINE__, FALSE, FALSE, L"test program", L"." },
358     { __LINE__, FALSE, FALSE, L"test program", L"system32" },
359     { __LINE__, FALSE, FALSE, L"test program", L"C:\\Program Files" },
360     { __LINE__, FALSE, FALSE, L"test program \"Test File.txt\"", NULL },
361     { __LINE__, FALSE, FALSE, L"test program \"Test File.txt\"", L"." },
362     { __LINE__, FALSE, TRUE, L"test program", NULL },
363     { __LINE__, FALSE, TRUE, L"test program", L"." },
364     { __LINE__, FALSE, TRUE, L"test program", L"system32" },
365     { __LINE__, FALSE, TRUE, L"test program", L"C:\\Program Files" },
366     { __LINE__, FALSE, TRUE, L"test program \"Test File.txt\"", NULL },
367     { __LINE__, FALSE, TRUE, L"test program \"Test File.txt\"", L"." },
368     // test program.exe
369     { __LINE__, FALSE, FALSE, L"test program.exe", NULL },
370     { __LINE__, FALSE, FALSE, L"test program.exe", L"." },
371     { __LINE__, FALSE, FALSE, L"test program.exe", L"system32" },
372     { __LINE__, FALSE, FALSE, L"test program.exe", L"C:\\Program Files" },
373     { __LINE__, FALSE, FALSE, L"test program.exe \"Test File.txt\"", NULL },
374     { __LINE__, FALSE, FALSE, L"test program.exe \"Test File.txt\"", L"." },
375     { __LINE__, FALSE, TRUE, L"test program.exe", NULL },
376     { __LINE__, FALSE, TRUE, L"test program.exe", L"." },
377     { __LINE__, FALSE, TRUE, L"test program.exe", L"system32" },
378     { __LINE__, FALSE, TRUE, L"test program.exe", L"C:\\Program Files" },
379     { __LINE__, FALSE, TRUE, L"test program.exe \"Test File.txt\"", NULL },
380     { __LINE__, FALSE, TRUE, L"test program.exe \"Test File.txt\"", L"." },
381     // test program.bat
382     { __LINE__, FALSE, FALSE, L"test program.bat", NULL },
383     { __LINE__, FALSE, FALSE, L"test program.bat", L"." },
384     { __LINE__, FALSE, FALSE, L"test program.bat", L"system32" },
385     { __LINE__, FALSE, FALSE, L"test program.bat", L"C:\\Program Files" },
386     { __LINE__, FALSE, FALSE, L"test program.bat \"Test File.txt\"", NULL },
387     { __LINE__, FALSE, FALSE, L"test program.bat \"Test File.txt\"", L"." },
388     { __LINE__, FALSE, TRUE, L"test program.bat", NULL },
389     { __LINE__, FALSE, TRUE, L"test program.bat", L"." },
390     { __LINE__, FALSE, TRUE, L"test program.bat", L"system32" },
391     { __LINE__, FALSE, TRUE, L"test program.bat", L"C:\\Program Files" },
392     { __LINE__, FALSE, TRUE, L"test program.bat \"Test File.txt\"", NULL },
393     { __LINE__, FALSE, TRUE, L"test program.bat \"Test File.txt\"", L"." },
394     // "test program"
395     { __LINE__, FALSE, FALSE, L"\"test program\"", NULL },
396     { __LINE__, FALSE, FALSE, L"\"test program\"", L"." },
397     { __LINE__, FALSE, FALSE, L"\"test program\"", L"system32" },
398     { __LINE__, FALSE, FALSE, L"\"test program\"", L"C:\\Program Files" },
399     { __LINE__, FALSE, FALSE, L"\"test program\" \"Test File.txt\"", NULL },
400     { __LINE__, FALSE, FALSE, L"\"test program\" \"Test File.txt\"", L"." },
401     { __LINE__, FALSE, TRUE, L"\"test program\"", NULL },
402     { __LINE__, FALSE, TRUE, L"\"test program\"", L"." },
403     { __LINE__, FALSE, TRUE, L"\"test program\"", L"system32" },
404     { __LINE__, FALSE, TRUE, L"\"test program\"", L"C:\\Program Files" },
405     { __LINE__, FALSE, TRUE, L"\"test program\" \"Test File.txt\"", NULL },
406     { __LINE__, FALSE, TRUE, L"\"test program\" \"Test File.txt\"", L"." },
407     // "test program.exe"
408     { __LINE__, TRUE, FALSE, L"\"test program.exe\"", NULL },
409     { __LINE__, TRUE, FALSE, L"\"test program.exe\"", L"." },
410     { __LINE__, TRUE, FALSE, L"\"test program.exe\"", L"system32" },
411     { __LINE__, TRUE, FALSE, L"\"test program.exe\"", L"C:\\Program Files" },
412     { __LINE__, TRUE, FALSE, L"\"test program.exe\" \"Test File.txt\"", NULL },
413     { __LINE__, TRUE, FALSE, L"\"test program.exe\" \"Test File.txt\"", L"." },
414     { __LINE__, TRUE, TRUE, L"\"test program.exe\"", NULL },
415     { __LINE__, TRUE, TRUE, L"\"test program.exe\"", L"." },
416     { __LINE__, TRUE, TRUE, L"\"test program.exe\"", L"system32" },
417     { __LINE__, TRUE, TRUE, L"\"test program.exe\"", L"C:\\Program Files" },
418     { __LINE__, TRUE, TRUE, L"\"test program.exe\" \"Test File.txt\"", NULL },
419     { __LINE__, TRUE, TRUE, L"\"test program.exe\" \"Test File.txt\"", L"." },
420     // "test program.bat"
421     { __LINE__, FALSE, FALSE, L"\"test program.bat\"", NULL },
422     { __LINE__, FALSE, FALSE, L"\"test program.bat\"", L"." },
423     { __LINE__, FALSE, FALSE, L"\"test program.bat\"", L"system32" },
424     { __LINE__, FALSE, FALSE, L"\"test program.bat\"", L"C:\\Program Files" },
425     { __LINE__, FALSE, FALSE, L"\"test program.bat\" \"Test File.txt\"", NULL },
426     { __LINE__, FALSE, FALSE, L"\"test program.bat\" \"Test File.txt\"", L"." },
427     { __LINE__, FALSE, TRUE, L"\"test program.bat\"", NULL },
428     { __LINE__, FALSE, TRUE, L"\"test program.bat\"", L"." },
429     { __LINE__, FALSE, TRUE, L"\"test program.bat\"", L"system32" },
430     { __LINE__, FALSE, TRUE, L"\"test program.bat\"", L"C:\\Program Files" },
431     { __LINE__, FALSE, TRUE, L"\"test program.bat\" \"Test File.txt\"", NULL },
432     { __LINE__, FALSE, TRUE, L"\"test program.bat\" \"Test File.txt\"", L"." },
433     // invalid program
434     { __LINE__, FALSE, FALSE, L"invalid program", NULL },
435     { __LINE__, FALSE, FALSE, L"invalid program", L"." },
436     { __LINE__, FALSE, FALSE, L"invalid program", L"system32" },
437     { __LINE__, FALSE, FALSE, L"invalid program", L"C:\\Program Files" },
438     { __LINE__, FALSE, FALSE, L"invalid program \"Test File.txt\"", NULL },
439     { __LINE__, FALSE, FALSE, L"invalid program \"Test File.txt\"", L"." },
440     { __LINE__, FALSE, TRUE, L"invalid program", NULL },
441     { __LINE__, FALSE, TRUE, L"invalid program", L"." },
442     { __LINE__, FALSE, TRUE, L"invalid program", L"system32" },
443     { __LINE__, FALSE, TRUE, L"invalid program", L"C:\\Program Files" },
444     { __LINE__, FALSE, TRUE, L"invalid program \"Test File.txt\"", NULL },
445     { __LINE__, FALSE, TRUE, L"invalid program \"Test File.txt\"", L"." },
446     // \"invalid program.exe\"
447     { __LINE__, FALSE, FALSE, L"\"invalid program.exe\"", NULL },
448     { __LINE__, FALSE, FALSE, L"\"invalid program.exe\"", L"." },
449     { __LINE__, FALSE, FALSE, L"\"invalid program.exe\"", L"system32" },
450     { __LINE__, FALSE, FALSE, L"\"invalid program.exe\"", L"C:\\Program Files" },
451     { __LINE__, FALSE, FALSE, L"\"invalid program.exe\" \"Test File.txt\"", NULL },
452     { __LINE__, FALSE, FALSE, L"\"invalid program.exe\" \"Test File.txt\"", L"." },
453     { __LINE__, FALSE, TRUE, L"\"invalid program.exe\"", NULL },
454     { __LINE__, FALSE, TRUE, L"\"invalid program.exe\"", L"." },
455     { __LINE__, FALSE, TRUE, L"\"invalid program.exe\"", L"system32" },
456     { __LINE__, FALSE, TRUE, L"\"invalid program.exe\"", L"C:\\Program Files" },
457     { __LINE__, FALSE, TRUE, L"\"invalid program.exe\" \"Test File.txt\"", NULL },
458     { __LINE__, FALSE, TRUE, L"\"invalid program.exe\" \"Test File.txt\"", L"." },
459     // My Documents
460     { __LINE__, TRUE, TRUE, L"::{450d8fba-ad25-11d0-98a8-0800361b1103}", NULL },
461     { __LINE__, TRUE, TRUE, L"shell:::{450d8fba-ad25-11d0-98a8-0800361b1103}", NULL },
462     // shell:sendto
463     { __LINE__, TRUE, TRUE, L"shell:sendto", NULL },
464     // iexplore.exe
465     { __LINE__, TRUE, FALSE, L"iexplore", NULL },
466     { __LINE__, TRUE, FALSE, L"iexplore.exe", NULL },
467     { __LINE__, TRUE, TRUE, L"iexplore", NULL },
468     { __LINE__, TRUE, TRUE, L"iexplore.exe", NULL },
469     // https://google.com
470     { __LINE__, TRUE, FALSE, L"https://google.com", NULL },
471     { __LINE__, TRUE, TRUE, L"https://google.com", NULL },
472     // Test File 1.txt
473     { __LINE__, FALSE, FALSE, L"Test File 1.txt", NULL },
474     { __LINE__, FALSE, FALSE, L"Test File 1.txt", L"." },
475     { __LINE__, FALSE, FALSE, L"Test File 1.txt", L"system32" },
476     { __LINE__, FALSE, FALSE, L"Test File 1.txt", s_cur_dir },
477     { __LINE__, FALSE, FALSE, L"\"Test File 1.txt\"", NULL },
478     { __LINE__, FALSE, FALSE, L"\"Test File 1.txt\"", L"." },
479     { __LINE__, FALSE, FALSE, L"\"Test File 1.txt\"", L"system32" },
480     { __LINE__, FALSE, FALSE, L"\"Test File 1.txt\"", s_cur_dir },
481     { __LINE__, FALSE, FALSE, L"\"Test File 1.txt\" \"Test File.txt\"", NULL },
482     { __LINE__, FALSE, FALSE, L"\"Test File 1.txt\" \"Test File.txt\"", L"." },
483     { __LINE__, FALSE, FALSE, L"\"Test File 1.txt\" \"Test File.txt\"", L"system32" },
484     { __LINE__, FALSE, TRUE, L"Test File 1.txt", NULL },
485     { __LINE__, TRUE, TRUE, L"Test File 1.txt", L"." },
486     { __LINE__, FALSE, TRUE, L"Test File 1.txt", L"system32" },
487     { __LINE__, TRUE, TRUE, L"Test File 1.txt", s_cur_dir },
488     { __LINE__, FALSE, TRUE, L"\"Test File 1.txt\"", NULL },
489     { __LINE__, TRUE, TRUE, L"\"Test File 1.txt\"", L"." },
490     { __LINE__, TRUE, TRUE, L"\"Test File 1.txt\"", L"system32" },
491     { __LINE__, TRUE, TRUE, L"\"Test File 1.txt\"", s_cur_dir },
492     { __LINE__, FALSE, TRUE, L"\"Test File 1.txt\" \"Test File.txt\"", NULL },
493     { __LINE__, TRUE, TRUE, L"\"Test File 1.txt\" \"Test File.txt\"", L"." },
494     { __LINE__, TRUE, TRUE, L"\"Test File 1.txt\" \"Test File.txt\"", L"system32" },
495     { __LINE__, TRUE, TRUE, L"\"Test File 1.txt\" \"Test File.txt\"", s_cur_dir },
496     // Test File 2.bat
497     { __LINE__, FALSE, FALSE, L"Test File 2.bat", NULL },
498     { __LINE__, FALSE, FALSE, L"Test File 2.bat", L"." },
499     { __LINE__, FALSE, FALSE, L"Test File 2.bat", L"system32" },
500     { __LINE__, FALSE, FALSE, L"Test File 2.bat", s_cur_dir },
501     { __LINE__, FALSE, FALSE, L"\"Test File 2.bat\"", NULL },
502     { __LINE__, FALSE, FALSE, L"\"Test File 2.bat\"", L"." },
503     { __LINE__, FALSE, FALSE, L"\"Test File 2.bat\"", L"system32" },
504     { __LINE__, FALSE, FALSE, L"\"Test File 2.bat\"", s_cur_dir },
505     { __LINE__, FALSE, FALSE, L"\"Test File 2.bat\" \"Test File.txt\"", NULL },
506     { __LINE__, FALSE, FALSE, L"\"Test File 2.bat\" \"Test File.txt\"", L"." },
507     { __LINE__, FALSE, FALSE, L"\"Test File 2.bat\" \"Test File.txt\"", L"system32" },
508     { __LINE__, FALSE, FALSE, L"\"Test File 2.bat\" \"Test File.txt\"", s_cur_dir },
509     { __LINE__, FALSE, TRUE, L"Test File 2.bat", NULL },
510     { __LINE__, FALSE, TRUE, L"Test File 2.bat", L"." },
511     { __LINE__, FALSE, TRUE, L"Test File 2.bat", L"system32" },
512     { __LINE__, FALSE, TRUE, L"Test File 2.bat", s_cur_dir },
513     { __LINE__, FALSE, TRUE, L"\"Test File 2.bat\"", NULL },
514     { __LINE__, FALSE, TRUE, L"\"Test File 2.bat\"", L"." },
515     { __LINE__, FALSE, TRUE, L"\"Test File 2.bat\"", L"system32" },
516     { __LINE__, FALSE, TRUE, L"\"Test File 2.bat\"", s_cur_dir },
517     { __LINE__, FALSE, TRUE, L"\"Test File 2.bat\" \"Test File.txt\"", NULL },
518     { __LINE__, FALSE, TRUE, L"\"Test File 2.bat\" \"Test File.txt\"", L"." },
519     { __LINE__, FALSE, TRUE, L"\"Test File 2.bat\" \"Test File.txt\"", L"system32" },
520     { __LINE__, FALSE, TRUE, L"\"Test File 2.bat\" \"Test File.txt\"", s_cur_dir },
521 };
522 
523 static const TEST_ENTRY s_entries_2[] =
524 {
525     // Test File 1.txt (with setting path)
526     { __LINE__, FALSE, FALSE, L"Test File 1.txt", NULL },
527     { __LINE__, FALSE, FALSE, L"Test File 1.txt", L"." },
528     { __LINE__, FALSE, FALSE, L"Test File 1.txt", L"system32" },
529     { __LINE__, FALSE, FALSE, L"Test File 1.txt", s_cur_dir },
530     { __LINE__, FALSE, FALSE, L"\"Test File 1.txt\"", NULL },
531     { __LINE__, FALSE, FALSE, L"\"Test File 1.txt\"", L"." },
532     { __LINE__, FALSE, FALSE, L"\"Test File 1.txt\"", L"system32" },
533     { __LINE__, FALSE, FALSE, L"\"Test File 1.txt\"", s_cur_dir },
534     { __LINE__, FALSE, FALSE, L"\"Test File 1.txt\" \"Test File.txt\"", NULL },
535     { __LINE__, FALSE, FALSE, L"\"Test File 1.txt\" \"Test File.txt\"", L"." },
536     { __LINE__, FALSE, FALSE, L"\"Test File 1.txt\" \"Test File.txt\"", L"system32" },
537     { __LINE__, FALSE, FALSE, L"\"Test File 1.txt\" \"Test File.txt\"", s_cur_dir },
538     { __LINE__, FALSE, TRUE, L"Test File 1.txt", NULL },
539     { __LINE__, TRUE, TRUE, L"Test File 1.txt", L"." },
540     { __LINE__, FALSE, TRUE, L"Test File 1.txt", L"system32" },
541     { __LINE__, TRUE, TRUE, L"Test File 1.txt", s_cur_dir },
542     { __LINE__, TRUE, TRUE, L"\"Test File 1.txt\"", NULL },
543     { __LINE__, TRUE, TRUE, L"\"Test File 1.txt\"", L"." },
544     { __LINE__, TRUE, TRUE, L"\"Test File 1.txt\"", L"system32" },
545     { __LINE__, TRUE, TRUE, L"\"Test File 1.txt\"", s_cur_dir },
546     { __LINE__, TRUE, TRUE, L"\"Test File 1.txt\" \"Test File.txt\"", NULL },
547     { __LINE__, TRUE, TRUE, L"\"Test File 1.txt\" \"Test File.txt\"", L"." },
548     { __LINE__, TRUE, TRUE, L"\"Test File 1.txt\" \"Test File.txt\"", L"system32" },
549     { __LINE__, TRUE, TRUE, L"\"Test File 1.txt\" \"Test File.txt\"", s_cur_dir },
550     // Test File 2.bat (with setting path)
551     { __LINE__, FALSE, FALSE, L"Test File 2.bat", NULL },
552     { __LINE__, FALSE, FALSE, L"Test File 2.bat", L"." },
553     { __LINE__, FALSE, FALSE, L"Test File 2.bat", L"system32" },
554     { __LINE__, FALSE, FALSE, L"Test File 2.bat", s_cur_dir },
555     { __LINE__, FALSE, FALSE, L"\"Test File 2.bat\"", NULL },
556     { __LINE__, FALSE, FALSE, L"\"Test File 2.bat\"", L"." },
557     { __LINE__, FALSE, FALSE, L"\"Test File 2.bat\"", L"system32" },
558     { __LINE__, FALSE, FALSE, L"\"Test File 2.bat\"", s_cur_dir },
559     { __LINE__, FALSE, FALSE, L"\"Test File 2.bat\" \"Test File.txt\"", NULL },
560     { __LINE__, FALSE, FALSE, L"\"Test File 2.bat\" \"Test File.txt\"", L"." },
561     { __LINE__, FALSE, FALSE, L"\"Test File 2.bat\" \"Test File.txt\"", L"system32" },
562     { __LINE__, FALSE, FALSE, L"\"Test File 2.bat\" \"Test File.txt\"", s_cur_dir },
563     { __LINE__, FALSE, TRUE, L"Test File 2.bat", NULL },
564     { __LINE__, FALSE, TRUE, L"Test File 2.bat", L"." },
565     { __LINE__, FALSE, TRUE, L"Test File 2.bat", L"system32" },
566     { __LINE__, FALSE, TRUE, L"Test File 2.bat", s_cur_dir },
567     { __LINE__, FALSE, TRUE, L"\"Test File 2.bat\"", NULL },
568     { __LINE__, FALSE, TRUE, L"\"Test File 2.bat\"", L"." },
569     { __LINE__, FALSE, TRUE, L"\"Test File 2.bat\"", L"system32" },
570     { __LINE__, FALSE, TRUE, L"\"Test File 2.bat\"", s_cur_dir },
571     { __LINE__, FALSE, TRUE, L"\"Test File 2.bat\" \"Test File.txt\"", NULL },
572     { __LINE__, FALSE, TRUE, L"\"Test File 2.bat\" \"Test File.txt\"", L"." },
573     { __LINE__, FALSE, TRUE, L"\"Test File 2.bat\" \"Test File.txt\"", L"system32" },
574     { __LINE__, FALSE, TRUE, L"\"Test File 2.bat\" \"Test File.txt\"", s_cur_dir },
575 };
576 
577 typedef struct OPENWNDS
578 {
579     UINT count;
580     HWND *phwnd;
581 } OPENWNDS;
582 
583 static OPENWNDS s_wi0 = { 0 }, s_wi1 = { 0 };
584 
585 static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
586 {
587     OPENWNDS *info = (OPENWNDS *)lParam;
588     info->phwnd = (HWND *)realloc(info->phwnd, (info->count + 1) * sizeof(HWND));
589     if (!info->phwnd)
590         return FALSE;
591     info->phwnd[info->count] = hwnd;
592     ++(info->count);
593     return TRUE;
594 }
595 
596 static void DoEntry(const TEST_ENTRY *pEntry)
597 {
598     HRESULT hr;
599     DWORD dwSeclFlags;
600     BOOL result;
601 
602     if (pEntry->bAllowNonExe)
603         dwSeclFlags = SECL_NO_UI | SECL_ALLOW_NONEXE;
604     else
605         dwSeclFlags = SECL_NO_UI;
606 
607     _SEH2_TRY
608     {
609         if (IsReactOS())
610         {
611             hr = proxy_ShellExecCmdLine(NULL, pEntry->pwszCommand, pEntry->pwszStartDir,
612                                         SW_SHOWNORMAL, NULL, dwSeclFlags);
613         }
614         else
615         {
616             hr = (*g_pShellExecCmdLine)(NULL, pEntry->pwszCommand, pEntry->pwszStartDir,
617                                         SW_SHOWNORMAL, NULL, dwSeclFlags);
618         }
619     }
620     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
621     {
622         hr = 0xBADFACE;
623     }
624     _SEH2_END;
625 
626     if (hr == 0xBADFACE)
627         result = hr;
628     else
629         result = (hr == S_OK);
630 
631     ok(result == pEntry->result, "Line %d: result expected %d, was %d\n",
632        pEntry->lineno, pEntry->result, result);
633 
634     // close newly opened windows
635     EnumWindows(EnumWindowsProc, (LPARAM)&s_wi1);
636     for (UINT i1 = 0; i1 < s_wi1.count; ++i1)
637     {
638         BOOL bFound = FALSE;
639         for (UINT i0 = 0; i0 < s_wi0.count; ++i0)
640         {
641             if (s_wi1.phwnd[i1] == s_wi0.phwnd[i0])
642             {
643                 bFound = TRUE;
644                 break;
645             }
646         }
647         if (!bFound)
648             PostMessageW(s_wi1.phwnd[i1], WM_CLOSE, 0, 0);
649     }
650     free(s_wi1.phwnd);
651     ZeroMemory(&s_wi1, sizeof(s_wi1));
652 }
653 
654 START_TEST(ShellExecCmdLine)
655 {
656     using namespace std;
657 
658     if (!IsReactOS())
659     {
660         if (!IsWindowsVistaOrGreater())
661         {
662             skip("ShellExecCmdLine is not available on this platform\n");
663             return;
664         }
665 
666         HMODULE hShell32 = GetModuleHandleA("shell32");
667         g_pShellExecCmdLine = (SHELLEXECCMDLINE)GetProcAddress(hShell32, (LPCSTR)(INT_PTR)265);
668         if (!g_pShellExecCmdLine)
669         {
670             skip("ShellExecCmdLine is not found\n");
671             return;
672         }
673     }
674 
675     if (!GetSubProgramPath())
676     {
677         skip("shell32_apitest_sub.exe is not found\n");
678         return;
679     }
680 
681     // record open windows
682     if (!EnumWindows(EnumWindowsProc, (LPARAM)&s_wi0))
683     {
684         skip("EnumWindows failed\n");
685         free(s_wi0.phwnd);
686         return;
687     }
688 
689     // s_win_test_exe
690     GetWindowsDirectoryW(s_win_test_exe, _countof(s_win_test_exe));
691     PathAppendW(s_win_test_exe, L"test program.exe");
692     BOOL ret = CopyFileW(s_sub_program, s_win_test_exe, FALSE);
693     if (!ret)
694     {
695         skip("Please retry with admin rights\n");
696         free(s_wi0.phwnd);
697         return;
698     }
699 
700     FILE *fp;
701 
702     // s_sys_bat_file
703     GetSystemDirectoryW(s_sys_bat_file, _countof(s_sys_bat_file));
704     PathAppendW(s_sys_bat_file, L"test program.bat");
705     fp = _wfopen(s_sys_bat_file, L"wb");
706     fclose(fp);
707     ok_int(PathFileExistsW(s_sys_bat_file), TRUE);
708 
709     // "Test File 1.txt"
710     fp = fopen("Test File 1.txt", "wb");
711     ok(fp != NULL, "failed to create a test file\n");
712     fclose(fp);
713     ok_int(PathFileExistsA("Test File 1.txt"), TRUE);
714 
715     // "Test File 2.bat"
716     fp = fopen("Test File 2.bat", "wb");
717     ok(fp != NULL, "failed to create a test file\n");
718     fclose(fp);
719     ok_int(PathFileExistsA("Test File 2.bat"), TRUE);
720 
721     // s_cur_dir
722     GetCurrentDirectoryW(_countof(s_cur_dir), s_cur_dir);
723 
724     // do tests
725     for (size_t i = 0; i < _countof(s_entries_1); ++i)
726     {
727         DoEntry(&s_entries_1[i]);
728     }
729     SetEnvironmentVariableW(L"PATH", s_cur_dir);
730     for (size_t i = 0; i < _countof(s_entries_2); ++i)
731     {
732         DoEntry(&s_entries_2[i]);
733     }
734 
735     // clean up
736     ok(DeleteFileW(s_win_test_exe), "failed to delete the test file\n");
737     ok(DeleteFileW(s_sys_bat_file), "failed to delete the test file\n");
738     ok(DeleteFileA("Test File 1.txt"), "failed to delete the test file\n");
739     ok(DeleteFileA("Test File 2.bat"), "failed to delete the test file\n");
740     free(s_wi0.phwnd);
741 }
742