Index: src/OFString+PathAdditions_DOS.m ================================================================== --- src/OFString+PathAdditions_DOS.m +++ src/OFString+PathAdditions_DOS.m @@ -56,10 +56,15 @@ return ret; } - (bool)isAbsolutePath { +#ifdef OF_WINDOWS + if ([self hasPrefix: @"\\\\"]) + return true; +#endif + return ([self containsString: @":\\"] || [self containsString: @":/"]); } - (OFArray *)pathComponents { @@ -70,10 +75,17 @@ if (cStringLength == 0) { objc_autoreleasePoolPop(pool); return ret; } + + if ([self hasPrefix: @"\\\\"]) { + [ret addObject: @"\\\\"]; + + cString += 2; + cStringLength -= 2; + } for (i = 0; i < cStringLength; i++) { if (cString[i] == '\\' || cString[i] == '/') { if (i - last != 0) [ret addObject: [OFString @@ -102,10 +114,15 @@ ssize_t i; OFString *ret; if ([self hasSuffix: @":\\"] || [self hasSuffix: @":/"]) return self; + +#ifdef OF_WINDOWS + if ([self isEqual: @"\\\\"]) + return self; +#endif cString = self.UTF8String; cStringLength = self.UTF8StringLength; if (cStringLength == 0) { @@ -172,34 +189,56 @@ - (OFString *)stringByDeletingLastPathComponent { void *pool = objc_autoreleasePoolPush(); const char *cString; size_t cStringLength; +#ifdef OF_WINDOWS + bool isUNC = false; +#endif OFString *ret; if ([self hasSuffix: @":\\"] || [self hasSuffix: @":/"]) return self; cString = self.UTF8String; cStringLength = self.UTF8StringLength; + +#ifdef OF_WINDOWS + if ([self hasPrefix: @"\\\\"]) { + isUNC = true; + cString += 2; + cStringLength -= 2; + } +#endif if (cStringLength == 0) { objc_autoreleasePoolPop(pool); - return @""; + return self; } if (cString[cStringLength - 1] == '\\' || cString[cStringLength - 1] == '/') cStringLength--; if (cStringLength == 0) { objc_autoreleasePoolPop(pool); +#ifdef OF_WINDOWS + return (isUNC ? @"\\\\" : @""); +#else return @""; +#endif } for (size_t i = cStringLength; i >= 1; i--) { if (cString[i - 1] == '\\' || cString[i - 1] == '/') { +#ifdef OF_WINDOWS + if (isUNC) { + cString -= 2; + i += 2; + } +#endif + ret = [[OFString alloc] initWithUTF8String: cString length: i - 1]; objc_autoreleasePoolPop(pool); @@ -207,11 +246,15 @@ } } objc_autoreleasePoolPop(pool); +#ifdef OF_WINDOWS + return (isUNC ? @"\\\\" : @"."); +#else return @"."; +#endif } - (OFString *)stringByDeletingPathExtension { void *pool; @@ -248,10 +291,13 @@ { void *pool = objc_autoreleasePoolPush(); OFArray OF_GENERIC(OFString *) *components; OFMutableArray OF_GENERIC(OFString *) *array; OFString *ret; +#ifdef OF_WINDOWS + bool isUNC = false; +#endif bool done = false; if (self.length == 0) return @""; @@ -261,10 +307,17 @@ objc_autoreleasePoolPop(pool); return [[self copy] autorelease]; } array = [[components mutableCopy] autorelease]; + +#ifdef OF_WINDOWS + if ([array.firstObject isEqual: @"\\\\"]) { + isUNC = true; + [array removeObjectAtIndex: 0]; + } +#endif while (!done) { size_t length = array.count; done = true; @@ -290,10 +343,21 @@ done = false; break; } } } + +#ifdef OF_WINDOWS + if (isUNC) { + /* + * Only one \ is needed, as the other one is added by + * -[componentsJoinedByString:]. + */ + [array insertObject: @"\\" + atIndex: 0]; + } +#endif if ([self hasSuffix: @"\\"] || [self hasSuffix: @"/"]) [array addObject: @""]; ret = [[array componentsJoinedByString: @"\\"] retain]; Index: src/OFURL.m ================================================================== --- src/OFURL.m +++ src/OFURL.m @@ -740,10 +740,26 @@ path = [currentDirectoryPath stringByAppendingPathComponent: path]; path = path.stringByStandardizingPath; } + +#ifdef OF_WINDOWS + if ([path hasPrefix: @"\\\\"]) { + OFArray *components = path.pathComponents; + + if (components.count < 2) + @throw [OFInvalidFormatException exception]; + + _URLEncodedHost = [[[components objectAtIndex: 1] + stringByURLEncodingWithAllowedCharacters: + [OFCharacterSet URLHostAllowedCharacterSet]] copy]; + path = [OFString pathWithComponents: + [components objectsInRange: + of_range(2, components.count - 2)]]; + } +#endif path = pathToURLPath(path); if (isDirectory && ![path hasSuffix: @"/"]) path = [path stringByAppendingString: @"/"]; @@ -1054,14 +1070,25 @@ if (![_URLEncodedPath hasPrefix: @"/"]) @throw [OFInvalidFormatException exception]; path = self.path; - if ([path hasSuffix: @"/"]) + if (path.length > 1 && [path hasSuffix: @"/"]) path = [path substringWithRange: of_range(0, path.length - 1)]; path = URLPathToPath(path); + +#ifdef OF_WINDOWS + if (_URLEncodedHost != nil) { + if (path.length == 0) + path = [OFString stringWithFormat: @"\\\\%@", + self.host]; + else + path = [OFString stringWithFormat: @"\\\\%@\\%@", + self.host, path]; + } +#endif [path retain]; objc_autoreleasePoolPop(pool); Index: tests/OFStringTests.m ================================================================== --- tests/OFStringTests.m +++ tests/OFStringTests.m @@ -539,11 +539,16 @@ TEST(@"-[stringByPrependingString:]", [[C(@"foo") stringByPrependingString: @"bar"] isEqual: @"barfoo"]) #ifdef OF_HAVE_FILES -# if defined(OF_WINDOWS) || defined(OF_MSDOS) +# if defined(OF_WINDOWS) + TEST(@"-[isAbsolutePath]", + [C(@"C:\\foo") isAbsolutePath] && [C(@"a:/foo") isAbsolutePath] && + ![C(@"foo") isAbsolutePath] && ![C(@"b:foo") isAbsolutePath] && + [C(@"\\\\foo") isAbsolutePath]) +# elif defined(OF_MSDOS) TEST(@"-[isAbsolutePath]", [C(@"C:\\foo") isAbsolutePath] && [C(@"a:/foo") isAbsolutePath] && ![C(@"foo") isAbsolutePath] && ![C(@"b:foo") isAbsolutePath]) # elif defined(OF_AMIGAOS) TEST(@"-[isAbsolutePath]", @@ -636,11 +641,27 @@ [[a objectAtIndex: i++] isEqual: @"bar"] && [[a objectAtIndex: i++] isEqual: @"baz"] && [a count] == i) #ifdef OF_HAVE_FILES -# if defined(OF_WINDOWS) || defined(OF_MSDOS) +# if defined(OF_WINDOWS) + TEST(@"+[pathWithComponents:]", + [[stringClass pathWithComponents: [OFArray arrayWithObjects: + @"foo", @"bar", @"baz", nil]] isEqual: @"foo\\bar\\baz"] && + [[stringClass pathWithComponents: [OFArray arrayWithObjects: + @"c:", @"foo", @"bar", @"baz", nil]] + isEqual: @"c:\\foo\\bar\\baz"] && + [[stringClass pathWithComponents: [OFArray arrayWithObjects: + @"foo/", @"bar\\", @"", @"baz", @"\\", nil]] + isEqual: @"foo/bar\\baz"] && + [[stringClass pathWithComponents: [OFArray arrayWithObjects: + @"foo", nil]] isEqual: @"foo"] && + [[stringClass pathWithComponents: [OFArray arrayWithObject: @"c:"]] + isEqual: @"c:\\"] && + [[stringClass pathWithComponents: [OFArray arrayWithObjects: + @"\\\\", @"foo", @"bar", nil]] isEqual: @"\\\\foo\\bar"]) +# elif defined(OF_MSDOS) TEST(@"+[pathWithComponents:]", [[stringClass pathWithComponents: [OFArray arrayWithObjects: @"foo", @"bar", @"baz", nil]] isEqual: @"foo\\bar\\baz"] && [[stringClass pathWithComponents: [OFArray arrayWithObjects: @"c:", @"foo", @"bar", @"baz", nil]] @@ -689,11 +710,42 @@ isEqual: @"foo/bar/baz"] && [[stringClass pathWithComponents: [OFArray arrayWithObjects: @"foo", nil]] isEqual: @"foo"]) # endif -# if defined(OF_WINDOWS) || defined(OF_MSDOS) +# if defined(OF_WINDOWS) + TEST(@"-[pathComponents]", + /* c:/tmp */ + (a = [C(@"c:/tmp") pathComponents]) && [a count] == 2 && + [[a objectAtIndex: 0] isEqual: @"c:"] && + [[a objectAtIndex: 1] isEqual: @"tmp"] && + /* c:\tmp\ */ + (a = [C(@"c:\\tmp\\") pathComponents]) && [a count] == 2 && + [[a objectAtIndex: 0] isEqual: @"c:"] && + [[a objectAtIndex: 1] isEqual: @"tmp"] && + /* c:/ */ + (a = [C(@"c:/") pathComponents]) && [a count] == 1 && + [[a objectAtIndex: 0] isEqual: @"c:"] && + /* foo\bar */ + (a = [C(@"foo\\bar") pathComponents]) && [a count] == 2 && + [[a objectAtIndex: 0] isEqual: @"foo"] && + [[a objectAtIndex: 1] isEqual: @"bar"] && + /* foo\bar/baz/ */ + (a = [C(@"foo\\bar/baz/") pathComponents]) && [a count] == 3 && + [[a objectAtIndex: 0] isEqual: @"foo"] && + [[a objectAtIndex: 1] isEqual: @"bar"] && + [[a objectAtIndex: 2] isEqual: @"baz"] && + /* foo\/ */ + (a = [C(@"foo\\/") pathComponents]) && [a count] == 1 && + [[a objectAtIndex: 0] isEqual: @"foo"] && + [[C(@"") pathComponents] count] == 0 && + /* \\foo\bar */ + (a = [C(@"\\\\foo\\bar") pathComponents]) && [a count] == 3 && + [[a objectAtIndex: 0] isEqual: @"\\\\"] && + [[a objectAtIndex: 1] isEqual: @"foo"] && + [[a objectAtIndex: 2] isEqual: @"bar"]) +# elif defined(OF_MSDOS) TEST(@"-[pathComponents]", /* c:/tmp */ (a = [C(@"c:/tmp") pathComponents]) && [a count] == 2 && [[a objectAtIndex: 0] isEqual: @"c:"] && [[a objectAtIndex: 1] isEqual: @"tmp"] && @@ -793,11 +845,23 @@ (a = [C(@"foo//") pathComponents]) && [a count] == 1 && [[a objectAtIndex: 0] isEqual: @"foo"] && [[C(@"") pathComponents] count] == 0) # endif -# if defined(OF_WINDOWS) || defined(OF_MSDOS) +# if defined(OF_WINDOWS) + TEST(@"-[lastPathComponent]", + [[C(@"c:/tmp") lastPathComponent] isEqual: @"tmp"] && + [[C(@"c:\\tmp\\") lastPathComponent] isEqual: @"tmp"] && + [[C(@"c:\\") lastPathComponent] isEqual: @"c:\\"] && + [[C(@"c:/") lastPathComponent] isEqual: @"c:/"] && + [[C(@"\\") lastPathComponent] isEqual: @""] && + [[C(@"foo") lastPathComponent] isEqual: @"foo"] && + [[C(@"foo\\bar") lastPathComponent] isEqual: @"bar"] && + [[C(@"foo/bar/baz/") lastPathComponent] isEqual: @"baz"] && + [[C(@"\\\\foo\\bar") lastPathComponent] isEqual: @"bar"] && + [[C(@"\\\\") lastPathComponent] isEqual: @"\\\\"]) +# elif defined(OF_MSDOS) TEST(@"-[lastPathComponent]", [[C(@"c:/tmp") lastPathComponent] isEqual: @"tmp"] && [[C(@"c:\\tmp\\") lastPathComponent] isEqual: @"tmp"] && [[C(@"c:\\") lastPathComponent] isEqual: @"c:\\"] && [[C(@"c:/") lastPathComponent] isEqual: @"c:/"] && @@ -837,11 +901,28 @@ [[C(@"foo.bar") pathExtension] isEqual: @"bar"] && [[C(@"foo/.bar") pathExtension] isEqual: @""] && [[C(@"foo/.bar.baz") pathExtension] isEqual: @"baz"] && [[C(@"foo/bar.baz/") pathExtension] isEqual: @"baz"]) -# if defined(OF_WINDOWS) || defined(OF_MSDOS) +# if defined(OF_WINDOWS) + TEST(@"-[stringByDeletingLastPathComponent]", + [[C(@"\\tmp") stringByDeletingLastPathComponent] isEqual: @""] && + [[C(@"/tmp/") stringByDeletingLastPathComponent] isEqual: @""] && + [[C(@"c:\\") stringByDeletingLastPathComponent] isEqual: @"c:\\"] && + [[C(@"c:/") stringByDeletingLastPathComponent] isEqual: @"c:/"] && + [[C(@"c:\\tmp/foo/") stringByDeletingLastPathComponent] + isEqual: @"c:\\tmp"] && + [[C(@"foo\\bar") stringByDeletingLastPathComponent] + isEqual: @"foo"] && + [[C(@"\\") stringByDeletingLastPathComponent] isEqual: @""] && + [[C(@"foo") stringByDeletingLastPathComponent] isEqual: @"."] && + [[C(@"\\\\foo\\bar") stringByDeletingLastPathComponent] + isEqual: @"\\\\foo"] && + [[C(@"\\\\foo") stringByDeletingLastPathComponent] + isEqual: @"\\\\"] && + [[C(@"\\\\") stringByDeletingLastPathComponent] isEqual: @"\\\\"]) +# elif defined(OF_MSDOS) TEST(@"-[stringByDeletingLastPathComponent]", [[C(@"\\tmp") stringByDeletingLastPathComponent] isEqual: @""] && [[C(@"/tmp/") stringByDeletingLastPathComponent] isEqual: @""] && [[C(@"c:\\") stringByDeletingLastPathComponent] isEqual: @"c:\\"] && [[C(@"c:/") stringByDeletingLastPathComponent] isEqual: @"c:/"] && @@ -937,10 +1018,17 @@ [[C(@".foo") stringByDeletingPathExtension] isEqual: @".foo"] && [[C(@".foo\\bar") stringByDeletingPathExtension] isEqual: @".foo\\bar"] && [[C(@".foo.bar") stringByDeletingPathExtension] isEqual: @".foo"]) # endif + +# ifdef OF_WINDOWS + /* TODO: Add more tests */ + TEST(@"-[stringByStandardizingPath]", + [[C(@"\\\\foo\\..\\bar\\qux") stringByStandardizingPath] + isEqual: @"\\\\bar\\qux"]) +# endif #endif TEST(@"-[decimalValue]", [C(@"1234") decimalValue] == 1234 && [C(@"\r\n+123 ") decimalValue] == 123 && Index: tests/OFURLTests.m ================================================================== --- tests/OFURLTests.m +++ tests/OFURLTests.m @@ -102,10 +102,23 @@ #ifdef OF_HAVE_FILES TEST(@"+[fileURLWithPath:]", [[[OFURL fileURLWithPath: @"testfile.txt"] fileSystemRepresentation] isEqual: [[[OFFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent: @"testfile.txt"]]) + +# ifdef OF_WINDOWS + OFURL *tmp; + TEST(@"+[fileURLWithPath:] with UNC", + (tmp = [OFURL fileURLWithPath: @"\\\\foo\\bar"]) && + [tmp.host isEqual: @"foo"] && [tmp.path isEqual: @"/bar"] && + [tmp.string isEqual: @"file://foo/bar"] && + [tmp.fileSystemRepresentation isEqual: @"\\\\foo\\bar"] && + (tmp = [OFURL fileURLWithPath: @"\\\\test"]) && + [tmp.host isEqual: @"test"] && [tmp.path isEqual: @"/"] && + [tmp.string isEqual: @"file://test/"] && + [tmp.fileSystemRepresentation isEqual: @"\\\\test"]) +# endif #endif TEST(@"-[string]", [[u1 string] isEqual: url_str] && [[u2 string] isEqual: @"http://foo:80"] && @@ -126,11 +139,11 @@ TEST(@"-[pathComponents]", [[u1 pathComponents] isEqual: [OFArray arrayWithObjects: @"", @"pa?th", nil]] && [[u4 pathComponents] isEqual: [OFArray arrayWithObjects: @"", @"etc", @"passwd", nil]]) - TEST(@"-[lastPathComponent", + TEST(@"-[lastPathComponent]", [[[OFURL URLWithString: @"http://host/foo//bar/baz"] lastPathComponent] isEqual: @"baz"] && [[[OFURL URLWithString: @"http://host/foo//bar/baz/"] lastPathComponent] isEqual: @"baz"] && [[[OFURL URLWithString: @"http://host/foo/"]