Index: src/OFArchiveURIHandler.m ================================================================== --- src/OFArchiveURIHandler.m +++ src/OFArchiveURIHandler.m @@ -175,11 +175,11 @@ OFURI * OFArchiveURIHandlerURIForFileInArchive(OFString *scheme, OFString *pathInArchive, OFURI *archiveURI) { static OFOnceControl onceControl = OFOnceControlInitValue; - OFMutableURI *ret = [OFMutableURI URI]; + OFMutableURI *ret = [OFMutableURI URIWithScheme: scheme]; void *pool = objc_autoreleasePoolPush(); OFString *archiveURIString; OFOnce(&onceControl, initPathAllowedCharacters); @@ -188,14 +188,13 @@ pathAllowedCharacters]; archiveURIString = [archiveURI.string stringByAddingPercentEncodingWithAllowedCharacters: pathAllowedCharacters]; - ret.scheme = scheme; ret.percentEncodedPath = [OFString stringWithFormat: @"%@!%@", archiveURIString, pathInArchive]; [ret makeImmutable]; objc_autoreleasePoolPop(pool); return ret; } Index: src/OFHTTPServer.m ================================================================== --- src/OFHTTPServer.m +++ src/OFHTTPServer.m @@ -532,12 +532,11 @@ [_host release]; _host = [_server.host copy]; _port = [_server port]; } - URI = [OFMutableURI URI]; - URI.scheme = @"http"; + URI = [OFMutableURI URIWithScheme: @"http"]; URI.host = _host; if (_port != 80) URI.port = [OFNumber numberWithUnsignedShort: _port]; @try { Index: src/OFMutableURI.h ================================================================== --- src/OFMutableURI.h +++ src/OFMutableURI.h @@ -32,11 +32,11 @@ * @brief The scheme part of the URI. * * @throw OFInvalidFormatException The scheme being set is not in the correct * format */ -@property OF_NULLABLE_PROPERTY (readwrite, copy, nonatomic) OFString *scheme; +@property (readwrite, copy, nonatomic) OFString *scheme; /** * @brief The host part of the URI. */ @property OF_NULLABLE_PROPERTY (readwrite, copy, nonatomic) OFString *host; @@ -96,11 +96,11 @@ OFString *percentEncodedPassword; /** * @brief The path part of the URI. */ -@property OF_NULLABLE_PROPERTY (readwrite, copy, nonatomic) OFString *path; +@property (readwrite, copy, nonatomic) OFString *path; /** * @brief The path part of the URI in percent-encoded form. * * Setting this retains the original percent-encoding used - if more characters @@ -107,22 +107,21 @@ * than necessary are percent-encoded, it is kept this way. * * @throw OFInvalidFormatException The path being set is not in the correct * format */ -@property OF_NULLABLE_PROPERTY (readwrite, copy, nonatomic) - OFString *percentEncodedPath; +@property (readwrite, copy, nonatomic) OFString *percentEncodedPath; /** * @brief The path of the URI split into components. * * The first component must always be empty to designate the root. * * @throw OFInvalidFormatException The path components being set are not in the * correct format */ -@property OF_NULLABLE_PROPERTY (readwrite, copy, nonatomic) +@property (readwrite, copy, nonatomic) OFArray OF_GENERIC(OFString *) *pathComponents; /** * @brief The query part of the URI. */ @@ -172,15 +171,25 @@ */ @property OF_NULLABLE_PROPERTY (readwrite, copy, nonatomic) OFString *percentEncodedFragment; /** - * @brief Creates a new mutable URI. + * @brief Creates a new mutable URI with the specified schemed. * + * @param scheme The scheme for the URI * @return A new, autoreleased OFMutableURI */ -+ (instancetype)URI; ++ (instancetype)URIWithScheme: (OFString *)scheme; + +/** + * @brief Initializes an already allocated mutable URI with the specified + * schemed. + * + * @param scheme The scheme for the URI + * @return An initialized OFMutableURI + */ +- (instancetype)initWithScheme: (OFString *)scheme; /** * @brief Appends the specified path component. * * @param component The component to append Index: src/OFMutableURI.m ================================================================== --- src/OFMutableURI.m +++ src/OFMutableURI.m @@ -14,10 +14,11 @@ */ #include "config.h" #import "OFMutableURI.h" +#import "OFURI+Private.h" #import "OFArray.h" #import "OFDictionary.h" #ifdef OF_HAVE_FILES # import "OFFileManager.h" #endif @@ -32,13 +33,28 @@ @dynamic scheme, host, percentEncodedHost, port, user, percentEncodedUser; @dynamic password, percentEncodedPassword, path, percentEncodedPath; @dynamic pathComponents, query, percentEncodedQuery, queryItems, fragment; @dynamic percentEncodedFragment; -+ (instancetype)URI ++ (instancetype)URIWithScheme: (OFString *)scheme +{ + return [[[self alloc] initWithScheme: scheme] autorelease]; +} + +- (instancetype)initWithScheme: (OFString *)scheme { - return [[[self alloc] init] autorelease]; + self = [super of_init]; + + @try { + self.scheme = scheme; + _percentEncodedPath = @""; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; } - (void)setScheme: (OFString *)scheme { void *pool = objc_autoreleasePoolPush(); @@ -174,13 +190,12 @@ - (void)setPercentEncodedPath: (OFString *)percentEncodedPath { OFString *old; - if (percentEncodedPath != nil) - OFURIVerifyIsEscaped(percentEncodedPath, - [OFCharacterSet URIPathAllowedCharacterSet], true); + OFURIVerifyIsEscaped(percentEncodedPath, + [OFCharacterSet URIPathAllowedCharacterSet], true); old = _percentEncodedPath; _percentEncodedPath = [percentEncodedPath copy]; [old release]; } @@ -187,20 +202,19 @@ - (void)setPathComponents: (OFArray *)components { void *pool = objc_autoreleasePoolPush(); - if (components == nil) { - self.path = nil; - return; - } - if (components.count == 0) @throw [OFInvalidFormatException exception]; - if ([components.firstObject length] != 0) - @throw [OFInvalidFormatException exception]; + if ([components.firstObject isEqual: @"/"]) { + OFMutableArray *mutComponents = + [[components mutableCopy] autorelease]; + [mutComponents replaceObjectAtIndex: 0 withObject: @""]; + components = mutComponents; + } self.path = [components componentsJoinedByString: @"/"]; objc_autoreleasePoolPop(pool); } @@ -363,27 +377,20 @@ objc_autoreleasePoolPop(pool); } - (void)standardizePath { - void *pool; + void *pool = objc_autoreleasePoolPush(); OFMutableArray OF_GENERIC(OFString *) *array; - bool done = false, endsWithEmpty; + bool done = false, startsWithEmpty, endsWithEmpty; OFString *path; - if (_percentEncodedPath == nil) - return; - - pool = objc_autoreleasePoolPush(); - array = [[[_percentEncodedPath componentsSeparatedByString: @"/"] mutableCopy] autorelease]; - if ([array.firstObject length] != 0) - @throw [OFInvalidFormatException exception]; - endsWithEmpty = ([array.lastObject length] == 0); + startsWithEmpty = ([array.firstObject length] == 0); while (!done) { size_t length = array.count; done = true; @@ -409,23 +416,24 @@ break; } } } - [array insertObject: @"" atIndex: 0]; + if (startsWithEmpty) + [array insertObject: @"" atIndex: 0]; if (endsWithEmpty) [array addObject: @""]; path = [array componentsJoinedByString: @"/"]; - if (path.length == 0) + if (startsWithEmpty && path.length == 0) path = @"/"; - [self setPercentEncodedPath: path]; + self.percentEncodedPath = path; objc_autoreleasePoolPop(pool); } - (void)makeImmutable { object_setClass(self, [OFURI class]); } @end ADDED src/OFURI+Private.h Index: src/OFURI+Private.h ================================================================== --- src/OFURI+Private.h +++ src/OFURI+Private.h @@ -0,0 +1,24 @@ +/* + * 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 "OFURI.h" + +OF_ASSUME_NONNULL_BEGIN + +@interface OFURI () +- (instancetype)of_init OF_METHOD_FAMILY(init); +@end + +OF_ASSUME_NONNULL_END Index: src/OFURI.h ================================================================== --- src/OFURI.h +++ src/OFURI.h @@ -30,25 +30,25 @@ * * @brief A class for parsing URIs as per RFC 3986 and accessing parts of it. */ @interface OFURI: OFObject { - OFString *_Nullable _scheme; + OFString *_scheme; OFString *_Nullable _percentEncodedHost; OFNumber *_Nullable _port; OFString *_Nullable _percentEncodedUser; OFString *_Nullable _percentEncodedPassword; - OFString *_Nullable _percentEncodedPath; + OFString *_percentEncodedPath; OFString *_Nullable _percentEncodedQuery; OFString *_Nullable _percentEncodedFragment; OF_RESERVE_IVARS(OFURI, 4) } /** * @brief The scheme part of the URI. */ -@property OF_NULLABLE_PROPERTY (readonly, copy, nonatomic) OFString *scheme; +@property (readonly, copy, nonatomic) OFString *scheme; /** * @brief The host part of the URI. */ @property OF_NULLABLE_PROPERTY (readonly, copy, nonatomic) OFString *host; @@ -87,33 +87,31 @@ OFString *percentEncodedPassword; /** * @brief The path part of the URI. */ -@property OF_NULLABLE_PROPERTY (readonly, copy, nonatomic) OFString *path; +@property (readonly, copy, nonatomic) OFString *path; /** * @brief The path part of the URI in percent-encoded form. */ -@property OF_NULLABLE_PROPERTY (readonly, copy, nonatomic) - OFString *percentEncodedPath; +@property (readonly, copy, nonatomic) OFString *percentEncodedPath; /** * @brief The path of the URI split into components. * * The first component must always be `/` to designate the root. */ -@property OF_NULLABLE_PROPERTY (readonly, copy, nonatomic) +@property (readonly, copy, nonatomic) OFArray OF_GENERIC(OFString *) *pathComponents; /** * @brief The last path component of the URI. * * Returns the empty string if the path is the root. */ -@property OF_NULLABLE_PROPERTY (readonly, copy, nonatomic) - OFString *lastPathComponent; +@property (readonly, copy, nonatomic) OFString *lastPathComponent; /** * @brief The query part of the URI. */ @property OF_NULLABLE_PROPERTY (readonly, copy, nonatomic) OFString *query; @@ -263,10 +261,12 @@ * @return An initialized OFURI */ - (instancetype)initFileURIWithPath: (OFString *)path isDirectory: (bool)isDirectory; #endif + +- (instancetype)init OF_UNAVAILABLE; /** * @brief Returns a new URI with the specified path component appended. * * If the URI is a file URI, the file system is queried whether the appended Index: src/OFURI.m ================================================================== --- src/OFURI.m +++ src/OFURI.m @@ -835,10 +835,20 @@ } return self; } #endif + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (instancetype)of_init +{ + return [super init]; +} - (instancetype)initWithSerialization: (OFXMLElement *)element { void *pool = objc_autoreleasePoolPush(); OFString *stringValue; @@ -885,11 +895,11 @@ if (![object isKindOfClass: [OFURI class]]) return false; URI = object; - if (URI->_scheme != _scheme && ![URI->_scheme isEqual: _scheme]) + if (![URI->_scheme isEqual: _scheme]) return false; if (URI->_percentEncodedHost != _percentEncodedHost && ![URI->_percentEncodedHost isEqual: _percentEncodedHost]) return false; if (URI->_port != _port && ![URI->_port isEqual: _port]) @@ -898,12 +908,11 @@ ![URI->_percentEncodedUser isEqual: _percentEncodedUser]) return false; if (URI->_percentEncodedPassword != _percentEncodedPassword && ![URI->_percentEncodedPassword isEqual: _percentEncodedPassword]) return false; - if (URI->_percentEncodedPath != _percentEncodedPath && - ![URI->_percentEncodedPath isEqual: _percentEncodedPath]) + if (![URI->_percentEncodedPath isEqual: _percentEncodedPath]) return false; if (URI->_percentEncodedQuery != _percentEncodedQuery && ![URI->_percentEncodedQuery isEqual: _percentEncodedQuery]) return false; if (URI->_percentEncodedFragment != _percentEncodedFragment && @@ -1048,15 +1057,10 @@ OFString *path = _percentEncodedPath; const char *UTF8String, *lastComponent; size_t length; OFString *ret; - if (path == nil) { - objc_autoreleasePoolPop(pool); - return nil; - } - if ([path isEqual: @"/"]) { objc_autoreleasePoolPop(pool); return @"/"; } @@ -1146,14 +1150,13 @@ return [self retain]; } - (id)mutableCopy { - OFURI *copy = [[OFMutableURI alloc] init]; + OFURI *copy = [[OFMutableURI alloc] initWithScheme: _scheme]; @try { - copy->_scheme = [_scheme copy]; copy->_percentEncodedHost = [_percentEncodedHost copy]; copy->_port = [_port copy]; copy->_percentEncodedUser = [_percentEncodedUser copy]; copy->_percentEncodedPassword = [_percentEncodedPassword copy]; copy->_percentEncodedPath = [_percentEncodedPath copy]; @@ -1187,12 +1190,11 @@ if (_percentEncodedHost != nil) [ret appendString: _percentEncodedHost]; if (_port != nil) [ret appendFormat: @":%@", _port]; - if (_percentEncodedPath != nil) - [ret appendString: _percentEncodedPath]; + [ret appendString: _percentEncodedPath]; if (_percentEncodedQuery != nil) [ret appendFormat: @"?%@", _percentEncodedQuery]; if (_percentEncodedFragment != nil) Index: tests/OFURITests.m ================================================================== --- tests/OFURITests.m +++ tests/OFURITests.m @@ -210,11 +210,11 @@ TEST(@"-[hash:]", URI1.hash == URI4.hash && URI2.hash != URI3.hash) EXPECT_EXCEPTION(@"Detection of invalid format", OFInvalidFormatException, [OFURI URIWithString: @"http"]) - mutableURI = [OFMutableURI URI]; + mutableURI = [OFMutableURI URIWithScheme: @"dummy"]; EXPECT_EXCEPTION( @"-[setPercentEncodedScheme:] with invalid characters fails", OFInvalidFormatException, mutableURI.scheme = @"%20")