// Client7z.cpp #include "StdAfx.h" #include <stdio.h> #include "../../../Common/Defs.h" #include "../../../Common/MyInitGuid.h" #include "../../../Common/IntToString.h" #include "../../../Common/StringConvert.h" #include "../../../Windows/DLL.h" #include "../../../Windows/FileDir.h" #include "../../../Windows/FileFind.h" #include "../../../Windows/FileName.h" #include "../../../Windows/NtCheck.h" #include "../../../Windows/PropVariant.h" #include "../../../Windows/PropVariantConv.h" #include "../../Common/FileStreams.h" #include "../../Archive/IArchive.h" #include "../../IPassword.h" #include "../../../../C/7zVersion.h" #ifdef _WIN32 HINSTANCE g_hInstance = 0; #endif // Tou can find the list of all GUIDs in Guid.txt file. // use another CLSIDs, if you want to support other formats (zip, rar, ...). // {23170F69-40C1-278A-1000-000110070000} DEFINE_GUID(CLSID_CFormat7z, 0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0x07, 0x00, 0x00); using namespace NWindows; using namespace NFile; using namespace NDir; #define kDllName "7z.dll" static const char *kCopyrightString = "\n7-Zip " MY_VERSION " (" kDllName " client) " MY_COPYRIGHT " " MY_DATE "\n"; static const char *kHelpString = "Usage: Client7z.exe [a | l | x ] archive.7z [fileName ...]\n" "Examples:\n" " Client7z.exe a archive.7z f1.txt f2.txt : compress two files to archive.7z\n" " Client7z.exe l archive.7z : List contents of archive.7z\n" " Client7z.exe x archive.7z : eXtract files from archive.7z\n"; static AString FStringToConsoleString(const FString &s) { return GetOemString(fs2us(s)); } static FString CmdStringToFString(const char *s) { return us2fs(GetUnicodeString(s)); } static void PrintString(const UString &s) { printf("%s", (LPCSTR)GetOemString(s)); } static void PrintString(const AString &s) { printf("%s", (LPCSTR)s); } static void PrintNewLine() { PrintString("\n"); } static void PrintStringLn(const AString &s) { PrintString(s); PrintNewLine(); } static void PrintError(const char *message, const FString &name) { printf("Error: %s", (LPCSTR)message); PrintNewLine(); PrintString(FStringToConsoleString(name)); PrintNewLine(); } static void PrintError(const AString &s) { PrintNewLine(); PrintString(s); PrintNewLine(); } static HRESULT IsArchiveItemProp(IInArchive *archive, UInt32 index, PROPID propID, bool &result) { NCOM::CPropVariant prop; RINOK(archive->GetProperty(index, propID, &prop)); if (prop.vt == VT_BOOL) result = VARIANT_BOOLToBool(prop.boolVal); else if (prop.vt == VT_EMPTY) result = false; else return E_FAIL; return S_OK; } static HRESULT IsArchiveItemFolder(IInArchive *archive, UInt32 index, bool &result) { return IsArchiveItemProp(archive, index, kpidIsDir, result); } static const wchar_t *kEmptyFileAlias = L"[Content]"; ////////////////////////////////////////////////////////////// // Archive Open callback class class CArchiveOpenCallback: public IArchiveOpenCallback, public ICryptoGetTextPassword, public CMyUnknownImp { public: MY_UNKNOWN_IMP1(ICryptoGetTextPassword) STDMETHOD(SetTotal)(const UInt64 *files, const UInt64 *bytes); STDMETHOD(SetCompleted)(const UInt64 *files, const UInt64 *bytes); STDMETHOD(CryptoGetTextPassword)(BSTR *password); bool PasswordIsDefined; UString Password; CArchiveOpenCallback() : PasswordIsDefined(false) {} }; STDMETHODIMP CArchiveOpenCallback::SetTotal(const UInt64 * /* files */, const UInt64 * /* bytes */) { return S_OK; } STDMETHODIMP CArchiveOpenCallback::SetCompleted(const UInt64 * /* files */, const UInt64 * /* bytes */) { return S_OK; } STDMETHODIMP CArchiveOpenCallback::CryptoGetTextPassword(BSTR *password) { if (!PasswordIsDefined) { // You can ask real password here from user // Password = GetPassword(OutStream); // PasswordIsDefined = true; PrintError("Password is not defined"); return E_ABORT; } return StringToBstr(Password, password); } ////////////////////////////////////////////////////////////// // Archive Extracting callback class static const char *kTestingString = "Testing "; static const char *kExtractingString = "Extracting "; static const char *kSkippingString = "Skipping "; static const char *kUnsupportedMethod = "Unsupported Method"; static const char *kCRCFailed = "CRC Failed"; static const char *kDataError = "Data Error"; static const char *kUnavailableData = "Unavailable data"; static const char *kUnexpectedEnd = "Unexpected end of data"; static const char *kDataAfterEnd = "There are some data after the end of the payload data"; static const char *kIsNotArc = "Is not archive"; static const char *kHeadersError = "Headers Error"; class CArchiveExtractCallback: public IArchiveExtractCallback, public ICryptoGetTextPassword, public CMyUnknownImp { public: MY_UNKNOWN_IMP1(ICryptoGetTextPassword) // IProgress STDMETHOD(SetTotal)(UInt64 size); STDMETHOD(SetCompleted)(const UInt64 *completeValue); // IArchiveExtractCallback STDMETHOD(GetStream)(UInt32 index, ISequentialOutStream **outStream, Int32 askExtractMode); STDMETHOD(PrepareOperation)(Int32 askExtractMode); STDMETHOD(SetOperationResult)(Int32 resultEOperationResult); // ICryptoGetTextPassword STDMETHOD(CryptoGetTextPassword)(BSTR *aPassword); private: CMyComPtr<IInArchive> _archiveHandler; FString _directoryPath; // Output directory UString _filePath; // name inside arcvhive FString _diskFilePath; // full path to file on disk bool _extractMode; struct CProcessedFileInfo { FILETIME MTime; UInt32 Attrib; bool isDir; bool AttribDefined; bool MTimeDefined; } _processedFileInfo; COutFileStream *_outFileStreamSpec; CMyComPtr<ISequentialOutStream> _outFileStream; public: void Init(IInArchive *archiveHandler, const FString &directoryPath); UInt64 NumErrors; bool PasswordIsDefined; UString Password; CArchiveExtractCallback() : PasswordIsDefined(false) {} }; void CArchiveExtractCallback::Init(IInArchive *archiveHandler, const FString &directoryPath) { NumErrors = 0; _archiveHandler = archiveHandler; _directoryPath = directoryPath; NName::NormalizeDirPathPrefix(_directoryPath); } STDMETHODIMP CArchiveExtractCallback::SetTotal(UInt64 /* size */) { return S_OK; } STDMETHODIMP CArchiveExtractCallback::SetCompleted(const UInt64 * /* completeValue */) { return S_OK; } STDMETHODIMP CArchiveExtractCallback::GetStream(UInt32 index, ISequentialOutStream **outStream, Int32 askExtractMode) { *outStream = 0; _outFileStream.Release(); { // Get Name NCOM::CPropVariant prop; RINOK(_archiveHandler->GetProperty(index, kpidPath, &prop)); UString fullPath; if (prop.vt == VT_EMPTY) fullPath = kEmptyFileAlias; else { if (prop.vt != VT_BSTR) return E_FAIL; fullPath = prop.bstrVal; } _filePath = fullPath; } if (askExtractMode != NArchive::NExtract::NAskMode::kExtract) return S_OK; { // Get Attrib NCOM::CPropVariant prop; RINOK(_archiveHandler->GetProperty(index, kpidAttrib, &prop)); if (prop.vt == VT_EMPTY) { _processedFileInfo.Attrib = 0; _processedFileInfo.AttribDefined = false; } else { if (prop.vt != VT_UI4) return E_FAIL; _processedFileInfo.Attrib = prop.ulVal; _processedFileInfo.AttribDefined = true; } } RINOK(IsArchiveItemFolder(_archiveHandler, index, _processedFileInfo.isDir)); { // Get Modified Time NCOM::CPropVariant prop; RINOK(_archiveHandler->GetProperty(index, kpidMTime, &prop)); _processedFileInfo.MTimeDefined = false; switch(prop.vt) { case VT_EMPTY: // _processedFileInfo.MTime = _utcMTimeDefault; break; case VT_FILETIME: _processedFileInfo.MTime = prop.filetime; _processedFileInfo.MTimeDefined = true; break; default: return E_FAIL; } } { // Get Size NCOM::CPropVariant prop; RINOK(_archiveHandler->GetProperty(index, kpidSize, &prop)); UInt64 newFileSize; /* bool newFileSizeDefined = */ ConvertPropVariantToUInt64(prop, newFileSize); } { // Create folders for file int slashPos = _filePath.ReverseFind(WCHAR_PATH_SEPARATOR); if (slashPos >= 0) CreateComplexDir(_directoryPath + us2fs(_filePath.Left(slashPos))); } FString fullProcessedPath = _directoryPath + us2fs(_filePath); _diskFilePath = fullProcessedPath; if (_processedFileInfo.isDir) { CreateComplexDir(fullProcessedPath); } else { NFind::CFileInfo fi; if (fi.Find(fullProcessedPath)) { if (!DeleteFileAlways(fullProcessedPath)) { PrintError("Can not delete output file", fullProcessedPath); return E_ABORT; } } _outFileStreamSpec = new COutFileStream; CMyComPtr<ISequentialOutStream> outStreamLoc(_outFileStreamSpec); if (!_outFileStreamSpec->Open(fullProcessedPath, CREATE_ALWAYS)) { PrintError("Can not open output file", fullProcessedPath); return E_ABORT; } _outFileStream = outStreamLoc; *outStream = outStreamLoc.Detach(); } return S_OK; } STDMETHODIMP CArchiveExtractCallback::PrepareOperation(Int32 askExtractMode) { _extractMode = false; switch (askExtractMode) { case NArchive::NExtract::NAskMode::kExtract: _extractMode = true; break; }; switch (askExtractMode) { case NArchive::NExtract::NAskMode::kExtract: PrintString(kExtractingString); break; case NArchive::NExtract::NAskMode::kTest: PrintString(kTestingString); break; case NArchive::NExtract::NAskMode::kSkip: PrintString(kSkippingString); break; }; PrintString(_filePath); return S_OK; } STDMETHODIMP CArchiveExtractCallback::SetOperationResult(Int32 operationResult) { switch (operationResult) { case NArchive::NExtract::NOperationResult::kOK: break; default: { NumErrors++; PrintString(" : "); const char *s = NULL; switch (operationResult) { case NArchive::NExtract::NOperationResult::kUnsupportedMethod: s = kUnsupportedMethod; break; case NArchive::NExtract::NOperationResult::kCRCError: s = kCRCFailed; break; case NArchive::NExtract::NOperationResult::kDataError: s = kDataError; break; case NArchive::NExtract::NOperationResult::kUnavailable: s = kUnavailableData; break; case NArchive::NExtract::NOperationResult::kUnexpectedEnd: s = kUnexpectedEnd; break; case NArchive::NExtract::NOperationResult::kDataAfterEnd: s = kDataAfterEnd; break; case NArchive::NExtract::NOperationResult::kIsNotArc: s = kIsNotArc; break; case NArchive::NExtract::NOperationResult::kHeadersError: s = kHeadersError; break; } if (s) { PrintString("Error : "); PrintString(s); } else { char temp[16]; ConvertUInt32ToString(operationResult, temp); PrintString("Error #"); PrintString(temp); } } } if (_outFileStream != NULL) { if (_processedFileInfo.MTimeDefined) _outFileStreamSpec->SetMTime(&_processedFileInfo.MTime); RINOK(_outFileStreamSpec->Close()); } _outFileStream.Release(); if (_extractMode && _processedFileInfo.AttribDefined) SetFileAttrib(_diskFilePath, _processedFileInfo.Attrib); PrintNewLine(); return S_OK; } STDMETHODIMP CArchiveExtractCallback::CryptoGetTextPassword(BSTR *password) { if (!PasswordIsDefined) { // You can ask real password here from user // Password = GetPassword(OutStream); // PasswordIsDefined = true; PrintError("Password is not defined"); return E_ABORT; } return StringToBstr(Password, password); } ////////////////////////////////////////////////////////////// // Archive Creating callback class struct CDirItem { UInt64 Size; FILETIME CTime; FILETIME ATime; FILETIME MTime; UString Name; FString FullPath; UInt32 Attrib; bool isDir() const { return (Attrib & FILE_ATTRIBUTE_DIRECTORY) != 0 ; } }; class CArchiveUpdateCallback: public IArchiveUpdateCallback2, public ICryptoGetTextPassword2, public CMyUnknownImp { public: MY_UNKNOWN_IMP2(IArchiveUpdateCallback2, ICryptoGetTextPassword2) // IProgress STDMETHOD(SetTotal)(UInt64 size); STDMETHOD(SetCompleted)(const UInt64 *completeValue); // IUpdateCallback2 STDMETHOD(EnumProperties)(IEnumSTATPROPSTG **enumerator); STDMETHOD(GetUpdateItemInfo)(UInt32 index, Int32 *newData, Int32 *newProperties, UInt32 *indexInArchive); STDMETHOD(GetProperty)(UInt32 index, PROPID propID, PROPVARIANT *value); STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **inStream); STDMETHOD(SetOperationResult)(Int32 operationResult); STDMETHOD(GetVolumeSize)(UInt32 index, UInt64 *size); STDMETHOD(GetVolumeStream)(UInt32 index, ISequentialOutStream **volumeStream); STDMETHOD(CryptoGetTextPassword2)(Int32 *passwordIsDefined, BSTR *password); public: CRecordVector<UInt64> VolumesSizes; UString VolName; UString VolExt; FString DirPrefix; const CObjectVector<CDirItem> *DirItems; bool PasswordIsDefined; UString Password; bool AskPassword; bool m_NeedBeClosed; FStringVector FailedFiles; CRecordVector<HRESULT> FailedCodes; CArchiveUpdateCallback(): PasswordIsDefined(false), AskPassword(false), DirItems(0) {}; ~CArchiveUpdateCallback() { Finilize(); } HRESULT Finilize(); void Init(const CObjectVector<CDirItem> *dirItems) { DirItems = dirItems; m_NeedBeClosed = false; FailedFiles.Clear(); FailedCodes.Clear(); } }; STDMETHODIMP CArchiveUpdateCallback::SetTotal(UInt64 /* size */) { return S_OK; } STDMETHODIMP CArchiveUpdateCallback::SetCompleted(const UInt64 * /* completeValue */) { return S_OK; } STDMETHODIMP CArchiveUpdateCallback::EnumProperties(IEnumSTATPROPSTG ** /* enumerator */) { return E_NOTIMPL; } STDMETHODIMP CArchiveUpdateCallback::GetUpdateItemInfo(UInt32 /* index */, Int32 *newData, Int32 *newProperties, UInt32 *indexInArchive) { if (newData != NULL) *newData = BoolToInt(true); if (newProperties != NULL) *newProperties = BoolToInt(true); if (indexInArchive != NULL) *indexInArchive = (UInt32)(Int32)-1; return S_OK; } STDMETHODIMP CArchiveUpdateCallback::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) { NCOM::CPropVariant prop; if (propID == kpidIsAnti) { prop = false; prop.Detach(value); return S_OK; } { const CDirItem &dirItem = (*DirItems)[index]; switch(propID) { case kpidPath: prop = dirItem.Name; break; case kpidIsDir: prop = dirItem.isDir(); break; case kpidSize: prop = dirItem.Size; break; case kpidAttrib: prop = dirItem.Attrib; break; case kpidCTime: prop = dirItem.CTime; break; case kpidATime: prop = dirItem.ATime; break; case kpidMTime: prop = dirItem.MTime; break; } } prop.Detach(value); return S_OK; } HRESULT CArchiveUpdateCallback::Finilize() { if (m_NeedBeClosed) { PrintNewLine(); m_NeedBeClosed = false; } return S_OK; } static void GetStream2(const wchar_t *name) { PrintString("Compressing "); if (name[0] == 0) name = kEmptyFileAlias; PrintString(name); } STDMETHODIMP CArchiveUpdateCallback::GetStream(UInt32 index, ISequentialInStream **inStream) { RINOK(Finilize()); const CDirItem &dirItem = (*DirItems)[index]; GetStream2(dirItem.Name); if (dirItem.isDir()) return S_OK; { CInFileStream *inStreamSpec = new CInFileStream; CMyComPtr<ISequentialInStream> inStreamLoc(inStreamSpec); FString path = DirPrefix + dirItem.FullPath; if (!inStreamSpec->Open(path)) { DWORD sysError = ::GetLastError(); FailedCodes.Add(sysError); FailedFiles.Add(path); // if (systemError == ERROR_SHARING_VIOLATION) { PrintNewLine(); PrintError("WARNING: can't open file"); // PrintString(NError::MyFormatMessageW(systemError)); return S_FALSE; } // return sysError; } *inStream = inStreamLoc.Detach(); } return S_OK; } STDMETHODIMP CArchiveUpdateCallback::SetOperationResult(Int32 /* operationResult */) { m_NeedBeClosed = true; return S_OK; } STDMETHODIMP CArchiveUpdateCallback::GetVolumeSize(UInt32 index, UInt64 *size) { if (VolumesSizes.Size() == 0) return S_FALSE; if (index >= (UInt32)VolumesSizes.Size()) index = VolumesSizes.Size() - 1; *size = VolumesSizes[index]; return S_OK; } STDMETHODIMP CArchiveUpdateCallback::GetVolumeStream(UInt32 index, ISequentialOutStream **volumeStream) { wchar_t temp[16]; ConvertUInt32ToString(index + 1, temp); UString res = temp; while (res.Len() < 2) res = UString(L'0') + res; UString fileName = VolName; fileName += L'.'; fileName += res; fileName += VolExt; COutFileStream *streamSpec = new COutFileStream; CMyComPtr<ISequentialOutStream> streamLoc(streamSpec); if (!streamSpec->Create(us2fs(fileName), false)) return ::GetLastError(); *volumeStream = streamLoc.Detach(); return S_OK; } STDMETHODIMP CArchiveUpdateCallback::CryptoGetTextPassword2(Int32 *passwordIsDefined, BSTR *password) { if (!PasswordIsDefined) { if (AskPassword) { // You can ask real password here from user // Password = GetPassword(OutStream); // PasswordIsDefined = true; PrintError("Password is not defined"); return E_ABORT; } } *passwordIsDefined = BoolToInt(PasswordIsDefined); return StringToBstr(Password, password); } ////////////////////////////////////////////////////////////////////////// // Main function #define NT_CHECK_FAIL_ACTION PrintError("Unsupported Windows version"); return 1; int MY_CDECL main(int numArgs, const char *args[]) { NT_CHECK PrintStringLn(kCopyrightString); if (numArgs < 3) { PrintStringLn(kHelpString); return 1; } NDLL::CLibrary lib; if (!lib.Load(NDLL::GetModuleDirPrefix() + FTEXT(kDllName))) { PrintError("Can not load 7-zip library"); return 1; } Func_CreateObject createObjectFunc = (Func_CreateObject)lib.GetProc("CreateObject"); if (!createObjectFunc) { PrintError("Can not get CreateObject"); return 1; } char c; { AString command = args[1]; if (command.Len() != 1) { PrintError("incorrect command"); return 1; } c = (char)MyCharLower_Ascii(command[0]); } FString archiveName = CmdStringToFString(args[2]); if (c == 'a') { // create archive command if (numArgs < 4) { PrintStringLn(kHelpString); return 1; } CObjectVector<CDirItem> dirItems; { int i; for (i = 3; i < numArgs; i++) { CDirItem di; FString name = CmdStringToFString(args[i]); NFind::CFileInfo fi; if (!fi.Find(name)) { PrintError("Can't find file", name); return 1; } di.Attrib = fi.Attrib; di.Size = fi.Size; di.CTime = fi.CTime; di.ATime = fi.ATime; di.MTime = fi.MTime; di.Name = fs2us(name); di.FullPath = name; dirItems.Add(di); } } COutFileStream *outFileStreamSpec = new COutFileStream; CMyComPtr<IOutStream> outFileStream = outFileStreamSpec; if (!outFileStreamSpec->Create(archiveName, false)) { PrintError("can't create archive file"); return 1; } CMyComPtr<IOutArchive> outArchive; if (createObjectFunc(&CLSID_CFormat7z, &IID_IOutArchive, (void **)&outArchive) != S_OK) { PrintError("Can not get class object"); return 1; } CArchiveUpdateCallback *updateCallbackSpec = new CArchiveUpdateCallback; CMyComPtr<IArchiveUpdateCallback2> updateCallback(updateCallbackSpec); updateCallbackSpec->Init(&dirItems); // updateCallbackSpec->PasswordIsDefined = true; // updateCallbackSpec->Password = L"1"; /* { const wchar_t *names[] = { L"s", L"x" }; const unsigned kNumProps = ARRAY_SIZE(names); NCOM::CPropVariant values[kNumProps] = { false, // solid mode OFF (UInt32)9 // compression level = 9 - ultra }; CMyComPtr<ISetProperties> setProperties; outArchive->QueryInterface(IID_ISetProperties, (void **)&setProperties); if (!setProperties) { PrintError("ISetProperties unsupported"); return 1; } RINOK(setProperties->SetProperties(names, values, kNumProps)); } */ HRESULT result = outArchive->UpdateItems(outFileStream, dirItems.Size(), updateCallback); updateCallbackSpec->Finilize(); if (result != S_OK) { PrintError("Update Error"); return 1; } FOR_VECTOR (i, updateCallbackSpec->FailedFiles) { PrintNewLine(); PrintError("Error for file", updateCallbackSpec->FailedFiles[i]); } if (updateCallbackSpec->FailedFiles.Size() != 0) return 1; } else { if (numArgs != 3) { PrintStringLn(kHelpString); return 1; } bool listCommand; if (c == 'l') listCommand = true; else if (c == 'x') listCommand = false; else { PrintError("incorrect command"); return 1; } CMyComPtr<IInArchive> archive; if (createObjectFunc(&CLSID_CFormat7z, &IID_IInArchive, (void **)&archive) != S_OK) { PrintError("Can not get class object"); return 1; } CInFileStream *fileSpec = new CInFileStream; CMyComPtr<IInStream> file = fileSpec; if (!fileSpec->Open(archiveName)) { PrintError("Can not open archive file", archiveName); return 1; } { CArchiveOpenCallback *openCallbackSpec = new CArchiveOpenCallback; CMyComPtr<IArchiveOpenCallback> openCallback(openCallbackSpec); openCallbackSpec->PasswordIsDefined = false; // openCallbackSpec->PasswordIsDefined = true; // openCallbackSpec->Password = L"1"; if (archive->Open(file, 0, openCallback) != S_OK) { PrintError("Can not open file as archive", archiveName); return 1; } } if (listCommand) { // List command UInt32 numItems = 0; archive->GetNumberOfItems(&numItems); for (UInt32 i = 0; i < numItems; i++) { { // Get uncompressed size of file NCOM::CPropVariant prop; archive->GetProperty(i, kpidSize, &prop); char s[32]; ConvertPropVariantToShortString(prop, s); PrintString(s); PrintString(" "); } { // Get name of file NCOM::CPropVariant prop; archive->GetProperty(i, kpidPath, &prop); if (prop.vt == VT_BSTR) PrintString(prop.bstrVal); else if (prop.vt != VT_EMPTY) PrintString("ERROR!"); } PrintNewLine(); } } else { // Extract command CArchiveExtractCallback *extractCallbackSpec = new CArchiveExtractCallback; CMyComPtr<IArchiveExtractCallback> extractCallback(extractCallbackSpec); extractCallbackSpec->Init(archive, FTEXT("")); // second parameter is output folder path extractCallbackSpec->PasswordIsDefined = false; // extractCallbackSpec->PasswordIsDefined = true; // extractCallbackSpec->Password = L"1"; HRESULT result = archive->Extract(NULL, (UInt32)(Int32)(-1), false, extractCallback); if (result != S_OK) { PrintError("Extract Error"); return 1; } } } return 0; }