summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjan.nijtmans <nijtmans@users.sourceforge.net>2014-05-08 12:46:47 (GMT)
committerjan.nijtmans <nijtmans@users.sourceforge.net>2014-05-08 12:46:47 (GMT)
commit435b75fc24a85e806029ac4159863e6bf87b6412 (patch)
treea431bbf05918d90b0a447370207404df576ce822
parentd9866626fadb0a1c94f0317d07cb7dcb5dfd7c86 (diff)
downloadtcl-435b75fc24a85e806029ac4159863e6bf87b6412.zip
tcl-435b75fc24a85e806029ac4159863e6bf87b6412.tar.gz
tcl-435b75fc24a85e806029ac4159863e6bf87b6412.tar.bz2
More efficient/robust implementation of function TclNativeCreateNativeRep(). rfe_3389978
- No more intermediate results in a Tcl_DString, just allocate space directly. - Let MultiByteToWideChar() do all the difficult work, inclusive checking for invalid byte sequences. - Handled extended win32 paths, inclusive UNC paths. Implementation for a great deal taken over from fossil.
-rw-r--r--win/tclWinFile.c109
1 files changed, 63 insertions, 46 deletions
diff --git a/win/tclWinFile.c b/win/tclWinFile.c
index e32cd94..5761eeb 100644
--- a/win/tclWinFile.c
+++ b/win/tclWinFile.c
@@ -2897,10 +2897,10 @@ ClientData
TclNativeCreateNativeRep(
Tcl_Obj *pathPtr)
{
- char *nativePathPtr, *str;
- Tcl_DString ds;
+ WCHAR *nativePathPtr;
+ const char *str;
Tcl_Obj *validPathPtr;
- int len, i = 2;
+ int len;
WCHAR *wp;
if (TclFSCwdIsNative()) {
@@ -2927,55 +2927,72 @@ TclNativeCreateNativeRep(
}
str = Tcl_GetStringFromObj(validPathPtr, &len);
- Tcl_WinUtfToTChar(str, len, &ds);
- len = Tcl_DStringLength(&ds) + sizeof(WCHAR);
- wp = (WCHAR *) Tcl_DStringValue(&ds);
- i=sizeof(WCHAR);
- if ((wp[0]=='/'||wp[0]=='\\') && (wp[1]=='/'||wp[1]=='\\')) {
- if (wp[2]=='?'){
- /* Extended path prefix: convert slashes but not the '?' */
- wp[0] = wp[1] = wp[3] = '\\';
- i += 8; wp+=4;
- if (((wp[0]>='A'&&wp[0]<='Z') || (wp[0]>='a'&&wp[0]<='z'))
- && (wp[1]==':') && (wp[2]=='/' || wp[2]=='\\')) {
- /* With drive, don't convert the ':' */
- i += 4; wp+=2;
- }
- }
- } else {
- if (((wp[0]>='A'&&wp[0]<='Z') || (wp[0]>='a'&&wp[0]<='z'))
- && (wp[1]==':') && (wp[2]=='/' || wp[2]=='\\')) {
- /* With drive, don't convert the ':' */
- i += 4; wp+=2;
- if (len > (MAX_PATH * sizeof(WCHAR))){
- /* Path is too long, needs an extended path prefix. */
- Tcl_DStringSetLength(&ds, len+=8);
- Tcl_DStringSetLength(&ds, len+1); /* Must end with two NUL bytes */
- wp = (WCHAR *) Tcl_DStringValue(&ds); /* wp might be re-allocated */
- memmove(wp+4, wp, len-8);
- memcpy(wp, L"\\\\?\\", 8);
- i+=12; wp += 6;
- }
+
+ if (strlen(str)!=len) {
+ /* String contains NUL-bytes. This is invalid. */
+ return 0;
+ }
+ /* Let MultiByteToWideChar check for other invalid sequences, like
+ * 0xC0 0x80 (== overlong NUL). See bug [3118489]: NUL in filenames */
+ len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str, -1, 0, 0);
+ if (len==0) {
+ return 0;
+ }
+ /* Overallocate 6 chars, making some room for extended paths */
+ wp = nativePathPtr = ckalloc( (len+6) * sizeof(WCHAR) );
+ if (nativePathPtr==0) {
+ return 0;
+ }
+ MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str, -1, nativePathPtr, len);
+ /*
+ ** If path starts with "//?/" or "\\?\" (extended path), translate
+ ** any slashes to backslashes but leave the '?' intact
+ */
+ if ((str[0]=='\\' || str[0]=='/') && (str[1]=='\\' || str[1]=='/')
+ && str[2]=='?' && (str[3]=='\\' || str[3]=='/')) {
+ wp[0] = wp[1] = wp[3] = '\\';
+ str += 4;
+ wp += 4;
+ }
+ /*
+ ** If there is no "\\?\" prefix but there is a drive or UNC
+ ** path prefix and the path is larger than MAX_PATH chars,
+ ** no Win32 API function can handle that unless it is
+ ** prefixed with the extended path prefix. See:
+ ** <http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath>
+ **/
+ if (((str[0]>='A'&&str[0]<='Z') || (str[0]>='a'&&str[0]<='z'))
+ && str[1]==':' && (str[2]=='\\' || str[2]=='/')) {
+ if (wp==nativePathPtr && len>MAX_PATH) {
+ memmove(wp+4, wp, len*sizeof(WCHAR));
+ memcpy(wp, L"\\\\?\\", 4*sizeof(WCHAR));
+ wp += 4;
}
+ /*
+ ** If (remainder of) path starts with "<drive>:/" or "<drive>:\",
+ ** leave the ':' intact but translate the backslash to a slash.
+ */
+ wp[2] = '\\';
+ wp += 3;
+ } else if (wp==nativePathPtr && len>MAX_PATH
+ && (str[0]=='\\' || str[0]=='/')
+ && (str[1]=='\\' || str[1]=='/') && str[2]!='?') {
+ memmove(wp+6, wp, len*sizeof(WCHAR));
+ memcpy(wp, L"\\\\?\\UNC", 7*sizeof(WCHAR));
+ wp += 7;
}
- for (; i<len; ++wp,i+=sizeof(WCHAR)) {
- if ( (*wp < ' ') || wcschr(L"\"*:<>?|", *wp) ){
- if (!*wp){
- /* See bug [3118489]: NUL in filenames */
- Tcl_DecrRefCount(validPathPtr);
- Tcl_DStringFree(&ds);
- return NULL;
- }
+ /*
+ ** In the remainder of the path, translate invalid characters to
+ ** characters in the Unicode private use area.
+ */
+ while (*wp != '\0') {
+ if ((*wp < ' ') || wcschr(L"\"*:<>?|", *wp)) {
*wp |= 0xF000;
- }else if (*wp=='/') {
+ } else if (*wp == '/') {
*wp = '\\';
}
+ ++wp;
}
- Tcl_DecrRefCount(validPathPtr);
- nativePathPtr = ckalloc(len);
- memcpy(nativePathPtr, Tcl_DStringValue(&ds), (size_t) len);
-
- Tcl_DStringFree(&ds);
return nativePathPtr;
}