Fossil SCM

Redesign the ETags mechanism to be simpler and safer.

drh 2018-02-24 20:14 UTC etags-cache-control
Commit ae660cd62f37ff07b70061d6e8cb553b12aadb92275733f3a76c8cae1134f13c
+8 -18
--- src/cgi.c
+++ src/cgi.c
@@ -244,11 +244,10 @@
244244
/*
245245
** Do a normal HTTP reply
246246
*/
247247
void cgi_reply(void){
248248
int total_size;
249
- char *zETag;
250249
if( iReplyStatus<=0 ){
251250
iReplyStatus = 200;
252251
zReplyStatus = "OK";
253252
}
254253
@@ -258,14 +257,19 @@
258257
fprintf(g.httpOut, "Connection: close\r\n");
259258
fprintf(g.httpOut, "X-UA-Compatible: IE=edge\r\n");
260259
}else{
261260
fprintf(g.httpOut, "Status: %d %s\r\n", iReplyStatus, zReplyStatus);
262261
}
263
- zETag = etag_generate(-1);
264
- if( zETag ){
265
- fprintf(g.httpOut, "ETag: %s\r\n", zETag);
262
+ if( g.isConst ){
263
+ /* isConst means that the reply is guaranteed to be invariant, even
264
+ ** after configuration changes and/or Fossil binary recompiles. */
265
+ fprintf(g.httpOut, "Cache-Control: max-age=31536000\r\n");
266
+ }else if( etag_tag()!=0 ){
267
+ fprintf(g.httpOut, "ETag: %s\r\n", etag_tag());
266268
fprintf(g.httpOut, "Cache-Control: max-age=%d\r\n", etag_maxage());
269
+ }else{
270
+ fprintf(g.httpOut, "Cache-control: no-cache\r\n");
267271
}
268272
269273
if( blob_size(&extraHeader)>0 ){
270274
fprintf(g.httpOut, "%s", blob_buffer(&extraHeader));
271275
}
@@ -286,24 +290,10 @@
286290
**
287291
** These headers are probably best added by the web server hosting fossil as
288292
** a CGI script.
289293
*/
290294
291
- if( g.isConst ){
292
- /* constant means that the input URL will _never_ generate anything
293
- ** else. In the case of attachments, the contents won't change because
294
- ** an attempt to change them generates a new attachment number. In the
295
- ** case of most /getfile calls for specific versions, the only way the
296
- ** content changes is if someone breaks the SCM. And if that happens, a
297
- ** stale cache is the least of the problem. So we provide an Expires
298
- ** header set to a reasonable period (default: one week).
299
- */
300
- fprintf(g.httpOut, "Cache-control: max-age=28800\r\n");
301
- }else{
302
- fprintf(g.httpOut, "Cache-control: no-cache\r\n");
303
- }
304
-
305295
/* Content intended for logged in users should only be cached in
306296
** the browser, not some shared location.
307297
*/
308298
fprintf(g.httpOut, "Content-Type: %s; charset=utf-8\r\n", zContentType);
309299
if( fossil_strcmp(zContentType,"application/x-fossil")==0 ){
310300
--- src/cgi.c
+++ src/cgi.c
@@ -244,11 +244,10 @@
244 /*
245 ** Do a normal HTTP reply
246 */
247 void cgi_reply(void){
248 int total_size;
249 char *zETag;
250 if( iReplyStatus<=0 ){
251 iReplyStatus = 200;
252 zReplyStatus = "OK";
253 }
254
@@ -258,14 +257,19 @@
258 fprintf(g.httpOut, "Connection: close\r\n");
259 fprintf(g.httpOut, "X-UA-Compatible: IE=edge\r\n");
260 }else{
261 fprintf(g.httpOut, "Status: %d %s\r\n", iReplyStatus, zReplyStatus);
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 }
268
269 if( blob_size(&extraHeader)>0 ){
270 fprintf(g.httpOut, "%s", blob_buffer(&extraHeader));
271 }
@@ -286,24 +290,10 @@
286 **
287 ** These headers are probably best added by the web server hosting fossil as
288 ** a CGI script.
289 */
290
291 if( g.isConst ){
292 /* constant means that the input URL will _never_ generate anything
293 ** else. In the case of attachments, the contents won't change because
294 ** an attempt to change them generates a new attachment number. In the
295 ** case of most /getfile calls for specific versions, the only way the
296 ** content changes is if someone breaks the SCM. And if that happens, a
297 ** stale cache is the least of the problem. So we provide an Expires
298 ** header set to a reasonable period (default: one week).
299 */
300 fprintf(g.httpOut, "Cache-control: max-age=28800\r\n");
301 }else{
302 fprintf(g.httpOut, "Cache-control: no-cache\r\n");
303 }
304
305 /* Content intended for logged in users should only be cached in
306 ** the browser, not some shared location.
307 */
308 fprintf(g.httpOut, "Content-Type: %s; charset=utf-8\r\n", zContentType);
309 if( fossil_strcmp(zContentType,"application/x-fossil")==0 ){
310
--- src/cgi.c
+++ src/cgi.c
@@ -244,11 +244,10 @@
244 /*
245 ** Do a normal HTTP reply
246 */
247 void cgi_reply(void){
248 int total_size;
 
249 if( iReplyStatus<=0 ){
250 iReplyStatus = 200;
251 zReplyStatus = "OK";
252 }
253
@@ -258,14 +257,19 @@
257 fprintf(g.httpOut, "Connection: close\r\n");
258 fprintf(g.httpOut, "X-UA-Compatible: IE=edge\r\n");
259 }else{
260 fprintf(g.httpOut, "Status: %d %s\r\n", iReplyStatus, zReplyStatus);
261 }
262 if( g.isConst ){
263 /* isConst means that the reply is guaranteed to be invariant, even
264 ** after configuration changes and/or Fossil binary recompiles. */
265 fprintf(g.httpOut, "Cache-Control: max-age=31536000\r\n");
266 }else if( etag_tag()!=0 ){
267 fprintf(g.httpOut, "ETag: %s\r\n", etag_tag());
268 fprintf(g.httpOut, "Cache-Control: max-age=%d\r\n", etag_maxage());
269 }else{
270 fprintf(g.httpOut, "Cache-control: no-cache\r\n");
271 }
272
273 if( blob_size(&extraHeader)>0 ){
274 fprintf(g.httpOut, "%s", blob_buffer(&extraHeader));
275 }
@@ -286,24 +290,10 @@
290 **
291 ** These headers are probably best added by the web server hosting fossil as
292 ** a CGI script.
293 */
294
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295 /* Content intended for logged in users should only be cached in
296 ** the browser, not some shared location.
297 */
298 fprintf(g.httpOut, "Content-Type: %s; charset=utf-8\r\n", zContentType);
299 if( fossil_strcmp(zContentType,"application/x-fossil")==0 ){
300
--- src/cookies.c
+++ src/cookies.c
@@ -123,11 +123,10 @@
123123
const char *zQVal = P(zQP);
124124
int i;
125125
cookie_parse();
126126
for(i=0; i<cookies.nParam && strcmp(zPName,cookies.aParam[i].zPName); i++){}
127127
if( zQVal==0 && (flags & COOKIE_READ)!=0 && i<cookies.nParam ){
128
- etag_require(ETAG_COOKIE);
129128
cgi_set_parameter_nocopy(zQP, cookies.aParam[i].zPValue, 1);
130129
return;
131130
}
132131
if( zQVal==0 ) zQVal = zDflt;
133132
if( (flags & COOKIE_WRITE)!=0
134133
--- src/cookies.c
+++ src/cookies.c
@@ -123,11 +123,10 @@
123 const char *zQVal = P(zQP);
124 int i;
125 cookie_parse();
126 for(i=0; i<cookies.nParam && strcmp(zPName,cookies.aParam[i].zPName); i++){}
127 if( zQVal==0 && (flags & COOKIE_READ)!=0 && i<cookies.nParam ){
128 etag_require(ETAG_COOKIE);
129 cgi_set_parameter_nocopy(zQP, cookies.aParam[i].zPValue, 1);
130 return;
131 }
132 if( zQVal==0 ) zQVal = zDflt;
133 if( (flags & COOKIE_WRITE)!=0
134
--- src/cookies.c
+++ src/cookies.c
@@ -123,11 +123,10 @@
123 const char *zQVal = P(zQP);
124 int i;
125 cookie_parse();
126 for(i=0; i<cookies.nParam && strcmp(zPName,cookies.aParam[i].zPName); i++){}
127 if( zQVal==0 && (flags & COOKIE_READ)!=0 && i<cookies.nParam ){
 
128 cgi_set_parameter_nocopy(zQP, cookies.aParam[i].zPValue, 1);
129 return;
130 }
131 if( zQVal==0 ) zQVal = zDflt;
132 if( (flags & COOKIE_WRITE)!=0
133
+3 -3
--- src/doc.c
+++ src/doc.c
@@ -642,11 +642,11 @@
642642
}
643643
if( isUV ){
644644
if( db_table_exists("repository","unversioned") ){
645645
char *zHash;
646646
zHash = db_text(0, "SELECT hash FROM unversioned WHERE name=%Q",zName);
647
- etag_require_hash(zHash);
647
+ etag_check(ETAG_HASH, zHash);
648648
if( unversioned_content(zName, &filebody)==0 ){
649649
rid = 1;
650650
zDfltTitle = zName;
651651
}
652652
}
@@ -842,19 +842,19 @@
842842
*/
843843
void logo_page(void){
844844
Blob logo;
845845
char *zMime;
846846
847
+ etag_check(ETAG_CONFIG, 0);
847848
zMime = db_get("logo-mimetype", "image/gif");
848849
blob_zero(&logo);
849850
db_blob(&logo, "SELECT value FROM config WHERE name='logo-image'");
850851
if( blob_size(&logo)==0 ){
851852
blob_init(&logo, (char*)aLogo, sizeof(aLogo));
852853
}
853854
cgi_set_content_type(zMime);
854855
cgi_set_content(&logo);
855
- g.isConst = 1;
856856
}
857857
858858
/*
859859
** The default background image: a 16x16 white GIF
860860
*/
@@ -876,19 +876,19 @@
876876
*/
877877
void background_page(void){
878878
Blob bgimg;
879879
char *zMime;
880880
881
+ etag_check(ETAG_CONFIG, 0);
881882
zMime = db_get("background-mimetype", "image/gif");
882883
blob_zero(&bgimg);
883884
db_blob(&bgimg, "SELECT value FROM config WHERE name='background-image'");
884885
if( blob_size(&bgimg)==0 ){
885886
blob_init(&bgimg, (char*)aBackground, sizeof(aBackground));
886887
}
887888
cgi_set_content_type(zMime);
888889
cgi_set_content(&bgimg);
889
- g.isConst = 1;
890890
}
891891
892892
893893
/*
894894
** WEBPAGE: docsrch
895895
--- src/doc.c
+++ src/doc.c
@@ -642,11 +642,11 @@
642 }
643 if( isUV ){
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 }
652 }
@@ -842,19 +842,19 @@
842 */
843 void logo_page(void){
844 Blob logo;
845 char *zMime;
846
 
847 zMime = db_get("logo-mimetype", "image/gif");
848 blob_zero(&logo);
849 db_blob(&logo, "SELECT value FROM config WHERE name='logo-image'");
850 if( blob_size(&logo)==0 ){
851 blob_init(&logo, (char*)aLogo, sizeof(aLogo));
852 }
853 cgi_set_content_type(zMime);
854 cgi_set_content(&logo);
855 g.isConst = 1;
856 }
857
858 /*
859 ** The default background image: a 16x16 white GIF
860 */
@@ -876,19 +876,19 @@
876 */
877 void background_page(void){
878 Blob bgimg;
879 char *zMime;
880
 
881 zMime = db_get("background-mimetype", "image/gif");
882 blob_zero(&bgimg);
883 db_blob(&bgimg, "SELECT value FROM config WHERE name='background-image'");
884 if( blob_size(&bgimg)==0 ){
885 blob_init(&bgimg, (char*)aBackground, sizeof(aBackground));
886 }
887 cgi_set_content_type(zMime);
888 cgi_set_content(&bgimg);
889 g.isConst = 1;
890 }
891
892
893 /*
894 ** WEBPAGE: docsrch
895
--- src/doc.c
+++ src/doc.c
@@ -642,11 +642,11 @@
642 }
643 if( isUV ){
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_check(ETAG_HASH, zHash);
648 if( unversioned_content(zName, &filebody)==0 ){
649 rid = 1;
650 zDfltTitle = zName;
651 }
652 }
@@ -842,19 +842,19 @@
842 */
843 void logo_page(void){
844 Blob logo;
845 char *zMime;
846
847 etag_check(ETAG_CONFIG, 0);
848 zMime = db_get("logo-mimetype", "image/gif");
849 blob_zero(&logo);
850 db_blob(&logo, "SELECT value FROM config WHERE name='logo-image'");
851 if( blob_size(&logo)==0 ){
852 blob_init(&logo, (char*)aLogo, sizeof(aLogo));
853 }
854 cgi_set_content_type(zMime);
855 cgi_set_content(&logo);
 
856 }
857
858 /*
859 ** The default background image: a 16x16 white GIF
860 */
@@ -876,19 +876,19 @@
876 */
877 void background_page(void){
878 Blob bgimg;
879 char *zMime;
880
881 etag_check(ETAG_CONFIG, 0);
882 zMime = db_get("background-mimetype", "image/gif");
883 blob_zero(&bgimg);
884 db_blob(&bgimg, "SELECT value FROM config WHERE name='background-image'");
885 if( blob_size(&bgimg)==0 ){
886 blob_init(&bgimg, (char*)aBackground, sizeof(aBackground));
887 }
888 cgi_set_content_type(zMime);
889 cgi_set_content(&bgimg);
 
890 }
891
892
893 /*
894 ** WEBPAGE: docsrch
895
+93 -164
--- src/etag.c
+++ src/etag.c
@@ -15,193 +15,124 @@
1515
**
1616
*******************************************************************************
1717
**
1818
** This file implements ETags: cache control for Fossil
1919
**
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.
20
+** An ETag is a hash that encodes attributes which must be the same for
21
+** the page to continue to be valid. Attributes that might be contained
22
+** in the ETag include:
23
+**
24
+** (1) The mtime on the Fossil executable
25
+** (2) The last change to the CONFIG table
26
+** (3) The last change to the EVENT table
27
+** (4) The value of the display cookie
28
+** (5) A hash value supplied by the page generator
29
+**
30
+** Item (1) is always included in the ETag. The other elements are
31
+** optional. Because (1) is always included as part of the ETag, all
32
+** outstanding ETags can be invalidated by touching the fossil executable.
33
+**
34
+** A page generator routine invokes etag_check() exactly once, with
35
+** arguments that indicates which of the above elements to include in the
36
+** hash. If the request contained an If-None-Match header which matches
37
+** the generated ETag, then a 304 Not Modified reply is generated and
38
+** the process exits. In other words, etag_check() never returns. But
39
+** if there is no If-None_Match header or if the ETag does not match,
40
+** then etag_check() returns normally. Later, during reply generation,
41
+** the cgi.c module will invoke etag_tag() to recover the generated tag
42
+** and include it in the reply header.
5643
*/
5744
#include "config.h"
5845
#include "etag.h"
5946
6047
#if INTERFACE
6148
/*
6249
** Things to monitor
6350
*/
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 */
51
+#define ETAG_CONFIG 0x01 /* Output depends on the CONFIG table */
52
+#define ETAG_DATA 0x02 /* Output depends on the EVENT table */
6753
#define ETAG_COOKIE 0x04 /* Output depends on a display cookie value */
6854
#define ETAG_HASH 0x08 /* Output depends on a hash */
69
-#define ETAG_DYNAMIC 0x10 /* Output is always different */
7055
#endif
7156
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
-}
57
+static char zETag[33]; /* The generated ETag */
58
+static int iMaxAge = 0; /* The max-age parameter in the reply */
9859
9960
/*
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.
61
+** Generate an ETag
10862
*/
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;
63
+void etag_check(unsigned eFlags, const char *zHash){
64
+ sqlite3_int64 mtime;
65
+ const char *zIfNoneMatch;
66
+ char zBuf[50];
67
+ assert( zETag[0]==0 ); /* Only call this routine once! */
68
+
69
+ iMaxAge = 86400;
70
+ md5sum_init();
71
+
72
+ /* Always include the mtime of the executable as part of the hash */
73
+ mtime = file_mtime(g.nameOfExe, ExtFILE);
74
+ sqlite3_snprintf(sizeof(zBuf),zBuf,"mtime: %lld\n", mtime);
75
+ md5sum_step_text(zBuf, -1);
76
+
77
+ if( (eFlags & ETAG_HASH)!=0 && zHash ){
78
+ md5sum_step_text("hash: ", -1);
79
+ md5sum_step_text(zHash, -1);
80
+ md5sum_step_text("\n", 1);
81
+ iMaxAge = 0;
82
+ }else if( eFlags & ETAG_DATA ){
83
+ int iKey = db_int(0, "SELECT max(rcvid) FROM rcvfrom");
84
+ sqlite3_snprintf(sizeof(zBuf),zBuf,"%d",iKey);
85
+ md5sum_step_text("data: ", -1);
86
+ md5sum_step_text(zBuf, -1);
87
+ md5sum_step_text("\n", 1);
88
+ iMaxAge = 60;
89
+ }else if( eFlags & ETAG_CONFIG ){
90
+ int iKey = db_int(0, "SELECT value FROM config WHERE name='cfgcnt'");
91
+ sqlite3_snprintf(sizeof(zBuf),zBuf,"%d",iKey);
92
+ md5sum_step_text("data: ", -1);
93
+ md5sum_step_text(zBuf, -1);
94
+ md5sum_step_text("\n", 1);
95
+ iMaxAge = 3600;
96
+ }
97
+
98
+ /* Include the display cookie */
99
+ if( eFlags & ETAG_COOKIE ){
100
+ md5sum_step_text("display-cookie: ", -1);
101
+ md5sum_step_text(PD(DISPLAY_SETTINGS_COOKIE,""), -1);
102
+ md5sum_step_text("\n", 1);
103
+ iMaxAge = 0;
104
+ }
105
+
106
+ /* Generate the ETag */
107
+ memcpy(zETag, md5sum_finish(0), 33);
108
+
109
+ /* Check to see if the generated ETag matches If-None-Match and
110
+ ** generate a 304 reply if it does. */
111
+ zIfNoneMatch = P("HTTP_IF_NONE_MATCH");
112
+ if( zIfNoneMatch==0 ) return;
113
+ if( strcmp(zIfNoneMatch,zETag)!=0 ) return;
113114
114115
/* If we get this far, it means that the content has
115116
** not changed and we can do a 304 reply */
116117
cgi_reset_content();
117118
cgi_set_status(304, "Not Modified");
118119
cgi_reply();
119120
fossil_exit(0);
120121
}
121122
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.
123
+/* Return the ETag, if there is one.
124
+*/
125
+const char *etag_tag(void){
126
+ return zETag;
127
+}
128
+
129
+/* Return the recommended max-age
147130
*/
148131
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
-}
132
+ return iMaxAge;
133
+}
203134
204135
/*
205136
** COMMAND: test-etag
206137
**
207138
** Usage: fossil test-etag -key KEY-NUMBER -hash HASH
@@ -213,17 +144,15 @@
213144
** 1 ETAG_CONFIG The config table version number
214145
** 2 ETAG_DATA The event table version number
215146
** 4 ETAG_COOKIE The display cookie
216147
*/
217148
void test_etag_cmd(void){
218
- char *zTag;
219
- const char *zHash;
149
+ const char *zHash = 0;
220150
const char *zKey;
151
+ int iKey = 0;
221152
db_find_and_open_repository(0, 0);
222153
zKey = find_option("key",0,1);
223154
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);
155
+ if( zKey ) iKey = atoi(zKey);
156
+ etag_check(iKey, zHash);
157
+ fossil_print("%s\n", etag_tag());
229158
}
230159
--- src/etag.c
+++ src/etag.c
@@ -15,193 +15,124 @@
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
@@ -213,17 +144,15 @@
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 }
230
--- src/etag.c
+++ src/etag.c
@@ -15,193 +15,124 @@
15 **
16 *******************************************************************************
17 **
18 ** This file implements ETags: cache control for Fossil
19 **
20 ** An ETag is a hash that encodes attributes which must be the same for
21 ** the page to continue to be valid. Attributes that might be contained
22 ** in the ETag include:
23 **
24 ** (1) The mtime on the Fossil executable
25 ** (2) The last change to the CONFIG table
26 ** (3) The last change to the EVENT table
27 ** (4) The value of the display cookie
28 ** (5) A hash value supplied by the page generator
29 **
30 ** Item (1) is always included in the ETag. The other elements are
31 ** optional. Because (1) is always included as part of the ETag, all
32 ** outstanding ETags can be invalidated by touching the fossil executable.
33 **
34 ** A page generator routine invokes etag_check() exactly once, with
35 ** arguments that indicates which of the above elements to include in the
36 ** hash. If the request contained an If-None-Match header which matches
37 ** the generated ETag, then a 304 Not Modified reply is generated and
38 ** the process exits. In other words, etag_check() never returns. But
39 ** if there is no If-None_Match header or if the ETag does not match,
40 ** then etag_check() returns normally. Later, during reply generation,
41 ** the cgi.c module will invoke etag_tag() to recover the generated tag
42 ** and include it in the reply header.
 
 
 
 
 
 
 
 
 
 
 
 
 
43 */
44 #include "config.h"
45 #include "etag.h"
46
47 #if INTERFACE
48 /*
49 ** Things to monitor
50 */
51 #define ETAG_CONFIG 0x01 /* Output depends on the CONFIG table */
52 #define ETAG_DATA 0x02 /* Output depends on the EVENT table */
 
53 #define ETAG_COOKIE 0x04 /* Output depends on a display cookie value */
54 #define ETAG_HASH 0x08 /* Output depends on a hash */
 
55 #endif
56
57 static char zETag[33]; /* The generated ETag */
58 static int iMaxAge = 0; /* The max-age parameter in the reply */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
60 /*
61 ** Generate an ETag
 
 
 
 
 
 
 
62 */
63 void etag_check(unsigned eFlags, const char *zHash){
64 sqlite3_int64 mtime;
65 const char *zIfNoneMatch;
66 char zBuf[50];
67 assert( zETag[0]==0 ); /* Only call this routine once! */
68
69 iMaxAge = 86400;
70 md5sum_init();
71
72 /* Always include the mtime of the executable as part of the hash */
73 mtime = file_mtime(g.nameOfExe, ExtFILE);
74 sqlite3_snprintf(sizeof(zBuf),zBuf,"mtime: %lld\n", mtime);
75 md5sum_step_text(zBuf, -1);
76
77 if( (eFlags & ETAG_HASH)!=0 && zHash ){
78 md5sum_step_text("hash: ", -1);
79 md5sum_step_text(zHash, -1);
80 md5sum_step_text("\n", 1);
81 iMaxAge = 0;
82 }else if( eFlags & ETAG_DATA ){
83 int iKey = db_int(0, "SELECT max(rcvid) FROM rcvfrom");
84 sqlite3_snprintf(sizeof(zBuf),zBuf,"%d",iKey);
85 md5sum_step_text("data: ", -1);
86 md5sum_step_text(zBuf, -1);
87 md5sum_step_text("\n", 1);
88 iMaxAge = 60;
89 }else if( eFlags & ETAG_CONFIG ){
90 int iKey = db_int(0, "SELECT value FROM config WHERE name='cfgcnt'");
91 sqlite3_snprintf(sizeof(zBuf),zBuf,"%d",iKey);
92 md5sum_step_text("data: ", -1);
93 md5sum_step_text(zBuf, -1);
94 md5sum_step_text("\n", 1);
95 iMaxAge = 3600;
96 }
97
98 /* Include the display cookie */
99 if( eFlags & ETAG_COOKIE ){
100 md5sum_step_text("display-cookie: ", -1);
101 md5sum_step_text(PD(DISPLAY_SETTINGS_COOKIE,""), -1);
102 md5sum_step_text("\n", 1);
103 iMaxAge = 0;
104 }
105
106 /* Generate the ETag */
107 memcpy(zETag, md5sum_finish(0), 33);
108
109 /* Check to see if the generated ETag matches If-None-Match and
110 ** generate a 304 reply if it does. */
111 zIfNoneMatch = P("HTTP_IF_NONE_MATCH");
112 if( zIfNoneMatch==0 ) return;
113 if( strcmp(zIfNoneMatch,zETag)!=0 ) return;
114
115 /* If we get this far, it means that the content has
116 ** not changed and we can do a 304 reply */
117 cgi_reset_content();
118 cgi_set_status(304, "Not Modified");
119 cgi_reply();
120 fossil_exit(0);
121 }
122
123 /* Return the ETag, if there is one.
124 */
125 const char *etag_tag(void){
126 return zETag;
127 }
128
129 /* Return the recommended max-age
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130 */
131 int etag_maxage(void){
132 return iMaxAge;
133 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
135 /*
136 ** COMMAND: test-etag
137 **
138 ** Usage: fossil test-etag -key KEY-NUMBER -hash HASH
@@ -213,17 +144,15 @@
144 ** 1 ETAG_CONFIG The config table version number
145 ** 2 ETAG_DATA The event table version number
146 ** 4 ETAG_COOKIE The display cookie
147 */
148 void test_etag_cmd(void){
149 const char *zHash = 0;
 
150 const char *zKey;
151 int iKey = 0;
152 db_find_and_open_repository(0, 0);
153 zKey = find_option("key",0,1);
154 zHash = find_option("hash",0,1);
155 if( zKey ) iKey = atoi(zKey);
156 etag_check(iKey, zHash);
157 fossil_print("%s\n", etag_tag());
 
 
158 }
159
-2
--- src/main.c
+++ src/main.c
@@ -1399,11 +1399,10 @@
13991399
const char *zPathInfo = PD("PATH_INFO", "");
14001400
char *zPath = NULL;
14011401
int i;
14021402
const CmdOrPage *pCmd = 0;
14031403
const char *zBase = g.zRepositoryName;
1404
- const char *zETag = 0;
14051404
14061405
/* Handle universal query parameters */
14071406
if( PB("utc") ){
14081407
g.fTimeFormat = 1;
14091408
}else if( PB("localtime") ){
@@ -1765,11 +1764,10 @@
17651764
if( !g.fNoThHook ){
17661765
rc = Th_WebpageHook(pCmd->zName+1, pCmd->eCmdFlags);
17671766
}else{
17681767
rc = TH_OK;
17691768
}
1770
- etag_require(CMDFLAG_TO_ETAG(pCmd->eType));
17711769
if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
17721770
if( rc==TH_OK || rc==TH_RETURN ){
17731771
#endif
17741772
pCmd->xFunc();
17751773
#ifdef FOSSIL_ENABLE_TH1_HOOKS
17761774
--- src/main.c
+++ src/main.c
@@ -1399,11 +1399,10 @@
1399 const char *zPathInfo = PD("PATH_INFO", "");
1400 char *zPath = NULL;
1401 int i;
1402 const CmdOrPage *pCmd = 0;
1403 const char *zBase = g.zRepositoryName;
1404 const char *zETag = 0;
1405
1406 /* Handle universal query parameters */
1407 if( PB("utc") ){
1408 g.fTimeFormat = 1;
1409 }else if( PB("localtime") ){
@@ -1765,11 +1764,10 @@
1765 if( !g.fNoThHook ){
1766 rc = Th_WebpageHook(pCmd->zName+1, pCmd->eCmdFlags);
1767 }else{
1768 rc = TH_OK;
1769 }
1770 etag_require(CMDFLAG_TO_ETAG(pCmd->eType));
1771 if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
1772 if( rc==TH_OK || rc==TH_RETURN ){
1773 #endif
1774 pCmd->xFunc();
1775 #ifdef FOSSIL_ENABLE_TH1_HOOKS
1776
--- src/main.c
+++ src/main.c
@@ -1399,11 +1399,10 @@
1399 const char *zPathInfo = PD("PATH_INFO", "");
1400 char *zPath = NULL;
1401 int i;
1402 const CmdOrPage *pCmd = 0;
1403 const char *zBase = g.zRepositoryName;
 
1404
1405 /* Handle universal query parameters */
1406 if( PB("utc") ){
1407 g.fTimeFormat = 1;
1408 }else if( PB("localtime") ){
@@ -1765,11 +1764,10 @@
1764 if( !g.fNoThHook ){
1765 rc = Th_WebpageHook(pCmd->zName+1, pCmd->eCmdFlags);
1766 }else{
1767 rc = TH_OK;
1768 }
 
1769 if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
1770 if( rc==TH_OK || rc==TH_RETURN ){
1771 #endif
1772 pCmd->xFunc();
1773 #ifdef FOSSIL_ENABLE_TH1_HOOKS
1774
+10 -29
--- src/mkindex.c
+++ src/mkindex.c
@@ -80,26 +80,19 @@
8080
8181
/***************************************************************************
8282
** These macros must match similar macros in dispatch.c.
8383
**
8484
** Allowed values for CmdOrPage.eCmdFlags. */
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)
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 */
10194
/**************************************************************************/
10295
10396
/*
10497
** Each entry looks like this:
10598
*/
@@ -205,11 +198,11 @@
205198
return;
206199
}
207200
while( fossil_isspace(zLine[i]) ){ i++; }
208201
if( zLine[i]=='/' ) i++;
209202
for(j=0; zLine[i+j] && !fossil_isspace(zLine[i+j]); j++){}
210
- aEntry[nUsed].eType = eType | CMDFLAG_DYNAMIC;
203
+ aEntry[nUsed].eType = eType;
211204
if( eType & CMDFLAG_WEBPAGE ){
212205
aEntry[nUsed].zPath = string_dup(&zLine[i-1], j+1);
213206
aEntry[nUsed].zPath[0] = '/';
214207
}else{
215208
aEntry[nUsed].zPath = string_dup(&zLine[i], j);
@@ -243,22 +236,10 @@
243236
aEntry[nUsed].eType &= ~(CMDFLAG_1ST_TIER|CMDFLAG_TEST);
244237
aEntry[nUsed].eType |= CMDFLAG_2ND_TIER;
245238
}else if( j==4 && strncmp(&zLine[i], "test", j)==0 ){
246239
aEntry[nUsed].eType &= ~(CMDFLAG_1ST_TIER|CMDFLAG_2ND_TIER);
247240
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;
260241
}else if( j==7 && strncmp(&zLine[i], "boolean", j)==0 ){
261242
aEntry[nUsed].eType &= ~(CMDFLAG_BLOCKTEXT);
262243
aEntry[nUsed].iWidth = 0;
263244
aEntry[nUsed].eType |= CMDFLAG_BOOLEAN;
264245
}else if( j==10 && strncmp(&zLine[i], "block-text", j)==0 ){
265246
--- src/mkindex.c
+++ src/mkindex.c
@@ -80,26 +80,19 @@
80
81 /***************************************************************************
82 ** These macros must match similar macros in dispatch.c.
83 **
84 ** Allowed values for CmdOrPage.eCmdFlags. */
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)
101 /**************************************************************************/
102
103 /*
104 ** Each entry looks like this:
105 */
@@ -205,11 +198,11 @@
205 return;
206 }
207 while( fossil_isspace(zLine[i]) ){ i++; }
208 if( zLine[i]=='/' ) i++;
209 for(j=0; zLine[i+j] && !fossil_isspace(zLine[i+j]); j++){}
210 aEntry[nUsed].eType = eType | CMDFLAG_DYNAMIC;
211 if( eType & CMDFLAG_WEBPAGE ){
212 aEntry[nUsed].zPath = string_dup(&zLine[i-1], j+1);
213 aEntry[nUsed].zPath[0] = '/';
214 }else{
215 aEntry[nUsed].zPath = string_dup(&zLine[i], j);
@@ -243,22 +236,10 @@
243 aEntry[nUsed].eType &= ~(CMDFLAG_1ST_TIER|CMDFLAG_TEST);
244 aEntry[nUsed].eType |= CMDFLAG_2ND_TIER;
245 }else if( j==4 && strncmp(&zLine[i], "test", j)==0 ){
246 aEntry[nUsed].eType &= ~(CMDFLAG_1ST_TIER|CMDFLAG_2ND_TIER);
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;
260 }else if( j==7 && strncmp(&zLine[i], "boolean", j)==0 ){
261 aEntry[nUsed].eType &= ~(CMDFLAG_BLOCKTEXT);
262 aEntry[nUsed].iWidth = 0;
263 aEntry[nUsed].eType |= CMDFLAG_BOOLEAN;
264 }else if( j==10 && strncmp(&zLine[i], "block-text", j)==0 ){
265
--- src/mkindex.c
+++ src/mkindex.c
@@ -80,26 +80,19 @@
80
81 /***************************************************************************
82 ** These macros must match similar macros in dispatch.c.
83 **
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 */
 
 
 
 
 
 
 
94 /**************************************************************************/
95
96 /*
97 ** Each entry looks like this:
98 */
@@ -205,11 +198,11 @@
198 return;
199 }
200 while( fossil_isspace(zLine[i]) ){ i++; }
201 if( zLine[i]=='/' ) i++;
202 for(j=0; zLine[i+j] && !fossil_isspace(zLine[i+j]); j++){}
203 aEntry[nUsed].eType = eType;
204 if( eType & CMDFLAG_WEBPAGE ){
205 aEntry[nUsed].zPath = string_dup(&zLine[i-1], j+1);
206 aEntry[nUsed].zPath[0] = '/';
207 }else{
208 aEntry[nUsed].zPath = string_dup(&zLine[i], j);
@@ -243,22 +236,10 @@
236 aEntry[nUsed].eType &= ~(CMDFLAG_1ST_TIER|CMDFLAG_TEST);
237 aEntry[nUsed].eType |= CMDFLAG_2ND_TIER;
238 }else if( j==4 && strncmp(&zLine[i], "test", j)==0 ){
239 aEntry[nUsed].eType &= ~(CMDFLAG_1ST_TIER|CMDFLAG_2ND_TIER);
240 aEntry[nUsed].eType |= CMDFLAG_TEST;
 
 
 
 
 
 
 
 
 
 
 
 
241 }else if( j==7 && strncmp(&zLine[i], "boolean", j)==0 ){
242 aEntry[nUsed].eType &= ~(CMDFLAG_BLOCKTEXT);
243 aEntry[nUsed].iWidth = 0;
244 aEntry[nUsed].eType |= CMDFLAG_BOOLEAN;
245 }else if( j==10 && strncmp(&zLine[i], "block-text", j)==0 ){
246
+10 -1
--- src/style.c
+++ src/style.c
@@ -833,15 +833,20 @@
833833
** WEBPAGE: builtin
834834
** URL: builtin/FILENAME
835835
**
836836
** Return the built-in text given by FILENAME. This is used internally
837837
** by many Fossil web pages to load built-in javascript files.
838
+**
839
+** If the id= query parameter is present, then Fossil assumes that the
840
+** result is immutable and sets a very large cache retention time (1 year).
838841
*/
839842
void page_builtin_text(void){
840843
Blob out;
841844
const char *zName = P("name");
842845
const char *zTxt = 0;
846
+ const char *zId = P("id");
847
+ int nId;
843848
if( zName ) zTxt = builtin_text(zName);
844849
if( zTxt==0 ){
845850
cgi_set_status(404, "Not Found");
846851
@ File "%h(zName)" not found
847852
return;
@@ -848,14 +853,18 @@
848853
}
849854
if( sqlite3_strglob("*.js", zName)==0 ){
850855
cgi_set_content_type("application/javascript");
851856
}else{
852857
cgi_set_content_type("text/plain");
858
+ }
859
+ if( zId && (nId = (int)strlen(zId))>=8 && strncmp(zId,MANIFEST_UUID,nId)==0 ){
860
+ g.isConst = 1;
861
+ }else{
862
+ etag_check(0,0);
853863
}
854864
blob_init(&out, zTxt, -1);
855865
cgi_set_content(&out);
856
- g.isConst = 1;
857866
}
858867
859868
860869
/*
861870
** WEBPAGE: test_env
862871
--- src/style.c
+++ src/style.c
@@ -833,15 +833,20 @@
833 ** WEBPAGE: builtin
834 ** URL: builtin/FILENAME
835 **
836 ** Return the built-in text given by FILENAME. This is used internally
837 ** by many Fossil web pages to load built-in javascript files.
 
 
 
838 */
839 void page_builtin_text(void){
840 Blob out;
841 const char *zName = P("name");
842 const char *zTxt = 0;
 
 
843 if( zName ) zTxt = builtin_text(zName);
844 if( zTxt==0 ){
845 cgi_set_status(404, "Not Found");
846 @ File "%h(zName)" not found
847 return;
@@ -848,14 +853,18 @@
848 }
849 if( sqlite3_strglob("*.js", zName)==0 ){
850 cgi_set_content_type("application/javascript");
851 }else{
852 cgi_set_content_type("text/plain");
 
 
 
 
 
853 }
854 blob_init(&out, zTxt, -1);
855 cgi_set_content(&out);
856 g.isConst = 1;
857 }
858
859
860 /*
861 ** WEBPAGE: test_env
862
--- src/style.c
+++ src/style.c
@@ -833,15 +833,20 @@
833 ** WEBPAGE: builtin
834 ** URL: builtin/FILENAME
835 **
836 ** Return the built-in text given by FILENAME. This is used internally
837 ** by many Fossil web pages to load built-in javascript files.
838 **
839 ** If the id= query parameter is present, then Fossil assumes that the
840 ** result is immutable and sets a very large cache retention time (1 year).
841 */
842 void page_builtin_text(void){
843 Blob out;
844 const char *zName = P("name");
845 const char *zTxt = 0;
846 const char *zId = P("id");
847 int nId;
848 if( zName ) zTxt = builtin_text(zName);
849 if( zTxt==0 ){
850 cgi_set_status(404, "Not Found");
851 @ File "%h(zName)" not found
852 return;
@@ -848,14 +853,18 @@
853 }
854 if( sqlite3_strglob("*.js", zName)==0 ){
855 cgi_set_content_type("application/javascript");
856 }else{
857 cgi_set_content_type("text/plain");
858 }
859 if( zId && (nId = (int)strlen(zId))>=8 && strncmp(zId,MANIFEST_UUID,nId)==0 ){
860 g.isConst = 1;
861 }else{
862 etag_check(0,0);
863 }
864 blob_init(&out, zTxt, -1);
865 cgi_set_content(&out);
 
866 }
867
868
869 /*
870 ** WEBPAGE: test_env
871
+2 -2
--- src/timeline.c
+++ src/timeline.c
@@ -1329,11 +1329,11 @@
13291329
/* If execution reaches this point, the pattern was empty. Return NULL. */
13301330
return 0;
13311331
}
13321332
13331333
/*
1334
-** WEBPAGE: timeline data
1334
+** WEBPAGE: timeline
13351335
**
13361336
** Query parameters:
13371337
**
13381338
** a=TIMEORTAG After this event
13391339
** b=TIMEORTAG Before this event
@@ -2510,11 +2510,11 @@
25102510
}
25112511
db_finalize(&q);
25122512
}
25132513
25142514
/*
2515
-** WEBPAGE: timewarps data
2515
+** WEBPAGE: timewarps
25162516
**
25172517
** Show all check-ins that are "timewarps". A timewarp is a
25182518
** check-in that occurs before its parent, according to the
25192519
** timestamp information on the check-in. This can only actually
25202520
** happen, of course, if a users system clock is set incorrectly.
25212521
--- src/timeline.c
+++ src/timeline.c
@@ -1329,11 +1329,11 @@
1329 /* If execution reaches this point, the pattern was empty. Return NULL. */
1330 return 0;
1331 }
1332
1333 /*
1334 ** WEBPAGE: timeline data
1335 **
1336 ** Query parameters:
1337 **
1338 ** a=TIMEORTAG After this event
1339 ** b=TIMEORTAG Before this event
@@ -2510,11 +2510,11 @@
2510 }
2511 db_finalize(&q);
2512 }
2513
2514 /*
2515 ** WEBPAGE: timewarps data
2516 **
2517 ** Show all check-ins that are "timewarps". A timewarp is a
2518 ** check-in that occurs before its parent, according to the
2519 ** timestamp information on the check-in. This can only actually
2520 ** happen, of course, if a users system clock is set incorrectly.
2521
--- src/timeline.c
+++ src/timeline.c
@@ -1329,11 +1329,11 @@
1329 /* If execution reaches this point, the pattern was empty. Return NULL. */
1330 return 0;
1331 }
1332
1333 /*
1334 ** WEBPAGE: timeline
1335 **
1336 ** Query parameters:
1337 **
1338 ** a=TIMEORTAG After this event
1339 ** b=TIMEORTAG Before this event
@@ -2510,11 +2510,11 @@
2510 }
2511 db_finalize(&q);
2512 }
2513
2514 /*
2515 ** WEBPAGE: timewarps
2516 **
2517 ** Show all check-ins that are "timewarps". A timewarp is a
2518 ** check-in that occurs before its parent, according to the
2519 ** timestamp information on the check-in. This can only actually
2520 ** happen, of course, if a users system clock is set incorrectly.
2521

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button