• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // ContextMenu.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../Common/ComTry.h"
6 #include "../../../Common/IntToString.h"
7 #include "../../../Common/StringConvert.h"
8 
9 #include "../../../Windows/COM.h"
10 #include "../../../Windows/DLL.h"
11 #include "../../../Windows/FileDir.h"
12 #include "../../../Windows/FileName.h"
13 #include "../../../Windows/Menu.h"
14 #include "../../../Windows/ProcessUtils.h"
15 
16 // for IS_INTRESOURCE():
17 #include "../../../Windows/Window.h"
18 
19 #include "../../PropID.h"
20 
21 #include "../Common/ArchiveName.h"
22 #include "../Common/CompressCall.h"
23 #include "../Common/ExtractingFilePath.h"
24 #include "../Common/ZipRegistry.h"
25 
26 #include "../FileManager/FormatUtils.h"
27 #include "../FileManager/LangUtils.h"
28 #include "../FileManager/PropertyName.h"
29 
30 #include "ContextMenu.h"
31 #include "ContextMenuFlags.h"
32 #include "MyMessages.h"
33 
34 #include "resource.h"
35 
36 
37 // #define SHOW_DEBUG_CTX_MENU
38 
39 #ifdef SHOW_DEBUG_CTX_MENU
40 #include <stdio.h>
41 #endif
42 
43 using namespace NWindows;
44 using namespace NFile;
45 using namespace NDir;
46 
47 #ifndef UNDER_CE
48 #define EMAIL_SUPPORT 1
49 #endif
50 
51 extern LONG g_DllRefCount;
52 
53 #ifdef _WIN32
54 extern HINSTANCE g_hInstance;
55 #endif
56 
57 #ifdef UNDER_CE
58   #define MY_IS_INTRESOURCE(_r) ((((ULONG_PTR)(_r)) >> 16) == 0)
59 #else
60   #define MY_IS_INTRESOURCE(_r) IS_INTRESOURCE(_r)
61 #endif
62 
63 
64 #ifdef SHOW_DEBUG_CTX_MENU
65 
PrintStringA(const char * name,LPCSTR ptr)66 static void PrintStringA(const char *name, LPCSTR ptr)
67 {
68   AString m;
69   m += name;
70   m += ": ";
71   char s[32];
72   sprintf(s, "%p", (const void *)ptr);
73   m += s;
74   if (!MY_IS_INTRESOURCE(ptr))
75   {
76     m += ": \"";
77     m += ptr;
78     m += "\"";
79   }
80   OutputDebugStringA(m);
81 }
82 
83 #if !defined(UNDER_CE)
PrintStringW(const char * name,LPCWSTR ptr)84 static void PrintStringW(const char *name, LPCWSTR ptr)
85 {
86   UString m;
87   m += name;
88   m += ": ";
89   char s[32];
90   sprintf(s, "%p", (const void *)ptr);
91   m += s;
92   if (!MY_IS_INTRESOURCE(ptr))
93   {
94     m += ": \"";
95     m += ptr;
96     m += "\"";
97   }
98   OutputDebugStringW(m);
99 }
100 #endif
101 
Print_Ptr(const void * p,const char * s)102 static void Print_Ptr(const void *p, const char *s)
103 {
104   char temp[32];
105   sprintf(temp, "%p", (const void *)p);
106   AString m;
107   m += temp;
108   m.Add_Space();
109   m += s;
110   OutputDebugStringA(m);
111 }
112 
Print_Number(UInt32 number,const char * s)113 static void Print_Number(UInt32 number, const char *s)
114 {
115   AString m;
116   m.Add_UInt32(number);
117   m.Add_Space();
118   m += s;
119   OutputDebugStringA(m);
120 }
121 
122 #define ODS(sz) { Print_Ptr(this, sz); }
123 #define ODS_U(s) { OutputDebugStringW(s); }
124 #define ODS_(op) { op; }
125 #define ODS_SPRF_s(x) { char s[256]; x; OutputDebugStringA(s); }
126 
127 #else
128 
129 #define ODS(sz)
130 #define ODS_U(s)
131 #define ODS_(op)
132 #define ODS_SPRF_s(x)
133 
134 #endif
135 
136 
137 /*
138 DOCs: In Windows 7 and later, the number of items passed to
139   a verb is limited to 16 when a shortcut menu is queried.
140   The verb is then re-created and re-initialized with the full
141   selection when that verb is invoked.
142 win10 tests:
143   if (the number of selected file/dir objects > 16)
144   {
145     Explorer does the following actions:
146     - it creates ctx_menu_1 IContextMenu object
147     - it calls ctx_menu_1->Initialize() with list of only up to 16 items
148     - it calls ctx_menu_1->QueryContextMenu(menu_1)
149     - if (some menu command is pressed)
150     {
151       - it gets shown string from selected menu item : shown_menu_1_string
152       - it creates another ctx_menu_2 IContextMenu object
153       - it calls ctx_menu_2->Initialize() with list of all items
154       - it calls ctx_menu_2->QueryContextMenu(menu_2)
155       - if there is menu item with shown_menu_1_string string in menu_2,
156          Explorer calls ctx_menu_2->InvokeCommand() for that item.
157       Explorer probably doesn't use VERB from first object ctx_menu_1.
158       So we must provide same shown menu strings for both objects:
159         ctx_menu_1 and ctx_menu_2.
160     }
161   }
162 */
163 
164 
CZipContextMenu()165 CZipContextMenu::CZipContextMenu():
166    _isMenuForFM(true),
167    _fileNames_WereReduced(true),
168    _dropMode(false),
169    _bitmap(NULL),
170    _writeZone((UInt32)(Int32)-1),
171    IsSeparator(false),
172    IsRoot(true),
173    CurrentSubCommand(0)
174 {
175   ODS("== CZipContextMenu()");
176   InterlockedIncrement(&g_DllRefCount);
177 }
178 
~CZipContextMenu()179 CZipContextMenu::~CZipContextMenu()
180 {
181   ODS("== ~CZipContextMenu");
182   if (_bitmap)
183     DeleteObject(_bitmap);
184   InterlockedDecrement(&g_DllRefCount);
185 }
186 
187 // IShellExtInit
188 
189 /*
190 IShellExtInit::Initialize()
191   pidlFolder:
192   - for property sheet extension:
193       NULL
194   - for shortcut menu extensions:
195       pidl of folder that contains the item whose shortcut menu is being displayed:
196   - for nondefault drag-and-drop menu extensions:
197       pidl of target folder: for nondefault drag-and-drop menu extensions
198   pidlFolder == NULL in (win10): for context menu
199 */
200 
Initialize(LPCITEMIDLIST pidlFolder,LPDATAOBJECT dataObject,HKEY)201 Z7_COMWF_B CZipContextMenu::Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT dataObject, HKEY /* hkeyProgID */)
202 {
203   COM_TRY_BEGIN
204   ODS("==== CZipContextMenu::Initialize START")
205   _isMenuForFM = false;
206   _fileNames_WereReduced = true;
207   _dropMode = false;
208   _attribs.Clear();
209   _fileNames.Clear();
210   _dropPath.Empty();
211 
212   if (pidlFolder)
213   {
214     ODS("==== CZipContextMenu::Initialize (pidlFolder != 0)")
215    #ifndef UNDER_CE
216     if (NShell::GetPathFromIDList(pidlFolder, _dropPath))
217     {
218       ODS("==== CZipContextMenu::Initialize path from (pidl):")
219       ODS_U(_dropPath);
220       /* win10 : path with "\\\\?\\\" prefix is returned by GetPathFromIDList, if path is long
221          we can remove super prefix here. But probably prefix
222          is not problem for following 7-zip code.
223          so we don't remove super prefix */
224       NFile::NName::If_IsSuperPath_RemoveSuperPrefix(_dropPath);
225       NName::NormalizeDirPathPrefix(_dropPath);
226       _dropMode = !_dropPath.IsEmpty();
227     }
228     else
229    #endif
230       _dropPath.Empty();
231   }
232 
233   if (!dataObject)
234     return E_INVALIDARG;
235 
236  #ifndef UNDER_CE
237 
238   RINOK(NShell::DataObject_GetData_HDROP_or_IDLIST_Names(dataObject, _fileNames))
239   // for (unsigned y = 0; y < 10000; y++)
240   if (NShell::DataObject_GetData_FILE_ATTRS(dataObject, _attribs) != S_OK)
241     _attribs.Clear();
242 
243  #endif
244 
245   ODS_SPRF_s(sprintf(s, "==== CZipContextMenu::Initialize END _files=%d",
246     _fileNames.Size()))
247 
248   return S_OK;
249   COM_TRY_END
250 }
251 
252 
253 /////////////////////////////
254 // IContextMenu
255 
256 static LPCSTR const kMainVerb = "SevenZip";
257 static LPCSTR const kOpenCascadedVerb = "SevenZip.OpenWithType.";
258 static LPCSTR const kCheckSumCascadedVerb = "SevenZip.Checksum";
259 
260 
261 struct CContextMenuCommand
262 {
263   UInt32 flag;
264   CZipContextMenu::enum_CommandInternalID CommandInternalID;
265   LPCSTR Verb;
266   UINT ResourceID;
267 };
268 
269 #define CMD_REC(cns, verb, ids)  { NContextMenuFlags::cns, CZipContextMenu::cns, verb, ids }
270 
271 static const CContextMenuCommand g_Commands[] =
272 {
273   CMD_REC( kOpen,        "Open",        IDS_CONTEXT_OPEN),
274   CMD_REC( kExtract,     "Extract",     IDS_CONTEXT_EXTRACT),
275   CMD_REC( kExtractHere, "ExtractHere", IDS_CONTEXT_EXTRACT_HERE),
276   CMD_REC( kExtractTo,   "ExtractTo",   IDS_CONTEXT_EXTRACT_TO),
277   CMD_REC( kTest,        "Test",        IDS_CONTEXT_TEST),
278   CMD_REC( kCompress,           "Compress",           IDS_CONTEXT_COMPRESS),
279   CMD_REC( kCompressEmail,      "CompressEmail",      IDS_CONTEXT_COMPRESS_EMAIL),
280   CMD_REC( kCompressTo7z,       "CompressTo7z",       IDS_CONTEXT_COMPRESS_TO),
281   CMD_REC( kCompressTo7zEmail,  "CompressTo7zEmail",  IDS_CONTEXT_COMPRESS_TO_EMAIL),
282   CMD_REC( kCompressToZip,      "CompressToZip",      IDS_CONTEXT_COMPRESS_TO),
283   CMD_REC( kCompressToZipEmail, "CompressToZipEmail", IDS_CONTEXT_COMPRESS_TO_EMAIL)
284 };
285 
286 
287 struct CHashCommand
288 {
289   CZipContextMenu::enum_CommandInternalID CommandInternalID;
290   LPCSTR UserName;
291   LPCSTR MethodName;
292 };
293 
294 static const CHashCommand g_HashCommands[] =
295 {
296   { CZipContextMenu::kHash_CRC32,  "CRC-32",  "CRC32" },
297   { CZipContextMenu::kHash_CRC64,  "CRC-64",  "CRC64" },
298   { CZipContextMenu::kHash_XXH64,  "XXH64",    "XXH64" },
299   { CZipContextMenu::kHash_SHA1,   "SHA-1",   "SHA1" },
300   { CZipContextMenu::kHash_SHA256, "SHA-256", "SHA256" },
301   { CZipContextMenu::kHash_BLAKE2SP, "BLAKE2sp", "BLAKE2sp" },
302   { CZipContextMenu::kHash_All,    "*",       "*" },
303   { CZipContextMenu::kHash_Generate_SHA256, "SHA-256 -> file.sha256", "SHA256" },
304   { CZipContextMenu::kHash_TestArc, "Checksum : Test", "Hash" }
305 };
306 
307 
FindCommand(CZipContextMenu::enum_CommandInternalID & id)308 static int FindCommand(CZipContextMenu::enum_CommandInternalID &id)
309 {
310   for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_Commands); i++)
311     if (g_Commands[i].CommandInternalID == id)
312       return (int)i;
313   return -1;
314 }
315 
316 
FillCommand(enum_CommandInternalID id,UString & mainString,CCommandMapItem & cmi) const317 void CZipContextMenu::FillCommand(enum_CommandInternalID id, UString &mainString, CCommandMapItem &cmi) const
318 {
319   mainString.Empty();
320   const int i = FindCommand(id);
321   if (i < 0)
322     throw 201908;
323   const CContextMenuCommand &command = g_Commands[(unsigned)i];
324   cmi.CommandInternalID = command.CommandInternalID;
325   cmi.Verb = kMainVerb;
326   cmi.Verb += command.Verb;
327   // cmi.HelpString = cmi.Verb;
328   LangString(command.ResourceID, mainString);
329   cmi.UserString = mainString;
330 }
331 
332 
LangStringAlt(UInt32 id,const char * altString)333 static UString LangStringAlt(UInt32 id, const char *altString)
334 {
335   UString s = LangString(id);
336   if (s.IsEmpty())
337     s = altString;
338   return s;
339 }
340 
341 
AddCommand(enum_CommandInternalID id,UString & mainString,CCommandMapItem & cmi)342 void CZipContextMenu::AddCommand(enum_CommandInternalID id, UString &mainString, CCommandMapItem &cmi)
343 {
344   FillCommand(id, mainString, cmi);
345   _commandMap.Add(cmi);
346 }
347 
348 
349 
350 /*
351 note: old msdn article:
352 Duplicate Menu Items In the File Menu For a Shell Context Menu Extension (214477)
353 ----------
354   On systems with Shell32.dll version 4.71 or higher, a context menu extension
355   for a file folder that inserts one or more pop-up menus results in duplicates
356   of these menu items.
357   This occurs when the file menu is activated more than once for the selected object.
358 
359 CAUSE
360   In a context menu extension, if pop-up menus are inserted using InsertMenu
361   or AppendMenu, then the ID for the pop-up menu item cannot be specified.
362   Instead, this field should take in the HMENU of the pop-up menu.
363   Because the ID is not specified for the pop-up menu item, the Shell does
364   not keep track of the menu item if the file menu is pulled down multiple times.
365   As a result, the pop-up menu items are added multiple times in the context menu.
366 
367   This problem occurs only when the file menu is pulled down, and does not happen
368   when the context menu is invoked by using the right button or the context menu key.
369 RESOLUTION
370   To work around this problem, use InsertMenuItem and specify the ID of the
371   pop-up menu  item in the wID member of the MENUITEMINFO structure.
372 */
373 
MyInsertMenu(CMenu & menu,unsigned pos,UINT id,const UString & s,HBITMAP bitmap)374 static void MyInsertMenu(CMenu &menu, unsigned pos, UINT id, const UString &s, HBITMAP bitmap)
375 {
376   if (!menu)
377     return;
378   CMenuItem mi;
379   mi.fType = MFT_STRING;
380   mi.fMask = MIIM_TYPE | MIIM_ID;
381   if (bitmap)
382     mi.fMask |= MIIM_CHECKMARKS;
383   mi.wID = id;
384   mi.StringValue = s;
385   mi.hbmpUnchecked = bitmap;
386   // mi.hbmpChecked = bitmap; // do we need hbmpChecked ???
387   if (!menu.InsertItem(pos, true, mi))
388     throw 20190816;
389 
390   // SetMenuItemBitmaps also works
391   // ::SetMenuItemBitmaps(menu, pos, MF_BYPOSITION, bitmap, NULL);
392 }
393 
394 
MyAddSubMenu(CObjectVector<CZipContextMenu::CCommandMapItem> & _commandMap,const char * verb,CMenu & menu,unsigned pos,UINT id,const UString & s,HMENU hSubMenu,HBITMAP bitmap)395 static void MyAddSubMenu(
396     CObjectVector<CZipContextMenu::CCommandMapItem> &_commandMap,
397     const char *verb,
398     CMenu &menu, unsigned pos, UINT id, const UString &s, HMENU hSubMenu, HBITMAP bitmap)
399 {
400   CZipContextMenu::CCommandMapItem cmi;
401   cmi.CommandInternalID = CZipContextMenu::kCommandNULL;
402   cmi.Verb = verb;
403   cmi.IsPopup = true;
404   // cmi.HelpString = verb;
405   cmi.UserString = s;
406   _commandMap.Add(cmi);
407 
408   if (!menu)
409     return;
410 
411   CMenuItem mi;
412   mi.fType = MFT_STRING;
413   mi.fMask = MIIM_SUBMENU | MIIM_TYPE | MIIM_ID;
414   if (bitmap)
415     mi.fMask |= MIIM_CHECKMARKS;
416   mi.wID = id;
417   mi.hSubMenu = hSubMenu;
418   mi.hbmpUnchecked = bitmap;
419 
420   mi.StringValue = s;
421   if (!menu.InsertItem(pos, true, mi))
422     throw 20190817;
423 }
424 
425 
426 static const char * const kArcExts[] =
427 {
428     "7z"
429   , "bz2"
430   , "gz"
431   , "rar"
432   , "zip"
433 };
434 
IsItArcExt(const UString & ext)435 static bool IsItArcExt(const UString &ext)
436 {
437   for (unsigned i = 0; i < Z7_ARRAY_SIZE(kArcExts); i++)
438     if (ext.IsEqualTo_Ascii_NoCase(kArcExts[i]))
439       return true;
440   return false;
441 }
442 
443 UString GetSubFolderNameForExtract(const UString &arcName);
GetSubFolderNameForExtract(const UString & arcName)444 UString GetSubFolderNameForExtract(const UString &arcName)
445 {
446   int dotPos = arcName.ReverseFind_Dot();
447   if (dotPos < 0)
448     return Get_Correct_FsFile_Name(arcName) + L'~';
449 
450   const UString ext = arcName.Ptr(dotPos + 1);
451   UString res = arcName.Left(dotPos);
452   res.TrimRight();
453   dotPos = res.ReverseFind_Dot();
454   if (dotPos > 0)
455   {
456     const UString ext2 = res.Ptr(dotPos + 1);
457     if ((ext.IsEqualTo_Ascii_NoCase("001") && IsItArcExt(ext2))
458         || (ext.IsEqualTo_Ascii_NoCase("rar") &&
459           (  ext2.IsEqualTo_Ascii_NoCase("part001")
460           || ext2.IsEqualTo_Ascii_NoCase("part01")
461           || ext2.IsEqualTo_Ascii_NoCase("part1"))))
462       res.DeleteFrom(dotPos);
463     res.TrimRight();
464   }
465   return Get_Correct_FsFile_Name(res);
466 }
467 
ReduceString(UString & s)468 static void ReduceString(UString &s)
469 {
470   const unsigned kMaxSize = 64;
471   if (s.Len() <= kMaxSize)
472     return;
473   s.Delete(kMaxSize / 2, s.Len() - kMaxSize);
474   s.Insert(kMaxSize / 2, L" ... ");
475 }
476 
GetQuotedReducedString(const UString & s)477 static UString GetQuotedReducedString(const UString &s)
478 {
479   UString s2 = s;
480   ReduceString(s2);
481   s2.Replace(L"&", L"&&");
482   return GetQuotedString(s2);
483 }
484 
MyFormatNew_ReducedName(UString & s,const UString & name)485 static void MyFormatNew_ReducedName(UString &s, const UString &name)
486 {
487   s = MyFormatNew(s, GetQuotedReducedString(name));
488 }
489 
490 static const char * const kExtractExcludeExtensions =
491   " 3gp"
492   " aac ans ape asc asm asp aspx avi awk"
493   " bas bat bmp"
494   " c cs cls clw cmd cpp csproj css ctl cxx"
495   " def dep dlg dsp dsw"
496   " eps"
497   " f f77 f90 f95 fla flac frm"
498   " gif"
499   " h hpp hta htm html hxx"
500   " ico idl inc ini inl"
501   " java jpeg jpg js"
502   " la lnk log"
503   " mak manifest wmv mov mp3 mp4 mpe mpeg mpg m4a"
504   " ofr ogg"
505   " pac pas pdf php php3 php4 php5 phptml pl pm png ps py pyo"
506   " ra rb rc reg rka rm rtf"
507   " sed sh shn shtml sln sql srt swa"
508   " tcl tex tiff tta txt"
509   " vb vcproj vbs"
510   " mkv wav webm wma wv"
511   " xml xsd xsl xslt"
512   " ";
513 
514 /*
515 static const char * const kNoOpenAsExtensions =
516   " 7z arj bz2 cab chm cpio flv gz lha lzh lzma rar swm tar tbz2 tgz wim xar xz z zip ";
517 */
518 
519 static const char * const kOpenTypes[] =
520 {
521     ""
522   , "*"
523   , "#"
524   , "#:e"
525   // , "#:a"
526   , "7z"
527   , "zip"
528   , "cab"
529   , "rar"
530 };
531 
532 
533 bool FindExt(const char *p, const UString &name, CStringFinder &finder);
FindExt(const char * p,const UString & name,CStringFinder & finder)534 bool FindExt(const char *p, const UString &name, CStringFinder &finder)
535 {
536   const int dotPos = name.ReverseFind_Dot();
537   if (dotPos < 0 || dotPos == (int)name.Len() - 1)
538     return false;
539   return finder.FindWord_In_LowCaseAsciiList_NoCase(p, name.Ptr(dotPos + 1));
540 }
541 
542 /* returns false, if extraction of that file extension is not expected */
DoNeedExtract(const UString & name,CStringFinder & finder)543 static bool DoNeedExtract(const UString &name, CStringFinder &finder)
544 {
545   // for (int y = 0; y < 1000; y++) FindExt(kExtractExcludeExtensions, name);
546   return !FindExt(kExtractExcludeExtensions, name, finder);
547 }
548 
549 // we must use diferent Verbs for Popup subMenu.
AddMapItem_ForSubMenu(const char * verb)550 void CZipContextMenu::AddMapItem_ForSubMenu(const char *verb)
551 {
552   CCommandMapItem cmi;
553   cmi.CommandInternalID = kCommandNULL;
554   cmi.Verb = verb;
555   // cmi.HelpString = verb;
556   _commandMap.Add(cmi);
557 }
558 
559 
RETURN_WIN32_LastError_AS_HRESULT()560 static HRESULT RETURN_WIN32_LastError_AS_HRESULT()
561 {
562   DWORD lastError = ::GetLastError();
563   if (lastError == 0)
564     return E_FAIL;
565   return HRESULT_FROM_WIN32(lastError);
566 }
567 
568 
569 /*
570   we add CCommandMapItem to _commandMap for each new Menu ID.
571   so then we use _commandMap[offset].
572   That way we can execute commands that have menu item.
573   Another non-implemented way:
574     We can return the number off all possible commands in QueryContextMenu().
575     so the caller could call InvokeCommand() via string verb even
576     without using menu items.
577 */
578 
579 
QueryContextMenu(HMENU hMenu,UINT indexMenu,UINT commandIDFirst,UINT commandIDLast,UINT flags)580 Z7_COMWF_B CZipContextMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu,
581       UINT commandIDFirst, UINT commandIDLast, UINT flags)
582 {
583   ODS("+ QueryContextMenu()")
584   COM_TRY_BEGIN
585   try {
586 
587   _commandMap.Clear();
588 
589   ODS_SPRF_s(sprintf(s, "QueryContextMenu: index=%u first=%u last=%u flags=%x _files=%u",
590       indexMenu, commandIDFirst, commandIDLast, flags, _fileNames.Size()))
591   /*
592   for (UInt32 i = 0; i < _fileNames.Size(); i++)
593   {
594     ODS_U(_fileNames[i])
595   }
596   */
597 
598   #define MAKE_HRESULT_SUCCESS_FAC0(code)  (HRESULT)(code)
599 
600   if (_fileNames.Size() == 0)
601   {
602     return MAKE_HRESULT_SUCCESS_FAC0(0);
603     // return E_INVALIDARG;
604   }
605 
606   if (commandIDFirst > commandIDLast)
607     return E_INVALIDARG;
608 
609   UINT currentCommandID = commandIDFirst;
610 
611   if ((flags & 0x000F) != CMF_NORMAL
612       && (flags & CMF_VERBSONLY) == 0
613       && (flags & CMF_EXPLORE) == 0)
614     return MAKE_HRESULT_SUCCESS_FAC0(currentCommandID - commandIDFirst);
615   // return MAKE_HRESULT_SUCCESS_FAC0(currentCommandID);
616   // 19.01 : we changed from (currentCommandID) to (currentCommandID - commandIDFirst)
617   // why it was so before?
618 
619 #ifdef Z7_LANG
620   LoadLangOneTime();
621 #endif
622 
623   CMenu popupMenu;
624   CMenuDestroyer menuDestroyer;
625 
626   ODS("### 40")
627   CContextMenuInfo ci;
628   ci.Load();
629   ODS("### 44")
630 
631   _elimDup = ci.ElimDup;
632   _writeZone = ci.WriteZone;
633 
634   HBITMAP bitmap = NULL;
635   if (ci.MenuIcons.Val)
636   {
637     ODS("### 45")
638     if (!_bitmap)
639       _bitmap = ::LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_MENU_LOGO));
640     bitmap = _bitmap;
641   }
642 
643   UINT subIndex = indexMenu;
644 
645   ODS("### 50")
646 
647   if (ci.Cascaded.Val)
648   {
649     if (hMenu)
650     if (!popupMenu.CreatePopup())
651       return RETURN_WIN32_LastError_AS_HRESULT();
652     menuDestroyer.Attach(popupMenu);
653 
654     /* 9.31: we commented the following code. Probably we don't need.
655     Check more systems. Maybe it was for old Windows? */
656     /*
657     AddMapItem_ForSubMenu();
658     currentCommandID++;
659     */
660     subIndex = 0;
661   }
662   else
663   {
664     popupMenu.Attach(hMenu);
665     CMenuItem mi;
666     mi.fType = MFT_SEPARATOR;
667     mi.fMask = MIIM_TYPE;
668     if (hMenu)
669     popupMenu.InsertItem(subIndex++, true, mi);
670   }
671 
672   const UInt32 contextMenuFlags = ci.Flags;
673 
674   NFind::CFileInfo fi0;
675   FString folderPrefix;
676 
677   if (_fileNames.Size() > 0)
678   {
679     const UString &fileName = _fileNames.Front();
680 
681     #if defined(_WIN32) && !defined(UNDER_CE)
682     if (NName::IsDevicePath(us2fs(fileName)))
683     {
684       // CFileInfo::Find can be slow for device files. So we don't call it.
685       // we need only name here.
686       fi0.Name = us2fs(fileName.Ptr(NName::kDevicePathPrefixSize));
687       folderPrefix =
688         #ifdef UNDER_CE
689           "\\";
690         #else
691           "C:\\";
692         #endif
693     }
694     else
695     #endif
696     {
697       if (!fi0.Find(us2fs(fileName)))
698       {
699         throw 20190820;
700         // return RETURN_WIN32_LastError_AS_HRESULT();
701       }
702       GetOnlyDirPrefix(us2fs(fileName), folderPrefix);
703     }
704   }
705 
706   ODS("### 100")
707 
708   UString mainString;
709   CStringFinder finder;
710   UStringVector fileNames_Reduced;
711   const unsigned k_Explorer_NumReducedItems = 16;
712   const bool needReduce = !_isMenuForFM && (_fileNames.Size() >= k_Explorer_NumReducedItems);
713   _fileNames_WereReduced = needReduce;
714   // _fileNames_WereReduced = true; // for debug;
715   const UStringVector *fileNames = &_fileNames;
716   if (needReduce)
717   {
718     for (unsigned i = 0; i < k_Explorer_NumReducedItems
719         && i < _fileNames.Size(); i++)
720       fileNames_Reduced.Add(_fileNames[i]);
721     fileNames = &fileNames_Reduced;
722   }
723 
724   /*
725   if (_fileNames.Size() == k_Explorer_NumReducedItems) // for debug
726   {
727     for (int i = 0; i < 10; i++)
728     {
729       CCommandMapItem cmi;
730       AddCommand(kCompressToZipEmail, mainString, cmi);
731       MyInsertMenu(popupMenu, subIndex++, currentCommandID++, mainString, bitmap);
732     }
733   }
734   */
735 
736   if (_fileNames.Size() == 1 && currentCommandID + 14 <= commandIDLast)
737   {
738     if (!fi0.IsDir() && DoNeedExtract(fs2us(fi0.Name), finder))
739     {
740       // Open
741       const bool thereIsMainOpenItem = ((contextMenuFlags & NContextMenuFlags::kOpen) != 0);
742       if (thereIsMainOpenItem)
743       {
744         CCommandMapItem cmi;
745         AddCommand(kOpen, mainString, cmi);
746         MyInsertMenu(popupMenu, subIndex++, currentCommandID++, mainString, bitmap);
747       }
748       if ((contextMenuFlags & NContextMenuFlags::kOpenAs) != 0
749           // && (!thereIsMainOpenItem || !FindExt(kNoOpenAsExtensions, fi0.Name))
750           && hMenu // we want to reduce number of menu items below 16
751           )
752       {
753         CMenu subMenu;
754         if (!hMenu || subMenu.CreatePopup())
755         {
756           MyAddSubMenu(_commandMap, kOpenCascadedVerb, popupMenu, subIndex++, currentCommandID++, LangString(IDS_CONTEXT_OPEN), subMenu, bitmap);
757           _commandMap.Back().CtxCommandType = CtxCommandType_OpenRoot;
758 
759           UINT subIndex2 = 0;
760           for (unsigned i = (thereIsMainOpenItem ? 1 : 0); i < Z7_ARRAY_SIZE(kOpenTypes); i++)
761           {
762             CCommandMapItem cmi;
763             if (i == 0)
764               FillCommand(kOpen, mainString, cmi);
765             else
766             {
767               mainString = kOpenTypes[i];
768               cmi.CommandInternalID = kOpen;
769               cmi.Verb = kMainVerb;
770               cmi.Verb += ".Open.";
771               cmi.Verb += mainString;
772               // cmi.HelpString = cmi.Verb;
773               cmi.ArcType = mainString;
774               cmi.CtxCommandType = CtxCommandType_OpenChild;
775             }
776             _commandMap.Add(cmi);
777             Set_UserString_in_LastCommand(mainString);
778             MyInsertMenu(subMenu, subIndex2++, currentCommandID++, mainString, bitmap);
779           }
780 
781           subMenu.Detach();
782         }
783       }
784     }
785   }
786 
787   ODS("### 150")
788 
789   if (_fileNames.Size() > 0 && currentCommandID + 10 <= commandIDLast)
790   {
791     ODS("### needExtract list START")
792     const bool needExtendedVerbs = ((flags & Z7_WIN_CMF_EXTENDEDVERBS) != 0);
793         // || _isMenuForFM;
794     bool needExtract = true;
795     bool areDirs = fi0.IsDir() || (unsigned)_attribs.FirstDirIndex < k_Explorer_NumReducedItems;
796     if (!needReduce)
797       areDirs = areDirs || (_attribs.FirstDirIndex != -1);
798     if (areDirs)
799       needExtract = false;
800 
801     if (!needExtendedVerbs)
802     if (needExtract)
803     {
804       UString name;
805       const unsigned numItemsCheck = fileNames->Size();
806       for (unsigned i = 0; i < numItemsCheck; i++)
807       {
808         const UString &a = (*fileNames)[i];
809         const int slash = a.ReverseFind_PathSepar();
810         name = a.Ptr(slash + 1);
811         // for (int y = 0; y < 600; y++) // for debug
812         const bool needExtr2 = DoNeedExtract(name, finder);
813         if (!needExtr2)
814         {
815           needExtract = needExtr2;
816           break;
817         }
818       }
819     }
820     ODS("### needExtract list END")
821 
822     if (needExtract)
823     {
824       {
825         UString baseFolder = fs2us(folderPrefix);
826         if (_dropMode)
827           baseFolder = _dropPath;
828 
829         UString specFolder ('*');
830         if (_fileNames.Size() == 1)
831           specFolder = GetSubFolderNameForExtract(fs2us(fi0.Name));
832         specFolder.Add_PathSepar();
833 
834         if ((contextMenuFlags & NContextMenuFlags::kExtract) != 0)
835         {
836           // Extract
837           CCommandMapItem cmi;
838           cmi.Folder = baseFolder + specFolder;
839           AddCommand(kExtract, mainString, cmi);
840           MyInsertMenu(popupMenu, subIndex++, currentCommandID++, mainString, bitmap);
841         }
842 
843         if ((contextMenuFlags & NContextMenuFlags::kExtractHere) != 0)
844         {
845           // Extract Here
846           CCommandMapItem cmi;
847           cmi.Folder = baseFolder;
848           AddCommand(kExtractHere, mainString, cmi);
849           MyInsertMenu(popupMenu, subIndex++, currentCommandID++, mainString, bitmap);
850         }
851 
852         if ((contextMenuFlags & NContextMenuFlags::kExtractTo) != 0)
853         {
854           // Extract To
855           CCommandMapItem cmi;
856           UString s;
857           cmi.Folder = baseFolder + specFolder;
858           AddCommand(kExtractTo, s, cmi);
859           MyFormatNew_ReducedName(s, specFolder);
860           Set_UserString_in_LastCommand(s);
861           MyInsertMenu(popupMenu, subIndex++, currentCommandID++, s, bitmap);
862         }
863       }
864 
865       if ((contextMenuFlags & NContextMenuFlags::kTest) != 0)
866       {
867         // Test
868         CCommandMapItem cmi;
869         AddCommand(kTest, mainString, cmi);
870         // if (_fileNames.Size() == 16) mainString += "_[16]"; // for debug
871         MyInsertMenu(popupMenu, subIndex++, currentCommandID++, mainString, bitmap);
872       }
873     }
874 
875     ODS("### CreateArchiveName START")
876     UString arcName_base;
877     const UString arcName = CreateArchiveName(
878         *fileNames,
879         false, // isHash
880         fileNames->Size() == 1 ? &fi0 : NULL,
881         arcName_base);
882     ODS("### CreateArchiveName END")
883     UString arcName_Show = arcName;
884     if (needReduce)
885     {
886       /* we need same arcName_Show for two calls from Explorer:
887             1) reduced call (only first 16 items)
888             2) full call with all items (can be >= 16 items)
889          (fileNames) array was reduced to 16 items.
890          So we will have same (arcName) in both reduced and full calls.
891          If caller (Explorer) uses (reduce_to_first_16_items) scheme,
892          we can use (arcName) here instead of (arcName_base).
893          (arcName_base) has no number in name.
894       */
895       arcName_Show = arcName_base; // we can comment that line
896       /* we use "_" in archive name as sign to user
897          that shows that final archive name can be changed. */
898       arcName_Show += "_";
899     }
900 
901     UString arcName_7z = arcName;
902     arcName_7z += ".7z";
903     UString arcName_7z_Show = arcName_Show;
904     arcName_7z_Show += ".7z";
905     UString arcName_zip = arcName;
906     arcName_zip += ".zip";
907     UString arcName_zip_Show = arcName_Show;
908     arcName_zip_Show += ".zip";
909 
910 
911     // Compress
912     if ((contextMenuFlags & NContextMenuFlags::kCompress) != 0)
913     {
914       CCommandMapItem cmi;
915       if (_dropMode)
916         cmi.Folder = _dropPath;
917       else
918         cmi.Folder = fs2us(folderPrefix);
919       cmi.ArcName = arcName;
920       AddCommand(kCompress, mainString, cmi);
921       MyInsertMenu(popupMenu, subIndex++, currentCommandID++, mainString, bitmap);
922     }
923 
924     #ifdef EMAIL_SUPPORT
925     // CompressEmail
926     if ((contextMenuFlags & NContextMenuFlags::kCompressEmail) != 0 && !_dropMode)
927     {
928       CCommandMapItem cmi;
929       cmi.ArcName = arcName;
930       AddCommand(kCompressEmail, mainString, cmi);
931       MyInsertMenu(popupMenu, subIndex++, currentCommandID++, mainString, bitmap);
932     }
933     #endif
934 
935     // CompressTo7z
936     if (contextMenuFlags & NContextMenuFlags::kCompressTo7z &&
937         !arcName_7z.IsEqualTo_NoCase(fs2us(fi0.Name)))
938     {
939       CCommandMapItem cmi;
940       UString s;
941       if (_dropMode)
942         cmi.Folder = _dropPath;
943       else
944         cmi.Folder = fs2us(folderPrefix);
945       cmi.ArcName = arcName_7z;
946       cmi.ArcType = "7z";
947       AddCommand(kCompressTo7z, s, cmi);
948       MyFormatNew_ReducedName(s, arcName_7z_Show);
949       Set_UserString_in_LastCommand(s);
950       MyInsertMenu(popupMenu, subIndex++, currentCommandID++, s, bitmap);
951     }
952 
953     #ifdef EMAIL_SUPPORT
954     // CompressTo7zEmail
955     if ((contextMenuFlags & NContextMenuFlags::kCompressTo7zEmail) != 0  && !_dropMode)
956     {
957       CCommandMapItem cmi;
958       UString s;
959       cmi.ArcName = arcName_7z;
960       cmi.ArcType = "7z";
961       AddCommand(kCompressTo7zEmail, s, cmi);
962       MyFormatNew_ReducedName(s, arcName_7z_Show);
963       Set_UserString_in_LastCommand(s);
964       MyInsertMenu(popupMenu, subIndex++, currentCommandID++, s, bitmap);
965     }
966     #endif
967 
968     // CompressToZip
969     if (contextMenuFlags & NContextMenuFlags::kCompressToZip &&
970         !arcName_zip.IsEqualTo_NoCase(fs2us(fi0.Name)))
971     {
972       CCommandMapItem cmi;
973       UString s;
974       if (_dropMode)
975         cmi.Folder = _dropPath;
976       else
977         cmi.Folder = fs2us(folderPrefix);
978       cmi.ArcName = arcName_zip;
979       cmi.ArcType = "zip";
980       AddCommand(kCompressToZip, s, cmi);
981       MyFormatNew_ReducedName(s, arcName_zip_Show);
982       Set_UserString_in_LastCommand(s);
983       MyInsertMenu(popupMenu, subIndex++, currentCommandID++, s, bitmap);
984     }
985 
986     #ifdef EMAIL_SUPPORT
987     // CompressToZipEmail
988     if ((contextMenuFlags & NContextMenuFlags::kCompressToZipEmail) != 0  && !_dropMode)
989     {
990       CCommandMapItem cmi;
991       UString s;
992       cmi.ArcName = arcName_zip;
993       cmi.ArcType = "zip";
994       AddCommand(kCompressToZipEmail, s, cmi);
995       MyFormatNew_ReducedName(s, arcName_zip_Show);
996       Set_UserString_in_LastCommand(s);
997       MyInsertMenu(popupMenu, subIndex++, currentCommandID++, s, bitmap);
998     }
999     #endif
1000   }
1001 
1002   ODS("### 300")
1003 
1004   // don't use InsertMenu:  See MSDN:
1005   // PRB: Duplicate Menu Items In the File Menu For a Shell Context Menu Extension
1006   // ID: Q214477
1007 
1008   if (ci.Cascaded.Val)
1009   {
1010     CMenu menu;
1011     menu.Attach(hMenu);
1012     menuDestroyer.Disable();
1013     MyAddSubMenu(_commandMap, kMainVerb, menu, indexMenu++, currentCommandID++, (UString)"7-Zip",
1014         popupMenu, // popupMenu.Detach(),
1015         bitmap);
1016   }
1017   else
1018   {
1019     // popupMenu.Detach();
1020     indexMenu = subIndex;
1021   }
1022 
1023   ODS("### 350")
1024 
1025   const bool needCrc = ((contextMenuFlags &
1026       (NContextMenuFlags::kCRC |
1027        NContextMenuFlags::kCRC_Cascaded)) != 0);
1028 
1029   if (
1030       // !_isMenuForFM && // 21.04: we don't hide CRC SHA menu in 7-Zip FM
1031       needCrc
1032       && currentCommandID + 1 < commandIDLast)
1033   {
1034     CMenu subMenu;
1035     // CMenuDestroyer menuDestroyer_CRC;
1036 
1037     UINT subIndex_CRC = 0;
1038 
1039     if (!hMenu || subMenu.CreatePopup())
1040     {
1041       // menuDestroyer_CRC.Attach(subMenu);
1042       const bool insertHashMenuTo7zipMenu = (ci.Cascaded.Val
1043           && (contextMenuFlags & NContextMenuFlags::kCRC_Cascaded) != 0);
1044 
1045       CMenu menu;
1046       {
1047         unsigned indexInParent;
1048         if (insertHashMenuTo7zipMenu)
1049         {
1050           indexInParent = subIndex;
1051           menu.Attach(popupMenu);
1052         }
1053         else
1054         {
1055           indexInParent = indexMenu;
1056           menu.Attach(hMenu);
1057           // menuDestroyer_CRC.Disable();
1058         }
1059         MyAddSubMenu(_commandMap, kCheckSumCascadedVerb, menu, indexInParent++, currentCommandID++, (UString)"CRC SHA", subMenu,
1060           /* insertHashMenuTo7zipMenu ? NULL : */ bitmap);
1061         _commandMap.Back().CtxCommandType = CtxCommandType_CrcRoot;
1062         if (!insertHashMenuTo7zipMenu)
1063           indexMenu = indexInParent;
1064       }
1065 
1066       ODS("### HashCommands")
1067 
1068       for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_HashCommands); i++)
1069       {
1070         if (currentCommandID >= commandIDLast)
1071           break;
1072         const CHashCommand &hc = g_HashCommands[i];
1073         CCommandMapItem cmi;
1074         cmi.CommandInternalID = hc.CommandInternalID;
1075         cmi.Verb = kCheckSumCascadedVerb;
1076         cmi.Verb.Add_Dot();
1077         UString s;
1078         s += hc.UserName;
1079 
1080         if (hc.CommandInternalID == kHash_Generate_SHA256)
1081         {
1082           cmi.Verb += "Generate";
1083           {
1084             popupMenu.Attach(hMenu);
1085             CMenuItem mi;
1086             mi.fType = MFT_SEPARATOR;
1087             mi.fMask = MIIM_TYPE;
1088             subMenu.InsertItem(subIndex_CRC++, true, mi);
1089           }
1090 
1091           UString name;
1092           UString showName;
1093           ODS("### Hash CreateArchiveName Start")
1094           // for (int y = 0; y < 10000; y++) // for debug
1095           // if (fileNames->Size() == 1) name = fs2us(fi0.Name); else
1096           name = CreateArchiveName(
1097               *fileNames,
1098               true, // isHash
1099               fileNames->Size() == 1 ? &fi0 : NULL,
1100               showName);
1101           if (needReduce)
1102             showName += "_";
1103           else
1104             showName = name;
1105 
1106           ODS("### Hash CreateArchiveName END")
1107           name += ".sha256";
1108           showName += ".sha256";
1109           cmi.Folder = fs2us(folderPrefix);
1110           cmi.ArcName = name;
1111           s = "SHA-256 -> ";
1112           s += showName;
1113         }
1114         else if (hc.CommandInternalID == kHash_TestArc)
1115         {
1116           cmi.Verb += "Test";
1117           s = LangStringAlt(IDS_CONTEXT_TEST, "Test archive");
1118           s += " : ";
1119           s += GetNameOfProperty(kpidChecksum, UString("Checksum"));
1120         }
1121         else
1122           cmi.Verb += "Calc";
1123 
1124         cmi.Verb.Add_Dot();
1125         cmi.Verb += hc.MethodName;
1126 
1127         // cmi.HelpString = cmi.Verb;
1128         cmi.UserString = s;
1129         cmi.CtxCommandType = CtxCommandType_CrcChild;
1130         _commandMap.Add(cmi);
1131         MyInsertMenu(subMenu, subIndex_CRC++, currentCommandID++, s, bitmap);
1132         ODS("### 380")
1133       }
1134 
1135       subMenu.Detach();
1136     }
1137   }
1138 
1139   popupMenu.Detach();
1140   /*
1141   if (!ci.Cascaded.Val)
1142     indexMenu = subIndex;
1143   */
1144   const unsigned numCommands = currentCommandID - commandIDFirst;
1145   ODS("+ QueryContextMenu() END")
1146   ODS_SPRF_s(sprintf(s, "Commands=%u currentCommandID - commandIDFirst = %u",
1147       _commandMap.Size(), numCommands))
1148   if (_commandMap.Size() != numCommands)
1149     throw 20190818;
1150   /*
1151   FOR_VECTOR (k, _commandMap)
1152   {
1153     ODS_U(_commandMap[k].Verb);
1154   }
1155   */
1156   }
1157   catch(...)
1158   {
1159     ODS_SPRF_s(sprintf(s, "catch() exception: Commands=%u", _commandMap.Size()))
1160     if (_commandMap.Size() == 0)
1161       throw;
1162   }
1163     /* we added some menu items already : num_added_menu_items,
1164        So we MUST return (number_of_defined_ids), where (number_of_defined_ids >= num_added_menu_items)
1165        This will prevent incorrect menu working, when same IDs can be
1166        assigned in multiple menu items from different subhandlers.
1167        And we must add items to _commandMap before adding to menu.
1168      */
1169   return MAKE_HRESULT_SUCCESS_FAC0(_commandMap.Size());
1170   COM_TRY_END
1171 }
1172 
1173 
FindVerb(const UString & verb) const1174 int CZipContextMenu::FindVerb(const UString &verb) const
1175 {
1176   FOR_VECTOR (i, _commandMap)
1177     if (_commandMap[i].Verb == verb)
1178       return (int)i;
1179   return -1;
1180 }
1181 
Get7zFmPath()1182 static UString Get7zFmPath()
1183 {
1184   return fs2us(NWindows::NDLL::GetModuleDirPrefix()) + L"7zFM.exe";
1185 }
1186 
1187 
InvokeCommand(LPCMINVOKECOMMANDINFO commandInfo)1188 Z7_COMWF_B CZipContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO commandInfo)
1189 {
1190   COM_TRY_BEGIN
1191 
1192   ODS("==== CZipContextMenu::InvokeCommand()")
1193 
1194   #ifdef SHOW_DEBUG_CTX_MENU
1195 
1196     ODS_SPRF_s(sprintf(s, ": InvokeCommand: cbSize=%u flags=%x ",
1197         (unsigned)commandInfo->cbSize, (unsigned)commandInfo->fMask))
1198 
1199     PrintStringA("Verb", commandInfo->lpVerb);
1200     PrintStringA("Parameters", commandInfo->lpParameters);
1201     PrintStringA("Directory", commandInfo->lpDirectory);
1202   #endif
1203 
1204   int commandOffset = -1;
1205 
1206   // xp64 / Win10 : explorer.exe sends 0 in lpVerbW
1207   // MSDN: if (IS_INTRESOURCE(lpVerbW)), we must use LOWORD(lpVerb) as command offset
1208 
1209   // FIXME: old MINGW doesn't define CMINVOKECOMMANDINFOEX / CMIC_MASK_UNICODE
1210   #if !defined(UNDER_CE) && defined(CMIC_MASK_UNICODE)
1211   bool unicodeVerb = false;
1212   if (commandInfo->cbSize == sizeof(CMINVOKECOMMANDINFOEX) &&
1213       (commandInfo->fMask & CMIC_MASK_UNICODE) != 0)
1214   {
1215     LPCMINVOKECOMMANDINFOEX commandInfoEx = (LPCMINVOKECOMMANDINFOEX)commandInfo;
1216     if (!MY_IS_INTRESOURCE(commandInfoEx->lpVerbW))
1217     {
1218       unicodeVerb = true;
1219       commandOffset = FindVerb(commandInfoEx->lpVerbW);
1220     }
1221 
1222     #ifdef SHOW_DEBUG_CTX_MENU
1223     PrintStringW("VerbW", commandInfoEx->lpVerbW);
1224     PrintStringW("ParametersW", commandInfoEx->lpParametersW);
1225     PrintStringW("DirectoryW", commandInfoEx->lpDirectoryW);
1226     PrintStringW("TitleW", commandInfoEx->lpTitleW);
1227     PrintStringA("Title", commandInfoEx->lpTitle);
1228     #endif
1229   }
1230   if (!unicodeVerb)
1231   #endif
1232   {
1233     ODS("use non-UNICODE verb")
1234     // if (HIWORD(commandInfo->lpVerb) == 0)
1235     if (MY_IS_INTRESOURCE(commandInfo->lpVerb))
1236       commandOffset = LOWORD(commandInfo->lpVerb);
1237     else
1238       commandOffset = FindVerb(GetUnicodeString(commandInfo->lpVerb));
1239   }
1240 
1241   ODS_SPRF_s(sprintf(s, "commandOffset=%d", commandOffset))
1242 
1243   if (/* commandOffset < 0 || */ (unsigned)commandOffset >= _commandMap.Size())
1244     return E_INVALIDARG;
1245   const CCommandMapItem &cmi = _commandMap[(unsigned)commandOffset];
1246   return InvokeCommandCommon(cmi);
1247   COM_TRY_END
1248 }
1249 
1250 
InvokeCommandCommon(const CCommandMapItem & cmi)1251 HRESULT CZipContextMenu::InvokeCommandCommon(const CCommandMapItem &cmi)
1252 {
1253   const enum_CommandInternalID cmdID = cmi.CommandInternalID;
1254 
1255   try
1256   {
1257     switch (cmdID)
1258     {
1259       case kOpen:
1260       {
1261         UString params;
1262         params = GetQuotedString(_fileNames[0]);
1263         if (!cmi.ArcType.IsEmpty())
1264         {
1265           params += " -t";
1266           params += cmi.ArcType;
1267         }
1268         MyCreateProcess(Get7zFmPath(), params);
1269         break;
1270       }
1271       case kExtract:
1272       case kExtractHere:
1273       case kExtractTo:
1274       {
1275         if (_attribs.FirstDirIndex != -1)
1276         {
1277           ShowErrorMessageRes(IDS_SELECT_FILES);
1278           break;
1279         }
1280         ExtractArchives(_fileNames, cmi.Folder,
1281             (cmdID == kExtract), // showDialog
1282             (cmdID == kExtractTo) && _elimDup.Val, // elimDup
1283             _writeZone
1284             );
1285         break;
1286       }
1287       case kTest:
1288       {
1289         TestArchives(_fileNames);
1290         break;
1291       }
1292       case kCompress:
1293       case kCompressEmail:
1294       case kCompressTo7z:
1295       case kCompressTo7zEmail:
1296       case kCompressToZip:
1297       case kCompressToZipEmail:
1298       {
1299         UString arcName = cmi.ArcName;
1300         if (_fileNames_WereReduced)
1301         {
1302           UString arcName_base;
1303           arcName = CreateArchiveName(
1304               _fileNames,
1305               false, // isHash
1306               NULL, // fi0
1307               arcName_base);
1308           const char *postfix = NULL;
1309           if (cmdID == kCompressTo7z ||
1310               cmdID == kCompressTo7zEmail)
1311             postfix = ".7z";
1312           else if (
1313               cmdID == kCompressToZip ||
1314               cmdID == kCompressToZipEmail)
1315             postfix = ".zip";
1316           if (postfix)
1317             arcName += postfix;
1318         }
1319 
1320         const bool email =
1321             cmdID == kCompressEmail ||
1322             cmdID == kCompressTo7zEmail ||
1323             cmdID == kCompressToZipEmail;
1324         const bool showDialog =
1325             cmdID == kCompress ||
1326             cmdID == kCompressEmail;
1327         const bool addExtension = showDialog;
1328         CompressFiles(cmi.Folder,
1329             arcName, cmi.ArcType,
1330             addExtension,
1331             _fileNames, email, showDialog,
1332             false // waitFinish
1333             );
1334         break;
1335       }
1336 
1337       case kHash_CRC32:
1338       case kHash_CRC64:
1339       case kHash_XXH64:
1340       case kHash_SHA1:
1341       case kHash_SHA256:
1342       case kHash_BLAKE2SP:
1343       case kHash_All:
1344       case kHash_Generate_SHA256:
1345       case kHash_TestArc:
1346       {
1347         for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_HashCommands); i++)
1348         {
1349           const CHashCommand &hc = g_HashCommands[i];
1350           if (hc.CommandInternalID == cmdID)
1351           {
1352             if (cmdID == kHash_TestArc)
1353             {
1354               TestArchives(_fileNames, true); // hashMode
1355               break;
1356             }
1357             UString generateName;
1358             if (cmdID == kHash_Generate_SHA256)
1359             {
1360               generateName = cmi.ArcName;
1361               if (_fileNames_WereReduced)
1362               {
1363                 UString arcName_base;
1364                 generateName = CreateArchiveName(_fileNames,
1365                     true, // isHash
1366                     NULL, // fi0
1367                     arcName_base);
1368                 generateName += ".sha256";
1369               }
1370             }
1371             CalcChecksum(_fileNames, (UString)hc.MethodName,
1372                 cmi.Folder, generateName);
1373             break;
1374           }
1375         }
1376         break;
1377       }
1378       case kCommandNULL:
1379         break;
1380     }
1381   }
1382   catch(...)
1383   {
1384     ShowErrorMessage(NULL, L"Error");
1385   }
1386   return S_OK;
1387 }
1388 
1389 
1390 
MyCopyString_isUnicode(void * dest,UINT size,const UString & src,bool writeInUnicode)1391 static void MyCopyString_isUnicode(void *dest, UINT size, const UString &src, bool writeInUnicode)
1392 {
1393   if (size != 0)
1394     size--;
1395   if (writeInUnicode)
1396   {
1397     UString s = src;
1398     s.DeleteFrom(size);
1399     MyStringCopy((wchar_t *)dest, s);
1400     ODS_U(s)
1401   }
1402   else
1403   {
1404     AString s = GetAnsiString(src);
1405     s.DeleteFrom(size);
1406     MyStringCopy((char *)dest, s);
1407   }
1408 }
1409 
1410 
GetCommandString(UINT commandOffset,UINT uType,UINT *,LPSTR pszName,UINT cchMax)1411 Z7_COMWF_B CZipContextMenu::GetCommandString(
1412       #ifdef Z7_OLD_WIN_SDK
1413         UINT
1414       #else
1415         UINT_PTR
1416       #endif
1417     commandOffset,
1418     UINT uType,
1419     UINT * /* pwReserved */ , LPSTR pszName, UINT cchMax)
1420 {
1421   COM_TRY_BEGIN
1422 
1423   ODS("GetCommandString")
1424 
1425   const int cmdOffset = (int)commandOffset;
1426 
1427   ODS_SPRF_s(sprintf(s, "GetCommandString: cmdOffset=%d uType=%d cchMax = %d",
1428       cmdOffset, uType, cchMax))
1429 
1430   if ((uType | GCS_UNICODE) == GCS_VALIDATEW)
1431   {
1432     if (/* cmdOffset < 0 || */ (unsigned)cmdOffset >= _commandMap.Size())
1433       return S_FALSE;
1434     return S_OK;
1435   }
1436 
1437   if (/* cmdOffset < 0 || */ (unsigned)cmdOffset >= _commandMap.Size())
1438   {
1439     ODS("------ cmdOffset: E_INVALIDARG")
1440     return E_INVALIDARG;
1441   }
1442 
1443   // we use Verb as HelpString
1444   if (cchMax != 0)
1445   if ((uType | GCS_UNICODE) == GCS_VERBW ||
1446       (uType | GCS_UNICODE) == GCS_HELPTEXTW)
1447   {
1448     const CCommandMapItem &cmi = _commandMap[(unsigned)cmdOffset];
1449     MyCopyString_isUnicode(pszName, cchMax, cmi.Verb, (uType & GCS_UNICODE) != 0);
1450     return S_OK;
1451   }
1452 
1453   return E_INVALIDARG;
1454 
1455   COM_TRY_END
1456 }
1457 
1458 
1459 
1460 // ---------- IExplorerCommand ----------
1461 
My_SHStrDupW(LPCWSTR src,LPWSTR * dest)1462 static HRESULT WINAPI My_SHStrDupW(LPCWSTR src, LPWSTR *dest)
1463 {
1464   if (src)
1465   {
1466     const SIZE_T size = (wcslen(src) + 1) * sizeof(WCHAR);
1467     WCHAR *p = (WCHAR *)CoTaskMemAlloc(size);
1468     if (p)
1469     {
1470       memcpy(p, src, size);
1471       *dest = p;
1472       return S_OK;
1473     }
1474   }
1475   *dest = NULL;
1476   return E_OUTOFMEMORY;
1477 }
1478 
1479 
1480 #define CZipExplorerCommand CZipContextMenu
1481 
1482 class CCoTaskWSTR
1483 {
1484   LPWSTR m_str;
1485   Z7_CLASS_NO_COPY(CCoTaskWSTR)
1486 public:
CCoTaskWSTR()1487   CCoTaskWSTR(): m_str(NULL) {}
~CCoTaskWSTR()1488   ~CCoTaskWSTR() { ::CoTaskMemFree(m_str); }
operator &()1489   LPWSTR* operator&() { return &m_str; }
operator LPCWSTR() const1490   operator LPCWSTR () const { return m_str; }
1491   // operator LPCOLESTR() const { return m_str; }
operator bool() const1492   operator bool() const { return m_str != NULL; }
1493   // bool operator!() const { return m_str == NULL; }
1494 
1495   /*
1496   void Wipe_and_Free()
1497   {
1498     if (m_str)
1499     {
1500       memset(m_str, 0, ::SysStringLen(m_str) * sizeof(*m_str));
1501       Empty();
1502     }
1503   }
1504   */
1505 
1506 private:
1507   /*
1508   CCoTaskWSTR(LPCOLESTR src) { m_str = ::CoTaskMemAlloc(src); }
1509 
1510   CCoTaskWSTR& operator=(LPCOLESTR src)
1511   {
1512     ::CoTaskMemFree(m_str);
1513     m_str = ::SysAllocString(src);
1514     return *this;
1515   }
1516 
1517 
1518   void Empty()
1519   {
1520     ::CoTaskMemFree(m_str);
1521     m_str = NULL;
1522   }
1523   */
1524 };
1525 
LoadPaths(IShellItemArray * psiItemArray,UStringVector & paths)1526 static HRESULT LoadPaths(IShellItemArray *psiItemArray, UStringVector &paths)
1527 {
1528   if (psiItemArray)
1529   {
1530     DWORD numItems = 0;
1531     RINOK(psiItemArray->GetCount(&numItems))
1532     {
1533       ODS_(Print_Number(numItems, " ==== LoadPaths START === "))
1534       for (DWORD i = 0; i < numItems; i++)
1535       {
1536         CMyComPtr<IShellItem> item;
1537         RINOK(psiItemArray->GetItemAt(i, &item))
1538         if (item)
1539         {
1540           CCoTaskWSTR displayName;
1541           if (item->GetDisplayName(SIGDN_FILESYSPATH, &displayName) == S_OK
1542               && (bool)displayName)
1543           {
1544             ODS_U(displayName)
1545             paths.Add((LPCWSTR)displayName);
1546           }
1547         }
1548       }
1549       ODS_(Print_Number(numItems, " ==== LoadPaths END === "))
1550     }
1551   }
1552   return S_OK;
1553 }
1554 
1555 
LoadItems(IShellItemArray * psiItemArray)1556 void CZipExplorerCommand::LoadItems(IShellItemArray *psiItemArray)
1557 {
1558   SubCommands.Clear();
1559   _fileNames.Clear();
1560   {
1561     UStringVector paths;
1562     if (LoadPaths(psiItemArray, paths) != S_OK)
1563       return;
1564     _fileNames = paths;
1565   }
1566   const HRESULT res = QueryContextMenu(
1567       NULL, // hMenu,
1568       0, // indexMenu,
1569       0, // commandIDFirst,
1570       0 + 999, // commandIDLast,
1571       CMF_NORMAL);
1572 
1573   if (FAILED(res))
1574     return /* res */;
1575 
1576   CZipExplorerCommand *crcHandler = NULL;
1577   CZipExplorerCommand *openHandler = NULL;
1578 
1579   bool useCascadedCrc = true; // false;
1580   bool useCascadedOpen = true; // false;
1581 
1582   for (unsigned i = 0; i < _commandMap.Size(); i++)
1583   {
1584     const CCommandMapItem &cmi = _commandMap[i];
1585 
1586     if (cmi.IsPopup)
1587       if (!cmi.IsSubMenu())
1588         continue;
1589 
1590     // if (cmi.IsSubMenu()) continue // for debug
1591 
1592     CZipContextMenu *shellExt = new CZipContextMenu();
1593     shellExt->IsRoot = false;
1594 
1595     if (cmi.CtxCommandType == CtxCommandType_CrcRoot && !useCascadedCrc)
1596       shellExt->IsSeparator = true;
1597 
1598     {
1599       CZipExplorerCommand *handler = this;
1600       if (cmi.CtxCommandType == CtxCommandType_CrcChild && crcHandler)
1601         handler = crcHandler;
1602       else if (cmi.CtxCommandType == CtxCommandType_OpenChild && openHandler)
1603         handler = openHandler;
1604       handler->SubCommands.AddNew() = shellExt;
1605     }
1606 
1607     shellExt->_commandMap_Cur.Add(cmi);
1608 
1609     ODS_U(cmi.UserString)
1610 
1611     if (cmi.CtxCommandType == CtxCommandType_CrcRoot && useCascadedCrc)
1612       crcHandler = shellExt;
1613     if (cmi.CtxCommandType == CtxCommandType_OpenRoot && useCascadedOpen)
1614     {
1615       // ODS("cmi.CtxCommandType == CtxCommandType_OpenRoot");
1616       openHandler = shellExt;
1617     }
1618   }
1619 }
1620 
1621 
GetTitle(IShellItemArray * psiItemArray,LPWSTR * ppszName)1622 Z7_COMWF_B CZipExplorerCommand::GetTitle(IShellItemArray *psiItemArray, LPWSTR *ppszName)
1623 {
1624   ODS("- GetTitle()")
1625  // COM_TRY_BEGIN
1626   if (IsSeparator)
1627   {
1628     *ppszName = NULL;
1629     return S_FALSE;
1630   }
1631 
1632   UString name;
1633   if (IsRoot)
1634   {
1635     LoadItems(psiItemArray);
1636     name = "7-Zip"; //  "New"
1637   }
1638   else
1639     name = "7-Zip item";
1640 
1641   if (!_commandMap_Cur.IsEmpty())
1642   {
1643     const CCommandMapItem &mi = _commandMap_Cur[0];
1644     // s += mi.Verb;
1645     // s += " : ";
1646     name = mi.UserString;
1647   }
1648 
1649   return My_SHStrDupW(name, ppszName);
1650   // return S_OK;
1651   // COM_TRY_END
1652 }
1653 
1654 
GetIcon(IShellItemArray *,LPWSTR * ppszIcon)1655 Z7_COMWF_B CZipExplorerCommand::GetIcon(IShellItemArray * /* psiItemArray */, LPWSTR *ppszIcon)
1656 {
1657   ODS("- GetIcon()")
1658   // COM_TRY_BEGIN
1659   *ppszIcon = NULL;
1660   // return E_NOTIMPL;
1661   UString imageName = fs2us(NWindows::NDLL::GetModuleDirPrefix());
1662   // imageName += "7zG.exe";
1663   imageName += "7-zip.dll";
1664   // imageName += ",190";
1665   return My_SHStrDupW(imageName, ppszIcon);
1666   // COM_TRY_END
1667 }
1668 
1669 
GetToolTip(IShellItemArray *,LPWSTR * ppszInfotip)1670 Z7_COMWF_B CZipExplorerCommand::GetToolTip (IShellItemArray * /* psiItemArray */, LPWSTR *ppszInfotip)
1671 {
1672   // COM_TRY_BEGIN
1673   ODS("- GetToolTip()")
1674   *ppszInfotip = NULL;
1675   return E_NOTIMPL;
1676   // COM_TRY_END
1677 }
1678 
1679 
GetCanonicalName(GUID * pguidCommandName)1680 Z7_COMWF_B CZipExplorerCommand::GetCanonicalName(GUID *pguidCommandName)
1681 {
1682   // COM_TRY_BEGIN
1683   ODS("- GetCanonicalName()")
1684   *pguidCommandName = GUID_NULL;
1685   return E_NOTIMPL;
1686   // COM_TRY_END
1687 }
1688 
1689 
GetState(IShellItemArray *,BOOL,EXPCMDSTATE * pCmdState)1690 Z7_COMWF_B CZipExplorerCommand::GetState(IShellItemArray * /* psiItemArray */, BOOL /* fOkToBeSlow */, EXPCMDSTATE *pCmdState)
1691 {
1692   // COM_TRY_BEGIN
1693   ODS("- GetState()")
1694   *pCmdState = ECS_ENABLED;
1695   return S_OK;
1696   // COM_TRY_END
1697 }
1698 
1699 
1700 
1701 
Invoke(IShellItemArray * psiItemArray,IBindCtx *)1702 Z7_COMWF_B CZipExplorerCommand::Invoke(IShellItemArray *psiItemArray, IBindCtx * /* pbc */)
1703 {
1704   COM_TRY_BEGIN
1705 
1706   if (_commandMap_Cur.IsEmpty())
1707     return E_INVALIDARG;
1708 
1709   ODS("- Invoke()")
1710   _fileNames.Clear();
1711   UStringVector paths;
1712   RINOK(LoadPaths(psiItemArray, paths))
1713   _fileNames = paths;
1714   return InvokeCommandCommon(_commandMap_Cur[0]);
1715 
1716   COM_TRY_END
1717 }
1718 
1719 
GetFlags(EXPCMDFLAGS * pFlags)1720 Z7_COMWF_B CZipExplorerCommand::GetFlags(EXPCMDFLAGS *pFlags)
1721 {
1722   ODS("- GetFlags()")
1723   // COM_TRY_BEGIN
1724   EXPCMDFLAGS f = ECF_DEFAULT;
1725   if (IsSeparator)
1726     f = ECF_ISSEPARATOR;
1727   else if (IsRoot)
1728     f = ECF_HASSUBCOMMANDS;
1729   else
1730   {
1731     if (!_commandMap_Cur.IsEmpty())
1732     {
1733       // const CCommandMapItem &cmi = ;
1734       if (_commandMap_Cur[0].IsSubMenu())
1735       {
1736         // ODS("ECF_HASSUBCOMMANDS")
1737         f = ECF_HASSUBCOMMANDS;
1738       }
1739     }
1740   }
1741   *pFlags = f;
1742   return S_OK;
1743   // COM_TRY_END
1744 }
1745 
1746 
EnumSubCommands(IEnumExplorerCommand ** ppEnum)1747 Z7_COMWF_B CZipExplorerCommand::EnumSubCommands(IEnumExplorerCommand **ppEnum)
1748 {
1749   ODS("- EnumSubCommands()")
1750   // COM_TRY_BEGIN
1751   *ppEnum = NULL;
1752 
1753   if (!_commandMap_Cur.IsEmpty() && _commandMap_Cur[0].IsSubMenu())
1754   {
1755   }
1756   else
1757   {
1758     if (!IsRoot)
1759       return E_NOTIMPL;
1760     if (SubCommands.IsEmpty())
1761     {
1762       return E_NOTIMPL;
1763     }
1764   }
1765 
1766   // shellExt->
1767   return QueryInterface(IID_IEnumExplorerCommand, (void **)ppEnum);
1768 
1769   // return S_OK;
1770   // COM_TRY_END
1771 }
1772 
1773 
Next(ULONG celt,IExplorerCommand ** pUICommand,ULONG * pceltFetched)1774 Z7_COMWF_B CZipContextMenu::Next(ULONG celt, IExplorerCommand **pUICommand, ULONG *pceltFetched)
1775 {
1776   ODS("CZipContextMenu::Next()")
1777   ODS_(Print_Number(celt, "celt"))
1778   ODS_(Print_Number(CurrentSubCommand, "CurrentSubCommand"))
1779   ODS_(Print_Number(SubCommands.Size(), "SubCommands.Size()"))
1780 
1781   COM_TRY_BEGIN
1782   ULONG fetched = 0;
1783 
1784   ULONG i;
1785   for (i = 0; i < celt; i++)
1786   {
1787     pUICommand[i] = NULL;
1788   }
1789 
1790   for (i = 0; i < celt && CurrentSubCommand < SubCommands.Size(); i++)
1791   {
1792     pUICommand[i] = SubCommands[CurrentSubCommand++];
1793     pUICommand[i]->AddRef();
1794     fetched++;
1795   }
1796 
1797   if (pceltFetched)
1798     *pceltFetched = fetched;
1799 
1800   ODS(fetched == celt ? " === OK === " : "=== ERROR ===")
1801 
1802   // we return S_FALSE for (fetched == 0)
1803   return (fetched == celt) ? S_OK : S_FALSE;
1804   COM_TRY_END
1805 }
1806 
1807 
Skip(ULONG)1808 Z7_COMWF_B CZipContextMenu::Skip(ULONG /* celt */)
1809 {
1810   ODS("CZipContextMenu::Skip()")
1811   return E_NOTIMPL;
1812 }
1813 
1814 
Reset(void)1815 Z7_COMWF_B CZipContextMenu::Reset(void)
1816 {
1817   ODS("CZipContextMenu::Reset()")
1818   CurrentSubCommand = 0;
1819   return S_OK;
1820 }
1821 
1822 
Clone(IEnumExplorerCommand ** ppenum)1823 Z7_COMWF_B CZipContextMenu::Clone(IEnumExplorerCommand **ppenum)
1824 {
1825   ODS("CZipContextMenu::Clone()")
1826   *ppenum = NULL;
1827   return E_NOTIMPL;
1828 }
1829