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

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

Webmaster