Annotation of libwww/Library/src/HTCache.c, revision 2.35
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.35 ! frystyk 6: ** @(#) $Id: HTCache.c,v 2.34 1997/01/29 16:54:45 frystyk 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.22 frystyk 27: #define HT_CACHE_ROOT "/tmp/w3c-lib"
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
47: #define DUMP_FREQUENCY 10 /* Dump index after 10 loads */
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)) {
918: if (CACHE_TRACE) HTTrace("Cache....... Entry %p locked\n");
919: return pres;
920: }
921:
2.24 frystyk 922: /* Calculate the various times */
2.26 frystyk 923: calculate_time(pres, request, response);
2.24 frystyk 924:
2.34 frystyk 925: /* Get the last-modified and etag values if any */
926: {
927: char * etag = HTAnchor_etag(anchor);
928: if (etag) StrAllocCopy(pres->etag, etag);
929: pres->lm = HTAnchor_lastModified(anchor);
930: }
931:
2.26 frystyk 932: /* Must we revalidate this every time? */
933: pres->must_revalidate = HTResponse_mustRevalidate(response);
2.22 frystyk 934: return pres;
935: }
936:
937: /*
938: ** Set the size of a cached object. We don't consider the metainformation as
939: ** part of the size which is the the reason for why the min cache size should
940: ** not be less than 5M. When we set the cache size we also check whether we
941: ** should run the gc or not.
942: */
2.27 frystyk 943: PRIVATE BOOL HTCache_setSize (HTCache * cache, long written, BOOL append)
2.22 frystyk 944: {
945: if (cache) {
2.26 frystyk 946: /*
947: ** First look to see if we already have registered this cache entry
948: ** with a certain size. This size may be a subpart of the total entity
949: ** (in case the download was interrupted)
950: */
2.27 frystyk 951: if (cache->size > 0 && !append) HTTotalSize -= cache->size;
952: cache->size = written;
953: HTTotalSize += written;
2.26 frystyk 954:
955: /*
956: ** Now add the new size to the total cache size. If the new size is
957: ** bigger than the legal cache size then start the gc.
958: */
2.22 frystyk 959: if (CACHE_TRACE) HTTrace("Cache....... Total size %ld\n", HTTotalSize);
960: if (HTTotalSize > HTCacheSize) HTCacheGarbage();
961: return YES;
962: }
963: return NO;
964: }
965:
2.2 frystyk 966: /*
967: ** Verifies if a cache object exists for this URL and if so returns a URL
968: ** for the cached object. It does not verify whether the object is valid or
969: ** not, for example it might have expired.
970: **
2.17 frystyk 971: ** Returns: file name If OK (must be freed by caller)
2.2 frystyk 972: ** NULL If no cache object found
973: */
2.22 frystyk 974: PUBLIC HTCache * HTCache_find (HTParentAnchor * anchor)
975: {
976: HTList * list = NULL;
977: HTCache * pres = NULL;
978:
979: /* Find a hash entry for this URL */
980: if (HTCacheMode_enabled() && anchor && CacheTable) {
981: char * url = HTAnchor_address((HTAnchor *) anchor);
982: int hash = 0;
983: char * ptr = url;
984: for (; *ptr; ptr++)
985: hash = (int) ((hash * 3 + (*(unsigned char *) ptr)) % HASH_SIZE);
986: if (!CacheTable[hash]) {
987: HT_FREE(url);
988: return NULL;
989: }
990: list = CacheTable[hash];
991:
992: /* Search the cache */
993: {
994: HTList * cur = list;
995: while ((pres = (HTCache *) HTList_nextObject(cur))) {
996: if (!strcmp(pres->url, url)) {
997: if (CACHE_TRACE) HTTrace("Cache....... Found %p hits %d\n",
998: pres, pres->hits);
999: break;
1000: }
1001: }
1002: }
1003: HT_FREE(url);
1004: }
1005: return pres;
1006: }
1007:
1008: /* HTCache_delete
1009: ** --------------
1010: ** Deletes a cache entry
1011: */
1012: PRIVATE BOOL HTCache_delete (HTCache * cache)
2.2 frystyk 1013: {
2.22 frystyk 1014: if (cache && CacheTable) {
1015: HTList * cur = CacheTable[cache->hash];
1016: return cur && delete_object(cur, cache);
1017: }
1018: return NO;
1019: }
1020:
1021: /* HTCache_deleteAll
1022: ** -----------------
1023: ** Destroys all cache entried in memory but does not write anything to
1024: ** disk
1025: */
1026: PUBLIC BOOL HTCache_deleteAll (void)
1027: {
1028: if (CacheTable) {
1029: HTList * cur;
1030: int cnt;
1031:
1032: /* Delete the rest */
1033: for (cnt=0; cnt<HASH_SIZE; cnt++) {
1034: if ((cur = CacheTable[cnt])) {
1035: HTCache * pres;
1036: while ((pres = (HTCache *) HTList_nextObject(cur)) != NULL)
1037: free_object(pres);
1038: }
1039: HTList_delete(CacheTable[cnt]);
2.2 frystyk 1040: }
2.22 frystyk 1041: HT_FREE(CacheTable);
1042: HTTotalSize = 0L;
1043: return YES;
2.2 frystyk 1044: }
2.22 frystyk 1045: return NO;
2.2 frystyk 1046: }
1047:
1048: /*
2.25 frystyk 1049: ** Is we have a valid entry in the cache then we also need a location
1050: ** where we can get it. Hopefully, we may be able to access it
1051: ** thourgh one of our protocol modules, for example the local file
1052: ** module. The name returned is in URL syntax and must be freed by
1053: ** the caller
1054: */
1055: PRIVATE char * HTCache_location (HTCache * cache, BOOL meta)
1056: {
1057: char * local = NULL;
1058: if (cache && HTCacheRoot) {
1059: if (meta) {
1060: if ((local = (char *) HT_MALLOC(strlen(HTCacheRoot) + 10 +
1061: strlen(HT_CACHE_META) +
1062: strlen(cache->cachename))) == NULL)
1063: HT_OUTOFMEM("HTCache_location");
1064: sprintf(local, "%s%d/%s%s", HTCacheRoot, cache->hash, cache->cachename,
1065: HT_CACHE_META);
1066: } else {
1067: if ((local = (char *) HT_MALLOC(strlen(HTCacheRoot) + 10 +
1068: strlen(cache->cachename))) == NULL)
1069: HT_OUTOFMEM("HTCache_location");
1070: sprintf(local, "%s%d/%s", HTCacheRoot, cache->hash, cache->cachename);
1071: }
1072: }
1073: return local;
1074: }
1075:
1076: /*
2.34 frystyk 1077: ** Walk through the set of headers and write those out that we are allowed
1078: ** to store in the cache. We look into the connection header to see what the
1079: ** hop-by-hop headers are and also into the cache-control directive to see what
1080: ** headers should not be cached.
1081: */
1082: PRIVATE BOOL meta_write (FILE * fp, HTRequest * request, HTResponse * response)
1083: {
1084: if (fp && request && response) {
1085: HTAssocList * headers = HTResponse_header(response);
1086: HTAssocList * connection = HTResponse_connection(response);
1087: char * nocache = HTResponse_noCache(response);
1088:
1089: /*
1090: ** If we don't have any headers then just return now.
1091: */
1092: if (!headers) return NO;
1093:
1094: /*
1095: ** Check whether either the connection header or the cache control
1096: ** header includes header names that we should not cache
1097: */
1098: if (connection || nocache) {
1099:
1100: /*
1101: ** Walk though the cache control no-cache directive
1102: */
1103: if (nocache) {
1104: char * line = NULL;
1105: char * ptr = NULL;
1106: char * field = NULL;
1107: StrAllocCopy(line, nocache); /* Get our own copy */
1108: ptr = line;
1109: while ((field = HTNextField(&ptr)) != NULL)
1110: HTAssocList_removeObject(headers, field);
1111: HT_FREE(line);
1112: }
1113:
1114: /*
1115: ** Walk though the connection header
1116: */
1117: if (connection) {
1118: HTAssoc * pres;
1119: while ((pres=(HTAssoc *) HTAssocList_nextObject(connection))) {
1120: char * field = HTAssoc_name(pres);
1121: HTAssocList_removeObject(headers, field);
1122: }
1123: }
1124: }
1125:
1126: /*
1127: ** Write out the remaining list of headers that we not already store
1128: ** in the index file.
1129: */
1130: {
1131: HTAssocList * cur = headers;
1132: HTAssoc * pres;
1133: while ((pres = (HTAssoc *) HTAssocList_nextObject(cur))) {
1134: if (fprintf(fp, "%s: %s\n", HTAssoc_name(pres), HTAssoc_value(pres))<0) {
1135: if (CACHE_TRACE) HTTrace("Cache....... Error writing metainfo\n");
1136: return NO;
1137: }
1138: }
1139: }
1140:
1141: /*
1142: ** Terminate the header with a newline
1143: */
1144: if (fprintf(fp, "\n") < 0) {
1145: if (CACHE_TRACE) HTTrace("Cache....... Error writing metainfo\n");
1146: return NO;
1147: }
1148: return YES;
1149: }
1150: return NO;
1151: }
1152:
1153: /*
1154: ** Save the metainformation for the data object. If no headers
1155: ** are available then the meta file is empty
1156: */
1157: PUBLIC BOOL HTCache_writeMeta (HTCache * cache, HTRequest * request,
1158: HTResponse * response)
1159: {
1160: if (cache && request && response) {
1161: BOOL status;
1162: FILE * fp;
1163: char * name = HTCache_location(cache, YES);
1164: if (!name) return NO;
1165: if ((fp = fopen(name, "wb")) == NULL) {
1166: if (CACHE_TRACE)
1167: HTTrace("Cache....... Can't open `%s\' for writing\n", name);
1168: HTCache_remove(cache);
1169: HT_FREE(name);
1170: return NO;
1171: }
1172: status = meta_write(fp, request, response);
1173: fclose(fp);
1174: HT_FREE(name);
1175: return status;
1176: }
1177: return NO;
1178: }
1179:
1180: PRIVATE BOOL meta_read (FILE * fp, HTRequest * request, HTStream * target)
1181: {
1182: if (fp && request && target) {
1183: int status;
1184: char buffer[512];
1185: while (1) {
1186: if ((status = fread(buffer, 1, 512, fp)) <= 0) {
1187: if (PROT_TRACE) HTTrace("Cache....... Meta information loaded\n");
1188: return YES;
1189: }
1190:
1191: /* Send the data down the pipe */
1192: status = (*target->isa->put_block)(target, buffer, status);
1193: if (status == HT_LOADED) {
1194: (*target->isa->flush)(target);
1195: return YES;
1196: }
1197: if (status < 0) {
1198: if (PROT_TRACE) HTTrace("Cache....... Target ERROR %d\n", status);
1199: break;
1200: }
1201: }
1202: }
1203: return NO;
1204: }
1205:
1206: /*
1207: ** Read the metainformation for the data object. If no headers are
1208: ** available then the meta file is empty
1209: */
1210: PRIVATE BOOL HTCache_readMeta (HTCache * cache, HTRequest * request)
1211: {
1212: HTParentAnchor * anchor = HTRequest_anchor(request);
1213: if (cache && request && anchor) {
1214: BOOL status;
1215: FILE * fp;
1216: char * name = HTCache_location(cache, YES);
1217: if (!name) return NO;
1218: if (CACHE_TRACE) HTTrace("Cache....... Looking for `%s\'\n", name);
1219: if ((fp = fopen(name, "rb")) == NULL) {
1220: if (CACHE_TRACE)
1221: HTTrace("Cache....... Can't open `%s\' for reading\n", name);
1222: HTCache_remove(cache);
1223: HT_FREE(name);
1224: } else {
1225: HTStream * target = HTStreamStack(WWW_MIME_HEAD, WWW_DEBUG,
1226: HTBlackHole(), request, NO);
1227: status = meta_read(fp, request, target);
1228: (*target->isa->_free)(target);
1229: fclose(fp);
1230: HT_FREE(name);
1231: return status;
1232: }
1233: }
1234: return NO;
1235: }
1236:
1237: /*
1238: ** Merge metainformation with existing version. This means that we have had a
1239: ** successful validation and hence a true cache hit. We only regard the
1240: ** following headers: Date, content-location, expires, cache-control, and vary.
1241: */
1242: PUBLIC BOOL HTCache_updateMeta (HTCache * cache, HTRequest * request,
1243: HTResponse * response)
1244: {
1245: if (cache && request && response) {
1246: cache->hits++;
1247: return calculate_time(cache, request, response);
1248: }
1249: return NO;
1250: }
1251:
1252: /*
2.25 frystyk 1253: ** Remove from disk. You must explicitly remove a lock
1254: ** before this operation can succeed
1255: */
1256: PRIVATE BOOL flush_object (HTCache * cache)
1257: {
1258: if (cache && !HTCache_hasLock(cache)) {
1259: char * head = HTCache_location(cache, YES);
1260: char * body = HTCache_location(cache, NO);
1261: REMOVE(head);
1262: REMOVE(body);
1263: HT_FREE(head);
1264: HT_FREE(body);
1265: return YES;
1266: }
1267: return NO;
1268: }
1269:
1270: /* HTCache_flushAll
1271: ** ----------------
1272: ** Destroys all cache entried in memory and on disk. Resets the cache
1273: ** to empty but the cache does not have to be reinitialized before we
1274: ** can use it again.
1275: */
1276: PUBLIC BOOL HTCache_flushAll (void)
1277: {
1278: if (CacheTable) {
1279: HTList * cur;
1280: int cnt;
1281:
1282: /* Delete the rest */
1283: for (cnt=0; cnt<HASH_SIZE; cnt++) {
1284: if ((cur = CacheTable[cnt])) {
1285: HTCache * pres;
1286: while ((pres = (HTCache *) HTList_nextObject(cur)) != NULL) {
1287: flush_object(pres);
1288: free_object(pres);
1289: }
1290: }
1291: HTList_delete(CacheTable[cnt]);
1292: CacheTable[cnt] = NULL;
1293: }
1294:
1295: /* Write the new empty index to disk */
1296: HTCacheIndex_write(HTCacheRoot);
1297: HTTotalSize = 0L;
1298: return YES;
1299: }
1300: return NO;
1301: }
1302:
1303: /*
2.2 frystyk 1304: ** This function checks whether a document has expired or not.
1305: ** The check is based on the metainformation passed in the anchor object
2.22 frystyk 1306: ** The function returns the level of validation needed for getting a fresh
1307: ** version. We also check the cache control directives in the request to
1308: ** see if they change the freshness discission.
1309: */
1310: PUBLIC HTReload HTCache_isFresh (HTCache * cache, HTRequest * request)
1311: {
1312: HTAssocList * cc = HTRequest_cacheControl(request);
1313: if (cache) {
1314: time_t max_age = -1;
1315: time_t max_stale = -1;
1316: time_t min_fresh = -1;
1317:
1318: /*
2.34 frystyk 1319: ** Make sure that we have the metainformation loaded from the
1320: ** persistent cache
1321: */
1322: HTParentAnchor * anchor = HTRequest_anchor(request);
1323: if (!HTAnchor_headerParsed(anchor)) HTCache_readMeta(cache, request);
1324:
1325: /*
2.26 frystyk 1326: ** If we only have a part of this request then make a range request
1327: ** using the If-Range condition GET request
1328: */
2.27 frystyk 1329: if (cache->range) {
2.26 frystyk 1330: char buf[20];
2.27 frystyk 1331: sprintf(buf, "%ld-", cache->size);
2.26 frystyk 1332: if (CACHE_TRACE) HTTrace("Cache....... Asking for range `%s\'\n", buf);
1333: HTRequest_addRange(request, "bytes", buf);
1334: HTRequest_addRqHd(request, HT_C_RANGE);
1335: return HT_CACHE_RANGE_VALIDATE;
1336: }
1337:
1338: /*
2.22 frystyk 1339: ** In case this entry is of type "must-revalidate" then we just
1340: ** go ahead and validate it.
1341: */
1342: if (cache->must_revalidate)
1343: return HT_CACHE_VALIDATE;
1344: /*
1345: ** Check whether we have any special constraints like min-fresh in
1346: ** the cache control
1347: */
1348: if (cc) {
1349: char * token = NULL;
1350: if ((token = HTAssocList_findObject(cc, "max-age")))
1351: max_age = atol(token);
1352: if ((token = HTAssocList_findObject(cc, "max-stale")))
1353: max_stale = atol(token);
1354: if ((token = HTAssocList_findObject(cc, "min-fresh")))
1355: min_fresh = atol(token);
1356: }
1357:
1358: /*
1359: ** Now do the checking against the age constraints that we've got
1360: */
1361: {
1362: time_t resident_time = time(NULL) - cache->response_time;
1363: time_t current_age = cache->corrected_initial_age + resident_time;
1364:
1365: /*
1366: ** Check that the max-age, max-stale, and min-fresh directives
1367: ** given in the request cache control header is followed.
1368: */
1369: if (max_age >= 0 && current_age > max_age) {
1370: if (CACHE_TRACE) HTTrace("Cache....... Max-age validation\n");
1371: return HT_CACHE_VALIDATE;
1372: }
1373: if (min_fresh >= 0 &&
1374: cache->freshness_lifetime < current_age + min_fresh) {
1375: if (CACHE_TRACE) HTTrace("Cache....... Min-fresh validation\n");
1376: return HT_CACHE_VALIDATE;
1377: }
1378:
1379: return (cache->freshness_lifetime +
1380: (max_stale >= 0 ? max_stale : 0) > current_age) ?
1381: HT_CACHE_OK : HT_CACHE_VALIDATE;
1382: }
1383: }
1384: return HT_CACHE_FLUSH;
1385: }
1386:
1387: /*
1388: ** While we are creating a new cache object or while we are validating an
1389: ** existing one, we must have a lock on the entry so that not other
1390: ** requests can get to it in the mean while.
1391: */
1392: PUBLIC BOOL HTCache_getLock (HTCache * cache, HTRequest * request)
1393: {
1394: if (cache && request) {
1395: if (CACHE_TRACE) HTTrace("Cache....... Locking cache entry %p\n", cache);
1396: cache->lock = request;
1397: return YES;
1398: }
1399: return NO;
1400: }
1401:
1402: PUBLIC BOOL HTCache_releaseLock (HTCache * cache)
1403: {
1404: if (cache) {
1405: if (CACHE_TRACE) HTTrace("Cache....... Unlocking cache entry %p\n", cache);
1406: cache->lock = NULL;
1407: return YES;
1408: }
1409: return NO;
1410: }
1411:
1412: PUBLIC BOOL HTCache_hasLock (HTCache * cache)
1413: {
1414: return cache && cache->lock;
1415: }
1416:
1417: PUBLIC BOOL HTCache_breakLock (HTCache * cache, HTRequest * request)
1418: {
1419: if (cache && cache->lock) {
1420: if (cache->lock == request) {
1421: if (CACHE_TRACE)
1422: HTTrace("Cache....... Breaking lock on entry %p\n", cache);
1423: cache->lock = NULL;
1424: return YES;
1425: }
1426: }
1427: return NO;
1428: }
1429:
1430: /*
1431: ** Is we have a valid entry in the cache then we also need a location
1432: ** where we can get it. Hopefully, we may be able to access it
1433: ** thourgh one of our protocol modules, for example the local file
1434: ** module. The name returned is in URL syntax and must be freed by
1435: ** the caller
1436: */
1437: PUBLIC char * HTCache_name (HTCache * cache)
1438: {
1439: if (cache && HTCacheRoot) {
1440: char * local = HTCache_location(cache, NO);
1441: char * url = HTParse(local, "cache:", PARSE_ALL);
1442: HT_FREE(local);
1443: return url;
1444: }
1445: return NULL;
1446: }
1447:
1448: /*
1449: ** Remove from memory AND from disk. You must explicitly remove a lock
1450: ** before this operation can succeed
2.2 frystyk 1451: */
2.22 frystyk 1452: PUBLIC BOOL HTCache_remove (HTCache * cache)
2.2 frystyk 1453: {
2.25 frystyk 1454: return flush_object(cache) && HTCache_delete(cache);
2.22 frystyk 1455: }
1456:
1457: PUBLIC BOOL HTCache_addHit (HTCache * cache)
1458: {
1459: if (cache) {
1460: cache->hits++;
1461: if (CACHE_TRACE) HTTrace("Cache....... Hits for %p is %d\n",
1462: cache, cache->hits);
1463: return YES;
1464: }
1465: return NO;
1466: }
1467:
2.34 frystyk 1468: /* ------------------------------------------------------------------------- */
1469: /* CACHE WRITER */
1470: /* ------------------------------------------------------------------------- */
2.1 frystyk 1471:
2.26 frystyk 1472: PRIVATE BOOL free_stream (HTStream * me, BOOL abort)
2.1 frystyk 1473: {
2.22 frystyk 1474: if (me) {
2.26 frystyk 1475: HTCache * cache = me->cache;
1476:
1477: /*
1478: ** We close the file object. This does not mean that we have the
1479: ** complete object. In case of an "abort" then we only have a part,
1480: ** however, next time we do a load we can use byte ranges to complete
1481: ** the request.
1482: */
2.22 frystyk 1483: if (me->fp) fclose(me->fp);
2.26 frystyk 1484:
2.22 frystyk 1485: /*
1486: ** We are done storing the object body and can update the cache entry.
1487: ** Also update the meta information entry on disk as well. When we
1488: ** are done we don't need the lock anymore.
1489: */
2.26 frystyk 1490: if (cache) {
1491: HTCache_writeMeta(cache, me->request, me->response);
1492: HTCache_releaseLock(cache);
2.22 frystyk 1493:
1494: /*
2.27 frystyk 1495: ** Remember if this is the full entity body or only a subpart
1496: ** We assume that an abort will only give a part of the object.
1497: */
1498: cache->range = abort;
1499:
1500: /*
2.26 frystyk 1501: ** Set the size and maybe do gc. If it is an abort then set the
1502: ** byte range so that we can start from this point next time. We
1503: ** take the byte range as the number of bytes that we have already
1504: ** written to the cache entry.
2.22 frystyk 1505: */
2.27 frystyk 1506: HTCache_setSize(cache, me->bytes_written, me->append);
2.22 frystyk 1507: }
1508:
1509: /*
1510: ** In order not to loose information, we dump the current cache index
1511: ** every time we have created DUMP_FREQUENCY new entries
1512: */
1513: if (new_entries > DUMP_FREQUENCY) {
1514: HTCacheIndex_write(HTCacheRoot);
1515: new_entries = 0;
1516: }
1517: HT_FREE(me);
2.26 frystyk 1518: return YES;
2.22 frystyk 1519: }
2.26 frystyk 1520: return NO;
1521: }
1522:
1523:
1524: PRIVATE int HTCache_free (HTStream * me)
1525: {
2.34 frystyk 1526: return free_stream(me, NO) ? HT_OK : HT_ERROR;
2.1 frystyk 1527: }
1528:
2.12 frystyk 1529: PRIVATE int HTCache_abort (HTStream * me, HTList * e)
2.1 frystyk 1530: {
2.22 frystyk 1531: if (CACHE_TRACE) HTTrace("Cache....... ABORTING\n");
2.34 frystyk 1532: free_stream(me, YES);
1533: return HT_ERROR;
2.1 frystyk 1534: }
1535:
2.34 frystyk 1536: PRIVATE int HTCache_flush (HTStream * me)
1537: {
1538: return (fflush(me->fp) == EOF) ? HT_ERROR : HT_OK;
1539: }
1540:
1541: PRIVATE int HTCache_putBlock (HTStream * me, const char * s, int l)
1542: {
1543: int status = (fwrite(s, 1, l, me->fp) != l) ? HT_ERROR : HT_OK;
1544: if (l > 1 && status == HT_OK) {
1545: HTCache_flush(me);
1546: me->bytes_written += l;
1547: }
1548: return status;
1549: }
1550:
1551: PRIVATE int HTCache_putChar (HTStream * me, char c)
1552: {
1553: return HTCache_putBlock(me, &c, 1);
1554: }
1555:
1556: PRIVATE int HTCache_putString (HTStream * me, const char * s)
1557: {
1558: return HTCache_putBlock(me, s, (int) strlen(s));
1559: }
1560:
2.15 frystyk 1561: PRIVATE const HTStreamClass HTCacheClass =
2.1 frystyk 1562: {
1563: "Cache",
1564: HTCache_flush,
2.17 frystyk 1565: HTCache_free,
2.1 frystyk 1566: HTCache_abort,
1567: HTCache_putChar,
1568: HTCache_putString,
1569: HTCache_putBlock
1570: };
1571:
2.26 frystyk 1572: PRIVATE HTStream * HTCacheStream (HTRequest * request, BOOL append)
2.1 frystyk 1573: {
2.22 frystyk 1574: HTCache * cache = NULL;
1575: FILE * fp = NULL;
2.26 frystyk 1576: HTResponse * response = HTRequest_response(request);
2.19 frystyk 1577: HTParentAnchor * anchor = HTRequest_anchor(request);
2.12 frystyk 1578: if (!HTCacheEnable) {
2.14 eric 1579: if (CACHE_TRACE) HTTrace("Cache....... Not enabled\n");
2.22 frystyk 1580: return NULL;
2.1 frystyk 1581: }
1582:
2.22 frystyk 1583: /* Get a new cache entry */
2.26 frystyk 1584: if ((cache = HTCache_new(request, response, anchor)) == NULL) {
2.22 frystyk 1585: if (CACHE_TRACE) HTTrace("Cache....... Can't get a cache object\n");
1586: return NULL;
1587: }
1588:
1589: /* Test that the cached object is not locked */
1590: if (HTCache_hasLock(cache)) {
1591: if (HTCache_breakLock(cache, request) == NO) {
1592: if (CACHE_TRACE) HTTrace("Cache....... Entry already in use\n");
1593: return NULL;
1594: }
1595: }
1596: HTCache_getLock(cache, request);
1597:
1598: /*
1599: ** Test that we can actually write to the cache file. If the entry already
1600: ** existed then it will be overridden with the new data.
1601: */
1602: {
1603: char * name = HTCache_location(cache, NO);
2.26 frystyk 1604: if ((fp = fopen(name, append ? "ab" : "wb")) == NULL) {
2.22 frystyk 1605: if (CACHE_TRACE)
1606: HTTrace("Cache....... Can't open `%s\' for writing\n", name);
1607: HTCache_delete(cache);
1608: HT_FREE(name);
1609: return NULL;
2.26 frystyk 1610: } else {
1611: if (CACHE_TRACE)
1612: HTTrace("Cache....... %s file `%s\'\n",
1613: append ? "Append to" : "Creating", name);
1614: }
2.22 frystyk 1615: HT_FREE(name);
1616: }
2.1 frystyk 1617:
1618: /* Set up the stream */
2.22 frystyk 1619: {
1620: HTStream * me = NULL;
1621: if ((me = (HTStream *) HT_CALLOC(1, sizeof(HTStream))) == NULL)
1622: HT_OUTOFMEM("Cache");
1623: me->isa = &HTCacheClass;
1624: me->request = request;
2.26 frystyk 1625: me->response = response;
2.22 frystyk 1626: me->cache = cache;
1627: me->fp = fp;
2.27 frystyk 1628: me->append = append;
2.22 frystyk 1629: return me;
1630: }
1631: return NULL;
1632: }
1633:
2.26 frystyk 1634: PUBLIC HTStream * HTCacheWriter (HTRequest * request,
1635: void * param,
1636: HTFormat input_format,
1637: HTFormat output_format,
1638: HTStream * output_stream)
1639: {
1640: return HTCacheStream(request, NO);
1641: }
1642:
1643: PUBLIC HTStream * HTCacheAppend (HTRequest * request,
1644: void * param,
1645: HTFormat input_format,
1646: HTFormat output_format,
1647: HTStream * output_stream)
1648: {
1649: return HTCacheStream(request, YES);
1650: }
1651:
2.22 frystyk 1652: /* ------------------------------------------------------------------------- */
1653: /* CACHE READER */
1654: /* ------------------------------------------------------------------------- */
1655:
1656: /*
1657: ** This function closes the connection and frees memory.
1658: ** Returns YES on OK, else NO
1659: */
2.32 eric 1660: PRIVATE int CacheCleanup (HTRequest * req, int status)
2.22 frystyk 1661: {
2.32 eric 1662: HTNet * net = HTRequest_net(req);
2.22 frystyk 1663: cache_info * cache = (cache_info *) HTNet_context(net);
2.32 eric 1664: HTStream * input = HTRequest_inputStream(req);
1665:
1666: /* Free stream with data TO Local cache system */
1667: if (input) {
1668: if (status == HT_INTERRUPTED)
1669: (*input->isa->abort)(input, NULL);
1670: else
1671: (*input->isa->_free)(input);
1672: HTRequest_setInputStream(req, NULL);
1673: }
1674:
2.34 frystyk 1675: if (status != HT_IGNORE) {
1676: HTNet_delete(net, status);
1677: if (cache) {
1678: HT_FREE(cache->local);
1679: HT_FREE(cache);
1680: }
2.22 frystyk 1681: }
1682: return YES;
1683: }
1684:
1685: /*
1686: ** This load function loads an object from the cache and puts it to the
1687: ** output defined by the request object. For the moment, this load function
1688: ** handles the persistent cache as if it was on local file but in fact
1689: ** it could be anywhere.
1690: **
1691: ** Returns HT_ERROR Error has occured in call back
1692: ** HT_OK Call back was OK
1693: */
2.29 frystyk 1694: PRIVATE int CacheEvent (SOCKET soc, void * pVoid, HTEventType type);
1695:
1696: PUBLIC int HTLoadCache (SOCKET soc, HTRequest * request)
2.22 frystyk 1697: {
2.29 frystyk 1698: cache_info * cache; /* Specific access information */
1699: HTParentAnchor * anchor = HTRequest_anchor(request);
2.22 frystyk 1700: HTNet * net = HTRequest_net(request);
1701:
1702: /*
1703: ** Initiate a new cache structure and bind to request structure
1704: ** This is actually state CACHE_BEGIN, but it can't be in the state
1705: ** machine as we need the structure first.
1706: */
2.29 frystyk 1707: if (PROT_TRACE) HTTrace("Load Cache.. Looking for `%s\'\n",
1708: HTAnchor_physical(anchor));
1709: if ((cache = (cache_info *) HT_CALLOC(1, sizeof(cache_info))) == NULL)
1710: HT_OUTOFMEM("HTLoadCACHE");
1711: cache->state = CL_BEGIN;
1712: cache->net = net;
1713: HTNet_setContext(net, cache);
1714: HTNet_setEventCallback(net, CacheEvent);
1715: HTNet_setEventParam(net, cache); /* callbacks get http* */
1716:
1717: return CacheEvent(soc, cache, HTEvent_BEGIN); /* get it started - ops is ignored */
1718: }
1719:
1720: PRIVATE int CacheEvent (SOCKET soc, void * pVoid, HTEventType type)
1721: {
1722: cache_info * cache = (cache_info *)pVoid;
1723: int status = HT_ERROR;
1724: HTNet * net = cache->net;
1725: HTRequest * request = HTNet_request(net);
1726: HTParentAnchor * anchor = HTRequest_anchor(request);
1727:
2.32 eric 1728: if (type == HTEvent_BEGIN) {
1729: cache->state = CL_BEGIN;
1730: } else if (type == HTEvent_CLOSE) {
2.22 frystyk 1731: HTRequest_addError(request, ERR_FATAL, NO, HTERR_INTERRUPTED,
1732: NULL, 0, "HTLoadCache");
1733: CacheCleanup(request, HT_INTERRUPTED);
1734: return HT_OK;
2.32 eric 1735: } else if (type == HTEvent_END) {
1736: CacheCleanup(request, HT_OK);
1737: return HT_OK;
1738: } else if (type == HTEvent_RESET) {
1739: CacheCleanup(request, HT_RECOVER_PIPE);
1740: cache->state = CL_BEGIN;
1741: return HT_OK;
1742: }
2.22 frystyk 1743:
1744: /* Now jump into the machine. We know the state from the previous run */
1745: while (1) {
1746: switch (cache->state) {
2.34 frystyk 1747:
2.22 frystyk 1748: case CL_BEGIN:
1749: if (HTLib_secure()) {
1750: if (PROT_TRACE)
1751: HTTrace("Load Cache.. No access to local file system\n");
1752: cache->state = CL_ERROR;
1753: break;
1754: }
2.32 eric 1755: if ((net->host = HTHost_new("<local access>", 0)) == NULL)
1756: return NO;
1757: if (HTHost_addNet(net->host, net) == HT_PENDING)
1758: if (PROT_TRACE) HTTrace("HTLoadCache. Pending...\n");
2.34 frystyk 1759: cache->state = CL_NEED_BODY;
2.22 frystyk 1760: break;
1761:
1762: case CL_NEED_BODY:
1763: cache->local = HTWWWToLocal(HTAnchor_physical(anchor), "",
1764: HTRequest_userProfile(request));
1765: if (cache->local) {
1766: if (HT_STAT(cache->local, &cache->stat_info) == -1) {
1767: if (PROT_TRACE)
1768: HTTrace("Load Cache.. Not found `%s\'\n", cache->local);
1769: HTRequest_addError(request, ERR_FATAL, NO, HTERR_NOT_FOUND,
1770: NULL, 0, "HTLoadCache");
1771: cache->state = CL_ERROR;
1772: break;
1773: }
1774:
1775: /*
1776: ** The cache entry may be empty in which case we just return
1777: */
1778: if (!cache->stat_info.st_size) {
1779: HTRequest_addError(request, ERR_FATAL, NO,HTERR_NO_CONTENT,
1780: NULL, 0, "HTLoadCache");
1781: cache->state = CL_NO_DATA;
1782: } else
1783: cache->state = CL_NEED_OPEN_FILE;
1784: } else
1785: cache->state = CL_ERROR;
1786: break;
1787:
1788: case CL_NEED_OPEN_FILE:
1789: status = HTFileOpen(net, cache->local, HT_FT_RDONLY);
1790: if (status == HT_OK) {
1791: /*
1792: ** Create the stream pipe FROM the channel to the application.
1793: ** The target for the input stream pipe is set up using the
1794: ** stream stack.
1795: */
2.34 frystyk 1796: net->readStream = HTStreamStack(HTAnchor_format(anchor),
1797: HTRequest_outputFormat(request),
1798: HTRequest_outputStream(request),
1799: request, YES);
1800: HTRequest_setOutputConnected(request, YES);
2.32 eric 1801:
2.35 ! frystyk 1802: #if 0
2.34 frystyk 1803: /*
1804: ** Create the stream pipe TO the channel from the application
1805: ** and hook it up to the request object
1806: */
1807: {
1808: HTOutputStream * output = HTNet_getOutput(net, NULL, 0);
1809: HTRequest_setInputStream(request, (HTStream *) output);
1810: }
2.35 ! frystyk 1811: #endif
2.32 eric 1812:
2.34 frystyk 1813: HTRequest_addError(request, ERR_INFO, NO, HTERR_OK,
1814: NULL, 0, "HTLoadCache");
1815: cache->state = CL_NEED_CONTENT;
2.22 frystyk 1816:
1817: #ifndef NO_UNIX_IO
2.34 frystyk 1818: /* If we are _not_ using preemptive mode and we are Unix fd's
1819: ** then return here to get the same effect as when we are
1820: ** connecting to a socket. That way, HTCache acts just like any
1821: ** other protocol module even though we are in fact doing
1822: ** blocking connect
1823: */
1824: if (!net->preemptive) {
1825: if (PROT_TRACE) HTTrace("Load Cache.. returning\n");
1826: HTEvent_register(HTNet_socket(net), HTEvent_READ, &net->event);
1827: return HT_OK;
1828: }
2.22 frystyk 1829: #endif
1830: } else if (status == HT_WOULD_BLOCK || status == HT_PENDING)
1831: return HT_OK;
1832: else {
1833: HTRequest_addError(request, ERR_INFO, NO, HTERR_INTERNAL,
1834: NULL, 0, "HTLoadCache");
1835: cache->state = CL_ERROR; /* Error or interrupt */
1836: }
1837: break;
1838:
1839: case CL_NEED_CONTENT:
2.29 frystyk 1840: status = HTHost_read(net->host, net);
2.22 frystyk 1841: if (status == HT_WOULD_BLOCK)
1842: return HT_OK;
1843: else if (status == HT_LOADED || status == HT_CLOSED) {
1844: cache->state = CL_GOT_DATA;
1845: } else {
1846: HTRequest_addError(request, ERR_INFO, NO, HTERR_FORBIDDEN,
1847: NULL, 0, "HTLoadCache");
1848: cache->state = CL_ERROR;
1849: }
1850: break;
1851:
1852: case CL_GOT_DATA:
2.35 ! frystyk 1853: CacheCleanup(request, HT_NOT_MODIFIED);
2.34 frystyk 1854: return HT_OK;
2.22 frystyk 1855: break;
2.1 frystyk 1856:
2.22 frystyk 1857: case CL_NO_DATA:
1858: CacheCleanup(request, HT_NO_DATA);
1859: return HT_OK;
1860: break;
1861:
1862: case CL_ERROR:
1863: CacheCleanup(request, HT_ERROR);
1864: return HT_OK;
1865: break;
1866: }
1867: } /* End of while(1) */
2.1 frystyk 1868: }
Webmaster