Skip to content

Instantly share code, notes, and snippets.

@VaslD
Created March 9, 2023 18:03
Show Gist options
  • Save VaslD/1eaee19546112b04052cfda22a3cb05d to your computer and use it in GitHub Desktop.
Save VaslD/1eaee19546112b04052cfda22a3cb05d to your computer and use it in GitHub Desktop.
Apple Archive
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