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