Created
March 9, 2023 18:03
-
-
Save VaslD/1eaee19546112b04052cfda22a3cb05d to your computer and use it in GitHub Desktop.
Apple Archive
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import AppleArchive | |
import CryptoKit | |
import Foundation | |
import System | |
public enum AA { | |
public static let version = Int(APPLE_ARCHIVE_API_VERSION) | |
static let fields = ArchiveHeader.FieldKeySet("TYP,PAT,LNK,DEV,UID,GID,MOD,FLG,MTM,CTM,SH2,DAT,SIZ")! | |
// MARK: aa archive | |
/// `aa archive -d $source -o $destination -a $algorithm` | |
public static func archive(_ source: URL, to destination: URL, algorithm: ArchiveCompression = .lzfse) throws { | |
var isDirectory: ObjCBool = false | |
guard FileManager.default.fileExists(atPath: source.path, isDirectory: &isDirectory) else { | |
throw POSIXError(.ENOENT) | |
} | |
guard let fileOut = ArchiveByteStream.fileStream( | |
path: FilePath(destination.path), mode: .writeOnly, | |
options: [.create, .noFollow], permissions: FilePermissions(rawValue: 0o644)) else { | |
throw POSIXError(.EBADF) | |
} | |
defer { try? fileOut.close() } | |
guard let streamOut = ArchiveByteStream.compressionStream(using: algorithm, writingTo: fileOut, | |
blockSize: 4 * 1024 * 1024, | |
flags: [.archiveDeduplicateData]) else { | |
throw POSIXError(.EIO) | |
} | |
defer { try? streamOut.close() } | |
guard let encoder = ArchiveStream.encodeStream(writingTo: streamOut) else { | |
throw POSIXError(.EIO) | |
} | |
defer { try? encoder.close() } | |
if isDirectory.boolValue { | |
try encoder.writeDirectoryContents(archiveFrom: FilePath(source.path), keySet: fields) | |
} else { | |
let header = ArchiveHeader(keySet: fields, directory: FilePath(source.deletingLastPathComponent().path), | |
path: FilePath(source.lastPathComponent), flags: [])! | |
try Data(contentsOf: source, options: .mappedIfSafe).withUnsafeBytes { | |
header.append(.blob(key: ArchiveHeader.FieldKey("DAT"), size: UInt64($0.count))) | |
try encoder.writeHeader(header) | |
try encoder.writeBlob(key: ArchiveHeader.FieldKey("DAT"), from: $0) | |
} | |
} | |
} | |
/// `aa archive -d $source -o $destination -a $algorithm -key $key` | |
@available(macOS 12, iOS 15, macCatalyst 15, tvOS 15, watchOS 8, *) | |
public static func archive(_ source: URL, to destination: URL, algorithm: ArchiveCompression = .lzfse, | |
key: inout SymmetricKey!) throws { | |
var isDirectory: ObjCBool = false | |
guard FileManager.default.fileExists(atPath: source.path, isDirectory: &isDirectory) else { | |
throw POSIXError(.ENOENT) | |
} | |
guard let fileOut = ArchiveByteStream.fileStream( | |
path: FilePath(destination.path), mode: .writeOnly, | |
options: [.create, .noFollow], permissions: FilePermissions(rawValue: 0o644)) else { | |
throw POSIXError(.EBADF) | |
} | |
defer { try? fileOut.close() } | |
let context = ArchiveEncryptionContext(profile: .hkdf_sha256_aesctr_hmac__symmetric__none, | |
compressionAlgorithm: algorithm) | |
if key == nil { | |
key = SymmetricKey(size: .bits256) | |
} | |
try context.setSymmetricKey(key) | |
guard let streamOut = ArchiveByteStream.encryptionStream(writingTo: fileOut, encryptionContext: context, | |
flags: [.archiveDeduplicateData]) else { | |
throw POSIXError(.EIO) | |
} | |
defer { try? streamOut.close() } | |
guard let encoder = ArchiveStream.encodeStream(writingTo: streamOut) else { | |
throw POSIXError(.EIO) | |
} | |
defer { try? encoder.close() } | |
if isDirectory.boolValue { | |
try encoder.writeDirectoryContents(archiveFrom: FilePath(source.path), keySet: fields) | |
} else { | |
let header = ArchiveHeader(keySet: fields, directory: FilePath(source.deletingLastPathComponent().path), | |
path: FilePath(source.lastPathComponent), flags: [])! | |
try Data(contentsOf: source, options: .mappedIfSafe).withUnsafeBytes { | |
header.append(.blob(key: ArchiveHeader.FieldKey("DAT"), size: UInt64($0.count))) | |
try encoder.writeHeader(header) | |
try encoder.writeBlob(key: ArchiveHeader.FieldKey("DAT"), from: $0) | |
} | |
} | |
} | |
// MARK: aa list | |
public class State { | |
let decoder: ArchiveStream | |
let streams: [ArchiveByteStream] | |
init(_ decoder: ArchiveStream, _ streams: ArchiveByteStream...) { | |
self.decoder = decoder | |
self.streams = streams | |
} | |
deinit { | |
try? self.decoder.close() | |
streams.forEach { try? $0.close() } | |
} | |
func next() -> ArchiveHeader? { | |
try? self.decoder.readHeader() | |
} | |
} | |
/// `aa list -i $archive` | |
public static func list(_ archive: URL) throws -> UnfoldSequence<ArchiveHeader, State> { | |
guard let fileIn = ArchiveByteStream.fileStream( | |
path: FilePath(archive.path), mode: .readOnly, | |
options: [.noFollow], permissions: FilePermissions(rawValue: 0o644)) else { | |
throw POSIXError(.EBADF) | |
} | |
guard let streamIn = ArchiveByteStream.decompressionStream(readingFrom: fileIn) else { | |
throw POSIXError(.EIO) | |
} | |
guard let decoder = ArchiveStream.decodeStream(readingFrom: streamIn) else { | |
throw POSIXError(.EIO) | |
} | |
return sequence(state: State(decoder, streamIn, fileIn)) { $0.next() } | |
} | |
// MARK: aa extract | |
/// `aa extract -d $directory -i $archive` | |
public static func extract(_ archive: URL, to directory: URL) throws { | |
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) | |
guard let fileIn = ArchiveByteStream.fileStream( | |
path: FilePath(archive.path), mode: .readOnly, | |
options: [.noFollow], permissions: FilePermissions(rawValue: 0o644)) else { | |
throw POSIXError(.EBADF) | |
} | |
defer { try? fileIn.close() } | |
guard let streamIn = ArchiveByteStream.decompressionStream(readingFrom: fileIn) else { | |
throw POSIXError(.EIO) | |
} | |
defer { try? streamIn.close() } | |
guard let decoder = ArchiveStream.decodeStream(readingFrom: streamIn) else { | |
throw POSIXError(.EIO) | |
} | |
defer { try? decoder.close() } | |
guard let extractor = ArchiveStream.extractStream(extractingTo: FilePath(directory.path), | |
flags: .ignoreOperationNotPermitted) else { | |
throw POSIXError(.EIO) | |
} | |
defer { try? extractor.close() } | |
_ = try ArchiveStream.process(readingFrom: decoder, writingTo: extractor) | |
} | |
/// `aa extract -d $directory -i $archive -key $key` | |
@available(macOS 12, iOS 15, macCatalyst 15, tvOS 15, watchOS 8, *) | |
public static func extract(_ archive: URL, to directory: URL, key: SymmetricKey) throws { | |
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) | |
guard let fileIn = ArchiveByteStream.fileStream( | |
path: FilePath(archive.path), mode: .readOnly, | |
options: [.noFollow], permissions: FilePermissions(rawValue: 0o644)) else { | |
throw POSIXError(.EBADF) | |
} | |
defer { try? fileIn.close() } | |
let context = ArchiveEncryptionContext(profile: .hkdf_sha256_aesctr_hmac__symmetric__none, | |
compressionAlgorithm: .lzfse) | |
try context.setSymmetricKey(key) | |
guard let streamIn = ArchiveByteStream.decryptionStream(readingFrom: fileIn, encryptionContext: context) else { | |
throw POSIXError(.EIO) | |
} | |
defer { try? streamIn.close() } | |
guard let decoder = ArchiveStream.decodeStream(readingFrom: streamIn) else { | |
throw POSIXError(.EIO) | |
} | |
defer { try? decoder.close() } | |
guard let extractor = ArchiveStream.extractStream(extractingTo: FilePath(directory.path), | |
flags: .ignoreOperationNotPermitted) else { | |
throw POSIXError(.EIO) | |
} | |
defer { try? extractor.close() } | |
_ = try ArchiveStream.process(readingFrom: decoder, writingTo: extractor) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment