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>]&nbsp;
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     @ &nbsp;
438   }
439   @ </td></tr>
440   if( nAction ){
441     int j;
442     int nChar;
443 
444     @ <tr bgcolor="%s(BG4)" class="bkgnd4">
445     @ <td>&nbsp;</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>]&nbsp;
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       @ &nbsp;
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 &copy; 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,"&bull;");
564   **   @ %s(z)
565   */
566   if( !strcmp(zIcon,"arrow") ){
567     cgi_printf("<font color=\"blue\">&rsaquo;</font>");
568   }else if( !strcmp(zIcon,"box") ){
569     cgi_printf("<font color=\"#007878\">&curren;</font>");
570   }else if( !strcmp(zIcon,"ck") ){
571     cgi_printf("<font color=\"green\">&radic;</font>");
572   }else if( !strcmp(zIcon,"dia") ){
573     cgi_printf("<font color=\"orange\">&diams;</font>");
574   }else if( !strcmp(zIcon,"dot") ){
575     cgi_printf("<font color=\"blue\">&bull;</font>");
576   }else if( !strcmp(zIcon,"ptr1") ){
577     cgi_printf("<font color=\"purple\">&raquo;</font>");
578   }else if( !strcmp(zIcon,"star") ){
579     cgi_printf("<font color=\"#8C80A300\">&#42;</font>");
580   }else if( !strcmp(zIcon,"x") ){
581     cgi_printf("<font color=\"red\">&times;</font>");
582   }else if( !strcmp(zIcon,"del") ){
583     cgi_printf("<font color=\"red\">&times;</font>");
584   }else if( !strcmp(zIcon,"file") ){
585     cgi_printf("<font color=\"black\">&bull;</font>");
586   }else if( !strcmp(zIcon,"dir") ){
587     cgi_printf("<font color=\"green\">&raquo</font>");
588   }else if( !strcmp(zIcon,"backup") ){
589     cgi_printf("<font color=\"black\">&laquo;</font>");
590   }
591 }
592