Updated authors list.
[wine] / dlls / shell32 / shlfileop.c
1 /*
2  * SHFileOperation
3  *
4  * Copyright 2000 Juergen Schmied
5  * Copyright 2002 Andriy Palamarchuk
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21
22 #include "config.h"
23 #include "wine/port.h"
24
25 #include <string.h>
26
27 #include "winreg.h"
28 #include "shellapi.h"
29 #include "shlobj.h"
30 #include "shresdef.h"
31 #include "shell32_main.h"
32 #include "undocshell.h"
33 #include "shlwapi.h"
34 #include "wine/debug.h"
35
36 WINE_DEFAULT_DEBUG_CHANNEL(shell);
37
38 BOOL SHELL_ConfirmDialog (int nKindOfDialog, LPCSTR szDir)
39 {
40         char szCaption[255], szText[255], szBuffer[MAX_PATH + 256];
41         UINT caption_resource_id, text_resource_id;
42
43         switch(nKindOfDialog) {
44
45         case ASK_DELETE_FILE:
46           caption_resource_id   = IDS_DELETEITEM_CAPTION;
47           text_resource_id      = IDS_DELETEITEM_TEXT;
48           break;
49         case ASK_DELETE_FOLDER:
50           caption_resource_id   = IDS_DELETEFOLDER_CAPTION;
51           text_resource_id      = IDS_DELETEITEM_TEXT;
52           break;
53         case ASK_DELETE_MULTIPLE_ITEM:
54           caption_resource_id   = IDS_DELETEITEM_CAPTION;
55           text_resource_id      = IDS_DELETEMULTIPLE_TEXT;
56           break;
57         case ASK_OVERWRITE_FILE:
58           caption_resource_id   = IDS_OVERWRITEFILE_CAPTION;
59           text_resource_id      = IDS_OVERWRITEFILE_TEXT;
60           break;
61         default:
62           FIXME(" Unhandled nKindOfDialog %d stub\n", nKindOfDialog);
63           return FALSE;
64         }
65
66         LoadStringA(shell32_hInstance, caption_resource_id, szCaption, sizeof(szCaption));
67         LoadStringA(shell32_hInstance, text_resource_id, szText, sizeof(szText));
68
69         FormatMessageA(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY,
70                        szText, 0, 0, szBuffer, sizeof(szBuffer), (va_list*)&szDir);
71
72         return (IDOK == MessageBoxA(GetActiveWindow(), szBuffer, szCaption, MB_OKCANCEL | MB_ICONEXCLAMATION));
73 }
74
75 /**************************************************************************
76  *      SHELL_DeleteDirectoryA()
77  *
78  * like rm -r
79  */
80
81 BOOL SHELL_DeleteDirectoryA(LPCSTR pszDir, BOOL bShowUI)
82 {
83         BOOL            ret = FALSE;
84         HANDLE          hFind;
85         WIN32_FIND_DATAA wfd;
86         char            szTemp[MAX_PATH];
87
88         strcpy(szTemp, pszDir);
89         PathAddBackslashA(szTemp);
90         strcat(szTemp, "*.*");
91
92         if (bShowUI && !SHELL_ConfirmDialog(ASK_DELETE_FOLDER, pszDir))
93           return FALSE;
94
95         if(INVALID_HANDLE_VALUE != (hFind = FindFirstFileA(szTemp, &wfd)))
96         {
97           do
98           {
99             if(strcasecmp(wfd.cFileName, ".") && strcasecmp(wfd.cFileName, ".."))
100             {
101               strcpy(szTemp, pszDir);
102               PathAddBackslashA(szTemp);
103               strcat(szTemp, wfd.cFileName);
104
105               if(FILE_ATTRIBUTE_DIRECTORY & wfd.dwFileAttributes)
106                 SHELL_DeleteDirectoryA(szTemp, FALSE);
107               else
108                 DeleteFileA(szTemp);
109             }
110           } while(FindNextFileA(hFind, &wfd));
111
112           FindClose(hFind);
113           ret = RemoveDirectoryA(pszDir);
114         }
115
116         return ret;
117 }
118
119 /**************************************************************************
120  *      SHELL_DeleteFileA()
121  */
122
123 BOOL SHELL_DeleteFileA(LPCSTR pszFile, BOOL bShowUI)
124 {
125         if (bShowUI && !SHELL_ConfirmDialog(ASK_DELETE_FILE, pszFile))
126                 return FALSE;
127
128         return DeleteFileA(pszFile);
129 }
130
131 /*************************************************************************
132  * SHCreateDirectory                       [SHELL32.165]
133  *
134  * NOTES
135  *  exported by ordinal
136  *  WinNT/2000 exports Unicode
137  */
138 DWORD WINAPI SHCreateDirectory(HWND hWnd, LPCVOID path)
139 {
140         if (SHELL_OsIsUnicode())
141           return SHCreateDirectoryExW(hWnd, path, NULL);
142         return SHCreateDirectoryExA(hWnd, path, NULL);
143 }
144
145 /*************************************************************************
146  * SHCreateDirectoryExA                     [SHELL32.@]
147  */
148 DWORD WINAPI SHCreateDirectoryExA(HWND hWnd, LPCSTR path, LPSECURITY_ATTRIBUTES sec)
149 {
150         DWORD ret;
151         TRACE("(%p, %s, %p)\n",hWnd, path, sec);
152         if ((ret = CreateDirectoryA(path, sec)))
153         {
154           SHChangeNotify(SHCNE_MKDIR, SHCNF_PATHA, path, NULL);
155         }
156         else if (hWnd)
157           FIXME("Semi-stub, non zero hWnd should be used as parent for error dialog!");
158         return ret;
159 }
160
161 /*************************************************************************
162  * SHCreateDirectoryExW                     [SHELL32.@]
163  */
164 DWORD WINAPI SHCreateDirectoryExW(HWND hWnd, LPCWSTR path, LPSECURITY_ATTRIBUTES sec)
165 {
166         DWORD ret;
167         TRACE("(%p, %s, %p)\n",hWnd, debugstr_w(path), sec);
168         if ((ret = CreateDirectoryW(path, sec)))
169         {
170           SHChangeNotify(SHCNE_MKDIR, SHCNF_PATHW, path, NULL);
171         }
172         else if (hWnd)
173           FIXME("Semi-stub, non zero hWnd should be used as parent for error dialog!");
174         return ret;
175 }
176
177 /************************************************************************
178  * Win32DeleteFile                         [SHELL32.164]
179  *
180  * Deletes a file. Also triggers a change notify if one exists.
181  *
182  * NOTES:
183  *  Verified on Win98 / IE 5 (SHELL32 4.72, March 1999 build) to be ANSI.
184  *  This is Unicode on NT/2000
185  */
186 static BOOL Win32DeleteFileA(LPCSTR fName)
187 {
188         TRACE("%p(%s)\n", fName, fName);
189
190         DeleteFileA(fName);
191         SHChangeNotify(SHCNE_DELETE, SHCNF_PATHA, fName, NULL);
192         return TRUE;
193 }
194
195 static BOOL Win32DeleteFileW(LPCWSTR fName)
196 {
197         TRACE("%p(%s)\n", fName, debugstr_w(fName));
198
199         DeleteFileW(fName);
200         SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, fName, NULL);
201         return TRUE;
202 }
203
204 DWORD WINAPI Win32DeleteFileAW(LPCVOID fName)
205 {
206         if (SHELL_OsIsUnicode())
207           return Win32DeleteFileW(fName);
208         return Win32DeleteFileA(fName);
209 }
210
211 /**************************************************************************
212  *      SHELL_FileNamesMatch()
213  *
214  * Accepts two \0 delimited lists of the file names. Checks whether number of
215  * files in the both lists is the same.
216  */
217 BOOL SHELL_FileNamesMatch(LPCSTR pszFiles1, LPCSTR pszFiles2)
218 {
219     while ((pszFiles1[strlen(pszFiles1) + 1] != '\0') &&
220            (pszFiles2[strlen(pszFiles2) + 1] != '\0'))
221     {
222         pszFiles1 += strlen(pszFiles1) + 1;
223         pszFiles2 += strlen(pszFiles2) + 1;
224     }
225
226     return
227         ((pszFiles1[strlen(pszFiles1) + 1] == '\0') &&
228          (pszFiles2[strlen(pszFiles2) + 1] == '\0')) ||
229         ((pszFiles1[strlen(pszFiles1) + 1] != '\0') &&
230          (pszFiles2[strlen(pszFiles2) + 1] != '\0'));
231 }
232
233 /*************************************************************************
234  * SHFileOperationA                             [SHELL32.@]
235  *
236  * NOTES
237  *     exported by name
238  */
239 DWORD WINAPI SHFileOperationA (LPSHFILEOPSTRUCTA lpFileOp)
240 {
241         LPSTR pFrom = (LPSTR)lpFileOp->pFrom;
242         LPSTR pTo = (LPSTR)lpFileOp->pTo;
243         LPSTR pTempTo;
244         TRACE("flags (0x%04x) : %s%s%s%s%s%s%s%s%s%s%s%s \n", lpFileOp->fFlags,
245                 lpFileOp->fFlags & FOF_MULTIDESTFILES ? "FOF_MULTIDESTFILES " : "",
246                 lpFileOp->fFlags & FOF_CONFIRMMOUSE ? "FOF_CONFIRMMOUSE " : "",
247                 lpFileOp->fFlags & FOF_SILENT ? "FOF_SILENT " : "",
248                 lpFileOp->fFlags & FOF_RENAMEONCOLLISION ? "FOF_RENAMEONCOLLISION " : "",
249                 lpFileOp->fFlags & FOF_NOCONFIRMATION ? "FOF_NOCONFIRMATION " : "",
250                 lpFileOp->fFlags & FOF_WANTMAPPINGHANDLE ? "FOF_WANTMAPPINGHANDLE " : "",
251                 lpFileOp->fFlags & FOF_ALLOWUNDO ? "FOF_ALLOWUNDO " : "",
252                 lpFileOp->fFlags & FOF_FILESONLY ? "FOF_FILESONLY " : "",
253                 lpFileOp->fFlags & FOF_SIMPLEPROGRESS ? "FOF_SIMPLEPROGRESS " : "",
254                 lpFileOp->fFlags & FOF_NOCONFIRMMKDIR ? "FOF_NOCONFIRMMKDIR " : "",
255                 lpFileOp->fFlags & FOF_NOERRORUI ? "FOF_NOERRORUI " : "",
256                 lpFileOp->fFlags & 0xf800 ? "MORE-UNKNOWN-Flags" : "");
257         switch(lpFileOp->wFunc) {
258         case FO_COPY:
259         case FO_MOVE:
260         {
261                 /* establish when pTo is interpreted as the name of the destination file
262                  * or the directory where the Fromfile should be copied to.
263                  * This depends on:
264                  * (1) pTo points to the name of an existing directory;
265                  * (2) the flag FOF_MULTIDESTFILES is present;
266                  * (3) whether pFrom point to multiple filenames.
267                  *
268                  * Some experiments:
269                  *
270                  * destisdir               1 1 1 1 0 0 0 0
271                  * FOF_MULTIDESTFILES      1 1 0 0 1 1 0 0
272                  * multiple from filenames 1 0 1 0 1 0 1 0
273                  *                         ---------------
274                  * copy files to dir       1 0 1 1 0 0 1 0
275                  * create dir              0 0 0 0 0 0 1 0
276                  */
277                 int multifrom = pFrom[strlen(pFrom) + 1] != '\0';
278                 int destisdir = PathIsDirectoryA( pTo );
279                 int todir = 0;
280
281                 if (lpFileOp->wFunc == FO_COPY)
282                     TRACE("File Copy:\n");
283                 else
284                     TRACE("File Move:\n");
285
286                 if( destisdir ) {
287                     if ( !((lpFileOp->fFlags & FOF_MULTIDESTFILES) && !multifrom))
288                         todir = 1;
289                 } else {
290                     if ( !(lpFileOp->fFlags & FOF_MULTIDESTFILES) && multifrom)
291                         todir = 1;
292                 }
293
294                 if ((pTo[strlen(pTo) + 1] != '\0') &&
295                     !(lpFileOp->fFlags & FOF_MULTIDESTFILES))
296                 {
297                     WARN("Attempt to use multiple file names as a destination "
298                          "without specifying FOF_MULTIDESTFILES\n");
299                     return 1;
300                 }
301
302                 if ((lpFileOp->fFlags & FOF_MULTIDESTFILES) &&
303                     !SHELL_FileNamesMatch(pTo, pFrom))
304                 {
305                     WARN("Attempt to use multiple file names as a destination "
306                          "with mismatching number of files in the source and "
307                          "destination lists\n");
308                     return 1;
309                 }
310
311                 if ( todir ) {
312                     char szTempFrom[MAX_PATH];
313                     char *fromfile;
314                     int lenPTo;
315                     if ( ! destisdir) {
316                       TRACE("   creating directory %s\n",pTo);
317                       SHCreateDirectoryExA(NULL, pTo, NULL);
318                     }
319                     lenPTo = strlen(pTo);
320                     while(1) {
321                         HANDLE hFind;
322                         WIN32_FIND_DATAA wfd;
323
324                         if(!pFrom[0]) break;
325                         TRACE("   From Pattern='%s'\n", pFrom);
326                         if(INVALID_HANDLE_VALUE != (hFind = FindFirstFileA(pFrom, &wfd)))
327                         {
328                           do
329                           {
330                             if(strcasecmp(wfd.cFileName, ".") && strcasecmp(wfd.cFileName, ".."))
331                             {
332                               strcpy(szTempFrom, pFrom);
333
334                               pTempTo = HeapAlloc(GetProcessHeap(), 0,
335                                                   lenPTo + strlen(wfd.cFileName) + 5);
336                               if (pTempTo) {
337                                   strcpy(pTempTo,pTo);
338                                   PathAddBackslashA(pTempTo);
339                                   strcat(pTempTo,wfd.cFileName);
340
341                                   fromfile = PathFindFileNameA(szTempFrom);
342                                   fromfile[0] = '\0';
343                                   PathAddBackslashA(szTempFrom);
344                                   strcat(szTempFrom, wfd.cFileName);
345                                   TRACE("   From='%s' To='%s'\n", szTempFrom, pTempTo);
346                                   if(lpFileOp->wFunc == FO_COPY)
347                                   {
348                                       if(FILE_ATTRIBUTE_DIRECTORY & wfd.dwFileAttributes)
349                                       {
350                                           /* copy recursively */
351                                           if(!(lpFileOp->fFlags & FOF_FILESONLY))
352                                           {
353                                               SHFILEOPSTRUCTA shfo;
354
355                                               SHCreateDirectoryExA(NULL, pTempTo, NULL);
356                                               PathAddBackslashA(szTempFrom);
357                                               strcat(szTempFrom, "*.*");
358                                               szTempFrom[strlen(szTempFrom) + 1] = '\0';
359                                               pTempTo[strlen(pTempTo) + 1] = '\0';
360                                               memcpy(&shfo, lpFileOp, sizeof(shfo));
361                                               shfo.pFrom = szTempFrom;
362                                               shfo.pTo = pTempTo;
363                                               SHFileOperationA(&shfo);
364
365                                               szTempFrom[strlen(szTempFrom) - 4] = '\0';
366                                           }
367                                       }
368                                       else
369                                           CopyFileA(szTempFrom, pTempTo, FALSE);
370                                   }
371                                   else
372                                   {
373                                       /* move file/directory */
374                                       MoveFileA(szTempFrom, pTempTo);
375                                   }
376                                   HeapFree(GetProcessHeap(), 0, pTempTo);
377                               }
378                             }
379                           } while(FindNextFileA(hFind, &wfd));
380                           FindClose(hFind);
381                         }
382                         else
383                         {
384                             /* can't find file with specified name */
385                             break;
386                         }
387                         pFrom += strlen(pFrom) + 1;
388                     }
389                 } else {
390                     while (1) {
391                             if(!pFrom[0]) break;
392                             if(!pTo[0]) break;
393                             TRACE("   From='%s' To='%s'\n", pFrom, pTo);
394
395                             pTempTo = HeapAlloc(GetProcessHeap(), 0, strlen(pTo)+1);
396                             if (pTempTo)
397                             {
398                                 strcpy( pTempTo, pTo );
399                                 PathRemoveFileSpecA(pTempTo);
400                                 TRACE("   Creating Directory '%s'\n", pTempTo);
401                                 SHCreateDirectoryExA(NULL, pTempTo, NULL);
402                                 HeapFree(GetProcessHeap(), 0, pTempTo);
403                             }
404                             if (lpFileOp->wFunc == FO_COPY)
405                                 CopyFileA(pFrom, pTo, FALSE);
406                             else
407                                 MoveFileA(pFrom, pTo);
408
409                             pFrom += strlen(pFrom) + 1;
410                             pTo += strlen(pTo) + 1;
411                     }
412                 }
413                 TRACE("Setting AnyOpsAborted=FALSE\n");
414                 lpFileOp->fAnyOperationsAborted=FALSE;
415                 return 0;
416         }
417
418         case FO_DELETE:
419         {
420                 HANDLE          hFind;
421                 WIN32_FIND_DATAA wfd;
422                 char            szTemp[MAX_PATH];
423                 char            *file_name;
424
425                 TRACE("File Delete:\n");
426                 while(1) {
427                         if(!pFrom[0]) break;
428                         TRACE("   Pattern='%s'\n", pFrom);
429                         if(INVALID_HANDLE_VALUE != (hFind = FindFirstFileA(pFrom, &wfd)))
430                         {
431                           do
432                           {
433                             if(strcasecmp(wfd.cFileName, ".") && strcasecmp(wfd.cFileName, ".."))
434                             {
435                               strcpy(szTemp, pFrom);
436                               file_name = PathFindFileNameA(szTemp);
437                               file_name[0] = '\0';
438                               PathAddBackslashA(szTemp);
439                               strcat(szTemp, wfd.cFileName);
440
441                               TRACE("   File='%s'\n", szTemp);
442                               if(FILE_ATTRIBUTE_DIRECTORY & wfd.dwFileAttributes)
443                               {
444                                 if(!(lpFileOp->fFlags & FOF_FILESONLY))
445                                     SHELL_DeleteDirectoryA(szTemp, FALSE);
446                               }
447                               else
448                                 DeleteFileA(szTemp);
449                             }
450                           } while(FindNextFileA(hFind, &wfd));
451
452                           FindClose(hFind);
453                         }
454                         pFrom += strlen(pFrom) + 1;
455                 }
456                 TRACE("Setting AnyOpsAborted=FALSE\n");
457                 lpFileOp->fAnyOperationsAborted=FALSE;
458                 return 0;
459         }
460
461         case FO_RENAME:
462             TRACE("File Rename:\n");
463             if (pFrom[strlen(pFrom) + 1] != '\0')
464             {
465                 WARN("Attempt to rename more than one file\n");
466                 return 1;
467             }
468             lpFileOp->fAnyOperationsAborted = FALSE;
469             TRACE("From %s, To %s\n", pFrom, pTo);
470             return !MoveFileA(pFrom, pTo);
471
472         default:
473                 FIXME("Unhandled shell file operation %d\n", lpFileOp->wFunc);
474         }
475
476         return 1;
477 }
478
479 /*************************************************************************
480  * SHFileOperationW                             [SHELL32.@]
481  *
482  * NOTES
483  *     exported by name
484  */
485 DWORD WINAPI SHFileOperationW (LPSHFILEOPSTRUCTW lpFileOp)
486 {
487         FIXME("(%p):stub.\n", lpFileOp);
488         return 1;
489 }
490
491 /*************************************************************************
492  * SHFileOperation                              [SHELL32.@]
493  *
494  */
495 DWORD WINAPI SHFileOperationAW(LPVOID lpFileOp)
496 {
497         if (SHELL_OsIsUnicode())
498           return SHFileOperationW(lpFileOp);
499         return SHFileOperationA(lpFileOp);
500 }
501
502 /*************************************************************************
503  * SheGetDirW [SHELL32.281]
504  *
505  */
506 HRESULT WINAPI SheGetDirW(LPWSTR u, LPWSTR v)
507 {       FIXME("%p %p stub\n",u,v);
508         return 0;
509 }
510
511 /*************************************************************************
512  * SheChangeDirW [SHELL32.274]
513  *
514  */
515 HRESULT WINAPI SheChangeDirW(LPWSTR u)
516 {       FIXME("(%s),stub\n",debugstr_w(u));
517         return 0;
518 }
519
520 /*************************************************************************
521  * IsNetDrive                   [SHELL32.66]
522  */
523 BOOL WINAPI IsNetDrive(DWORD drive)
524 {
525         char root[4];
526         strcpy(root, "A:\\");
527         root[0] += (char)drive;
528         return (GetDriveTypeA(root) == DRIVE_REMOTE);
529 }