view h2_absolute_redirect.t @ 1961:fe6f22da53ec default tip

Tests: tests for usage of discarded body. The client_max_body_size limit should be ignored when the request body is already discarded. In HTTP/1.x, this is done by checking the r->discard_body flag when the body is being discarded, and because r->headers_in.content_length_n is 0 when it's already discarded. This, however, does not happen with HTTP/2 and HTTP/3, and therefore "error_page 413" does not work without relaxing the limit. Further, with proxy_pass, r->headers_in.content_length_n is used to determine length of the request body, and therefore is not correct if discarding of the request body isn't yet complete. While discarding the request body, r->headers_in.content_length_n contains the rest of the body to discard (or, in case of chunked request body, the rest of the current chunk to discard). Similarly, the $content_length variable uses r->headers_in.content_length if available, and also incorrect. The $content_length variable is used when proxying with fastcgi_pass, grpc_pass, and uwsgi_pass (scgi_pass uses the value calculated based on the actual request body buffers, and therefore works correctly).
author Maxim Dounin <mdounin@mdounin.ru>
date Sat, 27 Apr 2024 18:55:50 +0300
parents 2a0a6035a1af
children
line wrap: on
line source

#!/usr/bin/perl

# (C) Sergey Kandaurov
# (C) Nginx, Inc.

# Tests for absolute_redirect directive and Location escaping.

###############################################################################

use warnings;
use strict;

use Test::More;

BEGIN { use FindBin; chdir($FindBin::Bin); }

use lib 'lib';
use Test::Nginx;
use Test::Nginx::HTTP2;

###############################################################################

select STDERR; $| = 1;
select STDOUT; $| = 1;

my $t = Test::Nginx->new()->has(qw/http http_v2 proxy rewrite/)
	->write_file_expand('nginx.conf', <<'EOF');

%%TEST_GLOBALS%%

daemon off;

events {
}

http {
    %%TEST_GLOBALS_HTTP%%

    absolute_redirect off;

    server {
        listen       127.0.0.1:8080 http2;
        server_name  on;

        absolute_redirect on;
        error_page 400 /return301;

        location / { }

        location /auto/ {
            proxy_pass http://127.0.0.1:8080;
        }

        location "/auto sp/" {
            proxy_pass http://127.0.0.1:8080;
        }

        location /return301 {
            return 301 /redirect;
        }

        location /return301/name {
            return 301 /redirect;
            server_name_in_redirect on;
        }

        location /return301/port {
            return 301 /redirect;
            port_in_redirect off;
        }

        location /i/ {
            alias %%TESTDIR%%/;
        }
    }

    server {
        listen       127.0.0.1:8080 http2;
        server_name  off;

        location / { }

        location /auto/ {
            proxy_pass http://127.0.0.1:8080;
        }

        location "/auto sp/" {
            proxy_pass http://127.0.0.1:8080;
        }

        location '/auto "#%<>?\^`{|}/' {
            proxy_pass http://127.0.0.1:8080;
        }

        location /return301 {
            return 301 /redirect;
        }

        location /i/ {
            alias %%TESTDIR%%/;
        }
    }
}

EOF

mkdir($t->testdir() . '/dir');
mkdir($t->testdir() . '/dir sp');

# suppress deprecation warning

open OLDERR, ">&", \*STDERR; close STDERR;
$t->run()->plan(23);
open STDERR, ">&", \*OLDERR;

###############################################################################

my $p = port(8080);

like(get('on', '/dir'), qr!Location: http://on:$p/dir/\x0d?$!m, 'directory');
like(get('on', '/i/dir'), qr!Location: http://on:$p/i/dir/\x0d?$!m,
	'directory alias');

like(get('on', '/dir%20sp'), qr!Location: http://on:$p/dir%20sp/\x0d?$!m,
	'directory escaped');
like(get('on', '/dir%20sp?a=b'),
	qr!Location: http://on:$p/dir%20sp/\?a=b\x0d?$!m,
	'directory escaped args');

like(get('on', '/auto'), qr!Location: http://on:$p/auto/\x0d?$!m, 'auto');
like(get('on', '/auto?a=b'), qr!Location: http://on:$p/auto/\?a=b\x0d?$!m,
	'auto args');

like(get('on', '/auto%20sp'), qr!Location: http://on:$p/auto%20sp/\x0d?$!m,
	'auto escaped');
like(get('on', '/auto%20sp?a=b'),
	qr!Location: http://on:$p/auto%20sp/\?a=b\x0d?$!m,
	'auto escaped args');

like(get('on', '/return301'), qr!Location: http://on:$p/redirect\x0d?$!m,
	'return');

like(get('host', '/return301/name'), qr!Location: http://on:$p/redirect\x0d?!m,
	'server_name_in_redirect on');
like(get('host', '/return301'), qr!Location: http://host:$p/redirect\x0d?$!m,
	'server_name_in_redirect off - using host');
my $ph = IO::Socket::INET->new("127.0.0.1:$p")->peerhost();
like(get('.', '/return301'), qr!Location: http://$ph:$p/redirect\x0d?$!m,
	'invalid host - using local sockaddr');
like(get('host', '/return301/port'), qr!Location: http://host/redirect\x0d?$!m,
	'port_in_redirect off');

like(get('off', '/dir'), qr!Location: /dir/\x0d?$!m, 'off directory');
like(get('off', '/i/dir'), qr!Location: /i/dir/\x0d?$!m, 'off directory alias');

like(get('off', '/dir%20sp'), qr!Location: /dir%20sp/\x0d?$!m,
	'off directory escaped');
like(get('off', '/dir%20sp?a=b'), qr!Location: /dir%20sp/\?a=b\x0d?$!m,
	'off directory escaped args');

like(get('off', '/auto'), qr!Location: /auto/\x0d?$!m, 'off auto');
like(get('off', '/auto?a=b'), qr!Location: /auto/\?a=b\x0d?$!m,
	'off auto args');

like(get('off', '/auto%20sp'), qr!Location: /auto%20sp/\x0d?$!m,
	'auto escaped');
like(get('off', '/auto%20sp?a=b'), qr!Location: /auto%20sp/\?a=b\x0d?$!m,
	'auto escaped args');

like(get('off', '/return301'), qr!Location: /redirect\x0d?$!m, 'off return');

# per RFC 3986, these characters are not allowed unescaped:
# %00-%1F, %7F-%FF, " ", """, "<", ">", "\", "^", "`", "{", "|", "}"
# additionally, all characters in ESCAPE_URI: "?", "%", "#"

SKIP: {
skip 'win32', 1 if $^O eq 'MSWin32';

like(get('off', '/auto%20%22%23%25%3C%3E%3F%5C%5E%60%7B%7C%7D'),
	qr!Location: /auto%20%22%23%25%3C%3E%3F%5C%5E%60%7B%7C%7D/\x0d?$!m,
	'auto escaped strict');

}

###############################################################################

sub get {
	my ($host, $uri) = @_;

	my $s = Test::Nginx::HTTP2->new();
	my $sid = $s->new_stream({ host => $host, path => $uri });
	my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]);

	my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
	return 'Location: ' . $frame->{headers}->{'location'};
}

###############################################################################