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