Fossil SCM

Add support for Last-Modified: and If-Modified-Since:. Currently only works for /uv but can be easily expanded to other resources. Also change Set-Cookie to use max-age= rather than expires=.

drh 2018-02-25 19:47 trunk
Commit f89eb80eec0b986e80d45e4e894f69b9ba76eca5cd472c968fee18cd7bc651be
4 files changed +38 -48 +8 -3 +52 -1 +2 -1
+38 -48
--- src/cgi.c
+++ src/cgi.c
@@ -217,14 +217,13 @@
217217
}
218218
if( g.zBaseURL!=0 && strncmp(g.zBaseURL, "https:", 6)==0 ){
219219
zSecure = " secure;";
220220
}
221221
if( lifetime>0 ){
222
- lifetime += (int)time(0);
223222
blob_appendf(&extraHeader,
224
- "Set-Cookie: %s=%t; Path=%s; expires=%z; HttpOnly;%s Version=1\r\n",
225
- zName, zValue, zPath, cgi_rfc822_datestamp(lifetime), zSecure);
223
+ "Set-Cookie: %s=%t; Path=%s; max-age=%d; HttpOnly;%s Version=1\r\n",
224
+ zName, zValue, zPath, lifetime, zSecure);
226225
}else{
227226
blob_appendf(&extraHeader,
228227
"Set-Cookie: %s=%t; Path=%s; HttpOnly;%s Version=1\r\n",
229228
zName, zValue, zPath, zSecure);
230229
}
@@ -267,10 +266,14 @@
267266
fprintf(g.httpOut, "ETag: %s\r\n", etag_tag());
268267
fprintf(g.httpOut, "Cache-Control: max-age=%d\r\n", etag_maxage());
269268
}else{
270269
fprintf(g.httpOut, "Cache-control: no-cache\r\n");
271270
}
271
+ if( etag_mtime()>0 ){
272
+ fprintf(g.httpOut, "Last-Modified: %s\r\n",
273
+ cgi_rfc822_datestamp(etag_mtime()));
274
+ }
272275
273276
if( blob_size(&extraHeader)>0 ){
274277
fprintf(g.httpOut, "%s", blob_buffer(&extraHeader));
275278
}
276279
@@ -1910,55 +1913,42 @@
19101913
**
19111914
** Note that this won't handle all the _allowed_ HTTP formats, just the
19121915
** most popular one (the one generated by cgi_rfc822_datestamp(), actually).
19131916
*/
19141917
time_t cgi_rfc822_parsedate(const char *zDate){
1915
- struct tm t;
1916
- char zIgnore[16];
1917
- char zMonth[16];
1918
-
1919
- memset(&t, 0, sizeof(t));
1920
- if( 7==sscanf(zDate, "%12[A-Za-z,] %d %12[A-Za-z] %d %d:%d:%d", zIgnore,
1921
- &t.tm_mday, zMonth, &t.tm_year, &t.tm_hour, &t.tm_min,
1922
- &t.tm_sec)){
1923
-
1924
- if( t.tm_year > 1900 ) t.tm_year -= 1900;
1925
- for(t.tm_mon=0; azMonths[t.tm_mon]; t.tm_mon++){
1926
- if( !fossil_strnicmp( azMonths[t.tm_mon], zMonth, 3 )){
1927
- return mkgmtime(&t);
1928
- }
1929
- }
1930
- }
1931
-
1932
- return 0;
1933
-}
1934
-
1935
-/*
1936
-** Convert a struct tm* that represents a moment in UTC into the number
1937
-** of seconds in 1970, UTC.
1938
-*/
1939
-time_t mkgmtime(struct tm *p){
1940
- time_t t;
1941
- int nDay;
1942
- int isLeapYr;
1943
- /* Days in each month: 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 */
1944
- static int priorDays[] = { 0, 31, 59, 90,120,151,181,212,243,273,304,334 };
1945
- if( p->tm_mon<0 ){
1946
- int nYear = (11 - p->tm_mon)/12;
1947
- p->tm_year -= nYear;
1948
- p->tm_mon += nYear*12;
1949
- }else if( p->tm_mon>11 ){
1950
- p->tm_year += p->tm_mon/12;
1951
- p->tm_mon %= 12;
1952
- }
1953
- isLeapYr = p->tm_year%4==0 && (p->tm_year%100!=0 || (p->tm_year+300)%400==0);
1954
- p->tm_yday = priorDays[p->tm_mon] + p->tm_mday - 1;
1955
- if( isLeapYr && p->tm_mon>1 ) p->tm_yday++;
1956
- nDay = (p->tm_year-70)*365 + (p->tm_year-69)/4 -p->tm_year/100 +
1957
- (p->tm_year+300)/400 + p->tm_yday;
1958
- t = ((nDay*24 + p->tm_hour)*60 + p->tm_min)*60 + p->tm_sec;
1959
- return t;
1918
+ int mday, mon, year, yday, hour, min, sec;
1919
+ char zIgnore[4];
1920
+ char zMonth[4];
1921
+ static const char *const azMonths[] =
1922
+ {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
1923
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0};
1924
+ if( 7==sscanf(zDate, "%3[A-Za-z], %d %3[A-Za-z] %d %d:%d:%d", zIgnore,
1925
+ &mday, zMonth, &year, &hour, &min, &sec)){
1926
+ if( year > 1900 ) year -= 1900;
1927
+ for(mon=0; azMonths[mon]; mon++){
1928
+ if( !strncmp( azMonths[mon], zMonth, 3 )){
1929
+ int nDay;
1930
+ int isLeapYr;
1931
+ static int priorDays[] =
1932
+ { 0, 31, 59, 90,120,151,181,212,243,273,304,334 };
1933
+ if( mon<0 ){
1934
+ int nYear = (11 - mon)/12;
1935
+ year -= nYear;
1936
+ mon += nYear*12;
1937
+ }else if( mon>11 ){
1938
+ year += mon/12;
1939
+ mon %= 12;
1940
+ }
1941
+ isLeapYr = year%4==0 && (year%100!=0 || (year+300)%400==0);
1942
+ yday = priorDays[mon] + mday - 1;
1943
+ if( isLeapYr && mon>1 ) yday++;
1944
+ nDay = (year-70)*365 + (year-69)/4 - year/100 + (year+300)/400 + yday;
1945
+ return ((time_t)(nDay*24 + hour)*60 + min)*60 + sec;
1946
+ }
1947
+ }
1948
+ }
1949
+ return 0;
19601950
}
19611951
19621952
/*
19631953
** Check the objectTime against the If-Modified-Since request header. If the
19641954
** object time isn't any newer than the header, we immediately send back
19651955
--- src/cgi.c
+++ src/cgi.c
@@ -217,14 +217,13 @@
217 }
218 if( g.zBaseURL!=0 && strncmp(g.zBaseURL, "https:", 6)==0 ){
219 zSecure = " secure;";
220 }
221 if( lifetime>0 ){
222 lifetime += (int)time(0);
223 blob_appendf(&extraHeader,
224 "Set-Cookie: %s=%t; Path=%s; expires=%z; HttpOnly;%s Version=1\r\n",
225 zName, zValue, zPath, cgi_rfc822_datestamp(lifetime), zSecure);
226 }else{
227 blob_appendf(&extraHeader,
228 "Set-Cookie: %s=%t; Path=%s; HttpOnly;%s Version=1\r\n",
229 zName, zValue, zPath, zSecure);
230 }
@@ -267,10 +266,14 @@
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 }
276
@@ -1910,55 +1913,42 @@
1910 **
1911 ** Note that this won't handle all the _allowed_ HTTP formats, just the
1912 ** most popular one (the one generated by cgi_rfc822_datestamp(), actually).
1913 */
1914 time_t cgi_rfc822_parsedate(const char *zDate){
1915 struct tm t;
1916 char zIgnore[16];
1917 char zMonth[16];
1918
1919 memset(&t, 0, sizeof(t));
1920 if( 7==sscanf(zDate, "%12[A-Za-z,] %d %12[A-Za-z] %d %d:%d:%d", zIgnore,
1921 &t.tm_mday, zMonth, &t.tm_year, &t.tm_hour, &t.tm_min,
1922 &t.tm_sec)){
1923
1924 if( t.tm_year > 1900 ) t.tm_year -= 1900;
1925 for(t.tm_mon=0; azMonths[t.tm_mon]; t.tm_mon++){
1926 if( !fossil_strnicmp( azMonths[t.tm_mon], zMonth, 3 )){
1927 return mkgmtime(&t);
1928 }
1929 }
1930 }
1931
1932 return 0;
1933 }
1934
1935 /*
1936 ** Convert a struct tm* that represents a moment in UTC into the number
1937 ** of seconds in 1970, UTC.
1938 */
1939 time_t mkgmtime(struct tm *p){
1940 time_t t;
1941 int nDay;
1942 int isLeapYr;
1943 /* Days in each month: 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 */
1944 static int priorDays[] = { 0, 31, 59, 90,120,151,181,212,243,273,304,334 };
1945 if( p->tm_mon<0 ){
1946 int nYear = (11 - p->tm_mon)/12;
1947 p->tm_year -= nYear;
1948 p->tm_mon += nYear*12;
1949 }else if( p->tm_mon>11 ){
1950 p->tm_year += p->tm_mon/12;
1951 p->tm_mon %= 12;
1952 }
1953 isLeapYr = p->tm_year%4==0 && (p->tm_year%100!=0 || (p->tm_year+300)%400==0);
1954 p->tm_yday = priorDays[p->tm_mon] + p->tm_mday - 1;
1955 if( isLeapYr && p->tm_mon>1 ) p->tm_yday++;
1956 nDay = (p->tm_year-70)*365 + (p->tm_year-69)/4 -p->tm_year/100 +
1957 (p->tm_year+300)/400 + p->tm_yday;
1958 t = ((nDay*24 + p->tm_hour)*60 + p->tm_min)*60 + p->tm_sec;
1959 return t;
1960 }
1961
1962 /*
1963 ** Check the objectTime against the If-Modified-Since request header. If the
1964 ** object time isn't any newer than the header, we immediately send back
1965
--- src/cgi.c
+++ src/cgi.c
@@ -217,14 +217,13 @@
217 }
218 if( g.zBaseURL!=0 && strncmp(g.zBaseURL, "https:", 6)==0 ){
219 zSecure = " secure;";
220 }
221 if( lifetime>0 ){
 
222 blob_appendf(&extraHeader,
223 "Set-Cookie: %s=%t; Path=%s; max-age=%d; HttpOnly;%s Version=1\r\n",
224 zName, zValue, zPath, lifetime, zSecure);
225 }else{
226 blob_appendf(&extraHeader,
227 "Set-Cookie: %s=%t; Path=%s; HttpOnly;%s Version=1\r\n",
228 zName, zValue, zPath, zSecure);
229 }
@@ -267,10 +266,14 @@
266 fprintf(g.httpOut, "ETag: %s\r\n", etag_tag());
267 fprintf(g.httpOut, "Cache-Control: max-age=%d\r\n", etag_maxage());
268 }else{
269 fprintf(g.httpOut, "Cache-control: no-cache\r\n");
270 }
271 if( etag_mtime()>0 ){
272 fprintf(g.httpOut, "Last-Modified: %s\r\n",
273 cgi_rfc822_datestamp(etag_mtime()));
274 }
275
276 if( blob_size(&extraHeader)>0 ){
277 fprintf(g.httpOut, "%s", blob_buffer(&extraHeader));
278 }
279
@@ -1910,55 +1913,42 @@
1913 **
1914 ** Note that this won't handle all the _allowed_ HTTP formats, just the
1915 ** most popular one (the one generated by cgi_rfc822_datestamp(), actually).
1916 */
1917 time_t cgi_rfc822_parsedate(const char *zDate){
1918 int mday, mon, year, yday, hour, min, sec;
1919 char zIgnore[4];
1920 char zMonth[4];
1921 static const char *const azMonths[] =
1922 {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
1923 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0};
1924 if( 7==sscanf(zDate, "%3[A-Za-z], %d %3[A-Za-z] %d %d:%d:%d", zIgnore,
1925 &mday, zMonth, &year, &hour, &min, &sec)){
1926 if( year > 1900 ) year -= 1900;
1927 for(mon=0; azMonths[mon]; mon++){
1928 if( !strncmp( azMonths[mon], zMonth, 3 )){
1929 int nDay;
1930 int isLeapYr;
1931 static int priorDays[] =
1932 { 0, 31, 59, 90,120,151,181,212,243,273,304,334 };
1933 if( mon<0 ){
1934 int nYear = (11 - mon)/12;
1935 year -= nYear;
1936 mon += nYear*12;
1937 }else if( mon>11 ){
1938 year += mon/12;
1939 mon %= 12;
1940 }
1941 isLeapYr = year%4==0 && (year%100!=0 || (year+300)%400==0);
1942 yday = priorDays[mon] + mday - 1;
1943 if( isLeapYr && mon>1 ) yday++;
1944 nDay = (year-70)*365 + (year-69)/4 - year/100 + (year+300)/400 + yday;
1945 return ((time_t)(nDay*24 + hour)*60 + min)*60 + sec;
1946 }
1947 }
1948 }
1949 return 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
1950 }
1951
1952 /*
1953 ** Check the objectTime against the If-Modified-Since request header. If the
1954 ** object time isn't any newer than the header, we immediately send back
1955
+8 -3
--- src/doc.c
+++ src/doc.c
@@ -640,13 +640,18 @@
640640
goto doc_not_found;
641641
}
642642
}
643643
if( isUV ){
644644
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);
645
+ Stmt q;
646
+ db_prepare(&q, "SELECT hash, mtime FROM unversioned"
647
+ " WHERE name=%Q", zName);
648
+ if( db_step(&q)==SQLITE_ROW ){
649
+ etag_check(ETAG_HASH, db_column_text(&q,0));
650
+ etag_last_modified(db_column_int64(&q,1));
651
+ }
652
+ db_finalize(&q);
648653
if( unversioned_content(zName, &filebody)==0 ){
649654
rid = 1;
650655
zDfltTitle = zName;
651656
}
652657
}
653658
--- src/doc.c
+++ src/doc.c
@@ -640,13 +640,18 @@
640 goto doc_not_found;
641 }
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 }
653
--- src/doc.c
+++ src/doc.c
@@ -640,13 +640,18 @@
640 goto doc_not_found;
641 }
642 }
643 if( isUV ){
644 if( db_table_exists("repository","unversioned") ){
645 Stmt q;
646 db_prepare(&q, "SELECT hash, mtime FROM unversioned"
647 " WHERE name=%Q", zName);
648 if( db_step(&q)==SQLITE_ROW ){
649 etag_check(ETAG_HASH, db_column_text(&q,0));
650 etag_last_modified(db_column_int64(&q,1));
651 }
652 db_finalize(&q);
653 if( unversioned_content(zName, &filebody)==0 ){
654 rid = 1;
655 zDfltTitle = zName;
656 }
657 }
658
+52 -1
--- src/etag.c
+++ src/etag.c
@@ -38,10 +38,20 @@
3838
** the process exits. In other words, etag_check() never returns. But
3939
** if there is no If-None_Match header or if the ETag does not match,
4040
** then etag_check() returns normally. Later, during reply generation,
4141
** the cgi.c module will invoke etag_tag() to recover the generated tag
4242
** and include it in the reply header.
43
+**
44
+** 2018-02-25:
45
+**
46
+** Also support Last-Modified: and If-Modified-Since:. The
47
+** etag_last_modified(mtime) API records a timestamp for the page in
48
+** seconds since 1970. This causes a Last-Modified: header to be
49
+** issued in the reply. Or, if the request contained If-Modified-Since:
50
+** and the new mtime is not greater than the mtime associated with
51
+** If-Modified-Since, then a 304 Not Modified reply is generated and
52
+** the etag_last_modified() API never returns.
4353
*/
4454
#include "config.h"
4555
#include "etag.h"
4656
4757
#if INTERFACE
@@ -54,10 +64,11 @@
5464
#define ETAG_HASH 0x08 /* Output depends on a hash */
5565
#endif
5666
5767
static char zETag[33]; /* The generated ETag */
5868
static int iMaxAge = 0; /* The max-age parameter in the reply */
69
+static sqlite3_int64 iEtagMtime = 0; /* Last-Modified time */
5970
6071
/*
6172
** Generate an ETag
6273
*/
6374
void etag_check(unsigned eFlags, const char *zHash){
@@ -117,10 +128,43 @@
117128
cgi_reset_content();
118129
cgi_set_status(304, "Not Modified");
119130
cgi_reply();
120131
fossil_exit(0);
121132
}
133
+
134
+/*
135
+** Accept a new Last-Modified time. This routine should be called by
136
+** page generators that know a valid last-modified time. This routine
137
+** might generate a 304 Not Modified reply and exit(), never returnning.
138
+** Or, if not, it will cause a Last-Modified: header to be included in the
139
+** reply.
140
+*/
141
+void etag_last_modified(sqlite3_int64 mtime){
142
+ const char *zIfModifiedSince;
143
+ sqlite3_int64 x, exeMtime;
144
+ assert( iEtagMtime==0 ); /* Only call this routine once */
145
+ assert( mtime>0 ); /* Only call with a valid mtime */
146
+ iEtagMtime = mtime;
147
+
148
+ /* Check to see the If-Modified-Since constraint is satisfied */
149
+ zIfModifiedSince = P("HTTP_IF_MODIFIED_SINCE");
150
+ if( zIfModifiedSince==0 ) return;
151
+ x = cgi_rfc822_parsedate(zIfModifiedSince);
152
+ if( x<=0 || x>mtime ) return;
153
+
154
+ /* If the Fossil executable is more recent than If-Modified-Since,
155
+ ** go ahead and regenerate the resource. */
156
+ exeMtime = file_mtime(g.nameOfExe, ExtFILE);
157
+ if( exeMtime>x ) return;
158
+
159
+ /* If we reach this point, it means that the resource has not changed
160
+ ** and that we should generate a 304 Not Modified reply */
161
+ cgi_reset_content();
162
+ cgi_set_status(304, "Not Modified");
163
+ cgi_reply();
164
+ fossil_exit(0);
165
+}
122166
123167
/* Return the ETag, if there is one.
124168
*/
125169
const char *etag_tag(void){
126170
return zETag;
@@ -128,11 +172,18 @@
128172
129173
/* Return the recommended max-age
130174
*/
131175
int etag_maxage(void){
132176
return iMaxAge;
133
-}
177
+}
178
+
179
+/* Return the last-modified time in seconds since 1970. Or return 0 if
180
+** there is no last-modified time.
181
+*/
182
+sqlite3_int64 etag_mtime(void){
183
+ return iEtagMtime;
184
+}
134185
135186
/*
136187
** COMMAND: test-etag
137188
**
138189
** Usage: fossil test-etag -key KEY-NUMBER -hash HASH
139190
--- src/etag.c
+++ src/etag.c
@@ -38,10 +38,20 @@
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
@@ -54,10 +64,11 @@
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){
@@ -117,10 +128,43 @@
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;
@@ -128,11 +172,18 @@
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
139
--- src/etag.c
+++ src/etag.c
@@ -38,10 +38,20 @@
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 ** 2018-02-25:
45 **
46 ** Also support Last-Modified: and If-Modified-Since:. The
47 ** etag_last_modified(mtime) API records a timestamp for the page in
48 ** seconds since 1970. This causes a Last-Modified: header to be
49 ** issued in the reply. Or, if the request contained If-Modified-Since:
50 ** and the new mtime is not greater than the mtime associated with
51 ** If-Modified-Since, then a 304 Not Modified reply is generated and
52 ** the etag_last_modified() API never returns.
53 */
54 #include "config.h"
55 #include "etag.h"
56
57 #if INTERFACE
@@ -54,10 +64,11 @@
64 #define ETAG_HASH 0x08 /* Output depends on a hash */
65 #endif
66
67 static char zETag[33]; /* The generated ETag */
68 static int iMaxAge = 0; /* The max-age parameter in the reply */
69 static sqlite3_int64 iEtagMtime = 0; /* Last-Modified time */
70
71 /*
72 ** Generate an ETag
73 */
74 void etag_check(unsigned eFlags, const char *zHash){
@@ -117,10 +128,43 @@
128 cgi_reset_content();
129 cgi_set_status(304, "Not Modified");
130 cgi_reply();
131 fossil_exit(0);
132 }
133
134 /*
135 ** Accept a new Last-Modified time. This routine should be called by
136 ** page generators that know a valid last-modified time. This routine
137 ** might generate a 304 Not Modified reply and exit(), never returnning.
138 ** Or, if not, it will cause a Last-Modified: header to be included in the
139 ** reply.
140 */
141 void etag_last_modified(sqlite3_int64 mtime){
142 const char *zIfModifiedSince;
143 sqlite3_int64 x, exeMtime;
144 assert( iEtagMtime==0 ); /* Only call this routine once */
145 assert( mtime>0 ); /* Only call with a valid mtime */
146 iEtagMtime = mtime;
147
148 /* Check to see the If-Modified-Since constraint is satisfied */
149 zIfModifiedSince = P("HTTP_IF_MODIFIED_SINCE");
150 if( zIfModifiedSince==0 ) return;
151 x = cgi_rfc822_parsedate(zIfModifiedSince);
152 if( x<=0 || x>mtime ) return;
153
154 /* If the Fossil executable is more recent than If-Modified-Since,
155 ** go ahead and regenerate the resource. */
156 exeMtime = file_mtime(g.nameOfExe, ExtFILE);
157 if( exeMtime>x ) return;
158
159 /* If we reach this point, it means that the resource has not changed
160 ** and that we should generate a 304 Not Modified reply */
161 cgi_reset_content();
162 cgi_set_status(304, "Not Modified");
163 cgi_reply();
164 fossil_exit(0);
165 }
166
167 /* Return the ETag, if there is one.
168 */
169 const char *etag_tag(void){
170 return zETag;
@@ -128,11 +172,18 @@
172
173 /* Return the recommended max-age
174 */
175 int etag_maxage(void){
176 return iMaxAge;
177 }
178
179 /* Return the last-modified time in seconds since 1970. Or return 0 if
180 ** there is no last-modified time.
181 */
182 sqlite3_int64 etag_mtime(void){
183 return iEtagMtime;
184 }
185
186 /*
187 ** COMMAND: test-etag
188 **
189 ** Usage: fossil test-etag -key KEY-NUMBER -hash HASH
190
+2 -1
--- src/style.c
+++ src/style.c
@@ -879,11 +879,12 @@
879879
char zCap[30];
880880
static const char *const azCgiVars[] = {
881881
"COMSPEC", "DOCUMENT_ROOT", "GATEWAY_INTERFACE",
882882
"HTTP_ACCEPT", "HTTP_ACCEPT_CHARSET", "HTTP_ACCEPT_ENCODING",
883883
"HTTP_ACCEPT_LANGUAGE", "HTTP_AUTHENICATION",
884
- "HTTP_CONNECTION", "HTTP_HOST", "HTTP_IF_NONE_MATCH",
884
+ "HTTP_CONNECTION", "HTTP_HOST",
885
+ "HTTP_IF_NONE_MATCH", "HTTP_IF_MODIFIED_SINCE",
885886
"HTTP_USER_AGENT", "HTTP_REFERER", "PATH_INFO", "PATH_TRANSLATED",
886887
"QUERY_STRING", "REMOTE_ADDR", "REMOTE_PORT",
887888
"REMOTE_USER", "REQUEST_METHOD",
888889
"REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL",
889890
"HOME", "FOSSIL_HOME", "USERNAME", "USER", "FOSSIL_USER",
890891
--- src/style.c
+++ src/style.c
@@ -879,11 +879,12 @@
879 char zCap[30];
880 static const char *const azCgiVars[] = {
881 "COMSPEC", "DOCUMENT_ROOT", "GATEWAY_INTERFACE",
882 "HTTP_ACCEPT", "HTTP_ACCEPT_CHARSET", "HTTP_ACCEPT_ENCODING",
883 "HTTP_ACCEPT_LANGUAGE", "HTTP_AUTHENICATION",
884 "HTTP_CONNECTION", "HTTP_HOST", "HTTP_IF_NONE_MATCH",
 
885 "HTTP_USER_AGENT", "HTTP_REFERER", "PATH_INFO", "PATH_TRANSLATED",
886 "QUERY_STRING", "REMOTE_ADDR", "REMOTE_PORT",
887 "REMOTE_USER", "REQUEST_METHOD",
888 "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL",
889 "HOME", "FOSSIL_HOME", "USERNAME", "USER", "FOSSIL_USER",
890
--- src/style.c
+++ src/style.c
@@ -879,11 +879,12 @@
879 char zCap[30];
880 static const char *const azCgiVars[] = {
881 "COMSPEC", "DOCUMENT_ROOT", "GATEWAY_INTERFACE",
882 "HTTP_ACCEPT", "HTTP_ACCEPT_CHARSET", "HTTP_ACCEPT_ENCODING",
883 "HTTP_ACCEPT_LANGUAGE", "HTTP_AUTHENICATION",
884 "HTTP_CONNECTION", "HTTP_HOST",
885 "HTTP_IF_NONE_MATCH", "HTTP_IF_MODIFIED_SINCE",
886 "HTTP_USER_AGENT", "HTTP_REFERER", "PATH_INFO", "PATH_TRANSLATED",
887 "QUERY_STRING", "REMOTE_ADDR", "REMOTE_PORT",
888 "REMOTE_USER", "REQUEST_METHOD",
889 "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL",
890 "HOME", "FOSSIL_HOME", "USERNAME", "USER", "FOSSIL_USER",
891

Keyboard Shortcuts

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