view src/http/modules/ngx_http_ssi_filter.c @ 489:45a460f82aec release-0.1.19

nginx-0.1.19-RELEASE import *) Bugfix: now, if request contains the zero, then the 404 error is returned for the local requests. *) Bugfix: nginx could not be built on NetBSD 2.0. *) Bugfix: the timeout may occur while reading of the the client request body via SSL connections.
author Igor Sysoev <igor@sysoev.ru>
date Wed, 16 Feb 2005 13:40:36 +0000
parents 42d11f017717
children d7c90bb5ce83
line wrap: on
line source


/*
 * Copyright (C) Igor Sysoev
 */


#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>


#define NGX_HTTP_SSI_COMMAND_LEN      31
#define NGX_HTTP_SSI_PARAM_LEN        31

#define NGX_HTTP_SSI_COPY             1
#define NGX_HTTP_SSI_INVALID_COMMAND  2
#define NGX_HTTP_SSI_INVALID_PARAM    3
#define NGX_HTTP_SSI_INVALID_VALUE    4
#define NGX_HTTP_SSI_LONG_VALUE       5


typedef struct {
    ngx_flag_t        enable;
    size_t            value_len;
} ngx_http_ssi_conf_t;


typedef struct {
    int               dummy;
} ngx_http_ssi_command_t;


typedef struct {
    ngx_hunk_t        *buf;

    u_char            *start;
    u_char            *last;
    u_char            *pos;

    ngx_str_t          command;
    ngx_array_t        params;
    ngx_table_elt_t   *param;

    ngx_chain_t       *in;
    ngx_chain_t       *out;
    ngx_chain_t      **last_out;
    ngx_chain_t       *busy;

    ngx_uint_t         state;
    size_t             saved;
} ngx_http_ssi_ctx_t;


typedef ngx_int_t (*ngx_http_ssi_opcode_pt) (ngx_http_request_t *r,
                                             ngx_http_ssi_ctx_t *ctx);

typedef struct {
    ngx_str_t               name;
    ngx_http_ssi_opcode_pt  op;
} ngx_http_ssi_op_t;


typedef enum {
    ssi_start_state = 0,
    ssi_tag_state,
    ssi_comment0_state,
    ssi_comment1_state,
    ssi_sharp_state,
    ssi_precommand_state,
    ssi_command_state,
    ssi_preparam_state,
    ssi_param_state,
    ssi_preequal_state,
    ssi_prevalue_state,
    ssi_double_quoted_value_state,
    ssi_double_quoted_value_quote_state,
    ssi_quoted_value_state,
    ssi_quoted_value_quote_state,
    ssi_comment_end0_state,
    ssi_comment_end1_state,
    ssi_error_state,
    ssi_error_end0_state,
    ssi_error_end1_state
} ngx_http_ssi_state_e;


static ngx_int_t ngx_http_ssi_error(ngx_http_request_t *r,
                                    ngx_http_ssi_ctx_t *ctx);
static ngx_int_t ngx_http_ssi_parse(ngx_http_request_t *r,
                                    ngx_http_ssi_ctx_t *ctx);

static ngx_int_t ngx_http_ssi_echo(ngx_http_request_t *r,
                                   ngx_http_ssi_ctx_t *ctx);

static void *ngx_http_ssi_create_conf(ngx_conf_t *cf);
static char *ngx_http_ssi_merge_conf(ngx_conf_t *cf,
                                     void *parent, void *child);
static int ngx_http_ssi_filter_init(ngx_cycle_t *cycle);


static ngx_http_ssi_op_t  ngx_http_ssi_commands[] = {
    { ngx_string("echo"), ngx_http_ssi_echo },
    { ngx_null_string, NULL }
};


static ngx_command_t  ngx_http_ssi_filter_commands[] = {

    { ngx_string("ssi"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_ssi_conf_t, enable),
      NULL },

     ngx_null_command
};


    
static ngx_http_module_t  ngx_http_ssi_filter_module_ctx = {
    NULL,                                  /* pre conf */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    ngx_http_ssi_create_conf,              /* create location configuration */
    ngx_http_ssi_merge_conf                /* merge location configuration */
};  


ngx_module_t  ngx_http_ssi_filter_module = {
    NGX_MODULE,
    &ngx_http_ssi_filter_module_ctx,       /* module context */
    ngx_http_ssi_filter_commands,          /* module directives */
    NGX_HTTP_MODULE,                       /* module type */
    ngx_http_ssi_filter_init,              /* init module */
    NULL                                   /* init child */
};


static int (*ngx_http_next_header_filter) (ngx_http_request_t *r);
static int (*ngx_http_next_body_filter) (ngx_http_request_t *r,
                                         ngx_chain_t *in);


static char ssi_string[] = "<!--#";
static char error_string[] = "[an error occurred while processing "
                             "the directive]";
static char stub_string[] = "[a stub ssi string]";


static int ngx_http_ssi_header_filter(ngx_http_request_t *r)
{
    ngx_http_ssi_ctx_t   *ctx;
    ngx_http_ssi_conf_t  *conf;

    conf = ngx_http_get_module_loc_conf(r, ngx_http_ssi_filter_module);

    if (!conf->enable) {
        return ngx_http_next_header_filter(r);
    }

    /* TODO: "text/html" -> custom types */

    if (r->headers_out.content_type
        && ngx_strncasecmp(r->headers_out.content_type->value.data,
                                                          "text/html", 5) != 0)
    {
        return ngx_http_next_header_filter(r);
    }

    ngx_http_create_ctx(r, ctx, ngx_http_ssi_filter_module,
                        sizeof(ngx_http_ssi_ctx_t), NGX_ERROR);

    ctx->last_out = &ctx->out;

    r->headers_out.content_length_n = -1;
    if (r->headers_out.content_length) {
        r->headers_out.content_length->key.len = 0;
        r->headers_out.content_length = NULL;
    }

    r->headers_out.last_modified_time = -1;
    if (r->headers_out.last_modified) {
        r->headers_out.last_modified->key.len = 0;
        r->headers_out.last_modified = NULL;
    }

    r->filter |= NGX_HTTP_FILTER_NEED_IN_MEMORY;

    return ngx_http_next_header_filter(r);
}


static int ngx_http_ssi_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    size_t               len;
    ngx_int_t            rc, found;
    ngx_hunk_t          *hunk;
    ngx_chain_t         *cl, *tl;
    ngx_http_ssi_op_t   *cmd;
    ngx_http_ssi_ctx_t  *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_http_ssi_filter_module);

    if (ctx == NULL || (in == NULL && ctx->in == NULL)) {
        return ngx_http_next_body_filter(r, in);
    }

    /* add the incoming hunk to the chain ctx->in */

    if (in) {
        if (ngx_chain_add_copy(r->pool, &ctx->in, in) == NGX_ERROR) {
            return NGX_ERROR;
        }
    }

    while (ctx->in) {
        if (ctx->buf == NULL) {
            ctx->buf = ctx->in->hunk;
            ctx->in = ctx->in->next;

            ctx->start = ctx->buf->pos;
            ctx->pos = ctx->buf->pos;
            ctx->last = ctx->buf->pos;

            if (ctx->saved) {
                len = ctx->buf->last - ctx->buf->pos;
                if (len > 5 - ctx->saved) {
                    len = 5 - ctx->saved;
                }

                if (ngx_strncmp(ctx->buf->pos, &ssi_string[ctx->saved], len)
                                                                          == 0)
                {
                    if (len < 5 - ctx->saved) {
                        ctx->buf = NULL;
                        continue;

                    } else {
                        ctx->saved = 0;
                        ctx->pos += len;
                        ctx->state = ssi_precommand_state;
                    }
                }
            }
        }

        while (ctx->pos < ctx->buf->last) {

            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "saved: %d state: %d", ctx->saved, ctx->state);

            rc = ngx_http_ssi_parse(r, ctx);

            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "parse: %d", rc);

            if (rc == NGX_AGAIN) {
                continue;

            } else if (rc == NGX_ERROR) {
                return rc;

            } else if (rc == NGX_HTTP_SSI_COPY) {

                ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                               "saved: %d copy: %d",
                               ctx->saved, ctx->last - ctx->start);

                if (ctx->saved) {

                    if (!(hunk = ngx_calloc_hunk(r->pool))) {
                        return NGX_ERROR;
                    }

                    hunk->type = NGX_HUNK_IN_MEMORY|NGX_HUNK_TEMP;
                    hunk->pos = ssi_string;
                    hunk->last = ssi_string + ctx->saved;

                    ngx_alloc_link_and_set_hunk(cl, hunk, r->pool, NGX_ERROR);

                    *ctx->last_out = cl;
                    ctx->last_out = &cl->next;

                    ctx->saved = 0;
                }

                if (!(hunk = ngx_calloc_hunk(r->pool))) {
                    return NGX_ERROR;
                }

                hunk->type = NGX_HUNK_IN_MEMORY|NGX_HUNK_TEMP|NGX_HUNK_RECYCLED;
                hunk->pos = ctx->start;
                hunk->last = ctx->last;
                hunk->shadow = ctx->buf;

                ngx_alloc_link_and_set_hunk(cl, hunk, r->pool, NGX_ERROR);

                *ctx->last_out = cl;
                ctx->last_out = &cl->next;

                if (ctx->pos == ctx->buf->last) {
                    ctx->saved = ctx->pos - ctx->last;
                }

                continue;

            } else if (rc >= NGX_HTTP_SSI_INVALID_COMMAND) {

                ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                               "saved: %d error: %d",
                               ctx->saved, rc);

                if (ngx_http_ssi_error(r, ctx) == NGX_ERROR) {
                    return NGX_ERROR;
                }

                if (rc == NGX_HTTP_SSI_INVALID_COMMAND) {
                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                                  "invalid SSI command \"%s\" found",
                                  ctx->command.data);

                } else if (rc == NGX_HTTP_SSI_INVALID_PARAM) {
                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                                  "invalid parameter \"%s\" found "
                                  "in SSI command \"%s\"",
                                  ctx->param->key.data, ctx->command.data);
                }

#if 0
            } else if (rc == NGX_HTTP_SSI_INVALID_COMMAND) {
            } else if (rc == NGX_HTTP_SSI_INVALID_PARAM) {
            } else if (rc == NGX_HTTP_SSI_INVALID_VALUE) {
            } else if (rc == NGX_HTTP_SSI_LONG_VALUE) {
#endif

            } else {

                found = 0;

                for (cmd = ngx_http_ssi_commands; cmd->name.len; cmd++) {
                     if (ctx->command.len != cmd->name.len) {
                         continue;
                     }

                     if (ngx_strncmp(ctx->command.data, cmd->name.data,
                                                           cmd->name.len) != 0)
                     {
                         continue;
                     }

                     found = 1;

                     if (cmd->op(r, ctx) == NGX_ERROR) {
                         return NGX_ERROR;
                     }
                }

                if (!found) {
                    if (ngx_http_ssi_error(r, ctx) == NGX_ERROR) {
                        return NGX_ERROR;
                    }
                }

#if 0
                if (!(hunk = ngx_calloc_hunk(r->pool))) {
                    return NGX_ERROR;
                }

                hunk->type = NGX_HUNK_IN_MEMORY|NGX_HUNK_TEMP;
                hunk->pos = stub_string;
                hunk->last = stub_string + sizeof(stub_string) - 1;

                ngx_alloc_link_and_set_hunk(cl, hunk, r->pool, NGX_ERROR);

                *ctx->last_out = cl;
                ctx->last_out = &cl->next;
#endif
            }
        }

        ctx->buf = NULL;
    }

    if (ctx->out) {
        if (ngx_http_next_body_filter(r, ctx->out) == NGX_ERROR) {
            return NGX_ERROR;
        }

        if (ctx->busy == NULL) {
            ctx->busy = ctx->out;

        } else {
            for (tl = ctx->busy; /* void */ ; tl = tl->next) {
                if (tl->next == NULL) { 
                    tl->next = ctx->out;
                    break;
                }
            }
        }
    
        ctx->out = NULL;
        ctx->last_out = &ctx->out;

        while (ctx->busy) {
            if (ngx_hunk_size(ctx->busy->hunk) != 0) {
                break;
            }

            /* TODO: NGX_HUNK_ZEROCOPY_BUSY */

            hunk = ctx->busy->hunk->shadow;
            if (hunk) {
                hunk->pos = ctx->busy->hunk->pos;

                len = hunk->last - hunk->pos;
                if (len < 5 && ngx_strncmp(hunk->pos, ssi_string, len) == 0) {
                    hunk->pos = hunk->last;
                }
            }

            ctx->busy = ctx->busy->next;
        }
    }

    return NGX_OK;
}


static ngx_int_t ngx_http_ssi_error(ngx_http_request_t *r,
                                    ngx_http_ssi_ctx_t *ctx)
{
    ngx_hunk_t   *hunk;
    ngx_chain_t  *cl;

    if (!(hunk = ngx_calloc_hunk(r->pool))) {
        return NGX_ERROR;
    }

    hunk->type = NGX_HUNK_IN_MEMORY|NGX_HUNK_TEMP;
    hunk->pos = error_string;
    hunk->last = error_string + sizeof(error_string) - 1;

    ngx_alloc_link_and_set_hunk(cl, hunk, r->pool, NGX_ERROR);

    *ctx->last_out = cl;
    ctx->last_out = &cl->next;

    return NGX_OK;
}


#if 0

static int ngx_http_ssi_copy_opcode(ngx_http_request_t *r,
                                    ngx_http_ssi_ctx_t *ctx, void *data)
{
    ngx_http_ssi_copy_t *copy = data;

    ngx_hunk_t   *h;
    ngx_chain_t   chain;

    h = ctx->incoming->hunk;

    if (ctx->looked == 0 && ctx->pos == h->last) {
        chain.hunk = h;
        chain.next = NULL;

        return next_body_filter(r, &chain);
    }

    if (ctx->hunk == NULL) {
        ngx_test_null(ctx->hunk, ngx_calloc_hunk(r->pool), NGX_ERROR);
        ctx->hunk->type = h->type & NGX_HUNK_STORAGE;
    }


    if (h->type & NGX_HUNK_FILE) {
        if (copy->start <= h->file_pos) {
            ctx->hunk->file_pos = h->file_pos;
        } else if (copy->start < h->file_last) {
            ctx->hunk->file_pos = copy->file_pos;
        }

        if (copy->end >= h->file_last) {
            ctx->hunk->file_last = h->file_last;
        } else if (copy->end > h->file_pos) {
        }

    }

    if (h->type & NGX_HUNK_IN_MEMORY) {
        if (copy->start <= ctx->offset + (h->pos - h->start)) {
            ctx->hunk->pos = h->pos;
        } else if (copy->start < ctx->offset + (h->last - h->start)) {
            ctx->hunk->pos = h->start + (copy->start - ctx->offset);
        }

        if (copy->end >= ctx->offset + (h->last - h->start) {
            ctx->hunk->last = h->last;
        } else if (copy->end > ctx->offset + (h->pos - h->start)) {
            ctx->hunk->last = h->start + (copy->end - ctx->offset);
        }
    }

    /* TODO: NGX_HUNK_FLUSH */

    if ((h->type & NGX_HUNK_LAST) && ctx->hunk->last == h->last)

    /* LAST */
}

#endif


static ngx_int_t
ngx_http_ssi_parse(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx)
{
    u_char                *p, *last, *end, ch;
    ngx_http_ssi_conf_t   *conf;
    ngx_http_ssi_state_e   state;

    conf = ngx_http_get_module_loc_conf(r, ngx_http_ssi_filter_module);

    state = ctx->state;
    end = ctx->buf->last;
    last = NULL;

    for (p = ctx->pos; p < end; p++) {

        ch = *p;

        if (state == ssi_start_state) {

            /* the tight loop */

            for ( /* void */ ; p < end; p++) {
                if (ch != '<') {
                    continue;
                }

                last = p;
                state = ssi_tag_state;
                break;
            }

            if (p == end) {
                ctx->last = p;
                ctx->pos = p;
                ctx->state = ssi_start_state;

                return NGX_HTTP_SSI_COPY;
            }
        }

        switch (state) {

        case ssi_start_state:
            break;

        case ssi_tag_state:
            switch (ch) {
            case '!':
                state = ssi_comment0_state;
                break;

            case '<':
                last = p;
                break;

            default:
                state = ssi_start_state;
                break;
            }

            break;

        case ssi_comment0_state:
            switch (ch) {
            case '-':
                state = ssi_comment1_state;
                break;

            case '<':
                last = p;
                state = ssi_tag_state;
                break;

            default:
                state = ssi_start_state;
                break;
            }

            break;

        case ssi_comment1_state:
            switch (ch) {
            case '-':
                state = ssi_sharp_state;
                break;

            case '<':
                last = p;
                state = ssi_tag_state;
                break;

            default:
                state = ssi_start_state;
                break;
            }

            break;

        case ssi_sharp_state:
            switch (ch) {
            case '#':
                ctx->last = last;
                ctx->pos = p;
                ctx->state = ssi_precommand_state;
                break;

            case '<':
                last = p;
                state = ssi_tag_state;
                break;

            default:
                state = ssi_start_state;
                break;
            }

            break;

        case ssi_precommand_state:
            switch (ch) {
            case ' ':
            case CR:
            case LF:
            case '\t':
                break;

            default:
                ctx->command.len = 1;
                ctx->command.data = 
                                 ngx_palloc(r->pool, NGX_HTTP_SSI_COMMAND_LEN);
                if (ctx->command.data == NULL) {
                    return NGX_ERROR;
                }

                ctx->command.data[0] = ch;
                state = ssi_command_state;
                break;
            }

            break;

        case ssi_command_state:
            switch (ch) {
            case ' ':
            case CR:
            case LF:
            case '\t':
                state = ssi_preparam_state;
                break;

            case '-':
                state = ssi_comment_end0_state;
                break;

            default:
                if (ctx->command.len == NGX_HTTP_SSI_COMMAND_LEN) {
                    ctx->pos = p;
                    ctx->state = ssi_error_state;

                    return NGX_HTTP_SSI_INVALID_COMMAND;
                }

                ctx->command.data[ctx->command.len++] = ch;
            }

            break;

        case ssi_preparam_state:
            switch (ch) {
            case ' ':
            case CR:
            case LF:
            case '\t':
                break;

            case '-':
                state = ssi_comment_end0_state;
                break;

            default:
                if (ctx->params.elts == NULL) {
                    if (ngx_array_init(&ctx->params = r->pool,
                                       4, sizeof(ngx_table_elt_t)) = NGX_ERROR)
                    {
                        return NGX_ERROR;
                    }
                }

                if (!(ctx->param = ngx_array_push(&ctx->params))) {
                    return NGX_ERROR;
                }

                ctx->param->key.len = 1;
                ctx->param->key.data =
                                   ngx_palloc(r->pool, NGX_HTTP_SSI_PARAM_LEN);
                if (ctx->param->key.data == NULL) {
                    return NGX_ERROR;
                }
                ctx->param->key.data[0] = ch;

                ctx->param->value.len = 0;
                ctx->param->value.data = ngx_palloc(r->pool, conf->value_len);
                if (ctx->param->value.data == NULL) {
                    return NGX_ERROR;
                }

                state = ssi_param_state;
                break;
            }

            break;

        case ssi_param_state:
            switch (ch) {
            case ' ':
            case CR:
            case LF:
            case '\t':
                state = ssi_preequal_state;
                break;

            case '=':
                state = ssi_prevalue_state;
                break;

            case '-':
                ctx->pos = p;
                ctx->state = ssi_error_end0_state;

                return NGX_HTTP_SSI_INVALID_PARAM;

            default:
                if (ctx->param->key.len == NGX_HTTP_SSI_PARAM_LEN) {
                    ctx->pos = p;
                    ctx->state = ssi_error_state;

                    return NGX_HTTP_SSI_INVALID_PARAM;
                }

                ctx->param->key.data[ctx->param->key.len++] = ch;
            }

            break;

        case ssi_preequal_state:
            switch (ch) {
            case ' ':
            case CR:
            case LF:
            case '\t':
                break;

            case '=':
                state = ssi_prevalue_state;
                break;

            case '-':
                ctx->pos = p;
                ctx->state = ssi_error_end0_state;

                return NGX_HTTP_SSI_INVALID_PARAM;

            default:
                ctx->last = last;
                ctx->pos = p;
                ctx->state = ssi_error_state;

                return NGX_HTTP_SSI_INVALID_PARAM;
            }

            break;

        case ssi_prevalue_state:
            switch (ch) {
            case ' ':
            case CR:
            case LF:
            case '\t':
                break;

            case '"':
                state = ssi_double_quoted_value_state;
                break;

            case '\'':
                state = ssi_quoted_value_state;
                break;

            case '-':
                ctx->last = last;
                ctx->pos = p;
                ctx->state = ssi_error_end0_state;

                return NGX_HTTP_SSI_INVALID_VALUE;

            default:
                ctx->last = last;
                ctx->pos = p;
                ctx->state = ssi_error_state;

                return NGX_HTTP_SSI_INVALID_VALUE;
            }

            break;

        case ssi_double_quoted_value_state:
            switch (ch) {
            case '\\':
                state = ssi_double_quoted_value_quote_state;
                break;

            case '"':
                state = ssi_preparam_state;
                break;

            default:
                if (ctx->param->value.len >= conf->value_len) {
                    ctx->last = last;
                    ctx->pos = p;
                    ctx->state = ssi_error_state;

                    return NGX_HTTP_SSI_LONG_VALUE;
                }

                ctx->param->value.data[ctx->param->value.len++] = ch;
            }

            break;

        case ssi_double_quoted_value_quote_state:
            if (ctx->param->value.len >= conf->value_len) {
                ctx->last = last;
                ctx->pos = p;
                ctx->state = ssi_error_state;

                return NGX_HTTP_SSI_LONG_VALUE;
            }

            ctx->param->value.data[ctx->param->value.len++] = ch;

            state = ssi_double_quoted_value_state;
            break;

        case ssi_quoted_value_state:
            switch (ch) {
            case '\\':
                state = ssi_quoted_value_quote_state;
                break;

            case '\'':
                state = ssi_preparam_state;
                break;

            default:
                if (ctx->param->value.len >= conf->value_len) {
                    ctx->last = last;
                    ctx->pos = p;
                    ctx->state = ssi_error_state;

                    return NGX_HTTP_SSI_LONG_VALUE;
                }

                ctx->param->value.data[ctx->param->value.len++] = ch;
            }

            break;

        case ssi_quoted_value_quote_state:
            if (ctx->param->value.len >= conf->value_len) {
                ctx->last = last;
                ctx->pos = p;
                ctx->state = ssi_error_state;

                return NGX_HTTP_SSI_LONG_VALUE;
            }

            ctx->param->value.data[ctx->param->value.len++] = ch;

            state = ssi_quoted_value_state;
            break;

        case ssi_comment_end0_state:
            switch (ch) {
            case '-':
                state = ssi_comment_end1_state;
                break;

            default:
                ctx->last = last;
                ctx->pos = p;
                ctx->state = ssi_error_state;

                return NGX_HTTP_SSI_INVALID_COMMAND;
            }

            break;

        case ssi_comment_end1_state:
            switch (ch) {
            case '>':
                ctx->state = ssi_start_state;
                ctx->start = p;
                ctx->pos = p;

                return NGX_OK;

            default:
                ctx->last = last;
                ctx->pos = p;
                ctx->state = ssi_error_state;

                return NGX_HTTP_SSI_INVALID_COMMAND;
            }

            break;

        case ssi_error_state:
            switch (ch) {
            case '-':
                state = ssi_error_end0_state;
                break;

            default:
                break;
            }

            break;

        case ssi_error_end0_state:
            switch (ch) {
            case '-':
                state = ssi_error_end1_state;
                break;

            default:
                state = ssi_error_state;
                break;
            }

            break;

        case ssi_error_end1_state:
            switch (ch) {
            case '>':
                ctx->state = ssi_start_state;
                ctx->start = p;
                ctx->pos = p;

                return NGX_AGAIN;

            default:
                state = ssi_error_state;
                break;
            }

            break;
        }
    }

    ctx->state = state;
    ctx->last = last;
    ctx->pos = p;

    return NGX_HTTP_SSI_COPY;
}


static ngx_int_t ngx_http_ssi_echo(ngx_http_request_t *r,
                                   ngx_http_ssi_ctx_t *ctx)
{
    return NGX_OK;
}


static void *ngx_http_ssi_create_conf(ngx_conf_t *cf)
{
    ngx_http_ssi_conf_t  *conf;

    if (!(conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_ssi_conf_t)))) {
        return NGX_CONF_ERROR;
    }

    conf->enable = NGX_CONF_UNSET;
    conf->value_len = NGX_CONF_UNSET_SIZE;

    return conf;
}


static char *ngx_http_ssi_merge_conf(ngx_conf_t *cf,
                                     void *parent, void *child)
{
    ngx_http_ssi_conf_t *prev = parent;
    ngx_http_ssi_conf_t *conf = child;

    ngx_conf_merge_value(conf->enable, prev->enable, 0);
    ngx_conf_merge_size_value(conf->value_len, prev->value_len, 256);

    return NGX_CONF_OK;
}


static int ngx_http_ssi_filter_init(ngx_cycle_t *cycle)
{
    ngx_http_next_header_filter = ngx_http_top_header_filter;
    ngx_http_top_header_filter = ngx_http_ssi_header_filter;

    ngx_http_next_body_filter = ngx_http_top_body_filter;
    ngx_http_top_body_filter = ngx_http_ssi_body_filter;

    return NGX_OK;
}