static ngx_int_t
ngx_http_subs_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_int_t		           rc;
    ngx_log_t                 *log;
    ngx_chain_t               *cl, *temp;
    ngx_http_subs_ctx_t       *ctx;
    ngx_http_subs_loc_conf_t  *slcf;

    log = r->connection->log;

    slcf = ngx_http_get_module_loc_conf(r, ngx_http_subs_filter_module);
    if (slcf == NULL) {
        return ngx_http_next_body_filter(r, in);
    }

    ctx = ngx_http_get_module_ctx(r, ngx_http_subs_filter_module);
    if (ctx == NULL) {
        return ngx_http_next_body_filter(r, in);
    }

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
                   "http subs filter \"%V\"", &r->uri);

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

    if (ngx_http_subs_body_filter_init_context(r, in) != NGX_OK){
        goto failed;
    }

    for (cl = ctx->in; cl; cl = cl->next) {

        if (cl->buf->last_buf || cl->buf->last_in_chain){
            ctx->last = 1;
        }

        /* TODO: check the flush flag */
        rc = ngx_http_subs_body_filter_process_buffer(r, cl->buf);

        if (rc == NGX_DECLINED) {
            continue;
        } else if (rc == NGX_ERROR) {
            goto failed;
        }

        if (cl->next != NULL) {
            continue;
        }

        if (ctx->last) {

            /* copy line_in to ctx->out. */
            if (ngx_buf_size(ctx->line_in) > 0) {

                ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0,
                    "[subs_filter] Lost last linefeed, output anyway.");

                if (ngx_http_subs_out_chain_append(r, ctx, ctx->line_in)
                    != NGX_OK) {
                    goto failed;
                }
            }

            if (ctx->out_buf == NULL) {

                ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0,
                               "[subs_filter] The last buffer is zero size.");

                /*
                 * This is a zero buffer, it should not be set the temporary
                 * or memory flag
                 * */
                ctx->out_buf = ngx_calloc_buf(r->pool);
                if (ctx->out_buf == NULL) {
                    goto failed;
                }

                ctx->out_buf->sync = 1;

                temp = ngx_alloc_chain_link(r->pool);
                if (temp == NULL) {
                    goto failed;
                }

                temp->buf = ctx->out_buf;
                temp->next = NULL;

                *ctx->last_out = temp;
                ctx->last_out = &temp->next;
            }

            ctx->out_buf->last_buf = (r == r->main) ? 1 : 0;
            ctx->out_buf->last_in_chain = cl->buf->last_in_chain;

            break;
        }
    }

    /* It doesn't output anything, return */
    if ((ctx->out == NULL) && (ctx->busy == NULL)) {
        return NGX_OK;
    }

    return ngx_http_subs_output(r, ctx, in);

failed:

    ngx_log_error(NGX_LOG_ERR, log, 0,
                  "[subs_filter] ngx_http_subs_body_filter error.");

    return NGX_ERROR;
}
/*
 * Do the substitutions from ctx->line_in
 * and output the chain buffers to ctx->out
 * */
static ngx_int_t
ngx_http_subs_match(ngx_http_request_t *r, ngx_http_subs_ctx_t *ctx)
{
    ngx_buf_t   *src, *dst, *temp;
    ngx_log_t   *log;
    ngx_int_t    count, match_count;
    sub_pair_t  *pairs, *pair;
    ngx_uint_t   i;

    count = 0;
    match_count = 0;

    log = r->connection->log;

    src = ctx->line_in;
    dst = ctx->line_dst;

    pairs = (sub_pair_t *) ctx->sub_pairs->elts;
    for (i = 0; i < ctx->sub_pairs->nelts; i++) {

        pair = &pairs[i];

        if (!pair->has_captured) {
            if (pair->sub.data == NULL) {
                if (ngx_http_script_run(r, &pair->sub, pair->sub_lengths->elts,
                                        0, pair->sub_values->elts) == NULL)
                {
                    goto failed;
                }
            }

        } else {
            pair->sub.data = NULL;
            pair->sub.len = 0;
        }

        /* exchange the src and dst buffer */
        if (dst->pos != dst->last) {

            temp = src;
            src = dst;
            dst = temp;

            ngx_buffer_init(dst);
        }

        if ((!pair->regex)
             && ((ngx_uint_t)(src->last - src->pos) < pair->match.len)) {
            continue;
        }

        if (pair->once && pair->matched) {
            continue;
        }

        if (pair->sub.data == NULL && !pair->has_captured) {

            if (ngx_http_script_run(r, &pair->sub, pair->sub_lengths->elts, 0,
                                    pair->sub_values->elts) == NULL)
            {
                goto failed;
            }
        }

        /* regex substitution */
        if (pair->regex || pair->insensitive) {
            count = ngx_http_subs_match_regex_substituion(r, pair, src, dst);
            if (count == NGX_ERROR) {
                goto failed;
            }

        } else {
            /* fixed string substituion */
            count = ngx_http_subs_match_fix_substituion(r, pair, src, dst);
            if (count == NGX_ERROR) {
                goto failed;
            }
        }

        /* no match. */
        if (count == 0){
            continue;
        }

        if (src->pos < src->last) {

            if (buffer_append_string(dst, src->pos, src->last - src->pos,
                                     r->pool) == NULL) {
                goto failed;
            }

            src->pos = src->last;
        }

        /* match */
        match_count += count;
    }

    /* no match last time */
    if (dst->pos == dst->last){
        dst = src;
    }

    if (ngx_http_subs_out_chain_append(r, ctx, dst) != NGX_OK) {
        goto failed;
    }

    ngx_buffer_init(ctx->line_in);
    ngx_buffer_init(ctx->line_dst);

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "match counts: %i", match_count);

    return match_count;

failed:

    ngx_log_error(NGX_LOG_ERR, log, 0,
                  "[subs_filter] ngx_http_subs_match error.");

    return -1;
}
static ngx_int_t 
ngx_http_subs_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_int_t		           rc; 
    ngx_log_t                 *log;
    ngx_chain_t               *cl, *ll;
    ngx_http_subs_ctx_t       *ctx;
    ngx_http_subs_loc_conf_t  *slcf;

    ll = NULL;
    log = r->connection->log;

    slcf = ngx_http_get_module_loc_conf(r, ngx_http_subs_filter_module);
    if (slcf == NULL) {
        return ngx_http_next_body_filter(r, in);
    }

    ctx = ngx_http_get_module_ctx(r, ngx_http_subs_filter_module);
    if (ctx == NULL) {
        return ngx_http_next_body_filter(r, in);
    }

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, 
                   "http subs filter \"%V\"", &r->uri);

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

    if (ngx_http_subs_body_filter_init_context(r, in) != NGX_OK){
        goto failed;
    }

    for (cl = ctx->in; cl; cl = cl->next) {

        if (cl->buf->last_buf || cl->buf->last_in_chain){
            ctx->last = 1;
        }

        /* TODO: check the flush tag */
        rc = ngx_http_subs_body_filter_process_buffer(r, cl->buf);

        if (rc == NGX_DECLINED) {
            continue;
        }
        else if (rc == NGX_ERROR) {
            goto failed;
        }

        if (cl->next != NULL) {
            continue;
        }

        if (ctx->last) {

            /* copy line_in to ctx->out. */
            if (ngx_buf_size(ctx->line_in) > 0) {

                ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0,
                        "[subs_filter] Lost last linefeed, output anyway.");

                if (ngx_http_subs_out_chain_append(r, ctx, ctx->line_in)
                    != NGX_OK) {
                    goto failed;
                }
            }

            if (ctx->out_buf == NULL) {
                if (ngx_http_subs_get_chain_buf(r, ctx) != NGX_OK) {
                    goto failed;
                }
            }

            ctx->out_buf->sync = 1;
            ctx->out_buf->last_buf = (r == r->main) ? 1 : 0;
            ctx->out_buf->last_in_chain = cl->buf->last_in_chain;

            break;
        }
    }

    /* It doesn't output anything, return */
    if ((ctx->last_out == &ctx->out) && (ctx->busy == NULL)) {
        return NGX_OK;
    }

    ngx_http_subs_body_filter_clean_context(r, ctx);

    return ngx_http_subs_output(r, ctx, in);

failed:

    ngx_http_subs_body_filter_clean_context(r, ctx);

    ngx_log_error(NGX_LOG_ERR, log, 0, 
                  "[subs_filter] ngx_http_subs_body_filter error.");

    return NGX_ERROR;
}