Fossil SCM
Allow repository to reside on an extended windows path, prefixed with "//?/". There were two problems. 1) The '?' doesn't survive fossil's command line globbing, therefore use a temporary file to hold the repository name when running "fossil ui" or "fossil server" on Windows. 2) In fossil_utf8_to_filename(), '?' was translated to another Unicode character, which shouldn't happen in the extended path prefix. testcase: "fossil test-move-repository //\?/C:/fossil/fossil.fossil" (the backslash is absorbed by cmd.exe, using quotes doesn't work)
Commit
8ab08d32c7771298d21dfc7e664257260d6d665d
Parent
2da197889ade0bb…
2 files changed
+24
-4
+15
-3
+24
-4
| --- src/utf8.c | ||
| +++ src/utf8.c | ||
| @@ -177,12 +177,13 @@ | ||
| 177 | 177 | ** Call fossil_filename_free() to deallocate any memory used to store the |
| 178 | 178 | ** returned pointer when done. |
| 179 | 179 | ** |
| 180 | 180 | ** On Windows, characters in the range U+0001 to U+0031 and the |
| 181 | 181 | ** characters '"', '*', ':', '<', '>', '?' and '|' are invalid |
| 182 | -** to be used. Therefore, translate those to characters in the | |
| 183 | -** in the range U+F001 - U+F07F (private use area), so those | |
| 182 | +** to be used, except in the 'extended path' prefix ('?') and | |
| 183 | +** as drive specifier (':'). Therefore, translate those to characters | |
| 184 | +** in the in the range U+F001 - U+F07F (private use area), so those | |
| 184 | 185 | ** characters never arrive in any Windows API. The filenames might |
| 185 | 186 | ** look strange in Windows explorer, but in the cygwin shell |
| 186 | 187 | ** everything looks as expected. |
| 187 | 188 | ** |
| 188 | 189 | ** See: <http://cygwin.com/cygwin-ug-net/using-specialnames.html> |
| @@ -195,16 +196,35 @@ | ||
| 195 | 196 | wchar_t *wUnicode = zUnicode; |
| 196 | 197 | if( zUnicode==0 ){ |
| 197 | 198 | return 0; |
| 198 | 199 | } |
| 199 | 200 | MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zUnicode, nChar); |
| 200 | - /* If path starts with "<drive>:/" or "<drive>:\", don't translate the ':' */ | |
| 201 | + /* | |
| 202 | + ** If path starts with "//?/" or "\\?\" (extended path), translate | |
| 203 | + ** any slashes to backslashes but leave the '?' intact | |
| 204 | + */ | |
| 205 | + if( (zUtf8[0]=='\\' || zUtf8[0]=='/') && (zUtf8[1]=='\\' || zUtf8[1]=='/') | |
| 206 | + && zUtf8[2]=='?' && (zUtf8[3]=='\\' || zUtf8[3]=='/')) { | |
| 207 | + wUnicode[0] = wUnicode[1] = wUnicode[3] = '\\'; | |
| 208 | + zUtf8 += 4; | |
| 209 | + wUnicode += 4; | |
| 210 | + } | |
| 211 | + /* | |
| 212 | + ** If (remainder of) path starts with "<drive>:/" or "<drive>:\", | |
| 213 | + ** leave the ':' intact | |
| 214 | + */ | |
| 201 | 215 | if( fossil_isalpha(zUtf8[0]) && zUtf8[1]==':' |
| 202 | 216 | && (zUtf8[2]=='\\' || zUtf8[2]=='/')) { |
| 203 | - zUnicode[2] = '\\'; | |
| 217 | + wUnicode[2] = '\\'; | |
| 204 | 218 | wUnicode += 3; |
| 205 | 219 | } |
| 220 | + /* | |
| 221 | + ** In the remainder of the path, translate invalid characters to | |
| 222 | + ** characters in the Unicode private use area. This is what makes | |
| 223 | + ** Win32 fossil.exe work well in a Cygwin environment even when a | |
| 224 | + ** filename contains characters which are invalid for Win32. | |
| 225 | + */ | |
| 206 | 226 | while( *wUnicode != '\0' ){ |
| 207 | 227 | if ( (*wUnicode < ' ') || wcschr(L"\"*:<>?|", *wUnicode) ){ |
| 208 | 228 | *wUnicode |= 0xF000; |
| 209 | 229 | }else if( *wUnicode == '/' ){ |
| 210 | 230 | *wUnicode = '\\'; |
| 211 | 231 |
| --- src/utf8.c | |
| +++ src/utf8.c | |
| @@ -177,12 +177,13 @@ | |
| 177 | ** Call fossil_filename_free() to deallocate any memory used to store the |
| 178 | ** returned pointer when done. |
| 179 | ** |
| 180 | ** On Windows, characters in the range U+0001 to U+0031 and the |
| 181 | ** characters '"', '*', ':', '<', '>', '?' and '|' are invalid |
| 182 | ** to be used. Therefore, translate those to characters in the |
| 183 | ** in the range U+F001 - U+F07F (private use area), so those |
| 184 | ** characters never arrive in any Windows API. The filenames might |
| 185 | ** look strange in Windows explorer, but in the cygwin shell |
| 186 | ** everything looks as expected. |
| 187 | ** |
| 188 | ** See: <http://cygwin.com/cygwin-ug-net/using-specialnames.html> |
| @@ -195,16 +196,35 @@ | |
| 195 | wchar_t *wUnicode = zUnicode; |
| 196 | if( zUnicode==0 ){ |
| 197 | return 0; |
| 198 | } |
| 199 | MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zUnicode, nChar); |
| 200 | /* If path starts with "<drive>:/" or "<drive>:\", don't translate the ':' */ |
| 201 | if( fossil_isalpha(zUtf8[0]) && zUtf8[1]==':' |
| 202 | && (zUtf8[2]=='\\' || zUtf8[2]=='/')) { |
| 203 | zUnicode[2] = '\\'; |
| 204 | wUnicode += 3; |
| 205 | } |
| 206 | while( *wUnicode != '\0' ){ |
| 207 | if ( (*wUnicode < ' ') || wcschr(L"\"*:<>?|", *wUnicode) ){ |
| 208 | *wUnicode |= 0xF000; |
| 209 | }else if( *wUnicode == '/' ){ |
| 210 | *wUnicode = '\\'; |
| 211 |
| --- src/utf8.c | |
| +++ src/utf8.c | |
| @@ -177,12 +177,13 @@ | |
| 177 | ** Call fossil_filename_free() to deallocate any memory used to store the |
| 178 | ** returned pointer when done. |
| 179 | ** |
| 180 | ** On Windows, characters in the range U+0001 to U+0031 and the |
| 181 | ** characters '"', '*', ':', '<', '>', '?' and '|' are invalid |
| 182 | ** to be used, except in the 'extended path' prefix ('?') and |
| 183 | ** as drive specifier (':'). Therefore, translate those to characters |
| 184 | ** in the in the range U+F001 - U+F07F (private use area), so those |
| 185 | ** characters never arrive in any Windows API. The filenames might |
| 186 | ** look strange in Windows explorer, but in the cygwin shell |
| 187 | ** everything looks as expected. |
| 188 | ** |
| 189 | ** See: <http://cygwin.com/cygwin-ug-net/using-specialnames.html> |
| @@ -195,16 +196,35 @@ | |
| 196 | wchar_t *wUnicode = zUnicode; |
| 197 | if( zUnicode==0 ){ |
| 198 | return 0; |
| 199 | } |
| 200 | MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zUnicode, nChar); |
| 201 | /* |
| 202 | ** If path starts with "//?/" or "\\?\" (extended path), translate |
| 203 | ** any slashes to backslashes but leave the '?' intact |
| 204 | */ |
| 205 | if( (zUtf8[0]=='\\' || zUtf8[0]=='/') && (zUtf8[1]=='\\' || zUtf8[1]=='/') |
| 206 | && zUtf8[2]=='?' && (zUtf8[3]=='\\' || zUtf8[3]=='/')) { |
| 207 | wUnicode[0] = wUnicode[1] = wUnicode[3] = '\\'; |
| 208 | zUtf8 += 4; |
| 209 | wUnicode += 4; |
| 210 | } |
| 211 | /* |
| 212 | ** If (remainder of) path starts with "<drive>:/" or "<drive>:\", |
| 213 | ** leave the ':' intact |
| 214 | */ |
| 215 | if( fossil_isalpha(zUtf8[0]) && zUtf8[1]==':' |
| 216 | && (zUtf8[2]=='\\' || zUtf8[2]=='/')) { |
| 217 | wUnicode[2] = '\\'; |
| 218 | wUnicode += 3; |
| 219 | } |
| 220 | /* |
| 221 | ** In the remainder of the path, translate invalid characters to |
| 222 | ** characters in the Unicode private use area. This is what makes |
| 223 | ** Win32 fossil.exe work well in a Cygwin environment even when a |
| 224 | ** filename contains characters which are invalid for Win32. |
| 225 | */ |
| 226 | while( *wUnicode != '\0' ){ |
| 227 | if ( (*wUnicode < ' ') || wcschr(L"\"*:<>?|", *wUnicode) ){ |
| 228 | *wUnicode |= 0xF000; |
| 229 | }else if( *wUnicode == '/' ){ |
| 230 | *wUnicode = '\\'; |
| 231 |
+15
-3
| --- src/winhttp.c | ||
| +++ src/winhttp.c | ||
| @@ -67,15 +67,18 @@ | ||
| 67 | 67 | HttpRequest *p = (HttpRequest*)pAppData; |
| 68 | 68 | FILE *in = 0, *out = 0; |
| 69 | 69 | int amt, got; |
| 70 | 70 | int wanted = 0; |
| 71 | 71 | char *z; |
| 72 | + char zCmdFName[MAX_PATH]; | |
| 72 | 73 | char zRequestFName[MAX_PATH]; |
| 73 | 74 | char zReplyFName[MAX_PATH]; |
| 74 | 75 | char zCmd[2000]; /* Command-line to process the request */ |
| 75 | 76 | char zHdr[2000]; /* The HTTP request header */ |
| 76 | 77 | |
| 78 | + sqlite3_snprintf(MAX_PATH, zCmdFName, | |
| 79 | + "%s_cmd%d.txt", zTempPrefix, p->id); | |
| 77 | 80 | sqlite3_snprintf(MAX_PATH, zRequestFName, |
| 78 | 81 | "%s_in%d.txt", zTempPrefix, p->id); |
| 79 | 82 | sqlite3_snprintf(MAX_PATH, zReplyFName, |
| 80 | 83 | "%s_out%d.txt", zTempPrefix, p->id); |
| 81 | 84 | amt = 0; |
| @@ -108,13 +111,21 @@ | ||
| 108 | 111 | } |
| 109 | 112 | wanted -= got; |
| 110 | 113 | } |
| 111 | 114 | fclose(out); |
| 112 | 115 | out = 0; |
| 113 | - sqlite3_snprintf(sizeof(zCmd), zCmd, "\"%s\" http \"%s\" %s %s %s --nossl%s", | |
| 114 | - g.nameOfExe, g.zRepositoryName, zRequestFName, zReplyFName, | |
| 115 | - inet_ntoa(p->addr.sin_addr), p->zOptions | |
| 116 | + sqlite3_snprintf(sizeof(zCmd), zCmd, "%s%s\n%s\n%s\n%s", | |
| 117 | + get_utf8_bom(0), g.zRepositoryName, zRequestFName, zReplyFName, | |
| 118 | + inet_ntoa(p->addr.sin_addr) | |
| 119 | + ); | |
| 120 | + out = fossil_fopen(zCmdFName, "wb"); | |
| 121 | + if( out==0 ) goto end_request; | |
| 122 | + fwrite(zCmd, 1, strlen(zCmd), out); | |
| 123 | + fclose(out); | |
| 124 | + | |
| 125 | + sqlite3_snprintf(sizeof(zCmd), zCmd, "\"%s\" http -args \"%s\" --nossl%s", | |
| 126 | + g.nameOfExe, zCmdFName, p->zOptions | |
| 116 | 127 | ); |
| 117 | 128 | fossil_system(zCmd); |
| 118 | 129 | in = fossil_fopen(zReplyFName, "rb"); |
| 119 | 130 | if( in ){ |
| 120 | 131 | while( (got = fread(zHdr, 1, sizeof(zHdr), in))>0 ){ |
| @@ -126,10 +137,11 @@ | ||
| 126 | 137 | if( out ) fclose(out); |
| 127 | 138 | if( in ) fclose(in); |
| 128 | 139 | closesocket(p->s); |
| 129 | 140 | file_delete(zRequestFName); |
| 130 | 141 | file_delete(zReplyFName); |
| 142 | + file_delete(zCmdFName); | |
| 131 | 143 | free(p); |
| 132 | 144 | } |
| 133 | 145 | |
| 134 | 146 | /* |
| 135 | 147 | ** Process a single incoming SCGI request. |
| 136 | 148 |
| --- src/winhttp.c | |
| +++ src/winhttp.c | |
| @@ -67,15 +67,18 @@ | |
| 67 | HttpRequest *p = (HttpRequest*)pAppData; |
| 68 | FILE *in = 0, *out = 0; |
| 69 | int amt, got; |
| 70 | int wanted = 0; |
| 71 | char *z; |
| 72 | char zRequestFName[MAX_PATH]; |
| 73 | char zReplyFName[MAX_PATH]; |
| 74 | char zCmd[2000]; /* Command-line to process the request */ |
| 75 | char zHdr[2000]; /* The HTTP request header */ |
| 76 | |
| 77 | sqlite3_snprintf(MAX_PATH, zRequestFName, |
| 78 | "%s_in%d.txt", zTempPrefix, p->id); |
| 79 | sqlite3_snprintf(MAX_PATH, zReplyFName, |
| 80 | "%s_out%d.txt", zTempPrefix, p->id); |
| 81 | amt = 0; |
| @@ -108,13 +111,21 @@ | |
| 108 | } |
| 109 | wanted -= got; |
| 110 | } |
| 111 | fclose(out); |
| 112 | out = 0; |
| 113 | sqlite3_snprintf(sizeof(zCmd), zCmd, "\"%s\" http \"%s\" %s %s %s --nossl%s", |
| 114 | g.nameOfExe, g.zRepositoryName, zRequestFName, zReplyFName, |
| 115 | inet_ntoa(p->addr.sin_addr), p->zOptions |
| 116 | ); |
| 117 | fossil_system(zCmd); |
| 118 | in = fossil_fopen(zReplyFName, "rb"); |
| 119 | if( in ){ |
| 120 | while( (got = fread(zHdr, 1, sizeof(zHdr), in))>0 ){ |
| @@ -126,10 +137,11 @@ | |
| 126 | if( out ) fclose(out); |
| 127 | if( in ) fclose(in); |
| 128 | closesocket(p->s); |
| 129 | file_delete(zRequestFName); |
| 130 | file_delete(zReplyFName); |
| 131 | free(p); |
| 132 | } |
| 133 | |
| 134 | /* |
| 135 | ** Process a single incoming SCGI request. |
| 136 |
| --- src/winhttp.c | |
| +++ src/winhttp.c | |
| @@ -67,15 +67,18 @@ | |
| 67 | HttpRequest *p = (HttpRequest*)pAppData; |
| 68 | FILE *in = 0, *out = 0; |
| 69 | int amt, got; |
| 70 | int wanted = 0; |
| 71 | char *z; |
| 72 | char zCmdFName[MAX_PATH]; |
| 73 | char zRequestFName[MAX_PATH]; |
| 74 | char zReplyFName[MAX_PATH]; |
| 75 | char zCmd[2000]; /* Command-line to process the request */ |
| 76 | char zHdr[2000]; /* The HTTP request header */ |
| 77 | |
| 78 | sqlite3_snprintf(MAX_PATH, zCmdFName, |
| 79 | "%s_cmd%d.txt", zTempPrefix, p->id); |
| 80 | sqlite3_snprintf(MAX_PATH, zRequestFName, |
| 81 | "%s_in%d.txt", zTempPrefix, p->id); |
| 82 | sqlite3_snprintf(MAX_PATH, zReplyFName, |
| 83 | "%s_out%d.txt", zTempPrefix, p->id); |
| 84 | amt = 0; |
| @@ -108,13 +111,21 @@ | |
| 111 | } |
| 112 | wanted -= got; |
| 113 | } |
| 114 | fclose(out); |
| 115 | out = 0; |
| 116 | sqlite3_snprintf(sizeof(zCmd), zCmd, "%s%s\n%s\n%s\n%s", |
| 117 | get_utf8_bom(0), g.zRepositoryName, zRequestFName, zReplyFName, |
| 118 | inet_ntoa(p->addr.sin_addr) |
| 119 | ); |
| 120 | out = fossil_fopen(zCmdFName, "wb"); |
| 121 | if( out==0 ) goto end_request; |
| 122 | fwrite(zCmd, 1, strlen(zCmd), out); |
| 123 | fclose(out); |
| 124 | |
| 125 | sqlite3_snprintf(sizeof(zCmd), zCmd, "\"%s\" http -args \"%s\" --nossl%s", |
| 126 | g.nameOfExe, zCmdFName, p->zOptions |
| 127 | ); |
| 128 | fossil_system(zCmd); |
| 129 | in = fossil_fopen(zReplyFName, "rb"); |
| 130 | if( in ){ |
| 131 | while( (got = fread(zHdr, 1, sizeof(zHdr), in))>0 ){ |
| @@ -126,10 +137,11 @@ | |
| 137 | if( out ) fclose(out); |
| 138 | if( in ) fclose(in); |
| 139 | closesocket(p->s); |
| 140 | file_delete(zRequestFName); |
| 141 | file_delete(zReplyFName); |
| 142 | file_delete(zCmdFName); |
| 143 | free(p); |
| 144 | } |
| 145 | |
| 146 | /* |
| 147 | ** Process a single incoming SCGI request. |
| 148 |