From 1f93e1ab3fd01d0f68a3cfe19185e00a07a7d884 Mon Sep 17 00:00:00 2001 From: dgp Date: Thu, 20 Aug 2009 14:59:53 +0000 Subject: * generic/tclPathObj.c: [Bug 2806250] Prevent the storage of strings starting with ~ in the "tail" part (normPathPtr field) of the path intrep when PATHFLAGS != 0. This establishes the assumptions relied on elsewhere that the name stored there is a relative path. Also refactored to make an AppendPath() routine instead of the cut/paste stanzas that were littered throughout. --- ChangeLog | 9 +++ generic/tclPathObj.c | 203 +++++++++++++++++++++++++-------------------------- 2 files changed, 108 insertions(+), 104 deletions(-) diff --git a/ChangeLog b/ChangeLog index 45e23f8..7ef505f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2009-08-20 Don Porter + + * generic/tclPathObj.c: [Bug 2806250] Prevent the storage of strings + starting with ~ in the "tail" part (normPathPtr field) of the path + intrep when PATHFLAGS != 0. This establishes the assumptions relied + on elsewhere that the name stored there is a relative path. Also + refactored to make an AppendPath() routine instead of the cut/paste + stanzas that were littered throughout. + 2009-08-20 Donal K. Fellows * generic/tclCmdIL.c (Tcl_LsortObjCmd): Plug memory leak. diff --git a/generic/tclPathObj.c b/generic/tclPathObj.c index 282b1fc..60220b9 100644 --- a/generic/tclPathObj.c +++ b/generic/tclPathObj.c @@ -10,7 +10,7 @@ * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclPathObj.c,v 1.66.2.8 2009/08/18 14:43:58 dgp Exp $ + * RCS: @(#) $Id: tclPathObj.c,v 1.66.2.9 2009/08/20 14:59:53 dgp Exp $ */ #include "tclInt.h" @@ -20,6 +20,7 @@ * Prototypes for functions defined later in this file. */ +static Tcl_Obj * AppendPath(Tcl_Obj *head, Tcl_Obj *tail); static void DupFsPathInternalRep(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr); static void FreeFsPathInternalRep(Tcl_Obj *pathPtr); @@ -1286,6 +1287,30 @@ TclNewFSPathObj( const char *p; int state = 0, count = 0; + /* [Bug 2806250] - this is only a partial solution of the problem. + * The PATHFLAGS != 0 representation assumes in many places that + * the "tail" part stored in the normPathPtr field is itself a + * relative path. Strings that begin with "~" are not relative paths, + * so we must prevent their storage in the normPathPtr field. + * + * More generally we ought to be testing "addStrRep" for any value + * that is not a relative path, but in an unconstrained VFS world + * that could be just about anything, and testing could be expensive. + * Since this routine plays a big role in [glob], anything that slows + * it down would be unwelcome. For now, continue the risk of further + * bugs when some Tcl_Filesystem uses otherwise relative path strings + * as absolute path strings. Sensible Tcl_Filesystems will avoid + * that by mounting on path prefixes like foo:// which cannot be the + * name of a file or directory read from a native [glob] operation. + */ + if (addStrRep[0] == '~') { + Tcl_Obj *tail = Tcl_NewStringObj(addStrRep, len); + + pathPtr = AppendPath(dirPtr, tail); + Tcl_DecrRefCount(tail); + return pathPtr; + } + tsdPtr = TCL_TSD_INIT(&tclFsDataKey); pathPtr = Tcl_NewObj(); @@ -1351,6 +1376,49 @@ TclNewFSPathObj( return pathPtr; } + +static Tcl_Obj * +AppendPath( + Tcl_Obj *head, + Tcl_Obj *tail) +{ + int numBytes; + const char *bytes; + Tcl_Obj *copy = Tcl_DuplicateObj(head); + + bytes = Tcl_GetStringFromObj(copy, &numBytes); + + /* + * Should we perhaps use 'Tcl_FSPathSeparator'? But then what about the + * Windows special case? Perhaps we should just check if cwd is a root + * volume. We should never get numBytes == 0 in this code path. + */ + + switch (tclPlatform) { + case TCL_PLATFORM_UNIX: + if (bytes[numBytes-1] != '/') { + Tcl_AppendToObj(copy, "/", 1); + } + break; + + case TCL_PLATFORM_WINDOWS: + /* + * We need the extra 'numBytes != 2', and ':' checks because a volume + * relative path doesn't get a '/'. For example 'glob C:*cat*.exe' + * will return 'C:cat32.exe' + */ + + if (bytes[numBytes-1] != '/' && bytes[numBytes-1] != '\\') { + if (numBytes!= 2 || bytes[1] != ':') { + Tcl_AppendToObj(copy, "/", 1); + } + } + break; + } + + Tcl_AppendObjToObj(copy, tail); + return copy; +} /* *--------------------------------------------------------------------------- @@ -1364,11 +1432,6 @@ TclNewFSPathObj( * directory. Returns a Tcl_Obj representing filename of the path * relative to the directory. * - * In the case where the resulting path would start with a '~', we take - * special care to return an ordinary string. This means to use that path - * (and not have it interpreted as a user name), one must prepend './'. - * This may seem strange, but that is how 'glob' is currently defined. - * * Results: * NULL on error, otherwise a valid object, typically with refCount of * zero, which it is assumed the caller will increment. @@ -1396,6 +1459,26 @@ TclFSMakePathRelative( && fsPathPtr->cwdPtr == cwdPtr) { pathPtr = fsPathPtr->normPathPtr; + /* TODO: Determine how much, if any, of this forcing + * the relative path tail into the "path" Tcl_ObjType + * with a recorded cwdPtr context has any actual value. + * + * Nothing is getting cached. Not normPathPtr, not nativePathPtr, + * nor fsRecPtr, so storing the cwdPtr context against which such + * cached values might later be validated appears to be of no + * value. Take that away, and all this code is just a mildly + * optimized equivalent of a call to SetFsPathFromAny(). That + * optimization may have some value, *if* these value in fact + * get used as "path" values before used as something else. + * If not, though, whatever cost we pay below to convert to + * one of the "path" intreps is just a waste, it seems. The + * usual convention in the core is to delay ObjType conversion + * until it is needed and demanded, and I don't see why this + * section of code should be an exception to that. Leaving it + * in place for the rest of the 8.5.* releases just for sake + * of stability. + */ + /* * Free old representation. */ @@ -1419,16 +1502,6 @@ TclFSMakePathRelative( * Now pathPtr is a string object. */ - if (Tcl_GetString(pathPtr)[0] == '~') { - /* - * If the first character of the path is a tilde, we must just - * return the path as is, to agree with the defined behaviour - * of 'glob'. - */ - - return pathPtr; - } - fsPathPtr = (FsPath *) ckalloc(sizeof(FsPath)); /* @@ -1796,7 +1869,6 @@ Tcl_FSGetNormalizedPath( Tcl_Obj *dir, *copy; int cwdLen; int pathType; - const char *cwdStr; ClientData clientData = NULL; pathType = Tcl_FSGetPathType(fsPathPtr->cwdPtr); @@ -1804,40 +1876,21 @@ Tcl_FSGetNormalizedPath( if (dir == NULL) { return NULL; } + /* TODO: Figure out why this is needed. */ if (pathPtr->bytes == NULL) { UpdateStringOfFsPath(pathPtr); } - copy = Tcl_DuplicateObj(dir); - Tcl_IncrRefCount(copy); + + copy = AppendPath(dir, fsPathPtr->normPathPtr); Tcl_IncrRefCount(dir); + Tcl_IncrRefCount(copy); /* * We now own a reference on both 'dir' and 'copy' */ - cwdStr = Tcl_GetStringFromObj(copy, &cwdLen); - - /* - * Should we perhaps use 'Tcl_FSPathSeparator'? But then what about - * the Windows special case? Perhaps we should just check if cwd is a - * root volume. We should never get cwdLen == 0 in this code path. - */ - - switch (tclPlatform) { - case TCL_PLATFORM_UNIX: - if (cwdStr[cwdLen-1] != '/') { - Tcl_AppendToObj(copy, "/", 1); - cwdLen++; - } - break; - case TCL_PLATFORM_WINDOWS: - if (cwdStr[cwdLen-1] != '/' && cwdStr[cwdLen-1] != '\\') { - Tcl_AppendToObj(copy, "/", 1); - cwdLen++; - } - break; - } - Tcl_AppendObjToObj(copy, fsPathPtr->normPathPtr); + (void) Tcl_GetStringFromObj(dir, &cwdLen); + cwdLen += (Tcl_GetString(copy)[cwdLen] == '/'); /* Normalize the combined string. */ @@ -1937,35 +1990,12 @@ Tcl_FSGetNormalizedPath( } else if (fsPathPtr->normPathPtr == NULL) { int cwdLen; Tcl_Obj *copy; - const char *cwdStr; ClientData clientData = NULL; - copy = Tcl_DuplicateObj(fsPathPtr->cwdPtr); - Tcl_IncrRefCount(copy); - cwdStr = Tcl_GetStringFromObj(copy, &cwdLen); + copy = AppendPath(fsPathPtr->cwdPtr, pathPtr); - /* - * Should we perhaps use 'Tcl_FSPathSeparator'? But then what - * about the Windows special case? Perhaps we should just check if - * cwd is a root volume. We should never get cwdLen == 0 in this - * code path. - */ - - switch (tclPlatform) { - case TCL_PLATFORM_UNIX: - if (cwdStr[cwdLen-1] != '/') { - Tcl_AppendToObj(copy, "/", 1); - cwdLen++; - } - break; - case TCL_PLATFORM_WINDOWS: - if (cwdStr[cwdLen-1] != '/' && cwdStr[cwdLen-1] != '\\') { - Tcl_AppendToObj(copy, "/", 1); - cwdLen++; - } - break; - } - Tcl_AppendObjToObj(copy, pathPtr); + (void) Tcl_GetStringFromObj(fsPathPtr->cwdPtr, &cwdLen); + cwdLen += (Tcl_GetString(copy)[cwdLen] == '/'); /* * Normalize the combined string, but only starting after the end @@ -2716,7 +2746,6 @@ UpdateStringOfFsPath( register Tcl_Obj *pathPtr) /* path obj with string rep to update. */ { FsPath *fsPathPtr = PATHOBJ(pathPtr); - const char *cwdStr; int cwdLen; Tcl_Obj *copy; @@ -2724,42 +2753,8 @@ UpdateStringOfFsPath( Tcl_Panic("Called UpdateStringOfFsPath with invalid object"); } - copy = Tcl_DuplicateObj(fsPathPtr->cwdPtr); - Tcl_IncrRefCount(copy); - - cwdStr = Tcl_GetStringFromObj(copy, &cwdLen); - - /* - * Should we perhaps use 'Tcl_FSPathSeparator'? But then what about the - * Windows special case? Perhaps we should just check if cwd is a root - * volume. We should never get cwdLen == 0 in this code path. - */ - - switch (tclPlatform) { - case TCL_PLATFORM_UNIX: - if (cwdStr[cwdLen-1] != '/') { - Tcl_AppendToObj(copy, "/", 1); - cwdLen++; - } - break; - - case TCL_PLATFORM_WINDOWS: - /* - * We need the extra 'cwdLen != 2', and ':' checks because a volume - * relative path doesn't get a '/'. For example 'glob C:*cat*.exe' - * will return 'C:cat32.exe' - */ - - if (cwdStr[cwdLen-1] != '/' && cwdStr[cwdLen-1] != '\\') { - if (cwdLen != 2 || cwdStr[1] != ':') { - Tcl_AppendToObj(copy, "/", 1); - cwdLen++; - } - } - break; - } + copy = AppendPath(fsPathPtr->cwdPtr, fsPathPtr->normPathPtr); - Tcl_AppendObjToObj(copy, fsPathPtr->normPathPtr); pathPtr->bytes = Tcl_GetStringFromObj(copy, &cwdLen); pathPtr->length = cwdLen; copy->bytes = tclEmptyStringRep; -- cgit v0.12