Archive packaging menu, auto refresh, trash favorite, test.zip/test.ipa

This commit is contained in:
admin committed 2026-11-12 14:07:57 +08:00
1 parent 718bf032c8
commit 71e5d72561
11 files changed
+954 -83

No files matched your search

+480
View File
@@ -43,6 +43,24 @@ extern const char *archive_error_string(WQArchiveReader *);
extern int archive_read_close(WQArchiveReader *);
extern int archive_read_free(WQArchiveReader *);
typedef struct archive WQArchiveWriter;
extern WQArchiveWriter *archive_write_new(void);
extern int archive_write_set_format_zip(WQArchiveWriter *);
extern int archive_write_set_format_pax_restricted(WQArchiveWriter *);
extern int archive_write_add_filter_gzip(WQArchiveWriter *);
extern int archive_write_open_filename(WQArchiveWriter *, const char *);
extern int archive_write_header(WQArchiveWriter *, WQArchiveEntry *);
extern ssize_t archive_write_data(WQArchiveWriter *, const void *, size_t);
extern int archive_write_close(WQArchiveWriter *);
extern int archive_write_free(WQArchiveWriter *);
extern WQArchiveEntry *archive_entry_new(void);
extern void archive_entry_free(WQArchiveEntry *);
extern void archive_entry_set_pathname(WQArchiveEntry *, const char *);
extern void archive_entry_set_filetype(WQArchiveEntry *, unsigned int);
extern void archive_entry_set_perm(WQArchiveEntry *, unsigned int);
extern void archive_entry_set_size(WQArchiveEntry *, long long);
extern void archive_entry_set_symlink(WQArchiveEntry *, const char *);
static NSString *WQArchiveSafeRelativePath(const char *name)
{
if (!name) return nil;
@@ -173,6 +191,468 @@ BOOL WQExtractArchive(NSString *archivePath, NSString *destination,
return YES;
}
static NSString *WQZipRelativePath(NSString *path, NSString *base)
{
if (!path.length) return nil;
if (base.length) {
NSString *canonicalPath = path.stringByStandardizingPath;
NSString *canonicalBase = base.stringByStandardizingPath;
NSString *prefix = [canonicalBase hasSuffix:@"/"] ? canonicalBase :
[canonicalBase stringByAppendingString:@"/"];
if ([canonicalPath hasPrefix:prefix])
return [canonicalPath substringFromIndex:prefix.length];
if ([canonicalPath isEqualToString:canonicalBase])
return canonicalPath.lastPathComponent;
}
if (![path isAbsolutePath]) {
NSString *relative = path.stringByStandardizingPath;
while ([relative hasPrefix:@"./"])
relative = [relative substringFromIndex:2];
return relative;
}
return path.lastPathComponent;
}
static BOOL WQZipAddPath(WQArchiveWriter *writer, NSString *path,
NSString *relative, NSString *prefix,
NSString *destination, NSString **message)
{
if ([path isEqualToString:destination]) return YES;
struct stat st = {0};
if (lstat(path.fileSystemRepresentation, &st) != 0) {
if (message) *message = [NSString stringWithFormat:@"zip: %@: %s",
path, strerror(errno)];
return NO;
}
if (!relative.length || [relative hasPrefix:@"/"] ||
[relative containsString:@".."]) {
if (message) *message = @"zip: unsafe archive path";
return NO;
}
WQArchiveEntry *entry = archive_entry_new();
if (!entry) {
if (message) *message = @"zip: cannot allocate entry";
return NO;
}
NSString *entryPath = prefix.length
? [prefix stringByAppendingPathComponent:relative] : relative;
if (!entryPath.length) entryPath = relative;
if (S_ISDIR(st.st_mode)) {
NSString *name = [entryPath hasSuffix:@"/"] ? entryPath :
[entryPath stringByAppendingString:@"/"];
archive_entry_set_pathname(entry, name.UTF8String);
archive_entry_set_filetype(entry, WQ_AE_IFDIR);
archive_entry_set_perm(entry, st.st_mode & 0777);
archive_entry_set_size(entry, 0);
int result = archive_write_header(writer, entry);
archive_entry_free(entry);
if (result != WQ_ARCHIVE_OK) {
if (message) *message = @"zip: unable to write directory";
return NO;
}
NSArray<NSString *> *children = [NSFileManager.defaultManager
contentsOfDirectoryAtPath:path error:nil];
for (NSString *child in [children sortedArrayUsingSelector:
@selector(localizedStandardCompare:)]) {
NSString *childPath = [path stringByAppendingPathComponent:child];
NSString *childRelative = [relative stringByAppendingPathComponent:child];
if (!WQZipAddPath(writer, childPath, childRelative, prefix,
destination, message))
return NO;
}
return YES;
}
if (S_ISLNK(st.st_mode)) {
char target[PATH_MAX];
ssize_t length = readlink(path.fileSystemRepresentation, target,
sizeof(target) - 1);
if (length <= 0) {
archive_entry_free(entry);
if (message) *message = [NSString stringWithFormat:@"zip: %@: %s",
path, strerror(errno)];
return NO;
}
target[length] = 0;
NSString *symlinkPath = prefix.length
? [prefix stringByAppendingPathComponent:relative] : relative;
archive_entry_set_pathname(entry, symlinkPath.UTF8String);
archive_entry_set_filetype(entry, WQ_AE_IFLNK);
archive_entry_set_perm(entry, 0755);
archive_entry_set_symlink(entry, target);
int result = archive_write_header(writer, entry);
archive_entry_free(entry);
if (result != WQ_ARCHIVE_OK) {
if (message) *message = @"zip: unable to write symlink";
return NO;
}
return YES;
}
if (!S_ISREG(st.st_mode)) {
archive_entry_free(entry);
return YES;
}
archive_entry_set_pathname(entry, entryPath.UTF8String);
archive_entry_set_filetype(entry, WQ_AE_IFREG);
archive_entry_set_perm(entry, st.st_mode & 0777);
archive_entry_set_size(entry, st.st_size);
int result = archive_write_header(writer, entry);
if (result != WQ_ARCHIVE_OK) {
archive_entry_free(entry);
if (message) *message = @"zip: unable to write header";
return NO;
}
int descriptor = open(path.fileSystemRepresentation,
O_RDONLY | O_CLOEXEC);
if (descriptor < 0) {
archive_entry_free(entry);
if (message) *message = [NSString stringWithFormat:@"zip: %@: %s",
path, strerror(errno)];
return NO;
}
char buffer[65536];
BOOL ok = YES;
ssize_t got;
while ((got = read(descriptor, buffer, sizeof(buffer))) > 0) {
ssize_t written = 0;
while (written < got) {
ssize_t step = archive_write_data(writer, buffer + written,
(size_t)(got - written));
if (step <= 0) {
ok = NO;
break;
}
written += step;
}
if (!ok) break;
}
if (got < 0) ok = NO;
close(descriptor);
archive_entry_free(entry);
if (!ok && message) *message = [NSString stringWithFormat:@"zip: %@: %s",
path, strerror(errno)];
return ok;
}
BOOL WQCreateZipArchive(NSArray<NSString *> *paths, NSString *destination,
NSString *baseDirectory, NSString **message)
{
if (!paths.count || !destination.length) {
if (message) *message = @"zip: missing input or destination";
return NO;
}
WQArchiveWriter *writer = archive_write_new();
if (!writer) {
if (message) *message = @"zip: cannot allocate archive";
return NO;
}
if (archive_write_set_format_zip(writer) != WQ_ARCHIVE_OK ||
archive_write_open_filename(writer,
destination.fileSystemRepresentation) != WQ_ARCHIVE_OK) {
if (message) *message = @"zip: cannot open destination";
archive_write_free(writer);
return NO;
}
BOOL ok = YES;
for (NSString *path in paths) {
NSString *relative = WQZipRelativePath(path, baseDirectory);
if (!WQZipAddPath(writer, path, relative, nil, destination, message)) {
ok = NO;
break;
}
}
archive_write_close(writer);
archive_write_free(writer);
if (ok && message)
*message = [NSString stringWithFormat:@"zip: %lu item(s) -> %@",
(unsigned long)paths.count, destination];
return ok;
}
static BOOL WQCreateArchiveWithPrefix(NSArray<NSString *> *paths,
NSString *destination,
NSString *baseDirectory,
NSString *prefix,
BOOL zip,
NSString **message)
{
if (!paths.count || !destination.length) {
if (message) *message = @"archive: missing input or destination";
return NO;
}
[NSFileManager.defaultManager removeItemAtPath:destination error:nil];
WQArchiveWriter *writer = archive_write_new();
if (!writer) {
if (message) *message = @"archive: cannot allocate writer";
return NO;
}
int result = zip ? archive_write_set_format_zip(writer)
: archive_write_set_format_pax_restricted(writer);
if (result != WQ_ARCHIVE_OK ||
(!zip && archive_write_add_filter_gzip(writer) != WQ_ARCHIVE_OK) ||
archive_write_open_filename(writer,
destination.fileSystemRepresentation) != WQ_ARCHIVE_OK) {
if (message) *message = @"archive: cannot open destination";
archive_write_free(writer);
return NO;
}
BOOL ok = YES;
for (NSString *path in paths) {
NSString *relative = WQZipRelativePath(path, baseDirectory);
if (!WQZipAddPath(writer, path, relative, prefix, destination,
message)) {
ok = NO;
break;
}
}
archive_write_close(writer);
archive_write_free(writer);
if (ok && message)
*message = [NSString stringWithFormat:@"archive: %lu item(s) -> %@",
(unsigned long)paths.count, destination];
return ok;
}
BOOL WQCreateZipArchiveWithPrefix(NSArray<NSString *> *paths,
NSString *destination,
NSString *baseDirectory,
NSString *prefix,
NSString **message)
{
return WQCreateArchiveWithPrefix(paths, destination, baseDirectory,
prefix, YES, message);
}
static BOOL WQCreateTarGz(NSArray<NSString *> *paths, NSString *destination,
NSString *baseDirectory, NSString *prefix,
NSString **message)
{
return WQCreateArchiveWithPrefix(paths, destination, baseDirectory,
prefix, NO, message);
}
static void WQArAppendMember(NSMutableData *archive, NSString *name,
NSData *data)
{
char header[61] = {0};
snprintf(header, sizeof(header),
"%-16s%-12s%-6s%-6s%-8s%-10s`\n",
name.UTF8String, "0", "0", "0", "100644",
[[NSString stringWithFormat:@"%lu", (unsigned long)data.length]
UTF8String]);
[archive appendBytes:header length:60];
[archive appendData:data];
if (data.length & 1) {
char padding = '\n';
[archive appendBytes:&padding length:1];
}
}
static NSString *WQSafePackageIdentifier(NSString *value)
{
NSMutableString *result = [NSMutableString string];
for (NSUInteger i = 0; i < value.length; i++) {
unichar c = [value characterAtIndex:i];
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || c == '.' || c == '-' || c == '_')
[result appendFormat:@"%C", c];
else
[result appendString:@"-"];
}
NSString *trimmed = [result stringByTrimmingCharactersInSet:
[NSCharacterSet characterSetWithCharactersInString:@"-._"]];
return trimmed.length ? trimmed.lowercaseString : @"com.filzaslop.package";
}
BOOL WQCreateDebPackage(NSString *source, NSString *destination,
NSString *packageID, NSString *displayName,
NSString *version, NSString *description,
NSString **message)
{
NSFileManager *fm = NSFileManager.defaultManager;
BOOL isDirectory = NO;
if (![fm fileExistsAtPath:source isDirectory:&isDirectory]) {
if (message) *message = @"deb: source not found";
return NO;
}
NSString *name = displayName.length ? displayName :
source.lastPathComponent.stringByDeletingPathExtension;
NSString *package = packageID.length ? packageID :
[@"com.filzaslop." stringByAppendingString:
WQSafePackageIdentifier(name)];
NSString *controlText = nil;
NSString *existingControl = [[source stringByAppendingPathComponent:@"DEBIAN"]
stringByAppendingPathComponent:@"control"];
if (isDirectory)
controlText = [NSString stringWithContentsOfFile:existingControl
encoding:NSUTF8StringEncoding error:nil];
if (!controlText.length) {
controlText = [NSString stringWithFormat:
@"Package: %@\n"
@"Name: %@\n"
@"Version: %@\n"
@"Architecture: iphoneos-arm64\n"
@"Maintainer: FilzaSlop <filzaslop@localhost>\n"
@"Section: Tweaks\n"
@"Priority: optional\n"
@"Description: %@\n",
package, name, version.length ? version : @"1.0",
description.length ? description :
[NSString stringWithFormat:@"Packaged from %@ by Filza 27.2.",
name]];
}
NSString *temp = [NSTemporaryDirectory() stringByAppendingPathComponent:
[NSString stringWithFormat:@"WQDeb-%@", NSUUID.UUID.UUIDString]];
NSString *controlDir = [temp stringByAppendingPathComponent:@"control"];
NSString *controlPath = [controlDir stringByAppendingPathComponent:@"control"];
NSString *controlTar = [temp stringByAppendingPathComponent:@"control.tar.gz"];
NSString *dataTar = [temp stringByAppendingPathComponent:@"data.tar.gz"];
NSString *debianBinary = [temp stringByAppendingPathComponent:@"debian-binary"];
[fm createDirectoryAtPath:controlDir withIntermediateDirectories:YES
attributes:nil error:nil];
[controlText writeToFile:controlPath atomically:YES
encoding:NSUTF8StringEncoding error:nil];
[@"2.0\n" writeToFile:debianBinary atomically:YES
encoding:NSUTF8StringEncoding error:nil];
NSString *error = nil;
if (!WQCreateTarGz(@[controlPath], controlTar, controlDir, nil, &error)) {
if (message) *message = [@"deb control: " stringByAppendingString:
(error ?: @"failed")];
[fm removeItemAtPath:temp error:nil];
return NO;
}
NSArray<NSString *> *dataPaths = nil;
NSString *dataBase = nil;
NSString *dataPrefix = nil;
if (isDirectory && [source.pathExtension.lowercaseString isEqualToString:@"app"]) {
dataPaths = @[source];
dataBase = source.stringByDeletingLastPathComponent;
dataPrefix = @"Applications";
} else if (isDirectory) {
NSArray *children = [fm contentsOfDirectoryAtPath:source error:nil];
NSMutableArray *paths = [NSMutableArray array];
for (NSString *child in children ?: @[]) {
if ([child isEqualToString:@"DEBIAN"]) continue;
[paths addObject:[source stringByAppendingPathComponent:child]];
}
dataPaths = paths;
dataBase = source;
} else {
dataPaths = @[source];
dataBase = source.stringByDeletingLastPathComponent;
}
if (!WQCreateTarGz(dataPaths ?: @[], dataTar, dataBase, dataPrefix,
&error)) {
if (message) *message = [@"deb data: " stringByAppendingString:
(error ?: @"failed")];
[fm removeItemAtPath:temp error:nil];
return NO;
}
NSData *controlData = [NSData dataWithContentsOfFile:controlTar];
NSData *dataData = [NSData dataWithContentsOfFile:dataTar];
NSData *binaryData = [NSData dataWithContentsOfFile:debianBinary];
if (!controlData.length || !dataData.length || !binaryData.length) {
if (message) *message = @"deb: temporary archive read failed";
[fm removeItemAtPath:temp error:nil];
return NO;
}
NSMutableData *ar = [NSMutableData dataWithBytes:"!<arch>\n" length:8];
WQArAppendMember(ar, @"debian-binary", binaryData);
WQArAppendMember(ar, @"control.tar.gz", controlData);
WQArAppendMember(ar, @"data.tar.gz", dataData);
BOOL ok = [ar writeToFile:destination atomically:YES];
[fm removeItemAtPath:temp error:nil];
if (ok && message)
*message = [NSString stringWithFormat:@"deb: %@ -> %@", package,
destination];
else if (!ok && message)
*message = @"deb: cannot write destination";
return ok;
}
BOOL WQCreateIpaPackage(NSString *source, NSString *destination,
NSString **message)
{
NSFileManager *fm = NSFileManager.defaultManager;
BOOL isDirectory = NO;
if (![fm fileExistsAtPath:source isDirectory:&isDirectory] || !isDirectory) {
if (message) *message = @"ipa: source must be a folder";
return NO;
}
NSArray<NSString *> *paths = nil;
NSString *base = nil;
NSString *prefix = nil;
NSString *payload = [source stringByAppendingPathComponent:@"Payload"];
BOOL payloadIsDirectory = NO;
if ([fm fileExistsAtPath:payload isDirectory:&payloadIsDirectory] &&
payloadIsDirectory) {
paths = @[payload];
base = source;
} else if ([source.pathExtension.lowercaseString isEqualToString:@"app"]) {
paths = @[source];
base = source.stringByDeletingLastPathComponent;
prefix = @"Payload";
} else {
NSString *appPath = nil;
for (NSString *child in [fm contentsOfDirectoryAtPath:source error:nil]) {
if ([child.pathExtension.lowercaseString isEqualToString:@"app"]) {
appPath = [source stringByAppendingPathComponent:child];
break;
}
}
if (appPath) {
paths = @[appPath];
base = source;
prefix = @"Payload";
}
}
if (!paths.count) {
if (message) *message = @"ipa: no Payload/ or .app found";
return NO;
}
BOOL ok = WQCreateZipArchiveWithPrefix(paths, destination, base, prefix,
message);
if (ok && message)
*message = [NSString stringWithFormat:@"ipa: -> %@", destination];
return ok;
}
BOOL WQExtractDebPayload(NSString *debPath, NSString *destination,
NSString **message)
{
NSFileManager *fm = NSFileManager.defaultManager;
NSString *temp = [NSTemporaryDirectory() stringByAppendingPathComponent:
[NSString stringWithFormat:@"WQDebX-%@", NSUUID.UUID.UUIDString]];
[fm createDirectoryAtPath:temp withIntermediateDirectories:YES
attributes:nil error:nil];
NSString *error = nil;
if (!WQExtractArchive(debPath, temp, &error)) {
[fm removeItemAtPath:temp error:nil];
if (message) *message = error ?: @"deb: cannot unpack container";
return NO;
}
NSString *dataArchive = nil;
for (NSString *name in [fm contentsOfDirectoryAtPath:temp error:nil]) {
if ([name hasPrefix:@"data.tar"]) {
dataArchive = [temp stringByAppendingPathComponent:name];
break;
}
}
if (!dataArchive.length) {
[fm removeItemAtPath:temp error:nil];
if (message) *message = @"deb: data.tar not found";
return NO;
}
BOOL ok = WQExtractArchive(dataArchive, destination, &error);
[fm removeItemAtPath:temp error:nil];
if (message) *message = ok ? [NSString stringWithFormat:
@"deb: payload -> %@", destination] : (error ?: @"deb: payload failed");
return ok;
}
NSString *WQDefaultExtractDestination(NSString *archivePath)
{
NSString *name = archivePath.lastPathComponent;