OSDN Git Service

Avoid infinite loops in the RegularExpression::subst() function when the length of...
[winmerge-jp/winmerge-jp.git] / Src / 7zCommon.cpp
1 /////////////////////////////////////////////////////////////////////////////\r
2 //    WinMerge:  an interactive diff/merge utility\r
3 //    Copyright (C) 1997-2000  Thingamahoochie Software\r
4 //    Author: Dean Grimm\r
5 //    SPDX-License-Identifier: GPL-2.0-or-later\r
6 //\r
7 /////////////////////////////////////////////////////////////////////////////\r
8 \r
9 /* 7zCommon.cpp: Implement 7z related classes and functions\r
10  * Copyright (c) 2003 Jochen Tucht\r
11  *\r
12  * Remarks:     Different versions of 7-Zip are interfaced through specific\r
13  *                      versions of Merge7z (Merge7z311.dll, Merge7z312.dll, etc.)\r
14  *                      WinMerge can either use an installed copy of the 7-Zip software,\r
15  *                      or fallback to a local set of 7-Zip DLLs, which are to be included\r
16  *                      in the WinMerge binary distribution.\r
17  *\r
18  *                      Fallback policies are as follows:\r
19  *\r
20  *                      1. Detect 7-Zip version installed (XXX).\r
21  *                      2. If there is a Merge7zXXX.dll, be happy to use it\r
22  *                      3. Detect 7-Zip version from WinMerge distribution (YYY).\r
23  *                      4. If there is a Merge7zYYY.dll, be happy to use it.\r
24  *                      5. Sorry, no way.\r
25  *\r
26  *                      These rules can be customized by setting a registry variable\r
27  *                      *Merge7z/Enable* of type DWORD to one of the following values:\r
28  *\r
29  *                      0 - Entirely disable 7-Zip integration.\r
30  *                      1 - Use installed 7-Zip if present. Otherwise, use local 7-Zip.\r
31  *                      2 - Always use local 7-Zip.\r
32  *\r
33 \r
34 Please mind 2. a) of the GNU General Public License, and log your changes below.\r
35 \r
36 DATE:           BY:                                     DESCRIPTION:\r
37 ==========      ==================      ================================================\r
38 2003-12-09      Jochen Tucht            Created\r
39 2003-12-16      Jochen Tucht            Properly generate .tar.gz and .tar.bz2\r
40 2003-12-16      Jochen Tucht            Obtain long path to temporary folder\r
41 2004-01-20      Jochen Tucht            Complain only once if Merge7z*.dll is missing\r
42 2004-01-25      Jochen Tucht            Fix bad default for OPENFILENAME::nFilterIndex\r
43 2004-03-15      Jochen Tucht            Fix Visual Studio 2003 build issue\r
44 2004-04-13      Jochen Tucht            Avoid StrNCat to get away with shlwapi 4.70\r
45 2004-08-25      Jochen Tucht            More explicit error message\r
46 2004-10-17      Jochen Tucht            Leave decision whether to recurse into folders\r
47                                                                 to enumerator (Mask.Recurse)\r
48 2004-11-03      Jochen Tucht            FIX [1048997] as proposed by Kimmo 2004-11-02\r
49 2005-01-15      Jochen Tucht            Read 7-Zip version from 7zip_pad.xml\r
50                                                                 Set Merge7z UI language if DllBuild_Merge7z >= 9\r
51 2005-01-22      Jochen Tucht            Better explain what's present/missing/outdated\r
52 2005-02-05      Jochen Tucht            Fall back to IDD_MERGE7ZMISMATCH template from\r
53                                                                 .exe if .lang file isn't up to date.\r
54 2005-02-26      Jochen Tucht            Add download link to error message\r
55 2005-02-26      Jochen Tucht            Use WinAPI to obtain ISO language/region codes\r
56 2005-02-27      Jochen Tucht            FIX [1152375]\r
57 2005-04-24      Kimmo Varis                     Don't use DiffContext exported from DirView\r
58 2005-06-08      Kimmo Varis                     Use DIFFITEM, not reference to it (hopefully only\r
59                                                                 temporarily, to sort out new directory compare)\r
60 2005-06-22      Jochen Tucht            Change recommended version of 7-Zip to 4.20\r
61                                                                 Remove noise from Nagbox\r
62 2005-07-03      Jochen Tucht            DIFFITEM has changed due to RFE [ 1205516 ]\r
63 2005-07-04      Jochen Tucht            New global ArchiveGuessFormat() checks for\r
64                                                                 formats to be handled by external command line\r
65                                                                 tools. These take precedence over Merge7z\r
66                                                                 internal handlers.\r
67 2005-07-05      Jochen Tucht            Move to Merge7z::Format::GetDefaultName() to\r
68                                                                 build intermediate filenames for multi-step\r
69                                                                 compression.\r
70 2005-07-15      Jochen Tucht            Remove external command line tool integration\r
71                                                                 for now. Rethink about it after 2.4 branch.\r
72 2005-08-20      Jochen Tucht            Option to guess archive format by signature\r
73                                                                 Map extensions through ExternalArchiveFormat.ini\r
74 2005-08-23      Jochen Tucht            Option to entirely disable 7-Zip integration\r
75 2007-01-04      Kimmo Varis                     Convert using COptionsMgr for options.\r
76 2007-06-16      Jochen Neubeck          FIX [1723263] "Zip --> Both" operation...\r
77 2007-12-22      Jochen Neubeck          Fix Merge7z UI lang for new translation system\r
78                                                                 Change recommended version of 7-Zip to 4.57\r
79 2010-05-16      Jochen Neubeck          Read 7-Zip version from 7z.dll (which has long\r
80                                                                 ago replaced the various format and codec DLLs)\r
81                                                                 Change recommended version of 7-Zip to 4.65\r
82 */\r
83 \r
84 \r
85 #include "stdafx.h"\r
86 #include "7zCommon.h"\r
87 #include <afxinet.h>\r
88 #include "OptionsDef.h"\r
89 #include "OptionsMgr.h"\r
90 #include "DirView.h"\r
91 #include "DirDoc.h"\r
92 #include "DirActions.h"\r
93 //#include "ExternalArchiveFormat.h"\r
94 #include "VersionInfo.h"\r
95 #include "paths.h"\r
96 #include "Environment.h"\r
97 #include "Merge7zFormatRegister.h"\r
98 \r
99 #ifdef _DEBUG\r
100 #define new DEBUG_NEW\r
101 #endif\r
102 \r
103 /**\r
104  * @brief Proxy for Merge7z\r
105  */\r
106 static __declspec(thread) Merge7z::Proxy m_Merge7z =\r
107 {\r
108         { 0, 0, DllBuild_Merge7z, },\r
109         "Merge7z\\Merge7z.dll",\r
110         "Merge7z",\r
111         nullptr\r
112 };\r
113 \r
114 std::vector<Merge7z::Format *(*)(const String& path)> Merge7zFormatRegister::optionalFormats;\r
115 \r
116 /**\r
117  * @brief assign BSTR to String, and return BSTR for optional SysFreeString()\r
118  */\r
119 inline BSTR Assign(CString &dst, BSTR src)\r
120 {\r
121         dst = src;\r
122         return src;\r
123 }\r
124 \r
125 bool IsArchiveFile(const String& pszFile)\r
126 {\r
127         try {\r
128                 Merge7z::Format *piHandler = ArchiveGuessFormat(pszFile);\r
129                 if (piHandler != nullptr)\r
130                         return true;\r
131                 else\r
132                         return false;\r
133         }\r
134         catch (CException *e)\r
135         {\r
136                 e->Delete();\r
137                 return false;\r
138         }\r
139 }\r
140 \r
141 /**\r
142  * @brief Wrap Merge7z::GuessFormat() to allow for some customizing:\r
143  * - Check if 7-Zip integration is enabled.\r
144  * - Check for filename extension mappings.\r
145  */\r
146 Merge7z::Format *ArchiveGuessFormat(const String& path)\r
147 {\r
148         if (!GetOptionsMgr()->GetBool(OPT_ARCHIVE_ENABLE))\r
149                 return nullptr;\r
150         if (paths::IsDirectory(path))\r
151                 return nullptr;\r
152         String path2(path);\r
153         // Map extensions through ExternalArchiveFormat.ini\r
154         static tchar_t null[] = _T("");\r
155         static const tchar_t section[] = _T("extensions");\r
156         String entry = paths::FindExtension(path);\r
157         tchar_t value[20];\r
158         static const tchar_t* filename = nullptr;\r
159         if (filename == nullptr)\r
160         {\r
161                 tchar_t cPath[INTERNET_MAX_PATH_LENGTH];\r
162                 DWORD cchPath = SearchPath(nullptr, _T("ExternalArchiveFormat.ini"), nullptr,\r
163                         INTERNET_MAX_PATH_LENGTH, cPath, nullptr);\r
164                 filename = cchPath && cchPath < INTERNET_MAX_PATH_LENGTH ? tc::tcsdup(cPath) : null;\r
165         }\r
166         if (*filename &&\r
167                 GetPrivateProfileString(section, entry.c_str(), null, value, 20, filename) &&\r
168                 *value == '.')\r
169         {\r
170                 // Remove end-of-line comments (in string returned from GetPrivateProfileString)\r
171                 // that is, remove semicolon & whatever follows it\r
172                 if (tchar_t* p = StrChr(value, ';'))\r
173                 {\r
174                         *p = '\0';\r
175                         StrTrim(value, _T(" \t"));\r
176                 }\r
177                 path2 = value;\r
178         }\r
179 \r
180         // PATCH [ 1229867 ] RFE [ 1205516 ], RFE [ 887948 ], and other issues\r
181         // command line integration portion is not yet applied\r
182         // so following code not yet valid, so temporarily commented out\r
183         // Look for command line tool first\r
184         /*Merge7z::Format *pFormat;\r
185         if (CExternalArchiveFormat::GuessFormat(path, pFormat))\r
186         {\r
187                 return pFormat;\r
188         }*/\r
189         // Default to Merge7z*.dll\r
190 \r
191         try\r
192         {\r
193                 Merge7z::Format* pFormat = nullptr;\r
194                 if (!paths::IsURL(path2))\r
195                         pFormat = m_Merge7z->GuessFormat(path2.c_str());\r
196                 if (pFormat == nullptr)\r
197                         pFormat = Merge7zFormatRegister::GuessFormat(path2);\r
198                 return pFormat;\r
199         }\r
200         catch (...)\r
201         {\r
202                 Merge7z::Format *pFormat = Merge7zFormatRegister::GuessFormat(path2);\r
203                 if (pFormat != nullptr)\r
204                         return pFormat;\r
205                 throw;\r
206         }\r
207 }\r
208 \r
209 /**\r
210  * @brief Check whether archive support is available.\r
211  */\r
212 int NTAPI HasZipSupport()\r
213 {\r
214         static int HasZipSupport = -1;\r
215         if (HasZipSupport == -1)\r
216         {\r
217                 try\r
218                 {\r
219                         m_Merge7z.operator->();\r
220                         HasZipSupport = 1;\r
221                 }\r
222                 catch (CException *e)\r
223                 {\r
224                         e->Delete();\r
225                         HasZipSupport = 0;\r
226                 }\r
227         }\r
228         return HasZipSupport;\r
229 }\r
230 \r
231 /**\r
232  * @brief Delete head of temp path context list, and return its parent context.\r
233  */\r
234 CTempPathContext *CTempPathContext::DeleteHead()\r
235 {\r
236         CTempPathContext *pParent = m_pParent;\r
237         delete this;\r
238         return pParent;\r
239 }\r
240 \r
241 void CTempPathContext::Swap(int idx1, int idx2)\r
242 {\r
243         std::swap(m_strDisplayRoot[idx1], m_strDisplayRoot[idx2]);\r
244         std::swap(m_strRoot[idx1], m_strRoot[idx2]);\r
245         if (m_pParent != nullptr)\r
246                 m_pParent->Swap(idx1, idx2);\r
247 }\r
248 \r
249 /**\r
250  * @brief Return installed or local version of 7-Zip.\r
251  */\r
252 DWORD NTAPI VersionOf7z()\r
253 {\r
254         String path = paths::ConcatPath(env::GetProgPath(), _T("Merge7z\\7z.dll"));\r
255         unsigned versionMS = 0;\r
256         unsigned versionLS = 0;\r
257         CVersionInfo(path.c_str()).GetFixedFileVersion(versionMS, versionLS);\r
258         return versionMS;\r
259 }\r
260 \r
261 /**\r
262  * @brief Access dll functions through proxy.\r
263  */\r
264 interface Merge7z *Merge7z::Proxy::operator->()\r
265 {\r
266         // As long as the Merge7z*.DLL has not yet been loaded, Merge7z\r
267         // [0] points to the name of the DLL (with placeholders for 7-\r
268         // Zip major and minor version numbers). Once the DLL has been\r
269         // loaded successfully, Merge7z[0] is set to nullptr, causing the\r
270         // if to fail on subsequent calls.\r
271 \r
272         if (const char *format = Merge7z[0])\r
273         {\r
274                 // Merge7z has not yet been loaded\r
275 \r
276                 if (!GetOptionsMgr()->GetBool(OPT_ARCHIVE_ENABLE))\r
277                         throw new CResourceException();\r
278                 if (DWORD ver = VersionOf7z())\r
279                 {\r
280                         Merge7z[0] = format;\r
281                         stub.Load();\r
282                 }\r
283                 else\r
284                 {\r
285                         throw new CResourceException();\r
286                 }\r
287                 LANGID wLangID = (LANGID)GetThreadLocale();\r
288                 DWORD flags = Initialize::Default | Initialize::Local7z | (wLangID << 16);\r
289                 if (GetOptionsMgr()->GetBool(OPT_ARCHIVE_PROBETYPE))\r
290                 {\r
291                         flags |= Initialize::GuessFormatBySignature | Initialize::GuessFormatByExtension;\r
292                 }\r
293                 if (Merge7z[1])\r
294                         ((interface Merge7z *)Merge7z[1])->Initialize(flags);\r
295         }\r
296         return ((interface Merge7z *)Merge7z[1]);\r
297 }\r
298 \r
299 /**\r
300  * @brief Tell Merge7z we are going to enumerate just 1 item.\r
301  */\r
302 UINT SingleItemEnumerator::Open()\r
303 {\r
304         return 1;\r
305 }\r
306 \r
307 /**\r
308  * @brief Pass information about the item to Merge7z.\r
309  */\r
310 Merge7z::Envelope *SingleItemEnumerator::Enum(Item &item)\r
311 {\r
312         item.Mask.Item = item.Mask.Name|item.Mask.FullPath|item.Mask.Recurse;\r
313         item.Name = Name;\r
314         item.FullPath = FullPath;\r
315         return 0;\r
316 }\r
317 \r
318 /**\r
319  * @brief SingleFileEnumerator constructor.\r
320  */\r
321 SingleItemEnumerator::SingleItemEnumerator(const tchar_t* path, const tchar_t* FullPath, const tchar_t* Name)\r
322 : FullPath(FullPath)\r
323 , Name(Name)\r
324 {\r
325 }\r
326 \r
327 /**\r
328  * @brief Construct a DirItemEnumerator.\r
329  *\r
330  * Argument *nFlags* controls operation as follows:\r
331  * LVNI_ALL:            Enumerate all items.\r
332  * LVNI_SELECTED:       Enumerate selected items only.\r
333  * Original:            Set folder prefix for first iteration to "original"\r
334  * Altered:                     Set folder prefix for second iteration to "altered"\r
335  * BalanceFolders:      Ensure that all nonempty folders on either side have a\r
336  *                                      corresponding folder on the other side, even if it is\r
337  *                                      empty (DirScan doesn't recurse into folders which\r
338  *                                      appear only on one side).\r
339  * DiffsOnly:           Enumerate diffs only.\r
340  */\r
341 DirItemEnumerator::DirItemEnumerator(CDirView *pView, int nFlags)\r
342 : m_pView(pView)\r
343 , m_nFlags(nFlags)\r
344 {\r
345         if (m_nFlags & Original)\r
346         {\r
347                 m_rgFolderPrefix.push_back(_T("original"));\r
348         }\r
349         if (m_nFlags & Altered)\r
350         {\r
351                 m_rgFolderPrefix.push_back(_T("altered"));\r
352         }\r
353         if (m_nFlags & BalanceFolders)\r
354         {\r
355                 const CDiffContext& ctxt = pView->GetDiffContext();\r
356                 // Collect implied folders\r
357                 for (UINT i = Open() ; i-- ; )\r
358                 {\r
359                         const DIFFITEM &di = Next();\r
360                         if ((m_nFlags & DiffsOnly) && !IsItemNavigableDiff(ctxt, di))\r
361                         {\r
362                                 continue;\r
363                         }\r
364                         // Enumerating items\r
365                         if (di.diffcode.exists(m_index))\r
366                         {\r
367                                 // Item is present on right side, i.e. folder is implied\r
368                                 m_rgImpliedFolders[m_index][di.diffFileInfo[m_index].path.get()] = PVOID(1);\r
369                         }\r
370                 }\r
371         }\r
372 }\r
373 \r
374 /**\r
375  * @brief Initialize enumerator, return number of items to be enumerated.\r
376  */\r
377 UINT DirItemEnumerator::Open()\r
378 {\r
379         m_nIndex = -1;\r
380         m_curFolderPrefix = m_rgFolderPrefix.begin();\r
381         if (m_pView->GetDocument()->m_nDirs < 3)\r
382                 m_index = (m_nFlags & Right) != 0 ? 1 : 0;\r
383         else\r
384                 m_index = ((m_nFlags & Right) != 0) ? 2 : ((m_nFlags & Middle) != 0 ? 1 : 0);\r
385         size_t nrgFolderPrefix = m_rgFolderPrefix.size();\r
386         if (nrgFolderPrefix)\r
387         {\r
388                 m_strFolderPrefix = *m_curFolderPrefix++;\r
389         }\r
390         else\r
391         {\r
392                 nrgFolderPrefix = 1;\r
393         }\r
394         return\r
395         static_cast<UINT>((\r
396                 (m_nFlags & LVNI_SELECTED)\r
397         ?       pView(m_pView)->GetSelectedCount()\r
398         :       pView(m_pView)->GetItemCount()\r
399         ) * nrgFolderPrefix);\r
400 }\r
401 \r
402 /**\r
403  * @brief Return next item.\r
404  */\r
405 const DIFFITEM &DirItemEnumerator::Next()\r
406 {\r
407         enum {nMask = LVNI_FOCUSED|LVNI_SELECTED|LVNI_CUT|LVNI_DROPHILITED};\r
408         while ((m_nIndex = pView(m_pView)->GetNextItem(m_nIndex, m_nFlags & nMask)) == -1)\r
409         {\r
410                 m_strFolderPrefix = *m_curFolderPrefix++;\r
411                 m_index++;\r
412         }\r
413         const auto& di = m_pView->GetDiffItem(m_nIndex);\r
414         // If the current item is a folder, ignore the current item if the next selected item is a child element of that folder.\r
415         if (m_index > (((di.diffcode.diffcode & DIFFCODE::THREEWAY) == 0) ? 1 : 2) || !di.diffcode.isDirectory())\r
416                 return di;\r
417         const int nextIndex = pView(m_pView)->GetNextItem(m_nIndex, m_nFlags & nMask);\r
418         if (nextIndex == -1)\r
419                 return di;\r
420         const auto& diNext = m_pView->GetDiffItem(nextIndex);\r
421         const String curRelPath = strutils::makelower(di.diffFileInfo[m_index].GetFile());\r
422         if (strutils::makelower(diNext.diffFileInfo[m_index].GetFile()).find(curRelPath) != 0)\r
423                 return di;\r
424         return *DIFFITEM::GetEmptyItem();\r
425 }\r
426 \r
427 /**\r
428  * @brief Pass information about an item to Merge7z.\r
429  *\r
430  * Information is passed through struct Merge7z::DirItemEnumerator::Item.\r
431  * The *mask* member denotes which of the other members contain valid data.\r
432  * If *mask* is zero upon return, which will be the case if Enum() decides to\r
433  * leave the struct untouched, Merge7z will ignore the item.\r
434  * If Enum() allocates temporary storage for string members, it must also\r
435  * allocate an Envelope, providing a Free() method to free the temporary\r
436  * storage, along with the Envelope itself. The Envelope pointer is passed to\r
437  * Merge7z as the return value of the function. It is not meant to be a success\r
438  * indicator, so if no temporary storage is required, it is perfectly alright\r
439  * to return `nullptr`.\r
440  */\r
441 Merge7z::Envelope *DirItemEnumerator::Enum(Item &item)\r
442 {\r
443         const CDiffContext& ctxt = m_pView->GetDiffContext();\r
444         const DIFFITEM &di = Next();\r
445 \r
446         if (di.isEmpty() || ((m_nFlags & DiffsOnly) && !IsItemNavigableDiff(ctxt, di)))\r
447         {\r
448                 return 0;\r
449         }\r
450 \r
451         bool isSideOnly = !di.diffcode.exists(m_index);\r
452 \r
453         Envelope *envelope = new Envelope;\r
454 \r
455         const String &sFilename = di.diffFileInfo[m_index].filename;\r
456         const String &sSubdir = di.diffFileInfo[m_index].path;\r
457         if (sSubdir.length())\r
458                 envelope->Name = paths::ConcatPath(sSubdir, sFilename);\r
459         else\r
460                 envelope->Name = sFilename;\r
461         envelope->FullPath = paths::ConcatPath(\r
462                         di.getFilepath(m_index, ctxt.GetNormalizedPath(m_index)),\r
463                         sFilename);\r
464 \r
465         UINT32 Recurse = item.Mask.Recurse;\r
466 \r
467         if (m_nFlags & BalanceFolders)\r
468         {\r
469                 // Enumerating items on right side\r
470                 if (isSideOnly)\r
471                 {\r
472                         // Item is missing on right side\r
473                         PVOID &implied = m_rgImpliedFolders[m_index][di.diffFileInfo[1-m_index].path.get()];\r
474                         if (implied == nullptr)\r
475                         {\r
476                                 // Folder is not implied by some other file, and has\r
477                                 // not been enumerated so far, so enumerate it now!\r
478                                 envelope->Name = di.diffFileInfo[1-m_index].path;\r
479                                 envelope->FullPath = di.getFilepath(1-m_index, ctxt.GetNormalizedPath(1-m_index));\r
480                                 implied = PVOID(2); // Don't enumerate same folder twice!\r
481                                 isSideOnly = false;\r
482                                 Recurse = 0;\r
483                         }\r
484                 }\r
485         }\r
486 \r
487         if (isSideOnly)\r
488         {\r
489                 return envelope;\r
490         }\r
491 \r
492         if (m_strFolderPrefix.length())\r
493         {\r
494                 if (envelope->Name.length())\r
495                         envelope->Name.insert(0, _T("\\"));\r
496                 envelope->Name.insert(0, m_strFolderPrefix);\r
497         }\r
498 \r
499         item.Mask.Item = item.Mask.Name|item.Mask.FullPath|item.Mask.CheckIfPresent|Recurse;\r
500         item.Name = envelope->Name.c_str();\r
501         item.FullPath = envelope->FullPath.c_str();\r
502         return envelope;\r
503 }\r
504 \r
505 /**\r
506  * @brief Apply appropriate handlers from left to right.\r
507  */\r
508 bool DirItemEnumerator::MultiStepCompressArchive(const tchar_t* path)\r
509 {\r
510         DeleteFile(path);\r
511         Merge7z::Format *piHandler = ArchiveGuessFormat(path);\r
512         if (piHandler != nullptr)\r
513         {\r
514                 HWND hwndOwner = CWnd::GetSafeOwner()->GetSafeHwnd();\r
515                 CString pathIntermediate;\r
516                 SysFreeString(Assign(pathIntermediate, piHandler->GetDefaultName(hwndOwner, path)));\r
517                 String pathPrepend = path;\r
518                 pathPrepend.resize(pathPrepend.rfind('\\') + 1);\r
519                 pathIntermediate.Insert(0, pathPrepend.c_str());\r
520                 bool bDone = MultiStepCompressArchive(pathIntermediate);\r
521                 if (bDone)\r
522                 {\r
523                         SingleItemEnumerator tmpEnumerator(path, pathIntermediate);\r
524                         piHandler->CompressArchive(hwndOwner, path, &tmpEnumerator);\r
525                         DeleteFile(pathIntermediate);\r
526                 }\r
527                 else\r
528                 {\r
529                         piHandler->CompressArchive(hwndOwner, path, this);\r
530                 }\r
531                 return true;\r
532         }\r
533         return false;\r
534 }\r
535 \r
536 /**\r
537  * @brief Generate archive from DirView items.\r
538  */\r
539 void DirItemEnumerator::CompressArchive(const tchar_t* path)\r
540 {\r
541         String strPath;\r
542         if (path == nullptr)\r
543         {\r
544                 // 7z311 can only write 7z, zip, and tar(.gz|.bz2) archives, so don't\r
545                 // offer other formats here!\r
546                 static const tchar_t _T_Filter[]\r
547                 (\r
548                         _T("7z|*.7z|")\r
549                         //_T("z|*.z|")\r
550                         _T("zip|*.zip|")\r
551                         _T("jar (zip)|*.jar|")\r
552                         _T("ear (zip)|*.ear|")\r
553                         _T("war (zip)|*.war|")\r
554                         _T("xpi (zip)|*.xpi|")\r
555                         //_T("rar|*.rar|")\r
556                         _T("tar|*.tar|")\r
557                         _T("tar.z|*.tar.z|")\r
558                         _T("tar.gz|*.tar.gz|")\r
559                         _T("tar.bz2|*.tar.bz2|")\r
560                         //_T("tz|*.tz|")\r
561                         _T("tgz|*.tgz|")\r
562                         _T("tbz2|*.tbz2|")\r
563                         //_T("lzh|*.lzh|")\r
564                         //_T("cab|*.cab|")\r
565                         //_T("arj|*.arj|")\r
566                         //_T("deb|*.deb|")\r
567                         //_T("rpm|*.rpm|")\r
568                         //_T("cpio|*.cpio|")\r
569                         //_T("|")\r
570                 );\r
571                 String strFilter; // = CExternalArchiveFormat::GetOpenFileFilterString();\r
572                 strFilter.insert(0, _T_Filter);\r
573                 strFilter += _T("|");\r
574                 CFileDialog dlg\r
575                 (\r
576                         FALSE,\r
577                         0,\r
578                         0,\r
579                         OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_NOREADONLYRETURN,\r
580                         strFilter.c_str()\r
581                 );\r
582                 dlg.m_ofn.nFilterIndex = GetOptionsMgr()->GetInt(OPT_ARCHIVE_FILTER_INDEX);\r
583                 // Use extension from current filter as default extension:\r
584                 if (int i = dlg.m_ofn.nFilterIndex)\r
585                 {\r
586                         dlg.m_ofn.lpstrDefExt = dlg.m_ofn.lpstrFilter;\r
587                         while (*dlg.m_ofn.lpstrDefExt && --i)\r
588                         {\r
589                                 dlg.m_ofn.lpstrDefExt += lstrlen(dlg.m_ofn.lpstrDefExt) + 1;\r
590                                 dlg.m_ofn.lpstrDefExt += lstrlen(dlg.m_ofn.lpstrDefExt) + 1;\r
591                         }\r
592                         if (*dlg.m_ofn.lpstrDefExt)\r
593                         {\r
594                                 dlg.m_ofn.lpstrDefExt += lstrlen(dlg.m_ofn.lpstrDefExt) + 3;\r
595                         }\r
596                 }\r
597                 if (dlg.DoModal() == IDOK)\r
598                 {\r
599                         strPath = dlg.GetPathName();\r
600                         path = strPath.c_str();\r
601                         GetOptionsMgr()->SaveOption(OPT_ARCHIVE_FILTER_INDEX, static_cast<int>(dlg.m_ofn.nFilterIndex));\r
602                 }\r
603         }\r
604         if (path && !MultiStepCompressArchive(path))\r
605         {\r
606                 LangMessageBox(IDS_UNKNOWN_ARCHIVE_FORMAT, MB_ICONEXCLAMATION);\r
607         }\r
608 }\r
609 \r
610 \r
611 DecompressResult DecompressArchive(HWND hWnd, const PathContext& files)\r
612 {\r
613         DecompressResult res(files, nullptr, paths::IS_EXISTING_DIR);\r
614         try\r
615         {\r
616                 HRESULT hr;\r
617                 String path;\r
618                 // Handle archives using 7-zip\r
619                 Merge7z::Format *piHandler;\r
620                 piHandler = ArchiveGuessFormat(res.files[0]);\r
621                 if (piHandler  != nullptr)\r
622                 {\r
623                         res.pTempPathContext = new CTempPathContext;\r
624                         path = env::GetTempChildPath();\r
625                         for (int index = 0; index < res.files.GetSize(); index++)\r
626                                 res.pTempPathContext->m_strDisplayRoot[index] = res.files[index];\r
627                         if (res.files.GetSize() == 2 && res.files[0] == res.files[1])\r
628                                 res.files[1].erase();\r
629                         do\r
630                         {\r
631                                 hr = piHandler->DeCompressArchive(hWnd, res.files[0].c_str(), path.c_str());\r
632                                 if (FAILED(hr))\r
633                                 {\r
634                                         res.hr = hr;\r
635                                         break;\r
636                                 }\r
637                                 if (res.files[0].find(path) == 0)\r
638                                 {\r
639                                         VERIFY(::DeleteFile(res.files[0].c_str()) || (LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), res.files[0])), false));\r
640                                 }\r
641                                 BSTR pTmp = piHandler->GetDefaultName(hWnd, res.files[0].c_str());\r
642                                 res.files[0] = ucr::toTString(pTmp);\r
643                                 SysFreeString(pTmp);\r
644                                 res.files[0].insert(0, _T("\\"));\r
645                                 res.files[0].insert(0, path);\r
646                                 piHandler = ArchiveGuessFormat(res.files[0]);\r
647                         } while (piHandler != nullptr);\r
648                         res.files[0] = path;\r
649                 }\r
650                 piHandler = res.files[1].empty() ? nullptr\r
651                                                                                  : ArchiveGuessFormat(res.files[1]);\r
652                 if (piHandler != nullptr)\r
653                 {\r
654                         if (res.pTempPathContext == nullptr)\r
655                         {\r
656                                 res.pTempPathContext = new CTempPathContext;\r
657                                 for (int index = 0; index < res.files.GetSize(); index++)\r
658                                         res.pTempPathContext->m_strDisplayRoot[index] = res.files[index];\r
659                         }\r
660                         path = env::GetTempChildPath();\r
661                         do\r
662                         {\r
663                                 hr = piHandler->DeCompressArchive(hWnd, res.files[1].c_str(), path.c_str());\r
664                                 if (FAILED(hr))\r
665                                 {\r
666                                         res.hr = hr;\r
667                                         break;\r
668                                 }\r
669                                 if (res.files[1].find(path) == 0)\r
670                                 {\r
671                                         VERIFY(::DeleteFile(res.files[1].c_str()) || (LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), res.files[1])), false));\r
672                                 }\r
673                                 BSTR pTmp = piHandler->GetDefaultName(hWnd, res.files[1].c_str());\r
674                                 res.files[1] = OLE2T(pTmp);\r
675                                 SysFreeString(pTmp);\r
676                                 res.files[1].insert(0, _T("\\"));\r
677                                 res.files[1].insert(0, path);\r
678                                 piHandler = ArchiveGuessFormat(res.files[1]);\r
679                         } while (piHandler != nullptr);\r
680                         res.files[1] = path;\r
681                 }\r
682                 piHandler = (res.files.GetSize() <= 2) ? nullptr : ArchiveGuessFormat(res.files[2]);\r
683                 if (piHandler != nullptr)\r
684                 {\r
685                         if (res.pTempPathContext == nullptr)\r
686                         {\r
687                                 res.pTempPathContext = new CTempPathContext;\r
688                                 for (int index = 0; index < res.files.GetSize(); index++)\r
689                                         res.pTempPathContext->m_strDisplayRoot[index] = res.files[index];\r
690                         }\r
691                         path = env::GetTempChildPath();\r
692                         do\r
693                         {\r
694                                 hr = piHandler->DeCompressArchive(hWnd, res.files[2].c_str(), path.c_str());\r
695                                 if (FAILED(hr))\r
696                                 {\r
697                                         res.hr = hr;\r
698                                         break;\r
699                                 }\r
700                                 if (res.files[2].find(path) == 0)\r
701                                 {\r
702                                         VERIFY(::DeleteFile(res.files[2].c_str()) || (LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), res.files[2])), false));\r
703                                 }\r
704                                 BSTR pTmp = piHandler->GetDefaultName(hWnd, res.files[2].c_str());\r
705                                 res.files[2] = OLE2T(pTmp);\r
706                                 SysFreeString(pTmp);\r
707                                 res.files[2].insert(0, _T("\\"));\r
708                                 res.files[2].insert(0, path);\r
709                                 piHandler = ArchiveGuessFormat(res.files[2]);\r
710                         } while (piHandler != nullptr);\r
711                         res.files[2] = path;\r
712                 }\r
713                 if (res.files[1].empty())\r
714                 {\r
715                         // assume Perry style patch\r
716                         res.files[1] = path;\r
717                         res.files[0] += _T("\\ORIGINAL");\r
718                         res.files[1] += _T("\\ALTERED");\r
719                         if (!PathFileExists(res.files[0].c_str()) || !PathFileExists(res.files[1].c_str()))\r
720                         {\r
721                                 // not a Perry style patch: diff with itself...\r
722                                 res.files[0] = path;\r
723                                 res.files[1] = std::move(path);\r
724                         }\r
725                         else\r
726                         {\r
727                                 res.pTempPathContext->m_strDisplayRoot[0] += _T("\\ORIGINAL");\r
728                                 res.pTempPathContext->m_strDisplayRoot[1] += _T("\\ALTERED");\r
729                         }\r
730                 }\r
731         }\r
732         catch (CException *e)\r
733         {\r
734                 res.hr = E_FAIL;\r
735                 e->Delete();\r
736         }\r
737         return res;\r
738 }\r
739 \r
740 \r