source: trunk/eraser/Eraser.Shell/CtxMenu.cpp @ 2531

Revision 2531, 32.1 KB checked in by lowjoel, 3 years ago (diff)

Remove debug code.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Rev URL
Line 
1/*
2 * $Id$
3 * Copyright 2008-2012 The Eraser Project
4 * Original Author: Kasra Nassiri <cjax@users.sourceforge.net>
5 * Modified By: Joel Low <lowjoel@users.sourceforge.net>
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22#include "stdafx.h"
23#include "CtxMenu.h"
24#include "DllMain.h"
25#include "Utils.h"
26#include <sstream>
27
28extern "C"
29{
30    typedef LONG NTSTATUS;
31    enum KEY_INFORMATION_CLASS
32    {
33        KeyBasicInformation,
34        KeyNodeInformation,
35        KeyFullInformation,
36        KeyNameInformation,
37        KeyCachedInformation,
38        KeyVirtualizationInformation
39    };
40
41    struct KEY_BASIC_INFORMATION
42    {
43        LARGE_INTEGER LastWriteTime;
44        ULONG TitleIndex;
45        ULONG NameLength;
46        WCHAR Name[1];
47    };
48
49    struct KEY_NODE_INFORMATION
50    {
51        LARGE_INTEGER LastWriteTime;
52        ULONG TitleIndex;
53        ULONG ClassOffset;
54        ULONG ClassLength;
55        ULONG NameLength;
56        WCHAR Name[1];
57    };
58
59    typedef NTSTATUS (__stdcall *pNtQueryKey)(HANDLE KeyHandle, KEY_INFORMATION_CLASS KeyInformationClass,
60        PVOID KeyInformation, ULONG Length, PULONG ResultLength);
61    pNtQueryKey NtQueryKey = NULL;
62}
63
64namespace Eraser {
65    HRESULT CCtxMenu::FinalConstruct()
66    {
67        //Initialise member variables.
68        MenuID = 0;
69        std::wstring menuTitle(LoadString(IDS_ERASER));
70        MenuTitle = new wchar_t[menuTitle.length() + 1];
71        wcscpy_s(MenuTitle, menuTitle.length() + 1, menuTitle.c_str());
72
73        //Check if the shell extension has been disabled.
74        Handle<HKEY> eraserKey;
75        LONG openKeyResult = RegOpenKeyEx(HKEY_CURRENT_USER,
76            L"Software\\Eraser\\Eraser 6\\3460478d-ed1b-4ecc-96c9-2ca0e8500557\\", 0,
77            KEY_READ, &static_cast<HKEY&>(eraserKey));
78
79        switch (openKeyResult)
80        {
81        case ERROR_FILE_NOT_FOUND:
82            //No settings defined: we default to enabling the shell extension.
83            return S_OK;
84
85        case ERROR_SUCCESS:
86            break;
87           
88        default:
89            return E_FAIL;
90        }
91
92        //Check the value of the IntegrateWithShell value.
93        DWORD value = 0;
94        DWORD valueType = 0;
95        DWORD valueSize = sizeof(value);
96        DWORD error = RegQueryValueEx(eraserKey, L"IntegrateWithShell", NULL, &valueType,
97            reinterpret_cast<BYTE*>(&value), &valueSize);
98        if (error == ERROR_SUCCESS && value == 0)
99        {
100            return E_FAIL;
101        }
102
103        return S_OK;
104    }
105
106    HRESULT CCtxMenu::FinalRelease()
107    {
108        delete[] MenuTitle;
109        return S_OK;
110    }
111
112    HRESULT CCtxMenu::Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj,
113                                 HKEY hProgID)
114    {
115        //Determine where the shell extension was invoked from.
116        if (GetHKeyPath(hProgID) == L"{645FF040-5081-101B-9F08-00AA002F954E}")
117        {
118            InvokeReason = INVOKEREASON_RECYCLEBIN;
119
120            //We can't do much other processing: the LPDATAOBJECT parameter contains
121            //data that is a private type so we don't know how to query for it.
122            return S_OK;
123        }
124
125        //Check whether pDataObj is NULL: if so, it is a directory background click
126        else if (pDataObj == NULL)
127        {
128            InvokeReason = INVOKEREASON_DIRECTORY_BACKGROUND;
129
130            //Translate the drop path to a location on the filesystem.
131            wchar_t dropTargetPath[MAX_PATH];
132            if (!SHGetPathFromIDList(pidlFolder, dropTargetPath))
133                return E_FAIL;
134
135            DragDropDestinationDirectory = dropTargetPath;
136
137            //We can't do anything else. The data object is null.
138            return S_OK;
139        }
140
141        //Check pidlFolder for the drop path, if it exists. This is for drag-and-drop
142        //context menus.
143        else if (pidlFolder != NULL)
144        {
145            InvokeReason = INVOKEREASON_DRAGDROP;
146
147            //Translate the drop path to a location on the filesystem.
148            wchar_t dropTargetPath[MAX_PATH];
149            if (!SHGetPathFromIDList(pidlFolder, dropTargetPath))
150                return E_FAIL;
151
152            DragDropDestinationDirectory = dropTargetPath;
153        }
154
155        //Okay, everything else is a simple context menu for a set of selected files/
156        //folders/drives.
157        else
158            InvokeReason = INVOKEREASON_FILEFOLDER;
159
160        //Look for CF_HDROP data in the data object.
161        FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
162        STGMEDIUM stg = { TYMED_HGLOBAL };
163        if (FAILED(pDataObj->GetData(&fmt, &stg)))
164            //Nope! Return an "invalid argument" error back to Explorer.
165            return E_INVALIDARG;
166
167        //Get a pointer to the actual data.
168        HDROP hDrop = static_cast<HDROP>(GlobalLock(stg.hGlobal));
169        if (hDrop == NULL)
170            return E_INVALIDARG;
171
172        //Assign the list of files selected.
173        SelectedFiles = GetHDropPaths(hDrop);
174
175        //Check if the selected files is only one item long and if that item is the
176        //Start button (for Windows XP)
177        if (SelectedFiles.size() == 1)
178        {
179            wchar_t startMenuPath[MAX_PATH];
180            if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_STARTMENU, NULL,
181                SHGFP_TYPE_CURRENT, startMenuPath)))
182            {
183                if (SelectedFiles.front() == startMenuPath)
184                    //Yes, it is. Don't display the Eraser context menu for this.
185                    return E_INVALIDARG;
186            }
187        }
188
189        //Clean up.
190        GlobalUnlock(stg.hGlobal);
191        ReleaseStgMedium(&stg);
192        return SelectedFiles.empty() ? E_INVALIDARG : S_OK;
193    }
194
195    HRESULT CCtxMenu::QueryContextMenu(HMENU hmenu, UINT uMenuIndex, UINT uidFirstCmd,
196                                       UINT /*uidLastCmd*/, UINT uFlags)
197    {
198        //First check if we're running on Vista or later
199        bool isVistaOrLater = false;
200        {
201            //Set the bitmap for the registered item. Vista machines will be set using a DIB,
202            //older machines will be ownerdrawn.
203            OSVERSIONINFO osvi;
204            ZeroMemory(&osvi, sizeof(osvi));
205            osvi.dwOSVersionInfoSize = sizeof(osvi);
206
207            isVistaOrLater = GetVersionEx(&osvi) && osvi.dwPlatformId == VER_PLATFORM_WIN32_NT &&
208                osvi.dwMajorVersion >= 6;
209        }
210
211        //If the flags include CMF_DEFAULTONLY then we shouldn't do anything.
212        if (uFlags & CMF_DEFAULTONLY)
213            return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
214
215        //If the flags include CMF_VERBSONLY then we shouldn't do anything as we do not
216        //want to operate on the target of a shortcut implicitly.
217        if (uFlags & CMF_VERBSONLY)
218            return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
219
220        //First, create and populate a submenu.
221        UINT uID = uidFirstCmd;
222        HMENU hSubmenu = CreatePopupMenu();
223
224        //Create the submenu, following the order defined in the CEraserLPVERB enum, creating
225        //only items which are applicable.
226        Actions applicableActions = GetApplicableActions();
227
228        //If we have no actions that the user can execute, just return an error code.
229        if (!applicableActions)
230            return E_INVALIDARG;
231
232        VerbMenuIndices.clear();
233        if (applicableActions & ACTION_ERASE)
234        {
235            InsertMenu(hSubmenu, ACTION_ERASE, MF_BYPOSITION, uID++,
236                LoadString(IDS_ACTION_ERASE).c_str());              //Erase
237            VerbMenuIndices.push_back(ACTION_ERASE);
238        }
239        if (applicableActions & ACTION_ERASE_UNUSED_SPACE)
240        {
241            MENUITEMINFO mii = { sizeof(MENUITEMINFO) };
242            mii.wID = uID++;
243            mii.fMask = MIIM_STRING | MIIM_ID;
244
245            std::wstring str(LoadString(IDS_ACTION_ERASEUNUSEDSPACE));
246            std::vector<wchar_t> buffer(str.length() + 1);
247            wcscpy_s(&buffer.front(), str.length() + 1, str.c_str());
248            mii.dwTypeData = &buffer.front();
249
250            if (isVistaOrLater)
251            {
252                SHSTOCKICONINFO sii;
253                ::ZeroMemory(&sii, sizeof(sii));
254                sii.cbSize = sizeof(sii);
255
256                static HMODULE shellAPI = LoadLibrary(L"Shell32.dll");
257                typedef HRESULT (__stdcall *pSHGetStockIconInfo)(SHSTOCKICONID siid, UINT uFlags,
258                    SHSTOCKICONINFO* psii);
259                pSHGetStockIconInfo SHGetStockIconInfo = reinterpret_cast<pSHGetStockIconInfo>(
260                    GetProcAddress(shellAPI, "SHGetStockIconInfo"));
261
262                unsigned dimensions = GetSystemMetrics(SM_CXSMICON);
263                if (SUCCEEDED(SHGetStockIconInfo(SIID_SHIELD, SHGSI_ICON | SHGSI_SMALLICON, &sii)))
264                {
265                    Handle<HICON> icon(sii.hIcon);
266                    static HBITMAP dib = NULL;
267
268                    if (dib == NULL)
269                    {
270                        dib = CreateDIB(dimensions, dimensions, NULL);
271                        Handle<HDC> hdc(CreateCompatibleDC(NULL));
272                        SelectObject(hdc, dib);
273
274                        DrawIconEx(hdc, 0, 0, icon, dimensions, dimensions, 0, NULL, DI_NORMAL);
275                        SelectObject(hdc, NULL);
276                    }
277
278                    mii.hbmpItem = dib;
279                    mii.fMask |= MIIM_BITMAP;
280                }
281            }
282           
283            InsertMenuItem(hSubmenu, ACTION_ERASE_UNUSED_SPACE, MF_BYPOSITION, &mii);
284            VerbMenuIndices.push_back(ACTION_ERASE_UNUSED_SPACE);
285        }
286        //-------------------------------------------------------------------------
287        if (applicableActions & ACTION_SECURE_MOVE)
288        {
289            //Insert the separator if we aren't the only menu item
290            if (applicableActions != ACTION_SECURE_MOVE)
291                InsertSeparator(hSubmenu);
292
293            InsertMenu(hSubmenu, ACTION_SECURE_MOVE, MF_BYPOSITION, uID++,
294                LoadString(IDS_ACTION_SECUREMOVE).c_str());         //Secure Move
295            VerbMenuIndices.push_back(ACTION_SECURE_MOVE);
296        }
297        if (applicableActions & ACTION_SECURE_PASTE)
298        {
299            VerbMenuIndices.push_back(ACTION_SECURE_PASTE);
300        }
301
302        //Insert the submenu into the Context menu provided by Explorer.
303        {
304            MENUITEMINFO mii = { sizeof(MENUITEMINFO) };
305            mii.wID = uID++;
306            mii.fMask = MIIM_STRING | MIIM_ID | MIIM_BITMAP;
307            if (InvokeReason != INVOKEREASON_DIRECTORY_BACKGROUND)
308            {
309                mii.fMask |= MIIM_SUBMENU;
310                mii.hSubMenu = hSubmenu;
311                mii.dwTypeData = const_cast<wchar_t*>(MenuTitle);
312            }
313            else
314            {
315                mii.dwTypeData = L"E&raser Secure Paste";
316            }
317           
318            MenuID = mii.wID;
319
320            //Set the bitmap for the registered item. Vista machines will be set using a DIB,
321            //older machines will be ownerdrawn.
322            if (isVistaOrLater)
323            {
324                Handle<HICON> icon(GetMenuIcon());
325                mii.hbmpItem = GetMenuBitmapFromIcon(icon);
326            }
327            else if (InvokeReason != INVOKEREASON_DRAGDROP)
328            {
329                mii.hbmpItem = HBMMENU_CALLBACK;
330            }
331
332            UINT menuIndex = uMenuIndex++;
333            InsertMenuItem(hmenu, menuIndex, TRUE, &mii);
334
335            //Disable the menu item - IF the user selected the recycle bin AND the
336            //recycle bin is empty
337            if (InvokeReason == INVOKEREASON_RECYCLEBIN)
338            {
339                SHQUERYRBINFO sqrbi;
340                ::ZeroMemory(&sqrbi, sizeof(sqrbi));
341                sqrbi.cbSize = sizeof(sqrbi);
342                if (SUCCEEDED(SHQueryRecycleBin(NULL, &sqrbi)))
343                {
344                    EnableMenuItem(hmenu, menuIndex, MF_BYPOSITION |
345                        ((sqrbi.i64NumItems != 0) ? MF_ENABLED : MF_DISABLED));
346                }
347            }
348        }
349
350        return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, uID - uidFirstCmd);
351    }
352
353    HRESULT CCtxMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
354    {
355        return HandleMenuMsg2(uMsg, wParam, lParam, NULL);
356    }
357
358    HRESULT CCtxMenu::HandleMenuMsg2(UINT uMsg, WPARAM /*wParam*/, LPARAM lParam,
359                                     LRESULT* result)
360    {
361        //Skip if we aren't handling our own.
362        bool handleResult = false;
363        switch (uMsg)
364        {
365        case WM_MEASUREITEM:
366            {
367                MEASUREITEMSTRUCT* mis = reinterpret_cast<MEASUREITEMSTRUCT*>(lParam);
368                if (mis->itemID == MenuID)
369                    handleResult = OnMeasureItem(mis->itemWidth, mis->itemHeight);
370                break;
371            }
372
373        case WM_DRAWITEM:
374            {
375                DRAWITEMSTRUCT* dis = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
376                if (dis->itemID == MenuID)
377                    handleResult = OnDrawItem(dis->hDC, dis->rcItem, dis->itemAction, dis->itemState);
378            }
379        }
380
381        if (result)
382            *result = handleResult;
383        return S_OK;
384    }
385
386    bool CCtxMenu::OnMeasureItem(UINT& itemWidth, UINT& itemHeight)
387    {
388        //Account for the size of the bitmap.
389        itemWidth = 0;
390        itemHeight = std::max<UINT>(GetSystemMetrics(SM_CYMENUCHECK), itemHeight);
391        return true;
392    }
393
394    bool CCtxMenu::OnDrawItem(HDC hdc, RECT rect, UINT /*action*/, UINT state)
395    {
396        //Get the icon and calculate its size.
397        Handle<HICON> icon(GetMenuIcon());
398        int iconSize = GetSystemMetrics(SM_CXMENUCHECK);
399        int iconMargin = GetSystemMetrics(SM_CXEDGE);
400
401        //Draw the bitmap.
402        DrawState(hdc, NULL, NULL, reinterpret_cast<LPARAM>(static_cast<HICON>(icon)),
403            NULL, rect.left - iconMargin - iconSize,
404            rect.top + (rect.bottom - rect.top - iconSize) / 2, 0, 0,
405            DST_ICON | ((state & ODS_DISABLED) ? DSS_DISABLED : 0));
406
407        return true;
408    }
409
410    HRESULT CCtxMenu::GetCommandString(UINT_PTR idCmd, UINT uFlags, UINT* /*pwReserved*/,
411                                       LPSTR pszName, UINT cchMax)
412    {
413        //We only know how to handle help string requests.
414        if (!(uFlags & GCS_HELPTEXT))
415            return E_INVALIDARG;
416
417        //Get the command string for the given id
418        if (idCmd >= VerbMenuIndices.size())
419            return E_INVALIDARG;
420
421        std::wstring commandString;
422        switch (VerbMenuIndices[idCmd])
423        {
424        case ACTION_ERASE:
425            commandString = LoadString(IDS_HELPSTRING_ERASE);
426            break;
427        case ACTION_ERASE_UNUSED_SPACE:
428            commandString = LoadString(IDS_HELPSTRING_ERASEUNUSEDSPACE);
429            break;
430        case ACTION_SECURE_MOVE:
431            commandString = LoadString(IDS_HELPSTRING_SECUREMOVE);
432            break;
433        default:
434            //We don't know what action this is: return E_INVALIDARG.
435            return E_INVALIDARG;
436        }
437
438        //Return the help string to Explorer.
439        if (uFlags & GCS_UNICODE)
440            wcscpy_s(reinterpret_cast<wchar_t*>(pszName), cchMax, commandString.c_str());
441        else
442        {
443            size_t convCount = 0;
444            wcstombs_s(&convCount, pszName, cchMax, commandString.c_str(), commandString.length());
445        }
446
447        return S_OK;
448    }
449
450    HRESULT CCtxMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pCmdInfo)
451    {
452        //If lpVerb really points to a string, ignore this function call and bail out.
453        if (HIWORD(pCmdInfo->lpVerb) != 0)
454            return E_INVALIDARG;
455
456        //If the verb index refers to an item outside the bounds of our VerbMenuIndices
457        //vector, exit.
458        if (LOWORD(pCmdInfo->lpVerb) >= VerbMenuIndices.size())
459            return E_INVALIDARG;
460
461        //Show a busy cursor.
462        BusyCursor cursor;
463
464        //Build the command line
465        bool commandElevate = false;
466        bool commandConfirm = true;
467        std::wstring commandLine;
468        switch (VerbMenuIndices[LOWORD(pCmdInfo->lpVerb)])
469        {
470        case ACTION_ERASE:
471            commandLine = GenerateEraseCommand();
472            break;
473
474        case ACTION_ERASE_UNUSED_SPACE:
475            //Erasing unused space requires elevation
476            commandLine = GenerateEraseUnusedSpaceCommand();
477            break;
478
479        case ACTION_SECURE_MOVE:
480            //Securely move the file/folder. If the DragDropDestinationDirectory member
481            //is blank, query the user for a path to copy the items to
482            if (DragDropDestinationDirectory.empty())
483            {
484                BROWSEINFO info;
485                ::ZeroMemory(&info, sizeof(info));
486
487                //Set the title of the dialog.
488                std::wstring title(LoadString(IDS_MESSAGE_SELECT_MOVE_DESTINATION));
489                std::vector<wchar_t> titleBuffer(title.length() + 1);
490                wcscpy_s(&titleBuffer.front(), title.length() + 1, title.c_str());
491
492                //Then set the display settings.
493                info.lpszTitle = &titleBuffer.front();
494                info.ulFlags = BIF_RETURNONLYFSDIRS | BIF_RETURNFSANCESTORS | BIF_USENEWUI | BIF_SHAREABLE;
495
496                //Display the dialog.
497                PIDLIST_ABSOLUTE pidl = SHBrowseForFolder(&info);
498
499                wchar_t buffer[MAX_PATH];
500                bool pathSucceeded = SHGetPathFromIDList(pidl, buffer) != FALSE;
501                ILFree(pidl);
502                if (pathSucceeded)
503                    DragDropDestinationDirectory = buffer;
504                else
505                    return E_ABORT;
506            }
507
508            if (*DragDropDestinationDirectory.rbegin() == '\\')
509                DragDropDestinationDirectory += '\\';
510            commandLine = GenerateSecureMoveCommand();
511            break;
512
513        case ACTION_SECURE_PASTE:
514            {
515                //Set the destination for the paste operation.
516                if (*DragDropDestinationDirectory.rbegin() == '\\')
517                    DragDropDestinationDirectory += '\\';
518
519                //Query the files from the clipboard.
520                std::vector<std::wstring> paths;
521                if (OpenClipboard(NULL))
522                {
523                    HDROP fileHandle = reinterpret_cast<HDROP>(GetClipboardData(CF_HDROP));
524                    if (fileHandle)
525                        SelectedFiles = GetHDropPaths(fileHandle);
526
527                    EmptyClipboard();
528                    CloseClipboard();
529                    commandLine = GenerateSecureMoveCommand();
530                }
531            }
532            break;
533
534        default:
535            if (!(pCmdInfo->fMask & CMIC_MASK_FLAG_NO_UI))
536            {
537                MessageBox(pCmdInfo->hwnd, FormatString(LoadString(IDS_ERROR_UNKNOWNACTION),
538                    VerbMenuIndices[LOWORD(pCmdInfo->lpVerb)]).c_str(),
539                    LoadString(IDS_ERASERSHELLEXT).c_str(), MB_OK | MB_ICONERROR);
540                return E_INVALIDARG;
541            }
542        }
543
544        try
545        {
546            RunEraser(commandLine, commandConfirm, commandElevate, pCmdInfo->hwnd, pCmdInfo->nShow);
547        }
548        catch (const std::wstring& e)
549        {
550            if (!(pCmdInfo->fMask & CMIC_MASK_FLAG_NO_UI))
551            {
552                MessageBox(pCmdInfo->hwnd, e.c_str(), LoadString(IDS_ERASERSHELLEXT).c_str(),
553                    MB_OK | MB_ICONERROR);
554            }
555        }
556
557        return S_OK;
558    }
559
560    CCtxMenu::Actions CCtxMenu::GetApplicableActions()
561    {
562        unsigned result = 0;
563       
564        //First decide the actions which are applicable to the current invocation
565        //reason.
566        switch (InvokeReason)
567        {
568        case INVOKEREASON_RECYCLEBIN:
569            result |= ACTION_ERASE;
570            break;
571        case INVOKEREASON_FILEFOLDER:
572            result |= ACTION_ERASE | ACTION_ERASE_UNUSED_SPACE;
573        case INVOKEREASON_DRAGDROP:
574            result |= ACTION_SECURE_MOVE;
575            break;
576        case INVOKEREASON_DIRECTORY_BACKGROUND:
577            result |= ACTION_SECURE_PASTE;
578        }
579
580        //Remove actions that don't apply to the current invocation reason.
581        for (std::list<std::wstring>::const_iterator i = SelectedFiles.begin();
582            i != SelectedFiles.end(); ++i)
583        {
584            //Remove trailing slashes if they are directories.
585            std::wstring item(*i);
586
587            //Check if the path is a path to a volume, if it is not, remove the
588            //erase unused space verb.
589            wchar_t volumeName[MAX_PATH];
590            if (!GetVolumeNameForVolumeMountPoint(item.c_str(), volumeName,
591                sizeof(volumeName) / sizeof(volumeName[0])))
592            {
593                result &= ~ACTION_ERASE_UNUSED_SPACE;
594            }
595        }
596
597        //Check that the clipboard has files for querying.
598        if (OpenClipboard(NULL))
599        {
600            const UINT preferredDropEffect = RegisterClipboardFormat(L"Preferred DropEffect");
601            bool hasFiles = false;
602            bool hasDropEffect = false;
603            DWORD dropEffect = DROPEFFECT_NONE;
604
605            UINT clipboardFormat = 0;
606            while ((clipboardFormat = EnumClipboardFormats(clipboardFormat)) != 0)
607            {
608                if (clipboardFormat == CF_HDROP)
609                    hasFiles = true;
610                else if (clipboardFormat == preferredDropEffect)
611                {
612                    hasDropEffect = true;
613                    HGLOBAL hGlobal = GetClipboardData(preferredDropEffect);
614                    DWORD* data = reinterpret_cast<DWORD*>(GlobalLock(hGlobal));
615
616                    if (data)
617                    {
618                        dropEffect = *data;
619                        GlobalUnlock(hGlobal);
620                    }
621                }
622            }
623
624            if (!hasFiles || hasDropEffect && dropEffect != DROPEFFECT_MOVE)
625                result &= ~ACTION_SECURE_PASTE;
626            CloseClipboard();
627        }
628        else
629            result &= ~ACTION_SECURE_PASTE;
630
631        return static_cast<Actions>(result);
632    }
633
634    LCID LocaleNameToLCID(const std::wstring& localeName)
635    {
636        LCID result = LOCALE_USER_DEFAULT;
637        IMultiLanguage* multiLanguage = NULL;
638
639        //Create an instance of the IMultiLanguage interface
640        if (SUCCEEDED(CoCreateInstance(CLSID_CMultiLanguage, NULL, CLSCTX_ALL,
641                IID_IMultiLanguage, (void**)&multiLanguage)))
642        {
643            //Convert our locale name to a BString
644            BSTR localeNameBStr = SysAllocString(localeName.c_str());
645            if (localeNameBStr)
646                multiLanguage->GetLcidFromRfc1766(&result, localeNameBStr);
647            SysFreeString(localeNameBStr);
648
649            //Clean up
650            multiLanguage->Release();
651        }
652
653        return result;
654    }
655
656    std::wstring CCtxMenu::LoadString(UINT stringID)
657    {
658        //Convert the resource ID to the block and item IDs.
659        UINT stringBlockID = (stringID >> 4) + 1;
660        UINT stringItemID = stringID % 16;
661        WORD langID = LANG_USER_DEFAULT;
662        std::wstring localeName;
663
664        if (localeName.empty())
665        {
666            bool foundLanguage = false;
667            Handle<HKEY> eraserKey;
668            LONG openKeyResult = RegOpenKeyEx(HKEY_CURRENT_USER,
669                L"Software\\Eraser\\Eraser 6\\3460478d-ed1b-4ecc-96c9-2ca0e8500557\\", 0,
670                KEY_READ, &static_cast<HKEY&>(eraserKey));
671
672            if (openKeyResult != ERROR_FILE_NOT_FOUND)
673            {
674                //Check the value of the Language value.
675                std::vector<wchar_t> value(256);
676                while (!foundLanguage)
677                {
678                    DWORD valueType = 0;
679                    DWORD valueSize = value.size();
680                    DWORD error = RegQueryValueEx(eraserKey, L"Language", NULL, &valueType,
681                        reinterpret_cast<BYTE*>(&value.front()), &valueSize);
682
683                    if (error == ERROR_SUCCESS)
684                        foundLanguage = true;
685                    else if (error == ERROR_INSUFFICIENT_BUFFER)
686                        value.resize(value.size() * 2);
687                    else
688                        break;
689                }
690
691                if (foundLanguage)
692                {
693                    localeName.assign(value.begin(), value.end());
694                    langID = LANGIDFROMLCID(LocaleNameToLCID(localeName));
695                }
696            }
697        }
698
699        //Obtain a pointer to the memory holding the string table.
700        WORD langIDStack[] = { langID, PRIMARYLANGID(langID), LANG_USER_DEFAULT, LANG_ENGLISH };
701        HRSRC resourceHandle = NULL;
702        for (size_t i = 0; resourceHandle == NULL &&
703            i < sizeof(langIDStack) / sizeof(langIDStack[0]); ++i)
704        {
705            resourceHandle = FindResourceEx(theApp.m_hInstance,
706                RT_STRING, MAKEINTRESOURCE(stringBlockID), langIDStack[i]);
707            if (GetLastError() == ERROR_RESOURCE_LANG_NOT_FOUND)
708                continue;
709        }
710
711        if (resourceHandle == NULL)
712        {
713            AfxMessageBox(FormatError().c_str());
714            return std::wstring();
715        }
716
717        DWORD sizeOfResource = SizeofResource(theApp.m_hInstance, resourceHandle);
718        HGLOBAL resourceBlock = LoadResource(theApp.m_hInstance, resourceHandle);
719        if (!sizeOfResource || !resourceBlock)
720            AfxMessageBox(FormatError().c_str());
721
722        wchar_t* stringTable = reinterpret_cast<wchar_t*>(LockResource(resourceBlock));
723
724        //Iterate over the string table. The string table is null-delimited with
725        //the first byte storing the length of the string entry.
726        for ( ; stringItemID != 0; --stringItemID)
727        {
728            if (*stringTable == L'\0')
729                ++stringTable;
730            else
731                stringTable += *stringTable + 1;
732        }
733
734        return std::wstring(stringTable + 1, *stringTable);
735    }
736
737    std::wstring CCtxMenu::FormatString(const std::wstring& formatString, ...)
738    {
739        std::vector<wchar_t> formatStr(formatString.length() + 1);
740        wcscpy_s(&formatStr.front(), formatStr.size(), formatString.c_str());
741
742        std::vector<wchar_t> buffer(formatStr.size());
743        for ( ; ; )
744        {
745            buffer.resize(buffer.size() * 2);
746            va_list arguments;
747            va_start(arguments, formatString);
748            int result = vswprintf_s(&buffer.front(), buffer.size(), &formatStr.front(),
749                arguments);
750            va_end(arguments);
751
752            if (result > 0 && static_cast<unsigned>(result) < buffer.size())
753            {
754                break;
755            }
756        }
757
758        //Return the result as a wstring
759        std::wstring result;
760        if (buffer.size() > 0)
761            result = &buffer.front();
762        return result;
763    }
764
765    std::wstring CCtxMenu::FormatError(DWORD lastError)
766    {
767        if (lastError == static_cast<DWORD>(-1))
768            lastError = GetLastError();
769
770        LPTSTR messageBuffer = NULL;
771        if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0,
772            lastError, 0, reinterpret_cast<LPWSTR>(&messageBuffer), 0, NULL) == 0)
773        {
774            return L"";
775        }
776
777        std::wstring result(messageBuffer);
778        LocalFree(messageBuffer);
779        return result;
780    }
781
782    std::wstring CCtxMenu::GetHKeyPath(HKEY handle)
783    {
784        if (NtQueryKey == NULL)
785            NtQueryKey = reinterpret_cast<pNtQueryKey>(GetProcAddress(
786                LoadLibrary(L"Ntdll.dll"), "NtQueryKey"));
787
788        //Keep querying for the key information until enough buffer space has been allocated.
789        std::vector<char> buffer(sizeof(KEY_NODE_INFORMATION));
790        NTSTATUS queryResult = STATUS_BUFFER_TOO_SMALL;
791        ULONG keyInfoSize = 0;
792
793        while (queryResult == STATUS_BUFFER_TOO_SMALL || queryResult == STATUS_BUFFER_OVERFLOW)
794        {
795            buffer.resize(buffer.size() + keyInfoSize);
796            ZeroMemory(&buffer.front(), buffer.size());
797            queryResult = NtQueryKey(handle, KeyNodeInformation, &buffer.front(),
798                static_cast<ULONG>(buffer.size()), &keyInfoSize);
799        }
800
801        if (queryResult != STATUS_SUCCESS)
802            return std::wstring();
803
804        KEY_NODE_INFORMATION* keyInfo = reinterpret_cast<KEY_NODE_INFORMATION*>(
805            &buffer.front());
806        return keyInfo->Name;
807    }
808
809    std::list<std::wstring> CCtxMenu::GetHDropPaths(HDROP hDrop)
810    {
811        //Sanity check - make sure there is at least one filename.
812        UINT uNumFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
813        if (!uNumFiles)
814            return std::list<std::wstring>();
815
816        //Collect all the files which have been selected.
817        HRESULT hr = S_OK;
818        WCHAR buffer[MAX_PATH] = {0};
819        std::list<std::wstring> result;
820        for (UINT i = 0; i < uNumFiles; i++)
821        {
822            UINT charsWritten = DragQueryFile(hDrop, i, buffer, sizeof(buffer) / sizeof(buffer[0]));
823            if (!charsWritten)
824            {
825                hr = E_INVALIDARG;
826                continue;
827            }
828
829            result.push_back(std::wstring(buffer, charsWritten));
830        }
831
832        return result;
833    }
834
835    bool CCtxMenu::IsUserAdmin()
836    {
837        SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
838        PSID AdministratorsGroup;
839        if (AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID,
840            DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &AdministratorsGroup))
841        {
842            BOOL result = false;
843            if (!CheckTokenMembership(NULL, AdministratorsGroup, &result))
844                result = false;
845
846            FreeSid(AdministratorsGroup);
847            return result != FALSE;
848        }
849
850        return false;
851    }
852
853    std::wstring CCtxMenu::GenerateEraseCommand()
854    {
855        std::wstring commandLine;
856
857        //See the invocation context: if it is executed from the recycle bin
858        //then the list of selected files will be empty.
859        if (InvokeReason == INVOKEREASON_RECYCLEBIN)
860        {
861            commandLine += L"recyclebin ";
862        }
863
864        //Add the list of items selected.
865        for (std::list<std::wstring>::const_iterator i = SelectedFiles.begin();
866            i != SelectedFiles.end(); ++i)
867        {
868            std::wstring path(*i);
869            if (path[path.length() - 1] == '\\')
870                path += '\\';
871
872            DWORD attributes = GetFileAttributes(path.c_str());
873            if (attributes == INVALID_FILE_ATTRIBUTES)
874                continue;
875            else if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
876                commandLine += L"\"file=" + path + L"\" ";
877            else
878                commandLine += L"\"dir=" + path + L"\" ";
879        }
880
881        return commandLine;
882    }
883
884    std::wstring CCtxMenu::GenerateEraseUnusedSpaceCommand()
885    {
886        std::wstring commandLine;
887
888        //Add the list of items selected.
889        for (std::list<std::wstring>::const_iterator i = SelectedFiles.begin();
890            i != SelectedFiles.end(); ++i)
891        {
892            std::wstring path(*i);
893            if (path[path.length() - 1] == '\\')
894                path += '\\';
895
896            commandLine += L"\"unused=" + path + L"\" ";
897        }
898
899        return commandLine;
900    }
901
902    std::wstring CCtxMenu::GenerateSecureMoveCommand()
903    {
904        std::wstring commandLine;
905
906        //Add the list of items selected.
907        for (std::list<std::wstring>::const_iterator i = SelectedFiles.begin();
908            i != SelectedFiles.end(); ++i)
909        {
910            std::wstring path(*i);
911            if (path[path.length() - 1] == '\\')
912                path += '\\';
913
914            commandLine += L"\"move=" + path + L"|" + DragDropDestinationDirectory + L"\" ";
915        }
916
917        return commandLine;
918    }
919
920    void CCtxMenu::RunEraser(const std::wstring& parameters, bool confirm, bool elevated,
921        HWND parent, int show)
922    {
923        //Get the path to this DLL so we can look for Eraser.exe
924        wchar_t fileName[MAX_PATH];
925        DWORD fileNameLength = GetModuleFileName(theApp.m_hInstance, fileName,
926            sizeof(fileName) / sizeof(fileName[0]));
927        if (!fileNameLength || fileNameLength >= sizeof(fileName) / sizeof(fileName[0]))
928            throw LoadString(IDS_ERROR_CANNOTFINDERASER);
929       
930        //Trim to the last \, then append Eraser.exe
931        std::wstring eraserPath(fileName, fileNameLength);
932        std::wstring::size_type lastBackslash = eraserPath.rfind('\\');
933        if (lastBackslash == std::wstring::npos)
934            throw LoadString(IDS_ERROR_CANNOTFINDERASER);
935
936        eraserPath.erase(eraserPath.begin() + lastBackslash + 1, eraserPath.end());
937        if (eraserPath.empty())
938            throw LoadString(IDS_ERROR_CANNOTFINDERASER);
939
940        eraserPath += L"Eraser.exe";
941
942        //Compile the final set of parameters we are going to pass to Eraser.
943        std::wostringstream finalParameters;
944        finalParameters << L"shell /quiet ";
945        if (!confirm)
946            finalParameters << L"/confirm=false ";
947
948        //Pass Explorer's HWND to the child process, in the event that it is required.
949        finalParameters << L" /parent=" << (size_t)parent << L' ';
950
951        //Then append the rest of the arguments, depending on the length.
952        {
953            //Depending on the length of the argument, we either use a response file
954            //or pass the arguments directly.
955            if (parameters.length() > 8192)
956            {
957                //The parameters are greater than 8kb, the response file would be
958                //more efficient.
959                wchar_t buffer[MAX_PATH];
960                wchar_t tempPath[MAX_PATH];
961                if (!GetTempPath(sizeof(tempPath) / sizeof(tempPath[0]), tempPath) ||
962                    !GetTempFileName(tempPath, L"ers", 0, buffer))
963                {
964                    throw LoadString(IDS_ERROR_CANNOT_GENERATE_TEMP_FILE);
965                }
966
967                std::wofstream stream(buffer);
968                stream << parameters;
969
970                finalParameters << L"\"@" << buffer << L'"';
971            }
972            else
973            {
974                //Short command line, pass directly to the program
975                finalParameters << parameters;
976            }
977        }
978
979        //If the process must be elevated we use ShellExecute with the runas verb
980        //to elevate the new process.
981        if (elevated && !IsUserAdmin())
982        {
983            int result = reinterpret_cast<int>(ShellExecute(parent, L"runas",
984                eraserPath.c_str(), finalParameters.str().c_str(), NULL, show));
985            if (result <= 32)
986                switch (result)
987                {
988                case SE_ERR_ACCESSDENIED:
989                    throw LoadString(IDS_ERROR_ACCESSDENIED);
990                default:
991                    throw LoadString(IDS_ERROR_UNKNOWN);
992                }
993        }
994
995        //If the process isn't to be elevated, we use CreateProcess so we can get
996        //read the output from the child process
997        else
998        {
999            //Create the process.
1000            STARTUPINFO startupInfo;
1001            ZeroMemory(&startupInfo, sizeof(startupInfo));
1002            startupInfo.cb = sizeof(startupInfo);
1003            startupInfo.dwFlags = STARTF_USESHOWWINDOW;
1004            startupInfo.wShowWindow = static_cast<WORD>(show);
1005            startupInfo.hStdInput = startupInfo.hStdOutput = startupInfo.hStdError =
1006                INVALID_HANDLE_VALUE;
1007
1008            //Create handles for output redirection
1009            Handle<HANDLE> readPipe;
1010            HANDLE writePipe;
1011            SECURITY_ATTRIBUTES security;
1012            ZeroMemory(&security, sizeof(security));
1013            security.nLength = sizeof(security);
1014            security.lpSecurityDescriptor = NULL;
1015            security.bInheritHandle = true;
1016
1017            if (CreatePipe(&static_cast<HANDLE&>(readPipe), &writePipe, &security, 0))
1018            {
1019                startupInfo.dwFlags |= STARTF_USESTDHANDLES;
1020                startupInfo.hStdOutput = startupInfo.hStdError =
1021                    writePipe;
1022            }
1023
1024            PROCESS_INFORMATION processInfo;
1025            ZeroMemory(&processInfo, sizeof(processInfo));
1026            std::vector<wchar_t> buffer(eraserPath.length() + finalParameters.str().length() + 4);
1027            wcscpy_s(&buffer.front(), buffer.size(), (L"\"" + eraserPath + L"\" " +
1028                finalParameters.str()).c_str());
1029
1030            if (!CreateProcess(NULL, &buffer.front(), NULL, NULL, true, CREATE_NO_WINDOW,
1031                NULL, NULL, &startupInfo, &processInfo))
1032            {
1033                //Why did we fail? Is it because we have too many files
1034                if (GetLastError() == ERROR_FILENAME_EXCED_RANGE)
1035                    throw FormatString(LoadString(IDS_ERROR_TOO_MANY_FILES));
1036               
1037                //Or if elevation is required for this operation
1038                else if (GetLastError() == ERROR_ELEVATION_REQUIRED)
1039                    return RunEraser(parameters, confirm, true, parent, show);
1040
1041                //Or otherwise?
1042                else
1043                    throw FormatString(LoadString(IDS_ERROR_MISC), FormatError().c_str());
1044            }
1045
1046            //Clean up all the opened handles -- our job is done.
1047            Handle<HANDLE> hProcess(processInfo.hProcess),
1048                           hThread(processInfo.hThread);
1049            CloseHandle(writePipe);
1050        }
1051    }
1052
1053    void CCtxMenu::InsertSeparator(HMENU menu)
1054    {
1055        MENUITEMINFO mii;
1056        mii.cbSize = sizeof(MENUITEMINFO);
1057        mii.fMask = MIIM_TYPE;
1058        mii.fType = MF_SEPARATOR;
1059        InsertMenuItem(menu, 0, false, &mii);
1060    }
1061
1062    HICON CCtxMenu::GetMenuIcon()
1063    {
1064        int smIconSize = GetSystemMetrics(SM_CXMENUCHECK);
1065        return static_cast<HICON>(LoadImage(theApp.m_hInstance, L"Eraser",
1066            IMAGE_ICON, smIconSize, smIconSize, LR_DEFAULTCOLOR));
1067    }
1068
1069    HBITMAP CCtxMenu::GetMenuBitmapFromIcon(HICON icon)
1070    {
1071        BITMAP bitmap;
1072        ICONINFO iconInfo;
1073        ZeroMemory(&bitmap, sizeof(bitmap));
1074        ZeroMemory(&iconInfo, sizeof(iconInfo));
1075
1076        //Try to get the icon's size, bitmap and bit depth. We will try to convert
1077        //the bitmap into a DIB for display on Vista if it contains an alpha channel.
1078        if (!GetIconInfo(icon, &iconInfo))
1079            return NULL;
1080
1081        Handle<HBITMAP> iconMask(iconInfo.hbmMask);
1082        if (!GetObject(iconInfo.hbmColor, sizeof(BITMAP), &bitmap) ||
1083            bitmap.bmBitsPixel < 32)
1084            return iconInfo.hbmColor;
1085
1086        //Draw the icon onto the DIB which will preseve its alpha values
1087        Handle<HDC> hdcDest = CreateCompatibleDC(NULL);
1088        HBITMAP dib = CreateDIB(bitmap.bmWidth, bitmap.bmHeight, NULL);
1089        SelectObject(hdcDest, dib);
1090
1091        Handle<HBITMAP> iconBitmap(iconInfo.hbmColor);
1092        DrawIconEx(hdcDest, 0, 0, icon, bitmap.bmWidth, bitmap.bmHeight, 0, NULL, DI_NORMAL);
1093        return dib;
1094    }
1095
1096    HBITMAP CCtxMenu::CreateDIB(LONG width, LONG height, char** bitmapBits)
1097    {
1098        BITMAPINFO info;
1099        ZeroMemory(&info, sizeof(info));
1100        info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
1101        info.bmiHeader.biWidth = width;
1102        info.bmiHeader.biHeight = height;
1103        info.bmiHeader.biPlanes = 1;
1104        info.bmiHeader.biBitCount = 32;
1105
1106        Handle<HDC> screenDC(GetDC(NULL));
1107        return ::CreateDIBSection(screenDC, &info, DIB_RGB_COLORS,
1108            reinterpret_cast<void**>(bitmapBits), NULL, 0);
1109    }
1110}
Note: See TracBrowser for help on using the repository browser.