Repository navigation
Conversation
Add ZipFileReader, ZipFileDataSource and ZipFileWriter, along with the ZLib and CZLib modules they use for deflate compression. ZipFileReader also conforms to ReadOnlyFileManagerProtocol, so that the contents of a zip file can be used wherever a read-only file system is expected. This raises the minimum macOS deployment target to 15 for the span types the reader uses. rdar://190233135
On Windows, we need to fetch and build zlib, because SwiftPM can't do that on its own. Also, we no longer support Swift 6.1, so exclude that version. rdar://190233135
|
@swift-ci Please test |
The Zip code was taken from a separate repo elsewhere, which is why it was all `public`. Make it `package` scope here as we don't need it outside of DocC (and we don't want the DocC repo to become a source for zip functionality). rdar://190233135
There is currently a problem preventing us from using `Span` types in this repository. Add some shims for now. We can remove these when the problem is resolved. rdar://190233135
|
@swift-ci Please test |
d-ronnqvist
left a comment
There was a problem hiding this comment.
This is a very large change and I've only started to look at it but here's some initial feedback from that first pass.
| do { | ||
| let data = try contents(of: URL(filePath: path)) | ||
| return data | ||
| } catch { | ||
| return nil | ||
| } |
There was a problem hiding this comment.
minor: I think this can be written more simply using try?
| do { | |
| let data = try contents(of: URL(filePath: path)) | |
| return data | |
| } catch { | |
| return nil | |
| } | |
| try? contents(of: URL(filePath: path)) |
| let buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 65536, | ||
| alignment: 16) |
There was a problem hiding this comment.
It would be good to have a comment that explains this byte count and alignment.
| internal(set) package var name: String | ||
| internal(set) package var date: Date | ||
| internal(set) package var comment: String? | ||
|
|
||
| internal(set) package var flags: UInt16 | ||
| internal(set) package var compression: UInt16 | ||
| internal(set) package var crc32: UInt32 | ||
| internal(set) package var compressedSize: Int | ||
| internal(set) package var uncompressedSize: Int |
There was a problem hiding this comment.
Question: What's modifying these properties that require them to be internal(set)? Would it be possible to restrict this further (private(set) or fileprivate(set))?
There was a problem hiding this comment.
Probably. I'll try restricting them some more and see if anything breaks.
| while pos >= 0 { | ||
| let ch = span.bytes.unsafeLoad(fromByteOffset: pos, as: UInt8.self) | ||
| switch ch { | ||
| case 0x50: // 'P' |
There was a problem hiding this comment.
FYI: you can initialize a UInt8 value for an ascii literal to make this more readable.
| case 0x50: // 'P' | |
| case .init(ascii: "P"): |
There was a problem hiding this comment.
I think that's more typing and less readable, honestly. We're really trying to find a magic number here too — the fact the PK part is ASCII is largely irrelevant.
| let disk = try source.read(from: eocdOffset + 4, as: UInt16.self) | ||
| let directoryDisk = try source.read(from: eocdOffset + 6, as: UInt16.self) | ||
| let entriesOnDisk = try source.read(from: eocdOffset + 8, as: UInt16.self) | ||
| let totalEntries = try source.read(from: eocdOffset + 10, as: UInt16.self) | ||
|
|
||
| if disk != directoryDisk || entriesOnDisk != totalEntries { | ||
| throw ZipFileError.spannedArchivesNotSupported | ||
| } | ||
|
|
||
| let cdSize = Int(try source.read(from: eocdOffset + 12, as: UInt32.self)) | ||
| let cdOffset = Int(try source.read(from: eocdOffset + 16, as: UInt32.self)) | ||
|
|
||
| let resolvedDirectory = try resolveZip64EndOfCentralDirectory( | ||
| eocdOffset: eocdOffset, cdSize: cdSize, cdOffset: cdOffset) | ||
|
|
||
| let commentLength = try source.read(from: eocdOffset + 20, as: UInt16.self) |
There was a problem hiding this comment.
Rather than having to update the offset for each read like this, I wonder if it's be worth having a "cursor" that automatically updates by MemoryLayout<T>.size for each successful read... what do you think?
| if span.bytes.unsafeLoad(fromByteOffset: pos + 1, as: UInt8.self) == 0x4b | ||
| && span.bytes.unsafeLoad(fromByteOffset: pos + 2, as: UInt8.self) == 0x05 | ||
| && span.bytes.unsafeLoad(fromByteOffset: pos + 3, as: UInt8.self) == 0x06 | ||
| { |
There was a problem hiding this comment.
FYI: IIRC it's possible to do a 3 byte load using a tuple type
| if span.bytes.unsafeLoad(fromByteOffset: pos + 1, as: UInt8.self) == 0x4b | |
| && span.bytes.unsafeLoad(fromByteOffset: pos + 2, as: UInt8.self) == 0x05 | |
| && span.bytes.unsafeLoad(fromByteOffset: pos + 3, as: UInt8.self) == 0x06 | |
| { | |
| if span.bytes.unsafeLoad(fromByteOffset: pos + 1, as: (UInt8, UInt8, UInt8).self) == (0x4b, 0x05, 0x06) { |
There was a problem hiding this comment.
That's a nice improvement. I hadn't considered that.
There was a problem hiding this comment.
Are these types surfaced in the Zip module's API or can they have an internal access level?
There was a problem hiding this comment.
They are in the API; ZipFileReader's File type, for instance, has a read(into span: inout OutputRawSpan) method.
| try sink.write(UInt32(0x0606_4b50)) // Zip64 EOCD record signature | ||
| try sink.write(UInt64(44)) // size of remaining record (fixed part only) | ||
| try sink.write(UInt16(45)) // version made by | ||
| try sink.write(UInt16(45)) // version needed to extract | ||
| try sink.write(UInt32(0)) // number of this disk | ||
| try sink.write(UInt32(0)) // disk with the start of the central directory | ||
| try sink.write(UInt64(files.count)) // entries on this disk | ||
| try sink.write(UInt64(files.count)) // entries in total | ||
| try sink.write(UInt64(centralDirectorySize)) // size of central directory | ||
| try sink.write(UInt64(centralDirectoryOffset)) // offset of central directory | ||
|
|
||
| bytesWritten += 56 |
There was a problem hiding this comment.
Is there a way that the write operation could accumulate the number of bytes written to eliminate the risk that this manually tracked offset gets out of sync?
| try sink.write(UInt16(0)) | ||
| } | ||
|
|
||
| try sink.close() |
There was a problem hiding this comment.
Is it expected that the sink doesn't close if it encounters a write error along the way? In other words; should this close() call be in a defer statement earlier in this method body?
There was a problem hiding this comment.
That's a good idea. Let's put it in a defer at the top.
There was a problem hiding this comment.
I would rather that we don't commit any formatting configuration files.
There was a problem hiding this comment.
Drat. I hadn't intended to add that (again). I should add it to .gitignores or similar, maybe.
Add ZipFileReader, ZipFileDataSource and ZipFileWriter, along with the ZLib and CZLib modules they use for deflate compression. ZipFileReader also conforms to ReadOnlyFileManagerProtocol, so that the contents of a zip file can be used wherever a read-only file system is expected.
This raises the minimum macOS deployment target to 15 for the span types the reader uses.
rdar://190233135