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