Index: src/file.c ================================================================== --- src/file.c +++ src/file.c @@ -64,23 +64,24 @@ ** Fill stat buf with information received from stat() or lstat(). ** lstat() is called on Unix if isWd is TRUE and allow-symlinks setting is on. ** */ static int fossil_stat(const char *zFilename, struct stat *buf, int isWd){ + int rc; #if !defined(_WIN32) + char *zMbcs = fossil_utf8_to_filename(zFilename); if( isWd && g.allowSymlinks ){ - return lstat(zFilename, buf); + rc = lstat(zMbcs, buf); }else{ - return stat(zFilename, buf); + rc = stat(zMbcs, buf); } #else - int rc = 0; - wchar_t *zMbcs = fossil_utf8_to_unicode(zFilename); + wchar_t *zMbcs = fossil_utf8_to_filename(zFilename); rc = _wstati64(zMbcs, buf); - fossil_unicode_free(zMbcs); +#endif + fossil_filename_free(zMbcs); return rc; -#endif } /* ** Fill in the fileStat variable for the file named zFilename. ** If zFilename==0, then use the previous value of fileStat if @@ -303,16 +304,17 @@ /* ** Wrapper around the access() system call. */ int file_access(const char *zFilename, int flags){ #ifdef _WIN32 - wchar_t *zMbcs = fossil_utf8_to_unicode(zFilename); + wchar_t *zMbcs = fossil_utf8_to_filename(zFilename); int rc = _waccess(zMbcs, flags); - fossil_unicode_free(zMbcs); #else - int rc = access(zFilename, flags); + char *zMbcs = fossil_utf8_to_filename(zFilename); + int rc = access(zMbcs, flags); #endif + fossil_filename_free(zMbcs); return rc; } /* ** Find an unused filename similar to zBase with zSuffix appended. @@ -402,19 +404,20 @@ #if !defined(_WIN32) struct timeval tv[2]; memset(tv, 0, sizeof(tv[0])*2); tv[0].tv_sec = newMTime; tv[1].tv_sec = newMTime; - utimes(zFilename, tv); + char *zMbcs = fossil_utf8_to_filename(zFilename); + utimes(zMbcs, tv); #else struct _utimbuf tb; - wchar_t *zMbcs = fossil_utf8_to_unicode(zFilename); + wchar_t *zMbcs = fossil_utf8_to_filename(zFilename); tb.actime = newMTime; tb.modtime = newMTime; _wutime(zMbcs, &tb); - fossil_unicode_free(zMbcs); #endif + fossil_filename_free(zMbcs); } /* ** COMMAND: test-set-mtime ** @@ -441,16 +444,17 @@ /* ** Delete a file. */ void file_delete(const char *zFilename){ #ifdef _WIN32 - wchar_t *z = fossil_utf8_to_unicode(zFilename); + wchar_t *z = fossil_utf8_to_filename(zFilename); _wunlink(z); - fossil_unicode_free(z); #else + char *z = fossil_utf8_to_filename(zFilename); unlink(zFilename); #endif + fossil_filename_free(z); } /* ** Create the directory named in the argument, if it does not already ** exist. If forceFlag is 1, delete any prior non-directory object @@ -464,18 +468,18 @@ if( !forceFlag ) return 1; file_delete(zName); } if( rc!=1 ){ #if defined(_WIN32) - int rc; - wchar_t *zMbcs = fossil_utf8_to_unicode(zName); + wchar_t *zMbcs = fossil_utf8_to_filename(zName); rc = _wmkdir(zMbcs); - fossil_unicode_free(zMbcs); - return rc; #else - return mkdir(zName, 0755); + char *zMbcs = fossil_utf8_to_filename(zName); + rc = mkdir(zName, 0755); #endif + fossil_filename_free(zMbcs); + return rc; } return 0; } /* @@ -483,11 +487,10 @@ ** a file in a repository. Valid filenames follow all of the ** following rules: ** ** * Does not begin with "/" ** * Does not contain any path element named "." or ".." -** * Does not contain any of these characters in the path: "\" ** * Does not end with "/". ** * Does not contain two or more "/" characters in a row. ** * Contains at least one character ** ** Invalid UTF8 characters result in a false return if bStrictUtf8 is @@ -547,12 +550,10 @@ if( (z[++i]&0xc0)!=0x80 ){ /* Invalid second continuation byte */ return 0; } } - }else if( bStrictUtf8 && (c=='\\') ){ - return 0; } if( c=='/' ){ if( z[i+1]=='/' ) return 0; if( z[i+1]=='.' ){ if( z[i+2]=='/' || z[i+2]==0 ) return 0; @@ -580,11 +581,11 @@ } /* ** Simplify a filename by ** -** * Convert all \ into / on windows +** * Convert all \ into / on windows and cygwin ** * removing any trailing and duplicate / ** * removing /./ ** * removing /A/../ ** ** Changes are made in-place. Return the new name length. @@ -593,12 +594,12 @@ */ int file_simplify_name(char *z, int n, int slash){ int i, j; if( n<0 ) n = strlen(z); - /* On windows convert all \ characters to / */ -#if defined(_WIN32) + /* On windows/cygwin convert all \ characters to / */ +#if defined(_WIN32) || defined(__CYGWIN__) for(i=0; i<n; i++){ if( z[i]=='\\' ) z[i] = '/'; } #endif @@ -705,11 +706,13 @@ ** Return true if zPath is an absolute pathname. Return false ** if it is relative. */ int file_is_absolute_path(const char *zPath){ if( zPath[0]=='/' -#if defined(_WIN32) +#if defined(__CYGWIN__) + || zPath[0]=='\\' +#elif defined(_WIN32) || zPath[0]=='\\' || (strlen(zPath)>3 && zPath[1]==':' && (zPath[2]=='\\' || zPath[2]=='/')) #endif ){ @@ -1137,14 +1140,14 @@ ** Like fopen() but always takes a UTF8 argument. */ FILE *fossil_fopen(const char *zName, const char *zMode){ #ifdef _WIN32 wchar_t *uMode = fossil_utf8_to_unicode(zMode); - wchar_t *uName = fossil_utf8_to_unicode(zName); + wchar_t *uName = fossil_utf8_to_filename(zName); FILE *f = _wfopen(uName, uMode); - fossil_unicode_free(uName); + fossil_filename_free(uName); fossil_unicode_free(uMode); #else FILE *f = fopen(zName, zMode); #endif return f; } Index: src/rebuild.c ================================================================== --- src/rebuild.c +++ src/rebuild.c @@ -841,11 +841,11 @@ Blob aContent; /* content of the just read artifact */ static int nFileRead = 0; void *zUnicodePath; char *zUtf8Name; - zUnicodePath = fossil_utf8_to_unicode(zPath); + zUnicodePath = fossil_utf8_to_filename(zPath); d = opendir(zUnicodePath); if( d ){ while( (pEntry=readdir(d))!=0 ){ Blob path; char *zSubpath; @@ -875,11 +875,11 @@ closedir(d); }else { fossil_panic("encountered error %d while trying to open \"%s\".", errno, g.argv[3]); } - fossil_unicode_free(zUnicodePath); + fossil_filename_free(zUnicodePath); } /* ** COMMAND: reconstruct* ** Index: src/utf8.c ================================================================== --- src/utf8.c +++ src/utf8.c @@ -111,19 +111,36 @@ ** Translate text from the filename character set into ** to precomposed UTF8. Return a pointer to the translated text. ** Call fossil_filename_free() to deallocate any memory used to store the ** returned pointer when done. */ -char *fossil_filename_to_utf8(const void *zFilename){ +char *fossil_filename_to_utf8(void *zFilename){ #if defined(_WIN32) - int nByte = WideCharToMultiByte(CP_UTF8, 0, zFilename, -1, 0, 0, 0, 0); - char *zUtf = sqlite3_malloc( nByte ); + int nByte; + char *zUtf; + WCHAR *wUnicode = zFilename; + while( *wUnicode != 0 ){ + if ( (*wUnicode & 0xFF80) == 0xF000 ){ + WCHAR converted = (*wUnicode & 0x7F); + /* Only really convert it when the resulting char is in the given range*/ + if ( (converted < 32) || wcschr(L"\"*<>?|:", converted) ){ + *wUnicode = converted; + } + } + ++wUnicode; + } + nByte = WideCharToMultiByte(CP_UTF8, 0, zFilename, -1, 0, 0, 0, 0); + zUtf = sqlite3_malloc( nByte ); if( zUtf==0 ){ return 0; } WideCharToMultiByte(CP_UTF8, 0, zFilename, -1, zUtf, nByte, 0, 0); return zUtf; +#elif defined(__CYGWIN__) + char *zOut; + zOut = fossil_strdup(zFilename); + return zOut; #elif defined(__APPLE__) && !defined(WITHOUT_ICONV) char *zIn = (char*)zFilename; char *zOut; iconv_t cd; size_t n, x; @@ -149,19 +166,71 @@ return zOut; #else return (char *)zFilename; /* No-op on non-mac unix */ #endif } + +/* +** Translate UTF8 to unicode for use in filename translations. +** Return a pointer to the translated text.. Call fossil_filename_free() +** to deallocate any memory used to store the returned pointer when done. +** +** On Windows, characters in the range U+0001 to U+0031 and the +** characters '"', '*', ':', '<', '>', '?' and '|' are invalid +** to be used. Therefore, translated those to characters in the +** (private use area), in the range U+F001 - U+F07F, so those +** characters never arrive in any Windows API. The filenames might +** look strange in Windows explorer, but in the cygwin shell +** everything looks as expected. +** +** See: <http://cygwin.com/cygwin-ug-net/using-specialnames.html> +** +*/ +void *fossil_utf8_to_filename(const char *zUtf8){ +#ifdef _WIN32 + WCHAR *zUnicode = fossil_utf8_to_unicode(zUtf8); + WCHAR *wUnicode = zUnicode; + /* If path starts with "<drive>:/" or "<drive>:\", don't translate the ':' */ + if( fossil_isalpha(zUtf8[0]) && zUtf8[1]==':' + && (zUtf8[2]=='\\' || zUtf8[2]=='/')) { + zUnicode[2] = '\\'; + wUnicode += 3; + } + while( *wUnicode != '\0' ){ + if ( (*wUnicode < 32) || wcschr(L"\"*<>?|:", *wUnicode) ){ + *wUnicode |= 0xF000; + }else if( *wUnicode == '/' ){ + *wUnicode = '\\'; + } + ++wUnicode; + } + + return zUnicode; +#elif defined(__CYGWIN__) + char *zPath = fossil_strdup(zUtf8); + char *p = zPath; + while( (*p = *zUtf8++) != 0){ + if (*p++ == '\\' ) { + p[-1] = '/'; + } + } + return zPath; +#elif defined(__APPLE__) && !defined(WITHOUT_ICONV) + return fossil_strdup(zUtf8); +#else + return (void *)zUtf8; /* No-op on unix */ +#endif +} /* ** Deallocate any memory that was previously allocated by -** fossil_filename_to_utf8(). +** fossil_filename_to_utf8() or fossil_utf8_to_filename(). */ -void fossil_filename_free(char *pOld){ +void fossil_filename_free(void *pOld){ #if defined(_WIN32) sqlite3_free(pOld); -#elif defined(__APPLE__) && !defined(WITHOUT_ICONV) +#elif (defined(__APPLE__) && !defined(WITHOUT_ICONV)) || defined(__CYGWIN__) fossil_free(pOld); #else /* No-op on all other unix */ #endif } Index: src/vfile.c ================================================================== --- src/vfile.c +++ src/vfile.c @@ -459,11 +459,11 @@ ); } depth++; zDir = blob_str(pPath); - zNative = fossil_utf8_to_unicode(zDir); + zNative = fossil_utf8_to_filename(zDir); d = opendir(zNative); if( d ){ while( (pEntry=readdir(d))!=0 ){ char *zPath; char *zUtf8; @@ -491,11 +491,11 @@ fossil_filename_free(zUtf8); blob_resize(pPath, origSize); } closedir(d); } - fossil_unicode_free(zNative); + fossil_filename_free(zNative); depth--; if( depth==0 ){ db_finalize(&ins); }