Fossil SCM

Add support for the "fossil all ui" and "fossil all server" commands.

drh 2016-12-01 16:41 trunk merge
Commit 239b4c1362c76dbd9c62743f00e661c761fb8d6c
3 files changed +11 -1 +15 +182 -104
+11 -1
--- src/allrepo.c
+++ src/allrepo.c
@@ -134,10 +134,14 @@
134134
**
135135
** setting Run the "setting", "set", or "unset" commands on all
136136
** set repositories. These command are particularly useful in
137137
** unset conjunction with the "max-loadavg" setting which cannot
138138
** otherwise be set globally.
139
+**
140
+** server Run the "ui" or "server" commands on all repositories.
141
+** ui The root URI gives a listing of all repos.
142
+**
139143
**
140144
** In addition, the following maintenance operations are supported:
141145
**
142146
** add Add all the repositories named to the set of repositories
143147
** tracked by Fossil. Normally Fossil is able to keep up with
@@ -189,10 +193,16 @@
189193
n = strlen(g.argv[2]);
190194
db_open_config(1, 0);
191195
blob_zero(&extra);
192196
zCmd = g.argv[2];
193197
if( !login_is_nobody() ) blob_appendf(&extra, " -U %s", g.zLogin);
198
+ if( strncmp(zCmd, "ui", n)==0 || strncmp(zCmd, "server", n)==0 ){
199
+ g.argv[1] = g.argv[2];
200
+ g.argv[2] = "/";
201
+ cmd_webserver();
202
+ return;
203
+ }
194204
if( strncmp(zCmd, "list", n)==0 || strncmp(zCmd,"ls",n)==0 ){
195205
zCmd = "list";
196206
useCheckouts = find_option("ckout","c",0)!=0;
197207
}else if( strncmp(zCmd, "clean", n)==0 ){
198208
zCmd = "clean --chdir";
@@ -355,11 +365,11 @@
355365
showLabel = 1;
356366
collect_argv(&extra, 3);
357367
}else{
358368
fossil_fatal("\"all\" subcommand should be one of: "
359369
"add cache changes clean dbstat extras fts-config ignore "
360
- "info list ls pull push rebuild setting sync unset");
370
+ "info list ls pull push rebuild server setting sync ui unset");
361371
}
362372
verify_all_options();
363373
zFossil = quoteFilename(g.nameOfExe);
364374
db_multi_exec("CREATE TEMP TABLE repolist(name,tag);");
365375
if( useCheckouts ){
366376
--- src/allrepo.c
+++ src/allrepo.c
@@ -134,10 +134,14 @@
134 **
135 ** setting Run the "setting", "set", or "unset" commands on all
136 ** set repositories. These command are particularly useful in
137 ** unset conjunction with the "max-loadavg" setting which cannot
138 ** otherwise be set globally.
 
 
 
 
139 **
140 ** In addition, the following maintenance operations are supported:
141 **
142 ** add Add all the repositories named to the set of repositories
143 ** tracked by Fossil. Normally Fossil is able to keep up with
@@ -189,10 +193,16 @@
189 n = strlen(g.argv[2]);
190 db_open_config(1, 0);
191 blob_zero(&extra);
192 zCmd = g.argv[2];
193 if( !login_is_nobody() ) blob_appendf(&extra, " -U %s", g.zLogin);
 
 
 
 
 
 
194 if( strncmp(zCmd, "list", n)==0 || strncmp(zCmd,"ls",n)==0 ){
195 zCmd = "list";
196 useCheckouts = find_option("ckout","c",0)!=0;
197 }else if( strncmp(zCmd, "clean", n)==0 ){
198 zCmd = "clean --chdir";
@@ -355,11 +365,11 @@
355 showLabel = 1;
356 collect_argv(&extra, 3);
357 }else{
358 fossil_fatal("\"all\" subcommand should be one of: "
359 "add cache changes clean dbstat extras fts-config ignore "
360 "info list ls pull push rebuild setting sync unset");
361 }
362 verify_all_options();
363 zFossil = quoteFilename(g.nameOfExe);
364 db_multi_exec("CREATE TEMP TABLE repolist(name,tag);");
365 if( useCheckouts ){
366
--- src/allrepo.c
+++ src/allrepo.c
@@ -134,10 +134,14 @@
134 **
135 ** setting Run the "setting", "set", or "unset" commands on all
136 ** set repositories. These command are particularly useful in
137 ** unset conjunction with the "max-loadavg" setting which cannot
138 ** otherwise be set globally.
139 **
140 ** server Run the "ui" or "server" commands on all repositories.
141 ** ui The root URI gives a listing of all repos.
142 **
143 **
144 ** In addition, the following maintenance operations are supported:
145 **
146 ** add Add all the repositories named to the set of repositories
147 ** tracked by Fossil. Normally Fossil is able to keep up with
@@ -189,10 +193,16 @@
193 n = strlen(g.argv[2]);
194 db_open_config(1, 0);
195 blob_zero(&extra);
196 zCmd = g.argv[2];
197 if( !login_is_nobody() ) blob_appendf(&extra, " -U %s", g.zLogin);
198 if( strncmp(zCmd, "ui", n)==0 || strncmp(zCmd, "server", n)==0 ){
199 g.argv[1] = g.argv[2];
200 g.argv[2] = "/";
201 cmd_webserver();
202 return;
203 }
204 if( strncmp(zCmd, "list", n)==0 || strncmp(zCmd,"ls",n)==0 ){
205 zCmd = "list";
206 useCheckouts = find_option("ckout","c",0)!=0;
207 }else if( strncmp(zCmd, "clean", n)==0 ){
208 zCmd = "clean --chdir";
@@ -355,11 +365,11 @@
365 showLabel = 1;
366 collect_argv(&extra, 3);
367 }else{
368 fossil_fatal("\"all\" subcommand should be one of: "
369 "add cache changes clean dbstat extras fts-config ignore "
370 "info list ls pull push rebuild server setting sync ui unset");
371 }
372 verify_all_options();
373 zFossil = quoteFilename(g.nameOfExe);
374 db_multi_exec("CREATE TEMP TABLE repolist(name,tag);");
375 if( useCheckouts ){
376
+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
+182 -104
--- src/main.c
+++ src/main.c
@@ -139,10 +139,11 @@
139139
int minPrefix; /* Number of digits needed for a distinct UUID */
140140
int fSqlTrace; /* True if --sqltrace flag is present */
141141
int fSqlStats; /* True if --sqltrace or --sqlstats are present */
142142
int fSqlPrint; /* True if -sqlprint flag is present */
143143
int fQuiet; /* True if -quiet flag is present */
144
+ int fJail; /* True if running with a chroot jail */
144145
int fHttpTrace; /* Trace outbound HTTP requests */
145146
int fAnyTrace; /* Any kind of tracing */
146147
char *zHttpAuth; /* HTTP Authorization user:pass information */
147148
int fSystemTrace; /* Trace calls to fossil_system(), --systemtrace */
148149
int fSshTrace; /* Trace the SSH setup traffic */
@@ -1149,10 +1150,11 @@
11491150
if( !noJail ){
11501151
if( file_isdir(zDir)==1 ){
11511152
if( file_chdir(zDir, 1) ){
11521153
fossil_fatal("unable to chroot into %s", zDir);
11531154
}
1155
+ g.fJail = 1;
11541156
zRepo = "/";
11551157
}else{
11561158
for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){}
11571159
if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo);
11581160
if( i>0 ){
@@ -1182,48 +1184,88 @@
11821184
}
11831185
11841186
/*
11851187
** Generate a web-page that lists all repositories located under the
11861188
** g.zRepositoryName directory and return non-zero.
1189
+**
1190
+** For the special case when g.zRepositoryName a non-chroot-jail "/",
1191
+** compose the list using the "repo:" entries in the global_config
1192
+** table of the configuration database. These entries comprise all
1193
+** of the repositories known to the "all" command. The special case
1194
+** processing is disallowed for chroot jails because g.zRepositoryName
1195
+** is always "/" inside a chroot jail and so it cannot be used as a flag
1196
+** to signal the special processing in that case. The special case
1197
+** processing is intended for the "fossil all ui" command which never
1198
+** runs in a chroot jail anyhow.
11871199
**
11881200
** Or, if no repositories can be located beneath g.zRepositoryName,
11891201
** return 0.
11901202
*/
11911203
static int repo_list_page(void){
11921204
Blob base;
11931205
int n = 0;
11941206
11951207
assert( g.db==0 );
1196
- blob_init(&base, g.zRepositoryName, -1);
1197
- sqlite3_open(":memory:", &g.db);
1198
- db_multi_exec("CREATE TABLE sfile(pathname TEXT);");
1199
- db_multi_exec("CREATE TABLE vfile(pathname);");
1200
- vfile_scan(&base, blob_size(&base), 0, 0, 0);
1201
- db_multi_exec("DELETE FROM sfile WHERE pathname NOT GLOB '*[^/].fossil'");
1208
+ if( fossil_strcmp(g.zRepositoryName,"/")==0 && !g.fJail ){
1209
+ /* For the special case of the "repository directory" being "/",
1210
+ ** show all of the repositories named in the ~/.fossil database.
1211
+ **
1212
+ ** On unix systems, then entries are of the form "repo:/home/..."
1213
+ ** and on Windows systems they are like "repo:C:/Users/...". We want
1214
+ ** to skip the first 6 characters on unix and the first 5 characters
1215
+ ** on Windows.
1216
+ */
1217
+ db_open_config(1, 0);
1218
+#ifdef _WIN32
1219
+ db_multi_exec(
1220
+ "CREATE TEMP VIEW sfile AS"
1221
+ " SELECT substr(name,6) AS 'pathname' FROM global_config"
1222
+ " WHERE name GLOB 'repo:*'"
1223
+ );
1224
+#else
1225
+ db_multi_exec(
1226
+ "CREATE TEMP VIEW sfile AS"
1227
+ " SELECT substr(name,7) AS 'pathname' FROM global_config"
1228
+ " WHERE name GLOB 'repo:*'"
1229
+ );
1230
+#endif
1231
+ }else{
1232
+ /* The default case: All repositories under the g.zRepositoryName
1233
+ ** directory.
1234
+ */
1235
+ blob_init(&base, g.zRepositoryName, -1);
1236
+ sqlite3_open(":memory:", &g.db);
1237
+ db_multi_exec("CREATE TABLE sfile(pathname TEXT);");
1238
+ db_multi_exec("CREATE TABLE vfile(pathname);");
1239
+ vfile_scan(&base, blob_size(&base), 0, 0, 0);
1240
+ db_multi_exec("DELETE FROM sfile WHERE pathname NOT GLOB '*[^/].fossil'");
1241
+ }
1242
+ @ <html>
1243
+ @ <head>
1244
+ @ <base href="%s(g.zBaseURL)/" />
1245
+ @ <title>Repository List</title>
1246
+ @ </head>
1247
+ @ <body>
12021248
n = db_int(0, "SELECT count(*) FROM sfile");
12031249
if( n>0 ){
12041250
Stmt q;
1205
- @ <html>
1206
- @ <head>
1207
- @ <base href="%s(g.zBaseURL)/" />
1208
- @ <title>Repository List</title>
1209
- @ </head>
1210
- @ <body>
12111251
@ <h1>Available Repositories:</h1>
12121252
@ <ol>
12131253
db_prepare(&q, "SELECT pathname, substr(pathname,-7,-100000)||'/home'"
12141254
" FROM sfile ORDER BY pathname COLLATE nocase;");
12151255
while( db_step(&q)==SQLITE_ROW ){
12161256
const char *zName = db_column_text(&q, 0);
12171257
const char *zUrl = db_column_text(&q, 1);
1218
- @ <li><a href="%R/%h(zUrl)" target="_blank">%h(zName)</a></li>
1258
+ @ <li><a href="%R/%T(zUrl)" target="_blank">%h(zName)</a></li>
12191259
}
12201260
@ </ol>
1221
- @ </body>
1222
- @ </html>
1223
- cgi_reply();
1261
+ }else{
1262
+ @ <h1>No Repositories Found</h1>
12241263
}
1264
+ @ </body>
1265
+ @ </html>
1266
+ cgi_reply();
12251267
sqlite3_close(g.db);
12261268
g.db = 0;
12271269
return n;
12281270
}
12291271
@@ -1252,15 +1294,15 @@
12521294
static void process_one_web_page(
12531295
const char *zNotFound, /* Redirect here on a 404 if not NULL */
12541296
Glob *pFileGlob, /* Deliver static files matching */
12551297
int allowRepoList /* Send repo list for "/" URL */
12561298
){
1257
- const char *zPathInfo;
1258
- const char *zDirPathInfo;
1299
+ const char *zPathInfo = PD("PATH_INFO", "");
12591300
char *zPath = NULL;
12601301
int i;
12611302
const CmdOrPage *pCmd = 0;
1303
+ const char *zBase = g.zRepositoryName;
12621304
12631305
/* Handle universal query parameters */
12641306
if( PB("utc") ){
12651307
g.fTimeFormat = 1;
12661308
}else if( PB("localtime") ){
@@ -1268,90 +1310,138 @@
12681310
}
12691311
12701312
/* If the repository has not been opened already, then find the
12711313
** repository based on the first element of PATH_INFO and open it.
12721314
*/
1273
- zDirPathInfo = zPathInfo = PD("PATH_INFO","");
1274
- /* For the PATH_INFO that will be used to help build the final
1275
- ** g.zBaseURL and g.zTop (only), skip over the initial directory
1276
- ** portion of PATH_INFO; otherwise, it may be duplicated.
1277
- */
1278
- if( g.zTop ){
1279
- int nTop = strlen(g.zTop);
1280
- if ( strncmp(zDirPathInfo, g.zTop, nTop)==0 ){
1281
- zDirPathInfo += nTop;
1282
- }
1283
- }
12841315
if( !g.repositoryOpen ){
1285
- char *zRepo, *zToFree;
1286
- const char *zOldScript = PD("SCRIPT_NAME", "");
1287
- char *zNewScript;
1288
- int j, k;
1289
- i64 szFile;
1316
+ char *zRepo; /* Candidate repository name */
1317
+ char *zToFree = 0; /* Malloced memory that needs to be freed */
1318
+ const char *zCleanRepo; /* zRepo with surplus leading "/" removed */
1319
+ const char *zOldScript = PD("SCRIPT_NAME", ""); /* Original SCRIPT_NAME */
1320
+ char *zNewScript; /* Revised SCRIPT_NAME after processing */
1321
+ int j, k; /* Loop variables */
1322
+ i64 szFile; /* File size of the candidate repository */
12901323
12911324
i = zPathInfo[0]!=0;
1325
+ if( fossil_strcmp(g.zRepositoryName, "/")==0 ){
1326
+ zBase++;
1327
+#ifdef _WIN32
1328
+ if( sqlite3_strglob("/[a-zA-Z]:/*", zPathInfo)==0 ) i = 4;
1329
+#endif
1330
+ }
12921331
while( 1 ){
12931332
while( zPathInfo[i] && zPathInfo[i]!='/' ){ i++; }
1294
- zRepo = zToFree = mprintf("%s%.*s.fossil",g.zRepositoryName,i,zPathInfo);
1333
+
1334
+ /* The candidate repository name is some prefix of the PATH_INFO
1335
+ ** with ".fossil" appended */
1336
+ zRepo = zToFree = mprintf("%s%.*s.fossil",zBase,i,zPathInfo);
1337
+ if( g.fHttpTrace ){
1338
+ @ <!-- Looking for repository named "%h(zRepo)" -->
1339
+ fprintf(stderr, "# looking for repository named \"%s\"\n", zRepo);
1340
+ }
1341
+
12951342
1296
- /* To avoid mischief, make sure the repository basename contains no
1343
+ /* For safety -- to prevent an attacker from accessing arbitrary disk
1344
+ ** files by sending a maliciously crafted request URI to a public
1345
+ ** server -- make sure the repository basename contains no
12971346
** characters other than alphanumerics, "/", "_", "-", and ".", and
12981347
** that "-" never occurs immediately after a "/" and that "." is always
12991348
** surrounded by two alphanumerics. Any character that does not
13001349
** satisfy these constraints is converted into "_".
13011350
*/
13021351
szFile = 0;
1303
- for(j=strlen(g.zRepositoryName)+1, k=0; zRepo[j] && k<i-1; j++, k++){
1352
+ for(j=strlen(zBase)+1, k=0; zRepo[j] && k<i-1; j++, k++){
13041353
char c = zRepo[j];
13051354
if( fossil_isalnum(c) ) continue;
1355
+#ifdef _WIN32
1356
+ /* Allow names to begin with "/X:/" on windows */
1357
+ if( c==':' && j==2 && sqlite3_strglob("/[a-zA-Z]:/*", zRepo)==0 ){
1358
+ continue;
1359
+ }
1360
+#endif
13061361
if( c=='/' ) continue;
13071362
if( c=='_' ) continue;
13081363
if( c=='-' && zRepo[j-1]!='/' ) continue;
13091364
if( c=='.' && fossil_isalnum(zRepo[j-1]) && fossil_isalnum(zRepo[j+1])){
13101365
continue;
13111366
}
1367
+ /* If we reach this point, it means that the request URI contains
1368
+ ** an illegal character or character combination. Provoke a
1369
+ ** "Not Found" error. */
13121370
szFile = 1;
1371
+ if( g.fHttpTrace ){
1372
+ @ <!-- Unsafe pathname rejected: "%h(zRepo)" -->
1373
+ fprintf(stderr, "# unsafe pathname rejected: %s\n", zRepo);
1374
+ }
13131375
break;
13141376
}
1377
+
1378
+ /* Check to see if a file name zRepo exists. If a file named zRepo
1379
+ ** does not exist, szFile will become -1. If the file does exist,
1380
+ ** then szFile will become zero (for an empty file) or positive.
1381
+ ** Special case: Assume any file with a basename of ".fossil" does
1382
+ ** not exist.
1383
+ */
1384
+ zCleanRepo = file_cleanup_fullpath(zRepo);
13151385
if( szFile==0 && sqlite3_strglob("*/.fossil",zRepo)!=0 ){
1316
- if( zRepo[0]=='/' && zRepo[1]=='/' ){ zRepo++; j--; }
1317
- szFile = file_size(zRepo);
1318
- /* this should only be set from the --baseurl option, not CGI */
1319
- if( g.zBaseURL && g.zBaseURL[0]!=0 && g.zTop && g.zTop[0]!=0 &&
1320
- file_isdir(g.zRepositoryName)==1 ){
1321
- if( zPathInfo==zDirPathInfo ){
1322
- g.zBaseURL = mprintf("%s%.*s", g.zBaseURL, i, zPathInfo);
1323
- g.zTop = mprintf("%s%.*s", g.zTop, i, zPathInfo);
1324
- }
1386
+ szFile = file_size(zCleanRepo);
1387
+ if( g.fHttpTrace ){
1388
+ @ <!-- file_size(%h(zCleanRepo)) is %lld(szFile) -->
1389
+ fprintf(stderr, "# file_size(%s) = %lld\n", zCleanRepo, szFile);
13251390
}
13261391
}
1392
+
1393
+ /* If no file named by zRepo exists, remove the added ".fossil" suffix
1394
+ ** and check to see if there is a file or directory with the same
1395
+ ** name as the raw PATH_INFO text.
1396
+ */
13271397
if( szFile<0 && i>0 ){
13281398
const char *zMimetype;
1329
- assert( fossil_strcmp(&zRepo[j], ".fossil")==0 );
1330
- zRepo[j] = 0;
1331
- if( zPathInfo[i]=='/' && file_isdir(zRepo)==1 ){
1399
+ assert( fossil_strcmp(&zRepo[j], ".fossil")==0 );
1400
+ zRepo[j] = 0; /* Remove the ".fossil" suffix */
1401
+
1402
+ /* The PATH_INFO prefix seen so far is a valid directory.
1403
+ ** Continue the loop with the next element of the PATH_INFO */
1404
+ if( zPathInfo[i]=='/' && file_isdir(zCleanRepo)==1 ){
13321405
fossil_free(zToFree);
13331406
i++;
13341407
continue;
13351408
}
1409
+
1410
+ /* If zRepo is the name of an ordinary file that matches the
1411
+ ** "--file GLOB" pattern, then the CGI reply is the text of
1412
+ ** of the file.
1413
+ **
1414
+ ** For safety, do not allow any file whose name contains ".fossil"
1415
+ ** to be returned this way, to prevent complete repositories from
1416
+ ** being delivered accidently. This is not intended to be a
1417
+ ** general-purpose web server. The "--file GLOB" mechanism is
1418
+ ** designed to allow the delivery of a few static images or HTML
1419
+ ** pages.
1420
+ */
13361421
if( pFileGlob!=0
1337
- && file_isfile(zRepo)
1338
- && glob_match(pFileGlob, zRepo)
1422
+ && file_isfile(zCleanRepo)
1423
+ && glob_match(pFileGlob, file_cleanup_fullpath(zRepo))
13391424
&& sqlite3_strglob("*.fossil*",zRepo)!=0
13401425
&& (zMimetype = mimetype_from_name(zRepo))!=0
13411426
&& strcmp(zMimetype, "application/x-fossil-artifact")!=0
13421427
){
13431428
Blob content;
1344
- blob_read_from_file(&content, zRepo);
1429
+ blob_read_from_file(&content, file_cleanup_fullpath(zRepo));
13451430
cgi_set_content_type(zMimetype);
13461431
cgi_set_content(&content);
13471432
cgi_reply();
13481433
return;
13491434
}
13501435
zRepo[j] = '.';
13511436
}
13521437
1438
+ /* If we reach this point, it means that the search of the PATH_INFO
1439
+ ** string is finished. Either zRepo contains the name of the
1440
+ ** repository to be used, or else no repository could be found an
1441
+ ** some kind of error response is required.
1442
+ */
13531443
if( szFile<1024 ){
13541444
set_base_url(0);
13551445
if( strcmp(zPathInfo,"/")==0
13561446
&& allowRepoList
13571447
&& repo_list_page() ){
@@ -1371,34 +1461,59 @@
13711461
}
13721462
return;
13731463
}
13741464
break;
13751465
}
1466
+
1467
+ /* Add the repository name (without the ".fossil" suffix) to the end
1468
+ ** of SCRIPT_NAME and g.zTop and g.zBaseURL and remove the repository
1469
+ ** name from the beginning of PATH_INFO.
1470
+ */
13761471
zNewScript = mprintf("%s%.*s", zOldScript, i, zPathInfo);
1472
+ if( g.zTop ) g.zTop = mprintf("%s%.*s", g.zTop, i, zPathInfo);
1473
+ if( g.zBaseURL ) g.zBaseURL = mprintf("%s%.*s", g.zBaseURL, i, zPathInfo);
13771474
cgi_replace_parameter("PATH_INFO", &zPathInfo[i+1]);
13781475
zPathInfo += i;
13791476
cgi_replace_parameter("SCRIPT_NAME", zNewScript);
1380
- db_open_repository(zRepo);
1477
+ db_open_repository(file_cleanup_fullpath(zRepo));
13811478
if( g.fHttpTrace ){
1479
+ @ <!-- repository: "%h(zRepo)" -->
1480
+ @ <!-- translated PATH_INFO: "%h(zPathInfo)" -->
1481
+ @ <!-- translated SCRIPT_NAME: "%h(zNewScript)" -->
13821482
fprintf(stderr,
13831483
"# repository: [%s]\n"
1384
- "# new PATH_INFO = [%s]\n"
1385
- "# new SCRIPT_NAME = [%s]\n",
1484
+ "# translated PATH_INFO = [%s]\n"
1485
+ "# translated SCRIPT_NAME = [%s]\n",
13861486
zRepo, zPathInfo, zNewScript);
1487
+ if( g.zTop ){
1488
+ @ <!-- translated g.zTop: "%h(g.zTop)" -->
1489
+ fprintf(stderr, "# translated g.zTop = [%s]\n", g.zTop);
1490
+ }
1491
+ if( g.zBaseURL ){
1492
+ @ <!-- translated g.zBaseURL: "%h(g.zBaseURL)" -->
1493
+ fprintf(stderr, "# translated g.zBaseURL = [%s]\n", g.zBaseURL);
1494
+ }
13871495
}
13881496
}
13891497
1390
- /* Find the page that the user has requested, construct and deliver that
1391
- ** page.
1498
+ /* At this point, the appropriate repository database file will have
1499
+ ** been opened. Use the first element of PATH_INFO as the page name
1500
+ ** and deliver the appropriate page back to the user.
13921501
*/
13931502
if( g.zContentType &&
13941503
strncmp(g.zContentType, "application/x-fossil", 20)==0 ){
1504
+ /* Special case: If the content mimetype shows that it is "fossil sync"
1505
+ ** payload, then pretend that the PATH_INFO is /xfer so that we always
1506
+ ** invoke the sync page. */
13951507
zPathInfo = "/xfer";
13961508
}
13971509
set_base_url(0);
13981510
if( zPathInfo==0 || zPathInfo[0]==0
13991511
|| (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
1512
+ /* Second special case: If the PATH_INFO is blank, issue a redirect to
1513
+ ** the home page identified by the "index-page" setting in the repository
1514
+ ** CONFIG table, to "/index" if there no "index-page" setting. */
14001515
#ifdef FOSSIL_ENABLE_JSON
14011516
if(g.json.isJsonMode){
14021517
json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
14031518
fossil_exit(0);
14041519
}
@@ -1415,56 +1530,10 @@
14151530
g.zPath = &zPath[1];
14161531
for(i=1; zPath[i] && zPath[i]!='/'; i++){}
14171532
if( zPath[i]=='/' ){
14181533
zPath[i] = 0;
14191534
g.zExtra = &zPath[i+1];
1420
-
1421
-#ifdef FOSSIL_ENABLE_SUBREPOSITORY
1422
- char *zAltRepo = 0;
1423
- /* 2016-09-21: Subrepos are undocumented and apparently no longer work.
1424
- ** So they are now removed unless the -DFOSSIL_ENABLE_SUBREPOSITORY
1425
- ** compile-time option is used. If there are no complaints after
1426
- ** a while, we can delete the code entirely.
1427
- */
1428
- /* Look for sub-repositories. A sub-repository is another repository
1429
- ** that accepts the login credentials of the current repository. A
1430
- ** subrepository is identified by a CONFIG table entry "subrepo:NAME"
1431
- ** where NAME is the first component of the path. The value of the
1432
- ** the CONFIG entries is the string "USER:FILENAME" where USER is the
1433
- ** USER name to log in as in the subrepository and FILENAME is the
1434
- ** repository filename.
1435
- */
1436
- zAltRepo = db_text(0, "SELECT value FROM config WHERE name='subrepo:%q'",
1437
- g.zPath);
1438
- if( zAltRepo ){
1439
- int nHost;
1440
- int jj;
1441
- char *zUser = zAltRepo;
1442
- login_check_credentials();
1443
- for(jj=0; zAltRepo[jj] && zAltRepo[jj]!=':'; jj++){}
1444
- if( zAltRepo[jj]==':' ){
1445
- zAltRepo[jj] = 0;
1446
- zAltRepo += jj+1;
1447
- }else{
1448
- zUser = "nobody";
1449
- }
1450
- if( g.zLogin==0 || g.zLogin[0]==0 ) zUser = "nobody";
1451
- if( zAltRepo[0]!='/' ){
1452
- zAltRepo = mprintf("%s/../%s", g.zRepositoryName, zAltRepo);
1453
- file_simplify_name(zAltRepo, -1, 0);
1454
- }
1455
- db_close(1);
1456
- db_open_repository(zAltRepo);
1457
- login_as_user(zUser);
1458
- g.perm.Password = 0;
1459
- zPath += i;
1460
- nHost = g.zTop - g.zBaseURL;
1461
- g.zBaseURL = mprintf("%z/%s", g.zBaseURL, g.zPath);
1462
- g.zTop = g.zBaseURL + nHost;
1463
- continue;
1464
- }
1465
-#endif /* FOSSIL_ENABLE_SUBREPOSITORY */
14661535
}else{
14671536
g.zExtra = 0;
14681537
}
14691538
break;
14701539
}
@@ -2203,10 +2272,15 @@
22032272
** list of glob patterns given by --files and that have known suffixes
22042273
** such as ".txt" or ".html" or ".jpeg" and do not match the pattern
22052274
** "*.fossil*" will be served as static content. With the "ui" command,
22062275
** the REPOSITORY can only be a directory if the --notfound option is
22072276
** also present.
2277
+**
2278
+** For the special case REPOSITORY name of "/", the list global configuration
2279
+** database is consulted for a list of all known repositories. The --repolist
2280
+** option is implied by this special case. See also the "fossil all ui"
2281
+** command.
22082282
**
22092283
** By default, the "ui" command provides full administrative access without
22102284
** having to log in. This can be disabled by turning off the "localauth"
22112285
** setting. Automatic login for the "server" command is available if the
22122286
** --localauth option is present and the "localauth" setting is off and the
@@ -2377,11 +2451,15 @@
23772451
if( g.fHttpTrace || g.fSqlTrace ){
23782452
fprintf(stderr, "====== SERVER pid %d =======\n", getpid());
23792453
}
23802454
g.cgiOutput = 1;
23812455
find_server_repository(2, 0);
2382
- g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail);
2456
+ if( fossil_strcmp(g.zRepositoryName,"/")==0 ){
2457
+ allowRepoList = 1;
2458
+ }else{
2459
+ g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail);
2460
+ }
23832461
if( flags & HTTP_SERVER_SCGI ){
23842462
cgi_handle_scgi_request();
23852463
}else{
23862464
cgi_handle_http_request(0);
23872465
}
23882466
--- src/main.c
+++ src/main.c
@@ -139,10 +139,11 @@
139 int minPrefix; /* Number of digits needed for a distinct UUID */
140 int fSqlTrace; /* True if --sqltrace flag is present */
141 int fSqlStats; /* True if --sqltrace or --sqlstats are present */
142 int fSqlPrint; /* True if -sqlprint flag is present */
143 int fQuiet; /* True if -quiet flag is present */
 
144 int fHttpTrace; /* Trace outbound HTTP requests */
145 int fAnyTrace; /* Any kind of tracing */
146 char *zHttpAuth; /* HTTP Authorization user:pass information */
147 int fSystemTrace; /* Trace calls to fossil_system(), --systemtrace */
148 int fSshTrace; /* Trace the SSH setup traffic */
@@ -1149,10 +1150,11 @@
1149 if( !noJail ){
1150 if( file_isdir(zDir)==1 ){
1151 if( file_chdir(zDir, 1) ){
1152 fossil_fatal("unable to chroot into %s", zDir);
1153 }
 
1154 zRepo = "/";
1155 }else{
1156 for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){}
1157 if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo);
1158 if( i>0 ){
@@ -1182,48 +1184,88 @@
1182 }
1183
1184 /*
1185 ** Generate a web-page that lists all repositories located under the
1186 ** g.zRepositoryName directory and return non-zero.
 
 
 
 
 
 
 
 
 
 
1187 **
1188 ** Or, if no repositories can be located beneath g.zRepositoryName,
1189 ** return 0.
1190 */
1191 static int repo_list_page(void){
1192 Blob base;
1193 int n = 0;
1194
1195 assert( g.db==0 );
1196 blob_init(&base, g.zRepositoryName, -1);
1197 sqlite3_open(":memory:", &g.db);
1198 db_multi_exec("CREATE TABLE sfile(pathname TEXT);");
1199 db_multi_exec("CREATE TABLE vfile(pathname);");
1200 vfile_scan(&base, blob_size(&base), 0, 0, 0);
1201 db_multi_exec("DELETE FROM sfile WHERE pathname NOT GLOB '*[^/].fossil'");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1202 n = db_int(0, "SELECT count(*) FROM sfile");
1203 if( n>0 ){
1204 Stmt q;
1205 @ <html>
1206 @ <head>
1207 @ <base href="%s(g.zBaseURL)/" />
1208 @ <title>Repository List</title>
1209 @ </head>
1210 @ <body>
1211 @ <h1>Available Repositories:</h1>
1212 @ <ol>
1213 db_prepare(&q, "SELECT pathname, substr(pathname,-7,-100000)||'/home'"
1214 " FROM sfile ORDER BY pathname COLLATE nocase;");
1215 while( db_step(&q)==SQLITE_ROW ){
1216 const char *zName = db_column_text(&q, 0);
1217 const char *zUrl = db_column_text(&q, 1);
1218 @ <li><a href="%R/%h(zUrl)" target="_blank">%h(zName)</a></li>
1219 }
1220 @ </ol>
1221 @ </body>
1222 @ </html>
1223 cgi_reply();
1224 }
 
 
 
1225 sqlite3_close(g.db);
1226 g.db = 0;
1227 return n;
1228 }
1229
@@ -1252,15 +1294,15 @@
1252 static void process_one_web_page(
1253 const char *zNotFound, /* Redirect here on a 404 if not NULL */
1254 Glob *pFileGlob, /* Deliver static files matching */
1255 int allowRepoList /* Send repo list for "/" URL */
1256 ){
1257 const char *zPathInfo;
1258 const char *zDirPathInfo;
1259 char *zPath = NULL;
1260 int i;
1261 const CmdOrPage *pCmd = 0;
 
1262
1263 /* Handle universal query parameters */
1264 if( PB("utc") ){
1265 g.fTimeFormat = 1;
1266 }else if( PB("localtime") ){
@@ -1268,90 +1310,138 @@
1268 }
1269
1270 /* If the repository has not been opened already, then find the
1271 ** repository based on the first element of PATH_INFO and open it.
1272 */
1273 zDirPathInfo = zPathInfo = PD("PATH_INFO","");
1274 /* For the PATH_INFO that will be used to help build the final
1275 ** g.zBaseURL and g.zTop (only), skip over the initial directory
1276 ** portion of PATH_INFO; otherwise, it may be duplicated.
1277 */
1278 if( g.zTop ){
1279 int nTop = strlen(g.zTop);
1280 if ( strncmp(zDirPathInfo, g.zTop, nTop)==0 ){
1281 zDirPathInfo += nTop;
1282 }
1283 }
1284 if( !g.repositoryOpen ){
1285 char *zRepo, *zToFree;
1286 const char *zOldScript = PD("SCRIPT_NAME", "");
1287 char *zNewScript;
1288 int j, k;
1289 i64 szFile;
 
 
1290
1291 i = zPathInfo[0]!=0;
 
 
 
 
 
 
1292 while( 1 ){
1293 while( zPathInfo[i] && zPathInfo[i]!='/' ){ i++; }
1294 zRepo = zToFree = mprintf("%s%.*s.fossil",g.zRepositoryName,i,zPathInfo);
 
 
 
 
 
 
 
 
1295
1296 /* To avoid mischief, make sure the repository basename contains no
 
 
1297 ** characters other than alphanumerics, "/", "_", "-", and ".", and
1298 ** that "-" never occurs immediately after a "/" and that "." is always
1299 ** surrounded by two alphanumerics. Any character that does not
1300 ** satisfy these constraints is converted into "_".
1301 */
1302 szFile = 0;
1303 for(j=strlen(g.zRepositoryName)+1, k=0; zRepo[j] && k<i-1; j++, k++){
1304 char c = zRepo[j];
1305 if( fossil_isalnum(c) ) continue;
 
 
 
 
 
 
1306 if( c=='/' ) continue;
1307 if( c=='_' ) continue;
1308 if( c=='-' && zRepo[j-1]!='/' ) continue;
1309 if( c=='.' && fossil_isalnum(zRepo[j-1]) && fossil_isalnum(zRepo[j+1])){
1310 continue;
1311 }
 
 
 
1312 szFile = 1;
 
 
 
 
1313 break;
1314 }
 
 
 
 
 
 
 
 
1315 if( szFile==0 && sqlite3_strglob("*/.fossil",zRepo)!=0 ){
1316 if( zRepo[0]=='/' && zRepo[1]=='/' ){ zRepo++; j--; }
1317 szFile = file_size(zRepo);
1318 /* this should only be set from the --baseurl option, not CGI */
1319 if( g.zBaseURL && g.zBaseURL[0]!=0 && g.zTop && g.zTop[0]!=0 &&
1320 file_isdir(g.zRepositoryName)==1 ){
1321 if( zPathInfo==zDirPathInfo ){
1322 g.zBaseURL = mprintf("%s%.*s", g.zBaseURL, i, zPathInfo);
1323 g.zTop = mprintf("%s%.*s", g.zTop, i, zPathInfo);
1324 }
1325 }
1326 }
 
 
 
 
 
1327 if( szFile<0 && i>0 ){
1328 const char *zMimetype;
1329 assert( fossil_strcmp(&zRepo[j], ".fossil")==0 );
1330 zRepo[j] = 0;
1331 if( zPathInfo[i]=='/' && file_isdir(zRepo)==1 ){
 
 
 
1332 fossil_free(zToFree);
1333 i++;
1334 continue;
1335 }
 
 
 
 
 
 
 
 
 
 
 
 
1336 if( pFileGlob!=0
1337 && file_isfile(zRepo)
1338 && glob_match(pFileGlob, zRepo)
1339 && sqlite3_strglob("*.fossil*",zRepo)!=0
1340 && (zMimetype = mimetype_from_name(zRepo))!=0
1341 && strcmp(zMimetype, "application/x-fossil-artifact")!=0
1342 ){
1343 Blob content;
1344 blob_read_from_file(&content, zRepo);
1345 cgi_set_content_type(zMimetype);
1346 cgi_set_content(&content);
1347 cgi_reply();
1348 return;
1349 }
1350 zRepo[j] = '.';
1351 }
1352
 
 
 
 
 
1353 if( szFile<1024 ){
1354 set_base_url(0);
1355 if( strcmp(zPathInfo,"/")==0
1356 && allowRepoList
1357 && repo_list_page() ){
@@ -1371,34 +1461,59 @@
1371 }
1372 return;
1373 }
1374 break;
1375 }
 
 
 
 
 
1376 zNewScript = mprintf("%s%.*s", zOldScript, i, zPathInfo);
 
 
1377 cgi_replace_parameter("PATH_INFO", &zPathInfo[i+1]);
1378 zPathInfo += i;
1379 cgi_replace_parameter("SCRIPT_NAME", zNewScript);
1380 db_open_repository(zRepo);
1381 if( g.fHttpTrace ){
 
 
 
1382 fprintf(stderr,
1383 "# repository: [%s]\n"
1384 "# new PATH_INFO = [%s]\n"
1385 "# new SCRIPT_NAME = [%s]\n",
1386 zRepo, zPathInfo, zNewScript);
 
 
 
 
 
 
 
 
1387 }
1388 }
1389
1390 /* Find the page that the user has requested, construct and deliver that
1391 ** page.
 
1392 */
1393 if( g.zContentType &&
1394 strncmp(g.zContentType, "application/x-fossil", 20)==0 ){
 
 
 
1395 zPathInfo = "/xfer";
1396 }
1397 set_base_url(0);
1398 if( zPathInfo==0 || zPathInfo[0]==0
1399 || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
 
 
 
1400 #ifdef FOSSIL_ENABLE_JSON
1401 if(g.json.isJsonMode){
1402 json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
1403 fossil_exit(0);
1404 }
@@ -1415,56 +1530,10 @@
1415 g.zPath = &zPath[1];
1416 for(i=1; zPath[i] && zPath[i]!='/'; i++){}
1417 if( zPath[i]=='/' ){
1418 zPath[i] = 0;
1419 g.zExtra = &zPath[i+1];
1420
1421 #ifdef FOSSIL_ENABLE_SUBREPOSITORY
1422 char *zAltRepo = 0;
1423 /* 2016-09-21: Subrepos are undocumented and apparently no longer work.
1424 ** So they are now removed unless the -DFOSSIL_ENABLE_SUBREPOSITORY
1425 ** compile-time option is used. If there are no complaints after
1426 ** a while, we can delete the code entirely.
1427 */
1428 /* Look for sub-repositories. A sub-repository is another repository
1429 ** that accepts the login credentials of the current repository. A
1430 ** subrepository is identified by a CONFIG table entry "subrepo:NAME"
1431 ** where NAME is the first component of the path. The value of the
1432 ** the CONFIG entries is the string "USER:FILENAME" where USER is the
1433 ** USER name to log in as in the subrepository and FILENAME is the
1434 ** repository filename.
1435 */
1436 zAltRepo = db_text(0, "SELECT value FROM config WHERE name='subrepo:%q'",
1437 g.zPath);
1438 if( zAltRepo ){
1439 int nHost;
1440 int jj;
1441 char *zUser = zAltRepo;
1442 login_check_credentials();
1443 for(jj=0; zAltRepo[jj] && zAltRepo[jj]!=':'; jj++){}
1444 if( zAltRepo[jj]==':' ){
1445 zAltRepo[jj] = 0;
1446 zAltRepo += jj+1;
1447 }else{
1448 zUser = "nobody";
1449 }
1450 if( g.zLogin==0 || g.zLogin[0]==0 ) zUser = "nobody";
1451 if( zAltRepo[0]!='/' ){
1452 zAltRepo = mprintf("%s/../%s", g.zRepositoryName, zAltRepo);
1453 file_simplify_name(zAltRepo, -1, 0);
1454 }
1455 db_close(1);
1456 db_open_repository(zAltRepo);
1457 login_as_user(zUser);
1458 g.perm.Password = 0;
1459 zPath += i;
1460 nHost = g.zTop - g.zBaseURL;
1461 g.zBaseURL = mprintf("%z/%s", g.zBaseURL, g.zPath);
1462 g.zTop = g.zBaseURL + nHost;
1463 continue;
1464 }
1465 #endif /* FOSSIL_ENABLE_SUBREPOSITORY */
1466 }else{
1467 g.zExtra = 0;
1468 }
1469 break;
1470 }
@@ -2203,10 +2272,15 @@
2203 ** list of glob patterns given by --files and that have known suffixes
2204 ** such as ".txt" or ".html" or ".jpeg" and do not match the pattern
2205 ** "*.fossil*" will be served as static content. With the "ui" command,
2206 ** the REPOSITORY can only be a directory if the --notfound option is
2207 ** also present.
 
 
 
 
 
2208 **
2209 ** By default, the "ui" command provides full administrative access without
2210 ** having to log in. This can be disabled by turning off the "localauth"
2211 ** setting. Automatic login for the "server" command is available if the
2212 ** --localauth option is present and the "localauth" setting is off and the
@@ -2377,11 +2451,15 @@
2377 if( g.fHttpTrace || g.fSqlTrace ){
2378 fprintf(stderr, "====== SERVER pid %d =======\n", getpid());
2379 }
2380 g.cgiOutput = 1;
2381 find_server_repository(2, 0);
2382 g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail);
 
 
 
 
2383 if( flags & HTTP_SERVER_SCGI ){
2384 cgi_handle_scgi_request();
2385 }else{
2386 cgi_handle_http_request(0);
2387 }
2388
--- src/main.c
+++ src/main.c
@@ -139,10 +139,11 @@
139 int minPrefix; /* Number of digits needed for a distinct UUID */
140 int fSqlTrace; /* True if --sqltrace flag is present */
141 int fSqlStats; /* True if --sqltrace or --sqlstats are present */
142 int fSqlPrint; /* True if -sqlprint flag is present */
143 int fQuiet; /* True if -quiet flag is present */
144 int fJail; /* True if running with a chroot jail */
145 int fHttpTrace; /* Trace outbound HTTP requests */
146 int fAnyTrace; /* Any kind of tracing */
147 char *zHttpAuth; /* HTTP Authorization user:pass information */
148 int fSystemTrace; /* Trace calls to fossil_system(), --systemtrace */
149 int fSshTrace; /* Trace the SSH setup traffic */
@@ -1149,10 +1150,11 @@
1150 if( !noJail ){
1151 if( file_isdir(zDir)==1 ){
1152 if( file_chdir(zDir, 1) ){
1153 fossil_fatal("unable to chroot into %s", zDir);
1154 }
1155 g.fJail = 1;
1156 zRepo = "/";
1157 }else{
1158 for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){}
1159 if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo);
1160 if( i>0 ){
@@ -1182,48 +1184,88 @@
1184 }
1185
1186 /*
1187 ** Generate a web-page that lists all repositories located under the
1188 ** g.zRepositoryName directory and return non-zero.
1189 **
1190 ** For the special case when g.zRepositoryName a non-chroot-jail "/",
1191 ** compose the list using the "repo:" entries in the global_config
1192 ** table of the configuration database. These entries comprise all
1193 ** of the repositories known to the "all" command. The special case
1194 ** processing is disallowed for chroot jails because g.zRepositoryName
1195 ** is always "/" inside a chroot jail and so it cannot be used as a flag
1196 ** to signal the special processing in that case. The special case
1197 ** processing is intended for the "fossil all ui" command which never
1198 ** runs in a chroot jail anyhow.
1199 **
1200 ** Or, if no repositories can be located beneath g.zRepositoryName,
1201 ** return 0.
1202 */
1203 static int repo_list_page(void){
1204 Blob base;
1205 int n = 0;
1206
1207 assert( g.db==0 );
1208 if( fossil_strcmp(g.zRepositoryName,"/")==0 && !g.fJail ){
1209 /* For the special case of the "repository directory" being "/",
1210 ** show all of the repositories named in the ~/.fossil database.
1211 **
1212 ** On unix systems, then entries are of the form "repo:/home/..."
1213 ** and on Windows systems they are like "repo:C:/Users/...". We want
1214 ** to skip the first 6 characters on unix and the first 5 characters
1215 ** on Windows.
1216 */
1217 db_open_config(1, 0);
1218 #ifdef _WIN32
1219 db_multi_exec(
1220 "CREATE TEMP VIEW sfile AS"
1221 " SELECT substr(name,6) AS 'pathname' FROM global_config"
1222 " WHERE name GLOB 'repo:*'"
1223 );
1224 #else
1225 db_multi_exec(
1226 "CREATE TEMP VIEW sfile AS"
1227 " SELECT substr(name,7) AS 'pathname' FROM global_config"
1228 " WHERE name GLOB 'repo:*'"
1229 );
1230 #endif
1231 }else{
1232 /* The default case: All repositories under the g.zRepositoryName
1233 ** directory.
1234 */
1235 blob_init(&base, g.zRepositoryName, -1);
1236 sqlite3_open(":memory:", &g.db);
1237 db_multi_exec("CREATE TABLE sfile(pathname TEXT);");
1238 db_multi_exec("CREATE TABLE vfile(pathname);");
1239 vfile_scan(&base, blob_size(&base), 0, 0, 0);
1240 db_multi_exec("DELETE FROM sfile WHERE pathname NOT GLOB '*[^/].fossil'");
1241 }
1242 @ <html>
1243 @ <head>
1244 @ <base href="%s(g.zBaseURL)/" />
1245 @ <title>Repository List</title>
1246 @ </head>
1247 @ <body>
1248 n = db_int(0, "SELECT count(*) FROM sfile");
1249 if( n>0 ){
1250 Stmt q;
 
 
 
 
 
 
1251 @ <h1>Available Repositories:</h1>
1252 @ <ol>
1253 db_prepare(&q, "SELECT pathname, substr(pathname,-7,-100000)||'/home'"
1254 " FROM sfile ORDER BY pathname COLLATE nocase;");
1255 while( db_step(&q)==SQLITE_ROW ){
1256 const char *zName = db_column_text(&q, 0);
1257 const char *zUrl = db_column_text(&q, 1);
1258 @ <li><a href="%R/%T(zUrl)" target="_blank">%h(zName)</a></li>
1259 }
1260 @ </ol>
1261 }else{
1262 @ <h1>No Repositories Found</h1>
 
1263 }
1264 @ </body>
1265 @ </html>
1266 cgi_reply();
1267 sqlite3_close(g.db);
1268 g.db = 0;
1269 return n;
1270 }
1271
@@ -1252,15 +1294,15 @@
1294 static void process_one_web_page(
1295 const char *zNotFound, /* Redirect here on a 404 if not NULL */
1296 Glob *pFileGlob, /* Deliver static files matching */
1297 int allowRepoList /* Send repo list for "/" URL */
1298 ){
1299 const char *zPathInfo = PD("PATH_INFO", "");
 
1300 char *zPath = NULL;
1301 int i;
1302 const CmdOrPage *pCmd = 0;
1303 const char *zBase = g.zRepositoryName;
1304
1305 /* Handle universal query parameters */
1306 if( PB("utc") ){
1307 g.fTimeFormat = 1;
1308 }else if( PB("localtime") ){
@@ -1268,90 +1310,138 @@
1310 }
1311
1312 /* If the repository has not been opened already, then find the
1313 ** repository based on the first element of PATH_INFO and open it.
1314 */
 
 
 
 
 
 
 
 
 
 
 
1315 if( !g.repositoryOpen ){
1316 char *zRepo; /* Candidate repository name */
1317 char *zToFree = 0; /* Malloced memory that needs to be freed */
1318 const char *zCleanRepo; /* zRepo with surplus leading "/" removed */
1319 const char *zOldScript = PD("SCRIPT_NAME", ""); /* Original SCRIPT_NAME */
1320 char *zNewScript; /* Revised SCRIPT_NAME after processing */
1321 int j, k; /* Loop variables */
1322 i64 szFile; /* File size of the candidate repository */
1323
1324 i = zPathInfo[0]!=0;
1325 if( fossil_strcmp(g.zRepositoryName, "/")==0 ){
1326 zBase++;
1327 #ifdef _WIN32
1328 if( sqlite3_strglob("/[a-zA-Z]:/*", zPathInfo)==0 ) i = 4;
1329 #endif
1330 }
1331 while( 1 ){
1332 while( zPathInfo[i] && zPathInfo[i]!='/' ){ i++; }
1333
1334 /* The candidate repository name is some prefix of the PATH_INFO
1335 ** with ".fossil" appended */
1336 zRepo = zToFree = mprintf("%s%.*s.fossil",zBase,i,zPathInfo);
1337 if( g.fHttpTrace ){
1338 @ <!-- Looking for repository named "%h(zRepo)" -->
1339 fprintf(stderr, "# looking for repository named \"%s\"\n", zRepo);
1340 }
1341
1342
1343 /* For safety -- to prevent an attacker from accessing arbitrary disk
1344 ** files by sending a maliciously crafted request URI to a public
1345 ** server -- make sure the repository basename contains no
1346 ** characters other than alphanumerics, "/", "_", "-", and ".", and
1347 ** that "-" never occurs immediately after a "/" and that "." is always
1348 ** surrounded by two alphanumerics. Any character that does not
1349 ** satisfy these constraints is converted into "_".
1350 */
1351 szFile = 0;
1352 for(j=strlen(zBase)+1, k=0; zRepo[j] && k<i-1; j++, k++){
1353 char c = zRepo[j];
1354 if( fossil_isalnum(c) ) continue;
1355 #ifdef _WIN32
1356 /* Allow names to begin with "/X:/" on windows */
1357 if( c==':' && j==2 && sqlite3_strglob("/[a-zA-Z]:/*", zRepo)==0 ){
1358 continue;
1359 }
1360 #endif
1361 if( c=='/' ) continue;
1362 if( c=='_' ) continue;
1363 if( c=='-' && zRepo[j-1]!='/' ) continue;
1364 if( c=='.' && fossil_isalnum(zRepo[j-1]) && fossil_isalnum(zRepo[j+1])){
1365 continue;
1366 }
1367 /* If we reach this point, it means that the request URI contains
1368 ** an illegal character or character combination. Provoke a
1369 ** "Not Found" error. */
1370 szFile = 1;
1371 if( g.fHttpTrace ){
1372 @ <!-- Unsafe pathname rejected: "%h(zRepo)" -->
1373 fprintf(stderr, "# unsafe pathname rejected: %s\n", zRepo);
1374 }
1375 break;
1376 }
1377
1378 /* Check to see if a file name zRepo exists. If a file named zRepo
1379 ** does not exist, szFile will become -1. If the file does exist,
1380 ** then szFile will become zero (for an empty file) or positive.
1381 ** Special case: Assume any file with a basename of ".fossil" does
1382 ** not exist.
1383 */
1384 zCleanRepo = file_cleanup_fullpath(zRepo);
1385 if( szFile==0 && sqlite3_strglob("*/.fossil",zRepo)!=0 ){
1386 szFile = file_size(zCleanRepo);
1387 if( g.fHttpTrace ){
1388 @ <!-- file_size(%h(zCleanRepo)) is %lld(szFile) -->
1389 fprintf(stderr, "# file_size(%s) = %lld\n", zCleanRepo, szFile);
 
 
 
 
 
1390 }
1391 }
1392
1393 /* If no file named by zRepo exists, remove the added ".fossil" suffix
1394 ** and check to see if there is a file or directory with the same
1395 ** name as the raw PATH_INFO text.
1396 */
1397 if( szFile<0 && i>0 ){
1398 const char *zMimetype;
1399 assert( fossil_strcmp(&zRepo[j], ".fossil")==0 );
1400 zRepo[j] = 0; /* Remove the ".fossil" suffix */
1401
1402 /* The PATH_INFO prefix seen so far is a valid directory.
1403 ** Continue the loop with the next element of the PATH_INFO */
1404 if( zPathInfo[i]=='/' && file_isdir(zCleanRepo)==1 ){
1405 fossil_free(zToFree);
1406 i++;
1407 continue;
1408 }
1409
1410 /* If zRepo is the name of an ordinary file that matches the
1411 ** "--file GLOB" pattern, then the CGI reply is the text of
1412 ** of the file.
1413 **
1414 ** For safety, do not allow any file whose name contains ".fossil"
1415 ** to be returned this way, to prevent complete repositories from
1416 ** being delivered accidently. This is not intended to be a
1417 ** general-purpose web server. The "--file GLOB" mechanism is
1418 ** designed to allow the delivery of a few static images or HTML
1419 ** pages.
1420 */
1421 if( pFileGlob!=0
1422 && file_isfile(zCleanRepo)
1423 && glob_match(pFileGlob, file_cleanup_fullpath(zRepo))
1424 && sqlite3_strglob("*.fossil*",zRepo)!=0
1425 && (zMimetype = mimetype_from_name(zRepo))!=0
1426 && strcmp(zMimetype, "application/x-fossil-artifact")!=0
1427 ){
1428 Blob content;
1429 blob_read_from_file(&content, file_cleanup_fullpath(zRepo));
1430 cgi_set_content_type(zMimetype);
1431 cgi_set_content(&content);
1432 cgi_reply();
1433 return;
1434 }
1435 zRepo[j] = '.';
1436 }
1437
1438 /* If we reach this point, it means that the search of the PATH_INFO
1439 ** string is finished. Either zRepo contains the name of the
1440 ** repository to be used, or else no repository could be found an
1441 ** some kind of error response is required.
1442 */
1443 if( szFile<1024 ){
1444 set_base_url(0);
1445 if( strcmp(zPathInfo,"/")==0
1446 && allowRepoList
1447 && repo_list_page() ){
@@ -1371,34 +1461,59 @@
1461 }
1462 return;
1463 }
1464 break;
1465 }
1466
1467 /* Add the repository name (without the ".fossil" suffix) to the end
1468 ** of SCRIPT_NAME and g.zTop and g.zBaseURL and remove the repository
1469 ** name from the beginning of PATH_INFO.
1470 */
1471 zNewScript = mprintf("%s%.*s", zOldScript, i, zPathInfo);
1472 if( g.zTop ) g.zTop = mprintf("%s%.*s", g.zTop, i, zPathInfo);
1473 if( g.zBaseURL ) g.zBaseURL = mprintf("%s%.*s", g.zBaseURL, i, zPathInfo);
1474 cgi_replace_parameter("PATH_INFO", &zPathInfo[i+1]);
1475 zPathInfo += i;
1476 cgi_replace_parameter("SCRIPT_NAME", zNewScript);
1477 db_open_repository(file_cleanup_fullpath(zRepo));
1478 if( g.fHttpTrace ){
1479 @ <!-- repository: "%h(zRepo)" -->
1480 @ <!-- translated PATH_INFO: "%h(zPathInfo)" -->
1481 @ <!-- translated SCRIPT_NAME: "%h(zNewScript)" -->
1482 fprintf(stderr,
1483 "# repository: [%s]\n"
1484 "# translated PATH_INFO = [%s]\n"
1485 "# translated SCRIPT_NAME = [%s]\n",
1486 zRepo, zPathInfo, zNewScript);
1487 if( g.zTop ){
1488 @ <!-- translated g.zTop: "%h(g.zTop)" -->
1489 fprintf(stderr, "# translated g.zTop = [%s]\n", g.zTop);
1490 }
1491 if( g.zBaseURL ){
1492 @ <!-- translated g.zBaseURL: "%h(g.zBaseURL)" -->
1493 fprintf(stderr, "# translated g.zBaseURL = [%s]\n", g.zBaseURL);
1494 }
1495 }
1496 }
1497
1498 /* At this point, the appropriate repository database file will have
1499 ** been opened. Use the first element of PATH_INFO as the page name
1500 ** and deliver the appropriate page back to the user.
1501 */
1502 if( g.zContentType &&
1503 strncmp(g.zContentType, "application/x-fossil", 20)==0 ){
1504 /* Special case: If the content mimetype shows that it is "fossil sync"
1505 ** payload, then pretend that the PATH_INFO is /xfer so that we always
1506 ** invoke the sync page. */
1507 zPathInfo = "/xfer";
1508 }
1509 set_base_url(0);
1510 if( zPathInfo==0 || zPathInfo[0]==0
1511 || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
1512 /* Second special case: If the PATH_INFO is blank, issue a redirect to
1513 ** the home page identified by the "index-page" setting in the repository
1514 ** CONFIG table, to "/index" if there no "index-page" setting. */
1515 #ifdef FOSSIL_ENABLE_JSON
1516 if(g.json.isJsonMode){
1517 json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
1518 fossil_exit(0);
1519 }
@@ -1415,56 +1530,10 @@
1530 g.zPath = &zPath[1];
1531 for(i=1; zPath[i] && zPath[i]!='/'; i++){}
1532 if( zPath[i]=='/' ){
1533 zPath[i] = 0;
1534 g.zExtra = &zPath[i+1];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1535 }else{
1536 g.zExtra = 0;
1537 }
1538 break;
1539 }
@@ -2203,10 +2272,15 @@
2272 ** list of glob patterns given by --files and that have known suffixes
2273 ** such as ".txt" or ".html" or ".jpeg" and do not match the pattern
2274 ** "*.fossil*" will be served as static content. With the "ui" command,
2275 ** the REPOSITORY can only be a directory if the --notfound option is
2276 ** also present.
2277 **
2278 ** For the special case REPOSITORY name of "/", the list global configuration
2279 ** database is consulted for a list of all known repositories. The --repolist
2280 ** option is implied by this special case. See also the "fossil all ui"
2281 ** command.
2282 **
2283 ** By default, the "ui" command provides full administrative access without
2284 ** having to log in. This can be disabled by turning off the "localauth"
2285 ** setting. Automatic login for the "server" command is available if the
2286 ** --localauth option is present and the "localauth" setting is off and the
@@ -2377,11 +2451,15 @@
2451 if( g.fHttpTrace || g.fSqlTrace ){
2452 fprintf(stderr, "====== SERVER pid %d =======\n", getpid());
2453 }
2454 g.cgiOutput = 1;
2455 find_server_repository(2, 0);
2456 if( fossil_strcmp(g.zRepositoryName,"/")==0 ){
2457 allowRepoList = 1;
2458 }else{
2459 g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail);
2460 }
2461 if( flags & HTTP_SERVER_SCGI ){
2462 cgi_handle_scgi_request();
2463 }else{
2464 cgi_handle_http_request(0);
2465 }
2466

Keyboard Shortcuts

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