/*
** Copyright (c) 2002 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code used to browse through the CVS repository.
*/
#include "config.h"
#include "browse.h"
#include <sys/times.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
/*
** This routine generates an HTML page that describes the complete
** revision history for a single file.
*/
static void revision_history(const char *zName, int showMilestones){
char **az;
int i;
const char *zTail;
if( zName[0]=='/' ) zName++; /* Be nice to TortoiceCVS */
zTail = strrchr(zName, '/');
if( zTail ) zTail++;
/* @ <h2>History of /%h(zName)</h2> */
if( showMilestones ){
common_add_action_item(mprintf("rlog?f=%t",zName), "Omit Milestones");
az = db_query("SELECT filechng.cn, date, vers, nins, ndel, prevvers,"
" message, user, branch "
"FROM filechng, chng "
"WHERE filename='%q' AND filechng.cn=chng.cn "
"UNION ALL "
"SELECT '',date,cn,NULL,NULL,NULL,message,user,branch "
"FROM chng "
"WHERE milestone=1 "
"ORDER BY 2 DESC", zName);
} else {
common_add_action_item(mprintf("rlog?f=%t&sms=1",zName), "Show Milestones");
az = db_query("SELECT filechng.cn, date, vers, nins, ndel, prevvers,"
" message, user, branch "
"FROM filechng, chng "
"WHERE filename='%q' AND filechng.cn=chng.cn "
"ORDER BY date DESC", zName);
}
common_header("History for /%h", zName);
cgi_printf("<table cellpadding=0 cellspacing=0 border=0>\n");
for(i=0; az[i]; i+=9){
time_t t;
struct tm *pTm;
char zDate[100];
t = atoi(az[i+1]);
pTm = localtime(&t);
strftime(zDate, sizeof(zDate), "%Y-%b-%d %H:%M", pTm);
if( i==0 ){
cgi_printf("<thread><tr><th>Date</th><th width=80>Version</th>\n"
" <th>Description</th></tr>\n"
"<tbody>\n");
}
if( i%2 ){
cgi_printf("<tr bgcolor=\"%s\" class=\"bkgnd4\">\n",BG4);
}else{
cgi_printf("<tr>\n");
}
cgi_printf("<td align=\"right\" valign=\"top\"><nobr>%s</nobr></td>\n",zDate);
if( az[i][0]==0 ){
cgi_printf("<td align=\"center\" valign=\"top\">\n");
common_icon("box");
cgi_printf("</td>\n");
if( az[i+8] && az[i+8][0] ){
cgi_printf("<td align=\"left\" bgcolor=\"%s\" class=\"bkgnd5\">\n"
"Milestone\n",BG5);
output_chng(atoi(az[i+2]));
cgi_printf(" on branch %h:\n",az[i+8]);
}else{
cgi_printf("<td align=\"left\">\n"
"Milestone\n");
output_chng(atoi(az[i+2]));
}
}else{
cgi_printf("<td valign=\"top\" align=\"center\"> \n"
"<a href=\"fileview?f=%T&v=%T\">\n"
" %h</a>\n"
" </td>\n",zName,az[i+2],printable_vers(az[i+2]));
if( az[i+8] && az[i+8][0] ){
cgi_printf("<td bgcolor=\"%s\" class=\"bkgnd5\">Check-in\n",BG5);
output_chng(atoi(az[i]));
cgi_printf(" on branch %h:\n",az[i+8]);
}else{
cgi_printf("<td>Check-in\n");
output_chng(atoi(az[i]));
cgi_printf(":\n");
}
}
output_formatted(az[i+6], 0);
cgi_printf(" By %z.\n",format_user(az[i+7]));
if( az[i][0]!=0 ){ /* Can't diff a Milestone */
if( g.okCheckout && az[i+5] && az[i+5][0] ){
cgi_printf("<a href=\"filediff?f=%T&v1=%T&v2=%T\">\n"
"(diff)</a>\n",zName,az[i+5],az[i+2]);
}
}
cgi_printf("</td></tr>\n");
}
if( i==0 ){
cgi_printf("<tr><td>Nothing is known about this file</td></tr>\n");
}
cgi_printf("</table>\n");
}
/*
** Adds all appropriate action bar links for file tools
*/
static void add_file_tools(
const char *zExcept,
const char *zFile,
const char *zVers1,
const char *zVers2
){
int i;
char *zLink;
char **azTools;
db_add_functions();
azTools = db_query("SELECT tool.name FROM tool,user "
"WHERE tool.object='file' AND user.id='%q' "
" AND cap_and(tool.perms,user.capabilities)!=''",
g.zUser);
for(i=0; azTools[i]; i++){
if( zExcept && 0==strcmp(zExcept,azTools[i]) ) continue;
zLink = mprintf("filetool?t=%T&f=%T%s%T%s%T",
azTools[i], zFile,
zVers1?"&v1=":"", zVers1?zVers1:"",
zVers2?"&v2=":"", zVers2?zVers2:"");
common_add_action_item(zLink, azTools[i]);
}
}
/*
** Adds all appropriate action bar links for directory tools
*/
static void add_dir_tools( const char *zExcept, const char *zDir ){
int i;
char *zLink;
char **azTools;
db_add_functions();
azTools = db_query("SELECT tool.name FROM tool,user "
"WHERE tool.object='dir' AND user.id='%q' "
" AND cap_and(tool.perms,user.capabilities)!=''",
g.zUser);
for(i=0; azTools[i]; i++){
if( zExcept && 0==strcmp(zExcept,azTools[i]) ) continue;
zLink = mprintf("dtool?t=%T&d=%T", azTools[i], zDir);
common_add_action_item(zLink, azTools[i]);
}
}
/*
** WEBPAGE: /dtool
**
** Execute an external tool on a given directory
*/
void dirtool(void){
const char *zDir = PD("d","");
const char *zTool = P("t");
char *zDirUrl;
char *zAction;
const char *azSubst[32];
int n = 0;
if( zDir==0 || zTool==0 ) cgi_redirect("index");
login_check_credentials();
if( !g.okCheckout ){ login_needed(); return; }
throttle(1,0);
history_update(0);
zDirUrl = mprintf("%T?d=%T", default_browse_url(), zDir);
zAction = db_short_query("SELECT command FROM tool "
"WHERE name='%q'", zTool);
if( zAction==0 || zAction[0]==0 ) cgi_redirect(zDirUrl);
common_standard_menu(0, "search?f=1");
common_add_action_item(zDirUrl,"Directory");
add_dir_tools(zTool,zDir);
common_header("%s for /%h", zTool, zDir);
azSubst[n++] = "F";
azSubst[n++] = quotable_string(zDir);
azSubst[n++] = 0;
n = execute_tool(zTool,zAction,0,azSubst);
free(zAction);
if( n<=0 ){
cgi_redirect(zDirUrl);
}
common_footer();
}
/*
** WEBPAGE: /filetool
**
** Execute an external tool on a given target
*/
void filetool(void){
const char *zFile = P("f");
const char *zVers1 = PD("v1","");
const char *zVers2 = PD("v2","");
const char *zTool = P("t");
char *zAction;
const char *azSubst[32];
int n = 0;
if( zFile==0 || zTool==0 ) cgi_redirect("index");
login_check_credentials();
if( !g.okCheckout ){ login_needed(); return; }
throttle(1,0);
history_update(0);
zAction = db_short_query("SELECT command FROM tool "
"WHERE name='%q'", zTool);
if( zAction==0 || zAction[0]==0 ) cgi_redirect("index");
common_standard_menu(0, "search?f=1");
common_add_action_item(mprintf("rlog?f=%T", zFile), "History");
add_file_tools(zTool,zFile,zVers1,zVers2);
common_header("%s for /%h", zTool, zFile);
cgi_printf("<a href=\"rlog?f=%T\">%h</a>\n",zFile,zFile);
if( zVers1 ){
char *zFV = mprintf("fileview?f=%T&v=%T", zFile, zVers1);
cgi_printf("<a href=\"%T\">%h</a><hr>\n",zFV,zVers1);
}
azSubst[n++] = "F";
azSubst[n++] = quotable_string(zFile);
azSubst[n++] = "V1";
azSubst[n++] = quotable_string(zVers1);
azSubst[n++] = "V2";
azSubst[n++] = quotable_string(zVers2);
azSubst[n++] = 0;
n = execute_tool(zTool,zAction,0,azSubst);
free(zAction);
if( n<=0 ){
cgi_redirect(mprintf("rlog?f=%T",zFile));
}
common_footer();
}
/*
** WEBPAGE: /rlog
**
** This page lists the revision history for a single file. Hyperlinks
** allow the file to be diffed or annotated.
*/
void browse_rlog(void){
char *zDir, *z;
int showMilestones;
const char *zFile;
login_check_credentials();
if( !g.okCheckout ){ login_needed(); return; }
throttle(1,0);
common_standard_menu(0, "search?f=1");
showMilestones = atoi(PD("sms","0"));
history_update(0);
zFile = PD("f","");
/* Make sure we always have '/' in zFile, otherwise link to parent
** directory won't work for file in repository root.
*/
if( strrchr(zFile, '/') ){
zDir = mprintf("%T?d=%T", default_browse_url(), zFile);
}else{
zDir = mprintf("%T?d=/%T", default_browse_url(), zFile);
}
z = strrchr(zDir, '/' );
if( z ){ *z = 0;}
common_add_action_item(zDir, "Directory");
add_file_tools(0,zFile,0,0);
common_add_help_item("CvstracFileHistory");
revision_history(zFile, showMilestones);
common_footer();
}
/*
** WEBPAGE: /filediff
**
** Show the differences between two versions of a file
*/
void browse_filediff(void){
const char *zFile = P("f");
const char *zV1 = P("v1");
const char *zV2 = P("v2");
login_check_credentials();
if( !g.okCheckout ){ login_needed(); return; }
throttle(1,0);
if( zFile==0 || zV1==0 || zV2==0 ){ cgi_redirect("index"); return; }
common_standard_menu(0, "search?f=1");
common_add_action_item(mprintf("rlog?f=%T", zFile), "History");
add_file_tools(0,zFile,zV1,zV2);
common_add_help_item("CvstracFileHistory");
common_header("Difference in %h versions %h and %h", zFile,
printable_vers(zV1), printable_vers(zV2));
if( diff_versions(zV1, zV2, zFile) ){
cgi_printf("<b>Diff failed!</b>\n");
}
common_footer();
}
/*
** WEBPAGE: /dir
**
** List all of the repository files in a single directory.
*/
void browse_dir(void){
const char *zName;
char *zDir;
char *zBase;
char **az;
int i, j;
int n;
int nRow;
const char *zCookieName;
int nCookieLife;
login_check_credentials();
if( !g.okCheckout ){ login_needed(); return; }
throttle(1,0);
history_update(0);
common_standard_menu("dir", "search?f=1");
/* P("sc") is set only when user explicitly switches to Long/Short view,
** via action bar link. In that case we make that users preference
** persistent via cookie.
*/
if( P("sc") ){
zCookieName = mprintf("%t_browse_url",g.zName);
nCookieLife = 86400*atoi(db_config("browse_url_cookie_life","90"));
if( nCookieLife ){
cgi_set_cookie(zCookieName, "dir", 0, nCookieLife);
}
}
zName = PD("d","");
if( zName==0 ){
zName = "";
}
common_add_help_item("CvstracBrowse");
if( zName[0] ){
common_add_action_item(
mprintf("timeline?x=1&c=2&dm=1&px=%h",zName),
"Activity"
);
}
add_dir_tools(0,zName);
zDir = mprintf("%s", zName);
zBase = strrchr(zDir, '/');
if( zBase==0 ){
zBase = zDir;
zDir = "";
}else{
*zBase = 0;
zBase++;
}
if( zName && zName[0] ){
/* this looks like navigation, but it's relative to the current page
*/
common_add_action_item("dir", "Top");
common_add_action_item(mprintf("dir?d=%T",zDir), "Up");
common_add_action_item(mprintf("dirview?d=%T&sc=1",zName), "Long");
}else{
common_add_action_item("dirview?sc=1","Long");
}
az = db_query("SELECT base, isdir FROM file WHERE dir='%q' ORDER BY base",
zName);
for(n=0; az[n*2]; n++){}
if( zName[0] ) n++;
nRow = (n+3)/4;
if( zName[0] ){ zName = mprintf("%s/",zName); }
common_header("Directory /%h", zName);
/* @ <h2>Contents of directory /%h(zName)</h2> */
cgi_printf("<table width=\"100%%\">\n"
"<tr>\n");
for(i=j=0; i<4; i++){
cgi_printf("<td valign=\"top\" width=\"25%%\">\n");
n = 0;
if( i==0 && zName[0] ){
cgi_printf("<a href=\"dir?d=%T\">\n",zDir);
common_icon("backup");
cgi_printf("</a> <a href=\"dir?d=%T\">..</a><br>\n",zDir);
n++;
}
while( n<nRow && az[j] ){
if( atoi(az[j+1]) ){
cgi_printf("<a href=\"dir?d=%T%T\">\n",zName,az[j]);
common_icon("dir");
cgi_printf("</a> <a href=\"dir?d=%T%T\">%h/</a><br>\n",zName,az[j],az[j]);
}else{
char *zIcon;
char *zFilename = mprintf("%s%s", zName, az[j]);
if(is_file_available(zFilename)){
zIcon = "file";
}else{
zIcon = "del";
}
if( zFilename!=0 ) free(zFilename);
cgi_printf("<a href=\"rlog?f=%T%T\">\n",zName,az[j]);
common_icon(zIcon);
cgi_printf("</a> <a href=\"rlog?f=%T%T\">%h</a><br>\n",zName,az[j],az[j]);
}
n++;
j += 2;
}
cgi_printf("</td>\n");
}
cgi_printf("</tr></table>\n");
common_footer();
}
/*
** This routine is used to represent age of files in english text.
** For example "1 week", "3 days", etc.
** It takes integer representing unix time of file's last modification and
** calculates it's age relative to current time.
*/
static char *file_age_to_text(int nModified){
int nAge, n;
int nYear = 31536000; /* Number of seconds in a year */
int nMonth = 2592000; /* Number of seconds in a month */
int nWeek = 604800; /* Number of seconds in a week */
int nDay = 86400; /* Number of seconds in a day */
if( nModified<=0 ){
/* FIXME: some error handling would be nice here */
return NULL;
}
nAge = (int)time(0)-nModified;
if( nAge<0 ){
/* FIXME: some error handling would be nice here */
return NULL;
}
if( (n = nAge/nYear)>1 ){
return mprintf("%d years", n);
}else if( (n = nAge/nMonth)>1 ){
return mprintf("%d months", n);
}else if( (n = nAge/nWeek)>1 ){
return mprintf("%d weeks", n);
}else if( (n = nAge/nDay)>1 ){
return mprintf("%d days", n);
}else if( (n = nAge/3600)>1 ){
return mprintf("%d hours", n);
}else{
n = nAge/60;
if( n<=1 ){
return mprintf("1 minute");
}else{
return mprintf("%d minutes", n);
}
}
}
static void column_header(
const char *zNameNS,
char zFld,
const char *zField,
const char *zColumn
){
int set = (zFld==zField[0]);
int desc = P("desc")!=0;
const char *zDesc = set ? (desc ? "" : "&desc" ) : "";
if(set){
cgi_printf("<th align=\"left\" bgcolor=\"%s\" class=\"bkgnd1\"><a\n",BG1);
}else{
cgi_printf("<th align=\"left\"><a\n");
}
cgi_printf(" href=\"dirview?d=%T&o=%s%s\">%h</a></th>\n",zNameNS,zField,zDesc,zColumn);
}
/*
** Output a long directory row
*/
static void row_content(
const char *zName,
int nCol,
const char **az
){
if( (nCol%2)==0 ){
cgi_printf("<tr bgcolor=\"%s\" class=\"bkgnd4\">\n",BG4);
}else{
cgi_printf("<tr>\n");
}
if( atoi(az[0])==1 ){
cgi_printf("<td colspan=\"3\">\n"
"<a href=\"dirview?d=%T%T\">\n",zName,az[1]);
common_icon("dir");
cgi_printf("</a> <a href=\"dirview?d=%T%T\">%h/</a></td>\n"
"<td valign=\"middle\" width=\"10%%\">%h</td>\n"
"<td></td>\n",zName,az[1],az[1],file_age_to_text(atoi(az[5])));
}else{
cgi_printf("<td valign=\"middle\" width=\"30%%\">\n"
"<a href=\"rlog?f=%T%T\">\n",zName,az[1]);
if( atoi(az[3])==2 ){
common_icon("del");
}else{
common_icon("file");
}
cgi_printf("</a> <a href=\"rlog?f=%T%T\">%h</a></td>\n"
"<td valign=\"middle\" width=\"5%%\">\n"
"<a href=\"fileview?f=%T%T&v=%T\">\n"
"%h</a></td> \n"
"<td valign=\"middle\" width=\"8%%\">%z</td>\n"
"<td valign=\"middle\" width=\"8%%\">%h</td>\n"
"<td valign=\"middle\">\n",zName,az[1],az[1],zName,az[1],az[2],printable_vers(az[2]),format_user(az[4]),file_age_to_text( atoi(az[5]) ));
if( output_trim_message(az[6], MN_CKIN_MSG, MX_CKIN_MSG) ){
output_formatted(az[6], 0);
cgi_printf(" [...]\n");
}else{
output_formatted(az[6], 0);
}
cgi_printf("</td>\n");
}
cgi_printf("</tr>\n");
}
/*
** WEBPAGE: /dirview
**
** This is a "long view" version of /dir page.
** List all of the repository files in a single directory and display
** information about their last change.
*/
void browse_dirview(void){
const char *zName;
const char *zNameNS; /* NoSlash */
char *zDir;
char *zBase;
char **az;
int i;
const char *zCookieName;
int nCookieLife;
char *zDesc;
char *zOrderBy = "1 DESC, 2";
const char *z;
char zFld = 0;
login_check_credentials();
if( !g.okCheckout ){ login_needed(); return; }
throttle(1,0);
history_update(0);
common_standard_menu("dirview", "search?f=1");
/* P("sc") is set only when user explicitly switches to Long/Short view,
** via action bar link. In that case we make that users preference
** persistent via cookie.
*/
if( P("sc") ){
zCookieName = mprintf("%t_browse_url",g.zName);
nCookieLife = 86400*atoi(db_config("browse_url_cookie_life","90"));
if( nCookieLife ){
cgi_set_cookie(zCookieName, "dirview", 0, nCookieLife);
}
}
zName = PD("d","");
if( zName==0 ){
zName = "";
}
if( zName[0] ){
common_add_action_item(
mprintf("timeline?x=1&c=2&dm=1&px=%T",zName),
"Activity"
);
}
add_dir_tools(0,zName);
zDir = mprintf("%s", zName);
zBase = strrchr(zDir, '/');
if( zBase==0 ){
zBase = zDir;
zDir = "";
}else{
*zBase = 0;
zBase++;
}
if( zName && zName[0] ){
/* this looks like navigation, but it's relative to the current page
*/
common_add_action_item(mprintf("dirview"), "Top");
common_add_action_item(mprintf("dirview?d=%T",zDir), "Up");
common_add_action_item(mprintf("dir?d=%T&sc=1",zName), "Short");
}else{
common_add_action_item("dir?sc=1", "Short");
}
common_add_help_item("CvstracBrowse");
zNameNS = mprintf("%s",zName);
if( zName[0] ){ zName = mprintf("%s/",zName); }
/* Figure out how should we order this and display our intent in <th>
** If no ordering preference is found, don't display anything in <th>
*/
zDesc = P("desc") ? "DESC" : "ASC";
z = P("o");
if( z ){
zFld = z[0];
switch( zFld ){
case 'f':
zOrderBy = mprintf("2 %s", zDesc);
break;
case 'v':
zOrderBy = mprintf("3 %s", zDesc);
break;
case 'u':
zOrderBy = mprintf("5 %s", zDesc);
break;
case 'd':
zOrderBy = mprintf("6 %s", (strcmp(zDesc,"ASC")==0)?"DESC":"ASC");
break;
case 'm':
zOrderBy = mprintf("7 %s", zDesc);
break;
default:
zFld = 0;
break;
}
}
db_add_functions();
az = db_query(
"SELECT 0, f.base, fc.vers, fc.chngtype, c.user, c.date, "
" '[' || f.lastcn || '] ' || c.message, f.lastcn "
"FROM file f, chng c, filechng fc "
"WHERE f.dir='%q' "
" AND f.isdir=0 "
" AND fc.filename=path(isdir,dir,base) "
" AND f.lastcn=fc.cn "
" AND f.lastcn=c.cn "
"UNION ALL "
"SELECT 1, f.base, NULL, NULL, NULL, c.date, "
" NULL, f.lastcn "
"FROM file f, chng c "
"WHERE f.dir='%q' "
" AND f.isdir=1 "
" AND f.lastcn=c.cn "
"ORDER BY %s",
zNameNS, zNameNS, zOrderBy
);
common_header("Directory /%h", zName);
cgi_printf("<table width=\"100%%\" border=0 cellspacing=0 cellpadding=3>\n"
"<tr>\n");
column_header(zNameNS,zFld,"file","File");
column_header(zNameNS,zFld,"vers","Vers");
column_header(zNameNS,zFld,"user","By");
column_header(zNameNS,zFld,"date","Age");
column_header(zNameNS,zFld,"msg","Check-in");
cgi_printf("</tr>\n");
if( zName[0] ){
cgi_printf("<tr><td colspan=\"5\">\n"
"<a href=\"dirview?d=%T\">\n",zDir);
common_icon("backup");
cgi_printf("</a> <a href=\"dirview?d=%T\">..</a></td></tr>\n",zDir);
}
/* In case dir is empty, exit nicely */
if( !az || !az[0] ){
cgi_printf("</table>\n");
common_footer();
return;
}
for(i=0; az[i]; i+=8){
row_content(zName,i/8,&az[i]);
}
cgi_printf("</table>\n");
db_query_free(az);
common_footer();
}
/*
** WEBPAGE: /fileview
**
** Show the file in a HTML page. In the case of things like images, show the
** content embedded in the page.
*/
void browse_fileview(void){
const char *zFile = g.zExtra ? g.zExtra : P("f");
const char *zVers = PD("v","");
char *zGetFile;
char *zDir, *z;
char *zSuffix;
char *zMime = "text/plain"; /* The default MIME type */
/* The following table lists some alternative MIME types based on
** the file suffix
*/
static const struct {
char *zSuffix;
char *zMime;
} aMime[] = {
{ "html", "text/html" },
{ "htm", "text/html" },
{ "gif", "image/gif" },
{ "jpeg", "image/jpeg" },
{ "jpg", "image/jpeg" },
{ "png", "image/png" },
{ "pdf", "application/pdf" },
{ "ps", "application/postscript" },
{ "eps", "application/postscript" },
};
login_check_credentials();
if( !g.okCheckout ){ login_needed(); return; }
throttle(1,0);
common_standard_menu(0, "search?f=1");
history_update(0);
/* Make sure we always have '/' in zFile, otherwise link to parent
** directory won't work for file in repository root.
*/
if( strrchr(zFile, '/') ){
zDir = mprintf("%T?d=%T", default_browse_url(), zFile);
}else{
zDir = mprintf("%T?d=/%T", default_browse_url(), zFile);
}
z = strrchr(zDir, '/' );
if( z ){ *z = 0;}
common_add_nav_item(zDir, "Directory");
zGetFile = mprintf("getfile?f=%T&v=%T", zFile, zVers);
common_add_action_item(zGetFile, "Raw");
add_file_tools(0,zFile,zVers,0);
common_add_help_item("CvstracFileview");
common_header("%h %h", zFile, printable_vers(zVers));
/* sort out the MIME type. We output HTML, but some things are embeddable. */
zSuffix = strrchr(zFile, '.');
if( zSuffix ){
char zLine[2000];
int i;
zSuffix++;
for(i=0; zSuffix[i] && i<sizeof(zLine)-1; i++){
zLine[i] = tolower(zSuffix[i]);
}
zLine[i] = 0;
for(i=0; i<sizeof(aMime)/sizeof(aMime[0]); i++){
if( strcmp(zLine, aMime[i].zSuffix)==0 ){
zMime = aMime[i].zMime;
break;
}
}
}
cgi_printf("<a href=\"rlog?f=%T\">%h</a>\n"
"<a href=\"%s\">%h</a><hr>\n",zFile,zFile,zGetFile,zVers);
/* For image types, embed in the page. Anything else, try to inline */
if( !strncmp(zMime,"image/",6) ){
cgi_printf("<img src=\"%s\" alt=\"%h %h\">\n",zGetFile,zFile,zVers);
}else{
if( dump_version(zVers,zFile,0) ){
cgi_redirect("index");
return;
}
}
cgi_printf("<hr>\n");
common_footer();
}
/*
** WEBPAGE: /getfile
**
** Return the complete content of a file
*/
void browse_getfile(void){
const char *zFile = g.zExtra ? g.zExtra : P("f");
const char *zVers = P("v");
char *zSuffix;
char *zMime = "text/plain"; /* The default MIME type */
/* The following table lists some alternative MIME types based on
** the file suffix
*/
static const struct {
char *zSuffix;
char *zMime;
} aMime[] = {
{ "html", "text/html" },
{ "htm", "text/html" },
{ "gif", "image/gif" },
{ "jpeg", "image/jpeg" },
{ "jpg", "image/jpeg" },
{ "png", "image/png" },
{ "pdf", "application/pdf" },
{ "ps", "application/postscript" },
{ "eps", "application/postscript" },
};
login_check_credentials();
if( !g.okCheckout || zFile==0 ){ login_needed(); return; }
throttle(1,0);
if( zVers!= 0 ){
/* A database query is almost definitely going to be faster than having
** to pull from from the repository, so we might as well try this first.
*/
char *z = db_short_query("SELECT chng.date FROM filechng, chng "
"WHERE filechng.filename='%q' "
" AND filechng.vers='%q' "
" AND filechng.cn=chng.cn ",
zFile, zVers);
if( z ){
cgi_modified_since(atoi(z));
cgi_append_header(mprintf("Last-Modified: %h\r\n",
cgi_rfc822_datestamp(atoi(z))));
free(z);
}
}
if( dump_version(zVers,zFile,1) ){
cgi_redirect("index");
return;
}
/* sort out the MIME type */
zSuffix = strrchr(zFile, '.');
if( zSuffix ){
char zLine[2000];
int i;
zSuffix++;
for(i=0; zSuffix[i] && i<sizeof(zLine)-1; i++){
zLine[i] = tolower(zSuffix[i]);
}
zLine[i] = 0;
for(i=0; i<sizeof(aMime)/sizeof(aMime[0]); i++){
if( strcmp(zLine, aMime[i].zSuffix)==0 ){
zMime = aMime[i].zMime;
break;
}
}
}
if( zVers && zVers[0] ){
g.isConst = 1;
}
cgi_set_content_type(zMime);
cgi_set_status(200, "OK");
return;
}
syntax highlighted by Code2HTML, v. 0.9.1