Index: src/OFConstantString.m ================================================================== --- src/OFConstantString.m +++ src/OFConstantString.m @@ -433,17 +433,10 @@ [self finishInitialization]; return [self stringByAppendingPathComponent: component]; } -- (OFString *)stringByAppendingURLPathComponent: (OFString *)component -{ - [self finishInitialization]; - - return [self stringByAppendingURLPathComponent: component]; -} - - (OFString *)stringByPrependingString: (OFString *)string { [self finishInitialization]; return [self stringByPrependingString: string]; Index: src/OFString.h ================================================================== --- src/OFString.h +++ src/OFString.h @@ -213,18 +213,10 @@ * If the string contains any non-number characters, an * OFInvalidEncodingException is thrown. */ @property (readonly, nonatomic) double doubleValue; -/*! - * @brief The string interpreted as a URL path with relative sub paths resolved. - * - * This works similar to @ref stringByStandardizingPath, but is intended for - * standardization of paths that are part of a URL. - */ -@property (readonly, nonatomic) OFString *stringByStandardizingURLPath; - /*! * @brief The string as an array of Unicode characters. * * The result is valid until the autorelease pool is released. If you want to * use the result outside the scope of the current autorelease pool, you have to @@ -1039,18 +1031,10 @@ * @return A new, autoreleased OFString with the specified format appended */ - (OFString *)stringByAppendingFormat: (OFConstantString *)format arguments: (va_list)arguments; -/*! - * @brief Creates a new string by appending a URL path component. - * - * @param component The URL path component to append - * @return A new, autoreleased OFString with the URL path component appended - */ -- (OFString *)stringByAppendingURLPathComponent: (OFString *)component; - /*! * @brief Creates a new string by prepending another string. * * @param string The string to prepend * @return A new autoreleased OFString with the specified string prepended Index: src/OFString.m ================================================================== --- src/OFString.m +++ src/OFString.m @@ -2040,26 +2040,10 @@ [new makeImmutable]; return new; } -- (OFString *)stringByAppendingURLPathComponent: (OFString *)component -{ - if ([self hasSuffix: @"/"]) - return [self stringByAppendingString: component]; - else { - OFMutableString *ret = [[self mutableCopy] autorelease]; - - [ret appendString: @"/"]; - [ret appendString: component]; - - [ret makeImmutable]; - - return ret; - } -} - - (OFString *)stringByPrependingString: (OFString *)string { OFMutableString *new = [[string mutableCopy] autorelease]; [new appendString: self]; @@ -2329,71 +2313,10 @@ objc_autoreleasePoolPop(pool); return array; } -- (OFString *)stringByStandardizingURLPath -{ - void *pool = objc_autoreleasePoolPush(); - OFArray OF_GENERIC(OFString *) *components = - [self componentsSeparatedByString: @"/"]; - OFMutableArray OF_GENERIC(OFString *) *array; - OFString *ret; - bool done = false, startsWithEmpty, endsWithEmpty; - - array = [[components mutableCopy] autorelease]; - - if ((startsWithEmpty = [[array firstObject] isEqual: @""])) - [array removeObjectAtIndex: 0]; - endsWithEmpty = [[array lastObject] isEqual: @""]; - - while (!done) { - size_t length = [array count]; - - done = true; - - for (size_t i = 0; i < length; i++) { - id object = [array objectAtIndex: i]; - id parent; - - if (i > 0) - parent = [array objectAtIndex: i - 1]; - else - parent = nil; - - if ([object isEqual: @"."] || - [object length] == 0) { - [array removeObjectAtIndex: i]; - - done = false; - break; - } - - if ([object isEqual: @".."] && parent != nil && - ![parent isEqual: @".."]) { - [array removeObjectsInRange: - of_range(i - 1, 2)]; - - done = false; - break; - } - } - } - - if (startsWithEmpty) - [array insertObject: @"" - atIndex: 0]; - if (endsWithEmpty) - [array addObject: @""]; - - ret = [[array componentsJoinedByString: @"/"] retain]; - - objc_autoreleasePoolPop(pool); - - return [ret autorelease]; -} - - (intmax_t)decimalValue { void *pool = objc_autoreleasePoolPush(); const of_unichar_t *characters = [self characters]; size_t i = 0, length = [self length]; Index: src/OFURL.h ================================================================== --- src/OFURL.h +++ src/OFURL.h @@ -140,10 +140,15 @@ /*! * @brief The URL as a string. */ @property (readonly, nonatomic) OFString *string; +/*! + * @brief The URL with relative sub paths resolved. + */ +@property (readonly, nonatomic) OFURL *URLByStandardizingPath; + #ifdef OF_HAVE_FILES /*! * @brief The local file system representation for a file URL. * * @note This only exists for URLs with the file scheme and throws an exception Index: src/OFURL.m ================================================================== --- src/OFURL.m +++ src/OFURL.m @@ -1136,10 +1136,77 @@ [ret makeImmutable]; return ret; } + +- (OFURL *)URLByStandardizingPath +{ + void *pool; + OFMutableArray OF_GENERIC(OFString *) *array; + bool done = false, endsWithEmpty; + OFString *path; + OFMutableURL *URL; + + if (_URLEncodedPath == nil) + return self; + + pool = objc_autoreleasePoolPush(); + + array = [[[_URLEncodedPath + componentsSeparatedByString: @"/"] mutableCopy] autorelease]; + + if ([[array firstObject] length] != 0) + @throw [OFInvalidFormatException exception]; + + endsWithEmpty = ([[array lastObject] length] == 0); + + while (!done) { + size_t length = [array count]; + + done = true; + + for (size_t i = 0; i < length; i++) { + id object = [array objectAtIndex: i]; + id parent = + (i > 0 ? [array objectAtIndex: i - 1] : nil); + + if ([object isEqual: @"."] || [object length] == 0) { + [array removeObjectAtIndex: i]; + + done = false; + break; + } + + if ([object isEqual: @".."] && parent != nil && + ![parent isEqual: @".."]) { + [array removeObjectsInRange: + of_range(i - 1, 2)]; + + done = false; + break; + } + } + } + + [array insertObject: @"" + atIndex: 0]; + if (endsWithEmpty) + [array addObject: @""]; + + path = [array componentsJoinedByString: @"/"]; + if ([path length] == 0) + path = @"/"; + + URL = [[self mutableCopy] autorelease]; + [URL setURLEncodedPath: path]; + [URL makeImmutable]; + + [URL retain]; + objc_autoreleasePoolPop(pool); + return [URL autorelease]; +} - (OFString *)description { return [OFString stringWithFormat: @"<%@: %@>", [self class], [self string]]; Index: tests/OFStringTests.m ================================================================== --- tests/OFStringTests.m +++ tests/OFStringTests.m @@ -589,21 +589,10 @@ TEST(@"-[stringByAppendingPathComponent:]", [[s[0] stringByAppendingPathComponent: @"baz"] isEqual: s[1]] && [[is stringByAppendingPathComponent: @"baz"] isEqual: s[1]]) #endif - s[0] = [mutableStringClass stringWithString: @"foo"]; - [s[0] appendString: @"/"]; - [s[0] appendString: @"bar"]; - s[1] = [mutableStringClass stringWithString: s[0]]; - [s[1] appendString: @"/"]; - is = [stringClass stringWithString: s[1]]; - [s[1] appendString: @"baz"]; - TEST(@"-[stringByAppendingURLPathComponent:]", - [[s[0] stringByAppendingURLPathComponent: @"baz"] isEqual: s[1]] && - [[is stringByAppendingURLPathComponent: @"baz"] isEqual: s[1]]) - TEST(@"-[hasPrefix:]", [C(@"foobar") hasPrefix: @"foo"] && ![C(@"foobar") hasPrefix: @"foobar0"]) TEST(@"-[hasSuffix:]", [C(@"foobar") hasSuffix: @"bar"] && ![C(@"foobar") hasSuffix: @"foobar0"]) Index: tests/OFURLTests.m ================================================================== --- tests/OFURLTests.m +++ tests/OFURLTests.m @@ -274,8 +274,22 @@ [[[OFURL URLWithString: @"file:///foo/bar/"] URLByAppendingPathComponent: @"/qu?x" isDirectory: true] isEqual: [OFURL URLWithString: @"file:///qu%3Fx/"]]) + TEST(@"-[URLByStandardizingPath]", + [[[OFURL URLWithString: @"http://foo/bar/.."] + URLByStandardizingPath] isEqual: + [OFURL URLWithString: @"http://foo/"]] && + [[[OFURL URLWithString: @"http://foo/bar/%2E%2E/../qux/"] + URLByStandardizingPath] isEqual: + [OFURL URLWithString: @"http://foo/bar/qux/"]] && + [[[OFURL URLWithString: @"http://foo/bar/./././qux/./"] + URLByStandardizingPath] isEqual: + [OFURL URLWithString: @"http://foo/bar/qux/"]] && + [[[OFURL URLWithString: @"http://foo/bar/../../qux"] + URLByStandardizingPath] isEqual: + [OFURL URLWithString: @"http://foo/../qux"]]) + [pool drain]; } @end