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

Revision 2128, 32.9 KB checked in by lowjoel, 5 years ago (diff)

Removed debug code.

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