mod_csfilter

カッとなって作った。詳しくは後日書く。

ビルド等は

% apxs -c -I /usr/include/ClearSilver -l neo_cs -l neo_utl -l neo_cgi mod_csfilter.c
% sudo apxs -i mod_csfilter.la

みたいな感じで。

#include "apr_strings.h"
#include "ap_config.h"
#include "httpd.h"
#include "http_config.h"
#include "http_protocol.h"
#include "http_log.h"
#include "http_main.h"
#include "http_request.h"

#include "mod_core.h"

#include <ClearSilver.h>

#define CSF_TPL_HANDLER_NAME    "clearsilver"
#define CSF_HDF_HANDLER_NAME    "clearsilver-hdf"
#define CSF_FILTER_NAME         "CLEARSILVER"

#define CSF_TEMPLATE_CONFIG_TAG "CSF_DefaultTemplate"
#define CSF_TEMPLATE_HEADER_TAG "X-CSF-Template"
#define CSF_TEMPLATE_ENV_TAG    "CSF_TEMPLATE"

/*********************************************************************
    ClearSilver
 *********************************************************************/

typedef struct {
    HDF     *hdf;
    CSPARSE *cs;
} cs_rec;

typedef struct {
    apr_pool_t         *pool;
    apr_bucket_brigade *bb;
} cs_render_cb_rec;

static NEOERR * csf_init_cs(cs_rec *csr)
{
    NEOERR *err;

    csr->hdf = NULL;
    csr->cs  = NULL;

    err = nerr_init();
    if (err != STATUS_OK)  return err;

    err = hdf_init(&csr->hdf);
    if (err != STATUS_OK)  return err;

    err = cs_init(&csr->cs, csr->hdf);

    /* Template Filters from CGI Kit */
    cs_register_strfunc(csr->cs, "url_escape",  cgi_url_escape);
    cs_register_strfunc(csr->cs, "html_escape", cgi_html_escape_strfunc);
    cs_register_strfunc(csr->cs, "js_escape",   cgi_js_escape);
    cs_register_strfunc(csr->cs, "text_html",   cgi_text_html_strfunc);
    cs_register_strfunc(csr->cs, "html_strip",  cgi_html_strip_strfunc);
    /* TODO: error check */

    return err;
}

static void csf_final_cs(cs_rec *csr)
{
    if (csr) {
        if (csr->cs)
            cs_destroy(&csr->cs);
        if (csr->hdf)
            hdf_destroy(&csr->hdf);
    }
}

static int csf_setup_env_callback(void *data, const char *key,
                                  const char *value)
{
    HDF *hdf = (HDF *) data;
    NEOERR *err;

    err = hdf_set_value(hdf, key, value);

    return 1;
}

static NEOERR * csf_setup_cs(cs_rec *csr, request_rec *r)
{
    NEOERR *err;
    HDF *hdf_env;

    err = hdf_get_node(csr->hdf, "ENV", &hdf_env);
    if (err != STATUS_OK)  return err;

    apr_table_do(csf_setup_env_callback, hdf_env, r->subprocess_env, NULL);

    return STATUS_OK;
}

static NEOERR * csf_cs_render_callback(void *data, char *s)
{
    cs_render_cb_rec *crr = data;
    apr_bucket       *b;
    char             *t;
    size_t           len = strlen(s);

    if (! s || ! len)
        return STATUS_OK;

    t = apr_pmemdup(crr->pool, s, len);
    b = apr_bucket_pool_create(t, len, crr->pool, crr->bb->bucket_alloc);
    if (! b)
        return nerr_raise_errno(NERR_IO, "bucket allocation failed");

    APR_BRIGADE_INSERT_TAIL(crr->bb, b);

    return STATUS_OK;
}

static void csf_cs_log_error(request_rec *r, NEOERR *err, const char *msg)
{
    STRING es;

    string_init(&es);
    nerr_error_string(err, &es);
    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                  "%s%s%s", msg ? msg : "", msg ? ": " : "", es.buf);
    string_clear(&es);
}

static apr_status_t csf_cs_render_file(apr_bucket_brigade **prbb,
                                       request_rec *r,
                                       const char *template,
                                       const char *hdf_file,
                                       const char *hdf_str)
{
    cs_rec             ocsr;
    cs_render_cb_rec   ocrr;
    NEOERR             *err;
    apr_bucket_brigade *bb;
    apr_bucket         *b;

    err = csf_init_cs(&ocsr);
    if (err != STATUS_OK) {
        csf_cs_log_error(r, err, "initialization of ClearSilver failed");
        csf_final_cs(&ocsr);
        return APR_EGENERAL;
    }

    /* load HDF */
    if (hdf_file) {
        err = hdf_read_file(ocsr.hdf, hdf_file);
        if (err != STATUS_OK) {
            csf_cs_log_error(r, err, "loading HDF failed");
            csf_final_cs(&ocsr);
            return APR_EGENERAL;
        }
    }
    if (hdf_str) {
        err = hdf_read_string(ocsr.hdf, hdf_str);
        if (err != STATUS_OK) {
            csf_cs_log_error(r, err, "reading HDF failed");
            csf_final_cs(&ocsr);
            return APR_EGENERAL;
        }
    }

    err = csf_setup_cs(&ocsr, r);
    if (err != STATUS_OK) {
        csf_cs_log_error(r, err, "setup of ClearSilver failed");
        csf_final_cs(&ocsr);
        return APR_EGENERAL;
    }

    err = cs_parse_file(ocsr.cs, template);
    if (err != STATUS_OK) {
        csf_cs_log_error(r, err, "parsing of ClearSilver failed");
        csf_final_cs(&ocsr);
        return APR_EGENERAL;
    }

    bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
    /* TODO: error check */

    ocrr.pool = r->pool;
    ocrr.bb   = bb;

    err = cs_render(ocsr.cs, &ocrr, csf_cs_render_callback);
    if (err != STATUS_OK) {
        csf_cs_log_error(r, err, "rendering of ClearSilver failed");
        apr_brigade_destroy(bb);
        csf_final_cs(&ocsr);
        return APR_EGENERAL;
    }

    csf_final_cs(&ocsr);

    b = apr_bucket_eos_create(r->connection->bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(bb, b);

    *prbb = bb;
    return APR_SUCCESS;
}

/*********************************************************************
    Apache
 *********************************************************************/

module AP_MODULE_DECLARE_DATA csfilter_module;

typedef struct {
    const char *default_template;
} csf_dir_config;

typedef struct {
    int                seen_eos;
    apr_bucket_brigade *bb;
    const char         *template_path;
} csf_out_ctx_t;

static apr_bucket_brigade * create_error_brigade(request_rec *r)
{
    apr_bucket_brigade *bb;
    apr_bucket         *b;

    bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
    /* TODO: error handling */

    /* custom message is not implemented in httpd-core yet */
    b = ap_bucket_error_create(HTTP_INTERNAL_SERVER_ERROR, NULL, r->pool,
                               r->connection->bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(bb, b);

    b = apr_bucket_eos_create(r->connection->bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(bb, b);

    return bb;
}

static const char *get_template_path(request_rec *r)
{
    csf_dir_config *conf = ap_get_module_config(r->per_dir_config,
                                                &csfilter_module);
    const char     *template_path;
    request_rec    *rr;

    do {

        /* from header */
        template_path = apr_table_get(r->headers_out,
                                      CSF_TEMPLATE_HEADER_TAG);
        if (template_path) {
            apr_table_unset(r->headers_out, CSF_TEMPLATE_HEADER_TAG);
            break;
        }

        /* from header */
        template_path = apr_table_get(r->err_headers_out,
                                      CSF_TEMPLATE_HEADER_TAG);
        if (template_path) {
            apr_table_unset(r->err_headers_out, CSF_TEMPLATE_HEADER_TAG);
            break;
        }

        /* from env */
        template_path = apr_table_get(r->subprocess_env,
                                      CSF_TEMPLATE_ENV_TAG);
        if (template_path)
            break;

        /* from dir-config */
        template_path = conf->default_template;

    } while (0);

    if (! template_path)
        return template_path;

    /* canonicalize relative path */
    rr = ap_sub_req_lookup_file(template_path, r, NULL);
    if (rr && rr->status == HTTP_OK && rr->finfo.filetype != 0) {
        template_path = apr_pstrdup(r->pool, rr->filename);
    }
    else {
        /* TODO */
    }

    if (rr) ap_destroy_sub_req(rr);

    return template_path;
}

/*********************************************************************
    Apache Handler
 *********************************************************************/

static int csf_handler_file_entity_check(request_rec *r)
{
    apr_file_t         *f = NULL;
    apr_status_t       rv;

    ap_allow_standard_methods(r, MERGE_ALLOW, M_GET, M_POST, M_OPTIONS, -1);

    if (r->finfo.filetype == 0) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                      "File does not exist: %s", r->filename);
        return HTTP_NOT_FOUND;
    }

    if ((rv = apr_file_open(&f, r->filename, APR_READ,
                APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                    "file permissions deny server access: %s", r->filename);
        return HTTP_FORBIDDEN;
    }

    ap_update_mtime(r, r->finfo.mtime);
    ap_set_last_modified(r);

    apr_file_close(f);

    ap_meets_conditions(r);

    return OK;
}

static int csf_tpl_handler(request_rec *r)
{
    int                ri;
    apr_status_t       rv;
    apr_bucket_brigade *bb;

    if ((ri = csf_handler_file_entity_check(r)) != OK)
        return ri;

    if (r->header_only)
        return OK;

    rv = csf_cs_render_file(&bb, r, r->filename, NULL, NULL);
    if (rv != APR_SUCCESS)
        return HTTP_INTERNAL_SERVER_ERROR;

    rv = ap_pass_brigade(r->output_filters, bb);
    if (rv != APR_SUCCESS)
        return HTTP_INTERNAL_SERVER_ERROR;

    return OK;
}

static int csf_hdf_handler(request_rec *r)
{
    int                ri;
    apr_status_t       rv;
    apr_bucket_brigade *bb;
    const char         *template_path;

    if ((ri = csf_handler_file_entity_check(r)) != OK)
        return ri;

    template_path = get_template_path(r);
    if (! template_path) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                      "CSHandler: template file path is not specified");
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    if (r->header_only)
        return OK;

    rv = csf_cs_render_file(&bb, r, template_path, r->filename, NULL);
    if (rv != APR_SUCCESS)
        return HTTP_INTERNAL_SERVER_ERROR;

    rv = ap_pass_brigade(r->output_filters, bb);
    if (rv != APR_SUCCESS)
        return HTTP_INTERNAL_SERVER_ERROR;

    return OK;
}

static int csf_handler(request_rec *r)
{
    if (! strcmp(r->handler, CSF_TPL_HANDLER_NAME))
        return csf_tpl_handler(r);

    if (! strcmp(r->handler, CSF_HDF_HANDLER_NAME))
        return csf_hdf_handler(r);

    return DECLINED;
}

/*********************************************************************
    Apache Filter
 *********************************************************************/

static apr_status_t csf_out_filter(ap_filter_t *f, apr_bucket_brigade *bb)
{
    request_rec    *r   = f->r;
    csf_out_ctx_t  *ctx = f->ctx;
    apr_bucket     *b;

    /* fast exit */
    if (APR_BRIGADE_EMPTY(bb))
        return ap_pass_brigade(f->next, bb);

    /* already done */
    if (ctx && ctx->seen_eos)
        return ap_pass_brigade(f->next, bb);

    if (! ctx) {
        /* first time initialization */
        const char *template_path = get_template_path(r);

        if (! template_path) {
            /* template path is not specified, so pass-thru */
            ap_remove_output_filter(f);
            return ap_pass_brigade(f->next, bb);
        }

        f->ctx = ctx = apr_palloc(r->pool, sizeof(*ctx));

        ctx->seen_eos      = 0;
        ctx->bb            = apr_brigade_create(r->pool, f->c->bucket_alloc);
        ctx->template_path = template_path;
    }

    b = APR_BRIGADE_FIRST(bb);
    while (b != APR_BRIGADE_SENTINEL(bb)) {
        apr_bucket *next_b = APR_BUCKET_NEXT(b);

        APR_BUCKET_REMOVE(b);       /* remove from original brigade */

        if (! ctx->seen_eos) {
            APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
        }

        if (APR_BUCKET_IS_EOS(b))
            ctx->seen_eos = 1;

        b = next_b;
    }

    /* given brigade to trash */
    apr_brigade_destroy(bb);

    if (ctx->seen_eos) {  /* reading stream completed */
        char *hdf_str;
        apr_size_t len;
        apr_status_t rv;
        apr_bucket_brigade *out_bb;

        rv = apr_brigade_pflatten(ctx->bb, &hdf_str, &len, r->pool);
        apr_brigade_destroy(ctx->bb);
        if (rv != APR_SUCCESS) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                          "apr_brigade_pflatten() failed");
            out_bb = create_error_brigade(r);
            return ap_pass_brigade(f->next, out_bb);
        }

        rv = csf_cs_render_file(&out_bb, r, ctx->template_path, NULL, hdf_str);
        if (rv != APR_SUCCESS) {
            out_bb = create_error_brigade(r);
            return ap_pass_brigade(f->next, out_bb);
        }

        /* forward brigade to next filter */
        return ap_pass_brigade(f->next, out_bb);
    }

    return APR_SUCCESS;
}

/*********************************************************************
    Apache Module Config
 *********************************************************************/

static void *create_csf_dir_config(apr_pool_t *p, char *dummy)
{
    csf_dir_config *result = apr_palloc(p, sizeof(csf_dir_config));
    
    result->default_template = NULL;
    
    return result;
}

static const char *set_default_template(cmd_parms *cmd, void *mconfig,
                                        const char *msg)
{
    csf_dir_config *conf = mconfig;
    conf->default_template = msg;

    return NULL;
}

static const command_rec csf_cmds[] = {
    AP_INIT_TAKE1(CSF_TEMPLATE_CONFIG_TAG, set_default_template, NULL, OR_ALL, "template file path"),
    {NULL},
};

static void register_hooks(apr_pool_t *p)
{
    ap_hook_handler(csf_handler, NULL, NULL, APR_HOOK_MIDDLE);
    ap_register_output_filter(CSF_FILTER_NAME, csf_out_filter, NULL, 1 ? AP_FTYPE_CONTENT_SET : AP_FTYPE_RESOURCE);
}

module AP_MODULE_DECLARE_DATA csfilter_module =
{
    STANDARD20_MODULE_STUFF,
    create_csf_dir_config, /* create per-directory config structure */
    NULL,                  /* merge per-directory config structures */
    NULL,                  /* create per-server config structure */
    NULL,                  /* merge per-server config structures */
    csf_cmds,              /* command apr_table_t */
    register_hooks         /* register hooks */
};

ライセンス表記ははずしてしまいましたが,Apache に準じます。