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 support for FileWrapper to OLEFile #9

Merged
merged 10 commits into from
Jan 18, 2021
15 changes: 8 additions & 7 deletions Sources/OLEKit/DataStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,26 @@ public final class DataReader: Reader {
let data: Data

/// Current byte offset within the stream.
var byteOffset = 0
var byteOffset: Int

init(_ data: Data) {
self.data = data
byteOffset = data.startIndex
}

public var totalBytes: Int {
data.count
}

public func seek(toOffset offset: Int) {
precondition(offset < data.count)
precondition(offset < data.endIndex)

byteOffset = offset
}

/// Read a single byte from the stream and increment `byteOffset` by 1.
public func read() -> UInt8 {
precondition(byteOffset + 1 <= data.count)
precondition(byteOffset + 1 <= data.endIndex)
defer { byteOffset += 1 }

return data[byteOffset]
Expand All @@ -58,7 +59,7 @@ public final class DataReader: Reader {
/// Read two bytes in little-endian order as a single `UInt16` value and
/// increment `byteOffset` by 2.
public func read() -> UInt16 {
precondition(byteOffset + 2 <= data.count)
precondition(byteOffset + 2 <= data.endIndex)
defer { byteOffset += 2 }

return (UInt16(data[byteOffset + 1]) << 8) + UInt16(data[byteOffset])
Expand All @@ -67,7 +68,7 @@ public final class DataReader: Reader {
/// Read four bytes in little-endian order as a single `UInt32` value and
/// increment `byteOffset` by 4.
public func read() -> UInt32 {
precondition(byteOffset + 4 <= data.count)
precondition(byteOffset + 4 <= data.endIndex)
defer { byteOffset += 4 }

return (UInt32(data[byteOffset + 3]) << 24)
Expand All @@ -78,7 +79,7 @@ public final class DataReader: Reader {

/// Read a given `count` of bytes as raw data and increment `byteOffset` by `count`.
public func readData(ofLength length: Int) -> Data {
precondition(byteOffset + length <= data.count)
precondition(byteOffset + length <= data.endIndex)
defer { byteOffset += length }

return data[byteOffset..<byteOffset + length]
Expand All @@ -89,6 +90,6 @@ public final class DataReader: Reader {

defer { byteOffset = data.count - 1 }

return data[byteOffset..<data.count]
return data[byteOffset..<data.endIndex]
}
}
8 changes: 4 additions & 4 deletions Sources/OLEKit/DirectoryEntry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,11 @@ public struct DirectoryEntry: Equatable {
private static func entries(
index: UInt32,
at sectorID: UInt32,
in fileHandle: FileHandle,
in reader: Reader,
_ header: Header,
fat: [UInt32]
) throws -> [DirectoryEntry] {
var stream = try fileHandle.oleStream(
var stream = try reader.oleStream(
sectorID: sectorID,
firstSectorOffset: UInt64(header.sectorSize),
sectorSize: header.sectorSize,
Expand All @@ -164,10 +164,10 @@ public struct DirectoryEntry: Equatable {

static func entries(
rootAt sectorID: UInt32,
in fileHandle: FileHandle,
in reader: Reader,
_ header: Header,
fat: [UInt32]
) throws -> [DirectoryEntry] {
try Self.entries(index: 0, at: sectorID, in: fileHandle, header, fat: fat)
try Self.entries(index: 0, at: sectorID, in: reader, header, fat: fat)
}
}
4 changes: 2 additions & 2 deletions Sources/OLEKit/FAT.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ enum SectorID: UInt32 {
Additional sectors are described by DIFAT blocks */
private let maxFATSectorsCount: UInt32 = 109

extension FileHandle {
extension Reader {
func loadSector(_ header: Header, index: UInt32) throws -> DataReader {
let sectorOffset = UInt64(header.sectorSize) * UInt64(index + 1)

guard sectorOffset < header.fileSize
else { throw OLEError.invalidFATSector(byteOffset: sectorOffset) }

seek(toFileOffset: sectorOffset)
seek(toOffset: Int(sectorOffset))
return DataReader(readData(ofLength: Int(header.sectorSize)))
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/OLEKit/Header.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ private let magic = Data([0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1])
SECT _sectFat[109]; // [4CH,436] the SECTs of first 109 FAT sectors
};
*/
struct Header {
struct Header: Equatable {
/// Size of the header, which is a sum of sizes of all fields from the spec.
static let sizeInBytes = 76

Expand Down
39 changes: 29 additions & 10 deletions Sources/OLEKit/OLEFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import Foundation

public final class OLEFile {
private var fileHandle: FileHandle
private var reader: Reader
let header: Header

/// File Allocation Table, also known as SAT – Sector Allocation Table
Expand All @@ -29,7 +29,7 @@ public final class OLEFile {

public let root: DirectoryEntry

public init(_ path: String) throws {
public convenience init(_ path: String) throws {
guard FileManager.default.fileExists(atPath: path)
else { throw OLEError.fileDoesNotExist(path) }

Expand All @@ -40,26 +40,45 @@ public final class OLEFile {
guard let fileHandle = FileHandle(forReadingAtPath: path)
else { throw OLEError.fileNotAvailableForReading(path: path) }

self.fileHandle = fileHandle
let allData = fileHandle.readDataToEndOfFile()
fileHandle.seek(toFileOffset: UInt64(0))

try self.init(data: allData, fileSize: fileSize, path: path)
}

#if os(iOS) || os(watchOS) || os(tvOS) || os(macOS)

public convenience init(_ fileWrapper: FileWrapper) throws {
let fileName = fileWrapper.filename ?? ""

guard
let data = fileWrapper.regularFileContents,
let fileSize = fileWrapper.fileAttributes[FileAttributeKey.size.rawValue] as? Int
else { throw OLEError.fileDoesNotExist(fileName) }

try self.init(data: data, fileSize: fileSize, path: fileName)
}

#endif

private init(data: Data, fileSize: Int, path: String) throws {
guard fileSize >= 512
else { throw OLEError.incompleteHeader }

let data = fileHandle.readData(ofLength: 512)

var stream = DataReader(data)
reader = DataReader(data)
var stream = DataReader(data[..<512])
header = try Header(&stream, fileSize: fileSize, path: path)

fat = try fileHandle.loadFAT(headerStream: &stream, header)
fat = try reader.loadFAT(headerStream: &stream, header)

root = try DirectoryEntry.entries(
rootAt: header.firstDirectorySector,
in: fileHandle,
in: reader,
header,
fat: fat
)[0]

miniFAT = try fileHandle.loadMiniFAT(header, root: root, fat: fat)
miniFAT = try reader.loadMiniFAT(header, root: root, fat: fat)
}

/// Return an instance of `DataReader` that contains a given stream entry
Expand Down Expand Up @@ -89,7 +108,7 @@ public final class OLEFile {

/// Always loads data according to FAT ignoring `miniStream` and `miniFAT`
func streamForceFAT(_ entry: DirectoryEntry) throws -> DataReader {
try fileHandle.oleStream(
try reader.oleStream(
sectorID: entry.firstStreamSector,
expectedStreamSize: entry.streamSize,
firstSectorOffset: UInt64(header.sectorSize),
Expand Down
7 changes: 1 addition & 6 deletions Sources/OLEKit/Reader.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import Foundation

/// Helper protocol that presents a unified interface for both `FileHandle` and `DataReader`.
/// Helper protocol that presents a unified interface for `FileHandle`, `FileWrapper` and `DataReader`.
protocol Reader: AnyObject {
func seek(toOffset: Int)
func readData(ofLength: Int) -> Data
func readDataToEnd() -> Data
}

extension FileHandle: Reader {
func seek(toOffset offset: Int) { seek(toFileOffset: UInt64(offset)) }
func readDataToEnd() -> Data { readDataToEndOfFile() }
}
17 changes: 17 additions & 0 deletions Tests/OLEKitTests/OLEKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,21 @@ final class OLEKitTests: XCTestCase {
"FileHeader",
])
}

#if os(iOS) || os(watchOS) || os(tvOS) || os(macOS)

func testOLEFileFromFileWrapper() throws {
let url = URL(fileURLWithPath: #file)
.deletingLastPathComponent()
.appendingPathComponent("blank.hwp")
let wrapper = try FileWrapper(url: url, options: .immediate)
let oleFromPath = try OLEFile(url.path)
let oleFromWrapper = try OLEFile(wrapper)
XCTAssertEqual(oleFromPath.header, oleFromWrapper.header)
XCTAssertEqual(oleFromPath.fat, oleFromWrapper.fat)
XCTAssertEqual(oleFromPath.miniFAT, oleFromWrapper.miniFAT)
XCTAssertEqual(oleFromPath.root, oleFromWrapper.root)
}

#endif
}