Index: src/cgi.c ================================================================== --- src/cgi.c +++ src/cgi.c @@ -228,62 +228,10 @@ "Set-Cookie: %s=%t; Path=%s; HttpOnly;%s Version=1\r\n", zName, zValue, zPath, zSecure); } } -#if 0 -/* -** Add an ETag header line -*/ -static char *cgi_add_etag(char *zTxt, int nLen){ - MD5Context ctx; - unsigned char digest[16]; - int i, j; - char zETag[64]; - - MD5Init(&ctx); - MD5Update(&ctx,zTxt,nLen); - MD5Final(digest,&ctx); - for(j=i=0; i<16; i++,j+=2){ - bprintf(&zETag[j],sizeof(zETag)-j,"%02x",(int)digest[i]); - } - blob_appendf(&extraHeader, "ETag: %s\r\n", zETag); - return fossil_strdup(zETag); -} - -/* -** Do some cache control stuff. First, we generate an ETag and include it in -** the response headers. Second, we do whatever is necessary to determine if -** the request was asking about caching and whether we need to send back the -** response body. If we shouldn't send a body, return non-zero. -** -** Currently, we just check the ETag against any If-None-Match header. -** -** FIXME: In some cases (attachments, file contents) we could check -** If-Modified-Since headers and always include Last-Modified in responses. -*/ -static int check_cache_control(void){ - /* FIXME: there's some gotchas wth cookies and some headers. */ - char *zETag = cgi_add_etag(blob_buffer(&cgiContent),blob_size(&cgiContent)); - char *zMatch = P("HTTP_IF_NONE_MATCH"); - - if( zETag!=0 && zMatch!=0 ) { - char *zBuf = fossil_strdup(zMatch); - if( zBuf!=0 ){ - char *zTok = 0; - char *zPos; - for( zTok = strtok_r(zBuf, ",\"",&zPos); - zTok && fossil_stricmp(zTok,zETag); - zTok = strtok_r(0, ",\"",&zPos)){} - fossil_free(zBuf); - if(zTok) return 1; - } - } - - return 0; -} -#endif /* ** Return true if the response should be sent with Content-Encoding: gzip. */ static int is_gzippable(void){ @@ -296,34 +244,33 @@ /* ** Do a normal HTTP reply */ void cgi_reply(void){ int total_size; + char *zETag; if( iReplyStatus<=0 ){ iReplyStatus = 200; zReplyStatus = "OK"; } -#if 0 - if( iReplyStatus==200 && check_cache_control() ) { - /* change the status to "unchanged" and we can skip sending the - ** actual response body. Obviously we only do this when we _have_ a - ** body (code 200). - */ - iReplyStatus = 304; - zReplyStatus = "Not Modified"; - } -#endif - if( g.fullHttpReply ){ fprintf(g.httpOut, "HTTP/1.0 %d %s\r\n", iReplyStatus, zReplyStatus); fprintf(g.httpOut, "Date: %s\r\n", cgi_rfc822_datestamp(time(0))); fprintf(g.httpOut, "Connection: close\r\n"); fprintf(g.httpOut, "X-UA-Compatible: IE=edge\r\n"); }else{ fprintf(g.httpOut, "Status: %d %s\r\n", iReplyStatus, zReplyStatus); } + if( g.isConst ){ + /* The g.isConst flag means that the result is immutable, even for + ** after configuration changes and/or rebuilds of the Fossil binary. + */ + fprintf(g.httpOut, "Cache-Control: max-age=31536000\r\n"); + }else if( (zETag = etag_generate(-1))!=0 ){ + fprintf(g.httpOut, "ETag: %s\r\n", zETag); + fprintf(g.httpOut, "Cache-Control: max-age=%d\r\n", etag_maxage()); + } if( blob_size(&extraHeader)>0 ){ fprintf(g.httpOut, "%s", blob_buffer(&extraHeader)); } @@ -343,24 +290,10 @@ ** ** These headers are probably best added by the web server hosting fossil as ** a CGI script. */ - if( g.isConst ){ - /* constant means that the input URL will _never_ generate anything - ** else. In the case of attachments, the contents won't change because - ** an attempt to change them generates a new attachment number. In the - ** case of most /getfile calls for specific versions, the only way the - ** content changes is if someone breaks the SCM. And if that happens, a - ** stale cache is the least of the problem. So we provide an Expires - ** header set to a reasonable period (default: one week). - */ - fprintf(g.httpOut, "Cache-control: max-age=28800\r\n"); - }else{ - fprintf(g.httpOut, "Cache-control: no-cache\r\n"); - } - /* Content intended for logged in users should only be cached in ** the browser, not some shared location. */ fprintf(g.httpOut, "Content-Type: %s; charset=utf-8\r\n", zContentType); if( fossil_strcmp(zContentType,"application/x-fossil")==0 ){ Index: src/cookies.c ================================================================== --- src/cookies.c +++ src/cookies.c @@ -123,10 +123,11 @@ const char *zQVal = P(zQP); int i; cookie_parse(); for(i=0; i>12) /**************************************************************************/ /* Values for the 2nd parameter to dispatch_name_search() */ -#define CMDFLAG_ANY 0x0038 /* Match anything */ -#define CMDFLAG_PREFIX 0x0200 /* Prefix match is ok */ +#define CMDFLAG_ANY 0x00038 /* Match anything */ +#define CMDFLAG_PREFIX 0x00200 /* Prefix match is ok */ #endif /* INTERFACE */ /* ** The page_index.h file contains the definition for aCommand[] - an array Index: src/doc.c ================================================================== --- src/doc.c +++ src/doc.c @@ -639,15 +639,18 @@ }else{ goto doc_not_found; } } if( isUV ){ - if( db_table_exists("repository","unversioned") - && unversioned_content(zName, &filebody)==0 - ){ - rid = 1; - zDfltTitle = zName; + if( db_table_exists("repository","unversioned") ){ + char *zHash; + zHash = db_text(0, "SELECT hash FROM unversioned WHERE name=%Q",zName); + etag_require_hash(zHash); + if( unversioned_content(zName, &filebody)==0 ){ + rid = 1; + zDfltTitle = zName; + } } }else if( fossil_strcmp(zCheckin,"ckout")==0 ){ /* Read from the local checkout */ char *zFullpath; db_must_be_within_tree(); @@ -829,11 +832,11 @@ 214, 163, 146, 117, 238, 213, 154, 128, 151, 109, 84, 64, 217, 13, 27, 10, 228, 39, 2, 235, 164, 168, 74, 8, 0, 59, }; /* -** WEBPAGE: logo +** WEBPAGE: logo config ** ** Return the logo image. This image is available to anybody who can see ** the login page. It is designed for use in the upper left-hand corner ** of the header. */ @@ -847,11 +850,10 @@ if( blob_size(&logo)==0 ){ blob_init(&logo, (char*)aLogo, sizeof(aLogo)); } cgi_set_content_type(zMime); cgi_set_content(&logo); - g.isConst = 1; } /* ** The default background image: a 16x16 white GIF */ @@ -864,11 +866,11 @@ 179, 62, 5, 0, 59, }; /* -** WEBPAGE: background +** WEBPAGE: background const ** ** Return the background image. If no background image is defined, a ** built-in 16x16 pixel white GIF is returned. */ void background_page(void){ @@ -881,11 +883,10 @@ if( blob_size(&bgimg)==0 ){ blob_init(&bgimg, (char*)aBackground, sizeof(aBackground)); } cgi_set_content_type(zMime); cgi_set_content(&bgimg); - g.isConst = 1; } /* ** WEBPAGE: docsrch ADDED src/etag.c Index: src/etag.c ================================================================== --- /dev/null +++ src/etag.c @@ -0,0 +1,234 @@ +/* +** Copyright (c) 2018 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) +** +** 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. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file implements ETags: cache control for Fossil +** +** Each ETag value is a text string that represents a sequence of conditionals +** like this: +** +** if( executable-has-change ) return; +** if( database-has-changed ) return; +** if( display-cookie-"n"-attribute-has-changes ) return; +** Output "304 Not Modified" message and abort; +** +** In other words, if all conditions specified by the ETag are met, then +** Fossil will return a 304 and avoid doing all the work, and all of the +** bandwidth, associating with regenerating the whole page. +** +** To make use of this feature, page generators can invoke the +** etag_require() interface with mask of ETAG_CONST, ETAG_CONFIG, +** ETAG_DATA, and/or ETAG_COOKIE. Or it can invoke etag_require_hash() +** with some kind of text hash. +** +** Or, in the WEBPAGE: line for the page generator, extra arguments +** can be added. "const", "config", "data", and/or "cookie" +** +** ETAG_CONST const The reply is always the same for the same +** build of the fossil binary. The content +** is independent of the repository. +** +** ETAG_CONFIG config The reply is the same as long as the repository +** config is constant. +** +** ETAG_DATA data The reply is the same as long as no new artifacts +** are added to the repository +** +** ETAG_COOKIE cookie The reply is the same as long as the display +** cookie is unchanged. +** +** Page generator routines can also invoke etag_require_hash(HASH) where +** HASH is some string. In that case, the reply is the same as long as +** the hash is the same. +*/ +#include "config.h" +#include "etag.h" + +#if INTERFACE +/* +** Things to monitor +*/ +#define ETAG_CONST 0x00 /* Output is independent of database or parameters */ +#define ETAG_CONFIG 0x01 /* Output depends on the configuration */ +#define ETAG_DATA 0x02 /* Output depends on 'event' table */ +#define ETAG_COOKIE 0x04 /* Output depends on a display cookie value */ +#define ETAG_HASH 0x08 /* Output depends on a hash */ +#define ETAG_DYNAMIC 0x10 /* Output is always different */ +#endif + +/* Set of all etag requirements */ +static int mEtag = 0; /* Mask of requirements */ +static const char *zEHash = 0; /* Hash value if ETAG_HASH is set */ + + +/* Check an ETag to see if all conditions are valid. If all conditions are +** valid, then return true. If any condition is false, return false. +*/ +static int etag_valid(const char *zTag){ + int iKey; + char *zCk; + int rc; + int nTag; + if( zTag==0 || zTag[0]<=0 ) return 0; + nTag = (int)strlen(zTag); + if( zTag[0]=='"' && zTag[nTag-1]=='"' ){ + zTag++; + nTag -= 2; + } + iKey = zTag[0] - '0'; + zCk = etag_generate(iKey); + rc = nTag==(int)strlen(zCk) && strncmp(zCk, zTag, nTag)==0; + fossil_free(zCk); + if( rc ) mEtag = iKey; + return rc; +} + +/* +** Check to see if there is an If-None-Match: header that +** matches the current etag settings. If there is, then +** generate a 304 Not Modified reply. +** +** This routine exits and does not return if the 304 Not Modified +** reply is generated. +** +** If the etag does not match, the routine returns normally. +*/ +static void etag_check(void){ + const char *zETag = P("HTTP_IF_NONE_MATCH"); + if( zETag==0 ) return; + if( !etag_valid(zETag) ) return; + + /* If we get this far, it means that the content has + ** not changed and we can do a 304 reply */ + cgi_reset_content(); + cgi_set_status(304, "Not Modified"); + cgi_reply(); + fossil_exit(0); +} + + +/* Add one or more new etag requirements. +** +** Page generator logic invokes one or both of these methods to signal +** under what conditions page generation can be skipped +** +** After each call to these routines, the HTTP_IF_NONE_MATCH cookie +** is checked, and if it contains a compatible ETag, then a +** 304 Not Modified return is generated and execution aborts. This +** routine does not return if the 304 is generated. +*/ +void etag_require(int code){ + if( code==ETAG_CONST ){ + mEtag = code; + }else{ + mEtag |= code; + } + etag_check(); +} +void etag_require_hash(const char *zHash){ + if( zHash ){ + zEHash = zHash; + mEtag = ETAG_HASH; + etag_check(); + } +} + +/* Return an appropriate max-age. +*/ +int etag_maxage(void){ + if( mEtag & (ETAG_DYNAMIC|ETAG_COOKIE) ) return 0; + if( mEtag & (ETAG_DATA|ETAG_HASH) ) return 60; + if( mEtag & (ETAG_CONFIG) ) return 300; + return 3600*24; +} + +/* Generate an appropriate ETags value that captures all requirements. +** Space is obtained from fossil_malloc(). +** +** The argument is the mask of attributes to include in the ETag. +** If the argument is -1 then whatever mask is found from prior +** calls to etag_require() and etag_require_hash() is used. +** +** Format: +** +** /// +** +** The is a single-character decimal number that is the mask of +** all required attributes: +** +** ETAG_CONFIG: 1 +** ETAG_DATA: 2 +** ETAG_COOKIE: 4 +** ETAG_HASH: 8 +** +** If ETAG_HASH is present, the others are omitted, so the number is +** never greater than 8. +** +** The is the mtime of the Fossil executable. Since this +** is part of the ETag, it means that recompiling or just "touch"-ing the +** fossil binary is sufficient to invalidate all prior caches. +** +** The other elements are only present if the appropriate mask bits +** appear in the first character. +*/ +char *etag_generate(int m){ + Blob x = BLOB_INITIALIZER; + static int mtime = 0; + if( m<0 ) m = mEtag; + if( m & ETAG_DYNAMIC ) return 0; + if( mtime==0 ) mtime = file_mtime(g.nameOfExe, ExtFILE); + blob_appendf(&x,"%d%x", m, mtime); + if( m & ETAG_HASH ){ + blob_appendf(&x, "/%s", zEHash); + }else if( m & ETAG_DATA ){ + int iKey = db_int(0, "SELECT max(rcvid) FROM rcvfrom"); + blob_appendf(&x, "/%x", iKey); + }else if( m & ETAG_CONFIG ){ + int iKey = db_int(0, "SELECT value FROM config WHERE name='cfgcnt'"); + blob_appendf(&x, "/%x", iKey); + } + if( m & ETAG_COOKIE ){ + blob_appendf(&x, "/%s", P(DISPLAY_SETTINGS_COOKIE)); + } + return blob_str(&x); +} + +/* +** COMMAND: test-etag +** +** Usage: fossil test-etag -key KEY-NUMBER -hash HASH +** +** Generate an etag given a KEY-NUMBER and/or a HASH. +** +** KEY-NUMBER is some combination of: +** +** 1 ETAG_CONFIG The config table version number +** 2 ETAG_DATA The event table version number +** 4 ETAG_COOKIE The display cookie +*/ +void test_etag_cmd(void){ + char *zTag; + const char *zHash; + const char *zKey; + db_find_and_open_repository(0, 0); + zKey = find_option("key",0,1); + zHash = find_option("hash",0,1); + if( zKey ) etag_require(atoi(zKey)); + if( zHash ) etag_require_hash(zHash); + zTag = etag_generate(mEtag); + fossil_print("%s\n", zTag); + fossil_free(zTag); +} Index: src/info.c ================================================================== --- src/info.c +++ src/info.c @@ -1688,13 +1688,15 @@ } login_check_credentials(); if( !g.perm.Read ){ login_needed(g.anon.Read); return; } if( rid==0 ) fossil_redirect_home(); zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); +#if 0 if( fossil_strcmp(P("name"), zUuid)==0 && login_is_nobody() ){ g.isConst = 1; } +#endif free(zUuid); zMime = P("m"); if( zMime==0 ){ char *zFName = db_text(0, "SELECT filename.name FROM mlink, filename" " WHERE mlink.fid=%d" Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -1399,10 +1399,11 @@ const char *zPathInfo = PD("PATH_INFO", ""); char *zPath = NULL; int i; const CmdOrPage *pCmd = 0; const char *zBase = g.zRepositoryName; + const char *zETag = 0; /* Handle universal query parameters */ if( PB("utc") ){ g.fTimeFormat = 1; }else if( PB("localtime") ){ @@ -1767,10 +1768,11 @@ rc = TH_OK; } if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ if( rc==TH_OK || rc==TH_RETURN ){ #endif + etag_require(CMDFLAG_TO_ETAG(pCmd->eCmdFlags)); pCmd->xFunc(); #ifdef FOSSIL_ENABLE_TH1_HOOKS } if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ Th_WebpageNotify(pCmd->zName+1, pCmd->eCmdFlags); Index: src/main.mk ================================================================== --- src/main.mk +++ src/main.mk @@ -43,10 +43,11 @@ $(SRCDIR)/diff.c \ $(SRCDIR)/diffcmd.c \ $(SRCDIR)/dispatch.c \ $(SRCDIR)/doc.c \ $(SRCDIR)/encode.c \ + $(SRCDIR)/etag.c \ $(SRCDIR)/event.c \ $(SRCDIR)/export.c \ $(SRCDIR)/file.c \ $(SRCDIR)/finfo.c \ $(SRCDIR)/foci.c \ @@ -243,10 +244,11 @@ $(OBJDIR)/diff_.c \ $(OBJDIR)/diffcmd_.c \ $(OBJDIR)/dispatch_.c \ $(OBJDIR)/doc_.c \ $(OBJDIR)/encode_.c \ + $(OBJDIR)/etag_.c \ $(OBJDIR)/event_.c \ $(OBJDIR)/export_.c \ $(OBJDIR)/file_.c \ $(OBJDIR)/finfo_.c \ $(OBJDIR)/foci_.c \ @@ -372,10 +374,11 @@ $(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 \ @@ -673,10 +676,11 @@ $(OBJDIR)/diff_.c:$(OBJDIR)/diff.h \ $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h \ $(OBJDIR)/dispatch_.c:$(OBJDIR)/dispatch.h \ $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h \ $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h \ + $(OBJDIR)/etag_.c:$(OBJDIR)/etag.h \ $(OBJDIR)/event_.c:$(OBJDIR)/event.h \ $(OBJDIR)/export_.c:$(OBJDIR)/export.h \ $(OBJDIR)/file_.c:$(OBJDIR)/file.h \ $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \ $(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \ @@ -1016,10 +1020,18 @@ $(OBJDIR)/encode.o: $(OBJDIR)/encode_.c $(OBJDIR)/encode.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/encode.o -c $(OBJDIR)/encode_.c $(OBJDIR)/encode.h: $(OBJDIR)/headers + +$(OBJDIR)/etag_.c: $(SRCDIR)/etag.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/etag.c >$@ + +$(OBJDIR)/etag.o: $(OBJDIR)/etag_.c $(OBJDIR)/etag.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/etag.o -c $(OBJDIR)/etag_.c + +$(OBJDIR)/etag.h: $(OBJDIR)/headers $(OBJDIR)/event_.c: $(SRCDIR)/event.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/event.c >$@ $(OBJDIR)/event.o: $(OBJDIR)/event_.c $(OBJDIR)/event.h $(SRCDIR)/config.h Index: src/makemake.tcl ================================================================== --- src/makemake.tcl +++ src/makemake.tcl @@ -55,10 +55,11 @@ diff diffcmd dispatch doc encode + etag event export file finfo foci Index: src/mkindex.c ================================================================== --- src/mkindex.c +++ src/mkindex.c @@ -80,19 +80,26 @@ /*************************************************************************** ** These macros must match similar macros in dispatch.c. ** ** Allowed values for CmdOrPage.eCmdFlags. */ -#define CMDFLAG_1ST_TIER 0x0001 /* Most important commands */ -#define CMDFLAG_2ND_TIER 0x0002 /* Obscure and seldom used commands */ -#define CMDFLAG_TEST 0x0004 /* Commands for testing only */ -#define CMDFLAG_WEBPAGE 0x0008 /* Web pages */ -#define CMDFLAG_COMMAND 0x0010 /* A command */ -#define CMDFLAG_SETTING 0x0020 /* A setting */ -#define CMDFLAG_VERSIONABLE 0x0040 /* A versionable setting */ -#define CMDFLAG_BLOCKTEXT 0x0080 /* Multi-line text setting */ -#define CMDFLAG_BOOLEAN 0x0100 /* A boolean setting */ +#define CMDFLAG_1ST_TIER 0x00001 /* Most important commands */ +#define CMDFLAG_2ND_TIER 0x00002 /* Obscure and seldom used commands */ +#define CMDFLAG_TEST 0x00004 /* Commands for testing only */ +#define CMDFLAG_WEBPAGE 0x00008 /* Web pages */ +#define CMDFLAG_COMMAND 0x00010 /* A command */ +#define CMDFLAG_SETTING 0x00020 /* A setting */ +#define CMDFLAG_VERSIONABLE 0x00040 /* A versionable setting */ +#define CMDFLAG_BLOCKTEXT 0x00080 /* Multi-line text setting */ +#define CMDFLAG_BOOLEAN 0x00100 /* A boolean setting */ +#define CMDFLAG_CONST 0x00000 /* ETAG_CONST */ +#define CMDFLAG_CONFIG 0x01000 /* ETAG_CONFIG */ +#define CMDFLAG_DATA 0x02000 /* ETAG_DATA */ +#define CMDFLAG_COOKIE 0x04000 /* ETAG_COOKIE */ +#define CMDFLAG_DYNAMIC 0x10000 /* ETAG_DYNAMIC - on by default */ +#define CMDFLAG_ETAG 0x1f000 /* Mask of all ETAG entries */ +#define CMDFLAG_TO_ETAG(X) ((X)>>12) /**************************************************************************/ /* ** Each entry looks like this: */ @@ -198,11 +205,11 @@ return; } while( fossil_isspace(zLine[i]) ){ i++; } if( zLine[i]=='/' ) i++; for(j=0; zLine[i+j] && !fossil_isspace(zLine[i+j]); j++){} - aEntry[nUsed].eType = eType; + aEntry[nUsed].eType = eType | CMDFLAG_DYNAMIC; if( eType & CMDFLAG_WEBPAGE ){ aEntry[nUsed].zPath = string_dup(&zLine[i-1], j+1); aEntry[nUsed].zPath[0] = '/'; }else{ aEntry[nUsed].zPath = string_dup(&zLine[i], j); @@ -236,10 +243,22 @@ aEntry[nUsed].eType &= ~(CMDFLAG_1ST_TIER|CMDFLAG_TEST); aEntry[nUsed].eType |= CMDFLAG_2ND_TIER; }else if( j==4 && strncmp(&zLine[i], "test", j)==0 ){ aEntry[nUsed].eType &= ~(CMDFLAG_1ST_TIER|CMDFLAG_2ND_TIER); aEntry[nUsed].eType |= CMDFLAG_TEST; + }else if( j==5 && strncmp(&zLine[i], "const", j)==0 ){ + aEntry[nUsed].eType &= ~CMDFLAG_ETAG; + aEntry[nUsed].eType |= CMDFLAG_CONST; + }else if( j==6 && strncmp(&zLine[i], "config", j)==0 ){ + aEntry[nUsed].eType &= ~CMDFLAG_ETAG; + aEntry[nUsed].eType |= CMDFLAG_CONFIG; + }else if( j==4 && strncmp(&zLine[i], "data", j)==0 ){ + aEntry[nUsed].eType &= ~CMDFLAG_ETAG; + aEntry[nUsed].eType |= CMDFLAG_DATA; + }else if( j==4 && strncmp(&zLine[i], "cookie", j)==0 ){ + aEntry[nUsed].eType &= ~CMDFLAG_ETAG; + aEntry[nUsed].eType |= CMDFLAG_COOKIE; }else if( j==7 && strncmp(&zLine[i], "boolean", j)==0 ){ aEntry[nUsed].eType &= ~(CMDFLAG_BLOCKTEXT); aEntry[nUsed].iWidth = 0; aEntry[nUsed].eType |= CMDFLAG_BOOLEAN; }else if( j==10 && strncmp(&zLine[i], "block-text", j)==0 ){ Index: src/setup.c ================================================================== --- src/setup.c +++ src/setup.c @@ -18,10 +18,25 @@ ** Implementation of the Setup page */ #include "config.h" #include #include "setup.h" + +/* +** Increment the "cfgcnt" variable, so that ETags will know that +** the configuration has changed. +*/ +void setup_incr_cfgcnt(void){ + static int once = 1; + if( once ){ + once = 0; + db_multi_exec("UPDATE config SET value=value+1 WHERE name='cfgcnt'"); + if( db_changes()==0 ){ + db_multi_exec("INSERT INTO config(name,value) VALUES('cfgcnt',1)"); + } + } +} /* ** Output a single entry for a menu generated using an HTML table. ** If zLink is not NULL or an empty string, then it is the page that ** the menu entry will hyperlink to. If zLink is NULL or "", then @@ -496,10 +511,11 @@ db_multi_exec( "REPLACE INTO user(uid,login,info,pw,cap,mtime) " "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())", uid, zLogin, P("info"), zPw, zCap ); + setup_incr_cfgcnt(); admin_log( "Updated user [%q] with capabilities [%q].", zLogin, zCap ); if( atoi(PD("all","0"))>0 ){ Blob sql; char *zErr = 0; Index: src/style.c ================================================================== --- src/style.c +++ src/style.c @@ -780,11 +780,11 @@ blob_reset(&css); } /* -** WEBPAGE: style.css +** WEBPAGE: style.css config ** ** Return the style sheet. */ void page_style_css(void){ Blob css; @@ -822,26 +822,28 @@ Th_Store("secureurl", login_wants_https_redirect()? g.zHttpsURL: g.zBaseURL); Th_Store("home", g.zTop); image_url_var("logo"); image_url_var("background"); Th_Render(blob_str(&css)); - - /* Tell CGI that the content returned by this page is considered cacheable */ - g.isConst = 1; } /* ** WEBPAGE: builtin ** URL: builtin/FILENAME ** ** Return the built-in text given by FILENAME. This is used internally ** by many Fossil web pages to load built-in javascript files. +** +** If the id= query parameter is present, then Fossil assumes that the +** result is immutable and sets a very large cache retention time (1 year). */ void page_builtin_text(void){ Blob out; const char *zName = P("name"); const char *zTxt = 0; + const char *zId = P("id"); + int nId; if( zName ) zTxt = builtin_text(zName); if( zTxt==0 ){ cgi_set_status(404, "Not Found"); @ File "%h(zName)" not found return; @@ -849,13 +851,17 @@ if( sqlite3_strglob("*.js", zName)==0 ){ cgi_set_content_type("application/javascript"); }else{ cgi_set_content_type("text/plain"); } + if( zId && (nId = (int)strlen(zId))>=8 && strncmp(zId,MANIFEST_UUID,nId)==0 ){ + g.isConst = 1; + }else{ + etag_require(ETAG_CONST); + } blob_init(&out, zTxt, -1); cgi_set_content(&out); - g.isConst = 1; } /* ** WEBPAGE: test_env Index: src/timeline.c ================================================================== --- src/timeline.c +++ src/timeline.c @@ -1329,11 +1329,11 @@ /* If execution reaches this point, the pattern was empty. Return NULL. */ return 0; } /* -** WEBPAGE: timeline +** WEBPAGE: timeline data ** ** Query parameters: ** ** a=TIMEORTAG After this event ** b=TIMEORTAG Before this event @@ -2510,11 +2510,11 @@ } db_finalize(&q); } /* -** WEBPAGE: timewarps +** WEBPAGE: timewarps data ** ** Show all check-ins that are "timewarps". A timewarp is a ** check-in that occurs before its parent, according to the ** timestamp information on the check-in. This can only actually ** happen, of course, if a users system clock is set incorrectly. Index: src/unversioned.c ================================================================== --- src/unversioned.c +++ src/unversioned.c @@ -153,11 +153,15 @@ ** 2: zName exists and is the same as zHash but has a older mtime ** 3: zName exists and is identical to mtime/zHash in all respects. ** 4: zName exists and is the same as zHash but has a newer mtime. ** 5: zName exists and should override the mtime/zHash remote. */ -int unversioned_status(const char *zName, sqlite3_int64 mtime, const char *zHash){ +int unversioned_status( + const char *zName, + sqlite3_int64 mtime, + const char *zHash +){ int iStatus = 0; Stmt q; db_prepare(&q, "SELECT mtime, hash FROM unversioned WHERE name=%Q", zName); if( db_step(&q)==SQLITE_ROW ){ const char *zLocalHash = db_column_text(&q, 1); Index: win/Makefile.dmc ================================================================== --- win/Makefile.dmc +++ win/Makefile.dmc @@ -28,13 +28,13 @@ 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 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 -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 +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 -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 +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 RC=$(DMDIR)\bin\rcc RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__ @@ -49,11 +49,11 @@ $(OBJDIR)\fossil.res: $B\win\fossil.rc $(RC) $(RCFLAGS) -o$@ $** $(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res - +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 > $@ + +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 > $@ +echo fossil >> $@ +echo fossil >> $@ +echo $(LIBS) >> $@ +echo. >> $@ +echo fossil >> $@ @@ -308,10 +308,16 @@ $(OBJDIR)\encode$O : encode_.c encode.h $(TCC) -o$@ -c encode_.c encode_.c : $(SRCDIR)\encode.c +translate$E $** > $@ + +$(OBJDIR)\etag$O : etag_.c etag.h + $(TCC) -o$@ -c etag_.c + +etag_.c : $(SRCDIR)\etag.c + +translate$E $** > $@ $(OBJDIR)\event$O : event_.c event.h $(TCC) -o$@ -c event_.c event_.c : $(SRCDIR)\event.c @@ -892,7 +898,7 @@ zip_.c : $(SRCDIR)\zip.c +translate$E $** > $@ headers: makeheaders$E page_index.h builtin_data.h default_css.h VERSION.h - +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 + +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 @copy /Y nul: headers Index: win/Makefile.mingw ================================================================== --- win/Makefile.mingw +++ win/Makefile.mingw @@ -452,10 +452,11 @@ $(SRCDIR)/diff.c \ $(SRCDIR)/diffcmd.c \ $(SRCDIR)/dispatch.c \ $(SRCDIR)/doc.c \ $(SRCDIR)/encode.c \ + $(SRCDIR)/etag.c \ $(SRCDIR)/event.c \ $(SRCDIR)/export.c \ $(SRCDIR)/file.c \ $(SRCDIR)/finfo.c \ $(SRCDIR)/foci.c \ @@ -652,10 +653,11 @@ $(OBJDIR)/diff_.c \ $(OBJDIR)/diffcmd_.c \ $(OBJDIR)/dispatch_.c \ $(OBJDIR)/doc_.c \ $(OBJDIR)/encode_.c \ + $(OBJDIR)/etag_.c \ $(OBJDIR)/event_.c \ $(OBJDIR)/export_.c \ $(OBJDIR)/file_.c \ $(OBJDIR)/finfo_.c \ $(OBJDIR)/foci_.c \ @@ -781,10 +783,11 @@ $(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 \ @@ -1129,10 +1132,11 @@ $(OBJDIR)/diff_.c:$(OBJDIR)/diff.h \ $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h \ $(OBJDIR)/dispatch_.c:$(OBJDIR)/dispatch.h \ $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h \ $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h \ + $(OBJDIR)/etag_.c:$(OBJDIR)/etag.h \ $(OBJDIR)/event_.c:$(OBJDIR)/event.h \ $(OBJDIR)/export_.c:$(OBJDIR)/export.h \ $(OBJDIR)/file_.c:$(OBJDIR)/file.h \ $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \ $(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \ @@ -1474,10 +1478,18 @@ $(OBJDIR)/encode.o: $(OBJDIR)/encode_.c $(OBJDIR)/encode.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/encode.o -c $(OBJDIR)/encode_.c $(OBJDIR)/encode.h: $(OBJDIR)/headers + +$(OBJDIR)/etag_.c: $(SRCDIR)/etag.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/etag.c >$@ + +$(OBJDIR)/etag.o: $(OBJDIR)/etag_.c $(OBJDIR)/etag.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/etag.o -c $(OBJDIR)/etag_.c + +$(OBJDIR)/etag.h: $(OBJDIR)/headers $(OBJDIR)/event_.c: $(SRCDIR)/event.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/event.c >$@ $(OBJDIR)/event.o: $(OBJDIR)/event_.c $(OBJDIR)/event.h $(SRCDIR)/config.h Index: win/Makefile.msc ================================================================== --- win/Makefile.msc +++ win/Makefile.msc @@ -382,10 +382,11 @@ diff_.c \ diffcmd_.c \ dispatch_.c \ doc_.c \ encode_.c \ + etag_.c \ event_.c \ export_.c \ file_.c \ finfo_.c \ foci_.c \ @@ -581,10 +582,11 @@ $(OX)\diff$O \ $(OX)\diffcmd$O \ $(OX)\dispatch$O \ $(OX)\doc$O \ $(OX)\encode$O \ + $(OX)\etag$O \ $(OX)\event$O \ $(OX)\export$O \ $(OX)\file$O \ $(OX)\finfo$O \ $(OX)\foci$O \ @@ -769,10 +771,11 @@ echo $(OX)\diff.obj >> $@ echo $(OX)\diffcmd.obj >> $@ echo $(OX)\dispatch.obj >> $@ echo $(OX)\doc.obj >> $@ echo $(OX)\encode.obj >> $@ + echo $(OX)\etag.obj >> $@ echo $(OX)\event.obj >> $@ echo $(OX)\export.obj >> $@ echo $(OX)\file.obj >> $@ echo $(OX)\finfo.obj >> $@ echo $(OX)\foci.obj >> $@ @@ -1168,10 +1171,16 @@ $(OX)\encode$O : encode_.c encode.h $(TCC) /Fo$@ -c encode_.c encode_.c : $(SRCDIR)\encode.c translate$E $** > $@ + +$(OX)\etag$O : etag_.c etag.h + $(TCC) /Fo$@ -c etag_.c + +etag_.c : $(SRCDIR)\etag.c + translate$E $** > $@ $(OX)\event$O : event_.c event.h $(TCC) /Fo$@ -c event_.c event_.c : $(SRCDIR)\event.c @@ -1785,10 +1794,11 @@ 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 \