#include "httpd.h"   
#include "http_config.h"   
#include "http_request.h"   
#include "http_protocol.h"   
#include "http_core.h"   
#include "http_main.h"  
#include "http_log.h"   
#include "util_md5.h"

/*  
 * C version of chapter 7's Apache::Checksum{1,2,3}
 */  
 
/*  
 * configure like so: 
 *  
 * LoadModule checksum_module modules/mod_checksum.so 
 * CheckSumTrans one|two|three
 * CheckSumDir conf/checksums
 * CheckSumPath /checksums
 *
 */

module MODULE_VAR_EXPORT checksum_module;

typedef int (*handler_func) (request_rec *);

typedef struct {
    handler_func cksm_trans;
    char *cksm_dir;
    char *cksm_path;
    char *cksm_ext;
    int cksm_ext_len;
    regex_t *cksm_re_trans;
} checksum_srv_config;

static int checksum_trans1(request_rec *r);
static int checksum_trans2(request_rec *r);
static int checksum_trans3(request_rec *r);

#ifndef DEFAULT_CHECKSUM_DIR
#define DEFAULT_CHECKSUM_DIR "/usr/tmp/checksums" 
#endif

#ifndef DEFAULT_CHECKSUM_PATH
#define DEFAULT_CHECKSUM_PATH "/checksums" 
#endif

static void *checksum_create_srv_config(pool *p, server_rec *s)  
{ 
    checksum_srv_config *scfg =  
       (checksum_srv_config *)ap_pcalloc(p, sizeof(*scfg));  
 
    scfg->cksm_trans = checksum_trans3;
    scfg->cksm_ext = ".cksm";
    scfg->cksm_ext_len = strlen(scfg->cksm_ext);
    scfg->cksm_dir  = DEFAULT_CHECKSUM_DIR;
    scfg->cksm_path = DEFAULT_CHECKSUM_PATH;
    /* we compile the regexp at startup to save overhead per-request */
    scfg->cksm_re_trans = ap_pregcomp(p, "^(.+)\\.cksm$", REG_EXTENDED|REG_ICASE); 

    return (void *)scfg;  
} 

static checksum_srv_config *checksum_get_srv_config(server_rec *s)
{
    return (checksum_srv_config *)ap_get_module_config(s->module_config,
						       &checksum_module);
}

static const char *checksum_dir_cmd(cmd_parms *parms, void *mconfig, const char *arg) 
{
    checksum_srv_config *scfg = checksum_get_srv_config(parms->server);

    scfg->cksm_dir = ap_server_root_relative(parms->pool, (char *)arg);

    return NULL;
}

static const char *checksum_path_cmd(cmd_parms *parms, void *mconfig, const char *arg) 
{
    checksum_srv_config *scfg = checksum_get_srv_config(parms->server);

    scfg->cksm_path = ap_pstrdup(parms->pool, arg);

    return NULL;
}

/* configure which trans handler to use */
static const char *checksum_trans_cmd(cmd_parms *parms, void *mconfig, const char *arg) 
{
    checksum_srv_config *scfg = checksum_get_srv_config(parms->server);

    ap_str_tolower((char *)arg);

    if (strcmp(arg, "one") == 0) {
	scfg->cksm_trans = checksum_trans1;
    }
    else if (strcmp(arg, "two") == 0) {
	scfg->cksm_trans = checksum_trans2;
    }
    else if (strcmp(arg, "three") == 0) {
	scfg->cksm_trans = checksum_trans3;
    }
    else {
	return ap_pstrcat(parms->pool, arg, 
			  " is not a valid checksum trans handler ", NULL);
    }

    return NULL;
}

/* dispatch to one of the 3 trans handlers */
static int checksum_trans(request_rec *r)
{
    checksum_srv_config *scfg = checksum_get_srv_config(r->server);
    return (*scfg->cksm_trans)(r);
}

/* Apache::Checksum1 */

/* sure, we could do these translations without regex-es
 * but this is a good chance to show you the regex basics in action
 */
static int checksum_trans1(request_rec *r)
{
    regmatch_t match[2];
    checksum_srv_config *scfg = checksum_get_srv_config(r->server);
    regex_t *pat = scfg->cksm_re_trans;
    
    if (regexec(pat, r->uri, pat->re_nsub+1, match, 0) == 0) {  
	char *repl = ap_pstrcat(r->pool, scfg->cksm_dir, "$1", NULL);
	r->filename = ap_pregsub(r->pool, repl,  
				 r->uri, pat->re_nsub+1, match);  
	return OK;
    } 
    else {
	return DECLINED;
    }
}

/* Apache::Checksum2 */
static int checksum_trans2(request_rec *r)
{
    regmatch_t match[2];
    checksum_srv_config *scfg = checksum_get_srv_config(r->server);
    regex_t *pat = scfg->cksm_re_trans;
    
    if (regexec(pat, r->uri, pat->re_nsub+1, match, 0) == 0) {  
	char *repl = ap_pstrcat(r->pool, scfg->cksm_path, "$1", NULL);
	r->uri = ap_pregsub(r->pool, repl,  
			    r->uri, pat->re_nsub+1, match);  
    } 

    return DECLINED;
}

/* Apache::Checksum3 */
static int checksum_trans3(request_rec *r)
{
    checksum_srv_config *scfg = checksum_get_srv_config(r->server);
    char *sub;
    int end;

    if (!(sub = strstr(r->uri, scfg->cksm_ext))) {
	return DECLINED;
    }

    /* faster than rolling a regexp with a $ anchor */
    if (strlen(sub) != scfg->cksm_ext_len) {
	return DECLINED;
    }

    /* chop off the .cksm extension */
    end = sub - r->uri;
    memset(r->uri+end, '\0', scfg->cksm_ext_len);

    /* SetHandler checksum */
    r->handler = "checksum";

    return DECLINED;
}

static char *checksum_hexdigest(request_rec *r)
{
    AP_MD5_CTX context;
    unsigned char buffer[1024];
    unsigned char hash[16];
    unsigned int len;
    char *ptr, result[33];
    int i;

    FILE *fh = ap_pfopen(r->pool, r->filename, "r");
    if (!fh) {
	return NULL;
    }
    ap_MD5Init(&context);
    while ((len = fread(buffer, sizeof(unsigned char), 1024, fh)) > 0) {
	ap_MD5Update(&context, buffer, len);
    }
    ap_MD5Final(hash, &context);
    for (i=0, ptr=result; i<16; i++, ptr+=2 ) {
	ap_snprintf(ptr, sizeof(result), "%02x", hash[i]);
    }
    *ptr = '\0';
    return ap_pstrdup(r->pool, result);
}

static int checksum_handler(request_rec *r) 
{
    r->content_type = "text/plain";
    ap_send_http_header(r);

    if (r->header_only) {
	return OK;
    }

    ap_rvputs(r, checksum_hexdigest(r), "\t", r->uri, "\n", NULL);

    return OK;
}

static const command_rec checksum_cmds[] = {  
    {  
        "ChecksumTrans", checksum_trans_cmd,  
	NULL, RSRC_CONF, TAKE1,  
        "one, two or three", 
    },  
    { 
	"ChecksumDir", checksum_dir_cmd, 
	NULL, RSRC_CONF, TAKE1, 
	"Checksum directory tree root, absolute or server root relative." 
    }, 
    { 
	"ChecksumPath", checksum_path_cmd, 
	NULL, RSRC_CONF, TAKE1, 
	"Checksum path root." 
    }, 
    { NULL }
};

static const handler_rec checksum_handlers[] = { 
    { "checksum", checksum_handler }, 
    { NULL }
};

module MODULE_VAR_EXPORT checksum_module = {
    STANDARD_MODULE_STUFF, 
    NULL,                  /* module initializer                  */
    NULL,                  /* create per-dir    config structures */
    NULL,                  /* merge  per-dir    config structures */
    checksum_create_srv_config, /* create per-server config structures */
    NULL,                  /* merge  per-server config structures */
    checksum_cmds,         /* table of config file commands       */
    checksum_handlers,     /* [#8] MIME-typed-dispatched handlers */
    checksum_trans,        /* [#1] URI to filename translation    */
    NULL,                  /* [#4] validate user id from request  */
    NULL,                  /* [#5] check if the user is ok _here_ */
    NULL,                  /* [#3] check access by host address   */
    NULL,                  /* [#6] determine MIME type            */
    NULL,                  /* [#7] pre-run fixups                 */
    NULL,                  /* [#9] log a transaction              */
    NULL,                  /* [#2] header parser                  */
    NULL,                  /* child_init                          */
    NULL,                  /* child_exit                          */
    NULL                   /* [#0] post read-request              */
};

