shlfileop.c 48 KB
Newer Older
1 2
/*
 * SHFileOperation
3 4
 *
 * Copyright 2000 Juergen Schmied
5
 * Copyright 2002 Andriy Palamarchuk
6 7
 * Copyright 2004 Dietrich Teickner (from Odin)
 * Copyright 2004 Rolf Kalbermatter
8 9 10 11 12 13 14 15 16 17 18 19 20
 *
 * 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
21
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22
 */
23 24 25 26

#include "config.h"
#include "wine/port.h"

27
#include <stdarg.h>
28
#include <string.h>
29
#include <ctype.h>
30
#include <assert.h>
31

32 33
#include "windef.h"
#include "winbase.h"
34 35
#include "winreg.h"
#include "shellapi.h"
36 37
#include "wingdi.h"
#include "winuser.h"
38
#include "shlobj.h"
39
#include "shresdef.h"
40 41
#define NO_SHLWAPI_STREAM
#include "shlwapi.h"
42
#include "shell32_main.h"
43
#include "undocshell.h"
44
#include "wine/debug.h"
45
#include "xdg.h"
46

47
WINE_DEFAULT_DEBUG_CHANNEL(shell);
48

49 50 51
#define IsAttrib(x, y)  ((INVALID_FILE_ATTRIBUTES != (x)) && ((x) & (y)))
#define IsAttribFile(x) (!((x) & FILE_ATTRIBUTE_DIRECTORY))
#define IsAttribDir(x)  IsAttrib(x, FILE_ATTRIBUTE_DIRECTORY)
52 53
#define IsDotDir(x)     ((x[0] == '.') && ((x[1] == 0) || ((x[1] == '.') && (x[2] == 0))))

54 55
#define FO_MASK         0xF

56
static const WCHAR wWildcardFile[] = {'*',0};
57
static const WCHAR wWildcardChars[] = {'*','?',0};
58

59 60 61 62 63 64
static DWORD SHNotifyCreateDirectoryA(LPCSTR path, LPSECURITY_ATTRIBUTES sec);
static DWORD SHNotifyCreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec);
static DWORD SHNotifyRemoveDirectoryA(LPCSTR path);
static DWORD SHNotifyRemoveDirectoryW(LPCWSTR path);
static DWORD SHNotifyDeleteFileA(LPCSTR path);
static DWORD SHNotifyDeleteFileW(LPCWSTR path);
65 66 67
static DWORD SHNotifyMoveFileW(LPCWSTR src, LPCWSTR dest);
static DWORD SHNotifyCopyFileW(LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists);
static DWORD SHFindAttrW(LPCWSTR pName, BOOL fileOnly);
68

69 70 71 72 73 74 75 76
typedef struct
{
    SHFILEOPSTRUCTW *req;
    DWORD dwYesToAllMask;
    BOOL bManyItems;
    BOOL bCancelled;
} FILE_OPERATION;

77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
/* Confirm dialogs with an optional "Yes To All" as used in file operations confirmations
 */
static const WCHAR CONFIRM_MSG_PROP[] = {'W','I','N','E','_','C','O','N','F','I','R','M',0};

struct confirm_msg_info
{
    LPWSTR lpszText;
    LPWSTR lpszCaption;
    HICON hIcon;
    BOOL bYesToAll;
};

/* as some buttons may be hidden and the dialog height may change we may need
 * to move the controls */
static void confirm_msg_move_button(HWND hDlg, INT iId, INT *xPos, INT yOffset, BOOL bShow)
{
    HWND hButton = GetDlgItem(hDlg, iId);
    RECT r;

    if (bShow) {
        POINT pt;
        int width;

        GetWindowRect(hButton, &r);
        width = r.right - r.left;
        pt.x = r.left;
        pt.y = r.top;
        ScreenToClient(hDlg, &pt);
        MoveWindow(hButton, *xPos - width, pt.y - yOffset, width, r.bottom - r.top, FALSE);
        *xPos -= width + 5;
    }
    else
        ShowWindow(hButton, SW_HIDE);
}

/* Note: we paint the text manually and don't use the static control to make
 * sure the text has the same height as the one computed in WM_INITDIALOG
 */
static INT_PTR CALLBACK ConfirmMsgBox_Paint(HWND hDlg)
{
    PAINTSTRUCT ps;
    HFONT hOldFont;
    RECT r;
    HDC hdc;

    BeginPaint(hDlg, &ps);
    hdc = ps.hdc;

    GetClientRect(GetDlgItem(hDlg, IDD_MESSAGE), &r);
    /* this will remap the rect to dialog coords */
    MapWindowPoints(GetDlgItem(hDlg, IDD_MESSAGE), hDlg, (LPPOINT)&r, 2);
    hOldFont = SelectObject(hdc, (HFONT)SendDlgItemMessageW(hDlg, IDD_MESSAGE, WM_GETFONT, 0, 0));
    DrawTextW(hdc, (LPWSTR)GetPropW(hDlg, CONFIRM_MSG_PROP), -1, &r, DT_NOPREFIX | DT_PATH_ELLIPSIS | DT_WORDBREAK);
    SelectObject(hdc, hOldFont);
    EndPaint(hDlg, &ps);
    return TRUE;
}

static INT_PTR CALLBACK ConfirmMsgBox_Init(HWND hDlg, LPARAM lParam)
{
    struct confirm_msg_info *info = (struct confirm_msg_info *)lParam;
    INT xPos, yOffset;
    int width, height;
    HFONT hOldFont;
    HDC hdc;
    RECT r;

    SetWindowTextW(hDlg, info->lpszCaption);
    ShowWindow(GetDlgItem(hDlg, IDD_MESSAGE), SW_HIDE);
    SetPropW(hDlg, CONFIRM_MSG_PROP, (HANDLE)info->lpszText);
    SendDlgItemMessageW(hDlg, IDD_ICON, STM_SETICON, (WPARAM)info->hIcon, 0);

    /* compute the text height and resize the dialog */
    GetClientRect(GetDlgItem(hDlg, IDD_MESSAGE), &r);
    hdc = GetDC(hDlg);
    yOffset = r.bottom;
    hOldFont = SelectObject(hdc, (HFONT)SendDlgItemMessageW(hDlg, IDD_MESSAGE, WM_GETFONT, 0, 0));
    DrawTextW(hdc, info->lpszText, -1, &r, DT_NOPREFIX | DT_PATH_ELLIPSIS | DT_WORDBREAK | DT_CALCRECT);
    SelectObject(hdc, hOldFont);
    yOffset -= r.bottom;
    yOffset = min(yOffset, 35);  /* don't make the dialog too small */
    ReleaseDC(hDlg, hdc);

    GetClientRect(hDlg, &r);
    xPos = r.right - 7;
    GetWindowRect(hDlg, &r);
    width = r.right - r.left;
    height = r.bottom - r.top - yOffset;
    MoveWindow(hDlg, (GetSystemMetrics(SM_CXSCREEN) - width)/2,
        (GetSystemMetrics(SM_CYSCREEN) - height)/2, width, height, FALSE);

    confirm_msg_move_button(hDlg, IDCANCEL,     &xPos, yOffset, info->bYesToAll);
    confirm_msg_move_button(hDlg, IDNO,         &xPos, yOffset, TRUE);
    confirm_msg_move_button(hDlg, IDD_YESTOALL, &xPos, yOffset, info->bYesToAll);
    confirm_msg_move_button(hDlg, IDYES,        &xPos, yOffset, TRUE);
    return TRUE;
}

static INT_PTR CALLBACK ConfirmMsgBoxProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_INITDIALOG:
            return ConfirmMsgBox_Init(hDlg, lParam);
        case WM_PAINT:
            return ConfirmMsgBox_Paint(hDlg);
        case WM_COMMAND:
            EndDialog(hDlg, wParam);
            break;
        case WM_CLOSE:
            EndDialog(hDlg, IDCANCEL);
            break;
    }
    return FALSE;
}

static int SHELL_ConfirmMsgBox(HWND hWnd, LPWSTR lpszText, LPWSTR lpszCaption, HICON hIcon, BOOL bYesToAll)
{
    static const WCHAR wszTemplate[] = {'S','H','E','L','L','_','Y','E','S','T','O','A','L','L','_','M','S','G','B','O','X',0};
    struct confirm_msg_info info;

    info.lpszText = lpszText;
    info.lpszCaption = lpszCaption;
    info.hIcon = hIcon;
    info.bYesToAll = bYesToAll;
    return DialogBoxParamW(shell32_hInstance, wszTemplate, hWnd, ConfirmMsgBoxProc, (LPARAM)&info);
}

/* confirmation dialogs content */
206
typedef struct
207
{
208 209
        HINSTANCE hIconInstance;
        UINT icon_resource_id;
210
	UINT caption_resource_id, text_resource_id;
211
} SHELL_ConfirmIDstruc;
212

213 214
static BOOL SHELL_ConfirmIDs(int nKindOfDialog, SHELL_ConfirmIDstruc *ids)
{
215
        ids->hIconInstance = shell32_hInstance;
216 217
	switch (nKindOfDialog) {
	  case ASK_DELETE_FILE:
218
            ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
219 220 221 222
	    ids->caption_resource_id  = IDS_DELETEITEM_CAPTION;
	    ids->text_resource_id  = IDS_DELETEITEM_TEXT;
	    return TRUE;
	  case ASK_DELETE_FOLDER:
223
            ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
224 225 226 227
	    ids->caption_resource_id  = IDS_DELETEFOLDER_CAPTION;
	    ids->text_resource_id  = IDS_DELETEITEM_TEXT;
	    return TRUE;
	  case ASK_DELETE_MULTIPLE_ITEM:
228
            ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
229 230 231
	    ids->caption_resource_id  = IDS_DELETEITEM_CAPTION;
	    ids->text_resource_id  = IDS_DELETEMULTIPLE_TEXT;
	    return TRUE;
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
          case ASK_TRASH_FILE:
            ids->icon_resource_id = IDI_SHELL_TRASH_FILE;
            ids->caption_resource_id = IDS_DELETEITEM_CAPTION;
            ids->text_resource_id = IDS_TRASHITEM_TEXT;
            return TRUE;
          case ASK_TRASH_FOLDER:
            ids->icon_resource_id = IDI_SHELL_TRASH_FILE;
            ids->caption_resource_id = IDS_DELETEFOLDER_CAPTION;
            ids->text_resource_id = IDS_TRASHFOLDER_TEXT;
            return TRUE;
          case ASK_TRASH_MULTIPLE_ITEM:
            ids->icon_resource_id = IDI_SHELL_TRASH_FILE;
            ids->caption_resource_id = IDS_DELETEITEM_CAPTION;
            ids->text_resource_id = IDS_TRASHMULTIPLE_TEXT;
            return TRUE;
247 248 249 250 251
          case ASK_CANT_TRASH_ITEM:
            ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
            ids->caption_resource_id  = IDS_DELETEITEM_CAPTION;
            ids->text_resource_id  = IDS_CANTTRASH_TEXT;
            return TRUE;
252 253 254 255 256
	  case ASK_DELETE_SELECTED:
            ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
            ids->caption_resource_id  = IDS_DELETEITEM_CAPTION;
            ids->text_resource_id  = IDS_DELETESELECTED_TEXT;
            return TRUE;
257
	  case ASK_OVERWRITE_FILE:
258 259
            ids->hIconInstance = NULL;
            ids->icon_resource_id = IDI_WARNING;
260 261
	    ids->caption_resource_id  = IDS_OVERWRITEFILE_CAPTION;
	    ids->text_resource_id  = IDS_OVERWRITEFILE_TEXT;
262 263 264 265 266 267 268
            return TRUE;
	  case ASK_OVERWRITE_FOLDER:
            ids->hIconInstance = NULL;
            ids->icon_resource_id = IDI_WARNING;
            ids->caption_resource_id  = IDS_OVERWRITEFILE_CAPTION;
            ids->text_resource_id  = IDS_OVERWRITEFOLDER_TEXT;
            return TRUE;
269 270
	  default:
	    FIXME(" Unhandled nKindOfDialog %d stub\n", nKindOfDialog);
271
	}
272 273 274
	return FALSE;
}

275
static BOOL SHELL_ConfirmDialogW(HWND hWnd, int nKindOfDialog, LPCWSTR szDir, FILE_OPERATION *op)
276 277 278
{
	WCHAR szCaption[255], szText[255], szBuffer[MAX_PATH + 256];
	SHELL_ConfirmIDstruc ids;
279
	DWORD_PTR args[1];
280
	HICON hIcon;
281
	int ret;
282

283 284
        assert(nKindOfDialog >= 0 && nKindOfDialog < 32);
        if (op && (op->dwYesToAllMask & (1 << nKindOfDialog)))
285
            return TRUE;
286

287
        if (!SHELL_ConfirmIDs(nKindOfDialog, &ids)) return FALSE;
288

289 290
	LoadStringW(shell32_hInstance, ids.caption_resource_id, szCaption, sizeof(szCaption)/sizeof(WCHAR));
	LoadStringW(shell32_hInstance, ids.text_resource_id, szText, sizeof(szText)/sizeof(WCHAR));
291

292
	args[0] = (DWORD_PTR)szDir;
293
	FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ARGUMENT_ARRAY,
294
	               szText, 0, 0, szBuffer, sizeof(szBuffer), (va_list*)args);
295
        hIcon = LoadIconW(ids.hIconInstance, (LPWSTR)MAKEINTRESOURCE(ids.icon_resource_id));
296

297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
        ret = SHELL_ConfirmMsgBox(hWnd, szBuffer, szCaption, hIcon, op && op->bManyItems);
        if (op) {
            if (ret == IDD_YESTOALL) {
                op->dwYesToAllMask |= (1 << nKindOfDialog);
                ret = IDYES;
            }
            if (ret == IDCANCEL)
                op->bCancelled = TRUE;
            if (ret != IDYES)
                op->req->fAnyOperationsAborted = TRUE;
        }
        return ret == IDYES;
}

BOOL SHELL_ConfirmYesNoW(HWND hWnd, int nKindOfDialog, LPCWSTR szDir)
{
    return SHELL_ConfirmDialogW(hWnd, nKindOfDialog, szDir, NULL);
314 315
}

316
static DWORD SHELL32_AnsiToUnicodeBuf(LPCSTR aPath, LPWSTR *wPath, DWORD minChars)
317 318 319
{
	DWORD len = MultiByteToWideChar(CP_ACP, 0, aPath, -1, NULL, 0);

320 321
	if (len < minChars)
	  len = minChars;
322

323
	*wPath = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
324 325 326 327 328 329 330 331 332 333 334 335 336
	if (*wPath)
	{
	  MultiByteToWideChar(CP_ACP, 0, aPath, -1, *wPath, len);
	  return NO_ERROR;
	}
	return E_OUTOFMEMORY;
}

static void SHELL32_FreeUnicodeBuf(LPWSTR wPath)
{
	HeapFree(GetProcessHeap(), 0, wPath);
}

337 338 339 340 341 342
HRESULT WINAPI SHIsFileAvailableOffline(LPCWSTR path, LPDWORD status)
{
    FIXME("(%s, %p) stub\n", debugstr_w(path), status);
    return E_FAIL;
}

343
/**************************************************************************
344
 * SHELL_DeleteDirectory()  [internal]
345
 *
346 347
 * Asks for confirmation when bShowUI is true and deletes the directory and
 * all its subdirectories and files if necessary.
348
 */
349
BOOL SHELL_DeleteDirectoryW(HWND hwnd, LPCWSTR pszDir, BOOL bShowUI)
350 351 352 353 354 355 356 357 358 359 360 361
{
	BOOL    ret = TRUE;
	HANDLE  hFind;
	WIN32_FIND_DATAW wfd;
	WCHAR   szTemp[MAX_PATH];

	/* Make sure the directory exists before eventually prompting the user */
	PathCombineW(szTemp, pszDir, wWildcardFile);
	hFind = FindFirstFileW(szTemp, &wfd);
	if (hFind == INVALID_HANDLE_VALUE)
	  return FALSE;

362
	if (!bShowUI || (ret = SHELL_ConfirmDialogW(hwnd, ASK_DELETE_FOLDER, pszDir, NULL)))
363 364 365
	{
	  do
	  {
366
	    if (IsDotDir(wfd.cFileName))
367
	      continue;
368
	    PathCombineW(szTemp, pszDir, wfd.cFileName);
369
	    if (FILE_ATTRIBUTE_DIRECTORY & wfd.dwFileAttributes)
370
	      ret = SHELL_DeleteDirectoryW(hwnd, szTemp, FALSE);
371
	    else
372
	      ret = (SHNotifyDeleteFileW(szTemp) == ERROR_SUCCESS);
373 374 375 376
	  } while (ret && FindNextFileW(hFind, &wfd));
	}
	FindClose(hFind);
	if (ret)
377
	  ret = (SHNotifyRemoveDirectoryW(pszDir) == ERROR_SUCCESS);
378 379 380
	return ret;
}

381 382
/**************************************************************************
 * Win32CreateDirectory      [SHELL32.93]
383
 *
384 385 386 387 388 389 390 391
 * Creates a directory. Also triggers a change notify if one exists.
 *
 * PARAMS
 *  path       [I]   path to directory to create
 *
 * RETURNS
 *  TRUE if successful, FALSE otherwise
 *
392
 * NOTES
393 394
 *  Verified on Win98 / IE 5 (SHELL32 4.72, March 1999 build) to be ANSI.
 *  This is Unicode on NT/2000
395
 */
396
static DWORD SHNotifyCreateDirectoryA(LPCSTR path, LPSECURITY_ATTRIBUTES sec)
397
{
398 399 400
	LPWSTR wPath;
	DWORD retCode;

401 402
	TRACE("(%s, %p)\n", debugstr_a(path), sec);

403 404 405 406 407 408 409
	retCode = SHELL32_AnsiToUnicodeBuf(path, &wPath, 0);
	if (!retCode)
	{
	  retCode = SHNotifyCreateDirectoryW(wPath, sec);
	  SHELL32_FreeUnicodeBuf(wPath);
	}
	return retCode;
410 411
}

412 413
/**********************************************************************/

414
static DWORD SHNotifyCreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec)
415 416 417
{
	TRACE("(%s, %p)\n", debugstr_w(path), sec);

418
	if (CreateDirectoryW(path, sec))
419 420
	{
	  SHChangeNotify(SHCNE_MKDIR, SHCNF_PATHW, path, NULL);
421
	  return ERROR_SUCCESS;
422
	}
423
	return GetLastError();
424 425
}

426 427
/**********************************************************************/

428
BOOL WINAPI Win32CreateDirectoryAW(LPCVOID path, LPSECURITY_ATTRIBUTES sec)
429 430
{
	if (SHELL_OsIsUnicode())
431 432
	  return (SHNotifyCreateDirectoryW(path, sec) == ERROR_SUCCESS);
	return (SHNotifyCreateDirectoryA(path, sec) == ERROR_SUCCESS);
433 434
}

435 436 437 438 439 440 441 442 443 444 445
/************************************************************************
 * Win32RemoveDirectory      [SHELL32.94]
 *
 * Deletes a directory. Also triggers a change notify if one exists.
 *
 * PARAMS
 *  path       [I]   path to directory to delete
 *
 * RETURNS
 *  TRUE if successful, FALSE otherwise
 *
446
 * NOTES
447 448
 *  Verified on Win98 / IE 5 (SHELL32 4.72, March 1999 build) to be ANSI.
 *  This is Unicode on NT/2000
449
 */
450
static DWORD SHNotifyRemoveDirectoryA(LPCSTR path)
451
{
452 453 454
	LPWSTR wPath;
	DWORD retCode;

455 456
	TRACE("(%s)\n", debugstr_a(path));

457 458 459 460 461 462 463
	retCode = SHELL32_AnsiToUnicodeBuf(path, &wPath, 0);
	if (!retCode)
	{
	  retCode = SHNotifyRemoveDirectoryW(wPath);
	  SHELL32_FreeUnicodeBuf(wPath);
	}
	return retCode;
464 465
}

466 467
/***********************************************************************/

468
static DWORD SHNotifyRemoveDirectoryW(LPCWSTR path)
469
{
470 471 472 473 474
	BOOL ret;
	TRACE("(%s)\n", debugstr_w(path));

	ret = RemoveDirectoryW(path);
	if (!ret)
475
	{
476 477
	  /* Directory may be write protected */
	  DWORD dwAttr = GetFileAttributesW(path);
478
	  if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY))
479 480
	    if (SetFileAttributesW(path, dwAttr & ~FILE_ATTRIBUTE_READONLY))
	      ret = RemoveDirectoryW(path);
481
	}
482
	if (ret)
483
	{
484
	  SHChangeNotify(SHCNE_RMDIR, SHCNF_PATHW, path, NULL);
485 486 487
	  return ERROR_SUCCESS;
	}
	return GetLastError();
488 489
}

490 491
/***********************************************************************/

492 493 494
BOOL WINAPI Win32RemoveDirectoryAW(LPCVOID path)
{
	if (SHELL_OsIsUnicode())
495 496
	  return (SHNotifyRemoveDirectoryW(path) == ERROR_SUCCESS);
	return (SHNotifyRemoveDirectoryA(path) == ERROR_SUCCESS);
497 498
}

499
/************************************************************************
500
 * Win32DeleteFile           [SHELL32.164]
501
 *
502
 * Deletes a file. Also triggers a change notify if one exists.
503
 *
504 505 506 507 508 509
 * PARAMS
 *  path       [I]   path to file to delete
 *
 * RETURNS
 *  TRUE if successful, FALSE otherwise
 *
510
 * NOTES
511 512
 *  Verified on Win98 / IE 5 (SHELL32 4.72, March 1999 build) to be ANSI.
 *  This is Unicode on NT/2000
513
 */
514
static DWORD SHNotifyDeleteFileA(LPCSTR path)
515
{
516 517 518
	LPWSTR wPath;
	DWORD retCode;

519 520
	TRACE("(%s)\n", debugstr_a(path));

521 522 523 524 525 526 527
	retCode = SHELL32_AnsiToUnicodeBuf(path, &wPath, 0);
	if (!retCode)
	{
	  retCode = SHNotifyDeleteFileW(wPath);
	  SHELL32_FreeUnicodeBuf(wPath);
	}
	return retCode;
528 529
}

530 531
/***********************************************************************/

532
static DWORD SHNotifyDeleteFileW(LPCWSTR path)
533
{
534
	BOOL ret;
535

536 537 538 539 540 541 542
	TRACE("(%s)\n", debugstr_w(path));

	ret = DeleteFileW(path);
	if (!ret)
	{
	  /* File may be write protected or a system file */
	  DWORD dwAttr = GetFileAttributesW(path);
543
	  if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))
544 545 546 547
	    if (SetFileAttributesW(path, dwAttr & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)))
	      ret = DeleteFileW(path);
	}
	if (ret)
548
	{
549
	  SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, path, NULL);
550 551 552
	  return ERROR_SUCCESS;
	}
	return GetLastError();
553 554
}

555 556
/***********************************************************************/

557 558 559
DWORD WINAPI Win32DeleteFileAW(LPCVOID path)
{
	if (SHELL_OsIsUnicode())
560 561
	  return (SHNotifyDeleteFileW(path) == ERROR_SUCCESS);
	return (SHNotifyDeleteFileA(path) == ERROR_SUCCESS);
562 563 564 565 566 567 568 569 570 571 572 573
}

/************************************************************************
 * SHNotifyMoveFile          [internal]
 *
 * Moves a file. Also triggers a change notify if one exists.
 *
 * PARAMS
 *  src        [I]   path to source file to move
 *  dest       [I]   path to target file to move to
 *
 * RETURNS
574
 *  ERORR_SUCCESS if successful
575
 */
576
static DWORD SHNotifyMoveFileW(LPCWSTR src, LPCWSTR dest)
577 578 579
{
	BOOL ret;

580
	TRACE("(%s %s)\n", debugstr_w(src), debugstr_w(dest));
581

582 583 584 585 586 587
        ret = MoveFileExW(src, dest, MOVEFILE_REPLACE_EXISTING);

        /* MOVEFILE_REPLACE_EXISTING fails with dirs, so try MoveFile */
        if (!ret)
            ret = MoveFileW(src, dest);

588 589
	if (!ret)
	{
590
	  DWORD dwAttr;
591

592 593
	  dwAttr = SHFindAttrW(dest, FALSE);
	  if (INVALID_FILE_ATTRIBUTES == dwAttr)
594
	  {
595 596 597 598 599
	    /* Source file may be write protected or a system file */
	    dwAttr = GetFileAttributesW(src);
	    if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))
	      if (SetFileAttributesW(src, dwAttr & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)))
	        ret = MoveFileW(src, dest);
600
	  }
601 602
	}
	if (ret)
603
	{
604
	  SHChangeNotify(SHCNE_RENAMEITEM, SHCNF_PATHW, src, dest);
605 606 607
	  return ERROR_SUCCESS;
	}
	return GetLastError();
608 609 610 611 612 613 614 615
}

/************************************************************************
 * SHNotifyCopyFile          [internal]
 *
 * Copies a file. Also triggers a change notify if one exists.
 *
 * PARAMS
616 617 618 619
 *  src           [I]   path to source file to move
 *  dest          [I]   path to target file to move to
 *  bFailIfExists [I]   if TRUE, the target file will not be overwritten if
 *                      a file with this name already exists
620 621
 *
 * RETURNS
622
 *  ERROR_SUCCESS if successful
623
 */
624
static DWORD SHNotifyCopyFileW(LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists)
625
{
626
	BOOL ret;
627

628
	TRACE("(%s %s %s)\n", debugstr_w(src), debugstr_w(dest), bFailIfExists ? "failIfExists" : "");
629

630
	ret = CopyFileW(src, dest, bFailIfExists);
631
	if (ret)
632
	{
633
	  SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, dest, NULL);
634 635
	  return ERROR_SUCCESS;
	}
636

637
	return GetLastError();
638 639
}

640 641 642
/*************************************************************************
 * SHCreateDirectory         [SHELL32.165]
 *
643 644 645
 * This function creates a file system folder whose fully qualified path is
 * given by path. If one or more of the intermediate folders do not exist,
 * they will be created as well.
646 647
 *
 * PARAMS
648 649
 *  hWnd       [I]
 *  path       [I]   path of directory to create
650 651 652 653 654
 *
 * RETURNS
 *  ERRROR_SUCCESS or one of the following values:
 *  ERROR_BAD_PATHNAME if the path is relative
 *  ERROR_FILE_EXISTS when a file with that name exists
655 656
 *  ERROR_PATH_NOT_FOUND can't find the path, probably invalid
 *  ERROR_INVLID_NAME if the path contains invalid chars
657 658 659 660 661 662 663 664 665
 *  ERROR_ALREADY_EXISTS when the directory already exists
 *  ERROR_FILENAME_EXCED_RANGE if the filename was to long to process
 *
 * NOTES
 *  exported by ordinal
 *  Win9x exports ANSI
 *  WinNT/2000 exports Unicode
 */
DWORD WINAPI SHCreateDirectory(HWND hWnd, LPCVOID path)
666 667
{
	if (SHELL_OsIsUnicode())
668 669 670 671 672 673 674
	  return SHCreateDirectoryExW(hWnd, path, NULL);
	return SHCreateDirectoryExA(hWnd, path, NULL);
}

/*************************************************************************
 * SHCreateDirectoryExA      [SHELL32.@]
 *
675 676 677
 * This function creates a file system folder whose fully qualified path is
 * given by path. If one or more of the intermediate folders do not exist,
 * they will be created as well.
678 679
 *
 * PARAMS
680 681
 *  hWnd       [I]
 *  path       [I]   path of directory to create
682 683 684 685
 *  sec        [I]   security attributes to use or NULL
 *
 * RETURNS
 *  ERRROR_SUCCESS or one of the following values:
686 687
 *  ERROR_BAD_PATHNAME or ERROR_PATH_NOT_FOUND if the path is relative
 *  ERROR_INVLID_NAME if the path contains invalid chars
688 689 690
 *  ERROR_FILE_EXISTS when a file with that name exists
 *  ERROR_ALREADY_EXISTS when the directory already exists
 *  ERROR_FILENAME_EXCED_RANGE if the filename was to long to process
691 692
 *
 *  FIXME: Not implemented yet;
693 694 695
 *  SHCreateDirectoryEx also verifies that the files in the directory will be visible
 *  if the path is a network path to deal with network drivers which might have a limited
 *  but unknown maximum path length. If not:
696 697 698 699 700 701 702
 *
 *  If hWnd is set to a valid window handle, a message box is displayed warning
 *  the user that the files may not be accessible. If the user chooses not to
 *  proceed, the function returns ERROR_CANCELLED.
 *
 *  If hWnd is set to NULL, no user interface is displayed and the function
 *  returns ERROR_CANCELLED.
703
 */
704
int WINAPI SHCreateDirectoryExA(HWND hWnd, LPCSTR path, LPSECURITY_ATTRIBUTES sec)
705
{
706 707 708 709
	LPWSTR wPath;
	DWORD retCode;

	TRACE("(%s, %p)\n", debugstr_a(path), sec);
710

711 712 713 714 715 716 717
	retCode = SHELL32_AnsiToUnicodeBuf(path, &wPath, 0);
	if (!retCode)
	{
	  retCode = SHCreateDirectoryExW(hWnd, wPath, sec);
	  SHELL32_FreeUnicodeBuf(wPath);
	}
	return retCode;
718 719 720 721
}

/*************************************************************************
 * SHCreateDirectoryExW      [SHELL32.@]
722 723
 *
 * See SHCreateDirectoryExA.
724
 */
725
int WINAPI SHCreateDirectoryExW(HWND hWnd, LPCWSTR path, LPSECURITY_ATTRIBUTES sec)
726
{
727
	int ret = ERROR_BAD_PATHNAME;
728
	TRACE("(%p, %s, %p)\n", hWnd, debugstr_w(path), sec);
729 730 731

	if (PathIsRelativeW(path))
	{
732
	  SetLastError(ret);
733 734 735
	}
	else
	{
736
	  ret = SHNotifyCreateDirectoryW(path, sec);
737
	  /* Refuse to work on certain error codes before trying to create directories recursively */
738 739
	  if (ret != ERROR_SUCCESS &&
	      ret != ERROR_FILE_EXISTS &&
740 741
	      ret != ERROR_ALREADY_EXISTS &&
	      ret != ERROR_FILENAME_EXCED_RANGE)
742
	  {
743 744 745 746 747 748 749 750 751 752 753 754 755
	    WCHAR *pEnd, *pSlash, szTemp[MAX_PATH + 1];  /* extra for PathAddBackslash() */

	    lstrcpynW(szTemp, path, MAX_PATH);
	    pEnd = PathAddBackslashW(szTemp);
	    pSlash = szTemp + 3;

	    while (*pSlash)
	    {
	      while (*pSlash && *pSlash != '\\')
	        pSlash = CharNextW(pSlash);

	      if (*pSlash)
	      {
756
	        *pSlash = 0;    /* terminate path at separator */
757 758 759

	        ret = SHNotifyCreateDirectoryW(szTemp, pSlash + 1 == pEnd ? sec : NULL);
	      }
760
	      *pSlash++ = '\\'; /* put the separator back */
761 762 763 764 765 766 767 768
	    }
	  }

	  if (ret && hWnd && (ERROR_CANCELLED != ret))
	  {
	    /* We failed and should show a dialog box */
	    FIXME("Show system error message, creating path %s, failed with error %d\n", debugstr_w(path), ret);
	    ret = ERROR_CANCELLED; /* Error has been already presented to user (not really yet!) */
769 770 771
	  }
	}
	return ret;
772 773
}

774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811
/*************************************************************************
 * SHFindAttrW      [internal]
 *
 * Get the Attributes for a file or directory. The difference to GetAttributes()
 * is that this function will also work for paths containing wildcard characters
 * in its filename.

 * PARAMS
 *  path       [I]   path of directory or file to check
 *  fileOnly   [I]   TRUE if only files should be found
 *
 * RETURNS
 *  INVALID_FILE_ATTRIBUTES if the path does not exist, the actual attributes of
 *  the first file or directory found otherwise
 */
static DWORD SHFindAttrW(LPCWSTR pName, BOOL fileOnly)
{
	WIN32_FIND_DATAW wfd;
	BOOL b_FileMask = fileOnly && (NULL != StrPBrkW(pName, wWildcardChars));
	DWORD dwAttr = INVALID_FILE_ATTRIBUTES;
	HANDLE hFind = FindFirstFileW(pName, &wfd);

	TRACE("%s %d\n", debugstr_w(pName), fileOnly);
	if (INVALID_HANDLE_VALUE != hFind)
	{
	  do
	  {
	    if (b_FileMask && IsAttribDir(wfd.dwFileAttributes))
	       continue;
	    dwAttr = wfd.dwFileAttributes;
	    break;
	  }
	  while (FindNextFileW(hFind, &wfd));
	  FindClose(hFind);
	}
	return dwAttr;
}

812
/*************************************************************************
813
 *
814
 * SHNameTranslate HelperFunction for SHFileOperationA
815
 *
816
 * Translates a list of 0 terminated ASCII strings into Unicode. If *wString
817 818 819 820
 * is NULL, only the necessary size of the string is determined and returned,
 * otherwise the ASCII strings are copied into it and the buffer is increased
 * to point to the location after the final 0 termination char.
 */
821
static DWORD SHNameTranslate(LPWSTR* wString, LPCWSTR* pWToFrom, BOOL more)
822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852
{
	DWORD size = 0, aSize = 0;
	LPCSTR aString = (LPCSTR)*pWToFrom;

	if (aString)
	{
	  do
	  {
	    size = lstrlenA(aString) + 1;
	    aSize += size;
	    aString += size;
	  } while ((size != 1) && more);
	  /* The two sizes might be different in the case of multibyte chars */
	  size = MultiByteToWideChar(CP_ACP, 0, aString, aSize, *wString, 0);
	  if (*wString) /* only in the second loop */
	  {
	    MultiByteToWideChar(CP_ACP, 0, (LPCSTR)*pWToFrom, aSize, *wString, size);
	    *pWToFrom = *wString;
	    *wString += size;
	  }
	}
	return size;
}
/*************************************************************************
 * SHFileOperationA          [SHELL32.@]
 *
 * Function to copy, move, delete and create one or more files with optional
 * user prompts.
 *
 * PARAMS
 *  lpFileOp   [I/O] pointer to a structure containing all the necessary information
853
 *
854
 * RETURNS
855 856
 *  Success: ERROR_SUCCESS.
 *  Failure: ERROR_CANCELLED.
857
 *
858
 * NOTES
859 860
 *  exported by name
 */
861
int WINAPI SHFileOperationA(LPSHFILEOPSTRUCTA lpFileOp)
862 863
{
	SHFILEOPSTRUCTW nFileOp = *((LPSHFILEOPSTRUCTW)lpFileOp);
864 865
	int retCode = 0;
	DWORD size;
866 867
	LPWSTR ForFree = NULL, /* we change wString in SHNameTranslate and can't use it for freeing */
	       wString = NULL; /* we change this in SHNameTranslate */
868

869
	TRACE("\n");
870 871 872 873
	if (FO_DELETE == (nFileOp.wFunc & FO_MASK))
	  nFileOp.pTo = NULL; /* we need a NULL or a valid pointer for translation */
	if (!(nFileOp.fFlags & FOF_SIMPLEPROGRESS))
	  nFileOp.lpszProgressTitle = NULL; /* we need a NULL or a valid pointer for translation */
874
	while (1) /* every loop calculate size, second translate also, if we have storage for this */
875 876 877 878 879
	{
	  size = SHNameTranslate(&wString, &nFileOp.lpszProgressTitle, FALSE); /* no loop */
	  size += SHNameTranslate(&wString, &nFileOp.pFrom, TRUE); /* internal loop */
	  size += SHNameTranslate(&wString, &nFileOp.pTo, TRUE); /* internal loop */

880 881 882
	  if (ForFree)
	  {
	    retCode = SHFileOperationW(&nFileOp);
883
	    HeapFree(GetProcessHeap(), 0, ForFree); /* we cannot use wString, it was changed */
884 885 886 887 888 889 890 891 892 893 894
	    break;
	  }
	  else
	  {
	    wString = ForFree = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
	    if (ForFree) continue;
	    retCode = ERROR_OUTOFMEMORY;
	    nFileOp.fAnyOperationsAborted = TRUE;
	    SetLastError(retCode);
	    return retCode;
	  }
895
	}
896

897 898 899 900 901
	lpFileOp->hNameMappings = nFileOp.hNameMappings;
	lpFileOp->fAnyOperationsAborted = nFileOp.fAnyOperationsAborted;
	return retCode;
}

902
#define ERROR_SHELL_INTERNAL_FILE_NOT_FOUND 1026
903

904
typedef struct
905
{
906
    DWORD attributes;
907 908 909 910 911 912 913
    LPWSTR szDirectory;
    LPWSTR szFilename;
    LPWSTR szFullPath;
    BOOL bFromWildcard;
    BOOL bFromRelative;
    BOOL bExists;
} FILE_ENTRY;
914

915 916 917
typedef struct
{
    FILE_ENTRY *feFiles;
918
    DWORD num_alloc;
919 920 921 922 923 924
    DWORD dwNumFiles;
    BOOL bAnyFromWildcard;
    BOOL bAnyDirectories;
    BOOL bAnyDontExist;
} FILE_LIST;

925

926
static inline void grow_list(FILE_LIST *list)
927
{
928 929 930 931
    FILE_ENTRY *new = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, list->feFiles,
                                  list->num_alloc * 2 * sizeof(*new) );
    list->feFiles = new;
    list->num_alloc *= 2;
932 933
}

934
/* adds a file to the FILE_ENTRY struct
935
 */
936
static void add_file_to_entry(FILE_ENTRY *feFile, LPWSTR szFile)
937
{
938
    DWORD dwLen = lstrlenW(szFile) + 1;
939 940 941
    LPWSTR ptr;

    feFile->szFullPath = HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
942
    lstrcpyW(feFile->szFullPath, szFile);
943 944 945 946 947 948 949 950

    ptr = StrRChrW(szFile, NULL, '\\');
    if (ptr)
    {
        dwLen = ptr - szFile + 1;
        feFile->szDirectory = HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
        lstrcpynW(feFile->szDirectory, szFile, dwLen);

951
        dwLen = lstrlenW(feFile->szFullPath) - dwLen + 1;
952
        feFile->szFilename = HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
953
        lstrcpyW(feFile->szFilename, ptr + 1); /* skip over backslash */
954
    }
955
    feFile->bFromWildcard = FALSE;
956 957 958 959 960 961 962 963 964 965
}

static LPWSTR wildcard_to_file(LPWSTR szWildCard, LPWSTR szFileName)
{
    LPWSTR szFullPath, ptr;
    DWORD dwDirLen, dwFullLen;

    ptr = StrRChrW(szWildCard, NULL, '\\');
    dwDirLen = ptr - szWildCard + 1;

966
    dwFullLen = dwDirLen + lstrlenW(szFileName) + 1;
967 968 969
    szFullPath = HeapAlloc(GetProcessHeap(), 0, dwFullLen * sizeof(WCHAR));

    lstrcpynW(szFullPath, szWildCard, dwDirLen + 1);
970
    lstrcatW(szFullPath, szFileName);
971 972 973

    return szFullPath;
}
974

975
static void parse_wildcard_files(FILE_LIST *flList, LPWSTR szFile, LPDWORD pdwListIndex)
976
{
977 978
    WIN32_FIND_DATAW wfd;
    HANDLE hFile = FindFirstFileW(szFile, &wfd);
979
    FILE_ENTRY *file;
980
    LPWSTR szFullPath;
981 982
    BOOL res;

983 984
    if (hFile == INVALID_HANDLE_VALUE) return;

985
    for (res = TRUE; res; res = FindNextFileW(hFile, &wfd))
986
    {
987
        if (IsDotDir(wfd.cFileName)) continue;
988
        if (*pdwListIndex >= flList->num_alloc) grow_list( flList );
989
        szFullPath = wildcard_to_file(szFile, wfd.cFileName);
990 991 992 993 994
        file = &flList->feFiles[(*pdwListIndex)++];
        add_file_to_entry(file, szFullPath);
        file->bFromWildcard = TRUE;
        file->attributes = wfd.dwFileAttributes;
        if (IsAttribDir(file->attributes)) flList->bAnyDirectories = TRUE;
995
        HeapFree(GetProcessHeap(), 0, szFullPath);
996 997
    }

998
    FindClose(hFile);
999 1000 1001 1002 1003 1004 1005
}

/* takes the null-separated file list and fills out the FILE_LIST */
static HRESULT parse_file_list(FILE_LIST *flList, LPCWSTR szFiles)
{
    LPCWSTR ptr = szFiles;
    WCHAR szCurFile[MAX_PATH];
1006
    DWORD i = 0, dwDirLen;
1007 1008 1009 1010 1011 1012 1013

    if (!szFiles)
        return ERROR_INVALID_PARAMETER;

    flList->bAnyFromWildcard = FALSE;
    flList->bAnyDirectories = FALSE;
    flList->bAnyDontExist = FALSE;
1014 1015
    flList->num_alloc = 32;
    flList->dwNumFiles = 0;
1016 1017

    /* empty list */
1018
    if (!szFiles[0])
1019 1020 1021
        return ERROR_ACCESS_DENIED;
        
    flList->feFiles = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
1022
                                flList->num_alloc * sizeof(FILE_ENTRY));
1023

1024
    while (*ptr)
1025
    {
1026
        if (i >= flList->num_alloc) grow_list( flList );
1027 1028 1029

        /* change relative to absolute path */
        if (PathIsRelativeW(ptr))
1030
        {
1031 1032 1033
            dwDirLen = GetCurrentDirectoryW(MAX_PATH, szCurFile) + 1;
            PathCombineW(szCurFile, szCurFile, ptr);
            flList->feFiles[i].bFromRelative = TRUE;
1034 1035 1036
        }
        else
        {
1037
            lstrcpyW(szCurFile, ptr);
1038 1039
            flList->feFiles[i].bFromRelative = FALSE;
        }
1040

1041 1042 1043 1044 1045 1046 1047
        /* parse wildcard files if they are in the filename */
        if (StrPBrkW(szCurFile, wWildcardChars))
        {
            parse_wildcard_files(flList, szCurFile, &i);
            flList->bAnyFromWildcard = TRUE;
            i--;
        }
1048 1049 1050 1051 1052
        else
        {
            FILE_ENTRY *file = &flList->feFiles[i];
            add_file_to_entry(file, szCurFile);
            file->attributes = GetFileAttributesW( file->szFullPath );
1053 1054
            file->bExists = (file->attributes != INVALID_FILE_ATTRIBUTES);
            if (!file->bExists) flList->bAnyDontExist = TRUE;
1055 1056
            if (IsAttribDir(file->attributes)) flList->bAnyDirectories = TRUE;
        }
1057 1058

        /* advance to the next string */
1059
        ptr += lstrlenW(ptr) + 1;
1060
        i++;
1061
    }
1062
    flList->dwNumFiles = i;
1063 1064

    return S_OK;
1065 1066
}

1067 1068
/* free the FILE_LIST */
static void destroy_file_list(FILE_LIST *flList)
1069
{
1070 1071 1072 1073 1074 1075
    DWORD i;

    if (!flList || !flList->feFiles)
        return;

    for (i = 0; i < flList->dwNumFiles; i++)
1076
    {
1077 1078 1079
        HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szDirectory);
        HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szFilename);
        HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szFullPath);
1080
    }
1081 1082

    HeapFree(GetProcessHeap(), 0, flList->feFiles);
1083 1084
}

1085
static void copy_dir_to_dir(FILE_OPERATION *op, FILE_ENTRY *feFrom, LPWSTR szDestPath)
1086
{
1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097
    WCHAR szFrom[MAX_PATH], szTo[MAX_PATH];
    SHFILEOPSTRUCTW fileOp;

    static const WCHAR wildCardFiles[] = {'*','.','*',0};

    if (IsDotDir(feFrom->szFilename))
        return;

    if (PathFileExistsW(szDestPath))
        PathCombineW(szTo, szDestPath, feFrom->szFilename);
    else
1098
        lstrcpyW(szTo, szDestPath);
1099

1100 1101 1102 1103 1104 1105 1106 1107 1108 1109
    if (!(op->req->fFlags & FOF_NOCONFIRMATION) && PathFileExistsW(szTo)) {
        if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FOLDER, feFrom->szFilename, op))
        {
            /* Vista returns an ERROR_CANCELLED even if user pressed "No" */
            if (!op->bManyItems)
                op->bCancelled = TRUE;
            return;
        }
    }

1110
    szTo[lstrlenW(szTo) + 1] = '\0';
1111 1112 1113
    SHNotifyCreateDirectoryW(szTo, NULL);

    PathCombineW(szFrom, feFrom->szFullPath, wildCardFiles);
1114
    szFrom[lstrlenW(szFrom) + 1] = '\0';
1115

1116
    memcpy(&fileOp, op->req, sizeof(fileOp));
1117 1118 1119 1120
    fileOp.pFrom = szFrom;
    fileOp.pTo = szTo;
    fileOp.fFlags &= ~FOF_MULTIDESTFILES; /* we know we're copying to one dir */

1121 1122 1123 1124 1125
    /* Don't ask the user about overwriting files when he accepted to overwrite the
       folder. FIXME: this is not exactly what Windows does - e.g. there would be
       an additional confirmation for a nested folder */
    fileOp.fFlags |= FOF_NOCONFIRMATION;  

1126
    SHFileOperationW(&fileOp);
1127 1128
}

1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139
static BOOL copy_file_to_file(FILE_OPERATION *op, WCHAR *szFrom, WCHAR *szTo)
{
    if (!(op->req->fFlags & FOF_NOCONFIRMATION) && PathFileExistsW(szTo))
    {
        if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FILE, PathFindFileNameW(szTo), op))
            return 0;
    }
    
    return SHNotifyCopyFileW(szFrom, szTo, FALSE) == 0;
}

1140
/* copy a file or directory to another directory */
1141
static void copy_to_dir(FILE_OPERATION *op, FILE_ENTRY *feFrom, FILE_ENTRY *feTo)
1142
{
1143 1144 1145
    if (!PathFileExistsW(feTo->szFullPath))
        SHNotifyCreateDirectoryW(feTo->szFullPath, NULL);

1146
    if (IsAttribFile(feFrom->attributes))
1147 1148 1149 1150
    {
        WCHAR szDestPath[MAX_PATH];

        PathCombineW(szDestPath, feTo->szFullPath, feFrom->szFilename);
1151
        copy_file_to_file(op, feFrom->szFullPath, szDestPath);
1152
    }
1153 1154
    else if (!(op->req->fFlags & FOF_FILESONLY && feFrom->bFromWildcard))
        copy_dir_to_dir(op, feFrom, feTo->szFullPath);
1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176
}

static void create_dest_dirs(LPWSTR szDestDir)
{
    WCHAR dir[MAX_PATH];
    LPWSTR ptr = StrChrW(szDestDir, '\\');

    /* make sure all directories up to last one are created */
    while (ptr && (ptr = StrChrW(ptr + 1, '\\')))
    {
        lstrcpynW(dir, szDestDir, ptr - szDestDir + 1);

        if (!PathFileExistsW(dir))
            SHNotifyCreateDirectoryW(dir, NULL);
    }

    /* create last directory */
    if (!PathFileExistsW(szDestDir))
        SHNotifyCreateDirectoryW(szDestDir, NULL);
}

/* the FO_COPY operation */
1177
static HRESULT copy_files(FILE_OPERATION *op, FILE_LIST *flFrom, FILE_LIST *flTo)
1178 1179 1180 1181 1182 1183 1184 1185 1186
{
    DWORD i;
    FILE_ENTRY *entryToCopy;
    FILE_ENTRY *fileDest = &flTo->feFiles[0];
    BOOL bCancelIfAnyDirectories = FALSE;

    if (flFrom->bAnyDontExist)
        return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND;

1187
    if (op->req->fFlags & FOF_MULTIDESTFILES && flFrom->bAnyFromWildcard)
1188 1189
        return ERROR_CANCELLED;

1190
    if (!(op->req->fFlags & FOF_MULTIDESTFILES) && flTo->dwNumFiles != 1)
1191 1192
        return ERROR_CANCELLED;

1193
    if (op->req->fFlags & FOF_MULTIDESTFILES && flFrom->dwNumFiles != 1 &&
1194 1195 1196 1197 1198 1199 1200
        flFrom->dwNumFiles != flTo->dwNumFiles)
    {
        return ERROR_CANCELLED;
    }

    if (flFrom->dwNumFiles > 1 && flTo->dwNumFiles == 1 &&
        !PathFileExistsW(flTo->feFiles[0].szFullPath) &&
1201
        IsAttribFile(fileDest->attributes))
1202 1203 1204 1205 1206 1207 1208
    {
        bCancelIfAnyDirectories = TRUE;
    }

    if (flFrom->dwNumFiles > 1 && flTo->dwNumFiles == 1 && fileDest->bFromRelative &&
        !PathFileExistsW(fileDest->szFullPath))
    {
1209
        op->req->fAnyOperationsAborted = TRUE;
1210 1211 1212
        return ERROR_CANCELLED;
    }

1213
    if (!(op->req->fFlags & FOF_MULTIDESTFILES) && flFrom->dwNumFiles != 1 &&
1214
        PathFileExistsW(fileDest->szFullPath) &&
1215
        IsAttribFile(fileDest->attributes))
1216 1217 1218 1219 1220 1221 1222 1223
    {
        return ERROR_CANCELLED;
    }

    for (i = 0; i < flFrom->dwNumFiles; i++)
    {
        entryToCopy = &flFrom->feFiles[i];

1224
        if (op->req->fFlags & FOF_MULTIDESTFILES)
1225 1226
            fileDest = &flTo->feFiles[i];

1227
        if (IsAttribDir(entryToCopy->attributes) &&
1228
            !lstrcmpiW(entryToCopy->szFullPath, fileDest->szDirectory))
1229
        {
1230
            return ERROR_SUCCESS;
1231
        }
1232

1233
        if (IsAttribDir(entryToCopy->attributes) && bCancelIfAnyDirectories)
1234 1235 1236 1237
            return ERROR_CANCELLED;

        create_dest_dirs(fileDest->szDirectory);

1238
        if (!lstrcmpiW(entryToCopy->szFullPath, fileDest->szFullPath))
1239
        {
1240
            if (IsAttribFile(entryToCopy->attributes))
1241 1242 1243
                return ERROR_NO_MORE_SEARCH_HANDLES;
            else
                return ERROR_SUCCESS;
1244
        }
1245 1246

        if ((flFrom->dwNumFiles > 1 && flTo->dwNumFiles == 1) ||
1247
            (flFrom->dwNumFiles == 1 && IsAttribDir(fileDest->attributes)))
1248
        {
1249
            copy_to_dir(op, entryToCopy, fileDest);
1250
        }
1251
        else if (IsAttribDir(entryToCopy->attributes))
1252
        {
1253
            copy_dir_to_dir(op, entryToCopy, fileDest->szFullPath);
1254 1255
        }
        else
1256
        {
1257
            if (!copy_file_to_file(op, entryToCopy->szFullPath, fileDest->szFullPath))
1258
            {
1259
                op->req->fAnyOperationsAborted = TRUE;
1260 1261 1262
                return ERROR_CANCELLED;
            }
        }
1263 1264 1265 1266

        /* Vista return code. XP would return e.g. ERROR_FILE_NOT_FOUND, ERROR_ALREADY_EXISTS */
        if (op->bCancelled)
            return ERROR_CANCELLED;
1267
    }
1268

1269 1270
    /* Vista return code. On XP if the used pressed "No" for the last item,
     * ERROR_ARENA_TRASHED would be returned */
1271 1272
    return ERROR_SUCCESS;
}
1273

1274
static BOOL confirm_delete_list(HWND hWnd, DWORD fFlags, BOOL fTrash, FILE_LIST *flFrom)
1275 1276 1277 1278 1279 1280 1281
{
    if (flFrom->dwNumFiles > 1)
    {
        WCHAR tmp[8];
        const WCHAR format[] = {'%','d',0};

        wnsprintfW(tmp, sizeof(tmp)/sizeof(tmp[0]), format, flFrom->dwNumFiles);
1282
        return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_MULTIPLE_ITEM:ASK_DELETE_MULTIPLE_ITEM), tmp, NULL);
1283 1284 1285 1286 1287 1288
    }
    else
    {
        FILE_ENTRY *fileEntry = &flFrom->feFiles[0];

        if (IsAttribFile(fileEntry->attributes))
1289
            return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_FILE:ASK_DELETE_FILE), fileEntry->szFullPath, NULL);
1290
        else if (!(fFlags & FOF_FILESONLY && fileEntry->bFromWildcard))
1291
            return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_FOLDER:ASK_DELETE_FOLDER), fileEntry->szFullPath, NULL);
1292 1293 1294 1295
    }
    return TRUE;
}

1296 1297 1298 1299 1300 1301
/* the FO_DELETE operation */
static HRESULT delete_files(LPSHFILEOPSTRUCTW lpFileOp, FILE_LIST *flFrom)
{
    FILE_ENTRY *fileEntry;
    DWORD i;
    BOOL bPathExists;
1302
    BOOL bTrash;
1303

1304 1305
    if (!flFrom->dwNumFiles)
        return ERROR_SUCCESS;
1306

1307 1308 1309 1310 1311 1312
    /* Windows also checks only the first item */
    bTrash = (lpFileOp->fFlags & FOF_ALLOWUNDO)
        && TRASH_CanTrashFile(flFrom->feFiles[0].szFullPath);

    if (!(lpFileOp->fFlags & FOF_NOCONFIRMATION) || (!bTrash && lpFileOp->fFlags & FOF_WANTNUKEWARNING))
        if (!confirm_delete_list(lpFileOp->hwnd, lpFileOp->fFlags, bTrash, flFrom))
1313 1314 1315 1316 1317
        {
            lpFileOp->fAnyOperationsAborted = TRUE;
            return 0;
        }

1318 1319 1320 1321
    for (i = 0; i < flFrom->dwNumFiles; i++)
    {
        bPathExists = TRUE;
        fileEntry = &flFrom->feFiles[i];
1322

1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334
        if (!IsAttribFile(fileEntry->attributes) &&
            (lpFileOp->fFlags & FOF_FILESONLY && fileEntry->bFromWildcard))
            continue;

        if (bTrash)
        {
            BOOL bDelete;
            if (TRASH_TrashFile(fileEntry->szFullPath))
                continue;

            /* Note: Windows silently deletes the file in such a situation, we show a dialog */
            if (!(lpFileOp->fFlags & FOF_NOCONFIRMATION) || (lpFileOp->fFlags & FOF_WANTNUKEWARNING))
1335
                bDelete = SHELL_ConfirmDialogW(lpFileOp->hwnd, ASK_CANT_TRASH_ITEM, fileEntry->szFullPath, NULL);
1336 1337 1338 1339 1340 1341 1342 1343 1344 1345
            else
                bDelete = TRUE;

            if (!bDelete)
            {
                lpFileOp->fAnyOperationsAborted = TRUE;
                break;
            }
        }
        
1346
        /* delete the file or directory */
1347
        if (IsAttribFile(fileEntry->attributes))
1348
            bPathExists = DeleteFileW(fileEntry->szFullPath);
1349
        else
1350
            bPathExists = SHELL_DeleteDirectoryW(lpFileOp->hwnd, fileEntry->szFullPath, FALSE);
1351

1352 1353 1354
        if (!bPathExists)
            return ERROR_PATH_NOT_FOUND;
    }
1355

1356 1357
    return ERROR_SUCCESS;
}
1358

1359 1360 1361 1362
static void move_dir_to_dir(LPSHFILEOPSTRUCTW lpFileOp, FILE_ENTRY *feFrom, LPWSTR szDestPath)
{
    WCHAR szFrom[MAX_PATH], szTo[MAX_PATH];
    SHFILEOPSTRUCTW fileOp;
1363

1364
    static const WCHAR wildCardFiles[] = {'*','.','*',0};
1365

1366 1367
    if (IsDotDir(feFrom->szFilename))
        return;
1368

1369
    SHNotifyCreateDirectoryW(szDestPath, NULL);
1370

1371
    PathCombineW(szFrom, feFrom->szFullPath, wildCardFiles);
1372
    szFrom[lstrlenW(szFrom) + 1] = '\0';
1373

1374 1375
    lstrcpyW(szTo, szDestPath);
    szTo[lstrlenW(szDestPath) + 1] = '\0';
1376

1377 1378 1379
    memcpy(&fileOp, lpFileOp, sizeof(fileOp));
    fileOp.pFrom = szFrom;
    fileOp.pTo = szTo;
1380

1381 1382
    SHFileOperationW(&fileOp);
}
1383

1384 1385 1386 1387
/* moves a file or directory to another directory */
static void move_to_dir(LPSHFILEOPSTRUCTW lpFileOp, FILE_ENTRY *feFrom, FILE_ENTRY *feTo)
{
    WCHAR szDestPath[MAX_PATH];
1388

1389
    PathCombineW(szDestPath, feTo->szFullPath, feFrom->szFilename);
1390

1391
    if (IsAttribFile(feFrom->attributes))
1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439
        SHNotifyMoveFileW(feFrom->szFullPath, szDestPath);
    else if (!(lpFileOp->fFlags & FOF_FILESONLY && feFrom->bFromWildcard))
        move_dir_to_dir(lpFileOp, feFrom, szDestPath);
}

/* the FO_MOVE operation */
static HRESULT move_files(LPSHFILEOPSTRUCTW lpFileOp, FILE_LIST *flFrom, FILE_LIST *flTo)
{
    DWORD i;
    FILE_ENTRY *entryToMove;
    FILE_ENTRY *fileDest;

    if (!flFrom->dwNumFiles || !flTo->dwNumFiles)
        return ERROR_CANCELLED;

    if (!(lpFileOp->fFlags & FOF_MULTIDESTFILES) &&
        flTo->dwNumFiles > 1 && flFrom->dwNumFiles > 1)
    {
        return ERROR_CANCELLED;
    }

    if (!(lpFileOp->fFlags & FOF_MULTIDESTFILES) &&
        !flFrom->bAnyDirectories &&
        flFrom->dwNumFiles > flTo->dwNumFiles)
    {
        return ERROR_CANCELLED;
    }

    if (!PathFileExistsW(flTo->feFiles[0].szDirectory))
        return ERROR_CANCELLED;

    if ((lpFileOp->fFlags & FOF_MULTIDESTFILES) &&
        flFrom->dwNumFiles != flTo->dwNumFiles)
    {
        return ERROR_CANCELLED;
    }

    fileDest = &flTo->feFiles[0];
    for (i = 0; i < flFrom->dwNumFiles; i++)
    {
        entryToMove = &flFrom->feFiles[i];

        if (lpFileOp->fFlags & FOF_MULTIDESTFILES)
            fileDest = &flTo->feFiles[i];

        if (!PathFileExistsW(fileDest->szDirectory))
            return ERROR_CANCELLED;

1440
        if (fileDest->bExists && IsAttribDir(fileDest->attributes))
1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477
            move_to_dir(lpFileOp, entryToMove, fileDest);
        else
            SHNotifyMoveFileW(entryToMove->szFullPath, fileDest->szFullPath);
    }

    return ERROR_SUCCESS;
}

/* the FO_RENAME files */
static HRESULT rename_files(LPSHFILEOPSTRUCTW lpFileOp, FILE_LIST *flFrom, FILE_LIST *flTo)
{
    FILE_ENTRY *feFrom;
    FILE_ENTRY *feTo;

    if (flFrom->dwNumFiles != 1)
        return ERROR_GEN_FAILURE;

    if (flTo->dwNumFiles != 1)
        return ERROR_CANCELLED;

    feFrom = &flFrom->feFiles[0];
    feTo= &flTo->feFiles[0];

    /* fail if destination doesn't exist */
    if (!feFrom->bExists)
        return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND;

    /* fail if destination already exists */
    if (feTo->bExists)
        return ERROR_ALREADY_EXISTS;

    return SHNotifyMoveFileW(feFrom->szFullPath, feTo->szFullPath);
}

/* alert the user if an unsupported flag is used */
static void check_flags(FILEOP_FLAGS fFlags)
{
1478
    WORD wUnsupportedFlags = FOF_NO_CONNECTED_ELEMENTS |
1479
        FOF_NOCOPYSECURITYATTRIBS | FOF_NORECURSEREPARSE |
1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492
        FOF_RENAMEONCOLLISION | FOF_WANTMAPPINGHANDLE;

    if (fFlags & wUnsupportedFlags)
        FIXME("Unsupported flags: %04x\n", fFlags);
}

/*************************************************************************
 * SHFileOperationW          [SHELL32.@]
 *
 * See SHFileOperationA
 */
int WINAPI SHFileOperationW(LPSHFILEOPSTRUCTW lpFileOp)
{
1493
    FILE_OPERATION op;
1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510
    FILE_LIST flFrom, flTo;
    int ret = 0;

    if (!lpFileOp)
        return ERROR_INVALID_PARAMETER;

    check_flags(lpFileOp->fFlags);

    ZeroMemory(&flFrom, sizeof(FILE_LIST));
    ZeroMemory(&flTo, sizeof(FILE_LIST));

    if ((ret = parse_file_list(&flFrom, lpFileOp->pFrom)))
        return ret;

    if (lpFileOp->wFunc != FO_DELETE)
        parse_file_list(&flTo, lpFileOp->pTo);

1511 1512 1513 1514
    ZeroMemory(&op, sizeof(op));
    op.req = lpFileOp;
    op.bManyItems = (flFrom.dwNumFiles > 1);

1515 1516 1517
    switch (lpFileOp->wFunc)
    {
        case FO_COPY:
1518
            ret = copy_files(&op, &flFrom, &flTo);
1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542
            break;
        case FO_DELETE:
            ret = delete_files(lpFileOp, &flFrom);
            break;
        case FO_MOVE:
            ret = move_files(lpFileOp, &flFrom, &flTo);
            break;
        case FO_RENAME:
            ret = rename_files(lpFileOp, &flFrom, &flTo);
            break;
        default:
            ret = ERROR_INVALID_PARAMETER;
            break;
    }

    destroy_file_list(&flFrom);

    if (lpFileOp->wFunc != FO_DELETE)
        destroy_file_list(&flTo);

    if (ret == ERROR_CANCELLED)
        lpFileOp->fAnyOperationsAborted = TRUE;
    
    return ret;
1543 1544
}

1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555
#define SHDSA_GetItemCount(hdsa) (*(int*)(hdsa))

/*************************************************************************
 * SHFreeNameMappings      [shell32.246]
 *
 * Free the mapping handle returned by SHFileoperation if FOF_WANTSMAPPINGHANDLE
 * was specified.
 *
 * PARAMS
 *  hNameMapping [I] handle to the name mappings used during renaming of files
 *
1556 1557
 * RETURNS
 *  Nothing
1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575
 */
void WINAPI SHFreeNameMappings(HANDLE hNameMapping)
{
	if (hNameMapping)
	{
	  int i = SHDSA_GetItemCount((HDSA)hNameMapping) - 1;

	  for (; i>= 0; i--)
	  {
	    LPSHNAMEMAPPINGW lp = DSA_GetItemPtr((HDSA)hNameMapping, i);

	    SHFree(lp->pszOldPath);
	    SHFree(lp->pszNewPath);
	  }
	  DSA_Destroy((HDSA)hNameMapping);
	}
}

1576
/*************************************************************************
1577 1578 1579 1580 1581 1582 1583 1584 1585 1586
 * SheGetDirA [SHELL32.@]
 *
 */
HRESULT WINAPI SheGetDirA(LPSTR u, LPSTR v)
{   FIXME("%p %p stub\n",u,v);
    return 0;
}

/*************************************************************************
 * SheGetDirW [SHELL32.@]
1587 1588 1589 1590 1591 1592 1593 1594
 *
 */
HRESULT WINAPI SheGetDirW(LPWSTR u, LPWSTR v)
{	FIXME("%p %p stub\n",u,v);
	return 0;
}

/*************************************************************************
1595 1596 1597 1598 1599 1600 1601 1602 1603 1604
 * SheChangeDirA [SHELL32.@]
 *
 */
HRESULT WINAPI SheChangeDirA(LPSTR u)
{   FIXME("(%s),stub\n",debugstr_a(u));
    return 0;
}

/*************************************************************************
 * SheChangeDirW [SHELL32.@]
1605 1606 1607 1608 1609 1610 1611
 *
 */
HRESULT WINAPI SheChangeDirW(LPWSTR u)
{	FIXME("(%s),stub\n",debugstr_w(u));
	return 0;
}

1612 1613 1614 1615 1616 1617 1618
/*************************************************************************
 * IsNetDrive			[SHELL32.66]
 */
BOOL WINAPI IsNetDrive(DWORD drive)
{
	char root[4];
	strcpy(root, "A:\\");
1619
	root[0] += (char)drive;
1620 1621
	return (GetDriveTypeA(root) == DRIVE_REMOTE);
}
1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632


/*************************************************************************
 * RealDriveType                [SHELL32.524]
 */
INT WINAPI RealDriveType(INT drive, BOOL bQueryNet)
{
    char root[] = "A:\\";
    root[0] += (char)drive;
    return GetDriveTypeA(root);
}