From a339be420071a893b2ae35de71e759e01477dc28 Mon Sep 17 00:00:00 2001 From: Richard Frith-Macdonald Date: Mon, 21 Mar 2005 12:29:02 +0000 Subject: [PATCH] Path handling updates ... basically tolerate windows paths. git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@20935 72102866-910b-0410-8b05-ffd578937521 --- ChangeLog | 12 + Headers/Foundation/NSFileManager.h | 145 ++++- Headers/Foundation/NSString.h | 233 ++++++- Source/GSHTTPURLHandle.m | 6 +- Source/NSString.m | 956 +++++++++++++++++------------ Source/NSUserDefaults.m | 15 + Testing/basic.m | 2 + 7 files changed, 969 insertions(+), 400 deletions(-) diff --git a/ChangeLog b/ChangeLog index 6cdcc0fd7..cbaee7fd1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2005-03-21 Richard Frith-Macdonald + + * Headers/Foundation/NSFileManager.h: Add path handling documentation. + * Headers/Foundation/NSString.h: Add path handling methods + documentation and new method to set global path handling mode. + * Source/NSUserDefaults.m: Update path handling mode when defaults + are read in. + * Source/NSString.m: Implement selectable path handling mode ... + gnustep/unix/windows. In the default gnustep mode we try to + handle paths in any format and just do the right thing. + Also updated handling of path extensions to match MacOSX behavior. + 2005-03-18 Richard Frith-Macdonald * Source/NSPathUtilities.m: Use fprintf rather than NSLog to try to diff --git a/Headers/Foundation/NSFileManager.h b/Headers/Foundation/NSFileManager.h index e4e6a8608..389b9f62f 100644 --- a/Headers/Foundation/NSFileManager.h +++ b/Headers/Foundation/NSFileManager.h @@ -1,7 +1,7 @@ -/* -*-objc-*- +/** NSFileManager.h - Copyright (C) 1997,1999 Free Software Foundation, Inc. + Copyright (C) 1997,1999-2005 Free Software Foundation, Inc. Author: Mircea Oancea Author: Ovidiu Predescu @@ -22,6 +22,147 @@ You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. + + + + File management +
+ Path handling +

The rules for path handling depend on the value in the + GSPathHandling user default and, to some extent, + on the platform on which the program mis running.
+ The understood values of GSPathHandling are unix + and windows. If GSPathHandling is any other value + (or has not been set), GNUstep interprets this as meaning + it should try to do-the-right-thing
+ In the default mode of operation the system is very tolerant + of paths and allows you to work with both unix and windows + style paths. The consequences of this are apparent in the + path handling methods of [NSString] rather than in [NSFileManager]. +

+ + unix +

On all Unix platforms, Path components are separated by slashes + and file names may contain any character other than slash.
+ The file names . and .. are special cases meaning current directory + and the parent of the current directory respectively.
+ Multiple adjacent slash characters are treated as a single separator. +

+ Here are various examples: + + / + An absolute path to the root directory. + + /etc/motd + An absolute path to the file named motd + in the subdirectory etc of the root directory. + + .. + A relative path to the parent of the current directory. + + program.m + A relative path to the file program.m + in the current directory. + + Source/program.m + A relative path to the file program.m in the + subdirectory Source of the current directory. + + ../GNUmakefile + A relative path to the file GNUmakefile + in the directory above the current directory. + + +
+ + windows +

On Microsoft Windows the native paths may be either UNC + or drive-relative, so GNUstep supports both.
+ Either or both slash (/) and backslash (\) may be used as + separators for path components in either type of name.
+ UNC paths follow the general form //host/share/path/file, + but must at least contain the host and share parts, + i.e. //host/share is a UNC path, but //host is not
+ Drive-relative names consist of an optional drive specifier + (consisting of a single letter followed by a single colon) + followed by an absolute or relative path.
+ In both forms, the names . and .. are refer to the curtrent + directory and the parent directory as in unix paths. +

+ Here are various examples: + + //host/share/file + An absolute UNC path to a file called file + in the top directory of the export point share on host. + + C: + A relative path to the current directory on drive C. + + C:program.m + A relative path to the file program.m on drive C. + + C:\program.m + An absolute path to the file program.m + in the top level directory on drive C. + + /Source\program.m + A drive-relative path to program.m in the directory + Source on the current drive. + + \\name + A drive-relative path to name in the top level directory + on the current drive. The '\\' is treated as a single backslash as + this is not a UNC name (there must be both a host and a share part in + a UNC name). + + +
+ + gnustep +

In the default mode, GNUstep handles both unix and windows paths so + it treats both slash (/) and backslash (\) as separators and understands + the windows UNC and drive relative path roots.
+ However, it treats any path beginning with a slash (/) as an absolute + path if running on a unix system. +

+
+ + Portability +

Attempting to pass absolute paths between applications working on + different systems is fraught with difficulty ... just don't do it.
+ Where paths need to be passed around (eg. in property lists or archives) + you should pass relative paths and use a standard mechanism to construct + an absolute path in the receiving application, for instance, appending + the relative path to the home directory of a user. +

+ Even using relative paths you should take care ... + + Use only the slash (/) as a path separator, not backslash (\). + + Never use a backslash (\) in a file name. + + Avoid colons in file names. + + Use no more than three letters in a path extension. + + + Remember that, while GNUstep will manipulate both windows and unix + paths, any path actually used to reference a file or directory + must be valid on the local system. +
+ + Tilde substitution +

GNUstep handles substitution of tilde (~) as foillows:
+ If a path is just ~ or begins ~/ then the value returned by + NSHomeDirectory() is substituted for the tilde.
+ If a path is of the form ~name or begins wityh a string like ~name/ + then name is used as the argument to NSHomeDirectoryForUser() and + the return value from that method (if non-nil) is used to replace + the tilde. +

+
+
+
*/ #ifndef __NSFileManager_h_GNUSTEP_BASE_INCLUDE diff --git a/Headers/Foundation/NSString.h b/Headers/Foundation/NSString.h index 0c43aa80c..865d23e99 100644 --- a/Headers/Foundation/NSString.h +++ b/Headers/Foundation/NSString.h @@ -173,7 +173,22 @@ enum { */ @interface NSString :NSObject -// Creating Temporary Strings +#ifndef NO_GNUSTEP +/** + * Sets the path handling mode for the NSString path manipulation methods.
+ * unix mode treats paths as Unix/POSIX paths in which a slash (/) + * is the only path separator, and a single leading slash indicates an + * absolute path.
+ * windows mode treats paths as windows drive-relative or UNC paths + * in which either slash (/) or backslash (\) may be used as path separators + * and the 'root' of a path may be of the form 'C:/' or '//host/share/'.
+ * The mode selected if you provide any other argument to this + * method is the default gnustep mode, in which the system tries + * to do-the-right-thing and support both windows and unix style + * paths at the same time. + */ ++ (void) setPathHandling: (NSString*)mode; +#endif + (id) string; + (id) stringWithCharacters: (const unichar*)chars length: (unsigned int)length; @@ -304,7 +319,16 @@ enum { - (NSStringEncoding) fastestEncoding; - (NSStringEncoding) smallestEncoding; -// Manipulating File System Paths +/** + * Attempts to complete this string as a path in the filesystem by finding + * a unique completion if one exists and returning it by reference in + * outputName (which must be a non-nil pointer), or if it finds a set of + * completions they are returned by reference in outputArray, if it is non-nil. + * filterTypes can be an array of strings specifying extensions to consider; + * files without these extensions will be ignored and will not constitute + * completions. Returns 0 if no match found, else a positive number that is + * only accurate if outputArray was non-nil. + */ - (unsigned int) completePathIntoString: (NSString**)outputName caseSensitive: (BOOL)flag matchesIntoArray: (NSArray**)outputArray @@ -313,18 +337,188 @@ enum { - (NSString*) localFromOpenStepPath; - (NSString*) openStepPathFromLocal; #endif + +/** + * Converts the receiver to a C string path expressed in the character + * encoding appropriate for the local host file system. This string will be + * automatically freed soon after it is returned, so copy it if you need it + * for long. + */ - (const char*) fileSystemRepresentation; + +/** + * Converts the receiver to a C string path using the character encoding + * appropriate to the local file system. This string will be + * stored into buffer if it is shorter than size, otherwise NO is returned. + */ - (BOOL) getFileSystemRepresentation: (char*)buffer maxLength: (unsigned int)size; + +/** + * Returns a string containing the last path component of the receiver.
+ * The path component is the last non-empty substring delimited by the ends + * of the string, or by path separator characters.
+ * If the receiver only contains a root part, this method returns it.
+ * If there are no non-empty substrings, this returns an empty string.
+ * NB. In a windows UNC path, the host and share specification is treated as + * a single path component, even though it contains separators. + * So a string of the form '//host/share' may be returned.
+ * Other special cases are apply when the string is the root. + * + * @"foo/bar" produces @"bar" + * @"foo/bar/" produces @"bar" + * @"/foo/bar" produces @"bar" + * @"/foo" produces @"foo" + * @"/" produces @"/" (root is a special case) + * @"" produces @"" + * @"C:/" produces @"C:/" (root is a special case) + * @"C:" produces @"C:" + * @"//host/share/" produces @"//host/share/" (root is a special case) + * @"//host/share" produces @"//host/share" + * + */ - (NSString*) lastPathComponent; + +/** + * Returns a new string containing the path extension of the receiver.
+ * The path extension is a suffix on the last path component which starts + * with the extension separator (a '.') (for example .tiff is the + * pathExtension for /foo/bar.tiff).
+ * Returns an empty string if no such extension exists. + * + * @"a.b" produces @"b" + * @"a.b/" produces @"b" + * @"/path/a.ext" produces @"ext" + * @"/path/a." produces @"" + * @"/path/.a" produces @"" (.a is not an extension to a file) + * @".a" produces @"" (.a is not an extension to a file) + * + */ - (NSString*) pathExtension; + +/** + * Returns a string where a prefix of the current user's home directory is + * abbreviated by '~', or returns the receiver (or an immutable copy) if + * it was not found to have the home directory as a prefix. + */ - (NSString*) stringByAbbreviatingWithTildeInPath; + +/** + * Returns a new string with the path component given in aString + * appended to the receiver.
+ * This removes trailing path separators from the receiver and the root + * part from aString and replaces them with a single slash as a path + * separator.
+ * Also condenses any multiple separator sequences in the result into + * single path separators. + * + * @"" with @"file" produces @"file" + * @"path" with @"file" produces @"path/file" + * @"/" with @"file" produces @"/file" + * @"/" with @"file" produces @"/file" + * @"/" with @"/file" produces @"/file" + * @"path with @"C:/file" produces @"path/file" + * + */ - (NSString*) stringByAppendingPathComponent: (NSString*)aString; + +/** + * Returns a new string with the path extension given in aString + * appended to the receiver after an extensionSeparator ('.').
+ * If the receiver has trailing path separator characters, they are + * stripped before the extension separator is added.
+ * If the receiver contains no components after the root, the extension + * cannot be apppended (an extension can only be appended to a file name), + * so a copy of the unmodified receiver is returned.
+ * An empty string may be used as an extension ... in which case the extension + * separator is appended.
+ * This behavior mirrors that of the -stringByDeletingPathExtension method. + * + * @"Mail" with @"app" produces @"Mail.app" + * @"Mail.app" with @"old" produces @"Mail.app.old" + * @"file" with @"" produces @"file." + * @"/" with @"app" produces @"/" (no file name to append to) + * @"" with @"app" produces @"" (no file name to append to) + * + */ - (NSString*) stringByAppendingPathExtension: (NSString*)aString; + +/** + * Returns a new string with the last path component (including any final + * path separators) removed from the receiver.
+ * A string without a path component other than the root is returned + * without alteration.
+ * See -lastPathComponent for a definition of a path component. + * + * @"hello/there" produces @"hello" + * @"hello" produces @"" + * @"/hello" produces @"/" + * @"/" produces @"/" + * @"C:file" produces @"C:" + * @"C:" produces @"C:" + * @"C:/file" produces @"C:/" + * @"C:/" produces @"C:/" + * @"//host/share/file" produces @"//host/share/" + * @"//host/share/" produces @"/host/share/" + * @"//host/share" produces @"/host/share" + * + */ - (NSString*) stringByDeletingLastPathComponent; + +/** + * Returns a new string with the path extension removed from the receiver.
+ * Strips any trailing path separators before checking for the extension + * separator.
+ * NB. This method does not consider a string which contains nothing + * between the root part and the extension separator ('.') to be a path + * extension. This mirrors the behavior of the -stringByAppendingPathExtension: + * method. + * + * @"file.ext" produces @"file" + * @"/file.ext" produces @"/file" + * @"/file.ext/" produces @"/file" (trailing path separators are ignored) + * @"/file..ext" produces @"/file." + * @"/file." produces @"/file" + * @"/.ext" produces @"/.ext" (there is no file to strip from) + * @".ext" produces @".ext" (there is no file to strip from) + * + */ - (NSString*) stringByDeletingPathExtension; + +/** + * Returns a string created by expanding the initial tilde ('~') and any + * following username to be the home directory of the current user or the + * named user.
+ * Returns the receiver or an immutable copy if it was not possible to + * expand it. + */ - (NSString*) stringByExpandingTildeInPath; + +/** + * Replaces path string by one in which path components representing symbolic + * links have been replaced by their referents.
+ * If links cannot be resolved, returns an unmodified coopy of the receiver. + */ - (NSString*) stringByResolvingSymlinksInPath; + +/** + * Returns a standardised form of the receiver, with unnecessary parts + * removed, tilde characters expanded, and symbolic links resolved + * where possible.
+ * NB. Refers to the local filesystem to resolve symbolic links in + * absolute paths, and to expand tildes ... so this can't be used for + * general path manipulation.
+ * If the string is an invalid path, the unmodified receiver is returned.
+ *

+ * Uses -stringByExpandingTildeInPath to expand tilde expressions.
+ * Simplifies '//' and '/./' sequences and removes trailing '/' or '.'.
+ *

+ *

+ * For absolute paths, uses -stringByResolvingSymlinksInPath to resolve + * any links, then gets rid of '/../' sequences and removes any '/private' + * prefix. + *

+ */ - (NSString*) stringByStandardizingPath; @@ -332,10 +526,45 @@ enum { - (int) _baseLength; #ifndef STRICT_OPENSTEP +/** + * Concatenates the path components in the array and returns the result.
+ * This method does not remove empty path components, but does recognize an + * empty initial component as a special case meaning that the string + * returned will begin with a slash. + */ + (NSString*) pathWithComponents: (NSArray*)components; + +/** + * Returns YES if the receiver represents an absolute path ...
+ * Returns NO otherwise.
+ * An absolute path in unix mode is one which begins + * with a slash or tilde.
+ * In windows mode a drive specification (eg C:) or a UNC server and share + * (eg //host/share) followed by a slash or backslash, is an absolute path, + * as is any path beginning with a tilde.
+ * In gnustep path handling mode, the rules are the same as for windows, + * except that a path whose root is a slash denotes an absolute path + * when running on unix and a relative path when running under windows. + */ - (BOOL) isAbsolutePath; + +/** + * Returns the path components of the receiver separated into an array.
+ * If the receiver begins with a root sequence such as the path separator + * character (or a drive specification in windows) then that is used as the + * first element in the array.
+ * Empty components are removed.
+ * A trailing path separator (which was not part of the root) is added as the + * last element in the array. + */ - (NSArray*) pathComponents; + +/** + * Returns an array of strings made by appending the values in paths + * to the receiver. + */ - (NSArray*) stringsByAppendingPaths: (NSArray*)paths; + + (NSString*) localizedStringWithFormat: (NSString*) format, ...; + (id) stringWithString: (NSString*) aString; diff --git a/Source/GSHTTPURLHandle.m b/Source/GSHTTPURLHandle.m index 0a2a162a2..6f6f5ad61 100644 --- a/Source/GSHTTPURLHandle.m +++ b/Source/GSHTTPURLHandle.m @@ -254,7 +254,11 @@ static void debugWrite(GSHTTPURLHandle *handle, NSData *data) - (void) dealloc { - RELEASE(sock); + if (sock != nil) + { + [sock closeFile]; + DESTROY(sock); + } RELEASE(u); RELEASE(url); RELEASE(dat); diff --git a/Source/NSString.m b/Source/NSString.m index 2bfe7de59..747748f32 100644 --- a/Source/NSString.m +++ b/Source/NSString.m @@ -147,49 +147,184 @@ static void setupWhitespace(void) #define GSEQ_S GSEQ_NS #include "GSeq.h" +/* + * The path handling mode. + */ +static enum { + PH_DO_THE_RIGHT_THING, + PH_UNIX, + PH_WINDOWS +} pathHandling = PH_DO_THE_RIGHT_THING; + +#define GSPathHandlingUnix() ((pathHandling == PH_UNIX) ? YES : NO) +#define GSPathHandlingWindows() ((pathHandling == PH_WINDOWS) ? YES : NO) -static NSCharacterSet *myPathSeps = nil; /* * The pathSeps character set is used for parsing paths ... it *must* * contain the '/' character, which is the internal path separator, * and *may* contain additiona system specific separators. * * We can't have a 'pathSeps' variable initialized in the +initialize - * method 'cos that would cause recursion. + * method because that would cause recursion. */ static NSCharacterSet* pathSeps(void) { - if (myPathSeps == nil) + static NSCharacterSet *wPathSeps = nil; + static NSCharacterSet *uPathSeps = nil; + if (GSPathHandlingUnix()) { -#if defined(__MINGW__) - myPathSeps = [NSCharacterSet characterSetWithCharactersInString: @"/\\"]; -#else - myPathSeps = [NSCharacterSet characterSetWithCharactersInString: @"/"]; -#endif - IF_NO_GC(RETAIN(myPathSeps)); + if (uPathSeps == nil) + { + uPathSeps + = [NSCharacterSet characterSetWithCharactersInString: @"/"]; + IF_NO_GC(RETAIN(uPathSeps)); + } + return uPathSeps; + } + else + { + if (wPathSeps == nil) + { + wPathSeps + = [NSCharacterSet characterSetWithCharactersInString: @"/\\"]; + IF_NO_GC(RETAIN(wPathSeps)); + } + return wPathSeps; } - return myPathSeps; } inline static BOOL pathSepMember(unichar c) { - -#if defined(__MINGW__) - if (c == (unichar)'\\' || c == (unichar)'/') -#else if (c == (unichar)'/') -#endif { return YES; } - else + if (GSPathHandlingUnix() == NO) { - return NO; + if (c == (unichar)'\\') + { + return YES; + } } + return NO; } +/* + * Find end of 'root' sequence in a string. Characters before this + * point in the string cannot be split into path components/extensions. + * Possible roots are - + * + * '/' absolute root on unix + * '' if entire path is empty string + * 'C:/' absolute root for a drive on windows + * 'C:' if entire path is 'C:' or 'C:relativepath' + * '//host/share/' absolute root for a host and share on windows + * '//host/share' if entire path is '//host/share' + * '~/' home directory for user + * '~' if entire path is '~' + * '~username/' home directory for user + * '~username' if entire path is '~username' + * + * Most roots are terminated in '/' (or '\') unless the root is the entire + * path. The exception is for windows drive-relative paths, where the root + * may be a drive letter followed by a colon, but there may still be path + * components after the root with no path separator. + * + * The presence of any non-empty root indicates an absolute path except - + * 1. A windows drive-relative path is not absolute unless the root + * ends with a path separator, since the path part on the drive is relative. + * 2. On windows, a root consisting of a single path separator indicates + * a drive-relative path with no drive ... so the path is relative. + */ +unsigned rootOf(NSString *s, unsigned l) +{ + unsigned root = 0; + + if (l > 0) + { + unichar c = [s characterAtIndex: 0]; + + if (c == '~') + { + NSRange range = NSMakeRange(1, l-1); + + range = [s rangeOfCharacterFromSet: pathSeps() + options: NSLiteralSearch + range: range]; + if (range.length == 0) + { + root = l; // ~ or ~name + } + else + { + root = NSMaxRange(range); // ~/... or ~name/... + } + } + else + { + if (pathSepMember(c)) + { + root++; + } + if (GSPathHandlingUnix() == NO) + { + if (root == 0 && l > 1 + && ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) + && [s characterAtIndex: 1] == ':') + { + // Got a drive relative path ... see if it's absolute. + root = 2; + if (l > 2 && pathSepMember([s characterAtIndex: 2])) + { + root++; + } + } + else if (root == 1 + && l > 4 && pathSepMember([s characterAtIndex: 1])) + { + NSRange range = NSMakeRange(2, l-2); + + range = [s rangeOfCharacterFromSet: pathSeps() + options: NSLiteralSearch + range: range]; + if (range.length > 0 && range.location > 2) + { + unsigned pos = NSMaxRange(range); + + // Found end of UNC host perhaps ... look for share + if (pos < l) + { + range = NSMakeRange(pos, l - pos); + range = [s rangeOfCharacterFromSet: pathSeps() + options: NSLiteralSearch + range: range]; + if (range.length > 0) + { + /* + * Found another slash ... but if it comes + * immediately after the last one this can't + * be a UNC path as it's '//host//' rather + * than '//host/share' + */ + if (range.location > pos) + { + root = NSMaxRange(range); + } + } + else + { + root = l; + } + } + } + } + } + } + } + return root; +} /* Convert a high-low surrogate pair into Unicode scalar code-point */ @@ -395,7 +530,21 @@ handle_printf_atsign (FILE *stream, return [NXConstantString class]; } -// Creating Temporary Strings ++ (void) setPathHandling: (NSString*)mode +{ + pathHandling = PH_DO_THE_RIGHT_THING; + if (mode != nil) + { + if ([mode caseInsensitiveCompare: @"windows"] == NSOrderedSame) + { + pathHandling = PH_WINDOWS; + } + else if ([mode caseInsensitiveCompare: @"unix"] == NSOrderedSame) + { + pathHandling = PH_UNIX; + } + } +} /** * Create an empty string. @@ -3112,30 +3261,17 @@ handle_printf_atsign (FILE *stream, return NSUnicodeStringEncoding; } - -// Manipulating File System Paths - -/** - * Attempts to complete this string as a path in the filesystem by finding - * a unique completion if one exists and returning it by reference in - * outputName (which must be a non-nil pointer), or if it finds a set of - * completions they are returned by reference in outputArray, if it is non-nil. - * filterTypes can be an array of strings specifying extensions to consider; - * files without these extensions will be ignored and will not constitute - * completions. Returns 0 if no match found, else a positive number that is - * only accurate if outputArray was non-nil. - */ - (unsigned int) completePathIntoString: (NSString**)outputName caseSensitive: (BOOL)flag matchesIntoArray: (NSArray**)outputArray filterTypes: (NSArray*)filterTypes { - NSString *base_path = [self stringByDeletingLastPathComponent]; - NSString *last_compo = [self lastPathComponent]; - NSString *tmp_path; + NSString *basePath = [self stringByDeletingLastPathComponent]; + NSString *lastComp = [self lastPathComponent]; + NSString *tmpPath; NSDirectoryEnumerator *e; NSMutableArray *op = nil; - unsigned match_count = 0; + unsigned matchCount = 0; if (outputArray != 0) { @@ -3147,64 +3283,57 @@ handle_printf_atsign (FILE *stream, *outputName = nil; } - if ([base_path length] == 0) + if ([basePath length] == 0) { - base_path = @"."; + basePath = @"."; } - e = [[NSFileManager defaultManager] enumeratorAtPath: base_path]; - while (tmp_path = [e nextObject], tmp_path) + e = [[NSFileManager defaultManager] enumeratorAtPath: basePath]; + while (tmpPath = [e nextObject], tmpPath) { /* Prefix matching */ if (flag == YES) { /* Case sensitive */ - if ([tmp_path hasPrefix: last_compo] == NO) + if ([tmpPath hasPrefix: lastComp] == NO) { continue; } } - else if ([[tmp_path uppercaseString] - hasPrefix: [last_compo uppercaseString]] == NO) + else if ([[tmpPath uppercaseString] + hasPrefix: [lastComp uppercaseString]] == NO) { continue; } /* Extensions filtering */ if (filterTypes - && ([filterTypes containsObject: [tmp_path pathExtension]] == NO)) + && ([filterTypes containsObject: [tmpPath pathExtension]] == NO)) { continue; } /* Found a completion */ - match_count++; + matchCount++; if (outputArray != NULL) { - [op addObject: tmp_path]; + [op addObject: tmpPath]; } if ((outputName != NULL) && - ((*outputName == nil) || (([*outputName length] < [tmp_path length])))) + ((*outputName == nil) || (([*outputName length] < [tmpPath length])))) { - *outputName = tmp_path; + *outputName = tmpPath; } } if (outputArray != NULL) { *outputArray = AUTORELEASE([op copy]); } - return match_count; + return matchCount; } static NSFileManager *fm = nil; -/** - * Converts this string, which is assumed to be a path in Unix notation ('/' - * is file separator, '.' is extension separator) to a C string path expressed - * in the convention for the host operating system. This string will be - * automatically freed soon after it is returned, so copy it if you need it - * for long. - */ - (const char*) fileSystemRepresentation { if (fm == nil) @@ -3245,13 +3374,6 @@ static NSFileManager *fm = nil; return [fm openStepPathFromLocal: self]; } - -/** - * Converts this string, which is assumed to be a path in Unix notation ('/' - * is file separator, '.' is extension separator) to a C string path expressed - * in the convention for the host operating system. This string will be - * stored into buffer if it is shorter than size, otherwise NO is returned. - */ - (BOOL) getFileSystemRepresentation: (char*)buffer maxLength: (unsigned int)size { @@ -3262,150 +3384,162 @@ static NSFileManager *fm = nil; return YES; } - -/** - * Returns a string containing the last path component of the receiver.
- * The path component is the last non-empty substring delimited by the ends - * of the string or by path * separator ('/') characters.
- * If the receiver is an empty string, it is simply returned.
- * If there are no non-empty substrings, the root string is returned. - */ - (NSString*) lastPathComponent { - NSString *substring; unsigned int l = [self length]; + NSRange range; + unsigned int i; if (l == 0) { - substring = self; // self is empty + return @""; // self is empty } - else + + // Skip back over any trailing path separators, but not in to root. + i = rootOf(self, l); + while (l > i && pathSepMember([self characterAtIndex: l-1]) == YES) { - NSRange range; - - range = [self rangeOfCharacterFromSet: pathSeps() - options: NSBackwardsSearch - range: ((NSRange){0, l})]; - if (range.length == 0) - { - substring = self; // No '/' in self - } - else if (range.location == (l - 1)) - { - if (range.location == 0) - { - substring = self; // Just '/' - } - else - { - l = range.location; - while (l > 0 && [self characterAtIndex: l - 1] == '/') - { - l--; - } - if (l > 0) - { - substring = [[self substringToIndex: l] lastPathComponent]; - } - else - { - substring = @"/"; // Multiple '/' characters. - } - } - } - else - { - substring = [self substringFromIndex: range.location + 1]; - } + l--; } - return substring; + // If only the root is left, return it. + if (i == l) + { + /* + * NB. tilde escapes should not have trailing separator in the + * path component as they are not trreated as true roots. + */ + if ([self characterAtIndex: 0] == '~' + && pathSepMember([self characterAtIndex: i-1]) == YES) + { + return [self substringToIndex: i-1]; + } + return [self substringToIndex: i]; + } + + // Got more than root ... find last component. + range = [self rangeOfCharacterFromSet: pathSeps() + options: NSBackwardsSearch + range: ((NSRange){i, l-i})]; + if (range.length > 0) + { + // Found separator ... adjust to point to component. + i = NSMaxRange(range); + } + return [self substringWithRange: ((NSRange){i, l-i})]; } -/** - * Returns a new string containing the path extension of the receiver.
- * The path extension is a suffix on the last path component which starts - * with the extension separator (a '.') (for example .tiff is the - * pathExtension for /foo/bar.tiff).
- * Returns an empty string if no such extension exists. - */ - (NSString*) pathExtension { NSRange range; - NSString *substring = @""; - unsigned int length = [self length]; + unsigned int l = [self length]; + unsigned int root; + + if (l == 0) + { + return @""; + } + root = rootOf(self, l); /* * Step past trailing path separators. */ - while (length > 1 && pathSepMember([self characterAtIndex: length-1]) == YES) + while (l > root && pathSepMember([self characterAtIndex: l-1]) == YES) { - length--; + l--; } - range = NSMakeRange(0, length); + range = NSMakeRange(root, l-root); /* - * Look for a dot in the path ... if there isn't one, there is no extension. + * Look for a dot in the path ... if there isn't one, or if it is + * immediately after the root or a path separator, there is no extension. */ range = [self rangeOfString: @"." options: NSBackwardsSearch range: range]; - if (range.length > 0) + if (range.length > 0 && range.location > root + && pathSepMember([self characterAtIndex: range.location-1]) == NO) { NSRange sepRange; /* * Found a dot, so we determine the range of the (possible) - * path extension, then cvheck to see if we have a path + * path extension, then check to see if we have a path * separator within it ... if we have a path separator then * the dot is inside the last path component and there is - * thereofore no extension. + * therefore no extension. */ range.location++; - range.length = length - range.location; + range.length = l - range.location; sepRange = [self rangeOfCharacterFromSet: pathSeps() options: NSBackwardsSearch range: range]; if (sepRange.length == 0) { - substring = [self substringFromRange: range]; + return [self substringFromRange: range]; } } - return substring; + return @""; } -/** - * Returns a new string with the path component given in aString - * appended to the receiver. - * Removes trailing separators and multiple separators. - */ - (NSString*) stringByAppendingPathComponent: (NSString*)aString { - unsigned length = [self length]; + unsigned originalLength = [self length]; + unsigned length = originalLength; unsigned aLength = [aString length]; + unsigned root = rootOf(aString, aLength); unichar buf[length+aLength+1]; - [self getCharacters: buf range: ((NSRange){0, length})]; - while (length > 1 && pathSepMember(buf[length-1]) == YES) + if (length == 0) { - length--; + [aString getCharacters: buf range: ((NSRange){0, aLength})]; + length = aLength; } - if (aLength > 0) + else { - if (length > 0 && pathSepMember(buf[length-1]) == NO) + [self getCharacters: buf range: ((NSRange){0, length})]; + + /* We strip back trailing path separators, and replace them with + * a single one ... except in the case where we have a windows + * drive specification, and the string being appended does not + * have a path separator as a root. In that case we just want to + * append to the drive specification directly, leaving a relative + * path like c:foo + */ + if (length != 2 || buf[1] != ':' || GSPathHandlingUnix() == YES + || buf[0] < 'A' || buf[0] > 'z' || (buf[0] > 'Z' && buf[0] < 'a') + || (root > 0 && pathSepMember([aString characterAtIndex: root-1]))) { + while (length > 0 && pathSepMember(buf[length-1]) == YES) + { + length--; + } buf[length++] = '/'; } - [aString getCharacters: &buf[length] range: ((NSRange){0, aLength})]; + + if ((aLength - root) > 0) + { + // appending .. discard root from aString + [aString getCharacters: &buf[length] + range: ((NSRange){root, aLength-root})]; + length += aLength-root; + } + // Find length of root part of new path. + root = rootOf(self, originalLength); } - length += aLength; + + // Trim trailing path separators while (length > 1 && pathSepMember(buf[length-1]) == YES) { length--; } + + /* Trim multi separator sequences outside root (root may contain an + * initial // pair if it is a windows UNC path). + */ if (length > 0) { aLength = length - 1; - while (aLength > 0) + while (aLength > root) { if (pathSepMember(buf[aLength]) == YES) { @@ -3419,6 +3553,7 @@ static NSFileManager *fm = nil; } length--; } + buf[aLength] = '/'; // Standardise } aLength--; } @@ -3426,108 +3561,135 @@ static NSFileManager *fm = nil; return [NSStringClass stringWithCharacters: buf length: length]; } -/** - * Returns a new string with the path extension given in aString - * appended to the receiver after the extensionSeparator ('.').
- * If the receiver has trailing '/' characters which are not part of the - * root directory, those '/' characters are stripped before the extension - * separator is added. - */ - (NSString*) stringByAppendingPathExtension: (NSString*)aString { - if ([aString length] == 0) - { - return [self stringByAppendingString: @"."]; - } - else - { - unsigned length = [self length]; - unsigned len = length; - NSString *base = self; + unsigned l = [self length]; + unsigned originalLength = l; + unsigned root; - /* - * Step past trailing path separators. - */ - while (len > 1 && pathSepMember([self characterAtIndex: len-1]) == YES) - { - len--; - } - if (length != len) - { - NSRange range = NSMakeRange(0, len); - - base = [base substringFromRange: range]; - } - return [base stringByAppendingFormat: @".%@", aString]; + if (l == 0) + { + NSLog(@"[%@-%@] cannot append extension '%@' to empty string", + NSStringFromClass([self class]), NSStringFromSelector(_cmd), aString); + return @""; // Must have a file name to append extension. } + root = rootOf(self, l); + /* + * Step past trailing path separators. + */ + while (l > root && pathSepMember([self characterAtIndex: l-1]) == YES) + { + l--; + } + if (root == l) + { + NSLog(@"[%@-%@] cannot append extension '%@' to path '%@'", + NSStringFromClass([self class]), NSStringFromSelector(_cmd), + aString, self); + return IMMUTABLE(self); // Must have a file name to append extension. + } + + /* MacOS-X prohibits an extension beginning with a path separator, + * but this code extends that a little to prohibit any root from + * being used as an extension. Perhaps we should be more permissive? + */ + root = rootOf(aString, [aString length]); + if (root > 0) + { + NSLog(@"[%@-%@] cannot append extension '%@' to path '%@'", + NSStringFromClass([self class]), NSStringFromSelector(_cmd), + aString, self); + return IMMUTABLE(self); // Must have a file name to append extension. + } + + if (originalLength != l) + { + NSRange range = NSMakeRange(0, l); + + return [[self substringFromRange: range] + stringByAppendingFormat: @".%@", aString]; + } + return [self stringByAppendingFormat: @".%@", aString]; } -/** - * Returns a new string with the last path component (including any final - * path separators) removed from the receiver.
- * A string without a path component other than the root is returned - * without alteration.
- * See -lastPathComponent for a definition of a path component. - */ - (NSString*) stringByDeletingLastPathComponent { NSRange range; - NSString *substring; - unsigned int length = [self length]; + unsigned int l = [self length]; + unsigned int i; + + if (l == 0) + { + return @""; + } + i = rootOf(self, l); + + /* + * Any root without a trailing path separator can be deleted + * as it's either a relative path or a tilde expression. + */ + if (i == l && pathSepMember([self characterAtIndex: i-1]) == NO) + { + return @""; // Delete relative root + } /* * Step past trailing path separators. */ - while (length > 1 && pathSepMember([self characterAtIndex: length-1]) == YES) + while (l > i && pathSepMember([self characterAtIndex: l-1]) == YES) { - length--; + l--; + } + + /* + * If all we have left is the root, return that root, except for the + * special case of a tilde expression ... which may be deleted even + * when it is followed by a separator. + */ + if (l == i) + { + if ([self characterAtIndex: 0] == '~') + { + return @""; // Tilde roots may be deleted. + } + return [self substringToIndex: i]; // Return root component. } - range = NSMakeRange(0, length); /* * Locate path separator preceeding last path component. */ range = [self rangeOfCharacterFromSet: pathSeps() options: NSBackwardsSearch - range: range]; + range: ((NSRange){i, l-i})]; if (range.length == 0) { - substring = @""; + return [self substringToIndex: i]; } - else if (range.location == 0) - { - substring = @"/"; - } - else - { - substring = [self substringToIndex: range.location]; - } - return substring; + return [self substringToIndex: range.location]; } -/** - * Returns a new string with the path extension removed from the receiver.
- * Strips any trailing path separators before checking for the extension - * separator.
- * Does not consider a string starting with the extension separator ('.') to - * be a path extension. - */ - (NSString*) stringByDeletingPathExtension { NSRange range; NSRange r0; NSRange r1; NSString *substring; - unsigned length = [self length]; + unsigned l = [self length]; + unsigned root; + + if ((root = rootOf(self, l)) == l) + { + return IMMUTABLE(self); + } /* - * Skip past any trailing path separators... but not a leading one. + * Skip past any trailing path separators... but not into root. */ - while (length > 1 && pathSepMember([self characterAtIndex: length-1]) == YES) + while (l > root && pathSepMember([self characterAtIndex: l-1]) == YES) { - length--; + l--; } - range = NSMakeRange(0, length); + range = NSMakeRange(root, l-root); /* * Locate path extension. */ @@ -3544,25 +3706,19 @@ static NSFileManager *fm = nil; * Assuming the extension separator was found in the last path * component, set the length of the substring we want. */ - if (r0.length > 0 && (r1.length == 0 || r1.location < r0.location)) + if (r0.length > 0 && r0.location > root + && (r1.length == 0 || r1.location < r0.location)) { - length = r0.location; + l = r0.location; } - substring = [self substringToIndex: length]; + substring = [self substringToIndex: l]; return substring; } -/** - * Returns a string created by expanding the initial tilde ('~') and any - * following username to be the home directory of the current user or the - * named user.
- * Returns the receiver or an immutable copy if it was not possible to - * expand it. - */ - (NSString*) stringByExpandingTildeInPath { NSString *homedir; - NSRange first_slash_range; + NSRange firstSlashRange; unsigned length; if ((length = [self length]) == 0) @@ -3574,7 +3730,7 @@ static NSFileManager *fm = nil; return IMMUTABLE(self); } - /* + /* FIXME ... should remove in future * Anything beginning '~@' is assumed to be a windows path specification * which can't be expanded. */ @@ -3583,40 +3739,40 @@ static NSFileManager *fm = nil; return IMMUTABLE(self); } - first_slash_range = [self rangeOfCharacterFromSet: pathSeps() + firstSlashRange = [self rangeOfCharacterFromSet: pathSeps() options: NSLiteralSearch range: ((NSRange){0, length})]; - if (first_slash_range.length == 0) + if (firstSlashRange.length == 0) { - first_slash_range.location = length; + firstSlashRange.location = length; } - /* + /* FIXME ... should remove in future * Anything beginning '~' followed by a single letter is assumed * to be a windows drive specification. */ - if (first_slash_range.location == 2 && isalpha([self characterAtIndex: 1])) + if (firstSlashRange.location == 2 && isalpha([self characterAtIndex: 1])) { return IMMUTABLE(self); } - if (first_slash_range.location != 1) + if (firstSlashRange.location != 1) { /* It is of the form `~username/blah/...' or '~username' */ - int uname_len; + int userNameLen; NSString *uname; - if (first_slash_range.length != 0) + if (firstSlashRange.length != 0) { - uname_len = first_slash_range.location - 1; + userNameLen = firstSlashRange.location - 1; } else { /* It is actually of the form `~username' */ - uname_len = [self length] - 1; - first_slash_range.location = [self length]; + userNameLen = [self length] - 1; + firstSlashRange.location = [self length]; } - uname = [self substringWithRange: ((NSRange){1, uname_len})]; + uname = [self substringWithRange: ((NSRange){1, userNameLen})]; homedir = NSHomeDirectoryForUser (uname); } else @@ -3627,10 +3783,10 @@ static NSFileManager *fm = nil; if (homedir != nil) { - if (first_slash_range.location < length) + if (firstSlashRange.location < length) { return [homedir stringByAppendingPathComponent: - [self substringFromIndex: first_slash_range.location]]; + [self substringFromIndex: firstSlashRange.location]]; } else { @@ -3643,11 +3799,6 @@ static NSFileManager *fm = nil; } } -/** - * Returns a string where a prefix of the current user's home directory is - * abbreviated by '~', or returns the receiver (or an immutable copy) if - * it was not found to have the home directory as a prefix. - */ - (NSString*) stringByAbbreviatingWithTildeInPath { NSString *homedir = NSHomeDirectory (); @@ -3844,10 +3995,6 @@ static NSFileManager *fm = nil; return s; } -/** - * Replaces path string by one in which path components representing symbolic - * links have been replaced by their referents. - */ - (NSString*) stringByResolvingSymlinksInPath { #if defined(__MINGW__) @@ -3856,10 +4003,10 @@ static NSFileManager *fm = nil; #ifndef MAX_PATH #define MAX_PATH 1024 #endif - char new_buf[MAX_PATH]; + char newBuf[MAX_PATH]; #ifdef HAVE_REALPATH - if (realpath([self fileSystemRepresentation], new_buf) == 0) + if (realpath([self fileSystemRepresentation], newBuf) == 0) return IMMUTABLE(self); #else char extra[MAX_PATH]; @@ -3869,17 +4016,18 @@ static NSFileManager *fm = nil; const char *end; unsigned num_links = 0; - if (name[0] != '/') { - if (!getcwd(new_buf, MAX_PATH)) - return IMMUTABLE(self); /* Couldn't get directory. */ - dest = strchr(new_buf, '\0'); + if (!getcwd(newBuf, MAX_PATH)) + { + return IMMUTABLE(self); /* Couldn't get directory. */ + } + dest = strchr(newBuf, '\0'); } else { - new_buf[0] = '/'; - dest = &new_buf[1]; + newBuf[0] = '/'; + dest = &newBuf[1]; } for (start = end = name; *start; start = end) @@ -3890,13 +4038,15 @@ static NSFileManager *fm = nil; /* Elide repeated path separators */ while (*start == '/') - start++; - + { + start++; + } /* Locate end of path component */ end = start; while (*end && *end != '/') - end++; - + { + end++; + } len = end - start; if (len == 0) { @@ -3911,7 +4061,7 @@ static NSFileManager *fm = nil; /* * Backup - if we are not at the root, remove the last component. */ - if (dest > &new_buf[1]) + if (dest > &newBuf[1]) { do { @@ -3923,34 +4073,40 @@ static NSFileManager *fm = nil; else { if (dest[-1] != '/') - *dest++ = '/'; - - if (&dest[len] >= &new_buf[MAX_PATH]) - return IMMUTABLE(self); /* Resolved name too long. */ - + { + *dest++ = '/'; + } + if (&dest[len] >= &newBuf[MAX_PATH]) + { + return IMMUTABLE(self); /* Resolved name too long. */ + } memcpy(dest, start, len); dest += len; *dest = '\0'; - if (lstat(new_buf, &st) < 0) - return IMMUTABLE(self); /* Unable to stat file. */ - + if (lstat(newBuf, &st) < 0) + { + return IMMUTABLE(self); /* Unable to stat file. */ + } if (S_ISLNK(st.st_mode)) { char buf[MAX_PATH]; if (++num_links > MAXSYMLINKS) - return IMMUTABLE(self); /* Too many symbolic links. */ - - n = readlink(new_buf, buf, MAX_PATH); + { + return IMMUTABLE(self); /* Too many links. */ + } + n = readlink(newBuf, buf, MAX_PATH); if (n < 0) - return IMMUTABLE(self); /* Couldn't resolve links. */ - + { + return IMMUTABLE(self); /* Couldn't resolve. */ + } buf[n] = '\0'; if ((n + strlen(end)) >= MAX_PATH) - return IMMUTABLE(self); /* Path would be too long. */ - + { + return IMMUTABLE(self); /* Path too long. */ + } /* * Concatenate the resolved name with the string still to * be processed, and start using the result as input. @@ -3964,14 +4120,14 @@ static NSFileManager *fm = nil; /* * For an absolute link, we start at root again. */ - dest = new_buf + 1; + dest = newBuf + 1; } else { /* * Backup - remove the last component. */ - if (dest > new_buf + 1) + if (dest > newBuf + 1) { do { @@ -3987,80 +4143,98 @@ static NSFileManager *fm = nil; } } } - if (dest > new_buf + 1 && dest[-1] == '/') - --dest; + if (dest > newBuf + 1 && dest[-1] == '/') + { + --dest; + } *dest = '\0'; #endif - if (strncmp(new_buf, "/private/", 9) == 0) + if (strncmp(newBuf, "/private/", 9) == 0) { struct stat st; - if (lstat(&new_buf[8], &st) == 0) - strcpy(new_buf, &new_buf[8]); + if (lstat(&newBuf[8], &st) == 0) + { + strcpy(newBuf, &newBuf[8]); + } } return [[NSFileManager defaultManager] - stringWithFileSystemRepresentation: new_buf - length: strlen(new_buf)]; + stringWithFileSystemRepresentation: newBuf length: strlen(newBuf)]; #endif /* (__MINGW__) */ } -/** - * Returns a standardised form of the receiver, with unnecessary parts - * removed, tilde characters expanded, and symbolic links resolved - * where possible.
- * If the string is an invalid path, the unmodified receiver is returned.
- *

- * Uses -stringByExpandingTildeInPath to expand tilde expressions.
- * Simplifies '//' and '/./' sequences.
- * Removes any '/private' prefix. - *

- *

- * For absolute paths, uses -stringByResolvingSymlinksInPath to resolve - * any links, then gets rid of '/../' sequences. - *

- */ - (NSString*) stringByStandardizingPath { NSMutableString *s; NSRange r; unichar (*caiImp)(NSString*, SEL, unsigned int); + unsigned int l = [self length]; + unichar c; + unsigned root; + + if (l == 0) + { + return @""; + } + c = [self characterAtIndex: 0]; + if (c == '~') + { + s = AUTORELEASE([[self stringByExpandingTildeInPath] mutableCopy]); + } + else + { + s = AUTORELEASE([self mutableCopy]); + } + [s replaceString: @"\\" withString: @"/"]; + l = [s length]; + root = rootOf(s, l); - /* Expand `~' in the path */ - s = AUTORELEASE([[self stringByExpandingTildeInPath] mutableCopy]); caiImp = (unichar (*)())[s methodForSelector: caiSel]; - /* Condense `//' and '/./' */ - r = NSMakeRange(0, [s length]); + // Condense multiple separator ('/') sequences. + r = (NSRange){root, l-root}; while ((r = [s rangeOfCharacterFromSet: pathSeps() options: 0 - range: r]).length) + range: r]).length == 1) { - unsigned length = [s length]; - - if (r.location + r.length + 1 <= length - && pathSepMember((*caiImp)(s, caiSel, r.location + 1)) == YES) - { - [s deleteCharactersInRange: r]; - } - else if (r.location + r.length + 2 <= length - && (*caiImp)(s, caiSel, r.location + 1) == (unichar)'.' - && pathSepMember((*caiImp)(s, caiSel, r.location + 2)) == YES) + while (NSMaxRange(r) < l + && pathSepMember((*caiImp)(s, caiSel, NSMaxRange(r))) == YES) { r.length++; + } + r.location++; + r.length--; + if (r.length > 0) + { [s deleteCharactersInRange: r]; + l -= r.length; + } + r.length = l - r.location; + } + // Condense ('/./') sequences. + r = (NSRange){root, l-root}; + while ((r = [s rangeOfString: @"/." options: 0 range: r]).length == 2) + { + if (NSMaxRange(r) == l || + pathSepMember((*caiImp)(s, caiSel, NSMaxRange(r))) == YES) + { + [s deleteCharactersInRange: r]; + l -= r.length; } else { r.location++; } - if ((r.length = [s length]) > r.location) - { - r.length -= r.location; - } - else - { - break; - } + r.length = l - r.location; + } + + // Strip trailing '/' if present. + if (l > root && [s hasSuffix: @"/"]) + { + r.length = 1; + r.location = l - r.length; + [s deleteCharactersInRange: r]; + l -= r.length; } if ([s isAbsolutePath] == NO) @@ -4068,10 +4242,11 @@ static NSFileManager *fm = nil; return s; } - /* Remove `/private' */ + // Remove leading `/private' if present. if ([s hasPrefix: @"/private"]) { - [s deleteCharactersInRange: ((NSRange){0,7})]; + [s deleteCharactersInRange: ((NSRange){0,8})]; + l -= 8; } /* @@ -4080,19 +4255,16 @@ static NSFileManager *fm = nil; */ #if defined(__MINGW__) /* Condense `/../' */ - r = NSMakeRange(0, [s length]); - while ((r = [s rangeOfCharacterFromSet: pathSeps() - options: 0 - range: r]).length) + r = (NSRange){root, l-root}; + while ((r = [s rangeOfString: @"/.." options: 0 range: r]).length == 3) { - if (r.location + r.length + 3 <= [s length] - && (*caiImp)(s, caiSel, r.location + 1) == (unichar)'.' - && (*caiImp)(s, caiSel, r.location + 2) == (unichar)'.' - && pathSepMember((*caiImp)(s, caiSel, r.location + 3)) == YES) + if (NSMaxRange(r) == l || + pathSepMember((*caiImp)(s, caiSel, NSMaxRange(r))) == YES) { - if (r.location > 0) + if (r.location > root) { - NSRange r2 = {0, r.location}; + NSRange r2 = {root, r.location-root}; + r = [s rangeOfCharacterFromSet: pathSeps() options: NSBackwardsSearch range: r2]; @@ -4102,28 +4274,21 @@ static NSFileManager *fm = nil; } else { - r.length = r2.length - r.location - 1; + r.length = NSMaxRange(r2) - r.location; } - r.length += 4; /* Add the `/../' */ + r.length += 3; /* Add the `/..' */ } [s deleteCharactersInRange: r]; + l -= r.length; } else { r.location++; } - - if ((r.length = [s length]) > r.location) - { - r.length -= r.location; - } - else - { - break; - } + r.length = l - r.location; } - return s; + return IMMUTABLE(s); #else return [s stringByResolvingSymlinksInPath]; #endif @@ -4209,10 +4374,6 @@ static NSFileManager *fm = nil; return blen; } -/** - * Concatenates the strings in the components array placing a path - * separator between each one and returns the result. - */ + (NSString*) pathWithComponents: (NSArray*)components { NSString *s; @@ -4236,91 +4397,96 @@ static NSFileManager *fm = nil; return s; } -/** - * Returns YES if the receiver represents an absolute path ... i.e. if it - * begins with a '/' or a '~'
- * Returns NO otherwise. - */ - (BOOL) isAbsolutePath { unichar c; + unsigned l = [self length]; + unsigned root; - if ([self length] == 0) + if (l == 0) { - return NO; + return NO; // Empty string ... not absolute } c = [self characterAtIndex: 0]; -#if defined(__MINGW__) - if (isalpha(c) && [self indexOfString: @":"] == 1) + if (c == (unichar)'~') { - return YES; + return YES; // Begins with tilde ... absolute } -#endif - if (c == (unichar)'/' || c == (unichar)'~') + root = rootOf(self, l); + if (root > 0 && pathSepMember([self characterAtIndex: root-1])) { - return YES; +#if defined(__MINGW__) + if (root == 1 && GSPathHandlingUnix() == NO) + { + return NO; // Single slash/backslash is not absolute. + } +#endif + if (root == 1 && c == '\\') + { + return NO; // Single backslash is not absolute + } + return YES; // Root ends with separator ... absolute. } return NO; } -/** - * Returns the path components of the reciever separated into an array.
- * If the receiver begins with a '/' character then that is used as the - * first element in the array.
- * Empty components are removed. - */ - (NSArray*) pathComponents { NSMutableArray *a; NSArray *r; + NSString *s = self; + unsigned int l = [s length]; + unsigned int root; + unsigned int i; + NSRange range; - if ([self length] == 0) + if (l == 0) { return [NSArray array]; } - a = [[self componentsSeparatedByString: @"/"] mutableCopy]; - if ([a count] > 0) + root = rootOf(s, l); + a = [[NSMutableArray alloc] initWithCapacity: 8]; + if (root > 0) { - int i; + [a addObject: [s substringToIndex: root]]; + } + i = root; - /* - * If the path began with a '/' then the first path component must - * be a '/' rather than an empty string so that our output could be - * fed into [+pathWithComponents: ] - */ - if ([[a objectAtIndex: 0] length] == 0) + while (i < l) + { + range = [s rangeOfCharacterFromSet: pathSeps() + options: NSLiteralSearch + range: ((NSRange){i, l - i})]; + if (range.length > 0) { - [a replaceObjectAtIndex: 0 withObject: @"/"]; - } - /* - * Similarly if the path ended with a path separator (other than the - * leading one). - */ - if ([[a objectAtIndex: [a count]-1] length] == 0) - { - if ([self length] > 1) + if (range.location > i) { - [a replaceObjectAtIndex: [a count]-1 withObject: @"/"]; + [a addObject: [s substringWithRange: + NSMakeRange(i, range.location - i)]]; } + i = NSMaxRange(range); } - /* Any empty path components must be removed. */ - for (i = [a count] - 1; i > 0; i--) + else { - if ([[a objectAtIndex: i] length] == 0) - { - [a removeObjectAtIndex: i]; - } + [a addObject: [s substringFromIndex: i]]; + i = l; } } + + /* + * If the path ended with a path separator which was not already + * added as part of the root, add it as final component. + */ + if (l > root && pathSepMember([s characterAtIndex: l-1])) + { + [a addObject: @"/"]; + } + r = [a copy]; RELEASE(a); return AUTORELEASE(r); } -/** - * Returns an array of strings made by appending the values in paths - * to the receiver. - */ - (NSArray*) stringsByAppendingPaths: (NSArray*)paths { NSMutableArray *a; diff --git a/Source/NSUserDefaults.m b/Source/NSUserDefaults.m index 978b3e128..483fefd7c 100644 --- a/Source/NSUserDefaults.m +++ b/Source/NSUserDefaults.m @@ -95,6 +95,7 @@ static void updateCache(NSUserDefaults *self) if (self == sharedDefaults) { NSArray *debug; + NSString *string; /** * If there is an array NSUserDefault called GNU-Debug, @@ -125,6 +126,20 @@ static void updateCache(NSUserDefaults *self) = [self boolForKey: @"GSLogThread"]; flags[NSWriteOldStylePropertyLists] = [self boolForKey: @"NSWriteOldStylePropertyLists"]; + + string = [self stringForKey: @"GSPathHandling"]; + if (string != nil) + { + /* + * NB. path handling defaults to the 'gnustep' tolerant mode + * so that files can be handled to read in the defaults database. + * only once the database has been read in will the defaults + * system update the mode. This avoids a horrible recursion + * if we were to try to initialise the path handling mode from + * the defaults system. + */ + [NSString setPathHandling: string]; + } } } diff --git a/Testing/basic.m b/Testing/basic.m index 3d52a91ea..6398974c5 100644 --- a/Testing/basic.m +++ b/Testing/basic.m @@ -88,6 +88,8 @@ int main () struct objc_struct_layout layout; unsigned i; + NSLog(@"%@", [@"//home//nicola" pathComponents]); + NSLog(@"Orig: %@", [NSUserDefaults userLanguages]); [NSUserDefaults setUserLanguages: [NSArray arrayWithObject: @"Bletch"]]; NSLog(@"Set: %@", [NSUserDefaults userLanguages]);