xref: /reactos/dll/win32/shell32/shlfileop.cpp (revision 3299e96e)
1 /*
2  * SHFileOperation
3  *
4  * Copyright 2000 Juergen Schmied
5  * Copyright 2002 Andriy Palamarchuk
6  * Copyright 2004 Dietrich Teickner (from Odin)
7  * Copyright 2004 Rolf Kalbermatter
8  * Copyright 2019-2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 2.1 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23  */
24 
25 #include "precomp.h"
26 
27 WINE_DEFAULT_DEBUG_CHANNEL(shell);
28 
29 #define IsAttrib(x, y)  ((INVALID_FILE_ATTRIBUTES != (x)) && ((x) & (y)))
30 #define IsAttribFile(x) (!((x) & FILE_ATTRIBUTE_DIRECTORY))
31 #define IsAttribDir(x)  IsAttrib(x, FILE_ATTRIBUTE_DIRECTORY)
32 #define IsDotDir(x)     ((x[0] == '.') && ((x[1] == 0) || ((x[1] == '.') && (x[2] == 0))))
33 
34 #define FO_MASK         0xF
35 
36 #define NEW_FILENAME_ON_COPY_TRIES 100
37 
38 typedef struct
39 {
40     SHFILEOPSTRUCTW *req;
41     DWORD dwYesToAllMask;
42     BOOL bManyItems;
43     BOOL bCancelled;
44     IProgressDialog *progress;
45     ULARGE_INTEGER completedSize;
46     ULARGE_INTEGER totalSize;
47     WCHAR szBuilderString[50];
48 } FILE_OPERATION;
49 
50 #define ERROR_SHELL_INTERNAL_FILE_NOT_FOUND 1026
51 
52 typedef struct
53 {
54     DWORD attributes;
55     LPWSTR szDirectory;
56     LPWSTR szFilename;
57     LPWSTR szFullPath;
58     BOOL bFromWildcard;
59     BOOL bFromRelative;
60     BOOL bExists;
61 } FILE_ENTRY;
62 
63 typedef struct
64 {
65     FILE_ENTRY *feFiles;
66     DWORD num_alloc;
67     DWORD dwNumFiles;
68     BOOL bAnyFromWildcard;
69     BOOL bAnyDirectories;
70     BOOL bAnyDontExist;
71 } FILE_LIST;
72 
73 static DWORD SHNotifyCreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec);
74 static DWORD SHNotifyRemoveDirectoryW(LPCWSTR path);
75 static DWORD SHNotifyDeleteFileW(FILE_OPERATION *op, LPCWSTR path);
76 static DWORD SHNotifyMoveFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL isdir);
77 static DWORD SHNotifyCopyFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists);
78 static DWORD SHFindAttrW(LPCWSTR pName, BOOL fileOnly);
79 static HRESULT copy_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, FILE_LIST *flTo);
80 static DWORD move_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, const FILE_LIST *flTo);
81 
82 DWORD WINAPI _FileOpCountManager(FILE_OPERATION *op, const FILE_LIST *flFrom);
83 static BOOL _FileOpCount(FILE_OPERATION *op, LPWSTR pwszBuf, BOOL bFolder, DWORD *ticks);
84 
85 /* Confirm dialogs with an optional "Yes To All" as used in file operations confirmations
86  */
87 struct confirm_msg_info
88 {
89     LPWSTR lpszText;
90     LPWSTR lpszCaption;
91     HICON hIcon;
92     BOOL bYesToAll;
93 };
94 
95 /* as some buttons may be hidden and the dialog height may change we may need
96  * to move the controls */
97 static void confirm_msg_move_button(HWND hDlg, INT iId, INT *xPos, INT yOffset, BOOL bShow)
98 {
99     HWND hButton = GetDlgItem(hDlg, iId);
100     RECT r;
101 
102     if (bShow)
103     {
104         POINT pt;
105         int width;
106 
107         GetWindowRect(hButton, &r);
108         width = r.right - r.left;
109         pt.x = r.left;
110         pt.y = r.top;
111         ScreenToClient(hDlg, &pt);
112         MoveWindow(hButton, *xPos - width, pt.y - yOffset, width, r.bottom - r.top, FALSE);
113         *xPos -= width + 5;
114     }
115     else
116         ShowWindow(hButton, SW_HIDE);
117 }
118 
119 /* Note: we paint the text manually and don't use the static control to make
120  * sure the text has the same height as the one computed in WM_INITDIALOG
121  */
122 static INT_PTR ConfirmMsgBox_Paint(HWND hDlg)
123 {
124     PAINTSTRUCT ps;
125     HFONT hOldFont;
126     RECT r;
127     HDC hdc;
128 
129     BeginPaint(hDlg, &ps);
130     hdc = ps.hdc;
131     SetBkMode(hdc, TRANSPARENT);
132 
133     GetClientRect(GetDlgItem(hDlg, IDC_YESTOALL_MESSAGE), &r);
134     /* this will remap the rect to dialog coords */
135     MapWindowPoints(GetDlgItem(hDlg, IDC_YESTOALL_MESSAGE), hDlg, (LPPOINT)&r, 2);
136     hOldFont = (HFONT)SelectObject(hdc, (HFONT)SendDlgItemMessageW(hDlg, IDC_YESTOALL_MESSAGE, WM_GETFONT, 0, 0));
137     DrawTextW(hdc, (LPWSTR)GetPropW(hDlg, L"WINE_CONFIRM"), -1, &r, DT_NOPREFIX | DT_PATH_ELLIPSIS | DT_WORDBREAK);
138     SelectObject(hdc, hOldFont);
139     EndPaint(hDlg, &ps);
140 
141     return TRUE;
142 }
143 
144 static INT_PTR ConfirmMsgBox_Init(HWND hDlg, LPARAM lParam)
145 {
146     struct confirm_msg_info *info = (struct confirm_msg_info *)lParam;
147     INT xPos, yOffset;
148     int width, height;
149     HFONT hOldFont;
150     HDC hdc;
151     RECT r;
152 
153     SetWindowTextW(hDlg, info->lpszCaption);
154     ShowWindow(GetDlgItem(hDlg, IDC_YESTOALL_MESSAGE), SW_HIDE);
155     SetPropW(hDlg, L"WINE_CONFIRM", info->lpszText);
156     SendDlgItemMessageW(hDlg, IDC_YESTOALL_ICON, STM_SETICON, (WPARAM)info->hIcon, 0);
157 
158     /* compute the text height and resize the dialog */
159     GetClientRect(GetDlgItem(hDlg, IDC_YESTOALL_MESSAGE), &r);
160     hdc = GetDC(hDlg);
161     yOffset = r.bottom;
162     hOldFont = (HFONT)SelectObject(hdc, (HFONT)SendDlgItemMessageW(hDlg, IDC_YESTOALL_MESSAGE, WM_GETFONT, 0, 0));
163     DrawTextW(hdc, info->lpszText, -1, &r, DT_NOPREFIX | DT_PATH_ELLIPSIS | DT_WORDBREAK | DT_CALCRECT);
164     SelectObject(hdc, hOldFont);
165     yOffset -= r.bottom;
166     yOffset = min(yOffset, 35);  /* don't make the dialog too small */
167     ReleaseDC(hDlg, hdc);
168 
169     GetClientRect(hDlg, &r);
170     xPos = r.right - 7;
171     GetWindowRect(hDlg, &r);
172     width = r.right - r.left;
173     height = r.bottom - r.top - yOffset;
174     MoveWindow(hDlg, (GetSystemMetrics(SM_CXSCREEN) - width)/2,
175         (GetSystemMetrics(SM_CYSCREEN) - height)/2, width, height, FALSE);
176 
177     confirm_msg_move_button(hDlg, IDCANCEL,     &xPos, yOffset, info->bYesToAll);
178     confirm_msg_move_button(hDlg, IDNO,         &xPos, yOffset, TRUE);
179     confirm_msg_move_button(hDlg, IDC_YESTOALL, &xPos, yOffset, info->bYesToAll);
180     confirm_msg_move_button(hDlg, IDYES,        &xPos, yOffset, TRUE);
181 
182     return TRUE;
183 }
184 
185 static INT_PTR CALLBACK ConfirmMsgBoxProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
186 {
187     switch (uMsg)
188     {
189         case WM_INITDIALOG:
190             return ConfirmMsgBox_Init(hDlg, lParam);
191         case WM_PAINT:
192             return ConfirmMsgBox_Paint(hDlg);
193         case WM_COMMAND:
194             EndDialog(hDlg, wParam);
195             break;
196         case WM_CLOSE:
197             EndDialog(hDlg, IDCANCEL);
198             break;
199     }
200     return FALSE;
201 }
202 
203 int SHELL_ConfirmMsgBox(HWND hWnd, LPWSTR lpszText, LPWSTR lpszCaption, HICON hIcon, BOOL bYesToAll)
204 {
205     struct confirm_msg_info info;
206 
207     info.lpszText = lpszText;
208     info.lpszCaption = lpszCaption;
209     info.hIcon = hIcon;
210     info.bYesToAll = bYesToAll;
211     return DialogBoxParamW(shell32_hInstance, MAKEINTRESOURCEW(IDD_YESTOALL_MSGBOX), hWnd, ConfirmMsgBoxProc, (LPARAM)&info);
212 }
213 
214 /* confirmation dialogs content */
215 typedef struct
216 {
217     HINSTANCE hIconInstance;
218     UINT icon_resource_id;
219     UINT caption_resource_id, text_resource_id;
220 } SHELL_ConfirmIDstruc;
221 
222 static BOOL SHELL_ConfirmIDs(int nKindOfDialog, SHELL_ConfirmIDstruc *ids)
223 {
224     ids->hIconInstance = shell32_hInstance;
225     switch (nKindOfDialog)
226     {
227         case ASK_DELETE_FILE:
228             ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
229             ids->caption_resource_id  = IDS_DELETEITEM_CAPTION;
230             ids->text_resource_id  = IDS_DELETEITEM_TEXT;
231             return TRUE;
232 
233         case ASK_DELETE_FOLDER:
234             ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
235             ids->caption_resource_id  = IDS_DELETEFOLDER_CAPTION;
236             ids->text_resource_id  = IDS_DELETEITEM_TEXT;
237             return TRUE;
238 
239         case ASK_DELETE_MULTIPLE_ITEM:
240             ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
241             ids->caption_resource_id  = IDS_DELETEITEM_CAPTION;
242             ids->text_resource_id  = IDS_DELETEMULTIPLE_TEXT;
243             return TRUE;
244 
245         case ASK_TRASH_FILE:
246             ids->icon_resource_id = IDI_SHELL_TRASH_FILE;
247             ids->caption_resource_id = IDS_DELETEITEM_CAPTION;
248             ids->text_resource_id = IDS_TRASHITEM_TEXT;
249             return TRUE;
250 
251         case ASK_TRASH_FOLDER:
252             ids->icon_resource_id = IDI_SHELL_TRASH_FILE;
253             ids->caption_resource_id = IDS_DELETEFOLDER_CAPTION;
254             ids->text_resource_id = IDS_TRASHFOLDER_TEXT;
255             return TRUE;
256 
257         case ASK_TRASH_MULTIPLE_ITEM:
258             ids->icon_resource_id = IDI_SHELL_TRASH_FILE;
259             ids->caption_resource_id = IDS_DELETEITEM_CAPTION;
260             ids->text_resource_id = IDS_TRASHMULTIPLE_TEXT;
261             return TRUE;
262 
263         case ASK_CANT_TRASH_ITEM:
264             ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
265             ids->caption_resource_id  = IDS_DELETEITEM_CAPTION;
266             ids->text_resource_id  = IDS_CANTTRASH_TEXT;
267             return TRUE;
268 
269         case ASK_DELETE_SELECTED:
270             ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
271             ids->caption_resource_id  = IDS_DELETEITEM_CAPTION;
272             ids->text_resource_id  = IDS_DELETESELECTED_TEXT;
273             return TRUE;
274 
275       case ASK_OVERWRITE_FILE:
276             ids->icon_resource_id = IDI_SHELL_FOLDER_MOVE2;
277             ids->caption_resource_id  = IDS_OVERWRITEFILE_CAPTION;
278             ids->text_resource_id  = IDS_OVERWRITEFILE_TEXT;
279             return TRUE;
280 
281       case ASK_OVERWRITE_FOLDER:
282             ids->icon_resource_id = IDI_SHELL_FOLDER_MOVE2;
283             ids->caption_resource_id  = IDS_OVERWRITEFILE_CAPTION;
284             ids->text_resource_id  = IDS_OVERWRITEFOLDER_TEXT;
285             return TRUE;
286 
287         default:
288             FIXME(" Unhandled nKindOfDialog %d stub\n", nKindOfDialog);
289     }
290     return FALSE;
291 }
292 
293 static BOOL SHELL_ConfirmDialogW(HWND hWnd, int nKindOfDialog, LPCWSTR szDir, FILE_OPERATION *op)
294 {
295     WCHAR szCaption[255], szText[255], szBuffer[MAX_PATH + 256];
296     SHELL_ConfirmIDstruc ids;
297     DWORD_PTR args[1];
298     HICON hIcon;
299     int ret;
300 
301     assert(nKindOfDialog >= 0 && nKindOfDialog < 32);
302     if (op && (op->dwYesToAllMask & (1 << nKindOfDialog)))
303         return TRUE;
304 
305     if (!SHELL_ConfirmIDs(nKindOfDialog, &ids)) return FALSE;
306 
307     LoadStringW(shell32_hInstance, ids.caption_resource_id, szCaption, sizeof(szCaption)/sizeof(WCHAR));
308     LoadStringW(shell32_hInstance, ids.text_resource_id, szText, sizeof(szText)/sizeof(WCHAR));
309 
310     args[0] = (DWORD_PTR)szDir;
311     FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ARGUMENT_ARRAY,
312                    szText, 0, 0, szBuffer, sizeof(szBuffer), (va_list*)args);
313     hIcon = LoadIconW(ids.hIconInstance, (LPWSTR)MAKEINTRESOURCE(ids.icon_resource_id));
314 
315     ret = SHELL_ConfirmMsgBox(hWnd, szBuffer, szCaption, hIcon, op && op->bManyItems);
316     if (op)
317     {
318         if (ret == IDC_YESTOALL)
319         {
320             op->dwYesToAllMask |= (1 << nKindOfDialog);
321             ret = IDYES;
322         }
323         if (ret == IDCANCEL)
324             op->bCancelled = TRUE;
325         if (ret != IDYES)
326             op->req->fAnyOperationsAborted = TRUE;
327     }
328     return ret == IDYES;
329 }
330 
331 BOOL SHELL_ConfirmYesNoW(HWND hWnd, int nKindOfDialog, LPCWSTR szDir)
332 {
333     return SHELL_ConfirmDialogW(hWnd, nKindOfDialog, szDir, NULL);
334 }
335 
336 static DWORD SHELL32_AnsiToUnicodeBuf(LPCSTR aPath, LPWSTR *wPath, DWORD minChars)
337 {
338     DWORD len = MultiByteToWideChar(CP_ACP, 0, aPath, -1, NULL, 0);
339 
340     if (len < minChars)
341       len = minChars;
342 
343     *wPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
344     if (*wPath)
345     {
346       MultiByteToWideChar(CP_ACP, 0, aPath, -1, *wPath, len);
347       return NO_ERROR;
348     }
349     return E_OUTOFMEMORY;
350 }
351 
352 static void SHELL32_FreeUnicodeBuf(LPWSTR wPath)
353 {
354     HeapFree(GetProcessHeap(), 0, wPath);
355 }
356 
357 EXTERN_C HRESULT WINAPI SHIsFileAvailableOffline(LPCWSTR path, LPDWORD status)
358 {
359     FIXME("(%s, %p) stub\n", debugstr_w(path), status);
360     return E_FAIL;
361 }
362 
363 /**************************************************************************
364  * SHELL_DeleteDirectory()  [internal]
365  *
366  * Asks for confirmation when bShowUI is true and deletes the directory and
367  * all its subdirectories and files if necessary.
368  */
369 BOOL SHELL_DeleteDirectoryW(FILE_OPERATION *op, LPCWSTR pszDir, BOOL bShowUI)
370 {
371     BOOL    ret = TRUE;
372     HANDLE  hFind;
373     WIN32_FIND_DATAW wfd;
374     WCHAR   szTemp[MAX_PATH];
375 
376     /* Make sure the directory exists before eventually prompting the user */
377     PathCombineW(szTemp, pszDir, L"*");
378     hFind = FindFirstFileW(szTemp, &wfd);
379     if (hFind == INVALID_HANDLE_VALUE)
380       return FALSE;
381 
382     if (!bShowUI || (ret = SHELL_ConfirmDialogW(op->req->hwnd, ASK_DELETE_FOLDER, pszDir, NULL)))
383     {
384         do
385         {
386             if (IsDotDir(wfd.cFileName))
387                 continue;
388             PathCombineW(szTemp, pszDir, wfd.cFileName);
389             if (FILE_ATTRIBUTE_DIRECTORY & wfd.dwFileAttributes)
390                 ret = SHELL_DeleteDirectoryW(op, szTemp, FALSE);
391             else
392                 ret = (SHNotifyDeleteFileW(op, szTemp) == ERROR_SUCCESS);
393 
394             if (op->progress != NULL)
395                 op->bCancelled |= op->progress->HasUserCancelled();
396         } while (ret && FindNextFileW(hFind, &wfd) && !op->bCancelled);
397     }
398     FindClose(hFind);
399     if (ret)
400         ret = (SHNotifyRemoveDirectoryW(pszDir) == ERROR_SUCCESS);
401     return ret;
402 }
403 
404 /**************************************************************************
405  * Win32CreateDirectory      [SHELL32.93]
406  *
407  * Creates a directory. Also triggers a change notify if one exists.
408  *
409  * PARAMS
410  *  path       [I]   path to directory to create
411  *
412  * RETURNS
413  *  TRUE if successful, FALSE otherwise
414  */
415 
416 static DWORD SHNotifyCreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec)
417 {
418     TRACE("(%s, %p)\n", debugstr_w(path), sec);
419 
420     if (CreateDirectoryW(path, sec))
421     {
422       SHChangeNotify(SHCNE_MKDIR, SHCNF_PATHW, path, NULL);
423       return ERROR_SUCCESS;
424     }
425     return GetLastError();
426 }
427 
428 /**********************************************************************/
429 
430 EXTERN_C BOOL WINAPI Win32CreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec)
431 {
432     return (SHNotifyCreateDirectoryW(path, sec) == ERROR_SUCCESS);
433 }
434 
435 /************************************************************************
436  * Win32RemoveDirectory      [SHELL32.94]
437  *
438  * Deletes a directory. Also triggers a change notify if one exists.
439  *
440  * PARAMS
441  *  path       [I]   path to directory to delete
442  *
443  * RETURNS
444  *  TRUE if successful, FALSE otherwise
445  */
446 static DWORD SHNotifyRemoveDirectoryW(LPCWSTR path)
447 {
448     BOOL ret;
449     TRACE("(%s)\n", debugstr_w(path));
450 
451     ret = RemoveDirectoryW(path);
452     if (!ret)
453     {
454       /* Directory may be write protected */
455       DWORD dwAttr = GetFileAttributesW(path);
456       if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY))
457         if (SetFileAttributesW(path, dwAttr & ~FILE_ATTRIBUTE_READONLY))
458           ret = RemoveDirectoryW(path);
459     }
460     if (ret)
461     {
462       SHChangeNotify(SHCNE_RMDIR, SHCNF_PATHW, path, NULL);
463       return ERROR_SUCCESS;
464     }
465     return GetLastError();
466 }
467 
468 /***********************************************************************/
469 
470 EXTERN_C BOOL WINAPI Win32RemoveDirectoryW(LPCWSTR path)
471 {
472     return (SHNotifyRemoveDirectoryW(path) == ERROR_SUCCESS);
473 }
474 
475 static void _SetOperationTitle(FILE_OPERATION *op) {
476     if (op->progress == NULL)
477         return;
478     WCHAR szTitle[50], szPreflight[50];
479     UINT animation_id = NULL;
480 
481     switch (op->req->wFunc)
482     {
483         case FO_COPY:
484             LoadStringW(shell32_hInstance, IDS_FILEOOP_COPYING, szTitle, sizeof(szTitle)/sizeof(WCHAR));
485             LoadStringW(shell32_hInstance, IDS_FILEOOP_FROM_TO, op->szBuilderString, sizeof( op->szBuilderString)/sizeof(WCHAR));
486             animation_id = IDA_SHELL_COPY;
487             break;
488         case FO_DELETE:
489             LoadStringW(shell32_hInstance, IDS_FILEOOP_DELETING, szTitle, sizeof(szTitle)/sizeof(WCHAR));
490             LoadStringW(shell32_hInstance, IDS_FILEOOP_FROM, op->szBuilderString, sizeof( op->szBuilderString)/sizeof(WCHAR));
491             animation_id = IDA_SHELL_DELETE;
492             break;
493         case FO_MOVE:
494             LoadStringW(shell32_hInstance, IDS_FILEOOP_MOVING, szTitle, sizeof(szTitle)/sizeof(WCHAR));
495             LoadStringW(shell32_hInstance, IDS_FILEOOP_FROM_TO, op->szBuilderString, sizeof( op->szBuilderString)/sizeof(WCHAR));
496             animation_id = IDA_SHELL_COPY;
497             break;
498         default:
499             return;
500     }
501     LoadStringW(shell32_hInstance, IDS_FILEOOP_PREFLIGHT, szPreflight, sizeof(szPreflight)/sizeof(WCHAR));
502 
503     op->progress->SetTitle(szTitle);
504     op->progress->SetLine(1, szPreflight, false, NULL);
505     op->progress->SetAnimation(shell32_hInstance, animation_id);
506 }
507 
508 static void _SetOperationTexts(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest) {
509     if (op->progress == NULL || src == NULL)
510         return;
511     LPWSTR fileSpecS, pathSpecS, fileSpecD, pathSpecD;
512     WCHAR szFolderS[50], szFolderD[50], szFinalString[260];
513 
514     DWORD_PTR args[2];
515 
516     fileSpecS = (pathSpecS = (LPWSTR) src);
517     fileSpecD = (pathSpecD = (LPWSTR) dest);
518 
519     // March across the string to get the file path and it's parent dir.
520     for (LPWSTR ptr = (LPWSTR) src; *ptr; ptr++) {
521         if (*ptr == '\\') {
522             pathSpecS = fileSpecS;
523             fileSpecS = ptr+1;
524         }
525     }
526     lstrcpynW(szFolderS, pathSpecS, min(50, fileSpecS - pathSpecS));
527     args[0] = (DWORD_PTR) szFolderS;
528 
529     switch (op->req->wFunc)
530     {
531         case FO_COPY:
532         case FO_MOVE:
533             if (dest == NULL)
534                 return;
535             for (LPWSTR ptr = (LPWSTR) dest; *ptr; ptr++) {
536                 if (*ptr == '\\') {
537                     pathSpecD = fileSpecD;
538                     fileSpecD = ptr + 1;
539                 }
540             }
541             lstrcpynW(szFolderD, pathSpecD, min(50, fileSpecD - pathSpecD));
542             args[1] = (DWORD_PTR) szFolderD;
543             break;
544         case FO_DELETE:
545             break;
546         default:
547             return;
548     }
549 
550     FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ARGUMENT_ARRAY,
551                    op->szBuilderString, 0, 0, szFinalString, sizeof(szFinalString), (va_list*)args);
552 
553     op->progress->SetLine(1, fileSpecS, false, NULL);
554     op->progress->SetLine(2, szFinalString, false, NULL);
555 }
556 
557 
558 DWORD CALLBACK SHCopyProgressRoutine(
559     LARGE_INTEGER TotalFileSize,
560     LARGE_INTEGER TotalBytesTransferred,
561     LARGE_INTEGER StreamSize,
562     LARGE_INTEGER StreamBytesTransferred,
563     DWORD dwStreamNumber,
564     DWORD dwCallbackReason,
565     HANDLE hSourceFile,
566     HANDLE hDestinationFile,
567     LPVOID lpData
568 ) {
569     FILE_OPERATION *op = (FILE_OPERATION *) lpData;
570 
571     if (op->progress) {
572         /*
573          * This is called at the start of each file. To keop less state,
574          * I'm adding the file to the completed size here, and the re-subtracting
575          * it when drawing the progress bar.
576          */
577         if (dwCallbackReason & CALLBACK_STREAM_SWITCH)
578             op->completedSize.QuadPart += TotalFileSize.QuadPart;
579 
580         op->progress->SetProgress64(op->completedSize.QuadPart -
581                                     TotalFileSize.QuadPart +
582                                     TotalBytesTransferred.QuadPart
583                                   , op->totalSize.QuadPart);
584 
585 
586         op->bCancelled = op->progress->HasUserCancelled();
587     }
588 
589     return 0;
590 }
591 
592 
593 /************************************************************************
594  * SHNotifyDeleteFileW          [internal]
595  *
596  * Deletes a file. Also triggers a change notify if one exists.
597  *
598  * PARAMS
599  *  op         [I]   File Operation context
600  *  path       [I]   path to source file to move
601  *
602  * RETURNS
603  *  ERORR_SUCCESS if successful
604  */
605 static DWORD SHNotifyDeleteFileW(FILE_OPERATION *op, LPCWSTR path)
606 {
607     BOOL ret;
608 
609     TRACE("(%s)\n", debugstr_w(path));
610 
611     _SetOperationTexts(op, path, NULL);
612 
613     LARGE_INTEGER FileSize;
614     FileSize.QuadPart = 0;
615 
616     WIN32_FIND_DATAW wfd;
617     HANDLE hFile = FindFirstFileW(path, &wfd);
618     if (hFile != INVALID_HANDLE_VALUE && IsAttribFile(wfd.dwFileAttributes)) {
619         ULARGE_INTEGER tmp;
620         tmp.u.LowPart  = wfd.nFileSizeLow;
621         tmp.u.HighPart = wfd.nFileSizeHigh;
622         FileSize.QuadPart = tmp.QuadPart;
623     }
624     FindClose(hFile);
625 
626     ret = DeleteFileW(path);
627     if (!ret)
628     {
629       /* File may be write protected or a system file */
630       DWORD dwAttr = GetFileAttributesW(path);
631       if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))
632         if (SetFileAttributesW(path, dwAttr & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)))
633             ret = DeleteFileW(path);
634     }
635     if (ret)
636     {
637         // Bit of a hack to make the progress bar move. We don't have progress inside the file, so inform when done.
638         SHCopyProgressRoutine(FileSize, FileSize, FileSize, FileSize, 0, CALLBACK_STREAM_SWITCH, NULL, NULL, op);
639         SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, path, NULL);
640         return ERROR_SUCCESS;
641     }
642     return GetLastError();
643 }
644 
645 /************************************************************************
646  * Win32DeleteFile           [SHELL32.164]
647  *
648  * Deletes a file. Also triggers a change notify if one exists.
649  *
650  * PARAMS
651  *  path       [I]   path to file to delete
652  *
653  * RETURNS
654  *  TRUE if successful, FALSE otherwise
655  */
656 EXTERN_C DWORD WINAPI Win32DeleteFileW(LPCWSTR path)
657 {
658     return (SHNotifyDeleteFileW(NULL, path) == ERROR_SUCCESS);
659 }
660 
661 /************************************************************************
662  * CheckForError          [internal]
663  *
664  * Show message box if operation failed
665  *
666  * PARAMS
667  *  op         [I]   File Operation context
668  *  error      [I]   Error code
669  *  src        [I]   Source file full name
670  *
671  */
672 static DWORD CheckForError(FILE_OPERATION *op, DWORD error, LPCWSTR src)
673 {
674     CStringW strTitle, strMask, strText;
675     LPWSTR lpMsgBuffer;
676 
677     if (error == ERROR_SUCCESS || (op->req->fFlags & (FOF_NOERRORUI | FOF_SILENT)))
678         goto exit;
679 
680     strTitle.LoadStringW(op->req->wFunc == FO_COPY ? IDS_COPYERRORTITLE : IDS_MOVEERRORTITLE);
681 
682     FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
683                    NULL,
684                    error,
685                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
686                    (LPWSTR)&lpMsgBuffer,
687                    0,
688                    NULL);
689 
690     strText.Format(op->req->wFunc == FO_COPY ? IDS_COPYERROR : IDS_MOVEERROR,
691                    PathFindFileNameW(src),
692                    lpMsgBuffer);
693 
694     MessageBoxW(op->req->hwnd, strText, strTitle, MB_ICONERROR);
695     LocalFree(lpMsgBuffer);
696 
697 exit:
698     return error;
699 }
700 
701 /************************************************************************
702  * SHNotifyMoveFile          [internal]
703  *
704  * Moves a file. Also triggers a change notify if one exists.
705  *
706  * PARAMS
707  *  op         [I]   File Operation context
708  *  src        [I]   path to source file to move
709  *  dest       [I]   path to target file to move to
710  *
711  * RETURNS
712  *  ERROR_SUCCESS if successful
713  */
714 static DWORD SHNotifyMoveFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL isdir)
715 {
716     BOOL ret;
717 
718     TRACE("(%s %s)\n", debugstr_w(src), debugstr_w(dest));
719 
720     _SetOperationTexts(op, src, dest);
721 
722     ret = MoveFileWithProgressW(src, dest, SHCopyProgressRoutine, op, MOVEFILE_REPLACE_EXISTING);
723 
724     /* MOVEFILE_REPLACE_EXISTING fails with dirs, so try MoveFile */
725     if (!ret)
726         ret = MoveFileW(src, dest);
727 
728     if (!ret)
729     {
730       DWORD dwAttr;
731 
732       dwAttr = SHFindAttrW(dest, FALSE);
733       if (INVALID_FILE_ATTRIBUTES == dwAttr)
734       {
735         /* Source file may be write protected or a system file */
736         dwAttr = GetFileAttributesW(src);
737         if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))
738             if (SetFileAttributesW(src, dwAttr & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)))
739                 ret = MoveFileW(src, dest);
740       }
741     }
742     if (ret)
743     {
744         SHChangeNotify(isdir ? SHCNE_MKDIR : SHCNE_CREATE, SHCNF_PATHW, dest, NULL);
745         SHChangeNotify(isdir ? SHCNE_RMDIR : SHCNE_DELETE, SHCNF_PATHW, src, NULL);
746         return ERROR_SUCCESS;
747     }
748 
749     return CheckForError(op, GetLastError(), src);
750 }
751 
752 static BOOL SHIsCdRom(LPCWSTR path)
753 {
754     WCHAR tmp[] = { L"A:\\" };
755 
756     if (!path || !path[0])
757         return FALSE;
758 
759     if (path[1] != UNICODE_NULL && path[1] != ':')
760         return FALSE;
761 
762     tmp[0] = path[0];
763 
764     return GetDriveTypeW(tmp) == DRIVE_CDROM;
765 }
766 
767 /************************************************************************
768  * SHNotifyCopyFile          [internal]
769  *
770  * Copies a file. Also triggers a change notify if one exists.
771  *
772  * PARAMS
773  *  src           [I]   path to source file to move
774  *  dest          [I]   path to target file to move to
775  *  bFailIfExists [I]   if TRUE, the target file will not be overwritten if
776  *                      a file with this name already exists
777  *
778  * RETURNS
779  *  ERROR_SUCCESS if successful
780  */
781 static DWORD SHNotifyCopyFileW(FILE_OPERATION *op, LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists)
782 {
783     BOOL ret;
784     DWORD attribs;
785 
786     TRACE("(%s %s %s)\n", debugstr_w(src), debugstr_w(dest), bFailIfExists ? "failIfExists" : "");
787 
788     _SetOperationTexts(op, src, dest);
789 
790     /* Destination file may already exist with read only attribute */
791     attribs = GetFileAttributesW(dest);
792     if (IsAttrib(attribs, FILE_ATTRIBUTE_READONLY))
793         SetFileAttributesW(dest, attribs & ~FILE_ATTRIBUTE_READONLY);
794 
795     if (GetFileAttributesW(dest) & FILE_ATTRIBUTE_READONLY)
796     {
797         SetFileAttributesW(dest, attribs & ~FILE_ATTRIBUTE_READONLY);
798         if (GetFileAttributesW(dest) & FILE_ATTRIBUTE_READONLY)
799         {
800             TRACE("[shell32, SHNotifyCopyFileW] STILL SHIT\n");
801         }
802     }
803 
804     ret = CopyFileExW(src, dest, SHCopyProgressRoutine, op, &op->bCancelled, bFailIfExists);
805     if (ret)
806     {
807         // We are copying from a CD-ROM volume, which is readonly
808         if (SHIsCdRom(src))
809         {
810             attribs = GetFileAttributesW(dest);
811             attribs &= ~FILE_ATTRIBUTE_READONLY;
812             SetFileAttributesW(dest, attribs);
813         }
814 
815         SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, dest, NULL);
816         return ERROR_SUCCESS;
817     }
818 
819     return CheckForError(op, GetLastError(), src);
820 }
821 
822 /*************************************************************************
823  * SHCreateDirectory         [SHELL32.165]
824  *
825  * This function creates a file system folder whose fully qualified path is
826  * given by path. If one or more of the intermediate folders do not exist,
827  * they will be created as well.
828  *
829  * PARAMS
830  *  hWnd       [I]
831  *  path       [I]   path of directory to create
832  *
833  * RETURNS
834  *  ERROR_SUCCESS or one of the following values:
835  *  ERROR_BAD_PATHNAME if the path is relative
836  *  ERROR_FILE_EXISTS when a file with that name exists
837  *  ERROR_PATH_NOT_FOUND can't find the path, probably invalid
838  *  ERROR_INVALID_NAME if the path contains invalid chars
839  *  ERROR_ALREADY_EXISTS when the directory already exists
840  *  ERROR_FILENAME_EXCED_RANGE if the filename was to long to process
841  *
842  * NOTES
843  *  exported by ordinal
844  *  Win9x exports ANSI
845  *  WinNT/2000 exports Unicode
846  */
847 int WINAPI SHCreateDirectory(HWND hWnd, LPCWSTR path)
848 {
849      return SHCreateDirectoryExW(hWnd, path, NULL);
850 }
851 
852 /*************************************************************************
853  * SHCreateDirectoryExA      [SHELL32.@]
854  *
855  * This function creates a file system folder whose fully qualified path is
856  * given by path. If one or more of the intermediate folders do not exist,
857  * they will be created as well.
858  *
859  * PARAMS
860  *  hWnd       [I]
861  *  path       [I]   path of directory to create
862  *  sec        [I]   security attributes to use or NULL
863  *
864  * RETURNS
865  *  ERROR_SUCCESS or one of the following values:
866  *  ERROR_BAD_PATHNAME or ERROR_PATH_NOT_FOUND if the path is relative
867  *  ERROR_INVALID_NAME if the path contains invalid chars
868  *  ERROR_FILE_EXISTS when a file with that name exists
869  *  ERROR_ALREADY_EXISTS when the directory already exists
870  *  ERROR_FILENAME_EXCED_RANGE if the filename was too long to process
871  *
872  *  FIXME: Not implemented yet;
873  *  SHCreateDirectoryEx also verifies that the files in the directory will be visible
874  *  if the path is a network path to deal with network drivers which might have a limited
875  *  but unknown maximum path length. If not:
876  *
877  *  If hWnd is set to a valid window handle, a message box is displayed warning
878  *  the user that the files may not be accessible. If the user chooses not to
879  *  proceed, the function returns ERROR_CANCELLED.
880  *
881  *  If hWnd is set to NULL, no user interface is displayed and the function
882  *  returns ERROR_CANCELLED.
883  */
884 int WINAPI SHCreateDirectoryExA(HWND hWnd, LPCSTR path, LPSECURITY_ATTRIBUTES sec)
885 {
886     LPWSTR wPath;
887     DWORD retCode;
888 
889     TRACE("(%s, %p)\n", debugstr_a(path), sec);
890 
891     retCode = SHELL32_AnsiToUnicodeBuf(path, &wPath, 0);
892     if (!retCode)
893     {
894         retCode = SHCreateDirectoryExW(hWnd, wPath, sec);
895         SHELL32_FreeUnicodeBuf(wPath);
896     }
897     return retCode;
898 }
899 
900 /*************************************************************************
901  * SHCreateDirectoryExW      [SHELL32.@]
902  *
903  * See SHCreateDirectoryExA.
904  */
905 int WINAPI SHCreateDirectoryExW(HWND hWnd, LPCWSTR path, LPSECURITY_ATTRIBUTES sec)
906 {
907     int ret = ERROR_BAD_PATHNAME;
908     TRACE("(%p, %s, %p)\n", hWnd, debugstr_w(path), sec);
909 
910     if (PathIsRelativeW(path))
911     {
912       SetLastError(ret);
913     }
914     else
915     {
916         ret = SHNotifyCreateDirectoryW(path, sec);
917         /* Refuse to work on certain error codes before trying to create directories recursively */
918         if (ret != ERROR_SUCCESS &&
919           ret != ERROR_FILE_EXISTS &&
920           ret != ERROR_ALREADY_EXISTS &&
921           ret != ERROR_FILENAME_EXCED_RANGE)
922         {
923             WCHAR *pEnd, *pSlash, szTemp[MAX_PATH + 1];  /* extra for PathAddBackslash() */
924 
925             lstrcpynW(szTemp, path, MAX_PATH);
926             pEnd = PathAddBackslashW(szTemp);
927             pSlash = szTemp + 3;
928 
929             while (*pSlash)
930             {
931                 while (*pSlash && *pSlash != '\\') pSlash++;
932                 if (*pSlash)
933                 {
934                     *pSlash = 0;    /* terminate path at separator */
935 
936                     ret = SHNotifyCreateDirectoryW(szTemp, pSlash + 1 == pEnd ? sec : NULL);
937                 }
938                 *pSlash++ = '\\'; /* put the separator back */
939             }
940         }
941 
942         if (ret && hWnd && (ERROR_CANCELLED != ret && ERROR_ALREADY_EXISTS != ret))
943         {
944             ShellMessageBoxW(shell32_hInstance, hWnd, MAKEINTRESOURCEW(IDS_CREATEFOLDER_DENIED), MAKEINTRESOURCEW(IDS_CREATEFOLDER_CAPTION),
945                                     MB_ICONEXCLAMATION | MB_OK, path);
946             ret = ERROR_CANCELLED;
947         }
948     }
949 
950     return ret;
951 }
952 
953 /*************************************************************************
954  * SHFindAttrW      [internal]
955  *
956  * Get the Attributes for a file or directory. The difference to GetAttributes()
957  * is that this function will also work for paths containing wildcard characters
958  * in its filename.
959 
960  * PARAMS
961  *  path       [I]   path of directory or file to check
962  *  fileOnly   [I]   TRUE if only files should be found
963  *
964  * RETURNS
965  *  INVALID_FILE_ATTRIBUTES if the path does not exist, the actual attributes of
966  *  the first file or directory found otherwise
967  */
968 static DWORD SHFindAttrW(LPCWSTR pName, BOOL fileOnly)
969 {
970     WIN32_FIND_DATAW wfd;
971     BOOL b_FileMask = fileOnly && (NULL != StrPBrkW(pName, L"*?"));
972     DWORD dwAttr = INVALID_FILE_ATTRIBUTES;
973     HANDLE hFind = FindFirstFileW(pName, &wfd);
974 
975     TRACE("%s %d\n", debugstr_w(pName), fileOnly);
976     if (INVALID_HANDLE_VALUE != hFind)
977     {
978         do
979         {
980             if (b_FileMask && IsAttribDir(wfd.dwFileAttributes))
981                continue;
982             dwAttr = wfd.dwFileAttributes;
983             break;
984         } while (FindNextFileW(hFind, &wfd));
985 
986       FindClose(hFind);
987     }
988     return dwAttr;
989 }
990 
991 /*************************************************************************
992  *
993  * _ConvertAtoW  helper function for SHFileOperationA
994  *
995  * Converts a string or string-list to unicode.
996  */
997 static DWORD _ConvertAtoW(PCSTR strSrc, PCWSTR* pStrDest, BOOL isList)
998 {
999     *pStrDest = NULL;
1000 
1001     // If the input is null, nothing to convert.
1002     if (!strSrc)
1003         return 0;
1004 
1005     // Measure the total size, depending on if it's a zero-terminated list.
1006     int sizeA = 0;
1007     if (isList)
1008     {
1009         PCSTR tmpSrc = strSrc;
1010         int size;
1011         do
1012         {
1013             size = lstrlenA(tmpSrc) + 1;
1014             sizeA += size;
1015             tmpSrc += size;
1016         } while (size != 1);
1017     }
1018     else
1019     {
1020         sizeA = lstrlenA(strSrc) + 1;
1021     }
1022 
1023     // Measure the needed allocation size.
1024     int sizeW = MultiByteToWideChar(CP_ACP, 0, strSrc, sizeA, NULL, 0);
1025     if (!sizeW)
1026         return GetLastError();
1027 
1028     PWSTR strDest = (PWSTR) HeapAlloc(GetProcessHeap(), 0, sizeW * sizeof(WCHAR));
1029     if (!strDest)
1030         return ERROR_OUTOFMEMORY;
1031 
1032     int err = MultiByteToWideChar(CP_ACP, 0, strSrc, sizeA, strDest, sizeW);
1033     if (!err)
1034     {
1035         HeapFree(GetProcessHeap(), 0, strDest);
1036         return GetLastError();
1037     }
1038 
1039     *pStrDest = strDest;
1040     return 0;
1041 }
1042 
1043 /*************************************************************************
1044  * SHFileOperationA          [SHELL32.@]
1045  *
1046  * Function to copy, move, delete and create one or more files with optional
1047  * user prompts.
1048  *
1049  * PARAMS
1050  *  lpFileOp   [I/O] pointer to a structure containing all the necessary information
1051  *
1052  * RETURNS
1053  *  Success: ERROR_SUCCESS.
1054  *  Failure: ERROR_CANCELLED.
1055  *
1056  * NOTES
1057  *  exported by name
1058  */
1059 int WINAPI SHFileOperationA(LPSHFILEOPSTRUCTA lpFileOp)
1060 {
1061     int errCode, retCode;
1062     SHFILEOPSTRUCTW nFileOp = { 0 };
1063 
1064     // Convert A information to W
1065     nFileOp.hwnd = lpFileOp->hwnd;
1066     nFileOp.wFunc = lpFileOp->wFunc;
1067     nFileOp.fFlags = lpFileOp->fFlags;
1068 
1069     errCode = _ConvertAtoW(lpFileOp->pFrom, &nFileOp.pFrom, TRUE);
1070     if (errCode != 0)
1071         goto cleanup;
1072 
1073     if (FO_DELETE != (nFileOp.wFunc & FO_MASK))
1074     {
1075         errCode = _ConvertAtoW(lpFileOp->pTo, &nFileOp.pTo, TRUE);
1076         if (errCode != 0)
1077             goto cleanup;
1078     }
1079 
1080     if (nFileOp.fFlags & FOF_SIMPLEPROGRESS)
1081     {
1082         errCode = _ConvertAtoW(lpFileOp->lpszProgressTitle, &nFileOp.lpszProgressTitle, FALSE);
1083         if (errCode != 0)
1084             goto cleanup;
1085     }
1086 
1087     // Call the actual function
1088     retCode = SHFileOperationW(&nFileOp);
1089     if (retCode)
1090     {
1091         ERR("SHFileOperationW failed with 0x%x\n", retCode);
1092     }
1093 
1094     // Cleanup
1095 cleanup:
1096     if (nFileOp.pFrom)
1097         HeapFree(GetProcessHeap(), 0, (PVOID) nFileOp.pFrom);
1098     if (nFileOp.pTo)
1099         HeapFree(GetProcessHeap(), 0, (PVOID) nFileOp.pTo);
1100     if (nFileOp.lpszProgressTitle)
1101         HeapFree(GetProcessHeap(), 0, (PVOID) nFileOp.lpszProgressTitle);
1102 
1103     if (errCode != 0)
1104     {
1105         lpFileOp->fAnyOperationsAborted = TRUE;
1106         SetLastError(errCode);
1107 
1108         return errCode;
1109     }
1110 
1111     // Thankfully, starting with NT4 the name mappings are always unicode, so no need to convert.
1112     lpFileOp->hNameMappings = nFileOp.hNameMappings;
1113     lpFileOp->fAnyOperationsAborted = nFileOp.fAnyOperationsAborted;
1114     return retCode;
1115 }
1116 
1117 static void __inline grow_list(FILE_LIST *list)
1118 {
1119     FILE_ENTRY *newx = (FILE_ENTRY *)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, list->feFiles,
1120                                   list->num_alloc * 2 * sizeof(*newx) );
1121     list->feFiles = newx;
1122     list->num_alloc *= 2;
1123 }
1124 
1125 /* adds a file to the FILE_ENTRY struct
1126  */
1127 static void add_file_to_entry(FILE_ENTRY *feFile, LPCWSTR szFile)
1128 {
1129     DWORD dwLen = lstrlenW(szFile) + 1;
1130     LPCWSTR ptr;
1131     LPCWSTR ptr2;
1132 
1133     feFile->szFullPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
1134     lstrcpyW(feFile->szFullPath, szFile);
1135 
1136     ptr = StrRChrW(szFile, NULL, '\\');
1137     ptr2 = StrRChrW(szFile, NULL, '/');
1138     if (!ptr || ptr < ptr2)
1139         ptr = ptr2;
1140     if (ptr)
1141     {
1142         dwLen = ptr - szFile + 1;
1143         feFile->szDirectory = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
1144         lstrcpynW(feFile->szDirectory, szFile, dwLen);
1145 
1146         dwLen = lstrlenW(feFile->szFullPath) - dwLen + 1;
1147         feFile->szFilename = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
1148         lstrcpyW(feFile->szFilename, ptr + 1); /* skip over backslash */
1149     }
1150     feFile->bFromWildcard = FALSE;
1151 }
1152 
1153 static LPWSTR wildcard_to_file(LPCWSTR szWildCard, LPCWSTR szFileName)
1154 {
1155     LPCWSTR ptr;
1156     LPCWSTR ptr2;
1157     LPWSTR szFullPath;
1158     DWORD dwDirLen, dwFullLen;
1159 
1160     ptr = StrRChrW(szWildCard, NULL, '\\');
1161     ptr2 = StrRChrW(szWildCard, NULL, '/');
1162     if (!ptr || ptr < ptr2)
1163         ptr = ptr2;
1164     dwDirLen = ptr - szWildCard + 1;
1165 
1166     dwFullLen = dwDirLen + lstrlenW(szFileName) + 1;
1167     szFullPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwFullLen * sizeof(WCHAR));
1168 
1169     lstrcpynW(szFullPath, szWildCard, dwDirLen + 1);
1170     lstrcatW(szFullPath, szFileName);
1171 
1172     return szFullPath;
1173 }
1174 
1175 static void parse_wildcard_files(FILE_LIST *flList, LPCWSTR szFile, LPDWORD pdwListIndex)
1176 {
1177     WIN32_FIND_DATAW wfd;
1178     HANDLE hFile = FindFirstFileW(szFile, &wfd);
1179     FILE_ENTRY *file;
1180     LPWSTR szFullPath;
1181     BOOL res;
1182 
1183     if (hFile == INVALID_HANDLE_VALUE) return;
1184 
1185     for (res = TRUE; res; res = FindNextFileW(hFile, &wfd))
1186     {
1187         if (IsDotDir(wfd.cFileName))
1188             continue;
1189 
1190         if (*pdwListIndex >= flList->num_alloc)
1191             grow_list( flList );
1192 
1193         szFullPath = wildcard_to_file(szFile, wfd.cFileName);
1194         file = &flList->feFiles[(*pdwListIndex)++];
1195         add_file_to_entry(file, szFullPath);
1196         file->bFromWildcard = TRUE;
1197         file->attributes = wfd.dwFileAttributes;
1198 
1199         if (IsAttribDir(file->attributes))
1200             flList->bAnyDirectories = TRUE;
1201 
1202         HeapFree(GetProcessHeap(), 0, szFullPath);
1203     }
1204 
1205     FindClose(hFile);
1206 }
1207 
1208 /* takes the null-separated file list and fills out the FILE_LIST */
1209 static HRESULT parse_file_list(FILE_LIST *flList, LPCWSTR szFiles)
1210 {
1211     LPCWSTR ptr = szFiles;
1212     WCHAR szCurFile[MAX_PATH];
1213     DWORD i = 0;
1214 
1215     if (!szFiles)
1216         return ERROR_INVALID_PARAMETER;
1217 
1218     flList->bAnyFromWildcard = FALSE;
1219     flList->bAnyDirectories = FALSE;
1220     flList->bAnyDontExist = FALSE;
1221     flList->num_alloc = 32;
1222     flList->dwNumFiles = 0;
1223 
1224     /* empty list */
1225     if (!szFiles[0])
1226         return ERROR_ACCESS_DENIED;
1227 
1228     flList->feFiles = (FILE_ENTRY *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
1229                                 flList->num_alloc * sizeof(FILE_ENTRY));
1230 
1231     while (*ptr)
1232     {
1233         if (i >= flList->num_alloc) grow_list( flList );
1234 
1235         /* change relative to absolute path */
1236         if (PathIsRelativeW(ptr))
1237         {
1238             GetCurrentDirectoryW(MAX_PATH, szCurFile);
1239             PathCombineW(szCurFile, szCurFile, ptr);
1240             flList->feFiles[i].bFromRelative = TRUE;
1241         }
1242         else
1243         {
1244             lstrcpyW(szCurFile, ptr);
1245             flList->feFiles[i].bFromRelative = FALSE;
1246         }
1247 
1248         /* parse wildcard files if they are in the filename */
1249         if (StrPBrkW(szCurFile, L"*?"))
1250         {
1251             parse_wildcard_files(flList, szCurFile, &i);
1252             flList->bAnyFromWildcard = TRUE;
1253             i--;
1254         }
1255         else
1256         {
1257             FILE_ENTRY *file = &flList->feFiles[i];
1258             add_file_to_entry(file, szCurFile);
1259             file->attributes = GetFileAttributesW( file->szFullPath );
1260             file->bExists = (file->attributes != INVALID_FILE_ATTRIBUTES);
1261 
1262             if (!file->bExists)
1263                 flList->bAnyDontExist = TRUE;
1264 
1265             if (IsAttribDir(file->attributes))
1266                 flList->bAnyDirectories = TRUE;
1267         }
1268 
1269         /* advance to the next string */
1270         ptr += lstrlenW(ptr) + 1;
1271         i++;
1272     }
1273     flList->dwNumFiles = i;
1274 
1275     return S_OK;
1276 }
1277 
1278 /* free the FILE_LIST */
1279 static void destroy_file_list(FILE_LIST *flList)
1280 {
1281     DWORD i;
1282 
1283     if (!flList || !flList->feFiles)
1284         return;
1285 
1286     for (i = 0; i < flList->dwNumFiles; i++)
1287     {
1288         HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szDirectory);
1289         HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szFilename);
1290         HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szFullPath);
1291     }
1292 
1293     HeapFree(GetProcessHeap(), 0, flList->feFiles);
1294 }
1295 
1296 static CStringW try_find_new_name(LPCWSTR szDestPath)
1297 {
1298     CStringW mask(szDestPath);
1299     CStringW ext(PathFindExtensionW(szDestPath));
1300 
1301     // cut off extension before inserting a "new file" mask
1302     if (!ext.IsEmpty())
1303     {
1304         mask = mask.Left(mask.GetLength() - ext.GetLength());
1305     }
1306     mask += L" (%d)" + ext;
1307 
1308     CStringW newName;
1309 
1310     // trying to find new file name
1311     for (int i = 1; i < NEW_FILENAME_ON_COPY_TRIES; i++)
1312     {
1313         newName.Format(mask, i);
1314 
1315         if (!PathFileExistsW(newName))
1316         {
1317             return newName;
1318         }
1319     }
1320 
1321     return CStringW();
1322 }
1323 
1324 static void copy_dir_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, LPCWSTR szDestPath)
1325 {
1326     WCHAR szFrom[MAX_PATH], szTo[MAX_PATH];
1327     FILE_LIST flFromNew, flToNew;
1328 
1329     if (feFrom->szFilename && IsDotDir(feFrom->szFilename))
1330         return;
1331 
1332     if (PathFileExistsW(szDestPath))
1333         PathCombineW(szTo, szDestPath, feFrom->szFilename);
1334     else
1335         lstrcpyW(szTo, szDestPath);
1336 
1337     if (PathFileExistsW(szTo))
1338     {
1339         if (op->req->fFlags & FOF_RENAMEONCOLLISION)
1340         {
1341             CStringW newPath = try_find_new_name(szTo);
1342             if (!newPath.IsEmpty())
1343             {
1344                 StringCchCopyW(szTo, _countof(szTo), newPath);
1345             }
1346         }
1347         else if (!(op->req->fFlags & FOF_NOCONFIRMATION))
1348         {
1349             if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FOLDER, feFrom->szFilename, op))
1350             {
1351                 /* Vista returns an ERROR_CANCELLED even if user pressed "No" */
1352                 if (!op->bManyItems)
1353                     op->bCancelled = TRUE;
1354                 return;
1355             }
1356         }
1357     }
1358 
1359     szTo[lstrlenW(szTo) + 1] = '\0';
1360     SHNotifyCreateDirectoryW(szTo, NULL);
1361 
1362     PathCombineW(szFrom, feFrom->szFullPath, L"*.*");
1363     szFrom[lstrlenW(szFrom) + 1] = '\0';
1364 
1365     ZeroMemory(&flFromNew, sizeof(FILE_LIST));
1366     ZeroMemory(&flToNew, sizeof(FILE_LIST));
1367     parse_file_list(&flFromNew, szFrom);
1368     parse_file_list(&flToNew, szTo);
1369 
1370     copy_files(op, FALSE, &flFromNew, &flToNew);
1371 
1372     destroy_file_list(&flFromNew);
1373     destroy_file_list(&flToNew);
1374 }
1375 
1376 static BOOL copy_file_to_file(FILE_OPERATION *op, const WCHAR *szFrom, const WCHAR *szTo)
1377 {
1378     if (PathFileExistsW(szTo))
1379     {
1380         if (op->req->fFlags & FOF_RENAMEONCOLLISION)
1381         {
1382             CStringW newPath = try_find_new_name(szTo);
1383             if (!newPath.IsEmpty())
1384             {
1385                 return SHNotifyCopyFileW(op, szFrom, newPath, FALSE) == 0;
1386             }
1387         }
1388         else if (!(op->req->fFlags & FOF_NOCONFIRMATION))
1389         {
1390             if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FILE, PathFindFileNameW(szTo), op))
1391                 return FALSE;
1392         }
1393     }
1394 
1395     return SHNotifyCopyFileW(op, szFrom, szTo, FALSE) == 0;
1396 }
1397 
1398 /* copy a file or directory to another directory */
1399 static void copy_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, const FILE_ENTRY *feTo)
1400 {
1401     if (!PathFileExistsW(feTo->szFullPath))
1402         SHNotifyCreateDirectoryW(feTo->szFullPath, NULL);
1403 
1404     if (IsAttribFile(feFrom->attributes))
1405     {
1406         WCHAR szDestPath[MAX_PATH];
1407 
1408         PathCombineW(szDestPath, feTo->szFullPath, feFrom->szFilename);
1409         copy_file_to_file(op, feFrom->szFullPath, szDestPath);
1410     }
1411     else if (!(op->req->fFlags & FOF_FILESONLY && feFrom->bFromWildcard))
1412         copy_dir_to_dir(op, feFrom, feTo->szFullPath);
1413 }
1414 
1415 static void create_dest_dirs(LPCWSTR szDestDir)
1416 {
1417     WCHAR dir[MAX_PATH];
1418     LPCWSTR ptr = StrChrW(szDestDir, '\\');
1419 
1420     /* make sure all directories up to last one are created */
1421     while (ptr && (ptr = StrChrW(ptr + 1, '\\')))
1422     {
1423         lstrcpynW(dir, szDestDir, ptr - szDestDir + 1);
1424 
1425         if (!PathFileExistsW(dir))
1426             SHNotifyCreateDirectoryW(dir, NULL);
1427     }
1428 
1429     /* create last directory */
1430     if (!PathFileExistsW(szDestDir))
1431         SHNotifyCreateDirectoryW(szDestDir, NULL);
1432 }
1433 
1434 /* the FO_COPY operation */
1435 static HRESULT copy_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, FILE_LIST *flTo)
1436 {
1437     DWORD i;
1438     const FILE_ENTRY *entryToCopy;
1439     const FILE_ENTRY *fileDest = &flTo->feFiles[0];
1440 
1441     if (flFrom->bAnyDontExist)
1442         return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND;
1443 
1444     if (flTo->dwNumFiles == 0)
1445     {
1446         /* If the destination is empty, SHFileOperation should use the current directory */
1447         WCHAR curdir[MAX_PATH+1];
1448 
1449         GetCurrentDirectoryW(MAX_PATH, curdir);
1450         curdir[lstrlenW(curdir)+1] = 0;
1451 
1452         destroy_file_list(flTo);
1453         ZeroMemory(flTo, sizeof(FILE_LIST));
1454         parse_file_list(flTo, curdir);
1455         fileDest = &flTo->feFiles[0];
1456     }
1457 
1458     if (multiDest)
1459     {
1460         if (flFrom->bAnyFromWildcard)
1461             return ERROR_CANCELLED;
1462 
1463         if (flFrom->dwNumFiles != flTo->dwNumFiles)
1464         {
1465             if (flFrom->dwNumFiles != 1 && !IsAttribDir(fileDest->attributes))
1466                 return ERROR_CANCELLED;
1467 
1468             /* Free all but the first entry. */
1469             for (i = 1; i < flTo->dwNumFiles; i++)
1470             {
1471                 HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szDirectory);
1472                 HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szFilename);
1473                 HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szFullPath);
1474             }
1475 
1476             flTo->dwNumFiles = 1;
1477         }
1478         else if (IsAttribDir(fileDest->attributes))
1479         {
1480             for (i = 1; i < flTo->dwNumFiles; i++)
1481                 if (!IsAttribDir(flTo->feFiles[i].attributes) ||
1482                     !IsAttribDir(flFrom->feFiles[i].attributes))
1483                 {
1484                     return ERROR_CANCELLED;
1485                 }
1486         }
1487     }
1488     else if (flFrom->dwNumFiles != 1)
1489     {
1490         if (flTo->dwNumFiles != 1 && !IsAttribDir(fileDest->attributes))
1491             return ERROR_CANCELLED;
1492 
1493         if (PathFileExistsW(fileDest->szFullPath) &&
1494             IsAttribFile(fileDest->attributes))
1495         {
1496             return ERROR_CANCELLED;
1497         }
1498 
1499         if (flTo->dwNumFiles == 1 && fileDest->bFromRelative &&
1500             !PathFileExistsW(fileDest->szFullPath))
1501         {
1502             return ERROR_CANCELLED;
1503         }
1504     }
1505 
1506     for (i = 0; i < flFrom->dwNumFiles; i++)
1507     {
1508         entryToCopy = &flFrom->feFiles[i];
1509 
1510         if ((multiDest) &&
1511             flTo->dwNumFiles > 1)
1512         {
1513             fileDest = &flTo->feFiles[i];
1514         }
1515 
1516         if (IsAttribDir(entryToCopy->attributes) &&
1517             !lstrcmpiW(entryToCopy->szFullPath, fileDest->szDirectory))
1518         {
1519             return ERROR_SUCCESS;
1520         }
1521 
1522         create_dest_dirs(fileDest->szDirectory);
1523 
1524         if (!lstrcmpiW(entryToCopy->szFullPath, fileDest->szFullPath))
1525         {
1526             if (IsAttribFile(entryToCopy->attributes))
1527                 return ERROR_NO_MORE_SEARCH_HANDLES;
1528             else
1529                 return ERROR_SUCCESS;
1530         }
1531 
1532         if ((flFrom->dwNumFiles > 1 && flTo->dwNumFiles == 1) ||
1533             IsAttribDir(fileDest->attributes))
1534         {
1535             copy_to_dir(op, entryToCopy, fileDest);
1536         }
1537         else if (IsAttribDir(entryToCopy->attributes))
1538         {
1539             copy_dir_to_dir(op, entryToCopy, fileDest->szFullPath);
1540         }
1541         else
1542         {
1543             if (!copy_file_to_file(op, entryToCopy->szFullPath, fileDest->szFullPath))
1544             {
1545                 op->req->fAnyOperationsAborted = TRUE;
1546                 return ERROR_CANCELLED;
1547             }
1548         }
1549 
1550         if (op->progress != NULL)
1551             op->bCancelled |= op->progress->HasUserCancelled();
1552         /* Vista return code. XP would return e.g. ERROR_FILE_NOT_FOUND, ERROR_ALREADY_EXISTS */
1553         if (op->bCancelled)
1554             return ERROR_CANCELLED;
1555     }
1556 
1557     /* Vista return code. On XP if the used pressed "No" for the last item,
1558      * ERROR_ARENA_TRASHED would be returned */
1559     return ERROR_SUCCESS;
1560 }
1561 
1562 static BOOL confirm_delete_list(HWND hWnd, DWORD fFlags, BOOL fTrash, const FILE_LIST *flFrom)
1563 {
1564     if (flFrom->dwNumFiles > 1)
1565     {
1566         WCHAR tmp[8];
1567 
1568         wnsprintfW(tmp, sizeof(tmp)/sizeof(tmp[0]), L"%d", flFrom->dwNumFiles);
1569         return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_MULTIPLE_ITEM:ASK_DELETE_MULTIPLE_ITEM), tmp, NULL);
1570     }
1571     else
1572     {
1573         const FILE_ENTRY *fileEntry = &flFrom->feFiles[0];
1574 
1575         if (IsAttribFile(fileEntry->attributes))
1576             return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_FILE:ASK_DELETE_FILE), fileEntry->szFullPath, NULL);
1577         else if (!(fFlags & FOF_FILESONLY && fileEntry->bFromWildcard))
1578             return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_FOLDER:ASK_DELETE_FOLDER), fileEntry->szFullPath, NULL);
1579     }
1580     return TRUE;
1581 }
1582 
1583 /* the FO_DELETE operation */
1584 static HRESULT delete_files(FILE_OPERATION *op, const FILE_LIST *flFrom)
1585 {
1586     const FILE_ENTRY *fileEntry;
1587     DWORD i;
1588     BOOL bPathExists;
1589     BOOL bTrash;
1590 
1591     if (!flFrom->dwNumFiles)
1592         return ERROR_SUCCESS;
1593 
1594     /* Windows also checks only the first item */
1595     bTrash = (op->req->fFlags & FOF_ALLOWUNDO)
1596         && TRASH_CanTrashFile(flFrom->feFiles[0].szFullPath);
1597 
1598     if (!(op->req->fFlags & FOF_NOCONFIRMATION) || (!bTrash && op->req->fFlags & FOF_WANTNUKEWARNING))
1599         if (!confirm_delete_list(op->req->hwnd, op->req->fFlags, bTrash, flFrom))
1600         {
1601             op->req->fAnyOperationsAborted = TRUE;
1602             return 0;
1603         }
1604 
1605     /* Check files. Do not delete one if one file does not exists */
1606     for (i = 0; i < flFrom->dwNumFiles; i++)
1607     {
1608         fileEntry = &flFrom->feFiles[i];
1609 
1610         if (fileEntry->attributes == (ULONG)-1)
1611         {
1612             // This is a windows 2003 server specific value which has been removed.
1613             // Later versions of windows return ERROR_FILE_NOT_FOUND.
1614             return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND;
1615         }
1616     }
1617 
1618     for (i = 0; i < flFrom->dwNumFiles; i++)
1619     {
1620         bPathExists = TRUE;
1621         fileEntry = &flFrom->feFiles[i];
1622 
1623         if (!IsAttribFile(fileEntry->attributes) &&
1624             (op->req->fFlags & FOF_FILESONLY && fileEntry->bFromWildcard))
1625             continue;
1626 
1627         if (bTrash)
1628         {
1629             BOOL bDelete;
1630             if (TRASH_TrashFile(fileEntry->szFullPath))
1631             {
1632                 SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, fileEntry->szFullPath, NULL);
1633                 continue;
1634             }
1635 
1636             /* Note: Windows silently deletes the file in such a situation, we show a dialog */
1637             if (!(op->req->fFlags & FOF_NOCONFIRMATION) || (op->req->fFlags & FOF_WANTNUKEWARNING))
1638                 bDelete = SHELL_ConfirmDialogW(op->req->hwnd, ASK_CANT_TRASH_ITEM, fileEntry->szFullPath, NULL);
1639             else
1640                 bDelete = TRUE;
1641 
1642             if (!bDelete)
1643             {
1644                 op->req->fAnyOperationsAborted = TRUE;
1645                 break;
1646             }
1647         }
1648 
1649         /* delete the file or directory */
1650         if (IsAttribFile(fileEntry->attributes))
1651         {
1652             bPathExists = (ERROR_SUCCESS == SHNotifyDeleteFileW(op, fileEntry->szFullPath));
1653         }
1654         else
1655             bPathExists = SHELL_DeleteDirectoryW(op, fileEntry->szFullPath, FALSE);
1656 
1657         if (!bPathExists)
1658         {
1659             DWORD err = GetLastError();
1660 
1661             if (ERROR_FILE_NOT_FOUND == err)
1662             {
1663                 // This is a windows 2003 server specific value which ahs been removed.
1664                 // Later versions of windows return ERROR_FILE_NOT_FOUND.
1665                 return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND;
1666             }
1667             else
1668             {
1669                 return err;
1670             }
1671         }
1672 
1673         if (op->progress != NULL)
1674             op->bCancelled |= op->progress->HasUserCancelled();
1675         /* Should fire on progress dialog only */
1676         if (op->bCancelled)
1677             return ERROR_CANCELLED;
1678     }
1679 
1680     return ERROR_SUCCESS;
1681 }
1682 
1683 static void move_dir_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, LPCWSTR szDestPath)
1684 {
1685     WCHAR szFrom[MAX_PATH], szTo[MAX_PATH];
1686     FILE_LIST flFromNew, flToNew;
1687 
1688     if (feFrom->szFilename && IsDotDir(feFrom->szFilename))
1689         return;
1690 
1691     SHNotifyCreateDirectoryW(szDestPath, NULL);
1692 
1693     PathCombineW(szFrom, feFrom->szFullPath, L"*.*");
1694     szFrom[lstrlenW(szFrom) + 1] = '\0';
1695 
1696     lstrcpyW(szTo, szDestPath);
1697     szTo[lstrlenW(szDestPath) + 1] = '\0';
1698 
1699     ZeroMemory(&flFromNew, sizeof(FILE_LIST));
1700     ZeroMemory(&flToNew, sizeof(FILE_LIST));
1701     parse_file_list(&flFromNew, szFrom);
1702     parse_file_list(&flToNew, szTo);
1703 
1704     move_files(op, FALSE, &flFromNew, &flToNew);
1705 
1706     destroy_file_list(&flFromNew);
1707     destroy_file_list(&flToNew);
1708 
1709     if (PathIsDirectoryEmptyW(feFrom->szFullPath))
1710         Win32RemoveDirectoryW(feFrom->szFullPath);
1711 }
1712 
1713 static BOOL move_file_to_file(FILE_OPERATION *op, const WCHAR *szFrom, const WCHAR *szTo)
1714 {
1715     if (PathFileExistsW(szTo))
1716     {
1717         if (op->req->fFlags & FOF_RENAMEONCOLLISION)
1718         {
1719             CStringW newPath = try_find_new_name(szTo);
1720             if (!newPath.IsEmpty())
1721             {
1722                 return SHNotifyMoveFileW(op, szFrom, newPath, FALSE) == 0;
1723             }
1724         }
1725         else if (!(op->req->fFlags & FOF_NOCONFIRMATION))
1726         {
1727             if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FILE, PathFindFileNameW(szTo), op))
1728                 return FALSE;
1729         }
1730     }
1731 
1732     return SHNotifyMoveFileW(op, szFrom, szTo, FALSE) == 0;
1733 }
1734 
1735 /* moves a file or directory to another directory */
1736 static void move_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, const FILE_ENTRY *feTo)
1737 {
1738     if (feFrom->attributes == INVALID_FILE_ATTRIBUTES)
1739         return;
1740 
1741     if (!PathFileExistsW(feTo->szFullPath))
1742         SHNotifyCreateDirectoryW(feTo->szFullPath, NULL);
1743 
1744     WCHAR szDestPath[MAX_PATH];
1745     PathCombineW(szDestPath, feTo->szFullPath, feFrom->szFilename);
1746 
1747     if (IsAttribFile(feFrom->attributes))
1748         move_file_to_file(op, feFrom->szFullPath, szDestPath);
1749     else if (!(op->req->fFlags & FOF_FILESONLY && feFrom->bFromWildcard))
1750         move_dir_to_dir(op, feFrom, szDestPath);
1751 }
1752 
1753 /* the FO_MOVE operation */
1754 static DWORD move_files(FILE_OPERATION *op, BOOL multiDest, const FILE_LIST *flFrom, const FILE_LIST *flTo)
1755 {
1756     DWORD i;
1757     INT mismatched = 0;
1758 
1759     const FILE_ENTRY *entryToMove;
1760     const FILE_ENTRY *fileDest;
1761 
1762     if (!flFrom->dwNumFiles)
1763         return ERROR_SUCCESS;
1764 
1765     if (!flTo->dwNumFiles)
1766         return ERROR_FILE_NOT_FOUND;
1767 
1768     if (flFrom->bAnyDontExist)
1769         return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND;
1770 
1771     if (!(multiDest) &&
1772         flTo->dwNumFiles > 1 && flFrom->dwNumFiles > 1)
1773     {
1774         return ERROR_CANCELLED;
1775     }
1776 
1777     if (!(multiDest) &&
1778         !flFrom->bAnyDirectories &&
1779         flFrom->dwNumFiles > flTo->dwNumFiles &&
1780         !(flTo->bAnyDirectories && flTo->dwNumFiles == 1))
1781     {
1782         return ERROR_CANCELLED;
1783     }
1784 
1785     if (!PathFileExistsW(flTo->feFiles[0].szDirectory))
1786         return ERROR_CANCELLED;
1787 
1788     if (multiDest)
1789         mismatched = flFrom->dwNumFiles - flTo->dwNumFiles;
1790 
1791     fileDest = &flTo->feFiles[0];
1792     for (i = 0; i < flFrom->dwNumFiles; i++)
1793     {
1794         entryToMove = &flFrom->feFiles[i];
1795 
1796         if (!PathFileExistsW(fileDest->szDirectory))
1797             return ERROR_CANCELLED;
1798 
1799         if (multiDest)
1800         {
1801             if (i >= flTo->dwNumFiles)
1802                 break;
1803             fileDest = &flTo->feFiles[i];
1804             if (mismatched && !fileDest->bExists)
1805             {
1806                 create_dest_dirs(flTo->feFiles[i].szFullPath);
1807                 flTo->feFiles[i].bExists = TRUE;
1808                 flTo->feFiles[i].attributes = FILE_ATTRIBUTE_DIRECTORY;
1809             }
1810         }
1811 
1812         if ((flFrom->dwNumFiles > 1 && flTo->dwNumFiles == 1) ||
1813             IsAttribDir(fileDest->attributes))
1814         {
1815             move_to_dir(op, entryToMove, fileDest);
1816         }
1817         else if (IsAttribDir(entryToMove->attributes))
1818         {
1819             move_dir_to_dir(op, entryToMove, fileDest->szFullPath);
1820         }
1821         else
1822         {
1823             if (!move_file_to_file(op, entryToMove->szFullPath, fileDest->szFullPath))
1824             {
1825                 op->req->fAnyOperationsAborted = TRUE;
1826                 return ERROR_CANCELLED;
1827             }
1828         }
1829 
1830         if (op->progress != NULL)
1831             op->bCancelled |= op->progress->HasUserCancelled();
1832         /* Should fire on progress dialog only */
1833         if (op->bCancelled)
1834             return ERROR_CANCELLED;
1835 
1836     }
1837 
1838     if (mismatched > 0)
1839     {
1840         if (flFrom->bAnyDirectories)
1841             return DE_DESTSAMETREE;
1842         else
1843             return DE_SAMEFILE;
1844     }
1845 
1846     return ERROR_SUCCESS;
1847 }
1848 
1849 /* the FO_RENAME files */
1850 static HRESULT rename_files(FILE_OPERATION *op,  const FILE_LIST *flFrom, const FILE_LIST *flTo)
1851 {
1852     const FILE_ENTRY *feFrom;
1853     const FILE_ENTRY *feTo;
1854 
1855     if (flFrom->dwNumFiles != 1)
1856         return ERROR_GEN_FAILURE;
1857 
1858     if (flTo->dwNumFiles != 1)
1859         return ERROR_CANCELLED;
1860 
1861     feFrom = &flFrom->feFiles[0];
1862     feTo= &flTo->feFiles[0];
1863 
1864     /* fail if destination doesn't exist */
1865     if (!feFrom->bExists)
1866         return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND;
1867 
1868     /* fail if destination already exists */
1869     if (feTo->bExists)
1870         return ERROR_ALREADY_EXISTS;
1871 
1872     return SHNotifyMoveFileW(op, feFrom->szFullPath, feTo->szFullPath, IsAttribDir(feFrom->attributes));
1873 }
1874 
1875 /* alert the user if an unsupported flag is used */
1876 static void check_flags(FILEOP_FLAGS fFlags)
1877 {
1878     WORD wUnsupportedFlags = FOF_NO_CONNECTED_ELEMENTS |
1879         FOF_NOCOPYSECURITYATTRIBS | FOF_NORECURSEREPARSE |
1880         FOF_WANTMAPPINGHANDLE;
1881 
1882     if (fFlags & wUnsupportedFlags)
1883         FIXME("Unsupported flags: %04x\n", fFlags);
1884 }
1885 
1886 static DWORD
1887 validate_operation(LPSHFILEOPSTRUCTW lpFileOp, FILE_LIST *flFrom, FILE_LIST *flTo)
1888 {
1889     DWORD i, k, dwNumDest;
1890     WCHAR szFrom[MAX_PATH], szTo[MAX_PATH];
1891     CStringW strTitle, strText;
1892     const FILE_ENTRY *feFrom;
1893     const FILE_ENTRY *feTo;
1894     UINT wFunc = lpFileOp->wFunc;
1895     HWND hwnd = lpFileOp->hwnd;
1896 
1897     dwNumDest = flTo->dwNumFiles;
1898 
1899     if (wFunc != FO_COPY && wFunc != FO_MOVE)
1900         return ERROR_SUCCESS;
1901 
1902     for (k = 0; k < dwNumDest; ++k)
1903     {
1904         feTo = &flTo->feFiles[k];
1905         for (i = 0; i < flFrom->dwNumFiles; ++i)
1906         {
1907             feFrom = &flFrom->feFiles[i];
1908             StringCbCopyW(szFrom, sizeof(szFrom), feFrom->szFullPath);
1909             StringCbCopyW(szTo, sizeof(szTo), feTo->szFullPath);
1910             if (IsAttribDir(feTo->attributes))
1911             {
1912                 PathAppendW(szTo, feFrom->szFilename);
1913             }
1914 
1915             // same path?
1916             if (lstrcmpiW(szFrom, szTo) == 0 &&
1917                 (wFunc == FO_MOVE || !(lpFileOp->fFlags & FOF_RENAMEONCOLLISION)))
1918             {
1919                 if (!(lpFileOp->fFlags & (FOF_NOERRORUI | FOF_SILENT)))
1920                 {
1921                     if (wFunc == FO_MOVE)
1922                     {
1923                         strTitle.LoadStringW(IDS_MOVEERRORTITLE);
1924                         if (IsAttribDir(feFrom->attributes))
1925                             strText.Format(IDS_MOVEERRORSAMEFOLDER, feFrom->szFilename);
1926                         else
1927                             strText.Format(IDS_MOVEERRORSAME, feFrom->szFilename);
1928                     }
1929                     else
1930                     {
1931                         strTitle.LoadStringW(IDS_COPYERRORTITLE);
1932                         strText.Format(IDS_COPYERRORSAME, feFrom->szFilename);
1933                         return ERROR_SUCCESS;
1934                     }
1935                     MessageBoxW(hwnd, strText, strTitle, MB_ICONERROR);
1936                     return DE_SAMEFILE;
1937                 }
1938                 return DE_OPCANCELLED;
1939             }
1940 
1941             // subfolder?
1942             if (IsAttribDir(feFrom->attributes))
1943             {
1944                 size_t cchFrom = PathAddBackslashW(szFrom) - szFrom;
1945                 size_t cchTo = PathAddBackslashW(szTo) - szTo;
1946                 if (cchFrom < cchTo)
1947                 {
1948                     WCHAR ch = szTo[cchFrom];
1949                     szTo[cchFrom] = 0;
1950                     int compare = lstrcmpiW(szFrom, szTo);
1951                     szTo[cchFrom] = ch;
1952 
1953                     if (compare == 0)
1954                     {
1955                         if (!(lpFileOp->fFlags & (FOF_NOERRORUI | FOF_SILENT)))
1956                         {
1957                             if (wFunc == FO_MOVE)
1958                             {
1959                                 strTitle.LoadStringW(IDS_MOVEERRORTITLE);
1960                                 strText.Format(IDS_MOVEERRORSUBFOLDER, feFrom->szFilename);
1961                             }
1962                             else
1963                             {
1964                                 strTitle.LoadStringW(IDS_COPYERRORTITLE);
1965                                 strText.Format(IDS_COPYERRORSUBFOLDER, feFrom->szFilename);
1966                             }
1967                             MessageBoxW(hwnd, strText, strTitle, MB_ICONERROR);
1968                             return DE_DESTSUBTREE;
1969                         }
1970                         return DE_OPCANCELLED;
1971                     }
1972                 }
1973             }
1974         }
1975     }
1976 
1977     return ERROR_SUCCESS;
1978 }
1979 
1980 /*************************************************************************
1981  * SHFileOperationW          [SHELL32.@]
1982  *
1983  * See SHFileOperationA
1984  */
1985 int WINAPI SHFileOperationW(LPSHFILEOPSTRUCTW lpFileOp)
1986 {
1987     FILE_OPERATION op;
1988     FILE_LIST flFrom, flTo;
1989     int ret = 0;
1990 
1991     if (!lpFileOp)
1992         return ERROR_INVALID_PARAMETER;
1993 
1994     ret = CoInitialize(NULL);
1995     if (FAILED(ret))
1996         return ret;
1997 
1998     lpFileOp->fAnyOperationsAborted = FALSE;
1999     check_flags(lpFileOp->fFlags);
2000 
2001     ZeroMemory(&flFrom, sizeof(FILE_LIST));
2002     ZeroMemory(&flTo, sizeof(FILE_LIST));
2003 
2004     if ((ret = parse_file_list(&flFrom, lpFileOp->pFrom)))
2005         return ret;
2006 
2007     if (lpFileOp->wFunc != FO_DELETE)
2008         parse_file_list(&flTo, lpFileOp->pTo);
2009 
2010     ZeroMemory(&op, sizeof(op));
2011     op.req = lpFileOp;
2012     op.totalSize.QuadPart = 0ull;
2013     op.completedSize.QuadPart = 0ull;
2014     op.bManyItems = (flFrom.dwNumFiles > 1);
2015 
2016     ret = validate_operation(lpFileOp, &flFrom, &flTo);
2017     if (ret)
2018         goto cleanup;
2019 
2020     if (lpFileOp->wFunc != FO_RENAME && !(lpFileOp->fFlags & FOF_SILENT)) {
2021         ret = CoCreateInstance(CLSID_ProgressDialog,
2022                                NULL,
2023                                CLSCTX_INPROC_SERVER,
2024                                IID_PPV_ARG(IProgressDialog, &op.progress));
2025         if (FAILED(ret))
2026             goto cleanup;
2027 
2028         op.progress->StartProgressDialog(op.req->hwnd, NULL, PROGDLG_NORMAL & PROGDLG_AUTOTIME, NULL);
2029         _SetOperationTitle(&op);
2030         _FileOpCountManager(&op, &flFrom);
2031     }
2032 
2033     switch (lpFileOp->wFunc)
2034     {
2035         case FO_COPY:
2036             ret = copy_files(&op, op.req->fFlags & FOF_MULTIDESTFILES, &flFrom, &flTo);
2037             break;
2038         case FO_DELETE:
2039             ret = delete_files(&op, &flFrom);
2040             break;
2041         case FO_MOVE:
2042             ret = move_files(&op, op.req->fFlags & FOF_MULTIDESTFILES, &flFrom, &flTo);
2043             break;
2044         case FO_RENAME:
2045             ret = rename_files(&op, &flFrom, &flTo);
2046             break;
2047         default:
2048             ret = ERROR_INVALID_PARAMETER;
2049             break;
2050     }
2051 
2052     if (op.progress) {
2053         op.progress->StopProgressDialog();
2054         op.progress->Release();
2055     }
2056 
2057 cleanup:
2058     destroy_file_list(&flFrom);
2059 
2060     if (lpFileOp->wFunc != FO_DELETE)
2061         destroy_file_list(&flTo);
2062 
2063     if (ret == ERROR_CANCELLED)
2064         lpFileOp->fAnyOperationsAborted = TRUE;
2065 
2066     CoUninitialize();
2067 
2068     return ret;
2069 }
2070 
2071 // Used by SHFreeNameMappings
2072 static int CALLBACK _DestroyCallback(void *p, void *pData)
2073 {
2074     LPSHNAMEMAPPINGW lp = (SHNAMEMAPPINGW *)p;
2075 
2076     SHFree(lp->pszOldPath);
2077     SHFree(lp->pszNewPath);
2078 
2079     return TRUE;
2080 }
2081 
2082 /*************************************************************************
2083  * SHFreeNameMappings      [shell32.246]
2084  *
2085  * Free the mapping handle returned by SHFileOperation if FOF_WANTSMAPPINGHANDLE
2086  * was specified.
2087  *
2088  * PARAMS
2089  *  hNameMapping [I] handle to the name mappings used during renaming of files
2090  *
2091  * RETURNS
2092  *  Nothing
2093  */
2094 void WINAPI SHFreeNameMappings(HANDLE hNameMapping)
2095 {
2096     if (hNameMapping)
2097     {
2098         DSA_DestroyCallback((HDSA) hNameMapping, _DestroyCallback, NULL);
2099     }
2100 }
2101 
2102 /*************************************************************************
2103  * SheGetDirA [SHELL32.@]
2104  *
2105  * drive = 0: returns the current directory path
2106  * drive > 0: returns the current directory path of the specified drive
2107  *            drive=1 -> A:  drive=2 -> B:  ...
2108  * returns 0 if successful
2109 */
2110 EXTERN_C DWORD WINAPI SheGetDirA(DWORD drive, LPSTR buffer)
2111 {
2112     WCHAR org_path[MAX_PATH];
2113     DWORD ret;
2114     char drv_path[3];
2115 
2116     /* change current directory to the specified drive */
2117     if (drive) {
2118         strcpy(drv_path, "A:");
2119         drv_path[0] += (char)drive-1;
2120 
2121         GetCurrentDirectoryW(MAX_PATH, org_path);
2122 
2123         SetCurrentDirectoryA(drv_path);
2124     }
2125 
2126     /* query current directory path of the specified drive */
2127     ret = GetCurrentDirectoryA(MAX_PATH, buffer);
2128 
2129     /* back to the original drive */
2130     if (drive)
2131         SetCurrentDirectoryW(org_path);
2132 
2133     if (!ret)
2134         return GetLastError();
2135 
2136     return 0;
2137 }
2138 
2139 /*************************************************************************
2140  * SheGetDirW [SHELL32.@]
2141  *
2142  * drive = 0: returns the current directory path
2143  * drive > 0: returns the current directory path of the specified drive
2144  *            drive=1 -> A:  drive=2 -> B:  ...
2145  * returns 0 if successful
2146  */
2147 EXTERN_C DWORD WINAPI SheGetDirW(DWORD drive, LPWSTR buffer)
2148 {
2149     WCHAR org_path[MAX_PATH];
2150     DWORD ret;
2151     char drv_path[3];
2152 
2153     /* change current directory to the specified drive */
2154     if (drive)
2155     {
2156         strcpy(drv_path, "A:");
2157         drv_path[0] += (char)drive-1;
2158 
2159         GetCurrentDirectoryW(MAX_PATH, org_path);
2160 
2161         SetCurrentDirectoryA(drv_path);
2162     }
2163 
2164     /* query current directory path of the specified drive */
2165     ret = GetCurrentDirectoryW(MAX_PATH, buffer);
2166 
2167     /* back to the original drive */
2168     if (drive)
2169         SetCurrentDirectoryW(org_path);
2170 
2171     if (!ret)
2172         return GetLastError();
2173 
2174     return 0;
2175 }
2176 
2177 /*************************************************************************
2178  * SheChangeDirA [SHELL32.@]
2179  *
2180  * changes the current directory to the specified path
2181  * and returns 0 if successful
2182  */
2183 EXTERN_C DWORD WINAPI SheChangeDirA(LPSTR path)
2184 {
2185     if (SetCurrentDirectoryA(path))
2186         return 0;
2187     else
2188         return GetLastError();
2189 }
2190 
2191 /*************************************************************************
2192  * SheChangeDirW [SHELL32.@]
2193  *
2194  * changes the current directory to the specified path
2195  * and returns 0 if successful
2196  */
2197 EXTERN_C DWORD WINAPI SheChangeDirW(LPWSTR path)
2198 {
2199     if (SetCurrentDirectoryW(path))
2200         return 0;
2201     else
2202         return GetLastError();
2203 }
2204 
2205 /*************************************************************************
2206  * IsNetDrive            [SHELL32.66]
2207  */
2208 EXTERN_C int WINAPI IsNetDrive(int drive)
2209 {
2210     char root[4];
2211     strcpy(root, "A:\\");
2212     root[0] += (char)drive;
2213     return (GetDriveTypeA(root) == DRIVE_REMOTE);
2214 }
2215 
2216 
2217 /*************************************************************************
2218  * RealDriveType                [SHELL32.524]
2219  */
2220 EXTERN_C INT WINAPI RealDriveType(INT drive, BOOL bQueryNet)
2221 {
2222     char root[] = "A:\\";
2223     root[0] += (char)drive;
2224     return GetDriveTypeA(root);
2225 }
2226 
2227 /***********************************************************************
2228  *              SHPathPrepareForWriteW (SHELL32.@)
2229  */
2230 EXTERN_C HRESULT WINAPI SHPathPrepareForWriteW(HWND hwnd, IUnknown *modless, LPCWSTR path, DWORD flags)
2231 {
2232     DWORD res;
2233     DWORD err;
2234     LPCWSTR realpath;
2235     int len;
2236     WCHAR* last_slash;
2237     WCHAR* temppath=NULL;
2238 
2239     TRACE("%p %p %s 0x%08x\n", hwnd, modless, debugstr_w(path), flags);
2240 
2241     if (flags & ~(SHPPFW_DIRCREATE|SHPPFW_ASKDIRCREATE|SHPPFW_IGNOREFILENAME))
2242         FIXME("unimplemented flags 0x%08x\n", flags);
2243 
2244     /* cut off filename if necessary */
2245     if (flags & SHPPFW_IGNOREFILENAME)
2246     {
2247         last_slash = StrRChrW(path, NULL, '\\');
2248         if (last_slash == NULL)
2249             len = 1;
2250         else
2251             len = last_slash - path + 1;
2252         temppath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
2253         if (!temppath)
2254             return E_OUTOFMEMORY;
2255         StrCpyNW(temppath, path, len);
2256         realpath = temppath;
2257     }
2258     else
2259     {
2260         realpath = path;
2261     }
2262 
2263     /* try to create the directory if asked to */
2264     if (flags & (SHPPFW_DIRCREATE|SHPPFW_ASKDIRCREATE))
2265     {
2266         if (flags & SHPPFW_ASKDIRCREATE)
2267             FIXME("treating SHPPFW_ASKDIRCREATE as SHPPFW_DIRCREATE\n");
2268 
2269         SHCreateDirectoryExW(0, realpath, NULL);
2270     }
2271 
2272     /* check if we can access the directory */
2273     res = GetFileAttributesW(realpath);
2274 
2275     HeapFree(GetProcessHeap(), 0, temppath);
2276 
2277     if (res == INVALID_FILE_ATTRIBUTES)
2278     {
2279         err = GetLastError();
2280         if (err == ERROR_FILE_NOT_FOUND)
2281             return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
2282         return HRESULT_FROM_WIN32(err);
2283     }
2284     else if (res & FILE_ATTRIBUTE_DIRECTORY)
2285         return S_OK;
2286     else
2287         return HRESULT_FROM_WIN32(ERROR_DIRECTORY);
2288 }
2289 
2290 /***********************************************************************
2291  *              SHPathPrepareForWriteA (SHELL32.@)
2292  */
2293 EXTERN_C HRESULT WINAPI SHPathPrepareForWriteA(HWND hwnd, IUnknown *modless, LPCSTR path, DWORD flags)
2294 {
2295     WCHAR wpath[MAX_PATH];
2296     MultiByteToWideChar( CP_ACP, 0, path, -1, wpath, MAX_PATH);
2297     return SHPathPrepareForWriteW(hwnd, modless, wpath, flags);
2298 }
2299 
2300 
2301 /*
2302  * The two following background operations were modified from filedefext.cpp
2303  * They use an inordinate amount of mutable state across the string functions,
2304  * so are not easy to follow and care is required when modifying.
2305  */
2306 
2307 DWORD WINAPI
2308 _FileOpCountManager(FILE_OPERATION *op, const FILE_LIST *from)
2309 {
2310     DWORD ticks = GetTickCount();
2311     FILE_ENTRY *entryToCount;
2312 
2313     for (UINT i = 0; i < from->dwNumFiles; i++)
2314     {
2315         entryToCount = &from->feFiles[i];
2316 
2317         WCHAR theFileName[MAX_PATH];
2318         StringCchCopyW(theFileName, MAX_PATH, entryToCount->szFullPath);
2319         _FileOpCount(op, theFileName, IsAttribDir(entryToCount->attributes), &ticks);
2320     }
2321     return 0;
2322 }
2323 
2324 // All path manipulations, even when this function is nested, occur on the one buffer.
2325 static BOOL
2326 _FileOpCount(FILE_OPERATION *op, LPWSTR pwszBuf, BOOL bFolder, DWORD *ticks)
2327 {
2328     /* Find filename position */
2329     UINT cchBuf = wcslen(pwszBuf);
2330     WCHAR *pwszFilename = pwszBuf + cchBuf;
2331     size_t cchFilenameMax = MAX_PATH - cchBuf;
2332     if (!cchFilenameMax)
2333         return FALSE;
2334 
2335     if (bFolder) {
2336         *(pwszFilename++) = '\\';
2337         --cchFilenameMax;
2338         /* Find all files, FIXME: shouldn't be "*"? */
2339         StringCchCopyW(pwszFilename, cchFilenameMax, L"*");
2340     }
2341 
2342     WIN32_FIND_DATAW wfd;
2343     HANDLE hFind = FindFirstFileW(pwszBuf, &wfd);
2344     if (hFind == INVALID_HANDLE_VALUE)
2345     {
2346         ERR("FindFirstFileW %ls failed\n", pwszBuf);
2347         return FALSE;
2348     }
2349 
2350     do
2351     {
2352         if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
2353         {
2354             /* Don't process "." and ".." items */
2355             if (!wcscmp(wfd.cFileName, L".") || !wcscmp(wfd.cFileName, L".."))
2356                 continue;
2357 
2358             StringCchCopyW(pwszFilename, cchFilenameMax, wfd.cFileName);
2359             _FileOpCount(op, pwszBuf, TRUE, ticks);
2360         }
2361         else
2362         {
2363             ULARGE_INTEGER FileSize;
2364             FileSize.u.LowPart  = wfd.nFileSizeLow;
2365             FileSize.u.HighPart = wfd.nFileSizeHigh;
2366             op->totalSize.QuadPart += FileSize.QuadPart;
2367         }
2368         if (GetTickCount() - *ticks > (DWORD) 500)
2369         {
2370             // Check if the dialog has ended. If it has, we'll spin down.
2371             if (op->progress != NULL)
2372                 op->bCancelled = op->progress->HasUserCancelled();
2373 
2374             if (op->bCancelled)
2375                 break;
2376             *ticks = GetTickCount();
2377         }
2378     } while(FindNextFileW(hFind, &wfd));
2379 
2380     FindClose(hFind);
2381     return TRUE;
2382 }
2383