Fossil

Check-in [787896c5]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Add support for the ETag: and If-None-Match: headers for improved cache control. Currently this only works for /uv but the mechanism is reasonably general and can be extended to other pages.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | etags
Files: files | file ages | folders
SHA3-256:787896c5eb427c4e11f716c9fdc4faa82ca53d197c61d14208405fba278d7db1
User & Date: drh 2018-02-24 03:49:22
Context
2018-02-24
17:08
Fix ETags support for when compiling with FOSSIL_ENABLE_TH1_HOOKS. check-in: 43a66b58 user: drh tags: etags
03:49
Add support for the ETag: and If-None-Match: headers for improved cache control. Currently this only works for /uv but the mechanism is reasonably general and can be extended to other pages. check-in: 787896c5 user: drh tags: etags
03:47
Optimizations to the ETag implementation. check-in: 2588d447 user: drh tags: etags-cache-control
2018-02-23
21:29
Make default project name "<unnamed>" in the output of the "extras" and "changes" commands. check-in: 203d82d8 user: drh tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to src/cgi.c.

   226    226     }else{
   227    227       blob_appendf(&extraHeader,
   228    228          "Set-Cookie: %s=%t; Path=%s; HttpOnly;%s Version=1\r\n",
   229    229          zName, zValue, zPath, zSecure);
   230    230     }
   231    231   }
   232    232   
   233         -#if 0
   234         -/*
   235         -** Add an ETag header line
   236         -*/
   237         -static char *cgi_add_etag(char *zTxt, int nLen){
   238         -  MD5Context ctx;
   239         -  unsigned char digest[16];
   240         -  int i, j;
   241         -  char zETag[64];
   242         -
   243         -  MD5Init(&ctx);
   244         -  MD5Update(&ctx,zTxt,nLen);
   245         -  MD5Final(digest,&ctx);
   246         -  for(j=i=0; i<16; i++,j+=2){
   247         -    bprintf(&zETag[j],sizeof(zETag)-j,"%02x",(int)digest[i]);
   248         -  }
   249         -  blob_appendf(&extraHeader, "ETag: %s\r\n", zETag);
   250         -  return fossil_strdup(zETag);
   251         -}
   252         -
   253         -/*
   254         -** Do some cache control stuff. First, we generate an ETag and include it in
   255         -** the response headers. Second, we do whatever is necessary to determine if
   256         -** the request was asking about caching and whether we need to send back the
   257         -** response body. If we shouldn't send a body, return non-zero.
   258         -**
   259         -** Currently, we just check the ETag against any If-None-Match header.
   260         -**
   261         -** FIXME: In some cases (attachments, file contents) we could check
   262         -** If-Modified-Since headers and always include Last-Modified in responses.
   263         -*/
   264         -static int check_cache_control(void){
   265         -  /* FIXME: there's some gotchas wth cookies and some headers. */
   266         -  char *zETag = cgi_add_etag(blob_buffer(&cgiContent),blob_size(&cgiContent));
   267         -  char *zMatch = P("HTTP_IF_NONE_MATCH");
   268         -
   269         -  if( zETag!=0 && zMatch!=0 ) {
   270         -    char *zBuf = fossil_strdup(zMatch);
   271         -    if( zBuf!=0 ){
   272         -      char *zTok = 0;
   273         -      char *zPos;
   274         -      for( zTok = strtok_r(zBuf, ",\"",&zPos);
   275         -           zTok && fossil_stricmp(zTok,zETag);
   276         -           zTok =  strtok_r(0, ",\"",&zPos)){}
   277         -      fossil_free(zBuf);
   278         -      if(zTok) return 1;
   279         -    }
   280         -  }
   281         -
   282         -  return 0;
   283         -}
   284         -#endif
   285    233   
   286    234   /*
   287    235   ** Return true if the response should be sent with Content-Encoding: gzip.
   288    236   */
   289    237   static int is_gzippable(void){
   290    238     if( strstr(PD("HTTP_ACCEPT_ENCODING", ""), "gzip")==0 ) return 0;
   291    239     return strncmp(zContentType, "text/", 5)==0
................................................................................
   294    242   }
   295    243   
   296    244   /*
   297    245   ** Do a normal HTTP reply
   298    246   */
   299    247   void cgi_reply(void){
   300    248     int total_size;
          249  +  char *zETag;
   301    250     if( iReplyStatus<=0 ){
   302    251       iReplyStatus = 200;
   303    252       zReplyStatus = "OK";
   304    253     }
   305    254   
   306         -#if 0
   307         -  if( iReplyStatus==200 && check_cache_control() ) {
   308         -    /* change the status to "unchanged" and we can skip sending the
   309         -    ** actual response body. Obviously we only do this when we _have_ a
   310         -    ** body (code 200).
   311         -    */
   312         -    iReplyStatus = 304;
   313         -    zReplyStatus = "Not Modified";
   314         -  }
   315         -#endif
   316         -
   317    255     if( g.fullHttpReply ){
   318    256       fprintf(g.httpOut, "HTTP/1.0 %d %s\r\n", iReplyStatus, zReplyStatus);
   319    257       fprintf(g.httpOut, "Date: %s\r\n", cgi_rfc822_datestamp(time(0)));
   320    258       fprintf(g.httpOut, "Connection: close\r\n");
   321    259       fprintf(g.httpOut, "X-UA-Compatible: IE=edge\r\n");
   322    260     }else{
   323    261       fprintf(g.httpOut, "Status: %d %s\r\n", iReplyStatus, zReplyStatus);
   324    262     }
          263  +  zETag = etag_generate(-1);
          264  +  if( zETag ){
          265  +    fprintf(g.httpOut, "ETag: %s\r\n", zETag);
          266  +    fprintf(g.httpOut, "Cache-Control: max-age=%d\r\n", etag_maxage());
          267  +  }
   325    268   
   326    269     if( blob_size(&extraHeader)>0 ){
   327    270       fprintf(g.httpOut, "%s", blob_buffer(&extraHeader));
   328    271     }
   329    272   
   330    273     /* Add headers to turn on useful security options in browsers. */
   331    274     fprintf(g.httpOut, "X-Frame-Options: SAMEORIGIN\r\n");

Changes to src/cookies.c.

   121    121     int flags               /* READ or WRITE or both */
   122    122   ){
   123    123     const char *zQVal = P(zQP);
   124    124     int i;
   125    125     cookie_parse();
   126    126     for(i=0; i<cookies.nParam && strcmp(zPName,cookies.aParam[i].zPName); i++){}
   127    127     if( zQVal==0 && (flags & COOKIE_READ)!=0 && i<cookies.nParam ){
          128  +    etag_require(ETAG_COOKIE);
   128    129       cgi_set_parameter_nocopy(zQP, cookies.aParam[i].zPValue, 1);
   129    130       return;
   130    131     }
   131    132     if( zQVal==0 ) zQVal = zDflt;
   132    133     if( (flags & COOKIE_WRITE)!=0
   133    134      && i<COOKIE_NPARAM
   134    135      && (i==cookies.nParam || strcmp(zQVal, cookies.aParam[i].zPValue))

Changes to src/doc.c.

   637    637             goto doc_not_found;
   638    638           }
   639    639         }else{
   640    640           goto doc_not_found;
   641    641         }
   642    642       }
   643    643       if( isUV ){
   644         -      if( db_table_exists("repository","unversioned")
   645         -       && unversioned_content(zName, &filebody)==0
   646         -      ){
   647         -        rid = 1;
   648         -        zDfltTitle = zName;
          644  +      if( db_table_exists("repository","unversioned") ){
          645  +        char *zHash;
          646  +        zHash = db_text(0, "SELECT hash FROM unversioned WHERE name=%Q",zName);
          647  +        etag_require_hash(zHash);
          648  +        if( unversioned_content(zName, &filebody)==0 ){
          649  +          rid = 1;
          650  +          zDfltTitle = zName;
          651  +        }
   649    652         }
   650    653       }else if( fossil_strcmp(zCheckin,"ckout")==0 ){
   651    654         /* Read from the local checkout */
   652    655         char *zFullpath;
   653    656         db_must_be_within_tree();
   654    657         zFullpath = mprintf("%s/%s", g.zLocalRoot, zName);
   655    658         if( file_isfile(zFullpath, RepoFILE)

Added src/etag.c.

            1  +/*
            2  +** Copyright (c) 2018 D. Richard Hipp
            3  +**
            4  +** This program is free software; you can redistribute it and/or
            5  +** modify it under the terms of the Simplified BSD License (also
            6  +** known as the "2-Clause License" or "FreeBSD License".)
            7  +**
            8  +** This program is distributed in the hope that it will be useful,
            9  +** but without any warranty; without even the implied warranty of
           10  +** merchantability or fitness for a particular purpose.
           11  +**
           12  +** Author contact information:
           13  +**   drh@hwaci.com
           14  +**   http://www.hwaci.com/drh/
           15  +**
           16  +*******************************************************************************
           17  +**
           18  +** This file implements ETags: cache control for Fossil
           19  +**
           20  +** Each ETag value is a text string that represents a sequence of conditionals
           21  +** like this:
           22  +**
           23  +**    if( executable-has-change ) return;
           24  +**    if( database-has-changed ) return;
           25  +**    if( display-cookie-"n"-attribute-has-changes ) return;
           26  +**    Output "304 Not Modified" message and abort;
           27  +**
           28  +** In other words, if all conditions specified by the ETag are met, then
           29  +** Fossil will return a 304 and avoid doing all the work, and all of the
           30  +** bandwidth, associating with regenerating the whole page.
           31  +**
           32  +** To make use of this feature, page generators can invoke the
           33  +** etag_require() interface with mask of ETAG_CONST, ETAG_CONFIG,
           34  +** ETAG_DATA, and/or ETAG_COOKIE.  Or it can invoke etag_require_hash()
           35  +** with some kind of text hash.
           36  +**
           37  +** Or, in the WEBPAGE: line for the page generator, extra arguments
           38  +** can be added.  "const", "config", "data", and/or "cookie"
           39  +**
           40  +** ETAG_CONST    const     The reply is always the same for the same
           41  +**                         build of the fossil binary.  The content
           42  +**                         is independent of the repository.
           43  +**
           44  +** ETAG_CONFIG   config    The reply is the same as long as the repository
           45  +**                         config is constant.
           46  +**
           47  +** ETAG_DATA     data      The reply is the same as long as no new artifacts
           48  +**                         are added to the repository
           49  +**
           50  +** ETAG_COOKIE   cookie    The reply is the same as long as the display
           51  +**                         cookie is unchanged.
           52  +**
           53  +** Page generator routines can also invoke etag_require_hash(HASH) where
           54  +** HASH is some string.  In that case, the reply is the same as long as
           55  +** the hash is the same.
           56  +*/
           57  +#include "config.h"
           58  +#include "etag.h"
           59  +
           60  +#if INTERFACE
           61  +/*
           62  +** Things to monitor
           63  +*/
           64  +#define ETAG_CONST    0x00 /* Output is independent of database or parameters */
           65  +#define ETAG_CONFIG   0x01 /* Output depends on the configuration */
           66  +#define ETAG_DATA     0x02 /* Output depends on 'event' table */
           67  +#define ETAG_COOKIE   0x04 /* Output depends on a display cookie value */
           68  +#define ETAG_HASH     0x08 /* Output depends on a hash */
           69  +#define ETAG_DYNAMIC  0x10 /* Output is always different */
           70  +#endif
           71  +
           72  +/* Set of all etag requirements */
           73  +static int mEtag = 0;           /* Mask of requirements */
           74  +static const char *zEHash = 0;  /* Hash value if ETAG_HASH is set */
           75  +
           76  +
           77  +/* Check an ETag to see if all conditions are valid.  If all conditions are
           78  +** valid, then return true.  If any condition is false, return false.
           79  +*/
           80  +static int etag_valid(const char *zTag){
           81  +  int iKey;
           82  +  char *zCk;
           83  +  int rc;
           84  +  int nTag;
           85  +  if( zTag==0 || zTag[0]<=0 ) return 0;
           86  +  nTag = (int)strlen(zTag);
           87  +  if( zTag[0]=='"' && zTag[nTag-1]=='"' ){
           88  +    zTag++;
           89  +    nTag -= 2;
           90  +  }
           91  +  iKey = zTag[0] - '0';
           92  +  zCk = etag_generate(iKey);
           93  +  rc = nTag==(int)strlen(zCk) && strncmp(zCk, zTag, nTag)==0;
           94  +  fossil_free(zCk);
           95  +  if( rc ) mEtag = iKey;
           96  +  return rc;
           97  +}
           98  +
           99  +/*
          100  +** Check to see if there is an If-None-Match: header that
          101  +** matches the current etag settings.  If there is, then
          102  +** generate a 304 Not Modified reply.
          103  +**
          104  +** This routine exits and does not return if the 304 Not Modified
          105  +** reply is generated.
          106  +**
          107  +** If the etag does not match, the routine returns normally.
          108  +*/
          109  +static void etag_check(void){
          110  +  const char *zETag = P("HTTP_IF_NONE_MATCH");
          111  +  if( zETag==0 ) return;
          112  +  if( !etag_valid(zETag) ) return;
          113  +
          114  +  /* If we get this far, it means that the content has
          115  +  ** not changed and we can do a 304 reply */
          116  +  cgi_reset_content();
          117  +  cgi_set_status(304, "Not Modified");
          118  +  cgi_reply();
          119  +  fossil_exit(0);
          120  +}
          121  +
          122  +
          123  +/* Add one or more new etag requirements.
          124  +**
          125  +** Page generator logic invokes one or both of these methods to signal
          126  +** under what conditions page generation can be skipped
          127  +**
          128  +** After each call to these routines, the HTTP_IF_NONE_MATCH cookie
          129  +** is checked, and if it contains a compatible ETag, then a
          130  +** 304 Not Modified return is generated and execution aborts.  This
          131  +** routine does not return if the 304 is generated.
          132  +*/
          133  +void etag_require(int code){
          134  +  if( (mEtag & code)==code ) return;
          135  +  mEtag |= code;
          136  +  etag_check();
          137  +}
          138  +void etag_require_hash(const char *zHash){
          139  +  if( zHash ){
          140  +    zEHash = zHash;
          141  +    mEtag = ETAG_HASH;
          142  +    etag_check();
          143  +  }
          144  +}
          145  +
          146  +/* Return an appropriate max-age.
          147  +*/
          148  +int etag_maxage(void){
          149  +  if( mEtag ) return 1;
          150  +  return 3600*24;
          151  +}
          152  +
          153  +/* Generate an appropriate ETags value that captures all requirements.
          154  +** Space is obtained from fossil_malloc().
          155  +**
          156  +** The argument is the mask of attributes to include in the ETag.
          157  +** If the argument is -1 then whatever mask is found from prior
          158  +** calls to etag_require() and etag_require_hash() is used.
          159  +**
          160  +** Format:
          161  +**
          162  +**    <mask><exec-mtime>/<data-or-config-key>/<cookie>/<hash>
          163  +**
          164  +** The <mask> is a single-character decimal number that is the mask of
          165  +** all required attributes:
          166  +**
          167  +**     ETAG_CONFIG:    1
          168  +**     ETAG_DATA:      2
          169  +**     ETAG_COOKIE:    4
          170  +**     ETAG_HASH:      8
          171  +**
          172  +** If ETAG_HASH is present, the others are omitted, so the number is
          173  +** never greater than 8.
          174  +**
          175  +** The <exec-mtime> is the mtime of the Fossil executable.  Since this
          176  +** is part of the ETag, it means that recompiling or just "touch"-ing the
          177  +** fossil binary is sufficient to invalidate all prior caches.
          178  +**
          179  +** The other elements are only present if the appropriate mask bits
          180  +** appear in the first character.
          181  +*/
          182  +char *etag_generate(int m){
          183  +  Blob x = BLOB_INITIALIZER;
          184  +  static int mtime = 0;
          185  +  if( m<0 ) m = mEtag;
          186  +  if( m & ETAG_DYNAMIC ) return 0;
          187  +  if( mtime==0 ) mtime = file_mtime(g.nameOfExe, ExtFILE);
          188  +  blob_appendf(&x,"%d%x", m, mtime);
          189  +  if( m & ETAG_HASH ){
          190  +    blob_appendf(&x, "/%s", zEHash);
          191  +  }else if( m & ETAG_DATA ){
          192  +    int iKey = db_int(0, "SELECT max(rcvid) FROM rcvfrom");
          193  +    blob_appendf(&x, "/%x", iKey);
          194  +  }else if( m & ETAG_CONFIG ){
          195  +    int iKey = db_int(0, "SELECT value FROM config WHERE name='cfgcnt'");
          196  +    blob_appendf(&x, "/%x", iKey);
          197  +  }
          198  +  if( m & ETAG_COOKIE ){
          199  +    blob_appendf(&x, "/%s", P(DISPLAY_SETTINGS_COOKIE));
          200  +  }
          201  +  return blob_str(&x);
          202  +}
          203  +
          204  +/* 
          205  +** COMMAND: test-etag
          206  +**
          207  +** Usage:  fossil test-etag -key KEY-NUMBER  -hash HASH
          208  +**
          209  +** Generate an etag given a KEY-NUMBER and/or a HASH.
          210  +**
          211  +** KEY-NUMBER is some combination of:
          212  +**
          213  +**    1   ETAG_CONFIG   The config table version number
          214  +**    2   ETAG_DATA     The event table version number
          215  +**    4   ETAG_COOKIE   The display cookie
          216  +*/
          217  +void test_etag_cmd(void){
          218  +  char *zTag;
          219  +  const char *zHash;
          220  +  const char *zKey;
          221  +  db_find_and_open_repository(0, 0);
          222  +  zKey = find_option("key",0,1);
          223  +  zHash = find_option("hash",0,1);
          224  +  if( zKey ) etag_require(atoi(zKey));
          225  +  if( zHash ) etag_require_hash(zHash);
          226  +  zTag = etag_generate(mEtag);
          227  +  fossil_print("%s\n", zTag);
          228  +  fossil_free(zTag);
          229  +}

Changes to src/main.c.

  1397   1397     int allowRepoList           /* Send repo list for "/" URL */
  1398   1398   ){
  1399   1399     const char *zPathInfo = PD("PATH_INFO", "");
  1400   1400     char *zPath = NULL;
  1401   1401     int i;
  1402   1402     const CmdOrPage *pCmd = 0;
  1403   1403     const char *zBase = g.zRepositoryName;
         1404  +  const char *zETag = 0;
  1404   1405   
  1405   1406     /* Handle universal query parameters */
  1406   1407     if( PB("utc") ){
  1407   1408       g.fTimeFormat = 1;
  1408   1409     }else if( PB("localtime") ){
  1409   1410       g.fTimeFormat = 2;
  1410   1411     }
................................................................................
  1762   1763       */
  1763   1764       int rc;
  1764   1765       if( !g.fNoThHook ){
  1765   1766         rc = Th_WebpageHook(pCmd->zName+1, pCmd->eCmdFlags);
  1766   1767       }else{
  1767   1768         rc = TH_OK;
  1768   1769       }
         1770  +    etag_require(CMDFLAG_TO_ETAG(pCmd->eType));
  1769   1771       if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
  1770   1772         if( rc==TH_OK || rc==TH_RETURN ){
  1771   1773   #endif
  1772   1774           pCmd->xFunc();
  1773   1775   #ifdef FOSSIL_ENABLE_TH1_HOOKS
  1774   1776         }
  1775   1777         if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){

Changes to src/main.mk.

    41     41     $(SRCDIR)/deltacmd.c \
    42     42     $(SRCDIR)/descendants.c \
    43     43     $(SRCDIR)/diff.c \
    44     44     $(SRCDIR)/diffcmd.c \
    45     45     $(SRCDIR)/dispatch.c \
    46     46     $(SRCDIR)/doc.c \
    47     47     $(SRCDIR)/encode.c \
           48  +  $(SRCDIR)/etag.c \
    48     49     $(SRCDIR)/event.c \
    49     50     $(SRCDIR)/export.c \
    50     51     $(SRCDIR)/file.c \
    51     52     $(SRCDIR)/finfo.c \
    52     53     $(SRCDIR)/foci.c \
    53     54     $(SRCDIR)/fshell.c \
    54     55     $(SRCDIR)/fusefs.c \
................................................................................
   241    242     $(OBJDIR)/deltacmd_.c \
   242    243     $(OBJDIR)/descendants_.c \
   243    244     $(OBJDIR)/diff_.c \
   244    245     $(OBJDIR)/diffcmd_.c \
   245    246     $(OBJDIR)/dispatch_.c \
   246    247     $(OBJDIR)/doc_.c \
   247    248     $(OBJDIR)/encode_.c \
          249  +  $(OBJDIR)/etag_.c \
   248    250     $(OBJDIR)/event_.c \
   249    251     $(OBJDIR)/export_.c \
   250    252     $(OBJDIR)/file_.c \
   251    253     $(OBJDIR)/finfo_.c \
   252    254     $(OBJDIR)/foci_.c \
   253    255     $(OBJDIR)/fshell_.c \
   254    256     $(OBJDIR)/fusefs_.c \
................................................................................
   370    372    $(OBJDIR)/deltacmd.o \
   371    373    $(OBJDIR)/descendants.o \
   372    374    $(OBJDIR)/diff.o \
   373    375    $(OBJDIR)/diffcmd.o \
   374    376    $(OBJDIR)/dispatch.o \
   375    377    $(OBJDIR)/doc.o \
   376    378    $(OBJDIR)/encode.o \
          379  + $(OBJDIR)/etag.o \
   377    380    $(OBJDIR)/event.o \
   378    381    $(OBJDIR)/export.o \
   379    382    $(OBJDIR)/file.o \
   380    383    $(OBJDIR)/finfo.o \
   381    384    $(OBJDIR)/foci.o \
   382    385    $(OBJDIR)/fshell.o \
   383    386    $(OBJDIR)/fusefs.o \
................................................................................
   671    674   	$(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h \
   672    675   	$(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h \
   673    676   	$(OBJDIR)/diff_.c:$(OBJDIR)/diff.h \
   674    677   	$(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h \
   675    678   	$(OBJDIR)/dispatch_.c:$(OBJDIR)/dispatch.h \
   676    679   	$(OBJDIR)/doc_.c:$(OBJDIR)/doc.h \
   677    680   	$(OBJDIR)/encode_.c:$(OBJDIR)/encode.h \
          681  +	$(OBJDIR)/etag_.c:$(OBJDIR)/etag.h \
   678    682   	$(OBJDIR)/event_.c:$(OBJDIR)/event.h \
   679    683   	$(OBJDIR)/export_.c:$(OBJDIR)/export.h \
   680    684   	$(OBJDIR)/file_.c:$(OBJDIR)/file.h \
   681    685   	$(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \
   682    686   	$(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \
   683    687   	$(OBJDIR)/fshell_.c:$(OBJDIR)/fshell.h \
   684    688   	$(OBJDIR)/fusefs_.c:$(OBJDIR)/fusefs.h \
................................................................................
  1014   1018   $(OBJDIR)/encode_.c:	$(SRCDIR)/encode.c $(OBJDIR)/translate
  1015   1019   	$(OBJDIR)/translate $(SRCDIR)/encode.c >$@
  1016   1020   
  1017   1021   $(OBJDIR)/encode.o:	$(OBJDIR)/encode_.c $(OBJDIR)/encode.h $(SRCDIR)/config.h
  1018   1022   	$(XTCC) -o $(OBJDIR)/encode.o -c $(OBJDIR)/encode_.c
  1019   1023   
  1020   1024   $(OBJDIR)/encode.h:	$(OBJDIR)/headers
         1025  +
         1026  +$(OBJDIR)/etag_.c:	$(SRCDIR)/etag.c $(OBJDIR)/translate
         1027  +	$(OBJDIR)/translate $(SRCDIR)/etag.c >$@
         1028  +
         1029  +$(OBJDIR)/etag.o:	$(OBJDIR)/etag_.c $(OBJDIR)/etag.h $(SRCDIR)/config.h
         1030  +	$(XTCC) -o $(OBJDIR)/etag.o -c $(OBJDIR)/etag_.c
         1031  +
         1032  +$(OBJDIR)/etag.h:	$(OBJDIR)/headers
  1021   1033   
  1022   1034   $(OBJDIR)/event_.c:	$(SRCDIR)/event.c $(OBJDIR)/translate
  1023   1035   	$(OBJDIR)/translate $(SRCDIR)/event.c >$@
  1024   1036   
  1025   1037   $(OBJDIR)/event.o:	$(OBJDIR)/event_.c $(OBJDIR)/event.h $(SRCDIR)/config.h
  1026   1038   	$(XTCC) -o $(OBJDIR)/event.o -c $(OBJDIR)/event_.c
  1027   1039   

Changes to src/makemake.tcl.

    53     53     deltacmd
    54     54     descendants
    55     55     diff
    56     56     diffcmd
    57     57     dispatch
    58     58     doc
    59     59     encode
           60  +  etag
    60     61     event
    61     62     export
    62     63     file
    63     64     finfo
    64     65     foci
    65     66     fshell
    66     67     fusefs

Changes to src/mkindex.c.

    78     78   #include <assert.h>
    79     79   #include <string.h>
    80     80   
    81     81   /***************************************************************************
    82     82   ** These macros must match similar macros in dispatch.c.
    83     83   **
    84     84   ** Allowed values for CmdOrPage.eCmdFlags. */
    85         -#define CMDFLAG_1ST_TIER    0x0001      /* Most important commands */
    86         -#define CMDFLAG_2ND_TIER    0x0002      /* Obscure and seldom used commands */
    87         -#define CMDFLAG_TEST        0x0004      /* Commands for testing only */
    88         -#define CMDFLAG_WEBPAGE     0x0008      /* Web pages */
    89         -#define CMDFLAG_COMMAND     0x0010      /* A command */
    90         -#define CMDFLAG_SETTING     0x0020      /* A setting */
    91         -#define CMDFLAG_VERSIONABLE 0x0040      /* A versionable setting */
    92         -#define CMDFLAG_BLOCKTEXT   0x0080      /* Multi-line text setting */
    93         -#define CMDFLAG_BOOLEAN     0x0100      /* A boolean setting */
           85  +#define CMDFLAG_1ST_TIER    0x00001      /* Most important commands */
           86  +#define CMDFLAG_2ND_TIER    0x00002      /* Obscure and seldom used commands */
           87  +#define CMDFLAG_TEST        0x00004      /* Commands for testing only */
           88  +#define CMDFLAG_WEBPAGE     0x00008      /* Web pages */
           89  +#define CMDFLAG_COMMAND     0x00010      /* A command */
           90  +#define CMDFLAG_SETTING     0x00020      /* A setting */
           91  +#define CMDFLAG_VERSIONABLE 0x00040      /* A versionable setting */
           92  +#define CMDFLAG_BLOCKTEXT   0x00080      /* Multi-line text setting */
           93  +#define CMDFLAG_BOOLEAN     0x00100      /* A boolean setting */
           94  +#define CMDFLAG_CONST       0x00000      /* ETAG_CONST */
           95  +#define CMDFLAG_CONFIG      0x01000      /* ETAG_CONFIG */
           96  +#define CMDFLAG_DATA        0x02000      /* ETAG_DATA */
           97  +#define CMDFLAG_COOKIE      0x04000      /* ETAG_COOKIE */
           98  +#define CMDFLAG_DYNAMIC     0x10000      /* ETAG_DYNAMIC - on by default */
           99  +#define CMDFLAG_ETAG        0x1f000      /* Mask of all ETAG entries */
          100  +#define CMDFLAG_TO_ETAG(X)  ((X)>>12)
    94    101   /**************************************************************************/
    95    102   
    96    103   /*
    97    104   ** Each entry looks like this:
    98    105   */
    99    106   typedef struct Entry {
   100    107     int eType;        /* CMDFLAG_* values */
................................................................................
   196    203       i += len;
   197    204     }else{
   198    205       return;
   199    206     }
   200    207     while( fossil_isspace(zLine[i]) ){ i++; }
   201    208     if( zLine[i]=='/' ) i++;
   202    209     for(j=0; zLine[i+j] && !fossil_isspace(zLine[i+j]); j++){}
   203         -  aEntry[nUsed].eType = eType;
          210  +  aEntry[nUsed].eType = eType | CMDFLAG_DYNAMIC;
   204    211     if( eType & CMDFLAG_WEBPAGE ){
   205    212       aEntry[nUsed].zPath = string_dup(&zLine[i-1], j+1);
   206    213       aEntry[nUsed].zPath[0] = '/';
   207    214     }else{
   208    215       aEntry[nUsed].zPath = string_dup(&zLine[i], j);
   209    216     }
   210    217     aEntry[nUsed].zFunc = 0;
................................................................................
   234    241         aEntry[nUsed].eType |= CMDFLAG_1ST_TIER;
   235    242       }else if( j==8 && strncmp(&zLine[i], "2nd-tier", j)==0 ){
   236    243         aEntry[nUsed].eType &= ~(CMDFLAG_1ST_TIER|CMDFLAG_TEST);
   237    244         aEntry[nUsed].eType |= CMDFLAG_2ND_TIER;
   238    245       }else if( j==4 && strncmp(&zLine[i], "test", j)==0 ){
   239    246         aEntry[nUsed].eType &= ~(CMDFLAG_1ST_TIER|CMDFLAG_2ND_TIER);
   240    247         aEntry[nUsed].eType |= CMDFLAG_TEST;
          248  +    }else if( j==5 && strncmp(&zLine[i], "const", j)==0 ){
          249  +      aEntry[nUsed].eType &= ~CMDFLAG_ETAG;
          250  +      aEntry[nUsed].eType |= CMDFLAG_CONST;
          251  +    }else if( j==6 && strncmp(&zLine[i], "config", j)==0 ){
          252  +      aEntry[nUsed].eType &= ~CMDFLAG_ETAG;
          253  +      aEntry[nUsed].eType |= CMDFLAG_CONFIG;
          254  +    }else if( j==4 && strncmp(&zLine[i], "data", j)==0 ){
          255  +      aEntry[nUsed].eType &= ~CMDFLAG_ETAG;
          256  +      aEntry[nUsed].eType |= CMDFLAG_DATA;
          257  +    }else if( j==4 && strncmp(&zLine[i], "cookie", j)==0 ){
          258  +      aEntry[nUsed].eType &= ~CMDFLAG_ETAG;
          259  +      aEntry[nUsed].eType |= CMDFLAG_COOKIE;
   241    260       }else if( j==7 && strncmp(&zLine[i], "boolean", j)==0 ){
   242    261         aEntry[nUsed].eType &= ~(CMDFLAG_BLOCKTEXT);
   243    262         aEntry[nUsed].iWidth = 0;
   244    263         aEntry[nUsed].eType |= CMDFLAG_BOOLEAN;
   245    264       }else if( j==10 && strncmp(&zLine[i], "block-text", j)==0 ){
   246    265         aEntry[nUsed].eType &= ~(CMDFLAG_BOOLEAN);
   247    266         aEntry[nUsed].eType |= CMDFLAG_BLOCKTEXT;

Changes to src/setup.c.

    16     16   *******************************************************************************
    17     17   **
    18     18   ** Implementation of the Setup page
    19     19   */
    20     20   #include "config.h"
    21     21   #include <assert.h>
    22     22   #include "setup.h"
           23  +
           24  +/*
           25  +** Increment the "cfgcnt" variable, so that ETags will know that
           26  +** the configuration has changed.
           27  +*/
           28  +void setup_incr_cfgcnt(void){
           29  +  static int once = 1;
           30  +  if( once ){
           31  +    once = 0;
           32  +    db_multi_exec("UPDATE config SET value=value+1 WHERE name='cfgcnt'");
           33  +    if( db_changes()==0 ){
           34  +      db_multi_exec("INSERT INTO config(name,value) VALUES('cfgcnt',1)");
           35  +    }
           36  +  }
           37  +}
    23     38   
    24     39   /*
    25     40   ** Output a single entry for a menu generated using an HTML table.
    26     41   ** If zLink is not NULL or an empty string, then it is the page that
    27     42   ** the menu entry will hyperlink to.  If zLink is NULL or "", then
    28     43   ** the menu entry has no hyperlink - it is disabled.
    29     44   */
................................................................................
   494    509       }
   495    510       login_verify_csrf_secret();
   496    511       db_multi_exec(
   497    512          "REPLACE INTO user(uid,login,info,pw,cap,mtime) "
   498    513          "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())",
   499    514         uid, zLogin, P("info"), zPw, zCap
   500    515       );
          516  +    setup_incr_cfgcnt();
   501    517       admin_log( "Updated user [%q] with capabilities [%q].",
   502    518                  zLogin, zCap );
   503    519       if( atoi(PD("all","0"))>0 ){
   504    520         Blob sql;
   505    521         char *zErr = 0;
   506    522         blob_zero(&sql);
   507    523         if( zOldLogin==0 ){

Changes to src/timeline.c.

  1327   1327     }
  1328   1328   
  1329   1329     /* If execution reaches this point, the pattern was empty.  Return NULL. */
  1330   1330     return 0;
  1331   1331   }
  1332   1332   
  1333   1333   /*
  1334         -** WEBPAGE: timeline
         1334  +** WEBPAGE: timeline data
  1335   1335   **
  1336   1336   ** Query parameters:
  1337   1337   **
  1338   1338   **    a=TIMEORTAG     After this event
  1339   1339   **    b=TIMEORTAG     Before this event
  1340   1340   **    c=TIMEORTAG     "Circa" this event
  1341   1341   **    m=TIMEORTAG     Mark this event
................................................................................
  2508   2508            db_column_text(&q, 3));
  2509   2509       }
  2510   2510     }
  2511   2511     db_finalize(&q);
  2512   2512   }
  2513   2513   
  2514   2514   /*
  2515         -** WEBPAGE: timewarps
         2515  +** WEBPAGE: timewarps data
  2516   2516   **
  2517   2517   ** Show all check-ins that are "timewarps".  A timewarp is a
  2518   2518   ** check-in that occurs before its parent, according to the
  2519   2519   ** timestamp information on the check-in.  This can only actually
  2520   2520   ** happen, of course, if a users system clock is set incorrectly.
  2521   2521   */
  2522   2522   void test_timewarp_page(void){

Changes to src/unversioned.c.

   151    151   **    0:     zName does not exist in the unversioned table.
   152    152   **    1:     zName exists and should be replaced by the mtime/zHash remote.
   153    153   **    2:     zName exists and is the same as zHash but has a older mtime
   154    154   **    3:     zName exists and is identical to mtime/zHash in all respects.
   155    155   **    4:     zName exists and is the same as zHash but has a newer mtime.
   156    156   **    5:     zName exists and should override the mtime/zHash remote.
   157    157   */
   158         -int unversioned_status(const char *zName, sqlite3_int64 mtime, const char *zHash){
          158  +int unversioned_status(
          159  +  const char *zName,
          160  +  sqlite3_int64 mtime,
          161  +  const char *zHash
          162  +){
   159    163     int iStatus = 0;
   160    164     Stmt q;
   161    165     db_prepare(&q, "SELECT mtime, hash FROM unversioned WHERE name=%Q", zName);
   162    166     if( db_step(&q)==SQLITE_ROW ){
   163    167       const char *zLocalHash = db_column_text(&q, 1);
   164    168       int hashCmp;
   165    169       sqlite3_int64 iLocalMtime = db_column_int64(&q, 0);

Changes to win/Makefile.dmc.

    26     26   TCC    = $(DMDIR)\bin\dmc $(CFLAGS) $(DMCDEF) $(SSL) $(INCL)
    27     27   LIBS   = $(DMDIR)\extra\lib\ zlib wsock32 advapi32
    28     28   
    29     29   SQLITE_OPTIONS = -DNDEBUG=1 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_GET_TABLE -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_USE_ALLOCA -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_USE_ZLIB -DSQLITE_INTROSPECTION_PRAGMAS -DSQLITE_ENABLE_DBPAGE_VTAB
    30     30   
    31     31   SHELL_OPTIONS = -Dmain=sqlite3_shell -DSQLITE_SHELL_IS_UTF8=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=fossil_open -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen
    32     32   
    33         -SRC   = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c foci_.c fshell_.c fusefs_.c glob_.c graph_.c gzip_.c hname_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c path_.c piechart_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c wysiwyg_.c xfer_.c xfersetup_.c zip_.c
           33  +SRC   = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c file_.c finfo_.c foci_.c fshell_.c fusefs_.c glob_.c graph_.c gzip_.c hname_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c path_.c piechart_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c wysiwyg_.c xfer_.c xfersetup_.c zip_.c
    34     34   
    35         -OBJ   = $(OBJDIR)\add$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\file$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\wysiwyg$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O
           35  +OBJ   = $(OBJDIR)\add$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\file$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\wysiwyg$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O
    36     36   
    37     37   
    38     38   RC=$(DMDIR)\bin\rcc
    39     39   RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__
    40     40   
    41     41   APPNAME = $(OBJDIR)\fossil$(E)
    42     42   
................................................................................
    47     47   	codecheck1$E $(SRC)
    48     48   	$(DMDIR)\bin\link @link
    49     49   
    50     50   $(OBJDIR)\fossil.res:	$B\win\fossil.rc
    51     51   	$(RC) $(RCFLAGS) -o$@ $**
    52     52   
    53     53   $(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res
    54         -	+echo add allrepo attach bag bisect blob branch browse builtin bundle cache captcha cgi checkin checkout clearsign clone comformat configure content cookies db delta deltacmd descendants diff diffcmd dispatch doc encode event export file finfo foci fshell fusefs glob graph gzip hname http http_socket http_ssl http_transport import info json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name path piechart pivot popen pqueue printf publish purge rebuild regexp report rss schema search security_audit setup sha1 sha1hard sha3 shun sitemap skins sqlcmd stash stat statrep style sync tag tar th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile wiki wikiformat winfile winhttp wysiwyg xfer xfersetup zip shell sqlite3 th th_lang > $@
           54  +	+echo add allrepo attach bag bisect blob branch browse builtin bundle cache captcha cgi checkin checkout clearsign clone comformat configure content cookies db delta deltacmd descendants diff diffcmd dispatch doc encode etag event export file finfo foci fshell fusefs glob graph gzip hname http http_socket http_ssl http_transport import info json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name path piechart pivot popen pqueue printf publish purge rebuild regexp report rss schema search security_audit setup sha1 sha1hard sha3 shun sitemap skins sqlcmd stash stat statrep style sync tag tar th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile wiki wikiformat winfile winhttp wysiwyg xfer xfersetup zip shell sqlite3 th th_lang > $@
    55     55   	+echo fossil >> $@
    56     56   	+echo fossil >> $@
    57     57   	+echo $(LIBS) >> $@
    58     58   	+echo. >> $@
    59     59   	+echo fossil >> $@
    60     60   
    61     61   translate$E: $(SRCDIR)\translate.c
................................................................................
   306    306   	+translate$E $** > $@
   307    307   
   308    308   $(OBJDIR)\encode$O : encode_.c encode.h
   309    309   	$(TCC) -o$@ -c encode_.c
   310    310   
   311    311   encode_.c : $(SRCDIR)\encode.c
   312    312   	+translate$E $** > $@
          313  +
          314  +$(OBJDIR)\etag$O : etag_.c etag.h
          315  +	$(TCC) -o$@ -c etag_.c
          316  +
          317  +etag_.c : $(SRCDIR)\etag.c
          318  +	+translate$E $** > $@
   313    319   
   314    320   $(OBJDIR)\event$O : event_.c event.h
   315    321   	$(TCC) -o$@ -c event_.c
   316    322   
   317    323   event_.c : $(SRCDIR)\event.c
   318    324   	+translate$E $** > $@
   319    325   
................................................................................
   890    896   $(OBJDIR)\zip$O : zip_.c zip.h
   891    897   	$(TCC) -o$@ -c zip_.c
   892    898   
   893    899   zip_.c : $(SRCDIR)\zip.c
   894    900   	+translate$E $** > $@
   895    901   
   896    902   headers: makeheaders$E page_index.h builtin_data.h default_css.h VERSION.h
   897         -	 +makeheaders$E add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h event_.c:event.h export_.c:export.h file_.c:file.h finfo_.c:finfo.h foci_.c:foci.h fshell_.c:fshell.h fusefs_.c:fusefs.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h path_.c:path.h piechart_.c:piechart.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h wysiwyg_.c:wysiwyg.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h
          903  +	 +makeheaders$E add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h file_.c:file.h finfo_.c:finfo.h foci_.c:foci.h fshell_.c:fshell.h fusefs_.c:fusefs.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h path_.c:path.h piechart_.c:piechart.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h wysiwyg_.c:wysiwyg.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h
   898    904   	@copy /Y nul: headers

Changes to win/Makefile.mingw.

   450    450     $(SRCDIR)/deltacmd.c \
   451    451     $(SRCDIR)/descendants.c \
   452    452     $(SRCDIR)/diff.c \
   453    453     $(SRCDIR)/diffcmd.c \
   454    454     $(SRCDIR)/dispatch.c \
   455    455     $(SRCDIR)/doc.c \
   456    456     $(SRCDIR)/encode.c \
          457  +  $(SRCDIR)/etag.c \
   457    458     $(SRCDIR)/event.c \
   458    459     $(SRCDIR)/export.c \
   459    460     $(SRCDIR)/file.c \
   460    461     $(SRCDIR)/finfo.c \
   461    462     $(SRCDIR)/foci.c \
   462    463     $(SRCDIR)/fshell.c \
   463    464     $(SRCDIR)/fusefs.c \
................................................................................
   650    651     $(OBJDIR)/deltacmd_.c \
   651    652     $(OBJDIR)/descendants_.c \
   652    653     $(OBJDIR)/diff_.c \
   653    654     $(OBJDIR)/diffcmd_.c \
   654    655     $(OBJDIR)/dispatch_.c \
   655    656     $(OBJDIR)/doc_.c \
   656    657     $(OBJDIR)/encode_.c \
          658  +  $(OBJDIR)/etag_.c \
   657    659     $(OBJDIR)/event_.c \
   658    660     $(OBJDIR)/export_.c \
   659    661     $(OBJDIR)/file_.c \
   660    662     $(OBJDIR)/finfo_.c \
   661    663     $(OBJDIR)/foci_.c \
   662    664     $(OBJDIR)/fshell_.c \
   663    665     $(OBJDIR)/fusefs_.c \
................................................................................
   779    781    $(OBJDIR)/deltacmd.o \
   780    782    $(OBJDIR)/descendants.o \
   781    783    $(OBJDIR)/diff.o \
   782    784    $(OBJDIR)/diffcmd.o \
   783    785    $(OBJDIR)/dispatch.o \
   784    786    $(OBJDIR)/doc.o \
   785    787    $(OBJDIR)/encode.o \
          788  + $(OBJDIR)/etag.o \
   786    789    $(OBJDIR)/event.o \
   787    790    $(OBJDIR)/export.o \
   788    791    $(OBJDIR)/file.o \
   789    792    $(OBJDIR)/finfo.o \
   790    793    $(OBJDIR)/foci.o \
   791    794    $(OBJDIR)/fshell.o \
   792    795    $(OBJDIR)/fusefs.o \
................................................................................
  1127   1130   		$(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h \
  1128   1131   		$(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h \
  1129   1132   		$(OBJDIR)/diff_.c:$(OBJDIR)/diff.h \
  1130   1133   		$(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h \
  1131   1134   		$(OBJDIR)/dispatch_.c:$(OBJDIR)/dispatch.h \
  1132   1135   		$(OBJDIR)/doc_.c:$(OBJDIR)/doc.h \
  1133   1136   		$(OBJDIR)/encode_.c:$(OBJDIR)/encode.h \
         1137  +		$(OBJDIR)/etag_.c:$(OBJDIR)/etag.h \
  1134   1138   		$(OBJDIR)/event_.c:$(OBJDIR)/event.h \
  1135   1139   		$(OBJDIR)/export_.c:$(OBJDIR)/export.h \
  1136   1140   		$(OBJDIR)/file_.c:$(OBJDIR)/file.h \
  1137   1141   		$(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \
  1138   1142   		$(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \
  1139   1143   		$(OBJDIR)/fshell_.c:$(OBJDIR)/fshell.h \
  1140   1144   		$(OBJDIR)/fusefs_.c:$(OBJDIR)/fusefs.h \
................................................................................
  1472   1476   $(OBJDIR)/encode_.c:	$(SRCDIR)/encode.c $(TRANSLATE)
  1473   1477   	$(TRANSLATE) $(SRCDIR)/encode.c >$@
  1474   1478   
  1475   1479   $(OBJDIR)/encode.o:	$(OBJDIR)/encode_.c $(OBJDIR)/encode.h $(SRCDIR)/config.h
  1476   1480   	$(XTCC) -o $(OBJDIR)/encode.o -c $(OBJDIR)/encode_.c
  1477   1481   
  1478   1482   $(OBJDIR)/encode.h:	$(OBJDIR)/headers
         1483  +
         1484  +$(OBJDIR)/etag_.c:	$(SRCDIR)/etag.c $(TRANSLATE)
         1485  +	$(TRANSLATE) $(SRCDIR)/etag.c >$@
         1486  +
         1487  +$(OBJDIR)/etag.o:	$(OBJDIR)/etag_.c $(OBJDIR)/etag.h $(SRCDIR)/config.h
         1488  +	$(XTCC) -o $(OBJDIR)/etag.o -c $(OBJDIR)/etag_.c
         1489  +
         1490  +$(OBJDIR)/etag.h:	$(OBJDIR)/headers
  1479   1491   
  1480   1492   $(OBJDIR)/event_.c:	$(SRCDIR)/event.c $(TRANSLATE)
  1481   1493   	$(TRANSLATE) $(SRCDIR)/event.c >$@
  1482   1494   
  1483   1495   $(OBJDIR)/event.o:	$(OBJDIR)/event_.c $(OBJDIR)/event.h $(SRCDIR)/config.h
  1484   1496   	$(XTCC) -o $(OBJDIR)/event.o -c $(OBJDIR)/event_.c
  1485   1497   

Changes to win/Makefile.msc.

   380    380           deltacmd_.c \
   381    381           descendants_.c \
   382    382           diff_.c \
   383    383           diffcmd_.c \
   384    384           dispatch_.c \
   385    385           doc_.c \
   386    386           encode_.c \
          387  +        etag_.c \
   387    388           event_.c \
   388    389           export_.c \
   389    390           file_.c \
   390    391           finfo_.c \
   391    392           foci_.c \
   392    393           fshell_.c \
   393    394           fusefs_.c \
................................................................................
   579    580           $(OX)\deltacmd$O \
   580    581           $(OX)\descendants$O \
   581    582           $(OX)\diff$O \
   582    583           $(OX)\diffcmd$O \
   583    584           $(OX)\dispatch$O \
   584    585           $(OX)\doc$O \
   585    586           $(OX)\encode$O \
          587  +        $(OX)\etag$O \
   586    588           $(OX)\event$O \
   587    589           $(OX)\export$O \
   588    590           $(OX)\file$O \
   589    591           $(OX)\finfo$O \
   590    592           $(OX)\foci$O \
   591    593           $(OX)\fshell$O \
   592    594           $(OX)\fusefs$O \
................................................................................
   767    769   	echo $(OX)\deltacmd.obj >> $@
   768    770   	echo $(OX)\descendants.obj >> $@
   769    771   	echo $(OX)\diff.obj >> $@
   770    772   	echo $(OX)\diffcmd.obj >> $@
   771    773   	echo $(OX)\dispatch.obj >> $@
   772    774   	echo $(OX)\doc.obj >> $@
   773    775   	echo $(OX)\encode.obj >> $@
          776  +	echo $(OX)\etag.obj >> $@
   774    777   	echo $(OX)\event.obj >> $@
   775    778   	echo $(OX)\export.obj >> $@
   776    779   	echo $(OX)\file.obj >> $@
   777    780   	echo $(OX)\finfo.obj >> $@
   778    781   	echo $(OX)\foci.obj >> $@
   779    782   	echo $(OX)\fshell.obj >> $@
   780    783   	echo $(OX)\fusefs.obj >> $@
................................................................................
  1166   1169   	translate$E $** > $@
  1167   1170   
  1168   1171   $(OX)\encode$O : encode_.c encode.h
  1169   1172   	$(TCC) /Fo$@ -c encode_.c
  1170   1173   
  1171   1174   encode_.c : $(SRCDIR)\encode.c
  1172   1175   	translate$E $** > $@
         1176  +
         1177  +$(OX)\etag$O : etag_.c etag.h
         1178  +	$(TCC) /Fo$@ -c etag_.c
         1179  +
         1180  +etag_.c : $(SRCDIR)\etag.c
         1181  +	translate$E $** > $@
  1173   1182   
  1174   1183   $(OX)\event$O : event_.c event.h
  1175   1184   	$(TCC) /Fo$@ -c event_.c
  1176   1185   
  1177   1186   event_.c : $(SRCDIR)\event.c
  1178   1187   	translate$E $** > $@
  1179   1188   
................................................................................
  1783   1792   			deltacmd_.c:deltacmd.h \
  1784   1793   			descendants_.c:descendants.h \
  1785   1794   			diff_.c:diff.h \
  1786   1795   			diffcmd_.c:diffcmd.h \
  1787   1796   			dispatch_.c:dispatch.h \
  1788   1797   			doc_.c:doc.h \
  1789   1798   			encode_.c:encode.h \
         1799  +			etag_.c:etag.h \
  1790   1800   			event_.c:event.h \
  1791   1801   			export_.c:export.h \
  1792   1802   			file_.c:file.h \
  1793   1803   			finfo_.c:finfo.h \
  1794   1804   			foci_.c:foci.h \
  1795   1805   			fshell_.c:fshell.h \
  1796   1806   			fusefs_.c:fusefs.h \