bool CZipCentralDir::RemoveDataDescr(bool bFromBuffer) { // this will not work if there are bytes before zip CZipFileMapping fm; char* pFile; ZIP_SIZE_TYPE uSize; if (bFromBuffer) { uSize = m_pStorage->m_uBytesInWriteBuffer; pFile = m_pStorage->m_pWriteBuffer; } else { uSize = (ZIP_SIZE_TYPE)m_pStorage->m_pFile->GetLength(); // we cannot use CZipMemFile in multi-volume archive // so it must be CZipFile if (!fm.CreateMapping(static_cast<CZipFile*>(m_pStorage->m_pFile))) return false; pFile = fm.GetMappedMemory(); } ZIP_SIZE_TYPE uOffsetToChange = 4; ZIP_SIZE_TYPE uPosInBuffer = 0; WORD uExtraHeaderLen; ZIP_INDEX_TYPE uCount = (ZIP_INDEX_TYPE)m_pHeaders->GetSize(); for (ZIP_INDEX_TYPE i = 0; i < uCount; i++) { CZipFileHeader* pHeader = (*m_pHeaders)[(ZIP_ARRAY_SIZE_TYPE)i]; char* pSource = pFile + pHeader->m_uOffset; if (pHeader->NeedsDataDescriptor()) uExtraHeaderLen = (WORD)(pHeader->IsEncrypted() ? 0 : 4); else { uExtraHeaderLen = pHeader->GetDataDescriptorSize(true); // removing data descriptor pHeader->m_uFlag &= ~8; // update local header: // write modified flag in the local header CBytesWriter::WriteBytes(pSource + 6, pHeader->m_uFlag); pHeader->WriteSmallDataDescriptor(pSource + 14, false); } ZIP_SIZE_TYPE uToCopy = (i == (uCount - 1) ? uSize : (*m_pHeaders)[(ZIP_ARRAY_SIZE_TYPE)(i + 1)]->m_uOffset) - pHeader->m_uOffset - uExtraHeaderLen; if (uToCopy > 0) // TODO: [postponed] the size_t limit on uToCopy, but creating such a big segment is unlikely (at least at the moment of writing) memmove(pFile + uPosInBuffer, pSource, (size_t)uToCopy); uPosInBuffer += uToCopy; pHeader->m_uOffset -= uOffsetToChange; uOffsetToChange += uExtraHeaderLen; } if (bFromBuffer) m_pStorage->m_uBytesInWriteBuffer = (DWORD)uPosInBuffer; else { m_pStorage->m_uBytesWritten = uPosInBuffer; fm.RemoveMapping(); m_pStorage->m_pFile->SetLength((ZIP_FILE_USIZE)uPosInBuffer); } return true; }
void CZipCentralDir::Write() { if (m_pInfo->m_bInArchive) return; m_pInfo->m_uEntriesNumber = (ZIP_INDEX_TYPE)m_pHeaders->GetSize(); if (!m_pStorage->IsSegmented()) { m_pStorage->Flush(); m_pStorage->m_pFile->SeekToEnd(); } // else // we are at the end already m_pInfo->m_uSize = 0; bool bDontAllowVolumeChange = false; if (m_pStorage->IsSegmented()) { // segmentation signature at the beginning (4 bytes) + the size of the data descr. for each file ZIP_SIZE_TYPE uSize = GetSize(true); // if there is a segmented archive in creation and it is only one-volume, // (current volume number is 0 so far, no bytes has been written so we know they are // all in the buffer) make sure that it will be after writing central dir // and make it a not segmented archive if (m_pStorage->GetCurrentVolume() == 0) { // calculate the size of data descriptors already in the buffer or on the disk // (they will be removed in the not segmented archive). ZIP_SIZE_TYPE uToGrow = uSize - 4; for (ZIP_INDEX_TYPE i = 0; i < m_pInfo->m_uEntriesNumber; i++) { CZipFileHeader* pHeader = (*this)[i]; if (pHeader->NeedsDataDescriptor()) { if (!pHeader->IsEncrypted()) uToGrow -= 4; // remove the signature only } else uToGrow -= pHeader->GetDataDescriptorSize(true); } ZIP_SIZE_TYPE uVolumeFree = m_pStorage->VolumeLeft(); if (uVolumeFree >= uToGrow) // lets make sure it will be one-volume archive { // can the operation be done only in the buffer? if (!m_pStorage->m_uBytesWritten && // no bytes on the disk yet (m_pStorage->GetFreeInBuffer() >= uToGrow)) // is the buffer big enough? { RemoveDataDescr(true); bDontAllowVolumeChange = true; // if a volume change occurs somehow, we'll throw an error later } else { m_pStorage->Flush(); if (RemoveDataDescr(false)) bDontAllowVolumeChange = true; // if a volume change occurs somehow, we'll throw an error later } } } // make sure that in a segmented archive, the whole central directory will fit on the single volume if (!bDontAllowVolumeChange && !m_pStorage->IsBinarySplit()) m_pStorage->AssureFree(uSize); } try { WriteHeaders(bDontAllowVolumeChange || !m_pStorage->IsSegmented()); WriteCentralEnd(); if (bDontAllowVolumeChange) { if (m_pStorage->GetCurrentVolume() != 0) ThrowError(CZipException::badZipFile); } } catch (...) { if (bDontAllowVolumeChange) { m_pStorage->FinalizeSegm(); m_pInfo->m_uLastVolume = 0; } throw; } m_pInfo->m_bInArchive = true; }