Fossil SCM
Integrate the improved filename escaping logic for windows. Also add the "fossil test-escaped-arg" command for testing the logic.
Commit
7a2d06a46a57b2b2bd867a2fdef710482693557e50401df2af628eca49821e67
Parent
ce16c59499bf9ac…
1 file changed
+53
-4
+53
-4
| --- src/blob.c | ||
| +++ src/blob.c | ||
| @@ -1301,35 +1301,84 @@ | ||
| 1301 | 1301 | char c; |
| 1302 | 1302 | int needEscape = 0; |
| 1303 | 1303 | int n = blob_size(pBlob); |
| 1304 | 1304 | char *z = blob_buffer(pBlob); |
| 1305 | 1305 | #if defined(_WIN32) |
| 1306 | + const char cDirSep = '\\'; /* Use \ as directory separator */ | |
| 1306 | 1307 | const char cQuote = '"'; /* Use "..." quoting on windows */ |
| 1308 | + const char cEscape = '^'; /* Use ^X escaping on windows */ | |
| 1307 | 1309 | #else |
| 1310 | + const char cDirSep = '/'; /* Use / as directory separator */ | |
| 1308 | 1311 | const char cQuote = '\''; /* Use '...' quoting on unix */ |
| 1312 | + const char cEscape = '\\'; /* Use \X escaping on unix */ | |
| 1309 | 1313 | #endif |
| 1310 | 1314 | |
| 1311 | 1315 | for(i=0; (c = zIn[i])!=0; i++){ |
| 1312 | 1316 | if( c==cQuote || (unsigned char)c<' ' || |
| 1313 | - c=='\\' || c==';' || c=='*' || c=='?' || c=='[' ){ | |
| 1317 | + c==cEscape || c==';' || c=='*' || c=='?' || c=='[' ){ | |
| 1314 | 1318 | Blob bad; |
| 1315 | 1319 | blob_token(pBlob, &bad); |
| 1316 | 1320 | fossil_fatal("the [%s] argument to the \"%s\" command contains " |
| 1317 | 1321 | "a character (ascii 0x%02x) that is a security risk", |
| 1318 | 1322 | zIn, blob_str(&bad), c); |
| 1319 | 1323 | } |
| 1320 | - if( !needEscape && !fossil_isalnum(c) && c!='/' && c!='.' && c!='_' ){ | |
| 1324 | + if( !needEscape && !fossil_isalnum(c) && c!=cDirSep && c!='.' && c!='_' ){ | |
| 1321 | 1325 | needEscape = 1; |
| 1322 | 1326 | } |
| 1323 | 1327 | } |
| 1324 | 1328 | if( n>0 && !fossil_isspace(z[n-1]) ){ |
| 1325 | 1329 | blob_append_char(pBlob, ' '); |
| 1326 | 1330 | } |
| 1327 | 1331 | if( needEscape ) blob_append_char(pBlob, cQuote); |
| 1328 | - if( zIn[0]=='-' ) blob_append(pBlob, "./", 2); | |
| 1332 | + if( zIn[0]=='-' ){ | |
| 1333 | + blob_append_char(pBlob, '.'); | |
| 1334 | + blob_append_char(pBlob, cDirSep); | |
| 1335 | + } | |
| 1336 | +#if defined(_WIN32) | |
| 1337 | + if( needEscape ){ | |
| 1338 | + for(i=0; (c = zIn[i])!=0; i++){ | |
| 1339 | + if( c==cQuote || c==cEscape ) blob_append_char(pBlob, cEscape); | |
| 1340 | + blob_append_char(pBlob, c); | |
| 1341 | + } | |
| 1342 | + }else{ | |
| 1343 | + blob_append(pBlob, zIn, -1); | |
| 1344 | + } | |
| 1345 | +#else | |
| 1329 | 1346 | blob_append(pBlob, zIn, -1); |
| 1330 | - if( needEscape ) blob_append_char(pBlob, cQuote); | |
| 1347 | +#endif | |
| 1348 | + if( needEscape ){ | |
| 1349 | +#if defined(_WIN32) | |
| 1350 | + /* NOTE: Trailing backslash must be doubled before final double quote. */ | |
| 1351 | + if( pBlob->aData[pBlob->nUsed-1]==cEscape ){ | |
| 1352 | + blob_append_char(pBlob, cEscape); | |
| 1353 | + } | |
| 1354 | +#endif | |
| 1355 | + blob_append_char(pBlob, cQuote); | |
| 1356 | + } | |
| 1357 | +} | |
| 1358 | + | |
| 1359 | +/* | |
| 1360 | +** COMMAND: test-escaped-arg | |
| 1361 | +** | |
| 1362 | +** Usage %fossil ARG ... | |
| 1363 | +** | |
| 1364 | +** Run each argment through blob_append_escaped_arg() and show the | |
| 1365 | +** result. Append each argument to "fossil test-echo" and run that | |
| 1366 | +** using fossil_system() to verify that it really does get escaped | |
| 1367 | +** correctly. | |
| 1368 | +*/ | |
| 1369 | +void test_escaped_arg__cmd(void){ | |
| 1370 | + int i; | |
| 1371 | + Blob x; | |
| 1372 | + blob_init(&x, 0, 0); | |
| 1373 | + for(i=2; i<g.argc; i++){ | |
| 1374 | + fossil_print("%3d [%s]: ", i, g.argv[i]); | |
| 1375 | + blob_appendf(&x, "fossil test-echo %$", g.argv[i]); | |
| 1376 | + fossil_print("%s\n", blob_str(&x)); | |
| 1377 | + fossil_system(blob_str(&x)); | |
| 1378 | + blob_reset(&x); | |
| 1379 | + } | |
| 1331 | 1380 | } |
| 1332 | 1381 | |
| 1333 | 1382 | /* |
| 1334 | 1383 | ** A read(2)-like impl for the Blob class. Reads (copies) up to nLen |
| 1335 | 1384 | ** bytes from pIn, starting at position pIn->iCursor, and copies them |
| 1336 | 1385 |
| --- src/blob.c | |
| +++ src/blob.c | |
| @@ -1301,35 +1301,84 @@ | |
| 1301 | char c; |
| 1302 | int needEscape = 0; |
| 1303 | int n = blob_size(pBlob); |
| 1304 | char *z = blob_buffer(pBlob); |
| 1305 | #if defined(_WIN32) |
| 1306 | const char cQuote = '"'; /* Use "..." quoting on windows */ |
| 1307 | #else |
| 1308 | const char cQuote = '\''; /* Use '...' quoting on unix */ |
| 1309 | #endif |
| 1310 | |
| 1311 | for(i=0; (c = zIn[i])!=0; i++){ |
| 1312 | if( c==cQuote || (unsigned char)c<' ' || |
| 1313 | c=='\\' || c==';' || c=='*' || c=='?' || c=='[' ){ |
| 1314 | Blob bad; |
| 1315 | blob_token(pBlob, &bad); |
| 1316 | fossil_fatal("the [%s] argument to the \"%s\" command contains " |
| 1317 | "a character (ascii 0x%02x) that is a security risk", |
| 1318 | zIn, blob_str(&bad), c); |
| 1319 | } |
| 1320 | if( !needEscape && !fossil_isalnum(c) && c!='/' && c!='.' && c!='_' ){ |
| 1321 | needEscape = 1; |
| 1322 | } |
| 1323 | } |
| 1324 | if( n>0 && !fossil_isspace(z[n-1]) ){ |
| 1325 | blob_append_char(pBlob, ' '); |
| 1326 | } |
| 1327 | if( needEscape ) blob_append_char(pBlob, cQuote); |
| 1328 | if( zIn[0]=='-' ) blob_append(pBlob, "./", 2); |
| 1329 | blob_append(pBlob, zIn, -1); |
| 1330 | if( needEscape ) blob_append_char(pBlob, cQuote); |
| 1331 | } |
| 1332 | |
| 1333 | /* |
| 1334 | ** A read(2)-like impl for the Blob class. Reads (copies) up to nLen |
| 1335 | ** bytes from pIn, starting at position pIn->iCursor, and copies them |
| 1336 |
| --- src/blob.c | |
| +++ src/blob.c | |
| @@ -1301,35 +1301,84 @@ | |
| 1301 | char c; |
| 1302 | int needEscape = 0; |
| 1303 | int n = blob_size(pBlob); |
| 1304 | char *z = blob_buffer(pBlob); |
| 1305 | #if defined(_WIN32) |
| 1306 | const char cDirSep = '\\'; /* Use \ as directory separator */ |
| 1307 | const char cQuote = '"'; /* Use "..." quoting on windows */ |
| 1308 | const char cEscape = '^'; /* Use ^X escaping on windows */ |
| 1309 | #else |
| 1310 | const char cDirSep = '/'; /* Use / as directory separator */ |
| 1311 | const char cQuote = '\''; /* Use '...' quoting on unix */ |
| 1312 | const char cEscape = '\\'; /* Use \X escaping on unix */ |
| 1313 | #endif |
| 1314 | |
| 1315 | for(i=0; (c = zIn[i])!=0; i++){ |
| 1316 | if( c==cQuote || (unsigned char)c<' ' || |
| 1317 | c==cEscape || c==';' || c=='*' || c=='?' || c=='[' ){ |
| 1318 | Blob bad; |
| 1319 | blob_token(pBlob, &bad); |
| 1320 | fossil_fatal("the [%s] argument to the \"%s\" command contains " |
| 1321 | "a character (ascii 0x%02x) that is a security risk", |
| 1322 | zIn, blob_str(&bad), c); |
| 1323 | } |
| 1324 | if( !needEscape && !fossil_isalnum(c) && c!=cDirSep && c!='.' && c!='_' ){ |
| 1325 | needEscape = 1; |
| 1326 | } |
| 1327 | } |
| 1328 | if( n>0 && !fossil_isspace(z[n-1]) ){ |
| 1329 | blob_append_char(pBlob, ' '); |
| 1330 | } |
| 1331 | if( needEscape ) blob_append_char(pBlob, cQuote); |
| 1332 | if( zIn[0]=='-' ){ |
| 1333 | blob_append_char(pBlob, '.'); |
| 1334 | blob_append_char(pBlob, cDirSep); |
| 1335 | } |
| 1336 | #if defined(_WIN32) |
| 1337 | if( needEscape ){ |
| 1338 | for(i=0; (c = zIn[i])!=0; i++){ |
| 1339 | if( c==cQuote || c==cEscape ) blob_append_char(pBlob, cEscape); |
| 1340 | blob_append_char(pBlob, c); |
| 1341 | } |
| 1342 | }else{ |
| 1343 | blob_append(pBlob, zIn, -1); |
| 1344 | } |
| 1345 | #else |
| 1346 | blob_append(pBlob, zIn, -1); |
| 1347 | #endif |
| 1348 | if( needEscape ){ |
| 1349 | #if defined(_WIN32) |
| 1350 | /* NOTE: Trailing backslash must be doubled before final double quote. */ |
| 1351 | if( pBlob->aData[pBlob->nUsed-1]==cEscape ){ |
| 1352 | blob_append_char(pBlob, cEscape); |
| 1353 | } |
| 1354 | #endif |
| 1355 | blob_append_char(pBlob, cQuote); |
| 1356 | } |
| 1357 | } |
| 1358 | |
| 1359 | /* |
| 1360 | ** COMMAND: test-escaped-arg |
| 1361 | ** |
| 1362 | ** Usage %fossil ARG ... |
| 1363 | ** |
| 1364 | ** Run each argment through blob_append_escaped_arg() and show the |
| 1365 | ** result. Append each argument to "fossil test-echo" and run that |
| 1366 | ** using fossil_system() to verify that it really does get escaped |
| 1367 | ** correctly. |
| 1368 | */ |
| 1369 | void test_escaped_arg__cmd(void){ |
| 1370 | int i; |
| 1371 | Blob x; |
| 1372 | blob_init(&x, 0, 0); |
| 1373 | for(i=2; i<g.argc; i++){ |
| 1374 | fossil_print("%3d [%s]: ", i, g.argv[i]); |
| 1375 | blob_appendf(&x, "fossil test-echo %$", g.argv[i]); |
| 1376 | fossil_print("%s\n", blob_str(&x)); |
| 1377 | fossil_system(blob_str(&x)); |
| 1378 | blob_reset(&x); |
| 1379 | } |
| 1380 | } |
| 1381 | |
| 1382 | /* |
| 1383 | ** A read(2)-like impl for the Blob class. Reads (copies) up to nLen |
| 1384 | ** bytes from pIn, starting at position pIn->iCursor, and copies them |
| 1385 |