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

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

Webmaster