#!/usr/bin/perl

# (C) Andrey Belov

# Tests for disable_symlinks directive.

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

use warnings;
use strict;

use Test::More;
use POSIX;
use Cwd qw/ realpath /;

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

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

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

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

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

%%TEST_GLOBALS%%

daemon off;

events {
}

http {
    %%TEST_GLOBALS_HTTP%%

    server {
        listen       127.0.0.1:8080;
        server_name  s1;

        location /on/ {
            disable_symlinks on;
        }

        location /not_owner/ {
            disable_symlinks if_not_owner;
        }

        location /try_on/ {
            disable_symlinks on;
            try_files $uri $uri.html =404;
        }

        location /try_not_owner/ {
            disable_symlinks if_not_owner;
            try_files $uri $uri.txt =404;
        }

        location /if_on/ {
            disable_symlinks on;
            if (-f $request_filename) {
                return 204;
            }
        }

        location /if_not_owner/ {
            disable_symlinks if_not_owner;
            if (-f $request_filename) {
                return 204;
            }
        }

        location /complex/1/ {
            disable_symlinks on;
            alias %%TESTDIR%%/./cached/../;
        }

        location /complex/2/ {
            disable_symlinks on;
            alias %%TESTDIR%%//./cached/..//;
        }

        location /complex/3/ {
            disable_symlinks on;
            alias ///%%TESTDIR%%//./cached/..//;
        }

        location ~ (.+/)tail$ {
            disable_symlinks on;
            alias %%TESTDIR%%/$1;
        }

        location ~ (.+/)tailowner$ {
            disable_symlinks if_not_owner;
            alias %%TESTDIR%%/$1;
        }

        location ~ (.+/)tailoff$ {
            disable_symlinks off;
            alias %%TESTDIR%%/$1;
        }

        location /dir {
            disable_symlinks on;
            try_files $uri/ =404;
        }

        location /from {
            disable_symlinks on from=$document_root;

            location /from/wo_slash {
                alias %%TESTDIR%%/dirlink;
            }
            location /from/with_slash/ {
                alias %%TESTDIR%%/dirlink/;
            }
            location ~ ^/from/exact/(.+)$ {
                alias %%TESTDIR%%/$1;
            }
        }
    }

    server {
        listen       127.0.0.1:8080;
        server_name  s2;

        open_file_cache max=16 inactive=60s;
        open_file_cache_valid 30s;
        open_file_cache_min_uses 1;
        open_file_cache_errors on;

        location /cached-off/ {
            disable_symlinks off;
            alias %%TESTDIR%%/cached/;
        }

        location /cached-on/ {
            disable_symlinks on;
            alias %%TESTDIR%%/cached/;
        }

        location /cached-if-not-owner/ {
            disable_symlinks if_not_owner;
            alias %%TESTDIR%%/cached/;
        }

        location / {
            disable_symlinks off;
        }
    }
}

EOF

my $uid = getuid();
my ($extfile) = grep { -f && !-l && $uid != (stat())[4] }
	('/etc/resolv.conf', '/etc/protocols', '/etc/host.conf');

plan(skip_all => 'no external file found')
	if !defined $extfile;

$t->try_run('no disable_symlinks')->plan(28);

my $d = $t->testdir();

mkdir("$d/on");
mkdir("$d/not_owner");
mkdir("$d/try_on");
mkdir("$d/try_not_owner");
mkdir("$d/if_on");
mkdir("$d/if_not_owner");
mkdir("$d/cached");

$t->write_file("empty.html", "");
symlink("empty.html", "$d/link");
symlink($extfile, "$d/link2");

$t->write_file("on/empty.html", "");
symlink("empty.html", "$d/on/link");
symlink($extfile, "$d/on/link2");

$t->write_file("not_owner/empty.html", "");
symlink("empty.html", "$d/not_owner/link");
symlink($extfile, "$d/not_owner/link2");

$t->write_file("try_on/try.html", "LOCAL TRY");
symlink($extfile, "$d/try_on/try");

$t->write_file("try_not_owner/try.html", "LOCAL TRY");
symlink($extfile, "$d/try_not_owner/try");
symlink("try.html", "$d/try_not_owner/try.txt");

$t->write_file("if_on/empty.html", "");
symlink("empty.html", "$d/if_on/link");
symlink($extfile, "$d/if_on/link2");

$t->write_file("if_not_owner/empty.html", "");
symlink("empty.html", "$d/if_not_owner/link");
symlink($extfile, "$d/if_not_owner/link2");

mkdir("$d/dir");
$t->write_file("dir/empty.html", "");
symlink("dir", "$d/dirlink");

symlink($extfile, "$d/cached/link");

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

SKIP: {
skip 'cannot test under symlink', 25 if $d ne realpath($d) or $^O eq 'netbsd';

like(http_get_host('s1', '/link'), qr!200 OK!, 'static (off, same uid)');
like(http_get_host('s1', '/link2'), qr!200 OK!, 'static (off, other uid)');

like(http_get_host('s1', '/on/link'), qr!403 Forbidden!,
	'static (on, same uid)');
like(http_get_host('s1', '/on/link2'), qr!403 Forbidden!,
	'static (on, other uid)');

like(http_get_host('s1', '/not_owner/link'), qr!200 OK!,
	'static (if_not_owner, same uid)');
like(http_get_host('s1', '/not_owner/link2'), qr!403 Forbidden!,
	'static (if_not_owner, other uid)');

like(http_get_host('s1', '/try_on/try'), qr/LOCAL TRY/,
	'try_files (on)');
like(http_get_host('s1', '/try_not_owner/try'), qr/LOCAL TRY/,
	'try_files (if_not_owner)');

like(http_get_host('s1', '/if_on/link'), qr!403 Forbidden!,
	'if (on, same uid)');
like(http_get_host('s1', '/if_on/link2'), qr!403 Forbidden!,
	'if (on, other uid)');

like(http_get_host('s1', '/if_not_owner/link'), qr!204 No Content!,
	'if (if_not_owner, same uid)');
like(http_get_host('s1', '/if_not_owner/link2'), qr!403 Forbidden!,
	'if (if_not_owner, other uid)');

like(http_get_host('s2', '/cached-off/link'), qr!200 OK!,
	'open_file_cache (pass 1)');
like(http_get_host('s2', '/cached-on/link'), qr!403 Forbidden!,
	'open_file_cache (pass 2)');
like(http_get_host('s2', '/cached-off/link'), qr!200 OK!,
	'open_file_cache (pass 3)');
like(http_get_host('s2', '/cached-if-not-owner/link'), qr!403 Forbidden!,
	'open_file_cache (pass 4)');
like(http_get_host('s2', '/cached-off/link'), qr!200 OK!,
	'open_file_cache (pass 5)');

like(http_get('/complex/1/empty.html'), qr!200 OK!, 'complex root 1');
like(http_get('/complex/2/empty.html'), qr!200 OK!, 'complex root 2');
like(http_get('/complex/3/empty.html'), qr!200 OK!, 'complex root 3');

# workaround for freebsd 8: we use O_EXEC instead of O_SEARCH (since there
# is no O_SEARCH), and O_DIRECTORY does nothing.  setting the 'x' bit
# tests to pass as openat() will correctly fail with ENOTDIR

chmod(0700, "$d/link");
my $rc = $^O eq 'darwin' ? 200 : 404;

like(http_get('/link/tail'), qr!40[34] !, 'file with trailing /, on');
like(http_get('/link/tailowner'), qr!404 !, 'file with trailing /, owner');
like(http_get('/link/tailoff'), qr!$rc !, 'file with trailing /, off');

like(http_get('/dirlink'), qr!404 !, 'directory without /');
like(http_get('/dirlink/'), qr!404 !, 'directory with trailing /');

} # SKIP: cannot test under symlink

like(http_get('/from/wo_slash/empty.html'), qr!200 OK!, '"from=" without /');
like(http_get('/from/with_slash/empty.html'), qr!200 OK!, '"from=" with /');
like(http_get('/from/exact/link'), qr!200 OK!, '"from=" exact match');

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

sub http_get_host {
	my ($host, $url) = @_;
	return http(<<EOF);
GET $url HTTP/1.0
Host: $host

EOF
}

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