Fossil SCM

fossil-scm / src / style.c
Source Blame History 1852 lines
dbda8d6… drh 1 /*
c19f34c… drh 2 ** Copyright (c) 2006,2007 D. Richard Hipp
dbda8d6… drh 3 **
dbda8d6… drh 4 ** This program is free software; you can redistribute it and/or
c06edd2… drh 5 ** modify it under the terms of the Simplified BSD License (also
c06edd2… drh 6 ** known as the "2-Clause License" or "FreeBSD License".)
c06edd2… drh 7
dbda8d6… drh 8 ** This program is distributed in the hope that it will be useful,
c06edd2… drh 9 ** but without any warranty; without even the implied warranty of
c06edd2… drh 10 ** merchantability or fitness for a particular purpose.
dbda8d6… drh 11 **
dbda8d6… drh 12 ** Author contact information:
dbda8d6… drh 13 ** [email protected]
dbda8d6… drh 14 ** http://www.hwaci.com/drh/
dbda8d6… drh 15 **
dbda8d6… drh 16 *******************************************************************************
dbda8d6… drh 17 **
dbda8d6… drh 18 ** This file contains code to implement the basic web page look and feel.
dbda8d6… drh 19 **
dbda8d6… drh 20 */
b065aff… mistachkin 21 #include "VERSION.h"
dbda8d6… drh 22 #include "config.h"
dbda8d6… drh 23 #include "style.h"
dbda8d6… drh 24
dbda8d6… drh 25 /*
dbda8d6… drh 26 ** Elements of the submenu are collected into the following
c0c0bae… drh 27 ** structure and displayed below the main menu.
dbda8d6… drh 28 **
b8e3dc1… jan.nijtmans 29 ** Populate these structure with calls to
c0c0bae… drh 30 **
c0c0bae… drh 31 ** style_submenu_element()
c0c0bae… drh 32 ** style_submenu_entry()
c0c0bae… drh 33 ** style_submenu_checkbox()
3cb9ba4… andygoth 34 ** style_submenu_binary()
c0c0bae… drh 35 ** style_submenu_multichoice()
3cb9ba4… andygoth 36 ** style_submenu_sql()
c0c0bae… drh 37 **
e6e8ea8… wyoung 38 ** prior to calling style_finish_page(). The style_finish_page() routine
c0c0bae… drh 39 ** will generate the appropriate HTML text just below the main
c0c0bae… drh 40 ** menu.
dbda8d6… drh 41 */
dbda8d6… drh 42 static struct Submenu {
c0c0bae… drh 43 const char *zLabel; /* Button label */
c0c0bae… drh 44 const char *zLink; /* Jump to this link when button is pressed */
dbda8d6… drh 45 } aSubmenu[30];
c0c0bae… drh 46 static int nSubmenu = 0; /* Number of buttons */
c0c0bae… drh 47 static struct SubmenuCtrl {
037e06b… drh 48 const char *zName; /* Form query parameter */
037e06b… drh 49 const char *zLabel; /* Label. Might be NULL for FF_MULTI */
259074d… drh 50 unsigned char eType; /* FF_ENTRY, FF_MULTI, FF_CHECKBOX */
259074d… drh 51 unsigned char eVisible; /* STYLE_NORMAL or STYLE_DISABLED */
037e06b… drh 52 short int iSize; /* Width for FF_ENTRY. Count for FF_MULTI */
037e06b… drh 53 const char *const *azChoice; /* value/display pairs for FF_MULTI */
037e06b… drh 54 const char *zFalse; /* FF_BINARY label when false */
037e06b… drh 55 const char *zJS; /* Javascript to run on toggle */
c0c0bae… drh 56 } aSubmenuCtrl[20];
c0c0bae… drh 57 static int nSubmenuCtrl = 0;
e91d267… drh 58 #define FF_ENTRY 1 /* Text entry box */
e91d267… drh 59 #define FF_MULTI 2 /* Combobox. Multiple choices. */
259074d… drh 60 #define FF_BINARY 3 /* Control for binary query parameter */
259074d… drh 61 #define FF_CHECKBOX 4 /* Check-box */
e91d267… drh 62
e91d267… drh 63 #if INTERFACE
e91d267… drh 64 #define STYLE_NORMAL 0 /* Normal display of control */
e91d267… drh 65 #define STYLE_DISABLED 1 /* Control is disabled */
e91d267… drh 66 #endif /* INTERFACE */
dcc4866… drh 67
dcc4866… drh 68 /*
dcc4866… drh 69 ** Remember that the header has been generated. The footer is omitted
dcc4866… drh 70 ** if an error occurs before the header.
dcc4866… drh 71 */
dcc4866… drh 72 static int headerHasBeenGenerated = 0;
dcc4866… drh 73
dcc4866… drh 74 /*
5a48a9b… drh 75 ** remember, if a sidebox was used
5a48a9b… drh 76 */
5a48a9b… drh 77 static int sideboxUsed = 0;
5a48a9b… drh 78
ff78d6d… drh 79 /*
ff78d6d… drh 80 ** Ad-unit styles.
ff78d6d… drh 81 */
ff78d6d… drh 82 static unsigned adUnitFlags = 0;
ff78d6d… drh 83
09494b0… drh 84 /*
220ed0a… drh 85 ** Submenu disable flag
220ed0a… drh 86 */
220ed0a… drh 87 static int submenuEnable = 1;
220ed0a… drh 88
220ed0a… drh 89 /*
6b645d6… drh 90 ** Flags for various javascript files needed prior to </body>
6b645d6… drh 91 */
6908832… drh 92 static int needHrefJs = 0; /* href.js */
ff747b5… drh 93
ff747b5… drh 94 /*
ff747b5… drh 95 ** Extra JS added to the end of the file.
ff747b5… drh 96 */
ff747b5… drh 97 static Blob blobOnLoad = BLOB_INITIALIZER;
433cde1… drh 98
433cde1… drh 99 /*
99a319b… wyoung 100 ** Generate and return an anchor tag like this:
433cde1… drh 101 **
433cde1… drh 102 ** <a href="URL">
433cde1… drh 103 ** or <a id="ID">
433cde1… drh 104 **
df337eb… drh 105 ** The form of the anchor tag is determined by the g.jsHref
8dd7542… drh 106 ** and g.perm.Hyperlink variables.
8dd7542… drh 107 **
df337eb… drh 108 ** g.perm.Hyperlink g.jsHref Returned anchor format
df337eb… drh 109 ** ---------------- -------- ------------------------
df337eb… drh 110 ** 0 0 (empty string)
df337eb… drh 111 ** 0 1 (empty string)
df337eb… drh 112 ** 1 0 <a href="URL">
df337eb… drh 113 ** 1 1 <a data-href="URL">
8dd7542… drh 114 **
8dd7542… drh 115 ** No anchor tag is generated if g.perm.Hyperlink is false.
df337eb… drh 116 ** The href="URL" form is used if g.jsHref is false.
df337eb… drh 117 ** If g.jsHref is true then the data-href="URL" and
8d4e114… drh 118 ** href="/honeypot" is generated and javascript is added to the footer
8d4e114… drh 119 ** to cause data-href values to be inserted into href
8d4e114… drh 120 ** after the page has loaded. The use of the data-href="URL" form
8dd7542… drh 121 ** instead of href="URL" is a defense against bots.
54085d5… drh 122 **
54085d5… drh 123 ** If the user lacks the Hyperlink (h) property and the "auto-hyperlink"
54085d5… drh 124 ** setting is true, then g.perm.Hyperlink is changed from 0 to 1 and
df337eb… drh 125 ** g.jsHref is set to 1 by login_check_credentials(). Thus
8dd7542… drh 126 ** the g.perm.Hyperlink property will be true even if the user does not
8dd7542… drh 127 ** have the "h" privilege if the "auto-hyperlink" setting is true.
8dd7542… drh 128 **
df337eb… drh 129 ** User has "h" auto-hyperlink g.perm.Hyperlink g.jsHref
8dd7542… drh 130 ** ------------ -------------- ---------------- ---------------------
8dd7542… drh 131 ** 0 0 0 0
8dd7542… drh 132 ** 1 0 1 0
8dd7542… drh 133 ** 0 1 1 1
8dd7542… drh 134 ** 1 1 1 0
8dd7542… drh 135 **
8dd7542… drh 136 ** So, in other words, tracing input configuration to final actions we have:
8dd7542… drh 137 **
8dd7542… drh 138 ** User has "h" auto-hyperlink Returned anchor format
8dd7542… drh 139 ** ------------ -------------- ----------------------
8dd7542… drh 140 ** 0 0 (empty string)
8dd7542… drh 141 ** 1 0 <a href="URL">
8d4e114… drh 142 ** 0 1 <a data-href="URL">
8d4e114… drh 143 ** 1 1 <a href="URL">
54085d5… drh 144 **
8dd7542… drh 145 ** The name of these routines are deliberately kept short so that can be
433cde1… drh 146 ** easily used within @-lines. Example:
433cde1… drh 147 **
433cde1… drh 148 ** @ %z(href("%R/artifact/%s",zUuid))%h(zFN)</a>
433cde1… drh 149 **
433cde1… drh 150 ** Note %z format. The string returned by this function is always
433cde1… drh 151 ** obtained from fossil_malloc() so rendering it with %z will reclaim
433cde1… drh 152 ** that memory space.
433cde1… drh 153 **
8394d2f… drh 154 ** There are three versions of this routine:
8394d2f… drh 155 **
8394d2f… drh 156 ** (1) href() does a plain hyperlink
8394d2f… drh 157 ** (2) xhref() adds extra attribute text
8394d2f… drh 158 ** (3) chref() adds a class name
54085d5… drh 159 **
54085d5… drh 160 ** g.perm.Hyperlink is true if the user has the Hyperlink (h) property.
54085d5… drh 161 ** Most logged in users should have this property, since we can assume
54085d5… drh 162 ** that a logged in user is not a bot. Only "nobody" lacks g.perm.Hyperlink,
54085d5… drh 163 ** typically.
433cde1… drh 164 */
433cde1… drh 165 char *xhref(const char *zExtra, const char *zFormat, ...){
433cde1… drh 166 char *zUrl;
433cde1… drh 167 va_list ap;
b190858… drh 168 if( !g.perm.Hyperlink ) return fossil_strdup("");
8394d2f… drh 169 va_start(ap, zFormat);
8394d2f… drh 170 zUrl = vmprintf(zFormat, ap);
8394d2f… drh 171 va_end(ap);
df337eb… drh 172 if( !g.jsHref ){
774fb77… drh 173 char *zHUrl;
774fb77… drh 174 if( zExtra ){
774fb77… drh 175 zHUrl = mprintf("<a %s href=\"%h\">", zExtra, zUrl);
774fb77… drh 176 }else{
774fb77… drh 177 zHUrl = mprintf("<a href=\"%h\">", zUrl);
774fb77… drh 178 }
8394d2f… drh 179 fossil_free(zUrl);
8394d2f… drh 180 return zHUrl;
8394d2f… drh 181 }
09494b0… drh 182 needHrefJs = 1;
774fb77… drh 183 if( zExtra==0 ){
774fb77… drh 184 return mprintf("<a data-href='%z' href='%R/honeypot'>", zUrl);
774fb77… drh 185 }else{
774fb77… drh 186 return mprintf("<a %s data-href='%z' href='%R/honeypot'>",
774fb77… drh 187 zExtra, zUrl);
774fb77… drh 188 }
8394d2f… drh 189 }
8394d2f… drh 190 char *chref(const char *zExtra, const char *zFormat, ...){
8394d2f… drh 191 char *zUrl;
8394d2f… drh 192 va_list ap;
b190858… drh 193 if( !g.perm.Hyperlink ) return fossil_strdup("");
433cde1… drh 194 va_start(ap, zFormat);
433cde1… drh 195 zUrl = vmprintf(zFormat, ap);
433cde1… drh 196 va_end(ap);
df337eb… drh 197 if( !g.jsHref ){
f54b4bf… drh 198 char *zHUrl = mprintf("<a class=\"%s\" href=\"%h\">", zExtra, zUrl);
d5c4684… drh 199 fossil_free(zUrl);
d5c4684… drh 200 return zHUrl;
d5c4684… drh 201 }
09494b0… drh 202 needHrefJs = 1;
8394d2f… drh 203 return mprintf("<a class='%s' data-href='%z' href='%R/honeypot'>",
8394d2f… drh 204 zExtra, zUrl);
433cde1… drh 205 }
433cde1… drh 206 char *href(const char *zFormat, ...){
433cde1… drh 207 char *zUrl;
433cde1… drh 208 va_list ap;
b190858… drh 209 if( !g.perm.Hyperlink ) return fossil_strdup("");
433cde1… drh 210 va_start(ap, zFormat);
433cde1… drh 211 zUrl = vmprintf(zFormat, ap);
433cde1… drh 212 va_end(ap);
df337eb… drh 213 if( !g.jsHref ){
d5c4684… drh 214 char *zHUrl = mprintf("<a href=\"%h\">", zUrl);
d5c4684… drh 215 fossil_free(zUrl);
d5c4684… drh 216 return zHUrl;
d5c4684… drh 217 }
09494b0… drh 218 needHrefJs = 1;
8394d2f… drh 219 return mprintf("<a data-href='%s' href='%R/honeypot'>",
8394d2f… drh 220 zUrl);
433cde1… drh 221 }
433cde1… drh 222
433cde1… drh 223 /*
8dd7542… drh 224 ** Generate <form method="post" action=ARG>. The ARG value is determined
8dd7542… drh 225 ** by the arguments.
8dd7542… drh 226 **
8dd7542… drh 227 ** As a defense against robots, the action=ARG might instead by data-action=ARG
8dd7542… drh 228 ** and javascript (href.js) added to the page so that the data-action= is
8dd7542… drh 229 ** changed into action= after the page loads. Whether or not this happens
8dd7542… drh 230 ** depends on if the user has the "h" privilege and whether or not the
e2bdc10… danield 231 ** auto-hyperlink setting is on. These settings determine the values of
df337eb… drh 232 ** variables g.perm.Hyperlink and g.jsHref.
8dd7542… drh 233 **
df337eb… drh 234 ** User has "h" auto-hyperlink g.perm.Hyperlink g.jsHref
df337eb… drh 235 ** ------------ -------------- ---------------- --------
df337eb… drh 236 ** 1: 0 0 0 0
df337eb… drh 237 ** 2: 1 0 1 0
df337eb… drh 238 ** 3: 0 1 1 1
df337eb… drh 239 ** 4: 1 1 1 0
8dd7542… drh 240 **
8dd7542… drh 241 ** The data-action=ARG form is used for cases 1 and 3. In case 1, the href.js
8dd7542… drh 242 ** javascript is omitted and so the form is effectively disabled.
433cde1… drh 243 */
dfa3579… drh 244 void form_begin(const char *zOtherArgs, const char *zAction, ...){
dfa3579… drh 245 char *zLink;
dfa3579… drh 246 va_list ap;
dfa3579… drh 247 if( zOtherArgs==0 ) zOtherArgs = "";
dfa3579… drh 248 va_start(ap, zAction);
dfa3579… drh 249 zLink = vmprintf(zAction, ap);
dfa3579… drh 250 va_end(ap);
8dd7542… drh 251 if( g.perm.Hyperlink ){
dfa3579… drh 252 @ <form method="POST" action="%z(zLink)" %s(zOtherArgs)>
dfa3579… drh 253 }else{
09494b0… drh 254 needHrefJs = 1;
8394d2f… drh 255 @ <form method="POST" data-action='%s(zLink)' action='%R/login' \
8394d2f… drh 256 @ %s(zOtherArgs)>
8394d2f… drh 257 }
920ace1… drh 258 login_insert_csrf_secret();
433cde1… drh 259 }
433cde1… drh 260
433cde1… drh 261 /*
dbda8d6… drh 262 ** Add a new element to the submenu
dbda8d6… drh 263 */
dbda8d6… drh 264 void style_submenu_element(
dbda8d6… drh 265 const char *zLabel,
d0305b3… aku 266 const char *zLink,
d0305b3… aku 267 ...
dbda8d6… drh 268 ){
d0305b3… aku 269 va_list ap;
3cb9ba4… andygoth 270 assert( nSubmenu < count(aSubmenu) );
dbda8d6… drh 271 aSubmenu[nSubmenu].zLabel = zLabel;
d0305b3… aku 272 va_start(ap, zLink);
d0305b3… aku 273 aSubmenu[nSubmenu].zLink = vmprintf(zLink, ap);
d0305b3… aku 274 va_end(ap);
dbda8d6… drh 275 nSubmenu++;
dbda8d6… drh 276 }
c0c0bae… drh 277 void style_submenu_entry(
c0c0bae… drh 278 const char *zName, /* Query parameter name */
c0c0bae… drh 279 const char *zLabel, /* Label before the entry box */
f76cfac… drh 280 int iSize, /* Size of the entry box */
e91d267… drh 281 int eVisible /* Visible or disabled */
c0c0bae… drh 282 ){
3cb9ba4… andygoth 283 assert( nSubmenuCtrl < count(aSubmenuCtrl) );
c0c0bae… drh 284 aSubmenuCtrl[nSubmenuCtrl].zName = zName;
c0c0bae… drh 285 aSubmenuCtrl[nSubmenuCtrl].zLabel = zLabel;
c0c0bae… drh 286 aSubmenuCtrl[nSubmenuCtrl].iSize = iSize;
e91d267… drh 287 aSubmenuCtrl[nSubmenuCtrl].eVisible = eVisible;
c0c0bae… drh 288 aSubmenuCtrl[nSubmenuCtrl].eType = FF_ENTRY;
3cb9ba4… andygoth 289 nSubmenuCtrl++;
3cb9ba4… andygoth 290 }
3cb9ba4… andygoth 291 void style_submenu_checkbox(
3cb9ba4… andygoth 292 const char *zName, /* Query parameter name */
3cb9ba4… andygoth 293 const char *zLabel, /* Label to display after the checkbox */
e91d267… drh 294 int eVisible, /* Visible or disabled */
037e06b… drh 295 const char *zJS /* Optional javascript to run on toggle */
3cb9ba4… andygoth 296 ){
3cb9ba4… andygoth 297 assert( nSubmenuCtrl < count(aSubmenuCtrl) );
3cb9ba4… andygoth 298 aSubmenuCtrl[nSubmenuCtrl].zName = zName;
3cb9ba4… andygoth 299 aSubmenuCtrl[nSubmenuCtrl].zLabel = zLabel;
e91d267… drh 300 aSubmenuCtrl[nSubmenuCtrl].eVisible = eVisible;
037e06b… drh 301 aSubmenuCtrl[nSubmenuCtrl].zJS = zJS;
3cb9ba4… andygoth 302 aSubmenuCtrl[nSubmenuCtrl].eType = FF_CHECKBOX;
c0c0bae… drh 303 nSubmenuCtrl++;
c0c0bae… drh 304 }
c0c0bae… drh 305 void style_submenu_binary(
c0c0bae… drh 306 const char *zName, /* Query parameter name */
c0c0bae… drh 307 const char *zTrue, /* Label to show when parameter is true */
f76cfac… drh 308 const char *zFalse, /* Label to show when the parameter is false */
e91d267… drh 309 int eVisible /* Visible or disabled */
c0c0bae… drh 310 ){
3cb9ba4… andygoth 311 assert( nSubmenuCtrl < count(aSubmenuCtrl) );
c0c0bae… drh 312 aSubmenuCtrl[nSubmenuCtrl].zName = zName;
c0c0bae… drh 313 aSubmenuCtrl[nSubmenuCtrl].zLabel = zTrue;
c0c0bae… drh 314 aSubmenuCtrl[nSubmenuCtrl].zFalse = zFalse;
e91d267… drh 315 aSubmenuCtrl[nSubmenuCtrl].eVisible = eVisible;
c0c0bae… drh 316 aSubmenuCtrl[nSubmenuCtrl].eType = FF_BINARY;
c0c0bae… drh 317 nSubmenuCtrl++;
c0c0bae… drh 318 }
c0c0bae… drh 319 void style_submenu_multichoice(
e91d267… drh 320 const char *zName, /* Query parameter name */
e91d267… drh 321 int nChoice, /* Number of options */
e91d267… drh 322 const char *const *azChoice, /* value/display pairs. 2*nChoice entries */
e91d267… drh 323 int eVisible /* Visible or disabled */
c0c0bae… drh 324 ){
3cb9ba4… andygoth 325 assert( nSubmenuCtrl < count(aSubmenuCtrl) );
c0c0bae… drh 326 aSubmenuCtrl[nSubmenuCtrl].zName = zName;
c0c0bae… drh 327 aSubmenuCtrl[nSubmenuCtrl].iSize = nChoice;
c0c0bae… drh 328 aSubmenuCtrl[nSubmenuCtrl].azChoice = azChoice;
e91d267… drh 329 aSubmenuCtrl[nSubmenuCtrl].eVisible = eVisible;
c0c0bae… drh 330 aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI;
c0c0bae… drh 331 nSubmenuCtrl++;
16ab6ee… drh 332 }
16ab6ee… drh 333 void style_submenu_sql(
16ab6ee… drh 334 const char *zName, /* Query parameter name */
16ab6ee… drh 335 const char *zLabel, /* Label on the control */
16ab6ee… drh 336 const char *zFormat, /* Format string for SQL command for choices */
16ab6ee… drh 337 ... /* Arguments to the format string */
16ab6ee… drh 338 ){
16ab6ee… drh 339 Stmt q;
16ab6ee… drh 340 int n = 0;
16ab6ee… drh 341 int nAlloc = 0;
16ab6ee… drh 342 char **az = 0;
16ab6ee… drh 343 va_list ap;
16ab6ee… drh 344
16ab6ee… drh 345 va_start(ap, zFormat);
16ab6ee… drh 346 db_vprepare(&q, 0, zFormat, ap);
16ab6ee… drh 347 va_end(ap);
16ab6ee… drh 348 while( SQLITE_ROW==db_step(&q) ){
16ab6ee… drh 349 if( n+2>=nAlloc ){
16ab6ee… drh 350 nAlloc += nAlloc + 20;
16ab6ee… drh 351 az = fossil_realloc(az, sizeof(char*)*nAlloc);
16ab6ee… drh 352 }
16ab6ee… drh 353 az[n++] = fossil_strdup(db_column_text(&q,0));
16ab6ee… drh 354 az[n++] = fossil_strdup(db_column_text(&q,1));
16ab6ee… drh 355 }
16ab6ee… drh 356 db_finalize(&q);
16ab6ee… drh 357 if( n>0 ){
16ab6ee… drh 358 aSubmenuCtrl[nSubmenuCtrl].zName = zName;
16ab6ee… drh 359 aSubmenuCtrl[nSubmenuCtrl].zLabel = zLabel;
16ab6ee… drh 360 aSubmenuCtrl[nSubmenuCtrl].iSize = n/2;
b43681d… jan.nijtmans 361 aSubmenuCtrl[nSubmenuCtrl].azChoice = (const char *const *)az;
e91d267… drh 362 aSubmenuCtrl[nSubmenuCtrl].eVisible = STYLE_NORMAL;
16ab6ee… drh 363 aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI;
16ab6ee… drh 364 nSubmenuCtrl++;
16ab6ee… drh 365 }
e91d267… drh 366 }
e91d267… drh 367
220ed0a… drh 368 /*
220ed0a… drh 369 ** Disable or enable the submenu
220ed0a… drh 370 */
220ed0a… drh 371 void style_submenu_enable(int onOff){
220ed0a… drh 372 submenuEnable = onOff;
220ed0a… drh 373 }
220ed0a… drh 374
dbda8d6… drh 375
dbda8d6… drh 376 /*
dbda8d6… drh 377 ** Compare two submenu items for sorting purposes
dbda8d6… drh 378 */
dbda8d6… drh 379 static int submenuCompare(const void *a, const void *b){
dbda8d6… drh 380 const struct Submenu *A = (const struct Submenu*)a;
d0305b3… aku 381 const struct Submenu *B = (const struct Submenu*)b;
31c52c7… drh 382 return fossil_strcmp(A->zLabel, B->zLabel);
31c52c7… drh 383 }
31c52c7… drh 384
064c1c9… stephan 385 /* Use this for the $current_page variable if it is not NULL. If it
064c1c9… stephan 386 ** is NULL then use g.zPath.
a116d97… drh 387 */
a116d97… drh 388 static char *local_zCurrentPage = 0;
a116d97… drh 389
a116d97… drh 390 /*
a116d97… drh 391 ** Set the desired $current_page to something other than g.zPath
a116d97… drh 392 */
a116d97… drh 393 void style_set_current_page(const char *zFormat, ...){
a116d97… drh 394 fossil_free(local_zCurrentPage);
a116d97… drh 395 if( zFormat==0 ){
a116d97… drh 396 local_zCurrentPage = 0;
a116d97… drh 397 }else{
a116d97… drh 398 va_list ap;
a116d97… drh 399 va_start(ap, zFormat);
a116d97… drh 400 local_zCurrentPage = vmprintf(zFormat, ap);
a116d97… drh 401 va_end(ap);
a116d97… drh 402 }
a116d97… drh 403 }
a116d97… drh 404
a116d97… drh 405 /*
6acd87f… drh 406 ** Create a TH1 variable containing the URL for the stylesheet.
6acd87f… drh 407 **
6acd87f… drh 408 ** The name of the new variable will be "stylesheet_url".
6acd87f… drh 409 **
6acd87f… drh 410 ** The value will be a URL for accessing the appropriate stylesheet.
6acd87f… drh 411 ** This URL will include query parameters such as "id=" and "once&skin="
6acd87f… drh 412 ** to cause the correct stylesheet to be loaded after a skin change
6acd87f… drh 413 ** or after a change to the stylesheet.
daff9d2… joel 414 */
6acd87f… drh 415 static void stylesheet_url_var(void){
6acd87f… drh 416 char *zBuiltin; /* Auxiliary page-specific CSS page */
6acd87f… drh 417 Blob url; /* The URL */
fc85382… stephan 418 const char * zPage = local_zCurrentPage ? local_zCurrentPage : g.zPath;
6acd87f… drh 419
6acd87f… drh 420 /* Initialize the URL to its baseline */
6acd87f… drh 421 url = empty_blob;
6acd87f… drh 422 blob_appendf(&url, "%R/style.css");
6acd87f… drh 423
6acd87f… drh 424 /* If page-specific CSS exists for the current page, then append
6acd87f… drh 425 ** the pathname for the page-specific CSS. The default CSS is
6acd87f… drh 426 **
6acd87f… drh 427 ** /style.css
6acd87f… drh 428 **
6acd87f… drh 429 ** But for the "/wikiedit" page (to name but one example), we
6acd87f… drh 430 ** append a path as follows:
6acd87f… drh 431 **
6acd87f… drh 432 ** /style.css/wikiedit
6acd87f… drh 433 **
6acd87f… drh 434 ** The /style.css page (implemented below) will detect this extra "wikiedit"
6acd87f… drh 435 ** path information and include the page-specific CSS along with the
6acd87f… drh 436 ** default CSS when it delivers the page.
6acd87f… drh 437 */
fc85382… stephan 438 zBuiltin = mprintf("style.%s.css", zPage);
6acd87f… drh 439 if( builtin_file(zBuiltin,0)!=0 ){
fc85382… stephan 440 blob_appendf(&url, "/%s", zPage);
6acd87f… drh 441 }
6acd87f… drh 442 fossil_free(zBuiltin);
6acd87f… drh 443
6acd87f… drh 444 /* Add query parameters that will change whenever the skin changes
6acd87f… drh 445 ** or after any updates to the CSS files
6acd87f… drh 446 */
6acd87f… drh 447 blob_appendf(&url, "?id=%x", skin_id("css"));
6acd87f… drh 448 if( P("once")!=0 && P("skin")!=0 ){
6acd87f… drh 449 blob_appendf(&url, "&skin=%s&once", skin_in_use());
6acd87f… drh 450 }
6acd87f… drh 451
275da70… danield 452 /* Generate the CSS URL variable */
6acd87f… drh 453 Th_Store("stylesheet_url", blob_str(&url));
6acd87f… drh 454 blob_reset(&url);
daff9d2… joel 455 }
daff9d2… joel 456
daff9d2… joel 457 /*
6acd87f… drh 458 ** Create a TH1 variable containing the URL for the specified image.
daff9d2… joel 459 ** The resulting variable name will be of the form $[zImageName]_image_url.
6acd87f… drh 460 ** The value will be a URL that includes an id= query parameter that
6acd87f… drh 461 ** changes if the underlying resource changes or if a different skin
6acd87f… drh 462 ** is selected.
daff9d2… joel 463 */
daff9d2… joel 464 static void image_url_var(const char *zImageName){
6acd87f… drh 465 char *zVarName; /* Name of the new TH1 variable */
6acd87f… drh 466 char *zResource; /* Name of CONFIG entry holding content */
6acd87f… drh 467 char *zUrl; /* The URL */
6acd87f… drh 468
6acd87f… drh 469 zResource = mprintf("%s-image", zImageName);
6acd87f… drh 470 zUrl = mprintf("%R/%s?id=%x", zImageName, skin_id(zResource));
6acd87f… drh 471 free(zResource);
275da70… danield 472 zVarName = mprintf("%s_image_url", zImageName);
6acd87f… drh 473 Th_Store(zVarName, zUrl);
6acd87f… drh 474 free(zVarName);
6acd87f… drh 475 free(zUrl);
759fbda… drh 476 }
759fbda… drh 477
759fbda… drh 478 /*
759fbda… drh 479 ** Output TEXT with a click-to-copy button next to it. Loads the copybtn.js
759fbda… drh 480 ** Javascript module, and generates HTML elements with the following IDs:
759fbda… drh 481 **
759fbda… drh 482 ** TARGETID: The <span> wrapper around TEXT.
63712b6… florian 483 ** copy-TARGETID: The <button> for the copy button.
759fbda… drh 484 **
759fbda… drh 485 ** If the FLIPPED argument is non-zero, the copy button is displayed after TEXT.
759fbda… drh 486 **
759fbda… drh 487 ** The COPYLENGTH argument defines the length of the substring of TEXT copied to
759fbda… drh 488 ** clipboard:
759fbda… drh 489 **
759fbda… drh 490 ** <= 0: No limit (default if the argument is omitted).
759fbda… drh 491 ** >= 3: Truncate TEXT after COPYLENGTH (single-byte) characters.
759fbda… drh 492 ** 1: Use the "hash-digits" setting as the limit.
759fbda… drh 493 ** 2: Use the length appropriate for URLs as the limit (defined at
759fbda… drh 494 ** compile-time by FOSSIL_HASH_DIGITS_URL, defaults to 16).
759fbda… drh 495 */
759fbda… drh 496 char *style_copy_button(
759fbda… drh 497 int bOutputCGI, /* Don't return result, but send to cgi_printf(). */
759fbda… drh 498 const char *zTargetId, /* The TARGETID argument. */
759fbda… drh 499 int bFlipped, /* The FLIPPED argument. */
759fbda… drh 500 int cchLength, /* The COPYLENGTH argument. */
759fbda… drh 501 const char *zTextFmt, /* Formatting of the TEXT argument (htmlized). */
759fbda… drh 502 ... /* Formatting parameters of the TEXT argument. */
759fbda… drh 503 ){
759fbda… drh 504 va_list ap;
759fbda… drh 505 char *zText;
759fbda… drh 506 char *zResult = 0;
759fbda… drh 507 va_start(ap,zTextFmt);
759fbda… drh 508 zText = vmprintf(zTextFmt/*works-like:?*/,ap);
759fbda… drh 509 va_end(ap);
759fbda… drh 510 if( cchLength==1 ) cchLength = hash_digits(0);
759fbda… drh 511 else if( cchLength==2 ) cchLength = hash_digits(1);
759fbda… drh 512 if( !bFlipped ){
759fbda… drh 513 const char *zBtnFmt =
759fbda… drh 514 "<span class=\"nobr\">"
63712b6… florian 515 "<button "
63712b6… florian 516 "class=\"copy-button\" "
63712b6… florian 517 "id=\"copy-%h\" "
63712b6… florian 518 "data-copytarget=\"%h\" "
63712b6… florian 519 "data-copylength=\"%d\">"
63712b6… florian 520 "<span>"
63712b6… florian 521 "</span>"
63712b6… florian 522 "</button>"
759fbda… drh 523 "<span id=\"%h\">"
63712b6… florian 524 "%s"
759fbda… drh 525 "</span>"
759fbda… drh 526 "</span>";
759fbda… drh 527 if( bOutputCGI ){
759fbda… drh 528 cgi_printf(
759fbda… drh 529 zBtnFmt/*works-like:"%h%h%d%h%s"*/,
759fbda… drh 530 zTargetId,zTargetId,cchLength,zTargetId,zText);
759fbda… drh 531 }else{
759fbda… drh 532 zResult = mprintf(
759fbda… drh 533 zBtnFmt/*works-like:"%h%h%d%h%s"*/,
759fbda… drh 534 zTargetId,zTargetId,cchLength,zTargetId,zText);
759fbda… drh 535 }
759fbda… drh 536 }else{
759fbda… drh 537 const char *zBtnFmt =
759fbda… drh 538 "<span class=\"nobr\">"
759fbda… drh 539 "<span id=\"%h\">"
63712b6… florian 540 "%s"
63712b6… florian 541 "</span>"
63712b6… florian 542 "<button "
63712b6… florian 543 "class=\"copy-button copy-button-flipped\" "
63712b6… florian 544 "id=\"copy-%h\" "
63712b6… florian 545 "data-copytarget=\"%h\" "
63712b6… florian 546 "data-copylength=\"%d\">"
63712b6… florian 547 "<span>"
63712b6… florian 548 "</span>"
63712b6… florian 549 "</button>"
759fbda… drh 550 "</span>";
759fbda… drh 551 if( bOutputCGI ){
759fbda… drh 552 cgi_printf(
759fbda… drh 553 zBtnFmt/*works-like:"%h%s%h%h%d"*/,
759fbda… drh 554 zTargetId,zText,zTargetId,zTargetId,cchLength);
759fbda… drh 555 }else{
759fbda… drh 556 zResult = mprintf(
759fbda… drh 557 zBtnFmt/*works-like:"%h%s%h%h%d"*/,
759fbda… drh 558 zTargetId,zText,zTargetId,zTargetId,cchLength);
759fbda… drh 559 }
759fbda… drh 560 }
759fbda… drh 561 free(zText);
036a9d5… drh 562 builtin_request_js("copybtn.js");
759fbda… drh 563 return zResult;
ff747b5… drh 564 }
ff747b5… drh 565
ff747b5… drh 566 /*
ff747b5… drh 567 ** Return a random nonce that is stored in static space. For a particular
ff747b5… drh 568 ** run, the same nonce is always returned.
ff747b5… drh 569 */
ff747b5… drh 570 char *style_nonce(void){
ff747b5… drh 571 static char zNonce[52];
ff747b5… drh 572 if( zNonce[0]==0 ){
ff747b5… drh 573 unsigned char zSeed[24];
ff747b5… drh 574 sqlite3_randomness(24, zSeed);
ff747b5… drh 575 encode16(zSeed,(unsigned char*)zNonce,24);
ff747b5… drh 576 }
ff747b5… drh 577 return zNonce;
ff747b5… drh 578 }
ff747b5… drh 579
ff747b5… drh 580 /*
14c81d9… drh 581 ** Return the default Content Security Policy (CSP) string.
14c81d9… drh 582 ** If the toHeader argument is true, then also add the
14c81d9… drh 583 ** CSP to the HTTP reply header.
14c81d9… drh 584 **
14c81d9… drh 585 ** The CSP comes from the "default-csp" setting if it exists and
14c81d9… drh 586 ** is non-empty. If that setting is an empty string, then the following
14c81d9… drh 587 ** default is used instead:
14c81d9… drh 588 **
14c81d9… drh 589 ** default-src 'self' data:;
14c81d9… drh 590 ** script-src 'self' 'nonce-$nonce';
14c81d9… drh 591 ** style-src 'self' 'unsafe-inline';
c184d64… drh 592 ** img-src * data:;
14c81d9… drh 593 **
14c81d9… drh 594 ** The text '$nonce' is replaced by style_nonce() if and whereever it
14c81d9… drh 595 ** occurs in the input string.
14c81d9… drh 596 **
14c81d9… drh 597 ** The string returned is obtained from fossil_malloc() and
14c81d9… drh 598 ** should be released by the caller.
14c81d9… drh 599 */
14c81d9… drh 600 char *style_csp(int toHeader){
275da70… danield 601 static const char zBackupCSP[] =
14c81d9… drh 602 "default-src 'self' data:; "
14c81d9… drh 603 "script-src 'self' 'nonce-$nonce'; "
025a007… drh 604 "style-src 'self' 'unsafe-inline'; "
c184d64… drh 605 "img-src * data:";
02961b8… drh 606 const char *zFormat;
14c81d9… drh 607 Blob csp;
14c81d9… drh 608 char *zNonce;
14c81d9… drh 609 char *zCsp;
e0f2283… drh 610 int i;
1f6ae1e… drh 611 zFormat = db_get("default-csp",0);
6b5606d… drh 612 if( zFormat==0 || zFormat[0]==0 ){
14c81d9… drh 613 zFormat = zBackupCSP;
14c81d9… drh 614 }
14c81d9… drh 615 blob_init(&csp, 0, 0);
14c81d9… drh 616 while( zFormat[0] && (zNonce = strstr(zFormat,"$nonce"))!=0 ){
14c81d9… drh 617 blob_append(&csp, zFormat, (int)(zNonce - zFormat));
14c81d9… drh 618 blob_append(&csp, style_nonce(), -1);
14c81d9… drh 619 zFormat = zNonce + 6;
14c81d9… drh 620 }
14c81d9… drh 621 blob_append(&csp, zFormat, -1);
14c81d9… drh 622 zCsp = blob_str(&csp);
e0f2283… drh 623 /* No whitespace other than actual space characters allowed in the CSP
e0f2283… drh 624 ** string. See https://fossil-scm.org/forum/forumpost/d29e3af43c */
e0f2283… drh 625 for(i=0; zCsp[i]; i++){ if( fossil_isspace(zCsp[i]) ) zCsp[i] = ' '; }
14c81d9… drh 626 if( toHeader ){
14c81d9… drh 627 cgi_printf_header("Content-Security-Policy: %s\r\n", zCsp);
14c81d9… drh 628 }
14c81d9… drh 629 return zCsp;
14c81d9… drh 630 }
14c81d9… drh 631
14c81d9… drh 632 /*
f1bb72e… drh 633 ** Default HTML page header text through <body>. If the repository-specific
f1bb72e… drh 634 ** header template lacks a <body> tag, then all of the following is
f1bb72e… drh 635 ** prepended.
f1bb72e… drh 636 */
275da70… danield 637 static const char zDfltHeader[] =
f1bb72e… drh 638 @ <html>
f1bb72e… drh 639 @ <head>
1997b71… drh 640 @ <meta charset="UTF-8">
f5482a0… wyoung 641 @ <base href="$baseurl/$current_page">
f5482a0… wyoung 642 @ <meta http-equiv="Content-Security-Policy" content="$default_csp">
9131af2… drh 643 @ <meta name="viewport" content="width=device-width, initial-scale=1.0">
f1bb72e… drh 644 @ <title>$<project_name>: $<title></title>
8394d2f… drh 645 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" \
f5482a0… wyoung 646 @ href="$home/timeline.rss">
f5482a0… wyoung 647 @ <link rel="stylesheet" href="$stylesheet_url" type="text/css">
f1bb72e… drh 648 @ </head>
b05a6c6… george 649 @ <body class="$current_feature rpage-$requested_page cpage-$canonical_page">
c1cb688… mistachkin 650 ;
c1cb688… mistachkin 651
c1cb688… mistachkin 652 /*
c1cb688… mistachkin 653 ** Returns the default page header.
c1cb688… mistachkin 654 */
c1cb688… mistachkin 655 const char *get_default_header(){
c1cb688… mistachkin 656 return zDfltHeader;
5f22712… drh 657 }
5f22712… drh 658
5f22712… drh 659 /*
5f22712… drh 660 ** The default TCL list that defines the main menu.
5f22712… drh 661 */
275da70… danield 662 static const char zDfltMainMenu[] =
5f22712… drh 663 @ Home /home * {}
5f22712… drh 664 @ Timeline /timeline {o r j} {}
5f22712… drh 665 @ Files /dir?ci=tip oh desktoponly
5f22712… drh 666 @ Branches /brlist o wideonly
5f22712… drh 667 @ Tags /taglist o wideonly
5f22712… drh 668 @ Forum /forum {@2 3 4 5 6} wideonly
5f22712… drh 669 @ Chat /chat C wideonly
5f22712… drh 670 @ Tickets /ticket r wideonly
5f22712… drh 671 @ Wiki /wiki j wideonly
7260ff2… wyoung 672 @ Admin /setup {a s} desktoponly
d7e4b48… drh 673 @ Logout /logout L wideonly
d7e4b48… drh 674 @ Login /login !L wideonly
5f22712… drh 675 ;
5f22712… drh 676
5f22712… drh 677 /*
5f22712… drh 678 ** Return the default menu
5f22712… drh 679 */
5f22712… drh 680 const char *style_default_mainmenu(void){
5f22712… drh 681 return zDfltMainMenu;
112c713… drh 682 }
112c713… drh 683
112c713… drh 684 /*
112c713… drh 685 ** Given a URL path, extract the first element as a "feature" name,
112c713… drh 686 ** used as the <body class="FEATURE"> value by default, though
112c713… drh 687 ** later-running code may override this, typically to group multiple
112c713… drh 688 ** Fossil UI URLs into a single "feature" so you can have per-feature
112c713… drh 689 ** CSS rules.
112c713… drh 690 **
112c713… drh 691 ** For example, "body.forum div.markdown blockquote" targets only
112c713… drh 692 ** block quotes made in forum posts, leaving other Markdown quotes
112c713… drh 693 ** alone. Because feature class "forum" groups /forummain, /forumpost,
112c713… drh 694 ** and /forume2, it works across all renderings of Markdown to HTML
112c713… drh 695 ** within the Fossil forum feature.
112c713… drh 696 */
112c713… drh 697 static const char* feature_from_page_path(const char *zPath){
112c713… drh 698 const char* zSlash = strchr(zPath, '/');
112c713… drh 699 if (zSlash) {
112c713… drh 700 return fossil_strndup(zPath, zSlash - zPath);
112c713… drh 701 } else {
112c713… drh 702 return zPath;
112c713… drh 703 }
112c713… drh 704 }
112c713… drh 705
112c713… drh 706 /*
112c713… drh 707 ** Override the value of the TH1 variable current_feature, its default
112c713… drh 708 ** set by feature_from_page_path(). We do not call this from
112c713… drh 709 ** style_init_th1_vars() because that uses Th_MaybeStore() instead to
112c713… drh 710 ** allow webpage implementations to call this before style_header()
112c713… drh 711 ** to override that "maybe" default with something better.
112c713… drh 712 */
112c713… drh 713 void style_set_current_feature(const char* zFeature){
112c713… drh 714 Th_Store("current_feature", zFeature);
112c713… drh 715 }
112c713… drh 716
112c713… drh 717 /*
73ca280… drh 718 ** Returns the current mainmenu value from either the --mainmenu flag
73ca280… drh 719 ** (handled by the server/ui/cgi commands), the "mainmenu" config
73ca280… drh 720 ** setting, or style_default_mainmenu(), in that order, returning the
73ca280… drh 721 ** first of those which is defined.
73ca280… drh 722 */
1f6ae1e… drh 723 const char *style_get_mainmenu(){
73ca280… drh 724 static const char *zMenu = 0;
73ca280… drh 725 if(!zMenu){
73ca280… drh 726 if(g.zMainMenuFile){
73ca280… drh 727 Blob b = empty_blob;
73ca280… drh 728 blob_read_from_file(&b, g.zMainMenuFile, ExtFILE);
73ca280… drh 729 zMenu = blob_str(&b);
73ca280… drh 730 }else{
73ca280… drh 731 zMenu = db_get("mainmenu", style_default_mainmenu());
73ca280… drh 732 }
73ca280… drh 733 }
73ca280… drh 734 return zMenu;
73ca280… drh 735 }
73ca280… drh 736
73ca280… drh 737 /*
9c88799… drh 738 ** Initialize all the default TH1 variables
9c88799… drh 739 */
9c88799… drh 740 static void style_init_th1_vars(const char *zTitle){
8a65cd1… mistachkin 741 const char *zNonce = style_nonce();
a44e3c7… george 742 char *zDfltCsp;
14c81d9… drh 743
14c81d9… drh 744 zDfltCsp = style_csp(1);
8a65cd1… mistachkin 745 /*
8a65cd1… mistachkin 746 ** Do not overwrite the TH1 variable "default_csp" if it exists, as this
8a65cd1… mistachkin 747 ** allows it to be properly overridden via the TH1 setup script (i.e. it
8a65cd1… mistachkin 748 ** is evaluated before the header is rendered).
8a65cd1… mistachkin 749 */
8a65cd1… mistachkin 750 Th_MaybeStore("default_csp", zDfltCsp);
14c81d9… drh 751 fossil_free(zDfltCsp);
8a65cd1… mistachkin 752 Th_Store("nonce", zNonce);
2116238… drh 753 Th_StoreUnsafe("project_name",
2116238… drh 754 db_get("project-name","Unnamed Fossil Project"));
2116238… drh 755 Th_StoreUnsafe("project_description", db_get("project-description",""));
5ea6e15… drh 756 if( zTitle ) Th_Store("title", html_lookalike(zTitle,-1));
9c88799… drh 757 Th_Store("baseurl", g.zBaseURL);
4aba9ea… drh 758 Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
9c88799… drh 759 Th_Store("home", g.zTop);
9c88799… drh 760 Th_Store("index_page", db_get("index-page","/home"));
9c88799… drh 761 if( local_zCurrentPage==0 ) style_set_current_page("%T", g.zPath);
9c88799… drh 762 Th_Store("current_page", local_zCurrentPage);
a44e3c7… george 763 if( g.zPath ){ /* store the first segment of a path; */
a44e3c7… george 764 char *pSlash = strchr(g.zPath,'/');
a44e3c7… george 765 if( pSlash ) *pSlash = 0; /* make a temporary cut if necessary */
c68fa2e… drh 766 Th_Store("requested_page", escape_quotes(g.zPath));
a44e3c7… george 767 if( pSlash ) *pSlash = '/';
a44e3c7… george 768 }else{
a44e3c7… george 769 Th_Store("requested_page", "");
c68fa2e… drh 770 }
b05a6c6… george 771 Th_Store("canonical_page", escape_quotes(g.zPhase+1));
9c88799… drh 772 Th_Store("csrf_token", g.zCsrfToken);
9c88799… drh 773 Th_Store("release_version", RELEASE_VERSION);
9c88799… drh 774 Th_Store("manifest_version", MANIFEST_VERSION);
9c88799… drh 775 Th_Store("manifest_date", MANIFEST_DATE);
9c88799… drh 776 Th_Store("compiler_name", COMPILER_NAME);
73ca280… drh 777 Th_Store("mainmenu", style_get_mainmenu());
6acd87f… drh 778 stylesheet_url_var();
9c88799… drh 779 image_url_var("logo");
9c88799… drh 780 image_url_var("background");
9c88799… drh 781 if( !login_is_nobody() ){
5ea6e15… drh 782 Th_Store("login", html_lookalike(g.zLogin,-1));
112c713… drh 783 }
112c713… drh 784 Th_MaybeStore("current_feature", feature_from_page_path(local_zCurrentPage) );
3990518… george 785 if( g.ftntsIssues[0] || g.ftntsIssues[1] ||
3990518… george 786 g.ftntsIssues[2] || g.ftntsIssues[3] ){
3990518… george 787 char buf[80];
275da70… danield 788 sqlite3_snprintf(sizeof(buf), buf, "%i %i %i %i", g.ftntsIssues[0],
275da70… danield 789 g.ftntsIssues[1], g.ftntsIssues[2], g.ftntsIssues[3]);
3990518… george 790 Th_Store("footnotes_issues_counters", buf);
3990518… george 791 }
9c88799… drh 792 }
daff9d2… joel 793
daff9d2… joel 794 /*
dbda8d6… drh 795 ** Draw the header.
dbda8d6… drh 796 */
55342eb… drh 797 void style_header(const char *zTitleFormat, ...){
55342eb… drh 798 va_list ap;
55342eb… drh 799 char *zTitle;
ed36e2e… drh 800 const char *zHeader = skin_get("header");
b312f5f… drh 801 login_check_credentials();
55342eb… drh 802
55342eb… drh 803 va_start(ap, zTitleFormat);
55342eb… drh 804 zTitle = vmprintf(zTitleFormat, ap);
55342eb… drh 805 va_end(ap);
d13143e… jan.nijtmans 806
b312f5f… drh 807 cgi_destination(CGI_HEADER);
97d651b… drh 808
97d651b… drh 809 @ <!DOCTYPE html>
d13143e… jan.nijtmans 810
f5482a0… wyoung 811 if( g.thTrace ) Th_Trace("BEGIN_HEADER<br>\n", -1);
f55c6a1… drh 812
b312f5f… drh 813 /* Generate the header up through the main menu */
9c88799… drh 814 style_init_th1_vars(zTitle);
24ecb3b… drh 815 if( sqlite3_strlike("%<body%", zHeader, 0)!=0 ){
f1bb72e… drh 816 Th_Render(zDfltHeader);
f1bb72e… drh 817 }
f5482a0… wyoung 818 if( g.thTrace ) Th_Trace("BEGIN_HEADER_SCRIPT<br>\n", -1);
588bb7c… aku 819 Th_Render(zHeader);
f5482a0… wyoung 820 if( g.thTrace ) Th_Trace("END_HEADER<br>\n", -1);
68c24b1… drh 821 Th_Unstore("title"); /* Avoid collisions with ticket field names */
588bb7c… aku 822 cgi_destination(CGI_BODY);
96722b6… drh 823 g.cgiOutput = 1;
dcc4866… drh 824 headerHasBeenGenerated = 1;
5a48a9b… drh 825 sideboxUsed = 0;
99fcc43… drh 826 if( g.perm.Debug && P("showqp") ){
99fcc43… drh 827 @ <div class="debug">
0204f4a… drh 828 cgi_print_all(0, 0, 0);
99fcc43… drh 829 @ </div>
99fcc43… drh 830 }
37ae94b… drh 831 fossil_free(zTitle);
ff78d6d… drh 832 }
ff78d6d… drh 833
ff78d6d… drh 834 #if INTERFACE
ff78d6d… drh 835 /* Allowed parameters for style_adunit() */
ff78d6d… drh 836 #define ADUNIT_OFF 0x0001 /* Do not allow ads on this page */
ff78d6d… drh 837 #define ADUNIT_RIGHT_OK 0x0002 /* Right-side vertical ads ok here */
9e318f6… jan.nijtmans 838 #endif
ff78d6d… drh 839
ff78d6d… drh 840 /*
ff78d6d… drh 841 ** Various page implementations can invoke this interface to let the
ff78d6d… drh 842 ** style manager know what kinds of ads are appropriate for this page.
ff78d6d… drh 843 */
ff78d6d… drh 844 void style_adunit_config(unsigned int mFlags){
ff78d6d… drh 845 adUnitFlags = mFlags;
4bf5cdc… drh 846 }
4bf5cdc… drh 847
4bf5cdc… drh 848 /*
ff78d6d… drh 849 ** Return the text of an ad-unit, if one should be rendered. Return
ff78d6d… drh 850 ** NULL if no ad-unit is desired.
ff78d6d… drh 851 **
ff78d6d… drh 852 ** The *pAdFlag value might be set to ADUNIT_RIGHT_OK if this is
ff78d6d… drh 853 ** a right-hand vertical ad.
4bf5cdc… drh 854 */
ff78d6d… drh 855 static const char *style_adunit_text(unsigned int *pAdFlag){
ff78d6d… drh 856 const char *zAd = 0;
ff78d6d… drh 857 *pAdFlag = 0;
ff78d6d… drh 858 if( adUnitFlags & ADUNIT_OFF ) return 0; /* Disallow ads on this page */
48d8af2… drh 859 if( db_get_boolean("adunit-disable",0) ) return 0;
4bf5cdc… drh 860 if( g.perm.Admin && db_get_boolean("adunit-omit-if-admin",0) ){
ff78d6d… drh 861 return 0;
840b762… drh 862 }
840b762… drh 863 if( !login_is_nobody()
840b762… drh 864 && fossil_strcmp(g.zLogin,"anonymous")!=0
840b762… drh 865 && db_get_boolean("adunit-omit-if-user",0)
840b762… drh 866 ){
ff78d6d… drh 867 return 0;
ff78d6d… drh 868 }
ff78d6d… drh 869 if( (adUnitFlags & ADUNIT_RIGHT_OK)!=0
ff78d6d… drh 870 && !fossil_all_whitespace(zAd = db_get("adunit-right", 0))
ff78d6d… drh 871 && !cgi_body_contains("<table")
ff78d6d… drh 872 ){
ff78d6d… drh 873 *pAdFlag = ADUNIT_RIGHT_OK;
ff78d6d… drh 874 return zAd;
ff78d6d… drh 875 }else if( !fossil_all_whitespace(zAd = db_get("adunit",0)) ){
ff78d6d… drh 876 return zAd;
ff78d6d… drh 877 }
ff78d6d… drh 878 return 0;
ff78d6d… drh 879 }
ff78d6d… drh 880
ff78d6d… drh 881 /*
6b645d6… drh 882 ** Indicate that the table-sorting javascript is needed.
09494b0… drh 883 */
6b645d6… drh 884 void style_table_sorter(void){
036a9d5… drh 885 builtin_request_js("sorttable.js");
09494b0… drh 886 }
09494b0… drh 887
09494b0… drh 888 /*
09494b0… drh 889 ** Generate code to load all required javascript files.
09494b0… drh 890 */
09494b0… drh 891 static void style_load_all_js_files(void){
b190858… drh 892 if( needHrefJs && g.perm.Hyperlink ){
09494b0… drh 893 int nDelay = db_get_int("auto-hyperlink-delay",0);
cef15ed… drh 894 int bMouseover = db_get_boolean("auto-hyperlink-mouseover",0)
cef15ed… drh 895 && sqlite3_strglob("*Android*",PD("HTTP_USER_AGENT",""));
7fcb462… stephan 896 @ <script id='href-data' type='text/json'>\
09494b0… drh 897 @ {"delay":%d(nDelay),"mouseover":%d(bMouseover)}</script>
ff747b5… drh 898 }
6854244… drh 899 @ <script nonce="%h(style_nonce())">/* style.c:%d(__LINE__) */
6908832… drh 900 @ function debugMsg(msg){
6908832… drh 901 @ var n = document.getElementById("debugMsg");
6908832… drh 902 @ if(n){n.textContent=msg;}
6908832… drh 903 @ }
b190858… drh 904 if( needHrefJs && g.perm.Hyperlink ){
036a9d5… drh 905 @ /* href.js */
ff747b5… drh 906 cgi_append_content(builtin_text("href.js"),-1);
ff747b5… drh 907 }
ff747b5… drh 908 if( blob_size(&blobOnLoad)>0 ){
ff747b5… drh 909 @ window.onload = function(){
ff747b5… drh 910 cgi_append_content(blob_buffer(&blobOnLoad), blob_size(&blobOnLoad));
ff747b5… drh 911 cgi_append_content("\n}\n", -1);
ff747b5… drh 912 }
ff747b5… drh 913 @ </script>
036a9d5… drh 914 builtin_fulfill_js_requests();
e8a051e… george 915 }
e8a051e… george 916
e8a051e… george 917 /*
e2bdc10… danield 918 ** Transform input string into a token that is safe for inclusion into
e8a051e… george 919 ** class attribute. Digits and low-case letter are passed unchanged,
e8a051e… george 920 ** upper-case letters are transformed to low-case, everything else is
e2bdc10… danield 921 ** transformed into hyphens; consecutive and pending hyphens are squeezed.
e8a051e… george 922 ** If result does not fit into szOut chars then it is truncated.
e8a051e… george 923 ** Result is always terminated with null.
e8a051e… george 924 */
e8a051e… george 925 void style_derive_classname(const char *zIn, char *zOut, int szOut){
e8a051e… george 926 assert( zOut );
e8a051e… george 927 assert( szOut>0 );
e8a051e… george 928 if( zIn ){
e8a051e… george 929 int n = 0; /* number of chars written to zOut */
e8a051e… george 930 char c;
e8a051e… george 931 for(--szOut; (c=*zIn) && n<szOut; zIn++) {
e8a051e… george 932 if( ('a'<=c && c<='z') || ('0'<=c && c<='9') ){
e8a051e… george 933 *zOut = c;
e8a051e… george 934 }else if( 'A'<=c && c<='Z' ){
e8a051e… george 935 *zOut = c - 'A' + 'a';
e8a051e… george 936 }else{
e8a051e… george 937 if( n==0 || zOut[-1]=='-' ) continue;
e8a051e… george 938 *zOut = '-';
e8a051e… george 939 }
e8a051e… george 940 zOut++;
e8a051e… george 941 n++;
e8a051e… george 942 }
e8a051e… george 943 if( n && zOut[-1]=='-' ) zOut--;
e8a051e… george 944 }
e8a051e… george 945 *zOut = 0;
112c713… drh 946 }
112c713… drh 947
112c713… drh 948 /*
3d6444f… drh 949 ** Invoke this routine after all of the content for a webpage has been
3d6444f… drh 950 ** generated. This routine should be called once for every webpage, at
3d6444f… drh 951 ** or near the end of page generation. This routine does the following:
3d6444f… drh 952 **
3d6444f… drh 953 ** * Populates the header of the page, including setting up appropriate
3d6444f… drh 954 ** submenu elements. The header generation is deferred until this point
3d6444f… drh 955 ** so that we know that all style_submenu_element() and similar have
3d6444f… drh 956 ** been received.
3d6444f… drh 957 **
3d6444f… drh 958 ** * Finalizes the page content.
3d6444f… drh 959 **
3d6444f… drh 960 ** * Appends the footer.
4bf5cdc… drh 961 */
112c713… drh 962 void style_finish_page(){
b312f5f… drh 963 const char *zFooter;
ff78d6d… drh 964 const char *zAd = 0;
ff78d6d… drh 965 unsigned int mAdFlags = 0;
dcc4866… drh 966
dcc4866… drh 967 if( !headerHasBeenGenerated ) return;
d13143e… jan.nijtmans 968
b312f5f… drh 969 /* Go back and put the submenu at the top of the page. We delay the
b312f5f… drh 970 ** creation of the submenu until the end so that we can add elements
b312f5f… drh 971 ** to the submenu while generating page text.
b312f5f… drh 972 */
23ed5e2… eric 973 cgi_destination(CGI_HEADER);
220ed0a… drh 974 if( submenuEnable && nSubmenu+nSubmenuCtrl>0 ){
249f1be… drh 975 int i;
e8a051e… george 976 char zClass[32]; /* reduced form of the main attribute */
c0c0bae… drh 977 if( nSubmenuCtrl ){
802939b… drh 978 @ <form id='f01' method='GET' action='%R/%s(g.zPath)'>
802939b… drh 979 @ <input type='hidden' name='udc' value='1'>
0f9b648… drh 980 cgi_tag_query_parameter("udc");
c0c0bae… drh 981 }
249f1be… drh 982 @ <div class="submenu">
c0c0bae… drh 983 if( nSubmenu>0 ){
c0c0bae… drh 984 qsort(aSubmenu, nSubmenu, sizeof(aSubmenu[0]), submenuCompare);
c0c0bae… drh 985 for(i=0; i<nSubmenu; i++){
c0c0bae… drh 986 struct Submenu *p = &aSubmenu[i];
e8a051e… george 987 style_derive_classname(p->zLabel, zClass, sizeof zClass);
3e35068… george 988 /* switching away from the %h formatting below might be dangerous
3e35068… george 989 ** because some places use %s to compose zLabel and zLink;
71b2216… george 990 ** e.g. /rptview page and the submenuCmd() function.
71b2216… george 991 ** "sml" stands for submenu link.
3e35068… george 992 */
9c21101… andygoth 993 if( p->zLink==0 ){
e8a051e… george 994 @ <span class="label sml-%s(zClass)">%h(p->zLabel)</span>
9c21101… andygoth 995 }else{
e8a051e… george 996 @ <a class="label sml-%s(zClass)" href="%h(p->zLink)">%h(p->zLabel)</a>
9c21101… andygoth 997 }
9c21101… andygoth 998 }
9c21101… andygoth 999 }
c0b9b44… stephan 1000 fossil_strcpy(zClass,"smc-"); /* common prefix for submenu controls */
9c21101… andygoth 1001 for(i=0; i<nSubmenuCtrl; i++){
9c21101… andygoth 1002 const char *zQPN = aSubmenuCtrl[i].zName;
e91d267… drh 1003 const char *zDisabled = "";
e91d267… drh 1004 const char *zXtraClass = "";
e91d267… drh 1005 if( aSubmenuCtrl[i].eVisible & STYLE_DISABLED ){
e91d267… drh 1006 zDisabled = " disabled";
e91d267… drh 1007 }else if( zQPN ){
9c21101… andygoth 1008 cgi_tag_query_parameter(zQPN);
9c21101… andygoth 1009 }
e8a051e… george 1010 style_derive_classname(zQPN, zClass+4, sizeof(zClass)-4);
9c21101… andygoth 1011 switch( aSubmenuCtrl[i].eType ){
9c21101… andygoth 1012 case FF_ENTRY:
e8a051e… george 1013 @ <span class='submenuctrl%s(zXtraClass) %s(zClass)'>\
9c21101… andygoth 1014 @ &nbsp;%h(aSubmenuCtrl[i].zLabel)\
9c21101… andygoth 1015 @ <input type='text' name='%s(zQPN)' value='%h(PD(zQPN, ""))' \
9c21101… andygoth 1016 if( aSubmenuCtrl[i].iSize<0 ){
9c21101… andygoth 1017 @ size='%d(-aSubmenuCtrl[i].iSize)' \
9c21101… andygoth 1018 }else if( aSubmenuCtrl[i].iSize>0 ){
9c21101… andygoth 1019 @ size='%d(aSubmenuCtrl[i].iSize)' \
9c21101… andygoth 1020 @ maxlength='%d(aSubmenuCtrl[i].iSize)' \
9c21101… andygoth 1021 }
3969757… drh 1022 @ id='submenuctrl-%d(i)'%s(zDisabled)></span>
3cb9ba4… andygoth 1023 break;
3cb9ba4… andygoth 1024 case FF_MULTI: {
3cb9ba4… andygoth 1025 int j;
3cb9ba4… andygoth 1026 const char *zVal = P(zQPN);
e91d267… drh 1027 if( zXtraClass[0] ){
e8a051e… george 1028 @ <span class='%s(zXtraClass+1) %s(zClass)'>
e91d267… drh 1029 }
3cb9ba4… andygoth 1030 if( aSubmenuCtrl[i].zLabel ){
9c21101… andygoth 1031 @ &nbsp;%h(aSubmenuCtrl[i].zLabel)\
3cb9ba4… andygoth 1032 }
e8a051e… george 1033 @ <select class='submenuctrl %s(zClass)' size='1' name='%s(zQPN)' \
3969757… drh 1034 @ id='submenuctrl-%d(i)'%s(zDisabled)>
3cb9ba4… andygoth 1035 for(j=0; j<aSubmenuCtrl[i].iSize*2; j+=2){
3cb9ba4… andygoth 1036 const char *zQPV = aSubmenuCtrl[i].azChoice[j];
9c21101… andygoth 1037 @ <option value='%h(zQPV)'\
9c21101… andygoth 1038 if( fossil_strcmp(zVal, zQPV)==0 ){
9c21101… andygoth 1039 @ selected\
9c21101… andygoth 1040 }
9c21101… andygoth 1041 @ >%h(aSubmenuCtrl[i].azChoice[j+1])</option>
3cb9ba4… andygoth 1042 }
3cb9ba4… andygoth 1043 @ </select>
e91d267… drh 1044 if( zXtraClass[0] ){
e91d267… drh 1045 @ </span>
e91d267… drh 1046 }
3cb9ba4… andygoth 1047 break;
3cb9ba4… andygoth 1048 }
3cb9ba4… andygoth 1049 case FF_BINARY: {
3cb9ba4… andygoth 1050 int isTrue = PB(zQPN);
259074d… drh 1051 @ <select class='submenuctrl%s(zXtraClass)' size='1' \
3969757… drh 1052 @ name='%s(zQPN)' id='submenuctrl-%d(i)'%s(zDisabled)>
9c21101… andygoth 1053 @ <option value='1'\
9c21101… andygoth 1054 if( isTrue ){
9c21101… andygoth 1055 @ selected\
9c21101… andygoth 1056 }
9c21101… andygoth 1057 @ >%h(aSubmenuCtrl[i].zLabel)</option>
9c21101… andygoth 1058 @ <option value='0'\
9c21101… andygoth 1059 if( !isTrue ){
9c21101… andygoth 1060 @ selected\
9c21101… andygoth 1061 }
9c21101… andygoth 1062 @ >%h(aSubmenuCtrl[i].zFalse)</option>
3cb9ba4… andygoth 1063 @ </select>
3cb9ba4… andygoth 1064 break;
3cb9ba4… andygoth 1065 }
037e06b… drh 1066 case FF_CHECKBOX: {
e8a051e… george 1067 @ <label class='submenuctrl submenuckbox%s(zXtraClass) %s(zClass)'>\
3969757… drh 1068 @ <input type='checkbox' name='%s(zQPN)' id='submenuctrl-%d(i)' \
9c21101… andygoth 1069 if( PB(zQPN) ){
9c21101… andygoth 1070 @ checked \
9c21101… andygoth 1071 }
037e06b… drh 1072 if( aSubmenuCtrl[i].zJS ){
3969757… drh 1073 @ data-ctrl='%s(aSubmenuCtrl[i].zJS)'%s(zDisabled)>\
037e06b… drh 1074 }else{
3969757… drh 1075 @ %s(zDisabled)>\
037e06b… drh 1076 }
9c21101… andygoth 1077 @ %h(aSubmenuCtrl[i].zLabel)</label>
3cb9ba4… andygoth 1078 break;
037e06b… drh 1079 }
249f1be… drh 1080 }
249f1be… drh 1081 }
ff78d6d… drh 1082 @ </div>
c0c0bae… drh 1083 if( nSubmenuCtrl ){
c0c0bae… drh 1084 cgi_query_parameters_to_hidden();
c0c0bae… drh 1085 cgi_tag_query_parameter(0);
c0c0bae… drh 1086 @ </form>
036a9d5… drh 1087 builtin_request_js("menu.js");
c0c0bae… drh 1088 }
ff78d6d… drh 1089 }
ff78d6d… drh 1090
ff78d6d… drh 1091 zAd = style_adunit_text(&mAdFlags);
ff78d6d… drh 1092 if( (mAdFlags & ADUNIT_RIGHT_OK)!=0 ){
ff78d6d… drh 1093 @ <div class="content adunit_right_container">
ff78d6d… drh 1094 @ <div class="adunit_right">
ff78d6d… drh 1095 cgi_append_content(zAd, -1);
ff78d6d… drh 1096 @ </div>
3d6444f… drh 1097 }else if( zAd ){
3d6444f… drh 1098 @ <div class="adunit_banner">
3d6444f… drh 1099 cgi_append_content(zAd, -1);
3d6444f… drh 1100 @ </div>
3d6444f… drh 1101 }
3d6444f… drh 1102
112c713… drh 1103 @ <div class="content"><span id="debugMsg"></span>
249f1be… drh 1104 cgi_destination(CGI_BODY);
249f1be… drh 1105
38421a9… jan.nijtmans 1106 if( sideboxUsed ){
5a48a9b… drh 1107 @ <div class="endContent"></div>
5a48a9b… drh 1108 }
3243e63… drh 1109 @ </div>
7fb59a6… drh 1110
3d6444f… drh 1111 /* Put the footer at the bottom of the page. */
ed36e2e… drh 1112 zFooter = skin_get("footer");
8394d2f… drh 1113 if( sqlite3_strlike("%</body>%", zFooter, 0)==0 ){
09494b0… drh 1114 style_load_all_js_files();
8394d2f… drh 1115 }
f5482a0… wyoung 1116 if( g.thTrace ) Th_Trace("BEGIN_FOOTER<br>\n", -1);
249f1be… drh 1117 Th_Render(zFooter);
f5482a0… wyoung 1118 if( g.thTrace ) Th_Trace("END_FOOTER<br>\n", -1);
d13143e… jan.nijtmans 1119
249f1be… drh 1120 /* Render trace log if TH1 tracing is enabled. */
249f1be… drh 1121 if( g.thTrace ){
f5482a0… wyoung 1122 cgi_append_content("<span class=\"thTrace\"><hr>\n", -1);
249f1be… drh 1123 cgi_append_content(blob_str(&g.thLog), blob_size(&g.thLog));
5a48a9b… drh 1124 cgi_append_content("</span>\n", -1);
5a48a9b… drh 1125 }
f1bb72e… drh 1126
f1bb72e… drh 1127 /* Add document end mark if it was not in the footer */
f1bb72e… drh 1128 if( sqlite3_strlike("%</body>%", zFooter, 0)!=0 ){
09494b0… drh 1129 style_load_all_js_files();
8394d2f… drh 1130 @ </body>
8394d2f… drh 1131 @ </html>
f1bb72e… drh 1132 }
5feac63… stephan 1133 /* Update the user display prefs cookie if it was modified */
3339420… drh 1134 cookie_render();
83ac468… drh 1135 }
83ac468… drh 1136
83ac468… drh 1137 /*
83ac468… drh 1138 ** Begin a side-box on the right-hand side of a page. The title and
83ac468… drh 1139 ** the width of the box are given as arguments. The width is usually
83ac468… drh 1140 ** a percentage of total screen width.
83ac468… drh 1141 */
83ac468… drh 1142 void style_sidebox_begin(const char *zTitle, const char *zWidth){
5a48a9b… drh 1143 sideboxUsed = 1;
34f9b9d… drh 1144 @ <div class="sidebox" style="width:%s(zWidth)">
34f9b9d… drh 1145 @ <div class="sideboxTitle">%h(zTitle)</div>
83ac468… drh 1146 }
83ac468… drh 1147
83ac468… drh 1148 /* End the side-box
83ac468… drh 1149 */
83ac468… drh 1150 void style_sidebox_end(void){
34f9b9d… drh 1151 @ </div>
34f9b9d… drh 1152 }
34f9b9d… drh 1153
ca869aa… drh 1154 /*
ca869aa… drh 1155 ** Search string zCss for zSelector.
a00a140… drh 1156 **
a00a140… drh 1157 ** Return true if found. Return false if not found
a00a140… drh 1158 */
ca869aa… drh 1159 static int containsSelector(const char *zCss, const char *zSelector){
31c81ac… drh 1160 const char *z;
a00a140… drh 1161 int n;
ca869aa… drh 1162 int selectorLen = (int)strlen(zSelector);
a00a140… drh 1163
31c81ac… drh 1164 for(z=zCss; *z; z+=selectorLen){
ca869aa… drh 1165 z = strstr(z, zSelector);
a00a140… drh 1166 if( z==0 ) return 0;
ca869aa… drh 1167 if( z!=zCss ){
ca869aa… drh 1168 for( n=-1; z+n!=zCss && fossil_isspace(z[n]); n--);
ca869aa… drh 1169 if( z+n!=zCss && z[n]!=',' && z[n]!= '}' && z[n]!='/' ) continue;
a00a140… drh 1170 }
ca869aa… drh 1171 for( n=selectorLen; z[n] && fossil_isspace(z[n]); n++ );
ca869aa… drh 1172 if( z[n]==',' || z[n]=='{' || z[n]=='/' ) return 1;
a00a140… drh 1173 }
a00a140… drh 1174 return 0;
a00a140… drh 1175 }
b8e3dc1… jan.nijtmans 1176
ca869aa… drh 1177 /*
ca869aa… drh 1178 ** COMMAND: test-contains-selector
ca869aa… drh 1179 **
ca869aa… drh 1180 ** Usage: %fossil test-contains-selector FILENAME SELECTOR
ca869aa… drh 1181 **
ca869aa… drh 1182 ** Determine if the CSS stylesheet FILENAME contains SELECTOR.
064c1c9… stephan 1183 **
064c1c9… stephan 1184 ** Note that as of 2020-05-28, the default rules are always emitted,
064c1c9… stephan 1185 ** so the containsSelector() logic is no longer applied when emitting
064c1c9… stephan 1186 ** style.css. It is unclear whether this test command is now obsolete
064c1c9… stephan 1187 ** or whether it may still serve a purpose.
ca869aa… drh 1188 */
ca869aa… drh 1189 void contains_selector_cmd(void){
ca869aa… drh 1190 int found;
ca869aa… drh 1191 char *zSelector;
ca869aa… drh 1192 Blob css;
ca869aa… drh 1193 if( g.argc!=4 ) usage("FILENAME SELECTOR");
1772357… drh 1194 blob_read_from_file(&css, g.argv[2], ExtFILE);
ca869aa… drh 1195 zSelector = g.argv[3];
ca869aa… drh 1196 found = containsSelector(blob_str(&css), zSelector);
ca869aa… drh 1197 fossil_print("%s %s\n", zSelector, found ? "found" : "not found");
ca869aa… drh 1198 blob_reset(&css);
ca869aa… drh 1199 }
ca869aa… drh 1200
9c88799… drh 1201 /*
9c88799… drh 1202 ** WEBPAGE: script.js
9c88799… drh 1203 **
9c88799… drh 1204 ** Return the "Javascript" content for the current skin (if there is any)
9c88799… drh 1205 */
9c88799… drh 1206 void page_script_js(void){
9c88799… drh 1207 const char *zScript = skin_get("js");
9c88799… drh 1208 if( P("test") ){
9c88799… drh 1209 /* Render the script as plain-text for testing purposes, if the "test"
9c88799… drh 1210 ** query parameter is present */
9c88799… drh 1211 cgi_set_content_type("text/plain");
9c88799… drh 1212 }else{
9c88799… drh 1213 /* Default behavior is to return javascript */
7fcb462… stephan 1214 cgi_set_content_type("text/javascript");
9c88799… drh 1215 }
9c88799… drh 1216 style_init_th1_vars(0);
9c88799… drh 1217 Th_Render(zScript?zScript:"");
9c88799… drh 1218 }
9c88799… drh 1219
064c1c9… stephan 1220 /*
d4c91b4… drh 1221 ** Check for "name" or "page" query parameters on an /style.css
d4c91b4… drh 1222 ** page request. If present, then page-specific CSS is requested,
d4c91b4… drh 1223 ** so add that CSS to pOut. If the "name" and "page" query parameters
e2bdc10… danield 1224 ** are omitted, then pOut is unchanged.
8eec01d… stephan 1225 */
8eec01d… stephan 1226 static void page_style_css_append_page_style(Blob *pOut){
8eec01d… stephan 1227 const char *zPage = PD("name",P("page"));
8eec01d… stephan 1228 char * zFile;
8eec01d… stephan 1229 int nFile = 0;
8eec01d… stephan 1230 const char *zBuiltin;
8eec01d… stephan 1231
8eec01d… stephan 1232 if(zPage==0 || zPage[0]==0){
8eec01d… stephan 1233 return;
8eec01d… stephan 1234 }
8eec01d… stephan 1235 zFile = mprintf("style.%s.css", zPage);
8eec01d… stephan 1236 zBuiltin = (const char *)builtin_file(zFile, &nFile);
8eec01d… stephan 1237 if(nFile>0){
8eec01d… stephan 1238 blob_appendf(pOut,
8eec01d… stephan 1239 "\n/***********************************************************\n"
d4c91b4… drh 1240 "** Page-specific CSS for \"%s\"\n"
8eec01d… stephan 1241 "***********************************************************/\n",
8eec01d… stephan 1242 zPage);
8eec01d… stephan 1243 blob_append(pOut, zBuiltin, nFile);
8eec01d… stephan 1244 fossil_free(zFile);
8eec01d… stephan 1245 return;
8eec01d… stephan 1246 }
8eec01d… stephan 1247 /* Potential TODO: check for aliases/page groups. e.g. group all
8eec01d… stephan 1248 ** /forumXYZ CSS into one file, all /setupXYZ into another, etc. As
8eec01d… stephan 1249 ** of this writing, doing so would only shave a few kb from
8eec01d… stephan 1250 ** default.css. */
8eec01d… stephan 1251 fossil_free(zFile);
8eec01d… stephan 1252 }
8eec01d… stephan 1253
8eec01d… stephan 1254 /*
9413395… drh 1255 ** WEBPAGE: style.css loadavg-exempt
d4c91b4… drh 1256 **
e2bdc10… danield 1257 ** Return the style sheet. The style sheet is assembled from
d4c91b4… drh 1258 ** multiple sources, in order:
d4c91b4… drh 1259 **
d4c91b4… drh 1260 ** (1) The built-in "default.css" style sheet containing basic defaults.
d4c91b4… drh 1261 **
d4c91b4… drh 1262 ** (2) The page-specific style sheet taken from the built-in
d4c91b4… drh 1263 ** called "PAGENAME.css" where PAGENAME is the value of the name=
d4c91b4… drh 1264 ** or page= query parameters. If neither name= nor page= exist,
d4c91b4… drh 1265 ** then this section is a no-op.
d4c91b4… drh 1266 **
d4c91b4… drh 1267 ** (3) The skin-specific "css.txt" file, if there one.
d4c91b4… drh 1268 **
d4c91b4… drh 1269 ** All of (1), (2), and (3) above (or as many as exist) are concatenated.
d4c91b4… drh 1270 ** The result is then run through TH1 with the following variables set:
d4c91b4… drh 1271 **
d4c91b4… drh 1272 ** * $basename
d4c91b4… drh 1273 ** * $secureurl
d4c91b4… drh 1274 ** * $home
d4c91b4… drh 1275 ** * $logo
d4c91b4… drh 1276 ** * $background
7ab0328… drh 1277 **
d4c91b4… drh 1278 ** The output from TH1 becomes the style sheet. Fossil always reports
275da70… danield 1279 ** that the style sheet is cacheable.
1942d58… drh 1280 */
1942d58… drh 1281 void page_style_css(void){
064c1c9… stephan 1282 Blob css = empty_blob;
34f9b9d… drh 1283 int i;
8eec01d… stephan 1284 const char * zDefaults;
d4c91b4… drh 1285 const char *zSkin;
1942d58… drh 1286
1942d58… drh 1287 cgi_set_content_type("text/css");
ec5a063… drh 1288 etag_check(0, 0);
064c1c9… stephan 1289 /* Emit all default rules... */
8eec01d… stephan 1290 zDefaults = (const char*)builtin_file("default.css", &i);
8eec01d… stephan 1291 blob_append(&css, zDefaults, i);
8eec01d… stephan 1292 /* Page-specific CSS, if any... */
8eec01d… stephan 1293 page_style_css_append_page_style(&css);
d4c91b4… drh 1294 zSkin = skin_in_use();
d4c91b4… drh 1295 if( zSkin==0 ) zSkin = "this repository";
d4c91b4… drh 1296 blob_appendf(&css,
8eec01d… stephan 1297 "\n/***********************************************************\n"
d4c91b4… drh 1298 "** Skin-specific CSS for %s\n"
8eec01d… stephan 1299 "***********************************************************/\n",
d4c91b4… drh 1300 zSkin);
8eec01d… stephan 1301 blob_append(&css,skin_get("css"),-1);
6239845… drh 1302 /* Process through TH1 in order to give an opportunity to substitute
6239845… drh 1303 ** variables such as $baseurl.
6239845… drh 1304 */
6239845… drh 1305 Th_Store("baseurl", g.zBaseURL);
4aba9ea… drh 1306 Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
6239845… drh 1307 Th_Store("home", g.zTop);
daff9d2… joel 1308 image_url_var("logo");
daff9d2… joel 1309 image_url_var("background");
6239845… drh 1310 Th_Render(blob_str(&css));
37ae94b… drh 1311 blob_reset(&css);
6239845… drh 1312
6239845… drh 1313 /* Tell CGI that the content returned by this page is considered cacheable */
1942d58… drh 1314 g.isConst = 1;
1942d58… drh 1315 }
1942d58… drh 1316
1942d58… drh 1317 /*
99fcc43… drh 1318 ** All possible capabilities
99fcc43… drh 1319 */
275da70… danield 1320 static const char allCap[] =
99fcc43… drh 1321 "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKL";
99fcc43… drh 1322
99fcc43… drh 1323 /*
99fcc43… drh 1324 ** Compute the current login capabilities
99fcc43… drh 1325 */
99fcc43… drh 1326 static char *find_capabilities(char *zCap){
99fcc43… drh 1327 int i, j;
99fcc43… drh 1328 char c;
99fcc43… drh 1329 for(i=j=0; (c = allCap[j])!=0; j++){
99fcc43… drh 1330 if( login_has_capability(&c, 1, 0) ) zCap[i++] = c;
99fcc43… drh 1331 }
99fcc43… drh 1332 zCap[i] = 0;
99fcc43… drh 1333 return zCap;
99fcc43… drh 1334 }
99fcc43… drh 1335
99fcc43… drh 1336 /*
99fcc43… drh 1337 ** Compute the current login capabilities that were
99fcc43… drh 1338 ** contributed by Anonymous
99fcc43… drh 1339 */
99fcc43… drh 1340 static char *find_anon_capabilities(char *zCap){
99fcc43… drh 1341 int i, j;
99fcc43… drh 1342 char c;
99fcc43… drh 1343 for(i=j=0; (c = allCap[j])!=0; j++){
99fcc43… drh 1344 if( login_has_capability(&c, 1, LOGIN_ANON)
99fcc43… drh 1345 && !login_has_capability(&c, 1, 0) ) zCap[i++] = c;
99fcc43… drh 1346 }
99fcc43… drh 1347 zCap[i] = 0;
99fcc43… drh 1348 return zCap;
99fcc43… drh 1349 }
99fcc43… drh 1350
99fcc43… drh 1351 /*
af57f63… drh 1352 ** WEBPAGE: test-title
af57f63… drh 1353 **
af57f63… drh 1354 ** Render a test page in which the page title is set by the "title"
af57f63… drh 1355 ** query parameter. This can be used to show that HTML or Javascript
af57f63… drh 1356 ** content in the title does not leak through into generated page, resulting
af57f63… drh 1357 ** in an XSS issue.
af57f63… drh 1358 **
af57f63… drh 1359 ** Due to the potential for abuse, this webpage is only available to
af57f63… drh 1360 ** administrators.
af57f63… drh 1361 */
af57f63… drh 1362 void page_test_title(void){
af57f63… drh 1363 const char *zTitle;
af57f63… drh 1364 login_check_credentials();
af57f63… drh 1365 if( !g.perm.Admin ){
af57f63… drh 1366 login_needed(0);
af57f63… drh 1367 }
af57f63… drh 1368 zTitle = P("title");
af57f63… drh 1369 if( zTitle==0 ){
af57f63… drh 1370 zTitle = "(No Title)";
af57f63… drh 1371 }
af57f63… drh 1372 style_header("%s", zTitle);
af57f63… drh 1373 @ <p>
af57f63… drh 1374 @ This page sets its title to the value of the "title" query parameter.
af57f63… drh 1375 @ The form below is a convenient way to set the title query parameter:
af57f63… drh 1376 @
af57f63… drh 1377 @ <form method="GET">
af57f63… drh 1378 @ Title: <input type="text" size="50" name="title" value="%h(zTitle)">
af57f63… drh 1379 @ <input type="submit" value="Submit">
af57f63… drh 1380 @ </form>
af57f63… drh 1381 style_finish_page();
af57f63… drh 1382 }
af57f63… drh 1383
af57f63… drh 1384 /*
caf286d… drh 1385 ** WEBPAGE: test-env
caf286d… drh 1386 ** WEBPAGE: test_env alias
7ab0328… drh 1387 **
7ab0328… drh 1388 ** Display CGI-variables and other aspects of the run-time
7ab0328… drh 1389 ** environment, for debugging and trouble-shooting purposes.
7ab0328… drh 1390 */
7ab0328… drh 1391 void page_test_env(void){
99fcc43… drh 1392 webpage_error("");
99fcc43… drh 1393 }
99fcc43… drh 1394
99fcc43… drh 1395 /*
99fcc43… drh 1396 ** Webpages that encounter an error due to missing or incorrect
99fcc43… drh 1397 ** query parameters can jump to this routine to render an error
99fcc43… drh 1398 ** message screen.
99fcc43… drh 1399 **
99fcc43… drh 1400 ** For administators, or if the test_env_enable setting is true, then
99fcc43… drh 1401 ** details of the request environment are displayed. Otherwise, just
99fcc43… drh 1402 ** the error message is shown.
99fcc43… drh 1403 **
caf286d… drh 1404 ** If zFormat is an empty string, then this is the /test-env page.
99fcc43… drh 1405 */
99fcc43… drh 1406 void webpage_error(const char *zFormat, ...){
9c40ddb… drh 1407 int showAll = 0;
99fcc43… drh 1408 char *zErr = 0;
99fcc43… drh 1409 int isAuth = 0;
99fcc43… drh 1410 char zCap[100];
7ab0328… drh 1411
7ab0328… drh 1412 login_check_credentials();
99fcc43… drh 1413 if( g.perm.Admin || g.perm.Setup || db_get_boolean("test_env_enable",0) ){
99fcc43… drh 1414 isAuth = 1;
99fcc43… drh 1415 }
4a7760e… drh 1416 cgi_load_environment();
f2a26bc… drh 1417 style_set_current_feature(zFormat[0]==0 ? "test" : "error");
99fcc43… drh 1418 if( zFormat[0] ){
99fcc43… drh 1419 va_list ap;
99fcc43… drh 1420 va_start(ap, zFormat);
99fcc43… drh 1421 zErr = vmprintf(zFormat, ap);
99fcc43… drh 1422 va_end(ap);
99fcc43… drh 1423 style_header("Bad Request");
99fcc43… drh 1424 @ <h1>/%h(g.zPath): %h(zErr)</h1>
99fcc43… drh 1425 showAll = 0;
99fcc43… drh 1426 cgi_set_status(500, "Bad Request");
99fcc43… drh 1427 }else if( !isAuth ){
7ab0328… drh 1428 login_needed(0);
7ab0328… drh 1429 return;
99fcc43… drh 1430 }else{
99fcc43… drh 1431 style_header("Environment Test");
99fcc43… drh 1432 showAll = PB("showall");
99fcc43… drh 1433 style_submenu_checkbox("showall", "Cookies", 0, 0);
99fcc43… drh 1434 style_submenu_element("Stats", "%R/stat");
99fcc43… drh 1435 }
99fcc43… drh 1436
99fcc43… drh 1437 if( isAuth ){
99fcc43… drh 1438 #if !defined(_WIN32)
f5482a0… wyoung 1439 @ uid=%d(getuid()), gid=%d(getgid())<br>
99fcc43… drh 1440 #endif
f5482a0… wyoung 1441 @ g.zBaseURL = %h(g.zBaseURL)<br>
f5482a0… wyoung 1442 @ g.zHttpsURL = %h(g.zHttpsURL)<br>
f5482a0… wyoung 1443 @ g.zTop = %h(g.zTop)<br>
f5482a0… wyoung 1444 @ g.zPath = %h(g.zPath)<br>
f5482a0… wyoung 1445 @ g.userUid = %d(g.userUid)<br>
f5482a0… wyoung 1446 @ g.zLogin = %h(g.zLogin)<br>
10006db… drh 1447 if( g.eAuthMethod!=AUTH_NONE ){
10006db… drh 1448 const char *zMethod[] = { "COOKIE", "LOCAL", "PW", "ENV", "HTTP" };
10006db… drh 1449 @ g.eAuthMethod = %d(g.eAuthMethod) (%h(zMethod[g.eAuthMethod-1]))\
10006db… drh 1450 @ <br>
10006db… drh 1451 }
16b3309… drh 1452 @ g.isRobot = %d(g.isRobot)<br>
f5482a0… wyoung 1453 @ g.jsHref = %d(g.jsHref)<br>
3df5d40… drh 1454 if( g.zLocalRoot ){
f5482a0… wyoung 1455 @ g.zLocalRoot = %h(g.zLocalRoot)<br>
3df5d40… drh 1456 }else{
f5482a0… wyoung 1457 @ g.zLocalRoot = <i>none</i><br>
3df5d40… drh 1458 }
99fcc43… drh 1459 if( g.nRequest ){
f5482a0… wyoung 1460 @ g.nRequest = %d(g.nRequest)<br>
99fcc43… drh 1461 }
99fcc43… drh 1462 if( g.nPendingRequest>1 ){
f5482a0… wyoung 1463 @ g.nPendingRequest = %d(g.nPendingRequest)<br>
99fcc43… drh 1464 }
f5482a0… wyoung 1465 @ capabilities = %s(find_capabilities(zCap))<br>
99fcc43… drh 1466 if( zCap[0] ){
f5482a0… wyoung 1467 @ anonymous-adds = %s(find_anon_capabilities(zCap))<br>
b00e20c… drh 1468 }
f5482a0… wyoung 1469 @ g.zRepositoryName = %h(g.zRepositoryName)<br>
f5482a0… wyoung 1470 @ load_average() = %f(load_average())<br>
b00e20c… drh 1471 #ifndef _WIN32
f5482a0… wyoung 1472 @ RSS = %.2f(fossil_rss()/1000000.0) MB</br>
b00e20c… drh 1473 #endif
920ace1… drh 1474 (void)cgi_csrf_safe(2);
920ace1… drh 1475 switch( g.okCsrf ){
920ace1… drh 1476 case 1: {
920ace1… drh 1477 @ CSRF safety = Same origin<br>
920ace1… drh 1478 break;
920ace1… drh 1479 }
920ace1… drh 1480 case 2: {
920ace1… drh 1481 @ CSRF safety = Same origin, POST<br>
920ace1… drh 1482 break;
920ace1… drh 1483 }
920ace1… drh 1484 case 3: {
920ace1… drh 1485 @ CSRF safety = Same origin, POST, CSRF token<br>
920ace1… drh 1486 break;
920ace1… drh 1487 }
920ace1… drh 1488 default: {
920ace1… drh 1489 @ CSRF safety = unsafe<br>
920ace1… drh 1490 break;
920ace1… drh 1491 }
920ace1… drh 1492 }
275da70… danield 1493
f5482a0… wyoung 1494 @ fossil_exe_id() = %h(fossil_exe_id())<br>
4350f32… drh 1495 if( g.perm.Admin ){
4350f32… drh 1496 int k;
4350f32… drh 1497 for(k=0; g.argvOrig[k]; k++){
4350f32… drh 1498 Blob t;
4350f32… drh 1499 blob_init(&t, 0, 0);
4350f32… drh 1500 blob_append_escaped_arg(&t, g.argvOrig[k], 0);
f5482a0… wyoung 1501 @ argv[%d(k)] = %h(blob_str(&t))<br>
4350f32… drh 1502 blob_zero(&t);
4350f32… drh 1503 }
4350f32… drh 1504 }
f5482a0… wyoung 1505 @ <hr>
99fcc43… drh 1506 P("HTTP_USER_AGENT");
f1729c4… drh 1507 P("SERVER_SOFTWARE");
0204f4a… drh 1508 cgi_print_all(showAll, 0, 0);
caf286d… drh 1509 @ <p><form method="POST" action="%R/test-env">
9c40ddb… drh 1510 @ <input type="hidden" name="showall" value="%d(showAll)">
9c40ddb… drh 1511 @ <input type="submit" name="post-test-button" value="POST Test">
9c40ddb… drh 1512 @ </form>
99fcc43… drh 1513 if( showAll && blob_size(&g.httpHeader)>0 ){
f5482a0… wyoung 1514 @ <hr>
99fcc43… drh 1515 @ <pre>
99fcc43… drh 1516 @ %h(blob_str(&g.httpHeader))
99fcc43… drh 1517 @ </pre>
99fcc43… drh 1518 }
99fcc43… drh 1519 }
79f7808… drh 1520 if( zErr && zErr[0] ){
112c713… drh 1521 style_finish_page();
99fcc43… drh 1522 cgi_reply();
99fcc43… drh 1523 fossil_exit(1);
79f7808… drh 1524 }else{
112c713… drh 1525 style_finish_page();
99fcc43… drh 1526 }
99fcc43… drh 1527 }
99fcc43… drh 1528
99fcc43… drh 1529 /*
99fcc43… drh 1530 ** Generate a Not Yet Implemented error page.
99fcc43… drh 1531 */
99fcc43… drh 1532 void webpage_not_yet_implemented(void){
99fcc43… drh 1533 webpage_error("Not yet implemented");
99fcc43… drh 1534 }
99fcc43… drh 1535
99fcc43… drh 1536 /*
99fcc43… drh 1537 ** Generate a webpage for a webpage_assert().
99fcc43… drh 1538 */
99fcc43… drh 1539 void webpage_assert_page(const char *zFile, int iLine, const char *zExpr){
99fcc43… drh 1540 fossil_warning("assertion fault at %s:%d - %s", zFile, iLine, zExpr);
99fcc43… drh 1541 cgi_reset_content();
99fcc43… drh 1542 webpage_error("assertion fault at %s:%d - %s", zFile, iLine, zExpr);
93a5d65… drh 1543 }
93a5d65… drh 1544
93a5d65… drh 1545 /*
93a5d65… drh 1546 ** Issue a 404 Not Found error for a webpage
93a5d65… drh 1547 */
93a5d65… drh 1548 void webpage_notfound_error(const char *zFormat, ...){
93a5d65… drh 1549 char *zMsg;
93a5d65… drh 1550 va_list ap;
93a5d65… drh 1551 if( zFormat ){
93a5d65… drh 1552 va_start(ap, zFormat);
93a5d65… drh 1553 zMsg = vmprintf(zFormat, ap);
93a5d65… drh 1554 va_end(ap);
93a5d65… drh 1555 }else{
93a5d65… drh 1556 zMsg = "Not Found";
93a5d65… drh 1557 }
112c713… drh 1558 style_set_current_feature("enotfound");
93a5d65… drh 1559 style_header("Not Found");
93a5d65… drh 1560 @ <p>%h(zMsg)</p>
93a5d65… drh 1561 cgi_set_status(404, "Not Found");
112c713… drh 1562 style_finish_page();
1243bf3… stephan 1563 }
1243bf3… stephan 1564
99fcc43… drh 1565 #if INTERFACE
99fcc43… drh 1566 # define webpage_assert(T) if(!(T)){webpage_assert_page(__FILE__,__LINE__,#T);}
63220d9… jan.nijtmans 1567 #endif
1243bf3… stephan 1568
1243bf3… stephan 1569 /*
1243bf3… stephan 1570 ** Returns a pseudo-random input field ID, for use in associating an
1243bf3… stephan 1571 ** ID-less input field with a label. The memory is owned by the
1243bf3… stephan 1572 ** caller.
1243bf3… stephan 1573 */
1243bf3… stephan 1574 static char * style_next_input_id(){
1243bf3… stephan 1575 static int inputID = 0;
1243bf3… stephan 1576 ++inputID;
1243bf3… stephan 1577 return mprintf("input-id-%d", inputID);
1243bf3… stephan 1578 }
1243bf3… stephan 1579
1243bf3… stephan 1580 /*
1243bf3… stephan 1581 ** Outputs a labeled checkbox element. zWrapperId is an optional ID
1243bf3… stephan 1582 ** value for the containing element (see below). zFieldName is the
1243bf3… stephan 1583 ** form element name. zLabel is the label for the checkbox. zValue is
1243bf3… stephan 1584 ** the optional value for the checkbox. zTip is an optional tooltip,
1243bf3… stephan 1585 ** which gets set as the "title" attribute of the outermost
1243bf3… stephan 1586 ** element. If isChecked is true, the checkbox gets the "checked"
1243bf3… stephan 1587 ** attribute set, else it is not.
1243bf3… stephan 1588 **
1243bf3… stephan 1589 ** Resulting structure:
1243bf3… stephan 1590 **
34f7fd7… stephan 1591 ** <div class='input-with-label' title={{zTip}} id={{zWrapperId}}>
1243bf3… stephan 1592 ** <input type='checkbox' name={{zFieldName}} value={{zValue}}
1243bf3… stephan 1593 ** id='A RANDOM VALUE'
1243bf3… stephan 1594 ** {{isChecked ? " checked : ""}}/>
1243bf3… stephan 1595 ** <label for='ID OF THE INPUT FIELD'>{{zLabel}}</label>
34f7fd7… stephan 1596 ** </div>
1243bf3… stephan 1597 **
1243bf3… stephan 1598 ** zLabel, and zValue are required. zFieldName, zWrapperId, and zTip
1243bf3… stephan 1599 ** are may be NULL or empty.
1243bf3… stephan 1600 **
1243bf3… stephan 1601 ** Be sure that the input-with-label CSS class is defined sensibly, in
1243bf3… stephan 1602 ** particular, having its display:inline-block is useful for alignment
1243bf3… stephan 1603 ** purposes.
1243bf3… stephan 1604 */
1243bf3… stephan 1605 void style_labeled_checkbox(const char * zWrapperId,
1243bf3… stephan 1606 const char *zFieldName, const char * zLabel,
1243bf3… stephan 1607 const char * zValue, int isChecked,
1243bf3… stephan 1608 const char * zTip){
1243bf3… stephan 1609 char * zLabelID = style_next_input_id();
34f7fd7… stephan 1610 CX("<div class='input-with-label'");
1243bf3… stephan 1611 if(zTip && *zTip){
1243bf3… stephan 1612 CX(" title='%h'", zTip);
1243bf3… stephan 1613 }
1243bf3… stephan 1614 if(zWrapperId && *zWrapperId){
1243bf3… stephan 1615 CX(" id='%s'",zWrapperId);
1243bf3… stephan 1616 }
1243bf3… stephan 1617 CX("><input type='checkbox' id='%s' ", zLabelID);
1243bf3… stephan 1618 if(zFieldName && *zFieldName){
1243bf3… stephan 1619 CX("name='%s' ",zFieldName);
1243bf3… stephan 1620 }
1243bf3… stephan 1621 CX("value='%T'%s/>",
1243bf3… stephan 1622 zValue ? zValue : "", isChecked ? " checked" : "");
34f7fd7… stephan 1623 CX("<label for='%s'>%h</label></div>", zLabelID, zLabel);
1243bf3… stephan 1624 fossil_free(zLabelID);
1243bf3… stephan 1625 }
1243bf3… stephan 1626
1243bf3… stephan 1627 /*
1243bf3… stephan 1628 ** Outputs a SELECT list from a compile-time list of integers.
1243bf3… stephan 1629 ** The vargs must be a list of (const char *, int) pairs, terminated
1243bf3… stephan 1630 ** with a single NULL. Each pair is interpreted as...
1243bf3… stephan 1631 **
1243bf3… stephan 1632 ** If the (const char *) is NULL, it is the end of the list, else
1243bf3… stephan 1633 ** a new OPTION entry is created. If the string is empty, the
1243bf3… stephan 1634 ** label and value of the OPTION is the integer part of the pair.
1243bf3… stephan 1635 ** If the string is not empty, it becomes the label and the integer
1243bf3… stephan 1636 ** the value. If that value == selectedValue then that OPTION
1243bf3… stephan 1637 ** element gets the 'selected' attribute.
1243bf3… stephan 1638 **
1243bf3… stephan 1639 ** Note that the pairs are not in (int, const char *) order because
1243bf3… stephan 1640 ** there is no well-known integer value which we can definitively use
1243bf3… stephan 1641 ** as a list terminator.
1243bf3… stephan 1642 **
1243bf3… stephan 1643 ** zWrapperId is an optional ID value for the containing element (see
1243bf3… stephan 1644 ** below).
1243bf3… stephan 1645 **
1243bf3… stephan 1646 ** zFieldName is the value of the form element's name attribute. Note
1243bf3… stephan 1647 ** that fossil prefers underscores over '-' for separators in form
1243bf3… stephan 1648 ** element names.
1243bf3… stephan 1649 **
1243bf3… stephan 1650 ** zLabel is an optional string to use as a "label" for the element
1243bf3… stephan 1651 ** (see below).
1243bf3… stephan 1652 **
1243bf3… stephan 1653 ** zTooltip is an optional value for the SELECT's title attribute.
1243bf3… stephan 1654 **
1243bf3… stephan 1655 ** The structure of the emitted HTML is:
1243bf3… stephan 1656 **
34f7fd7… stephan 1657 ** <div class='input-with-label' title={{zToolTip}} id={{zWrapperId}}>
1243bf3… stephan 1658 ** <label for='SELECT ELEMENT ID'>{{zLabel}}</label>
1243bf3… stephan 1659 ** <select id='RANDOM ID' name={{zFieldName}}>...</select>
34f7fd7… stephan 1660 ** </div>
1243bf3… stephan 1661 **
1243bf3… stephan 1662 ** Example:
1243bf3… stephan 1663 **
1243bf3… stephan 1664 ** style_select_list_int("my-grapes", "my_grapes", "Grapes",
1243bf3… stephan 1665 ** "Select the number of grapes",
1243bf3… stephan 1666 ** atoi(PD("my_field","0")),
1243bf3… stephan 1667 ** "", 1, "2", 2, "Three", 3,
1243bf3… stephan 1668 ** NULL);
275da70… danield 1669 **
1243bf3… stephan 1670 */
1243bf3… stephan 1671 void style_select_list_int(const char * zWrapperId,
1243bf3… stephan 1672 const char *zFieldName, const char * zLabel,
1243bf3… stephan 1673 const char * zToolTip, int selectedVal,
1243bf3… stephan 1674 ... ){
1243bf3… stephan 1675 char * zLabelID = style_next_input_id();
1243bf3… stephan 1676 va_list vargs;
1243bf3… stephan 1677
1243bf3… stephan 1678 va_start(vargs,selectedVal);
34f7fd7… stephan 1679 CX("<div class='input-with-label'");
1243bf3… stephan 1680 if(zToolTip && *zToolTip){
1243bf3… stephan 1681 CX(" title='%h'",zToolTip);
1243bf3… stephan 1682 }
1243bf3… stephan 1683 if(zWrapperId && *zWrapperId){
1243bf3… stephan 1684 CX(" id='%s'",zWrapperId);
1243bf3… stephan 1685 }
1243bf3… stephan 1686 CX(">");
1243bf3… stephan 1687 if(zLabel && *zLabel){
b82cb27… stephan 1688 CX("<label for='%s'>%h</label>", zLabelID, zLabel);
1243bf3… stephan 1689 }
1243bf3… stephan 1690 CX("<select name='%s' id='%s'>",zFieldName, zLabelID);
1243bf3… stephan 1691 while(1){
1243bf3… stephan 1692 const char * zOption = va_arg(vargs,char *);
1243bf3… stephan 1693 int v;
1243bf3… stephan 1694 if(NULL==zOption){
1243bf3… stephan 1695 break;
1243bf3… stephan 1696 }
1243bf3… stephan 1697 v = va_arg(vargs,int);
1243bf3… stephan 1698 CX("<option value='%d'%s>",
1243bf3… stephan 1699 v, v==selectedVal ? " selected" : "");
1243bf3… stephan 1700 if(*zOption){
1243bf3… stephan 1701 CX("%s", zOption);
1243bf3… stephan 1702 }else{
1243bf3… stephan 1703 CX("%d",v);
1243bf3… stephan 1704 }
1243bf3… stephan 1705 CX("</option>\n");
1243bf3… stephan 1706 }
1243bf3… stephan 1707 CX("</select>\n");
34f7fd7… stephan 1708 CX("</div>\n");
1243bf3… stephan 1709 va_end(vargs);
1243bf3… stephan 1710 fossil_free(zLabelID);
1243bf3… stephan 1711 }
1243bf3… stephan 1712
1243bf3… stephan 1713 /*
1243bf3… stephan 1714 ** The C-string counterpart of style_select_list_int(), this variant
1243bf3… stephan 1715 ** differs only in that its variadic arguments are C-strings in pairs
1243bf3… stephan 1716 ** of (optionLabel, optionValue). If a given optionLabel is an empty
1243bf3… stephan 1717 ** string, the corresponding optionValue is used as its label. If any
1243bf3… stephan 1718 ** given value matches zSelectedVal, that option gets preselected. If
1243bf3… stephan 1719 ** no options match zSelectedVal then the first entry is selected by
1243bf3… stephan 1720 ** default.
1243bf3… stephan 1721 **
1243bf3… stephan 1722 ** Any of (zWrapperId, zTooltip, zSelectedVal) may be NULL or empty.
1243bf3… stephan 1723 **
1243bf3… stephan 1724 ** Example:
1243bf3… stephan 1725 **
1243bf3… stephan 1726 ** style_select_list_str("my-grapes", "my_grapes", "Grapes",
1243bf3… stephan 1727 ** "Select the number of grapes",
1243bf3… stephan 1728 ** P("my_field"),
1243bf3… stephan 1729 ** "1", "One", "2", "Two", "", "3",
1243bf3… stephan 1730 ** NULL);
1243bf3… stephan 1731 */
1243bf3… stephan 1732 void style_select_list_str(const char * zWrapperId,
1243bf3… stephan 1733 const char *zFieldName, const char * zLabel,
1243bf3… stephan 1734 const char * zToolTip, char const * zSelectedVal,
1243bf3… stephan 1735 ... ){
1243bf3… stephan 1736 char * zLabelID = style_next_input_id();
1243bf3… stephan 1737 va_list vargs;
1243bf3… stephan 1738
1243bf3… stephan 1739 va_start(vargs,zSelectedVal);
1243bf3… stephan 1740 if(!zSelectedVal){
1243bf3… stephan 1741 zSelectedVal = __FILE__/*some string we'll never match*/;
1243bf3… stephan 1742 }
34f7fd7… stephan 1743 CX("<div class='input-with-label'");
1243bf3… stephan 1744 if(zToolTip && *zToolTip){
1243bf3… stephan 1745 CX(" title='%h'",zToolTip);
1243bf3… stephan 1746 }
1243bf3… stephan 1747 if(zWrapperId && *zWrapperId){
1243bf3… stephan 1748 CX(" id='%s'",zWrapperId);
1243bf3… stephan 1749 }
1243bf3… stephan 1750 CX(">");
1243bf3… stephan 1751 if(zLabel && *zLabel){
1243bf3… stephan 1752 CX("<label for='%s'>%h</label>", zLabelID, zLabel);
1243bf3… stephan 1753 }
1243bf3… stephan 1754 CX("<select name='%s' id='%s'>",zFieldName, zLabelID);
1243bf3… stephan 1755 while(1){
1243bf3… stephan 1756 const char * zLabel = va_arg(vargs,char *);
1243bf3… stephan 1757 const char * zVal;
1243bf3… stephan 1758 if(NULL==zLabel){
1243bf3… stephan 1759 break;
1243bf3… stephan 1760 }
1243bf3… stephan 1761 zVal = va_arg(vargs,char *);
1243bf3… stephan 1762 CX("<option value='%T'%s>",
1243bf3… stephan 1763 zVal, 0==fossil_strcmp(zVal, zSelectedVal) ? " selected" : "");
1243bf3… stephan 1764 if(*zLabel){
1243bf3… stephan 1765 CX("%s", zLabel);
1243bf3… stephan 1766 }else{
1243bf3… stephan 1767 CX("%h",zVal);
1243bf3… stephan 1768 }
1243bf3… stephan 1769 CX("</option>\n");
1243bf3… stephan 1770 }
1243bf3… stephan 1771 CX("</select>\n");
34f7fd7… stephan 1772 CX("</div>\n");
1243bf3… stephan 1773 va_end(vargs);
1243bf3… stephan 1774 fossil_free(zLabelID);
1243bf3… stephan 1775 }
1243bf3… stephan 1776
34f7fd7… stephan 1777 /*
6854244… drh 1778 ** Generate a <script> with an appropriate nonce.
6854244… drh 1779 **
6854244… drh 1780 ** zOrigin and iLine are the source code filename and line number
6854244… drh 1781 ** that generated this request.
6854244… drh 1782 */
6854244… drh 1783 void style_script_begin(const char *zOrigin, int iLine){
6854244… drh 1784 const char *z;
6854244… drh 1785 for(z=zOrigin; z[0]!=0; z++){
6854244… drh 1786 if( z[0]=='/' || z[0]=='\\' ){
6854244… drh 1787 zOrigin = z+1;
6854244… drh 1788 }
6854244… drh 1789 }
6854244… drh 1790 CX("<script nonce='%s'>/* %s:%d */\n", style_nonce(), zOrigin, iLine);
6854244… drh 1791 }
6854244… drh 1792
275da70… danield 1793 /* Generate the closing </script> tag
070716d… stephan 1794 */
6854244… drh 1795 void style_script_end(void){
6854244… drh 1796 CX("</script>\n");
070716d… stephan 1797 }
070716d… stephan 1798
070716d… stephan 1799 /*
070716d… stephan 1800 ** Emits a NOSCRIPT tag with an error message stating that JS is
070716d… stephan 1801 ** required for the current page. This "should" be called near the top
070716d… stephan 1802 ** of pages which *require* JS. The inner DIV has the CSS class
070716d… stephan 1803 ** 'error' and can be styled via a (noscript > .error) CSS selector.
070716d… stephan 1804 */
070716d… stephan 1805 void style_emit_noscript_for_js_page(void){
070716d… stephan 1806 CX("<noscript><div class='error'>"
070716d… stephan 1807 "This page requires JavaScript (ES2015, a.k.a. ES6, or newer)."
070716d… stephan 1808 "</div></noscript>");
cadfcba… drh 1809 }
cadfcba… drh 1810
cadfcba… drh 1811 /*
cadfcba… drh 1812 ** SETTING: robots-txt width=70 block-text keep-empty
cadfcba… drh 1813 **
cadfcba… drh 1814 ** This setting is the override value for the /robots.txt file that
cadfcba… drh 1815 ** Fossil returns when run as a stand-alone server for a domain. As
cadfcba… drh 1816 ** Fossil is seldom run as a stand-alone server (and is more commonly
cadfcba… drh 1817 ** deployed as a CGI or SCGI or behind a reverse proxy) this setting
cadfcba… drh 1818 ** rarely needed. A reasonable default robots.txt is sent if this
cadfcba… drh 1819 ** setting is empty.
cadfcba… drh 1820 */
cadfcba… drh 1821
cadfcba… drh 1822 /*
cadfcba… drh 1823 ** WEBPAGE: robots.txt
cadfcba… drh 1824 **
cadfcba… drh 1825 ** Return text/plain which is the content of the "robots-txt" setting, if
cadfcba… drh 1826 ** such a setting exists and is non-empty. Or construct an RFC-9309 complaint
cadfcba… drh 1827 ** robots.txt file and return that if there is not "robots.txt" setting.
cadfcba… drh 1828 **
cadfcba… drh 1829 ** This is useful for robot exclusion in cases where Fossil is run as a
cadfcba… drh 1830 ** stand-alone server in its own domain. For the more common case where
cadfcba… drh 1831 ** Fossil is run as a CGI, or SCGI, or a server that responding to a reverse
cadfcba… drh 1832 ** proxy, the returns robots.txt file will not be at the top level of the
cadfcba… drh 1833 ** domain, and so it will be pointless.
cadfcba… drh 1834 */
cadfcba… drh 1835 void robotstxt_page(void){
cadfcba… drh 1836 const char *z;
cadfcba… drh 1837 static const char *zDflt =
cadfcba… drh 1838 "User-agent: *\n"
cadfcba… drh 1839 "Allow: /doc\n"
cadfcba… drh 1840 "Allow: /home\n"
cadfcba… drh 1841 "Allow: /forum\n"
cadfcba… drh 1842 "Allow: /technote\n"
cadfcba… drh 1843 "Allow: /tktview\n"
cadfcba… drh 1844 "Allow: /wiki\n"
cadfcba… drh 1845 "Allow: /uv/\n"
cadfcba… drh 1846 "Allow: /$\n"
cadfcba… drh 1847 "Disallow: /*\n"
cadfcba… drh 1848 ;
cadfcba… drh 1849 z = db_get("robots-txt",zDflt);
cadfcba… drh 1850 cgi_set_content_type("text/plain");
cadfcba… drh 1851 cgi_append_content(z, -1);
1243bf3… stephan 1852 }

Keyboard Shortcuts

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