1 /* -*- Mode: C++; c-basic-offset: 2; tab-width: 8; indent-tabs-mode: nil; -*- */
2 /*****************************************************************************
4 * This 7z Library is based the 7z Client and 7z Standalone Extracting Plugin
5 * code from the LZMA SDK.
6 * It is in the public domain (see http://www.7-zip.org/sdk.html).
8 * Any copyright in these files held by contributors to the Mozilla Project is
9 * also dedicated to the Public Domain.
10 * http://creativecommons.org/licenses/publicdomain/
13 * Alex Pakhotin <alexp@mozilla.com>
15 *****************************************************************************/
17 #include "Common/MyWindows.h"
18 #include "Common/NewHandler.h"
20 #include "Common/IntToString.h"
21 #include "Common/MyInitGuid.h"
22 #include "Common/StringConvert.h"
24 #include "Windows/DLL.h"
25 #include "Windows/FileDir.h"
26 #include "Windows/FileFind.h"
27 #include "Windows/FileName.h"
28 #include "Windows/NtCheck.h"
29 #include "Windows/PropVariant.h"
30 #include "Windows/PropVariantConversions.h"
32 #include "7zip/Common/FileStreams.h"
34 #include "7zip/ICoder.h"
35 #include "7zip/Archive/IArchive.h"
37 #include "7zip/IPassword.h"
38 #include "7zip/MyVersion.h"
40 // Used for global structures initialization
41 #include "../C/7zCrc.h"
42 #include "7zip/Common/RegisterArc.h"
43 #include "7zip/Common/RegisterCodec.h"
44 #include "7zip/Archive/7z/7zHandler.h"
45 #include "7zip/Compress/Bcj2Coder.h"
46 #include "7zip/Compress/BcjCoder.h"
47 #include "7zip/Compress/CopyCoder.h"
48 #include "7zip/Compress/Lzma2Decoder.h"
49 #include "7zip/Compress/LzmaDecoder.h"
53 using namespace NWindows
;
55 STDAPI
CreateArchiver(const GUID
*classID
, const GUID
*iid
, void **outObject
);
57 DEFINE_GUID(CLSID_CArchiveHandler
,
58 0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00);
60 DEFINE_GUID(CLSID_CFormat7z
,
61 0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0x07, 0x00, 0x00);
63 // Global static structures copied here from *Register.cpp files
64 // Static global variable defined in a module didn't work when used in a library
67 static IInArchive
*CreateArc() { return new NArchive::N7z::CHandler
; }
68 static CArcInfo g_ArcInfo
=
69 { L
"7z", L
"7z", 0, 7, {'7', 'z', 0xBC, 0xAF, 0x27, 0x1C}, 6, false, CreateArc
, NULL
};
72 static void *CreateCodecBCJ2() { return (void *)(ICompressCoder2
*)(new NCompress::NBcj2::CDecoder()); }
74 static CCodecInfo g_CodecInfoBCJ2
=
75 { CreateCodecBCJ2
, NULL
, 0x0303011B, L
"BCJ2", 4, false };
78 static void *CreateCodecBCJ() { return (void *)(ICompressFilter
*)(new CBCJ_x86_Decoder()); }
80 static CCodecInfo g_CodecInfoBCJ
=
81 { CreateCodecBCJ
, NULL
, 0x03030103, L
"BCJ", 1, true };
84 static void *CreateCodecCopy() { return (void *)(ICompressCoder
*)(new NCompress::CCopyCoder
); }
86 static CCodecInfo g_CodecInfoCopy
=
87 { CreateCodecCopy
, CreateCodecCopy
, 0x00, L
"Copy", 1, false };
90 static void *CreateCodecLZMA2() { return (void *)(ICompressCoder
*)(new NCompress::NLzma2::CDecoder
); }
92 static CCodecInfo g_CodecInfoLZMA2
=
93 { CreateCodecLZMA2
, NULL
, 0x21, L
"LZMA2", 1, false };
96 static void *CreateCodecLZMA() { return (void *)(ICompressCoder
*)(new NCompress::NLzma::CDecoder
); }
98 static CCodecInfo g_CodecInfoLZMA
=
99 { CreateCodecLZMA
, NULL
, 0x030101, L
"LZMA", 1, false };
101 // Initialize all global structures
102 static void Initialize7z()
104 static bool bInitialized
= false;
111 RegisterArc(&g_ArcInfo
);
113 RegisterCodec(&g_CodecInfoBCJ2
);
114 RegisterCodec(&g_CodecInfoBCJ
);
115 RegisterCodec(&g_CodecInfoCopy
);
116 RegisterCodec(&g_CodecInfoLZMA2
);
117 RegisterCodec(&g_CodecInfoLZMA
);
125 void PrintString(const UString
&s
)
127 printf("%s", (LPCSTR
)GetOemString(s
));
130 void PrintString(const AString
&s
)
132 printf("%s", (LPCSTR
)s
);
140 void PrintStringLn(const AString
&s
)
146 void PrintError(const AString
&s
)
154 #define PrintString(s)
155 #define PrintString(s)
156 #define PrintNewLine()
157 #define PrintStringLn(s)
159 static UString g_sError
;
161 void PrintError(const AString
&s
)
163 g_sError
+= GetUnicodeString(s
) + L
"\n";
166 const wchar_t* GetExtractorError()
168 return (const wchar_t*)g_sError
;
173 static HRESULT
IsArchiveItemProp(IInArchive
*archive
, UInt32 index
, PROPID propID
, bool &result
)
175 NCOM::CPropVariant prop
;
176 RINOK(archive
->GetProperty(index
, propID
, &prop
));
177 if (prop
.vt
== VT_BOOL
)
178 result
= VARIANT_BOOLToBool(prop
.boolVal
);
179 else if (prop
.vt
== VT_EMPTY
)
186 static HRESULT
IsArchiveItemFolder(IInArchive
*archive
, UInt32 index
, bool &result
)
188 return IsArchiveItemProp(archive
, index
, kpidIsDir
, result
);
192 static const wchar_t *kEmptyFileAlias
= L
"[Content]";
195 //////////////////////////////////////////////////////////////
196 // Archive Open callback class
198 class CArchiveOpenCallback
:
199 public IArchiveOpenCallback
,
200 public ICryptoGetTextPassword
,
204 MY_UNKNOWN_IMP1(ICryptoGetTextPassword
)
206 STDMETHOD(SetTotal
)(const UInt64
*files
, const UInt64
*bytes
);
207 STDMETHOD(SetCompleted
)(const UInt64
*files
, const UInt64
*bytes
);
209 STDMETHOD(CryptoGetTextPassword
)(BSTR
*password
);
211 bool PasswordIsDefined
;
214 CArchiveOpenCallback() : PasswordIsDefined(false) {}
217 STDMETHODIMP
CArchiveOpenCallback::SetTotal(const UInt64
* /* files */, const UInt64
* /* bytes */)
222 STDMETHODIMP
CArchiveOpenCallback::SetCompleted(const UInt64
* /* files */, const UInt64
* /* bytes */)
227 STDMETHODIMP
CArchiveOpenCallback::CryptoGetTextPassword(BSTR
*password
)
229 if (!PasswordIsDefined
)
231 // You can ask real password here from user
232 // Password = GetPassword(OutStream);
233 // PasswordIsDefined = true;
234 PrintError("Password is not defined");
237 return StringToBstr(Password
, password
);
241 //////////////////////////////////////////////////////////////
242 // Archive Extracting callback class
244 static const wchar_t *kCantDeleteOutputFile
= L
"ERROR: Can not delete output file ";
246 static const char *kTestingString
= "Testing ";
247 static const char *kExtractingString
= "Extracting ";
248 static const char *kSkippingString
= "Skipping ";
250 static const char *kUnsupportedMethod
= "Unsupported Method";
251 static const char *kCRCFailed
= "CRC Failed";
252 static const char *kDataError
= "Data Error";
253 static const char *kUnknownError
= "Unknown Error";
255 class CArchiveExtractCallback
:
256 public IArchiveExtractCallback
,
257 public ICryptoGetTextPassword
,
261 MY_UNKNOWN_IMP1(ICryptoGetTextPassword
)
264 STDMETHOD(SetTotal
)(UInt64 size
);
265 STDMETHOD(SetCompleted
)(const UInt64
*completeValue
);
267 // IArchiveExtractCallback
268 STDMETHOD(GetStream
)(UInt32 index
, ISequentialOutStream
**outStream
, Int32 askExtractMode
);
269 STDMETHOD(PrepareOperation
)(Int32 askExtractMode
);
270 STDMETHOD(SetOperationResult
)(Int32 resultEOperationResult
);
272 // ICryptoGetTextPassword
273 STDMETHOD(CryptoGetTextPassword
)(BSTR
*aPassword
);
276 CMyComPtr
<IInArchive
> _archiveHandler
;
277 UString _directoryPath
; // Output directory
278 UString _filePath
; // name inside archive
279 UString _diskFilePath
; // full path to file on disk
281 struct CProcessedFileInfo
288 } _processedFileInfo
;
290 COutFileStream
*_outFileStreamSpec
;
291 CMyComPtr
<ISequentialOutStream
> _outFileStream
;
293 SzExtractProgressCallback
*_progressCallback
;
294 UInt32 _numItemsTotal
;
295 UInt32 _numItemsExtracted
;
298 void Init(IInArchive
*archiveHandler
, const UString
&directoryPath
, SzExtractProgressCallback
*progressCallback
);
301 bool PasswordIsDefined
;
304 CArchiveExtractCallback() : PasswordIsDefined(false) {}
307 void CArchiveExtractCallback::Init(IInArchive
*archiveHandler
, const UString
&directoryPath
, SzExtractProgressCallback
*progressCallback
)
310 _archiveHandler
= archiveHandler
;
311 _directoryPath
= directoryPath
;
312 NFile::NName::NormalizeDirPathPrefix(_directoryPath
);
313 _progressCallback
= progressCallback
;
315 _numItemsExtracted
= 0;
316 archiveHandler
->GetNumberOfItems(&_numItemsTotal
);
319 // SetTotal and SetCompleted callback methods show progress
320 // based on the input buffer, which does not really correspond
321 // to the actual extraction progress.
322 // Current implementation uses number of files as the progress indicator,
323 // which gives better result with an archive containing a lot of files.
324 STDMETHODIMP
CArchiveExtractCallback::SetTotal(UInt64
/* size */)
328 ConvertUInt64ToString(size, s);
329 PrintString(AString("\n--- Total: "));
335 STDMETHODIMP
CArchiveExtractCallback::SetCompleted(const UInt64
* /* completeValue */)
339 ConvertUInt64ToString(*completeValue, s);
340 PrintString(AString("\n--- Completed: "));
346 STDMETHODIMP
CArchiveExtractCallback::GetStream(UInt32 index
,
347 ISequentialOutStream
**outStream
, Int32 askExtractMode
)
350 _outFileStream
.Release();
354 NCOM::CPropVariant prop
;
355 RINOK(_archiveHandler
->GetProperty(index
, kpidPath
, &prop
));
358 if (prop
.vt
== VT_EMPTY
)
359 fullPath
= kEmptyFileAlias
;
362 if (prop
.vt
!= VT_BSTR
)
364 fullPath
= prop
.bstrVal
;
366 _filePath
= fullPath
;
369 if (askExtractMode
!= NArchive::NExtract::NAskMode::kExtract
)
374 NCOM::CPropVariant prop
;
375 RINOK(_archiveHandler
->GetProperty(index
, kpidAttrib
, &prop
));
376 if (prop
.vt
== VT_EMPTY
)
378 _processedFileInfo
.Attrib
= 0;
379 _processedFileInfo
.AttribDefined
= false;
383 if (prop
.vt
!= VT_UI4
)
385 _processedFileInfo
.Attrib
= prop
.ulVal
;
386 _processedFileInfo
.AttribDefined
= true;
390 RINOK(IsArchiveItemFolder(_archiveHandler
, index
, _processedFileInfo
.isDir
));
394 NCOM::CPropVariant prop
;
395 RINOK(_archiveHandler
->GetProperty(index
, kpidMTime
, &prop
));
396 _processedFileInfo
.MTimeDefined
= false;
400 // _processedFileInfo.MTime = _utcMTimeDefault;
403 _processedFileInfo
.MTime
= prop
.filetime
;
404 _processedFileInfo
.MTimeDefined
= true;
414 NCOM::CPropVariant prop;
415 RINOK(_archiveHandler->GetProperty(index, kpidSize, &prop));
416 bool newFileSizeDefined = (prop.vt != VT_EMPTY);
418 if (newFileSizeDefined)
419 newFileSize = ConvertPropVariantToUInt64(prop);
424 // Create folders for file
425 int slashPos
= _filePath
.ReverseFind(WCHAR_PATH_SEPARATOR
);
427 NFile::NDirectory::CreateComplexDirectory(_directoryPath
+ _filePath
.Left(slashPos
));
430 UString fullProcessedPath
= _directoryPath
+ _filePath
;
431 _diskFilePath
= fullProcessedPath
;
433 if (_processedFileInfo
.isDir
)
435 NFile::NDirectory::CreateComplexDirectory(fullProcessedPath
);
439 NFile::NFind::CFileInfoW fi
;
440 if (fi
.Find(fullProcessedPath
))
442 if (!NFile::NDirectory::DeleteFileAlways(fullProcessedPath
))
444 PrintString(UString(kCantDeleteOutputFile
) + fullProcessedPath
);
449 _outFileStreamSpec
= new COutFileStream
;
450 CMyComPtr
<ISequentialOutStream
> outStreamLoc(_outFileStreamSpec
);
451 if (!_outFileStreamSpec
->Open(fullProcessedPath
, CREATE_ALWAYS
))
453 PrintString((UString
)L
"can not open output file " + fullProcessedPath
);
456 _outFileStream
= outStreamLoc
;
457 *outStream
= outStreamLoc
.Detach();
462 STDMETHODIMP
CArchiveExtractCallback::PrepareOperation(Int32 askExtractMode
)
464 _extractMode
= false;
465 switch (askExtractMode
)
467 case NArchive::NExtract::NAskMode::kExtract
: _extractMode
= true; break;
469 switch (askExtractMode
)
471 case NArchive::NExtract::NAskMode::kExtract
: PrintString(kExtractingString
); break;
472 case NArchive::NExtract::NAskMode::kTest
: PrintString(kTestingString
); break;
473 case NArchive::NExtract::NAskMode::kSkip
: PrintString(kSkippingString
); break;
475 PrintString(_filePath
);
476 _numItemsExtracted
++;
477 if (_progressCallback
)
479 _progressCallback(_numItemsExtracted
* 100 / _numItemsTotal
);
484 STDMETHODIMP
CArchiveExtractCallback::SetOperationResult(Int32 operationResult
)
486 switch(operationResult
)
488 case NArchive::NExtract::NOperationResult::kOK
:
494 switch(operationResult
)
496 case NArchive::NExtract::NOperationResult::kUnSupportedMethod
:
497 PrintString(kUnsupportedMethod
);
499 case NArchive::NExtract::NOperationResult::kCRCError
:
500 PrintString(kCRCFailed
);
502 case NArchive::NExtract::NOperationResult::kDataError
:
503 PrintString(kDataError
);
506 PrintString(kUnknownError
);
511 if (_outFileStream
!= NULL
)
513 if (_processedFileInfo
.MTimeDefined
)
514 _outFileStreamSpec
->SetMTime(&_processedFileInfo
.MTime
);
515 RINOK(_outFileStreamSpec
->Close());
517 _outFileStream
.Release();
518 if (_extractMode
&& _processedFileInfo
.AttribDefined
)
519 NFile::NDirectory::MySetFileAttributes(_diskFilePath
, _processedFileInfo
.Attrib
);
525 STDMETHODIMP
CArchiveExtractCallback::CryptoGetTextPassword(BSTR
*password
)
527 if (!PasswordIsDefined
)
529 // You can ask real password here from user
530 // Password = GetPassword(OutStream);
531 // PasswordIsDefined = true;
532 PrintError("Password is not defined");
535 return StringToBstr(Password
, password
);
538 static WRes
MyCreateDir(const WCHAR
*name
)
540 return CreateDirectoryW(name
, NULL
) ? 0 : GetLastError();
543 static WRes
CreateOutputDir(const WCHAR
*outputDir
)
546 WCHAR name
[MAX_PATH
];
548 if (outputDir
== NULL
|| outputDir
[0] == 0)
549 return SZ_ERROR_PARAM
;
550 wcsncpy(name
, outputDir
, MAX_PATH
-1);
551 name
[MAX_PATH
-1] = 0;
553 for (j
= 1; name
[j
] != 0 && res
== 0; j
++)
555 if (name
[j
] == CHAR_PATH_SEPARATOR
)
558 res
= MyCreateDir(name
);
559 name
[j
] = CHAR_PATH_SEPARATOR
;
562 if (res
== 0 && name
[wcslen(name
) - 1] != CHAR_PATH_SEPARATOR
)
564 res
= MyCreateDir(name
);
569 //////////////////////////////////////////////////////////////////////////
570 // Main extract functions
575 * @param archiveFileName Name of the archive
576 * @param fileToExtract Name of the file to extract (if NULL - extract all files)
577 * @param outputDir Output directory for extracted files
578 * @param progressCallback Function to be called on each file - can show the progress
580 int SzExtract(const WCHAR
*archiveName
,
581 const WCHAR
*fileToExtract
, const WCHAR
*outputDir
,
582 SzExtractProgressCallback
*progressCallback
)
584 return SzExtractSfx(archiveName
, 0, fileToExtract
, outputDir
, progressCallback
);
588 * Extract 7z-SFX-archive
590 * @param archiveFileName Name of the archive
591 * @param sfxStubSize Size of the stub at the beginning of the file before the actual archive data (could be 0)
592 * @param fileToExtract Name of the file to extract (if NULL - extract all files)
593 * @param outputDir Output directory for extracted files
594 * @param progressCallback Function to be called on each file to show the progress
596 int SzExtractSfx(const WCHAR
*archiveName
, DWORD sfxStubSize
,
597 const WCHAR
*fileToExtract
, const WCHAR
*outputDir
,
598 SzExtractProgressCallback
*progressCallback
)
602 CreateOutputDir(outputDir
);
605 PrintString("Loading archive ");
606 PrintString(archiveName
);
612 CMyComPtr
<IInArchive
> archive
;
613 if (CreateArchiver(&CLSID_CFormat7z
, &IID_IInArchive
, (void **)&archive
) != S_OK
)
615 PrintError("Can not get class object");
616 return SZ_ERROR_FAIL
;
620 PrintStringLn("Created archiver");
623 CInFileStream
*fileSpec
= new CInFileStream
;
624 CMyComPtr
<IInStream
> file
= fileSpec
;
626 if (!fileSpec
->Open(archiveName
))
628 PrintError("Can not open archive file");
629 return SZ_ERROR_NO_ARCHIVE
;
632 file
->Seek(sfxStubSize
, STREAM_SEEK_SET
, NULL
);
635 PrintStringLn("Opened file");
639 CArchiveOpenCallback
*openCallbackSpec
= new CArchiveOpenCallback
;
640 CMyComPtr
<IArchiveOpenCallback
> openCallback(openCallbackSpec
);
641 openCallbackSpec
->PasswordIsDefined
= false;
642 // openCallbackSpec->PasswordIsDefined = true;
643 // openCallbackSpec->Password = L"1";
645 if (archive
->Open(file
, 0, openCallback
) != S_OK
)
647 PrintError("Can not open archive");
648 return SZ_ERROR_NO_ARCHIVE
;
653 PrintStringLn("Extracting...");
656 CArchiveExtractCallback
*extractCallbackSpec
= new CArchiveExtractCallback
;
657 CMyComPtr
<IArchiveExtractCallback
> extractCallback(extractCallbackSpec
);
658 extractCallbackSpec
->Init(archive
, outputDir
, progressCallback
);
659 extractCallbackSpec
->PasswordIsDefined
= false;
660 // extractCallbackSpec->PasswordIsDefined = true;
661 // extractCallbackSpec->Password = L"1";
663 HRESULT result
= S_OK
;
665 archive
->GetNumberOfItems(&numItems
);
668 PrintError("No files found in the archive");
669 return SZ_ERROR_DATA
;
674 for (UInt32 i
= 0; i
< numItems
; i
++)
677 NWindows::NCOM::CPropVariant prop
;
678 archive
->GetProperty(i
, kpidPath
, &prop
);
679 UString s
= ConvertPropVariantToString(prop
);
680 if (wcscmp(fileToExtract
, s
) == 0)
684 // Extract the current file
685 result
= archive
->Extract(&i
, 1, false, extractCallback
);
693 result
= archive
->Extract(NULL
, (UInt32
)(Int32
)(-1), false, extractCallback
);
697 PrintError("Extract Error");
698 return SZ_ERROR_DATA
;
705 * Get information about 7z-SFX-archive
707 * @param archiveFileName Name of the archive
708 * @param sfxStubSize Size of the stub at the beginning of the file before the actual archive data (could be 0)
711 * @param pUncompressedSize Pointer to 64 bit integer for the total uncompressed size
712 * @param pNumberOfFiles (optional) Pointer to a number of files in the archive
713 * @param pNumberOfDirs (optional) Pointer to a number of directories in the archive
715 int SzGetSfxArchiveInfo(const WCHAR
*archiveName
, const DWORD sfxStubSize
,
716 ULONGLONG
*pUncompressedSize
, DWORD
*pNumberOfFiles
, DWORD
*pNumberOfDirs
)
718 if (!archiveName
|| !pUncompressedSize
)
719 return SZ_ERROR_PARAM
;
721 *pUncompressedSize
= 0;
725 CMyComPtr
<IInArchive
> archive
;
726 if (CreateArchiver(&CLSID_CFormat7z
, &IID_IInArchive
, (void **)&archive
) != S_OK
)
728 PrintError("Can not get class object");
729 return SZ_ERROR_FAIL
;
732 CInFileStream
*fileSpec
= new CInFileStream
;
733 CMyComPtr
<IInStream
> file
= fileSpec
;
735 if (!fileSpec
->Open(archiveName
))
737 PrintError("Can not open archive file");
738 return SZ_ERROR_NO_ARCHIVE
;
741 file
->Seek(sfxStubSize
, STREAM_SEEK_SET
, NULL
);
743 CArchiveOpenCallback
*openCallbackSpec
= new CArchiveOpenCallback
;
744 CMyComPtr
<IArchiveOpenCallback
> openCallback(openCallbackSpec
);
745 openCallbackSpec
->PasswordIsDefined
= false;
747 if (archive
->Open(file
, 0, openCallback
) != S_OK
)
749 PrintError("Can not open archive");
750 return SZ_ERROR_NO_ARCHIVE
;
754 archive
->GetNumberOfItems(&numItems
);
757 PrintError("No files found in the archive");
758 return SZ_ERROR_DATA
;
767 // Iterate through all items
768 for (UInt32 i
= 0; i
< numItems
; i
++)
771 RINOK(IsArchiveItemFolder(archive
, i
, isDir
));
779 UInt64 unpackSize
= 0;
781 NWindows::NCOM::CPropVariant prop
;
782 if (archive
->GetProperty(i
, kpidSize
, &prop
) != S_OK
)
784 PrintError("Cannot get size property value");
785 return SZ_ERROR_DATA
;
787 if (prop
.vt
!= VT_EMPTY
)
788 unpackSize
= ConvertPropVariantToUInt64(prop
);
790 (*pUncompressedSize
) += unpackSize
;