// Copyright (c) 2017 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information import Foundation /// Represents a GZip archive's header. public struct GzipHeader { struct Flags { static let ftext: UInt8 = 0x01 static let fhcrc: UInt8 = 0x02 static let fextra: UInt8 = 0x04 static let fname: UInt8 = 0x08 static let fcomment: UInt8 = 0x10 } /// Supported compression methods in GZip archive. public enum CompressionMethod: Int { /// The only one supported compression method (Deflate). case deflate = 8 } /// Type of file system on which GZip archive was created. public enum FileSystemType: Int { /** One of many UNIX-like systems. - Note: It seems like modern macOS systems also fall into this category. */ case unix = 3 /// Older Macintosh systems. case macintosh = 7 /// File system used in Microsoft(TM)(R)(C) Windows(TM)(R)(C). case ntfs = 11 /// File system was unknown to the archiver. case unknown = 255 /// File system is one of the rare systems. case other = 256 } /// Compression method of archive. Currently, always equals to `.deflate`. public let compressionMethod: CompressionMethod /** The most recent modification time of the original file. If corresponding archive's field is set to 0, which means that no time was specified, then this property is `nil`. */ public let modificationTime: Date? /// Type of file system on which archivation took place. public let osType: FileSystemType /// Name of the original file. If archive doesn't contain file's name, then `nil`. public let fileName: String? /// Comment stored in archive. If archive doesn't contain any comment, then `nil`. public let comment: String? /// Check if file is likely to be text file or ASCII-file. public let isTextFile: Bool /** Initializes the structure with the values from the first 'member' of GZip `archive`. If data passed is not actually a GZip archive, `GzipError` will be thrown. - Parameter archive: Data archived with GZip. - Throws: `GzipError`. It may indicate that either archive is damaged or it might not be archived with GZip at all. */ public init(archive data: Data) throws { let pointerData = DataWithPointer(data: data) try self.init(pointerData) } init(_ pointerData: DataWithPointer) throws { // First two bytes should be correct 'magic' bytes let magic = pointerData.uint16() guard magic == 0x8b1f else { throw GzipError.wrongMagic } var headerBytes: [UInt8] = [0x1f, 0x8b] // Third byte is a method of compression. Only type 8 (DEFLATE) compression is supported let method = pointerData.byte() guard method == 8 else { throw GzipError.wrongCompressionMethod } headerBytes.append(method) self.compressionMethod = .deflate let flags = pointerData.byte() guard (flags & 0x20 == 0) && (flags & 0x40 == 0) && (flags & 0x80 == 0) else { throw GzipError.wrongFlags } headerBytes.append(flags) var mtime = 0 for i in 0..<4 { let byte = pointerData.byte() mtime |= byte.toInt() << (8 * i) headerBytes.append(byte) } self.modificationTime = mtime == 0 ? nil : Date(timeIntervalSince1970: TimeInterval(mtime)) let extraFlags = pointerData.byte() headerBytes.append(extraFlags) self.osType = FileSystemType(rawValue: pointerData.byte().toInt()) ?? .other headerBytes.append(self.osType.rawValue.toUInt8()) self.isTextFile = flags & Flags.ftext != 0 // Some archives may contain extra fields if flags & Flags.fextra != 0 { var xlen = 0 for i in 0..<2 { let byte = pointerData.byte() xlen |= byte.toInt() << (8 * i) headerBytes.append(byte) } for _ in 0..