diff --git a/rostests/winetests/shell32/generated.c b/rostests/winetests/shell32/generated.c index dee76384554..b424065f46e 100755 --- a/rostests/winetests/shell32/generated.c +++ b/rostests/winetests/shell32/generated.c @@ -201,14 +201,16 @@ static void test_pack_COAUTHINFO(void) TEST_FIELD(COAUTHINFO, DWORD, dwCapabilities, 24, 4, 4); } -static void test_pack_COSERVERINFO(void) +static void test_pack_DATE(void) { - /* COSERVERINFO (pack 4) */ - TEST_TYPE(COSERVERINFO, 16, 4); - TEST_FIELD(COSERVERINFO, DWORD, dwReserved1, 0, 4, 4); - TEST_FIELD(COSERVERINFO, LPWSTR, pwszName, 4, 4, 4); - TEST_FIELD(COSERVERINFO, COAUTHINFO *, pAuthInfo, 8, 4, 4); - TEST_FIELD(COSERVERINFO, DWORD, dwReserved2, 12, 4, 4); + /* DATE */ + TEST_TYPE(DATE, 8, 8); +} + +static void test_pack_DOUBLE(void) +{ + /* DOUBLE */ + TEST_TYPE(DOUBLE, 8, 8); } static void test_pack_DWORD_SIZEDARR(void) @@ -255,7 +257,6 @@ static void test_pack_LPBLOB(void) { /* LPBLOB */ TEST_TYPE(LPBLOB, 4, 4); - TEST_TYPE_POINTER(LPBLOB, 8, 4); } static void test_pack_LPBSTR(void) @@ -269,7 +270,6 @@ static void test_pack_LPBSTRBLOB(void) { /* LPBSTRBLOB */ TEST_TYPE(LPBSTRBLOB, 4, 4); - TEST_TYPE_POINTER(LPBSTRBLOB, 8, 4); } static void test_pack_LPCOLESTR(void) @@ -835,7 +835,6 @@ static void test_pack_LPITEMIDLIST(void) { /* LPITEMIDLIST */ TEST_TYPE(LPITEMIDLIST, 4, 4); - TEST_TYPE_POINTER(LPITEMIDLIST, 3, 1); } static void test_pack_LPSHELLDETAILS(void) @@ -848,7 +847,6 @@ static void test_pack_LPSHITEMID(void) { /* LPSHITEMID */ TEST_TYPE(LPSHITEMID, 4, 4); - TEST_TYPE_POINTER(LPSHITEMID, 3, 1); } static void test_pack_LPSTRRET(void) @@ -1011,26 +1009,6 @@ static void test_pack_FILEGROUPDESCRIPTORW(void) TEST_FIELD(FILEGROUPDESCRIPTORW, FILEDESCRIPTORW[1], fgd, 4, 592, 1); } -static void test_pack_IFileSystemBindData(void) -{ - /* IFileSystemBindData */ -} - -static void test_pack_IFileSystemBindDataVtbl(void) -{ - /* IFileSystemBindDataVtbl */ -} - -static void test_pack_IShellChangeNotify(void) -{ - /* IShellChangeNotify */ -} - -static void test_pack_IShellIcon(void) -{ - /* IShellIcon */ -} - static void test_pack_LPBROWSEINFOA(void) { /* LPBROWSEINFOA */ @@ -1282,8 +1260,9 @@ static void test_pack(void) test_pack_CLSID(); test_pack_COAUTHIDENTITY(); test_pack_COAUTHINFO(); - test_pack_COSERVERINFO(); test_pack_CSFV(); + test_pack_DATE(); + test_pack_DOUBLE(); test_pack_DRAGINFOA(); test_pack_DRAGINFOW(); test_pack_DROPFILES(); @@ -1299,11 +1278,7 @@ static void test_pack(void) test_pack_GUID(); test_pack_HMETAFILEPICT(); test_pack_HYPER_SIZEDARR(); - test_pack_IFileSystemBindData(); - test_pack_IFileSystemBindDataVtbl(); test_pack_IID(); - test_pack_IShellChangeNotify(); - test_pack_IShellIcon(); test_pack_ITEMIDLIST(); test_pack_LPBLOB(); test_pack_LPBROWSEINFOA(); diff --git a/rostests/winetests/shell32/shell32.rbuild b/rostests/winetests/shell32/shell32.rbuild index a4c85e1123d..60962d359f0 100644 --- a/rostests/winetests/shell32/shell32.rbuild +++ b/rostests/winetests/shell32/shell32.rbuild @@ -1,17 +1,27 @@ - . - - ntdll - shell32 - kernel32 - advapi32 - shlwapi - ole32 - shelllink.c - shellpath.c - shlexec.c - shlfileop.c - shlfolder.c - string.c - testlist.c + . + + 0x600 + 0x501 + 0x501 + wine + shell32 + ole32 + oleaut32 + shlwapi + user32 + gdi32 + advapi32 + kernel32 + uuid + ntdll + generated.c + shelllink.c + shellpath.c + shlexec.c + shlfileop.c + shlfolder.c + string.c + systray.c + testlist.c diff --git a/rostests/winetests/shell32/shell32_test.h b/rostests/winetests/shell32/shell32_test.h index fc377896189..64a2be1ef62 100755 --- a/rostests/winetests/shell32/shell32_test.h +++ b/rostests/winetests/shell32/shell32_test.h @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ diff --git a/rostests/winetests/shell32/shelllink.c b/rostests/winetests/shell32/shelllink.c index 88924e8f0e0..091522be32b 100755 --- a/rostests/winetests/shell32/shelllink.c +++ b/rostests/winetests/shell32/shelllink.c @@ -15,61 +15,65 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA * This is a test program for the SHGet{Special}Folder{Path|Location} functions * of shell32, that get either a filesytem path or a LPITEMIDLIST (shell * namespace) path for a given folder (CSIDL value). * */ -#define _WIN32_IE 0x0400 - #define COBJMACROS -#include -#include -#include "windef.h" -#include "winbase.h" -#include "basetyps.h" +#include #include "shlguid.h" -//#include "wine/shobjidl.h" +#include "shobjidl.h" #include "shlobj.h" #include "wine/test.h" #include "shell32_test.h" -extern BOOL WINAPI ILIsEqual(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2); -extern HRESULT WINAPI SHILCreateFromPath(LPCWSTR path, LPITEMIDLIST * ppidl, DWORD * attributes); -extern void WINAPI ILFree(LPITEMIDLIST pidl); + +typedef void (WINAPI *fnILFree)(LPITEMIDLIST); +typedef BOOL (WINAPI *fnILIsEqual)(LPCITEMIDLIST, LPCITEMIDLIST); +typedef HRESULT (WINAPI *fnSHILCreateFromPath)(LPCWSTR, LPITEMIDLIST *,DWORD*); + +static fnILFree pILFree; +static fnILIsEqual pILIsEqual; +static fnSHILCreateFromPath pSHILCreateFromPath; + +static DWORD (WINAPI *pGetLongPathNameA)(LPCSTR, LPSTR, DWORD); + +static const GUID _IID_IShellLinkDataList = { + 0x45e2b4ae, 0xb1c3, 0x11d0, + { 0xb9, 0x2f, 0x00, 0xa0, 0xc9, 0x03, 0x12, 0xe1 } +}; static const WCHAR lnkfile[]= { 'C',':','\\','t','e','s','t','.','l','n','k',0 }; static const WCHAR notafile[]= { 'C',':','\\','n','o','n','e','x','i','s','t','e','n','t','\\','f','i','l','e',0 }; -#if 0 // FIXME: needed to build. Please update shell32 winetest. -const GUID IID_IPersistFile = { 0x0000010b, 0x0000, 0x0000, { 0xc0,0x00, 0x00,0x00,0x00,0x00,0x00,0x46 } }; -#endif /* For some reason SHILCreateFromPath does not work on Win98 and * SHSimpleIDListFromPathA does not work on NT4. But if we call both we * get what we want on all platforms. */ -static LPITEMIDLIST (WINAPI *pSHSimpleIDListFromPathA)(LPCSTR)=NULL; +static LPITEMIDLIST (WINAPI *pSHSimpleIDListFromPathAW)(LPCVOID); static LPITEMIDLIST path_to_pidl(const char* path) { LPITEMIDLIST pidl; - if (!pSHSimpleIDListFromPathA) + if (!pSHSimpleIDListFromPathAW) { - HMODULE hdll=LoadLibraryA("shell32.dll"); - pSHSimpleIDListFromPathA=(void*)GetProcAddress(hdll, (char*)162); - if (!pSHSimpleIDListFromPathA) - trace("SHSimpleIDListFromPathA not found in shell32.dll\n"); + HMODULE hdll=GetModuleHandleA("shell32.dll"); + pSHSimpleIDListFromPathAW=(void*)GetProcAddress(hdll, (char*)162); + if (!pSHSimpleIDListFromPathAW) + trace("SHSimpleIDListFromPathAW not found in shell32.dll\n"); } pidl=NULL; - if (pSHSimpleIDListFromPathA) - pidl=pSHSimpleIDListFromPathA(path); + /* pSHSimpleIDListFromPathAW maps to A on non NT platforms */ + if (pSHSimpleIDListFromPathAW && (GetVersion() & 0x80000000)) + pidl=pSHSimpleIDListFromPathAW(path); if (!pidl) { @@ -81,9 +85,9 @@ static LPITEMIDLIST path_to_pidl(const char* path) pathW=HeapAlloc(GetProcessHeap(), 0, len*sizeof(WCHAR)); MultiByteToWideChar(CP_ACP, 0, path, -1, pathW, len); - r=SHILCreateFromPath(pathW, &pidl, NULL); + r=pSHILCreateFromPath(pathW, &pidl, NULL); todo_wine { - ok(SUCCEEDED(r), "SHILCreateFromPath failed (0x%08lx)\n", r); + ok(SUCCEEDED(r), "SHILCreateFromPath failed (0x%08x)\n", r); } HeapFree(GetProcessHeap(), 0, pathW); } @@ -108,71 +112,72 @@ static void test_get_set(void) r = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkA, (LPVOID*)&sl); - ok(SUCCEEDED(r), "no IID_IShellLinkA (0x%08lx)\n", r); + ok(SUCCEEDED(r), "no IID_IShellLinkA (0x%08x)\n", r); if (!SUCCEEDED(r)) return; /* Test Getting / Setting the description */ strcpy(buffer,"garbage"); r = IShellLinkA_GetDescription(sl, buffer, sizeof(buffer)); - ok(SUCCEEDED(r), "GetDescription failed (0x%08lx)\n", r); + ok(SUCCEEDED(r), "GetDescription failed (0x%08x)\n", r); ok(*buffer=='\0', "GetDescription returned '%s'\n", buffer); str="Some description"; r = IShellLinkA_SetDescription(sl, str); - ok(SUCCEEDED(r), "SetDescription failed (0x%08lx)\n", r); + ok(SUCCEEDED(r), "SetDescription failed (0x%08x)\n", r); strcpy(buffer,"garbage"); r = IShellLinkA_GetDescription(sl, buffer, sizeof(buffer)); - ok(SUCCEEDED(r), "GetDescription failed (0x%08lx)\n", r); + ok(SUCCEEDED(r), "GetDescription failed (0x%08x)\n", r); ok(lstrcmp(buffer,str)==0, "GetDescription returned '%s'\n", buffer); /* Test Getting / Setting the work directory */ strcpy(buffer,"garbage"); r = IShellLinkA_GetWorkingDirectory(sl, buffer, sizeof(buffer)); - ok(SUCCEEDED(r), "GetWorkingDirectory failed (0x%08lx)\n", r); + ok(SUCCEEDED(r), "GetWorkingDirectory failed (0x%08x)\n", r); ok(*buffer=='\0', "GetWorkingDirectory returned '%s'\n", buffer); str="c:\\nonexistent\\directory"; r = IShellLinkA_SetWorkingDirectory(sl, str); - ok(SUCCEEDED(r), "SetWorkingDirectory failed (0x%08lx)\n", r); + ok(SUCCEEDED(r), "SetWorkingDirectory failed (0x%08x)\n", r); strcpy(buffer,"garbage"); r = IShellLinkA_GetWorkingDirectory(sl, buffer, sizeof(buffer)); - ok(SUCCEEDED(r), "GetWorkingDirectory failed (0x%08lx)\n", r); + ok(SUCCEEDED(r), "GetWorkingDirectory failed (0x%08x)\n", r); ok(lstrcmpi(buffer,str)==0, "GetWorkingDirectory returned '%s'\n", buffer); /* Test Getting / Setting the work directory */ strcpy(buffer,"garbage"); r = IShellLinkA_GetPath(sl, buffer, sizeof(buffer), NULL, SLGP_RAWPATH); - ok(SUCCEEDED(r), "GetPath failed (0x%08lx)\n", r); + ok(SUCCEEDED(r), "GetPath failed (0x%08x)\n", r); ok(*buffer=='\0', "GetPath returned '%s'\n", buffer); r = IShellLinkA_SetPath(sl, ""); - ok(r==S_OK, "SetPath failed (0x%08lx)\n", r); + ok(r==S_OK, "SetPath failed (0x%08x)\n", r); strcpy(buffer,"garbage"); r = IShellLinkA_GetPath(sl, buffer, sizeof(buffer), NULL, SLGP_RAWPATH); - ok(SUCCEEDED(r), "GetPath failed (0x%08lx)\n", r); + ok(SUCCEEDED(r), "GetPath failed (0x%08x)\n", r); ok(*buffer=='\0', "GetPath returned '%s'\n", buffer); + /* Win98 returns S_FALSE, but WinXP returns S_OK */ str="c:\\nonexistent\\file"; r = IShellLinkA_SetPath(sl, str); - ok(r==S_FALSE, "SetPath failed (0x%08lx)\n", r); + ok(r==S_FALSE || r==S_OK, "SetPath failed (0x%08x)\n", r); strcpy(buffer,"garbage"); r = IShellLinkA_GetPath(sl, buffer, sizeof(buffer), NULL, SLGP_RAWPATH); - ok(SUCCEEDED(r), "GetPath failed (0x%08lx)\n", r); + ok(SUCCEEDED(r), "GetPath failed (0x%08x)\n", r); ok(lstrcmpi(buffer,str)==0, "GetPath returned '%s'\n", buffer); /* Get some a real path to play with */ r=GetModuleFileName(NULL, mypath, sizeof(mypath)); - ok(r>=0 && r=0 && rdescription) { r = IShellLinkA_SetDescription(sl, desc->description); - lok(SUCCEEDED(r), "SetDescription failed (0x%08lx)\n", r); + lok(SUCCEEDED(r), "SetDescription failed (0x%08x)\n", r); } if (desc->workdir) { r = IShellLinkA_SetWorkingDirectory(sl, desc->workdir); - lok(SUCCEEDED(r), "SetWorkingDirectory failed (0x%08lx)\n", r); + lok(SUCCEEDED(r), "SetWorkingDirectory failed (0x%08x)\n", r); } if (desc->path) { r = IShellLinkA_SetPath(sl, desc->path); - lok(SUCCEEDED(r), "SetPath failed (0x%08lx)\n", r); + lok(SUCCEEDED(r), "SetPath failed (0x%08x)\n", r); } if (desc->pidl) { r = IShellLinkA_SetIDList(sl, desc->pidl); - lok(SUCCEEDED(r), "SetIDList failed (0x%08lx)\n", r); + lok(SUCCEEDED(r), "SetIDList failed (0x%08x)\n", r); } if (desc->arguments) { r = IShellLinkA_SetArguments(sl, desc->arguments); - lok(SUCCEEDED(r), "SetArguments failed (0x%08lx)\n", r); + lok(SUCCEEDED(r), "SetArguments failed (0x%08x)\n", r); } if (desc->showcmd) { r = IShellLinkA_SetShowCmd(sl, desc->showcmd); - lok(SUCCEEDED(r), "SetShowCmd failed (0x%08lx)\n", r); + lok(SUCCEEDED(r), "SetShowCmd failed (0x%08x)\n", r); } if (desc->icon) { r = IShellLinkA_SetIconLocation(sl, desc->icon, desc->icon_id); - lok(SUCCEEDED(r), "SetIconLocation failed (0x%08lx)\n", r); + lok(SUCCEEDED(r), "SetIconLocation failed (0x%08x)\n", r); } if (desc->hotkey) { r = IShellLinkA_SetHotkey(sl, desc->hotkey); - lok(SUCCEEDED(r), "SetHotkey failed (0x%08lx)\n", r); + lok(SUCCEEDED(r), "SetHotkey failed (0x%08x)\n", r); } r = IShellLinkW_QueryInterface(sl, &IID_IPersistFile, (LPVOID*)&pf); - lok(SUCCEEDED(r), "no IID_IPersistFile (0x%08lx)\n", r); + lok(SUCCEEDED(r), "no IID_IPersistFile (0x%08x)\n", r); if (SUCCEEDED(r)) { r = IPersistFile_Save(pf, path, TRUE); if (save_fails) { todo_wine { - lok(SUCCEEDED(r), "save failed (0x%08lx)\n", r); + lok(SUCCEEDED(r), "save failed (0x%08x)\n", r); } } else { - lok(SUCCEEDED(r), "save failed (0x%08lx)\n", r); + lok(SUCCEEDED(r), "save failed (0x%08x)\n", r); } IPersistFile_Release(pf); } @@ -357,7 +391,7 @@ void create_lnk_(int line, const WCHAR* path, lnk_desc_t* desc, int save_fails) IShellLinkA_Release(sl); } -static void check_lnk_(int line, const WCHAR* path, lnk_desc_t* desc) +static void check_lnk_(int line, const WCHAR* path, lnk_desc_t* desc, int todo) { HRESULT r; IShellLinkA *sl; @@ -366,12 +400,12 @@ static void check_lnk_(int line, const WCHAR* path, lnk_desc_t* desc) r = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkA, (LPVOID*)&sl); - lok(SUCCEEDED(r), "no IID_IShellLinkA (0x%08lx)\n", r); + lok(SUCCEEDED(r), "no IID_IShellLinkA (0x%08x)\n", r); if (!SUCCEEDED(r)) return; r = IShellLinkA_QueryInterface(sl, &IID_IPersistFile, (LPVOID*)&pf); - lok(SUCCEEDED(r), "no IID_IPersistFile (0x%08lx)\n", r); + lok(SUCCEEDED(r), "no IID_IPersistFile (0x%08x)\n", r); if (!SUCCEEDED(r)) { IShellLinkA_Release(sl); @@ -379,7 +413,7 @@ static void check_lnk_(int line, const WCHAR* path, lnk_desc_t* desc) } r = IPersistFile_Load(pf, path, STGM_READ); - lok(SUCCEEDED(r), "load failed (0x%08lx)\n", r); + lok(SUCCEEDED(r), "load failed (0x%08x)\n", r); IPersistFile_Release(pf); if (!SUCCEEDED(r)) { @@ -391,8 +425,8 @@ static void check_lnk_(int line, const WCHAR* path, lnk_desc_t* desc) { strcpy(buffer,"garbage"); r = IShellLinkA_GetDescription(sl, buffer, sizeof(buffer)); - lok(SUCCEEDED(r), "GetDescription failed (0x%08lx)\n", r); - lok(lstrcmp(buffer, desc->description)==0, + lok(SUCCEEDED(r), "GetDescription failed (0x%08x)\n", r); + lok_todo_4(0x1, lstrcmp(buffer, desc->description)==0, "GetDescription returned '%s' instead of '%s'\n", buffer, desc->description); } @@ -400,8 +434,8 @@ static void check_lnk_(int line, const WCHAR* path, lnk_desc_t* desc) { strcpy(buffer,"garbage"); r = IShellLinkA_GetWorkingDirectory(sl, buffer, sizeof(buffer)); - lok(SUCCEEDED(r), "GetWorkingDirectory failed (0x%08lx)\n", r); - lok(lstrcmpi(buffer, desc->workdir)==0, + lok(SUCCEEDED(r), "GetWorkingDirectory failed (0x%08x)\n", r); + lok_todo_4(0x2, lstrcmpi(buffer, desc->workdir)==0, "GetWorkingDirectory returned '%s' instead of '%s'\n", buffer, desc->workdir); } @@ -409,8 +443,8 @@ static void check_lnk_(int line, const WCHAR* path, lnk_desc_t* desc) { strcpy(buffer,"garbage"); r = IShellLinkA_GetPath(sl, buffer, sizeof(buffer), NULL, SLGP_RAWPATH); - lok(SUCCEEDED(r), "GetPath failed (0x%08lx)\n", r); - lok(lstrcmpi(buffer, desc->path)==0, + lok(SUCCEEDED(r), "GetPath failed (0x%08x)\n", r); + lok_todo_4(0x4, lstrcmpi(buffer, desc->path)==0, "GetPath returned '%s' instead of '%s'\n", buffer, desc->path); } @@ -418,16 +452,16 @@ static void check_lnk_(int line, const WCHAR* path, lnk_desc_t* desc) { LPITEMIDLIST pidl=NULL; r = IShellLinkA_GetIDList(sl, &pidl); - lok(SUCCEEDED(r), "GetIDList failed (0x%08lx)\n", r); - lok(ILIsEqual(pidl, desc->pidl), + lok(SUCCEEDED(r), "GetIDList failed (0x%08x)\n", r); + lok_todo_2(0x8, pILIsEqual(pidl, desc->pidl), "GetIDList returned an incorrect pidl\n"); } if (desc->showcmd) { int i=0xdeadbeef; r = IShellLinkA_GetShowCmd(sl, &i); - lok(SUCCEEDED(r), "GetShowCmd failed (0x%08lx)\n", r); - lok(i==desc->showcmd, + lok(SUCCEEDED(r), "GetShowCmd failed (0x%08x)\n", r); + lok_todo_4(0x10, i==desc->showcmd, "GetShowCmd returned 0x%0x instead of 0x%0x\n", i, desc->showcmd); } @@ -436,11 +470,11 @@ static void check_lnk_(int line, const WCHAR* path, lnk_desc_t* desc) int i=0xdeadbeef; strcpy(buffer,"garbage"); r = IShellLinkA_GetIconLocation(sl, buffer, sizeof(buffer), &i); - lok(SUCCEEDED(r), "GetIconLocation failed (0x%08lx)\n", r); - lok(lstrcmpi(buffer, desc->icon)==0, + lok(SUCCEEDED(r), "GetIconLocation failed (0x%08x)\n", r); + lok_todo_4(0x20, lstrcmpi(buffer, desc->icon)==0, "GetIconLocation returned '%s' instead of '%s'\n", buffer, desc->icon); - lok(i==desc->icon_id, + lok_todo_4(0x20, i==desc->icon_id, "GetIconLocation returned 0x%0x instead of 0x%0x\n", i, desc->icon_id); } @@ -448,8 +482,8 @@ static void check_lnk_(int line, const WCHAR* path, lnk_desc_t* desc) { WORD i=0xbeef; r = IShellLinkA_GetHotkey(sl, &i); - lok(SUCCEEDED(r), "GetHotkey failed (0x%08lx)\n", r); - lok(i==desc->hotkey, + lok(SUCCEEDED(r), "GetHotkey failed (0x%08x)\n", r); + lok_todo_4(0x40, i==desc->hotkey, "GetHotkey returned 0x%04x instead of 0x%04x\n", i, desc->hotkey); } @@ -462,7 +496,9 @@ static void test_load_save(void) lnk_desc_t desc; char mypath[MAX_PATH]; char mydir[MAX_PATH]; + char realpath[MAX_PATH]; char* p; + HANDLE hf; DWORD r; /* Save an empty .lnk file */ @@ -475,8 +511,7 @@ static void test_load_save(void) desc.path=""; desc.arguments=""; desc.icon=""; - check_lnk(lnkfile, &desc); - + check_lnk(lnkfile, &desc, 0x0); /* Point a .lnk file to nonexistent files */ desc.description=""; @@ -489,16 +524,15 @@ static void test_load_save(void) desc.icon_id=1234; desc.hotkey=0; create_lnk(lnkfile, &desc, 0); - check_lnk(lnkfile, &desc); + check_lnk(lnkfile, &desc, 0x0); r=GetModuleFileName(NULL, mypath, sizeof(mypath)); - ok(r>=0 && r=0 && r=0 && r=0 && r','p','l','T',']','j','I','{','j','f','(', + '=','1','&','L','[','-','8','1','-',']',':',':',0 }; + static const WCHAR comp[] = { + '2','6',',','!','!','g','x','s','f','(','N','g',']','q','F','`','H','{', + 'L','s','A','C','C','E','S','S','F','i','l','e','s','>','p','l','T',']', + 'j','I','{','j','f','(','=','1','&','L','[','-','8','1','-',']',0 }; + IShellLinkDataList *dl = NULL; + IShellLinkW *sl = NULL; + HRESULT r; + DWORD flags = 0; + EXP_DARWIN_LINK *dar; + + r = CoCreateInstance( &CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, + &IID_IShellLinkW, (LPVOID*)&sl ); + ok( r == S_OK || r == E_NOINTERFACE, "CoCreateInstance failed (0x%08x)\n", r); + if (!sl) + { + skip("no shelllink\n"); + return; + } + + r = IShellLinkW_QueryInterface( sl, &_IID_IShellLinkDataList, (LPVOID*) &dl ); + ok(r == S_OK, "IShellLinkW_QueryInterface failed (0x%08x)\n", r); + + if (!dl) + { + skip("no datalink interface\n"); + return; + } + + flags = 0; + r = dl->lpVtbl->GetFlags( dl, &flags ); + ok( r == S_OK, "GetFlags failed\n"); + ok( flags == 0, "GetFlags returned wrong flags\n"); + + dar = (void*)-1; + r = dl->lpVtbl->CopyDataBlock( dl, EXP_DARWIN_ID_SIG, (LPVOID*) &dar ); + ok( r == E_FAIL, "CopyDataBlock failed\n"); + ok( dar == NULL, "should be null\n"); + + r = IShellLinkW_SetPath(sl, lnk); + ok(r == S_OK, "set path failed\n"); + + /* + * The following crashes: + * r = dl->lpVtbl->GetFlags( dl, NULL ); + */ + + flags = 0; + r = dl->lpVtbl->GetFlags( dl, &flags ); + ok( r == S_OK, "GetFlags failed\n"); + ok( flags == (SLDF_HAS_DARWINID|SLDF_HAS_LOGO3ID), + "GetFlags returned wrong flags\n"); + + dar = NULL; + r = dl->lpVtbl->CopyDataBlock( dl, EXP_DARWIN_ID_SIG, (LPVOID*) &dar ); + ok( r == S_OK, "CopyDataBlock failed\n"); + + ok( dar && ((DATABLOCK_HEADER*)dar)->dwSignature == EXP_DARWIN_ID_SIG, "signature wrong\n"); + ok( dar && 0==lstrcmpW(dar->szwDarwinID, comp ), "signature wrong\n"); + + LocalFree( dar ); + + IUnknown_Release( dl ); + IShellLinkW_Release( sl ); } START_TEST(shelllink) { HRESULT r; + HMODULE hmod = GetModuleHandleA("shell32.dll"); + HMODULE hkernel32 = GetModuleHandleA("kernel32.dll"); + + pILFree = (fnILFree) GetProcAddress(hmod, (LPSTR)155); + pILIsEqual = (fnILIsEqual) GetProcAddress(hmod, (LPSTR)21); + pSHILCreateFromPath = (fnSHILCreateFromPath) GetProcAddress(hmod, (LPSTR)28); + + pGetLongPathNameA = (void *)GetProcAddress(hkernel32, "GetLongPathNameA"); r = CoInitialize(NULL); - ok(SUCCEEDED(r), "CoInitialize failed (0x%08lx)\n", r); + ok(SUCCEEDED(r), "CoInitialize failed (0x%08x)\n", r); if (!SUCCEEDED(r)) return; test_get_set(); test_load_save(); + test_datalink(); CoUninitialize(); } diff --git a/rostests/winetests/shell32/shlexec.c b/rostests/winetests/shell32/shlexec.c index 4d0750e27c2..66b94065269 100755 --- a/rostests/winetests/shell32/shlexec.c +++ b/rostests/winetests/shell32/shlexec.c @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ /* TODO: @@ -35,6 +35,10 @@ #include #include +/* Needed to get SEE_MASK_NOZONECHECKS with the PSDK */ +#define NTDDI_WINXPSP1 0x05010100 +#define NTDDI_VERSION NTDDI_WINXPSP1 + #include "wtypes.h" #include "winbase.h" #include "windef.h" @@ -49,17 +53,24 @@ static char argv0[MAX_PATH]; static int myARGC; static char** myARGV; static char tmpdir[MAX_PATH]; +static char child_file[MAX_PATH]; +static DLLVERSIONINFO dllver; -static const char* testfiles[]= + +/*** + * + * ShellExecute wrappers + * + ***/ +static void dump_child(void); + +static HANDLE hEvent; +static void init_event(const char* child_file) { - "%s\\test file.shlexec", - "%s\\test file.noassoc", - "%s\\test file.noassoc.shlexec", - "%s\\test file.shlexec.noassoc", - "%s\\test_shortcut_shlexec.lnk", - NULL -}; - + char* event_name; + event_name=strrchr(child_file, '\\')+1; + hEvent=CreateEvent(NULL, FALSE, FALSE, event_name); +} static void strcat_param(char* str, const char* param) { @@ -78,6 +89,8 @@ static void strcat_param(char* str, const char* param) static char shell_call[2048]=""; static int shell_execute(LPCSTR operation, LPCSTR file, LPCSTR parameters, LPCSTR directory) { + int rc; + strcpy(shell_call, "ShellExecute("); strcat_param(shell_call, operation); strcat(shell_call, ", "); @@ -90,13 +103,30 @@ static int shell_execute(LPCSTR operation, LPCSTR file, LPCSTR parameters, LPCST if (winetest_debug > 1) trace("%s\n", shell_call); + DeleteFile(child_file); SetLastError(0xcafebabe); + /* FIXME: We cannot use ShellExecuteEx() here because if there is no * association it displays the 'Open With' dialog and I could not find * a flag to prevent this. */ - return (int)ShellExecute(NULL, operation, file, parameters, directory, - SW_SHOWNORMAL); + rc=(int)ShellExecute(NULL, operation, file, parameters, directory, + SW_SHOWNORMAL); + + if (rc > 32) + { + int wait_rc; + wait_rc=WaitForSingleObject(hEvent, 5000); + ok(wait_rc==WAIT_OBJECT_0, "WaitForSingleObject returned %d\n", wait_rc); + } + /* The child process may have changed the result file, so let profile + * functions know about it + */ + WritePrivateProfileStringA(NULL, NULL, NULL, child_file); + if (rc > 32) + dump_child(); + + return rc; } static int shell_execute_ex(DWORD mask, LPCSTR operation, LPCSTR file, @@ -119,7 +149,7 @@ static int shell_execute_ex(DWORD mask, LPCSTR operation, LPCSTR file, trace("%s\n", shell_call); sei.cbSize=sizeof(sei); - sei.fMask=mask; + sei.fMask=SEE_MASK_NOCLOSEPROCESS | mask; sei.hwnd=NULL; sei.lpVerb=operation; sei.lpFile=file; @@ -131,16 +161,45 @@ static int shell_execute_ex(DWORD mask, LPCSTR operation, LPCSTR file, sei.lpClass=NULL; sei.hkeyClass=NULL; sei.dwHotKey=0; - sei.hIcon=NULL; + U(sei).hIcon=NULL; + sei.hProcess=NULL; /* Out */ + DeleteFile(child_file); SetLastError(0xcafebabe); success=ShellExecuteEx(&sei); rc=(int)sei.hInstApp; - ok((success && rc >= 32) || (!success && rc < 32), + ok((success && rc > 32) || (!success && rc <= 32), "%s rc=%d and hInstApp=%d is not allowed\n", shell_call, success, rc); + + if (rc > 32) + { + int wait_rc; + if (sei.hProcess!=NULL) + { + wait_rc=WaitForSingleObject(sei.hProcess, 5000); + ok(wait_rc==WAIT_OBJECT_0, "WaitForSingleObject(hProcess) returned %d\n", wait_rc); + } + wait_rc=WaitForSingleObject(hEvent, 5000); + ok(wait_rc==WAIT_OBJECT_0, "WaitForSingleObject returned %d\n", wait_rc); + } + /* The child process may have changed the result file, so let profile + * functions know about it + */ + WritePrivateProfileStringA(NULL, NULL, NULL, child_file); + if (rc > 32) + dump_child(); + return rc; } + + +/*** + * + * Functions to create / delete associations wrappers + * + ***/ + static void create_test_association(const char* extension) { HKEY hkey, hkey_shell; @@ -151,7 +210,7 @@ static void create_test_association(const char* extension) rc=RegCreateKeyEx(HKEY_CLASSES_ROOT, extension, 0, NULL, 0, KEY_SET_VALUE, NULL, &hkey, NULL); assert(rc==ERROR_SUCCESS); - rc=RegSetValueEx(hkey, NULL, 0, REG_SZ, class, strlen(class)+1); + rc=RegSetValueEx(hkey, NULL, 0, REG_SZ, (LPBYTE) class, strlen(class)+1); assert(rc==ERROR_SUCCESS); CloseHandle(hkey); @@ -174,7 +233,10 @@ static void delete_test_association(const char* extension) SHDeleteKey(HKEY_CLASSES_ROOT, extension); } -static void create_test_verb(const char* extension, const char* verb) +static void create_test_verb_dde(const char* extension, const char* verb, + int rawcmd, const char* cmdtail, const char *ddeexec, + const char *application, const char *topic, + const char *ifexec) { HKEY hkey_shell, hkey_verb, hkey_cmd; char shell[MAX_PATH]; @@ -192,56 +254,350 @@ static void create_test_verb(const char* extension, const char* verb) NULL, &hkey_cmd, NULL); assert(rc==ERROR_SUCCESS); - cmd=malloc(strlen(argv0)+13+1); - sprintf(cmd,"%s shlexec \"%%1\"", argv0); - rc=RegSetValueEx(hkey_cmd, NULL, 0, REG_SZ, cmd, strlen(cmd)+1); - assert(rc==ERROR_SUCCESS); + if (rawcmd) + { + rc=RegSetValueEx(hkey_cmd, NULL, 0, REG_SZ, (LPBYTE)cmdtail, strlen(cmdtail)+1); + } + else + { + cmd=malloc(strlen(argv0)+10+strlen(child_file)+2+strlen(cmdtail)+1); + sprintf(cmd,"%s shlexec \"%s\" %s", argv0, child_file, cmdtail); + rc=RegSetValueEx(hkey_cmd, NULL, 0, REG_SZ, (LPBYTE)cmd, strlen(cmd)+1); + assert(rc==ERROR_SUCCESS); + free(cmd); + } + + if (ddeexec) + { + HKEY hkey_ddeexec, hkey_application, hkey_topic, hkey_ifexec; + + rc=RegCreateKeyEx(hkey_verb, "ddeexec", 0, NULL, 0, KEY_SET_VALUE | + KEY_CREATE_SUB_KEY, NULL, &hkey_ddeexec, NULL); + assert(rc==ERROR_SUCCESS); + rc=RegSetValueEx(hkey_ddeexec, NULL, 0, REG_SZ, (LPBYTE)ddeexec, + strlen(ddeexec)+1); + assert(rc==ERROR_SUCCESS); + if (application) + { + rc=RegCreateKeyEx(hkey_ddeexec, "application", 0, NULL, 0, KEY_SET_VALUE, + NULL, &hkey_application, NULL); + assert(rc==ERROR_SUCCESS); + rc=RegSetValueEx(hkey_application, NULL, 0, REG_SZ, (LPBYTE)application, + strlen(application)+1); + assert(rc==ERROR_SUCCESS); + CloseHandle(hkey_application); + } + if (topic) + { + rc=RegCreateKeyEx(hkey_ddeexec, "topic", 0, NULL, 0, KEY_SET_VALUE, + NULL, &hkey_topic, NULL); + assert(rc==ERROR_SUCCESS); + rc=RegSetValueEx(hkey_topic, NULL, 0, REG_SZ, (LPBYTE)topic, + strlen(topic)+1); + assert(rc==ERROR_SUCCESS); + CloseHandle(hkey_topic); + } + if (ifexec) + { + rc=RegCreateKeyEx(hkey_ddeexec, "ifexec", 0, NULL, 0, KEY_SET_VALUE, + NULL, &hkey_ifexec, NULL); + assert(rc==ERROR_SUCCESS); + rc=RegSetValueEx(hkey_ifexec, NULL, 0, REG_SZ, (LPBYTE)ifexec, + strlen(ifexec)+1); + assert(rc==ERROR_SUCCESS); + CloseHandle(hkey_ifexec); + } + CloseHandle(hkey_ddeexec); + } - free(cmd); CloseHandle(hkey_shell); CloseHandle(hkey_verb); CloseHandle(hkey_cmd); } +static void create_test_verb(const char* extension, const char* verb, + int rawcmd, const char* cmdtail) +{ + create_test_verb_dde(extension, verb, rawcmd, cmdtail, NULL, NULL, + NULL, NULL); +} + +/*** + * + * Functions to check that the child process was started just right + * (borrowed from dlls/kernel32/tests/process.c) + * + ***/ + +static const char* encodeA(const char* str) +{ + static char encoded[2*1024+1]; + char* ptr; + size_t len,i; + + if (!str) return ""; + len = strlen(str) + 1; + if (len >= sizeof(encoded)/2) + { + fprintf(stderr, "string is too long!\n"); + assert(0); + } + ptr = encoded; + for (i = 0; i < len; i++) + sprintf(&ptr[i * 2], "%02x", (unsigned char)str[i]); + ptr[2 * len] = '\0'; + return ptr; +} + +static unsigned decode_char(char c) +{ + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + assert(c >= 'A' && c <= 'F'); + return c - 'A' + 10; +} + +static char* decodeA(const char* str) +{ + static char decoded[1024]; + char* ptr; + size_t len,i; + + len = strlen(str) / 2; + if (!len--) return NULL; + if (len >= sizeof(decoded)) + { + fprintf(stderr, "string is too long!\n"); + assert(0); + } + ptr = decoded; + for (i = 0; i < len; i++) + ptr[i] = (decode_char(str[2 * i]) << 4) | decode_char(str[2 * i + 1]); + ptr[len] = '\0'; + return ptr; +} + +static void childPrintf(HANDLE h, const char* fmt, ...) +{ + va_list valist; + char buffer[1024]; + DWORD w; + + va_start(valist, fmt); + vsprintf(buffer, fmt, valist); + va_end(valist); + WriteFile(h, buffer, strlen(buffer), &w, NULL); +} + +static void doChild(int argc, char** argv) +{ + char* filename; + HANDLE hFile; + int i; + + filename=argv[2]; + hFile=CreateFileA(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0); + if (hFile == INVALID_HANDLE_VALUE) + return; + + /* Arguments */ + childPrintf(hFile, "[Arguments]\r\n"); + if (winetest_debug > 2) + trace("argcA=%d\n", argc); + childPrintf(hFile, "argcA=%d\r\n", argc); + for (i = 0; i < argc; i++) + { + if (winetest_debug > 2) + trace("argvA%d=%s\n", i, argv[i]); + childPrintf(hFile, "argvA%d=%s\r\n", i, encodeA(argv[i])); + } + CloseHandle(hFile); + + init_event(filename); + SetEvent(hEvent); + CloseHandle(hEvent); +} + +static char* getChildString(const char* sect, const char* key) +{ + char buf[1024]; + char* ret; + + GetPrivateProfileStringA(sect, key, "-", buf, sizeof(buf), child_file); + if (buf[0] == '\0' || (buf[0] == '-' && buf[1] == '\0')) return NULL; + assert(!(strlen(buf) & 1)); + ret = decodeA(buf); + return ret; +} + +static void dump_child(void) +{ + if (winetest_debug > 1) + { + char key[18]; + char* str; + int i, c; + + c=GetPrivateProfileIntA("Arguments", "argcA", -1, child_file); + trace("argcA=%d\n",c); + for (i=0;irc==0) + if ((test->todo & 0x40)==0) { - if (test->todo) - { - todo_wine - { - ok(rc>=32, "%s failed: rc=%d err=%ld\n", shell_call, - rc, GetLastError()); - } - } - else - { - ok(rc>=32, "%s failed: rc=%d err=%ld\n", shell_call, - rc, GetLastError()); - } + rc=shell_execute(test->verb, filename, NULL, NULL); } else { - if (test->todo) + char quoted[MAX_PATH + 2]; + sprintf(quoted, "\"%s\"", filename); + rc=shell_execute(test->verb, quoted, NULL, NULL); + } + if (rc > 32) + rc=33; + if ((test->todo & 0x1)==0) + { + ok(rc==test->rc, "%s failed: rc=%d err=%d\n", shell_call, + rc, GetLastError()); + } + else todo_wine + { + ok(rc==test->rc, "%s failed: rc=%d err=%d\n", shell_call, + rc, GetLastError()); + } + if (rc == 33) + { + const char* verb; + if ((test->todo & 0x2)==0) { - todo_wine - { - ok(rc==test->rc, "%s returned %d\n", shell_call, rc); - } + okChildInt("argcA", 5); } - else + else todo_wine { - ok(rc==test->rc, "%s returned %d\n", shell_call, rc); + okChildInt("argcA", 5); + } + verb=(test->verb ? test->verb : "Open"); + if ((test->todo & 0x4)==0) + { + okChildString("argvA3", verb); + } + else todo_wine + { + okChildString("argvA3", verb); + } + if ((test->todo & 0x8)==0) + { + okChildPath("argvA4", filename); + } + else todo_wine + { + okChildPath("argvA4", filename); } } test++; } - hdll=GetModuleHandleA("shell32.dll"); - pDllGetVersion=(void*)GetProcAddress(hdll, "DllGetVersion"); - if (pDllGetVersion) + test=noquotes_tests; + while (test->basename) { - dllver.cbSize=sizeof(dllver); - pDllGetVersion(&dllver); - trace("major=%ld minor=%ld build=%ld platform=%ld\n", - dllver.dwMajorVersion, dllver.dwMinorVersion, - dllver.dwBuildNumber, dllver.dwPlatformID); + sprintf(filename, test->basename, tmpdir); + rc=shell_execute(test->verb, filename, NULL, NULL); + if (rc > 32) + rc=33; + if ((test->todo & 0x1)==0) + { + ok(rc==test->rc, "%s failed: rc=%d err=%d\n", shell_call, + rc, GetLastError()); + } + else todo_wine + { + ok(rc==test->rc, "%s failed: rc=%d err=%d\n", shell_call, + rc, GetLastError()); + } + if (rc==0) + { + int count; + const char* verb; + char* str; + verb=(test->verb ? test->verb : "Open"); + if ((test->todo & 0x4)==0) + { + okChildString("argvA3", verb); + } + else todo_wine + { + okChildString("argvA3", verb); + } + + count=4; + str=filename; + while (1) + { + char attrib[18]; + char* space; + space=strchr(str, ' '); + if (space) + *space='\0'; + sprintf(attrib, "argvA%d", count); + if ((test->todo & 0x8)==0) + { + okChildPath(attrib, str); + } + else todo_wine + { + okChildPath(attrib, str); + } + count++; + if (!space) + break; + str=space+1; + } + if ((test->todo & 0x2)==0) + { + okChildInt("argcA", count); + } + else todo_wine + { + okChildInt("argcA", count); + } + } + test++; + } + + if (dllver.dwMajorVersion != 0) + { /* The more recent versions of shell32.dll accept quoted filenames * while older ones (e.g. 4.00) don't. Still we want to test this * because IE 6 depends on the new behavior. @@ -311,15 +747,83 @@ static void test_filename() */ sprintf(filename, "\"%s\\test file.shlexec\"", tmpdir); rc=shell_execute(NULL, filename, NULL, NULL); - ok(rc>=32, "%s failed: rc=%d err=%ld\n", shell_call, rc, + ok(rc > 32, "%s failed: rc=%d err=%d\n", shell_call, rc, GetLastError()); + okChildInt("argcA", 5); + okChildString("argvA3", "Open"); + sprintf(filename, "%s\\test file.shlexec", tmpdir); + okChildPath("argvA4", filename); + } +} - if (dllver.dwMajorVersion>=6) +static void test_find_executable(void) +{ + char filename[MAX_PATH]; + char command[MAX_PATH]; + const filename_tests_t* test; + int rc; + + create_test_association(".sfe"); + create_test_verb(".sfe", "Open", 1, "%1"); + + /* Don't test FindExecutable(..., NULL), it always crashes */ + + strcpy(command, "your word"); + rc=(int)FindExecutableA(NULL, NULL, command); + ok(rc == SE_ERR_FNF || rc > 32 /* nt4 */, "FindExecutable(NULL) returned %d\n", rc); + ok(strcmp(command, "your word") != 0, "FindExecutable(NULL) returned command=[%s]\n", command); + + strcpy(command, "your word"); + rc=(int)FindExecutableA(tmpdir, NULL, command); + ok(rc == SE_ERR_NOASSOC /* >= win2000 */ || rc > 32 /* win98, nt4 */, "FindExecutable(NULL) returned %d\n", rc); + ok(strcmp(command, "your word") != 0, "FindExecutable(NULL) returned command=[%s]\n", command); + + sprintf(filename, "%s\\test file.sfe", tmpdir); + rc=(int)FindExecutableA(filename, NULL, command); + ok(rc > 32, "FindExecutable(%s) returned %d\n", filename, rc); + /* Depending on the platform, command could be '%1' or 'test file.sfe' */ + + rc=(int)FindExecutableA("test file.sfe", tmpdir, command); + ok(rc > 32, "FindExecutable(%s) returned %d\n", filename, rc); + + rc=(int)FindExecutableA("test file.sfe", NULL, command); + todo_wine ok(rc == SE_ERR_FNF, "FindExecutable(%s) returned %d\n", filename, rc); + + delete_test_association(".sfe"); + + create_test_association(".shl"); + create_test_verb(".shl", "Open", 0, "Open"); + + sprintf(filename, "%s\\test file.shl", tmpdir); + rc=(int)FindExecutableA(filename, NULL, command); + ok(rc == SE_ERR_FNF /* NT4 */ || rc > 32, "FindExecutable(%s) returned %d\n", filename, rc); + + sprintf(filename, "%s\\test file.shlfoo", tmpdir); + rc=(int)FindExecutableA(filename, NULL, command); + + delete_test_association(".shl"); + + if (rc > 32) + { + /* On Windows XP and 2003 FindExecutable() is completely broken. + * Probably what it does is convert the filename to 8.3 format, + * which as a side effect converts the '.shlfoo' extension to '.shl', + * and then tries to find an association for '.shl'. This means it + * will normally fail on most extensions with more than 3 characters, + * like '.mpeg', etc. + * Also it means we cannot do any other test. + */ + trace("FindExecutable() is broken -> skipping 4+ character extension tests\n"); + return; + } + + test=filename_tests; + while (test->basename) + { + sprintf(filename, test->basename, tmpdir); + if (strchr(filename, '/')) { - /* Recent versions of shell32.dll accept '/'s in shortcut paths. - * Older versions don't or are quite buggy in this regard. - */ - sprintf(filename, "%s\\test_shortcut_shlexec.lnk", tmpdir); + char* c; c=filename; while (*c) { @@ -327,48 +831,587 @@ static void test_filename() *c='/'; c++; } - rc=shell_execute(NULL, filename, NULL, NULL); - todo_wine { - ok(rc>=32, "%s failed: rc=%d err=%ld\n", shell_call, rc, - GetLastError()); + } + /* Win98 does not '\0'-terminate command! */ + memset(command, '\0', sizeof(command)); + rc=(int)FindExecutableA(filename, NULL, command); + if (rc > 32) + rc=33; + if ((test->todo & 0x10)==0) + { + ok(rc==test->rc, "FindExecutable(%s) failed: rc=%d\n", filename, rc); + } + else todo_wine + { + ok(rc==test->rc, "FindExecutable(%s) failed: rc=%d\n", filename, rc); + } + if (rc > 32) + { + int equal; + equal=strcmp(command, argv0) == 0 || + /* NT4 returns an extra 0x8 character! */ + (strlen(command) == strlen(argv0)+1 && strncmp(command, argv0, strlen(argv0)) == 0); + if ((test->todo & 0x20)==0) + { + ok(equal, "FindExecutable(%s) returned command='%s' instead of '%s'\n", + filename, command, argv0); + } + else todo_wine + { + ok(equal, "FindExecutable(%s) returned command='%s' instead of '%s'\n", + filename, command, argv0); } } + test++; } } -static void test_exes() +static filename_tests_t lnk_tests[]= +{ + /* Pass bad / nonexistent filenames as a parameter */ + {NULL, "%s\\nonexistent.shlexec", 0xa, 33}, + {NULL, "%s\\nonexistent.noassoc", 0xa, 33}, + + /* Pass regular paths as a parameter */ + {NULL, "%s\\test file.shlexec", 0xa, 33}, + {NULL, "%s/%%nasty%% $file.shlexec", 0xa, 33}, + + /* Pass filenames with no association as a parameter */ + {NULL, "%s\\test file.noassoc", 0xa, 33}, + + {NULL, NULL, 0} +}; + +static void test_lnks(void) { char filename[MAX_PATH]; + char params[MAX_PATH]; + const filename_tests_t* test; int rc; + sprintf(filename, "%s\\test_shortcut_shlexec.lnk", tmpdir); + rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL); + ok(rc > 32, "%s failed: rc=%d err=%d\n", shell_call, rc, + GetLastError()); + okChildInt("argcA", 5); + okChildString("argvA3", "Open"); + sprintf(filename, "%s\\test file.shlexec", tmpdir); + okChildPath("argvA4", filename); + + sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir); + rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL); + ok(rc > 32, "%s failed: rc=%d err=%d\n", shell_call, rc, + GetLastError()); + okChildInt("argcA", 4); + okChildString("argvA3", "Lnk"); + + if (dllver.dwMajorVersion>=6) + { + char* c; + /* Recent versions of shell32.dll accept '/'s in shortcut paths. + * Older versions don't or are quite buggy in this regard. + */ + sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir); + c=filename; + while (*c) + { + if (*c=='\\') + *c='/'; + c++; + } + rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, NULL, NULL); + ok(rc > 32, "%s failed: rc=%d err=%d\n", shell_call, rc, + GetLastError()); + okChildInt("argcA", 4); + okChildString("argvA3", "Lnk"); + } + + sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir); + test=lnk_tests; + while (test->basename) + { + params[0]='\"'; + sprintf(params+1, test->basename, tmpdir); + strcat(params,"\""); + rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, filename, params, + NULL); + if (rc > 32) + rc=33; + if ((test->todo & 0x1)==0) + { + ok(rc==test->rc, "%s failed: rc=%d err=%d\n", shell_call, + rc, GetLastError()); + } + else todo_wine + { + ok(rc==test->rc, "%s failed: rc=%d err=%d\n", shell_call, + rc, GetLastError()); + } + if (rc==0) + { + if ((test->todo & 0x2)==0) + { + okChildInt("argcA", 5); + } + else + { + okChildInt("argcA", 5); + } + if ((test->todo & 0x4)==0) + { + okChildString("argvA3", "Lnk"); + } + else todo_wine + { + okChildString("argvA3", "Lnk"); + } + sprintf(params, test->basename, tmpdir); + if ((test->todo & 0x8)==0) + { + okChildPath("argvA4", params); + } + else + { + okChildPath("argvA4", params); + } + } + test++; + } +} + + +static void test_exes(void) +{ + char filename[MAX_PATH]; + char params[1024]; + int rc; + + sprintf(params, "shlexec \"%s\" Exec", child_file); + /* We need NOZONECHECKS on Win2003 to block a dialog */ - rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, argv0, "shlexec -nop", + rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, argv0, params, NULL); - ok(rc>=32, "%s returned %d\n", shell_call, rc); + ok(rc > 32, "%s returned %d\n", shell_call, rc); + okChildInt("argcA", 4); + okChildString("argvA3", "Exec"); sprintf(filename, "%s\\test file.noassoc", tmpdir); if (CopyFile(argv0, filename, FALSE)) { - rc=shell_execute(NULL, filename, "shlexec -nop", NULL); + rc=shell_execute(NULL, filename, params, NULL); todo_wine { ok(rc==SE_ERR_NOASSOC, "%s succeeded: rc=%d\n", shell_call, rc); } } } - -static void init_test() +static void test_exes_long(void) { + char filename[MAX_PATH]; + char params[2024]; + char longparam[MAX_PATH]; + int rc; + + for (rc = 0; rc < MAX_PATH; rc++) + longparam[rc]='a'+rc%26; + longparam[MAX_PATH-1]=0; + + + sprintf(params, "shlexec \"%s\" %s", child_file,longparam); + + /* We need NOZONECHECKS on Win2003 to block a dialog */ + rc=shell_execute_ex(SEE_MASK_NOZONECHECKS, NULL, argv0, params, + NULL); + ok(rc > 32, "%s returned %d\n", shell_call, rc); + okChildInt("argcA", 4); + okChildString("argvA3", longparam); + + sprintf(filename, "%s\\test file.noassoc", tmpdir); + if (CopyFile(argv0, filename, FALSE)) + { + rc=shell_execute(NULL, filename, params, NULL); + todo_wine { + ok(rc==SE_ERR_NOASSOC, "%s succeeded: rc=%d\n", shell_call, rc); + } + } +} + +typedef struct +{ + const char* command; + const char* ddeexec; + const char* application; + const char* topic; + const char* ifexec; + int expectedArgs; + const char* expectedDdeExec; + int todo; + int rc; +} dde_tests_t; + +static dde_tests_t dde_tests[] = +{ + /* Test passing and not passing command-line + * argument, no DDE */ + {"", NULL, NULL, NULL, NULL, FALSE, "", 0x0, 33}, + {"\"%1\"", NULL, NULL, NULL, NULL, TRUE, "", 0x0, 33}, + + /* Test passing and not passing command-line + * argument, with DDE */ + {"", "[open(\"%1\")]", "shlexec", "dde", NULL, FALSE, "[open(\"%s\")]", 0x0, 33}, + {"\"%1\"", "[open(\"%1\")]", "shlexec", "dde", NULL, TRUE, "[open(\"%s\")]", 0x0, 33}, + + /* Test unquoted %1 in command and ddeexec + * (test filename has space) */ + {"%1", "[open(%1)]", "shlexec", "dde", NULL, 2, "[open(%s)]", 0x0, 33}, + + /* Test ifexec precedence over ddeexec */ + {"", "[open(\"%1\")]", "shlexec", "dde", "[ifexec(\"%1\")]", FALSE, "[ifexec(\"%s\")]", 0x0, 33}, + + /* Test default DDE topic */ + {"", "[open(\"%1\")]", "shlexec", NULL, NULL, FALSE, "[open(\"%s\")]", 0x0, 33}, + + /* Test default DDE application */ + {"", "[open(\"%1\")]", NULL, "dde", NULL, FALSE, "[open(\"%s\")]", 0x0, 33}, + + {NULL, NULL, NULL, NULL, NULL, 0, 0x0, 0} +}; + +static DWORD ddeInst; +static HSZ hszTopic; +static char ddeExec[MAX_PATH], ddeApplication[MAX_PATH]; +static BOOL denyNextConnection; + +static HDDEDATA CALLBACK ddeCb(UINT uType, UINT uFmt, HCONV hConv, + HSZ hsz1, HSZ hsz2, HDDEDATA hData, + ULONG_PTR dwData1, ULONG_PTR dwData2) +{ + DWORD size = 0; + + if (winetest_debug > 2) + trace("dde_cb: %04x, %04x, %p, %p, %p, %p, %08lx, %08lx\n", + uType, uFmt, hConv, hsz1, hsz2, hData, dwData1, dwData2); + + switch (uType) + { + case XTYP_CONNECT: + if (!DdeCmpStringHandles(hsz1, hszTopic)) + { + if (denyNextConnection) + denyNextConnection = FALSE; + else + { + size = DdeQueryString(ddeInst, hsz2, ddeApplication, MAX_PATH, CP_WINANSI); + assert(size < MAX_PATH); + return (HDDEDATA)TRUE; + } + } + return (HDDEDATA)FALSE; + + case XTYP_EXECUTE: + size = DdeGetData(hData, (LPBYTE)ddeExec, MAX_PATH, 0L); + assert(size < MAX_PATH); + DdeFreeDataHandle(hData); + return (HDDEDATA)DDE_FACK; + + default: + return NULL; + } +} + +typedef struct +{ + char *filename; + DWORD threadIdParent; +} dde_thread_info_t; + +static DWORD CALLBACK ddeThread(LPVOID arg) +{ + dde_thread_info_t *info = (dde_thread_info_t *)arg; + assert(info && info->filename); + PostThreadMessage(info->threadIdParent, + WM_QUIT, + shell_execute_ex(SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI, NULL, info->filename, NULL, NULL), + 0L); + ExitThread(0); +} + +/* ShellExecute won't successfully send DDE commands to console applications after starting them, + * so we run a DDE server in this application, deny the first connection request to make + * ShellExecute start the application, and then process the next DDE connection in this application + * to see the execute command that is sent. */ +static void test_dde(void) +{ + char filename[MAX_PATH], defApplication[MAX_PATH]; + HSZ hszApplication; + dde_thread_info_t info = { filename, GetCurrentThreadId() }; + const dde_tests_t* test; + char params[1024]; + DWORD threadId; + MSG msg; + int rc; + + ddeInst = 0; + rc = DdeInitializeA(&ddeInst, ddeCb, CBF_SKIP_ALLNOTIFICATIONS | CBF_FAIL_ADVISES | + CBF_FAIL_POKES | CBF_FAIL_REQUESTS, 0L); + assert(rc == DMLERR_NO_ERROR); + + sprintf(filename, "%s\\test file.sde", tmpdir); + + /* Default service is application name minus path and extension */ + strcpy(defApplication, strrchr(argv0, '\\')+1); + *strchr(defApplication, '.') = 0; + + test = dde_tests; + while (test->command) + { + create_test_association(".sde"); + create_test_verb_dde(".sde", "Open", 0, test->command, test->ddeexec, + test->application, test->topic, test->ifexec); + hszApplication = DdeCreateStringHandleA(ddeInst, test->application ? + test->application : defApplication, CP_WINANSI); + hszTopic = DdeCreateStringHandleA(ddeInst, test->topic ? test->topic : SZDDESYS_TOPIC, + CP_WINANSI); + assert(hszApplication && hszTopic); + assert(DdeNameService(ddeInst, hszApplication, 0L, DNS_REGISTER)); + denyNextConnection = TRUE; + ddeExec[0] = 0; + + assert(CreateThread(NULL, 0, ddeThread, (LPVOID)&info, 0, &threadId)); + while (GetMessage(&msg, NULL, 0, 0)) DispatchMessage(&msg); + rc = msg.wParam > 32 ? 33 : msg.wParam; + if ((test->todo & 0x1)==0) + { + ok(rc==test->rc, "%s failed: rc=%d err=%d\n", shell_call, + rc, GetLastError()); + } + else todo_wine + { + ok(rc==test->rc, "%s failed: rc=%d err=%d\n", shell_call, + rc, GetLastError()); + } + if (rc == 33) + { + if ((test->todo & 0x2)==0) + { + okChildInt("argcA", test->expectedArgs + 3); + } + else todo_wine + { + okChildInt("argcA", test->expectedArgs + 3); + } + if (test->expectedArgs == 1) + { + if ((test->todo & 0x4) == 0) + { + okChildPath("argvA3", filename); + } + else todo_wine + { + okChildPath("argvA3", filename); + } + } + if ((test->todo & 0x8) == 0) + { + sprintf(params, test->expectedDdeExec, filename); + ok(StrCmpPath(params, ddeExec) == 0, + "ddeexec expected '%s', got '%s'\n", params, ddeExec); + } + else todo_wine + { + sprintf(params, test->expectedDdeExec, filename); + ok(StrCmpPath(params, ddeExec) == 0, + "ddeexec expected '%s', got '%s'\n", params, ddeExec); + } + } + + assert(DdeNameService(ddeInst, hszApplication, 0L, DNS_UNREGISTER)); + assert(DdeFreeStringHandle(ddeInst, hszTopic)); + assert(DdeFreeStringHandle(ddeInst, hszApplication)); + delete_test_association(".sde"); + test++; + } + + assert(DdeUninitialize(ddeInst)); +} + +#define DDE_DEFAULT_APP_VARIANTS 2 +typedef struct +{ + const char* command; + const char* expectedDdeApplication[DDE_DEFAULT_APP_VARIANTS]; + int todo; + int rc[DDE_DEFAULT_APP_VARIANTS]; +} dde_default_app_tests_t; + +static dde_default_app_tests_t dde_default_app_tests[] = +{ + /* Windows XP and 98 handle default DDE app names in different ways. + * The application name we see in the first test determines the pattern + * of application names and return codes we will look for. */ + + /* Test unquoted existing filename with a space */ + {"%s\\test file.exe", {"test file", "test"}, 0x0, {33, 33}}, + {"%s\\test file.exe param", {"test file", "test"}, 0x0, {33, 33}}, + + /* Test quoted existing filename with a space */ + {"\"%s\\test file.exe\"", {"test file", "test file"}, 0x0, {33, 33}}, + {"\"%s\\test file.exe\" param", {"test file", "test file"}, 0x0, {33, 33}}, + + /* Test unquoted filename with a space that doesn't exist, but + * test2.exe does */ + {"%s\\test2 file.exe", {"test2", "test2"}, 0x0, {33, 33}}, + {"%s\\test2 file.exe param", {"test2", "test2"}, 0x0, {33, 33}}, + + /* Test quoted filename with a space that does not exist */ + {"\"%s\\test2 file.exe\"", {"", "test2 file"}, 0x0, {5, 33}}, + {"\"%s\\test2 file.exe\" param", {"", "test2 file"}, 0x0, {5, 33}}, + + /* Test filename supplied without the extension */ + {"%s\\test2", {"test2", "test2"}, 0x0, {33, 33}}, + {"%s\\test2 param", {"test2", "test2"}, 0x0, {33, 33}}, + + /* Test an unquoted nonexistent filename */ + {"%s\\notexist.exe", {"", "notexist"}, 0x0, {5, 33}}, + {"%s\\notexist.exe param", {"", "notexist"}, 0x0, {5, 33}}, + + /* Test an application that will be found on the path */ + {"cmd", {"cmd", "cmd"}, 0x0, {33, 33}}, + {"cmd param", {"cmd", "cmd"}, 0x0, {33, 33}}, + + /* Test an application that will not be found on the path */ + {"xyzwxyzwxyz", {"", "xyzwxyzwxyz"}, 0x0, {5, 33}}, + {"xyzwxyzwxyz param", {"", "xyzwxyzwxyz"}, 0x0, {5, 33}}, + + {NULL, {NULL}, 0, {0}} +}; + +static void test_dde_default_app(void) +{ + char filename[MAX_PATH]; + HSZ hszApplication; + dde_thread_info_t info = { filename, GetCurrentThreadId() }; + const dde_default_app_tests_t* test; + char params[1024]; + DWORD threadId; + MSG msg; + int rc, which = 0; + + ddeInst = 0; + rc = DdeInitializeA(&ddeInst, ddeCb, CBF_SKIP_ALLNOTIFICATIONS | CBF_FAIL_ADVISES | + CBF_FAIL_POKES | CBF_FAIL_REQUESTS, 0L); + assert(rc == DMLERR_NO_ERROR); + + sprintf(filename, "%s\\test file.sde", tmpdir); + + /* It is strictly not necessary to register an application name here, but wine's + * DdeNameService implementation complains if 0L is passed instead of + * hszApplication with DNS_FILTEROFF */ + hszApplication = DdeCreateStringHandleA(ddeInst, "shlexec", CP_WINANSI); + hszTopic = DdeCreateStringHandleA(ddeInst, "shlexec", CP_WINANSI); + assert(hszApplication && hszTopic); + assert(DdeNameService(ddeInst, hszApplication, 0L, DNS_REGISTER | DNS_FILTEROFF)); + + test = dde_default_app_tests; + while (test->command) + { + create_test_association(".sde"); + sprintf(params, test->command, tmpdir); + create_test_verb_dde(".sde", "Open", 1, params, "[test]", NULL, + "shlexec", NULL); + denyNextConnection = FALSE; + ddeApplication[0] = 0; + + /* No application will be run as we will respond to the first DDE event, + * so don't wait for it */ + SetEvent(hEvent); + + assert(CreateThread(NULL, 0, ddeThread, (LPVOID)&info, 0, &threadId)); + while (GetMessage(&msg, NULL, 0, 0)) DispatchMessage(&msg); + rc = msg.wParam > 32 ? 33 : msg.wParam; + + /* First test, find which set of test data we expect to see */ + if (test == dde_default_app_tests) + { + int i; + for (i=0; iexpectedDdeApplication[i])) + { + which = i; + break; + } + } + if (i == DDE_DEFAULT_APP_VARIANTS) + skip("Default DDE application test does not match any available results, using first expected data set.\n"); + } + + if ((test->todo & 0x1)==0) + { + ok(rc==test->rc[which], "%s failed: rc=%d err=%d\n", shell_call, + rc, GetLastError()); + } + else todo_wine + { + ok(rc==test->rc[which], "%s failed: rc=%d err=%d\n", shell_call, + rc, GetLastError()); + } + if (rc == 33) + { + if ((test->todo & 0x2)==0) + { + ok(!strcmp(ddeApplication, test->expectedDdeApplication[which]), + "Expected application '%s', got '%s'\n", + test->expectedDdeApplication[which], ddeApplication); + } + else todo_wine + { + ok(!strcmp(ddeApplication, test->expectedDdeApplication[which]), + "Expected application '%s', got '%s'\n", + test->expectedDdeApplication[which], ddeApplication); + } + } + + delete_test_association(".sde"); + test++; + } + + assert(DdeNameService(ddeInst, hszApplication, 0L, DNS_UNREGISTER)); + assert(DdeFreeStringHandle(ddeInst, hszTopic)); + assert(DdeFreeStringHandle(ddeInst, hszApplication)); + assert(DdeUninitialize(ddeInst)); +} + +static void init_test(void) +{ + HMODULE hdll; + HRESULT (WINAPI *pDllGetVersion)(DLLVERSIONINFO*); char filename[MAX_PATH]; WCHAR lnkfile[MAX_PATH]; + char params[1024]; const char* const * testfile; lnk_desc_t desc; DWORD rc; HRESULT r; + hdll=GetModuleHandleA("shell32.dll"); + pDllGetVersion=(void*)GetProcAddress(hdll, "DllGetVersion"); + if (pDllGetVersion) + { + dllver.cbSize=sizeof(dllver); + pDllGetVersion(&dllver); + trace("major=%d minor=%d build=%d platform=%d\n", + dllver.dwMajorVersion, dllver.dwMinorVersion, + dllver.dwBuildNumber, dllver.dwPlatformID); + } + else + { + memset(&dllver, 0, sizeof(dllver)); + } + r = CoInitialize(NULL); - ok(SUCCEEDED(r), "CoInitialize failed (0x%08lx)\n", r); + ok(SUCCEEDED(r), "CoInitialize failed (0x%08x)\n", r); if (!SUCCEEDED(r)) exit(1); @@ -382,6 +1425,8 @@ static void init_test() } GetTempPathA(sizeof(tmpdir)/sizeof(*tmpdir), tmpdir); + assert(GetTempFileNameA(tmpdir, "wt", 0, child_file)!=0); + init_event(child_file); /* Set up the test files */ testfile=testfiles; @@ -394,7 +1439,7 @@ static void init_test() FILE_ATTRIBUTE_NORMAL, NULL); if (hfile==INVALID_HANDLE_VALUE) { - trace("unable to create '%s': err=%ld\n", filename, GetLastError()); + trace("unable to create '%s': err=%d\n", filename, GetLastError()); assert(0); } CloseHandle(hfile); @@ -409,7 +1454,21 @@ static void init_test() sprintf(filename, "%s\\test file.shlexec", tmpdir); desc.path=filename; desc.pidl=NULL; - desc.arguments=""; + desc.arguments="ignored"; + desc.showcmd=0; + desc.icon=NULL; + desc.icon_id=0; + desc.hotkey=0; + create_lnk(lnkfile, &desc, 0); + + sprintf(filename, "%s\\test_shortcut_exe.lnk", tmpdir); + MultiByteToWideChar(CP_ACP, 0, filename, -1, lnkfile, sizeof(lnkfile)/sizeof(*lnkfile)); + desc.description=NULL; + desc.workdir=NULL; + desc.path=argv0; + desc.pidl=NULL; + sprintf(params, "shlexec \"%s\" Lnk", child_file); + desc.arguments=params; desc.showcmd=0; desc.icon=NULL; desc.icon_id=0; @@ -418,10 +1477,15 @@ static void init_test() /* Create a basic association suitable for most tests */ create_test_association(".shlexec"); - create_test_verb(".shlexec", "Open"); + create_test_verb(".shlexec", "Open", 0, "Open \"%1\""); + create_test_verb(".shlexec", "NoQuotes", 0, "NoQuotes %1"); + create_test_verb(".shlexec", "LowerL", 0, "LowerL %l"); + create_test_verb(".shlexec", "QuotedLowerL", 0, "QuotedLowerL \"%l\""); + create_test_verb(".shlexec", "UpperL", 0, "UpperL %L"); + create_test_verb(".shlexec", "QuotedUpperL", 0, "QuotedUpperL \"%L\""); } -static void cleanup_test() +static void cleanup_test(void) { char filename[MAX_PATH]; const char* const * testfile; @@ -434,10 +1498,13 @@ static void cleanup_test() DeleteFile(filename); testfile++; } + DeleteFile(child_file); /* Delete the test association */ delete_test_association(".shlexec"); + CloseHandle(hEvent); + CoUninitialize(); } @@ -445,18 +1512,21 @@ START_TEST(shlexec) { myARGC = winetest_get_mainargs(&myARGV); - if (myARGC>=3) + if (myARGC >= 3) { - /* FIXME: We should dump the parameters we got - * and have the parent verify them - */ + doChild(myARGC, myARGV); exit(0); } init_test(); test_filename(); + test_find_executable(); + test_lnks(); test_exes(); + test_exes_long(); + test_dde(); + test_dde_default_app(); cleanup_test(); } diff --git a/rostests/winetests/shell32/shlfileop.c b/rostests/winetests/shell32/shlfileop.c index 03b197d3233..7c5354bfde7 100644 --- a/rostests/winetests/shell32/shlfileop.c +++ b/rostests/winetests/shell32/shlfileop.c @@ -15,32 +15,37 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #define WINE_NOWINSOCK -#include "windef.h" -#include "winbase.h" -#include "wtypes.h" +#include #include "shellapi.h" #include "shlobj.h" #include "wine/test.h" -CHAR CURR_DIR[MAX_PATH]; +#ifndef FOF_NORECURSION +#define FOF_NORECURSION 0x1000 +#endif + +static CHAR CURR_DIR[MAX_PATH]; +static const WCHAR UNICODE_PATH[] = {'c',':','\\',0x00c4,'\0','\0'}; + /* "c:\Ä", or "c:\A" with diaeresis */ + /* Double-null termination needed for pFrom field of SHFILEOPSTRUCT */ static HMODULE hshell32; static int (WINAPI *pSHCreateDirectoryExA)(HWND, LPCSTR, LPSECURITY_ATTRIBUTES); +static int (WINAPI *pSHCreateDirectoryExW)(HWND, LPCWSTR, LPSECURITY_ATTRIBUTES); static void InitFunctionPointers(void) { hshell32 = GetModuleHandleA("shell32.dll"); - - if(hshell32) - pSHCreateDirectoryExA = (void*)GetProcAddress(hshell32, "SHCreateDirectoryExA"); + pSHCreateDirectoryExA = (void*)GetProcAddress(hshell32, "SHCreateDirectoryExA"); + pSHCreateDirectoryExW = (void*)GetProcAddress(hshell32, "SHCreateDirectoryExW"); } /* creates a file with the specified name for tests */ @@ -56,11 +61,40 @@ static void createTestFile(const CHAR *name) CloseHandle(file); } +static void createTestFileW(const WCHAR *name) +{ + HANDLE file; + + file = CreateFileW(name, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); + ok(file != INVALID_HANDLE_VALUE, "Failure to open file\n"); + CloseHandle(file); +} + static BOOL file_exists(const CHAR *name) { return GetFileAttributesA(name) != INVALID_FILE_ATTRIBUTES; } +static BOOL file_existsW(LPCWSTR name) +{ + return GetFileAttributesW(name) != INVALID_FILE_ATTRIBUTES; +} + +static BOOL file_has_content(const CHAR *name, const CHAR *content) +{ + CHAR buf[MAX_PATH]; + HANDLE file; + DWORD read; + + file = CreateFileA(name, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); + if (file == INVALID_HANDLE_VALUE) + return FALSE; + ReadFile(file, buf, MAX_PATH - 1, &read, NULL); + buf[read] = 0; + CloseHandle(file); + return strcmp(buf, content)==0; +} + /* initializes the tests */ static void init_shfo_tests(void) { @@ -72,31 +106,125 @@ static void init_shfo_tests(void) if(len && (CURR_DIR[len-1] == '\\')) CURR_DIR[len-1] = 0; - createTestFile(".\\test1.txt"); - createTestFile(".\\test2.txt"); - createTestFile(".\\test3.txt"); - CreateDirectoryA(".\\test4.txt", NULL); - CreateDirectoryA(".\\testdir2", NULL); + createTestFile("test1.txt"); + createTestFile("test2.txt"); + createTestFile("test3.txt"); + createTestFile("test_5.txt"); + CreateDirectoryA("test4.txt", NULL); + CreateDirectoryA("testdir2", NULL); + CreateDirectoryA("testdir2\\nested", NULL); + createTestFile("testdir2\\one.txt"); + createTestFile("testdir2\\nested\\two.txt"); } /* cleans after tests */ static void clean_after_shfo_tests(void) { - DeleteFileA(".\\test1.txt"); - DeleteFileA(".\\test2.txt"); - DeleteFileA(".\\test3.txt"); - DeleteFileA(".\\test4.txt\\test1.txt"); - DeleteFileA(".\\test4.txt\\test2.txt"); - DeleteFileA(".\\test4.txt\\test3.txt"); - RemoveDirectoryA(".\\test4.txt"); - DeleteFileA(".\\testdir2\\test1.txt"); - DeleteFileA(".\\testdir2\\test2.txt"); - DeleteFileA(".\\testdir2\\test3.txt"); - DeleteFileA(".\\testdir2\\test4.txt\\test1.txt"); - RemoveDirectoryA(".\\testdir2\\test4.txt"); - RemoveDirectoryA(".\\testdir2"); + DeleteFileA("test1.txt"); + DeleteFileA("test2.txt"); + DeleteFileA("test3.txt"); + DeleteFileA("test_5.txt"); + DeleteFileA("one.txt"); + DeleteFileA("test4.txt\\test1.txt"); + DeleteFileA("test4.txt\\test2.txt"); + DeleteFileA("test4.txt\\test3.txt"); + RemoveDirectoryA("test4.txt"); + DeleteFileA("testdir2\\one.txt"); + DeleteFileA("testdir2\\test1.txt"); + DeleteFileA("testdir2\\test2.txt"); + DeleteFileA("testdir2\\test3.txt"); + DeleteFileA("testdir2\\test4.txt\\test1.txt"); + DeleteFileA("testdir2\\nested\\two.txt"); + RemoveDirectoryA("testdir2\\test4.txt"); + RemoveDirectoryA("testdir2\\nested"); + RemoveDirectoryA("testdir2"); + RemoveDirectoryA("c:\\testdir3"); + DeleteFileA("nonexistent\\notreal\\test2.txt"); + RemoveDirectoryA("nonexistent\\notreal"); + RemoveDirectoryA("nonexistent"); } + +static void test_get_file_info(void) +{ + DWORD rc, rc2; + SHFILEINFO shfi, shfi2; + char notepad[MAX_PATH]; + + /* Test some flag combinations that MSDN claims are not allowed, + * but which work anyway + */ + shfi.dwAttributes=0xdeadbeef; + rc=SHGetFileInfoA("c:\\nonexistent", FILE_ATTRIBUTE_DIRECTORY, + &shfi, sizeof(shfi), + SHGFI_ATTRIBUTES | SHGFI_USEFILEATTRIBUTES); + todo_wine ok(rc, "SHGetFileInfoA(c:\\nonexistent | SHGFI_ATTRIBUTES) failed\n"); + if (rc) + ok(shfi.dwAttributes != 0xdeadbeef, "dwFileAttributes is not set\n"); + + rc=SHGetFileInfoA("c:\\nonexistent", FILE_ATTRIBUTE_DIRECTORY, + &shfi, sizeof(shfi), + SHGFI_EXETYPE | SHGFI_USEFILEATTRIBUTES); + todo_wine ok(rc == 1, "SHGetFileInfoA(c:\\nonexistent | SHGFI_EXETYPE) returned %d\n", rc); + + /* Test SHGFI_USEFILEATTRIBUTES support */ + strcpy(shfi.szDisplayName, "dummy"); + shfi.iIcon=0xdeadbeef; + rc=SHGetFileInfoA("c:\\nonexistent", FILE_ATTRIBUTE_DIRECTORY, + &shfi, sizeof(shfi), + SHGFI_ICONLOCATION | SHGFI_USEFILEATTRIBUTES); + ok(rc, "SHGetFileInfoA(c:\\nonexistent) failed\n"); + if (rc) + { + ok(strcpy(shfi.szDisplayName, "dummy") != 0, "SHGetFileInfoA(c:\\nonexistent) displayname is not set\n"); + ok(shfi.iIcon != 0xdeadbeef, "SHGetFileInfoA(c:\\nonexistent) iIcon is not set\n"); + } + + /* Wine does not have a default icon for text files, and Windows 98 fails + * if we give it an empty executable. So use notepad.exe as the test + */ + if (SearchPath(NULL, "notepad.exe", NULL, sizeof(notepad), notepad, NULL)) + { + strcpy(shfi.szDisplayName, "dummy"); + shfi.iIcon=0xdeadbeef; + rc=SHGetFileInfoA(notepad, GetFileAttributes(notepad), + &shfi, sizeof(shfi), + SHGFI_ICONLOCATION | SHGFI_USEFILEATTRIBUTES); + ok(rc, "SHGetFileInfoA(%s, SHGFI_USEFILEATTRIBUTES) failed\n", notepad); + strcpy(shfi2.szDisplayName, "dummy"); + shfi2.iIcon=0xdeadbeef; + rc2=SHGetFileInfoA(notepad, 0, + &shfi2, sizeof(shfi2), + SHGFI_ICONLOCATION); + ok(rc2, "SHGetFileInfoA(%s) failed\n", notepad); + if (rc && rc2) + { + ok(lstrcmpi(shfi2.szDisplayName, shfi.szDisplayName) == 0, "wrong display name %s != %s\n", shfi.szDisplayName, shfi2.szDisplayName); + ok(shfi2.iIcon == shfi.iIcon, "wrong icon index %d != %d\n", shfi.iIcon, shfi2.iIcon); + } + } + + /* with a directory now */ + strcpy(shfi.szDisplayName, "dummy"); + shfi.iIcon=0xdeadbeef; + rc=SHGetFileInfoA("test4.txt", GetFileAttributes("test4.txt"), + &shfi, sizeof(shfi), + SHGFI_ICONLOCATION | SHGFI_USEFILEATTRIBUTES); + ok(rc, "SHGetFileInfoA(test4.txt/, SHGFI_USEFILEATTRIBUTES) failed\n"); + strcpy(shfi2.szDisplayName, "dummy"); + shfi2.iIcon=0xdeadbeef; + rc2=SHGetFileInfoA("test4.txt", 0, + &shfi2, sizeof(shfi2), + SHGFI_ICONLOCATION); + ok(rc2, "SHGetFileInfoA(test4.txt/) failed\n"); + if (rc && rc2) + { + ok(lstrcmpi(shfi2.szDisplayName, shfi.szDisplayName) == 0, "wrong display name %s != %s\n", shfi.szDisplayName, shfi2.szDisplayName); + ok(shfi2.iIcon == shfi.iIcon, "wrong icon index %d != %d\n", shfi.iIcon, shfi2.iIcon); + } +} + + /* puts into the specified buffer file names with current directory. files - string with file names, separated by null characters. Ends on a double @@ -124,7 +252,7 @@ static void test_delete(void) { SHFILEOPSTRUCTA shfo; DWORD ret; - CHAR buf[MAX_PATH]; + CHAR buf[sizeof(CURR_DIR)+sizeof("/test?.txt")+1]; sprintf(buf, "%s\\%s", CURR_DIR, "test?.txt"); buf[strlen(buf) + 1] = '\0'; @@ -137,43 +265,105 @@ static void test_delete(void) shfo.hNameMappings = NULL; shfo.lpszProgressTitle = NULL; - ok(!SHFileOperationA(&shfo), "Deletion was successful\n"); - ok(file_exists(".\\test4.txt"), "Directory should not be removed\n"); - ok(!file_exists(".\\test1.txt"), "File should be removed\n"); + ok(!SHFileOperationA(&shfo), "Deletion was not successful\n"); + ok(file_exists("test4.txt"), "Directory should not have been removed\n"); + ok(!file_exists("test1.txt"), "File should have been removed\n"); ret = SHFileOperationA(&shfo); - ok(!ret, "Directory exists, but is not removed, ret=%ld\n", ret); - ok(file_exists(".\\test4.txt"), "Directory should not be removed\n"); + ok(!ret, "Directory exists, but is not removed, ret=%d\n", ret); + ok(file_exists("test4.txt"), "Directory should not have been removed\n"); shfo.fFlags = FOF_NOCONFIRMATION | FOF_SILENT | FOF_NOERRORUI; - ok(!SHFileOperationA(&shfo), "Directory removed\n"); - ok(!file_exists(".\\test4.txt"), "Directory should be removed\n"); + ok(!SHFileOperationA(&shfo), "Directory is not removed\n"); + ok(!file_exists("test4.txt"), "Directory should have been removed\n"); ret = SHFileOperationA(&shfo); - ok(!ret, "The requested file does not exist, ret=%ld\n", ret); + ok(!ret, "The requested file does not exist, ret=%d\n", ret); init_shfo_tests(); sprintf(buf, "%s\\%s", CURR_DIR, "test4.txt"); buf[strlen(buf) + 1] = '\0'; - ok(MoveFileA(".\\test1.txt", ".\\test4.txt\\test1.txt"), "Fill the subdirectory\n"); - ok(!SHFileOperationA(&shfo), "Directory removed\n"); - ok(!file_exists(".\\test4.txt"), "Directory is removed\n"); + ok(MoveFileA("test1.txt", "test4.txt\\test1.txt"), "Filling the subdirectory failed\n"); + ok(!SHFileOperationA(&shfo), "Directory is not removed\n"); + ok(!file_exists("test4.txt"), "Directory is not removed\n"); init_shfo_tests(); - shfo.pFrom = ".\\test1.txt\0.\\test4.txt\0"; - ok(!SHFileOperationA(&shfo), "Directory and a file removed\n"); - ok(!file_exists(".\\test1.txt"), "The file should be removed\n"); - ok(!file_exists(".\\test4.txt"), "Directory should be removed\n"); - ok(file_exists(".\\test2.txt"), "This file should not be removed\n"); + shfo.pFrom = "test1.txt\0test4.txt\0"; + ok(!SHFileOperationA(&shfo), "Directory and a file are not removed\n"); + ok(!file_exists("test1.txt"), "The file should have been removed\n"); + ok(!file_exists("test4.txt"), "Directory should have been removed\n"); + ok(file_exists("test2.txt"), "This file should not have been removed\n"); + + /* FOF_FILESONLY does not delete a dir matching a wildcard */ + init_shfo_tests(); + shfo.fFlags |= FOF_FILESONLY; + shfo.pFrom = "*.txt\0"; + ok(!SHFileOperation(&shfo), "Failed to delete files\n"); + ok(!file_exists("test1.txt"), "test1.txt should have been removed\n"); + ok(!file_exists("test_5.txt"), "test_5.txt should have been removed\n"); + ok(file_exists("test4.txt"), "test4.txt should not have been removed\n"); + + /* FOF_FILESONLY only deletes a dir if explicitly specified */ + init_shfo_tests(); + shfo.pFrom = "test_?.txt\0test4.txt\0"; + ok(!SHFileOperation(&shfo), "Failed to delete files and directory\n"); + ok(!file_exists("test4.txt"), "test4.txt should have been removed\n"); + ok(!file_exists("test_5.txt"), "test_5.txt should have been removed\n"); + ok(file_exists("test1.txt"), "test1.txt should not have been removed\n"); + + /* try to delete an invalid filename */ + init_shfo_tests(); + shfo.pFrom = "\0"; + shfo.fFlags &= ~FOF_FILESONLY; + shfo.fAnyOperationsAborted = FALSE; + ret = SHFileOperation(&shfo); + ok(ret == ERROR_ACCESS_DENIED, "Expected ERROR_ACCESS_DENIED, got %d\n", ret); + ok(!shfo.fAnyOperationsAborted, "Expected no aborted operations\n"); + ok(file_exists("test1.txt"), "Expected test1.txt to exist\n"); + + /* try an invalid function */ + init_shfo_tests(); + shfo.pFrom = "test1.txt\0"; + shfo.wFunc = 0; + ret = SHFileOperation(&shfo); + ok(ret == ERROR_INVALID_PARAMETER, "Expected ERROR_INVALID_PARAMETER, got %d\n", ret); + ok(file_exists("test1.txt"), "Expected test1.txt to exist\n"); + + /* try an invalid list, only one null terminator */ + init_shfo_tests(); + shfo.pFrom = ""; + shfo.wFunc = FO_DELETE; + ret = SHFileOperation(&shfo); + ok(ret == ERROR_ACCESS_DENIED, "Expected ERROR_ACCESS_DENIED, got %d\n", ret); + ok(file_exists("test1.txt"), "Expected test1.txt to exist\n"); + + /* delete a dir, and then a file inside the dir, same as + * deleting a nonexistent file + */ + init_shfo_tests(); + shfo.pFrom = "testdir2\0testdir2\\one.txt\0"; + ret = SHFileOperation(&shfo); + ok(ret == ERROR_PATH_NOT_FOUND, "Expected ERROR_PATH_NOT_FOUND, got %d\n", ret); + ok(!file_exists("testdir2"), "Expected testdir2 to not exist\n"); + ok(!file_exists("testdir2\\one.txt"), "Expected testdir2\\one.txt to not exist\n"); + + /* try the FOF_NORECURSION flag, continues deleting subdirs */ + init_shfo_tests(); + shfo.pFrom = "testdir2\0"; + shfo.fFlags |= FOF_NORECURSION; + ret = SHFileOperation(&shfo); + ok(ret == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", ret); + ok(!file_exists("testdir2\\one.txt"), "Expected testdir2\\one.txt to not exist\n"); + ok(!file_exists("testdir2\\nested"), "Expected testdir2\\nested to exist\n"); } /* tests the FO_RENAME action */ static void test_rename(void) { SHFILEOPSTRUCTA shfo, shfo2; - CHAR from[MAX_PATH]; - CHAR to[MAX_PATH]; + CHAR from[5*MAX_PATH]; + CHAR to[5*MAX_PATH]; DWORD retval; shfo.hwnd = NULL; @@ -188,19 +378,19 @@ static void test_rename(void) set_curr_dir_path(to, "test4.txt\0"); ok(SHFileOperationA(&shfo), "File is not renamed moving to other directory " "when specifying directory name only\n"); - ok(file_exists(".\\test1.txt"), "The file is removed\n"); + ok(file_exists("test1.txt"), "The file is removed\n"); set_curr_dir_path(from, "test3.txt\0"); set_curr_dir_path(to, "test4.txt\\test1.txt\0"); ok(!SHFileOperationA(&shfo), "File is renamed moving to other directory\n"); - ok(file_exists(".\\test4.txt\\test1.txt"), "The file is not renamed\n"); + ok(file_exists("test4.txt\\test1.txt"), "The file is not renamed\n"); set_curr_dir_path(from, "test1.txt\0test2.txt\0test4.txt\0"); set_curr_dir_path(to, "test6.txt\0test7.txt\0test8.txt\0"); retval = SHFileOperationA(&shfo); /* W98 returns 0, W2K and newer returns ERROR_GEN_FAILURE, both do nothing */ ok(!retval || retval == ERROR_GEN_FAILURE || retval == ERROR_INVALID_TARGET_HANDLE, - "Can't rename many files, retval = %ld\n", retval); - ok(file_exists(".\\test1.txt"), "The file is renamed - many files are specified\n"); + "Can't rename many files, retval = %d\n", retval); + ok(file_exists("test1.txt"), "The file is renamed - many files are specified\n"); memcpy(&shfo2, &shfo, sizeof(SHFILEOPSTRUCTA)); shfo2.fFlags |= FOF_MULTIDESTFILES; @@ -209,40 +399,80 @@ static void test_rename(void) set_curr_dir_path(to, "test6.txt\0test7.txt\0test8.txt\0"); retval = SHFileOperationA(&shfo2); /* W98 returns 0, W2K and newer returns ERROR_GEN_FAILURE, both do nothing */ ok(!retval || retval == ERROR_GEN_FAILURE || retval == ERROR_INVALID_TARGET_HANDLE, - "Can't rename many files, retval = %ld\n", retval); - ok(file_exists(".\\test1.txt"), "The file is not renamed - many files are specified\n"); + "Can't rename many files, retval = %d\n", retval); + ok(file_exists("test1.txt"), "The file is not renamed - many files are specified\n"); set_curr_dir_path(from, "test1.txt\0"); set_curr_dir_path(to, "test6.txt\0"); retval = SHFileOperationA(&shfo); - ok(!retval, "Rename file failed, retval = %ld\n", retval); - ok(!file_exists(".\\test1.txt"), "The file is not renamed\n"); - ok(file_exists(".\\test6.txt"), "The file is not renamed\n"); + ok(!retval, "Rename file failed, retval = %d\n", retval); + ok(!file_exists("test1.txt"), "The file is not renamed\n"); + ok(file_exists("test6.txt"), "The file is not renamed\n"); set_curr_dir_path(from, "test6.txt\0"); set_curr_dir_path(to, "test1.txt\0"); retval = SHFileOperationA(&shfo); - ok(!retval, "Rename file back failed, retval = %ld\n", retval); + ok(!retval, "Rename file back failed, retval = %d\n", retval); set_curr_dir_path(from, "test4.txt\0"); set_curr_dir_path(to, "test6.txt\0"); retval = SHFileOperationA(&shfo); - ok(!retval, "Rename dir failed, retval = %ld\n", retval); - ok(!file_exists(".\\test4.txt"), "The dir is not renamed\n"); - ok(file_exists(".\\test6.txt"), "The dir is not renamed\n"); + ok(!retval, "Rename dir failed, retval = %d\n", retval); + ok(!file_exists("test4.txt"), "The dir is not renamed\n"); + ok(file_exists("test6.txt"), "The dir is not renamed\n"); set_curr_dir_path(from, "test6.txt\0"); set_curr_dir_path(to, "test4.txt\0"); retval = SHFileOperationA(&shfo); - ok(!retval, "Rename dir back failed, retval = %ld\n", retval); + ok(!retval, "Rename dir back failed, retval = %d\n", retval); + + /* try to rename more than one file to a single file */ + shfo.pFrom = "test1.txt\0test2.txt\0"; + shfo.pTo = "a.txt\0"; + retval = SHFileOperationA(&shfo); + ok(retval == ERROR_GEN_FAILURE, "Expected ERROR_GEN_FAILURE, got %d\n", retval); + ok(file_exists("test1.txt"), "Expected test1.txt to exist\n"); + ok(file_exists("test2.txt"), "Expected test2.txt to exist\n"); + + /* pFrom doesn't exist */ + shfo.pFrom = "idontexist\0"; + shfo.pTo = "newfile\0"; + retval = SHFileOperationA(&shfo); + ok(retval == 1026, "Expected 1026, got %d\n", retval); + ok(!file_exists("newfile"), "Expected newfile to not exist\n"); + + /* pTo already exist */ + shfo.pFrom = "test1.txt\0"; + shfo.pTo = "test2.txt\0"; + retval = SHFileOperationA(&shfo); + ok(retval == ERROR_ALREADY_EXISTS, "Expected ERROR_ALREADY_EXISTS, got %d\n", retval); + + /* pFrom is valid, but pTo is empty */ + shfo.pFrom = "test1.txt\0"; + shfo.pTo = "\0"; + retval = SHFileOperationA(&shfo); + ok(retval == ERROR_CANCELLED, "Expected ERROR_CANCELLED, got %d\n", retval); + ok(file_exists("test1.txt"), "Expected test1.txt to exist\n"); + + /* pFrom is empty */ + shfo.pFrom = "\0"; + retval = SHFileOperationA(&shfo); + ok(retval == ERROR_ACCESS_DENIED, "Expected ERROR_ACCESS_DENIED, got %d\n", retval); + + /* pFrom is NULL, commented out because it crashes on nt 4.0 */ +#if 0 + shfo.pFrom = NULL; + retval = SHFileOperationA(&shfo); + ok(retval == ERROR_INVALID_PARAMETER, "Expected ERROR_INVALID_PARAMETER, got %d\n", retval); +#endif } /* tests the FO_COPY action */ static void test_copy(void) { SHFILEOPSTRUCTA shfo, shfo2; - CHAR from[MAX_PATH]; - CHAR to[MAX_PATH]; + CHAR from[5*MAX_PATH]; + CHAR to[5*MAX_PATH]; FILEOP_FLAGS tmp_flags; DWORD retval; @@ -257,7 +487,7 @@ static void test_copy(void) set_curr_dir_path(from, "test1.txt\0test2.txt\0test4.txt\0"); set_curr_dir_path(to, "test6.txt\0test7.txt\0test8.txt\0"); ok(SHFileOperationA(&shfo), "Can't copy many files\n"); - ok(!file_exists(".\\test6.txt"), "The file is not copied - many files are " + ok(!file_exists("test6.txt"), "The file is not copied - many files are " "specified as a target\n"); memcpy(&shfo2, &shfo, sizeof(SHFILEOPSTRUCTA)); @@ -266,50 +496,50 @@ static void test_copy(void) set_curr_dir_path(from, "test1.txt\0test2.txt\0test4.txt\0"); set_curr_dir_path(to, "test6.txt\0test7.txt\0test8.txt\0"); ok(!SHFileOperationA(&shfo2), "Can't copy many files\n"); - ok(file_exists(".\\test6.txt"), "The file is copied - many files are " + ok(file_exists("test6.txt"), "The file is copied - many files are " "specified as a target\n"); - DeleteFileA(".\\test6.txt"); - DeleteFileA(".\\test7.txt"); - RemoveDirectoryA(".\\test8.txt"); + DeleteFileA("test6.txt"); + DeleteFileA("test7.txt"); + RemoveDirectoryA("test8.txt"); /* number of sources do not correspond to number of targets */ set_curr_dir_path(from, "test1.txt\0test2.txt\0test4.txt\0"); set_curr_dir_path(to, "test6.txt\0test7.txt\0"); ok(SHFileOperationA(&shfo2), "Can't copy many files\n"); - ok(!file_exists(".\\test6.txt"), "The file is not copied - many files are " + ok(!file_exists("test6.txt"), "The file is not copied - many files are " "specified as a target\n"); set_curr_dir_path(from, "test1.txt\0"); set_curr_dir_path(to, "test4.txt\0"); ok(!SHFileOperationA(&shfo), "Prepare test to check how directories are copied recursively\n"); - ok(file_exists(".\\test4.txt\\test1.txt"), "The file is copied\n"); + ok(file_exists("test4.txt\\test1.txt"), "The file is copied\n"); set_curr_dir_path(from, "test?.txt\0"); set_curr_dir_path(to, "testdir2\0"); - ok(!file_exists(".\\testdir2\\test1.txt"), "The file is not copied yet\n"); - ok(!file_exists(".\\testdir2\\test4.txt"), "The directory is not copied yet\n"); + ok(!file_exists("testdir2\\test1.txt"), "The file is not copied yet\n"); + ok(!file_exists("testdir2\\test4.txt"), "The directory is not copied yet\n"); ok(!SHFileOperationA(&shfo), "Files and directories are copied to directory\n"); - ok(file_exists(".\\testdir2\\test1.txt"), "The file is copied\n"); - ok(file_exists(".\\testdir2\\test4.txt"), "The directory is copied\n"); - ok(file_exists(".\\testdir2\\test4.txt\\test1.txt"), "The file in subdirectory is copied\n"); + ok(file_exists("testdir2\\test1.txt"), "The file is copied\n"); + ok(file_exists("testdir2\\test4.txt"), "The directory is copied\n"); + ok(file_exists("testdir2\\test4.txt\\test1.txt"), "The file in subdirectory is copied\n"); clean_after_shfo_tests(); init_shfo_tests(); shfo.fFlags |= FOF_FILESONLY; - ok(!file_exists(".\\testdir2\\test1.txt"), "The file is not copied yet\n"); - ok(!file_exists(".\\testdir2\\test4.txt"), "The directory is not copied yet\n"); + ok(!file_exists("testdir2\\test1.txt"), "The file is not copied yet\n"); + ok(!file_exists("testdir2\\test4.txt"), "The directory is not copied yet\n"); ok(!SHFileOperationA(&shfo), "Files are copied to other directory\n"); - ok(file_exists(".\\testdir2\\test1.txt"), "The file is copied\n"); - ok(!file_exists(".\\testdir2\\test4.txt"), "The directory is copied\n"); + ok(file_exists("testdir2\\test1.txt"), "The file is copied\n"); + ok(!file_exists("testdir2\\test4.txt"), "The directory is copied\n"); clean_after_shfo_tests(); init_shfo_tests(); set_curr_dir_path(from, "test1.txt\0test2.txt\0"); - ok(!file_exists(".\\testdir2\\test1.txt"), "The file is not copied yet\n"); - ok(!file_exists(".\\testdir2\\test2.txt"), "The file is not copied yet\n"); - ok(!SHFileOperationA(&shfo), "Files are copied to other directory \n"); - ok(file_exists(".\\testdir2\\test1.txt"), "The file is copied\n"); - ok(file_exists(".\\testdir2\\test2.txt"), "The file is copied\n"); + ok(!file_exists("testdir2\\test1.txt"), "The file is not copied yet\n"); + ok(!file_exists("testdir2\\test2.txt"), "The file is not copied yet\n"); + ok(!SHFileOperationA(&shfo), "Files are copied to other directory\n"); + ok(file_exists("testdir2\\test1.txt"), "The file is copied\n"); + ok(file_exists("testdir2\\test2.txt"), "The file is copied\n"); clean_after_shfo_tests(); /* Copying multiple files with one not existing as source, fails the @@ -317,28 +547,301 @@ static void test_copy(void) init_shfo_tests(); tmp_flags = shfo.fFlags; set_curr_dir_path(from, "test1.txt\0test10.txt\0test2.txt\0"); - ok(!file_exists(".\\testdir2\\test1.txt"), "The file is not copied yet\n"); - ok(!file_exists(".\\testdir2\\test2.txt"), "The file is not copied yet\n"); + ok(!file_exists("testdir2\\test1.txt"), "The file is not copied yet\n"); + ok(!file_exists("testdir2\\test2.txt"), "The file is not copied yet\n"); retval = SHFileOperationA(&shfo); if (!retval) - /* Win 95/NT returns success but copies only the files up to the nonexistent source */ - ok(file_exists(".\\testdir2\\test1.txt"), "The file is not copied\n"); + /* Win 95/NT returns success but copies only the files up to the nonexistent source */ + ok(file_exists("testdir2\\test1.txt"), "The file is not copied\n"); else { - /* Win 98/ME/2K/XP fail the entire operation with return code 1026 if one source file does not exist */ - ok(retval == 1026, "Files are copied to other directory\n"); - ok(!file_exists(".\\testdir2\\test1.txt"), "The file is copied\n"); + /* Win 98/ME/2K/XP fail the entire operation with return code 1026 if one source file does not exist */ + ok(retval == 1026, "Files are copied to other directory\n"); + ok(!file_exists("testdir2\\test1.txt"), "The file is copied\n"); } - ok(!file_exists(".\\testdir2\\test2.txt"), "The file is copied\n"); + ok(!file_exists("testdir2\\test2.txt"), "The file is copied\n"); shfo.fFlags = tmp_flags; + + /* copy into a nonexistent directory */ + init_shfo_tests(); + shfo.fFlags = FOF_NOCONFIRMMKDIR; + set_curr_dir_path(from, "test1.txt\0"); + set_curr_dir_path(to, "nonexistent\\notreal\\test2.txt\0"); + retval= SHFileOperation(&shfo); + ok(!retval, "Error copying into nonexistent directory\n"); + ok(file_exists("nonexistent"), "nonexistent not created\n"); + ok(file_exists("nonexistent\\notreal"), "nonexistent\\notreal not created\n"); + ok(file_exists("nonexistent\\notreal\\test2.txt"), "Directory not created\n"); + ok(!file_exists("nonexistent\\notreal\\test1.txt"), "test1.txt should not exist\n"); + + /* a relative dest directory is OK */ + clean_after_shfo_tests(); + init_shfo_tests(); + shfo.pFrom = "test1.txt\0test2.txt\0test3.txt\0"; + shfo.pTo = "testdir2\0"; + retval = SHFileOperation(&shfo); + ok(retval == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", retval); + ok(file_exists("testdir2\\test1.txt"), "Expected testdir2\\test1 to exist\n"); + + /* try to copy files to a file */ + clean_after_shfo_tests(); + init_shfo_tests(); + shfo.pFrom = from; + shfo.pTo = to; + set_curr_dir_path(from, "test1.txt\0test2.txt\0"); + set_curr_dir_path(to, "test3.txt\0"); + retval = SHFileOperation(&shfo); + ok(retval == ERROR_CANCELLED, "Expected ERROR_CANCELLED, got %d\n", retval); + ok(shfo.fAnyOperationsAborted, "Expected aborted operations\n"); + ok(!file_exists("test3.txt\\test2.txt"), "Expected test3.txt\\test2.txt to not exist\n"); + + /* try to copy many files to nonexistent directory */ + DeleteFile(to); + shfo.fAnyOperationsAborted = FALSE; + retval = SHFileOperation(&shfo); + ok(retval == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", retval); + ok(DeleteFile("test3.txt\\test1.txt"), "Expected test3.txt\\test1.txt to exist\n"); + ok(DeleteFile("test3.txt\\test2.txt"), "Expected test3.txt\\test1.txt to exist\n"); + ok(RemoveDirectory(to), "Expected test3.txt to exist\n"); + + /* send in FOF_MULTIDESTFILES with too many destination files */ + init_shfo_tests(); + shfo.pFrom = "test1.txt\0test2.txt\0test3.txt\0"; + shfo.pTo = "testdir2\\a.txt\0testdir2\\b.txt\0testdir2\\c.txt\0testdir2\\d.txt\0"; + shfo.fFlags |= FOF_NOERRORUI | FOF_MULTIDESTFILES; + retval = SHFileOperation(&shfo); + ok(retval == ERROR_CANCELLED, "Expected ERROR_CANCELLED, got %d\n", retval); + ok(shfo.fAnyOperationsAborted, "Expected aborted operations\n"); + ok(!file_exists("testdir2\\a.txt"), "Expected testdir2\\a.txt to not exist\n"); + + /* send in FOF_MULTIDESTFILES with too many destination files */ + shfo.pFrom = "test1.txt\0test2.txt\0test3.txt\0"; + shfo.pTo = "e.txt\0f.txt\0"; + shfo.fAnyOperationsAborted = FALSE; + retval = SHFileOperation(&shfo); + ok(retval == ERROR_CANCELLED, "Expected ERROR_CANCELLED, got %d\n", retval); + ok(shfo.fAnyOperationsAborted, "Expected aborted operations\n"); + ok(!file_exists("e.txt"), "Expected e.txt to not exist\n"); + + /* use FOF_MULTIDESTFILES with files and a source directory */ + shfo.pFrom = "test1.txt\0test2.txt\0test4.txt\0"; + shfo.pTo = "testdir2\\a.txt\0testdir2\\b.txt\0testdir2\\c.txt\0"; + shfo.fAnyOperationsAborted = FALSE; + retval = SHFileOperation(&shfo); + ok(retval == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", retval); + ok(DeleteFile("testdir2\\a.txt"), "Expected testdir2\\a.txt to exist\n"); + ok(DeleteFile("testdir2\\b.txt"), "Expected testdir2\\b.txt to exist\n"); + ok(RemoveDirectory("testdir2\\c.txt"), "Expected testdir2\\c.txt to exist\n"); + + /* try many dest files without FOF_MULTIDESTFILES flag */ + shfo.pFrom = "test1.txt\0test2.txt\0test3.txt\0"; + shfo.pTo = "a.txt\0b.txt\0c.txt\0"; + shfo.fAnyOperationsAborted = FALSE; + shfo.fFlags &= ~FOF_MULTIDESTFILES; + retval = SHFileOperation(&shfo); + ok(retval == ERROR_CANCELLED, "Expected ERROR_CANCELLED, got %d\n", retval); + ok(!file_exists("a.txt"), "Expected a.txt to not exist\n"); + + /* try a glob */ + shfo.pFrom = "test?.txt\0"; + shfo.pTo = "testdir2\0"; + shfo.fFlags &= ~FOF_MULTIDESTFILES; + retval = SHFileOperation(&shfo); + ok(retval == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", retval); + ok(file_exists("testdir2\\test1.txt"), "Expected testdir2\\test1.txt to exist\n"); + + /* try a glob with FOF_FILESONLY */ + clean_after_shfo_tests(); + init_shfo_tests(); + shfo.pFrom = "test?.txt\0"; + shfo.fFlags |= FOF_FILESONLY; + retval = SHFileOperation(&shfo); + ok(retval == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", retval); + ok(file_exists("testdir2\\test1.txt"), "Expected testdir2\\test1.txt to exist\n"); + ok(!file_exists("testdir2\\test4.txt"), "Expected testdir2\\test4.txt to not exist\n"); + + /* try a glob with FOF_MULTIDESTFILES and the same number + * of dest files that we would expect + */ + clean_after_shfo_tests(); + init_shfo_tests(); + shfo.pTo = "testdir2\\a.txt\0testdir2\\b.txt\0testdir2\\c.txt\0testdir2\\d.txt\0"; + shfo.fFlags &= ~FOF_FILESONLY; + shfo.fFlags |= FOF_MULTIDESTFILES; + retval = SHFileOperation(&shfo); + ok(retval == ERROR_CANCELLED, "Expected ERROR_CANCELLED, got %d\n", retval); + ok(shfo.fAnyOperationsAborted, "Expected aborted operations\n"); + ok(!file_exists("testdir2\\a.txt"), "Expected testdir2\\test1.txt to not exist\n"); + ok(!RemoveDirectory("b.txt"), "b.txt should not exist\n"); + + /* copy one file to two others, second is ignored */ + clean_after_shfo_tests(); + init_shfo_tests(); + shfo.pFrom = "test1.txt\0"; + shfo.pTo = "b.txt\0c.txt\0"; + shfo.fAnyOperationsAborted = FALSE; + retval = SHFileOperation(&shfo); + ok(retval == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", retval); + ok(DeleteFile("b.txt"), "Expected b.txt to exist\n"); + ok(!DeleteFile("c.txt"), "Expected c.txt to not exist\n"); + + /* copy two file to three others, all fail */ + shfo.pFrom = "test1.txt\0test2.txt\0"; + shfo.pTo = "b.txt\0c.txt\0d.txt\0"; + retval = SHFileOperation(&shfo); + ok(retval == ERROR_CANCELLED, "Expected ERROR_CANCELLED, got %d\n", retval); + ok(shfo.fAnyOperationsAborted, "Expected operations to be aborted\n"); + ok(!DeleteFile("b.txt"), "Expected b.txt to not exist\n"); + + /* copy one file and one directory to three others */ + shfo.pFrom = "test1.txt\0test4.txt\0"; + shfo.pTo = "b.txt\0c.txt\0d.txt\0"; + shfo.fAnyOperationsAborted = FALSE; + retval = SHFileOperation(&shfo); + ok(retval == ERROR_CANCELLED, "Expected ERROR_CANCELLED, got %d\n", retval); + ok(shfo.fAnyOperationsAborted, "Expected operations to be aborted\n"); + ok(!DeleteFile("b.txt"), "Expected b.txt to not exist\n"); + ok(!DeleteFile("c.txt"), "Expected c.txt to not exist\n"); + + /* copy a directory with a file beneath it, plus some files */ + createTestFile("test4.txt\\a.txt"); + shfo.pFrom = "test4.txt\0test1.txt\0"; + shfo.pTo = "testdir2\0"; + shfo.fFlags &= ~FOF_MULTIDESTFILES; + shfo.fAnyOperationsAborted = FALSE; + retval = SHFileOperation(&shfo); + ok(retval == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", retval); + ok(DeleteFile("testdir2\\test1.txt"), "Expected newdir\\test1.txt to exist\n"); + ok(DeleteFile("testdir2\\test4.txt\\a.txt"), "Expected a.txt to exist\n"); + ok(RemoveDirectory("testdir2\\test4.txt"), "Expected testdir2\\test4.txt to exist\n"); + + /* copy one directory and a file in that dir to another dir */ + shfo.pFrom = "test4.txt\0test4.txt\\a.txt\0"; + shfo.pTo = "testdir2\0"; + retval = SHFileOperation(&shfo); + ok(retval == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", retval); + ok(DeleteFile("testdir2\\test4.txt\\a.txt"), "Expected a.txt to exist\n"); + ok(DeleteFile("testdir2\\a.txt"), "Expected testdir2\\a.txt to exist\n"); + + /* copy a file in a directory first, and then the directory to a nonexistent dir */ + shfo.pFrom = "test4.txt\\a.txt\0test4.txt\0"; + shfo.pTo = "nonexistent\0"; + retval = SHFileOperation(&shfo); + ok(retval == ERROR_CANCELLED, "Expected ERROR_CANCELLED, got %d\n", retval); + ok(shfo.fAnyOperationsAborted, "Expected operations to be aborted\n"); + ok(!file_exists("nonexistent\\test4.txt"), "Expected nonexistent\\test4.txt to not exist\n"); + DeleteFile("test4.txt\\a.txt"); + + /* destination is same as source file */ + shfo.pFrom = "test1.txt\0test2.txt\0test3.txt\0"; + shfo.pTo = "b.txt\0test2.txt\0c.txt\0"; + shfo.fAnyOperationsAborted = FALSE; + shfo.fFlags = FOF_NOERRORUI | FOF_MULTIDESTFILES; + retval = SHFileOperation(&shfo); + ok(retval == ERROR_NO_MORE_SEARCH_HANDLES, + "Expected ERROR_NO_MORE_SEARCH_HANDLES, got %d\n", retval); + ok(!shfo.fAnyOperationsAborted, "Expected no operations to be aborted\n"); + ok(DeleteFile("b.txt"), "Expected b.txt to exist\n"); + ok(!file_exists("c.txt"), "Expected c.txt to not exist\n"); + + /* destination is same as source directory */ + shfo.pFrom = "test1.txt\0test4.txt\0test3.txt\0"; + shfo.pTo = "b.txt\0test4.txt\0c.txt\0"; + shfo.fAnyOperationsAborted = FALSE; + retval = SHFileOperation(&shfo); + ok(retval == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", retval); + ok(DeleteFile("b.txt"), "Expected b.txt to exist\n"); + ok(!file_exists("c.txt"), "Expected c.txt to not exist\n"); + + /* copy a directory into itself, error displayed in UI */ + shfo.pFrom = "test4.txt\0"; + shfo.pTo = "test4.txt\\newdir\0"; + shfo.fFlags &= ~FOF_MULTIDESTFILES; + shfo.fAnyOperationsAborted = FALSE; + retval = SHFileOperation(&shfo); + ok(retval == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", retval); + ok(!RemoveDirectory("test4.txt\\newdir"), "Expected test4.txt\\newdir to not exist\n"); + + /* copy a directory to itself, error displayed in UI */ + shfo.pFrom = "test4.txt\0"; + shfo.pTo = "test4.txt\0"; + shfo.fAnyOperationsAborted = FALSE; + retval = SHFileOperation(&shfo); + ok(retval == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", retval); + + /* copy a file into a directory, and the directory into itself */ + shfo.pFrom = "test1.txt\0test4.txt\0"; + shfo.pTo = "test4.txt\0"; + shfo.fAnyOperationsAborted = FALSE; + shfo.fFlags |= FOF_NOCONFIRMATION; + retval = SHFileOperation(&shfo); + ok(retval == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", retval); + ok(DeleteFile("test4.txt\\test1.txt"), "Expected test4.txt\\test1.txt to exist\n"); + + /* copy a file to a file, and the directory into itself */ + shfo.pFrom = "test1.txt\0test4.txt\0"; + shfo.pTo = "test4.txt\\a.txt\0"; + shfo.fAnyOperationsAborted = FALSE; + retval = SHFileOperation(&shfo); + ok(retval == ERROR_CANCELLED, "Expected ERROR_CANCELLED, got %d\n", retval); + ok(!file_exists("test4.txt\\a.txt"), "Expected test4.txt\\a.txt to not exist\n"); + + /* copy a nonexistent file to a nonexistent directory */ + shfo.pFrom = "e.txt\0"; + shfo.pTo = "nonexistent\0"; + shfo.fAnyOperationsAborted = FALSE; + retval = SHFileOperation(&shfo); + ok(retval == 1026, "Expected 1026, got %d\n", retval); + ok(!file_exists("nonexistent\\e.txt"), "Expected nonexistent\\e.txt to not exist\n"); + ok(!file_exists("nonexistent"), "Expected nonexistent to not exist\n"); + + /* Overwrite tests */ + clean_after_shfo_tests(); + init_shfo_tests(); + shfo.fFlags = FOF_NOCONFIRMATION; + shfo.pFrom = "test1.txt\0"; + shfo.pTo = "test2.txt\0"; + shfo.fAnyOperationsAborted = FALSE; + /* without FOF_NOCOFIRMATION the confirmation is Yes/No */ + retval = SHFileOperation(&shfo); + ok(retval == 0, "Expected 0, got %d\n", retval); + ok(file_has_content("test2.txt", "test1.txt\n"), "The file was not copied\n"); + + shfo.pFrom = "test3.txt\0test1.txt\0"; + shfo.pTo = "test2.txt\0one.txt\0"; + shfo.fFlags = FOF_NOCONFIRMATION | FOF_MULTIDESTFILES; + /* without FOF_NOCOFIRMATION the confirmation is Yes/Yes to All/No/Cancel */ + retval = SHFileOperation(&shfo); + ok(retval == 0, "Expected 0, got %d\n", retval); + ok(file_has_content("test2.txt", "test3.txt\n"), "The file was not copied\n"); + + shfo.pFrom = "one.txt\0"; + shfo.pTo = "testdir2\0"; + shfo.fFlags = FOF_NOCONFIRMATION; + /* without FOF_NOCOFIRMATION the confirmation is Yes/No */ + retval = SHFileOperation(&shfo); + ok(retval == 0, "Expected 0, got %d\n", retval); + ok(file_has_content("testdir2\\one.txt", "test1.txt\n"), "The file was not copied\n"); + + createTestFile("test4.txt\\test1.txt"); + shfo.pFrom = "test4.txt\0"; + shfo.pTo = "testdir2\0"; + shfo.fFlags = FOF_NOCONFIRMATION; + ok(!SHFileOperation(&shfo), "First SHFileOperation failed\n"); + createTestFile("test4.txt\\.\\test1.txt"); /* modify the content of the file */ + /* without FOF_NOCOFIRMATION the confirmation is "This folder already contains a folder named ..." */ + retval = SHFileOperation(&shfo); + ok(retval == 0, "Expected 0, got %d\n", retval); + ok(file_has_content("testdir2\\test4.txt\\test1.txt", "test4.txt\\.\\test1.txt\n"), "The file was not copied\n"); } /* tests the FO_MOVE action */ static void test_move(void) { SHFILEOPSTRUCTA shfo, shfo2; - CHAR from[MAX_PATH]; - CHAR to[MAX_PATH]; + CHAR from[5*MAX_PATH]; + CHAR to[5*MAX_PATH]; + DWORD retval; shfo.hwnd = NULL; shfo.wFunc = FO_MOVE; @@ -351,16 +854,17 @@ static void test_move(void) set_curr_dir_path(from, "test1.txt\0"); set_curr_dir_path(to, "test4.txt\0"); ok(!SHFileOperationA(&shfo), "Prepare test to check how directories are moved recursively\n"); - ok(file_exists(".\\test4.txt\\test1.txt"), "The file is moved\n"); + ok(!file_exists("test1.txt"), "test1.txt should not exist\n"); + ok(file_exists("test4.txt\\test1.txt"), "The file is not moved\n"); set_curr_dir_path(from, "test?.txt\0"); set_curr_dir_path(to, "testdir2\0"); - ok(!file_exists(".\\testdir2\\test2.txt"), "The file is not moved yet\n"); - ok(!file_exists(".\\testdir2\\test4.txt"), "The directory is not moved yet\n"); + ok(!file_exists("testdir2\\test2.txt"), "The file is not moved yet\n"); + ok(!file_exists("testdir2\\test4.txt"), "The directory is not moved yet\n"); ok(!SHFileOperationA(&shfo), "Files and directories are moved to directory\n"); - ok(file_exists(".\\testdir2\\test2.txt"), "The file is moved\n"); - ok(file_exists(".\\testdir2\\test4.txt"), "The directory is moved\n"); - ok(file_exists(".\\testdir2\\test4.txt\\test1.txt"), "The file in subdirectory is moved\n"); + ok(file_exists("testdir2\\test2.txt"), "The file is moved\n"); + ok(file_exists("testdir2\\test4.txt"), "The directory is moved\n"); + ok(file_exists("testdir2\\test4.txt\\test1.txt"), "The file in subdirectory is moved\n"); clean_after_shfo_tests(); init_shfo_tests(); @@ -371,11 +875,11 @@ static void test_move(void) set_curr_dir_path(from, "test1.txt\0test2.txt\0test4.txt\0"); set_curr_dir_path(to, "test6.txt\0test7.txt\0test8.txt\0"); ok(!SHFileOperationA(&shfo2), "Move many files\n"); - ok(file_exists(".\\test6.txt"), "The file is moved - many files are " + ok(file_exists("test6.txt"), "The file is moved - many files are " "specified as a target\n"); - DeleteFileA(".\\test6.txt"); - DeleteFileA(".\\test7.txt"); - RemoveDirectoryA(".\\test8.txt"); + DeleteFileA("test6.txt"); + DeleteFileA("test7.txt"); + RemoveDirectoryA("test8.txt"); init_shfo_tests(); @@ -383,7 +887,7 @@ static void test_move(void) set_curr_dir_path(from, "test1.txt\0test2.txt\0test4.txt\0"); set_curr_dir_path(to, "test6.txt\0test7.txt\0"); ok(SHFileOperationA(&shfo2), "Can't move many files\n"); - ok(!file_exists(".\\test6.txt"), "The file is not moved - many files are " + ok(!file_exists("test6.txt"), "The file is not moved - many files are " "specified as a target\n"); init_shfo_tests(); @@ -391,19 +895,19 @@ static void test_move(void) set_curr_dir_path(from, "test3.txt\0"); set_curr_dir_path(to, "test4.txt\\test1.txt\0"); ok(!SHFileOperationA(&shfo), "File is moved moving to other directory\n"); - ok(file_exists(".\\test4.txt\\test1.txt"), "The file is moved\n"); + ok(file_exists("test4.txt\\test1.txt"), "The file is moved\n"); set_curr_dir_path(from, "test1.txt\0test2.txt\0test4.txt\0"); set_curr_dir_path(to, "test6.txt\0test7.txt\0test8.txt\0"); ok(SHFileOperationA(&shfo), "Cannot move many files\n"); - ok(file_exists(".\\test1.txt"), "The file is not moved. Many files are specified\n"); - ok(file_exists(".\\test4.txt"), "The directory is not moved. Many files are specified\n"); + ok(file_exists("test1.txt"), "The file is not moved. Many files are specified\n"); + ok(file_exists("test4.txt"), "The directory is not moved. Many files are specified\n"); set_curr_dir_path(from, "test1.txt\0"); set_curr_dir_path(to, "test6.txt\0"); ok(!SHFileOperationA(&shfo), "Move file\n"); - ok(!file_exists(".\\test1.txt"), "The file is moved\n"); - ok(file_exists(".\\test6.txt"), "The file is moved\n"); + ok(!file_exists("test1.txt"), "The file is moved\n"); + ok(file_exists("test6.txt"), "The file is moved\n"); set_curr_dir_path(from, "test6.txt\0"); set_curr_dir_path(to, "test1.txt\0"); ok(!SHFileOperationA(&shfo), "Move file back\n"); @@ -411,11 +915,67 @@ static void test_move(void) set_curr_dir_path(from, "test4.txt\0"); set_curr_dir_path(to, "test6.txt\0"); ok(!SHFileOperationA(&shfo), "Move dir\n"); - ok(!file_exists(".\\test4.txt"), "The dir is moved\n"); - ok(file_exists(".\\test6.txt"), "The dir is moved\n"); + ok(!file_exists("test4.txt"), "The dir is moved\n"); + ok(file_exists("test6.txt"), "The dir is moved\n"); set_curr_dir_path(from, "test6.txt\0"); set_curr_dir_path(to, "test4.txt\0"); ok(!SHFileOperationA(&shfo), "Move dir back\n"); + + /* move one file to two others */ + init_shfo_tests(); + shfo.pFrom = "test1.txt\0"; + shfo.pTo = "a.txt\0b.txt\0"; + retval = SHFileOperationA(&shfo); + ok(retval == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", retval); + ok(!file_exists("test1.txt"), "Expected test1.txt to not exist\n"); + ok(DeleteFile("a.txt"), "Expected a.txt to exist\n"); + ok(!file_exists("b.txt"), "Expected b.txt to not exist\n"); + + /* move two files to one other */ + shfo.pFrom = "test2.txt\0test3.txt\0"; + shfo.pTo = "test1.txt\0"; + retval = SHFileOperationA(&shfo); + ok(retval == ERROR_CANCELLED, "Expected ERROR_CANCELLED, got %d\n", retval); + ok(!file_exists("test1.txt"), "Expected test1.txt to not exist\n"); + ok(file_exists("test2.txt"), "Expected test2.txt to exist\n"); + ok(file_exists("test3.txt"), "Expected test3.txt to exist\n"); + + /* move a directory into itself */ + shfo.pFrom = "test4.txt\0"; + shfo.pTo = "test4.txt\\b.txt\0"; + retval = SHFileOperationA(&shfo); + ok(retval == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", retval); + ok(!RemoveDirectory("test4.txt\\b.txt"), "Expected test4.txt\\b.txt to not exist\n"); + ok(file_exists("test4.txt"), "Expected test4.txt to exist\n"); + + /* move many files without FOF_MULTIDESTFILES */ + shfo.pFrom = "test2.txt\0test3.txt\0"; + shfo.pTo = "d.txt\0e.txt\0"; + retval = SHFileOperationA(&shfo); + ok(retval == ERROR_CANCELLED, "Expected ERROR_CANCELLED, got %d\n", retval); + ok(!DeleteFile("d.txt"), "Expected d.txt to not exist\n"); + ok(!DeleteFile("e.txt"), "Expected e.txt to not exist\n"); + + /* number of sources != number of targets */ + shfo.pTo = "d.txt\0"; + shfo.fFlags |= FOF_MULTIDESTFILES; + retval = SHFileOperationA(&shfo); + ok(retval == ERROR_CANCELLED, "Expected ERROR_CANCELLED, got %d\n", retval); + ok(!DeleteFile("d.txt"), "Expected d.txt to not exist\n"); + + /* FO_MOVE does not create dest directories */ + shfo.pFrom = "test2.txt\0"; + shfo.pTo = "dir1\\dir2\\test2.txt\0"; + retval = SHFileOperationA(&shfo); + ok(retval == ERROR_CANCELLED, "Expected ERROR_CANCELLED, got %d\n", retval); + ok(!file_exists("dir1"), "Expected dir1 to not exist\n"); + + /* try to overwrite an existing file */ + shfo.pTo = "test3.txt\0"; + retval = SHFileOperationA(&shfo); + ok(retval == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", retval); + ok(!file_exists("test2.txt"), "Expected test2.txt to not exist\n"); + ok(file_exists("test3.txt"), "Expected test3.txt to exist\n"); } static void test_sh_create_dir(void) @@ -432,11 +992,81 @@ static void test_sh_create_dir(void) set_curr_dir_path(path, "testdir2\\test4.txt\0"); ret = pSHCreateDirectoryExA(NULL, path, NULL); ok(ERROR_SUCCESS == ret, "SHCreateDirectoryEx failed to create directory recursively, ret = %d\n", ret); - ok(file_exists(".\\testdir2"), "The first directory is not created\n"); - ok(file_exists(".\\testdir2\\test4.txt"), "The second directory is not created\n"); + ok(file_exists("testdir2"), "The first directory is not created\n"); + ok(file_exists("testdir2\\test4.txt"), "The second directory is not created\n"); ret = pSHCreateDirectoryExA(NULL, path, NULL); ok(ERROR_ALREADY_EXISTS == ret, "SHCreateDirectoryEx should fail to create existing directory, ret = %d\n", ret); + + ret = pSHCreateDirectoryExA(NULL, "c:\\testdir3", NULL); + ok(file_exists("c:\\testdir3"), "The directory is not created\n"); +} + +static void test_unicode(void) +{ + SHFILEOPSTRUCTW shfoW; + int ret; + HANDLE file; + + shfoW.hwnd = NULL; + shfoW.wFunc = FO_DELETE; + shfoW.pFrom = UNICODE_PATH; + shfoW.pTo = '\0'; + shfoW.fFlags = FOF_NOCONFIRMATION | FOF_SILENT | FOF_NOERRORUI; + shfoW.hNameMappings = NULL; + shfoW.lpszProgressTitle = NULL; + + /* Clean up before start test */ + DeleteFileW(UNICODE_PATH); + RemoveDirectoryW(UNICODE_PATH); + + /* Make sure we are on a system that supports unicode */ + SetLastError(0xdeadbeef); + file = CreateFileW(UNICODE_PATH, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); + if (GetLastError()==ERROR_CALL_NOT_IMPLEMENTED) + { + skip("Unicode tests skipped on non-unicode system\n"); + return; + } + CloseHandle(file); + + /* Try to delete a file with unicode filename */ + ok(file_existsW(UNICODE_PATH), "The file does not exist\n"); + ret = SHFileOperationW(&shfoW); + ok(!ret, "File is not removed, ErrorCode: %d\n", ret); + ok(!file_existsW(UNICODE_PATH), "The file should have been removed\n"); + + /* Try to trash a file with unicode filename */ + createTestFileW(UNICODE_PATH); + shfoW.fFlags |= FOF_ALLOWUNDO; + ok(file_existsW(UNICODE_PATH), "The file does not exist\n"); + ret = SHFileOperationW(&shfoW); + ok(!ret, "File is not removed, ErrorCode: %d\n", ret); + ok(!file_existsW(UNICODE_PATH), "The file should have been removed\n"); + + if(!pSHCreateDirectoryExW) + { + skip("Skipping SHCreateDirectoryExW tests\n"); + return; + } + + /* Try to delete a directory with unicode filename */ + ret = pSHCreateDirectoryExW(NULL, UNICODE_PATH, NULL); + ok(!ret, "SHCreateDirectoryExW returned %d\n", ret); + ok(file_existsW(UNICODE_PATH), "The directory is not created\n"); + shfoW.fFlags &= ~FOF_ALLOWUNDO; + ret = SHFileOperationW(&shfoW); + ok(!ret, "Directory is not removed, ErrorCode: %d\n", ret); + ok(!file_existsW(UNICODE_PATH), "The directory should have been removed\n"); + + /* Try to trash a directory with unicode filename */ + ret = pSHCreateDirectoryExW(NULL, UNICODE_PATH, NULL); + ok(!ret, "SHCreateDirectoryExW returned %d\n", ret); + ok(file_existsW(UNICODE_PATH), "The directory was not created\n"); + shfoW.fFlags |= FOF_ALLOWUNDO; + ret = SHFileOperationW(&shfoW); + ok(!ret, "Directory is not removed, ErrorCode: %d\n", ret); + ok(!file_existsW(UNICODE_PATH), "The directory should have been removed\n"); } START_TEST(shlfileop) @@ -445,6 +1075,10 @@ START_TEST(shlfileop) clean_after_shfo_tests(); + init_shfo_tests(); + test_get_file_info(); + clean_after_shfo_tests(); + init_shfo_tests(); test_delete(); clean_after_shfo_tests(); @@ -463,4 +1097,6 @@ START_TEST(shlfileop) test_sh_create_dir(); clean_after_shfo_tests(); + + test_unicode(); } diff --git a/rostests/winetests/shell32/shlfolder.c b/rostests/winetests/shell32/shlfolder.c index 3566c7ba217..0cf398a11a7 100644 --- a/rostests/winetests/shell32/shlfolder.c +++ b/rostests/winetests/shell32/shlfolder.c @@ -15,13 +15,14 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #define COBJMACROS +#define CONST_VTABLE #include "windef.h" #include "winbase.h" @@ -31,11 +32,11 @@ #include "shlguid.h" #include "shlobj.h" -//#include "shobjidl.h" +#include "shobjidl.h" #include "shlwapi.h" +#include "ocidl.h" +#include "oleauto.h" - -#include "wine/unicode.h" #include "wine/test.h" @@ -43,20 +44,28 @@ static IMalloc *ppM; static HRESULT (WINAPI *pSHBindToParent)(LPCITEMIDLIST, REFIID, LPVOID*, LPCITEMIDLIST*); static BOOL (WINAPI *pSHGetSpecialFolderPathW)(HWND, LPWSTR, int, BOOL); +static HRESULT (WINAPI *pStrRetToBufW)(STRRET*,LPCITEMIDLIST,LPWSTR,UINT); +static LPITEMIDLIST (WINAPI *pILFindLastID)(LPCITEMIDLIST); +static void (WINAPI *pILFree)(LPITEMIDLIST); +static BOOL (WINAPI *pILIsEqual)(LPCITEMIDLIST, LPCITEMIDLIST); static void init_function_pointers(void) { - HMODULE hmod = GetModuleHandleA("shell32.dll"); + HMODULE hmod; HRESULT hr; - if(hmod) - { - pSHBindToParent = (void*)GetProcAddress(hmod, "SHBindToParent"); - pSHGetSpecialFolderPathW = (void*)GetProcAddress(hmod, "SHGetSpecialFolderPathW"); - } + hmod = GetModuleHandleA("shell32.dll"); + pSHBindToParent = (void*)GetProcAddress(hmod, "SHBindToParent"); + pSHGetSpecialFolderPathW = (void*)GetProcAddress(hmod, "SHGetSpecialFolderPathW"); + pILFindLastID = (void *)GetProcAddress(hmod, (LPCSTR)16); + pILFree = (void*)GetProcAddress(hmod, (LPSTR)155); + pILIsEqual = (void*)GetProcAddress(hmod, (LPSTR)21); + + hmod = GetModuleHandleA("shlwapi.dll"); + pStrRetToBufW = (void*)GetProcAddress(hmod, "StrRetToBufW"); hr = SHGetMalloc(&ppM); - ok(hr == S_OK, "SHGetMalloc failed %08lx\n", hr); + ok(hr == S_OK, "SHGetMalloc failed %08x\n", hr); } static void test_ParseDisplayName(void) @@ -68,6 +77,7 @@ static void test_ParseDisplayName(void) DWORD res; WCHAR cTestDirW [MAX_PATH] = {0}; ITEMIDLIST *newPIDL; + BOOL bRes; hr = SHGetDesktopFolder(&IDesktopFolder); if(hr != S_OK) return; @@ -79,7 +89,7 @@ static void test_ParseDisplayName(void) hr = IShellFolder_ParseDisplayName(IDesktopFolder, NULL, NULL, cTestDirW, NULL, &newPIDL, 0); ok((hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) || (hr == E_FAIL), - "ParseDisplayName returned %08lx, expected 80070002 or E_FAIL\n", hr); + "ParseDisplayName returned %08x, expected 80070002 or E_FAIL\n", hr); res = GetFileAttributesA(cNonExistDir2A); if(res != INVALID_FILE_ATTRIBUTES) return; @@ -87,7 +97,28 @@ static void test_ParseDisplayName(void) MultiByteToWideChar(CP_ACP, 0, cNonExistDir2A, -1, cTestDirW, MAX_PATH); hr = IShellFolder_ParseDisplayName(IDesktopFolder, NULL, NULL, cTestDirW, NULL, &newPIDL, 0); - ok((hr == E_FAIL), "ParseDisplayName returned %08lx, expected E_FAIL\n", hr); + ok((hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) || (hr == E_FAIL) || (hr == E_INVALIDARG), + "ParseDisplayName returned %08x, expected 80070002, E_FAIL or E_INVALIDARG\n", hr); + + /* I thought that perhaps the DesktopFolder's ParseDisplayName would recognize the + * path corresponding to CSIDL_PERSONAL and return a CLSID_MyDocuments PIDL. Turns + * out it doesn't. The magic seems to happen in the file dialogs, then. */ + if (!pSHGetSpecialFolderPathW || !pILFindLastID) goto finished; + + bRes = pSHGetSpecialFolderPathW(NULL, cTestDirW, CSIDL_PERSONAL, FALSE); + ok(bRes, "SHGetSpecialFolderPath(CSIDL_PERSONAL) failed! %u\n", GetLastError()); + if (!bRes) goto finished; + + hr = IShellFolder_ParseDisplayName(IDesktopFolder, NULL, NULL, cTestDirW, NULL, &newPIDL, 0); + ok(SUCCEEDED(hr), "DesktopFolder->ParseDisplayName failed. hr = %08x.\n", hr); + if (FAILED(hr)) goto finished; + + ok(pILFindLastID(newPIDL)->mkid.abID[0] == 0x31, "Last pidl should be of type " + "PT_FOLDER, but is: %02x\n", pILFindLastID(newPIDL)->mkid.abID[0]); + IMalloc_Free(ppM, newPIDL); + +finished: + IShellFolder_Release(IDesktopFolder); } /* creates a file with the specified name for tests */ @@ -149,18 +180,19 @@ static void test_EnumObjects(IShellFolder *iFolder) { 1, 1, 1, 1, 0} }; - /* Just test SFGAO_FILESYSTEM | SFGAO_FOLDER | SFGAO_FILESYSANCESTOR for now */ +#define SFGAO_testfor SFGAO_FILESYSTEM | SFGAO_FOLDER | SFGAO_FILESYSANCESTOR | SFGAO_CAPABILITYMASK + /* Don't test for SFGAO_HASSUBFOLDER since we return real state and native cached */ static const ULONG attrs[5] = { - SFGAO_FILESYSTEM | SFGAO_FOLDER | SFGAO_FILESYSANCESTOR, - SFGAO_FILESYSTEM | SFGAO_FOLDER | SFGAO_FILESYSANCESTOR, - SFGAO_FILESYSTEM, - SFGAO_FILESYSTEM, - SFGAO_FILESYSTEM, + SFGAO_CAPABILITYMASK | SFGAO_FILESYSTEM | SFGAO_FOLDER | SFGAO_FILESYSANCESTOR, + SFGAO_CAPABILITYMASK | SFGAO_FILESYSTEM | SFGAO_FOLDER | SFGAO_FILESYSANCESTOR, + SFGAO_CAPABILITYMASK | SFGAO_FILESYSTEM, + SFGAO_CAPABILITYMASK | SFGAO_FILESYSTEM, + SFGAO_CAPABILITYMASK | SFGAO_FILESYSTEM, }; hr = IShellFolder_EnumObjects(iFolder, NULL, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN, &iEnumList); - ok(hr == S_OK, "EnumObjects failed %08lx\n", hr); + ok(hr == S_OK, "EnumObjects failed %08x\n", hr); /* This is to show that, contrary to what is said on MSDN, on IEnumIDList::Next, * the filesystem shellfolders return S_OK even if less than 'celt' items are @@ -171,7 +203,7 @@ static void test_EnumObjects(IShellFolder *iFolder) ok (i == 5, "i: %d\n", i); hr = IEnumIDList_Release(iEnumList); - ok(hr == S_OK, "IEnumIDList_Release failed %08lx\n", hr); + ok(hr == S_OK, "IEnumIDList_Release failed %08x\n", hr); /* Sort them first in case of wrong order from system */ for (i=0;i<5;i++) for (j=0;j<5;j++) @@ -185,18 +217,25 @@ static void test_EnumObjects(IShellFolder *iFolder) for (i=0;i<5;i++) for (j=0;j<5;j++) { hr = IShellFolder_CompareIDs(iFolder, 0, idlArr[i], idlArr[j]); - ok(hr == iResults[i][j], "Got %lx expected [%d]-[%d]=%x\n", hr, i, j, iResults[i][j]); + ok(hr == iResults[i][j], "Got %x expected [%d]-[%d]=%x\n", hr, i, j, iResults[i][j]); } for (i = 0; i < 5; i++) { SFGAOF flags; - flags = SFGAO_FILESYSTEM | SFGAO_FOLDER | SFGAO_FILESYSANCESTOR; + /* Native returns all flags no matter what we ask for */ + flags = SFGAO_CANCOPY; hr = IShellFolder_GetAttributesOf(iFolder, 1, (LPCITEMIDLIST*)(idlArr + i), &flags); - flags &= SFGAO_FILESYSTEM | SFGAO_FOLDER | SFGAO_FILESYSANCESTOR; - ok(hr == S_OK, "GetAttributesOf returns %08lx\n", hr); - ok(flags == attrs[i], "GetAttributesOf gets attrs %08lx, expects %08lx\n", flags, attrs[i]); + flags &= SFGAO_testfor; + ok(hr == S_OK, "GetAttributesOf returns %08x\n", hr); + ok(flags == (attrs[i]), "GetAttributesOf[%i] got %08x, expected %08x\n", i, flags, attrs[i]); + + flags = SFGAO_testfor; + hr = IShellFolder_GetAttributesOf(iFolder, 1, (LPCITEMIDLIST*)(idlArr + i), &flags); + flags &= SFGAO_testfor; + ok(hr == S_OK, "GetAttributesOf returns %08x\n", hr); + ok(flags == attrs[i], "GetAttributesOf[%i] got %08x, expected %08x\n", i, flags, attrs[i]); } for (i=0;i<5;i++) @@ -211,6 +250,7 @@ static void test_BindToObject(void) SHITEMID emptyitem = { 0, { 0 } }; LPITEMIDLIST pidlMyComputer, pidlSystemDir, pidlEmpty = (LPITEMIDLIST)&emptyitem; WCHAR wszSystemDir[MAX_PATH]; + char szSystemDir[MAX_PATH]; WCHAR wszMyComputer[] = { ':',':','{','2','0','D','0','4','F','E','0','-','3','A','E','A','-','1','0','6','9','-', 'A','2','D','8','-','0','8','0','0','2','B','3','0','3','0','9','D','}',0 }; @@ -219,61 +259,68 @@ static void test_BindToObject(void) * with an empty pidl. This is tested for Desktop, MyComputer and the FS ShellFolder */ hr = SHGetDesktopFolder(&psfDesktop); - ok (SUCCEEDED(hr), "SHGetDesktopFolder failed! hr = %08lx\n", hr); + ok (SUCCEEDED(hr), "SHGetDesktopFolder failed! hr = %08x\n", hr); if (FAILED(hr)) return; hr = IShellFolder_BindToObject(psfDesktop, pidlEmpty, NULL, &IID_IShellFolder, (LPVOID*)&psfChild); - ok (hr == E_INVALIDARG, "Desktop's BindToObject should fail, when called with empty pidl! hr = %08lx\n", hr); + ok (hr == E_INVALIDARG, "Desktop's BindToObject should fail, when called with empty pidl! hr = %08x\n", hr); hr = IShellFolder_BindToObject(psfDesktop, NULL, NULL, &IID_IShellFolder, (LPVOID*)&psfChild); - ok (hr == E_INVALIDARG, "Desktop's BindToObject should fail, when called with NULL pidl! hr = %08lx\n", hr); + ok (hr == E_INVALIDARG, "Desktop's BindToObject should fail, when called with NULL pidl! hr = %08x\n", hr); hr = IShellFolder_ParseDisplayName(psfDesktop, NULL, NULL, wszMyComputer, NULL, &pidlMyComputer, NULL); - ok (SUCCEEDED(hr), "Desktop's ParseDisplayName failed to parse MyComputer's CLSID! hr = %08lx\n", hr); + ok (SUCCEEDED(hr), "Desktop's ParseDisplayName failed to parse MyComputer's CLSID! hr = %08x\n", hr); if (FAILED(hr)) { IShellFolder_Release(psfDesktop); return; } hr = IShellFolder_BindToObject(psfDesktop, pidlMyComputer, NULL, &IID_IShellFolder, (LPVOID*)&psfMyComputer); - ok (SUCCEEDED(hr), "Desktop failed to bind to MyComputer object! hr = %08lx\n", hr); + ok (SUCCEEDED(hr), "Desktop failed to bind to MyComputer object! hr = %08x\n", hr); IShellFolder_Release(psfDesktop); IMalloc_Free(ppM, pidlMyComputer); if (FAILED(hr)) return; hr = IShellFolder_BindToObject(psfMyComputer, pidlEmpty, NULL, &IID_IShellFolder, (LPVOID*)&psfChild); - ok (hr == E_INVALIDARG, "MyComputers's BindToObject should fail, when called with empty pidl! hr = %08lx\n", hr); + ok (hr == E_INVALIDARG, "MyComputers's BindToObject should fail, when called with empty pidl! hr = %08x\n", hr); +#if 0 + /* this call segfaults on 98SE */ hr = IShellFolder_BindToObject(psfMyComputer, NULL, NULL, &IID_IShellFolder, (LPVOID*)&psfChild); - ok (hr == E_INVALIDARG, "MyComputers's BindToObject should fail, when called with NULL pidl! hr = %08lx\n", hr); + ok (hr == E_INVALIDARG, "MyComputers's BindToObject should fail, when called with NULL pidl! hr = %08x\n", hr); +#endif - cChars = GetSystemDirectoryW(wszSystemDir, MAX_PATH); - ok (cChars > 0 && cChars < MAX_PATH, "GetSystemDirectoryW failed! LastError: %08lx\n", GetLastError()); + cChars = GetSystemDirectoryA(szSystemDir, MAX_PATH); + ok (cChars > 0 && cChars < MAX_PATH, "GetSystemDirectoryA failed! LastError: %u\n", GetLastError()); if (cChars == 0 || cChars >= MAX_PATH) { IShellFolder_Release(psfMyComputer); return; } + MultiByteToWideChar(CP_ACP, 0, szSystemDir, -1, wszSystemDir, MAX_PATH); hr = IShellFolder_ParseDisplayName(psfMyComputer, NULL, NULL, wszSystemDir, NULL, &pidlSystemDir, NULL); - ok (SUCCEEDED(hr), "MyComputers's ParseDisplayName failed to parse the SystemDirectory! hr = %08lx\n", hr); + ok (SUCCEEDED(hr), "MyComputers's ParseDisplayName failed to parse the SystemDirectory! hr = %08x\n", hr); if (FAILED(hr)) { IShellFolder_Release(psfMyComputer); return; } hr = IShellFolder_BindToObject(psfMyComputer, pidlSystemDir, NULL, &IID_IShellFolder, (LPVOID*)&psfSystemDir); - ok (SUCCEEDED(hr), "MyComputer failed to bind to a FileSystem ShellFolder! hr = %08lx\n", hr); + ok (SUCCEEDED(hr), "MyComputer failed to bind to a FileSystem ShellFolder! hr = %08x\n", hr); IShellFolder_Release(psfMyComputer); IMalloc_Free(ppM, pidlSystemDir); if (FAILED(hr)) return; hr = IShellFolder_BindToObject(psfSystemDir, pidlEmpty, NULL, &IID_IShellFolder, (LPVOID*)&psfChild); ok (hr == E_INVALIDARG, - "FileSystem ShellFolder's BindToObject should fail, when called with empty pidl! hr = %08lx\n", hr); + "FileSystem ShellFolder's BindToObject should fail, when called with empty pidl! hr = %08x\n", hr); +#if 0 + /* this call segfaults on 98SE */ hr = IShellFolder_BindToObject(psfSystemDir, NULL, NULL, &IID_IShellFolder, (LPVOID*)&psfChild); ok (hr == E_INVALIDARG, - "FileSystem ShellFolder's BindToObject should fail, when called with NULL pidl! hr = %08lx\n", hr); + "FileSystem ShellFolder's BindToObject should fail, when called with NULL pidl! hr = %08x\n", hr); +#endif IShellFolder_Release(psfSystemDir); } @@ -284,10 +331,13 @@ static void test_GetDisplayName(void) HRESULT hr; HANDLE hTestFile; WCHAR wszTestFile[MAX_PATH], wszTestFile2[MAX_PATH], wszTestDir[MAX_PATH]; + char szTestFile[MAX_PATH], szTestDir[MAX_PATH]; + DWORD attr; STRRET strret; LPSHELLFOLDER psfDesktop, psfPersonal; IUnknown *psfFile; - LPITEMIDLIST pidlTestFile; + SHITEMID emptyitem = { 0, { 0 } }; + LPITEMIDLIST pidlTestFile, pidlEmpty = (LPITEMIDLIST)&emptyitem; LPCITEMIDLIST pidlLast; static const WCHAR wszFileName[] = { 'w','i','n','e','t','e','s','t','.','f','o','o',0 }; static const WCHAR wszDirName[] = { 'w','i','n','e','t','e','s','t',0 }; @@ -304,74 +354,137 @@ static void test_GetDisplayName(void) /* First creating a directory in MyDocuments and a file in this directory. */ result = pSHGetSpecialFolderPathW(NULL, wszTestDir, CSIDL_PERSONAL, FALSE); - ok(result, "SHGetSpecialFolderPathW failed! Last error: %08lx\n", GetLastError()); + ok(result, "SHGetSpecialFolderPathW failed! Last error: %u\n", GetLastError()); if (!result) return; PathAddBackslashW(wszTestDir); lstrcatW(wszTestDir, wszDirName); - result = CreateDirectoryW(wszTestDir, NULL); - ok(result, "CreateDirectoryW failed! Last error: %08lx\n", GetLastError()); - if (!result) return; + /* Use ANSI file functions so this works on Windows 9x */ + WideCharToMultiByte(CP_ACP, 0, wszTestDir, -1, szTestDir, MAX_PATH, 0, 0); + CreateDirectoryA(szTestDir, NULL); + attr=GetFileAttributesA(szTestDir); + if (attr == INVALID_FILE_ATTRIBUTES || !(attr & FILE_ATTRIBUTE_DIRECTORY)) + { + ok(0, "unable to create the '%s' directory\n", szTestDir); + return; + } lstrcpyW(wszTestFile, wszTestDir); PathAddBackslashW(wszTestFile); lstrcatW(wszTestFile, wszFileName); + WideCharToMultiByte(CP_ACP, 0, wszTestFile, -1, szTestFile, MAX_PATH, 0, 0); - hTestFile = CreateFileW(wszTestFile, GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, NULL); - ok(hTestFile != INVALID_HANDLE_VALUE, "CreateFileW failed! Last error: %08lx\n", GetLastError()); + hTestFile = CreateFileA(szTestFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); + ok((hTestFile != INVALID_HANDLE_VALUE), "CreateFileA failed! Last error: %u\n", GetLastError()); if (hTestFile == INVALID_HANDLE_VALUE) return; CloseHandle(hTestFile); /* Getting an itemidlist for the file. */ hr = SHGetDesktopFolder(&psfDesktop); - ok(SUCCEEDED(hr), "SHGetDesktopFolder failed! hr = %08lx\n", hr); + ok(SUCCEEDED(hr), "SHGetDesktopFolder failed! hr = %08x\n", hr); if (FAILED(hr)) return; hr = IShellFolder_ParseDisplayName(psfDesktop, NULL, NULL, wszTestFile, NULL, &pidlTestFile, NULL); - ok(SUCCEEDED(hr), "Desktop->ParseDisplayName failed! hr = %08lx\n", hr); + ok(SUCCEEDED(hr), "Desktop->ParseDisplayName failed! hr = %08x\n", hr); if (FAILED(hr)) { IShellFolder_Release(psfDesktop); return; } + /* WinXP stores the filenames as both ANSI and UNICODE in the pidls */ + pidlLast = pILFindLastID(pidlTestFile); + ok(pidlLast->mkid.cb >=76, "Expected pidl length of at least 76, got %d.\n", pidlLast->mkid.cb); + if (pidlLast->mkid.cb >= 76) { + ok(!lstrcmpW((WCHAR*)&pidlLast->mkid.abID[46], wszFileName), + "WinXP stores the filename as a wchar-string at this position!\n"); + } + /* It seems as if we cannot bind to regular files on windows, but only directories. */ hr = IShellFolder_BindToObject(psfDesktop, pidlTestFile, NULL, &IID_IUnknown, (VOID**)&psfFile); - todo_wine { ok (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), "hr = %08lx\n", hr); } + todo_wine { ok (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), "hr = %08x\n", hr); } if (SUCCEEDED(hr)) { IShellFolder_Release(psfFile); } - + + /* Some tests for IShellFolder::SetNameOf */ + hr = pSHBindToParent(pidlTestFile, &IID_IShellFolder, (VOID**)&psfPersonal, &pidlLast); + ok(SUCCEEDED(hr), "SHBindToParent failed! hr = %08x\n", hr); + if (SUCCEEDED(hr)) { + /* It's ok to use this fixed path. Call will fail anyway. */ + WCHAR wszAbsoluteFilename[] = { 'C',':','\\','w','i','n','e','t','e','s','t', 0 }; + LPITEMIDLIST pidlNew; + + /* The pidl returned through the last parameter of SetNameOf is a simple one. */ + hr = IShellFolder_SetNameOf(psfPersonal, NULL, pidlLast, wszDirName, SHGDN_NORMAL, &pidlNew); + ok (SUCCEEDED(hr), "SetNameOf failed! hr = %08x\n", hr); + ok (((LPITEMIDLIST)((LPBYTE)pidlNew+pidlNew->mkid.cb))->mkid.cb == 0, + "pidl returned from SetNameOf should be simple!\n"); + + /* Passing an absolute path to SetNameOf fails. The HRESULT code indicates that SetNameOf + * is implemented on top of SHFileOperation in WinXP. */ + hr = IShellFolder_SetNameOf(psfPersonal, NULL, pidlNew, wszAbsoluteFilename, + SHGDN_FORPARSING, NULL); + ok (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED), "SetNameOf succeeded! hr = %08x\n", hr); + + /* Rename the file back to its original name. SetNameOf ignores the fact, that the + * SHGDN flags specify an absolute path. */ + hr = IShellFolder_SetNameOf(psfPersonal, NULL, pidlNew, wszFileName, SHGDN_FORPARSING, NULL); + ok (SUCCEEDED(hr), "SetNameOf failed! hr = %08x\n", hr); + + pILFree(pidlNew); + IShellFolder_Release(psfPersonal); + } + /* Deleting the file and the directory */ - DeleteFileW(wszTestFile); - RemoveDirectoryW(wszTestDir); + DeleteFileA(szTestFile); + RemoveDirectoryA(szTestDir); /* SHGetPathFromIDListW still works, although the file is not present anymore. */ result = SHGetPathFromIDListW(pidlTestFile, wszTestFile2); - ok (result, "SHGetPathFromIDListW failed! Last error: %08lx\n", GetLastError()); + ok (result, "SHGetPathFromIDListW failed! Last error: %u\n", GetLastError()); ok (!lstrcmpiW(wszTestFile, wszTestFile2), "SHGetPathFromIDListW returns incorrect path!\n"); if(!pSHBindToParent) return; + /* SHBindToParent fails, if called with a NULL PIDL. */ + hr = pSHBindToParent(NULL, &IID_IShellFolder, (VOID**)&psfPersonal, &pidlLast); + ok (FAILED(hr), "SHBindToParent(NULL) should fail!\n"); + + /* But it succeeds with an empty PIDL. */ + hr = pSHBindToParent(pidlEmpty, &IID_IShellFolder, (VOID**)&psfPersonal, &pidlLast); + ok (SUCCEEDED(hr), "SHBindToParent(empty PIDL) should succeed! hr = %08x\n", hr); + ok (pidlLast == pidlEmpty, "The last element of an empty PIDL should be the PIDL itself!\n"); + if (SUCCEEDED(hr)) + IShellFolder_Release(psfPersonal); + /* Binding to the folder and querying the display name of the file also works. */ hr = pSHBindToParent(pidlTestFile, &IID_IShellFolder, (VOID**)&psfPersonal, &pidlLast); - ok (SUCCEEDED(hr), "SHBindToParent failed! hr = %08lx\n", hr); + ok (SUCCEEDED(hr), "SHBindToParent failed! hr = %08x\n", hr); if (FAILED(hr)) { IShellFolder_Release(psfDesktop); return; } + /* This test shows that Windows doesn't allocate a new pidlLast, but returns a pointer into + * pidlTestFile (In accordance with MSDN). */ + ok (pILFindLastID(pidlTestFile) == pidlLast, + "SHBindToParent doesn't return the last id of the pidl param!\n"); + hr = IShellFolder_GetDisplayNameOf(psfPersonal, pidlLast, SHGDN_FORPARSING, &strret); - ok (SUCCEEDED(hr), "Personal->GetDisplayNameOf failed! hr = %08lx\n", hr); + ok (SUCCEEDED(hr), "Personal->GetDisplayNameOf failed! hr = %08x\n", hr); if (FAILED(hr)) { IShellFolder_Release(psfDesktop); IShellFolder_Release(psfPersonal); return; } - - hr = StrRetToBufW(&strret, pidlLast, wszTestFile2, MAX_PATH); - ok (SUCCEEDED(hr), "StrRetToBufW failed! hr = %08lx\n", hr); - ok (!lstrcmpiW(wszTestFile, wszTestFile2), "GetDisplayNameOf returns incorrect path!\n"); + + if (pStrRetToBufW) + { + hr = pStrRetToBufW(&strret, pidlLast, wszTestFile2, MAX_PATH); + ok (SUCCEEDED(hr), "StrRetToBufW failed! hr = %08x\n", hr); + ok (!lstrcmpiW(wszTestFile, wszTestFile2), "GetDisplayNameOf returns incorrect path!\n"); + } IShellFolder_Release(psfDesktop); IShellFolder_Release(psfPersonal); @@ -407,13 +520,13 @@ static void test_CallForAttributes(void) * on MSDN. This test is meant to document the observed behaviour on WinXP SP2. */ hr = SHGetDesktopFolder(&psfDesktop); - ok (SUCCEEDED(hr), "SHGetDesktopFolder failed! hr = %08lx\n", hr); + ok (SUCCEEDED(hr), "SHGetDesktopFolder failed! hr = %08x\n", hr); if (FAILED(hr)) return; hr = IShellFolder_ParseDisplayName(psfDesktop, NULL, NULL, wszMyDocuments, NULL, &pidlMyDocuments, NULL); ok (SUCCEEDED(hr), - "Desktop's ParseDisplayName failed to parse MyDocuments's CLSID! hr = %08lx\n", hr); + "Desktop's ParseDisplayName failed to parse MyDocuments's CLSID! hr = %08x\n", hr); if (FAILED(hr)) { IShellFolder_Release(psfDesktop); return; @@ -422,11 +535,10 @@ static void test_CallForAttributes(void) dwAttributes = 0xffffffff; hr = IShellFolder_GetAttributesOf(psfDesktop, 1, (LPCITEMIDLIST*)&pidlMyDocuments, &dwAttributes); - ok (SUCCEEDED(hr), "Desktop->GetAttributesOf(MyDocuments) failed! hr = %08lx\n", hr); + ok (SUCCEEDED(hr), "Desktop->GetAttributesOf(MyDocuments) failed! hr = %08x\n", hr); /* We need the following setup (as observed on WinXP SP2), for the tests to make sense. */ - todo_wine{ ok (dwAttributes & SFGAO_FILESYSTEM, - "SFGAO_FILESYSTEM attribute is not set for MyDocuments!\n"); } + ok (dwAttributes & SFGAO_FILESYSTEM, "SFGAO_FILESYSTEM attribute is not set for MyDocuments!\n"); ok (!(dwAttributes & SFGAO_ISSLOW), "SFGAO_ISSLOW attribute is set for MyDocuments!\n"); ok (!(dwAttributes & SFGAO_GHOSTED), "SFGAO_GHOSTED attribute is set for MyDocuments!\n"); @@ -434,7 +546,7 @@ static void test_CallForAttributes(void) * key. So the test will return at this point, if run on wine. */ lResult = RegOpenKeyExW(HKEY_CLASSES_ROOT, wszMyDocumentsKey, 0, KEY_WRITE|KEY_READ, &hKey); - todo_wine { ok (lResult == ERROR_SUCCESS, "RegOpenKeyEx failed! result: %08lx\n", lResult); } + ok (lResult == ERROR_SUCCESS, "RegOpenKeyEx failed! result: %08x\n", lResult); if (lResult != ERROR_SUCCESS) { IMalloc_Free(ppM, pidlMyDocuments); IShellFolder_Release(psfDesktop); @@ -444,7 +556,7 @@ static void test_CallForAttributes(void) /* Query MyDocuments' Attributes value, to be able to restore it later. */ dwSize = sizeof(DWORD); lResult = RegQueryValueExW(hKey, wszAttributes, NULL, NULL, (LPBYTE)&dwOrigAttributes, &dwSize); - ok (lResult == ERROR_SUCCESS, "RegQueryValueEx failed! result: %08lx\n", lResult); + ok (lResult == ERROR_SUCCESS, "RegQueryValueEx failed! result: %08x\n", lResult); if (lResult != ERROR_SUCCESS) { RegCloseKey(hKey); IMalloc_Free(ppM, pidlMyDocuments); @@ -456,7 +568,7 @@ static void test_CallForAttributes(void) dwSize = sizeof(DWORD); lResult = RegQueryValueExW(hKey, wszCallForAttributes, NULL, NULL, (LPBYTE)&dwOrigCallForAttributes, &dwSize); - ok (lResult == ERROR_SUCCESS, "RegQueryValueEx failed! result: %08lx\n", lResult); + ok (lResult == ERROR_SUCCESS, "RegQueryValueEx failed! result: %08x\n", lResult); if (lResult != ERROR_SUCCESS) { RegCloseKey(hKey); IMalloc_Free(ppM, pidlMyDocuments); @@ -481,10 +593,10 @@ static void test_CallForAttributes(void) dwAttributes = SFGAO_ISSLOW|SFGAO_GHOSTED|SFGAO_FILESYSTEM; hr = IShellFolder_GetAttributesOf(psfDesktop, 1, (LPCITEMIDLIST*)&pidlMyDocuments, &dwAttributes); - ok (SUCCEEDED(hr), "Desktop->GetAttributesOf(MyDocuments) failed! hr = %08lx\n", hr); + ok (SUCCEEDED(hr), "Desktop->GetAttributesOf(MyDocuments) failed! hr = %08x\n", hr); if (SUCCEEDED(hr)) ok (dwAttributes == SFGAO_FILESYSTEM, - "Desktop->GetAttributes(MyDocuments) returned unexpected attributes: %08lx\n", + "Desktop->GetAttributes(MyDocuments) returned unexpected attributes: %08x\n", dwAttributes); /* Restore MyDocuments' original Attributes and CallForAttributes registry values */ @@ -504,37 +616,44 @@ static void test_GetAttributesOf(void) LPCITEMIDLIST pidlEmpty = (LPCITEMIDLIST)&emptyitem; LPITEMIDLIST pidlMyComputer; DWORD dwFlags; - const static DWORD dwDesktopFlags = /* As observed on WinXP SP2 */ + static const DWORD dwDesktopFlags = /* As observed on WinXP SP2 */ SFGAO_STORAGE | SFGAO_HASPROPSHEET | SFGAO_STORAGEANCESTOR | SFGAO_FILESYSANCESTOR | SFGAO_FOLDER | SFGAO_FILESYSTEM | SFGAO_HASSUBFOLDER; - const static DWORD dwMyComputerFlags = /* As observed on WinXP SP2 */ + static const DWORD dwMyComputerFlags = /* As observed on WinXP SP2 */ SFGAO_CANRENAME | SFGAO_CANDELETE | SFGAO_HASPROPSHEET | SFGAO_DROPTARGET | SFGAO_FILESYSANCESTOR | SFGAO_FOLDER | SFGAO_HASSUBFOLDER; WCHAR wszMyComputer[] = { ':',':','{','2','0','D','0','4','F','E','0','-','3','A','E','A','-','1','0','6','9','-', 'A','2','D','8','-','0','8','0','0','2','B','3','0','3','0','9','D','}',0 }; + char cCurrDirA [MAX_PATH] = {0}; + WCHAR cCurrDirW [MAX_PATH]; + static WCHAR cTestDirW[] = {'t','e','s','t','d','i','r',0}; + static const WCHAR cBackSlash[] = {'\\',0}; + IShellFolder *IDesktopFolder, *testIShellFolder; + ITEMIDLIST *newPIDL; + int len; hr = SHGetDesktopFolder(&psfDesktop); - ok (SUCCEEDED(hr), "SHGetDesktopFolder failed! hr = %08lx\n", hr); + ok (SUCCEEDED(hr), "SHGetDesktopFolder failed! hr = %08x\n", hr); if (FAILED(hr)) return; /* The Desktop attributes can be queried with a single empty itemidlist, .. */ dwFlags = 0xffffffff; hr = IShellFolder_GetAttributesOf(psfDesktop, 1, &pidlEmpty, &dwFlags); - ok (SUCCEEDED(hr), "Desktop->GetAttributesOf(empty pidl) failed! hr = %08lx\n", hr); - ok (dwFlags == dwDesktopFlags, "Wrong Desktop attributes: %08lx, expected: %08lx\n", + ok (SUCCEEDED(hr), "Desktop->GetAttributesOf(empty pidl) failed! hr = %08x\n", hr); + ok (dwFlags == dwDesktopFlags, "Wrong Desktop attributes: %08x, expected: %08x\n", dwFlags, dwDesktopFlags); /* .. or with no itemidlist at all. */ dwFlags = 0xffffffff; hr = IShellFolder_GetAttributesOf(psfDesktop, 0, NULL, &dwFlags); - ok (SUCCEEDED(hr), "Desktop->GetAttributesOf(NULL) failed! hr = %08lx\n", hr); - ok (dwFlags == dwDesktopFlags, "Wrong Desktop attributes: %08lx, expected: %08lx\n", + ok (SUCCEEDED(hr), "Desktop->GetAttributesOf(NULL) failed! hr = %08x\n", hr); + ok (dwFlags == dwDesktopFlags, "Wrong Desktop attributes: %08x, expected: %08x\n", dwFlags, dwDesktopFlags); /* Testing the attributes of the MyComputer shellfolder */ hr = IShellFolder_ParseDisplayName(psfDesktop, NULL, NULL, wszMyComputer, NULL, &pidlMyComputer, NULL); - ok (SUCCEEDED(hr), "Desktop's ParseDisplayName failed to parse MyComputer's CLSID! hr = %08lx\n", hr); + ok (SUCCEEDED(hr), "Desktop's ParseDisplayName failed to parse MyComputer's CLSID! hr = %08x\n", hr); if (FAILED(hr)) { IShellFolder_Release(psfDesktop); return; @@ -549,26 +668,87 @@ static void test_GetAttributesOf(void) */ dwFlags = 0xffffffff; hr = IShellFolder_GetAttributesOf(psfDesktop, 1, (LPCITEMIDLIST*)&pidlMyComputer, &dwFlags); - ok (SUCCEEDED(hr), "Desktop->GetAttributesOf(MyComputer) failed! hr = %08lx\n", hr); - todo_wine { ok ((dwFlags & ~(DWORD)SFGAO_CANLINK) == dwMyComputerFlags, - "Wrong MyComputer attributes: %08lx, expected: %08lx\n", dwFlags, dwMyComputerFlags); } + ok (SUCCEEDED(hr), "Desktop->GetAttributesOf(MyComputer) failed! hr = %08x\n", hr); + ok ((dwFlags & ~(DWORD)SFGAO_CANLINK) == dwMyComputerFlags, + "Wrong MyComputer attributes: %08x, expected: %08x\n", dwFlags, dwMyComputerFlags); hr = IShellFolder_BindToObject(psfDesktop, pidlMyComputer, NULL, &IID_IShellFolder, (LPVOID*)&psfMyComputer); - ok (SUCCEEDED(hr), "Desktop failed to bind to MyComputer object! hr = %08lx\n", hr); + ok (SUCCEEDED(hr), "Desktop failed to bind to MyComputer object! hr = %08x\n", hr); IShellFolder_Release(psfDesktop); IMalloc_Free(ppM, pidlMyComputer); if (FAILED(hr)) return; hr = IShellFolder_GetAttributesOf(psfMyComputer, 1, &pidlEmpty, &dwFlags); - todo_wine {ok (hr == E_INVALIDARG, "MyComputer->GetAttributesOf(emtpy pidl) should fail! hr = %08lx\n", hr); } + todo_wine {ok (hr == E_INVALIDARG, "MyComputer->GetAttributesOf(emtpy pidl) should fail! hr = %08x\n", hr); } dwFlags = 0xffffffff; hr = IShellFolder_GetAttributesOf(psfMyComputer, 0, NULL, &dwFlags); - ok (SUCCEEDED(hr), "MyComputer->GetAttributesOf(NULL) failed! hr = %08lx\n", hr); + ok (SUCCEEDED(hr), "MyComputer->GetAttributesOf(NULL) failed! hr = %08x\n", hr); todo_wine { ok (dwFlags == dwMyComputerFlags, - "Wrong MyComputer attributes: %08lx, expected: %08lx\n", dwFlags, dwMyComputerFlags); } + "Wrong MyComputer attributes: %08x, expected: %08x\n", dwFlags, dwMyComputerFlags); } IShellFolder_Release(psfMyComputer); + + /* create test directory */ + CreateFilesFolders(); + + GetCurrentDirectoryA(MAX_PATH, cCurrDirA); + len = lstrlenA(cCurrDirA); + + if (len == 0) { + trace("GetCurrentDirectoryA returned empty string. Skipping test_EnumObjects_and_CompareIDs\n"); + return; + } + if(cCurrDirA[len-1] == '\\') + cCurrDirA[len-1] = 0; + + MultiByteToWideChar(CP_ACP, 0, cCurrDirA, -1, cCurrDirW, MAX_PATH); + + hr = SHGetDesktopFolder(&IDesktopFolder); + ok(hr == S_OK, "SHGetDesktopfolder failed %08x\n", hr); + + hr = IShellFolder_ParseDisplayName(IDesktopFolder, NULL, NULL, cCurrDirW, NULL, &newPIDL, 0); + ok(hr == S_OK, "ParseDisplayName failed %08x\n", hr); + + hr = IShellFolder_BindToObject(IDesktopFolder, newPIDL, NULL, (REFIID)&IID_IShellFolder, (LPVOID *)&testIShellFolder); + ok(hr == S_OK, "BindToObject failed %08x\n", hr); + + IMalloc_Free(ppM, newPIDL); + + /* get relative PIDL */ + hr = IShellFolder_ParseDisplayName(testIShellFolder, NULL, NULL, cTestDirW, NULL, &newPIDL, 0); + ok(hr == S_OK, "ParseDisplayName failed %08x\n", hr); + + /* test the shell attributes of the test directory using the relative PIDL */ + dwFlags = SFGAO_FOLDER; + hr = IShellFolder_GetAttributesOf(testIShellFolder, 1, (LPCITEMIDLIST*)&newPIDL, &dwFlags); + ok (SUCCEEDED(hr), "Desktop->GetAttributesOf() failed! hr = %08x\n", hr); + ok ((dwFlags&SFGAO_FOLDER), "Wrong directory attribute for relative PIDL: %08x\n", dwFlags); + + /* free memory */ + IMalloc_Free(ppM, newPIDL); + + /* append testdirectory name to path */ + lstrcatW(cCurrDirW, cBackSlash); + lstrcatW(cCurrDirW, cTestDirW); + + hr = IShellFolder_ParseDisplayName(IDesktopFolder, NULL, NULL, cCurrDirW, NULL, &newPIDL, 0); + ok(hr == S_OK, "ParseDisplayName failed %08x\n", hr); + + /* test the shell attributes of the test directory using the absolute PIDL */ + dwFlags = SFGAO_FOLDER; + hr = IShellFolder_GetAttributesOf(IDesktopFolder, 1, (LPCITEMIDLIST*)&newPIDL, &dwFlags); + ok (SUCCEEDED(hr), "Desktop->GetAttributesOf() failed! hr = %08x\n", hr); + ok ((dwFlags&SFGAO_FOLDER), "Wrong directory attribute for absolute PIDL: %08x\n", dwFlags); + + /* free memory */ + IMalloc_Free(ppM, newPIDL); + + IShellFolder_Release(testIShellFolder); + + Cleanup(); + + IShellFolder_Release(IDesktopFolder); } static void test_SHGetPathFromIDList(void) @@ -583,35 +763,122 @@ static void test_SHGetPathFromIDList(void) WCHAR wszMyComputer[] = { ':',':','{','2','0','D','0','4','F','E','0','-','3','A','E','A','-','1','0','6','9','-', 'A','2','D','8','-','0','8','0','0','2','B','3','0','3','0','9','D','}',0 }; + WCHAR wszFileName[MAX_PATH]; + LPITEMIDLIST pidlTestFile; + HANDLE hTestFile; + STRRET strret; + static WCHAR wszTestFile[] = { + 'w','i','n','e','t','e','s','t','.','f','o','o',0 }; + HRESULT (WINAPI *pSHGetSpecialFolderLocation)(HWND, int, LPITEMIDLIST *); + HMODULE hShell32; + LPITEMIDLIST pidlPrograms; if(!pSHGetSpecialFolderPathW) return; + /* Calling SHGetPathFromIDList with no pidl should return the empty string */ + wszPath[0] = 'a'; + wszPath[1] = '\0'; + result = SHGetPathFromIDListW(NULL, wszPath); + ok(!result, "Expected failure\n"); + ok(!wszPath[0], "Expected empty string\n"); + /* Calling SHGetPathFromIDList with an empty pidl should return the desktop folder's path. */ result = pSHGetSpecialFolderPathW(NULL, wszDesktop, CSIDL_DESKTOP, FALSE); - ok(result, "SHGetSpecialFolderPathW(CSIDL_DESKTOP) failed! Last error: %08lx\n", GetLastError()); + ok(result, "SHGetSpecialFolderPathW(CSIDL_DESKTOP) failed! Last error: %u\n", GetLastError()); if (!result) return; result = SHGetPathFromIDListW(pidlEmpty, wszPath); - ok(result, "SHGetPathFromIDListW failed! Last error: %08lx\n", GetLastError()); + ok(result, "SHGetPathFromIDListW failed! Last error: %u\n", GetLastError()); if (!result) return; ok(!lstrcmpiW(wszDesktop, wszPath), "SHGetPathFromIDList didn't return desktop path for empty pidl!\n"); /* MyComputer does not map to a filesystem path. SHGetPathFromIDList should fail. */ hr = SHGetDesktopFolder(&psfDesktop); - ok (SUCCEEDED(hr), "SHGetDesktopFolder failed! hr = %08lx\n", hr); + ok (SUCCEEDED(hr), "SHGetDesktopFolder failed! hr = %08x\n", hr); if (FAILED(hr)) return; hr = IShellFolder_ParseDisplayName(psfDesktop, NULL, NULL, wszMyComputer, NULL, &pidlMyComputer, NULL); - ok (SUCCEEDED(hr), "Desktop's ParseDisplayName failed to parse MyComputer's CLSID! hr = %08lx\n", hr); - IShellFolder_Release(psfDesktop); - if (FAILED(hr)) return; + ok (SUCCEEDED(hr), "Desktop's ParseDisplayName failed to parse MyComputer's CLSID! hr = %08x\n", hr); + if (FAILED(hr)) { + IShellFolder_Release(psfDesktop); + return; + } SetLastError(0xdeadbeef); + wszPath[0] = 'a'; + wszPath[1] = '\0'; result = SHGetPathFromIDListW(pidlMyComputer, wszPath); ok (!result, "SHGetPathFromIDList succeeded where it shouldn't!\n"); - ok (GetLastError()==0xdeadbeef, "SHGetPathFromIDList shouldn't set last error! Last error: %08lx\n", GetLastError()); + ok (GetLastError()==0xdeadbeef, "SHGetPathFromIDList shouldn't set last error! Last error: %u\n", GetLastError()); + ok (!wszPath[0], "Expected empty path\n"); + if (result) { + IShellFolder_Release(psfDesktop); + return; + } IMalloc_Free(ppM, pidlMyComputer); + + result = pSHGetSpecialFolderPathW(NULL, wszFileName, CSIDL_DESKTOPDIRECTORY, FALSE); + ok(result, "SHGetSpecialFolderPathW failed! Last error: %u\n", GetLastError()); + if (!result) { + IShellFolder_Release(psfDesktop); + return; + } + PathAddBackslashW(wszFileName); + lstrcatW(wszFileName, wszTestFile); + hTestFile = CreateFileW(wszFileName, GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, NULL); + ok(hTestFile != INVALID_HANDLE_VALUE, "CreateFileW failed! Last error: %u\n", GetLastError()); + if (hTestFile == INVALID_HANDLE_VALUE) { + IShellFolder_Release(psfDesktop); + return; + } + CloseHandle(hTestFile); + + hr = IShellFolder_ParseDisplayName(psfDesktop, NULL, NULL, wszTestFile, NULL, &pidlTestFile, NULL); + ok (SUCCEEDED(hr), "Desktop's ParseDisplayName failed to parse filename hr = %08x\n", hr); + if (FAILED(hr)) { + IShellFolder_Release(psfDesktop); + DeleteFileW(wszFileName); + IMalloc_Free(ppM, pidlTestFile); + return; + } + + /* This test is to show that the Desktop shellfolder prepends the CSIDL_DESKTOPDIRECTORY + * path for files placed on the desktop, if called with SHGDN_FORPARSING. */ + hr = IShellFolder_GetDisplayNameOf(psfDesktop, pidlTestFile, SHGDN_FORPARSING, &strret); + ok (SUCCEEDED(hr), "Desktop's GetDisplayNamfOf failed! hr = %08x\n", hr); + IShellFolder_Release(psfDesktop); + DeleteFileW(wszFileName); + if (FAILED(hr)) { + IMalloc_Free(ppM, pidlTestFile); + return; + } + if (pStrRetToBufW) + { + pStrRetToBufW(&strret, pidlTestFile, wszPath, MAX_PATH); + ok(0 == lstrcmpW(wszFileName, wszPath), + "Desktop->GetDisplayNameOf(pidlTestFile, SHGDN_FORPARSING) " + "returned incorrect path for file placed on desktop\n"); + } + + result = SHGetPathFromIDListW(pidlTestFile, wszPath); + ok(result, "SHGetPathFromIDListW failed! Last error: %u\n", GetLastError()); + IMalloc_Free(ppM, pidlTestFile); + if (!result) return; + ok(0 == lstrcmpW(wszFileName, wszPath), "SHGetPathFromIDListW returned incorrect path for file placed on desktop\n"); + + + /* Test if we can get the path from the start menu "program files" PIDL. */ + hShell32 = GetModuleHandleA("shell32"); + pSHGetSpecialFolderLocation = (void *)GetProcAddress(hShell32, "SHGetSpecialFolderLocation"); + + hr = pSHGetSpecialFolderLocation(NULL, CSIDL_PROGRAM_FILES, &pidlPrograms); + ok(SUCCEEDED(hr), "SHGetFolderLocation failed: 0x%08x\n", hr); + + SetLastError(0xdeadbeef); + result = SHGetPathFromIDListW(pidlPrograms, wszPath); + IMalloc_Free(ppM, pidlPrograms); + ok(result, "SHGetPathFromIDList failed\n"); } static void test_EnumObjects_and_CompareIDs(void) @@ -635,27 +902,438 @@ static void test_EnumObjects_and_CompareIDs(void) cCurrDirA[len-1] = 0; MultiByteToWideChar(CP_ACP, 0, cCurrDirA, -1, cCurrDirW, MAX_PATH); - strcatW(cCurrDirW, cTestDirW); + lstrcatW(cCurrDirW, cTestDirW); hr = SHGetDesktopFolder(&IDesktopFolder); - ok(hr == S_OK, "SHGetDesktopfolder failed %08lx\n", hr); + ok(hr == S_OK, "SHGetDesktopfolder failed %08x\n", hr); CreateFilesFolders(); hr = IShellFolder_ParseDisplayName(IDesktopFolder, NULL, NULL, cCurrDirW, NULL, &newPIDL, 0); - ok(hr == S_OK, "ParseDisplayName failed %08lx\n", hr); + ok(hr == S_OK, "ParseDisplayName failed %08x\n", hr); hr = IShellFolder_BindToObject(IDesktopFolder, newPIDL, NULL, (REFIID)&IID_IShellFolder, (LPVOID *)&testIShellFolder); - ok(hr == S_OK, "BindToObject failed %08lx\n", hr); + ok(hr == S_OK, "BindToObject failed %08x\n", hr); test_EnumObjects(testIShellFolder); - hr = IShellFolder_Release(testIShellFolder); - ok(hr == S_OK, "IShellFolder_Release failed %08lx\n", hr); + IShellFolder_Release(testIShellFolder); Cleanup(); IMalloc_Free(ppM, newPIDL); + + IShellFolder_Release(IDesktopFolder); +} + +/* A simple implementation of an IPropertyBag, which returns fixed values for + * 'Target' and 'Attributes' properties. + */ +static HRESULT WINAPI InitPropertyBag_IPropertyBag_QueryInterface(IPropertyBag *iface, REFIID riid, + void **ppvObject) +{ + if (!ppvObject) + return E_INVALIDARG; + + if (IsEqualIID(&IID_IUnknown, riid) || IsEqualIID(&IID_IPropertyBag, riid)) { + *ppvObject = iface; + } else { + ok (FALSE, "InitPropertyBag asked for unknown interface!\n"); + return E_NOINTERFACE; + } + + IPropertyBag_AddRef(iface); + return S_OK; +} + +static ULONG WINAPI InitPropertyBag_IPropertyBag_AddRef(IPropertyBag *iface) { + return 2; +} + +static ULONG WINAPI InitPropertyBag_IPropertyBag_Release(IPropertyBag *iface) { + return 1; +} + +static HRESULT WINAPI InitPropertyBag_IPropertyBag_Read(IPropertyBag *iface, LPCOLESTR pszPropName, + VARIANT *pVar, IErrorLog *pErrorLog) +{ + static const WCHAR wszTargetSpecialFolder[] = { + 'T','a','r','g','e','t','S','p','e','c','i','a','l','F','o','l','d','e','r',0 }; + static const WCHAR wszTarget[] = { + 'T','a','r','g','e','t',0 }; + static const WCHAR wszAttributes[] = { + 'A','t','t','r','i','b','u','t','e','s',0 }; + static const WCHAR wszResolveLinkFlags[] = { + 'R','e','s','o','l','v','e','L','i','n','k','F','l','a','g','s',0 }; + + if (!lstrcmpW(pszPropName, wszTargetSpecialFolder)) { + ok(V_VT(pVar) == VT_I4, "Wrong variant type for 'TargetSpecialFolder' property!\n"); + return E_INVALIDARG; + } + + if (!lstrcmpW(pszPropName, wszResolveLinkFlags)) + { + ok(V_VT(pVar) == VT_UI4, "Wrong variant type for 'ResolveLinkFlags' property!\n"); + return E_INVALIDARG; + } + + if (!lstrcmpW(pszPropName, wszTarget)) { + WCHAR wszPath[MAX_PATH]; + BOOL result; + + ok(V_VT(pVar) == VT_BSTR, "Wrong variant type for 'Target' property!\n"); + if (V_VT(pVar) != VT_BSTR) return E_INVALIDARG; + + result = pSHGetSpecialFolderPathW(NULL, wszPath, CSIDL_DESKTOPDIRECTORY, FALSE); + ok(result, "SHGetSpecialFolderPathW(DESKTOPDIRECTORY) failed! %u\n", GetLastError()); + if (!result) return E_INVALIDARG; + + V_BSTR(pVar) = SysAllocString(wszPath); + return S_OK; + } + + if (!lstrcmpW(pszPropName, wszAttributes)) { + ok(V_VT(pVar) == VT_UI4, "Wrong variant type for 'Attributes' property!\n"); + if (V_VT(pVar) != VT_UI4) return E_INVALIDARG; + V_UI4(pVar) = SFGAO_FOLDER|SFGAO_HASSUBFOLDER|SFGAO_FILESYSANCESTOR| + SFGAO_CANRENAME|SFGAO_FILESYSTEM; + return S_OK; + } + + ok(FALSE, "PropertyBag was asked for unknown property (vt=%d)!\n", V_VT(pVar)); + return E_INVALIDARG; +} + +static HRESULT WINAPI InitPropertyBag_IPropertyBag_Write(IPropertyBag *iface, LPCOLESTR pszPropName, + VARIANT *pVar) +{ + ok(FALSE, "Unexpected call to IPropertyBag_Write\n"); + return E_NOTIMPL; +} + +static const IPropertyBagVtbl InitPropertyBag_IPropertyBagVtbl = { + InitPropertyBag_IPropertyBag_QueryInterface, + InitPropertyBag_IPropertyBag_AddRef, + InitPropertyBag_IPropertyBag_Release, + InitPropertyBag_IPropertyBag_Read, + InitPropertyBag_IPropertyBag_Write +}; + +static struct IPropertyBag InitPropertyBag = { + &InitPropertyBag_IPropertyBagVtbl +}; + +static void test_FolderShortcut(void) { + IPersistPropertyBag *pPersistPropertyBag; + IShellFolder *pShellFolder, *pDesktopFolder; + IPersistFolder3 *pPersistFolder3; + HRESULT hr; + STRRET strret; + WCHAR wszDesktopPath[MAX_PATH], wszBuffer[MAX_PATH]; + BOOL result; + CLSID clsid; + LPITEMIDLIST pidlCurrentFolder, pidlWineTestFolder, pidlSubFolder; + HKEY hShellExtKey; + WCHAR wszWineTestFolder[] = { + ':',':','{','9','B','3','5','2','E','B','F','-','2','7','6','5','-','4','5','C','1','-', + 'B','4','C','6','-','8','5','C','C','7','F','7','A','B','C','6','4','}',0 }; + WCHAR wszShellExtKey[] = { 'S','o','f','t','w','a','r','e','\\', + 'M','i','c','r','o','s','o','f','t','\\','W','i','n','d','o','w','s','\\', + 'C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\', + 'E','x','p','l','o','r','e','r','\\','D','e','s','k','t','o','p','\\', + 'N','a','m','e','S','p','a','c','e','\\', + '{','9','b','3','5','2','e','b','f','-','2','7','6','5','-','4','5','c','1','-', + 'b','4','c','6','-','8','5','c','c','7','f','7','a','b','c','6','4','}',0 }; + + WCHAR wszSomeSubFolder[] = { 'S','u','b','F','o','l','d','e','r', 0}; + static const GUID CLSID_UnixDosFolder = + {0x9d20aae8, 0x0625, 0x44b0, {0x9c, 0xa7, 0x71, 0x88, 0x9c, 0x22, 0x54, 0xd9}}; + + if (!pSHGetSpecialFolderPathW || !pStrRetToBufW) return; + + /* These tests basically show, that CLSID_FolderShortcuts are initialized + * via their IPersistPropertyBag interface. And that the target folder + * is taken from the IPropertyBag's 'Target' property. + */ + hr = CoCreateInstance(&CLSID_FolderShortcut, NULL, CLSCTX_INPROC_SERVER, + &IID_IPersistPropertyBag, (LPVOID*)&pPersistPropertyBag); + ok (SUCCEEDED(hr), "CoCreateInstance failed! hr = 0x%08x\n", hr); + if (FAILED(hr)) return; + + hr = IPersistPropertyBag_Load(pPersistPropertyBag, &InitPropertyBag, NULL); + ok(SUCCEEDED(hr), "IPersistPropertyBag_Load failed! hr = %08x\n", hr); + if (FAILED(hr)) { + IPersistPropertyBag_Release(pPersistPropertyBag); + return; + } + + hr = IPersistPropertyBag_QueryInterface(pPersistPropertyBag, &IID_IShellFolder, + (LPVOID*)&pShellFolder); + IPersistPropertyBag_Release(pPersistPropertyBag); + ok(SUCCEEDED(hr), "IPersistPropertyBag_QueryInterface(IShellFolder) failed! hr = %08x\n", hr); + if (FAILED(hr)) return; + + hr = IShellFolder_GetDisplayNameOf(pShellFolder, NULL, SHGDN_FORPARSING, &strret); + ok(SUCCEEDED(hr), "IShellFolder_GetDisplayNameOf(NULL) failed! hr = %08x\n", hr); + if (FAILED(hr)) { + IShellFolder_Release(pShellFolder); + return; + } + + result = pSHGetSpecialFolderPathW(NULL, wszDesktopPath, CSIDL_DESKTOPDIRECTORY, FALSE); + ok(result, "SHGetSpecialFolderPathW(CSIDL_DESKTOPDIRECTORY) failed! %u\n", GetLastError()); + if (!result) return; + + pStrRetToBufW(&strret, NULL, wszBuffer, MAX_PATH); + ok(!lstrcmpiW(wszDesktopPath, wszBuffer), "FolderShortcut returned incorrect folder!\n"); + + hr = IShellFolder_QueryInterface(pShellFolder, &IID_IPersistFolder3, (LPVOID*)&pPersistFolder3); + IShellFolder_Release(pShellFolder); + ok(SUCCEEDED(hr), "IShellFolder_QueryInterface(IID_IPersistFolder3 failed! hr = 0x%08x\n", hr); + if (FAILED(hr)) return; + + hr = IPersistFolder3_GetClassID(pPersistFolder3, &clsid); + ok(SUCCEEDED(hr), "IPersistFolder3_GetClassID failed! hr=0x%08x\n", hr); + ok(IsEqualCLSID(&clsid, &CLSID_FolderShortcut), "Unexpected CLSID!\n"); + + hr = IPersistFolder3_GetCurFolder(pPersistFolder3, &pidlCurrentFolder); + ok(SUCCEEDED(hr), "IPersistFolder3_GetCurFolder failed! hr=0x%08x\n", hr); + ok(!pidlCurrentFolder, "IPersistFolder3_GetCurFolder should return a NULL pidl!\n"); + + /* For FolderShortcut objects, the Initialize method initialized the folder's position in the + * shell namespace. The target folder, read from the property bag above, remains untouched. + * The following tests show this: The itemidlist for some imaginary shellfolder object + * is created and the FolderShortcut is initialized with it. GetCurFolder now returns this + * itemidlist, but GetDisplayNameOf still returns the path from above. + */ + hr = SHGetDesktopFolder(&pDesktopFolder); + ok (SUCCEEDED(hr), "SHGetDesktopFolder failed! hr = %08x\n", hr); + if (FAILED(hr)) return; + + /* Temporarily register WineTestFolder as a shell namespace extension at the Desktop. + * Otherwise ParseDisplayName fails on WinXP with E_INVALIDARG */ + RegCreateKeyW(HKEY_CURRENT_USER, wszShellExtKey, &hShellExtKey); + RegCloseKey(hShellExtKey); + hr = IShellFolder_ParseDisplayName(pDesktopFolder, NULL, NULL, wszWineTestFolder, NULL, + &pidlWineTestFolder, NULL); + RegDeleteKeyW(HKEY_CURRENT_USER, wszShellExtKey); + IShellFolder_Release(pDesktopFolder); + ok (SUCCEEDED(hr), "IShellFolder::ParseDisplayName failed! hr = %08x\n", hr); + if (FAILED(hr)) return; + + hr = IPersistFolder3_Initialize(pPersistFolder3, pidlWineTestFolder); + ok (SUCCEEDED(hr), "IPersistFolder3::Initialize failed! hr = %08x\n", hr); + if (FAILED(hr)) { + IPersistFolder3_Release(pPersistFolder3); + pILFree(pidlWineTestFolder); + return; + } + + hr = IPersistFolder3_GetCurFolder(pPersistFolder3, &pidlCurrentFolder); + ok(SUCCEEDED(hr), "IPersistFolder3_GetCurFolder failed! hr=0x%08x\n", hr); + ok(pILIsEqual(pidlCurrentFolder, pidlWineTestFolder), + "IPersistFolder3_GetCurFolder should return pidlWineTestFolder!\n"); + pILFree(pidlCurrentFolder); + pILFree(pidlWineTestFolder); + + hr = IPersistFolder3_QueryInterface(pPersistFolder3, &IID_IShellFolder, (LPVOID*)&pShellFolder); + IPersistFolder3_Release(pPersistFolder3); + ok(SUCCEEDED(hr), "IPersistFolder3_QueryInterface(IShellFolder) failed! hr = %08x\n", hr); + if (FAILED(hr)) return; + + hr = IShellFolder_GetDisplayNameOf(pShellFolder, NULL, SHGDN_FORPARSING, &strret); + ok(SUCCEEDED(hr), "IShellFolder_GetDisplayNameOf(NULL) failed! hr = %08x\n", hr); + if (FAILED(hr)) { + IShellFolder_Release(pShellFolder); + return; + } + + pStrRetToBufW(&strret, NULL, wszBuffer, MAX_PATH); + ok(!lstrcmpiW(wszDesktopPath, wszBuffer), "FolderShortcut returned incorrect folder!\n"); + + /* Next few lines are meant to show that children of FolderShortcuts are not FolderShortcuts, + * but ShellFSFolders. */ + PathAddBackslashW(wszDesktopPath); + lstrcatW(wszDesktopPath, wszSomeSubFolder); + if (!CreateDirectoryW(wszDesktopPath, NULL)) { + IShellFolder_Release(pShellFolder); + return; + } + + hr = IShellFolder_ParseDisplayName(pShellFolder, NULL, NULL, wszSomeSubFolder, NULL, + &pidlSubFolder, NULL); + RemoveDirectoryW(wszDesktopPath); + ok (SUCCEEDED(hr), "IShellFolder::ParseDisplayName failed! hr = %08x\n", hr); + if (FAILED(hr)) { + IShellFolder_Release(pShellFolder); + return; + } + + hr = IShellFolder_BindToObject(pShellFolder, pidlSubFolder, NULL, &IID_IPersistFolder3, + (LPVOID*)&pPersistFolder3); + IShellFolder_Release(pShellFolder); + pILFree(pidlSubFolder); + ok (SUCCEEDED(hr), "IShellFolder::BindToObject failed! hr = %08x\n", hr); + if (FAILED(hr)) + return; + + /* On windows, we expect CLSID_ShellFSFolder. On wine we relax this constraint + * a little bit and also allow CLSID_UnixDosFolder. */ + hr = IPersistFolder3_GetClassID(pPersistFolder3, &clsid); + ok(SUCCEEDED(hr), "IPersistFolder3_GetClassID failed! hr=0x%08x\n", hr); + ok(IsEqualCLSID(&clsid, &CLSID_ShellFSFolder) || IsEqualCLSID(&clsid, &CLSID_UnixDosFolder), + "IPersistFolder3::GetClassID returned unexpected CLSID!\n"); + + IPersistFolder3_Release(pPersistFolder3); +} + +#include "pshpack1.h" +struct FileStructA { + BYTE type; + BYTE dummy; + DWORD dwFileSize; + WORD uFileDate; /* In our current implementation this is */ + WORD uFileTime; /* FileTimeToDosDate(WIN32_FIND_DATA->ftLastWriteTime) */ + WORD uFileAttribs; + CHAR szName[1]; +}; + +struct FileStructW { + WORD cbLen; /* Length of this element. */ + BYTE abFooBar1[6]; /* Beyond any recognition. */ + WORD uDate; /* FileTimeToDosDate(WIN32_FIND_DATA->ftCreationTime)? */ + WORD uTime; /* (this is currently speculation) */ + WORD uDate2; /* FileTimeToDosDate(WIN32_FIND_DATA->ftLastAccessTime)? */ + WORD uTime2; /* (this is currently speculation) */ + BYTE abFooBar2[4]; /* Beyond any recognition. */ + WCHAR wszName[1]; /* The long filename in unicode. */ + /* Just for documentation: Right after the unicode string: */ + WORD cbOffset; /* FileStructW's offset from the beginning of the SHITMEID. + * SHITEMID->cb == uOffset + cbLen */ +}; +#include "poppack.h" + +static void test_ITEMIDLIST_format(void) { + WCHAR wszPersonal[MAX_PATH]; + LPSHELLFOLDER psfDesktop, psfPersonal; + LPITEMIDLIST pidlPersonal, pidlFile; + HANDLE hFile; + HRESULT hr; + BOOL bResult; + WCHAR wszFile[3][17] = { { 'e','v','e','n','_',0 }, { 'o','d','d','_',0 }, + { 'l','o','n','g','e','r','_','t','h','a','n','.','8','_','3',0 } }; + int i; + + if(!pSHGetSpecialFolderPathW) return; + + bResult = pSHGetSpecialFolderPathW(NULL, wszPersonal, CSIDL_PERSONAL, FALSE); + ok(bResult, "SHGetSpecialFolderPathW failed! Last error: %u\n", GetLastError()); + if (!bResult) return; + + bResult = SetCurrentDirectoryW(wszPersonal); + ok(bResult, "SetCurrentDirectory failed! Last error: %u\n", GetLastError()); + if (!bResult) return; + + hr = SHGetDesktopFolder(&psfDesktop); + ok(SUCCEEDED(hr), "SHGetDesktopFolder failed! hr: %08x\n", hr); + if (FAILED(hr)) return; + + hr = IShellFolder_ParseDisplayName(psfDesktop, NULL, NULL, wszPersonal, NULL, &pidlPersonal, NULL); + ok(SUCCEEDED(hr), "psfDesktop->ParseDisplayName failed! hr = %08x\n", hr); + if (FAILED(hr)) { + IShellFolder_Release(psfDesktop); + return; + } + + hr = IShellFolder_BindToObject(psfDesktop, pidlPersonal, NULL, &IID_IShellFolder, + (LPVOID*)&psfPersonal); + IShellFolder_Release(psfDesktop); + pILFree(pidlPersonal); + ok(SUCCEEDED(hr), "psfDesktop->BindToObject failed! hr = %08x\n", hr); + if (FAILED(hr)) return; + + for (i=0; i<3; i++) { + CHAR szFile[MAX_PATH]; + struct FileStructA *pFileStructA; + WORD cbOffset; + + WideCharToMultiByte(CP_ACP, 0, wszFile[i], -1, szFile, MAX_PATH, NULL, NULL); + + hFile = CreateFileW(wszFile[i], GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_FLAG_WRITE_THROUGH, NULL); + ok(hFile != INVALID_HANDLE_VALUE, "CreateFile failed! (%u)\n", GetLastError()); + if (hFile == INVALID_HANDLE_VALUE) { + IShellFolder_Release(psfPersonal); + return; + } + CloseHandle(hFile); + + hr = IShellFolder_ParseDisplayName(psfPersonal, NULL, NULL, wszFile[i], NULL, &pidlFile, NULL); + DeleteFileW(wszFile[i]); + ok(SUCCEEDED(hr), "psfPersonal->ParseDisplayName failed! hr: %08x\n", hr); + if (FAILED(hr)) { + IShellFolder_Release(psfPersonal); + return; + } + + pFileStructA = (struct FileStructA *)pidlFile->mkid.abID; + ok(pFileStructA->type == 0x32, "PIDLTYPE should be 0x32!\n"); + ok(pFileStructA->dummy == 0x00, "Dummy Byte should be 0x00!\n"); + ok(pFileStructA->dwFileSize == 0, "Filesize should be zero!\n"); + + if (i < 2) /* First two file names are already in valid 8.3 format */ + ok(!strcmp(szFile, (CHAR*)&pidlFile->mkid.abID[12]), "Wrong file name!\n"); + else + /* WinXP stores a derived 8.3 dos name (LONGER~1.8_3) here. We probably + * can't implement this correctly, since unix filesystems don't support + * this nasty short/long filename stuff. So we'll probably stay with our + * current habbit of storing the long filename here, which seems to work + * just fine. */ + todo_wine { ok(pidlFile->mkid.abID[18] == '~', "Should be derived 8.3 name!\n"); } + + if (i == 0) /* First file name has an even number of chars. No need for alignment. */ + ok(pidlFile->mkid.abID[12 + strlen(szFile) + 1] != '\0', + "Alignment byte, where there shouldn't be!\n"); + + if (i == 1) /* Second file name has an uneven number of chars => alignment byte */ + ok(pidlFile->mkid.abID[12 + strlen(szFile) + 1] == '\0', + "There should be an alignment byte, but isn't!\n"); + + /* The offset of the FileStructW member is stored as a WORD at the end of the pidl. */ + cbOffset = *(WORD*)(((LPBYTE)pidlFile)+pidlFile->mkid.cb-sizeof(WORD)); + ok (cbOffset >= sizeof(struct FileStructA) && + cbOffset <= pidlFile->mkid.cb - sizeof(struct FileStructW), + "Wrong offset value (%d) stored at the end of the PIDL\n", cbOffset); + + if (cbOffset >= sizeof(struct FileStructA) && + cbOffset <= pidlFile->mkid.cb - sizeof(struct FileStructW)) + { + struct FileStructW *pFileStructW = (struct FileStructW *)(((LPBYTE)pidlFile)+cbOffset); + + ok(pidlFile->mkid.cb == cbOffset + pFileStructW->cbLen, + "FileStructW's offset and length should add up to the PIDL's length!\n"); + + if (pidlFile->mkid.cb == cbOffset + pFileStructW->cbLen) { + /* Since we just created the file, time of creation, + * time of last access and time of last write access just be the same. + * These tests seem to fail sometimes (on WinXP), if the test is run again shortly + * after the first run. I do remember something with NTFS keeping the creation time + * if a file is deleted and then created again within a couple of seconds or so. + * Might be the reason. */ + ok (pFileStructA->uFileDate == pFileStructW->uDate && + pFileStructA->uFileTime == pFileStructW->uTime, + "Last write time should match creation time!\n"); + + ok (pFileStructA->uFileDate == pFileStructW->uDate2 && + pFileStructA->uFileTime == pFileStructW->uTime2, + "Last write time should match last access time!\n"); + + ok (!lstrcmpW(wszFile[i], pFileStructW->wszName), + "The filename should be stored in unicode at this position!\n"); + } + } + + pILFree(pidlFile); + } } START_TEST(shlfolder) @@ -672,6 +1350,8 @@ START_TEST(shlfolder) test_GetAttributesOf(); test_SHGetPathFromIDList(); test_CallForAttributes(); + test_FolderShortcut(); + test_ITEMIDLIST_format(); OleUninitialize(); } diff --git a/rostests/winetests/shell32/string.c b/rostests/winetests/shell32/string.c index 0a3d5925c07..c748f350e95 100755 --- a/rostests/winetests/shell32/string.c +++ b/rostests/winetests/shell32/string.c @@ -15,14 +15,12 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include -#define NONAMELESSUNION -#define NONAMELESSSTRUCT #define WINE_NOWINSOCK #include "windef.h" #include "winbase.h" @@ -67,21 +65,21 @@ static void test_StrRetToStringNW(void) trace("StrRetToStringNAW is Unicode\n"); strret.uType = STRRET_WSTR; - strret.u.pOleStr = CoDupStrW("Test"); + U(strret).pOleStr = CoDupStrW("Test"); memset(buff, 0xff, sizeof(buff)); ret = pStrRetToStrNAW(buff, sizeof(buff)/sizeof(WCHAR), &strret, NULL); ok(ret == TRUE && !strcmpW(buff, szTestW), "STRRET_WSTR: dup failed, ret=%d\n", ret); strret.uType = STRRET_CSTR; - lstrcpyA(strret.u.cStr, "Test"); + lstrcpyA(U(strret).cStr, "Test"); memset(buff, 0xff, sizeof(buff)); ret = pStrRetToStrNAW(buff, sizeof(buff)/sizeof(WCHAR), &strret, NULL); ok(ret == TRUE && !strcmpW(buff, szTestW), "STRRET_CSTR: dup failed, ret=%d\n", ret); strret.uType = STRRET_OFFSET; - strret.u.uOffset = 1; + U(strret).uOffset = 1; strcpy((char*)&iidl, " Test"); memset(buff, 0xff, sizeof(buff)); ret = pStrRetToStrNAW(buff, sizeof(buff)/sizeof(WCHAR), &strret, iidl); @@ -92,7 +90,7 @@ static void test_StrRetToStringNW(void) #if 0 /* Invalid dest - should return FALSE, except NT4 does not, so we don't check. */ strret.uType = STRRET_WSTR; - strret.u.pOleStr = CoDupStrW("Test"); + U(strret).pOleStr = CoDupStrW("Test"); pStrRetToStrNAW(NULL, sizeof(buff)/sizeof(WCHAR), &strret, NULL); trace("NULL dest: ret=%d\n", ret); #endif @@ -102,9 +100,7 @@ START_TEST(string) { CoInitialize(0); - hShell32 = LoadLibraryA("shell32.dll"); - if (!hShell32) - return; + hShell32 = GetModuleHandleA("shell32.dll"); pStrRetToStrNAW = (void*)GetProcAddress(hShell32, (LPSTR)96); if (pStrRetToStrNAW) diff --git a/rostests/winetests/shell32/systray.c b/rostests/winetests/shell32/systray.c new file mode 100644 index 00000000000..e6d72a998c9 --- /dev/null +++ b/rostests/winetests/shell32/systray.c @@ -0,0 +1,106 @@ +/* Unit tests for systray + * + * Copyright 2007 Mikolaj Zalewski + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ +#define _WIN32_IE 0x600 +#include +#include + +#include + +#include "wine/test.h" + + +static HWND hMainWnd; + +void test_cbsize() +{ + NOTIFYICONDATAW nidW; + NOTIFYICONDATAA nidA; + + ZeroMemory(&nidW, sizeof(nidW)); + nidW.cbSize = NOTIFYICONDATAW_V1_SIZE; + nidW.hWnd = hMainWnd; + nidW.uID = 1; + nidW.uFlags = NIF_ICON|NIF_MESSAGE; + nidW.hIcon = LoadIcon(NULL, IDI_APPLICATION); + nidW.uCallbackMessage = WM_USER+17; + ok(Shell_NotifyIconW(NIM_ADD, &nidW), "NIM_ADD failed!\n"); + + /* using an invalid cbSize does work */ + nidW.cbSize = 3; + nidW.hWnd = hMainWnd; + nidW.uID = 1; + ok(Shell_NotifyIconW(NIM_DELETE, &nidW), "NIM_DELETE failed!\n"); + /* as icon doesn't exist anymore - now there will be an error */ + nidW.cbSize = sizeof(nidW); + /* wine currently doesn't return error code put prints an ERR(...) */ + todo_wine ok(!Shell_NotifyIconW(NIM_DELETE, &nidW), "The icon was not deleted\n"); + + /* same for Shell_NotifyIconA */ + ZeroMemory(&nidA, sizeof(nidA)); + nidA.cbSize = NOTIFYICONDATAA_V1_SIZE; + nidA.hWnd = hMainWnd; + nidA.uID = 1; + nidA.uFlags = NIF_ICON|NIF_MESSAGE; + nidA.hIcon = LoadIcon(NULL, IDI_APPLICATION); + nidA.uCallbackMessage = WM_USER+17; + ok(Shell_NotifyIconA(NIM_ADD, &nidA), "NIM_ADD failed!\n"); + + /* using an invalid cbSize does work */ + nidA.cbSize = 3; + nidA.hWnd = hMainWnd; + nidA.uID = 1; + ok(Shell_NotifyIconA(NIM_DELETE, &nidA), "NIM_DELETE failed!\n"); + /* as icon doesn't exist anymore - now there will be an error */ + nidA.cbSize = sizeof(nidA); + /* wine currently doesn't return error code put prints an ERR(...) */ + todo_wine ok(!Shell_NotifyIconA(NIM_DELETE, &nidA), "The icon was not deleted\n"); +} + +START_TEST(systray) +{ + WNDCLASSA wc; + MSG msg; + RECT rc; + + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = GetModuleHandleA(NULL); + wc.hIcon = NULL; + wc.hCursor = LoadCursorA(NULL, MAKEINTRESOURCEA(IDC_IBEAM)); + wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW); + wc.lpszMenuName = NULL; + wc.lpszClassName = "MyTestWnd"; + wc.lpfnWndProc = DefWindowProc; + RegisterClassA(&wc); + + hMainWnd = CreateWindowExA(0, "MyTestWnd", "Blah", WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, 680, 260, NULL, NULL, GetModuleHandleA(NULL), 0); + GetClientRect(hMainWnd, &rc); + ShowWindow(hMainWnd, SW_SHOW); + + test_cbsize(); + + PostQuitMessage(0); + while(GetMessageA(&msg,0,0,0)) { + TranslateMessage(&msg); + DispatchMessageA(&msg); + } + DestroyWindow(hMainWnd); +} diff --git a/rostests/winetests/shell32/testlist.c b/rostests/winetests/shell32/testlist.c index 603044ec6b0..1eb0a0e6a17 100644 --- a/rostests/winetests/shell32/testlist.c +++ b/rostests/winetests/shell32/testlist.c @@ -13,15 +13,17 @@ extern void func_shlexec(void); extern void func_shlfileop(void); extern void func_shlfolder(void); extern void func_string(void); +extern void func_systray(void); const struct test winetest_testlist[] = { -// { "generated", func_generated }, + { "generated", func_generated }, { "shelllink", func_shelllink }, { "shellpath", func_shellpath }, { "shlexec", func_shlexec }, { "shlfileop", func_shlfileop }, { "shlfolder", func_shlfolder }, { "string", func_string }, + { "systray", func_systray }, { 0, 0 } };