/* ** Copyright (c) 2018 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** ** This program is distributed in the hope that it will be useful, ** but without any warranty; without even the implied warranty of ** merchantability or fitness for a particular purpose. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** This module contains code to implement the repository list page when ** "fossil server" or "fossil ui" is serving a directory full of repositories. */ #include "config.h" #include "repolist.h" #if INTERFACE /* ** Return value from the remote_repo_info() command. zRepoName is the ** input. All other fields are outputs. */ struct RepoInfo { char *zRepoName; /* Name of the repository file */ int isValid; /* True if zRepoName is a valid Fossil repository */ int isRepolistSkin; /* 1 or 2 if this repository wants to be the skin ** for the repository list. 2 means do use this ** repository but do not display it in the list. */ char *zProjName; /* Project Name. Memory from fossil_malloc() */ char *zLoginGroup; /* Name of login group, or NULL. Malloced() */ double rMTime; /* Last update. Julian day number */ }; #endif /* ** Discover information about the repository given by ** pRepo->zRepoName. The discovered information is stored in other ** fields of the RepoInfo object. */ static void remote_repo_info(RepoInfo *pRepo){ sqlite3 *db; sqlite3_stmt *pStmt; int rc; pRepo->isRepolistSkin = 0; pRepo->isValid = 0; pRepo->zProjName = 0; pRepo->zLoginGroup = 0; pRepo->rMTime = 0.0; g.dbIgnoreErrors++; rc = sqlite3_open_v2(pRepo->zRepoName, &db, SQLITE_OPEN_READWRITE, 0); if( rc ) goto finish_repo_list; rc = sqlite3_prepare_v2(db, "SELECT value FROM config" " WHERE name='repolist-skin'", -1, &pStmt, 0); if( rc ) goto finish_repo_list; if( sqlite3_step(pStmt)==SQLITE_ROW ){ pRepo->isRepolistSkin = sqlite3_column_int(pStmt,0); } sqlite3_finalize(pStmt); if( rc ) goto finish_repo_list; rc = sqlite3_prepare_v2(db, "SELECT value FROM config" " WHERE name='project-name'", -1, &pStmt, 0); if( rc ) goto finish_repo_list; if( sqlite3_step(pStmt)==SQLITE_ROW ){ pRepo->zProjName = fossil_strdup((char*)sqlite3_column_text(pStmt,0)); } sqlite3_finalize(pStmt); rc = sqlite3_prepare_v2(db, "SELECT value FROM config" " WHERE name='login-group-name'", -1, &pStmt, 0); if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ pRepo->zLoginGroup = fossil_strdup((char*)sqlite3_column_text(pStmt,0)); } sqlite3_finalize(pStmt); rc = sqlite3_prepare_v2(db, "SELECT max(mtime) FROM event", -1, &pStmt, 0); if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ pRepo->rMTime = sqlite3_column_double(pStmt,0); } pRepo->isValid = 1; sqlite3_finalize(pStmt); finish_repo_list: g.dbIgnoreErrors--; sqlite3_close(db); } /* ** Generate a web-page that lists all repositories located under the ** g.zRepositoryName directory and return non-zero. ** ** For the special case when g.zRepositoryName is a non-chroot-jail "/", ** compose the list using the "repo:" entries in the global_config ** table of the configuration database. These entries comprise all ** of the repositories known to the "all" command. The special case ** processing is disallowed for chroot jails because g.zRepositoryName ** is always "/" inside a chroot jail and so it cannot be used as a flag ** to signal the special processing in that case. The special case ** processing is intended for the "fossil all ui" command which never ** runs in a chroot jail anyhow. ** ** Or, if no repositories can be located beneath g.zRepositoryName, ** close g.db and return 0. */ int repo_list_page(void){ Blob base; /* document root for all repositories */ int n = 0; /* Number of repositories found */ int allRepo; /* True if running "fossil ui all". ** False if a directory scan of base for repos */ Blob html; /* Html for the body of the repository list */ char *zSkinRepo = 0; /* Name of the repository database used for skins */ char *zSkinUrl = 0; /* URL for the skin database */ assert( g.db==0 ); blob_init(&html, 0, 0); if( fossil_strcmp(g.zRepositoryName,"/")==0 && !g.fJail ){ /* For the special case of the "repository directory" being "/", ** show all of the repositories named in the ~/.fossil database. ** ** On unix systems, then entries are of the form "repo:/home/..." ** and on Windows systems they are like on unix, starting with a "/" ** or they can begin with a drive letter: "repo:C:/Users/...". In either ** case, we want returned path to omit any initial "/". */ db_open_config(1, 0); db_multi_exec( "CREATE TEMP VIEW sfile AS" " SELECT ltrim(substr(name,6),'/') AS 'pathname' FROM global_config" " WHERE name GLOB 'repo:*'" ); allRepo = 1; }else{ /* The default case: All repositories under the g.zRepositoryName ** directory. */ blob_init(&base, g.zRepositoryName, -1); sqlite3_open(":memory:", &g.db); db_multi_exec("CREATE TABLE sfile(pathname TEXT);"); db_multi_exec("CREATE TABLE vfile(pathname);"); vfile_scan(&base, blob_size(&base), 0, 0, 0, ExtFILE); db_multi_exec("DELETE FROM sfile WHERE pathname NOT GLOB '*[^/].fossil'" #if USE_SEE " AND pathname NOT GLOB '*[^/].efossil'" #endif ); allRepo = 0; } n = db_int(0, "SELECT count(*) FROM sfile"); if( n==0 ){ sqlite3_close(g.db); g.db = 0; g.repositoryOpen = 0; g.localOpen = 0; return 0; }else{ Stmt q; double rNow; blob_append_sql(&html, "
Filename | " " | Project Name | " " | Last Modified | " " | Login Group | ");
if( !file_ends_with_repository_extension(zName,0) ){
/* The "fossil server DIRECTORY" and "fossil ui DIRECTORY" commands
** do not work for repositories whose names do not end in ".fossil".
** So do not hyperlink those cases. */
blob_append_sql(&html,"%h",zName);
} else if( sqlite3_strglob("*/.*", zName)==0 ){
/* Do not show hyperlinks for hidden repos */
blob_append_sql(&html, "%h (hidden)", zName);
} else if( allRepo && sqlite3_strglob("[a-zA-Z]:/?*", zName)!=0 ){
blob_append_sql(&html,
"/%h\n",
zUrl, zName);
}else if( file_ends_with_repository_extension(zName,1) ){
/* As described in
** https://fossil-scm.org/forum/info/f50f647c97c72fc1: if
** foo.fossil and foo/bar.fossil both exist and we create a
** link to foo/bar/... then the URI dispatcher will instead
** see that as a link to foo.fossil. In such cases, do not
** emit a link to foo/bar.fossil. */
char * zDirPart = file_dirname(zName);
if( db_exists("SELECT 1 FROM sfile "
"WHERE pathname=(%Q || '.fossil') COLLATE nocase"
#if USE_SEE
" OR pathname=(%Q || '.efossil') COLLATE nocase"
#endif
, zDirPart
#if USE_SEE
, zDirPart
#endif
) ){
blob_append_sql(&html,
" | %h | \n", x.zProjName); fossil_free(x.zProjName); }else{ blob_append_sql(&html, "\n"); } blob_append_sql(&html, " | %h | \n", (int)iAge, zAge); fossil_free(zAge); if( x.zLoginGroup ){ blob_append_sql(&html, "%h | \n", x.zLoginGroup); fossil_free(x.zLoginGroup); }else{ blob_append_sql(&html, "\n"); } sqlite3_free(zUrl); } db_finalize(&q); blob_append_sql(&html," |
---|