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