@@ -53,15 +53,12 @@ public struct TarReader {
5353 }
5454
5555 /**
56- Processes the next TAR entry by reading it from the container and calling the provided closure on the result.
56+ Processes the next TAR entry by reading it from the container and calling the provided closure on the result. If
57+ the argument supplied to the closure is `nil` this indicates that the end of the input was reached.
5758
5859 On Darwin platforms both the reading and the call to the closure are performed inside the `autoreleasepool` which
5960 allows to reduce the peak memory usage.
6061
61- If the argument supplied to the closure is `nil` this indicates that the end of the input was reached. After that
62- the repeated `TarReader.process(_:)` or `TarReader.read()` calls will result in the `DataError.truncated` being
63- thrown.
64-
6562 - Throws: `DataError.truncated` if the input is truncated. `TarError` is thrown in case of malformed input. Errors
6663 thrown by `FileHandle` operations are also propagated.
6764 */
@@ -81,8 +78,7 @@ public struct TarReader {
8178 - Throws: `DataError.truncated` if the input is truncated. `TarError` is thrown in case of malformed input. Errors
8279 thrown by `FileHandle` operations are also propagated.
8380
84- - Returns: The next entry from the container or `nil` if the end of the input has been reached. After that the
85- repeated `TarReader.process(_:)` or `TarReader.read()` calls will result in the `DataError.truncated` being thrown.
81+ - Returns: The next entry from the container or `nil` if the end of the input has been reached.
8682 */
8783 public mutating func read( ) throws -> TarEntry ? {
8884 let headerData = try getData ( size: 512 )
@@ -94,6 +90,10 @@ public struct TarReader {
9490 if try getData ( size: 512 ) == Data ( count: 512 ) {
9591 return nil
9692 } else {
93+ // In this case we have a zero-filled block immediately followed by a non-zero-filled block which do not
94+ // match the EOF marker signature. In practice, this indicates a malformed TAR container, since a
95+ // zero-filled block is not a valid TAR header (and in fact the end result is an error being thrown in
96+ // TarHeader initializer later down the line).
9797 try set ( offset: offset)
9898 }
9999 } else if headerData. count < 512 {
@@ -162,12 +162,18 @@ public struct TarReader {
162162
163163 private func getData( size: Int ) throws -> Data {
164164 assert ( size >= 0 , " TarReader.getData(size:): negative size. " )
165- guard size > 0
166- else { return Data ( ) }
167165 if #available( macOS 10 . 15 . 4 , iOS 13 . 4 , watchOS 6 . 2 , tvOS 13 . 4 , * ) {
168- guard let chunkData = try handle. read ( upToCount: size)
169- else { throw DataError . truncated }
170- return chunkData
166+ // The documentation for FileHandle.read(upToCount:) is a bit misleading. This method does "return the data
167+ // obtained by reading length bytes starting at the current file pointer" even if the requested amount is
168+ // larger than the available data. What is not clear is when the method returns nil. Apparently, there are
169+ // (at least) two cases when it happens:
170+ // - the file pointer is at the EOF regardless of the argument value,
171+ // - the argument is zero.
172+ // It is also unclear what happens when the argument is negative (it seems that it reads everything until
173+ // the EOF), but the assertion above takes care of this. In any case, instead of returning nil we return
174+ // empty data since both of these situations logically seem equivalent for our purposes. This also allows us
175+ // to eliminate additional guard-check for the size parameter.
176+ return try handle. read ( upToCount: size) ?? Data ( )
171177 } else {
172178 // Technically, this can throw NSException, but since it is ObjC exception we cannot handle it in Swift.
173179 return handle. readData ( ofLength: size)
0 commit comments