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