Annotation of libwww/Library/src/HTCache.c, revision 2.41
2.1 frystyk 1: /* HTCache.c
2: ** CACHE WRITER
3: **
4: ** (c) COPYRIGHT MIT 1995.
5: ** Please first read the full copyright statement in the file COPYRIGH.
2.41 ! frystyk 6: ** @(#) $Id: HTCache.c,v 2.40 1997/02/13 17:09:34 eric Exp $
2.1 frystyk 7: **
8: ** This modules manages the cache
9: **
10: ** History:
11: ** HFN: spawned from HTFwrite
12: ** HWL: converted the caching scheme to be hierachical by taking
13: ** AL code from Deamon
14: **
15: */
16:
17: /* Library include files */
2.15 frystyk 18: #include "sysdep.h"
2.19 frystyk 19: #include "WWWUtil.h"
20: #include "WWWCore.h"
2.22 frystyk 21: #include "WWWTrans.h"
22: #include "WWWApp.h"
23: #include "HTNetMan.h"
2.1 frystyk 24: #include "HTCache.h" /* Implemented here */
25:
2.7 frystyk 26: /* This is the default cache directory: */
2.36 frystyk 27: #define HT_CACHE_ROOT "/tmp/w3c-cache"
2.22 frystyk 28: #define HT_CACHE_INDEX ".index"
29: #define HT_CACHE_META ".meta"
2.34 frystyk 30: #define HT_CACHE_ETAG "@w3c@"
2.22 frystyk 31:
2.31 frystyk 32: /* Default heuristics cache expirations - thanks to Jeff Mogul for good comments! */
33: #define NO_LM_EXPIRATION 24*3600 /* 24 hours */
34: #define MAX_LM_EXPIRATION 48*3600 /* Max expiration from LM */
35:
36: /*
37: ** If using LM to find the expiration then take 10% and no more than
38: ** MAX_LM_EXPIRATION.
39: */
40: #ifndef LM_EXPIRATION
41: #define LM_EXPIRATION(t) (HTMIN((MAX_LM_EXPIRATION), (t) / 10))
42: #endif
43:
44: #define WARN_HEURISTICS 24*3600 /* When to issue a warning */
45:
2.22 frystyk 46: #define HASH_SIZE 67
2.41 ! frystyk 47: #define DUMP_FREQUENCY 20 /* Dump index after 20 loads */
2.22 frystyk 48:
49: #define MEGA 0x100000L
50: #define CACHE_SIZE (20*MEGA) /* Default cache size is 20M */
2.23 frystyk 51: #define MIN_CACHE_SIZE (5*MEGA) /* Min cache size */
2.22 frystyk 52: #define SIZE_BUFFER (1*MEGA) /* Buffer for metainfo and directories */
53:
54: /* Final states have negative value */
55: typedef enum _CacheState {
56: CL_ERROR = -3,
57: CL_NO_DATA = -2,
58: CL_GOT_DATA = -1,
59: CL_BEGIN = 0,
60: CL_NEED_BODY,
61: CL_NEED_OPEN_FILE,
62: CL_NEED_CONTENT
63: } CacheState;
64:
65: /* This is the context structure for the this module */
66: typedef struct _cache_info {
67: CacheState state; /* Current state of the connection */
68: char * local; /* Local representation of file name */
69: struct stat stat_info; /* Contains actual file chosen */
2.29 frystyk 70: HTNet * net;
2.22 frystyk 71: } cache_info;
72:
73: struct _HTCache {
2.24 frystyk 74: /* Location */
2.22 frystyk 75: int hash;
76: char * url;
77: char * cachename;
2.24 frystyk 78:
79: /* GC parameters */
2.35 frystyk 80: char * etag;
2.27 frystyk 81: BOOL range; /* Is this the full part or a subpart? */
2.35 frystyk 82: BOOL must_revalidate;
2.26 frystyk 83: int hits; /* Hit counts */
2.35 frystyk 84: long size; /* Size of cached entity body */
2.34 frystyk 85: time_t lm; /* Last modified */
2.35 frystyk 86: time_t expires;
2.22 frystyk 87: time_t freshness_lifetime;
88: time_t response_time;
89: time_t corrected_initial_age;
90: HTRequest * lock;
91: };
2.1 frystyk 92:
93: struct _HTStream {
2.15 frystyk 94: const HTStreamClass * isa;
2.1 frystyk 95: FILE * fp;
2.26 frystyk 96: long bytes_written; /* Number of bytes written */
2.7 frystyk 97: HTCache * cache;
2.1 frystyk 98: HTRequest * request;
2.26 frystyk 99: HTResponse * response;
2.22 frystyk 100: HTChunk * buffer; /* For index reading */
101: HTEOLState EOLstate;
2.27 frystyk 102: BOOL append; /* Creating or appending? */
2.22 frystyk 103: };
104:
105: struct _HTInputStream {
106: const HTInputStreamClass * isa;
2.1 frystyk 107: };
108:
2.22 frystyk 109: /* Cache parameters */
110: PRIVATE BOOL HTCacheEnable = NO; /* Disabled by default */
2.1 frystyk 111: PRIVATE char * HTCacheRoot = NULL; /* Destination for cache */
2.22 frystyk 112: PRIVATE HTExpiresMode HTExpMode = HT_EXPIRES_IGNORE;
113: PRIVATE HTDisconnectedMode DisconnectedMode = HT_DISCONNECT_NONE;
2.1 frystyk 114:
2.31 frystyk 115: /* Heuristic expiration parameters */
116: PRIVATE int DefaultExpiration = NO_LM_EXPIRATION;
117:
2.22 frystyk 118: /* List of cache entries */
119: PRIVATE HTList ** CacheTable = NULL;
120:
121: /* Cache size variables */
122: PRIVATE long HTCacheSize = CACHE_SIZE;
123: PRIVATE long HTTotalSize = 0L;
124:
125: PRIVATE int new_entries = 0; /* Number of new entries */
2.2 frystyk 126:
2.1 frystyk 127: /* ------------------------------------------------------------------------- */
2.22 frystyk 128: /* CACHE GARBAGE COLLECTOR */
2.1 frystyk 129: /* ------------------------------------------------------------------------- */
130:
2.22 frystyk 131: PRIVATE BOOL HTCacheGarbage (void)
2.1 frystyk 132: {
2.22 frystyk 133: long old_size = HTTotalSize;
134: if (CACHE_TRACE) HTTrace("Cache....... Garbage collecting\n");
135: if (CacheTable) {
136: time_t cur_time = time(NULL);
137: HTList * cur;
138: int cnt;
2.23 frystyk 139: int hits;
2.22 frystyk 140:
141: /*
142: ** Tell the use that we're gc'ing.
143: */
2.1 frystyk 144: {
2.22 frystyk 145: HTAlertCallback * cbf = HTAlert_find(HT_PROG_GC);
146: if (cbf) (*cbf)(NULL, HT_PROG_GC, HT_MSG_NULL,NULL, NULL, NULL);
147: }
148:
149: /*
150: ** Walk through and delete all the expired entries. If this is not
151: ** sufficient then take the fresh ones which have the lowest cache
152: ** hit count. This algorithm could be made a lot fancier by including
153: ** the size and also the pain it took to get the document in the first
154: ** case. It could also include max_stale.
155: */
2.23 frystyk 156: if (CACHE_TRACE) HTTrace("Cache....... Collecting Stale entries\n");
2.22 frystyk 157: for (cnt=0; cnt<HASH_SIZE; cnt++) {
158: if ((cur = CacheTable[cnt])) {
2.35 frystyk 159: HTList * old_cur = cur;
2.22 frystyk 160: HTCache * pres;
161: while ((pres = (HTCache *) HTList_nextObject(cur)) != NULL) {
162: time_t resident_time = cur_time - pres->response_time;
163: time_t current_age = pres->corrected_initial_age +
164: resident_time;
165: if (pres->freshness_lifetime < current_age) {
166: HTCache_remove(pres);
167: cur = old_cur;
168: } else {
169: old_cur = cur;
170: }
2.1 frystyk 171: }
172: }
173: }
2.23 frystyk 174:
175: /*
176: ** We must at least free the min buffer size so that we don't
177: ** dead lock ourselves. We start from the bottom up by taking
178: ** all the documents with 0 hits, 1 hits, 2 hits, etc.
179: */
180: hits = 0;
181: while (1) {
182: BOOL removed = NO;
183: if (CACHE_TRACE)
184: HTTrace("Cache....... Collecting entries with %d hits\n",hits);
185: if (HTTotalSize + SIZE_BUFFER > HTCacheSize) {
186: for (cnt=0; cnt<HASH_SIZE; cnt++) {
187: if ((cur = CacheTable[cnt])) {
2.35 frystyk 188: HTList * old_cur = cur;
2.23 frystyk 189: HTCache * pres;
190: while ((pres = (HTCache *) HTList_nextObject(cur))) {
191: if (pres->hits <= hits) {
192: HTCache_remove(pres);
193: cur = old_cur;
194: removed = YES;
195: } else {
196: old_cur = cur;
197: }
198: }
199: }
200: }
201: } else
202: break;
203: if (!removed) break;
204: hits++;
205: }
2.22 frystyk 206: if (CACHE_TRACE)
207: HTTrace("Cache....... Size reduced from %ld to %ld\n",
208: old_size, HTTotalSize);
2.23 frystyk 209: /*
210: ** Dump the new content to the index file
211: */
212: HTCacheIndex_write(HTCacheRoot);
213: new_entries = 0;
2.22 frystyk 214: return YES;
2.1 frystyk 215: }
2.22 frystyk 216: return NO;
2.1 frystyk 217: }
218:
2.22 frystyk 219: /* ------------------------------------------------------------------------- */
220: /* CACHE INDEX */
221: /* ------------------------------------------------------------------------- */
2.1 frystyk 222:
2.22 frystyk 223: PRIVATE char * cache_index_name (const char * cache_root)
2.1 frystyk 224: {
2.22 frystyk 225: if (cache_root) {
226: char * location = NULL;
227: if ((location = (char *)
228: HT_MALLOC(strlen(cache_root) + strlen(HT_CACHE_INDEX) + 1)) == NULL)
229: HT_OUTOFMEM("cache_index_name");
230: strcpy(location, cache_root);
231: strcat(location, HT_CACHE_INDEX);
232: return location;
2.1 frystyk 233: }
2.22 frystyk 234: return NULL;
2.1 frystyk 235: }
236:
237: /*
2.22 frystyk 238: ** Remove the cache index file
2.1 frystyk 239: */
2.22 frystyk 240: PUBLIC BOOL HTCacheIndex_delete (const char * cache_root)
2.1 frystyk 241: {
2.22 frystyk 242: if (cache_root) {
243: char * index = cache_index_name(cache_root);
244: REMOVE(index);
245: HT_FREE(index);
2.1 frystyk 246: return YES;
2.22 frystyk 247: }
248: return NO;
249: }
2.1 frystyk 250:
2.22 frystyk 251: /*
252: ** Walk through the list of cached objects and save them to disk.
253: ** We override any existing version but that is normally OK as we have
254: ** already read its contents.
255: */
256: PUBLIC BOOL HTCacheIndex_write (const char * cache_root)
257: {
258: if (cache_root && CacheTable) {
259: char * index = cache_index_name(cache_root);
260: FILE * fp = NULL;
261: if (CACHE_TRACE) HTTrace("Cache Index. Writing index `%s\'\n", index);
262:
263: /*
264: ** Open the file for writing. Note - we don't take a backup!
265: ** This should probably be fixed!
266: */
267: if (!index) return NO;
268: if ((fp = fopen(index, "wb")) == NULL) {
269: if (CACHE_TRACE)
270: HTTrace("Cache Index. Can't open `%s\' for writing\n", index);
271: HT_FREE(index);
272: return NO;
273: }
2.1 frystyk 274:
2.22 frystyk 275: /*
276: ** Walk through the list and write it out. The format is really
2.25 frystyk 277: ** simple as we keep it all in ASCII.
2.22 frystyk 278: */
279: {
280: HTList * cur;
281: int cnt;
282: for (cnt=0; cnt<HASH_SIZE; cnt++) {
283: if ((cur = CacheTable[cnt])) {
284: HTCache * pres;
2.24 frystyk 285: while ((pres = (HTCache *) HTList_nextObject(cur))) {
2.34 frystyk 286: if (fprintf(fp, "%s %s %s %ld %ld %ld %c %d %d %ld %ld %ld %c\r\n",
2.24 frystyk 287: pres->url,
288: pres->cachename,
2.34 frystyk 289: pres->etag ? pres->etag : HT_CACHE_ETAG,
290: pres->lm,
2.24 frystyk 291: pres->expires,
292: pres->size,
2.27 frystyk 293: pres->range+0x30,
2.24 frystyk 294: pres->hash,
295: pres->hits,
296: pres->freshness_lifetime,
297: pres->response_time,
2.22 frystyk 298: pres->corrected_initial_age,
299: pres->must_revalidate+0x30) < 0) {
300: if (CACHE_TRACE)
301: HTTrace("Cache Index. Error writing cache index\n");
302: return NO;
303: }
304: }
305: }
306: }
307: }
2.1 frystyk 308:
2.22 frystyk 309: /* Done writing */
310: fclose(fp);
311: HT_FREE(index);
312: }
2.1 frystyk 313: return NO;
314: }
315:
316: /*
2.22 frystyk 317: ** Load one line of index file
318: ** Returns YES if line OK, else NO
2.2 frystyk 319: */
2.22 frystyk 320: PRIVATE BOOL HTCacheIndex_parseLine (char * line)
2.2 frystyk 321: {
2.22 frystyk 322: HTCache * cache = NULL;
323: if (line) {
324: char validate;
2.27 frystyk 325: char range;
2.22 frystyk 326: if ((cache = (HTCache *) HT_CALLOC(1, sizeof(HTCache))) == NULL)
327: HT_OUTOFMEM("HTCacheIndex_parseLine");
328:
329: /*
330: ** Read the line and create the cache object
331: */
332: {
333: char * url = HTNextField(&line);
334: char * cachename = HTNextField(&line);
2.34 frystyk 335: char * etag = HTNextField(&line);
2.22 frystyk 336: StrAllocCopy(cache->url, url);
337: StrAllocCopy(cache->cachename, cachename);
2.34 frystyk 338: if (strcmp(etag, HT_CACHE_ETAG)) StrAllocCopy(cache->etag, etag);
2.22 frystyk 339: }
2.35 frystyk 340: #if SIZEOF_LONG==8
341: /*
342: ** On some 64 bit machines (alpha) time_t is of type int and not long.
343: ** This means that we have to adjust sscanf accordingly so that we
344: ** know what we are looking for. Otherwise er may get unalignment
345: ** problems.
346: */
347: if (sscanf(line, "%d %d %d %c %d %d %d %d %d %c",
348: #else
2.34 frystyk 349: if (sscanf(line, "%ld %ld %ld %c %d %d %ld %ld %ld %c",
2.35 frystyk 350: #endif
2.34 frystyk 351: &cache->lm,
2.24 frystyk 352: &cache->expires,
353: &cache->size,
2.28 frystyk 354: &range,
2.24 frystyk 355: &cache->hash,
356: &cache->hits,
2.22 frystyk 357: &cache->freshness_lifetime,
358: &cache->response_time,
359: &cache->corrected_initial_age,
360: &validate) < 0) {
361: if (CACHE_TRACE) HTTrace("Cache Index. Error reading cache index\n");
362: return NO;
363: }
2.27 frystyk 364: cache->range = range-0x30;
2.22 frystyk 365: cache->must_revalidate = validate-0x30;
366:
367: /*
2.26 frystyk 368: ** Create the new anchor and fill in the expire information we have read
369: ** in the index.
2.25 frystyk 370: */
371: if (cache) {
372: HTAnchor * anchor = HTAnchor_findAddress(cache->url);
373: HTParentAnchor * parent = HTAnchor_parent(anchor);
2.34 frystyk 374: HTAnchor_setExpires(parent, cache->expires);
375: HTAnchor_setLastModified(parent, cache->lm);
376: if (cache->etag) HTAnchor_setEtag(parent, cache->etag);
2.25 frystyk 377: }
378:
379: /*
2.22 frystyk 380: ** Create the cache table if not already existent and add the new
381: ** entry. Also check that the hash is still within bounds
382: */
383: if (!CacheTable) {
384: if ((CacheTable = (HTList **) HT_CALLOC(HASH_SIZE,
385: sizeof(HTList *))) == NULL)
386: HT_OUTOFMEM("HTCache_parseLine");
387: }
388: if (cache->hash >= 0 && cache->hash < HASH_SIZE) {
389: int hash = cache->hash;
390: if (!CacheTable[hash]) CacheTable[hash] = HTList_new();
391: HTList_addObject(CacheTable[hash], (void *) cache);
2.2 frystyk 392: }
2.22 frystyk 393:
394: /* Update the total cache size */
395: HTTotalSize += cache->size;
396:
397: return YES;
2.2 frystyk 398: }
2.22 frystyk 399: return NO;
2.2 frystyk 400: }
401:
402: /*
2.22 frystyk 403: ** Folding is either of CF LWS, LF LWS, CRLF LWS
2.2 frystyk 404: */
2.22 frystyk 405: PRIVATE int HTCacheIndex_put_block (HTStream * me, const char * b, int l)
2.2 frystyk 406: {
2.22 frystyk 407: while (l > 0) {
408: if (me->EOLstate == EOL_FCR) {
409: if (*b == LF) /* CRLF */
410: me->EOLstate = EOL_FLF;
411: else if (WHITE(*b)) /* Folding: CR SP */
412: me->EOLstate = EOL_DOT;
413: else { /* New line */
414: HTCacheIndex_parseLine(HTChunk_data(me->buffer));
415: me->EOLstate = EOL_BEGIN;
416: HTChunk_clear(me->buffer);
417: continue;
418: }
419: } else if (me->EOLstate == EOL_FLF) {
420: if (WHITE(*b)) /* Folding: LF SP or CR LF SP */
421: me->EOLstate = EOL_DOT;
422: else { /* New line */
423: HTCacheIndex_parseLine(HTChunk_data(me->buffer));
424: me->EOLstate = EOL_BEGIN;
425: HTChunk_clear(me->buffer);
426: continue;
427: }
428: } else if (me->EOLstate == EOL_DOT) {
429: if (WHITE(*b)) {
430: me->EOLstate = EOL_BEGIN;
431: HTChunk_putc(me->buffer, ' ');
432: } else {
433: HTCacheIndex_parseLine(HTChunk_data(me->buffer));
434: me->EOLstate = EOL_BEGIN;
435: HTChunk_clear(me->buffer);
436: continue;
437: }
438: } else if (*b == CR) {
439: me->EOLstate = EOL_FCR;
440: } else if (*b == LF) {
441: me->EOLstate = EOL_FLF; /* Line found */
442: } else
443: HTChunk_putc(me->buffer, *b);
444: l--; b++;
2.2 frystyk 445: }
2.22 frystyk 446: return HT_OK;
2.2 frystyk 447: }
448:
2.22 frystyk 449: PRIVATE int HTCacheIndex_put_character (HTStream * me, char c)
450: {
451: return HTCacheIndex_put_block(me, &c, 1);
452: }
2.2 frystyk 453:
2.22 frystyk 454: PRIVATE int HTCacheIndex_put_string (HTStream * me, const char * s)
2.1 frystyk 455: {
2.22 frystyk 456: return HTCacheIndex_put_block(me, s, (int) strlen(s));
457: }
2.1 frystyk 458:
2.22 frystyk 459: PRIVATE int HTCacheIndex_flush (HTStream * me)
460: {
461: if (me) {
462: char * flush = HTChunk_data(me->buffer);
463: if (flush) HTCacheIndex_parseLine(flush);
464: HTChunk_clear(me->buffer);
465: }
466: return HT_OK;
467: }
2.1 frystyk 468:
2.22 frystyk 469: PRIVATE int HTCacheIndex_free (HTStream * me)
470: {
471: if (me) {
472: int status = HTCacheIndex_flush(me);
473: if (APP_TRACE) HTTrace("Cache Index. FREEING....\n");
474: HTChunk_delete(me->buffer);
475: HT_FREE(me);
476: return status;
477: }
478: return HT_ERROR;
479: }
2.1 frystyk 480:
2.22 frystyk 481: PRIVATE int HTCacheIndex_abort (HTStream * me, HTList * e)
482: {
483: if (me) {
484: int status = HT_ERROR;
485: if (APP_TRACE) HTTrace("Cache Index. ABORTING...\n");
486: HTChunk_delete(me->buffer);
487: HT_FREE(me);
488: return status;
2.1 frystyk 489: }
2.22 frystyk 490: return HT_ERROR;
491: }
2.1 frystyk 492:
2.22 frystyk 493: /* Structured Object Class
494: ** -----------------------
495: */
496: PRIVATE const HTStreamClass HTCacheIndexClass =
497: {
498: "CacheIndexParser",
499: HTCacheIndex_flush,
500: HTCacheIndex_free,
501: HTCacheIndex_abort,
502: HTCacheIndex_put_character,
503: HTCacheIndex_put_string,
504: HTCacheIndex_put_block
505: };
2.1 frystyk 506:
2.22 frystyk 507: PRIVATE HTStream * HTCacheIndexReader (HTRequest * request)
508: {
509: HTStream * me;
510: if ((me = (HTStream *) HT_CALLOC(1, sizeof(HTStream))) == NULL)
511: HT_OUTOFMEM("HTCacheIndexs");
512: me->isa = &HTCacheIndexClass;
513: me->request = request;
514: me->buffer = HTChunk_new(512);
515: me->EOLstate = EOL_BEGIN;
516: return me;
517: }
518:
519: /*
520: ** Read the saved set of cached entries from disk. we only allow the index
521: ** ro be read when there is no entries in memory. That way we can ensure
522: ** consistancy.
523: */
524: PUBLIC BOOL HTCacheIndex_read (const char * cache_root)
525: {
526: BOOL status = NO;
527: if (cache_root && CacheTable == NULL) {
2.33 eric 528: BOOL wasInteractive;
2.22 frystyk 529: char * file = cache_index_name(cache_root);
530: char * index = HTParse(file, "cache:", PARSE_ALL);
2.34 frystyk 531: HTAnchor * anchor = HTAnchor_findAddress(index);
2.22 frystyk 532: HTRequest * request = HTRequest_new();
533: HTRequest_setPreemptive(request, YES);
534: HTRequest_setOutputFormat(request, WWW_SOURCE);
535: HTRequest_setOutputStream(request, HTCacheIndexReader(request));
2.23 frystyk 536: HTRequest_setAnchor(request, anchor);
2.34 frystyk 537: HTAnchor_setFormat((HTParentAnchor *) anchor, HTAtom_for("www/cache-index"));
2.33 eric 538: wasInteractive = HTAlert_interactive();
2.23 frystyk 539: HTAlert_setInteractive(NO);
540: status = HTLoad(request, NO);
2.33 eric 541: HTAlert_setInteractive(wasInteractive);
2.22 frystyk 542: HTRequest_delete(request);
543: HT_FREE(file);
544: HT_FREE(index);
2.1 frystyk 545: }
2.22 frystyk 546: return status;
2.1 frystyk 547: }
548:
2.22 frystyk 549: /* ------------------------------------------------------------------------- */
550: /* CACHE PARAMETERS */
551: /* ------------------------------------------------------------------------- */
2.1 frystyk 552:
2.22 frystyk 553: PRIVATE BOOL create_cache_root (const char * cache_root)
2.1 frystyk 554: {
555: struct stat stat_info;
2.22 frystyk 556: char * loc = NULL;
2.1 frystyk 557: char * cur = NULL;
558: BOOL create = NO;
2.22 frystyk 559: if (!cache_root) return NO;
560: StrAllocCopy(loc, cache_root); /* Get our own copy */
561: cur = loc+1;
2.1 frystyk 562: while ((cur = strchr(cur, '/'))) {
2.22 frystyk 563: *cur = '\0';
564: if (create || HT_STAT(loc, &stat_info) == -1) {
565: create = YES; /* To avoid doing stat()s in vain */
566: if (CACHE_TRACE) HTTrace("Cache....... Creating dir `%s\'\n", loc);
567: if (MKDIR(loc, 0777) < 0) {
568: if (CACHE_TRACE) HTTrace("Cache....... can't create\n");
569: HT_FREE(loc);
2.1 frystyk 570: return NO;
571: }
572: } else {
2.22 frystyk 573: if (CACHE_TRACE)
574: HTTrace("Cache....... dir `%s\' already exists\n", loc);
2.1 frystyk 575: }
2.22 frystyk 576: *cur++ = '/';
2.1 frystyk 577: }
2.22 frystyk 578: HT_FREE(loc);
2.1 frystyk 579: return YES;
580: }
581:
2.22 frystyk 582: /*
583: ** If `cache_root' is NULL then the current value (might be a define)
584: ** Should we check if the cache_root is actually OK? I think not!
2.1 frystyk 585: */
2.22 frystyk 586: PRIVATE BOOL HTCacheMode_setRoot (const char * cache_root)
2.1 frystyk 587: {
2.22 frystyk 588: StrAllocCopy(HTCacheRoot, cache_root ? cache_root : HT_CACHE_ROOT);
589: if (*(HTCacheRoot+strlen(HTCacheRoot)-1) != '/')
590: StrAllocCat(HTCacheRoot, "/");
591: if (create_cache_root(HTCacheRoot) == NO) return NO;
592: if (CACHE_TRACE) HTTrace("Cache Root.. Root set to `%s\'\n", HTCacheRoot);
593: return YES;
2.1 frystyk 594: }
595:
596: /*
2.22 frystyk 597: ** Return the value of the cache root. The cache root can only be
598: ** set through the HTCacheInit() function
599: */
600: PUBLIC const char * HTCacheMode_getRoot (void)
601: {
602: return HTCacheRoot;
2.1 frystyk 603: }
604:
2.22 frystyk 605: /*
2.1 frystyk 606: ** If `cache_root' is NULL then reuse old value or use HT_CACHE_ROOT.
607: ** An empty string will make '/' as cache root
2.12 frystyk 608: ** We can only enable the cache if the HTSecure flag is not set. This
609: ** is for example the case if using an application as a telnet shell.
2.1 frystyk 610: */
2.22 frystyk 611: PUBLIC BOOL HTCacheInit (const char * cache_root, int size)
2.1 frystyk 612: {
2.22 frystyk 613: if (!HTLib_secure() && !HTCacheRoot) {
614:
615: /*
616: ** Find an appropriate root for the cache
617: */
618: if (HTCacheMode_setRoot(cache_root) != YES) return NO;
619:
620: /*
621: ** Set the max size of the cache
622: */
623: HTCacheMode_setMaxSize(size);
624:
625: /*
626: ** Look for the cache index and read the contents
627: */
628: HTCacheIndex_read(HTCacheRoot);
629:
630: /*
631: ** Do caching from now on
632: */
2.12 frystyk 633: HTCacheEnable = YES;
634: return YES;
635: }
636: return NO;
2.1 frystyk 637: }
638:
2.22 frystyk 639: /*
640: ** Turns off the cache and updates entries on disk.
2.1 frystyk 641: */
2.22 frystyk 642: PUBLIC BOOL HTCacheTerminate (void)
2.1 frystyk 643: {
2.22 frystyk 644: /*
645: ** Write the index to file
646: */
647: HTCacheIndex_write(HTCacheRoot);
648:
649: /*
650: ** Cleanup memory by deleting all HTCache objects
651: */
652: HTCache_deleteAll();
653:
654: /*
655: ** Don't do anymore caching from now on
656: */
657: HT_FREE(HTCacheRoot);
2.1 frystyk 658: HTCacheEnable = NO;
659: return YES;
660: }
661:
2.22 frystyk 662: /*
663: ** The cache can be temporarily suspended by using the enable/disable
664: ** flag. This does not prevent the cache from being enabled/disable at
665: ** a later point in time.
2.1 frystyk 666: */
2.22 frystyk 667: PUBLIC void HTCacheMode_setEnabled (BOOL mode)
668: {
669: HTCacheEnable = mode;
670: }
671:
672: PUBLIC BOOL HTCacheMode_enabled (void)
2.1 frystyk 673: {
2.12 frystyk 674: return HTCacheEnable;
2.1 frystyk 675: }
676:
2.22 frystyk 677: /*
678: ** We can set the cache to operate in disconnected mode in which we only
679: ** return (valid) responses from the cache. Disconnected mode does not
680: ** automatically deliver stale documents as this must be declared
681: ** explicitly.
2.1 frystyk 682: */
2.22 frystyk 683: PUBLIC void HTCacheMode_setDisconnected (HTDisconnectedMode mode)
2.1 frystyk 684: {
2.22 frystyk 685: DisconnectedMode = mode;
2.1 frystyk 686: }
687:
2.22 frystyk 688: PUBLIC HTDisconnectedMode HTCacheMode_disconnected (void)
2.1 frystyk 689: {
2.22 frystyk 690: return DisconnectedMode;
2.1 frystyk 691: }
692:
2.22 frystyk 693: PUBLIC BOOL HTCacheMode_isDisconnected (HTReload mode)
2.1 frystyk 694: {
2.22 frystyk 695: return (DisconnectedMode != HT_DISCONNECT_NONE);
2.1 frystyk 696: }
697:
2.7 frystyk 698: /*
699: ** Set the mode for how we handle Expires header from the local history
700: ** list. The following modes are available:
701: **
702: ** HT_EXPIRES_IGNORE : No update in the history list
703: ** HT_EXPIRES_NOTIFY : The user is notified but no reload
704: ** HT_EXPIRES_AUTO : Automatic reload
705: */
2.22 frystyk 706: PUBLIC void HTCacheMode_setExpires (HTExpiresMode mode)
2.7 frystyk 707: {
708: HTExpMode = mode;
709: }
710:
2.22 frystyk 711: PUBLIC HTExpiresMode HTCacheMode_expires (void)
2.7 frystyk 712: {
713: return HTExpMode;
714: }
715:
2.22 frystyk 716: /*
717: ** Cache size management. We set the default cache size to 20M.
718: ** We set the minimum size to 5M in order not to get into weird
719: ** problems while writing the cache. The size is indicated in Mega
720: ** bytes
721: */
722: PUBLIC BOOL HTCacheMode_setMaxSize (int size)
723: {
724: long new_size = size < 5 ? MIN_CACHE_SIZE : size * MEGA;
725: if (new_size < HTTotalSize) HTCacheGarbage();
726: HTCacheSize = new_size - SIZE_BUFFER;
727: if (CACHE_TRACE) HTTrace("Cache...... Total cache size: %ld\n", new_size);
728: return YES;
729: }
730:
731: PUBLIC int HTCacheMode_maxSize (void)
732: {
733: return HTCacheSize / MEGA;
734: }
735:
2.7 frystyk 736: /* ------------------------------------------------------------------------- */
2.22 frystyk 737: /* CACHE OBJECT */
2.2 frystyk 738: /* ------------------------------------------------------------------------- */
739:
2.22 frystyk 740: PRIVATE BOOL free_object (HTCache * me)
741: {
742: HT_FREE(me->url);
743: HT_FREE(me->cachename);
2.34 frystyk 744: HT_FREE(me->etag);
2.22 frystyk 745: HT_FREE(me);
746: return YES;
747: }
748:
749: PRIVATE BOOL delete_object (HTList * list, HTCache * me)
750: {
751: if (CACHE_TRACE) HTTrace("Cache....... delete %p from list %p\n",me, list);
752: HTList_removeObject(list, (void *) me);
753: HTTotalSize -= me->size;
754: free_object(me);
755: return YES;
756: }
757:
758: /*
759: ** Create directory path for cache file
760: **
761: ** On exit:
762: ** return YES
763: ** if directories created -- after that caller
764: ** can rely on fopen(cfn,"w") succeeding.
765: **
766: */
767: PRIVATE BOOL HTCache_createLocation (HTCache * me)
768: {
769: if (me && HTCacheRoot) {
770: BOOL status = YES;
771: char * path = NULL;
772: struct stat stat_info;
773: if ((path = (char *) HT_MALLOC(strlen(HTCacheRoot) + 10)) == NULL)
774: HT_OUTOFMEM("HTCache_createLocation");
775: sprintf(path, "%s%d", HTCacheRoot, me->hash);
776: if (HT_STAT(path, &stat_info) == -1) {
777: if (CACHE_TRACE) HTTrace("Cache....... Create dir `%s\'\n", path);
778: if (MKDIR(path, 0777) < 0) {
779: if (CACHE_TRACE) HTTrace("Cache....... Can't create...\n");
780: status = NO;
781: }
782: } else {
783: if (CACHE_TRACE)
784: HTTrace("Cache....... Directory `%s\' already exists\n", path);
785: }
786: HT_FREE(path);
787: return status;
788: }
789: return NO;
790: }
791:
792: /*
793: ** Find a cache filename for this cache object.
794: */
795: PRIVATE BOOL HTCache_findName (HTCache * me)
796: {
797: if (me) {
798: /*
799: ** Create path for this cache entry. We base the cache location on the
800: ** hash calculated as a function of the URL. That way, we ensure a
801: ** resonably uniform distribution.
802: */
803: me->cachename = HTGetTmpFileName(NULL);
804: return HTCache_createLocation(me);
805: }
806: return NO;
807: }
808:
809: /*
2.24 frystyk 810: ** Calculate the corrected_initial_age of the object. We use the time
811: ** when this function is called as the response_time as this is when
812: ** we have received the complete response. This may cause a delay if
2.26 frystyk 813: ** the reponse header is very big but should not cause any incorrect
2.24 frystyk 814: ** behavior.
815: */
2.26 frystyk 816: PRIVATE BOOL calculate_time (HTCache * me, HTRequest * request,
817: HTResponse * response)
2.24 frystyk 818: {
819: if (me && request) {
820: HTParentAnchor * anchor = HTRequest_anchor(request);
2.26 frystyk 821: time_t date = HTAnchor_date(anchor);
2.24 frystyk 822: me->response_time = time(NULL);
823: me->expires = HTAnchor_expires(anchor);
824: {
2.26 frystyk 825: time_t apparent_age = HTMAX(0, me->response_time - date);
2.24 frystyk 826: time_t corrected_received_age = HTMAX(apparent_age, HTAnchor_age(anchor));
827: time_t response_delay = me->response_time - HTRequest_date(request);
828: me->corrected_initial_age = corrected_received_age + response_delay;
829: }
830:
831: /*
832: ** Estimate an expires time using the max-age and expires time. If we
833: ** don't have an explicit expires time then set it to 10% of the LM
2.31 frystyk 834: ** date (although max 24 h). If no LM date is available then use 24 hours.
2.24 frystyk 835: */
836: {
2.26 frystyk 837: time_t freshness_lifetime = HTResponse_maxAge(response);
2.24 frystyk 838: if (freshness_lifetime < 0) {
839: if (me->expires < 0) {
840: time_t lm = HTAnchor_lastModified(anchor);
2.31 frystyk 841: if (lm < 0) {
842: freshness_lifetime = DefaultExpiration;
843: } else {
844: freshness_lifetime = LM_EXPIRATION(date - lm);
845: if (freshness_lifetime > WARN_HEURISTICS)
846: HTRequest_addError(request, ERR_WARN, NO,
847: HTERR_HEURISTIC_EXPIRATION, NULL, 0,
848: "calculate_time");
849: }
2.24 frystyk 850: } else
2.26 frystyk 851: freshness_lifetime = me->expires - date;
2.24 frystyk 852: }
853: me->freshness_lifetime = HTMAX(0, freshness_lifetime);
854: }
855: if (CACHE_TRACE) {
856: HTTrace("Cache....... Received Age %d, corrected %d, freshness lifetime %d\n",
857: HTAnchor_age(anchor),
858: me->corrected_initial_age,
859: me->freshness_lifetime);
860: }
861: return YES;
862: }
863: return NO;
864: }
865:
866: /*
2.22 frystyk 867: ** Create a new cache entry and add it to the list
868: */
2.26 frystyk 869: PRIVATE HTCache * HTCache_new (HTRequest * request, HTResponse * response,
870: HTParentAnchor * anchor)
2.22 frystyk 871: {
872: HTList * list = NULL; /* Current list in cache */
873: HTCache * pres = NULL;
874: int hash = 0;
875: char * url = NULL;
2.26 frystyk 876: if (!request || !response || !anchor) {
2.22 frystyk 877: if (CORE_TRACE) HTTrace("Cache....... Bad argument\n");
878: return NULL;
879: }
880:
881: /* Find a hash for this anchor */
882: if ((url = HTAnchor_address((HTAnchor *) anchor))) {
883: char * ptr;
884: for (ptr=url; *ptr; ptr++)
885: hash = (int) ((hash * 3 + (*(unsigned char *) ptr)) % HASH_SIZE);
886: if (!CacheTable) {
887: if ((CacheTable = (HTList **) HT_CALLOC(HASH_SIZE,
888: sizeof(HTList *))) == NULL)
889: HT_OUTOFMEM("HTCache_new");
890: }
891: if (!CacheTable[hash]) CacheTable[hash] = HTList_new();
892: list = CacheTable[hash];
893: } else
894: return NULL;
895:
896: /* Search the cache */
897: {
898: HTList * cur = list;
899: while ((pres = (HTCache *) HTList_nextObject(cur))) {
900: if (!strcmp(pres->url, url)) break;
901: }
902: }
903:
904: /* If not found then create new cache object, else use existing one */
905: if (!pres) {
906: if ((pres = (HTCache *) HT_CALLOC(1, sizeof(HTCache))) == NULL)
907: HT_OUTOFMEM("HTCache_new");
908: pres->hash = hash;
909: pres->url = url;
2.27 frystyk 910: pres->range = NO;
2.22 frystyk 911: HTCache_findName(pres);
912: HTList_addObject(list, (void *) pres);
913: new_entries++;
2.24 frystyk 914: } else
915: HT_FREE(url);
2.22 frystyk 916:
917: if (HTCache_hasLock(pres)) {
2.39 frystyk 918: if (HTCache_breakLock(pres, request) == NO) {
919: if (CACHE_TRACE) HTTrace("Cache....... Entry %p already in use\n");
920: return pres;
921: }
2.22 frystyk 922: }
2.39 frystyk 923: HTCache_getLock(pres, request);
2.22 frystyk 924:
2.39 frystyk 925:
2.24 frystyk 926: /* Calculate the various times */
2.26 frystyk 927: calculate_time(pres, request, response);
2.24 frystyk 928:
2.34 frystyk 929: /* Get the last-modified and etag values if any */
930: {
931: char * etag = HTAnchor_etag(anchor);
932: if (etag) StrAllocCopy(pres->etag, etag);
933: pres->lm = HTAnchor_lastModified(anchor);
934: }
935:
2.26 frystyk 936: /* Must we revalidate this every time? */
937: pres->must_revalidate = HTResponse_mustRevalidate(response);
2.22 frystyk 938: return pres;
939: }
940:
941: /*
942: ** Set the size of a cached object. We don't consider the metainformation as
943: ** part of the size which is the the reason for why the min cache size should
944: ** not be less than 5M. When we set the cache size we also check whether we
945: ** should run the gc or not.
946: */
2.27 frystyk 947: PRIVATE BOOL HTCache_setSize (HTCache * cache, long written, BOOL append)
2.22 frystyk 948: {
949: if (cache) {
2.26 frystyk 950: /*
951: ** First look to see if we already have registered this cache entry
952: ** with a certain size. This size may be a subpart of the total entity
953: ** (in case the download was interrupted)
954: */
2.27 frystyk 955: if (cache->size > 0 && !append) HTTotalSize -= cache->size;
956: cache->size = written;
957: HTTotalSize += written;
2.26 frystyk 958:
959: /*
960: ** Now add the new size to the total cache size. If the new size is
961: ** bigger than the legal cache size then start the gc.
962: */
2.22 frystyk 963: if (CACHE_TRACE) HTTrace("Cache....... Total size %ld\n", HTTotalSize);
964: if (HTTotalSize > HTCacheSize) HTCacheGarbage();
965: return YES;
966: }
967: return NO;
968: }
969:
2.2 frystyk 970: /*
971: ** Verifies if a cache object exists for this URL and if so returns a URL
972: ** for the cached object. It does not verify whether the object is valid or
973: ** not, for example it might have expired.
974: **
2.17 frystyk 975: ** Returns: file name If OK (must be freed by caller)
2.2 frystyk 976: ** NULL If no cache object found
977: */
2.22 frystyk 978: PUBLIC HTCache * HTCache_find (HTParentAnchor * anchor)
979: {
980: HTList * list = NULL;
981: HTCache * pres = NULL;
982:
983: /* Find a hash entry for this URL */
984: if (HTCacheMode_enabled() && anchor && CacheTable) {
985: char * url = HTAnchor_address((HTAnchor *) anchor);
986: int hash = 0;
987: char * ptr = url;
988: for (; *ptr; ptr++)
989: hash = (int) ((hash * 3 + (*(unsigned char *) ptr)) % HASH_SIZE);
990: if (!CacheTable[hash]) {
991: HT_FREE(url);
992: return NULL;
993: }
994: list = CacheTable[hash];
995:
996: /* Search the cache */
997: {
998: HTList * cur = list;
999: while ((pres = (HTCache *) HTList_nextObject(cur))) {
1000: if (!strcmp(pres->url, url)) {
1001: if (CACHE_TRACE) HTTrace("Cache....... Found %p hits %d\n",
1002: pres, pres->hits);
1003: break;
1004: }
1005: }
1006: }
1007: HT_FREE(url);
1008: }
1009: return pres;
1010: }
1011:
1012: /* HTCache_delete
1013: ** --------------
1014: ** Deletes a cache entry
1015: */
1016: PRIVATE BOOL HTCache_delete (HTCache * cache)
2.2 frystyk 1017: {
2.22 frystyk 1018: if (cache && CacheTable) {
1019: HTList * cur = CacheTable[cache->hash];
1020: return cur && delete_object(cur, cache);
1021: }
1022: return NO;
1023: }
1024:
1025: /* HTCache_deleteAll
1026: ** -----------------
1027: ** Destroys all cache entried in memory but does not write anything to
1028: ** disk
1029: */
1030: PUBLIC BOOL HTCache_deleteAll (void)
1031: {
1032: if (CacheTable) {
1033: HTList * cur;
1034: int cnt;
1035:
1036: /* Delete the rest */
1037: for (cnt=0; cnt<HASH_SIZE; cnt++) {
1038: if ((cur = CacheTable[cnt])) {
1039: HTCache * pres;
1040: while ((pres = (HTCache *) HTList_nextObject(cur)) != NULL)
1041: free_object(pres);
1042: }
1043: HTList_delete(CacheTable[cnt]);
2.2 frystyk 1044: }
2.22 frystyk 1045: HT_FREE(CacheTable);
1046: HTTotalSize = 0L;
1047: return YES;
2.2 frystyk 1048: }
2.22 frystyk 1049: return NO;
2.2 frystyk 1050: }
1051:
1052: /*
2.25 frystyk 1053: ** Is we have a valid entry in the cache then we also need a location
1054: ** where we can get it. Hopefully, we may be able to access it
1055: ** thourgh one of our protocol modules, for example the local file
1056: ** module. The name returned is in URL syntax and must be freed by
1057: ** the caller
1058: */
1059: PRIVATE char * HTCache_location (HTCache * cache, BOOL meta)
1060: {
1061: char * local = NULL;
1062: if (cache && HTCacheRoot) {
1063: if (meta) {
1064: if ((local = (char *) HT_MALLOC(strlen(HTCacheRoot) + 10 +
1065: strlen(HT_CACHE_META) +
1066: strlen(cache->cachename))) == NULL)
1067: HT_OUTOFMEM("HTCache_location");
1068: sprintf(local, "%s%d/%s%s", HTCacheRoot, cache->hash, cache->cachename,
1069: HT_CACHE_META);
1070: } else {
1071: if ((local = (char *) HT_MALLOC(strlen(HTCacheRoot) + 10 +
1072: strlen(cache->cachename))) == NULL)
1073: HT_OUTOFMEM("HTCache_location");
1074: sprintf(local, "%s%d/%s", HTCacheRoot, cache->hash, cache->cachename);
1075: }
1076: }
1077: return local;
1078: }
1079:
1080: /*
2.34 frystyk 1081: ** Walk through the set of headers and write those out that we are allowed
1082: ** to store in the cache. We look into the connection header to see what the
1083: ** hop-by-hop headers are and also into the cache-control directive to see what
1084: ** headers should not be cached.
1085: */
1086: PRIVATE BOOL meta_write (FILE * fp, HTRequest * request, HTResponse * response)
1087: {
1088: if (fp && request && response) {
1089: HTAssocList * headers = HTResponse_header(response);
1090: HTAssocList * connection = HTResponse_connection(response);
1091: char * nocache = HTResponse_noCache(response);
1092:
1093: /*
1094: ** If we don't have any headers then just return now.
1095: */
1096: if (!headers) return NO;
1097:
1098: /*
1099: ** Check whether either the connection header or the cache control
1100: ** header includes header names that we should not cache
1101: */
1102: if (connection || nocache) {
1103:
1104: /*
1105: ** Walk though the cache control no-cache directive
1106: */
1107: if (nocache) {
1108: char * line = NULL;
1109: char * ptr = NULL;
1110: char * field = NULL;
1111: StrAllocCopy(line, nocache); /* Get our own copy */
1112: ptr = line;
1113: while ((field = HTNextField(&ptr)) != NULL)
1114: HTAssocList_removeObject(headers, field);
1115: HT_FREE(line);
1116: }
1117:
1118: /*
1119: ** Walk though the connection header
1120: */
1121: if (connection) {
1122: HTAssoc * pres;
1123: while ((pres=(HTAssoc *) HTAssocList_nextObject(connection))) {
1124: char * field = HTAssoc_name(pres);
1125: HTAssocList_removeObject(headers, field);
1126: }
1127: }
1128: }
1129:
1130: /*
1131: ** Write out the remaining list of headers that we not already store
1132: ** in the index file.
1133: */
1134: {
1135: HTAssocList * cur = headers;
1136: HTAssoc * pres;
1137: while ((pres = (HTAssoc *) HTAssocList_nextObject(cur))) {
1138: if (fprintf(fp, "%s: %s\n", HTAssoc_name(pres), HTAssoc_value(pres))<0) {
1139: if (CACHE_TRACE) HTTrace("Cache....... Error writing metainfo\n");
1140: return NO;
1141: }
1142: }
1143: }
1144:
1145: /*
1146: ** Terminate the header with a newline
1147: */
1148: if (fprintf(fp, "\n") < 0) {
1149: if (CACHE_TRACE) HTTrace("Cache....... Error writing metainfo\n");
1150: return NO;
1151: }
1152: return YES;
1153: }
1154: return NO;
1155: }
1156:
1157: /*
1158: ** Save the metainformation for the data object. If no headers
1159: ** are available then the meta file is empty
1160: */
1161: PUBLIC BOOL HTCache_writeMeta (HTCache * cache, HTRequest * request,
1162: HTResponse * response)
1163: {
1164: if (cache && request && response) {
1165: BOOL status;
1166: FILE * fp;
1167: char * name = HTCache_location(cache, YES);
1168: if (!name) return NO;
1169: if ((fp = fopen(name, "wb")) == NULL) {
1170: if (CACHE_TRACE)
1171: HTTrace("Cache....... Can't open `%s\' for writing\n", name);
1172: HTCache_remove(cache);
1173: HT_FREE(name);
1174: return NO;
1175: }
1176: status = meta_write(fp, request, response);
1177: fclose(fp);
1178: HT_FREE(name);
1179: return status;
1180: }
1181: return NO;
1182: }
1183:
1184: PRIVATE BOOL meta_read (FILE * fp, HTRequest * request, HTStream * target)
1185: {
1186: if (fp && request && target) {
1187: int status;
1188: char buffer[512];
1189: while (1) {
1190: if ((status = fread(buffer, 1, 512, fp)) <= 0) {
1191: if (PROT_TRACE) HTTrace("Cache....... Meta information loaded\n");
1192: return YES;
1193: }
1194:
1195: /* Send the data down the pipe */
1196: status = (*target->isa->put_block)(target, buffer, status);
1197: if (status == HT_LOADED) {
1198: (*target->isa->flush)(target);
1199: return YES;
1200: }
1201: if (status < 0) {
1202: if (PROT_TRACE) HTTrace("Cache....... Target ERROR %d\n", status);
1203: break;
1204: }
1205: }
1206: }
1207: return NO;
1208: }
1209:
1210: /*
1211: ** Read the metainformation for the data object. If no headers are
1212: ** available then the meta file is empty
1213: */
1214: PRIVATE BOOL HTCache_readMeta (HTCache * cache, HTRequest * request)
1215: {
1216: HTParentAnchor * anchor = HTRequest_anchor(request);
1217: if (cache && request && anchor) {
1218: BOOL status;
1219: FILE * fp;
1220: char * name = HTCache_location(cache, YES);
1221: if (!name) return NO;
1222: if (CACHE_TRACE) HTTrace("Cache....... Looking for `%s\'\n", name);
1223: if ((fp = fopen(name, "rb")) == NULL) {
1224: if (CACHE_TRACE)
1225: HTTrace("Cache....... Can't open `%s\' for reading\n", name);
1226: HTCache_remove(cache);
1227: HT_FREE(name);
1228: } else {
1229: HTStream * target = HTStreamStack(WWW_MIME_HEAD, WWW_DEBUG,
1230: HTBlackHole(), request, NO);
1231: status = meta_read(fp, request, target);
1232: (*target->isa->_free)(target);
1233: fclose(fp);
1234: HT_FREE(name);
1235: return status;
1236: }
1237: }
1238: return NO;
1239: }
1240:
1241: /*
1242: ** Merge metainformation with existing version. This means that we have had a
1243: ** successful validation and hence a true cache hit. We only regard the
1244: ** following headers: Date, content-location, expires, cache-control, and vary.
1245: */
1246: PUBLIC BOOL HTCache_updateMeta (HTCache * cache, HTRequest * request,
1247: HTResponse * response)
1248: {
1249: if (cache && request && response) {
2.36 frystyk 1250: HTParentAnchor * anchor = HTRequest_anchor(request);
2.34 frystyk 1251: cache->hits++;
2.36 frystyk 1252:
1253: /* Calculate the various times */
1254: calculate_time(cache, request, response);
1255:
1256: /* Get the last-modified and etag values if any */
1257: {
1258: char * etag = HTAnchor_etag(anchor);
1259: if (etag) StrAllocCopy(cache->etag, etag);
1260: cache->lm = HTAnchor_lastModified(anchor);
1261: }
1262:
1263: /* Must we revalidate this every time? */
1264: cache->must_revalidate = HTResponse_mustRevalidate(response);
1265:
1266: return YES;
2.34 frystyk 1267: }
1268: return NO;
1269: }
1270:
1271: /*
2.25 frystyk 1272: ** Remove from disk. You must explicitly remove a lock
1273: ** before this operation can succeed
1274: */
1275: PRIVATE BOOL flush_object (HTCache * cache)
1276: {
1277: if (cache && !HTCache_hasLock(cache)) {
1278: char * head = HTCache_location(cache, YES);
1279: char * body = HTCache_location(cache, NO);
1280: REMOVE(head);
1281: REMOVE(body);
1282: HT_FREE(head);
1283: HT_FREE(body);
1284: return YES;
1285: }
1286: return NO;
1287: }
1288:
1289: /* HTCache_flushAll
1290: ** ----------------
1291: ** Destroys all cache entried in memory and on disk. Resets the cache
1292: ** to empty but the cache does not have to be reinitialized before we
1293: ** can use it again.
1294: */
1295: PUBLIC BOOL HTCache_flushAll (void)
1296: {
1297: if (CacheTable) {
1298: HTList * cur;
1299: int cnt;
1300:
1301: /* Delete the rest */
1302: for (cnt=0; cnt<HASH_SIZE; cnt++) {
1303: if ((cur = CacheTable[cnt])) {
1304: HTCache * pres;
1305: while ((pres = (HTCache *) HTList_nextObject(cur)) != NULL) {
1306: flush_object(pres);
1307: free_object(pres);
1308: }
1309: }
1310: HTList_delete(CacheTable[cnt]);
1311: CacheTable[cnt] = NULL;
1312: }
1313:
1314: /* Write the new empty index to disk */
1315: HTCacheIndex_write(HTCacheRoot);
1316: HTTotalSize = 0L;
1317: return YES;
1318: }
1319: return NO;
1320: }
1321:
1322: /*
2.2 frystyk 1323: ** This function checks whether a document has expired or not.
1324: ** The check is based on the metainformation passed in the anchor object
2.22 frystyk 1325: ** The function returns the level of validation needed for getting a fresh
1326: ** version. We also check the cache control directives in the request to
1327: ** see if they change the freshness discission.
1328: */
1329: PUBLIC HTReload HTCache_isFresh (HTCache * cache, HTRequest * request)
1330: {
1331: HTAssocList * cc = HTRequest_cacheControl(request);
1332: if (cache) {
1333: time_t max_age = -1;
1334: time_t max_stale = -1;
1335: time_t min_fresh = -1;
1336:
1337: /*
2.34 frystyk 1338: ** Make sure that we have the metainformation loaded from the
1339: ** persistent cache
1340: */
1341: HTParentAnchor * anchor = HTRequest_anchor(request);
1342: if (!HTAnchor_headerParsed(anchor)) HTCache_readMeta(cache, request);
1343:
1344: /*
2.26 frystyk 1345: ** If we only have a part of this request then make a range request
1346: ** using the If-Range condition GET request
1347: */
2.27 frystyk 1348: if (cache->range) {
2.26 frystyk 1349: char buf[20];
2.27 frystyk 1350: sprintf(buf, "%ld-", cache->size);
2.26 frystyk 1351: if (CACHE_TRACE) HTTrace("Cache....... Asking for range `%s\'\n", buf);
1352: HTRequest_addRange(request, "bytes", buf);
1353: HTRequest_addRqHd(request, HT_C_RANGE);
1354: return HT_CACHE_RANGE_VALIDATE;
1355: }
1356:
1357: /*
2.22 frystyk 1358: ** In case this entry is of type "must-revalidate" then we just
1359: ** go ahead and validate it.
1360: */
1361: if (cache->must_revalidate)
1362: return HT_CACHE_VALIDATE;
1363: /*
1364: ** Check whether we have any special constraints like min-fresh in
1365: ** the cache control
1366: */
1367: if (cc) {
1368: char * token = NULL;
1369: if ((token = HTAssocList_findObject(cc, "max-age")))
1370: max_age = atol(token);
1371: if ((token = HTAssocList_findObject(cc, "max-stale")))
1372: max_stale = atol(token);
1373: if ((token = HTAssocList_findObject(cc, "min-fresh")))
1374: min_fresh = atol(token);
1375: }
1376:
1377: /*
1378: ** Now do the checking against the age constraints that we've got
1379: */
1380: {
1381: time_t resident_time = time(NULL) - cache->response_time;
1382: time_t current_age = cache->corrected_initial_age + resident_time;
1383:
1384: /*
1385: ** Check that the max-age, max-stale, and min-fresh directives
1386: ** given in the request cache control header is followed.
1387: */
1388: if (max_age >= 0 && current_age > max_age) {
1389: if (CACHE_TRACE) HTTrace("Cache....... Max-age validation\n");
1390: return HT_CACHE_VALIDATE;
1391: }
1392: if (min_fresh >= 0 &&
1393: cache->freshness_lifetime < current_age + min_fresh) {
1394: if (CACHE_TRACE) HTTrace("Cache....... Min-fresh validation\n");
1395: return HT_CACHE_VALIDATE;
1396: }
1397:
1398: return (cache->freshness_lifetime +
1399: (max_stale >= 0 ? max_stale : 0) > current_age) ?
1400: HT_CACHE_OK : HT_CACHE_VALIDATE;
1401: }
1402: }
1403: return HT_CACHE_FLUSH;
1404: }
1405:
1406: /*
1407: ** While we are creating a new cache object or while we are validating an
1408: ** existing one, we must have a lock on the entry so that not other
1409: ** requests can get to it in the mean while.
1410: */
1411: PUBLIC BOOL HTCache_getLock (HTCache * cache, HTRequest * request)
1412: {
1413: if (cache && request) {
1414: if (CACHE_TRACE) HTTrace("Cache....... Locking cache entry %p\n", cache);
1415: cache->lock = request;
1416: return YES;
1417: }
1418: return NO;
1419: }
1420:
1421: PUBLIC BOOL HTCache_releaseLock (HTCache * cache)
1422: {
1423: if (cache) {
1424: if (CACHE_TRACE) HTTrace("Cache....... Unlocking cache entry %p\n", cache);
1425: cache->lock = NULL;
1426: return YES;
1427: }
1428: return NO;
1429: }
1430:
1431: PUBLIC BOOL HTCache_hasLock (HTCache * cache)
1432: {
1433: return cache && cache->lock;
1434: }
1435:
1436: PUBLIC BOOL HTCache_breakLock (HTCache * cache, HTRequest * request)
1437: {
1438: if (cache && cache->lock) {
1439: if (cache->lock == request) {
1440: if (CACHE_TRACE)
1441: HTTrace("Cache....... Breaking lock on entry %p\n", cache);
1442: cache->lock = NULL;
1443: return YES;
1444: }
1445: }
1446: return NO;
1447: }
1448:
1449: /*
1450: ** Is we have a valid entry in the cache then we also need a location
1451: ** where we can get it. Hopefully, we may be able to access it
1452: ** thourgh one of our protocol modules, for example the local file
1453: ** module. The name returned is in URL syntax and must be freed by
1454: ** the caller
1455: */
1456: PUBLIC char * HTCache_name (HTCache * cache)
1457: {
1458: if (cache && HTCacheRoot) {
1459: char * local = HTCache_location(cache, NO);
1460: char * url = HTParse(local, "cache:", PARSE_ALL);
1461: HT_FREE(local);
1462: return url;
1463: }
1464: return NULL;
1465: }
1466:
1467: /*
1468: ** Remove from memory AND from disk. You must explicitly remove a lock
1469: ** before this operation can succeed
2.2 frystyk 1470: */
2.22 frystyk 1471: PUBLIC BOOL HTCache_remove (HTCache * cache)
2.2 frystyk 1472: {
2.25 frystyk 1473: return flush_object(cache) && HTCache_delete(cache);
2.22 frystyk 1474: }
1475:
1476: PUBLIC BOOL HTCache_addHit (HTCache * cache)
1477: {
1478: if (cache) {
1479: cache->hits++;
1480: if (CACHE_TRACE) HTTrace("Cache....... Hits for %p is %d\n",
1481: cache, cache->hits);
1482: return YES;
1483: }
1484: return NO;
1485: }
1486:
2.34 frystyk 1487: /* ------------------------------------------------------------------------- */
1488: /* CACHE WRITER */
1489: /* ------------------------------------------------------------------------- */
2.1 frystyk 1490:
2.26 frystyk 1491: PRIVATE BOOL free_stream (HTStream * me, BOOL abort)
2.1 frystyk 1492: {
2.22 frystyk 1493: if (me) {
2.26 frystyk 1494: HTCache * cache = me->cache;
1495:
1496: /*
1497: ** We close the file object. This does not mean that we have the
1498: ** complete object. In case of an "abort" then we only have a part,
1499: ** however, next time we do a load we can use byte ranges to complete
1500: ** the request.
1501: */
2.22 frystyk 1502: if (me->fp) fclose(me->fp);
2.26 frystyk 1503:
2.22 frystyk 1504: /*
1505: ** We are done storing the object body and can update the cache entry.
1506: ** Also update the meta information entry on disk as well. When we
1507: ** are done we don't need the lock anymore.
1508: */
2.26 frystyk 1509: if (cache) {
1510: HTCache_writeMeta(cache, me->request, me->response);
1511: HTCache_releaseLock(cache);
2.22 frystyk 1512:
1513: /*
2.27 frystyk 1514: ** Remember if this is the full entity body or only a subpart
1515: ** We assume that an abort will only give a part of the object.
1516: */
1517: cache->range = abort;
1518:
1519: /*
2.26 frystyk 1520: ** Set the size and maybe do gc. If it is an abort then set the
1521: ** byte range so that we can start from this point next time. We
1522: ** take the byte range as the number of bytes that we have already
1523: ** written to the cache entry.
2.22 frystyk 1524: */
2.27 frystyk 1525: HTCache_setSize(cache, me->bytes_written, me->append);
2.22 frystyk 1526: }
1527:
1528: /*
1529: ** In order not to loose information, we dump the current cache index
1530: ** every time we have created DUMP_FREQUENCY new entries
1531: */
1532: if (new_entries > DUMP_FREQUENCY) {
1533: HTCacheIndex_write(HTCacheRoot);
1534: new_entries = 0;
1535: }
1536: HT_FREE(me);
2.26 frystyk 1537: return YES;
2.22 frystyk 1538: }
2.26 frystyk 1539: return NO;
1540: }
1541:
1542:
1543: PRIVATE int HTCache_free (HTStream * me)
1544: {
2.34 frystyk 1545: return free_stream(me, NO) ? HT_OK : HT_ERROR;
2.1 frystyk 1546: }
1547:
2.12 frystyk 1548: PRIVATE int HTCache_abort (HTStream * me, HTList * e)
2.1 frystyk 1549: {
2.22 frystyk 1550: if (CACHE_TRACE) HTTrace("Cache....... ABORTING\n");
2.34 frystyk 1551: free_stream(me, YES);
1552: return HT_ERROR;
2.1 frystyk 1553: }
1554:
2.34 frystyk 1555: PRIVATE int HTCache_flush (HTStream * me)
1556: {
1557: return (fflush(me->fp) == EOF) ? HT_ERROR : HT_OK;
1558: }
1559:
1560: PRIVATE int HTCache_putBlock (HTStream * me, const char * s, int l)
1561: {
1562: int status = (fwrite(s, 1, l, me->fp) != l) ? HT_ERROR : HT_OK;
1563: if (l > 1 && status == HT_OK) {
1564: HTCache_flush(me);
1565: me->bytes_written += l;
1566: }
2.37 frystyk 1567: return status;
2.34 frystyk 1568: }
1569:
1570: PRIVATE int HTCache_putChar (HTStream * me, char c)
1571: {
1572: return HTCache_putBlock(me, &c, 1);
1573: }
1574:
1575: PRIVATE int HTCache_putString (HTStream * me, const char * s)
1576: {
1577: return HTCache_putBlock(me, s, (int) strlen(s));
1578: }
1579:
2.15 frystyk 1580: PRIVATE const HTStreamClass HTCacheClass =
2.1 frystyk 1581: {
1582: "Cache",
1583: HTCache_flush,
2.17 frystyk 1584: HTCache_free,
2.1 frystyk 1585: HTCache_abort,
1586: HTCache_putChar,
1587: HTCache_putString,
1588: HTCache_putBlock
1589: };
1590:
2.26 frystyk 1591: PRIVATE HTStream * HTCacheStream (HTRequest * request, BOOL append)
2.1 frystyk 1592: {
2.22 frystyk 1593: HTCache * cache = NULL;
1594: FILE * fp = NULL;
2.36 frystyk 1595:
2.26 frystyk 1596: HTResponse * response = HTRequest_response(request);
2.19 frystyk 1597: HTParentAnchor * anchor = HTRequest_anchor(request);
2.12 frystyk 1598: if (!HTCacheEnable) {
2.14 eric 1599: if (CACHE_TRACE) HTTrace("Cache....... Not enabled\n");
2.22 frystyk 1600: return NULL;
2.1 frystyk 1601: }
1602:
2.22 frystyk 1603: /* Get a new cache entry */
2.26 frystyk 1604: if ((cache = HTCache_new(request, response, anchor)) == NULL) {
2.22 frystyk 1605: if (CACHE_TRACE) HTTrace("Cache....... Can't get a cache object\n");
1606: return NULL;
1607: }
1608:
1609: /* Test that the cached object is not locked */
1610: if (HTCache_hasLock(cache)) {
1611: if (HTCache_breakLock(cache, request) == NO) {
1612: if (CACHE_TRACE) HTTrace("Cache....... Entry already in use\n");
1613: return NULL;
1614: }
1615: }
1616: HTCache_getLock(cache, request);
1617:
1618: /*
1619: ** Test that we can actually write to the cache file. If the entry already
1620: ** existed then it will be overridden with the new data.
1621: */
1622: {
1623: char * name = HTCache_location(cache, NO);
2.26 frystyk 1624: if ((fp = fopen(name, append ? "ab" : "wb")) == NULL) {
2.22 frystyk 1625: if (CACHE_TRACE)
1626: HTTrace("Cache....... Can't open `%s\' for writing\n", name);
1627: HTCache_delete(cache);
1628: HT_FREE(name);
1629: return NULL;
2.26 frystyk 1630: } else {
1631: if (CACHE_TRACE)
1632: HTTrace("Cache....... %s file `%s\'\n",
1633: append ? "Append to" : "Creating", name);
1634: }
2.22 frystyk 1635: HT_FREE(name);
1636: }
2.1 frystyk 1637:
1638: /* Set up the stream */
2.22 frystyk 1639: {
1640: HTStream * me = NULL;
1641: if ((me = (HTStream *) HT_CALLOC(1, sizeof(HTStream))) == NULL)
1642: HT_OUTOFMEM("Cache");
1643: me->isa = &HTCacheClass;
1644: me->request = request;
2.26 frystyk 1645: me->response = response;
2.22 frystyk 1646: me->cache = cache;
2.38 frystyk 1647: me->fp = fp;
2.27 frystyk 1648: me->append = append;
2.22 frystyk 1649: return me;
1650: }
1651: return NULL;
1652: }
1653:
2.26 frystyk 1654: PUBLIC HTStream * HTCacheWriter (HTRequest * request,
1655: void * param,
1656: HTFormat input_format,
1657: HTFormat output_format,
1658: HTStream * output_stream)
1659: {
1660: return HTCacheStream(request, NO);
1661: }
1662:
1663: PUBLIC HTStream * HTCacheAppend (HTRequest * request,
1664: void * param,
1665: HTFormat input_format,
1666: HTFormat output_format,
1667: HTStream * output_stream)
1668: {
1669: return HTCacheStream(request, YES);
1670: }
1671:
2.22 frystyk 1672: /* ------------------------------------------------------------------------- */
1673: /* CACHE READER */
1674: /* ------------------------------------------------------------------------- */
1675:
1676: /*
1677: ** This function closes the connection and frees memory.
1678: ** Returns YES on OK, else NO
1679: */
2.32 eric 1680: PRIVATE int CacheCleanup (HTRequest * req, int status)
2.22 frystyk 1681: {
2.32 eric 1682: HTNet * net = HTRequest_net(req);
2.22 frystyk 1683: cache_info * cache = (cache_info *) HTNet_context(net);
2.32 eric 1684: HTStream * input = HTRequest_inputStream(req);
1685:
1686: /* Free stream with data TO Local cache system */
1687: if (input) {
1688: if (status == HT_INTERRUPTED)
1689: (*input->isa->abort)(input, NULL);
1690: else
1691: (*input->isa->_free)(input);
1692: HTRequest_setInputStream(req, NULL);
1693: }
1694:
2.34 frystyk 1695: if (status != HT_IGNORE) {
1696: HTNet_delete(net, status);
1697: if (cache) {
1698: HT_FREE(cache->local);
1699: HT_FREE(cache);
1700: }
2.22 frystyk 1701: }
1702: return YES;
1703: }
1704:
1705: /*
1706: ** This load function loads an object from the cache and puts it to the
1707: ** output defined by the request object. For the moment, this load function
1708: ** handles the persistent cache as if it was on local file but in fact
1709: ** it could be anywhere.
1710: **
1711: ** Returns HT_ERROR Error has occured in call back
1712: ** HT_OK Call back was OK
1713: */
2.29 frystyk 1714: PRIVATE int CacheEvent (SOCKET soc, void * pVoid, HTEventType type);
1715:
1716: PUBLIC int HTLoadCache (SOCKET soc, HTRequest * request)
2.22 frystyk 1717: {
2.29 frystyk 1718: cache_info * cache; /* Specific access information */
1719: HTParentAnchor * anchor = HTRequest_anchor(request);
2.22 frystyk 1720: HTNet * net = HTRequest_net(request);
1721:
1722: /*
1723: ** Initiate a new cache structure and bind to request structure
1724: ** This is actually state CACHE_BEGIN, but it can't be in the state
1725: ** machine as we need the structure first.
1726: */
2.29 frystyk 1727: if (PROT_TRACE) HTTrace("Load Cache.. Looking for `%s\'\n",
1728: HTAnchor_physical(anchor));
1729: if ((cache = (cache_info *) HT_CALLOC(1, sizeof(cache_info))) == NULL)
1730: HT_OUTOFMEM("HTLoadCACHE");
1731: cache->state = CL_BEGIN;
1732: cache->net = net;
1733: HTNet_setContext(net, cache);
1734: HTNet_setEventCallback(net, CacheEvent);
1735: HTNet_setEventParam(net, cache); /* callbacks get http* */
1736:
1737: return CacheEvent(soc, cache, HTEvent_BEGIN); /* get it started - ops is ignored */
1738: }
1739:
1740: PRIVATE int CacheEvent (SOCKET soc, void * pVoid, HTEventType type)
1741: {
1742: cache_info * cache = (cache_info *)pVoid;
1743: int status = HT_ERROR;
1744: HTNet * net = cache->net;
1745: HTRequest * request = HTNet_request(net);
1746: HTParentAnchor * anchor = HTRequest_anchor(request);
1747:
2.32 eric 1748: if (type == HTEvent_BEGIN) {
1749: cache->state = CL_BEGIN;
1750: } else if (type == HTEvent_CLOSE) {
2.22 frystyk 1751: HTRequest_addError(request, ERR_FATAL, NO, HTERR_INTERRUPTED,
1752: NULL, 0, "HTLoadCache");
1753: CacheCleanup(request, HT_INTERRUPTED);
1754: return HT_OK;
2.32 eric 1755: } else if (type == HTEvent_END) {
1756: CacheCleanup(request, HT_OK);
1757: return HT_OK;
1758: } else if (type == HTEvent_RESET) {
1759: CacheCleanup(request, HT_RECOVER_PIPE);
1760: cache->state = CL_BEGIN;
1761: return HT_OK;
1762: }
2.22 frystyk 1763:
1764: /* Now jump into the machine. We know the state from the previous run */
1765: while (1) {
1766: switch (cache->state) {
2.34 frystyk 1767:
2.22 frystyk 1768: case CL_BEGIN:
1769: if (HTLib_secure()) {
1770: if (PROT_TRACE)
1771: HTTrace("Load Cache.. No access to local file system\n");
1772: cache->state = CL_ERROR;
1773: break;
1774: }
2.40 eric 1775: cache->local = HTWWWToLocal(HTAnchor_physical(anchor), "",
1776: HTRequest_userProfile(request));
1777: if (!cache->local) {
1778: cache->state = CL_ERROR;
1779: break;
1780: }
1781: if ((net->host = HTHost_new(cache->local, 0)) == NULL)
2.32 eric 1782: return NO;
1783: if (HTHost_addNet(net->host, net) == HT_PENDING)
1784: if (PROT_TRACE) HTTrace("HTLoadCache. Pending...\n");
2.34 frystyk 1785: cache->state = CL_NEED_BODY;
2.22 frystyk 1786: break;
1787:
1788: case CL_NEED_BODY:
2.40 eric 1789: if (HT_STAT(cache->local, &cache->stat_info) == -1) {
1790: if (PROT_TRACE)
1791: HTTrace("Load Cache.. Not found `%s\'\n", cache->local);
1792: HTRequest_addError(request, ERR_FATAL, NO, HTERR_NOT_FOUND,
1793: NULL, 0, "HTLoadCache");
1794: cache->state = CL_ERROR;
1795: break;
1796: }
2.22 frystyk 1797:
2.40 eric 1798: /*
1799: ** The cache entry may be empty in which case we just return
1800: */
1801: if (!cache->stat_info.st_size) {
1802: HTRequest_addError(request, ERR_FATAL, NO,HTERR_NO_CONTENT,
1803: NULL, 0, "HTLoadCache");
1804: cache->state = CL_NO_DATA;
1805: } else
1806: cache->state = CL_NEED_OPEN_FILE;
2.22 frystyk 1807: break;
1808:
1809: case CL_NEED_OPEN_FILE:
1810: status = HTFileOpen(net, cache->local, HT_FT_RDONLY);
1811: if (status == HT_OK) {
1812: /*
1813: ** Create the stream pipe FROM the channel to the application.
1814: ** The target for the input stream pipe is set up using the
1815: ** stream stack.
1816: */
2.34 frystyk 1817: net->readStream = HTStreamStack(HTAnchor_format(anchor),
1818: HTRequest_outputFormat(request),
1819: HTRequest_outputStream(request),
1820: request, YES);
1821: HTRequest_setOutputConnected(request, YES);
2.32 eric 1822:
2.36 frystyk 1823: /* Set the return code as being OK */
2.34 frystyk 1824: HTRequest_addError(request, ERR_INFO, NO, HTERR_OK,
1825: NULL, 0, "HTLoadCache");
1826: cache->state = CL_NEED_CONTENT;
2.22 frystyk 1827:
1828: #ifndef NO_UNIX_IO
2.34 frystyk 1829: /* If we are _not_ using preemptive mode and we are Unix fd's
1830: ** then return here to get the same effect as when we are
1831: ** connecting to a socket. That way, HTCache acts just like any
1832: ** other protocol module even though we are in fact doing
1833: ** blocking connect
1834: */
1835: if (!net->preemptive) {
1836: if (PROT_TRACE) HTTrace("Load Cache.. returning\n");
1837: HTEvent_register(HTNet_socket(net), HTEvent_READ, &net->event);
1838: return HT_OK;
1839: }
2.22 frystyk 1840: #endif
1841: } else if (status == HT_WOULD_BLOCK || status == HT_PENDING)
1842: return HT_OK;
1843: else {
1844: HTRequest_addError(request, ERR_INFO, NO, HTERR_INTERNAL,
1845: NULL, 0, "HTLoadCache");
1846: cache->state = CL_ERROR; /* Error or interrupt */
1847: }
1848: break;
1849:
1850: case CL_NEED_CONTENT:
2.29 frystyk 1851: status = HTHost_read(net->host, net);
2.22 frystyk 1852: if (status == HT_WOULD_BLOCK)
1853: return HT_OK;
1854: else if (status == HT_LOADED || status == HT_CLOSED) {
1855: cache->state = CL_GOT_DATA;
1856: } else {
1857: HTRequest_addError(request, ERR_INFO, NO, HTERR_FORBIDDEN,
1858: NULL, 0, "HTLoadCache");
1859: cache->state = CL_ERROR;
1860: }
1861: break;
1862:
1863: case CL_GOT_DATA:
2.35 frystyk 1864: CacheCleanup(request, HT_NOT_MODIFIED);
2.34 frystyk 1865: return HT_OK;
2.22 frystyk 1866: break;
2.1 frystyk 1867:
2.22 frystyk 1868: case CL_NO_DATA:
1869: CacheCleanup(request, HT_NO_DATA);
1870: return HT_OK;
1871: break;
1872:
1873: case CL_ERROR:
1874: CacheCleanup(request, HT_ERROR);
1875: return HT_OK;
1876: break;
1877: }
1878: } /* End of while(1) */
2.1 frystyk 1879: }
Webmaster