Annotation of libwww/Library/src/HTCache.c, revision 2.22

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

Webmaster