
/* 
 * Based on original work found on
 * http://www.atm.cs.ndsu.nodak.edu/~tatarino/apache
 */

#include "mod_cache.h"

/* for core_dir_config */
#define CORE_PRIVATE
#include "http_core.h"
#include "ap_md5.h"
#include "util_md5.h"
#include "http_protocol.h"
#include "http_log.h"
#include "http_request.h"

static void cache_cleanup(void *cf)
{
    cache_conf *cfg = cf;
    if (cfg->mmap_cache_size > 0)
        mmap_cache_exit();
}

static void cache_init(server_rec * r, pool * p)
{
    cache_conf *cfg = ap_get_module_config(r->module_config, &cache_module);

    if (cfg->mmap_cache_size > 0)
        mmap_cache_init(cfg->mmap_cache_size);
    ap_register_cleanup(p, cfg, cache_cleanup, cache_cleanup);
}

static void *create_cache_config(pool * p, server_rec * s)
{
    cache_conf *cfg = ap_pcalloc(p, sizeof(cache_conf));
    cfg->mmap_cache_size = 0;
    cfg->mmap_cache_threshold = MMAP_CACHE_DEFAULT_THRESHOLD;
    return cfg;
}

static const char *set_mmap_cache_size(cmd_parms * parms, char *ptr, char *arg)
{
    cache_conf *cfg =
    ap_get_module_config(parms->server->module_config, &cache_module);
    int val;

    if (sscanf(arg, "%d", &val) != 1 || val < 0)
        return "MmapCacheSize  must be an integer >= 0";
    cfg->mmap_cache_size = val;
    ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_NOTICE, NULL,
                "Setting mmap cache size to %d", val);
    return NULL;
}


static const char *set_mmap_cache_threshold(cmd_parms * parms, char *ptr, char *arg)
{
    cache_conf *cfg =
    ap_get_module_config(parms->server->module_config, &cache_module);
    int val;

    if (sscanf(arg, "%d", &val) != 1 || val < 0)
        return "MmapCacheThreshold  must be an integer >= 0";
    cfg->mmap_cache_threshold = val;

    ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_NOTICE, NULL,
                "Setting mmap cache threshold to %d", val);
    return NULL;
}


static int cache_handler(request_rec * r)
{
    /* just follow default_handler mostly */
    cache_conf *cfg = ap_get_module_config(r->server->module_config, &cache_module);
    core_dir_config *d = ap_get_module_config(r->per_dir_config, &core_module);
    int rangestatus, errstatus;
    caddr_t mmap_addr;

    /* fprintf(stderr, "Handling %s\n", r->filename); */

    if (cfg->mmap_cache_size == 0)
        return DECLINED;

    if (r->finfo.st_size < cfg->mmap_cache_threshold ||
        (r->header_only && !(d->content_md5 & 1)))
        return DECLINED;

    if ((errstatus = ap_discard_request_body(r)) != OK)
        return errstatus;

    r->allowed |= (1 << M_GET);
    r->allowed |= (1 << M_OPTIONS);

    if (r->method_number == M_INVALID) {
        ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, r->server,
                    "Invalid method (%s) in request %s", r->method, r->the_request);
        return NOT_IMPLEMENTED;
    }
    if (r->method_number == M_OPTIONS)
        return ap_send_http_options(r);
    if (r->method_number == M_PUT)
        return METHOD_NOT_ALLOWED;

    if (r->finfo.st_mode == 0 || (r->path_info && *r->path_info)) {
        ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "File does not exist: %s",
                    r->path_info ? ap_pstrcat(r->pool, r->filename, r->path_info, NULL)
                    : r->filename);
        return NOT_FOUND;
    }
    if (r->method_number != M_GET)
        return METHOD_NOT_ALLOWED;

    ap_update_mtime(r, r->finfo.st_mtime);
    ap_set_last_modified(r);
    ap_set_etag(r);

    if (((errstatus = ap_meets_conditions(r)) != OK)
        || (errstatus = ap_set_content_length(r, r->finfo.st_size))) {
        return errstatus;
    }

    ap_block_alarms();
    /* the file will be opened here. We were supposed to do that before
       setting last modified, etc. Hopefully, it's not a big problem.
       (occasional "No permission" with last_modified header) */
    errstatus = mmap_cache_handle_request(r, &mmap_addr);
    ap_unblock_alarms();
    if (errstatus != OK)
        return errstatus;

    if (d->content_md5 & 1) {
        AP_MD5_CTX context;
        ap_MD5Init(&context);
        ap_MD5Update(&context, (void *) mmap_addr, r->finfo.st_size);
        ap_table_set(r->headers_out, "Content-MD5",
                  ap_md5contextTo64(r->pool, &context));
    }

    rangestatus = ap_set_byterange(r);
    ap_send_http_header(r);

    if (!r->header_only) {
        if (!rangestatus)
            ap_send_mmap(mmap_addr, r, 0, r->finfo.st_size);
        else {
            long offset, length;
            while (ap_each_byterange(r, &offset, &length))
                ap_send_mmap(mmap_addr, r, offset, length);
        }
    }

    return OK;
}

static handler_rec cache_handlers[] =
{
    { "*/*", cache_handler },
    { NULL, NULL }
};

static command_rec cache_cmds[] =
{
    { "MmapCacheSize", set_mmap_cache_size, NULL, RSRC_CONF, TAKE1,
      "mmap cache size in entries" },
    { "MmapCacheThreshold", set_mmap_cache_threshold, NULL, RSRC_CONF, TAKE1,
      "mmap cache threshold in bytes" },
    { NULL, NULL }
};

module MODULE_VAR_EXPORT cache_module =
{
    STANDARD_MODULE_STUFF,
    cache_init,                 /* module initializer */
    NULL,                       /* per-directory config creator */
    NULL,                       /* dir config merger */
    create_cache_config,        /* server config creator */
    NULL,                       /* server config merger */
    cache_cmds,                 /* command table */
    cache_handlers,             /* [7] list of handlers */
    NULL,                       /* [2] filename-to-URI translation */
    NULL,                       /* [5] check/validate user_id */
    NULL,                       /* [6] check user_id is valid *here* */
    NULL,                       /* [4] check access by host address */
    NULL,                       /* [7] MIME type checker/setter */
    NULL,                       /* [8] fixups */
    NULL,                       /* [10] logger */
    NULL,                       /* [3] header parser */
    NULL,                       /* process initializer */
    NULL,                       /* process exit/cleanup */
    NULL                        /* [1] post read_request handling */
};

