1 /*
2 ** Copyright (c) 2007 D. Richard Hipp
3 **
4 ** This program is free software; you can redistribute it and/or
5 ** modify it under the terms of the Simplified BSD License (also
6 ** known as the "2-Clause License" or "FreeBSD License".)
7 **
8 ** This program is distributed in the hope that it will be useful,
9 ** but without any warranty; without even the implied warranty of
10 ** merchantability or fitness for a particular purpose.
11 **
12 ** Author contact information:
13 ** drh@hwaci.com
14 ** http://www.hwaci.com/drh/
15 **
16 *******************************************************************************
17 **
18 ** Implementation of the Setup page
19 */
20 #include "config.h"
21 #include <assert.h>
22 #include "setup.h"
23
24 /*
25 ** Increment the "cfgcnt" variable, so that ETags will know that
26 ** the configuration has changed.
27 */
setup_incr_cfgcnt(void)28 void setup_incr_cfgcnt(void){
29 static int once = 1;
30 if( once ){
31 once = 0;
32 db_unprotect(PROTECT_CONFIG);
33 db_multi_exec("UPDATE config SET value=value+1 WHERE name='cfgcnt'");
34 if( db_changes()==0 ){
35 db_multi_exec("INSERT INTO config(name,value) VALUES('cfgcnt',1)");
36 }
37 db_protect_pop();
38 }
39 }
40
41 /*
42 ** Output a single entry for a menu generated using an HTML table.
43 ** If zLink is not NULL or an empty string, then it is the page that
44 ** the menu entry will hyperlink to. If zLink is NULL or "", then
45 ** the menu entry has no hyperlink - it is disabled.
46 */
setup_menu_entry(const char * zTitle,const char * zLink,const char * zDesc)47 void setup_menu_entry(
48 const char *zTitle,
49 const char *zLink,
50 const char *zDesc
51 ){
52 @ <tr><td valign="top" align="right">
53 if( zLink && zLink[0] ){
54 @ <a href="%s(zLink)">%h(zTitle)</a>
55 }else{
56 @ %h(zTitle)
57 }
58 @ </td><td width="5"></td><td valign="top">%h(zDesc)</td></tr>
59 }
60
61
62
63 /*
64 ** WEBPAGE: setup
65 **
66 ** Main menu for the administrative pages. Requires Admin or Setup
67 ** privileges. Links to sub-pages only usable by Setup users are
68 ** shown only to Setup users.
69 */
setup_page(void)70 void setup_page(void){
71 int setup_user = 0;
72 login_check_credentials();
73 if( !g.perm.Admin ){
74 login_needed(0);
75 }
76 setup_user = g.perm.Setup;
77
78 style_set_current_feature("setup");
79 style_header("Server Administration");
80
81 /* Make sure the header contains <base href="...">. Issue a warning
82 ** if it does not. */
83 if( !cgi_header_contains("<base href=") ){
84 @ <p class="generalError"><b>Configuration Error:</b> Please add
85 @ <tt><base href="$secureurl/$current_page"></tt> after
86 @ <tt><head></tt> in the
87 @ <a href="setup_skinedit?w=2">HTML header</a>!</p>
88 }
89
90 #if !defined(_WIN32)
91 /* Check for /dev/null and /dev/urandom. We want both devices to be present,
92 ** but they are sometimes omitted (by mistake) from chroot jails. */
93 if( access("/dev/null", R_OK|W_OK) ){
94 @ <p class="generalError">WARNING: Device "/dev/null" is not available
95 @ for reading and writing.</p>
96 }
97 if( access("/dev/urandom", R_OK) ){
98 @ <p class="generalError">WARNING: Device "/dev/urandom" is not available
99 @ for reading. This means that the pseudo-random number generator used
100 @ by SQLite will be poorly seeded.</p>
101 }
102 #endif
103
104 @ <table border="0" cellspacing="3">
105 setup_menu_entry("Users", "setup_ulist",
106 "Grant privileges to individual users.");
107 if( setup_user ){
108 setup_menu_entry("Access", "setup_access",
109 "Control access settings.");
110 setup_menu_entry("Configuration", "setup_config",
111 "Configure the WWW components of the repository");
112 }
113 setup_menu_entry("Security-Audit", "secaudit0",
114 "Analyze the current configuration for security problems");
115 if( setup_user ){
116 setup_menu_entry("Settings", "setup_settings",
117 "Web interface to the \"fossil settings\" command");
118 }
119 setup_menu_entry("Timeline", "setup_timeline",
120 "Timeline display preferences");
121 if( setup_user ){
122 setup_menu_entry("Login-Group", "setup_login_group",
123 "Manage single sign-on between this repository and others"
124 " on the same server");
125 setup_menu_entry("Tickets", "tktsetup",
126 "Configure the trouble-ticketing system for this repository");
127 setup_menu_entry("Wiki", "setup_wiki",
128 "Configure the wiki for this repository");
129 setup_menu_entry("Chat", "setup_chat",
130 "Configure the chatroom");
131 }
132 setup_menu_entry("Search","srchsetup",
133 "Configure the built-in search engine");
134 setup_menu_entry("URL Aliases", "waliassetup",
135 "Configure URL aliases");
136 if( setup_user ){
137 setup_menu_entry("Notification", "setup_notification",
138 "Automatic notifications of changes via outbound email");
139 setup_menu_entry("Transfers", "xfersetup",
140 "Configure the transfer system for this repository");
141 }
142 setup_menu_entry("Skins", "setup_skin",
143 "Select and/or modify the web interface \"skins\"");
144 setup_menu_entry("Moderation", "setup_modreq",
145 "Enable/Disable requiring moderator approval of Wiki and/or Ticket"
146 " changes and attachments.");
147 setup_menu_entry("Ad-Unit", "setup_adunit",
148 "Edit HTML text for an ad unit inserted after the menu bar");
149 setup_menu_entry("URLs & Checkouts", "urllist",
150 "Show URLs used to access this repo and known check-outs");
151 if( setup_user ){
152 setup_menu_entry("Web-Cache", "cachestat",
153 "View the status of the expensive-page cache");
154 }
155 setup_menu_entry("Logo", "setup_logo",
156 "Change the logo and background images for the server");
157 setup_menu_entry("Shunned", "shun",
158 "Show artifacts that are shunned by this repository");
159 setup_menu_entry("Artifact Receipts Log", "rcvfromlist",
160 "A record of received artifacts and their sources");
161 setup_menu_entry("User Log", "access_log",
162 "A record of login attempts");
163 setup_menu_entry("Administrative Log", "admin_log",
164 "View the admin_log entries");
165 setup_menu_entry("Error Log", "errorlog",
166 "View the Fossil server error log");
167 setup_menu_entry("Unversioned Files", "uvlist?byage=1",
168 "Show all unversioned files held");
169 setup_menu_entry("Stats", "stat",
170 "Repository Status Reports");
171 setup_menu_entry("Sitemap", "sitemap",
172 "Links to miscellaneous pages");
173 if( setup_user ){
174 setup_menu_entry("SQL", "admin_sql",
175 "Enter raw SQL commands");
176 setup_menu_entry("TH1", "admin_th1",
177 "Enter raw TH1 commands");
178 }
179 @ </table>
180
181 style_finish_page();
182 }
183
184 /*
185 ** Generate a checkbox for an attribute.
186 */
187 void onoff_attribute(
188 const char *zLabel, /* The text label on the checkbox */
189 const char *zVar, /* The corresponding row in the CONFIG table */
190 const char *zQParm, /* The query parameter */
191 int dfltVal, /* Default value if CONFIG table entry does not exist */
192 int disabled /* 1 if disabled */
193 ){
194 const char *zQ = P(zQParm);
195 int iVal = db_get_boolean(zVar, dfltVal);
196 if( zQ==0 && !disabled && P("submit") ){
197 zQ = "off";
198 }
199 if( zQ ){
200 int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ);
201 if( iQ!=iVal ){
202 login_verify_csrf_secret();
203 db_protect_only(PROTECT_NONE);
204 db_set(zVar/*works-like:"x"*/, iQ ? "1" : "0", 0);
205 db_protect_pop();
206 setup_incr_cfgcnt();
207 admin_log("Set option [%q] to [%q].",
208 zVar, iQ ? "on" : "off");
209 iVal = iQ;
210 }
211 }
212 @ <label><input type="checkbox" name="%s(zQParm)" \
213 @ aria-label="%s(zLabel[0]?zLabel:zQParm)" \
214 if( iVal ){
215 @ checked="checked" \
216 }
217 if( disabled ){
218 @ disabled="disabled" \
219 }
220 @ /> <b>%s(zLabel)</b></label>
221 }
222
223 /*
224 ** Generate an entry box for an attribute.
225 */
226 void entry_attribute(
227 const char *zLabel, /* The text label on the entry box */
228 int width, /* Width of the entry box */
229 const char *zVar, /* The corresponding row in the CONFIG table */
230 const char *zQParm, /* The query parameter */
231 const char *zDflt, /* Default value if CONFIG table entry does not exist */
232 int disabled /* 1 if disabled */
233 ){
234 const char *zVal = db_get(zVar, zDflt);
235 const char *zQ = P(zQParm);
236 if( zQ && fossil_strcmp(zQ,zVal)!=0 ){
237 const int nZQ = (int)strlen(zQ);
238 login_verify_csrf_secret();
239 setup_incr_cfgcnt();
240 db_protect_only(PROTECT_NONE);
241 db_set(zVar/*works-like:"x"*/, zQ, 0);
242 db_protect_pop();
243 admin_log("Set entry_attribute %Q to: %.*s%s",
244 zVar, 20, zQ, (nZQ>20 ? "..." : ""));
245 zVal = zQ;
246 }
247 @ <input aria-label="%h(zLabel[0]?zLabel:zQParm)" type="text" \
248 @ id="%s(zQParm)" name="%s(zQParm)" value="%h(zVal)" size="%d(width)" \
249 if( disabled ){
250 @ disabled="disabled" \
251 }
252 @ /> <b>%s(zLabel)</b>
253 }
254
255 /*
256 ** Generate a text box for an attribute.
257 */
258 const char *textarea_attribute(
259 const char *zLabel, /* The text label on the textarea */
260 int rows, /* Rows in the textarea */
261 int cols, /* Columns in the textarea */
262 const char *zVar, /* The corresponding row in the CONFIG table */
263 const char *zQP, /* The query parameter */
264 const char *zDflt, /* Default value if CONFIG table entry does not exist */
265 int disabled /* 1 if the textarea should not be editable */
266 ){
267 const char *z = db_get(zVar, zDflt);
268 const char *zQ = P(zQP);
269 if( zQ && !disabled && fossil_strcmp(zQ,z)!=0){
270 const int nZQ = (int)strlen(zQ);
271 login_verify_csrf_secret();
272 db_protect_only(PROTECT_NONE);
273 db_set(zVar/*works-like:"x"*/, zQ, 0);
274 db_protect_pop();
275 setup_incr_cfgcnt();
276 admin_log("Set textarea_attribute %Q to: %.*s%s",
277 zVar, 20, zQ, (nZQ>20 ? "..." : ""));
278 z = zQ;
279 }
280 if( rows>0 && cols>0 ){
281 @ <textarea id="id%s(zQP)" name="%s(zQP)" rows="%d(rows)" \
282 @ aria-label="%h(zLabel[0]?zLabel:zQP)" \
283 if( disabled ){
284 @ disabled="disabled" \
285 }
286 @ cols="%d(cols)">%h(z)</textarea>
287 if( *zLabel ){
288 @ <span class="textareaLabel">%s(zLabel)</span>
289 }
290 }
291 return z;
292 }
293
294 /*
295 ** Generate a text box for an attribute.
296 */
297 void multiple_choice_attribute(
298 const char *zLabel, /* The text label on the menu */
299 const char *zVar, /* The corresponding row in the CONFIG table */
300 const char *zQP, /* The query parameter */
301 const char *zDflt, /* Default value if CONFIG table entry does not exist */
302 int nChoice, /* Number of choices */
303 const char *const *azChoice /* Choices in pairs (VAR value, Display) */
304 ){
305 const char *z = db_get(zVar, zDflt);
306 const char *zQ = P(zQP);
307 int i;
308 if( zQ && fossil_strcmp(zQ,z)!=0){
309 const int nZQ = (int)strlen(zQ);
310 login_verify_csrf_secret();
311 db_unprotect(PROTECT_ALL);
312 db_set(zVar/*works-like:"x"*/, zQ, 0);
313 setup_incr_cfgcnt();
314 db_protect_pop();
315 admin_log("Set multiple_choice_attribute %Q to: %.*s%s",
316 zVar, 20, zQ, (nZQ>20 ? "..." : ""));
317 z = zQ;
318 }
319 @ <select aria-label="%h(zLabel)" size="1" name="%s(zQP)" id="id%s(zQP)">
320 for(i=0; i<nChoice*2; i+=2){
321 const char *zSel = fossil_strcmp(azChoice[i],z)==0 ? " selected" : "";
322 @ <option value="%h(azChoice[i])"%s(zSel)>%h(azChoice[i+1])</option>
323 }
324 @ </select> <b>%h(zLabel)</b>
325 }
326
327
328 /*
329 ** WEBPAGE: setup_access
330 **
331 ** The access-control settings page. Requires Setup privileges.
332 */
333 void setup_access(void){
334 static const char *const azRedirectOpts[] = {
335 "0", "Off",
336 "1", "Login Page Only",
337 "2", "All Pages"
338 };
339 login_check_credentials();
340 if( !g.perm.Setup ){
341 login_needed(0);
342 return;
343 }
344
345 style_set_current_feature("setup");
346 style_header("Access Control Settings");
347 db_begin_transaction();
348 @ <form action="%R/setup_access" method="post"><div>
349 login_insert_csrf_secret();
350 @ <input type="submit" name="submit" value="Apply Changes" /></p>
351 @ <hr />
352 multiple_choice_attribute("Redirect to HTTPS",
353 "redirect-to-https", "redirhttps", "0",
354 count(azRedirectOpts)/2, azRedirectOpts);
355 @ <p>Force the use of HTTPS by redirecting to HTTPS when an
356 @ unencrypted request is received. This feature can be enabled
357 @ for the Login page only, or for all pages.
358 @ <p>Further details: When enabled, this option causes the $secureurl TH1
359 @ variable is set to an "https:" variant of $baseurl. Otherwise,
360 @ $secureurl is just an alias for $baseurl.
361 @ (Property: "redirect-to-https". "0" for off, "1" for Login page only,
362 @ "2" otherwise.)
363 @ <hr />
364 onoff_attribute("Require password for local access",
365 "localauth", "localauth", 0, 0);
366 @ <p>When enabled, the password sign-in is always required for
367 @ web access. When disabled, unrestricted web access from 127.0.0.1
368 @ is allowed for the <a href="%R/help/ui">fossil ui</a> command or
369 @ from the <a href="%R/help/server">fossil server</a>,
370 @ <a href="%R/help/http">fossil http</a> commands when the
371 @ "--localauth" command line options is used, or from the
372 @ <a href="%R/help/cgi">fossil cgi</a> if a line containing
373 @ the word "localauth" appears in the CGI script.
374 @
375 @ <p>A password is always required if any one or more
376 @ of the following are true:
377 @ <ol>
378 @ <li> This button is checked
379 @ <li> The inbound TCP/IP connection is not from 127.0.0.1
380 @ <li> The server is started using either of the
381 @ <a href="%R/help/server">fossil server</a> or
382 @ <a href="%R/help/server">fossil http</a> commands
383 @ without the "--localauth" option.
384 @ <li> The server is started from CGI without the "localauth" keyword
385 @ in the CGI script.
386 @ </ol>
387 @ (Property: "localauth")
388 @
389 @ <hr />
390 onoff_attribute("Enable /test_env",
391 "test_env_enable", "test_env_enable", 0, 0);
392 @ <p>When enabled, the %h(g.zBaseURL)/test_env URL is available to all
393 @ users. When disabled (the default) only users Admin and Setup can visit
394 @ the /test_env page.
395 @ (Property: "test_env_enable")
396 @ </p>
397 @
398 @ <hr />
399 onoff_attribute("Enable /artifact_stats",
400 "artifact_stats_enable", "artifact_stats_enable", 0, 0);
401 @ <p>When enabled, the %h(g.zBaseURL)/artifact_stats URL is available to all
402 @ users. When disabled (the default) only users with check-in privilege may
403 @ access the /artifact_stats page.
404 @ (Property: "artifact_stats_enable")
405 @ </p>
406 @
407 @ <hr />
408 onoff_attribute("Allow REMOTE_USER authentication",
409 "remote_user_ok", "remote_user_ok", 0, 0);
410 @ <p>When enabled, if the REMOTE_USER environment variable is set to the
411 @ login name of a valid user and no other login credentials are available,
412 @ then the REMOTE_USER is accepted as an authenticated user.
413 @ (Property: "remote_user_ok")
414 @ </p>
415 @
416 @ <hr />
417 onoff_attribute("Allow HTTP_AUTHENTICATION authentication",
418 "http_authentication_ok", "http_authentication_ok", 0, 0);
419 @ <p>When enabled, allow the use of the HTTP_AUTHENTICATION environment
420 @ variable or the "Authentication:" HTTP header to find the username and
421 @ password. This is another way of supporting Basic Authentication.
422 @ (Property: "http_authentication_ok")
423 @ </p>
424 @
425 @ <hr />
426 entry_attribute("Login expiration time", 6, "cookie-expire", "cex",
427 "8766", 0);
428 @ <p>The number of hours for which a login is valid. This must be a
429 @ positive number. The default is 8766 hours which is approximately equal
430 @ to a year.
431 @ (Property: "cookie-expire")</p>
432
433 @ <hr />
434 entry_attribute("Download packet limit", 10, "max-download", "mxdwn",
435 "5000000", 0);
436 @ <p>Fossil tries to limit out-bound sync, clone, and pull packets
437 @ to this many bytes, uncompressed. If the client requires more data
438 @ than this, then the client will issue multiple HTTP requests.
439 @ Values below 1 million are not recommended. 5 million is a
440 @ reasonable number. (Property: "max-download")</p>
441
442 @ <hr />
443 entry_attribute("Download time limit", 11, "max-download-time", "mxdwnt",
444 "30", 0);
445
446 @ <p>Fossil tries to spend less than this many seconds gathering
447 @ the out-bound data of sync, clone, and pull packets.
448 @ If the client request takes longer, a partial reply is given similar
449 @ to the download packet limit. 30s is a reasonable default.
450 @ (Property: "max-download-time")</p>
451
452 @ <hr />
453 entry_attribute("Server Load Average Limit", 11, "max-loadavg", "mxldavg",
454 "0.0", 0);
455 @ <p>Some expensive operations (such as computing tarballs, zip archives,
456 @ or annotation/blame pages) are prohibited if the load average on the host
457 @ computer is too large. Set the threshold for disallowing expensive
458 @ computations here. Set this to 0.0 to disable the load average limit.
459 @ This limit is only enforced on Unix servers. On Linux systems,
460 @ access to the /proc virtual filesystem is required, which means this limit
461 @ might not work inside a chroot() jail.
462 @ (Property: "max-loadavg")</p>
463
464 @ <hr />
465 onoff_attribute(
466 "Enable hyperlinks for \"nobody\" based on User-Agent and Javascript",
467 "auto-hyperlink", "autohyperlink", 1, 0);
468 @ <p>Enable hyperlinks (the equivalent of the "h" permission) for all users,
469 @ including user "nobody", as long as
470 @ <ol><li>the User-Agent string in the
471 @ HTTP header indicates that the request is coming from an actual human
472 @ being, and
473 @ <li>the user agent is able to
474 @ run Javascript in order to set the href= attribute of hyperlinks, and
475 @ <li>mouse movement is detected (optional - see the checkbox below), and
476 @ <li>a number of milliseconds have passed since the page loaded.</ol>
477 @
478 @ <p>This setting is designed to give easy access to humans while
479 @ keeping out robots and spiders.
480 @ You do not normally want a robot to walk your entire repository because
481 @ if it does, your server will end up computing diffs and annotations for
482 @ every historical version of every file and creating ZIPs and tarballs of
483 @ every historical check-in, which can use a lot of CPU and bandwidth
484 @ even for relatively small projects.</p>
485 @
486 @ <p>Additional parameters that control this behavior:</p>
487 @ <blockquote>
488 onoff_attribute("Require mouse movement before enabling hyperlinks",
489 "auto-hyperlink-mouseover", "ahmo", 0, 0);
490 @ <br />
491 entry_attribute("Delay in milliseconds before enabling hyperlinks", 5,
492 "auto-hyperlink-delay", "ah-delay", "50", 0);
493 @ </blockquote>
494 @ <p>For maximum robot defense, the "require mouse movement" should
495 @ be turned on and the "Delay" should be at least 50 milliseconds.</p>
496 @ (Properties: "auto-hyperlink",
497 @ "auto-hyperlink-mouseover", and "auto-hyperlink-delay")</p>
498
499 @ <hr />
500 onoff_attribute("Require a CAPTCHA if not logged in",
501 "require-captcha", "reqcapt", 1, 0);
502 @ <p>Require a CAPTCHA for edit operations (appending, creating, or
503 @ editing wiki or tickets or adding attachments to wiki or tickets)
504 @ for users who are not logged in. (Property: "require-captcha")</p>
505
506 @ <hr />
507 entry_attribute("Public pages", 30, "public-pages",
508 "pubpage", "", 0);
509 @ <p>A comma-separated list of glob patterns for pages that are accessible
510 @ without needing a login and using the privileges given by the
511 @ "Default privileges" setting below.
512 @
513 @ <p>Example use case: Set this field to "/doc/trunk/www/*" and set
514 @ the "Default privileges" to include the "o" privilege
515 @ to give anonymous users read-only permission to the
516 @ latest version of the embedded documentation in the www/ folder without
517 @ allowing them to see the rest of the source code.
518 @ (Property: "public-pages")
519 @ </p>
520
521 @ <hr />
522 onoff_attribute("Allow users to register themselves",
523 "self-register", "selfreg", 0, 0);
524 @ <p>Allow users to register themselves on the /register webpage.
525 @ A self-registration creates a new entry in the USER table and
526 @ perhaps also in the SUBSCRIBER table if email notification is
527 @ enabled.
528 @ (Property: "self-register")</p>
529
530 @ <hr />
531 onoff_attribute("Email verification required for self-registration",
532 "selfreg-verify", "sfverify", 0, 0);
533 @ <p>If enabled, self-registration creates a new entry in the USER table
534 @ with only capabilities "7". The default user capabilities are not
535 @ added until the email address associated with the self-registration
536 @ has been verified. This setting only makes sense if
537 @ email notifications are enabled.
538 @ (Property: "selfreg-verify")</p>
539
540 @ <hr />
541 onoff_attribute("Allow anonymous subscriptions",
542 "anon-subscribe", "anonsub", 1, 0);
543 @ <p>If disabled, email notification subscriptions are only allowed
544 @ for users with a login. If Nobody or Anonymous visit the /subscribe
545 @ page, they are redirected to /register or /login.
546 @ (Property: "anon-subscribe")</p>
547
548 @ <hr />
549 entry_attribute("Authorized subscription email addresses", 35,
550 "auth-sub-email", "asemail", "", 0);
551 @ <p>This is a comma-separated list of GLOB patterns that specify
552 @ email addresses that are authorized to subscriptions. If blank
553 @ (the usual case), then any email address can be used to self-register.
554 @ This setting is used to limit subscriptions to members of a particular
555 @ organization or group based on their email address.
556 @ (Property: "auth-sub-email")</p>
557
558 @ <hr />
559 entry_attribute("Default privileges", 10, "default-perms",
560 "defaultperms", "u", 0);
561 @ <p>Permissions given to users that... <ul><li>register themselves using
562 @ the self-registration procedure (if enabled), or <li>access "public"
563 @ pages identified by the public-pages glob pattern above, or <li>
564 @ are users newly created by the administrator.</ul>
565 @ <p>Recommended value: "u" for Reader.
566 @ <a href="%R/setup_ucap_list">Capability Key</a>.
567 @ (Property: "default-perms")
568 @ </p>
569
570 @ <hr />
571 onoff_attribute("Show javascript button to fill in CAPTCHA",
572 "auto-captcha", "autocaptcha", 0, 0);
573 @ <p>When enabled, a button appears on the login screen for user
574 @ "anonymous" that will automatically fill in the CAPTCHA password.
575 @ This is less secure than forcing the user to do it manually, but is
576 @ probably secure enough and it is certainly more convenient for
577 @ anonymous users. (Property: "auto-captcha")</p>
578
579 @ <hr />
580 @ <p><input type="submit" name="submit" value="Apply Changes" /></p>
581 @ </div></form>
582 db_end_transaction(0);
583 style_finish_page();
584 }
585
586 /*
587 ** WEBPAGE: setup_login_group
588 **
589 ** Change how the current repository participates in a login
590 ** group.
591 */
592 void setup_login_group(void){
593 const char *zGroup;
594 char *zErrMsg = 0;
595 Blob fullName;
596 char *zSelfRepo;
597 const char *zRepo = PD("repo", "");
598 const char *zLogin = PD("login", "");
599 const char *zPw = PD("pw", "");
600 const char *zNewName = PD("newname", "New Login Group");
601
602 login_check_credentials();
603 if( !g.perm.Setup ){
604 login_needed(0);
605 return;
606 }
607 file_canonical_name(g.zRepositoryName, &fullName, 0);
608 zSelfRepo = fossil_strdup(blob_str(&fullName));
609 blob_reset(&fullName);
610 if( P("join")!=0 ){
611 login_group_join(zRepo, 1, zLogin, zPw, zNewName, &zErrMsg);
612 }else if( P("leave") ){
613 login_group_leave(&zErrMsg);
614 }
615 style_set_current_feature("setup");
616 style_header("Login Group Configuration");
617 if( zErrMsg ){
618 @ <p class="generalError">%s(zErrMsg)</p>
619 }
620 zGroup = login_group_name();
621 if( zGroup==0 ){
622 @ <p>This repository (in the file named "%h(zSelfRepo)")
623 @ is not currently part of any login-group.
624 @ To join a login group, fill out the form below.</p>
625 @
626 @ <form action="%R/setup_login_group" method="post"><div>
627 login_insert_csrf_secret();
628 @ <blockquote><table border="0">
629 @
630 @ <tr><th align="right" id="rfigtj">Repository filename \
631 @ in group to join:</th>
632 @ <td width="5"></td><td>
633 @ <input aria-labelledby="rfigtj" type="text" size="50" \
634 @ value="%h(zRepo)" name="repo"></td></tr>
635 @
636 @ <tr><th align="right" id="lotar">Login on the above repo:</th>
637 @ <td width="5"></td><td>
638 @ <input aria-labelledby="lotar" type="text" size="20" \
639 @ value="%h(zLogin)" name="login"></td></tr>
640 @
641 @ <tr><th align="right" id="lgpw">Password:</th>
642 @ <td width="5"></td><td>
643 @ <input aria-labelledby="lgpw" type="password" size="20" name="pw">\
644 @ </td></tr>
645 @
646 @ <tr><th align="right" id="nolg">Name of login-group:</th>
647 @ <td width="5"></td><td>
648 @ <input aria-labelledby="nolg" type="text" size="30" \
649 @ value="%h(zNewName)" name="newname">
650 @ (only used if creating a new login-group).</td></tr>
651 @
652 @ <tr><td colspan="3" align="center">
653 @ <input type="submit" value="Join" name="join"></td></tr>
654 @ </table></blockquote></div></form>
655 }else{
656 Stmt q;
657 int n = 0;
658 @ <p>This repository (in the file "%h(zSelfRepo)")
659 @ is currently part of the "<b>%h(zGroup)</b>" login group.
660 @ Other repositories in that group are:</p>
661 @ <table border="0" cellspacing="4">
662 @ <tr><td colspan="2"><th align="left">Project Name<td>
663 @ <th align="left">Repository File</tr>
664 db_prepare(&q,
665 "SELECT value,"
666 " (SELECT value FROM config"
667 " WHERE name=('peer-name-' || substr(x.name,11)))"
668 " FROM config AS x"
669 " WHERE name GLOB 'peer-repo-*'"
670 " ORDER BY value"
671 );
672 while( db_step(&q)==SQLITE_ROW ){
673 const char *zRepo = db_column_text(&q, 0);
674 const char *zTitle = db_column_text(&q, 1);
675 n++;
676 @ <tr><td align="right">%d(n).</td><td width="4">
677 @ <td>%h(zTitle)<td width="10"><td>%h(zRepo)</tr>
678 }
679 db_finalize(&q);
680 @ </table>
681 @
682 @ <p><form action="%R/setup_login_group" method="post"><div>
683 login_insert_csrf_secret();
684 @ To leave this login group press
685 @ <input type="submit" value="Leave Login Group" name="leave">
686 @ </form></p>
687 @ <hr /><h2>Implementation Details</h2>
688 @ <p>The following are fields from the CONFIG table related to login-groups,
689 @ provided here for instructional and debugging purposes:</p>
690 @ <table border='1' class='sortable' data-column-types='ttt' \
691 @ data-init-sort='1'>
692 @ <thead><tr>
693 @ <th>Config.Name<th>Config.Value<th>Config.mtime</tr>
694 @ </thead><tbody>
695 db_prepare(&q, "SELECT name, value, datetime(mtime,'unixepoch') FROM config"
696 " WHERE name GLOB 'peer-*'"
697 " OR name GLOB 'project-*'"
698 " OR name GLOB 'login-group-*'"
699 " ORDER BY name");
700 while( db_step(&q)==SQLITE_ROW ){
701 @ <tr><td>%h(db_column_text(&q,0))</td>
702 @ <td>%h(db_column_text(&q,1))</td>
703 @ <td>%h(db_column_text(&q,2))</td></tr>
704 }
705 db_finalize(&q);
706 @ </tbody></table>
707 style_table_sorter();
708 }
709 style_finish_page();
710 }
711
712 /*
713 ** WEBPAGE: setup_timeline
714 **
715 ** Edit administrative settings controlling the display of
716 ** timelines.
717 */
718 void setup_timeline(void){
719 double tmDiff;
720 char zTmDiff[20];
721 static const char *const azTimeFormats[] = {
722 "0", "HH:MM",
723 "1", "HH:MM:SS",
724 "2", "YYYY-MM-DD HH:MM",
725 "3", "YYMMDD HH:MM",
726 "4", "(off)"
727 };
728 login_check_credentials();
729 if( !g.perm.Admin ){
730 login_needed(0);
731 return;
732 }
733
734 style_set_current_feature("setup");
735 style_header("Timeline Display Preferences");
736 db_begin_transaction();
737 @ <form action="%R/setup_timeline" method="post"><div>
738 login_insert_csrf_secret();
739 @ <p><input type="submit" name="submit" value="Apply Changes" /></p>
740
741 @ <hr />
742 onoff_attribute("Allow block-markup in timeline",
743 "timeline-block-markup", "tbm", 0, 0);
744 @ <p>In timeline displays, check-in comments can be displayed with or
745 @ without block markup such as paragraphs, tables, etc.
746 @ (Property: "timeline-block-markup")</p>
747
748 @ <hr />
749 onoff_attribute("Plaintext comments on timelines",
750 "timeline-plaintext", "tpt", 0, 0);
751 @ <p>In timeline displays, check-in comments are displayed literally,
752 @ without any wiki or HTML interpretation. Use CSS to change
753 @ display formatting features such as fonts and line-wrapping behavior.
754 @ (Property: "timeline-plaintext")</p>
755
756 @ <hr />
757 onoff_attribute("Truncate comment at first blank line (Git-style)",
758 "timeline-truncate-at-blank", "ttb", 0, 0);
759 @ <p>In timeline displays, check-in comments are displayed only through
760 @ the first blank line. This is the traditional way to display comments
761 @ in Git repositories (Property: "timeline-truncate-at-blank")</p>
762
763 @ <hr />
764 onoff_attribute("Break comments at newline characters",
765 "timeline-hard-newlines", "thnl", 0, 0);
766 @ <p>In timeline displays, newline characters in check-in comments force
767 @ a line break on the display.
768 @ (Property: "timeline-hard-newlines")</p>
769
770 @ <hr />
771 onoff_attribute("Use Universal Coordinated Time (UTC)",
772 "timeline-utc", "utc", 1, 0);
773 @ <p>Show times as UTC (also sometimes called Greenwich Mean Time (GMT) or
774 @ Zulu) instead of in local time. On this server, local time is currently
775 tmDiff = db_double(0.0, "SELECT julianday('now')");
776 tmDiff = db_double(0.0,
777 "SELECT (julianday(%.17g,'localtime')-julianday(%.17g))*24.0",
778 tmDiff, tmDiff);
779 sqlite3_snprintf(sizeof(zTmDiff), zTmDiff, "%.1f", tmDiff);
780 if( strcmp(zTmDiff, "0.0")==0 ){
781 @ the same as UTC and so this setting will make no difference in
782 @ the display.</p>
783 }else if( tmDiff<0.0 ){
784 sqlite3_snprintf(sizeof(zTmDiff), zTmDiff, "%.1f", -tmDiff);
785 @ %s(zTmDiff) hours behind UTC.</p>
786 }else{
787 @ %s(zTmDiff) hours ahead of UTC.</p>
788 }
789 @ <p>(Property: "timeline-utc")
790
791 @ <hr />
792 multiple_choice_attribute("Style", "timeline-default-style",
793 "tdss", "0", N_TIMELINE_VIEW_STYLE, timeline_view_styles);
794 @ <p>The default timeline viewing style, for when the user has not
795 @ specified an alternative. (Property: "timeline-default-style")</p>
796
797 @ <hr />
798 entry_attribute("Default Number Of Rows", 6, "timeline-default-length",
799 "tldl", "50", 0);
800 @ <p>The maximum number of rows to show on a timeline in the absence
801 @ of a user display preference cookie setting or an explicit n= query
802 @ parameter. (Property: "timeline-default-length")</p>
803
804 @ <hr />
805 multiple_choice_attribute("Per-Item Time Format", "timeline-date-format",
806 "tdf", "0", count(azTimeFormats)/2, azTimeFormats);
807 @ <p>If the "HH:MM" or "HH:MM:SS" format is selected, then the date is shown
808 @ in a separate box (using CSS class "timelineDate") whenever the date
809 @ changes. With the "YYYY-MM-DD HH:MM" and "YYMMDD ..." formats,
810 @ the complete date and time is shown on every timeline entry using the
811 @ CSS class "timelineTime". (Property: "timeline-date-format")</p>
812
813 @ <hr />
814 entry_attribute("Max timeline comment length", 6,
815 "timeline-max-comment", "tmc", "0", 0);
816 @ <p>The maximum length of a comment to be displayed in a timeline.
817 @ "0" there is no length limit.
818 @ (Property: "timeline-max-comment")</p>
819
820 @ <hr />
821 entry_attribute("Tooltip dwell time (milliseconds)", 6,
822 "timeline-dwelltime", "tdt", "100", 0);
823 @ <br>
824 entry_attribute("Tooltip close time (milliseconds)", 6,
825 "timeline-closetime", "tct", "250", 0);
826 @ <p>The <strong>dwell time</strong> defines how long the mouse pointer
827 @ should be stationary above an object of the graph before a tooltip
828 @ appears.<br>
829 @ The <strong>close time</strong> defines how long the mouse pointer
830 @ can be away from an object before a tooltip is closed.</p>
831 @ <p>Set <strong>dwell time</strong> to "0" to disable tooltips.<br>
832 @ Set <strong>close time</strong> to "0" to keep tooltips visible until
833 @ the mouse is clicked elsewhere.<p>
834 @ <p>(Properties: "timeline-dwelltime", "timeline-closetime")</p>
835
836 @ <hr />
837 onoff_attribute("Timestamp hyperlinks to /info",
838 "timeline-tslink-info", "ttlti", 0, 0);
839 @ <p>The hyperlink on the timestamp associated with each timeline entry,
840 @ on the far left-hand side of the screen, normally targets another
841 @ /timeline page that shows the entry in context. However, if this
842 @ option is turned on, that hyperlink targets the /info page showing
843 @ the details of the entry.
844 @ <p>The /timeline link is the default since it is often useful to
845 @ see an entry in context, and because that link is not otherwise
846 @ accessible on the timeline. The /info link is also accessible by
847 @ double-clicking the timeline node or by clicking on the hash that
848 @ follows "check-in:" in the supplemental information section on the
849 @ right of the entry.
850 @ <p>(Properties: "timeline-tslink-info")
851
852 @ <hr />
853 @ <p><input type="submit" name="submit" value="Apply Changes" /></p>
854 @ </div></form>
855 db_end_transaction(0);
856 style_finish_page();
857 }
858
859 /*
860 ** WEBPAGE: setup_settings
861 **
862 ** Change or view miscellaneous settings. Part of the
863 ** /setup pages requiring Setup privileges.
864 */
865 void setup_settings(void){
866 int nSetting;
867 int i;
868 Setting const *pSet;
869 const Setting *aSetting = setting_info(&nSetting);
870
871 login_check_credentials();
872 if( !g.perm.Setup ){
873 login_needed(0);
874 return;
875 }
876
877 style_set_current_feature("setup");
878 style_header("Settings");
879 if(!g.repositoryOpen){
880 /* Provide read-only access to versioned settings,
881 but only if no repo file was explicitly provided. */
882 db_open_local(0);
883 }
884 db_begin_transaction();
885 @ <p>Settings marked with (v) are "versionable" and will be overridden
886 @ by the contents of managed files named
887 @ "<tt>.fossil-settings/</tt><i>SETTING-NAME</i>".
888 @ If the file for a versionable setting exists, the value cannot be
889 @ changed on this screen.</p><hr /><p>
890 @
891 @ <form action="%R/setup_settings" method="post"><div>
892 @ <table border="0"><tr><td valign="top">
893 login_insert_csrf_secret();
894 for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
895 if( pSet->width==0 ){
896 int hasVersionableValue = pSet->versionable &&
897 (db_get_versioned(pSet->name, NULL)!=0);
898 onoff_attribute("", pSet->name,
899 pSet->var!=0 ? pSet->var : pSet->name /*works-like:"x"*/,
900 is_truth(pSet->def), hasVersionableValue);
901 @ <a href='%R/help?cmd=%s(pSet->name)'>%h(pSet->name)</a>
902 if( pSet->versionable ){
903 @ (v)<br />
904 } else {
905 @ <br />
906 }
907 }
908 }
909 @ <br /><input type="submit" name="submit" value="Apply Changes" />
910 @ </td><td style="width:50px;"></td><td valign="top">
911 @ <table>
912 for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
913 if( pSet->width>0 && !pSet->forceTextArea ){
914 int hasVersionableValue = pSet->versionable &&
915 (db_get_versioned(pSet->name, NULL)!=0);
916 @ <tr><td>
917 @ <a href='%R/help?cmd=%s(pSet->name)'>%h(pSet->name)</a>
918 if( pSet->versionable ){
919 @ (v)
920 } else {
921 @
922 }
923 @</td><td>
924 entry_attribute("", /*pSet->width*/ 25, pSet->name,
925 pSet->var!=0 ? pSet->var : pSet->name /*works-like:"x"*/,
926 (char*)pSet->def, hasVersionableValue);
927 @</td></tr>
928 }
929 }
930 @</table>
931 @ </td><td style="width:50px;"></td><td valign="top">
932 for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
933 if( pSet->width>0 && pSet->forceTextArea ){
934 int hasVersionableValue = db_get_versioned(pSet->name, NULL)!=0;
935 @ <a href='%R/help?cmd=%s(pSet->name)'>%s(pSet->name)</a>
936 if( pSet->versionable ){
937 @ (v)<br />
938 } else {
939 @ <br />
940 }
941 textarea_attribute("", /*rows*/ 2, /*cols*/ 35, pSet->name,
942 pSet->var!=0 ? pSet->var : pSet->name /*works-like:"x"*/,
943 (char*)pSet->def, hasVersionableValue);
944 @<br />
945 }
946 }
947 @ </td></tr></table>
948 @ </div></form>
949 db_end_transaction(0);
950 style_finish_page();
951 }
952
953 /*
954 ** SETTING: mainmenu width=70 block-text
955 **
956 ** The mainmenu setting specifies the entries on the main menu
957 ** for many skins. The mainmenu should be a TCL list. Each set
958 ** of four consecutive values defines a single main menu item:
959 **
960 ** * The first term is text that appears on the menu.
961 **
962 ** * The second term is a hyperlink to take when a user clicks on the
963 ** entry. Hyperlinks that start with "/" are relative to the
964 ** repository root.
965 **
966 ** * The third term is an argument to the TH1 "capexpr" command.
967 ** If capexpr evalutes to true, then the entry is shown. If not,
968 ** the entry is omitted. "*" is always true. "{}" is never true.
969 **
970 ** * The fourth term is a list of extra class names to apply to the
971 ** new menu entry. Some skins use classes "desktoponly" and
972 ** "wideonly" to only show the entries when the web browser
973 ** screen is wide or very wide, respectively.
974 **
975 ** Some custom skins might not use this property. Whether the property
976 ** is used or not a choice made by the skin designer. Some skins may add
977 ** extra choices (such as the hamburger button) to the menu.
978 */
979 /*
980 ** SETTING: sitemap-extra width=70 block-text
981 **
982 ** The sitemap-extra setting defines extra links to appear on the
983 ** /sitemap web page as sub-items of the "Home Page" entry before the
984 ** "Documentation Search" entry (if any). For skins that use the /sitemap
985 ** page to construct a hamburger menu dropdown, new entries added here
986 ** will appear on the hamburger menu.
987 **
988 ** This setting should be a TCL list divided into triples. Each
989 ** triple defines a new entry:
990 **
991 ** * The first term is the display name of the /sitemap entry
992 **
993 ** * The second term is a hyperlink to take when a user clicks on the
994 ** entry. Hyperlinks that start with "/" are relative to the
995 ** repository root.
996 **
997 ** * The third term is an argument to the TH1 "capexpr" command.
998 ** If capexpr evalutes to true, then the entry is shown. If not,
999 ** the entry is omitted. "*" is always true.
1000 **
1001 ** The default value is blank, meaning no added entries.
1002 */
1003
1004
1005 /*
1006 ** WEBPAGE: setup_config
1007 **
1008 ** The "Admin/Configuration" page. Requires Setup privilege.
1009 */
1010 void setup_config(void){
1011 login_check_credentials();
1012 if( !g.perm.Setup ){
1013 login_needed(0);
1014 return;
1015 }
1016
1017 style_set_current_feature("setup");
1018 style_header("WWW Configuration");
1019 db_begin_transaction();
1020 @ <form action="%R/setup_config" method="post"><div>
1021 login_insert_csrf_secret();
1022 @ <input type="submit" name="submit" value="Apply Changes" /></p>
1023 @ <hr />
1024 entry_attribute("Project Name", 60, "project-name", "pn", "", 0);
1025 @ <p>A brief project name so visitors know what this site is about.
1026 @ The project name will also be used as the RSS feed title.
1027 @ (Property: "project-name")
1028 @ </p>
1029 @ <hr />
1030 textarea_attribute("Project Description", 3, 80,
1031 "project-description", "pd", "", 0);
1032 @ <p>Describe your project. This will be used in page headers for search
1033 @ engines as well as a short RSS description.
1034 @ (Property: "project-description")</p>
1035 @ <hr />
1036 entry_attribute("Tarball and ZIP-archive Prefix", 20, "short-project-name",
1037 "spn", "", 0);
1038 @ <p>This is used as a prefix on the names of generated tarballs and
1039 @ ZIP archive. For best results, keep this prefix brief and avoid special
1040 @ characters such as "/" and "\".
1041 @ If no tarball prefix is specified, then the full Project Name above is used.
1042 @ (Property: "short-project-name")
1043 @ </p>
1044 @ <hr />
1045 entry_attribute("Download Tag", 20, "download-tag", "dlt", "trunk", 0);
1046 @ <p>The <a href='%R/download'>/download</a> page is designed to provide
1047 @ a convenient place for newbies
1048 @ to download a ZIP archive or a tarball of the project. By default,
1049 @ the latest trunk check-in is downloaded. Change this tag to something
1050 @ else (ex: release) to alter the behavior of the /download page.
1051 @ (Property: "download-tag")
1052 @ </p>
1053 @ <hr />
1054 entry_attribute("Index Page", 60, "index-page", "idxpg", "/home", 0);
1055 @ <p>Enter the pathname of the page to display when the "Home" menu
1056 @ option is selected and when no pathname is
1057 @ specified in the URL. For example, if you visit the url:</p>
1058 @
1059 @ <blockquote><p>%h(g.zBaseURL)</p></blockquote>
1060 @
1061 @ <p>And you have specified an index page of "/home" the above will
1062 @ automatically redirect to:</p>
1063 @
1064 @ <blockquote><p>%h(g.zBaseURL)/home</p></blockquote>
1065 @
1066 @ <p>The default "/home" page displays a Wiki page with the same name
1067 @ as the Project Name specified above. Some sites prefer to redirect
1068 @ to a documentation page (ex: "/doc/trunk/index.wiki") or to "/timeline".</p>
1069 @
1070 @ <p>Note: To avoid a redirect loop or other problems, this entry must
1071 @ begin with "/" and it must specify a valid page. For example,
1072 @ "<b>/home</b>" will work but "<b>home</b>" will not, since it omits the
1073 @ leading "/".</p>
1074 @ <p>(Property: "index-page")
1075 @ <hr>
1076 @ <p>The main menu for the web interface
1077 @ <p>
1078 @
1079 @ <p>This setting should be a TCL list. Each set of four consecutive
1080 @ values defines a single main menu item:
1081 @ <ol>
1082 @ <li> The first term is text that appears on the menu.
1083 @ <li> The second term is a hyperlink to take when a user clicks on the
1084 @ entry. Hyperlinks that start with "/" are relative to the
1085 @ repository root.
1086 @ <li> The third term is an argument to the TH1 "capexpr" command.
1087 @ If capexpr evalutes to true, then the entry is shown. If not,
1088 @ the entry is omitted. "*" is always true. "{}" is never true.
1089 @ <li> The fourth term is a list of extra class names to apply to the new
1090 @ menu entry. Some skins use classes "desktoponly" and "wideonly"
1091 @ to only show the entries when the web browser screen is wide or
1092 @ very wide, respectively.
1093 @ </ol>
1094 @
1095 @ <p>Some custom skins might not use this property. Whether the property
1096 @ is used or not a choice made by the skin designer. Some skins may add extra
1097 @ choices (such as the hamburger button) to the menu that are not shown
1098 @ on this list. (Property: mainmenu)
1099 @ <p>
1100 if(P("resetMenu")!=0){
1101 db_unset("mainmenu", 0);
1102 cgi_delete_parameter("mmenu");
1103 }
1104 textarea_attribute("Main Menu", 12, 80,
1105 "mainmenu", "mmenu", style_default_mainmenu(), 0);
1106 @ </p>
1107 @ <p><input type='checkbox' id='cbResetMenu' name='resetMenu' value='1'>
1108 @ <label for='cbResetMenu'>Reset menu to default value</label>
1109 @ </p>
1110 @ <hr>
1111 @ <p>Extra links to appear on the <a href="%R/sitemap">/sitemap</a> page,
1112 @ as sub-items of the "Home Page" entry, appearing before the
1113 @ "Documentation Search" entry (if any). In skins that use the /sitemap
1114 @ page to construct a hamburger menu dropdown, new entries added here
1115 @ will appear on the hamburger menu.
1116 @
1117 @ <p>This setting should be a TCL list divided into triples. Each
1118 @ triple defines a new entry:
1119 @ <ol>
1120 @ <li> The first term is the display name of the /sitemap entry
1121 @ <li> The second term is a hyperlink to take when a user clicks on the
1122 @ entry. Hyperlinks that start with "/" are relative to the
1123 @ repository root.
1124 @ <li> The third term is an argument to the TH1 "capexpr" command.
1125 @ If capexpr evalutes to true, then the entry is shown. If not,
1126 @ the entry is omitted. "*" is always true.
1127 @ </ol>
1128 @
1129 @ <p>The default value is blank, meaning no added entries.
1130 @ (Property: sitemap-extra)
1131 @ <p>
1132 textarea_attribute("Custom Sitemap Entries", 8, 80,
1133 "sitemap-extra", "smextra", "", 0);
1134 @ <hr />
1135 @ <p><input type="submit" name="submit" value="Apply Changes" /></p>
1136 @ </div></form>
1137 db_end_transaction(0);
1138 style_finish_page();
1139 }
1140
1141 /*
1142 ** WEBPAGE: setup_wiki
1143 **
1144 ** The "Admin/Wiki" page. Requires Setup privilege.
1145 */
1146 void setup_wiki(void){
1147 login_check_credentials();
1148 if( !g.perm.Setup ){
1149 login_needed(0);
1150 return;
1151 }
1152
1153 style_set_current_feature("setup");
1154 style_header("Wiki Configuration");
1155 db_begin_transaction();
1156 @ <form action="%R/setup_wiki" method="post"><div>
1157 login_insert_csrf_secret();
1158 @ <input type="submit" name="submit" value="Apply Changes" /></p>
1159 @ <hr />
1160 onoff_attribute("Associate Wiki Pages With Branches, Tags, or Checkins",
1161 "wiki-about", "wiki-about", 1, 0);
1162 @ <p>
1163 @ Associate wiki pages with branches, tags, or checkins, based on
1164 @ the wiki page name. Wiki pages that begin with "branch/", "checkin/"
1165 @ or "tag/" and which continue with the name of an existing branch, checkin
1166 @ or tag are treated specially when this feature is enabled.
1167 @ <ul>
1168 @ <li> <b>branch/</b><i>branch-name</i>
1169 @ <li> <b>checkin/</b><i>full-checkin-hash</i>
1170 @ <li> <b>tag/</b><i>tag-name</i>
1171 @ </ul>
1172 @ (Property: "wiki-about")</p>
1173 @ <hr />
1174 entry_attribute("Allow Unsafe HTML In Markdown", 6,
1175 "safe-html", "safe-html", "", 0);
1176 @ <p>Allow "unsafe" HTML (ex: <script>, <form>, etc) to be
1177 @ generated by <a href="%R/md_rules">Markdown-formatted</a> documents.
1178 @ This setting is a string where each character indicates a "type" of
1179 @ document in which to allow unsafe HTML:
1180 @ <ul>
1181 @ <li> <b>b</b> → checked-in files, embedded documentation
1182 @ <li> <b>f</b> → forum posts
1183 @ <li> <b>t</b> → tickets
1184 @ <li> <b>w</b> → wiki pages
1185 @ </ul>
1186 @ Include letters for each type of document for which unsafe HTML should
1187 @ be allowed. For example, to allow unsafe HTML only for checked-in files,
1188 @ make this setting be just "<b>b</b>". To allow unsafe HTML anywhere except
1189 @ in forum posts, make this setting be "<b>btw</b>". The default is an
1190 @ empty string which means that Fossil never allows Markdown documents
1191 @ to generate unsafe HTML.
1192 @ (Property: "safe-html")</p>
1193 @ <hr />
1194 @ The current interwiki tag map is as follows:
1195 interwiki_append_map_table(cgi_output_blob());
1196 @ <p>Visit <a href="./intermap">%R/intermap</a> for details or to
1197 @ modify the interwiki tag map.
1198 @ <hr />
1199 onoff_attribute("Use HTML as wiki markup language",
1200 "wiki-use-html", "wiki-use-html", 0, 0);
1201 @ <p>Use HTML as the wiki markup language. Wiki links will still be parsed
1202 @ but all other wiki formatting will be ignored.</p>
1203 @ <p><strong>CAUTION:</strong> when
1204 @ enabling, <i>all</i> HTML tags and attributes are accepted in the wiki.
1205 @ No sanitization is done. This means that it is very possible for malicious
1206 @ users to inject dangerous HTML, CSS and JavaScript code into your wiki.</p>
1207 @ <p>This should <strong>only</strong> be enabled when wiki editing is limited
1208 @ to trusted users. It should <strong>not</strong> be used on a publicly
1209 @ editable wiki.</p>
1210 @ (Property: "wiki-use-html")
1211 @ <hr />
1212 @ <p><input type="submit" name="submit" value="Apply Changes" /></p>
1213 @ </div></form>
1214 db_end_transaction(0);
1215 style_finish_page();
1216 }
1217
1218 /*
1219 ** WEBPAGE: setup_chat
1220 **
1221 ** The "Admin/Chat" page. Requires Setup privilege.
1222 */
1223 void setup_chat(void){
1224 static const char *const azAlerts[] = {
1225 "alerts/plunk.wav", "Plunk",
1226 "alerts/bflat3.wav", "Tone-1",
1227 "alerts/bflat2.wav", "Tone-2",
1228 "alerts/bloop.wav", "Bloop",
1229 };
1230
1231 login_check_credentials();
1232 if( !g.perm.Setup ){
1233 login_needed(0);
1234 return;
1235 }
1236
1237 style_set_current_feature("setup");
1238 style_header("Chat Configuration");
1239 db_begin_transaction();
1240 @ <form action="%R/setup_chat" method="post"><div>
1241 login_insert_csrf_secret();
1242 @ <input type="submit" name="submit" value="Apply Changes" /></p>
1243 @ <hr />
1244 entry_attribute("Initial Chat History Size", 10,
1245 "chat-initial-history", "chatih", "50", 0);
1246 @ <p>When /chat first starts up, it preloads up to this many historical
1247 @ messages.
1248 @ (Property: "chat-initial-history")</p>
1249 @ <hr />
1250 entry_attribute("Minimum Number Of Historical Messages To Retain", 10,
1251 "chat-keep-count", "chatkc", "50", 0);
1252 @ <p>The chat subsystem purges older messages. But it will always retain
1253 @ the N most recent messages where N is the value of this setting.
1254 @ (Property: "chat-keep-count")</p>
1255 @ <hr />
1256 entry_attribute("Maximum Message Age In Days", 10,
1257 "chat-keep-days", "chatkd", "7", 0);
1258 @ <p>Chat message are removed after N days, where N is the value of
1259 @ this setting. N may be fractional. So, for example, to only keep
1260 @ an historical record of chat messages for 12 hours, set this value
1261 @ to 0.5.
1262 @ (Property: "chat-keep-days")</p>
1263 @ <hr />
1264 entry_attribute("Chat Polling Timeout", 10,
1265 "chat-poll-timeout", "chatpt", "420", 0);
1266 @ <p>New chat content is downloaded using the "long poll" technique.
1267 @ HTTP requests are made to /chat-poll which blocks waiting on new
1268 @ content to arrive. But the /chat-poll cannot block forever. It
1269 @ eventual must give up and return an empty message set. This setting
1270 @ determines how long /chat-poll will wait before giving up. The
1271 @ default setting of approximately 7 minutes works well on many systems.
1272 @ Shorter delays might be required on installations that use proxies
1273 @ or web-servers with short timeouts. For best efficiency, this value
1274 @ should be larger rather than smaller.
1275 @ (Property: "chat-poll-timeout")</p>
1276 @ <hr />
1277
1278 multiple_choice_attribute("Alert sound",
1279 "chat-alert-sound", "snd", azAlerts[0],
1280 count(azAlerts)/2, azAlerts);
1281 @ <p>The sound used in the client-side chat to indicate that a new
1282 @ chat message has arrived.
1283 @ (Property: "chat-alert-sound")</p>
1284 @ <hr/>
1285 @ <p><input type="submit" name="submit" value="Apply Changes" /></p>
1286 @ </div></form>
1287 db_end_transaction(0);
1288 @ <script nonce="%h(style_nonce())">
1289 @ (function(){
1290 @ var w = document.getElementById('idsnd');
1291 @ w.onchange = function(){
1292 @ var audio = new Audio('%s(g.zBaseURL)/builtin/' + w.value);
1293 @ audio.currentTime = 0;
1294 @ audio.play();
1295 @ }
1296 @ })();
1297 @ </script>
1298 style_finish_page();
1299 }
1300
1301 /*
1302 ** WEBPAGE: setup_modreq
1303 **
1304 ** Admin page for setting up moderation of tickets and wiki.
1305 */
1306 void setup_modreq(void){
1307 login_check_credentials();
1308 if( !g.perm.Admin ){
1309 login_needed(0);
1310 return;
1311 }
1312
1313 style_set_current_feature("setup");
1314 style_header("Moderator For Wiki And Tickets");
1315 db_begin_transaction();
1316 @ <form action="%R/setup_modreq" method="post"><div>
1317 login_insert_csrf_secret();
1318 @ <hr />
1319 onoff_attribute("Moderate ticket changes",
1320 "modreq-tkt", "modreq-tkt", 0, 0);
1321 @ <p>When enabled, any change to tickets is subject to the approval
1322 @ by a ticket moderator - a user with the "q" or Mod-Tkt privilege.
1323 @ Ticket changes enter the system and are shown locally, but are not
1324 @ synced until they are approved. The moderator has the option to
1325 @ delete the change rather than approve it. Ticket changes made by
1326 @ a user who has the Mod-Tkt privilege are never subject to
1327 @ moderation. (Property: "modreq-tkt")
1328 @
1329 @ <hr />
1330 onoff_attribute("Moderate wiki changes",
1331 "modreq-wiki", "modreq-wiki", 0, 0);
1332 @ <p>When enabled, any change to wiki is subject to the approval
1333 @ by a wiki moderator - a user with the "l" or Mod-Wiki privilege.
1334 @ Wiki changes enter the system and are shown locally, but are not
1335 @ synced until they are approved. The moderator has the option to
1336 @ delete the change rather than approve it. Wiki changes made by
1337 @ a user who has the Mod-Wiki privilege are never subject to
1338 @ moderation. (Property: "modreq-wiki")
1339 @ </p>
1340
1341 @ <hr />
1342 @ <p><input type="submit" name="submit" value="Apply Changes" /></p>
1343 @ </div></form>
1344 db_end_transaction(0);
1345 style_finish_page();
1346
1347 }
1348
1349 /*
1350 ** WEBPAGE: setup_adunit
1351 **
1352 ** Administrative page for configuring and controlling ad units
1353 ** and how they are displayed.
1354 */
1355 void setup_adunit(void){
1356 login_check_credentials();
1357 if( !g.perm.Admin ){
1358 login_needed(0);
1359 return;
1360 }
1361 db_begin_transaction();
1362 if( P("clear")!=0 && cgi_csrf_safe(1) ){
1363 db_unprotect(PROTECT_CONFIG);
1364 db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'");
1365 db_protect_pop();
1366 cgi_replace_parameter("adunit","");
1367 cgi_replace_parameter("adright","");
1368 setup_incr_cfgcnt();
1369 }
1370
1371 style_set_current_feature("setup");
1372 style_header("Edit Ad Unit");
1373 @ <form action="%R/setup_adunit" method="post"><div>
1374 login_insert_csrf_secret();
1375 @ <b>Banner Ad-Unit:</b><br />
1376 textarea_attribute("", 6, 80, "adunit", "adunit", "", 0);
1377 @ <br />
1378 @ <b>Right-Column Ad-Unit:</b><br />
1379 textarea_attribute("", 6, 80, "adunit-right", "adright", "", 0);
1380 @ <br />
1381 onoff_attribute("Omit ads to administrator",
1382 "adunit-omit-if-admin", "oia", 0, 0);
1383 @ <br />
1384 onoff_attribute("Omit ads to logged-in users",
1385 "adunit-omit-if-user", "oiu", 0, 0);
1386 @ <br />
1387 onoff_attribute("Temporarily disable all ads",
1388 "adunit-disable", "oall", 0, 0);
1389 @ <br />
1390 @ <input type="submit" name="submit" value="Apply Changes" />
1391 @ <input type="submit" name="clear" value="Delete Ad-Unit" />
1392 @ </div></form>
1393 @ <hr />
1394 @ <b>Ad-Unit Notes:</b><ul>
1395 @ <li>Leave both Ad-Units blank to disable all advertising.
1396 @ <li>The "Banner Ad-Unit" is used for wide pages.
1397 @ <li>The "Right-Column Ad-Unit" is used on pages with tall, narrow content.
1398 @ <li>If the "Right-Column Ad-Unit" is blank, the "Banner Ad-Unit" is
1399 @ used on all pages.
1400 @ <li>Properties: "adunit", "adunit-right", "adunit-omit-if-admin", and
1401 @ "adunit-omit-if-user".
1402 @ <li>Suggested <a href="setup_skinedit?w=0">CSS</a> changes:
1403 @ <blockquote><pre>
1404 @ div.adunit_banner {
1405 @ margin: auto;
1406 @ width: 100%%;
1407 @ }
1408 @ div.adunit_right {
1409 @ float: right;
1410 @ }
1411 @ div.adunit_right_container {
1412 @ min-height: <i>height-of-right-column-ad-unit</i>;
1413 @ }
1414 @ </pre></blockquote>
1415 @ <li>For a place-holder Ad-Unit for testing, Copy/Paste the following
1416 @ with appropriate adjustments to "width:" and "height:".
1417 @ <blockquote><pre>
1418 @ <div style='
1419 @ margin: 0 auto;
1420 @ width: 600px;
1421 @ height: 90px;
1422 @ border: 1px solid #f11;
1423 @ background-color: #fcc;
1424 @ '>Demo Ad</div>
1425 @ </pre></blockquote>
1426 @ </li>
1427 style_finish_page();
1428 db_end_transaction(0);
1429 }
1430
1431 /*
1432 ** WEBPAGE: setup_logo
1433 **
1434 ** Administrative page for changing the logo, background, and icon images.
1435 */
1436 void setup_logo(void){
1437 const char *zLogoMtime = db_get_mtime("logo-image", 0, 0);
1438 const char *zLogoMime = db_get("logo-mimetype","image/gif");
1439 const char *aLogoImg = P("logoim");
1440 int szLogoImg = atoi(PD("logoim:bytes","0"));
1441 const char *zBgMtime = db_get_mtime("background-image", 0, 0);
1442 const char *zBgMime = db_get("background-mimetype","image/gif");
1443 const char *aBgImg = P("bgim");
1444 int szBgImg = atoi(PD("bgim:bytes","0"));
1445 const char *zIconMtime = db_get_mtime("icon-image", 0, 0);
1446 const char *zIconMime = db_get("icon-mimetype","image/gif");
1447 const char *aIconImg = P("iconim");
1448 int szIconImg = atoi(PD("iconim:bytes","0"));
1449 if( szLogoImg>0 ){
1450 zLogoMime = PD("logoim:mimetype","image/gif");
1451 }
1452 if( szBgImg>0 ){
1453 zBgMime = PD("bgim:mimetype","image/gif");
1454 }
1455 if( szIconImg>0 ){
1456 zIconMime = PD("iconim:mimetype","image/gif");
1457 }
1458 login_check_credentials();
1459 if( !g.perm.Admin ){
1460 login_needed(0);
1461 return;
1462 }
1463 db_begin_transaction();
1464 if( !cgi_csrf_safe(1) ){
1465 /* Allow no state changes if not safe from CSRF */
1466 }else if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){
1467 Blob img;
1468 Stmt ins;
1469 blob_init(&img, aLogoImg, szLogoImg);
1470 db_unprotect(PROTECT_CONFIG);
1471 db_prepare(&ins,
1472 "REPLACE INTO config(name,value,mtime)"
1473 " VALUES('logo-image',:bytes,now())"
1474 );
1475 db_bind_blob(&ins, ":bytes", &img);
1476 db_step(&ins);
1477 db_finalize(&ins);
1478 db_multi_exec(
1479 "REPLACE INTO config(name,value,mtime) VALUES('logo-mimetype',%Q,now())",
1480 zLogoMime
1481 );
1482 db_protect_pop();
1483 db_end_transaction(0);
1484 cgi_redirect("setup_logo");
1485 }else if( P("clrlogo")!=0 ){
1486 db_unprotect(PROTECT_CONFIG);
1487 db_multi_exec(
1488 "DELETE FROM config WHERE name IN "
1489 "('logo-image','logo-mimetype')"
1490 );
1491 db_protect_pop();
1492 db_end_transaction(0);
1493 cgi_redirect("setup_logo");
1494 }else if( P("setbg")!=0 && zBgMime && zBgMime[0] && szBgImg>0 ){
1495 Blob img;
1496 Stmt ins;
1497 blob_init(&img, aBgImg, szBgImg);
1498 db_unprotect(PROTECT_CONFIG);
1499 db_prepare(&ins,
1500 "REPLACE INTO config(name,value,mtime)"
1501 " VALUES('background-image',:bytes,now())"
1502 );
1503 db_bind_blob(&ins, ":bytes", &img);
1504 db_step(&ins);
1505 db_finalize(&ins);
1506 db_multi_exec(
1507 "REPLACE INTO config(name,value,mtime)"
1508 " VALUES('background-mimetype',%Q,now())",
1509 zBgMime
1510 );
1511 db_protect_pop();
1512 db_end_transaction(0);
1513 cgi_redirect("setup_logo");
1514 }else if( P("clrbg")!=0 ){
1515 db_unprotect(PROTECT_CONFIG);
1516 db_multi_exec(
1517 "DELETE FROM config WHERE name IN "
1518 "('background-image','background-mimetype')"
1519 );
1520 db_protect_pop();
1521 db_end_transaction(0);
1522 cgi_redirect("setup_logo");
1523 }else if( P("seticon")!=0 && zIconMime && zIconMime[0] && szIconImg>0 ){
1524 Blob img;
1525 Stmt ins;
1526 blob_init(&img, aIconImg, szIconImg);
1527 db_unprotect(PROTECT_CONFIG);
1528 db_prepare(&ins,
1529 "REPLACE INTO config(name,value,mtime)"
1530 " VALUES('icon-image',:bytes,now())"
1531 );
1532 db_bind_blob(&ins, ":bytes", &img);
1533 db_step(&ins);
1534 db_finalize(&ins);
1535 db_multi_exec(
1536 "REPLACE INTO config(name,value,mtime)"
1537 " VALUES('icon-mimetype',%Q,now())",
1538 zIconMime
1539 );
1540 db_protect_pop();
1541 db_end_transaction(0);
1542 cgi_redirect("setup_logo");
1543 }else if( P("clricon")!=0 ){
1544 db_unprotect(PROTECT_CONFIG);
1545 db_multi_exec(
1546 "DELETE FROM config WHERE name IN "
1547 "('icon-image','icon-mimetype')"
1548 );
1549 db_protect_pop();
1550 db_end_transaction(0);
1551 cgi_redirect("setup_logo");
1552 }
1553 style_set_current_feature("setup");
1554 style_header("Edit Project Logo And Background");
1555 @ <p>The current project logo has a MIME-Type of <b>%h(zLogoMime)</b>
1556 @ and looks like this:</p>
1557 @ <blockquote><p><img src="%R/logo/%z(zLogoMtime)" \
1558 @ alt="logo" border="1" />
1559 @ </p></blockquote>
1560 @
1561 @ <form action="%R/setup_logo" method="post"
1562 @ enctype="multipart/form-data"><div>
1563 @ <p>The logo is accessible to all users at this URL:
1564 @ <a href="%s(g.zBaseURL)/logo">%s(g.zBaseURL)/logo</a>.
1565 @ The logo may or may not appear on each
1566 @ page depending on the <a href="setup_skinedit?w=0">CSS</a> and
1567 @ <a href="setup_skinedit?w=2">header setup</a>.
1568 @ To change the logo image, use the following form:</p>
1569 login_insert_csrf_secret();
1570 @ Logo Image file:
1571 @ <input type="file" name="logoim" size="60" accept="image/*" />
1572 @ <p align="center">
1573 @ <input type="submit" name="setlogo" value="Change Logo" />
1574 @ <input type="submit" name="clrlogo" value="Revert To Default" /></p>
1575 @ <p>(Properties: "logo-image" and "logo-mimetype")
1576 @ </div></form>
1577 @ <hr />
1578 @
1579 @ <p>The current background image has a MIME-Type of <b>%h(zBgMime)</b>
1580 @ and looks like this:</p>
1581 @ <blockquote><p><img src="%R/background/%z(zBgMtime)" \
1582 @ alt="background" border=1 />
1583 @ </p></blockquote>
1584 @
1585 @ <form action="%R/setup_logo" method="post"
1586 @ enctype="multipart/form-data"><div>
1587 @ <p>The background image is accessible to all users at this URL:
1588 @ <a href="%s(g.zBaseURL)/background">%s(g.zBaseURL)/background</a>.
1589 @ The background image may or may not appear on each
1590 @ page depending on the <a href="setup_skinedit?w=0">CSS</a> and
1591 @ <a href="setup_skinedit?w=2">header setup</a>.
1592 @ To change the background image, use the following form:</p>
1593 login_insert_csrf_secret();
1594 @ Background image file:
1595 @ <input type="file" name="bgim" size="60" accept="image/*" />
1596 @ <p align="center">
1597 @ <input type="submit" name="setbg" value="Change Background" />
1598 @ <input type="submit" name="clrbg" value="Revert To Default" /></p>
1599 @ </div></form>
1600 @ <p>(Properties: "background-image" and "background-mimetype")
1601 @ <hr />
1602 @
1603 @ <p>The current icon image has a MIME-Type of <b>%h(zIconMime)</b>
1604 @ and looks like this:</p>
1605 @ <blockquote><p><img src="%R/favicon.ico/%z(zIconMtime)" \
1606 @ alt="icon" border=1 />
1607 @ </p></blockquote>
1608 @
1609 @ <form action="%R/setup_logo" method="post"
1610 @ enctype="multipart/form-data"><div>
1611 @ <p>The icon image is accessible to all users at this URL:
1612 @ <a href="%s(g.zBaseURL)/favicon.ico">%s(g.zBaseURL)/favicon.ico</a>.
1613 @ The icon image may or may not appear on each
1614 @ page depending on the web browser in use and the MIME-Types that it
1615 @ supports for icon images.
1616 @ To change the icon image, use the following form:</p>
1617 login_insert_csrf_secret();
1618 @ Icon image file:
1619 @ <input type="file" name="iconim" size="60" accept="image/*" />
1620 @ <p align="center">
1621 @ <input type="submit" name="seticon" value="Change Icon" />
1622 @ <input type="submit" name="clricon" value="Revert To Default" /></p>
1623 @ </div></form>
1624 @ <p>(Properties: "icon-image" and "icon-mimetype")
1625 @ <hr />
1626 @
1627 @ <p><span class="note">Note:</span> Your browser has probably cached these
1628 @ images, so you may need to press the Reload button before changes will
1629 @ take effect. </p>
1630 style_finish_page();
1631 db_end_transaction(0);
1632 }
1633
1634 /*
1635 ** Prevent the RAW SQL feature from being used to ATTACH a different
1636 ** database and query it.
1637 **
1638 ** Actually, the RAW SQL feature only does a single statement per request.
1639 ** So it is not possible to ATTACH and then do a separate query. This
1640 ** routine is not strictly necessary, therefore. But it does not hurt
1641 ** to be paranoid.
1642 */
1643 int raw_sql_query_authorizer(
1644 void *pError,
1645 int code,
1646 const char *zArg1,
1647 const char *zArg2,
1648 const char *zArg3,
1649 const char *zArg4
1650 ){
1651 if( code==SQLITE_ATTACH ){
1652 return SQLITE_DENY;
1653 }
1654 return SQLITE_OK;
1655 }
1656
1657
1658 /*
1659 ** WEBPAGE: admin_sql
1660 **
1661 ** Run raw SQL commands against the database file using the web interface.
1662 ** Requires Setup privileges.
1663 */
1664 void sql_page(void){
1665 const char *zQ;
1666 int go = P("go")!=0;
1667 login_check_credentials();
1668 if( !g.perm.Setup ){
1669 login_needed(0);
1670 return;
1671 }
1672 add_content_sql_commands(g.db);
1673 zQ = cgi_csrf_safe(1) ? P("q") : 0;
1674 style_set_current_feature("setup");
1675 style_header("Raw SQL Commands");
1676 @ <p><b>Caution:</b> There are no restrictions on the SQL that can be
1677 @ run by this page. You can do serious and irrepairable damage to the
1678 @ repository. Proceed with extreme caution.</p>
1679 @
1680 #if 0
1681 @ <p>Only the first statement in the entry box will be run.
1682 @ Any subsequent statements will be silently ignored.</p>
1683 @
1684 @ <p>Database names:<ul><li>repository
1685 if( g.zConfigDbName ){
1686 @ <li>configdb
1687 }
1688 if( g.localOpen ){
1689 @ <li>localdb
1690 }
1691 @ </ul></p>
1692 #endif
1693
1694 if( P("configtab") ){
1695 /* If the user presses the "CONFIG Table Query" button, populate the
1696 ** query text with a pre-packaged query against the CONFIG table */
1697 zQ = "SELECT\n"
1698 " CASE WHEN length(name)<50 THEN name ELSE printf('%.50s...',name)"
1699 " END AS name,\n"
1700 " CASE WHEN typeof(value)<>'blob' AND length(value)<80 THEN value\n"
1701 " ELSE '...' END AS value,\n"
1702 " datetime(mtime, 'unixepoch') AS mtime\n"
1703 "FROM config\n"
1704 "-- ORDER BY mtime DESC; -- optional";
1705 go = 1;
1706 }
1707 @
1708 @ <form method="post" action="%R/admin_sql">
1709 login_insert_csrf_secret();
1710 @ SQL:<br />
1711 @ <textarea name="q" rows="8" cols="80">%h(zQ)</textarea><br />
1712 @ <input type="submit" name="go" value="Run SQL">
1713 @ <input type="submit" name="schema" value="Show Schema">
1714 @ <input type="submit" name="tablelist" value="List Tables">
1715 @ <input type="submit" name="configtab" value="CONFIG Table Query">
1716 @ </form>
1717 if( P("schema") ){
1718 zQ = sqlite3_mprintf(
1719 "SELECT sql FROM repository.sqlite_schema"
1720 " WHERE sql IS NOT NULL ORDER BY name");
1721 go = 1;
1722 }else if( P("tablelist") ){
1723 zQ = sqlite3_mprintf(
1724 "SELECT name FROM repository.sqlite_schema WHERE type='table'"
1725 " ORDER BY name");
1726 go = 1;
1727 }
1728 if( go ){
1729 sqlite3_stmt *pStmt;
1730 int rc;
1731 const char *zTail;
1732 int nCol;
1733 int nRow = 0;
1734 int i;
1735 @ <hr />
1736 login_verify_csrf_secret();
1737 sqlite3_set_authorizer(g.db, raw_sql_query_authorizer, 0);
1738 rc = sqlite3_prepare_v2(g.db, zQ, -1, &pStmt, &zTail);
1739 if( rc!=SQLITE_OK ){
1740 @ <div class="generalError">%h(sqlite3_errmsg(g.db))</div>
1741 sqlite3_finalize(pStmt);
1742 }else if( pStmt==0 ){
1743 /* No-op */
1744 }else if( (nCol = sqlite3_column_count(pStmt))==0 ){
1745 sqlite3_step(pStmt);
1746 rc = sqlite3_finalize(pStmt);
1747 if( rc ){
1748 @ <div class="generalError">%h(sqlite3_errmsg(g.db))</div>
1749 }
1750 }else{
1751 @ <table border=1>
1752 while( sqlite3_step(pStmt)==SQLITE_ROW ){
1753 if( nRow==0 ){
1754 @ <tr>
1755 for(i=0; i<nCol; i++){
1756 @ <th>%h(sqlite3_column_name(pStmt, i))</th>
1757 }
1758 @ </tr>
1759 }
1760 nRow++;
1761 @ <tr>
1762 for(i=0; i<nCol; i++){
1763 switch( sqlite3_column_type(pStmt, i) ){
1764 case SQLITE_INTEGER:
1765 case SQLITE_FLOAT: {
1766 @ <td align="right" valign="top">
1767 @ %s(sqlite3_column_text(pStmt, i))</td>
1768 break;
1769 }
1770 case SQLITE_NULL: {
1771 @ <td valign="top" align="center"><i>NULL</i></td>
1772 break;
1773 }
1774 case SQLITE_TEXT: {
1775 const char *zText = (const char*)sqlite3_column_text(pStmt, i);
1776 @ <td align="left" valign="top"
1777 @ style="white-space:pre;">%h(zText)</td>
1778 break;
1779 }
1780 case SQLITE_BLOB: {
1781 @ <td valign="top" align="center">
1782 @ <i>%d(sqlite3_column_bytes(pStmt, i))-byte BLOB</i></td>
1783 break;
1784 }
1785 }
1786 }
1787 @ </tr>
1788 }
1789 sqlite3_finalize(pStmt);
1790 @ </table>
1791 }
1792 }
1793 style_finish_page();
1794 }
1795
1796
1797 /*
1798 ** WEBPAGE: admin_th1
1799 **
1800 ** Run raw TH1 commands using the web interface. If Tcl integration was
1801 ** enabled at compile-time and the "tcl" setting is enabled, Tcl commands
1802 ** may be run as well. Requires Admin privilege.
1803 */
1804 void th1_page(void){
1805 const char *zQ = P("q");
1806 int go = P("go")!=0;
1807 login_check_credentials();
1808 if( !g.perm.Setup ){
1809 login_needed(0);
1810 return;
1811 }
1812 style_set_current_feature("setup");
1813 style_header("Raw TH1 Commands");
1814 @ <p><b>Caution:</b> There are no restrictions on the TH1 that can be
1815 @ run by this page. If Tcl integration was enabled at compile-time and
1816 @ the "tcl" setting is enabled, Tcl commands may be run as well.</p>
1817 @
1818 @ <form method="post" action="%R/admin_th1">
1819 login_insert_csrf_secret();
1820 @ TH1:<br />
1821 @ <textarea name="q" rows="5" cols="80">%h(zQ)</textarea><br />
1822 @ <input type="submit" name="go" value="Run TH1">
1823 @ </form>
1824 if( go ){
1825 const char *zR;
1826 int rc;
1827 int n;
1828 @ <hr />
1829 login_verify_csrf_secret();
1830 rc = Th_Eval(g.interp, 0, zQ, -1);
1831 zR = Th_GetResult(g.interp, &n);
1832 if( rc==TH_OK ){
1833 @ <pre class="th1result">%h(zR)</pre>
1834 }else{
1835 @ <pre class="th1error">%h(zR)</pre>
1836 }
1837 }
1838 style_finish_page();
1839 }
1840
1841 /*
1842 ** WEBPAGE: admin_log
1843 **
1844 ** Shows the contents of the admin_log table, which is only created if
1845 ** the admin-log setting is enabled. Requires Admin or Setup ('a' or
1846 ** 's') permissions.
1847 */
1848 void page_admin_log(){
1849 Stmt stLog;
1850 int limit; /* How many entries to show */
1851 int ofst; /* Offset to the first entry */
1852 int fLogEnabled;
1853 int counter = 0;
1854 login_check_credentials();
1855 if( !g.perm.Admin ){
1856 login_needed(0);
1857 return;
1858 }
1859 style_set_current_feature("setup");
1860 style_header("Admin Log");
1861 create_admin_log_table();
1862 limit = atoi(PD("n","200"));
1863 ofst = atoi(PD("x","0"));
1864 fLogEnabled = db_get_boolean("admin-log", 0);
1865 @ <div>Admin logging is %s(fLogEnabled?"on":"off").
1866 @ (Change this on the <a href="setup_settings">settings</a> page.)</div>
1867
1868 if( ofst>0 ){
1869 int prevx = ofst - limit;
1870 if( prevx<0 ) prevx = 0;
1871 @ <p><a href="admin_log?n=%d(limit)&x=%d(prevx)">[Newer]</a></p>
1872 }
1873 db_prepare(&stLog,
1874 "SELECT datetime(time,'unixepoch'), who, page, what "
1875 "FROM admin_log "
1876 "ORDER BY time DESC");
1877 style_table_sorter();
1878 @ <table class="sortable adminLogTable" width="100%%" \
1879 @ data-column-types='Tttx' data-init-sort='1'>
1880 @ <thead>
1881 @ <th>Time</th>
1882 @ <th>User</th>
1883 @ <th>Page</th>
1884 @ <th width="60%%">Message</th>
1885 @ </thead><tbody>
1886 while( SQLITE_ROW == db_step(&stLog) ){
1887 const char *zTime = db_column_text(&stLog, 0);
1888 const char *zUser = db_column_text(&stLog, 1);
1889 const char *zPage = db_column_text(&stLog, 2);
1890 const char *zMessage = db_column_text(&stLog, 3);
1891 counter++;
1892 if( counter<ofst ) continue;
1893 if( counter>ofst+limit ) break;
1894 @ <tr class="row%d(counter%2)">
1895 @ <td class="adminTime">%s(zTime)</td>
1896 @ <td>%s(zUser)</td>
1897 @ <td>%s(zPage)</td>
1898 @ <td>%h(zMessage)</td>
1899 @ </tr>
1900 }
1901 db_finalize(&stLog);
1902 @ </tbody></table>
1903 if( counter>ofst+limit ){
1904 @ <p><a href="admin_log?n=%d(limit)&x=%d(limit+ofst)">[Older]</a></p>
1905 }
1906 style_finish_page();
1907 }
1908
1909 /*
1910 ** WEBPAGE: srchsetup
1911 **
1912 ** Configure the search engine. Requires Admin privilege.
1913 */
1914 void page_srchsetup(){
1915 login_check_credentials();
1916 if( !g.perm.Admin ){
1917 login_needed(0);
1918 return;
1919 }
1920 style_set_current_feature("setup");
1921 style_header("Search Configuration");
1922 @ <form action="%R/srchsetup" method="post"><div>
1923 login_insert_csrf_secret();
1924 @ <div style="text-align:center;font-weight:bold;">
1925 @ Server-specific settings that affect the
1926 @ <a href="%R/search">/search</a> webpage.
1927 @ </div>
1928 @ <hr />
1929 textarea_attribute("Document Glob List", 3, 35, "doc-glob", "dg", "", 0);
1930 @ <p>The "Document Glob List" is a comma- or newline-separated list
1931 @ of GLOB expressions that identify all documents within the source
1932 @ tree that are to be searched when "Document Search" is enabled.
1933 @ Some examples:
1934 @ <table border=0 cellpadding=2 align=center>
1935 @ <tr><td>*.wiki,*.html,*.md,*.txt<td style="width: 4x;">
1936 @ <td>Search all wiki, HTML, Markdown, and Text files</tr>
1937 @ <tr><td>doc/*.md,*/README.txt,README.txt<td>
1938 @ <td>Search all Markdown files in the doc/ subfolder and all README.txt
1939 @ files.</tr>
1940 @ <tr><td>*<td><td>Search all checked-in files</tr>
1941 @ <tr><td><i>(blank)</i><td>
1942 @ <td>Search nothing. (Disables document search).</tr>
1943 @ </table>
1944 @ <hr />
1945 entry_attribute("Document Branch", 20, "doc-branch", "db", "trunk", 0);
1946 @ <p>When searching documents, use the versions of the files found at the
1947 @ type of the "Document Branch" branch. Recommended value: "trunk".
1948 @ Document search is disabled if blank.
1949 @ <hr />
1950 onoff_attribute("Search Check-in Comments", "search-ci", "sc", 0, 0);
1951 @ <br />
1952 onoff_attribute("Search Documents", "search-doc", "sd", 0, 0);
1953 @ <br />
1954 onoff_attribute("Search Tickets", "search-tkt", "st", 0, 0);
1955 @ <br />
1956 onoff_attribute("Search Wiki", "search-wiki", "sw", 0, 0);
1957 @ <br />
1958 onoff_attribute("Search Tech Notes", "search-technote", "se", 0, 0);
1959 @ <br />
1960 onoff_attribute("Search Forum", "search-forum", "sf", 0, 0);
1961 @ <hr />
1962 @ <p><input type="submit" name="submit" value="Apply Changes" /></p>
1963 @ <hr />
1964 if( P("fts0") ){
1965 search_drop_index();
1966 }else if( P("fts1") ){
1967 search_drop_index();
1968 search_create_index();
1969 search_fill_index();
1970 search_update_index(search_restrict(SRCH_ALL));
1971 }
1972 if( search_index_exists() ){
1973 @ <p>Currently using an SQLite FTS4 search index. This makes search
1974 @ run faster, especially on large repositories, but takes up space.</p>
1975 onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0);
1976 @ <p><input type="submit" name="fts0" value="Delete The Full-Text Index">
1977 @ <input type="submit" name="fts1" value="Rebuild The Full-Text Index">
1978 style_submenu_element("FTS Index Debugging","%R/test-ftsdocs");
1979 }else{
1980 @ <p>The SQLite FTS4 search index is disabled. All searching will be
1981 @ a full-text scan. This usually works fine, but can be slow for
1982 @ larger repositories.</p>
1983 onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0);
1984 @ <p><input type="submit" name="fts1" value="Create A Full-Text Index">
1985 }
1986 @ </div></form>
1987 style_finish_page();
1988 }
1989
1990 /*
1991 ** A URL Alias originally called zOldName is now zNewName/zValue.
1992 ** Write SQL to make this change into pSql.
1993 **
1994 ** If zNewName or zValue is an empty string, then delete the entry.
1995 **
1996 ** If zOldName is an empty string, create a new entry.
1997 */
1998 static void setup_update_url_alias(
1999 Blob *pSql,
2000 const char *zOldName,
2001 const char *zNewName,
2002 const char *zValue
2003 ){
2004 if( !cgi_csrf_safe(1) ) return;
2005 if( zNewName[0]==0 || zValue[0]==0 ){
2006 if( zOldName[0] ){
2007 blob_append_sql(pSql,
2008 "DELETE FROM config WHERE name='walias:%q';\n",
2009 zOldName);
2010 }
2011 return;
2012 }
2013 if( zOldName[0]==0 ){
2014 blob_append_sql(pSql,
2015 "INSERT INTO config(name,value,mtime) VALUES('walias:%q',%Q,now());\n",
2016 zNewName, zValue);
2017 return;
2018 }
2019 if( strcmp(zOldName, zNewName)!=0 ){
2020 blob_append_sql(pSql,
2021 "UPDATE config SET name='walias:%q', value=%Q, mtime=now()"
2022 " WHERE name='walias:%q';\n",
2023 zNewName, zValue, zOldName);
2024 }else{
2025 blob_append_sql(pSql,
2026 "UPDATE config SET value=%Q, mtime=now()"
2027 " WHERE name='walias:%q' AND value<>%Q;\n",
2028 zValue, zOldName, zValue);
2029 }
2030 }
2031
2032 /*
2033 ** WEBPAGE: waliassetup
2034 **
2035 ** Configure the URL aliases
2036 */
2037 void page_waliassetup(){
2038 Stmt q;
2039 int cnt = 0;
2040 Blob namelist;
2041 login_check_credentials();
2042 if( !g.perm.Admin ){
2043 login_needed(0);
2044 return;
2045 }
2046 style_set_current_feature("setup");
2047 style_header("URL Alias Configuration");
2048 if( P("submit")!=0 ){
2049 Blob token;
2050 Blob sql;
2051 const char *zNewName;
2052 const char *zValue;
2053 char zCnt[10];
2054 login_verify_csrf_secret();
2055 blob_init(&namelist, PD("namelist",""), -1);
2056 blob_init(&sql, 0, 0);
2057 while( blob_token(&namelist, &token) ){
2058 const char *zOldName = blob_str(&token);
2059 sqlite3_snprintf(sizeof(zCnt), zCnt, "n%d", cnt);
2060 zNewName = PD(zCnt, "");
2061 sqlite3_snprintf(sizeof(zCnt), zCnt, "v%d", cnt);
2062 zValue = PD(zCnt, "");
2063 setup_update_url_alias(&sql, zOldName, zNewName, zValue);
2064 cnt++;
2065 blob_reset(&token);
2066 }
2067 sqlite3_snprintf(sizeof(zCnt), zCnt, "n%d", cnt);
2068 zNewName = PD(zCnt,"");
2069 sqlite3_snprintf(sizeof(zCnt), zCnt, "v%d", cnt);
2070 zValue = PD(zCnt,"");
2071 setup_update_url_alias(&sql, "", zNewName, zValue);
2072 db_unprotect(PROTECT_CONFIG);
2073 db_multi_exec("%s", blob_sql_text(&sql));
2074 db_protect_pop();
2075 blob_reset(&sql);
2076 blob_reset(&namelist);
2077 cnt = 0;
2078 }
2079 db_prepare(&q,
2080 "SELECT substr(name,8), value FROM config WHERE name GLOB 'walias:/*'"
2081 " UNION ALL SELECT '', ''"
2082 );
2083 @ <form action="%R/waliassetup" method="post"><div>
2084 login_insert_csrf_secret();
2085 @ <table border=0 cellpadding=5>
2086 @ <tr><th>Alias<th>URI That The Alias Maps Into
2087 blob_init(&namelist, 0, 0);
2088 while( db_step(&q)==SQLITE_ROW ){
2089 const char *zName = db_column_text(&q, 0);
2090 const char *zValue = db_column_text(&q, 1);
2091 @ <tr><td>
2092 @ <input type='text' size='20' value='%h(zName)' name='n%d(cnt)'>
2093 @ </td><td>
2094 @ <input type='text' size='80' value='%h(zValue)' name='v%d(cnt)'>
2095 @ </td></tr>
2096 cnt++;
2097 if( blob_size(&namelist)>0 ) blob_append(&namelist, " ", 1);
2098 blob_append(&namelist, zName, -1);
2099 }
2100 db_finalize(&q);
2101 @ <tr><td>
2102 @ <input type='hidden' name='namelist' value='%h(blob_str(&namelist))'>
2103 @ <input type='submit' name='submit' value="Apply Changes">
2104 @ </td><td></td></tr>
2105 @ </table></form>
2106 @ <hr>
2107 @ <p>When the first term of an incoming URL exactly matches one of
2108 @ the "Aliases" on the left-hand side (LHS) above, the URL is
2109 @ converted into the corresponding form on the right-hand side (RHS).
2110 @ <ul>
2111 @ <li><p>
2112 @ The LHS is compared against only the first term of the incoming URL.
2113 @ All LHS entries in the alias table should therefore begin with a
2114 @ single "/" followed by a single path element.
2115 @ <li><p>
2116 @ The RHS entries in the alias table should begin with a single "/"
2117 @ followed by a path element, and optionally followed by "?" and a
2118 @ list of query parameters.
2119 @ <li><p>
2120 @ Query parameters on the RHS are added to the set of query parameters
2121 @ in the incoming URL.
2122 @ <li><p>
2123 @ If the same query parameter appears in both the incoming URL and
2124 @ on the RHS of the alias, the RHS query parameter value overwrites
2125 @ the value on the incoming URL.
2126 @ <li><p>
2127 @ If a query parameter on the RHS of the alias is of the form "X!"
2128 @ (a name followed by "!") then the X query parameter is removed
2129 @ from the incoming URL if
2130 @ it exists.
2131 @ <li><p>
2132 @ Only a single alias operation occurs. It is not possible to nest aliases.
2133 @ The RHS entries must be built-in webpage names.
2134 @ <li><p>
2135 @ The alias table is only checked if no built-in webpage matches
2136 @ the incoming URL.
2137 @ Hence, it is not possible to override a built-in webpage using aliases.
2138 @ This is by design.
2139 @ </ul>
2140 @
2141 @ <p>To delete an entry from the alias table, change its name or value to an
2142 @ empty string and press "Apply Changes".
2143 @
2144 @ <p>To add a new alias, fill in the name and value in the bottom row
2145 @ of the table above and press "Apply Changes".
2146 style_finish_page();
2147 }
2148