// ZipOut.cpp #include "StdAfx.h" #include "../../../../C/7zCrc.h" #include "../../../Windows/TimeUtils.h" #include "../../Common/OffsetStream.h" #include "ZipOut.h" namespace NArchive { namespace NZip { HRESULT COutArchive::Create(IOutStream *outStream) { m_CurPos = 0; if (!m_OutBuffer.Create(1 << 16)) return E_OUTOFMEMORY; m_Stream = outStream; m_OutBuffer.SetStream(outStream); m_OutBuffer.Init(); return m_Stream->Seek(0, STREAM_SEEK_CUR, &m_Base); } void COutArchive::SeekToCurPos() { HRESULT res = m_Stream->Seek((Int64)(m_Base + m_CurPos), STREAM_SEEK_SET, NULL); if (res != S_OK) throw CSystemException(res); } #define DOES_NEED_ZIP64(v) (v >= (UInt32)0xFFFFFFFF) // #define DOES_NEED_ZIP64(v) (v >= 0) void COutArchive::WriteBytes(const void *data, size_t size) { m_OutBuffer.WriteBytes(data, size); m_CurPos += size; } void COutArchive::Write8(Byte b) { m_OutBuffer.WriteByte(b); m_CurPos++; } void COutArchive::Write16(UInt16 val) { Write8((Byte)val); Write8((Byte)(val >> 8)); } void COutArchive::Write32(UInt32 val) { for (int i = 0; i < 4; i++) { Write8((Byte)val); val >>= 8; } } void COutArchive::Write64(UInt64 val) { for (int i = 0; i < 8; i++) { Write8((Byte)val); val >>= 8; } } void COutArchive::WriteExtra(const CExtraBlock &extra) { FOR_VECTOR (i, extra.SubBlocks) { const CExtraSubBlock &subBlock = extra.SubBlocks[i]; Write16((UInt16)subBlock.ID); Write16((UInt16)subBlock.Data.Size()); WriteBytes(subBlock.Data, (UInt16)subBlock.Data.Size()); } } void COutArchive::WriteCommonItemInfo(const CLocalItem &item, bool isZip64) { { Byte ver = item.ExtractVersion.Version; if (isZip64 && ver < NFileHeader::NCompressionMethod::kExtractVersion_Zip64) ver = NFileHeader::NCompressionMethod::kExtractVersion_Zip64; Write8(ver); } Write8(item.ExtractVersion.HostOS); Write16(item.Flags); Write16(item.Method); Write32(item.Time); } #define WRITE_32_VAL_SPEC(__v, __isZip64) Write32((__isZip64) ? 0xFFFFFFFF : (UInt32)(__v)); void COutArchive::WriteUtfName(const CItemOut &item) { if (item.Name_Utf.Size() == 0) return; Write16(NFileHeader::NExtraID::kIzUnicodeName); Write16((UInt16)(5 + item.Name_Utf.Size())); Write8(1); // (1 = version) of that extra field Write32(CrcCalc(item.Name.Ptr(), item.Name.Len())); WriteBytes(item.Name_Utf, (UInt16)item.Name_Utf.Size()); } static const unsigned k_Ntfs_ExtraSize = 4 + 2 + 2 + (3 * 8); static const unsigned k_UnixTime_ExtraSize = 1 + (1 * 4); void COutArchive::WriteTimeExtra(const CItemOut &item, bool writeNtfs) { if (writeNtfs) { // windows explorer ignores that extra Write16(NFileHeader::NExtraID::kNTFS); Write16(k_Ntfs_ExtraSize); Write32(0); // reserved Write16(NFileHeader::NNtfsExtra::kTagTime); Write16(8 * 3); WriteNtfsTime(item.Ntfs_MTime); WriteNtfsTime(item.Ntfs_ATime); WriteNtfsTime(item.Ntfs_CTime); } if (item.Write_UnixTime) { // windows explorer ignores that extra // by specification : should we write to local header also? Write16(NFileHeader::NExtraID::kUnixTime); Write16(k_UnixTime_ExtraSize); const Byte flags = (Byte)((unsigned)1 << NFileHeader::NUnixTime::kMTime); Write8(flags); UInt32 unixTime; NWindows::NTime::FileTime_To_UnixTime(item.Ntfs_MTime, unixTime); Write32(unixTime); } } void COutArchive::WriteLocalHeader(CItemOut &item, bool needCheck) { m_LocalHeaderPos = m_CurPos; item.LocalHeaderPos = m_CurPos; bool isZip64 = DOES_NEED_ZIP64(item.PackSize) || DOES_NEED_ZIP64(item.Size); if (needCheck && m_IsZip64) isZip64 = true; // Why don't we write NTFS timestamps to local header? // Probably we want to reduce size of archive? const bool writeNtfs = false; // do not write NTFS timestamp to local header // const bool writeNtfs = item.Write_NtfsTime; // write NTFS time to local header const UInt32 localExtraSize = (UInt32)( (isZip64 ? (4 + 8 + 8): 0) + (writeNtfs ? 4 + k_Ntfs_ExtraSize : 0) + (item.Write_UnixTime ? 4 + k_UnixTime_ExtraSize : 0) + item.Get_UtfName_ExtraSize() + item.LocalExtra.GetSize()); if ((UInt16)localExtraSize != localExtraSize) throw CSystemException(E_FAIL); if (needCheck && m_ExtraSize != localExtraSize) throw CSystemException(E_FAIL); m_IsZip64 = isZip64; m_ExtraSize = localExtraSize; item.LocalExtra.IsZip64 = isZip64; Write32(NSignature::kLocalFileHeader); WriteCommonItemInfo(item, isZip64); Write32(item.HasDescriptor() ? 0 : item.Crc); UInt64 packSize = item.PackSize; UInt64 size = item.Size; if (item.HasDescriptor()) { packSize = 0; size = 0; } WRITE_32_VAL_SPEC(packSize, isZip64); WRITE_32_VAL_SPEC(size, isZip64); Write16((UInt16)item.Name.Len()); Write16((UInt16)localExtraSize); WriteBytes((const char *)item.Name, (UInt16)item.Name.Len()); if (isZip64) { Write16(NFileHeader::NExtraID::kZip64); Write16(8 + 8); Write64(size); Write64(packSize); } WriteTimeExtra(item, writeNtfs); WriteUtfName(item); WriteExtra(item.LocalExtra); const UInt32 localFileHeaderSize = (UInt32)(m_CurPos - m_LocalHeaderPos); if (needCheck && m_LocalFileHeaderSize != localFileHeaderSize) throw CSystemException(E_FAIL); m_LocalFileHeaderSize = localFileHeaderSize; m_OutBuffer.FlushWithCheck(); } void COutArchive::WriteLocalHeader_Replace(CItemOut &item) { m_CurPos = m_LocalHeaderPos + m_LocalFileHeaderSize + item.PackSize; if (item.HasDescriptor()) { WriteDescriptor(item); m_OutBuffer.FlushWithCheck(); return; // we don't replace local header, if we write Descriptor. // so local header with Descriptor flag must be written to local header before. } const UInt64 nextPos = m_CurPos; m_CurPos = m_LocalHeaderPos; SeekToCurPos(); WriteLocalHeader(item, true); m_CurPos = nextPos; SeekToCurPos(); } void COutArchive::WriteDescriptor(const CItemOut &item) { Byte buf[kDataDescriptorSize64]; SetUi32(buf, NSignature::kDataDescriptor); SetUi32(buf + 4, item.Crc); unsigned descriptorSize; if (m_IsZip64) { SetUi64(buf + 8, item.PackSize); SetUi64(buf + 16, item.Size); descriptorSize = kDataDescriptorSize64; } else { SetUi32(buf + 8, (UInt32)item.PackSize); SetUi32(buf + 12, (UInt32)item.Size); descriptorSize = kDataDescriptorSize32; } WriteBytes(buf, descriptorSize); } void COutArchive::WriteCentralHeader(const CItemOut &item) { const bool isUnPack64 = DOES_NEED_ZIP64(item.Size); const bool isPack64 = DOES_NEED_ZIP64(item.PackSize); const bool isPosition64 = DOES_NEED_ZIP64(item.LocalHeaderPos); const bool isZip64 = isPack64 || isUnPack64 || isPosition64; Write32(NSignature::kCentralFileHeader); Write8(item.MadeByVersion.Version); Write8(item.MadeByVersion.HostOS); WriteCommonItemInfo(item, isZip64); Write32(item.Crc); WRITE_32_VAL_SPEC(item.PackSize, isPack64); WRITE_32_VAL_SPEC(item.Size, isUnPack64); Write16((UInt16)item.Name.Len()); const UInt16 zip64ExtraSize = (UInt16)((isUnPack64 ? 8: 0) + (isPack64 ? 8: 0) + (isPosition64 ? 8: 0)); const bool writeNtfs = item.Write_NtfsTime; const size_t centralExtraSize = (isZip64 ? 4 + zip64ExtraSize : 0) + (writeNtfs ? 4 + k_Ntfs_ExtraSize : 0) + (item.Write_UnixTime ? 4 + k_UnixTime_ExtraSize : 0) + item.Get_UtfName_ExtraSize() + item.CentralExtra.GetSize(); const UInt16 centralExtraSize16 = (UInt16)centralExtraSize; if (centralExtraSize16 != centralExtraSize) throw CSystemException(E_FAIL); Write16(centralExtraSize16); const UInt16 commentSize = (UInt16)item.Comment.Size(); Write16(commentSize); Write16(0); // DiskNumberStart; Write16(item.InternalAttrib); Write32(item.ExternalAttrib); WRITE_32_VAL_SPEC(item.LocalHeaderPos, isPosition64); WriteBytes((const char *)item.Name, item.Name.Len()); if (isZip64) { Write16(NFileHeader::NExtraID::kZip64); Write16(zip64ExtraSize); if (isUnPack64) Write64(item.Size); if (isPack64) Write64(item.PackSize); if (isPosition64) Write64(item.LocalHeaderPos); } WriteTimeExtra(item, writeNtfs); WriteUtfName(item); WriteExtra(item.CentralExtra); if (commentSize != 0) WriteBytes(item.Comment, commentSize); } void COutArchive::WriteCentralDir(const CObjectVector &items, const CByteBuffer *comment) { const UInt64 cdOffset = GetCurPos(); FOR_VECTOR (i, items) WriteCentralHeader(items[i]); const UInt64 cd64EndOffset = GetCurPos(); const UInt64 cdSize = cd64EndOffset - cdOffset; const bool cdOffset64 = DOES_NEED_ZIP64(cdOffset); const bool cdSize64 = DOES_NEED_ZIP64(cdSize); const bool items64 = items.Size() >= 0xFFFF; const bool isZip64 = (cdOffset64 || cdSize64 || items64); // isZip64 = true; // to test Zip64 if (isZip64) { Write32(NSignature::kEcd64); Write64(kEcd64_MainSize); // to test extra block: // const UInt32 extraSize = 1 << 26; // Write64(kEcd64_MainSize + extraSize); Write16(45); // made by version Write16(45); // extract version Write32(0); // ThisDiskNumber = 0; Write32(0); // StartCentralDirectoryDiskNumber;; Write64((UInt64)items.Size()); Write64((UInt64)items.Size()); Write64((UInt64)cdSize); Write64((UInt64)cdOffset); // for (UInt32 iii = 0; iii < extraSize; iii++) Write8(1); Write32(NSignature::kEcd64Locator); Write32(0); // number of the disk with the start of the zip64 end of central directory Write64(cd64EndOffset); Write32(1); // total number of disks } Write32(NSignature::kEcd); Write16(0); // ThisDiskNumber = 0; Write16(0); // StartCentralDirectoryDiskNumber; Write16((UInt16)(items64 ? 0xFFFF: items.Size())); Write16((UInt16)(items64 ? 0xFFFF: items.Size())); WRITE_32_VAL_SPEC(cdSize, cdSize64); WRITE_32_VAL_SPEC(cdOffset, cdOffset64); const UInt16 commentSize = (UInt16)(comment ? comment->Size() : 0); Write16((UInt16)commentSize); if (commentSize != 0) WriteBytes((const Byte *)*comment, commentSize); m_OutBuffer.FlushWithCheck(); } void COutArchive::CreateStreamForCompressing(CMyComPtr &outStream) { COffsetOutStream *streamSpec = new COffsetOutStream; outStream = streamSpec; streamSpec->Init(m_Stream, m_Base + m_CurPos); } void COutArchive::CreateStreamForCopying(CMyComPtr &outStream) { outStream = m_Stream; } }}