1 /*
2 ** Copyright (c) 2002 D. Richard Hipp
3 **
4 ** This program is free software; you can redistribute it and/or
5 ** modify it under the terms of the GNU General Public
6 ** License as published by the Free Software Foundation; either
7 ** version 2 of the License, or (at your option) any later version.
8 **
9 ** This program is distributed in the hope that it will be useful,
10 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 ** General Public License for more details.
13 **
14 ** You should have received a copy of the GNU General Public
15 ** License along with this library; if not, write to the
16 ** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 ** Boston, MA 02111-1307, USA.
18 **
19 ** Author contact information:
20 ** drh@hwaci.com
21 ** http://www.hwaci.com/drh/
22 **
23 *******************************************************************************
24 **
25 ** Routines shared by many pages
26 */
27 #include "config.h"
28 #include "common.h"
29
30 /*
31 ** Output a string with the following substitutions:
32 **
33 ** %T The title of the current page.
34 ** %N Project name
35 ** %V CVSTrac version number
36 ** %B Base URL
37 ** %% The character '%'
38 */
output_with_subst(const char * zText,const char * zTitle)39 static void output_with_subst(const char *zText, const char *zTitle){
40 int i;
41 while( zText[0] ){
42 for(i=0; zText[i] && zText[i]!='%'; i++){}
43 if( i>0 ) cgi_append_content(zText, i);
44 if( zText[i]==0 ) break;
45 switch( zText[i+1] ){
46 case 'T':
47 zText += i+2;
48 cgi_printf("%h", zTitle);
49 break;
50 case 'N':
51 zText += i+2;
52 cgi_printf("%h", g.zName);
53 break;
54 case 'V':
55 zText += i+2;
56 cgi_printf("%h", "@VERSION@");
57 break;
58 case 'B':
59 zText += i+2;
60 cgi_printf("%h", g.zBaseURL);
61 break;
62 case '%':
63 zText += i+2;
64 cgi_printf("%%");
65 break;
66 default:
67 zText += i+1;
68 cgi_printf("%%");
69 break;
70 }
71 }
72 }
73
74 /*
75 ** Read the whole contents of a file into memory obtained from
76 ** malloc(). Return a pointer to the file contents. Be sure
77 ** the string is null terminated.
78 **
79 ** A NULL pointer is returned if the file could not be read
80 ** for any reason.
81 */
common_readfile(const char * zFilename)82 char *common_readfile(const char *zFilename) {
83 FILE *fp;
84 char *zContent = NULL;
85 size_t n;
86
87 if ((fp = fopen(zFilename, "r")) != NULL) {
88 fseek(fp, 0, SEEK_END);
89 if ((n = ftell(fp)) > 0) {
90 if ((zContent = (char *)malloc(n+1)) == NULL) {
91 fclose(fp);
92 return NULL;
93 }
94 fseek(fp, 0, SEEK_SET);
95 if ((n = fread(zContent, 1, n, fp)) == 0) {
96 free(zContent);
97 fclose(fp);
98 return NULL;
99 }
100 zContent[n] = '\0';
101 }
102 else {
103 zContent = strdup("");
104 }
105 fclose(fp);
106 }
107 return zContent;
108 }
109
110 /*
111 ** Read the whole contents of a file pointer into memory obtained from
112 ** malloc(). Return a pointer to the file contents. Be sure
113 ** the string is null terminated.
114 **
115 ** A NULL pointer is returned if the file could not be read
116 ** for any reason. The caller is responsible for closing the file.
117 */
common_readfp(FILE * fp)118 char *common_readfp(FILE* fp) {
119 char *zContent = NULL;
120 char *z;
121 size_t n = 0, m = 0;
122 size_t rc;
123
124 while( fp && !feof(fp) && !ferror(fp) ) {
125 if( (n+1)>=m ){
126 m = m ? (m*2) : 1024;
127 z = realloc(zContent, m);
128 if( z==NULL ){
129 if( zContent!=NULL ) free(zContent);
130 return NULL;
131 }
132 zContent = z;
133 }
134 rc = fread(&zContent[n], 1, m-(n+1), fp);
135 if( rc>0 ){
136 n += rc;
137 }
138 zContent[n] = 0;
139 }
140
141 return zContent;
142 }
143
144 /*
145 ** Generate an error message screen.
146 */
common_err(const char * zFormat,...)147 void common_err(const char *zFormat, ...){
148 char *zMsg;
149
150 va_list ap;
151 va_start(ap, zFormat);
152 zMsg = vmprintf(zFormat, ap);
153 va_end(ap);
154 cgi_reset_content();
155 common_standard_menu(0,0);
156 common_header("Oops!");
157 @ <p>The following error has occurred:</p>
158 @ <blockquote>%h(zMsg)</blockquote>
159 if( g.okSetup ){
160 @ <p>Query parameters:<p>
161 cgi_print_all();
162 }
163 common_footer();
164 cgi_append_header("Pragma: no-cache\r\n");
165 cgi_reply();
166 exit(0);
167 }
168
169 /*
170 ** The menu on the top bar of every page is defined by the following
171 ** variables. Links are navigation links to other pages. Actions are
172 ** links which apply to the current page.
173 */
174 static const char *azLink[50];
175 static int nLink = 0;
176
177 static const char *azAction[50];
178 static int nAction = 0;
179
default_browse_url(void)180 const char *default_browse_url(void){
181 if( !strncmp(g.zPath,"dir",3) ) {
182 /* If the _current_ path is already a browse path, go with it */
183 return g.zPath;
184 }else{
185 /* If cookie is set, it overrides default setting */
186 char *zBrowseUrlCookieName = mprintf("%t_browse_url",g.zName);
187 const char *zCookieValue = P(zBrowseUrlCookieName);
188 free(zBrowseUrlCookieName);
189
190 if( zCookieValue ) {
191 if( !strcmp("dir",zCookieValue) ){
192 return "dir";
193 }else if( !strcmp("dirview",zCookieValue) ){
194 return "dirview";
195 }
196 }
197 }
198 return db_config("default_browse_url","dir");
199 }
200
201 /*
202 ** Prepopulate the set of navigation items with a standard set that includes
203 ** links to all top-level pages except for zOmit. If zOmit is NULL then
204 ** include all items.
205 **
206 ** If zSrchUrl is not NULL then use it as the URL for the "Search" menu
207 ** option.
208 */
common_standard_menu(const char * zOmit,const char * zSrchUrl)209 void common_standard_menu(const char *zOmit, const char *zSrchUrl){
210 const char *zLimit;
211 if( g.okNewTkt ){
212 azLink[nLink++] = "tktnew";
213 azLink[nLink++] = "Ticket";
214 }
215 if( g.okCheckout ){
216 azLink[nLink++] = default_browse_url();
217 azLink[nLink++] = "Browse";
218 }
219 if( g.okRead ){
220 azLink[nLink++] = "reportlist";
221 azLink[nLink++] = "Reports";
222 }
223 if( g.okRdWiki || g.okRead || g.okCheckout ){
224 azLink[nLink++] = "timeline";
225 azLink[nLink++] = "Timeline";
226 }
227 if( g.okRdWiki ){
228 azLink[nLink++] = "wiki";
229 azLink[nLink++] = "Wiki";
230 }
231 if( g.okRdWiki || g.okRead || g.okCheckout ){
232 azLink[nLink++] = zSrchUrl ? zSrchUrl : "search";
233 azLink[nLink++] = "Search";
234 }
235 if( g.okCheckin ){
236 azLink[nLink++] = "msnew";
237 azLink[nLink++] = "Milestone";
238 }
239 if( g.okWrite && !g.isAnon ){
240 azLink[nLink++] = "userlist";
241 azLink[nLink++] = "Users";
242 }
243 if( g.okAdmin ){
244 azLink[nLink++] = "setup";
245 azLink[nLink++] = "Setup";
246 }
247 azLink[nLink++] = "login";
248 if( g.isAnon ){
249 azLink[nLink++] = "Login";
250 }else{
251 azLink[nLink++] = "Logout";
252 }
253 if( g.isAnon && (zLimit = db_config("throttle",0))!=0 && atof(zLimit)>0.0 ){
254 azLink[nLink++] = "honeypot";
255 azLink[nLink++] = "0Honeypot";
256 }
257 if( nLink>2 ){
258 azLink[nLink++] = "index";
259 azLink[nLink++] = "Home";
260 }
261 if( zOmit ){
262 int j;
263 for(j=0; j<nLink; j+=2){
264 if( azLink[j][0]==zOmit[0] && strcmp(zOmit,azLink[j])==0 ){
265 azLink[j] = azLink[nLink-2];
266 azLink[j+1] = azLink[nLink-1];
267 nLink -= 2;
268 break;
269 }
270 }
271 }
272 azLink[nLink] = 0;
273 }
274
275 /*
276 ** Add a new navigation entry to the menu that will appear at the top of the
277 ** page. zUrl is the URL that we jump to when the user clicks on
278 ** the link and zName is the text that appears in the link.
279 */
common_add_nav_item(const char * zUrl,const char * zName)280 void common_add_nav_item(
281 const char *zUrl, /* The URL to be appended */
282 const char *zName /* The menu entry name */
283 ){
284 azLink[nLink++] = zUrl;
285 azLink[nLink++] = zName;
286 azLink[nLink] = 0;
287 }
288
289 /*
290 ** Add a new action entry to the menu that will appear at the top of the
291 ** page. zUrl is the URL that we jump to when the user clicks on
292 ** the link and zName is the text that appears in the link.
293 */
common_add_action_item(const char * zUrl,const char * zName)294 void common_add_action_item(
295 const char *zUrl, /* The URL to be appended */
296 const char *zName /* The menu entry name */
297 ){
298 azAction[nAction++] = zUrl;
299 azAction[nAction++] = zName;
300 azAction[nAction] = 0;
301 }
302
303 /*
304 ** Add a "help" link to a specific Wiki page. Currently, we place the help
305 ** link in the "link" section rather than "action" section, although arguably
306 ** it's context dependent. However, there should be a help link on pretty much
307 ** any page, so...
308 */
common_add_help_item(const char * zWikiPage)309 void common_add_help_item(
310 const char *zWikiPage /* name of the help page */
311 ){
312 if( g.okRdWiki
313 && db_exists("SELECT 1 FROM wiki WHERE name='%q'", zWikiPage)){
314 azLink[nLink++] = mprintf("wiki?p=%s", zWikiPage);
315 azLink[nLink++] = "Help";
316 azLink[nLink] = 0;
317 }
318 }
319
320 /*
321 ** Replace an existing navigation item with the new version given here.
322 ** We don't have a corresponding function for the action menu since it's
323 ** never prepopulated.
324 */
common_replace_nav_item(const char * zUrl,const char * zName)325 void common_replace_nav_item(
326 const char *zUrl, /* The new URL */
327 const char *zName /* The menu entry name to be replaced */
328 ){
329 int i;
330 for(i=0; i<nLink; i+=2){
331 if( strcmp(azLink[i+1],zName)==0 ){
332 if( zUrl==0 ){
333 azLink[i] = azLink[nLink-2];
334 azLink[i+1] = azLink[nLink-1];
335 nLink--;
336 }else{
337 azLink[i] = zUrl;
338 }
339 break;
340 }
341 }
342 }
343
344 /*
345 ** Function used for sorting entries in azLinks[]
346 */
link_compare(const void * a,const void * b)347 static int link_compare(const void *a, const void *b){
348 const char **pA = (const char **)a;
349 const char **pB = (const char **)b;
350 return strcmp(pA[1], pB[1]);
351 }
352
353 /*
354 ** Generate an HTML header common to all web pages. zTitle is the
355 ** title for the page, zUrl (optional) is the link for that title.
356 ** azLink is an array of URI/Name pairs that
357 ** are used to generate navigation quick-links on the title bar. azAction
358 ** is an array of context-dependent actions applicable to the current page.
359 */
common_vlink_header(const char * zUrl,const char * zTitle,va_list ap)360 void common_vlink_header(const char *zUrl, const char *zTitle, va_list ap){
361 int i = 0;
362 int brk;
363 const char *zHeader = 0;
364 char *zTitleTxt;
365
366 zTitleTxt = vmprintf(zTitle, ap);
367 zHeader = db_config("header", HEADER);
368 if( zHeader && zHeader[0] ){
369 char *z;
370 if( zHeader[0]=='/' && (z = common_readfile(zHeader))!=0 ){
371 zHeader = z;
372 }
373 output_with_subst(zHeader, zTitleTxt);
374 }else{
375 output_with_subst(HEADER, zTitleTxt);
376 }
377 @ <table width="100%%" cellpadding=2 border=0>
378 @ <tr><td bgcolor="%s(BORDER1)" class="border1">
379 @ <table width="100%%" border=0 cellpadding=2 cellspacing=0>
380 @ <tr bgcolor="%s(BG1)" class="bkgnd1">
381 @ <td valign="top" align="left">
382 if( zUrl ){
383 @ <big><b>%h(g.zName) -
384 @ <a rel="nofollow" href="%h(zUrl)">%h(zTitleTxt)</a></b></big><br>
385 }else{
386 @ <big><b>%h(g.zName) - %h(zTitleTxt)</b></big><br>
387 }
388 if( !g.isAnon ){
389 @ <small><a href="logout" title="Logout %h(g.zUser)">Logged in</a> as
390 if( !strcmp(g.zUser,"setup") ){
391 @ setup
392 }else{
393 @ <a href="wiki?p=%T(g.zUser)">%h(g.zUser)</a>
394 }
395 @ </small>
396 }else{
397 /* We don't want to be redirected back to captcha page, but ratehr to
398 ** one from which we were redirected to captcha in the first place.
399 */
400 const char *zUri = (P("cnxp")!=0) ? P("cnxp") : getenv("REQUEST_URI");
401 @ <a href="honeypot"><small><notatag arg="meaningless"></small></a>
402 @ <small><a href="login?nxp=%T(zUri)" title="Log in">Not logged in</a></small>
403 }
404 @ </td>
405 @ <td valign="bottom" align="right">
406 if( nLink ){
407 int j;
408 int nChar;
409 nLink /= 2;
410 qsort(azLink, nLink, 2*sizeof(azLink[0]), link_compare);
411 nChar = 0;
412 for(i=0; azLink[i]; i+=2){
413 nChar += strlen(azLink[i+1]) + 3;
414 }
415 if( nChar<=60 ){
416 brk = nChar;
417 }else if( nChar<=120 ){
418 brk = nChar/2;
419 }else{
420 brk = nChar/3;
421 }
422 nChar = 0;
423 @ <nobr>
424 for(i=0, j=1; azLink[i] && azLink[i+1]; i+=2, j++){
425 const char *z = azLink[i+1];
426 if( z[0]<'A' ) z++;
427 @ [<a href="%h(azLink[i])">%h(z)</a>]
428 nChar += strlen(azLink[i+1]) + 3;
429 if( nChar>=brk && azLink[i+2] ){
430 nChar = 0;
431 @ </nobr><br><nobr>
432 }
433 }
434 @ </nobr>
435 }
436 if( i==0 ){
437 @
438 }
439 @ </td></tr>
440 if( nAction ){
441 int j;
442 int nChar;
443
444 @ <tr bgcolor="%s(BG4)" class="bkgnd4">
445 @ <td> </td>
446 @ <td valign="bottom" align="right">
447
448 nAction /= 2;
449 qsort(azAction, nAction, 2*sizeof(azAction[0]), link_compare);
450 nChar = 0;
451 for(i=0; azAction[i]; i+=2){
452 nChar += strlen(azAction[i+1]) + 3;
453 }
454 if( nChar<=60 ){
455 brk = nChar;
456 }else if( nChar<=120 ){
457 brk = nChar/2;
458 }else{
459 brk = nChar/3;
460 }
461 nChar = 0;
462 @ <nobr>
463 for(i=0, j=1; azAction[i] && azAction[i+1]; i+=2, j++){
464 const char *z = azAction[i+1];
465 if( z[0]<'A' ) z++;
466 @ [<a href="%h(azAction[i])" rel="nofollow">%h(z)</a>]
467 nChar += strlen(azAction[i+1]) + 3;
468 if( nChar>=brk && azAction[i+2] ){
469 nChar = 0;
470 @ </nobr><br><nobr>
471 }
472 }
473 @ </nobr>
474 if( i==0 ){
475 @
476 }
477 @ </td></tr>
478 }
479 @ </table>
480 @ </td></tr></table>
481 @ <div id="body">
482 free(zTitleTxt);
483 }
484
485 /*
486 ** Generate an HTML header common to all web pages. zTitle is the
487 ** title for the page, zUrl (optional) is the link for that title.
488 */
common_link_header(const char * zUrl,const char * zTitle,...)489 void common_link_header(const char *zUrl, const char *zTitle,...){
490 va_list ap;
491 va_start(ap,zTitle);
492 assert(zUrl != NULL);
493 common_vlink_header(zUrl,zTitle,ap);
494 va_end(ap);
495 }
496
497 /*
498 ** Generate an HTML header common to all web pages. zTitle is the
499 ** title for the page.
500 */
common_header(const char * zTitle,...)501 void common_header(const char *zTitle,...){
502 va_list ap;
503 va_start(ap,zTitle);
504 common_vlink_header(NULL,zTitle,ap);
505 va_end(ap);
506 }
507
508 /*
509 ** Generate a common footer
510 */
common_footer(void)511 void common_footer(void){
512 const char *zFooter;
513 @ </div>
514 zFooter = db_config("footer", FOOTER);
515 if( zFooter && zFooter[0] ){
516 char *z;
517 if( zFooter[0]=='/' && (z = common_readfile(zFooter))!=0 ){
518 zFooter = z;
519 }
520 output_with_subst(zFooter, "");
521 }else{
522 output_with_subst(FOOTER, "");
523 }
524 }
525
526 /*
527 ** Generate an about screen
528 **
529 ** WEBPAGE: /about
530 */
common_about(void)531 void common_about(void){
532 login_check_credentials();
533 common_add_nav_item("index", "Home");
534 common_header("About This Server", azLink);
535 @ <p>This website is implemented using CVSTrac version @VERSION@.</p>
536 @
537 @ <p>CVSTrac implements a patch-set and
538 @ bug tracking system for %h(g.scm.zName).
539 @ For additional information, visit the CVSTrac homepage at</p>
540 @ <blockquote>
541 @ <a href="http://www.cvstrac.org/">http://www.cvstrac.org/</a>
542 @ </blockquote>
543 @
544 @ <p>Copyright © 2002-2006 <a href="mailto:drh@hwaci.com">
545 @ D. Richard Hipp</a>.
546 @ The CVSTrac server is released under the terms of the GNU
547 @ <a href="http://www.gnu.org/copyleft/gpl.html">
548 @ General Public License</a>.</p>
549 common_footer();
550 }
551
552 /*
553 ** Generate an "icon" using HTML entity characters (i.e. "gt" or
554 ** numeric value like "#62").
555 */
common_icon(const char * zIcon)556 void common_icon(const char* zIcon){
557 /* use HTML 4.0 character entities to create symbolic "icons". However,
558 ** it seems that some browsers (Konqueror, maybe Safari) don't support
559 ** the full set of entities. Some compromises are made.
560 **
561 ** These would all fit very nicely into the config table. Then this
562 ** function could be reduced to just:
563 ** z=db_config(zIcon,"•");
564 ** @ %s(z)
565 */
566 if( !strcmp(zIcon,"arrow") ){
567 cgi_printf("<font color=\"blue\">›</font>");
568 }else if( !strcmp(zIcon,"box") ){
569 cgi_printf("<font color=\"#007878\">¤</font>");
570 }else if( !strcmp(zIcon,"ck") ){
571 cgi_printf("<font color=\"green\">√</font>");
572 }else if( !strcmp(zIcon,"dia") ){
573 cgi_printf("<font color=\"orange\">♦</font>");
574 }else if( !strcmp(zIcon,"dot") ){
575 cgi_printf("<font color=\"blue\">•</font>");
576 }else if( !strcmp(zIcon,"ptr1") ){
577 cgi_printf("<font color=\"purple\">»</font>");
578 }else if( !strcmp(zIcon,"star") ){
579 cgi_printf("<font color=\"#8C80A300\">*</font>");
580 }else if( !strcmp(zIcon,"x") ){
581 cgi_printf("<font color=\"red\">×</font>");
582 }else if( !strcmp(zIcon,"del") ){
583 cgi_printf("<font color=\"red\">×</font>");
584 }else if( !strcmp(zIcon,"file") ){
585 cgi_printf("<font color=\"black\">•</font>");
586 }else if( !strcmp(zIcon,"dir") ){
587 cgi_printf("<font color=\"green\">»</font>");
588 }else if( !strcmp(zIcon,"backup") ){
589 cgi_printf("<font color=\"black\">«</font>");
590 }
591 }
592