Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -202,10 +202,11 @@ OFRectValue.m \ OFSandbox.m \ OFSizeValue.m \ OFStrPTime.m \ OFSubarray.m \ + OFTarURIHandler.m \ OFUTF8String.m \ OFZIPURIHandler.m \ ${LIBBASES_M} \ ${RUNTIME_AUTORELEASE_M} \ ${RUNTIME_INSTANCE_M} \ Index: src/OFTarArchive.h ================================================================== --- src/OFTarArchive.h +++ src/OFTarArchive.h @@ -77,10 +77,21 @@ * archive. * @return A new, autoreleased OFTarArchive */ + (instancetype)archiveWithURI: (OFURI *)URI mode: (OFString *)mode; +/** + * @brief Creates a URI for accessing a the specified file within the specified + * tar archive. + * + * @param path The path of the file within the archive + * @param archive The URI of the archive + * @return A URI for accessing the specified file within the specified Tar + * archive + */ ++ (OFURI *)URIForFile: (OFString *)path inArchive: (OFURI *)archive; + - (instancetype)init OF_UNAVAILABLE; /** * @brief Initializes an already allocated OFTarArchive object with the * specified stream. Index: src/OFTarArchive.m ================================================================== --- src/OFTarArchive.m +++ src/OFTarArchive.m @@ -21,10 +21,11 @@ #import "OFTarArchiveEntry.h" #import "OFTarArchiveEntry+Private.h" #import "OFDate.h" #import "OFSeekableStream.h" #import "OFStream.h" +#import "OFURI.h" #import "OFURIHandler.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFNotOpenException.h" @@ -68,10 +69,34 @@ + (instancetype)archiveWithURI: (OFURI *)URI mode: (OFString *)mode { return [[[self alloc] initWithURI: URI mode: mode] autorelease]; } + ++ (OFURI *)URIForFile: (OFString *)path inArchive: (OFURI *)archive +{ + OFMutableURI *URI = [OFMutableURI URI]; + void *pool = objc_autoreleasePoolPush(); + OFCharacterSet *characterSet = [OFCharacterSet + of_URIPathAllowedCharacterSetWithoutExclamationMark]; + OFString *archiveURI; + + path = [path + stringByAddingPercentEncodingWithAllowedCharacters: characterSet]; + archiveURI = [archive.string + stringByAddingPercentEncodingWithAllowedCharacters: characterSet]; + + URI.scheme = @"of-tar"; + URI.percentEncodedPath = [OFString stringWithFormat: @"%@!%@", + archiveURI, path]; + + [URI makeImmutable]; + + objc_autoreleasePoolPop(pool); + + return URI; +} - (instancetype)init { OF_INVALID_INIT_METHOD } ADDED src/OFTarURIHandler.h Index: src/OFTarURIHandler.h ================================================================== --- src/OFTarURIHandler.h +++ src/OFTarURIHandler.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2008-2022 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFURIHandler.h" + +OF_ASSUME_NONNULL_BEGIN + +@interface OFTarURIHandler: OFURIHandler +@end + +OF_ASSUME_NONNULL_END ADDED src/OFTarURIHandler.m Index: src/OFTarURIHandler.m ================================================================== --- src/OFTarURIHandler.m +++ src/OFTarURIHandler.m @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2008-2022 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#import "OFTarURIHandler.h" +#import "OFTarArchive.h" +#import "OFURI.h" + +#import "OFInvalidArgumentException.h" +#import "OFOpenItemFailedException.h" + +@implementation OFTarURIHandler +- (OFStream *)openItemAtURI: (OFURI *)URI mode: (OFString *)mode +{ + OFString *percentEncodedPath, *archiveURI, *path; + size_t pos; + OFTarArchive *archive; + OFTarArchiveEntry *entry; + + if (![URI.scheme isEqual: @"of-tar"] || URI.host != nil || + URI.port != nil || URI.user != nil || URI.password != nil || + URI.query != nil || URI.fragment != nil) + @throw [OFInvalidArgumentException exception]; + + if (![mode isEqual: @"r"]) + /* + * Writing has some implications that are not decided yet: Will + * it always append to an archive? What happens if the file + * already exists? + */ + @throw [OFInvalidArgumentException exception]; + + percentEncodedPath = URI.percentEncodedPath; + pos = [percentEncodedPath rangeOfString: @"!"].location; + + if (pos == OFNotFound) + @throw [OFInvalidArgumentException exception]; + + archiveURI = [percentEncodedPath substringWithRange: + OFMakeRange(0, pos)].stringByRemovingPercentEncoding; + path = [percentEncodedPath substringWithRange: + OFMakeRange(pos + 1, percentEncodedPath.length - pos - 1)] + .stringByRemovingPercentEncoding; + + archive = [OFTarArchive + archiveWithURI: [OFURI URIWithString: archiveURI] + mode: @"r"]; + + while ((entry = [archive nextEntry]) != nil) + if ([entry.fileName isEqual: path]) + return [archive streamForReadingCurrentEntry]; + + @throw [OFOpenItemFailedException exceptionWithURI: URI + mode: mode + errNo: ENOENT]; +} +@end Index: src/OFURIHandler.m ================================================================== --- src/OFURIHandler.m +++ src/OFURIHandler.m @@ -29,10 +29,11 @@ # import "OFFileURIHandler.h" #endif #if defined(OF_HAVE_SOCKETS) && defined(OF_HAVE_THREADS) # import "OFHTTPURIHandler.h" #endif +#import "OFTarURIHandler.h" #import "OFZIPURIHandler.h" #import "OFUnsupportedProtocolException.h" static OFMutableDictionary OF_GENERIC(OFString *, OFURIHandler *) *handlers; @@ -67,10 +68,11 @@ #endif #if defined(OF_HAVE_SOCKETS) && defined(OF_HAVE_THREADS) [self registerClass: [OFHTTPURIHandler class] forScheme: @"http"]; [self registerClass: [OFHTTPURIHandler class] forScheme: @"https"]; #endif + [self registerClass: [OFTarURIHandler class] forScheme: @"of-tar"]; [self registerClass: [OFZIPURIHandler class] forScheme: @"of-zip"]; } + (bool)registerClass: (Class)class forScheme: (OFString *)scheme {