Index: attach.c
--- attach.c.orig	2006-05-26 16:20:28 +0200
+++ attach.c	2006-10-30 20:14:51 +0100
@@ -210,17 +210,20 @@
 }
 
 static int output_attachment_callback(
-  int *nGot,       /* Set if we got results */
+  void *_nGot,      /* Set if we got results */
   int nArg,        /* Number of columns in this result row */
   char **azArg,    /* Text of data in all columns */
   char **azName    /* Names of the columns */
 ){
-  if( nArg != 4 ) return 0;
+  int *nGot = (int *)_nGot;
+  if( nArg != 5 ) return 0;
   (*nGot) ++;
   cgi_set_content_type(azArg[1]);
   cgi_modified_since(atoi(azArg[3]));
   cgi_append_header(
     mprintf("Last-Modified: %s\r\n",cgi_rfc822_datestamp(atoi(azArg[3]))));
+  cgi_append_header(mprintf("Content-disposition: attachment; "
+        "filename=\"%T\"\r\n", azArg[4]));
   cgi_append_content(azArg[2], atoi(azArg[0]));
   g.isConst = 1;
   return 0;
@@ -235,7 +238,7 @@
   */
   int got = 0;
   db_callback_query( output_attachment_callback, &got,
-                     "SELECT size, mime, content, date "
+                     "SELECT size, mime, content, date, fname "
                      "FROM attachment "
                      "WHERE atn=%d", atn);
   if( !got ){
Index: browse.c
--- browse.c.orig	2006-05-26 16:20:28 +0200
+++ browse.c	2006-10-30 20:14:51 +0100
@@ -503,6 +503,13 @@
 ){
   int set = (zFld==zField[0]);
   int desc = P("desc")!=0;
+  /* Clicking same column header 3 times in a row resets any sorting.
+  */
+  if(set && desc){
+    @ <th align="left" bgcolor="%s(BG1)" class="bkgnd1">
+    @   <a href="dirview?d=%T(zNameNS)">%h(zColumn)</a></th>
+    return;
+  }
   const char *zDesc = set ? (desc ? "" : "&desc" ) : "";
   if(set){
     @ <th align="left" bgcolor="%s(BG1)" class="bkgnd1"><a
@@ -517,8 +524,9 @@
 */
 static void row_content(
   const char *zName,
+  const char *zSortUrl,
   int nCol,
-  const char **az
+  char **az
 ){
   if( (nCol%2)==0 ){
     @ <tr bgcolor="%s(BG4)" class="bkgnd4">
@@ -528,9 +536,9 @@
   
   if( atoi(az[0])==1 ){
     @ <td colspan="3">
-    @ <a href="dirview?d=%T(zName)%T(az[1])">
+    @ <a href="dirview?d=%T(zName)%T(az[1])&%s(zSortUrl)">
     common_icon("dir");
-    @ </a>&nbsp;<a href="dirview?d=%T(zName)%T(az[1])">%h(az[1])/</a></td>
+    @ </a>&nbsp;<a href="dirview?d=%T(zName)%T(az[1])&%s(zSortUrl)">%h(az[1])/</a></td>
     @ <td valign="middle" width="10%%">%h(file_age_to_text(atoi(az[5])))</td>
     @ <td></td>
   }else{
@@ -580,6 +588,7 @@
   char *zOrderBy = "1 DESC, 2";
   const char *z;
   char zFld = 0;
+  char *zSortUrl = "";
 
   login_check_credentials();
   if( !g.okCheckout ){ login_needed(); return; }
@@ -617,19 +626,6 @@
     *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>
@@ -638,6 +634,7 @@
   
   z = P("o");
   if( z ){
+    zSortUrl = mprintf("o=%t%s", z, (zDesc[0]=='D')?"&desc":"");
     zFld = z[0];
     switch( zFld ){
       case 'f':
@@ -660,6 +657,22 @@
         break;
     }
   }
+
+  if( zName && zName[0] ){
+    /* this looks like navigation, but it's relative to the current page
+    */
+    common_add_action_item(
+      mprintf("dirview%s%s",(zSortUrl[0])?"?":"",zSortUrl), "Top");
+    common_add_action_item(
+      mprintf("dirview?d=%T%s%s",zDir,(zSortUrl[0])?"&":"",zSortUrl), "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); }
   
   db_add_functions();
   az = db_query(
@@ -694,9 +707,9 @@
 
   if( zName[0] ){
     @ <tr><td colspan="5">
-    @ <a href="dirview?d=%T(zDir)">
+    @ <a href="dirview?d=%T(zDir)&%s(zSortUrl)">
     common_icon("backup");
-    @ </a>&nbsp;<a href="dirview?d=%T(zDir)">..</a></td></tr>
+    @ </a>&nbsp;<a href="dirview?d=%T(zDir)&%s(zSortUrl)">..</a></td></tr>
   }
   
   /* In case dir is empty, exit nicely */
@@ -707,7 +720,7 @@
   }
   
   for(i=0; az[i]; i+=8){
-    row_content(zName,i/8,&az[i]);
+    row_content(zName,zSortUrl,i/8,&az[i]);
   }
   @ </table>
   db_query_free(az);
@@ -815,6 +828,7 @@
   const char *zFile = g.zExtra ? g.zExtra : P("f");
   const char *zVers = P("v");
   char *zSuffix;
+  const char *zName;
   char *zMime = "text/plain";  /* The default MIME type */
 
   /* The following table lists some alternative MIME types based on
@@ -878,6 +892,16 @@
       }
     }
   }
+
+  /*
+  ** This means the user gets something meaningful as a default filename
+  ** when they try to save to file (depending on the browser).
+  */
+  zName = strrchr(zFile, '/');
+  if (zName) zName += 1;
+  cgi_append_header(mprintf("Content-disposition: attachment; "
+        "filename=\"%T\"\r\n", zName ? zName : zFile));
+
   if( zVers && zVers[0] ){
     g.isConst = 1;
   }
Index: cgi.c
--- cgi.c.orig	2006-05-26 16:20:28 +0200
+++ cgi.c	2006-10-30 20:14:51 +0100
@@ -187,7 +187,7 @@
   char zETag[64];
 
   MD5Init(&ctx);
-  MD5Update(&ctx,zTxt,nLen);
+  MD5Update(&ctx,(unsigned char *)zTxt,nLen);
   MD5Final(digest,&ctx);
   for(j=i=0; i<16; i++,j+=2){
     bprintf(&zETag[j],sizeof(zETag)-j,"%02x",(int)digest[i]);
@@ -382,7 +382,7 @@
             continue;
           }
         }
-        if( zDest[i+1]=='/' ) continue;
+        if( zDest[i+1]=='/' && (i==0 || zDest[i-1]!=':') ) continue;
       }
       zDest[j++] = zDest[i];
     }
@@ -2343,7 +2343,7 @@
   int i, n;
 
   if( nData<=0 ){
-    nData = strlen(zData);
+    nData = strlen((const char *)zData);
   }
   z64 = malloc( (nData*4)/3 + 6 );
   if(z64==0) return 0;
@@ -2385,7 +2385,7 @@
     for(i=0; zBase[i]; i++){ trans[zBase[i] & 0x7f] = i; }
     isInit = 1;
   }
-  n64 = strlen(z64);
+  n64 = strlen((const char *)z64);
   while( n64>0 && z64[n64-1]=='=' ) n64--;
   zData = malloc( (n64*3)/4 + 4 );
   if( zData==0 ) return 0;
Index: common.c
--- common.c.orig	2006-05-26 16:20:28 +0200
+++ common.c	2006-10-30 20:14:51 +0100
@@ -394,7 +394,10 @@
     }
     @ </small>
   }else{
-    const char *zUri = getenv("REQUEST_URI");
+    /* We don't want to be redirected back to captcha page, but ratehr to 
+    ** one from which we were redirected to captcha in the first place.
+    */
+    const char *zUri = (P("cnxp")!=0) ? P("cnxp") : getenv("REQUEST_URI");
     @ <a href="honeypot"><small><notatag arg="meaningless"></small></a>
     @ <small><a href="login?nxp=%T(zUri)" title="Log in">Not logged in</a></small>
   }
Index: config.h
--- config.h.orig	2006-05-26 16:20:28 +0200
+++ config.h	2006-10-30 20:14:51 +0100
@@ -9,6 +9,9 @@
 #include <stdarg.h>
 #include <sqlite3.h>
 #include <assert.h>
+#if defined(__linux__) || defined(__sun__)
+#include <crypt.h>
+#endif
 
 /*
 ** Standard colors.  These colors can also be changed using a stylesheet.
Index: cvs.c
--- cvs.c.orig	2006-05-26 16:20:28 +0200
+++ cvs.c	2006-10-30 20:14:51 +0100
@@ -234,6 +234,7 @@
   char **azFileList;
   FILE *in;
   int i, nField;
+  unsigned long ul;
   time_t minTime, maxTime, tm;
   struct stat statbuf;
   int cnum = 0;     /* check-in number to use for this checkin */
@@ -307,7 +308,8 @@
     **      T    Tagging operations
     */
     if( (c = zLine[0])!='A' && c!='M' && c!='T' && c!='R' ) continue;
-    if( sscanf(&zLine[1],"%lx",&tm)!=1 ) continue;
+    if( sscanf(&zLine[1],"%lx",&ul)!=1 ) continue;
+    tm = (time_t)ul;
     if( tm<minTime ) minTime = tm;
     if( tm>maxTime ) maxTime = tm;
 
Index: db.c
--- db.c.orig	2006-05-26 16:20:28 +0200
+++ db.c	2006-10-30 20:14:51 +0100
@@ -107,6 +107,10 @@
   extern int sqlite3StrICmp(const char*, const char*);
   if( type==SQLITE_SELECT ){
     return SQLITE_OK;
+#ifdef SQLITE_FUNCTION
+  }else if( type==SQLITE_FUNCTION ){
+    return SQLITE_OK;
+#endif
   }else if( type==SQLITE_READ ){
     if( sqlite3StrICmp(zArg1,"user")==0 ){
       if( sqlite3StrICmp(zArg2,"passwd")==0 || sqlite3StrICmp(zArg2,"email")==0 ){
@@ -602,7 +606,7 @@
   }
   if( argc>1 && g.nAux<MX_AUX ){
     char *zErr;
-    const char *zQuery = (const char*)sqlite3_value_text(argv[1]);
+    char *zQuery = (char *)sqlite3_value_text(argv[1]);
     if( zQuery==0 ) {
       db_err("Query cannot be NULL", "NULL", "Illegal option() query");
     }
@@ -1044,12 +1048,13 @@
 @ FROM ticket");
 @ ----------------------------------------------------------------------------
 @ INSERT INTO reportfmt VALUES(5,NULL,'Tickets counts',NULL,"SELECT
-@   status,
-@   count(case when type='code' then 'x' end),
-@   count(case when type='doc' then 'x' end),
-@   count(case when type='new' then 'x' end),
-@   count(case when type NOT IN ('code','doc','new') then 'x' end),
-@   count(*)
+@   status AS 'Status',
+@   count(case when type='code' then 'x' end) AS 'Code Bugs',
+@   count(case when type='doc' then 'x' end) AS 'Doc Bugs',
+@   count(case when type='new' then 'x' end) AS 'Enhancements',
+@   count(case when type NOT IN ('code','doc','new') then 'x' end)
+@         AS 'Other',
+@   count(*) AS 'All Types'
 @ FROM ticket GROUP BY status
 @ UNION
 @ SELECT
@@ -1790,10 +1795,10 @@
 */
 static void f_decode(sqlite3_context *context, int argc, sqlite3_value **argv){
   if( argc==2 ) {
-    const char *zIn = (const char*)sqlite3_value_text(argv[0]);
+    const unsigned char *zIn = sqlite3_value_text(argv[0]);
     int nBytes = sqlite3_value_int(argv[1]);
     if( zIn && zIn[0] && nBytes>0 ){
-      const char *zOut = calloc(nBytes,1);
+      char unsigned *zOut = calloc(nBytes,1);
       int nDecoded;
       if( zOut==0 ){
         db_err( strerror(errno), 0,
Index: format.c
--- format.c.orig	2006-05-26 16:20:28 +0200
+++ format.c	2006-10-30 20:14:51 +0100
@@ -760,7 +760,7 @@
       if( az && az[0] && az[1] ){
         cgi_printf("<a href=\"%z\" title=\"%h\">",zLink,az[0]);
         /* FIXME: should use a <span> with the ticket status as a class */
-        if( az[1][0] == 'n' || az[1][0] == 'a' ){
+        if( az[1][0] != 'c' ){
           cgi_printf("#%d",tn);
         }else{
           cgi_printf("<strike>#%d</strike>",tn);
Index: login.c
--- login.c.orig	2006-05-26 16:20:28 +0200
+++ login.c	2006-10-30 20:14:51 +0100
@@ -55,8 +55,8 @@
   zGoto = P("g");
   if( P("out")!=0 ){
     const char *zCookieName = login_cookie_name();
-    cgi_set_cookie(zCookieName, "", 0, 0);
-    db_execute("DELETE FROM cookie WHERE cookie='%q'", zCookieName);
+    cgi_set_cookie(zCookieName, "", 0, time(NULL)-(60*60*24));
+    db_execute("DELETE FROM cookie WHERE cookie='%q'", P(zCookieName));
     cgi_redirect(PD("nxp","index"));
     return;
   }
@@ -102,15 +102,15 @@
       const char *zAddr;
       const char *zAgent;
       char zHash[200];
-      char zRawDigest[16];
+      unsigned char zRawDigest[16];
       MD5Context ctx;
 
       time(&now);
       bprintf(zHash,sizeof(zHash),"%d%d%.19s", getpid(), (int)now, zPasswd);
       MD5Init(&ctx);
-      MD5Update(&ctx, zHash, strlen(zHash));
+      MD5Update(&ctx, (unsigned char *)zHash, strlen(zHash));
       MD5Final(zRawDigest, &ctx);
-      zDigest = encode64(zRawDigest, 16);
+      zDigest = (char *)encode64(zRawDigest, 16);
       zAddr = getenv("REMOTE_ADDR");
       if( zAddr==0 ) zAddr = "0.0.0.0";
       zAgent = getenv("HTTP_USER_AGENT");
Index: makeheaders.c
--- makeheaders.c.orig	2006-05-26 16:20:28 +0200
+++ makeheaders.c	2006-10-30 20:14:51 +0100
@@ -11,9 +11,8 @@
 #include <assert.h>
 #ifndef WIN32
 # include <unistd.h>
-#else
-# include <string.h>
 #endif
+#include <string.h>
 
 /*
 ** Macros for debugging.
@@ -2137,7 +2136,7 @@
       zArg++;
     }
     if( *zArg==0 || *zArg=='\n' ){ return 0; }
-    nArg = pToken->nText + (int)pToken->zText - (int)zArg;
+    nArg = pToken->nText + pToken->zText - zArg;
     if( nArg==9 && strncmp(zArg,"INTERFACE",9)==0 ){
       PushIfMacro(0,0,0,pToken->nLine,PS_Interface);
     }else if( nArg==16 && strncmp(zArg,"EXPORT_INTERFACE",16)==0 ){
@@ -2156,7 +2155,7 @@
       zArg++;
     }
     if( *zArg==0 || *zArg=='\n' ){ return 0; }
-    nArg = pToken->nText + (int)pToken->zText - (int)zArg;
+    nArg = pToken->nText + pToken->zText - zArg;
     PushIfMacro("defined",zArg,nArg,pToken->nLine,0);
   }else if( nCmd==6 && strncmp(zCmd,"ifndef",6)==0 ){
     /*
@@ -2167,7 +2166,7 @@
       zArg++;
     }
     if( *zArg==0 || *zArg=='\n' ){ return 0; }
-    nArg = pToken->nText + (int)pToken->zText - (int)zArg;
+    nArg = pToken->nText + pToken->zText - zArg;
     PushIfMacro("!defined",zArg,nArg,pToken->nLine,0);
   }else if( nCmd==4 && strncmp(zCmd,"else",4)==0 ){
     /*
@@ -2793,7 +2792,7 @@
     fprintf(stderr,
        "%s: Can't overwrite this file because it wasn't previously\n"
        "%*s  generated by 'makeheaders'.\n",
-       pFile->zHdr, strlen(pFile->zHdr), "");
+       pFile->zHdr, (int)strlen(pFile->zHdr), "");
     nErr++;
   }else if( strcmp(zOldVersion,zNewVersion)!=0 ){
     if( report ) fprintf(report,"updated\n");
@@ -2952,10 +2951,10 @@
        pDecl->zName,
        zLabel,
        pDecl->zFile,
-       pDecl->pComment ? (int)pDecl->pComment/sizeof(Token) : 0,
+       pDecl->pComment ? /* TOTALLY BOGUS: (int)pDecl->pComment/sizeof(Token) */ 0 : 0,
        pDecl->pComment ? pDecl->pComment->nText+1 : 0,
-       pDecl->zIf ? strlen(pDecl->zIf)+1 : 0,
-       zDecl ? strlen(zDecl) : 0,
+       pDecl->zIf ? (int)strlen(pDecl->zIf)+1 : 0,
+       zDecl ? (int)strlen(zDecl) : 0,
        pDecl->pComment ? pDecl->pComment->nLine : 0,
        pDecl->tokenCode.nText ? pDecl->tokenCode.nText+1 : 0
     );
Index: search.c
--- search.c.orig	2006-05-26 16:20:28 +0200
+++ search.c	2006-10-30 20:14:51 +0100
@@ -88,7 +88,7 @@
   const char *zPattern;
 
   if( argc<2 || argv[0]==0 ) return;
-  zPattern = sqlite3_value_text(argv[0]);
+  zPattern = (const char *)sqlite3_value_text(argv[0]);
   total = 0;
   if( zPattern && zPattern[0] ){
     for(i=0; zPattern[i]; i++){
@@ -97,7 +97,7 @@
       score = 0;
       for(k=1; k<argc; k++){
         int one_score;
-        char *zWord = sqlite3_value_text(argv[k]);
+        char *zWord = (char *)sqlite3_value_text(argv[k]);
         if( zWord==0 || zWord[0]==0 ) continue;
         one_score = score_word(&zPattern[i], zWord, j, 0);
         if( one_score>score ) score = one_score;
@@ -145,7 +145,7 @@
   ** We must have a pattern and at least one text block to function.
   */
   if( argc<2 || argv[0]==0 ) return;
-  zPattern = sqlite3_value_text(argv[0]);
+  zPattern = (char *)sqlite3_value_text(argv[0]);
   if( zPattern[0]==0 ) return;
 
   /*
Index: setup.c
--- setup.c.orig	2006-05-26 16:20:28 +0200
+++ setup.c	2006-10-30 20:15:28 +0100
@@ -334,7 +334,7 @@
     @ passwords of all CVSTrac users.  Enable or disable this feature
     @ below.</p>
     @ <p>Write User Changes to CVSROOT/passwd?
-    cgi_optionmenu(0, "wpw", zOldWPswd, "Yes", "yes", "No", "no", 0);
+    cgi_optionmenu(0, "wpw", zOldWPswd, "Yes", "yes", "No", "no", NULL);
     @ <input type="submit" value="Submit">
     @ </p>
   }
@@ -499,7 +499,7 @@
   @ <form action="%s(g.zPath)" method="POST">
   @ Select a user to whom new tickets will be assigned by default:</p><p>
   @ Assigned To:
-  azResult = db_query("SELECT id FROM user UNION SELECT '' ORDER BY id");
+  azResult = db_query("SELECT id FROM user WHERE capabilities LIKE '%%w%%' UNION SELECT '' ORDER BY id");
   cgi_v_optionmenu(0, "asgnto", zOldAsgnto, (const char**)azResult);
   @ </p>
   @
@@ -521,14 +521,14 @@
   @ Select the default priority for new tickets:</p><p>
   @ Default Priority:
   cgi_optionmenu(0, "pri", zOldPri, "1", "1", "2", "2", "3", "3", "4", "4",
-      "5", "5", (char*)0);
+      "5", "5", NULL);
   @ </p>
   @
   @ <p>
   @ Select the default severity for new tickets:</p><p>
   @ Default Severity:
   cgi_optionmenu(0, "sev", zOldSev, "1", "1", "2", "2", "3", "3", "4", "4",
-      "5", "5", (char*)0);
+      "5", "5", NULL);
   @ </p>
   @
   @ <p>
Index: throttle.c
--- throttle.c.orig	2006-05-26 16:20:28 +0200
+++ throttle.c	2006-10-30 20:14:51 +0100
@@ -165,7 +165,7 @@
     }
 
     if( zUrl==0 ) zUrl = "index";
-    cgi_redirect(mprintf("captcha?nxp=%T", zUrl));
+    cgi_redirect(mprintf("captcha?cnxp=%T", zUrl));
   }else if( overload && exitOnOverload ){
     /* Just block the client */
     lockout();
Index: ticket.c
--- ticket.c.orig	2006-05-26 16:20:28 +0200
+++ ticket.c	2006-10-30 20:14:51 +0100
@@ -358,7 +358,7 @@
   @   <td align="right"><nobr>
   @     Severity:
   cgi_optionmenu(0, "r", zSev,
-         "1", "1", "2", "2", "3", "3", "4", "4", "5", "5", 0);
+         "1", "1", "2", "2", "3", "3", "4", "4", "5", "5", NULL);
   @   </nobr></td>
   @   <td>
   @     How debilitating is the problem?  "1" is a show-stopper defect with
@@ -372,7 +372,7 @@
   @   <td align="right"><nobr>
   @     Priority:
   cgi_optionmenu(0, "p", zPri,
-         "1", "1", "2", "2", "3", "3", "4", "4", "5", "5", 0);
+         "1", "1", "2", "2", "3", "3", "4", "4", "5", "5", NULL);
   @   </nobr></td>
   @   <td>
   @     How quickly do you need this ticket to be resolved?
@@ -388,7 +388,7 @@
     @ <tr>
     @   <td align="right"><nobr>
     @     Assigned To:
-    az = db_query("SELECT id FROM user UNION SELECT '' ORDER BY id");
+    az = db_query("SELECT id FROM user WHERE capabilities LIKE '%%w%%' UNION SELECT '' ORDER BY id");
     cgi_v_optionmenu(0, "w", zWho, (const char **)az);
     db_query_free(az);
     @   </nobr></td>
@@ -397,6 +397,8 @@
     @   </td>
     @ </tr>
     @
+  }
+  if (1) {
     az = db_query("SELECT '', '' UNION ALL "
             "SELECT name, value  FROM enums WHERE type='subsys'");
     if( az[0] && az[1] && az[2] ){
@@ -1316,14 +1318,14 @@
   @ <nobr>
   @ Severity: 
   cgi_optionmenu(0, "e", aParm[5].zNew,
-         "1", "1", "2", "2", "3", "3", "4", "4", "5", "5", 0);
+         "1", "1", "2", "2", "3", "3", "4", "4", "5", "5", NULL);
   @ </nobr>
   @ &nbsp;&nbsp;&nbsp;
   @ 
   @ <nobr>
   @ Assigned To: 
   azUsers = (const char**)db_query(
-              "SELECT id FROM user UNION SELECT '' ORDER BY id");
+              "SELECT id FROM user WHERE capabilities LIKE '%%w%%' UNION SELECT '' ORDER BY id");
   cgi_v_optionmenu(0, "a", aParm[4].zNew, azUsers);
   @ </nobr>
   @ &nbsp;&nbsp;&nbsp;
@@ -1349,7 +1351,7 @@
   @ <nobr>
   @ Priority:
   cgi_optionmenu(0, "p", aParm[6].zNew,
-         "1", "1", "2", "2", "3", "3", "4", "4", "5", "5", 0);
+         "1", "1", "2", "2", "3", "3", "4", "4", "5", "5", NULL);
   @ </nobr>
   @ &nbsp;&nbsp;&nbsp;
   @ 
@@ -1442,16 +1444,18 @@
   int doSubmit;
   const char *zText;
   const char *zTn;
+  const char *zOwner;
   char *zErrMsg = 0;
   char *zTktTitle;
 
   login_check_credentials();
-  if( !g.okWrite ){ login_needed(); return; }
   throttle(1,1);
   tn = rn = 0;
   zTn = PD("tn","");
   sscanf(zTn, "%d,%d", &tn, &rn);
   if( tn<=0 ){ cgi_redirect("index"); return; }
+  zOwner = db_short_query("SELECT owner FROM ticket WHERE tn=%d", tn);
+  if( !g.okWrite && !(zOwner != NULL && strcmp(zOwner, g.zUser) == 0) ){ login_needed(); return; }
   bprintf(zPage,sizeof(zPage),"%d",tn);
   doPreview = P("pre")!=0;
   doSubmit = P("submit")!=0;
Index: timeline.c
--- timeline.c.orig	2006-05-26 16:20:28 +0200
+++ timeline.c	2006-10-30 20:14:51 +0100
@@ -1105,6 +1105,9 @@
   z = db_short_query("SELECT date FROM chng WHERE cn=%d",cn);
   if( z ) cgi_modified_since(atoi(z));
 
+  cgi_append_header(mprintf("Content-disposition: attachment; "
+        "filename=\"%T_patchset_%d.diff\"\r\n", g.zName, cn));
+
   /* patchsets are raw output based one what are supposed to be unchanging file
   ** revisions.
   */
@@ -1383,7 +1386,7 @@
   @    <td><input type="text" name="t" value="%s(zDate)" size=26></td>
   @ <td width=20>&nbsp;</td>
   @ <td>Type: 
-  cgi_optionmenu(2,"y", zMType, "Release", "1", "Event", "2", 0);
+  cgi_optionmenu(2,"y", zMType, "Release", "1", "Event", "2", NULL);
   @ </td></tr>
   @ </td></tr>
   @ <tr><td align="right">Branch:</td><td>
