From 4f49b16079b27d7ce633619e4cddfdf2bffbc952 Mon Sep 17 00:00:00 2001 From: cashmere Date: Fri, 4 Sep 2026 09:05:24 +0200 Subject: initial commit --- src/setup.c | 2575 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2575 insertions(+) create mode 100644 src/setup.c (limited to 'src/setup.c') diff --git a/src/setup.c b/src/setup.c new file mode 100644 index 0000000..fb86474 --- /dev/null +++ b/src/setup.c @@ -0,0 +1,2575 @@ +/* +** Copyright (c) 2007 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/ +** +******************************************************************************* +** +** Implementation of the Setup page +*/ +#include "config.h" +#include +#include "setup.h" + +/* +** Increment the "cfgcnt" variable, so that ETags will know that +** the configuration has changed. +*/ +void setup_incr_cfgcnt(void){ + static int once = 1; + if( once ){ + once = 0; + db_unprotect(PROTECT_CONFIG); + db_multi_exec("UPDATE config SET value=value+1 WHERE name='cfgcnt'"); + if( db_changes()==0 ){ + db_multi_exec("INSERT INTO config(name,value) VALUES('cfgcnt',1)"); + } + db_protect_pop(); + } +} + +/* +** Output a single entry for a menu generated using an HTML table. +** If zLink is neither NULL nor an empty string, then it is the page that +** the menu entry will hyperlink to. If zLink is NULL or "", then +** the menu entry has no hyperlink - it is disabled. +*/ +void setup_menu_entry( + const char *zTitle, + const char *zLink, + const char *zDesc /* Caution! Rendered using %s. May contain raw HTML. */ +){ + @ + if( zLink && zLink[0] ){ + @ %h(zTitle) + }else{ + @ %h(zTitle) + } + @ %s(zDesc) +} + + + +/* +** WEBPAGE: setup +** +** Main menu for the administrative pages. Requires Admin or Setup +** privileges. Links to sub-pages only usable by Setup users are +** shown only to Setup users. +*/ +void setup_page(void){ + int setup_user = 0; + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + } + setup_user = g.perm.Setup; + + style_set_current_feature("setup"); + style_header("Server Administration"); + + /* Make sure the header contains . Issue a warning + ** if it does not. */ + if( !cgi_header_contains("Configuration Error: Please add + @ <base href="$secureurl/$current_page"> after + @ <head> in the + @ HTML header!

+ } + +#if !defined(_WIN32) + /* Check for /dev/null and /dev/urandom. We want both devices to be present, + ** but they are sometimes omitted (by mistake) from chroot jails. */ + if( access("/dev/null", R_OK|W_OK) ){ + @

WARNING: Device "/dev/null" is not available + @ for reading and writing.

+ } + if( access("/dev/urandom", R_OK) ){ + @

WARNING: Device "/dev/urandom" is not available + @ for reading. This means that the pseudo-random number generator used + @ by SQLite will be poorly seeded.

+ } +#endif + + @ + setup_menu_entry("Users", "setup_ulist", + "Grant privileges to individual users."); + if( setup_user ){ + setup_menu_entry("Access", "setup_access", + "Control access settings."); + setup_menu_entry("Configuration", "setup_config", + "Configure the WWW components of the repository"); + } + setup_menu_entry("Security-Audit", "secaudit0", + "Analyze the current configuration for security problems"); + if( setup_user ){ + setup_menu_entry("Robot-Defense", "setup_robot", + "Settings for configure defense against robots"); + setup_menu_entry("Settings", "setup_settings", + "Web interface to the \"fossil settings\" command"); + } + setup_menu_entry("Timeline", "setup_timeline", + "Timeline display preferences"); + setup_menu_entry("Tarballs and ZIPs", "setup_download", + "Preferences for auto-generated tarballs and ZIP files"); + if( setup_user ){ + setup_menu_entry("Login-Group", "setup_login_group", + "Manage single sign-on between this repository and others" + " on the same server"); + setup_menu_entry("Tickets", "tktsetup", + "Configure the trouble-ticketing system for this repository"); + setup_menu_entry("Wiki", "setup_wiki", + "Configure the wiki for this repository"); + setup_menu_entry("Interwiki Map", "intermap", + "Mapping keywords for interwiki links"); + setup_menu_entry("Chat", "setup_chat", + "Configure the chatroom"); + setup_menu_entry("Forum", "setup_forum", + "Forum config and metrics"); + } + setup_menu_entry("Search","srchsetup", + "Configure the built-in search engine"); + setup_menu_entry("URL Aliases", "waliassetup", + "Configure URL aliases"); + if( setup_user ){ + setup_menu_entry("Notification", "setup_notification", + "Automatic notifications of changes via outbound email"); +#if 0 /* Disabled for now. Does this even work? */ + setup_menu_entry("Transfers", "xfersetup", + "Configure the transfer system for this repository"); +#endif + } + setup_menu_entry("Skins", "setup_skin_admin", + "Select and/or modify the web interface \"skins\""); + setup_menu_entry("Moderation", "setup_modreq", + "Enable/Disable requiring moderator approval of Wiki and/or Ticket" + " changes and attachments."); + setup_menu_entry("Ad-Unit", "setup_adunit", + "Edit HTML text for an ad unit inserted after the menu bar"); + setup_menu_entry("URLs & Checkouts", "urllist", + "Show URLs used to access this repo and known check-outs"); + if( setup_user ){ + setup_menu_entry("Web-Cache", "cachestat", + "View the status of the expensive-page cache"); + } + setup_menu_entry("Logo", "setup_logo", + "Change the logo and background images for the server"); + setup_menu_entry("Shunned", "shun", + "Show artifacts that are shunned by this repository"); + setup_menu_entry("Log Files", "setup-logmenu", + "A menu of available log files"); + setup_menu_entry("Unversioned Files", "uvlist?byage=1", + "Show all unversioned files held"); + setup_menu_entry("Stats", "stat", + "Repository Status Reports"); + setup_menu_entry("Sitemap", "sitemap", + "Links to miscellaneous pages"); + if( setup_user ){ + setup_menu_entry("SQL", "admin_sql", + "Enter raw SQL commands"); + setup_menu_entry("TH1", "admin_th1", + "Enter raw TH1 commands"); + } + @
+ + style_finish_page(); +} + + +/* +** WEBPAGE: setup-logmenu +** +** Show a menu of available log renderings accessible to an administrator, +** together with a succinct explanation of each. +** +** This page is only accessible by administrators. +*/ +void setup_logmenu_page(void){ + Blob desc; + int bErrLog; /* True if Error Log enabled */ + blob_init(&desc, 0, 0); + + /* Administrator access only */ + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + return; + } + style_header("Log Menu"); + @ + + if( db_get_boolean("admin-log",1)==0 ){ + blob_appendf(&desc, + "The admin log records configuration changes to the repository.\n" + "Disabled: Turn on the " + " admin-log setting to enable." + ); + setup_menu_entry("Admin Log", 0, blob_str(&desc)); + blob_reset(&desc); + }else{ + setup_menu_entry("Admin Log", "admin_log", + "The admin log records configuration changes to the repository\n" + "in the \"admin_log\" table.\n" + ); + } + setup_menu_entry("Xfer Log", "rcvfromlist", + "The artifact log records when new content is added in the\n" + "\"rcvfrom\" table.\n" + ); + if( db_get_boolean("access-log",1) ){ + setup_menu_entry("User Log", "user_log", + "Login attempts recorded in the \"accesslog\" table." + ); + }else{ + blob_appendf(&desc, + "Login attempts recorded in the \"accesslog\" table.\n" + "Disabled: Turn on the " + "access-log setting to enable." + ); + setup_menu_entry("User Log", 0, blob_str(&desc)); + blob_reset(&desc); + } + + blob_appendf(&desc, + "A separate text file to which warning and error\n" + "messages are appended. A single error log can and often is shared\n" + "across multiple repositories.\n" + ); + if( g.zErrlog==0 || fossil_strcmp(g.zErrlog,"-")==0 ){ + blob_appendf(&desc,"Disabled: " + "To enable the error log "); + if( fossil_strcmp(g.zCmdName, "cgi")==0 ){ + blob_appendf(&desc, + "make an entry like \"errorlog: FILENAME\"" + " in the CGI script at %h", + P("SCRIPT_FILENAME") + ); + }else{ + blob_appendf(&desc, + " add the \"--errorlog FILENAME\" option to the\n" + "\"%h %h\" command that launched the server.", + g.argv[0], g.zCmdName + ); + } + bErrLog = 0; + }else{ + blob_appendf(&desc,"In this repository, the error log is the file " + "named \"%s\".", g.zErrlog); + bErrLog = 1; + } + setup_menu_entry("Error Log", bErrLog ? "errorlog" : 0, blob_str(&desc)); + blob_reset(&desc); + + @
+ style_finish_page(); +} + +/* +** Generate a checkbox for an attribute. +*/ +void onoff_attribute( + const char *zLabel, /* The text label on the checkbox */ + const char *zVar, /* The corresponding row in the CONFIG table */ + const char *zQParm, /* The query parameter */ + int dfltVal, /* Default value if CONFIG table entry does not exist */ + int disabled /* 1 if disabled */ +){ + const char *zQ = P(zQParm); + int iVal = db_get_boolean(zVar, dfltVal); + if( zQ==0 && !disabled && P("submit") ){ + zQ = "off"; + } + if( zQ ){ + int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ); + if( iQ!=iVal && cgi_csrf_safe(2) ){ + db_protect_only(PROTECT_NONE); + db_set(zVar/*works-like:"x"*/, iQ ? "1" : "0", 0); + db_protect_pop(); + setup_incr_cfgcnt(); + admin_log("Set option [%q] to [%q].", + zVar, iQ ? "on" : "off"); + iVal = iQ; + } + } + @ +} + +/* +** Generate an entry box for an attribute. +*/ +void entry_attribute( + const char *zLabel, /* The text label on the entry box */ + int width, /* Width of the entry box */ + const char *zVar, /* The corresponding row in the CONFIG table */ + const char *zQParm, /* The query parameter */ + const char *zDflt, /* Default value if CONFIG table entry does not exist */ + int disabled /* 1 if disabled */ +){ + const char *zVal = db_get(zVar, zDflt); + const char *zQ = P(zQParm); + if( zQ && fossil_strcmp(zQ,zVal)!=0 && cgi_csrf_safe(2) ){ + const int nZQ = (int)strlen(zQ); + setup_incr_cfgcnt(); + db_protect_only(PROTECT_NONE); + db_set(zVar/*works-like:"x"*/, zQ, 0); + db_protect_pop(); + admin_log("Set entry_attribute %Q to: %.*s%s", + zVar, 20, zQ, (nZQ>20 ? "..." : "")); + zVal = zQ; + } + @ %s(zLabel) +} + +/* +** Generate a text box for an attribute. +*/ +const char *textarea_attribute( + const char *zLabel, /* The text label on the textarea */ + int rows, /* Rows in the textarea */ + int cols, /* Columns in the textarea */ + const char *zVar, /* The corresponding row in the CONFIG table */ + const char *zQP, /* The query parameter */ + const char *zDflt, /* Default value if CONFIG table entry does not exist */ + int disabled /* 1 if the textarea should not be editable */ +){ + const char *z = db_get(zVar, zDflt); + const char *zQ = P(zQP); + if( zQ && !disabled && fossil_strcmp(zQ,z)!=0 && cgi_csrf_safe(2) ){ + const int nZQ = (int)strlen(zQ); + db_protect_only(PROTECT_NONE); + db_set(zVar/*works-like:"x"*/, zQ, 0); + db_protect_pop(); + setup_incr_cfgcnt(); + admin_log("Set textarea_attribute %Q to: %.*s%s", + zVar, 20, zQ, (nZQ>20 ? "..." : "")); + z = zQ; + } + if( rows>0 && cols>0 ){ + @ + if( *zLabel ){ + @ %s(zLabel) + } + } + return z; +} + +/* +** Generate a text box for an attribute. +*/ +void multiple_choice_attribute( + const char *zLabel, /* The text label on the menu */ + const char *zVar, /* The corresponding row in the CONFIG table */ + const char *zQP, /* The query parameter */ + const char *zDflt, /* Default value if CONFIG table entry does not exist */ + int nChoice, /* Number of choices */ + const char *const *azChoice /* Choices in pairs (VAR value, Display) */ +){ + const char *z = db_get(zVar, zDflt); + const char *zQ = P(zQP); + int i; + if( zQ && fossil_strcmp(zQ,z)!=0 && cgi_csrf_safe(2) ){ + const int nZQ = (int)strlen(zQ); + db_unprotect(PROTECT_ALL); + db_set(zVar/*works-like:"x"*/, zQ, 0); + setup_incr_cfgcnt(); + db_protect_pop(); + admin_log("Set multiple_choice_attribute %Q to: %.*s%s", + zVar, 20, zQ, (nZQ>20 ? "..." : "")); + z = zQ; + } + @ %h(zLabel) +} + +/* +** Insert code into the current page that allows the user to configure +** auto-hyperlink related robot defense settings. +*/ +static void addAutoHyperlinkSettings(void){ + static const char *const azDefenseOpts[] = { + "0", "Off", + "2", "HTTP Header Only", + "1", "HTTP Header And Javascript", + }; + multiple_choice_attribute( + "Enable hyperlinks base on HTTP Header and/or Javascript", + "auto-hyperlink", "autohyperlink", "1", + count(azDefenseOpts)/2, azDefenseOpts); + @
+ entry_attribute("Delay in milliseconds before enabling hyperlinks", 5, + "auto-hyperlink-delay", "ah-delay", "50", 0); + @
+ onoff_attribute("Also require a mouse event before enabling hyperlinks", + "auto-hyperlink-mouseover", "ahmo", 0, 0); + @

Enable hyperlinks (the equivalent of the "h" permission) for all users, + @ including user "nobody" if the request appears to be from a human. + @ Disabling hyperlinks helps prevent robots from walking your site and + @ soaking up all your CPU and bandwidth. + @ If this setting is "HTTP Header Only" (2) then only the HTTP header + @ content (including the UserAgent string and the fossil-client-ok cookie) + @ is considered when enabling hyperlinks. If the value of this + @ setting is "HTTP Header And Javascript" (1) then Javascript is added that + @ runs after the page loads and fills in the href= values of <a> + @ elements. In either case, <a> tags are not generated unless the + @ UserAgent string and/or the fossil-client-ok cookie indicate that the + @ client is likely human. + @ (Property: "auto-hyperlink")

+ @ + @

For maximum robot defense, "Delay" should be at least 50 milliseconds + @ and "require a mouse event" should be turned on. These values only come + @ into play when the main auto-hyperlink settings is 2 ("UserAgent and + @ Javascript"). + @ (Properties: "auto-hyperlink-delay" and "auto-hyperlink-mouseover")

+ @ + @

To see if Javascript-base hyperlink enabling mechanism is working, + @ visit the /test-env page from a separate + @ web browser that is not logged in, even as "anonymous" and verify + @ that the "g.jsHref" value is "1".

+} + +/* +** WEBPAGE: setup_robot +** +** Settings associated with defense against robots. Requires setup privilege. +*/ +void setup_robots(void){ + login_check_credentials(); + if( !g.perm.Setup ){ + login_needed(0); + return; + } + style_set_current_feature("setup"); + style_header("Robot Defense Settings"); + db_begin_transaction(); + @

A Fossil website can have billions of pages in its tree, even for a + @ modest project. Many of those pages (examples: diffs and tarballs) + @ might be expensive to compute. A robot that tries to walk the entire + @ website can present a crippling CPU and bandwidth load. + @ + @

The settings on this page are intended to help administrators + @ defend against abusive robots. + @ + @

+ login_insert_csrf_secret(); + @

+ @
+ @

Do not allow robots access to these pages.
+ @ If the page name matches the GLOB pattern of this setting, and the + @ users is "nobody", and the client has not previously passed a captcha + @ test to show that it is not a robot, then the page is not displayed. + @ A captcha test is rendered instead. + @ The default value for this setting is: + @

+ @    %h(robot_restrict_default()) + @

+ @ Usually the tag should exactly match the page name. Exceptions: + @

    + @
  • The "diff" tag covers all diffing pages such as /vdiff, + @ /fdiff, and /vpatch. + @
  • The "annotate" tag covers /annotate and also /blame and + @ /praise. + @
  • The "zip" covers itself and also /tarball and /sqlar. + @
  • If a tag has an "X" character appended (ex: "timelineX") + @ then it only applies if query parameters are such that + @ the page is expensive and/or unusual. + @
  • The "ext" tag covers all extensions, but a tag like + @ "ext/PATH" only covers the specific extension at PATH. + @
+ @ Make this setting "off" to disable all robot restrictions, or "*" + @ to exclude robots from all pages other than "/login" and "/xfer". + @ (Property: robot-restrict) + @
+ textarea_attribute("", 2, 80, + "robot-restrict", "rbrestrict", robot_restrict_default(), 0); + + @

Exception #1
+ @ If "zipX" appears in the robot-restrict list above, then tarballs, + @ ZIP-archives, and SQL-archives may be downloaded by robots if + @ the check-in is a leaf (robot-zip-leaf):
+ onoff_attribute("Allow tarballs for leaf check-ins", + "robot-zip-leaf", "rzleaf", 0, 0); + + @

Exception #2
+ @ If "zipX" appears in the robot-restrict list above, then tarballs, + @ ZIP-archives, and SQL-archives may be downloaded by robots if + @ the check-in has one or more tags that match the following + @ list of GLOB patterns: (robot-zip-tag)
+ textarea_attribute("", 2, 80, + "robot-zip-tag", "rztag", "", 0); + + @

Exception #3
+ @ If the request URI matches any of the following + @ regular expressions (one per line), then the + @ request is exempt from anti-robot defenses. + @ The regular expression is matched against the REQUEST_URI with the + @ SCRIPT_NAME prefix removed, and with QUERY_STRING appended following + @ a "?" if QUERY_STRING exists. (Property: robot-exception)
+ textarea_attribute("", 3, 80, + "robot-exception", "rbexcept", "", 0); + @


+ addAutoHyperlinkSettings(); + + @
+ entry_attribute("Anonymous Login Validity", 11, "anon-cookie-lifespan", + "anoncookls", "840", 0); + @

The number of minutes for which an anonymous login cookie is valid. + @ Set to zero to disable anonymous login. + @ (property: anon-cookie-lifespan) + + @


+ entry_attribute("Server Load Average Limit", 11, "max-loadavg", "mxldavg", + "0.0", 0); + @

Some expensive operations (such as computing tarballs, zip archives, + @ or annotation/blame pages) are prohibited if the load average on the host + @ computer is too large. Set the threshold for disallowing expensive + @ computations here. Set this to 0.0 to disable the load average limit. + @ This limit is only enforced on Unix servers. On Linux systems, + @ access to the /proc virtual filesystem is required, which means this limit + @ might not work inside a chroot() jail. + @ (Property: "max-loadavg")

+ @ + @
+ @

+ @
+ db_end_transaction(0); + style_finish_page(); +} + +/* +** WEBPAGE: setup_access +** +** The access-control settings page. Requires Setup privileges. +*/ +void setup_access(void){ + static const char *const azRedirectOpts[] = { + "0", "Off", + "1", "Login Page Only", + "2", "All Pages" + }; + login_check_credentials(); + if( !g.perm.Setup ){ + login_needed(0); + return; + } + + style_set_current_feature("setup"); + style_header("Access Control Settings"); + db_begin_transaction(); + @
+ login_insert_csrf_secret(); + @

+ @
+ multiple_choice_attribute("Redirect to HTTPS", + "redirect-to-https", "redirhttps", "0", + count(azRedirectOpts)/2, azRedirectOpts); + @

Force the use of HTTPS by redirecting to HTTPS when an + @ unencrypted request is received. This feature can be enabled + @ for the Login page only, or for all pages. + @

Further details: When enabled, this option causes the $secureurl TH1 + @ variable is set to an "https:" variant of $baseurl. Otherwise, + @ $secureurl is just an alias for $baseurl. + @ (Property: "redirect-to-https". "0" for off, "1" for Login page only, + @ "2" otherwise.) + @


+ onoff_attribute("Require password for local access", + "localauth", "localauth", 0, 0); + @

When enabled, the password sign-in is always required for + @ web access. When disabled, unrestricted web access from 127.0.0.1 + @ is allowed for the fossil ui command or + @ from the fossil server, + @ fossil http commands when the + @ "--localauth" command line options is used, or from the + @ fossil cgi if a line containing + @ the word "localauth" appears in the CGI script. + @ + @

A password is always required if any one or more + @ of the following are true: + @

    + @
  1. This button is checked + @
  2. The inbound TCP/IP connection is not from 127.0.0.1 + @
  3. The server is started using either of the + @ fossil server or + @ fossil http commands + @ without the "--localauth" option. + @
  4. The server is started from CGI without the "localauth" keyword + @ in the CGI script. + @
+ @ (Property: "localauth") + @ + @
+ onoff_attribute("Enable /test-env", + "test_env_enable", "test_env_enable", 0, 0); + @

When enabled, the %h(g.zBaseURL)/test_env URL is available to all + @ users. When disabled (the default) only users Admin and Setup can visit + @ the /test_env page. + @ (Property: "test_env_enable") + @

+ @ + @
+ onoff_attribute("Enable /artifact_stats", + "artifact_stats_enable", "artifact_stats_enable", 0, 0); + @

When enabled, the %h(g.zBaseURL)/artifact_stats URL is available to all + @ users. When disabled (the default) only users with check-in privilege may + @ access the /artifact_stats page. + @ (Property: "artifact_stats_enable") + @

+ @ + @
+ onoff_attribute("Allow REMOTE_USER authentication", + "remote_user_ok", "remote_user_ok", 0, 0); + @

When enabled, if the REMOTE_USER environment variable is set to the + @ login name of a valid user and no other login credentials are available, + @ then the REMOTE_USER is accepted as an authenticated user. + @ (Property: "remote_user_ok") + @

+ @ + @
+ onoff_attribute("Allow HTTP_AUTHENTICATION authentication", + "http_authentication_ok", "http_authentication_ok", 0, 0); + @

When enabled, allow the use of the HTTP_AUTHENTICATION environment + @ variable or the "Authentication:" HTTP header to find the username and + @ password. This is another way of supporting Basic Authentication. + @ (Property: "http_authentication_ok") + @

+ @ + @
+ entry_attribute("Login expiration time", 6, "cookie-expire", "cex", + "8766", 0); + @

The number of hours for which a login is valid. This must be a + @ positive number. The default is 8766 hours which is approximately equal + @ to a year. + @ (Property: "cookie-expire")

+ + @
+ entry_attribute("Download packet limit", 10, "max-download", "mxdwn", + "5000000", 0); + @

Fossil tries to limit out-bound sync, clone, and pull packets + @ to this many bytes, uncompressed. If the client requires more data + @ than this, then the client will issue multiple HTTP requests. + @ Values below 1 million are not recommended. 5 million is a + @ reasonable number. (Property: "max-download")

+ + @
+ entry_attribute("Download time limit", 11, "max-download-time", "mxdwnt", + "30", 0); + + @

Fossil tries to spend less than this many seconds gathering + @ the out-bound data of sync, clone, and pull packets. + @ If the client request takes longer, a partial reply is given similar + @ to the download packet limit. 30s is a reasonable default. + @ (Property: "max-download-time")

+ + @ + @
+ entry_attribute("Server Load Average Limit", 11, "max-loadavg", "mxldavg", + "0.0", 0); + @

Some expensive operations (such as computing tarballs, zip archives, + @ or annotation/blame pages) are prohibited if the load average on the host + @ computer is too large. Set the threshold for disallowing expensive + @ computations here. Set this to 0.0 to disable the load average limit. + @ This limit is only enforced on Unix servers. On Linux systems, + @ access to the /proc virtual filesystem is required, which means this limit + @ might not work inside a chroot() jail. + @ (Property: "max-loadavg")

+ + /* Add the auto-hyperlink settings controls. These same controls + ** are also accessible from the /setup_robot page. + */ + @
+ addAutoHyperlinkSettings(); + + @
+ onoff_attribute("Require a CAPTCHA if not logged in", + "require-captcha", "reqcapt", 1, 0); + @

Require a CAPTCHA for edit operations (appending, creating, or + @ editing wiki or tickets or adding attachments to wiki or tickets) + @ for users who are not logged in. (Property: "require-captcha")

+ + @
+ entry_attribute("Public pages", 30, "public-pages", + "pubpage", "", 0); + @

A comma-separated list of glob patterns for pages that are accessible + @ without needing a login and using the privileges given by the + @ "Default privileges" setting below. + @ + @

Example use case: Set this field to "/doc/trunk/www/*" and set + @ the "Default privileges" to include the "o" privilege + @ to give anonymous users read-only permission to the + @ latest version of the embedded documentation in the www/ folder without + @ allowing them to see the rest of the source code. + @ (Property: "public-pages") + @

+ + @
+ onoff_attribute("Allow users to register themselves", + "self-register", "selfreg", 0, 0); + @

Allow users to register themselves on the /register webpage. + @ A self-registration creates a new entry in the USER table and + @ perhaps also in the SUBSCRIBER table if email notification is + @ enabled. + @ (Property: "self-register")

+ + @
+ onoff_attribute("Allow users to reset their own passwords", + "self-pw-reset", "selfpw", 0, 0); + @

Allow users to request that an email contains a hyperlink to a + @ password reset page be sent to their email address of record. This + @ enables forgetful users to recover their forgotten passwords without + @ administrator intervention. + @ (Property: "self-pw-reset")

+ + @
+ onoff_attribute("Email verification required for self-registration", + "selfreg-verify", "sfverify", 0, 0); + @

If enabled, self-registration creates a new entry in the USER table + @ with only capabilities "7". The default user capabilities are not + @ added until the email address associated with the self-registration + @ has been verified. This setting only makes sense if + @ email notifications are enabled. + @ (Property: "selfreg-verify")

+ + @
+ onoff_attribute("Allow anonymous subscriptions", + "anon-subscribe", "anonsub", 1, 0); + @

If disabled, email notification subscriptions are only allowed + @ for users with a login. If Nobody or Anonymous visit the /subscribe + @ page, they are redirected to /register or /login. + @ (Property: "anon-subscribe")

+ + @
+ entry_attribute("Authorized subscription email addresses", 35, + "auth-sub-email", "asemail", "", 0); + @

This is a comma-separated list of GLOB patterns that specify + @ email addresses that are authorized to subscriptions. If blank + @ (the usual case), then any email address can be used to self-register. + @ This setting is used to limit subscriptions to members of a particular + @ organization or group based on their email address. + @ (Property: "auth-sub-email")

+ + @
+ entry_attribute("Default privileges", 10, "default-perms", + "defaultperms", "u", 0); + @

Permissions given to users that...

  • register themselves using + @ the self-registration procedure (if enabled), or
  • access "public" + @ pages identified by the public-pages glob pattern above, or
  • + @ are users newly created by the administrator.
+ @

Recommended value: "u" for Reader. + @ Capability Key. + @ (Property: "default-perms") + @

+ + @
+ onoff_attribute("Show javascript button to fill in CAPTCHA", + "auto-captcha", "autocaptcha", 0, 0); + @

When enabled, a button appears on the login screen for user + @ "anonymous" that will automatically fill in the CAPTCHA password. + @ This is less secure than forcing the user to do it manually, but is + @ probably secure enough and it is certainly more convenient for + @ anonymous users. (Property: "auto-captcha")

+ + @
+ entry_attribute("Anonymous Login Validity", 11, "anon-cookie-lifespan", + "anoncookls", "840", 0); + @

The number of minutes for which an anonymous login cookie is valid. + @ Set to zero to disable anonymous logins. + @ (property: anon-cookie-lifespan) + + @


+ @

+ @
+ db_end_transaction(0); + style_finish_page(); +} + +/* +** WEBPAGE: setup_login_group +** +** Change how the current repository participates in a login +** group. +*/ +void setup_login_group(void){ + const char *zGroup; + char *zErrMsg = 0; + Stmt q; + Blob fullName; + char *zSelfRepo; + const char *zRepo = PD("repo", ""); + const char *zLogin = PD("login", ""); + const char *zPw = PD("pw", ""); + const char *zNewName = PD("newname", "New Login Group"); + + login_check_credentials(); + if( !g.perm.Setup ){ + login_needed(0); + return; + } + file_canonical_name(g.zRepositoryName, &fullName, 0); + zSelfRepo = fossil_strdup(blob_str(&fullName)); + blob_reset(&fullName); + if( P("join")!=0 ){ + login_group_join(zRepo, 1, zLogin, zPw, zNewName, &zErrMsg); + }else if( P("leave") ){ + login_group_leave(&zErrMsg); + }else if( P("rotate") ){ + captcha_secret_rotate(); + } + style_set_current_feature("setup"); + style_header("Login Group Configuration"); + if( zErrMsg ){ + @

%s(zErrMsg)

+ } + zGroup = login_group_name(); + if( zGroup==0 ){ + @

This repository (in the file named "%h(zSelfRepo)") + @ is not currently part of any login-group. + @ To join a login group, fill out the form below.

+ @ + @
+ login_insert_csrf_secret(); + @
+ @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + @
Repository filename \ + @ in group to join: + @
Login on the above repo: + @
Password: + @ \ + @
Name of login-group: + @ + @ (only used if creating a new login-group).
+ @
+ }else{ + int n = 0; + @

This repository (in the file "%h(zSelfRepo)") + @ is currently part of the "%h(zGroup)" login group. + @ Other repositories in that group are:

+ @ + @ + db_prepare(&q, + "SELECT value," + " (SELECT value FROM config" + " WHERE name=('peer-name-' || substr(x.name,11)))" + " FROM config AS x" + " WHERE name GLOB 'peer-repo-*'" + " ORDER BY value" + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zRepo = db_column_text(&q, 0); + const char *zTitle = db_column_text(&q, 1); + n++; + @ + } + db_finalize(&q); + @
Project Name + @ Repository File
%d(n). + @ %h(zTitle)%h(zRepo)
+ @ + @

+ login_insert_csrf_secret(); + @

To leave this login group press: + @ + @

Setting a common captcha-secret on all repositories in the login-group + @ allows anonymous logins for one repository in the login group to be used + @ by all other repositories of the group within the same domain. Warning: + @ If a captcha dialog was painted before setting the common captcha-secret + @ and the "Speak password for 'anonymous'" button is pressed afterwards, + @ the spoken text will be incorrect. + @ + @

+ } + @

Implementation Details

+ @

The following are fields from the CONFIG table related to login-groups. + @

+ @ + @ + @ + @ + db_prepare(&q, "SELECT name, value, datetime(mtime,'unixepoch') FROM config" + " WHERE name GLOB 'peer-*'" + " OR name GLOB 'project-*'" + " OR name GLOB 'login-group-*'" + " ORDER BY name"); + while( db_step(&q)==SQLITE_ROW ){ + @ + @ + @ + } + db_finalize(&q); + @
Config.NameConfig.ValueConfig.mtime
%h(db_column_text(&q,0))%h(db_column_text(&q,1))%h(db_column_text(&q,2))
+ @

Interpretation

+ @
    + @
  • login-group-code → + @ A random code assigned to each login-group. The login-group-code is + @ a unique identifier for the login-group. + @ + @

  • login-group-name → + @ The human-readable name of the login-group. + @ + @

  • project-code → + @ A random code assigned to each project. The project-code is + @ a unique identifier for the project. Multiple repositories can share + @ the same project-code. When two or more repositories have the same + @ project code, that mean those repositories are clones of each other. + @ Repositories are only able to sync if they share the same project-code. + @ + @

  • project-description → + @ A description of project in this repository. This is a verbose form + @ of project-name. This description can be edited in the second entry + @ box on the Setup/Configuration page. + @ + @

  • project-name → + @ The human-readable name for the project. The project-name can be + @ modified in the first entry on the + @ Setup/Configuration page. + @ + @

  • peer-repo-CODE → + @ CODE is 16-character prefix of the project-code for another + @ repository that is part of the same login-group. The value is the + @ filename for the peer repository. + @ + @

  • peer-name-CODE → + @ CODE is 16-character prefix of the project-code for another + @ repository that is part of the same login-group. The value is + @ project-name value for the other repository. + @

+ style_table_sorter(); + style_finish_page(); +} + +/* +** WEBPAGE: setup_timeline +** +** Edit administrative settings controlling the display of +** timelines. +*/ +void setup_timeline(void){ + double tmDiff; + char zTmDiff[20]; + static const char *const azTimeFormats[] = { + "0", "HH:MM", + "1", "HH:MM:SS", + "2", "YYYY-MM-DD HH:MM", + "3", "YYMMDD HH:MM", + "4", "(off)" + }; + static const char *const azLeafMark[] = { + "0", "No", + "1", "Yes", + "2", "Yes - with emphasis", + }; + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + return; + } + + style_set_current_feature("setup"); + style_header("Timeline Display Preferences"); + db_begin_transaction(); + @
+ login_insert_csrf_secret(); + @

+ + @
+ onoff_attribute("Plaintext comments on timelines", + "timeline-plaintext", "tpt", 0, 0); + @

In timeline displays, check-in comments are displayed literally, + @ without any wiki or HTML interpretation. Use CSS to change + @ display formatting features such as fonts and line-wrapping behavior. + @ (Property: "timeline-plaintext")

+ + @
+ onoff_attribute("Truncate comment at first blank line (Git-style)", + "timeline-truncate-at-blank", "ttb", 0, 0); + @

In timeline displays, check-in comments are displayed only through + @ the first blank line. This is the traditional way to display comments + @ in Git repositories (Property: "timeline-truncate-at-blank")

+ + @
+ onoff_attribute("Break comments at newline characters", + "timeline-hard-newlines", "thnl", 0, 0); + @

In timeline displays, newline characters in check-in comments force + @ a line break on the display. + @ (Property: "timeline-hard-newlines")

+ + @
+ onoff_attribute("Do not adjust user-selected background colors", + "raw-bgcolor", "rbgc", 0, 0); + @

Fossil normally attempts to adjust the saturation and intensity of + @ user-specified background colors on check-ins and branches so that the + @ foreground text is easily readable on all skins. Enable this setting + @ to omit that adjustment and use exactly the background color specified + @ by users. + @ (Property: "raw-bgcolor")

+ + @
+ onoff_attribute("Use Universal Coordinated Time (UTC)", + "timeline-utc", "utc", 1, 0); + @

Show times as UTC (also sometimes called Greenwich Mean Time (GMT) or + @ Zulu) instead of in local time. On this server, local time is currently + tmDiff = db_double(0.0, "SELECT julianday('now')"); + tmDiff = db_double(0.0, + "SELECT (julianday(%.17g,'localtime')-julianday(%.17g))*24.0", + tmDiff, tmDiff); + sqlite3_snprintf(sizeof(zTmDiff), zTmDiff, "%.1f", tmDiff); + if( strcmp(zTmDiff, "0.0")==0 ){ + @ the same as UTC and so this setting will make no difference in + @ the display.

+ }else if( tmDiff<0.0 ){ + sqlite3_snprintf(sizeof(zTmDiff), zTmDiff, "%.1f", -tmDiff); + @ %s(zTmDiff) hours behind UTC.

+ }else{ + @ %s(zTmDiff) hours ahead of UTC.

+ } + @

(Property: "timeline-utc") + + @


+ multiple_choice_attribute("Style", "timeline-default-style", + "tdss", "0", N_TIMELINE_VIEW_STYLE, timeline_view_styles); + @

The default timeline viewing style, for when the user has not + @ specified an alternative. (Property: "timeline-default-style")

+ + @
+ entry_attribute("Default Number Of Rows", 6, "timeline-default-length", + "tldl", "50", 0); + @

The maximum number of rows to show on a timeline in the absence + @ of a user display preference cookie setting or an explicit n= query + @ parameter. (Property: "timeline-default-length")

+ + @
+ multiple_choice_attribute("Per-Item Time Format", "timeline-date-format", + "tdf", "0", count(azTimeFormats)/2, azTimeFormats); + @

If the "HH:MM" or "HH:MM:SS" format is selected, then the date is shown + @ in a separate box (using CSS class "timelineDate") whenever the date + @ changes. With the "YYYY-MM-DD HH:MM" and "YYMMDD ..." formats, + @ the complete date and time is shown on every timeline entry using the + @ CSS class "timelineTime". (Property: "timeline-date-format")

+ + @
+ multiple_choice_attribute("Leaf Markings", "timeline-mark-leaves", + "tml", "1", count(azLeafMark)/2, azLeafMark); + @

Should timeline entries for leaf check-ins be identified in the + @ detail section. (Property: "timeline-mark-leaves")

+ + @
+ entry_attribute("Max timeline comment length", 6, + "timeline-max-comment", "tmc", "0", 0); + @

The maximum length of a comment to be displayed in a timeline. + @ "0" there is no length limit. + @ (Property: "timeline-max-comment")

+ + @
+ entry_attribute("Tooltip dwell time (milliseconds)", 6, + "timeline-dwelltime", "tdt", "100", 0); + @
+ entry_attribute("Tooltip close time (milliseconds)", 6, + "timeline-closetime", "tct", "250", 0); + @

The dwell time defines how long the mouse pointer + @ should be stationary above an object of the graph before a tooltip + @ appears.
+ @ The close time defines how long the mouse pointer + @ can be away from an object before a tooltip is closed.

+ @

Set dwell time to "0" to disable tooltips.
+ @ Set close time to "0" to keep tooltips visible until + @ the mouse is clicked elsewhere.

+ @

(Properties: "timeline-dwelltime", "timeline-closetime")

+ + @
+ onoff_attribute("Timestamp hyperlinks to /info", + "timeline-tslink-info", "ttlti", 0, 0); + @

The hyperlink on the timestamp associated with each timeline entry, + @ on the far left-hand side of the screen, normally targets another + @ /timeline page that shows the entry in context. However, if this + @ option is turned on, that hyperlink targets the /info page showing + @ the details of the entry. + @

The /timeline link is the default since it is often useful to + @ see an entry in context, and because that link is not otherwise + @ accessible on the timeline. The /info link is also accessible by + @ double-clicking the timeline node or by clicking on the hash that + @ follows "check-in:" in the supplemental information section on the + @ right of the entry. + @

(Properties: "timeline-tslink-info") + + @


+ @

+ @
+ db_end_transaction(0); + style_finish_page(); +} + +/* +** WEBPAGE: setup_settings +** +** Change or view miscellaneous settings. Part of the +** /setup pages requiring Setup privileges. +*/ +void setup_settings(void){ + int nSetting; + int i; + Setting const *pSet; + int bIfChng = P("all")==0; + const Setting *aSetting = setting_info(&nSetting); + + login_check_credentials(); + if( !g.perm.Setup ){ + login_needed(0); + return; + } + + style_set_current_feature("setup"); + style_header("Settings"); + if(!g.repositoryOpen){ + /* Provide read-only access to versioned settings, + but only if no repo file was explicitly provided. */ + db_open_local(0); + } + db_begin_transaction(); + if( bIfChng ){ + @

Only settings whose value is different from the default are shown. + @ Click the "All" button above to set all settings. + } + @

Settings marked with (v) are "versionable" and will be overridden + @ by the contents of managed files named + @ ".fossil-settings/SETTING-NAME". + @ If the file for a versionable setting exists, the value cannot be + @ changed on this screen.


+ @ + @

+ if( bIfChng ){ + style_submenu_element("All", "%R/setup_settings?all"); + }else{ + @ + style_submenu_element("Changes-Only", "%R/setup_settings"); + } + @
+ login_insert_csrf_secret(); + for(i=0, pSet=aSetting; iwidth==0 ){ + int hasVersionableValue = pSet->versionable && + (db_get_versioned(pSet->name, NULL, NULL)!=0); + if( bIfChng && setting_has_default_value(pSet, db_get(pSet->name,0)) ){ + continue; + } + onoff_attribute("", pSet->name, + pSet->var!=0 ? pSet->var : pSet->name /*works-like:"x"*/, + is_truth(pSet->def), hasVersionableValue); + @ %h(pSet->name) + if( pSet->versionable ){ + @ (v)
+ } else { + @
+ } + } + } + @
+ @
+ @ + for(i=0, pSet=aSetting; iwidth>0 && !pSet->forceTextArea ){ + int hasVersionableValue = pSet->versionable && + (db_get_versioned(pSet->name, NULL, NULL)!=0); + if( bIfChng && setting_has_default_value(pSet, db_get(pSet->name,0)) ){ + continue; + } + @ + } + } + @
+ @ %h(pSet->name) + if( pSet->versionable ){ + @ (v) + } else { + @ + } + @ + entry_attribute("", /*pSet->width*/ 25, pSet->name, + pSet->var!=0 ? pSet->var : pSet->name /*works-like:"x"*/, + (char*)pSet->def, hasVersionableValue); + @
+ @
+ for(i=0, pSet=aSetting; iwidth>0 && pSet->forceTextArea ){ + int hasVersionableValue = db_get_versioned(pSet->name, NULL, NULL)!=0; + if( bIfChng && setting_has_default_value(pSet, db_get(pSet->name,0)) ){ + continue; + } + @ %s(pSet->name) + if( pSet->versionable ){ + @ (v)
+ } else { + @
+ } + textarea_attribute("", /*rows*/ 2, /*cols*/ 35, pSet->name, + pSet->var!=0 ? pSet->var : pSet->name /*works-like:"x"*/, + (char*)pSet->def, hasVersionableValue); + @
+ } + } + @
+ @
+ db_end_transaction(0); + style_finish_page(); +} + +/* +** SETTING: mainmenu width=70 block-text keep-empty +** +** The mainmenu setting specifies the entries on the main menu +** for many skins. The mainmenu should be a TCL list. Each set +** of four consecutive values defines a single main menu item: +** +** * The first term is text that appears on the menu. +** +** * The second term is a hyperlink to take when a user clicks on the +** entry. Hyperlinks that start with "/" are relative to the +** repository root. +** +** * The third term is an argument to the TH1 "capexpr" command. +** If capexpr evaluates to true, then the entry is shown. If not, +** the entry is omitted. "*" is always true. "{}" is never true. +** +** * The fourth term is a list of extra class names to apply to the +** new menu entry. Some skins use classes "desktoponly" and +** "wideonly" to only show the entries when the web browser +** screen is wide or very wide, respectively. +** +** Some custom skins might not use this property. Whether the property +** is used or not a choice made by the skin designer. Some skins may add +** extra choices (such as the hamburger button) to the menu. +*/ +/* +** SETTING: sitemap-extra width=70 block-text +** +** The sitemap-extra setting defines extra links to appear on the +** /sitemap web page as sub-items of the "Home Page" entry before the +** "Documentation Search" entry (if any). For skins that use the /sitemap +** page to construct a hamburger menu dropdown, new entries added here +** will appear on the hamburger menu. +** +** This setting should be a TCL list divided into triples. Each +** triple defines a new entry: +** +** * The first term is the display name of the /sitemap entry +** +** * The second term is a hyperlink to take when a user clicks on the +** entry. Hyperlinks that start with "/" are relative to the +** repository root. +** +** * The third term is an argument to the TH1 "capexpr" command. +** If capexpr evaluates to true, then the entry is shown. If not, +** the entry is omitted. "*" is always true. +** +** The default value is blank, meaning no added entries. +*/ + + +/* +** WEBPAGE: setup_config +** +** The "Admin/Configuration" page. Requires Setup privilege. +*/ +void setup_config(void){ + login_check_credentials(); + if( !g.perm.Setup ){ + login_needed(0); + return; + } + + style_set_current_feature("setup"); + style_header("WWW Configuration"); + db_begin_transaction(); + @
+ login_insert_csrf_secret(); + @

+ @
+ entry_attribute("Project Name", 60, "project-name", "pn", "", 0); + @

A brief project name so visitors know what this site is about. + @ The project name will also be used as the RSS feed title. + @ (Property: "project-name") + @

+ @
+ textarea_attribute("Project Description", 3, 80, + "project-description", "pd", "", 0); + @

Describe your project. This will be used in page headers for search + @ engines, the repository listing and a short RSS description. + @ (Property: "project-description")

+ @
+ entry_attribute("Canonical Server URL", 40, "email-url", + "eurl", "", 0); + @

This is the URL used to access this repository as a server. + @ Other repositories use this URL to clone or sync against this repository. + @ This is also the basename for hyperlinks included in email alert text. + @ Omit the trailing "/". + @ If this repo will not be set up as a persistent server and will not + @ be sending email alerts, then leave this entry blank. + @ Suggested value: "%h(g.zBaseURL)" + @ (Property: "email-url")

+ @
+ entry_attribute("Index Page", 60, "index-page", "idxpg", "/home", 0); + @

Enter the pathname of the page to display when the "Home" menu + @ option is selected and when no pathname is + @ specified in the URL. For example, if you visit the url:

+ @ + @

%h(g.zBaseURL)

+ @ + @

And you have specified an index page of "/home" the above will + @ automatically redirect to:

+ @ + @

%h(g.zBaseURL)/home

+ @ + @

The default "/home" page displays a Wiki page with the same name + @ as the Project Name specified above. Some sites prefer to redirect + @ to a documentation page (ex: "/doc/trunk/index.wiki") or to "/timeline".

+ @ + @

Note: To avoid a redirect loop or other problems, this entry must + @ begin with "/" and it must specify a valid page. For example, + @ "/home" will work but "home" will not, since it omits the + @ leading "/".

+ @

(Property: "index-page") + @


+ @

The main menu for the web interface + @

+ @ + @

This setting should be a TCL list. Each set of four consecutive + @ values defines a single main menu item: + @

    + @
  1. The first term is text that appears on the menu. + @
  2. The second term is a hyperlink to take when a user clicks on the + @ entry. Hyperlinks that start with "/" are relative to the + @ repository root. + @
  3. The third term is an argument to the TH1 "capexpr" command. + @ If capexpr evaluates to true, then the entry is shown. If not, + @ the entry is omitted. "*" is always true. "{}" is never true. + @
  4. The fourth term is a list of extra class names to apply to the new + @ menu entry. Some skins use classes "desktoponly" and "wideonly" + @ to only show the entries when the web browser screen is wide or + @ very wide, respectively. + @
+ @ + @

Some custom skins might not use this property. Whether the property + @ is used or not a choice made by the skin designer. Some skins may add extra + @ choices (such as the hamburger button) to the menu that are not shown + @ on this list. (Property: mainmenu) + @

+ if(P("resetMenu")!=0){ + db_unset("mainmenu", 0); + cgi_delete_parameter("mmenu"); + } + textarea_attribute("Main Menu", 12, 80, + "mainmenu", "mmenu", style_default_mainmenu(), 0); + @

+ @

+ @ + @

+ @
+ @

Extra links to appear on the /sitemap page, + @ as sub-items of the "Home Page" entry, appearing before the + @ "Documentation Search" entry (if any). In skins that use the /sitemap + @ page to construct a hamburger menu dropdown, new entries added here + @ will appear on the hamburger menu. + @ + @

This setting should be a TCL list divided into triples. Each + @ triple defines a new entry: + @

    + @
  1. The first term is the display name of the /sitemap entry + @
  2. The second term is a hyperlink to take when a user clicks on the + @ entry. Hyperlinks that start with "/" are relative to the + @ repository root. + @
  3. The third term is an argument to the TH1 "capexpr" command. + @ If capexpr evaluates to true, then the entry is shown. If not, + @ the entry is omitted. "*" is always true. + @
+ @ + @

The default value is blank, meaning no added entries. + @ (Property: sitemap-extra) + @

+ textarea_attribute("Custom Sitemap Entries", 8, 80, + "sitemap-extra", "smextra", "", 0); + @


+ @

+ @
+ db_end_transaction(0); + style_finish_page(); +} + +/* +** WEBPAGE: setup_download +** +** The "Admin/Download" page. Requires Setup privilege. +*/ +void setup_download(void){ + login_check_credentials(); + if( !g.perm.Setup ){ + login_needed(0); + return; + } + + style_set_current_feature("setup"); + style_header("Tarball and ZIP Downloads"); + db_begin_transaction(); + @
+ login_insert_csrf_secret(); + @

+ @
+ entry_attribute("Tarball and ZIP Name Prefix", 20, "short-project-name", + "spn", "", 0); + @

This is used as a prefix for the names of generated tarballs and + @ ZIP archive. Keep this prefix brief and use only lower-case ASCII + @ characters, digits, "_", "-" in the name. If this setting is blank, + @ then the full project-name setting + @ is used instead. + @ (Property: "short-project-name") + @

+ @
+ @

Configuration for the /download page. + @

The value is a TCL list divided into groups of four tokens: + @

    + @
  1. Maximum number of matches (COUNT). + @
  2. Tag to match using glob (TAG). + @
  3. Maximum age of check-ins to match (MAX_AGE). + @
  4. Comment to apply to matches (COMMENT). + @
+ @ Each 4-tuple will match zero or more check-ins. The /download page + @ displays the union of matches from all 4-tuples. + @ See the suggested-downloads + @ setting documentation for further detail. + @

+ @ The /download page is omitted from the /sitemap + @ if the first token is "0" or "off" or "no". The default value + @ for this setting is "off". + @ (Property: suggested-downloads) + @

+ textarea_attribute("", 4, 80, + "suggested-downloads", "sgtrlst", "off", 0); + @


+ @

+ @
+ db_end_transaction(0); + style_finish_page(); +} + +/* +** WEBPAGE: setup_wiki +** +** The "Admin/Wiki" page. Requires Setup privilege. +*/ +void setup_wiki(void){ + login_check_credentials(); + if( !g.perm.Setup ){ + login_needed(0); + return; + } + + style_set_current_feature("setup"); + style_header("Wiki Configuration"); + db_begin_transaction(); + @
+ login_insert_csrf_secret(); + @

+ @
+ onoff_attribute("Associate Wiki Pages With Branches, Tags, Tickets, or Checkins", + "wiki-about", "wiki-about", 1, 0); + @

+ @ Associate wiki pages with branches, tags, tickets, or checkins, based on + @ the wiki page name. Wiki pages that begin with "branch/", "checkin/", + @ "tag/" or "ticket" and which continue with the name of an existing branch, + @ check-in, tag or ticket are treated specially when this feature is enabled. + @

    + @
  • branch/branch-name + @
  • checkin/full-check-in-hash + @
  • tag/tag-name + @
  • ticket/full-ticket-hash + @
+ @ (Property: "wiki-about")

+ @
+ entry_attribute("Allow Unsafe HTML In Markdown", 6, + "safe-html", "safe-html", "", 0); + @

Allow "unsafe" HTML (ex: <script>, <form>, etc) to be + @ generated by Markdown-formatted documents. + @ This setting is a string where each character indicates a "type" of + @ document in which to allow unsafe HTML: + @

    + @
  • b → checked-in files, embedded documentation + @
  • f → forum posts + @
  • t → tickets + @
  • w → wiki pages + @
+ @ Include letters for each type of document for which unsafe HTML should + @ be allowed. For example, to allow unsafe HTML only for checked-in files, + @ make this setting be just "b". To allow unsafe HTML anywhere except + @ in forum posts, make this setting be "btw". The default is an + @ empty string which means that Fossil never allows Markdown documents + @ to generate unsafe HTML. + @ (Property: "safe-html")

+ @
+ @ The current interwiki tag map is as follows: + interwiki_append_map_table(cgi_output_blob()); + @

Visit %R/intermap for details or to + @ modify the interwiki tag map. + @


+ onoff_attribute("Use HTML as wiki markup language", + "wiki-use-html", "wiki-use-html", 0, 0); + @

Use HTML as the wiki markup language. Wiki links will still be parsed + @ but all other wiki formatting will be ignored.

+ @

CAUTION: when + @ enabling, all HTML tags and attributes are accepted in the wiki. + @ No sanitization is done. This means that it is very possible for malicious + @ users to inject dangerous HTML, CSS and JavaScript code into your wiki.

+ @

This should only be enabled when wiki editing is limited + @ to trusted users. It should not be used on a publicly + @ editable wiki.

+ @ (Property: "wiki-use-html") + @
+ @

+ @
+ db_end_transaction(0); + style_finish_page(); +} + +/* +** WEBPAGE: setup_chat +** +** The "Admin/Chat" page. Requires Setup privilege. +*/ +void setup_chat(void){ + static const char *const azAlerts[] = { + "alerts/plunk.wav", "Plunk", + "alerts/bflat3.wav", "Tone-1", + "alerts/bflat2.wav", "Tone-2", + "alerts/bloop.wav", "Bloop", + }; + + login_check_credentials(); + if( !g.perm.Setup ){ + login_needed(0); + return; + } + + style_set_current_feature("setup"); + style_header("Chat Configuration"); + db_begin_transaction(); + if( P("rbldchatidx") && cgi_csrf_safe(2) ){ + chat_rebuild_index(1); + } + @
+ login_insert_csrf_secret(); + @

+ @
+ entry_attribute("Initial Chat History Size", 10, + "chat-initial-history", "chatih", "50", 0); + @

When /chat first starts up, it preloads up to this many historical + @ messages. + @ (Property: "chat-initial-history")

+ @
+ entry_attribute("Minimum Number Of Historical Messages To Retain", 10, + "chat-keep-count", "chatkc", "50", 0); + @

The chat subsystem purges older messages. But it will always retain + @ the N most recent messages where N is the value of this setting. + @ (Property: "chat-keep-count")

+ @
+ entry_attribute("Maximum Message Age In Days", 10, + "chat-keep-days", "chatkd", "7", 0); + @

Chat message are removed after N days, where N is the value of + @ this setting. N may be fractional. So, for example, to only keep + @ an historical record of chat messages for 12 hours, set this value + @ to 0.5. + @ (Property: "chat-keep-days")

+ @
+ entry_attribute("Chat Polling Timeout", 10, + "chat-poll-timeout", "chatpt", "420", 0); + @

New chat content is downloaded using the "long poll" technique. + @ HTTP requests are made to /chat-poll which blocks waiting on new + @ content to arrive. But the /chat-poll cannot block forever. It + @ eventual must give up and return an empty message set. This setting + @ determines how long /chat-poll will wait before giving up. The + @ default setting of approximately 7 minutes works well on many systems. + @ Shorter delays might be required on installations that use proxies + @ or web-servers with short timeouts. For best efficiency, this value + @ should be larger rather than smaller. + @ (Property: "chat-poll-timeout")

+ @
+ entry_attribute("Chat Timeline Robot Username", 15, + "chat-timeline-user", "chatrobot", "", 0); + @

If this setting is not an empty string, then any changes that appear + @ on the timeline are announced in the chatroom under the username + @ supplied. The username does not need to actually exist in the USER table. + @ Suggested username: "chat-robot". + @ (Property: "chat-timeline-user")

+ @
+ + multiple_choice_attribute("Alert sound", + "chat-alert-sound", "snd", azAlerts[0], + count(azAlerts)/2, azAlerts); + @

The sound used in the client-side chat to indicate that a new + @ chat message has arrived. + @ (Property: "chat-alert-sound")

+ + @
+ @

+ @

+ @
+ + /* Validate the chat FTS search index */ + if( db_table_exists("repository","chatfts1") ){ + char *zMissing; + zMissing = db_text(0, + "SELECT group_concat(rowid,', ') FROM chat" + " WHERE rowid NOT IN (SELECT rowid FROM chatfts1_docsize)" + ); + if( zMissing && zMissing[0] ){ + @

WARNING: The following chat messages are missing + @ from the full-text index. Press the "Rebuild Full-Text Search Index" + @ button above to fix this.

+ @

%h(zMissing)

+ } + fossil_free(zMissing); + } + + db_end_transaction(0); + @ + style_finish_page(); +} + +/* +** WEBPAGE: setup_modreq +** +** Admin page for setting up moderation of tickets and wiki. +*/ +void setup_modreq(void){ + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + return; + } + + style_set_current_feature("setup"); + style_header("Moderator For Wiki And Tickets"); + db_begin_transaction(); + @
+ login_insert_csrf_secret(); + @
+ onoff_attribute("Moderate ticket changes", + "modreq-tkt", "modreq-tkt", 0, 0); + @

When enabled, any change to tickets is subject to the approval + @ by a ticket moderator - a user with the "q" or Mod-Tkt privilege. + @ Ticket changes enter the system and are shown locally, but are not + @ synced until they are approved. The moderator has the option to + @ delete the change rather than approve it. Ticket changes made by + @ a user who has the Mod-Tkt privilege are never subject to + @ moderation. (Property: "modreq-tkt") + @ + @


+ onoff_attribute("Moderate wiki changes", + "modreq-wiki", "modreq-wiki", 0, 0); + @

When enabled, any change to wiki is subject to the approval + @ by a wiki moderator - a user with the "l" or Mod-Wiki privilege. + @ Wiki changes enter the system and are shown locally, but are not + @ synced until they are approved. The moderator has the option to + @ delete the change rather than approve it. Wiki changes made by + @ a user who has the Mod-Wiki privilege are never subject to + @ moderation. (Property: "modreq-wiki") + @

+ + @
+ @

+ @
+ db_end_transaction(0); + style_finish_page(); + +} + +/* +** WEBPAGE: setup_adunit +** +** Administrative page for configuring and controlling ad units +** and how they are displayed. +*/ +void setup_adunit(void){ + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + return; + } + db_begin_transaction(); + if( P("clear")!=0 && cgi_csrf_safe(2) ){ + db_unprotect(PROTECT_CONFIG); + db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'"); + db_protect_pop(); + cgi_replace_parameter("adunit",""); + cgi_replace_parameter("adright",""); + setup_incr_cfgcnt(); + } + + style_set_current_feature("setup"); + style_header("Edit Ad Unit"); + @
+ login_insert_csrf_secret(); + @ Banner Ad-Unit:
+ textarea_attribute("", 6, 80, "adunit", "adunit", "", 0); + @
+ @ Right-Column Ad-Unit:
+ textarea_attribute("", 6, 80, "adunit-right", "adright", "", 0); + @
+ onoff_attribute("Omit ads to administrator", + "adunit-omit-if-admin", "oia", 0, 0); + @
+ onoff_attribute("Omit ads to logged-in users", + "adunit-omit-if-user", "oiu", 0, 0); + @
+ onoff_attribute("Temporarily disable all ads", + "adunit-disable", "oall", 0, 0); + @
+ @ + @ + @
+ @
+ @ Ad-Unit Notes:
    + @
  • Leave both Ad-Units blank to disable all advertising. + @
  • The "Banner Ad-Unit" is used for wide pages. + @
  • The "Right-Column Ad-Unit" is used on pages with tall, narrow content. + @
  • If the "Right-Column Ad-Unit" is blank, the "Banner Ad-Unit" is + @ used on all pages. + @
  • Properties: "adunit", "adunit-right", "adunit-omit-if-admin", and + @ "adunit-omit-if-user". + @
  • Suggested CSS changes: + @
    +  @ div.adunit_banner {
    +  @   margin: auto;
    +  @   width: 100%%;
    +  @ }
    +  @ div.adunit_right {
    +  @   float: right;
    +  @ }
    +  @ div.adunit_right_container {
    +  @   min-height: height-of-right-column-ad-unit;
    +  @ }
    +  @ 
    + @
  • For a place-holder Ad-Unit for testing, Copy/Paste the following + @ with appropriate adjustments to "width:" and "height:". + @
    +  @ <div style='
    +  @   margin: 0 auto;
    +  @   width: 600px;
    +  @   height: 90px;
    +  @   border: 1px solid #f11;
    +  @   background-color: #fcc;
    +  @ '>Demo Ad</div>
    +  @ 
    + @
  • + style_finish_page(); + db_end_transaction(0); +} + +/* +** WEBPAGE: setup_logo +** +** Administrative page for changing the logo, background, and icon images. +*/ +void setup_logo(void){ + const char *zLogoMtime = db_get_mtime("logo-image", 0, 0); + const char *zLogoMime = db_get("logo-mimetype","image/gif"); + const char *aLogoImg = P("logoim"); + int szLogoImg = atoi(PD("logoim:bytes","0")); + const char *zBgMtime = db_get_mtime("background-image", 0, 0); + const char *zBgMime = db_get("background-mimetype","image/gif"); + const char *aBgImg = P("bgim"); + int szBgImg = atoi(PD("bgim:bytes","0")); + const char *zIconMtime = db_get_mtime("icon-image", 0, 0); + const char *zIconMime = db_get("icon-mimetype","image/gif"); + const char *aIconImg = P("iconim"); + int szIconImg = atoi(PD("iconim:bytes","0")); + if( szLogoImg>0 ){ + zLogoMime = PD("logoim:mimetype","image/gif"); + } + if( szBgImg>0 ){ + zBgMime = PD("bgim:mimetype","image/gif"); + } + if( szIconImg>0 ){ + zIconMime = PD("iconim:mimetype","image/gif"); + } + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + return; + } + db_begin_transaction(); + if( !cgi_csrf_safe(2) ){ + /* Allow no state changes if not safe from CSRF */ + }else if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){ + Blob img; + Stmt ins; + blob_init(&img, aLogoImg, szLogoImg); + db_unprotect(PROTECT_CONFIG); + db_prepare(&ins, + "REPLACE INTO config(name,value,mtime)" + " VALUES('logo-image',:bytes,now())" + ); + db_bind_blob(&ins, ":bytes", &img); + db_step(&ins); + db_finalize(&ins); + db_multi_exec( + "REPLACE INTO config(name,value,mtime) VALUES('logo-mimetype',%Q,now())", + zLogoMime + ); + db_protect_pop(); + db_end_transaction(0); + cgi_redirect("setup_logo"); + }else if( P("clrlogo")!=0 ){ + db_unprotect(PROTECT_CONFIG); + db_multi_exec( + "DELETE FROM config WHERE name IN " + "('logo-image','logo-mimetype')" + ); + db_protect_pop(); + db_end_transaction(0); + cgi_redirect("setup_logo"); + }else if( P("setbg")!=0 && zBgMime && zBgMime[0] && szBgImg>0 ){ + Blob img; + Stmt ins; + blob_init(&img, aBgImg, szBgImg); + db_unprotect(PROTECT_CONFIG); + db_prepare(&ins, + "REPLACE INTO config(name,value,mtime)" + " VALUES('background-image',:bytes,now())" + ); + db_bind_blob(&ins, ":bytes", &img); + db_step(&ins); + db_finalize(&ins); + db_multi_exec( + "REPLACE INTO config(name,value,mtime)" + " VALUES('background-mimetype',%Q,now())", + zBgMime + ); + db_protect_pop(); + db_end_transaction(0); + cgi_redirect("setup_logo"); + }else if( P("clrbg")!=0 ){ + db_unprotect(PROTECT_CONFIG); + db_multi_exec( + "DELETE FROM config WHERE name IN " + "('background-image','background-mimetype')" + ); + db_protect_pop(); + db_end_transaction(0); + cgi_redirect("setup_logo"); + }else if( P("seticon")!=0 && zIconMime && zIconMime[0] && szIconImg>0 ){ + Blob img; + Stmt ins; + blob_init(&img, aIconImg, szIconImg); + db_unprotect(PROTECT_CONFIG); + db_prepare(&ins, + "REPLACE INTO config(name,value,mtime)" + " VALUES('icon-image',:bytes,now())" + ); + db_bind_blob(&ins, ":bytes", &img); + db_step(&ins); + db_finalize(&ins); + db_multi_exec( + "REPLACE INTO config(name,value,mtime)" + " VALUES('icon-mimetype',%Q,now())", + zIconMime + ); + db_protect_pop(); + db_end_transaction(0); + cgi_redirect("setup_logo"); + }else if( P("clricon")!=0 ){ + db_unprotect(PROTECT_CONFIG); + db_multi_exec( + "DELETE FROM config WHERE name IN " + "('icon-image','icon-mimetype')" + ); + db_protect_pop(); + db_end_transaction(0); + cgi_redirect("setup_logo"); + } + style_set_current_feature("setup"); + style_header("Edit Project Logo And Background"); + @

    The current project logo has a MIME-Type of %h(zLogoMime) + @ and looks like this:

    + @

    + @ logo + @

    + @ + @
    + @

    The logo is accessible to all users at this URL: + @ %s(g.zBaseURL)/logo. + @ The logo may or may not appear on each + @ page depending on the CSS and + @ header setup. + @ To change the logo image, use the following form:

    + login_insert_csrf_secret(); + @ Logo Image file: + @ + @

    + @ + @

    + @

    (Properties: "logo-image" and "logo-mimetype") + @

    + @
    + @ + @

    The current background image has a MIME-Type of %h(zBgMime) + @ and looks like this:

    + @

    background + @

    + @ + @
    + @

    The background image is accessible to all users at this URL: + @ %s(g.zBaseURL)/background. + @ The background image may or may not appear on each + @ page depending on the CSS and + @ header setup. + @ To change the background image, use the following form:

    + login_insert_csrf_secret(); + @ Background image file: + @ + @

    + @ + @

    + @
    + @

    (Properties: "background-image" and "background-mimetype") + @


    + @ + @

    The current icon image has a MIME-Type of %h(zIconMime) + @ and looks like this:

    + @

    icon + @

    + @ + @
    + @

    The icon image is accessible to all users at this URL: + @ %s(g.zBaseURL)/favicon.ico. + @ The icon image may or may not appear on each + @ page depending on the web browser in use and the MIME-Types that it + @ supports for icon images. + @ To change the icon image, use the following form:

    + login_insert_csrf_secret(); + @ Icon image file: + @ + @

    + @ + @

    + @
    + @

    (Properties: "icon-image" and "icon-mimetype") + @


    + @ + @

    Note: Your browser has probably cached these + @ images, so you may need to press the Reload button before changes will + @ take effect.

    + style_finish_page(); + db_end_transaction(0); +} + +/* +** Prevent the RAW SQL feature from being used to ATTACH a different +** database and query it. +** +** Actually, the RAW SQL feature only does a single statement per request. +** So it is not possible to ATTACH and then do a separate query. This +** routine is not strictly necessary, therefore. But it does not hurt +** to be paranoid. +*/ +int raw_sql_query_authorizer( + void *pError, + int code, + const char *zArg1, + const char *zArg2, + const char *zArg3, + const char *zArg4 +){ + if( code==SQLITE_ATTACH ){ + return SQLITE_DENY; + } + return SQLITE_OK; +} + + +/* +** WEBPAGE: admin_sql +** +** Run raw SQL commands against the database file using the web interface. +** Requires Setup privileges. +*/ +void sql_page(void){ + const char *zQ; + int go = P("go")!=0; + login_check_credentials(); + if( !g.perm.Setup ){ + login_needed(0); + return; + } + add_content_sql_commands(g.db); + zQ = cgi_csrf_safe(2) ? P("q") : 0; + style_set_current_feature("setup"); + style_header("Raw SQL Commands"); + @

    Caution: There are no restrictions on the SQL that can be + @ run by this page. You can do serious and irrepairable damage to the + @ repository. Proceed with extreme caution.

    + @ +#if 0 + @

    Only the first statement in the entry box will be run. + @ Any subsequent statements will be silently ignored.

    + @ + @

    Database names:

    • repository + if( g.zConfigDbName ){ + @
    • configdb + } + if( g.localOpen ){ + @
    • localdb + } + @

    +#endif + + if( P("configtab") ){ + /* If the user presses the "CONFIG Table Query" button, populate the + ** query text with a pre-packaged query against the CONFIG table */ + zQ = "SELECT\n" + " CASE WHEN length(name)<50 THEN name ELSE printf('%.50s...',name)" + " END AS name,\n" + " CASE WHEN typeof(value)<>'blob' AND length(value)<80 THEN value\n" + " ELSE '...' END AS value,\n" + " datetime(mtime, 'unixepoch') AS mtime\n" + "FROM config\n" + "-- ORDER BY mtime DESC; -- optional"; + go = 1; + } + @ + @
    + login_insert_csrf_secret(); + @ SQL:
    + @
    + @ + @ + @ + @ + @
    + if( P("schema") ){ + zQ = sqlite3_mprintf( + "SELECT sql FROM repository.sqlite_schema" + " WHERE sql IS NOT NULL ORDER BY name"); + go = 1; + }else if( P("tablelist") ){ + zQ = sqlite3_mprintf("SELECT*FROM pragma_table_list ORDER BY schema, name"); + go = 1; + } + if( go && cgi_csrf_safe(2) ){ + sqlite3_stmt *pStmt; + int rc; + const char *zTail; + int nCol; + int nRow = 0; + int i; + @
    + sqlite3_set_authorizer(g.db, raw_sql_query_authorizer, 0); + search_sql_setup(g.db); + rc = sqlite3_prepare_v2(g.db, zQ, -1, &pStmt, &zTail); + if( rc!=SQLITE_OK ){ + @
    %h(sqlite3_errmsg(g.db))
    + sqlite3_finalize(pStmt); + }else if( pStmt==0 ){ + /* No-op */ + }else if( (nCol = sqlite3_column_count(pStmt))==0 ){ + sqlite3_step(pStmt); + rc = sqlite3_finalize(pStmt); + if( rc ){ + @
    %h(sqlite3_errmsg(g.db))
    + } + }else{ + @ + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + if( nRow==0 ){ + @ + for(i=0; i%h(sqlite3_column_name(pStmt, i)) + } + @ + } + nRow++; + @ + for(i=0; i + @ %s(sqlite3_column_text(pStmt, i)) + break; + } + case SQLITE_NULL: { + @ + break; + } + case SQLITE_TEXT: { + const char *zText = (const char*)sqlite3_column_text(pStmt, i); + @ + break; + } + case SQLITE_BLOB: { + @ + break; + } + } + } + @ + } + sqlite3_finalize(pStmt); + @
    NULL%h(zText) + @ %d(sqlite3_column_bytes(pStmt, i))-byte BLOB
    + } + } + style_finish_page(); +} + + +/* +** WEBPAGE: admin_th1 +** +** Run raw TH1 commands using the web interface. If Tcl integration was +** enabled at compile-time and the "tcl" setting is enabled, Tcl commands +** may be run as well. Requires Admin privilege. +*/ +void th1_page(void){ + const char *zQ = P("q"); + int go = P("go")!=0; + login_check_credentials(); + if( !g.perm.Setup ){ + login_needed(0); + return; + } + style_set_current_feature("setup"); + style_header("Raw TH1 Commands"); + @

    Caution: There are no restrictions on the TH1 that can be + @ run by this page. If Tcl integration was enabled at compile-time and + @ the "tcl" setting is enabled, Tcl commands may be run as well.

    + @ + form_begin(0, "%R/admin_th1"); + @ TH1:
    + @
    + @ + @ + if( go && cgi_csrf_safe(2) ){ + const char *zR; + int rc; + int n; + @
    + rc = Th_Eval(g.interp, 0, zQ, -1); + zR = Th_GetResult(g.interp, &n); + if( rc==TH_OK ){ + @
    %h(zR)
    + }else{ + @
    %h(zR)
    + } + } + style_finish_page(); +} + +/* +** WEBPAGE: admin_log +** +** Shows the contents of the admin_log table, which is only created if +** the admin-log setting is enabled. Requires Admin or Setup ('a' or +** 's') permissions. +*/ +void page_admin_log(){ + Stmt stLog; + int limit; /* How many entries to show */ + int ofst; /* Offset to the first entry */ + int fLogEnabled; + int counter = 0; + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + return; + } + style_set_current_feature("setup"); + style_header("Admin Log"); + style_submenu_element("Log-Menu", "setup-logmenu"); + create_admin_log_table(); + limit = atoi(PD("n","200")); + ofst = atoi(PD("x","0")); + fLogEnabled = db_get_boolean("admin-log", 1); + @
    Admin logging is %s(fLogEnabled?"on":"off"). + @ (Change this on the settings page.)
    + + if( ofst>0 ){ + int prevx = ofst - limit; + if( prevx<0 ) prevx = 0; + @

    [Newer]

    + } + db_prepare(&stLog, + "SELECT datetime(time,'unixepoch'), who, page, what " + "FROM admin_log " + "ORDER BY time DESC, rowid DESC"); + style_table_sorter(); + @ + @ + @ + @ + @ + @ + @ + while( SQLITE_ROW == db_step(&stLog) ){ + const char *zTime = db_column_text(&stLog, 0); + const char *zUser = db_column_text(&stLog, 1); + const char *zPage = db_column_text(&stLog, 2); + const char *zMessage = db_column_text(&stLog, 3); + counter++; + if( counterofst+limit ) break; + @ + @ + @ + @ + @ + @ + } + db_finalize(&stLog); + @
    TimeUserPageMessage
    %s(zTime)%s(zUser)%s(zPage)%h(zMessage)
    + if( counter>ofst+limit ){ + @

    [Older]

    + } + style_finish_page(); +} + + +/* +** Renders a selection list of values for the search-tokenizer +** setting, using the form field name "ftstok". +*/ +static void select_fts_tokenizer(void){ + const char *const aTokenizer[] = { + "off", "None", + "porter", "Porter Stemmer", + "unicode61", "Unicode without stemming", + "trigram", "Trigram", + }; + multiple_choice_attribute("FTS Tokenizer", "search-tokenizer", + "ftstok", "off", 4, aTokenizer); +} + +/* +** WEBPAGE: srchsetup +** +** Configure the search engine. Requires Admin privilege. +*/ +void page_srchsetup(){ + const char *zMainBranch; + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + return; + } + style_set_current_feature("setup"); + style_header("Search Configuration"); + @
    + login_insert_csrf_secret(); + @
    + @ Server-specific settings that affect the + @ /search webpage. + @
    + @
    + textarea_attribute("Document Glob List", 3, 35, "doc-glob", "dg", "", 0); + @

    The "Document Glob List" is a comma- or newline-separated list + @ of GLOB expressions that identify all documents within the source + @ tree that are to be searched when "Document Search" is enabled. + @ Some examples: + @ + @ + @ + @ + @ + @
    *.wiki,*.html,*.md,*.txt + @ Search all wiki, HTML, Markdown, and Text files
    doc/*.md,*/README.txt,README.txt + @ Search all Markdown files in the doc/ subfolder and all README.txt + @ files.
    *Search all checked-in files
    (blank) + @ Search nothing. (Disables document search).
    + @


    + zMainBranch = db_main_branch(); + entry_attribute("Document Branches", 20, "doc-branch", "db", zMainBranch, 0); + @

    When searching documents, use the versions of the files found at the + @ type of the "Document Branches" branch. Recommended value: the name of + @ the main branch (usually "trunk"). + @ Document search is disabled if blank. It may be a list of branch names + @ separated by spaces and/or commas. + @


    + onoff_attribute("Search Check-in Comments", "search-ci", "sc", 0, 0); + @
    + onoff_attribute("Search Documents", "search-doc", "sd", 0, 0); + @
    + onoff_attribute("Search Tickets", "search-tkt", "st", 0, 0); + @
    + onoff_attribute("Search Wiki", "search-wiki", "sw", 0, 0); + @
    + onoff_attribute("Search Tech Notes", "search-technote", "se", 0, 0); + @
    + onoff_attribute("Search Forum", "search-forum", "sf", 0, 0); + @
    + onoff_attribute("Search Built-in Help Text", "search-help", "sh", 0, 0); + @
    + @

    + @
    + if( cgi_csrf_safe(2) ){ + if( P("fts0") ){ + search_drop_index(); + }else if( P("fts1") ){ + const char *zTokenizer = PD("ftstok","off"); + search_set_tokenizer(zTokenizer); + search_drop_index(); + search_create_index(); + search_fill_index(); + search_update_index(search_restrict(SRCH_ALL)); + } + if( P("rbldchatidx") ){ + chat_rebuild_index(1); + } + } + if( search_index_exists() ){ + int pgsz = db_int64(0, "PRAGMA repository.page_size;"); + i64 nTotal = db_int64(0, "PRAGMA repository.page_count;")*pgsz; + i64 nFts = db_int64(0, "SELECT count(*) FROM dbstat" + " WHERE schema='repository'" + " AND name LIKE 'fts%%'")*pgsz; + char zSize[30]; + approxSizeName(sizeof(zSize),zSize,nFts); + @

    Currently using an SQLite FTS%d(search_index_type(0)) search index. + @ The index helps search run faster, especially on large repositories, + @ but takes up space. The index is currently using about %s(zSize) + @ or %.1f(100.0*(double)nFts/(double)nTotal)%% of the repository.

    + select_fts_tokenizer(); + @

    + @ + if( db_table_exists("repository","chat") ){ + @ + } + style_submenu_element("FTS Index Debugging","%R/test-ftsdocs"); + }else{ + @

    The SQLite search index is disabled. All searching will be + @ a full-text scan. This usually works fine, but can be slow for + @ larger repositories.

    + select_fts_tokenizer(); + @

    + } + @

    + style_finish_page(); +} + +/* +** A URL Alias originally called zOldName is now zNewName/zValue. +** Write SQL to make this change into pSql. +** +** If zNewName or zValue is an empty string, then delete the entry. +** +** If zOldName is an empty string, create a new entry. +*/ +static void setup_update_url_alias( + Blob *pSql, + const char *zOldName, + const char *zNewName, + const char *zValue +){ + if( !cgi_csrf_safe(2) ) return; + if( zNewName[0]==0 || zValue[0]==0 ){ + if( zOldName[0] ){ + blob_append_sql(pSql, + "DELETE FROM config WHERE name='walias:%q';\n", + zOldName); + } + return; + } + if( zOldName[0]==0 ){ + blob_append_sql(pSql, + "INSERT INTO config(name,value,mtime) VALUES('walias:%q',%Q,now());\n", + zNewName, zValue); + return; + } + if( strcmp(zOldName, zNewName)!=0 ){ + blob_append_sql(pSql, + "UPDATE config SET name='walias:%q', value=%Q, mtime=now()" + " WHERE name='walias:%q';\n", + zNewName, zValue, zOldName); + }else{ + blob_append_sql(pSql, + "UPDATE config SET value=%Q, mtime=now()" + " WHERE name='walias:%q' AND value<>%Q;\n", + zValue, zOldName, zValue); + } +} + +/* +** WEBPAGE: waliassetup +** +** Configure the URL aliases +*/ +void page_waliassetup(){ + Stmt q; + int cnt = 0; + Blob namelist; + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + return; + } + style_set_current_feature("setup"); + style_header("URL Alias Configuration"); + if( P("submit")!=0 && cgi_csrf_safe(2) ){ + Blob token; + Blob sql; + const char *zNewName; + const char *zValue; + char zCnt[10]; + blob_init(&namelist, PD("namelist",""), -1); + blob_init(&sql, 0, 0); + while( blob_token(&namelist, &token) ){ + const char *zOldName = blob_str(&token); + sqlite3_snprintf(sizeof(zCnt), zCnt, "n%d", cnt); + zNewName = PD(zCnt, ""); + sqlite3_snprintf(sizeof(zCnt), zCnt, "v%d", cnt); + zValue = PD(zCnt, ""); + setup_update_url_alias(&sql, zOldName, zNewName, zValue); + cnt++; + blob_reset(&token); + } + sqlite3_snprintf(sizeof(zCnt), zCnt, "n%d", cnt); + zNewName = PD(zCnt,""); + sqlite3_snprintf(sizeof(zCnt), zCnt, "v%d", cnt); + zValue = PD(zCnt,""); + setup_update_url_alias(&sql, "", zNewName, zValue); + db_unprotect(PROTECT_CONFIG); + db_multi_exec("%s", blob_sql_text(&sql)); + db_protect_pop(); + blob_reset(&sql); + blob_reset(&namelist); + cnt = 0; + } + db_prepare(&q, + "SELECT substr(name,8), value FROM config WHERE name GLOB 'walias:/*'" + " UNION ALL SELECT '', ''" + ); + @
    + login_insert_csrf_secret(); + @ + @ + cnt++; + if( blob_size(&namelist)>0 ) blob_append(&namelist, " ", 1); + blob_append(&namelist, zName, -1); + } + db_finalize(&q); + @ + @
    AliasURI That The Alias Maps Into + blob_init(&namelist, 0, 0); + while( db_step(&q)==SQLITE_ROW ){ + const char *zName = db_column_text(&q, 0); + const char *zValue = db_column_text(&q, 1); + @
    + @ + @ + @ + @
    + @ + @ + @
    + @
    + @

    When the first term of an incoming URL exactly matches one of + @ the "Aliases" on the left-hand side (LHS) above, the URL is + @ converted into the corresponding form on the right-hand side (RHS). + @

      + @
    • + @ The LHS is compared against only the first term of the incoming URL. + @ All LHS entries in the alias table should therefore begin with a + @ single "/" followed by a single path element. + @

    • + @ The RHS entries in the alias table should begin with a single "/" + @ followed by a path element, and optionally followed by "?" and a + @ list of query parameters. + @

    • + @ Query parameters on the RHS are added to the set of query parameters + @ in the incoming URL. + @

    • + @ If the same query parameter appears in both the incoming URL and + @ on the RHS of the alias, the RHS query parameter value overwrites + @ the value on the incoming URL. + @

    • + @ If a query parameter on the RHS of the alias is of the form "X!" + @ (a name followed by "!") then the X query parameter is removed + @ from the incoming URL if + @ it exists. + @

    • + @ Only a single alias operation occurs. It is not possible to nest aliases. + @ The RHS entries must be built-in webpage names. + @

    • + @ The alias table is only checked if no built-in webpage matches + @ the incoming URL. + @ Hence, it is not possible to override a built-in webpage using aliases. + @ This is by design. + @

    + @ + @

    To delete an entry from the alias table, change its name or value to an + @ empty string and press "Apply Changes". + @ + @

    To add a new alias, fill in the name and value in the bottom row + @ of the table above and press "Apply Changes". + style_finish_page(); +} -- cgit v1.3.1