# Copyright 2014-2021 SUSE LLC
# SPDX-License-Identifier: GPL-2.0-or-later

use Test::Most;

use FindBin;
use File::Spec;
use lib "$FindBin::Bin/../lib", "$FindBin::Bin/../../external/os-autoinst-common/lib";
use Test::Mojo;
use Test::Warnings ':report_warnings';
use Mojo::File 'path';
use OpenQA::Test::TimeLimit '8';
use OpenQA::Test::Case;

my $schema = OpenQA::Test::Case->new->init_data(fixtures_glob => '01-jobs.pl 05-job_modules.pl 07-needles.pl');
$schema->resultset('Assets')->search({size => undef})->update({size => 0});

my $t = Test::Mojo->new('OpenQA::WebAPI');

# Exact size of logpackages-1.png
$t->get_ok('/tests/99938/images/logpackages-1.png')->status_is(200)->content_type_is('image/png')
  ->header_is('Content-Length' => '48019');

$t->get_ok('/tests/99937/../99938/images/logpackages-1.png')->status_is(404);

$t->get_ok('/tests/99938/images/thumb/logpackages-1.png')->status_is(200)->content_type_is('image/png')
  ->header_is('Content-Length' => '6769');

# Not the same logpackages-1.png
$t->get_ok('/tests/99946/images/logpackages-1.png')->header_is('Content-Length' => '211');

$t->get_ok('/tests/99938/images/doesntexist.png')->status_is(404);

$t->get_ok('/tests/99938/images/thumb/doesntexist.png')->status_is(404);

$t->get_ok('/tests/99938/file/video.ogv')->status_is(200)->content_type_is('video/ogg');

$t->get_ok('/tests/99938/file/serial0.txt')->status_is(200);
$t->content_type_is('text/plain;charset=UTF-8', 'content type for text file returned');
$t->header_is('X-Content-Type-Options' => 'nosniff', 'nosniff header added to prevent download prompt for text file');

$t->get_ok('/tests/99938/file/y2logs.tar.bz2')->status_is(200);
$t->content_type_is('application/x-bzip2', 'content type for archive returned');
$t->header_isnt('X-Content-Type-Options' => 'nosniff', 'nosniff header not added for archive/binary file');

$t->get_ok('/tests/99938/file/ulogs/y2logs.tar.bz2')->status_is(404);

subtest 'needle download' => sub {
    # clean leftovers from previous run
    my $needle_path = 't/data/openqa/share/tests/opensuse/needles';
    my $abs_needle_path = File::Spec->rel2abs($needle_path);
    my $needle_dir = Mojo::File->new($needle_path);
    $needle_dir->remove_tree();

    $t->get_ok('/needles/opensuse/inst-timezone-text.png')->status_is(404, '404 if image not present');
    $t->get_ok('/needles/1/image')->status_is(404, '404 if image not present');
    $t->get_ok('/needles/1/json')->status_is(404, '404 if json not present');

    # create fake json file and image
    $needle_dir->make_path();
    my $json
      = '{"area" : [{"height": 217, "type": "match", "width": 384, "xpos": 0, "ypos": 0},{"height": 60, "type": "exclude", "width": 160, "xpos": 175, "ypos": 45}], "tags": ["inst-timezone"]}';
    path("$needle_dir/inst-timezone-text.png")->spew("png\n");
    path("$needle_dir/inst-timezone-text.json")->spew($json);

    # and another, in a subdirectory, to test that
    my $needle_subdir = Mojo::File->new('t/data/openqa/share/tests/opensuse/needles/subdirectory');
    $needle_subdir->make_path();
    my $json2
      = '{"area" : [{"height": 217, "type": "match", "width": 384, "xpos": 0, "ypos": 0},{"height": 60, "type": "exclude", "width": 160, "xpos": 175, "ypos": 45}], "tags": ["inst-subdirectory"]}';
    path("$needle_subdir/inst-subdirectory.png")->spew("png\n");
    path("$needle_subdir/inst-subdirectory.json")->spew($json2);

    $t->get_ok('/needles/opensuse/inst-timezone-text.png')->status_is(200)->content_type_is('image/png')
      ->content_is("png\n");
    $t->get_ok('/needles/1/image')->status_is(200)->content_type_is('image/png')->content_is("png\n");
    $t->get_ok('/needles/1/json')->status_is(200)->content_type_is('application/json;charset=UTF-8')->content_is($json);

    # broken requests we saw in production that should not get past input validation
    $t->get_ok('/needles/root-console-20200501/image')->status_is(404);
    $t->get_ok('/needles/root-console-20200501/json')->status_is(404);

    $t->get_ok('/needles/opensuse/subdirectory/inst-subdirectory.png');

    # currently you can only find a needle in a subdirectory by passing the
    # jsonfile query parameter like this:
    $t->get_ok("/needles/opensuse/inst-subdirectory.png?jsonfile=$needle_path/subdirectory/inst-subdirectory.json")
      ->status_is(200)->content_type_is('image/png')->content_is("png\n");
    # also test with jsonfile as absolute path (as usual in production)
    $t->get_ok("/needles/opensuse/inst-subdirectory.png?jsonfile=$abs_needle_path/subdirectory/inst-subdirectory.json")
      ->status_is(200)->content_type_is('image/png')->content_is("png\n");

    my $other_subdir = Mojo::File->new('t/data/openqa/share/tests/subdir');
    $t->get_ok("/needles/subdir/needle.png?jsonfile=$other_subdir/needle.json");
    $t->status_is(404, 'can specify path in any subdir under t/data/openqa/share/tests without running into 403 error');

    # getting needle image and json by ID also does not work for needles
    # in subdirectories, but arguably should do and should be tested:
    #$t->get_ok('/needles/2/image')->status_is(200)->content_type_is('image/png')->content_is("png\n");
  #$t->get_ok('/needles/2/json')->status_is(200)->content_type_is('application/json;charset=UTF-8')->content_is($json2);
};


# check the download links
$t->get_ok('/tests/99946/downloads_ajax')->status_is(200)->element_exists('#asset_1')->element_exists('#asset_5');
my $res = OpenQA::Test::Case::trim_whitespace($t->tx->res->dom->at('#asset_1')->text);
is $res, 'openSUSE-13.1-DVD-i586-Build0091-Media.iso';
is $t->tx->res->dom->at('#asset_1')->{href}, '/tests/99946/asset/iso/openSUSE-13.1-DVD-i586-Build0091-Media.iso';
$res = OpenQA::Test::Case::trim_whitespace($t->tx->res->dom->at('#asset_5')->text);
is $res, 'openSUSE-13.1-x86_64.hda';
is $t->tx->res->dom->at('#asset_5')->{href}, '/tests/99946/asset/hdd/openSUSE-13.1-x86_64.hda';
$t->get_ok('/tests/99938/downloads_ajax')->status_is(200)
  ->element_exists('a[href=/tests/99938/video?filename=video.ogv]', 'link to video player contains filename');

# downloads are currently redirects
$t->get_ok('/tests/99946/asset/1')->status_is(302)
  ->header_like(Location => qr/(?:http:\/\/localhost:\d+)?\/assets\/iso\/openSUSE-13.1-DVD-i586-Build0091-Media.iso/);
$t->get_ok('/tests/99946/asset/iso/openSUSE-13.1-DVD-i586-Build0091-Media.iso')->status_is(302)
  ->header_like(Location => qr/(?:http:\/\/localhost:\d+)?\/assets\/iso\/openSUSE-13.1-DVD-i586-Build0091-Media.iso/);

$t->get_ok('/tests/99946/asset/5')->status_is(302)
  ->header_like(Location => qr/(?:http:\/\/localhost:\d+)?\/assets\/hdd\/fixed\/openSUSE-13.1-x86_64.hda/);

# verify error on invalid downloads
$t->get_ok('/tests/99946/asset/iso/foobar.iso')->status_is(404);
$t->get_ok('/tests/99938/asset/0/foo')->status_is(400)->content_like(qr/wrong parameters/i);

$t->get_ok('/tests/99961/asset/repo/testrepo/README')->status_is(302)
  ->header_like(Location => qr/(?:http:\/\/localhost:\d+)?\/assets\/repo\/testrepo\/README/);
$t->get_ok('/tests/99961/asset/repo/testrepo/README/../README')->status_is(400)
  ->content_is('invalid character in path');

# download_asset is handled by apache normally, but make sure it works - important for fullstack test
$t->get_ok('/assets/repo/testrepo/README')->status_is(200)->content_type_is('application/octet-stream');
$t->get_ok('/assets/repo/testrepo/README.html')->status_is(200);
$t->content_type_is('text/html;charset=UTF-8', 'correct content type for HTML document');
$t->header_is(
    'Content-Disposition',
    'attachment; filename=README.html;',
    'HTML by default served as attachment for security'
);
$t->get_ok('/assets/iso/openSUSE-13.1-DVD-i586-Build0091-Media.iso')->status_is(200)
  ->content_type_is('application/octet-stream');
$t->get_ok('/assets/iso/../iso/openSUSE-13.1-DVD-i586-Build0091-Media.iso')->status_is(404);
# created with `qemu-img create -f qcow2 t/data/openqa/share/factory/hdd/foo.qcow2 0`
$t->get_ok('/assets/hdd/foo.qcow2')->status_is(200)->content_type_is('application/octet-stream');
$t->get_ok('/assets/repo/testrepo/doesnotexist')->status_is(404);

subtest 'file security policy' => sub {

    subtest 'file domain' => sub {
        my $config = $t->app->config->{global};
        $config->{file_security_policy} = 'domain:openqa-files';
        $config->{file_domain} = 'openqa-files';
        $t->get_ok('/tests/99938/downloads_ajax')->status_is(200);
        my $link = $t->tx->res->dom->find('a')->grep(qr/vars.json/)->map(attr => 'href')->first;
        like $link, qr{^/}, 'link to vars.json points to same domain';
        $link = $t->tx->res->dom->find('a')->grep(qr/report.html/)->map(attr => 'href')->first;
        like $link, qr{^http://openqa-files/}, 'link to user HTML file points to file domain subdomain';

        for my $url (qw(/assets/repo/testrepo/README.html /tests/99938/file/report.html)) {
            $t->get_ok($url)->status_is(200);
            $t->header_like(
                'Content-Disposition',
                qr/attachment; filename=.*.html;/,
                "$url - HTML under original domain served as attachment"
            );
        }

        $t->get_ok('/tests/99961/asset/repo/testrepo/README')->status_is(302, 'asset is redirected to asset url');
        $t->header_like(Location => qr|^/assets/repo/testrepo/README$|);
    };

    subtest 'insecure-browsing' => sub {
        my $config = $t->app->config->{global};
        delete $config->{file_domain};
        $config->{file_security_policy} = 'insecure-browsing';
        $t->get_ok('/tests/99938/downloads_ajax')->status_is(200);
        my $link = $t->tx->res->dom->find('a')->grep(qr/report.html/)->map(attr => 'href')->first;
        like $link, qr{^/}, 'link to HTML file points to same domain';

        for my $url (qw(/assets/repo/testrepo/README.html /tests/99938/file/report.html)) {
            $t->get_ok($url)->status_is(200);
            $t->header_is('Content-Disposition', undef, "$url - Not served as attachment");
        }
    };

    subtest 'download-prompt' => sub {
        my $config = $t->app->config->{global};
        $config->{file_security_policy} = 'download-prompt';
        $t->get_ok('/tests/99938/downloads_ajax')->status_is(200);
        my $link = $t->tx->res->dom->find('a')->grep(qr/report.html/)->map(attr => 'href')->first;
        like $link, qr{^/}, 'link to HTML file points to same domain';

        $t->get_ok('/assets/repo/testrepo/README.html')->status_is(200);
        $t->header_is(
            'Content-Disposition',
            'attachment; filename=README.html;',
            'HTML under original domain served as attachment'
        );
    };
};

done_testing();
