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