static int
ngx_http_lua_ngx_echo(lua_State *L, unsigned newline)
{
    ngx_http_request_t          *r;
    ngx_http_lua_ctx_t          *ctx;
    const char                  *p;
    size_t                       len;
    size_t                       size;
    ngx_buf_t                   *b;
    ngx_chain_t                 *cl;
    ngx_int_t                    rc;
    int                          i;
    int                          nargs;
    int                          type;
    const char                  *msg;
    ngx_buf_tag_t                tag;

    lua_pushlightuserdata(L, &ngx_http_lua_request_key);
    lua_rawget(L, LUA_GLOBALSINDEX);
    r = lua_touserdata(L, -1);
    lua_pop(L, 1);

    if (r == NULL) {
        return luaL_error(L, "no request object found");
    }

    ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module);

    if (ctx == NULL) {
        return luaL_error(L, "no request ctx found");
    }

    ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE
                               | NGX_HTTP_LUA_CONTEXT_ACCESS
                               | NGX_HTTP_LUA_CONTEXT_CONTENT);

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

    if (ctx->eof) {
        return luaL_error(L, "seen eof already");
    }

    nargs = lua_gettop(L);
    size = 0;

    for (i = 1; i <= nargs; i++) {

        type = lua_type(L, i);

        switch (type) {
            case LUA_TNUMBER:
            case LUA_TSTRING:

                lua_tolstring(L, i, &len);
                size += len;
                break;

            case LUA_TNIL:

                size += sizeof("nil") - 1;
                break;

            case LUA_TBOOLEAN:

                if (lua_toboolean(L, i)) {
                    size += sizeof("true") - 1;

                } else {
                    size += sizeof("false") - 1;
                }

                break;

            case LUA_TTABLE:

                size += ngx_http_lua_calc_strlen_in_table(L, i, i,
                                                          0 /* strict */);
                break;

            case LUA_TLIGHTUSERDATA:

                dd("userdata: %p", lua_touserdata(L, i));

                if (lua_touserdata(L, i) == NULL) {
                    size += sizeof("null") - 1;
                    break;
                }

                continue;

            default:

                msg = lua_pushfstring(L, "string, number, boolean, nil, "
                                      "ngx.null, or array table expected, "
                                      "but got %s", lua_typename(L, type));

                return luaL_argerror(L, i, msg);
        }
    }

    if (newline) {
        size += sizeof("\n") - 1;
    }

    if (size == 0) {
        /* do nothing for empty strings */
        return 0;
    }

    tag = (ngx_buf_tag_t) &ngx_http_lua_module;

    cl = ngx_http_lua_chains_get_free_buf(r->connection->log, r->pool,
                                          &ctx->free_bufs, size, tag);

    if (cl == NULL) {
        return luaL_error(L, "out of memory");
    }

    b = cl->buf;

    for (i = 1; i <= nargs; i++) {
        type = lua_type(L, i);
        switch (type) {
            case LUA_TNUMBER:
            case LUA_TSTRING:
                p = lua_tolstring(L, i, &len);
                b->last = ngx_copy(b->last, (u_char *) p, len);
                break;

            case LUA_TNIL:
                *b->last++ = 'n';
                *b->last++ = 'i';
                *b->last++ = 'l';
                break;

            case LUA_TBOOLEAN:
                if (lua_toboolean(L, i)) {
                    *b->last++ = 't';
                    *b->last++ = 'r';
                    *b->last++ = 'u';
                    *b->last++ = 'e';

                } else {
                    *b->last++ = 'f';
                    *b->last++ = 'a';
                    *b->last++ = 'l';
                    *b->last++ = 's';
                    *b->last++ = 'e';
                }

                break;

            case LUA_TTABLE:
                b->last = ngx_http_lua_copy_str_in_table(L, i, b->last);
                break;

            case LUA_TLIGHTUSERDATA:
                *b->last++ = 'n';
                *b->last++ = 'u';
                *b->last++ = 'l';
                *b->last++ = 'l';
                break;

            default:
                return luaL_error(L, "impossible to reach here");
        }
    }

    if (newline) {
        *b->last++ = '\n';
    }

#if 0
    if (b->last != b->end) {
        return luaL_error(L, "buffer error: %p != %p", b->last, b->end);
    }
#endif

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   newline ? "lua say response" : "lua print response");

    rc = ngx_http_lua_send_chain_link(r, ctx, cl);

    if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) {
        return luaL_error(L, "failed to send data through the output filters");
    }

    dd("downstream write: %d, buf len: %d", (int) rc,
            (int) (b->last - b->pos));

    if (!ctx->out) {
#if nginx_version >= 1001004
        ngx_chain_update_chains(r->pool,
#else
        ngx_chain_update_chains(
#endif
                                &ctx->free_bufs, &ctx->busy_bufs, &cl, tag);

        dd("out lua buf tag: %p, buffered: %x, busy bufs: %p",
            &ngx_http_lua_module, (int) r->connection->buffered,
            ctx->busy_bufs);
    }
int
ngx_http_lua_body_filter_param_set(lua_State *L, ngx_http_request_t *r,
        ngx_http_lua_ctx_t *ctx)
{
    int                      type;
    int                      idx;
    u_char                  *data;
    size_t                   size;
    unsigned                 last;
    ngx_chain_t             *cl;
    ngx_chain_t             *in;
    ngx_buf_tag_t            tag;

    idx = luaL_checkint(L, 2);

    dd("index: %d", idx);

    if (idx != 1 && idx != 2) {
        return luaL_error(L, "bad index: %d", idx);
    }

    if (idx == 2) {
        /* overwriting the eof flag */
        last = lua_toboolean(L, 3);

        lua_pushlightuserdata(L, &ngx_http_lua_body_filter_chain_key);
        lua_rawget(L, LUA_GLOBALSINDEX);
        in = lua_touserdata(L, -1);

        if (last) {
            if (in) {
                for (cl = in; cl; cl = cl->next) {
                    if (cl->next == NULL) {
                        cl->buf->last_buf = 1;
                        break;
                    }
                }

            } else {
                tag = (ngx_buf_tag_t) &ngx_http_lua_module;

                cl = ngx_http_lua_chains_get_free_buf(r->connection->log,
                                                      r->pool, &ctx->free_bufs,
                                                      0, tag);

                if (cl == NULL) {
                    return luaL_error(L, "out of memory");
                }

                cl->buf->last_buf = 1;

                lua_pushlightuserdata(L, &ngx_http_lua_body_filter_chain_key);
                lua_pushlightuserdata(L, cl);
                lua_rawset(L, LUA_GLOBALSINDEX);
            }

        } else {
            /* last == 0 */

            if (in) {
                for (size = 0, cl = in; cl; cl = cl->next) {
                    if (cl->buf->last_buf) {
                        cl->buf->last_buf = 0;
                    }

                    size += cl->buf->last - cl->buf->pos;
                }

                if (size == 0) {
                    lua_pushlightuserdata(L,
                                          &ngx_http_lua_body_filter_chain_key);
                    lua_pushlightuserdata(L, NULL);
                    lua_rawset(L, LUA_GLOBALSINDEX);
                }
            }
        }

        return 0;
    }

    /* idx == 1, overwriting the chunk data */

    type = lua_type(L, 3);

    switch (type) {
    case LUA_TSTRING:
    case LUA_TNUMBER:
        data = (u_char *) lua_tolstring(L, 3, &size);
        break;

    case LUA_TNIL:
        /* discard the buffers */
        lua_pushlightuserdata(L, &ngx_http_lua_body_filter_chain_key); /* key */
        lua_pushvalue(L, -1); /* key key */
        lua_rawget(L, LUA_GLOBALSINDEX); /* key val */
        in = lua_touserdata(L, -1);
        lua_pop(L, 1); /* key */

        for (cl = in; cl; cl = cl->next) {
            dd("mark the buf as consumed: %d", (int) ngx_buf_size(cl->buf));
            cl->buf->pos = cl->buf->last;
        }

        lua_pushlightuserdata(L, NULL); /* key val */
        lua_rawset(L, LUA_GLOBALSINDEX);
        return 0;

    case LUA_TTABLE:
        size = ngx_http_lua_calc_strlen_in_table(L, 3 /* index */, 3 /* arg */,
                                                 1 /* strict */);
        data = NULL;
        break;

    default:
        return luaL_error(L, "bad chunk data type: %s",
                          lua_typename(L, type));
    }

    lua_pushlightuserdata(L, &ngx_http_lua_body_filter_chain_key);
    lua_rawget(L, LUA_GLOBALSINDEX);
    in = lua_touserdata(L, -1);
    lua_pop(L, 1);

    last = 0;
    for (cl = in; cl; cl = cl->next) {
        if (cl->buf->last_buf) {
            last = 1;
        }

        dd("mark the buf as consumed: %d", (int) ngx_buf_size(cl->buf));
        cl->buf->pos = cl->buf->last;
    }

    if (size == 0) {
        if (last) {
            if (in) {
                in->buf->last_buf = 1;

            } else {

                tag = (ngx_buf_tag_t) &ngx_http_lua_module;

                cl = ngx_http_lua_chains_get_free_buf(r->connection->log,
                                                      r->pool, &ctx->free_bufs,
                                                      0, tag);

                if (cl == NULL) {
                    return luaL_error(L, "out of memory");
                }

                cl->buf->last_buf = 1;

                lua_pushlightuserdata(L, &ngx_http_lua_body_filter_chain_key);
                lua_pushlightuserdata(L, cl);
                lua_rawset(L, LUA_GLOBALSINDEX);
            }
        }

        return 0;
    }

    tag = (ngx_buf_tag_t) &ngx_http_lua_module;

    cl = ngx_http_lua_chains_get_free_buf(r->connection->log, r->pool,
                                          &ctx->free_bufs, size, tag);
    if (cl == NULL) {
        return luaL_error(L, "out of memory");
    }

    if (type == LUA_TTABLE) {
        cl->buf->last = ngx_http_lua_copy_str_in_table(L, 3, cl->buf->last);

    } else {
        cl->buf->last = ngx_copy(cl->buf->pos, data, size);
    }

    cl->buf->last_buf = last;

    lua_pushlightuserdata(L, &ngx_http_lua_body_filter_chain_key);
    lua_pushlightuserdata(L, cl);
    lua_rawset(L, LUA_GLOBALSINDEX);
    return 0;
}
int
ngx_http_lua_body_filter_param_set(lua_State *L, ngx_http_request_t *r,
    ngx_http_lua_ctx_t *ctx)
{
    int                      type;
    int                      idx;
    int                      found;
    u_char                  *data;
    size_t                   size;
    unsigned                 last;
    ngx_chain_t             *cl;
    ngx_chain_t             *in;
    ngx_buf_tag_t            tag;

    idx = luaL_checkint(L, 2);

    dd("index: %d", idx);

    if (idx != 1 && idx != 2) {
        return luaL_error(L, "bad index: %d", idx);
    }

    if (idx == 2) {
        /* overwriting the eof flag */
        last = lua_toboolean(L, 3);

        lua_getglobal(L, ngx_http_lua_chain_key);
        in = lua_touserdata(L, -1);

        if (last) {
            ctx->seen_last_in_filter = 1;

            for (cl = in; cl; cl = cl->next) {
                if (cl->next == NULL) {
                    if (r == r->main) {
                        cl->buf->last_buf = 1;

                    } else {
                        cl->buf->last_in_chain = 1;
                    }

                    break;
                }
            }

        } else {
            /* last == 0 */

            found = 0;

            for (cl = in; cl; cl = cl->next) {
                if (cl->buf->last_buf) {
                    cl->buf->last_buf = 0;
                    found = 1;
                }

                if (cl->buf->last_in_chain) {
                    cl->buf->last_in_chain = 0;
                    found = 1;
                }

                if (found && cl->buf->last - cl->buf->pos == 0) {
                    /* make it a special sync buf to make
                     * ngx_http_write_filter_module happy. */

                    cl->buf->temporary = 0;
                    cl->buf->memory = 0;
                    cl->buf->mmap = 0;
                    cl->buf->in_file = 0;
                    cl->buf->sync = 1;
                }
            }
        }

        return 0;
    }

    /* idx == 1, overwriting the chunk data */

    type = lua_type(L, 3);

    switch (type) {
    case LUA_TSTRING:
    case LUA_TNUMBER:
        data = (u_char *) lua_tolstring(L, 3, &size);
        break;

    case LUA_TNIL:
        /* discard the buffers */
        lua_getglobal(L, ngx_http_lua_chain_key); /* key val */
        in = lua_touserdata(L, -1);
        lua_pop(L, 1);
        lua_pushliteral(L, ngx_http_lua_chain_key); /* key */

        for (cl = in; cl; cl = cl->next) {
            dd("mark the buf as consumed: %d", (int) ngx_buf_size(cl->buf));
            cl->buf->pos = cl->buf->last;

            /* will later be skipped by ngx_http_lua_body_filter()
             * and never be forwarded to the next filters. */
        }

        return 0;

    case LUA_TTABLE:
        size = ngx_http_lua_calc_strlen_in_table(L, 3 /* index */, 3 /* arg */,
                                                 1 /* strict */);
        data = NULL;
        break;

    default:
        return luaL_error(L, "bad chunk data type: %s",
                          lua_typename(L, type));
    }

    lua_getglobal(L, ngx_http_lua_chain_key);
    in = lua_touserdata(L, -1);
    lua_pop(L, 1);

    last = 0;
    for (cl = in; cl; cl = cl->next) {
        if (cl->buf->last_buf || cl->buf->last_in_chain) {
            last = 1;
        }

        dd("mark the buf as consumed: %d", (int) ngx_buf_size(cl->buf));
        cl->buf->pos = cl->buf->last;

        /* will later be skipped by ngx_http_lua_body_filter() and
         * never be forwarded to the next filters */
    }

    if (size == 0 && !last) {
        return 0;
    }

    tag = (ngx_buf_tag_t) &ngx_http_lua_module;

    cl = ngx_http_lua_chains_get_free_buf(r->connection->log, r->pool,
                                          &ctx->free_bufs, size, tag);
    if (cl == NULL) {
        return luaL_error(L, "out of memory");
    }

    if (size == 0) {

        /* "last" must already be set */

        if (r == r->main) {
            cl->buf->last_buf = 1;

        } else {
            cl->buf->last_in_chain = 1;
        }

        return 0;
    }

    if (type == LUA_TTABLE) {
        cl->buf->last = ngx_http_lua_copy_str_in_table(L, 3, cl->buf->last);

    } else {
        cl->buf->last = ngx_copy(cl->buf->pos, data, size);
    }

    if (last) {
        ctx->seen_last_in_filter = 1;

        if (r == r->main) {
            cl->buf->last_buf = 1;

        } else {
            cl->buf->last_in_chain = 1;
        }
    }

    lua_pushlightuserdata(L, cl);
    lua_setglobal(L, ngx_http_lua_chain_key);
    return 0;
}