Fossil SCM

Fix the "fossil all ui" command so that it works on Windows. But also comment out some very confused logic in process_on_web_page() that is associated with "--baseurl" option. This logic needs to be fixed prior to merging with trunk.

drh 2016-12-01 14:56 UTC trunk
Commit da1c769cd236de86ccbc93cf5b5a8bd46ff043cc
2 files changed +15 +131 -74
+15
--- src/file.c
+++ src/file.c
@@ -1435,5 +1435,20 @@
14351435
int i;
14361436
for(i=2; i<g.argc; i++){
14371437
fossil_print("%s %s\n", file_is_win_reserved(g.argv[i]), g.argv[i]);
14381438
}
14391439
}
1440
+
1441
+/*
1442
+** Remove surplus "/" characters from the beginning of a full pathname.
1443
+** Extra leading "/" characters are benign on unix. But on Windows
1444
+** machines, they must be removed. Example: Convert "/C:/fossil/xyx.fossil"
1445
+** into "c:/fossil/xyz.fossil".
1446
+*/
1447
+const char *file_cleanup_fullpath(const char *z){
1448
+#ifdef _WIN32
1449
+ if( z[0]=='/' && fossil_isalpha(z[1]) && z[2]==':' && z[3]=='/' ) z++;
1450
+#else
1451
+ while( z[0]=='/' && z[1]=='/' ) z++;
1452
+#endif
1453
+ return z;
1454
+}
14401455
--- src/file.c
+++ src/file.c
@@ -1435,5 +1435,20 @@
1435 int i;
1436 for(i=2; i<g.argc; i++){
1437 fossil_print("%s %s\n", file_is_win_reserved(g.argv[i]), g.argv[i]);
1438 }
1439 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1440
--- src/file.c
+++ src/file.c
@@ -1435,5 +1435,20 @@
1435 int i;
1436 for(i=2; i<g.argc; i++){
1437 fossil_print("%s %s\n", file_is_win_reserved(g.argv[i]), g.argv[i]);
1438 }
1439 }
1440
1441 /*
1442 ** Remove surplus "/" characters from the beginning of a full pathname.
1443 ** Extra leading "/" characters are benign on unix. But on Windows
1444 ** machines, they must be removed. Example: Convert "/C:/fossil/xyx.fossil"
1445 ** into "c:/fossil/xyz.fossil".
1446 */
1447 const char *file_cleanup_fullpath(const char *z){
1448 #ifdef _WIN32
1449 if( z[0]=='/' && fossil_isalpha(z[1]) && z[2]==':' && z[3]=='/' ) z++;
1450 #else
1451 while( z[0]=='/' && z[1]=='/' ) z++;
1452 #endif
1453 return z;
1454 }
1455
+131 -74
--- src/main.c
+++ src/main.c
@@ -1199,17 +1199,30 @@
11991199
12001200
assert( g.db==0 );
12011201
if( fossil_strcmp(g.zRepositoryName,"/")==0 ){
12021202
/* For the special case of the "repository directory" being "/",
12031203
** show all of the repositories named in the ~/.fossil database.
1204
+ **
1205
+ ** On unix systems, then entries are of the form "repo:/home/..."
1206
+ ** and on Windows systems they are like "repo:C:/Users/...". We want
1207
+ ** to skip the first 6 characters on unix and the first 5 characters
1208
+ ** on Windows.
12041209
*/
12051210
db_open_config(1, 0);
1211
+#ifdef _WIN32
1212
+ db_multi_exec(
1213
+ "CREATE TEMP VIEW sfile AS"
1214
+ " SELECT substr(name,6) AS 'pathname' FROM global_config"
1215
+ " WHERE name GLOB 'repo:*'"
1216
+ );
1217
+#else
12061218
db_multi_exec(
12071219
"CREATE TEMP VIEW sfile AS"
12081220
" SELECT substr(name,7) AS 'pathname' FROM global_config"
12091221
" WHERE name GLOB 'repo:*'"
12101222
);
1223
+#endif
12111224
}else{
12121225
/* The default case: All repositories under the g.zRepositoryName
12131226
** directory.
12141227
*/
12151228
blob_init(&base, g.zRepositoryName, -1);
@@ -1233,11 +1246,11 @@
12331246
db_prepare(&q, "SELECT pathname, substr(pathname,-7,-100000)||'/home'"
12341247
" FROM sfile ORDER BY pathname COLLATE nocase;");
12351248
while( db_step(&q)==SQLITE_ROW ){
12361249
const char *zName = db_column_text(&q, 0);
12371250
const char *zUrl = db_column_text(&q, 1);
1238
- @ <li><a href="%R/%h(zUrl)" target="_blank">%h(zName)</a></li>
1251
+ @ <li><a href="%R/%T(zUrl)" target="_blank">%h(zName)</a></li>
12391252
}
12401253
@ </ol>
12411254
}else{
12421255
@ <h1>No Repositories Found</h1>
12431256
}
@@ -1274,27 +1287,25 @@
12741287
static void process_one_web_page(
12751288
const char *zNotFound, /* Redirect here on a 404 if not NULL */
12761289
Glob *pFileGlob, /* Deliver static files matching */
12771290
int allowRepoList /* Send repo list for "/" URL */
12781291
){
1279
- const char *zPathInfo;
1280
- const char *zDirPathInfo;
1292
+ const char *zPathInfo = PD("PATH_INFO", "");
1293
+ /* const char *zDirPathInfo; **** refactor needed */
12811294
char *zPath = NULL;
12821295
int i;
12831296
const CmdOrPage *pCmd = 0;
1297
+ const char *zBase = g.zRepositoryName;
12841298
12851299
/* Handle universal query parameters */
12861300
if( PB("utc") ){
12871301
g.fTimeFormat = 1;
12881302
}else if( PB("localtime") ){
12891303
g.fTimeFormat = 2;
12901304
}
12911305
1292
- /* If the repository has not been opened already, then find the
1293
- ** repository based on the first element of PATH_INFO and open it.
1294
- */
1295
- zDirPathInfo = zPathInfo = PD("PATH_INFO","");
1306
+#if 0 /* Refactor needed */
12961307
/* For the PATH_INFO that will be used to help build the final
12971308
** g.zBaseURL and g.zTop (only), skip over the initial directory
12981309
** portion of PATH_INFO; otherwise, it may be duplicated.
12991310
*/
13001311
if( g.zTop ){
@@ -1301,79 +1312,155 @@
13011312
int nTop = strlen(g.zTop);
13021313
if ( strncmp(zDirPathInfo, g.zTop, nTop)==0 ){
13031314
zDirPathInfo += nTop;
13041315
}
13051316
}
1317
+#endif
1318
+
1319
+ /* If the repository has not been opened already, then find the
1320
+ ** repository based on the first element of PATH_INFO and open it.
1321
+ */
13061322
if( !g.repositoryOpen ){
1307
- char *zRepo, *zToFree;
1308
- const char *zOldScript = PD("SCRIPT_NAME", "");
1309
- char *zNewScript;
1310
- int j, k;
1311
- i64 szFile;
1323
+ char *zRepo; /* Candidate repository name */
1324
+ char *zToFree = 0; /* Malloced memory that needs to be freed */
1325
+ const char *zCleanRepo; /* zRepo with surplus leading "/" removed */
1326
+ const char *zOldScript = PD("SCRIPT_NAME", ""); /* Original SCRIPT_NAME */
1327
+ char *zNewScript; /* Revised SCRIPT_NAME after processing */
1328
+ int j, k; /* Loop variables */
1329
+ i64 szFile; /* File size of the candidate repository */
13121330
13131331
i = zPathInfo[0]!=0;
1332
+ if( fossil_strcmp(g.zRepositoryName, "/")==0 ){
1333
+ zBase++;
1334
+#ifdef _WIN32
1335
+ if( sqlite3_strglob("/[a-zA-Z]:/*", zPathInfo)==0 ) i = 4;
1336
+#endif
1337
+ }
13141338
while( 1 ){
13151339
while( zPathInfo[i] && zPathInfo[i]!='/' ){ i++; }
1316
- zRepo = zToFree = mprintf("%s%.*s.fossil",g.zRepositoryName,i,zPathInfo);
1340
+
1341
+ /* The candidate repository name is some prefix of the PATH_INFO
1342
+ ** with ".fossil" appended */
1343
+ zRepo = zToFree = mprintf("%s%.*s.fossil",zBase,i,zPathInfo);
1344
+ if( g.fHttpTrace ){
1345
+ @ <!-- Looking for repository named "%h(zRepo)" -->
1346
+ fprintf(stderr, "# looking for repository named \"%s\"\n", zRepo);
1347
+ }
1348
+
13171349
1318
- /* To avoid mischief, make sure the repository basename contains no
1350
+ /* For safety -- to prevent an attacker from accessing arbitrary disk
1351
+ ** files by sending a maliciously crafted request URI to a public
1352
+ ** server -- make sure the repository basename contains no
13191353
** characters other than alphanumerics, "/", "_", "-", and ".", and
13201354
** that "-" never occurs immediately after a "/" and that "." is always
13211355
** surrounded by two alphanumerics. Any character that does not
13221356
** satisfy these constraints is converted into "_".
13231357
*/
13241358
szFile = 0;
1325
- for(j=strlen(g.zRepositoryName)+1, k=0; zRepo[j] && k<i-1; j++, k++){
1359
+ for(j=strlen(zBase)+1, k=0; zRepo[j] && k<i-1; j++, k++){
13261360
char c = zRepo[j];
13271361
if( fossil_isalnum(c) ) continue;
1362
+#ifdef _WIN32
1363
+ /* Allow names to begin with "/X:/" on windows */
1364
+ if( c==':' && j==2 && sqlite3_strglob("/[a-zA-Z]:/*", zRepo)==0 ){
1365
+ continue;
1366
+ }
1367
+#endif
13281368
if( c=='/' ) continue;
13291369
if( c=='_' ) continue;
13301370
if( c=='-' && zRepo[j-1]!='/' ) continue;
13311371
if( c=='.' && fossil_isalnum(zRepo[j-1]) && fossil_isalnum(zRepo[j+1])){
13321372
continue;
13331373
}
1374
+ /* If we reach this point, it means that the request URI contains
1375
+ ** an illegal character or character combination. Provoke a
1376
+ ** "Not Found" error. */
13341377
szFile = 1;
1378
+ if( g.fHttpTrace ){
1379
+ @ <!-- Unsafe pathname rejected: "%h(zRepo)" -->
1380
+ fprintf(stderr, "# unsafe pathname rejected: %s\n", zRepo);
1381
+ }
13351382
break;
13361383
}
1384
+
1385
+ /* Check to see if a file name zRepo exists. If a file named zRepo
1386
+ ** does not exist, szFile will become -1. If the file does exist,
1387
+ ** then szFile will become zero (for an empty file) or positive.
1388
+ ** Special case: Assume any file with a basename of ".fossil" does
1389
+ ** not exist.
1390
+ */
1391
+ zCleanRepo = file_cleanup_fullpath(zRepo);
13371392
if( szFile==0 && sqlite3_strglob("*/.fossil",zRepo)!=0 ){
1338
- if( zRepo[0]=='/' && zRepo[1]=='/' ){ zRepo++; j--; }
1339
- szFile = file_size(zRepo);
1340
- /* this should only be set from the --baseurl option, not CGI */
1393
+ szFile = file_size(zCleanRepo);
1394
+ if( g.fHttpTrace ){
1395
+ @ <!-- file_size(%h(zCleanRepo)) is %lld(szFile) -->
1396
+ fprintf(stderr, "# file_size(%s) = %lld\n", zCleanRepo, szFile);
1397
+ }
1398
+
1399
+#if 0
1400
+ /* This logic for handling --baseurl is confused and needs to be
1401
+ ** completely rethought. */
13411402
if( g.zBaseURL && g.zBaseURL[0]!=0 && g.zTop && g.zTop[0]!=0 &&
13421403
file_isdir(g.zRepositoryName)==1 ){
13431404
if( zPathInfo==zDirPathInfo ){
13441405
g.zBaseURL = mprintf("%s%.*s", g.zBaseURL, i, zPathInfo);
13451406
g.zTop = mprintf("%s%.*s", g.zTop, i, zPathInfo);
13461407
}
13471408
}
1409
+#endif
13481410
}
1411
+
1412
+ /* If no file named by zRepo exists, remove the added ".fossil" suffix
1413
+ ** and check to see if there is a file or directory with the same
1414
+ ** name as the raw PATH_INFO text.
1415
+ */
13491416
if( szFile<0 && i>0 ){
13501417
const char *zMimetype;
1351
- assert( fossil_strcmp(&zRepo[j], ".fossil")==0 );
1352
- zRepo[j] = 0;
1353
- if( zPathInfo[i]=='/' && file_isdir(zRepo)==1 ){
1418
+ assert( fossil_strcmp(&zRepo[j], ".fossil")==0 );
1419
+ zRepo[j] = 0; /* Remove the ".fossil" suffix */
1420
+
1421
+ /* The PATH_INFO prefix seen so far is a valid directory.
1422
+ ** Continue the loop with the next element of the PATH_INFO */
1423
+ if( zPathInfo[i]=='/' && file_isdir(zCleanRepo)==1 ){
13541424
fossil_free(zToFree);
13551425
i++;
13561426
continue;
13571427
}
1428
+
1429
+ /* If zRepo is the name of an ordinary file that matches the
1430
+ ** "--file GLOB" pattern, then the CGI reply is the text of
1431
+ ** of the file.
1432
+ **
1433
+ ** For safety, do not allow any file whose name contains ".fossil"
1434
+ ** to be returned this way, to prevent complete repositories from
1435
+ ** being delivered accidently. This is not intended to be a
1436
+ ** general-purpose web server. The "--file GLOB" mechanism is
1437
+ ** designed to allow the delivery of a few static images or HTML
1438
+ ** pages.
1439
+ */
13581440
if( pFileGlob!=0
1359
- && file_isfile(zRepo)
1360
- && glob_match(pFileGlob, zRepo)
1441
+ && file_isfile(zCleanRepo)
1442
+ && glob_match(pFileGlob, file_cleanup_fullpath(zRepo))
13611443
&& sqlite3_strglob("*.fossil*",zRepo)!=0
13621444
&& (zMimetype = mimetype_from_name(zRepo))!=0
13631445
&& strcmp(zMimetype, "application/x-fossil-artifact")!=0
13641446
){
13651447
Blob content;
1366
- blob_read_from_file(&content, zRepo);
1448
+ blob_read_from_file(&content, file_cleanup_fullpath(zRepo));
13671449
cgi_set_content_type(zMimetype);
13681450
cgi_set_content(&content);
13691451
cgi_reply();
13701452
return;
13711453
}
13721454
zRepo[j] = '.';
13731455
}
13741456
1457
+ /* If we reach this point, it means that the search of the PATH_INFO
1458
+ ** string is finished. Either zRepo contains the name of the
1459
+ ** repository to be used, or else no repository could be found an
1460
+ ** some kind of error response is required.
1461
+ */
13751462
if( szFile<1024 ){
13761463
set_base_url(0);
13771464
if( strcmp(zPathInfo,"/")==0
13781465
&& allowRepoList
13791466
&& repo_list_page() ){
@@ -1393,34 +1480,49 @@
13931480
}
13941481
return;
13951482
}
13961483
break;
13971484
}
1485
+
1486
+ /* Add the repository name (without the ".fossil" suffix) to the end
1487
+ ** of SCRIPT_NAME and remove the repository name from the beginning
1488
+ ** of PATH_INFO.
1489
+ */
13981490
zNewScript = mprintf("%s%.*s", zOldScript, i, zPathInfo);
13991491
cgi_replace_parameter("PATH_INFO", &zPathInfo[i+1]);
14001492
zPathInfo += i;
14011493
cgi_replace_parameter("SCRIPT_NAME", zNewScript);
1402
- db_open_repository(zRepo);
1494
+ db_open_repository(file_cleanup_fullpath(zRepo));
14031495
if( g.fHttpTrace ){
1496
+ @ <!-- repository: "%h(zRepo)" -->
1497
+ @ <!-- new PATH_INFO: "%h(zPathInfo)" -->
1498
+ @ <!-- new SCRIPT_NAME: "%h(zNewScript)" -->
14041499
fprintf(stderr,
14051500
"# repository: [%s]\n"
14061501
"# new PATH_INFO = [%s]\n"
14071502
"# new SCRIPT_NAME = [%s]\n",
14081503
zRepo, zPathInfo, zNewScript);
14091504
}
14101505
}
14111506
1412
- /* Find the page that the user has requested, construct and deliver that
1413
- ** page.
1507
+ /* At this point, the appropriate repository database file will have
1508
+ ** been opened. Use the first element of PATH_INFO as the page name
1509
+ ** and deliver the appropriate page back to the user.
14141510
*/
14151511
if( g.zContentType &&
14161512
strncmp(g.zContentType, "application/x-fossil", 20)==0 ){
1513
+ /* Special case: If the content mimetype shows that it is "fossil sync"
1514
+ ** payload, then pretend that the PATH_INFO is /xfer so that we always
1515
+ ** invoke the sync page. */
14171516
zPathInfo = "/xfer";
14181517
}
14191518
set_base_url(0);
14201519
if( zPathInfo==0 || zPathInfo[0]==0
14211520
|| (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
1521
+ /* Second special case: If the PATH_INFO is blank, issue a redirect to
1522
+ ** the home page identified by the "index-page" setting in the repository
1523
+ ** CONFIG table, to "/index" if there no "index-page" setting. */
14221524
#ifdef FOSSIL_ENABLE_JSON
14231525
if(g.json.isJsonMode){
14241526
json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
14251527
fossil_exit(0);
14261528
}
@@ -1437,56 +1539,10 @@
14371539
g.zPath = &zPath[1];
14381540
for(i=1; zPath[i] && zPath[i]!='/'; i++){}
14391541
if( zPath[i]=='/' ){
14401542
zPath[i] = 0;
14411543
g.zExtra = &zPath[i+1];
1442
-
1443
-#ifdef FOSSIL_ENABLE_SUBREPOSITORY
1444
- char *zAltRepo = 0;
1445
- /* 2016-09-21: Subrepos are undocumented and apparently no longer work.
1446
- ** So they are now removed unless the -DFOSSIL_ENABLE_SUBREPOSITORY
1447
- ** compile-time option is used. If there are no complaints after
1448
- ** a while, we can delete the code entirely.
1449
- */
1450
- /* Look for sub-repositories. A sub-repository is another repository
1451
- ** that accepts the login credentials of the current repository. A
1452
- ** subrepository is identified by a CONFIG table entry "subrepo:NAME"
1453
- ** where NAME is the first component of the path. The value of the
1454
- ** the CONFIG entries is the string "USER:FILENAME" where USER is the
1455
- ** USER name to log in as in the subrepository and FILENAME is the
1456
- ** repository filename.
1457
- */
1458
- zAltRepo = db_text(0, "SELECT value FROM config WHERE name='subrepo:%q'",
1459
- g.zPath);
1460
- if( zAltRepo ){
1461
- int nHost;
1462
- int jj;
1463
- char *zUser = zAltRepo;
1464
- login_check_credentials();
1465
- for(jj=0; zAltRepo[jj] && zAltRepo[jj]!=':'; jj++){}
1466
- if( zAltRepo[jj]==':' ){
1467
- zAltRepo[jj] = 0;
1468
- zAltRepo += jj+1;
1469
- }else{
1470
- zUser = "nobody";
1471
- }
1472
- if( g.zLogin==0 || g.zLogin[0]==0 ) zUser = "nobody";
1473
- if( zAltRepo[0]!='/' ){
1474
- zAltRepo = mprintf("%s/../%s", g.zRepositoryName, zAltRepo);
1475
- file_simplify_name(zAltRepo, -1, 0);
1476
- }
1477
- db_close(1);
1478
- db_open_repository(zAltRepo);
1479
- login_as_user(zUser);
1480
- g.perm.Password = 0;
1481
- zPath += i;
1482
- nHost = g.zTop - g.zBaseURL;
1483
- g.zBaseURL = mprintf("%z/%s", g.zBaseURL, g.zPath);
1484
- g.zTop = g.zBaseURL + nHost;
1485
- continue;
1486
- }
1487
-#endif /* FOSSIL_ENABLE_SUBREPOSITORY */
14881544
}else{
14891545
g.zExtra = 0;
14901546
}
14911547
break;
14921548
}
@@ -2228,11 +2284,12 @@
22282284
** the REPOSITORY can only be a directory if the --notfound option is
22292285
** also present.
22302286
**
22312287
** For the special case REPOSITORY name of "/", the list global configuration
22322288
** database is consulted for a list of all known repositories. The --repolist
2233
-** option is implied by this special case.
2289
+** option is implied by this special case. See also the "fossil all ui"
2290
+** command.
22342291
**
22352292
** By default, the "ui" command provides full administrative access without
22362293
** having to log in. This can be disabled by turning off the "localauth"
22372294
** setting. Automatic login for the "server" command is available if the
22382295
** --localauth option is present and the "localauth" setting is off and the
22392296
--- src/main.c
+++ src/main.c
@@ -1199,17 +1199,30 @@
1199
1200 assert( g.db==0 );
1201 if( fossil_strcmp(g.zRepositoryName,"/")==0 ){
1202 /* For the special case of the "repository directory" being "/",
1203 ** show all of the repositories named in the ~/.fossil database.
 
 
 
 
 
1204 */
1205 db_open_config(1, 0);
 
 
 
 
 
 
 
1206 db_multi_exec(
1207 "CREATE TEMP VIEW sfile AS"
1208 " SELECT substr(name,7) AS 'pathname' FROM global_config"
1209 " WHERE name GLOB 'repo:*'"
1210 );
 
1211 }else{
1212 /* The default case: All repositories under the g.zRepositoryName
1213 ** directory.
1214 */
1215 blob_init(&base, g.zRepositoryName, -1);
@@ -1233,11 +1246,11 @@
1233 db_prepare(&q, "SELECT pathname, substr(pathname,-7,-100000)||'/home'"
1234 " FROM sfile ORDER BY pathname COLLATE nocase;");
1235 while( db_step(&q)==SQLITE_ROW ){
1236 const char *zName = db_column_text(&q, 0);
1237 const char *zUrl = db_column_text(&q, 1);
1238 @ <li><a href="%R/%h(zUrl)" target="_blank">%h(zName)</a></li>
1239 }
1240 @ </ol>
1241 }else{
1242 @ <h1>No Repositories Found</h1>
1243 }
@@ -1274,27 +1287,25 @@
1274 static void process_one_web_page(
1275 const char *zNotFound, /* Redirect here on a 404 if not NULL */
1276 Glob *pFileGlob, /* Deliver static files matching */
1277 int allowRepoList /* Send repo list for "/" URL */
1278 ){
1279 const char *zPathInfo;
1280 const char *zDirPathInfo;
1281 char *zPath = NULL;
1282 int i;
1283 const CmdOrPage *pCmd = 0;
 
1284
1285 /* Handle universal query parameters */
1286 if( PB("utc") ){
1287 g.fTimeFormat = 1;
1288 }else if( PB("localtime") ){
1289 g.fTimeFormat = 2;
1290 }
1291
1292 /* If the repository has not been opened already, then find the
1293 ** repository based on the first element of PATH_INFO and open it.
1294 */
1295 zDirPathInfo = zPathInfo = PD("PATH_INFO","");
1296 /* For the PATH_INFO that will be used to help build the final
1297 ** g.zBaseURL and g.zTop (only), skip over the initial directory
1298 ** portion of PATH_INFO; otherwise, it may be duplicated.
1299 */
1300 if( g.zTop ){
@@ -1301,79 +1312,155 @@
1301 int nTop = strlen(g.zTop);
1302 if ( strncmp(zDirPathInfo, g.zTop, nTop)==0 ){
1303 zDirPathInfo += nTop;
1304 }
1305 }
 
 
 
 
 
1306 if( !g.repositoryOpen ){
1307 char *zRepo, *zToFree;
1308 const char *zOldScript = PD("SCRIPT_NAME", "");
1309 char *zNewScript;
1310 int j, k;
1311 i64 szFile;
 
 
1312
1313 i = zPathInfo[0]!=0;
 
 
 
 
 
 
1314 while( 1 ){
1315 while( zPathInfo[i] && zPathInfo[i]!='/' ){ i++; }
1316 zRepo = zToFree = mprintf("%s%.*s.fossil",g.zRepositoryName,i,zPathInfo);
 
 
 
 
 
 
 
 
1317
1318 /* To avoid mischief, make sure the repository basename contains no
 
 
1319 ** characters other than alphanumerics, "/", "_", "-", and ".", and
1320 ** that "-" never occurs immediately after a "/" and that "." is always
1321 ** surrounded by two alphanumerics. Any character that does not
1322 ** satisfy these constraints is converted into "_".
1323 */
1324 szFile = 0;
1325 for(j=strlen(g.zRepositoryName)+1, k=0; zRepo[j] && k<i-1; j++, k++){
1326 char c = zRepo[j];
1327 if( fossil_isalnum(c) ) continue;
 
 
 
 
 
 
1328 if( c=='/' ) continue;
1329 if( c=='_' ) continue;
1330 if( c=='-' && zRepo[j-1]!='/' ) continue;
1331 if( c=='.' && fossil_isalnum(zRepo[j-1]) && fossil_isalnum(zRepo[j+1])){
1332 continue;
1333 }
 
 
 
1334 szFile = 1;
 
 
 
 
1335 break;
1336 }
 
 
 
 
 
 
 
 
1337 if( szFile==0 && sqlite3_strglob("*/.fossil",zRepo)!=0 ){
1338 if( zRepo[0]=='/' && zRepo[1]=='/' ){ zRepo++; j--; }
1339 szFile = file_size(zRepo);
1340 /* this should only be set from the --baseurl option, not CGI */
 
 
 
 
 
 
1341 if( g.zBaseURL && g.zBaseURL[0]!=0 && g.zTop && g.zTop[0]!=0 &&
1342 file_isdir(g.zRepositoryName)==1 ){
1343 if( zPathInfo==zDirPathInfo ){
1344 g.zBaseURL = mprintf("%s%.*s", g.zBaseURL, i, zPathInfo);
1345 g.zTop = mprintf("%s%.*s", g.zTop, i, zPathInfo);
1346 }
1347 }
 
1348 }
 
 
 
 
 
1349 if( szFile<0 && i>0 ){
1350 const char *zMimetype;
1351 assert( fossil_strcmp(&zRepo[j], ".fossil")==0 );
1352 zRepo[j] = 0;
1353 if( zPathInfo[i]=='/' && file_isdir(zRepo)==1 ){
 
 
 
1354 fossil_free(zToFree);
1355 i++;
1356 continue;
1357 }
 
 
 
 
 
 
 
 
 
 
 
 
1358 if( pFileGlob!=0
1359 && file_isfile(zRepo)
1360 && glob_match(pFileGlob, zRepo)
1361 && sqlite3_strglob("*.fossil*",zRepo)!=0
1362 && (zMimetype = mimetype_from_name(zRepo))!=0
1363 && strcmp(zMimetype, "application/x-fossil-artifact")!=0
1364 ){
1365 Blob content;
1366 blob_read_from_file(&content, zRepo);
1367 cgi_set_content_type(zMimetype);
1368 cgi_set_content(&content);
1369 cgi_reply();
1370 return;
1371 }
1372 zRepo[j] = '.';
1373 }
1374
 
 
 
 
 
1375 if( szFile<1024 ){
1376 set_base_url(0);
1377 if( strcmp(zPathInfo,"/")==0
1378 && allowRepoList
1379 && repo_list_page() ){
@@ -1393,34 +1480,49 @@
1393 }
1394 return;
1395 }
1396 break;
1397 }
 
 
 
 
 
1398 zNewScript = mprintf("%s%.*s", zOldScript, i, zPathInfo);
1399 cgi_replace_parameter("PATH_INFO", &zPathInfo[i+1]);
1400 zPathInfo += i;
1401 cgi_replace_parameter("SCRIPT_NAME", zNewScript);
1402 db_open_repository(zRepo);
1403 if( g.fHttpTrace ){
 
 
 
1404 fprintf(stderr,
1405 "# repository: [%s]\n"
1406 "# new PATH_INFO = [%s]\n"
1407 "# new SCRIPT_NAME = [%s]\n",
1408 zRepo, zPathInfo, zNewScript);
1409 }
1410 }
1411
1412 /* Find the page that the user has requested, construct and deliver that
1413 ** page.
 
1414 */
1415 if( g.zContentType &&
1416 strncmp(g.zContentType, "application/x-fossil", 20)==0 ){
 
 
 
1417 zPathInfo = "/xfer";
1418 }
1419 set_base_url(0);
1420 if( zPathInfo==0 || zPathInfo[0]==0
1421 || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
 
 
 
1422 #ifdef FOSSIL_ENABLE_JSON
1423 if(g.json.isJsonMode){
1424 json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
1425 fossil_exit(0);
1426 }
@@ -1437,56 +1539,10 @@
1437 g.zPath = &zPath[1];
1438 for(i=1; zPath[i] && zPath[i]!='/'; i++){}
1439 if( zPath[i]=='/' ){
1440 zPath[i] = 0;
1441 g.zExtra = &zPath[i+1];
1442
1443 #ifdef FOSSIL_ENABLE_SUBREPOSITORY
1444 char *zAltRepo = 0;
1445 /* 2016-09-21: Subrepos are undocumented and apparently no longer work.
1446 ** So they are now removed unless the -DFOSSIL_ENABLE_SUBREPOSITORY
1447 ** compile-time option is used. If there are no complaints after
1448 ** a while, we can delete the code entirely.
1449 */
1450 /* Look for sub-repositories. A sub-repository is another repository
1451 ** that accepts the login credentials of the current repository. A
1452 ** subrepository is identified by a CONFIG table entry "subrepo:NAME"
1453 ** where NAME is the first component of the path. The value of the
1454 ** the CONFIG entries is the string "USER:FILENAME" where USER is the
1455 ** USER name to log in as in the subrepository and FILENAME is the
1456 ** repository filename.
1457 */
1458 zAltRepo = db_text(0, "SELECT value FROM config WHERE name='subrepo:%q'",
1459 g.zPath);
1460 if( zAltRepo ){
1461 int nHost;
1462 int jj;
1463 char *zUser = zAltRepo;
1464 login_check_credentials();
1465 for(jj=0; zAltRepo[jj] && zAltRepo[jj]!=':'; jj++){}
1466 if( zAltRepo[jj]==':' ){
1467 zAltRepo[jj] = 0;
1468 zAltRepo += jj+1;
1469 }else{
1470 zUser = "nobody";
1471 }
1472 if( g.zLogin==0 || g.zLogin[0]==0 ) zUser = "nobody";
1473 if( zAltRepo[0]!='/' ){
1474 zAltRepo = mprintf("%s/../%s", g.zRepositoryName, zAltRepo);
1475 file_simplify_name(zAltRepo, -1, 0);
1476 }
1477 db_close(1);
1478 db_open_repository(zAltRepo);
1479 login_as_user(zUser);
1480 g.perm.Password = 0;
1481 zPath += i;
1482 nHost = g.zTop - g.zBaseURL;
1483 g.zBaseURL = mprintf("%z/%s", g.zBaseURL, g.zPath);
1484 g.zTop = g.zBaseURL + nHost;
1485 continue;
1486 }
1487 #endif /* FOSSIL_ENABLE_SUBREPOSITORY */
1488 }else{
1489 g.zExtra = 0;
1490 }
1491 break;
1492 }
@@ -2228,11 +2284,12 @@
2228 ** the REPOSITORY can only be a directory if the --notfound option is
2229 ** also present.
2230 **
2231 ** For the special case REPOSITORY name of "/", the list global configuration
2232 ** database is consulted for a list of all known repositories. The --repolist
2233 ** option is implied by this special case.
 
2234 **
2235 ** By default, the "ui" command provides full administrative access without
2236 ** having to log in. This can be disabled by turning off the "localauth"
2237 ** setting. Automatic login for the "server" command is available if the
2238 ** --localauth option is present and the "localauth" setting is off and the
2239
--- src/main.c
+++ src/main.c
@@ -1199,17 +1199,30 @@
1199
1200 assert( g.db==0 );
1201 if( fossil_strcmp(g.zRepositoryName,"/")==0 ){
1202 /* For the special case of the "repository directory" being "/",
1203 ** show all of the repositories named in the ~/.fossil database.
1204 **
1205 ** On unix systems, then entries are of the form "repo:/home/..."
1206 ** and on Windows systems they are like "repo:C:/Users/...". We want
1207 ** to skip the first 6 characters on unix and the first 5 characters
1208 ** on Windows.
1209 */
1210 db_open_config(1, 0);
1211 #ifdef _WIN32
1212 db_multi_exec(
1213 "CREATE TEMP VIEW sfile AS"
1214 " SELECT substr(name,6) AS 'pathname' FROM global_config"
1215 " WHERE name GLOB 'repo:*'"
1216 );
1217 #else
1218 db_multi_exec(
1219 "CREATE TEMP VIEW sfile AS"
1220 " SELECT substr(name,7) AS 'pathname' FROM global_config"
1221 " WHERE name GLOB 'repo:*'"
1222 );
1223 #endif
1224 }else{
1225 /* The default case: All repositories under the g.zRepositoryName
1226 ** directory.
1227 */
1228 blob_init(&base, g.zRepositoryName, -1);
@@ -1233,11 +1246,11 @@
1246 db_prepare(&q, "SELECT pathname, substr(pathname,-7,-100000)||'/home'"
1247 " FROM sfile ORDER BY pathname COLLATE nocase;");
1248 while( db_step(&q)==SQLITE_ROW ){
1249 const char *zName = db_column_text(&q, 0);
1250 const char *zUrl = db_column_text(&q, 1);
1251 @ <li><a href="%R/%T(zUrl)" target="_blank">%h(zName)</a></li>
1252 }
1253 @ </ol>
1254 }else{
1255 @ <h1>No Repositories Found</h1>
1256 }
@@ -1274,27 +1287,25 @@
1287 static void process_one_web_page(
1288 const char *zNotFound, /* Redirect here on a 404 if not NULL */
1289 Glob *pFileGlob, /* Deliver static files matching */
1290 int allowRepoList /* Send repo list for "/" URL */
1291 ){
1292 const char *zPathInfo = PD("PATH_INFO", "");
1293 /* const char *zDirPathInfo; **** refactor needed */
1294 char *zPath = NULL;
1295 int i;
1296 const CmdOrPage *pCmd = 0;
1297 const char *zBase = g.zRepositoryName;
1298
1299 /* Handle universal query parameters */
1300 if( PB("utc") ){
1301 g.fTimeFormat = 1;
1302 }else if( PB("localtime") ){
1303 g.fTimeFormat = 2;
1304 }
1305
1306 #if 0 /* Refactor needed */
 
 
 
1307 /* For the PATH_INFO that will be used to help build the final
1308 ** g.zBaseURL and g.zTop (only), skip over the initial directory
1309 ** portion of PATH_INFO; otherwise, it may be duplicated.
1310 */
1311 if( g.zTop ){
@@ -1301,79 +1312,155 @@
1312 int nTop = strlen(g.zTop);
1313 if ( strncmp(zDirPathInfo, g.zTop, nTop)==0 ){
1314 zDirPathInfo += nTop;
1315 }
1316 }
1317 #endif
1318
1319 /* If the repository has not been opened already, then find the
1320 ** repository based on the first element of PATH_INFO and open it.
1321 */
1322 if( !g.repositoryOpen ){
1323 char *zRepo; /* Candidate repository name */
1324 char *zToFree = 0; /* Malloced memory that needs to be freed */
1325 const char *zCleanRepo; /* zRepo with surplus leading "/" removed */
1326 const char *zOldScript = PD("SCRIPT_NAME", ""); /* Original SCRIPT_NAME */
1327 char *zNewScript; /* Revised SCRIPT_NAME after processing */
1328 int j, k; /* Loop variables */
1329 i64 szFile; /* File size of the candidate repository */
1330
1331 i = zPathInfo[0]!=0;
1332 if( fossil_strcmp(g.zRepositoryName, "/")==0 ){
1333 zBase++;
1334 #ifdef _WIN32
1335 if( sqlite3_strglob("/[a-zA-Z]:/*", zPathInfo)==0 ) i = 4;
1336 #endif
1337 }
1338 while( 1 ){
1339 while( zPathInfo[i] && zPathInfo[i]!='/' ){ i++; }
1340
1341 /* The candidate repository name is some prefix of the PATH_INFO
1342 ** with ".fossil" appended */
1343 zRepo = zToFree = mprintf("%s%.*s.fossil",zBase,i,zPathInfo);
1344 if( g.fHttpTrace ){
1345 @ <!-- Looking for repository named "%h(zRepo)" -->
1346 fprintf(stderr, "# looking for repository named \"%s\"\n", zRepo);
1347 }
1348
1349
1350 /* For safety -- to prevent an attacker from accessing arbitrary disk
1351 ** files by sending a maliciously crafted request URI to a public
1352 ** server -- make sure the repository basename contains no
1353 ** characters other than alphanumerics, "/", "_", "-", and ".", and
1354 ** that "-" never occurs immediately after a "/" and that "." is always
1355 ** surrounded by two alphanumerics. Any character that does not
1356 ** satisfy these constraints is converted into "_".
1357 */
1358 szFile = 0;
1359 for(j=strlen(zBase)+1, k=0; zRepo[j] && k<i-1; j++, k++){
1360 char c = zRepo[j];
1361 if( fossil_isalnum(c) ) continue;
1362 #ifdef _WIN32
1363 /* Allow names to begin with "/X:/" on windows */
1364 if( c==':' && j==2 && sqlite3_strglob("/[a-zA-Z]:/*", zRepo)==0 ){
1365 continue;
1366 }
1367 #endif
1368 if( c=='/' ) continue;
1369 if( c=='_' ) continue;
1370 if( c=='-' && zRepo[j-1]!='/' ) continue;
1371 if( c=='.' && fossil_isalnum(zRepo[j-1]) && fossil_isalnum(zRepo[j+1])){
1372 continue;
1373 }
1374 /* If we reach this point, it means that the request URI contains
1375 ** an illegal character or character combination. Provoke a
1376 ** "Not Found" error. */
1377 szFile = 1;
1378 if( g.fHttpTrace ){
1379 @ <!-- Unsafe pathname rejected: "%h(zRepo)" -->
1380 fprintf(stderr, "# unsafe pathname rejected: %s\n", zRepo);
1381 }
1382 break;
1383 }
1384
1385 /* Check to see if a file name zRepo exists. If a file named zRepo
1386 ** does not exist, szFile will become -1. If the file does exist,
1387 ** then szFile will become zero (for an empty file) or positive.
1388 ** Special case: Assume any file with a basename of ".fossil" does
1389 ** not exist.
1390 */
1391 zCleanRepo = file_cleanup_fullpath(zRepo);
1392 if( szFile==0 && sqlite3_strglob("*/.fossil",zRepo)!=0 ){
1393 szFile = file_size(zCleanRepo);
1394 if( g.fHttpTrace ){
1395 @ <!-- file_size(%h(zCleanRepo)) is %lld(szFile) -->
1396 fprintf(stderr, "# file_size(%s) = %lld\n", zCleanRepo, szFile);
1397 }
1398
1399 #if 0
1400 /* This logic for handling --baseurl is confused and needs to be
1401 ** completely rethought. */
1402 if( g.zBaseURL && g.zBaseURL[0]!=0 && g.zTop && g.zTop[0]!=0 &&
1403 file_isdir(g.zRepositoryName)==1 ){
1404 if( zPathInfo==zDirPathInfo ){
1405 g.zBaseURL = mprintf("%s%.*s", g.zBaseURL, i, zPathInfo);
1406 g.zTop = mprintf("%s%.*s", g.zTop, i, zPathInfo);
1407 }
1408 }
1409 #endif
1410 }
1411
1412 /* If no file named by zRepo exists, remove the added ".fossil" suffix
1413 ** and check to see if there is a file or directory with the same
1414 ** name as the raw PATH_INFO text.
1415 */
1416 if( szFile<0 && i>0 ){
1417 const char *zMimetype;
1418 assert( fossil_strcmp(&zRepo[j], ".fossil")==0 );
1419 zRepo[j] = 0; /* Remove the ".fossil" suffix */
1420
1421 /* The PATH_INFO prefix seen so far is a valid directory.
1422 ** Continue the loop with the next element of the PATH_INFO */
1423 if( zPathInfo[i]=='/' && file_isdir(zCleanRepo)==1 ){
1424 fossil_free(zToFree);
1425 i++;
1426 continue;
1427 }
1428
1429 /* If zRepo is the name of an ordinary file that matches the
1430 ** "--file GLOB" pattern, then the CGI reply is the text of
1431 ** of the file.
1432 **
1433 ** For safety, do not allow any file whose name contains ".fossil"
1434 ** to be returned this way, to prevent complete repositories from
1435 ** being delivered accidently. This is not intended to be a
1436 ** general-purpose web server. The "--file GLOB" mechanism is
1437 ** designed to allow the delivery of a few static images or HTML
1438 ** pages.
1439 */
1440 if( pFileGlob!=0
1441 && file_isfile(zCleanRepo)
1442 && glob_match(pFileGlob, file_cleanup_fullpath(zRepo))
1443 && sqlite3_strglob("*.fossil*",zRepo)!=0
1444 && (zMimetype = mimetype_from_name(zRepo))!=0
1445 && strcmp(zMimetype, "application/x-fossil-artifact")!=0
1446 ){
1447 Blob content;
1448 blob_read_from_file(&content, file_cleanup_fullpath(zRepo));
1449 cgi_set_content_type(zMimetype);
1450 cgi_set_content(&content);
1451 cgi_reply();
1452 return;
1453 }
1454 zRepo[j] = '.';
1455 }
1456
1457 /* If we reach this point, it means that the search of the PATH_INFO
1458 ** string is finished. Either zRepo contains the name of the
1459 ** repository to be used, or else no repository could be found an
1460 ** some kind of error response is required.
1461 */
1462 if( szFile<1024 ){
1463 set_base_url(0);
1464 if( strcmp(zPathInfo,"/")==0
1465 && allowRepoList
1466 && repo_list_page() ){
@@ -1393,34 +1480,49 @@
1480 }
1481 return;
1482 }
1483 break;
1484 }
1485
1486 /* Add the repository name (without the ".fossil" suffix) to the end
1487 ** of SCRIPT_NAME and remove the repository name from the beginning
1488 ** of PATH_INFO.
1489 */
1490 zNewScript = mprintf("%s%.*s", zOldScript, i, zPathInfo);
1491 cgi_replace_parameter("PATH_INFO", &zPathInfo[i+1]);
1492 zPathInfo += i;
1493 cgi_replace_parameter("SCRIPT_NAME", zNewScript);
1494 db_open_repository(file_cleanup_fullpath(zRepo));
1495 if( g.fHttpTrace ){
1496 @ <!-- repository: "%h(zRepo)" -->
1497 @ <!-- new PATH_INFO: "%h(zPathInfo)" -->
1498 @ <!-- new SCRIPT_NAME: "%h(zNewScript)" -->
1499 fprintf(stderr,
1500 "# repository: [%s]\n"
1501 "# new PATH_INFO = [%s]\n"
1502 "# new SCRIPT_NAME = [%s]\n",
1503 zRepo, zPathInfo, zNewScript);
1504 }
1505 }
1506
1507 /* At this point, the appropriate repository database file will have
1508 ** been opened. Use the first element of PATH_INFO as the page name
1509 ** and deliver the appropriate page back to the user.
1510 */
1511 if( g.zContentType &&
1512 strncmp(g.zContentType, "application/x-fossil", 20)==0 ){
1513 /* Special case: If the content mimetype shows that it is "fossil sync"
1514 ** payload, then pretend that the PATH_INFO is /xfer so that we always
1515 ** invoke the sync page. */
1516 zPathInfo = "/xfer";
1517 }
1518 set_base_url(0);
1519 if( zPathInfo==0 || zPathInfo[0]==0
1520 || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
1521 /* Second special case: If the PATH_INFO is blank, issue a redirect to
1522 ** the home page identified by the "index-page" setting in the repository
1523 ** CONFIG table, to "/index" if there no "index-page" setting. */
1524 #ifdef FOSSIL_ENABLE_JSON
1525 if(g.json.isJsonMode){
1526 json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
1527 fossil_exit(0);
1528 }
@@ -1437,56 +1539,10 @@
1539 g.zPath = &zPath[1];
1540 for(i=1; zPath[i] && zPath[i]!='/'; i++){}
1541 if( zPath[i]=='/' ){
1542 zPath[i] = 0;
1543 g.zExtra = &zPath[i+1];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1544 }else{
1545 g.zExtra = 0;
1546 }
1547 break;
1548 }
@@ -2228,11 +2284,12 @@
2284 ** the REPOSITORY can only be a directory if the --notfound option is
2285 ** also present.
2286 **
2287 ** For the special case REPOSITORY name of "/", the list global configuration
2288 ** database is consulted for a list of all known repositories. The --repolist
2289 ** option is implied by this special case. See also the "fossil all ui"
2290 ** command.
2291 **
2292 ** By default, the "ui" command provides full administrative access without
2293 ** having to log in. This can be disabled by turning off the "localauth"
2294 ** setting. Automatic login for the "server" command is available if the
2295 ** --localauth option is present and the "localauth" setting is off and the
2296

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button