# vim:set ft= ts=4 sw=4 et fdm=marker:

use Test::Nginx::Socket::Lua;
use Test::Nginx::Util 'is_tcp_port_used';

#master_on();
#workers(1);
#worker_connections(1014);
#log_level('warn');
#master_process_enabled(1);

no_root_location;
repeat_each(2);

plan tests => repeat_each() * (blocks() * 3 + 22);

$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211;
$ENV{TEST_NGINX_HTML_DIR} ||= html_dir();

# NB: tcp_listen_port needs to be greater than 10000,
# because the test cases expect it to be a 5-digit number
my $tcp_listen_port = 19113;
while (++$tcp_listen_port < 65535) {
    if (!is_tcp_port_used $tcp_listen_port) {
        last;
    }
}
$ENV{TEST_NGINX_TCP_LISTEN_PORT} = $tcp_listen_port;

#no_diff();
no_long_string();
#no_shuffle();

run_tests();

__DATA__

=== TEST 1: DELETE
--- config
    location /other {
        default_type 'foo/bar';
        echo $echo_request_method;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/other",
                { method = ngx.HTTP_DELETE });

            ngx.print(res.body)
        ';
    }
--- request
GET /lua
--- response_body
DELETE
--- error_log
lua http subrequest "/other?"
--- no_error_log
[error]



=== TEST 2: DELETE (proxy method)
--- config
    location /other {
        default_type 'foo/bar';
        echo $echo_request_method;
    }

    location /foo {
        proxy_pass http://127.0.0.1:$server_port/other;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/foo",
                { method = ngx.HTTP_DELETE });

            ngx.print(res.body)
        ';
    }
--- request
GET /lua
--- response_body
DELETE
--- no_error_log
[error]



=== TEST 3: POST (nobody, proxy method)
--- config
    location /other {
        default_type 'foo/bar';
        echo $echo_request_method;
    }

    location /foo {
        proxy_pass http://127.0.0.1:$server_port/other;
    }

    location /t {
        content_by_lua '
            local res = ngx.location.capture("/foo",
                { method = ngx.HTTP_POST });

            ngx.print(res.body)
        ';
    }
--- request
GET /t
--- response_body
POST
--- no_error_log
[error]



=== TEST 4: HEAD
--- config
    location /other {
        default_type 'foo/bar';
        echo $echo_request_method;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/other",
                { method = ngx.HTTP_HEAD });

            ngx.print(res.body)
        ';
    }
--- request
GET /lua
--- response_body
--- no_error_log
[error]



=== TEST 5: explicit GET
--- config
    location /other {
        default_type 'foo/bar';
        echo $echo_request_method;
    }

    location /foo {
        proxy_pass http://127.0.0.1:$server_port/other;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/foo",
                { method = ngx.HTTP_GET });

            ngx.print(res.body)
        ';
    }
--- request
GET /lua
--- response_body
GET
--- no_error_log
[error]



=== TEST 6: implicit GET
--- config
    location /other {
        default_type 'foo/bar';
        echo $echo_request_method;
    }

    location /foo {
        proxy_pass http://127.0.0.1:$server_port/other;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/foo")

            ngx.print(res.body)
        ';
    }
--- request
GET /lua
--- response_body
GET
--- no_error_log
[error]



=== TEST 7: implicit GET (empty option table)
--- config
    location /other {
        default_type 'foo/bar';
        echo $echo_request_method;
    }

    location /foo {
        proxy_pass http://127.0.0.1:$server_port/other;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/foo", {})

            ngx.print(res.body)
        ';
    }
--- request
GET /lua
--- response_body
GET
--- no_error_log
[error]



=== TEST 8: PUT (with body, proxy method)
--- config
    location /other {
        default_type 'foo/bar';
        echo_read_request_body;

        echo $echo_request_method;
        echo_request_body;
    }

    location /foo {
        proxy_pass http://127.0.0.1:$server_port/other;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/foo",
                { method = ngx.HTTP_PUT, body = "hello" });

            ngx.print(res.body)
        ';
    }
--- request
GET /lua
--- response_body chomp
PUT
hello
--- no_error_log
[error]



=== TEST 9: PUT (with body, no proxy method)
--- config
    location /other {
        default_type 'foo/bar';
        #echo_read_request_body;

        echo $echo_request_method;
        #echo $echo_request_body;
        echo_request_body;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/other",
                { method = ngx.HTTP_PUT, body = "hello" });

            ngx.print(res.body)
        ';
    }
--- request
GET /lua
--- response_body chomp
PUT
hello
--- no_error_log
[error]



=== TEST 10: PUT (no body, no proxy method)
--- config
    location /other {
        default_type 'foo/bar';
        #echo_read_request_body;

        echo $echo_request_method;
        #echo $echo_request_body;
        echo_request_body;
        #echo "[$http_content_length]";
        echo;
    }

    location /foo {
        echo $echo_request_method;
        echo -n "[$http_content_length]";
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/other",
                { method = ngx.HTTP_PUT, body = "hello" });

            ngx.print(res.body)

            res = ngx.location.capture("/foo")
            ngx.say(res.body)

        ';
    }
--- request
GET /lua
--- response_body
PUT
hello
GET
[]
--- no_error_log
[error]



=== TEST 11: POST (with body, proxy method)
--- config
    location /other {
        default_type 'foo/bar';
        echo_read_request_body;

        echo $echo_request_method;
        echo_request_body;
    }

    location /foo {
        proxy_pass http://127.0.0.1:$server_port/other;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/foo",
                { method = ngx.HTTP_POST, body = "hello" });

            ngx.print(res.body)
        ';
    }
--- request
GET /lua
--- response_body chomp
POST
hello
--- no_error_log
[error]



=== TEST 12: POST (with body, memc method)
--- config
    location /flush {
        set $memc_cmd flush_all;
        memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
    }

    location /memc {
        set $memc_key $echo_request_uri;
        set $memc_exptime 600;
        memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
    }

    location /lua {
        content_by_lua '
            ngx.location.capture("/flush");

            local res = ngx.location.capture("/memc");
            ngx.say("GET: " .. res.status);

            res = ngx.location.capture("/memc",
                { method = ngx.HTTP_PUT, body = "hello" });
            ngx.say("PUT: " .. res.status);

            res = ngx.location.capture("/memc");
            ngx.say("cached: " .. res.body);

        ';
    }
--- request
GET /lua
--- response_body
GET: 404
PUT: 201
cached: hello
--- no_error_log
[error]



=== TEST 13: POST (with body, memc method)
--- config
    location /flush {
        set $memc_cmd flush_all;
        memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
    }

    location /memc {
        set $memc_cmd "";
        set $memc_key $echo_request_uri;
        set $memc_exptime 600;
        memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
    }

    location /lua {
        content_by_lua '
            ngx.location.capture("/flush",
                { share_all_vars = true });

            local res = ngx.location.capture("/memc",
                { share_all_vars = true });
            ngx.say("GET: " .. res.status);

            res = ngx.location.capture("/memc",
                { method = ngx.HTTP_PUT, body = "hello", share_all_vars = true });
            ngx.say("PUT: " .. res.status);

            res = ngx.location.capture("/memc", { share_all_vars = true });
            ngx.say("cached: " .. res.body);
        ';
    }
--- request
GET /lua
--- response_body
GET: 404
PUT: 201
cached: hello
--- no_error_log
[error]



=== TEST 14: empty args option table
--- config
    location /foo {
        echo $query_string;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/foo",
                { args = {} })
            ngx.print(res.body)
        ';
    }
--- request
GET /lua
--- response_body eval: "\n"
--- no_error_log
[error]



=== TEST 15: non-empty args option table (1 pair)
--- config
    location /foo {
        echo $query_string;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/foo",
                { args = { ["fo="] = "=>" } })
            ngx.print(res.body)
        ';
    }
--- request
GET /lua
--- response_body
fo%3D=%3D%3E
--- no_error_log
[error]



=== TEST 16: non-empty args option table (2 pairs)
--- config
    location /foo {
        echo $query_string;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/foo",
                { args = { ["fo="] = "=>",
                    ["="] = ":" } })
            ngx.print(res.body)
        ';
    }
--- request
GET /lua
--- response_body_like chop
^(?:fo%3D=%3D%3E\&%3D=%3A|%3D=%3A\&fo%3D=%3D%3E)$
--- no_error_log
[error]
--- no_error_log
[error]



=== TEST 17: non-empty args option table (2 pairs, no special chars)
--- config
    location /foo {
        echo $query_string;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/foo",
                { args = { foo = 3,
                    bar = "hello" } })
            ngx.print(res.body)
        ';
    }
--- request
GET /lua
--- response_body_like chop
^(?:bar=hello\&foo=3|foo=3\&bar=hello)$
--- no_error_log
[error]



=== TEST 18: non-empty args option table (number key)
--- config
    location /foo {
        echo $query_string;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/foo",
                { args = { [57] = "hi" } })
            ngx.print(res.body)
        ';
    }
--- request
GET /lua
--- response_body_like: 500 Internal Server Error
--- error_code: 500
--- error_log
attempt to use a non-string key in the "args" option table



=== TEST 19: non-empty args option table (plain arrays)
--- config
    location /foo {
        echo $query_string;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/foo",
                { args = { "hi" } })
            ngx.print(res.body)
        ';
    }
--- request
GET /lua
--- response_body_like: 500 Internal Server Error
--- error_code: 500
--- error_log
attempt to use a non-string key in the "args" option table



=== TEST 20: more args
--- config
    location /foo {
        echo $query_string;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/foo?a=3",
                { args = { b = 4 } })
            ngx.print(res.body)
        ';
    }
--- request
GET /lua
--- response_body
a=3&b=4
--- no_error_log
[error]



=== TEST 21: more args
--- config
    location /foo {
        echo $query_string;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/foo?a=3",
                { args = "b=4" })
            ngx.print(res.body)
        ';
    }
--- request
GET /lua
--- response_body
a=3&b=4
--- no_error_log
[error]



=== TEST 22: is_subrequest in main request
--- config
    location /lua {
        content_by_lua '
            if ngx.is_subrequest then
                ngx.say("sub req")
            else
                ngx.say("main req")
            end
        ';
    }
--- request
    GET /lua
--- response_body
main req
--- no_error_log
[error]



=== TEST 23: is_subrequest in sub request
--- config
    location /main {
        echo_location /lua;
    }

    location /lua {
        content_by_lua '
            if ngx.is_subrequest then
                ngx.say("sub req")
            else
                ngx.say("main req")
            end
        ';
    }
--- request
    GET /main
--- response_body
sub req
--- no_error_log
[error]



=== TEST 24: is_subrequest in sub request in set_by_lua
--- config
    location /main {
        echo_location /lua;
    }

    location /lua {
        set_by_lua $a '
            if ngx.is_subrequest then
                return "sub req"
            else
                return "main req"
            end
        ';
        echo $a;
    }
--- request
    GET /main
--- response_body
sub req
--- no_error_log
[error]



=== TEST 25: header inheritance bug (without body) (github issue 38)
https://github.com/chaoslawful/lua-nginx-module/issues/38
--- config
    location /other {
        default_type 'foo/bar';
        echo -n $http_foo;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/other",
                { method = ngx.HTTP_GET });
            ngx.say("header foo: [", res.body, "]")
        ';
    }
--- request
GET /lua
--- more_headers
Foo: bar
--- response_body
header foo: [bar]
--- no_error_log
[error]



=== TEST 26: header inheritance bug (with body) (github issue 38)
https://github.com/chaoslawful/lua-nginx-module/issues/38
--- config
    location /other {
        default_type 'foo/bar';
        echo -n $http_foo;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/other",
                { body = "abc" });
            ngx.say("header foo: [", res.body, "]")
        ';
    }
--- request
GET /lua
--- more_headers
Foo: bar
--- response_body
header foo: [bar]
--- no_error_log
[error]



=== TEST 27: lua calls lua via subrequests
--- config
    location /a {
        content_by_lua '
            ngx.say("hello, a");
        ';
    }
    location /b {
        content_by_lua '
            ngx.say("hello, b");
        ';
    }
    location /c {
        content_by_lua '
            ngx.say("hello, c");
        ';
    }
    location /main {
        content_by_lua '
            local res1, res2 = ngx.location.capture_multi({{"/a"}, {"/b"}})
            local res3 = ngx.location.capture("/c")
            ngx.print(res1.body, res2.body, res3.body)
        ';
    }
--- request
    GET /main
--- response_body
hello, a
hello, b
hello, c
--- error_log
lua reuse free buf memory
--- no_error_log
[error]



=== TEST 28: POST (with body, proxy method, main request is a POST too)
--- config
    location /other {
        default_type 'foo/bar';
        echo_read_request_body;

        echo $echo_request_method;
        echo_request_body;
    }

    location /foo {
        proxy_pass http://127.0.0.1:$server_port/other;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/foo",
                { method = ngx.HTTP_POST, body = "hello" });

            ngx.print(res.body)
        ';
    }
--- request
POST /lua
hi
--- response_body chomp
POST
hello
--- no_error_log
[error]



=== TEST 29: Last-Modified response header for static file subrequest
--- config
    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/foo.html")

            ngx.say(res.status)
            ngx.say(res.header["Last-Modified"])
            ngx.print(res.body)
        ';
    }
--- request
GET /lua
--- user_files
>>> foo.html
hello, static file
--- response_body_like chomp
^200
[A-Za-z]+, \d{1,2} [A-Za-z]+ \d{4} \d{2}:\d{2}:\d{2} GMT
hello, static file$
--- no_error_log
[error]



=== TEST 30: custom ctx table for subrequest
--- config
    location /sub {
        content_by_lua '
            ngx.ctx.foo = "bar";
        ';
    }
    location /lua {
        content_by_lua '
            local ctx = {}
            local res = ngx.location.capture("/sub", { ctx = ctx })

            ngx.say(ctx.foo);
            ngx.say(ngx.ctx.foo);
        ';
    }
--- request
GET /lua
--- response_body
bar
nil
--- no_error_log
[error]



=== TEST 31: share the ctx with the parent
--- config
    location /sub {
        content_by_lua '
            ngx.ctx.foo = "bar";
        ';
    }
    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/sub", { ctx = ngx.ctx })
            ngx.say(ngx.ctx.foo);
        ';
    }
--- request
GET /lua
--- response_body
bar
--- no_error_log
[error]



=== TEST 32: test memcached with subrequests
--- http_config
    upstream memc {
        server 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
        keepalive 100;
    }
--- config
    location /memc {
        set $memc_key some_key;
        set $memc_exptime 600;
        memc_pass memc;
    }

    location /t {
        content_by_lua '
            local res = ngx.location.capture("/memc",
                { method = ngx.HTTP_PUT, body = "hello 1234" });
            -- ngx.say("PUT: " .. res.status);

            res = ngx.location.capture("/memc");
            ngx.say("some_key: " .. res.body);
        ';
    }
--- request
GET /t
--- response_body
some_key: hello 1234
--- error_log
lua reuse free buf chain, but reallocate memory because
--- no_error_log
[error]



=== TEST 33: main POST, sub GET (main does not read the body)
--- config
    location /other {
        default_type 'foo/bar';
        content_by_lua '
            ngx.req.read_body()
            ngx.say(ngx.var.request_method)
            ngx.say(ngx.req.get_body_data())
        ';
    }

    location /foo {
        proxy_pass http://127.0.0.1:$server_port/other;
        #proxy_pass http://127.0.0.1:8892/other;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/foo",
                { method = ngx.HTTP_GET });

            ngx.print(res.body)
        ';
    }
--- request
POST /lua
hello, world
--- response_body
GET
nil
--- no_error_log
[error]



=== TEST 34: main POST, sub GET (main has read the body)
--- config
    location /other {
        default_type 'foo/bar';
        content_by_lua '
            ngx.req.read_body()
            ngx.say(ngx.var.request_method)
            ngx.say(ngx.req.get_body_data())
        ';
    }

    location /foo {
        proxy_pass http://127.0.0.1:$server_port/other;
        #proxy_pass http://127.0.0.1:8892/other;
    }

    location /lua {
        content_by_lua '
            ngx.req.read_body()

            local res = ngx.location.capture("/foo",
                { method = ngx.HTTP_GET });

            ngx.print(res.body)
        ';
    }
--- request
POST /lua
hello, world
--- response_body
GET
nil
--- no_error_log
[error]



=== TEST 35: main POST, sub POST (inherit bodies directly)
--- config
    location /other {
        default_type 'foo/bar';
        content_by_lua '
            ngx.req.read_body()
            ngx.say(ngx.var.request_method)
            ngx.say(ngx.req.get_body_data())
        ';
    }

    location /foo {
        proxy_pass http://127.0.0.1:$server_port/other;
        #proxy_pass http://127.0.0.1:8892/other;
    }

    location /lua {
        content_by_lua '
            ngx.req.read_body()

            local res = ngx.location.capture("/foo",
                { method = ngx.HTTP_POST });

            ngx.print(res.body)
        ';
    }
--- request
POST /lua
hello, world
--- response_body
POST
hello, world
--- no_error_log
[error]



=== TEST 36: main POST, sub PUT (inherit bodies directly)
--- config
    location /other {
        default_type 'foo/bar';
        content_by_lua '
            ngx.req.read_body()
            ngx.say(ngx.var.request_method)
            ngx.say(ngx.req.get_body_data())
        ';
    }

    location /foo {
        proxy_pass http://127.0.0.1:$server_port/other;
        #proxy_pass http://127.0.0.1:8892/other;
    }

    location /lua {
        content_by_lua '
            ngx.req.read_body()

            local res = ngx.location.capture("/foo",
                { method = ngx.HTTP_PUT });

            ngx.print(res.body)
        ';
    }
--- request
POST /lua
hello, world
--- response_body
PUT
hello, world
--- no_error_log
[error]



=== TEST 37: recursive calls
--- config
    location /t {
        content_by_lua '
            ngx.location.capture("/t")
        ';
    }
--- request
    GET /t
--- ignore_response
--- error_log
lua subrequests cycle while processing "/t"



=== TEST 38: OPTIONS
--- config
    location /other {
        default_type 'foo/bar';
        echo $echo_request_method;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/other",
                { method = ngx.HTTP_OPTIONS });

            ngx.print(res.body)
        ';
    }
--- request
GET /lua
--- response_body
OPTIONS
--- no_error_log
[error]



=== TEST 39: OPTIONS with a body
--- config
    location /other {
        default_type 'foo/bar';
        echo $echo_request_method;
        echo_request_body;
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/other",
                { method = ngx.HTTP_OPTIONS, body = "hello world" });

            ngx.print(res.body)
        ';
    }
--- request
GET /lua
--- response_body chop
OPTIONS
hello world
--- no_error_log
[error]



=== TEST 40: encode args table with a multi-value arg.
--- config
    location /t {
        content_by_lua '
            local args, err = ngx.req.get_uri_args()
            if err then
                ngx.say("err: ", err)
            end

            local res = ngx.location.capture("/sub", { args = args })
            ngx.print(res.body)
        ';
    }

    location /sub {
        echo $query_string;
    }
--- request
GET /t?r[]=http%3A%2F%2Fajax.googleapis.com%3A80%2Fajax%2Flibs%2Fjquery%2F1.7.2%2Fjquery.min.js&r[]=http%3A%2F%2Fajax.googleapis.com%3A80%2Fajax%2Flibs%2Fdojo%2F1.7.2%2Fdojo%2Fdojo.js.uncompressed.js
--- response_body
r%5B%5D=http%3A%2F%2Fajax.googleapis.com%3A80%2Fajax%2Flibs%2Fjquery%2F1.7.2%2Fjquery.min.js&r%5B%5D=http%3A%2F%2Fajax.googleapis.com%3A80%2Fajax%2Flibs%2Fdojo%2F1.7.2%2Fdojo%2Fdojo.js.uncompressed.js
--- no_error_log
[error]



=== TEST 41: subrequests finalized with NGX_ERROR
--- config
    location /sub {
        content_by_lua '
            ngx.exit(ngx.ERROR)
        ';
    }

    location /main {
        content_by_lua '
            local res = ngx.location.capture("/sub")
            ngx.say("status: ", res.status)
            ngx.say("body: ", res.body)
        ';
    }
--- request
GET /main
--- response_body
status: 500
body: 



=== TEST 42: subrequests finalized with 500
--- config
    location /sub {
        return 500;
    }

    location /main {
        content_by_lua '
            local res = ngx.location.capture("/sub")
            ngx.say("status: ", res.status)
            ngx.say("body: ", res.body)
        ';
    }
--- request
GET /main
--- response_body
status: 500
body: 



=== TEST 43: subrequests with an output body filter returning NGX_ERROR
--- no_http2
--- config
    location /sub {
        echo hello world;
        body_filter_by_lua '
            return ngx.ERROR
        ';
    }

    location /main {
        content_by_lua '
            local res = ngx.location.capture("/sub")
            ngx.say("status: ", res.status)
            ngx.say("body: ", res.body)
        ';
    }
--- request
GET /main
--- stap2
F(ngx_http_upstream_finalize_request) {
    printf("upstream fin req: error=%d eof=%d rc=%d\n",
        $r->upstream->peer->connection->read->error,
        $r->upstream->peer->connection->read->eof,
        $rc)
    #print_ubacktrace()
}
F(ngx_connection_error) {
    printf("conn err: %d: %s\n", $err, user_string($text))
    #print_ubacktrace()
}
F(ngx_http_lua_post_subrequest) {
    printf("post subreq: rc=%d, status=%d\n", $rc, $r->headers_out->status)
    #print_ubacktrace()
}
F(ngx_http_finalize_request) {
    printf("finalize: %d\n", $rc)
}
--- response_body
--- error_code
--- no_error_log
[error]
--- curl_error eval
qr{(\Qcurl: (52) Empty reply from server\E|\Qcurl: (95) HTTP/3 stream 0 reset by server\E)}ms



=== TEST 44: subrequests truncated in its response body due to premature connection close (nonbuffered)
--- config
    server_tokens off;
    location /memc {
        internal;

        set $memc_key 'foo';
        #set $memc_exptime 300;
        memc_pass 127.0.0.1:$TEST_NGINX_RAND_PORT_1; #$TEST_NGINX_MEMCACHED_PORT;
    }

    location /main {
        content_by_lua '
            local res = ngx.location.capture("/memc")
            ngx.say("status: ", res.status)
            ngx.say("body: ", res.body)
            ngx.say("truncated: ", res.truncated)
        ';
    }
--- request
GET /main
--- tcp_listen: $TEST_NGINX_RAND_PORT_1
--- tcp_query_len: 9
--- tcp_reply eval
"VALUE foo 0 1024\r\nhello world"

--- stap2
F(ngx_http_lua_capture_body_filter) {
    if (pid() == target() && $r != $r->main) {
        printf("lua capture body output: %s\n", ngx_chain_dump($in))
        if ($in->buf->last_in_chain) {
            print_ubacktrace()
        }
    }
}

--- stap
F(ngx_http_upstream_finalize_request) {
    printf("upstream fin req: error=%d eof=%d rc=%d\n",
        $r->upstream->peer->connection->read->error,
        $r->upstream->peer->connection->read->eof,
        $rc)
    #print_ubacktrace()
}
F(ngx_connection_error) {
    printf("conn err: %d: %s\n", $err, user_string($text))
    #print_ubacktrace()
}
F(ngx_http_lua_post_subrequest) {
    printf("post subreq: rc=%d, status=%d\n", $rc, $r->headers_out->status)
    #print_ubacktrace()
}
/*
F(ngx_http_finalize_request) {
    printf("finalize: %d\n", $rc)
}
*/
--- stap_out
upstream fin req: error=0 eof=1 rc=502
post subreq: rc=0, status=200

--- response_body
status: 200
body: hello world
truncated: true
--- error_log
upstream prematurely closed connection



=== TEST 45: subrequests truncated in its response body due to upstream read timeout (nonbuffered)
--- config
    memc_read_timeout 100ms;
    location /memc {
        internal;

        set $memc_key 'foo';
        #set $memc_exptime 300;
        memc_pass 127.0.0.1:$TEST_NGINX_RAND_PORT_1; #$TEST_NGINX_MEMCACHED_PORT;
    }

    location /main {
        content_by_lua '
            local res = ngx.location.capture("/memc")
            ngx.say("status: ", res.status)
            ngx.say("body: ", res.body)
            ngx.say("truncated: ", res.truncated)
        ';
    }
--- request
GET /main
--- tcp_listen: $TEST_NGINX_RAND_PORT_1
--- tcp_no_close
--- tcp_reply eval
"VALUE foo 0 1024\r\nhello world"

--- stap2
F(ngx_http_lua_capture_body_filter) {
    if (pid() == target() && $r != $r->main) {
        printf("lua capture body output: %s\n", ngx_chain_dump($in))
        //if ($in->buf->last_in_chain) {
            print_ubacktrace()
        //}
    }
}

--- stap
F(ngx_http_upstream_finalize_request) {
    printf("upstream fin req: error=%d eof=%d rc=%d\n",
        $r->upstream->peer->connection->read->error,
        $r->upstream->peer->connection->read->eof,
        $rc)
    #print_ubacktrace()
}
F(ngx_connection_error) {
    printf("conn err: %d: %s\n", $err, user_string($text))
    #print_ubacktrace()
}
F(ngx_http_lua_post_subrequest) {
    printf("post subreq: rc=%d, status=%d\n", $rc, $r->headers_out->status)
    #print_ubacktrace()
}
/*
F(ngx_http_finalize_request) {
    printf("finalize: %d\n", $rc)
}
*/
--- stap_out
conn err: 110: upstream timed out
upstream fin req: error=0 eof=0 rc=504
post subreq: rc=0, status=200

--- response_body_like chop
^status: 200
body: [^\n]*
truncated: true

--- error_log
upstream timed out



=== TEST 46: subrequests truncated in its response body due to premature connection close (buffered)
--- config
    server_tokens off;

    location /proxy {
        internal;

        #proxy_read_timeout 100ms;
        proxy_buffering on;
        proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT;
        proxy_http_version 1.0;
        proxy_set_header Connection close;
    }

    location /main {
        content_by_lua '
            local res = ngx.location.capture("/proxy")
            ngx.say("status: ", res.status)
            ngx.say("body: ", res.body)
            ngx.say("truncated: ", res.truncated)
        ';
    }
--- request
GET /main
--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT
--- tcp_query_len: 65
--- tcp_reply eval
"HTTP/1.0 200 OK\r\nContent-Length: 1024\r\n\r\nhello world"

--- response_body
status: 200
body: hello world
truncated: true

--- error_log
upstream prematurely closed connection



=== TEST 47: subrequests truncated in its response body due to read timeout (buffered)
--- config
    location /proxy {
        internal;

        proxy_read_timeout 100ms;
        proxy_buffering on;
        proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT;
    }

    location /main {
        content_by_lua '
            local res = ngx.location.capture("/proxy")
            ngx.say("status: ", res.status)
            ngx.say("body: ", res.body)
            ngx.say("truncated: ", res.truncated)
        ';
    }
--- request
GET /main
--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT
--- tcp_no_close
--- tcp_reply eval
"HTTP/1.0 200 OK\r\nContent-Length: 1024\r\n\r\nhello world"

--- stap
F(ngx_http_upstream_finalize_request) {
    printf("upstream fin req: error=%d eof=%d rc=%d\n",
        $r->upstream->peer->connection->read->error,
        $r->upstream->peer->connection->read->eof,
        $rc)
    #print_ubacktrace()
}
F(ngx_connection_error) {
    printf("conn err: %d: %s\n", $err, user_string($text))
    #print_ubacktrace()
}
F(ngx_http_lua_post_subrequest) {
    printf("post subreq: rc=%d, status=%d\n", $rc, $r->headers_out->status)
    #print_ubacktrace()
}
/*
F(ngx_http_finalize_request) {
    printf("finalize: %d\n", $rc)
}
*/
--- stap_out
conn err: 110: upstream timed out
upstream fin req: error=0 eof=0 rc=502
post subreq: rc=0, status=200

--- response_body
status: 200
body: 
truncated: true

--- error_log
upstream timed out



=== TEST 48: subrequests truncated in its response body due to premature connection close (buffered, no content-length)
--- config
    server_tokens off;
    location /proxy {
        internal;

        #proxy_read_timeout 100ms;
        proxy_buffering on;
        proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT;
        proxy_http_version 1.0;
        proxy_set_header Connection close;
    }

    location /main {
        content_by_lua '
            local res = ngx.location.capture("/proxy")
            ngx.say("status: ", res.status)
            ngx.say("body: ", res.body)
            ngx.say("truncated: ", res.truncated)
        ';
    }
--- request
GET /main
--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT
--- tcp_query_len: 65
--- tcp_reply eval
"HTTP/1.0 200 OK\r\n\r\nhello world"

--- stap
F(ngx_http_upstream_finalize_request) {
    printf("upstream fin req: error=%d eof=%d rc=%d\n",
        $r->upstream->peer->connection->read->error,
        $r->upstream->peer->connection->read->eof,
        $rc)
    #print_ubacktrace()
}
F(ngx_connection_error) {
    printf("conn err: %d: %s\n", $err, user_string($text))
    #print_ubacktrace()
}
F(ngx_http_lua_post_subrequest) {
    printf("post subreq: rc=%d, status=%d\n", $rc, $r->headers_out->status)
    #print_ubacktrace()
}
/*
F(ngx_http_finalize_request) {
    printf("finalize: %d\n", $rc)
}
*/
--- stap_out
upstream fin req: error=0 eof=1 rc=0
post subreq: rc=0, status=200

--- response_body
status: 200
body: hello world
truncated: false

--- no_error_log
[error]



=== TEST 49: subrequests truncated in its response body due to read timeout (buffered, no content-length)
--- config
    location /proxy {
        internal;

        proxy_read_timeout 100ms;
        proxy_buffering on;
        proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT;
    }

    location /main {
        content_by_lua '
            local res = ngx.location.capture("/proxy")
            ngx.say("status: ", res.status)
            ngx.say("body: ", res.body)
            ngx.say("truncated: ", res.truncated)
        ';
    }
--- request
GET /main
--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT
--- tcp_no_close
--- tcp_reply eval
"HTTP/1.0 200 OK\r\n\r\nhello world"

--- stap
F(ngx_http_upstream_finalize_request) {
    printf("upstream fin req: error=%d eof=%d rc=%d\n",
        $r->upstream->peer->connection->read->error,
        $r->upstream->peer->connection->read->eof,
        $rc)
    #print_ubacktrace()
}
F(ngx_connection_error) {
    printf("conn err: %d: %s\n", $err, user_string($text))
    #print_ubacktrace()
}
F(ngx_http_lua_post_subrequest) {
    printf("post subreq: rc=%d, status=%d\n", $rc, $r->headers_out->status)
    #print_ubacktrace()
}
/*
F(ngx_http_finalize_request) {
    printf("finalize: %d\n", $rc)
}
*/
--- stap_out
conn err: 110: upstream timed out
upstream fin req: error=0 eof=0 rc=502
post subreq: rc=0, status=200

--- response_body
status: 200
body: 
truncated: true

--- error_log
upstream timed out



=== TEST 50: subrequests truncated in its response body due to premature connection close (nonbuffered, no content-length)
--- config
    server_tokens off;

    location /proxy {
        internal;

        #proxy_read_timeout 100ms;
        proxy_buffering off;
        proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT;
        proxy_set_header Connection close;
    }

    location /main {
        content_by_lua '
            local res = ngx.location.capture("/proxy")
            ngx.say("status: ", res.status)
            ngx.say("body: ", res.body)
            ngx.say("truncated: ", res.truncated)
        ';
    }
--- request
GET /main
--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT
--- tcp_query_len: 65
--- tcp_reply eval
"HTTP/1.0 200 OK\r\n\r\nhello world"

--- stap
F(ngx_http_upstream_finalize_request) {
    printf("upstream fin req: error=%d eof=%d rc=%d\n",
        $r->upstream->peer->connection->read->error,
        $r->upstream->peer->connection->read->eof,
        $rc)
    #print_ubacktrace()
}
F(ngx_connection_error) {
    printf("conn err: %d: %s\n", $err, user_string($text))
    #print_ubacktrace()
}
F(ngx_http_lua_post_subrequest) {
    printf("post subreq: rc=%d, status=%d\n", $rc, $r->headers_out->status)
    #print_ubacktrace()
}
/*
F(ngx_http_finalize_request) {
    printf("finalize: %d\n", $rc)
}
*/
--- stap_out
upstream fin req: error=0 eof=1 rc=0
post subreq: rc=0, status=200

--- response_body
status: 200
body: hello world
truncated: false

--- no_error_log
[error]



=== TEST 51: subrequests truncated in its response body due to read timeout (nonbuffered, no content-length)
--- config
    location /proxy {
        internal;

        proxy_read_timeout 500ms;
        proxy_buffering off;
        proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT;
    }

    location /main {
        content_by_lua '
            local res = ngx.location.capture("/proxy")
            ngx.say("status: ", res.status)
            ngx.say("body: ", res.body)
            ngx.say("truncated: ", res.truncated)
        ';
    }
--- request
GET /main
--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT
--- tcp_no_close
--- tcp_reply eval
"HTTP/1.0 200 OK\r\n\r\nhello world"

--- stap
F(ngx_http_upstream_finalize_request) {
    printf("upstream fin req: error=%d eof=%d rc=%d\n",
        $r->upstream->peer->connection->read->error,
        $r->upstream->peer->connection->read->eof,
        $rc)
    #print_ubacktrace()
}
F(ngx_connection_error) {
    printf("conn err: %d: %s\n", $err, user_string($text))
    #print_ubacktrace()
}
F(ngx_http_lua_post_subrequest) {
    printf("post subreq: rc=%d, status=%d\n", $rc, $r->headers_out->status)
    #print_ubacktrace()
}
/*
F(ngx_http_finalize_request) {
    printf("finalize: %d\n", $rc)
}
*/
--- stap_out
conn err: 110: upstream timed out
upstream fin req: error=0 eof=0 rc=504
post subreq: rc=0, status=200

--- response_body
status: 200
body: hello world
truncated: true

--- error_log
upstream timed out



=== TEST 52: forwarding in-memory request bodies to multiple subrequests
--- config
    location /other {
        default_type 'foo/bar';
        proxy_pass http://127.0.0.1:$server_port/back;
    }

    location /back {
        echo_read_request_body;
        echo_request_body;
    }

    location /lua {
        content_by_lua '
            ngx.req.read_body()

            for i = 1, 2 do
                local res = ngx.location.capture("/other",
                    { method = ngx.HTTP_POST });

                ngx.say(res.body)
            end
        ';
    }

--- request eval
"POST /lua
" . "hello world"

--- response_body
hello world
hello world

--- no_error_log
[error]



=== TEST 53: forwarding in-file request bodies to multiple subrequests (client_body_in_file_only)
--- config
    location /other {
        default_type 'foo/bar';
        proxy_pass http://127.0.0.1:$server_port/back;
    }

    location /back {
        echo_read_request_body;
        echo_request_body;
    }

    client_body_in_file_only on;

    location /lua {
        content_by_lua '
            ngx.req.read_body()

            for i = 1, 2 do
                local res = ngx.location.capture("/other",
                    { method = ngx.HTTP_POST });

                ngx.say(res.body)
            end
        ';
    }

--- request eval
"POST /lua
" . "hello world"

--- response_body
hello world
hello world

--- no_error_log
[error]



=== TEST 54: forwarding in-file request bodies to multiple subrequests (exceeding client_body_buffer_size)
--- config
    location /other {
        default_type 'foo/bar';
        proxy_pass http://127.0.0.1:$server_port/back;
    }

    location /back {
        echo_read_request_body;
        echo_request_body;
    }

    location /lua {
        #client_body_in_file_only on;
        client_body_buffer_size 1;
        content_by_lua '
            ngx.req.read_body()

            for i = 1, 2 do
                local res = ngx.location.capture("/other",
                    { method = ngx.HTTP_POST });

                ngx.say(res.body)
            end
        ';
    }
--- request eval
"POST /lua
" . ("hello world" x 100)

--- stap2
global valid = 0
global fds

F(ngx_http_handler) { valid = 1  }

probe syscall.open {
    if (valid && pid() == target()) {
        print(name, "(", argstr, ")")
    }
}

probe syscall.close {
    if (valid && pid() == target() && fds[sprintf("%d", $fd)]) {
        println(name, "(", argstr, ")")
    }
}

probe syscall.unlink {
    if (valid && pid() == target()) {
        println(name, "(", argstr, ")")
    }
}

probe syscall.open.return {
    if (valid && pid() == target()) {
        println(" = ", retstr)
        fds[retstr] = 1
    }
}

F(ngx_http_lua_subrequest) {
    println("lua subrequest")
}

F(ngx_output_chain) {
    printf("output chain: %s\n", ngx_chain_dump($in))
}

F(ngx_pool_run_cleanup_file) {
    println("clean up file: ", $fd)
}

--- response_body eval
("hello world" x 100) . "\n"
. ("hello world" x 100) . "\n"

--- no_error_log
[error]
--- error_log
a client request body is buffered to a temporary file



=== TEST 55: subrequests truncated in its response body due to premature connection close (buffered + chunked)
--- config
    server_tokens off;

    location /proxy {
        internal;

        #proxy_read_timeout 100ms;
        proxy_http_version 1.1;
        proxy_buffering on;
        proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT;
        proxy_set_header Connection close;
    }

    location /main {
        content_by_lua '
            local res = ngx.location.capture("/proxy")
            ngx.say("status: ", res.status)
            ngx.say("body: ", res.body)
            ngx.say("truncated: ", res.truncated)
        ';
    }
--- request
GET /main
--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT
--- tcp_query_len: 65
--- tcp_reply eval
"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\nb\r\nhello world\r"

--- stap
F(ngx_http_upstream_finalize_request) {
    printf("upstream fin req: error=%d eof=%d rc=%d\n",
        $r->upstream->peer->connection->read->error,
        $r->upstream->peer->connection->read->eof,
        $rc)
    #print_ubacktrace()
}
F(ngx_connection_error) {
    printf("conn err: %d: %s\n", $err, user_string($text))
    #print_ubacktrace()
}
F(ngx_http_lua_post_subrequest) {
    printf("post subreq: rc=%d, status=%d\n", $rc, $r->headers_out->status)
    #print_ubacktrace()
}
/*
F(ngx_http_finalize_request) {
    printf("finalize: %d\n", $rc)
}
*/
--- stap_out
upstream fin req: error=0 eof=1 rc=502
post subreq: rc=0, status=200

--- response_body
status: 200
body: hello world
truncated: true

--- error_log
upstream prematurely closed connection



=== TEST 56: subrequests truncated in its response body due to premature connection close (nonbuffered + chunked)
--- config
    server_tokens off;

    location /proxy {
        internal;

        #proxy_read_timeout 100ms;
        proxy_http_version 1.1;
        proxy_buffering off;
        proxy_set_header Connection close;
        proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT;
    }

    location /main {
        content_by_lua '
            local res = ngx.location.capture("/proxy")
            ngx.say("status: ", res.status)
            ngx.say("body: ", res.body)
            ngx.say("truncated: ", res.truncated)
        ';
    }
--- request
GET /main
--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT
--- tcp_query_len: 65
--- tcp_reply eval
"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\nb\r\nhello world\r"

--- stap
F(ngx_http_upstream_finalize_request) {
    printf("upstream fin req: error=%d eof=%d rc=%d\n",
        $r->upstream->peer->connection->read->error,
        $r->upstream->peer->connection->read->eof,
        $rc)
    #print_ubacktrace()
}
F(ngx_connection_error) {
    printf("conn err: %d: %s\n", $err, user_string($text))
    #print_ubacktrace()
}
F(ngx_http_lua_post_subrequest) {
    printf("post subreq: rc=%d, status=%d\n", $rc, $r->headers_out->status)
    #print_ubacktrace()
}
/*
F(ngx_http_finalize_request) {
    printf("finalize: %d\n", $rc)
}
*/
--- stap_out
upstream fin req: error=0 eof=1 rc=502
post subreq: rc=0, status=200

--- response_body
status: 200
body: hello world
truncated: true

--- error_log
upstream prematurely closed connection



=== TEST 57: subrequests truncated in its response body due to read timeout (buffered + chunked)
--- config
    location /proxy {
        internal;

        proxy_read_timeout 100ms;
        proxy_buffering on;
        proxy_http_version 1.1;
        proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT;
    }

    location /main {
        content_by_lua '
            local res = ngx.location.capture("/proxy")
            ngx.say("status: ", res.status)
            ngx.say("body: ", res.body)
            ngx.say("truncated: ", res.truncated)
        ';
    }
--- request
GET /main
--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT
--- tcp_no_close
--- tcp_reply eval
"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\nb\r\nhello world\r"

--- stap
F(ngx_http_upstream_finalize_request) {
    printf("upstream fin req: error=%d eof=%d rc=%d\n",
        $r->upstream->peer->connection->read->error,
        $r->upstream->peer->connection->read->eof,
        $rc)
    #print_ubacktrace()
}
F(ngx_connection_error) {
    printf("conn err: %d: %s\n", $err, user_string($text))
    #print_ubacktrace()
}
F(ngx_http_lua_post_subrequest) {
    printf("post subreq: rc=%d, status=%d\n", $rc, $r->headers_out->status)
    #print_ubacktrace()
}
/*
F(ngx_http_finalize_request) {
    printf("finalize: %d\n", $rc)
}
*/
--- stap_out
conn err: 110: upstream timed out
upstream fin req: error=0 eof=0 rc=502
post subreq: rc=0, status=200

--- response_body
status: 200
body: 
truncated: true

--- error_log
upstream timed out



=== TEST 58: good chunked response (buffered)
--- config
    location /proxy {
        internal;

        #proxy_read_timeout 100ms;
        proxy_buffering on;
        proxy_http_version 1.1;
        proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT;
    }

    location /main {
        content_by_lua '
            local res = ngx.location.capture("/proxy")
            ngx.say("status: ", res.status)
            ngx.say("body: ", res.body)
            ngx.say("truncated: ", res.truncated)
        ';
    }
--- request
GET /main
--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT
--- tcp_no_close
--- tcp_reply eval
"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0\r\n\r\n"

--- stap
F(ngx_http_upstream_finalize_request) {
    printf("upstream fin req: error=%d eof=%d rc=%d\n",
        $r->upstream->peer->connection->read->error,
        $r->upstream->peer->connection->read->eof,
        $rc)
    #print_ubacktrace()
}
F(ngx_connection_error) {
    printf("conn err: %d: %s\n", $err, user_string($text))
    #print_ubacktrace()
}
F(ngx_http_lua_post_subrequest) {
    printf("post subreq: rc=%d, status=%d\n", $rc, $r->headers_out->status)
    #print_ubacktrace()
}
/*
F(ngx_http_finalize_request) {
    printf("finalize: %d\n", $rc)
}
*/
--- stap_out
upstream fin req: error=0 eof=0 rc=0
post subreq: rc=0, status=200

--- response_body
status: 200
body: hello
truncated: false



=== TEST 59: good chunked response (nonbuffered)
--- config
    location /proxy {
        internal;

        #proxy_read_timeout 100ms;
        proxy_buffering off;
        proxy_http_version 1.1;
        proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT;
    }

    location /main {
        content_by_lua '
            local res = ngx.location.capture("/proxy")
            ngx.say("status: ", res.status)
            ngx.say("body: ", res.body)
            ngx.say("truncated: ", res.truncated)
        ';
    }
--- request
GET /main
--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT
--- tcp_no_close
--- tcp_reply eval
"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0\r\n\r\n"

--- stap
F(ngx_http_upstream_finalize_request) {
    printf("upstream fin req: error=%d eof=%d rc=%d\n",
        $r->upstream->peer->connection->read->error,
        $r->upstream->peer->connection->read->eof,
        $rc)
    #print_ubacktrace()
}
F(ngx_connection_error) {
    printf("conn err: %d: %s\n", $err, user_string($text))
    #print_ubacktrace()
}
F(ngx_http_lua_post_subrequest) {
    printf("post subreq: rc=%d, status=%d\n", $rc, $r->headers_out->status)
    #print_ubacktrace()
}
/*
F(ngx_http_finalize_request) {
    printf("finalize: %d\n", $rc)
}
*/
--- stap_out
upstream fin req: error=0 eof=0 rc=0
post subreq: rc=0, status=200

--- response_body
status: 200
body: hello
truncated: false



=== TEST 60: subrequests truncated in its response body due to premature connection close (nonbuffered + proxy)
--- config
    server_tokens off;

    location /proxy {
        internal;

        #proxy_read_timeout 100ms;
        proxy_buffering off;
        proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT;
        proxy_set_header Connection close;
    }

    location /main {
        content_by_lua '
            local res = ngx.location.capture("/proxy")
            ngx.say("status: ", res.status)
            ngx.say("body: ", res.body)
            ngx.say("truncated: ", res.truncated)
        ';
    }
--- request
GET /main
--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT
--- tcp_query_len: 65
--- tcp_reply eval
"HTTP/1.0 200 OK\r\nContent-Length: 1024\r\n\r\nhello world"

--- stap
F(ngx_http_upstream_finalize_request) {
    printf("upstream fin req: error=%d eof=%d rc=%d\n",
        $r->upstream->peer->connection->read->error,
        $r->upstream->peer->connection->read->eof,
        $rc)
    #print_ubacktrace()
}
F(ngx_connection_error) {
    printf("conn err: %d: %s\n", $err, user_string($text))
    #print_ubacktrace()
}
F(ngx_http_lua_post_subrequest) {
    printf("post subreq: rc=%d, status=%d\n", $rc, $r->headers_out->status)
    #print_ubacktrace()
}
/*
F(ngx_http_finalize_request) {
    printf("finalize: %d\n", $rc)
}
*/
--- stap_out
upstream fin req: error=0 eof=1 rc=502
post subreq: rc=0, status=200

--- response_body
status: 200
body: hello world
truncated: true

--- error_log
upstream prematurely closed connection



=== TEST 61: WebDAV methods
--- config
    location /other {
        echo "method: $echo_request_method";
    }

    location /lua {
        content_by_lua '
            local methods = {
                ngx.HTTP_MKCOL,
                ngx.HTTP_COPY,
                ngx.HTTP_MOVE,
                ngx.HTTP_PROPFIND,
                ngx.HTTP_PROPPATCH,
                ngx.HTTP_LOCK,
                ngx.HTTP_UNLOCK,
                ngx.HTTP_PATCH,
                ngx.HTTP_TRACE,
            }

            for i, method in ipairs(methods) do
                local res = ngx.location.capture("/other",
                    { method = method })
                ngx.print(res.body)
            end
        ';
    }
--- request
GET /lua
--- response_body
method: MKCOL
method: COPY
method: MOVE
method: PROPFIND
method: PROPPATCH
method: LOCK
method: UNLOCK
method: PATCH
method: TRACE

--- no_error_log
[error]



=== TEST 62: by default DELETE subrequests don't forward request bodies
--- config
    location /other {
        default_type 'foo/bar';
        content_by_lua '
            ngx.req.read_body()
            ngx.say(ngx.req.get_body_data())
        ';
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/other",
                { method = ngx.HTTP_DELETE });

            ngx.print(res.body)
        ';
    }
--- request
DELETE /lua
hello world
--- response_body
nil
--- no_error_log
[error]
--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3}



=== TEST 63: DELETE subrequests do forward request bodies when always_forward_body == true
--- config
    location = /other {
        default_type 'foo/bar';
        content_by_lua '
            ngx.req.read_body()
            ngx.say(ngx.req.get_body_data())
        ';
    }

    location /lua {
        content_by_lua '
            ngx.req.read_body()
            local res = ngx.location.capture("/other",
                { method = ngx.HTTP_DELETE, always_forward_body = true });

            ngx.print(res.body)
        ';
    }
--- request
DELETE /lua
hello world
--- response_body
hello world
--- no_error_log
[error]



=== TEST 64: DELETE subrequests do forward request bodies when always_forward_body == true (on disk)
--- config
    location = /other {
        default_type 'foo/bar';
        content_by_lua '
            ngx.req.read_body()
            ngx.say(ngx.req.get_body_data())
        ';
    }

    location /lua {
        content_by_lua '
            ngx.req.read_body()
            local res = ngx.location.capture("/other",
                { method = ngx.HTTP_DELETE, always_forward_body = true });

            ngx.print(res.body)
        ';
    }
--- request
DELETE /lua
hello world
--- stap2
global c
probe process("$LIBLUA_PATH").function("rehashtab") {
    c++
    //print_ubacktrace()
    printf("rehash: %d\n", c)
}
--- stap_out2
--- response_body
hello world
--- no_error_log
[error]



=== TEST 65: DELETE
--- config
    location = /t {
        content_by_lua '
            local res = ngx.location.capture("/sub")
            ngx.print(res.body)
        ';
    }
    location = /sub {
        echo hello;
        echo world;
    }
--- request
GET /t
--- response_body
hello
world
--- stap
F(ngx_http_lua_capture_header_filter) {
    println("capture header filter")
}

F(ngx_http_lua_capture_body_filter) {
    println("capture body filter")
}

--- stap_out
capture header filter
capture body filter
capture body filter
capture body filter
capture header filter
capture body filter
capture body filter
--- no_error_log
[error]



=== TEST 66: leafo test case 1 for assertion failures
--- config
    location = /t {
        echo hello;
    }

    location /proxy {
        internal;
        rewrite_by_lua "
          local req = ngx.req
          print(ngx.var._url)

          for k,v in pairs(req.get_headers()) do
            if k ~= 'content-length' then
              req.clear_header(k)
            end
          end

          if ngx.ctx.headers then
            for k,v in pairs(ngx.ctx.headers) do
              req.set_header(k, v)
            end
          end
        ";

        proxy_http_version 1.1;
        proxy_pass $_url;
    }

    location /first {
      set $_url "";
      content_by_lua '
        local res = ngx.location.capture("/proxy", {
          ctx = {
            headers = {
              ["Content-type"] = "application/x-www-form-urlencoded"
            }
          },
          vars = { _url = "http://127.0.0.1:" .. ngx.var.server_port .. "/t" }
        })

        ngx.print(res.body)

        local res = ngx.location.capture("/proxy", {
          ctx = {
            headers = {
              ["x-some-date"] = "Sun, 01 Dec 2013 11:47:41 GMT",
              ["x-hello-world-header"] = "123412341234",
              ["Authorization"] = "Hello"
            }
          },
          vars = { _url = "http://127.0.0.1:" .. ngx.var.server_port .. "/t" }
        })

        ngx.print(res.body)
      ';
    }
--- request
GET /first
--- response_body
hello
hello
--- no_error_log eval
[
"[error]",
qr/Assertion .*? failed/
]



=== TEST 67: leafo test case 2 for assertion failures
--- config
    location = /t {
        echo hello;
    }

    location /proxy {
        internal;
        rewrite_by_lua "
          local req = ngx.req
          print(ngx.var._url)

          for k,v in pairs(req.get_headers()) do
            if k ~= 'content-length' then
              req.clear_header(k)
            end
          end

          if ngx.ctx.headers then
            for k,v in pairs(ngx.ctx.headers) do
              req.set_header(k, v)
            end
          end
        ";

        proxy_http_version 1.1;
        proxy_pass $_url;
    }

    location /second {
      set $_url "";
      content_by_lua '
        local res = ngx.location.capture("/proxy", {
          method = ngx.HTTP_POST,
          body = ("x"):rep(600),
          ctx = {
            headers = {
              ["Content-type"] = "application/x-www-form-urlencoded"
            }
          },
          vars = { _url = "http://127.0.0.1:" .. ngx.var.server_port .. "/t" }
        })

        ngx.print(res.body)

        local res = ngx.location.capture("/proxy", {
          ctx = {
            headers = {
              ["x-some-date"] = "Sun, 01 Dec 2013 11:47:41 GMT",
              ["x-hello-world-header"] = "123412341234",
              ["Authorization"] = "Hello"
            }
          },
          vars = { _url = "http://127.0.0.1:" .. ngx.var.server_port .. "/t" }
        })

        ngx.print(res.body)

        local res = ngx.location.capture("/proxy", {
          vars = { _url = "http://127.0.0.1:" .. ngx.var.server_port .. "/t" }
        })

        ngx.print(res.body)
      ';
    }
--- request
GET /second
--- response_body
hello
hello
hello
--- no_error_log eval
[
"[error]",
qr/Assertion .*? failed/
]



=== TEST 68: fetch subrequest's builtin request headers
--- config
    location = /sub {
        echo "sr: User-Agent: $http_user_agent";
        echo "sr: Host: $http_host";
    }

    location = /t {
        content_by_lua '
            local res = ngx.location.capture("/sub")
            ngx.print(res.body)
            ngx.say("pr: User-Agent: ", ngx.var.http_user_agent)
            ngx.say("pr: Host: ", ngx.var.http_host)
        ';
    }
--- request
    GET /t
--- more_headers
User-Agent: foo
--- response_body
sr: User-Agent: foo
sr: Host: localhost
pr: User-Agent: foo
pr: Host: localhost

--- no_error_log
[error]
--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3}



=== TEST 69: modify subrequest's builtin request headers
--- config
    location = /sub {
        rewrite_by_lua '
            ngx.req.set_header("User-Agent", "bar")
        ';
        echo "sr: User-Agent: $http_user_agent";
        echo "sr: Host: $http_host";
    }

    location = /t {
        content_by_lua '
            local res = ngx.location.capture("/sub")
            ngx.print(res.body)
            ngx.say("pr: User-Agent: ", ngx.var.http_user_agent)
            ngx.say("pr: Host: ", ngx.var.http_host)
        ';
    }
--- request
    GET /t
--- more_headers
User-Agent: foo
--- response_body
sr: User-Agent: bar
sr: Host: localhost
pr: User-Agent: foo
pr: Host: localhost

--- no_error_log
[error]
--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3}



=== TEST 70: modify subrequest's builtin request headers (main req is POST)
--- config
    location = /sub {
        rewrite_by_lua '
            ngx.req.set_header("User-Agent", "bar")
        ';
        echo "sr: User-Agent: $http_user_agent";
        echo "sr: Host: $http_host";
    }

    location = /t {
        content_by_lua '
            local res = ngx.location.capture("/sub")
            ngx.print(res.body)
            ngx.say("pr: User-Agent: ", ngx.var.http_user_agent)
            ngx.say("pr: Host: ", ngx.var.http_host)
        ';
    }
--- request
POST /t
hello world
--- more_headers
User-Agent: foo
--- response_body
sr: User-Agent: bar
sr: Host: localhost
pr: User-Agent: foo
pr: Host: localhost

--- no_error_log
[error]



=== TEST 71: duplicate request headers (main req is POST)
--- config
    location = /sub {
        echo "sr: Cookie: $http_cookie";
    }

    location = /t {
        content_by_lua '
            local res = ngx.location.capture("/sub")
            ngx.print(res.body)
            ngx.say("pr: Cookie: ", ngx.var.http_cookie)
        ';
    }
--- request
POST /t
hello world
--- more_headers
Cookie: foo
Cookie: bar
--- response_body
sr: Cookie: foo; bar
pr: Cookie: foo; bar

--- no_error_log
[error]



=== TEST 72: duplicate request headers (main req is GET)
--- config
    location = /sub {
        echo "sr: Cookie: $http_cookie";
    }

    location = /t {
        content_by_lua '
            local res = ngx.location.capture("/sub")
            ngx.print(res.body)
            ngx.say("pr: Cookie: ", ngx.var.http_cookie)
        ';
    }
--- request
GET /t
--- more_headers
Cookie: foo
Cookie: bar
--- response_body
sr: Cookie: foo; bar
pr: Cookie: foo; bar

--- no_error_log
[error]



=== TEST 73: HEAD subrequest (github #347)
--- config
    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/index.html",
                { method = ngx.HTTP_HEAD });
            ngx.say("content-length: ", res.header["Content-Length"])
            ngx.say("body: [", res.body, "]")
        ';
    }
--- request
GET /lua
--- response_body_like chop
^content-length: \d+
body: \[\]
$
--- no_error_log
[error]



=== TEST 74: image_filter + ngx.location.capture
ngx_http_image_filter_module's header filter intercepts
the header filter chain so the r->header_sent flag won't
get set right after the header filter chain is first invoked.

--- config

location = /back {
    empty_gif;
}

location = /t {
    image_filter rotate 90;

    content_by_lua '
        local res = ngx.location.capture("/back")
        for k, v in pairs(res.header) do
            ngx.header[k] = v
        end
        ngx.status = res.status
        ngx.print(res.body)
    ';
}

--- request
GET /t
--- response_body_like: .
--- stap
F(ngx_http_image_header_filter) {
    println("image header filter")
}
--- stap_out
image header filter

--- no_error_log
[error]



=== TEST 75: WebDAV + MOVE
--- config
    location = /t {
        content_by_lua_block {
            local file1 = "/file1.txt"
            local file2 = "/file2.txt"
            ngx.req.set_header( "Destination", file2 )
            local res = ngx.location.capture(
                file1, { method = ngx.HTTP_MOVE }
            )

            ngx.say(
                "MOVE ", file1, " -> ", file2,
                ", response status: ", res.status
            )
        }
    }

    location / {
        dav_methods MOVE;
    }

--- user_files
>>> file1.txt
hello, world!

--- request
GET /t

--- response_body
MOVE /file1.txt -> /file2.txt, response status: 204

--- no_error_log
[error]
--- error_code: 200



=== TEST 76: WebDAV + DELETE
--- config
    location = /t {
        content_by_lua_block {
            local file = "/file.txt"
            local res = ngx.location.capture(
                file, { method = ngx.HTTP_DELETE }
            )

            ngx.say(
                "DELETE ", file,
                ", response status: ", res.status
            )
        }
    }

    location / {
        dav_methods DELETE;
    }

--- user_files
>>> file.txt
hello, world!

--- request
GET /t

--- response_body
DELETE /file.txt, response status: 204

--- no_error_log
[error]
--- error_code: 200



=== TEST 77: avoid request smuggling 1/4 (default capture + smuggle in header)
--- http_config
    upstream backend {
        server unix:$TEST_NGINX_HTML_DIR/nginx.sock;
        keepalive 32;
    }

    server {
        listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;

        location / {
            content_by_lua_block {
                ngx.say("method: ", ngx.var.request_method,
                        ", uri: ", ngx.var.uri,
                        ", X: ", ngx.var.http_x)
            }
        }
    }
--- config
    location /proxy {
        proxy_http_version 1.1;
        proxy_set_header   Connection "";
        proxy_pass         http://backend/foo;
    }

    location /capture {
        server_tokens off;
        more_clear_headers Date;

        content_by_lua_block {
            local res = ngx.location.capture("/proxy")
            ngx.print(res.body)
        }
    }

    location /t {
        content_by_lua_block {
            local req = [[
GET /capture HTTP/1.1
Host: test.com
Content-Length: 37
Transfer-Encoding: chunked

0

GET /capture HTTP/1.1
Host: test.com
X: GET /bar HTTP/1.0

]]

            local sock = ngx.socket.tcp()
            sock:settimeout(1000)

            local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
            if not ok then
                ngx.say("failed to connect: ", err)
                return
            end

            local bytes, err = sock:send(req)
            if not bytes then
                ngx.say("failed to send req: ", err)
                return
            end

            ngx.say("req bytes: ", bytes)

            local n_resp = 0

            local reader = sock:receiveuntil("\r\n")
            while true do
                local line, err = reader()
                if line then
                    ngx.say(line)
                    if line == "0" then
                        n_resp = n_resp + 1
                    end

                    if n_resp >= 2 then
                        break
                    end

                else
                    ngx.say("err: ", err)
                    break
                end
            end

            sock:close()
        }
    }
--- request
GET /t
--- response_body
req bytes: 146
HTTP/1.1 200 OK
Server: nginx
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive

1f
method: GET, uri: /foo, X: nil

0

HTTP/1.1 200 OK
Server: nginx
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive

2d
method: GET, uri: /foo, X: GET /bar HTTP/1.0

0
--- no_error_log
[error]
--- skip_nginx: 3: >= 1.21.1



=== TEST 78: avoid request smuggling 2/4 (POST capture + smuggle in body)
--- http_config
    upstream backend {
        server unix:$TEST_NGINX_HTML_DIR/nginx.sock;
        keepalive 32;
    }

    server {
        listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;

        location / {
            content_by_lua_block {
                ngx.say("method: ", ngx.var.request_method,
                        ", uri: ", ngx.var.uri)
            }
        }
    }
--- config
    location /proxy {
        proxy_http_version 1.1;
        proxy_set_header   Connection "";
        proxy_pass         http://backend/foo;
    }

    location /capture {
        server_tokens off;
        more_clear_headers Date;

        content_by_lua_block {
            ngx.req.read_body()
            local res = ngx.location.capture("/proxy", { method = ngx.HTTP_POST })
            ngx.print(res.body)
        }
    }

    location /t {
        content_by_lua_block {
            local req = [[
GET /capture HTTP/1.1
Host: test.com
Content-Length: 57
Transfer-Encoding: chunked

0

POST /capture HTTP/1.1
Host: test.com
Content-Length: 60

POST /bar HTTP/1.1
Host: test.com
Content-Length: 5

hello

]]

            local sock = ngx.socket.tcp()
            sock:settimeout(1000)

            local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
            if not ok then
                ngx.say("failed to connect: ", err)
                return
            end

            local bytes, err = sock:send(req)
            if not bytes then
                ngx.say("failed to send req: ", err)
                return
            end

            ngx.say("req bytes: ", bytes)

            local n_resp = 0

            local reader = sock:receiveuntil("\r\n")
            while true do
                local line, err = reader()
                if line then
                    ngx.say(line)
                    if line == "0" then
                        n_resp = n_resp + 1
                    end

                    if n_resp >= 2 then
                        break
                    end

                else
                    ngx.say("err: ", err)
                    break
                end
            end

            sock:close()
        }
    }
--- request
GET /t
--- response_body
req bytes: 205
HTTP/1.1 200 OK
Server: nginx
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive

18
method: POST, uri: /foo

0

HTTP/1.1 200 OK
Server: nginx
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive

18
method: POST, uri: /foo

0
--- no_error_log
[error]
--- skip_nginx
3: >= 1.21.1



=== TEST 79: avoid request smuggling 3/4 (POST capture w/ always_forward_body + smuggle in body)
--- http_config
    upstream backend {
        server unix:$TEST_NGINX_HTML_DIR/nginx.sock;
        keepalive 32;
    }

    server {
        listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;

        location / {
            content_by_lua_block {
                ngx.say("method: ", ngx.var.request_method,
                        ", uri: ", ngx.var.uri)
            }
        }
    }
--- config
    location /proxy {
        proxy_http_version 1.1;
        proxy_set_header   Connection "";
        proxy_pass         http://backend/foo;
    }

    location /capture {
        server_tokens off;
        more_clear_headers Date;

        content_by_lua_block {
            ngx.req.read_body()
            local res = ngx.location.capture("/proxy", {
                method = ngx.HTTP_POST,
                always_forward_body = true
            })
            ngx.print(res.body)
        }
    }

    location /t {
        content_by_lua_block {
            local req = [[
GET /capture HTTP/1.1
Host: test.com
Content-Length: 57
Transfer-Encoding: chunked

0

POST /capture HTTP/1.1
Host: test.com
Content-Length: 60

POST /bar HTTP/1.1
Host: test.com
Content-Length: 5

hello

]]

            local sock = ngx.socket.tcp()
            sock:settimeout(1000)

            local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
            if not ok then
                ngx.say("failed to connect: ", err)
                return
            end

            local bytes, err = sock:send(req)
            if not bytes then
                ngx.say("failed to send req: ", err)
                return
            end

            ngx.say("req bytes: ", bytes)

            local n_resp = 0

            local reader = sock:receiveuntil("\r\n")
            while true do
                local line, err = reader()
                if line then
                    ngx.say(line)
                    if line == "0" then
                        n_resp = n_resp + 1
                    end

                    if n_resp >= 2 then
                        break
                    end

                else
                    ngx.say("err: ", err)
                    break
                end
            end

            sock:close()
        }
    }
--- request
GET /t
--- response_body
req bytes: 205
HTTP/1.1 200 OK
Server: nginx
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive

18
method: POST, uri: /foo

0

HTTP/1.1 200 OK
Server: nginx
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive

18
method: POST, uri: /foo

0
--- no_error_log
[error]
--- skip_nginx
3: >= 1.21.1



=== TEST 80: avoid request smuggling 4/4 (POST capture w/ body + smuggle in body)
--- http_config
    upstream backend {
        server unix:$TEST_NGINX_HTML_DIR/nginx.sock;
        keepalive 32;
    }

    server {
        listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;

        location / {
            content_by_lua_block {
                ngx.say("method: ", ngx.var.request_method,
                        ", uri: ", ngx.var.uri)
            }
        }
    }
--- config
    location /proxy {
        proxy_http_version 1.1;
        proxy_set_header   Connection "";
        proxy_pass         http://backend/foo;
    }

    location /capture {
        server_tokens off;
        more_clear_headers Date;

        content_by_lua_block {
            ngx.req.read_body()
            local res = ngx.location.capture("/proxy", {
                method = ngx.HTTP_POST,
                always_forward_body = true,
                body = ngx.req.get_body_data()
            })
            ngx.print(res.body)
        }
    }

    location /t {
        content_by_lua_block {
            local req = [[
GET /capture HTTP/1.1
Host: test.com
Content-Length: 57
Transfer-Encoding: chunked

0

POST /capture HTTP/1.1
Host: test.com
Content-Length: 60

POST /bar HTTP/1.1
Host: test.com
Content-Length: 5

hello

]]

            local sock = ngx.socket.tcp()
            sock:settimeout(1000)

            local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
            if not ok then
                ngx.say("failed to connect: ", err)
                return
            end

            local bytes, err = sock:send(req)
            if not bytes then
                ngx.say("failed to send req: ", err)
                return
            end

            ngx.say("req bytes: ", bytes)

            local n_resp = 0

            local reader = sock:receiveuntil("\r\n")
            while true do
                local line, err = reader()
                if line then
                    ngx.say(line)
                    if line == "0" then
                        n_resp = n_resp + 1
                    end

                    if n_resp >= 2 then
                        break
                    end

                else
                    ngx.say("err: ", err)
                    break
                end
            end

            sock:close()
        }
    }
--- request
GET /t
--- response_body
req bytes: 205
HTTP/1.1 200 OK
Server: nginx
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive

18
method: POST, uri: /foo

0

HTTP/1.1 200 OK
Server: nginx
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive

18
method: POST, uri: /foo

0
--- no_error_log
[error]
--- skip_nginx
3: >= 1.21.1



=== TEST 81: bad HTTP method
--- config
    location /other { }

    location /lua {
        content_by_lua_block {
            local res = ngx.location.capture("/other",
                { method = 10240 });
        }
    }
--- request
GET /lua
--- response_body_like: 500 Internal Server Error
--- error_code: 500
--- error_log
unsupported HTTP method: 10240



=== TEST 82: bad requests with both Content-Length and Transfer-Encoding (nginx >= 1.21.1)
--- http_config
    upstream backend {
        server unix:$TEST_NGINX_HTML_DIR/nginx.sock;
        keepalive 32;
    }

    server {
        listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;

        location / {
            content_by_lua_block {
                ngx.say("method: ", ngx.var.request_method,
                        ", uri: ", ngx.var.uri,
                        ", X: ", ngx.var.http_x)
            }
        }
    }
--- config
    location /proxy {
        proxy_http_version 1.1;
        proxy_set_header   Connection "";
        proxy_pass         http://backend/foo;
    }

    location /capture {
        server_tokens off;
        more_clear_headers Date;

        content_by_lua_block {
            local res = ngx.location.capture("/proxy")
            ngx.print(res.body)
        }
    }

    location /t {
        content_by_lua_block {
            local req = [[
GET /capture HTTP/1.1
Host: test.com
Content-Length: 37
Transfer-Encoding: chunked

0

GET /capture HTTP/1.1
Host: test.com
X: GET /bar HTTP/1.0

]]

            local sock = ngx.socket.tcp()
            sock:settimeout(1000)

            local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
            if not ok then
                ngx.say("failed to connect: ", err)
                return
            end

            local bytes, err = sock:send(req)
            if not bytes then
                ngx.say("failed to send req: ", err)
                return
            end

            ngx.say("req bytes: ", bytes)

            local n_resp = 0

            local reader = sock:receiveuntil("\r\n")
            while true do
                local line, err = reader()
                if line then
                    ngx.say(line)
                    if line == "0" then
                        n_resp = n_resp + 1
                    end

                    if n_resp >= 2 then
                        break
                    end

                else
                    ngx.say("err: ", err)
                    break
                end
            end

            sock:close()
        }
    }
--- request
GET /t
--- response_body_like
req bytes: 146
HTTP/1.1 400 Bad Request
--- no_error_log
[error]
--- skip_nginx
3: < 1.21.1



=== TEST 83: avoid request smuggling of HEAD req
--- config
    location /capture {
        server_tokens off;
        more_clear_headers Date;

        content_by_lua_block {
            ngx.say("Hello")
        }
    }

    location /t {
        content_by_lua_block {
            local req = [[
HEAD /capture HTTP/1.1
Host: test.com
Content-Length: 63

GET /capture HTTP/1.1
Host: test.com
X: GET /bar HTTP/1.0

]]

            local sock = ngx.socket.tcp()
            sock:settimeout(1000)

            local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
            if not ok then
                ngx.say("failed to connect: ", err)
                return
            end

            local bytes, err = sock:send(req)
            if not bytes then
                ngx.say("failed to send req: ", err)
                return
            end

            ngx.say("req bytes: ", bytes)

            local n_resp = 0

            local reader = sock:receiveuntil("\r\n")
            while true do
                local line, err = reader()
                if line then
                    ngx.say(line)
                    if line == "0" then
                        n_resp = n_resp + 1
                    end

                    if n_resp >= 2 then
                        break
                    end

                else
                    ngx.say("err: ", err)
                    break
                end
            end

            sock:close()
        }
    }
--- request
GET /t
--- response_body
req bytes: 117
HTTP/1.1 200 OK
Server: nginx
Content-Type: text/plain
Connection: keep-alive

err: timeout
--- error_log
lua tcp socket read timed out
