misc/libphysfs/lzma/CPP/7zip/Archive/7z/7zHandler.cpp
author nemo
Mon, 10 Apr 2017 12:06:43 -0400
changeset 12213 bb5522e88ab2
permissions -rw-r--r--
bulk copy of latest physfs to our misc/libphysfs since this seems to fix an off-by-1 error reliably hit in readln read of 1 byte probably introduced in the addition of the buffered read. Whether this is excessive or whether libphysfs should even be maintained by us is another matter. But at least we shouldn't crash

// 7zHandler.cpp

#include "StdAfx.h"

#include "7zHandler.h"
#include "7zProperties.h"

#include "../../../Common/IntToString.h"
#include "../../../Common/ComTry.h"
#include "../../../Windows/Defs.h"

#include "../Common/ItemNameUtils.h"
#ifdef _7Z_VOL
#include "../Common/MultiStream.h"
#endif

#ifdef __7Z_SET_PROPERTIES
#ifdef EXTRACT_ONLY
#include "../Common/ParseProperties.h"
#endif
#endif

#ifdef COMPRESS_MT
#include "../../../Windows/System.h"
#endif

using namespace NWindows;

extern UString ConvertMethodIdToString(UInt64 id);

namespace NArchive {
namespace N7z {

CHandler::CHandler()
{
  _crcSize = 4;

  #ifdef EXTRACT_ONLY
  #ifdef COMPRESS_MT
  _numThreads = NWindows::NSystem::GetNumberOfProcessors();
  #endif
  #else
  Init();
  #endif
}

STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
{
  *numItems = 
  #ifdef _7Z_VOL
  _refs.Size();
  #else
  *numItems = _database.Files.Size();
  #endif
  return S_OK;
}

#ifdef _SFX

IMP_IInArchive_ArcProps_NO

STDMETHODIMP CHandler::GetNumberOfProperties(UInt32 * /* numProperties */)
{
  return E_NOTIMPL;
}

STDMETHODIMP CHandler::GetPropertyInfo(UInt32 /* index */,     
      BSTR * /* name */, PROPID * /* propID */, VARTYPE * /* varType */)
{
  return E_NOTIMPL;
}


#else

STATPROPSTG kArcProps[] = 
{
  { NULL, kpidMethod, VT_BSTR},
  { NULL, kpidSolid, VT_BOOL},
  { NULL, kpidNumBlocks, VT_UI4}
};

STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
{
  COM_TRY_BEGIN
  NWindows::NCOM::CPropVariant prop;
  switch(propID)
  {
    case kpidMethod:
    {
      UString resString;
      CRecordVector<UInt64> ids;
      int i;
      for (i = 0; i < _database.Folders.Size(); i++)
      {
        const CFolder &f = _database.Folders[i];
        for (int j = f.Coders.Size() - 1; j >= 0; j--)
          ids.AddToUniqueSorted(f.Coders[j].MethodID);
      }

      for (i = 0; i < ids.Size(); i++)
      {
        UInt64 id = ids[i];
        UString methodName;
        /* bool methodIsKnown = */ FindMethod(EXTERNAL_CODECS_VARS id, methodName);
        if (methodName.IsEmpty())
          methodName = ConvertMethodIdToString(id);
        if (!resString.IsEmpty())
          resString += L' ';
        resString += methodName;
      }
      prop = resString; 
      break;
    }
    case kpidSolid: prop = _database.IsSolid(); break;
    case kpidNumBlocks: prop = (UInt32)_database.Folders.Size(); break;
  }
  prop.Detach(value);
  return S_OK;
  COM_TRY_END
}

IMP_IInArchive_ArcProps

#endif

static void MySetFileTime(bool timeDefined, FILETIME unixTime, NWindows::NCOM::CPropVariant &prop)
{
  if (timeDefined)
    prop = unixTime;
}

#ifndef _SFX

static UString ConvertUInt32ToString(UInt32 value)
{
  wchar_t buffer[32];
  ConvertUInt64ToString(value, buffer);
  return buffer;
}

static UString GetStringForSizeValue(UInt32 value)
{
  for (int i = 31; i >= 0; i--)
    if ((UInt32(1) << i) == value)
      return ConvertUInt32ToString(i);
  UString result;
  if (value % (1 << 20) == 0)
  {
    result += ConvertUInt32ToString(value >> 20);
    result += L"m";
  }
  else if (value % (1 << 10) == 0)
  {
    result += ConvertUInt32ToString(value >> 10);
    result += L"k";
  }
  else
  {
    result += ConvertUInt32ToString(value);
    result += L"b";
  }
  return result;
}

static const UInt64 k_Copy = 0x0;
static const UInt64 k_LZMA  = 0x030101;
static const UInt64 k_PPMD  = 0x030401;

static wchar_t GetHex(Byte value)
{
  return (wchar_t)((value < 10) ? (L'0' + value) : (L'A' + (value - 10)));
}
static inline UString GetHex2(Byte value)
{
  UString result;
  result += GetHex((Byte)(value >> 4));
  result += GetHex((Byte)(value & 0xF));
  return result;
}

#endif

static const UInt64 k_AES  = 0x06F10701;

#ifndef _SFX
static inline UInt32 GetUInt32FromMemLE(const Byte *p)
{
  return p[0] | (((UInt32)p[1]) << 8) | (((UInt32)p[2]) << 16) | (((UInt32)p[3]) << 24);
}
#endif

bool CHandler::IsEncrypted(UInt32 index2) const
{
  CNum folderIndex = _database.FileIndexToFolderIndexMap[index2];
  if (folderIndex != kNumNoIndex)
  {
    const CFolder &folderInfo = _database.Folders[folderIndex];
    for (int i = folderInfo.Coders.Size() - 1; i >= 0; i--)
      if (folderInfo.Coders[i].MethodID == k_AES)
        return true;
  }
  return false;
}

STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID,  PROPVARIANT *value)
{
  COM_TRY_BEGIN
  NWindows::NCOM::CPropVariant prop;
  
  /*
  const CRef2 &ref2 = _refs[index];
  if (ref2.Refs.IsEmpty())
    return E_FAIL;
  const CRef &ref = ref2.Refs.Front();
  */
  
  #ifdef _7Z_VOL
  const CRef &ref = _refs[index];
  const CVolume &volume = _volumes[ref.VolumeIndex];
  const CArchiveDatabaseEx &_database = volume.Database;
  UInt32 index2 = ref.ItemIndex;
  const CFileItem &item = _database.Files[index2];
  #else
  const CFileItem &item = _database.Files[index];
  UInt32 index2 = index;
  #endif

  switch(propID)
  {
    case kpidPath:
    {
      if (!item.Name.IsEmpty())
        prop = NItemName::GetOSName(item.Name);
      break;
    }
    case kpidIsFolder:
      prop = item.IsDirectory;
      break;
    case kpidSize:
    {
      prop = item.UnPackSize;
      // prop = ref2.UnPackSize;
      break;
    }
    case kpidPosition:
    {
      /*
      if (ref2.Refs.Size() > 1)
        prop = ref2.StartPos;
      else
      */
        if (item.IsStartPosDefined)
          prop = item.StartPos;
      break;
    }
    case kpidPackedSize:
    {
      // prop = ref2.PackSize;
      {
        CNum folderIndex = _database.FileIndexToFolderIndexMap[index2];
        if (folderIndex != kNumNoIndex)
        {
          if (_database.FolderStartFileIndex[folderIndex] == (CNum)index2)
            prop = _database.GetFolderFullPackSize(folderIndex);
          /*
          else
            prop = (UInt64)0;
          */
        }
        else
          prop = (UInt64)0;
      }
      break;
    }
    case kpidLastAccessTime:
      MySetFileTime(item.IsLastAccessTimeDefined, item.LastAccessTime, prop);
      break;
    case kpidCreationTime:
      MySetFileTime(item.IsCreationTimeDefined, item.CreationTime, prop);
      break;
    case kpidLastWriteTime:
      MySetFileTime(item.IsLastWriteTimeDefined, item.LastWriteTime, prop);
      break;
    case kpidAttributes:
      if (item.AreAttributesDefined)
        prop = item.Attributes;
      break;
    case kpidCRC:
      if (item.IsFileCRCDefined)
        prop = item.FileCRC;
      break;
    case kpidEncrypted:
    {
      prop = IsEncrypted(index2);
      break;
    }
    #ifndef _SFX
    case kpidMethod:
      {
        CNum folderIndex = _database.FileIndexToFolderIndexMap[index2];
        if (folderIndex != kNumNoIndex)
        {
          const CFolder &folderInfo = _database.Folders[folderIndex];
          UString methodsString;
          for (int i = folderInfo.Coders.Size() - 1; i >= 0; i--)
          {
            const CCoderInfo &coderInfo = folderInfo.Coders[i];
            if (!methodsString.IsEmpty())
              methodsString += L' ';

            {
              UString methodName;
              bool methodIsKnown = FindMethod(
                  EXTERNAL_CODECS_VARS 
                  coderInfo.MethodID, methodName);

              if (methodIsKnown)
              {
                methodsString += methodName;
                if (coderInfo.MethodID == k_LZMA)
                {
                  if (coderInfo.Properties.GetCapacity() >= 5)
                  {
                    methodsString += L":";
                    UInt32 dicSize = GetUInt32FromMemLE(
                      ((const Byte *)coderInfo.Properties + 1));
                    methodsString += GetStringForSizeValue(dicSize);
                  }
                }
                else if (coderInfo.MethodID == k_PPMD)
                {
                  if (coderInfo.Properties.GetCapacity() >= 5)
                  {
                    Byte order = *(const Byte *)coderInfo.Properties;
                    methodsString += L":o";
                    methodsString += ConvertUInt32ToString(order);
                    methodsString += L":mem";
                    UInt32 dicSize = GetUInt32FromMemLE(
                      ((const Byte *)coderInfo.Properties + 1));
                    methodsString += GetStringForSizeValue(dicSize);
                  }
                }
                else if (coderInfo.MethodID == k_AES)
                {
                  if (coderInfo.Properties.GetCapacity() >= 1)
                  {
                    methodsString += L":";
                    const Byte *data = (const Byte *)coderInfo.Properties;
                    Byte firstByte = *data++;
                    UInt32 numCyclesPower = firstByte & 0x3F;
                    methodsString += ConvertUInt32ToString(numCyclesPower);
                    /*
                    if ((firstByte & 0xC0) != 0)
                    {
                      methodsString += L":";
                      return S_OK;
                      UInt32 saltSize = (firstByte >> 7) & 1;
                      UInt32 ivSize = (firstByte >> 6) & 1;
                      if (coderInfo.Properties.GetCapacity() >= 2)
                      {
                        Byte secondByte = *data++;
                        saltSize += (secondByte >> 4);
                        ivSize += (secondByte & 0x0F);
                      }
                    }
                    */
                  }
                }
                else
                {
                  if (coderInfo.Properties.GetCapacity() > 0)
                  {
                    methodsString += L":[";
                    for (size_t bi = 0; bi < coderInfo.Properties.GetCapacity(); bi++)
                    {
                      if (bi > 5 && bi + 1 < coderInfo.Properties.GetCapacity())
                      {
                        methodsString += L"..";
                        break;
                      }
                      else
                        methodsString += GetHex2(coderInfo.Properties[bi]);
                    }
                    methodsString += L"]";
                  }
                }
              }
              else
              {
                methodsString += ConvertMethodIdToString(coderInfo.MethodID);
              }
            }
          }
          prop = methodsString;
        }
      }
      break;
    case kpidBlock:
      {
        CNum folderIndex = _database.FileIndexToFolderIndexMap[index2];
        if (folderIndex != kNumNoIndex)
          prop = (UInt32)folderIndex;
      }
      break;
    case kpidPackedSize0:
    case kpidPackedSize1:
    case kpidPackedSize2:
    case kpidPackedSize3:
    case kpidPackedSize4:
      {
        CNum folderIndex = _database.FileIndexToFolderIndexMap[index2];
        if (folderIndex != kNumNoIndex)
        {
          const CFolder &folderInfo = _database.Folders[folderIndex];
          if (_database.FolderStartFileIndex[folderIndex] == (CNum)index2 &&
              folderInfo.PackStreams.Size() > (int)(propID - kpidPackedSize0))
          {
            prop = _database.GetFolderPackStreamSize(folderIndex, propID - kpidPackedSize0);
          }
          else
            prop = (UInt64)0;
        }
        else
          prop = (UInt64)0;
      }
      break;
    #endif
    case kpidIsAnti:
      prop = item.IsAnti;
      break;
  }
  prop.Detach(value);
  return S_OK;
  COM_TRY_END
}

#ifdef _7Z_VOL

static const wchar_t *kExt = L"7z";
static const wchar_t *kAfterPart = L".7z";

class CVolumeName
{
  bool _first;
  UString _unchangedPart;
  UString _changedPart;    
  UString _afterPart;    
public:
  bool InitName(const UString &name)
  {
    _first = true;
    int dotPos = name.ReverseFind('.');
    UString basePart = name;
    if (dotPos >= 0)
    {
      UString ext = name.Mid(dotPos + 1);
      if (ext.CompareNoCase(kExt)==0 || 
        ext.CompareNoCase(L"EXE") == 0)
      {
        _afterPart = kAfterPart;
        basePart = name.Left(dotPos);
      }
    }

    int numLetters = 1;
    bool splitStyle = false;
    if (basePart.Right(numLetters) == L"1")
    {
      while (numLetters < basePart.Length())
      {
        if (basePart[basePart.Length() - numLetters - 1] != '0')
          break;
        numLetters++;
      }
    }
    else 
      return false;
    _unchangedPart = basePart.Left(basePart.Length() - numLetters);
    _changedPart = basePart.Right(numLetters);
    return true;
  }

  UString GetNextName()
  {
    UString newName; 
    // if (_newStyle || !_first)
    {
      int i;
      int numLetters = _changedPart.Length();
      for (i = numLetters - 1; i >= 0; i--)
      {
        wchar_t c = _changedPart[i];
        if (c == L'9')
        {
          c = L'0';
          newName = c + newName;
          if (i == 0)
            newName = UString(L'1') + newName;
          continue;
        }
        c++;
        newName = UString(c) + newName;
        i--;
        for (; i >= 0; i--)
          newName = _changedPart[i] + newName;
        break;
      }
      _changedPart = newName;
    }
    _first = false;
    return _unchangedPart + _changedPart + _afterPart;
  }
};

#endif

STDMETHODIMP CHandler::Open(IInStream *stream,
    const UInt64 *maxCheckStartPosition, 
    IArchiveOpenCallback *openArchiveCallback)
{
  COM_TRY_BEGIN
  Close();
  #ifndef _SFX
  _fileInfoPopIDs.Clear();
  #endif
  try
  {
    CMyComPtr<IArchiveOpenCallback> openArchiveCallbackTemp = openArchiveCallback;
    #ifdef _7Z_VOL
    CVolumeName seqName;

    CMyComPtr<IArchiveOpenVolumeCallback> openVolumeCallback;
    #endif

    #ifndef _NO_CRYPTO
    CMyComPtr<ICryptoGetTextPassword> getTextPassword;
    if (openArchiveCallback)
    {
      openArchiveCallbackTemp.QueryInterface(
          IID_ICryptoGetTextPassword, &getTextPassword);
    }
    #endif
    #ifdef _7Z_VOL
    if (openArchiveCallback)
    {
      openArchiveCallbackTemp.QueryInterface(IID_IArchiveOpenVolumeCallback, &openVolumeCallback);
    }
    for (;;)
    {
      CMyComPtr<IInStream> inStream;
      if (!_volumes.IsEmpty())
      {
        if (!openVolumeCallback)
          break;
        if(_volumes.Size() == 1)
        {
          UString baseName;
          {
            NCOM::CPropVariant prop;
            RINOK(openVolumeCallback->GetProperty(kpidName, &prop));
            if (prop.vt != VT_BSTR)
              break;
            baseName = prop.bstrVal;
          }
          seqName.InitName(baseName);
        }

        UString fullName = seqName.GetNextName();
        HRESULT result = openVolumeCallback->GetStream(fullName, &inStream);
        if (result == S_FALSE)
          break;
        if (result != S_OK)
          return result;
        if (!stream)
          break;
      }
      else
        inStream = stream;

      CInArchive archive;
      RINOK(archive.Open(inStream, maxCheckStartPosition));

      _volumes.Add(CVolume());
      CVolume &volume = _volumes.Back();
      CArchiveDatabaseEx &database = volume.Database;
      volume.Stream = inStream;
      volume.StartRef2Index = _refs.Size();

      HRESULT result = archive.ReadDatabase(database
          #ifndef _NO_CRYPTO
          , getTextPassword
          #endif
          );
      if (result != S_OK)
      {
        _volumes.Clear();
        return result;
      }
      database.Fill();
      for(int i = 0; i < database.Files.Size(); i++)
      {
        CRef refNew;
        refNew.VolumeIndex = _volumes.Size() - 1;
        refNew.ItemIndex = i;
        _refs.Add(refNew);
        /*
        const CFileItem &file = database.Files[i];
        int j;
        */
        /*
        for (j = _refs.Size() - 1; j >= 0; j--)
        {
          CRef2 &ref2 = _refs[j];
          const CRef &ref = ref2.Refs.Back();
          const CVolume &volume2 = _volumes[ref.VolumeIndex];
          const CArchiveDatabaseEx &database2 = volume2.Database;
          const CFileItem &file2 = database2.Files[ref.ItemIndex];
          if (file2.Name.CompareNoCase(file.Name) == 0)
          {
            if (!file.IsStartPosDefined)
              continue;
            if (file.StartPos != ref2.StartPos + ref2.UnPackSize)
              continue;
            ref2.Refs.Add(refNew);
            break;
          }
        }
        */
        /*
        j = -1;
        if (j < 0)
        {
          CRef2 ref2New;
          ref2New.Refs.Add(refNew);
          j = _refs.Add(ref2New);
        }
        CRef2 &ref2 = _refs[j];
        ref2.UnPackSize += file.UnPackSize;
        ref2.PackSize += database.GetFilePackSize(i);
        if (ref2.Refs.Size() == 1 && file.IsStartPosDefined)
          ref2.StartPos = file.StartPos;
        */
      }
      if (database.Files.Size() != 1)
        break;
      const CFileItem &file = database.Files.Front();
      if (!file.IsStartPosDefined)
        break;
    }
    #else
    CInArchive archive;
    RINOK(archive.Open(stream, maxCheckStartPosition));
    HRESULT result = archive.ReadDatabase(
      EXTERNAL_CODECS_VARS
      _database
      #ifndef _NO_CRYPTO
      , getTextPassword
      #endif
      );
    RINOK(result);
    _database.Fill();
    _inStream = stream;
    #endif
  }
  catch(...)
  {
    Close();
    return S_FALSE;
  }
  // _inStream = stream;
  #ifndef _SFX
  FillPopIDs();
  #endif
  return S_OK;
  COM_TRY_END
}

STDMETHODIMP CHandler::Close()
{
  COM_TRY_BEGIN
  #ifdef _7Z_VOL
  _volumes.Clear();
  _refs.Clear();
  #else
  _inStream.Release();
  _database.Clear();
  #endif
  return S_OK;
  COM_TRY_END
}

#ifdef _7Z_VOL
STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream)
{
  if (index != 0)
    return E_INVALIDARG;
  *stream = 0;
  CMultiStream *streamSpec = new CMultiStream;
  CMyComPtr<ISequentialInStream> streamTemp = streamSpec;
  
  UInt64 pos = 0;
  const UString *fileName;
  for (int i = 0; i < _refs.Size(); i++)
  {
    const CRef &ref = _refs[i];
    const CVolume &volume = _volumes[ref.VolumeIndex];
    const CArchiveDatabaseEx &database = volume.Database;
    const CFileItem &file = database.Files[ref.ItemIndex];
    if (i == 0)
      fileName = &file.Name;
    else
      if (fileName->Compare(file.Name) != 0)
        return S_FALSE;
    if (!file.IsStartPosDefined)
      return S_FALSE;
    if (file.StartPos != pos)
      return S_FALSE;
    CNum folderIndex = database.FileIndexToFolderIndexMap[ref.ItemIndex];
    if (folderIndex == kNumNoIndex)
    {
      if (file.UnPackSize != 0)
        return E_FAIL;
      continue;
    }
    if (database.NumUnPackStreamsVector[folderIndex] != 1)
      return S_FALSE;
    const CFolder &folder = database.Folders[folderIndex];
    if (folder.Coders.Size() != 1)
      return S_FALSE;
    const CCoderInfo &coder = folder.Coders.Front();
    if (coder.NumInStreams != 1 || coder.NumOutStreams != 1)
      return S_FALSE;
    if (coder.MethodID != k_Copy)
      return S_FALSE;

    pos += file.UnPackSize;
    CMultiStream::CSubStreamInfo subStreamInfo;
    subStreamInfo.Stream = volume.Stream;
    subStreamInfo.Pos = database.GetFolderStreamPos(folderIndex, 0);
    subStreamInfo.Size = file.UnPackSize;
    streamSpec->Streams.Add(subStreamInfo);
  }
  streamSpec->Init();
  *stream = streamTemp.Detach();
  return S_OK;
}
#endif


#ifdef __7Z_SET_PROPERTIES
#ifdef EXTRACT_ONLY

STDMETHODIMP CHandler::SetProperties(const wchar_t **names, const PROPVARIANT *values, Int32 numProperties)
{
  COM_TRY_BEGIN
  #ifdef COMPRESS_MT
  const UInt32 numProcessors = NSystem::GetNumberOfProcessors();
  _numThreads = numProcessors;
  #endif

  for (int i = 0; i < numProperties; i++)
  {
    UString name = names[i];
    name.MakeUpper();
    if (name.IsEmpty())
      return E_INVALIDARG;
    const PROPVARIANT &value = values[i];
    UInt32 number;
    int index = ParseStringToUInt32(name, number);
    if (index == 0)
    {
      if(name.Left(2).CompareNoCase(L"MT") == 0)
      {
        #ifdef COMPRESS_MT
        RINOK(ParseMtProp(name.Mid(2), value, numProcessors, _numThreads));
        #endif
        continue;
      }
      else
        return E_INVALIDARG;
    }
  }
  return S_OK;
  COM_TRY_END
}  

#endif
#endif

IMPL_ISetCompressCodecsInfo

}}