Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add extraction of entry ranges #350

Open
wants to merge 2 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions Sources/ZIPFoundation/Archive+Reading.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,111 @@
}
return checksum
}

Check failure on line 120 in Sources/ZIPFoundation/Archive+Reading.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
/// Read a portion of a ZIP `Entry` from the receiver and forward its contents to a `Consumer` closure.
///
/// - Parameters:
/// - range: The portion range in the (decompressed) entry.
/// - entry: The ZIP `Entry` to read.
/// - bufferSize: The maximum size of the read buffer and the decompression buffer (if needed).
/// - consumer: A closure that consumes contents of `Entry` as `Data` chunks.
/// - Throws: An error if the destination file cannot be written or the entry contains malformed content.
public func extractRange(
_ range: Range<UInt64>,
of entry: Entry,
bufferSize: Int = defaultReadChunkSize,
consumer: Consumer
) throws {
guard entry.type == .file else {
throw ArchiveError.entryIsNotAFile
}
guard bufferSize > 0 else {
throw ArchiveError.invalidBufferSize
}
guard range.lowerBound >= 0, range.upperBound <= entry.uncompressedSize else {
throw ArchiveError.rangeOutOfBounds
}
let localFileHeader = entry.localFileHeader
guard entry.dataOffset <= .max else {
throw ArchiveError.invalidLocalHeaderDataOffset
}

Check failure on line 148 in Sources/ZIPFoundation/Archive+Reading.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
guard let compressionMethod = CompressionMethod(rawValue: localFileHeader.compressionMethod) else {
throw ArchiveError.invalidCompressionMethod
}

Check failure on line 152 in Sources/ZIPFoundation/Archive+Reading.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
switch compressionMethod {
case .none:
try extractStoredRange(range, of: entry, bufferSize: bufferSize, consumer: consumer)

Check failure on line 156 in Sources/ZIPFoundation/Archive+Reading.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
case .deflate:
try extractCompressedRange(range, of: entry, bufferSize: bufferSize, consumer: consumer)
}
}

Check failure on line 161 in Sources/ZIPFoundation/Archive+Reading.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
/// Ranges of stored entries can be accessed directly, as the requested
/// indices match the ones in the archive file.
private func extractStoredRange(
_ range: Range<UInt64>,
of entry: Entry,
bufferSize: Int,
consumer: Consumer
) throws {
fseeko(archiveFile, off_t(entry.dataOffset + range.lowerBound), SEEK_SET)

Check failure on line 171 in Sources/ZIPFoundation/Archive+Reading.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
_ = try Data.consumePart(
of: Int64(range.count),
chunkSize: bufferSize,
skipCRC32: true,
provider: { pos, chunkSize -> Data in
try Data.readChunk(of: chunkSize, from: self.archiveFile)
},
consumer: consumer
)
}

Check failure on line 182 in Sources/ZIPFoundation/Archive+Reading.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
/// Ranges of deflated entries cannot be accessed randomly. We must read
/// and inflate the entry from the start until we reach the requested range.
private func extractCompressedRange(
_ range: Range<UInt64>,
of entry: Entry,
bufferSize: Int,
consumer: Consumer
) throws {
var bytesRead: UInt64 = 0

Check failure on line 192 in Sources/ZIPFoundation/Archive+Reading.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
do {
fseeko(archiveFile, off_t(entry.dataOffset), SEEK_SET)

Check failure on line 195 in Sources/ZIPFoundation/Archive+Reading.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
_ = try readCompressed(
entry: entry,
bufferSize: bufferSize,
skipCRC32: true
) { chunk in
let chunkSize = UInt64(chunk.count)

Check failure on line 202 in Sources/ZIPFoundation/Archive+Reading.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
if bytesRead >= range.lowerBound {
if bytesRead + chunkSize > range.upperBound {
let remainingBytes = range.upperBound - bytesRead
try consumer(chunk[..<remainingBytes])
} else {
try consumer(chunk)
}
} else if bytesRead + chunkSize > range.lowerBound {
// Calculate the overlap and pass the relevant portion of the chunk
let start = range.lowerBound - bytesRead
let end = Swift.min(chunkSize, range.upperBound - bytesRead)
try consumer(chunk[start..<end])
}

bytesRead += chunkSize

guard bytesRead < range.upperBound else {
throw EndOfRange()
}
}
} catch is EndOfRange { }
}

private struct EndOfRange: Error {}
}
4 changes: 4 additions & 0 deletions Sources/ZIPFoundation/Archive.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ public final class Archive: Sequence {
case missingEndOfCentralDirectoryRecord
/// Thrown when an entry contains a symlink pointing to a path outside the destination directory.
case uncontainedSymlink
/// Thrown when the requested range is out of bounds for the entry.
case rangeOutOfBounds
/// The requested entry is not a file but a directory or symlink.
case entryIsNotAFile
}

/// The access mode for an `Archive`.
Expand Down
Loading