# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2019 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT
package CloudLinux;

use strict;
use warnings;

use JSON::XS;
use Text::Trim qw(trim);
use Cpanel::SafeRun::Object();
use Whostmgr::HTMLInterface ();
use Whostmgr::ACLS ();
use MIME::Base64;
use constant ASSETS_PATH => "/3rdparty/cloudlinux/assets";

use constant OWNER_ADMIN => 'admin';
use constant OWNER_USER => 'user';
use constant OWNER_RESELLER => 'reseller';

use constant APP_MODE => 'PRODUCTION_MODE';
use constant DEFAULT_LANGUAGE => 'en';
use constant DOC_ROOT => "/usr/local/cpanel/whostmgr/docroot";
use constant CLOUDLINUX_CLI => '/usr/share/l.v.e-manager/utils/cloudlinux-cli.py';
use constant CLOUDLINUX_CLI_USER => '/usr/share/l.v.e-manager/utils/cloudlinux-cli-user.py';

my $CURRENT_USER = $ENV{'TEAM_OWNER'} ? $ENV{'TEAM_OWNER'} : $ENV{'REMOTE_USER'};
my $current_locale;
my $user_type;

sub detectLocale
{
    $current_locale = _getCurrentLocale($_[0]);
}

sub parseForm {
    my (%DATA) = @_;
    my %result;

    foreach my $key (keys %DATA) {
        if ($key =~ /^file\-/) {
            if ($key =~ /^file-(.+)-key$/) {
                my $fileName = $1;
                my $filePath = $DATA{"file-$1"};
                if ($filePath =~ /\/Cpanel_Form_file\.upload\.[a-z0-9]{8,10}$/) {
                    unshift (@{$result{$DATA{$key}}}, {'name' => $fileName, 'file' => $filePath});
                }
            }
        }
        elsif ($key =~ /^([^\[\]]+)(\[.+\])$/) {
            my $name_of_param = $1;
            my $path = $2;
            my @parts = $path=~/\[([^\[\]]+)\]/g;
            unshift(@parts, $name_of_param);
            creatBranch(\@parts, \%result, $DATA{$key});
        } else {
            $result{$key} = $DATA{$key};
        }
    }
    return %result;
}

sub creatBranch {
    my ($parts, $post, $value) = @_;

    my $first = shift(@$parts);

    if (ref($_[1]) eq 'HASH') {
        if (exists $_[1]{$first}) {
            creatBranch(\@$parts, $_[1]{$first}, $value);
        } else {
            $_[1]{$first} = @$parts ? \%{getInnerValues($value, @$parts)} : $value;
        }
    }
}

sub getInnerValues {
    my ($value, @parts) = @_;
    my $first = shift(@parts);
    if (@parts) {
        return {$first => getInnerValues($value, @parts)} ;
    } else {
        return {$first => $value};
    }
}

sub _getApplicationMode
{
    my $modeFile = '/usr/share/l.v.e-manager/spa/app_mode.status';
    if (-e $modeFile) {
        return trim(safeRun('cat '.$modeFile));
    }
    return APP_MODE;
}

sub safeRun {
    my $command;
    if(ref($_[0]) eq 'ARRAY'){
        $command = join ' ', $_[0];
    } else {
       $command = join ' ', @_;
    }
    my $proc = Cpanel::SafeRun::Object->new(
        'program'  => '/bin/bash',
        'args'     => [ '-c', $command ],
        'keep_env' => 1
    );
    my $stdout = trim($proc->stdout());
    my $stderr = $proc->stderr();

    if($stdout eq '') {
        return $stderr;
    }
    return $stdout;
}

sub _getUserIdByName
{
    my ($user_name) = @_;
    return trim(safeRun(
        sprintf('id -u %s', $user_name)
    ));
}

sub setJsonHeader {
    my ($content) = @_;
    responseCustomHeaders("Content-type: application/json\n\n", $content);
}

sub responseFile {
    my ($filename) = @_;
    my $filesize;
    sendError("File download error", 0, 0, "File $filename not available for panel user") if !-e $filename;
    open FILE, "< $filename" or sendError("File download error", 0, 0, "File $filename is not available for reading");
    binmode FILE;
    $filesize = -s $filename;
    print "Content-Type:application/x-download\n";
    print "Content-Length: $filesize\n\n";
    local $/ = \10240;
    while (<FILE>){
        print $_;
    }
    exit;
}

sub responseCustomHeaders {
    my ($headers, $content) = @_;
    print "HTTP/1.1 200 OK\n";
    print $headers;
    print $content;
}

sub knockKnock
{
    setJsonHeader('{"result":"success"}');
}


sub sendError {
    my ($errorMessage, $isJSON, $logoutSignal, $details) = @_;
    print "HTTP/1.1 503 Service Unavailable\n";
    print "Content-type: application/json\n\n";
    if ($isJSON) {
        print $errorMessage;
    } else {
        my %res = (
            'result' => $errorMessage,
            'logoutSignal' => $logoutSignal ? $logoutSignal : 0,
            'details' => $details || ''
         );
        print encode_json \%res;
    }
    exit;
}

sub sendUnavailableError {
    my ($pluginName) = @_;
    print "HTTP/1.1 503 Service Unavailable\n";
    print "Content-type: application/json\n\n";
    my %res = (
        'result'   => '',
        'code'     => 503,
        'error_id' => 'ERROR.not_available_plugin',
        'context'  => {
            'pluginName' => $pluginName,
        },
        'icon'     => 'disabled'
    );
    print encode_json \%res;
    exit;
}

sub checkMethod {
    if(($ENV{REQUEST_METHOD} ne $_[0]) && (!defined($_[1]) || $ENV{REQUEST_METHOD} ne $_[1])) {
        print "HTTP/1.1 405 Method Not Allowed\n";
        print "Content-type: text/html\n\n";
        print "Method Not Allowed";
        exit;
    }
}

sub getPluginVersion
{
    return safeRun('cat /usr/share/l.v.e-manager/version');
}

sub _getCurrentLocale
{
    my $cgi = $_[0];
    my $locale =  _getLocaleFromCookie($cgi) || _getSystemLocale();
    return $locale;
}

sub _getLocaleFromCookie
{
    my $cgi = $_[0];
    return $cgi->cookie('session_locale');
}

sub _getSystemLocale
{
    my $userArgument = $user_type eq OWNER_USER ? '' : sprintf('--user=%s', $CURRENT_USER);
    my $responseInJson = safeRun(
        sprintf('cpapi2 %s Locale get_user_locale --output=json 2>/dev/null', $userArgument)
    );
    my %response;
    eval {
        %response = %{decode_json($responseInJson)};
    };

    # If decode_json is catched an exeption or specified key in result doesn't exist
    # set default language
    if ($@ || !exists $response{'cpanelresult'}{'data'}[0]{'locale'}) {
        return DEFAULT_LANGUAGE;
    } else {
        return $response{'cpanelresult'}{'data'}[0]{'locale'};
    }
}

sub loadAssets {
    my ($assetsPath, $mainBundle, $config, $assetsStaticPath) = @_;
    Whostmgr::HTMLInterface::load_css($assetsPath.'/css/bootstrap.min.css');
    Whostmgr::HTMLInterface::load_css($assetsPath.'/css/lvemanager.css');
    Whostmgr::HTMLInterface::load_css($assetsPath.'/static/common-styles.css');

    loadGlobalVariables($assetsStaticPath);

    Whostmgr::HTMLInterface::load_js($assetsPath.'/js/jquery.min.js');
    Whostmgr::HTMLInterface::load_js($assetsPath.'/js/bootstrap.min.js');
    Whostmgr::HTMLInterface::load_js($assetsPath.'/js/'.$config.'.js');
    Whostmgr::HTMLInterface::load_js($assetsPath.'/js/common.js');

    # For integration tests, don't remove comment in production in line below
    #Whostmgr::HTMLInterface::load_js($assetsPath.'/js/interceptor.js'); #for integration tests

    my $pluginVersion = getPluginVersion();

    Whostmgr::HTMLInterface::load_js(
        sprintf('%s/static/common.bundle.min.js?v=%s', $assetsPath, $pluginVersion)
    );
    Whostmgr::HTMLInterface::load_js(
        sprintf('%s/static/polyfills.bundle.min.js?v=%s', $assetsPath, $pluginVersion)
    );
    Whostmgr::HTMLInterface::load_js(
        sprintf('%s/static/vendor.bundle.min.js?v=%s', $assetsPath, $pluginVersion)
    );
    Whostmgr::HTMLInterface::load_js(
        sprintf('%s/static/%s.bundle.min.js?v=%s', $assetsPath, $mainBundle, $pluginVersion)
    );
}

sub getDataContent {
    my ($folder, $file_name, $print) = @_;
    my $file = DOC_ROOT.ASSETS_PATH."/$folder/$file_name";
    my $content = '';
    if (-e $file) {
        open(FH, $file);
        while (<FH>){
            $content .= $_;
        }
        close(FH);
    } else {
        $content = qq{<div class="error_block">
            The specified file does not exist</div>};
    }

    if ($print) {
        print $content;
    } else {
        return $content;
    }
}

sub jsonHandler {
    my %data;
    my %REQUEST = %{$_[0]};
    my $requestBody = $_[1];

    my @ALLOWED_COMMANDS = qw(lvectl  cloudlinux-awp-admin  cloudlinux-limits);

    $data{'owner'} = $user_type;
    $data{'command'} = 'lvectl';

    $data{'plugin_name'} = 'jsonhandler';

    foreach my $param (keys %REQUEST)
    {
        if ($param eq 'handler') {
            $data{'method'} = $REQUEST{'handler'};
        } elsif ($param eq 'command') {
            if (grep {$REQUEST{'command'} eq $_} @ALLOWED_COMMANDS) {
                $data{'command'} = $REQUEST{'command'};
            }
            else {
                sendError('COMMAND NOT ALLOWED');
            }
        } elsif (ref($param) ne 'HASH' && $param ne 'cgiaction' ) {
            if (exists $REQUEST{'command'} && $REQUEST{'command'} eq 'cloudlinux-limits' && $param eq 'lveid') {
                $data{'params'}{'lve-id'} = $REQUEST{$param};
            } else {
                $data{'params'}{$param} = $REQUEST{$param};
            }
        }
    }

    if(defined $requestBody) {
        $data{'params'}{'stdin'} = $requestBody;
    }
    my $fullCommandStr;

    $fullCommandStr = "ulimit -m unlimited -v unlimited && " . CLOUDLINUX_CLI;

    $fullCommandStr = sprintf(
        "%s --data=%s",
        $fullCommandStr, encode_base64(encode_json(\%data), '')
    );

    my $responseInJson = safeRun($fullCommandStr);

    setJsonHeader($responseInJson);
}

sub lvemanagerHandler
{
    my ($REQUEST_REF, $plugin_name) = @_;
    my %REQUEST = %$REQUEST_REF;
    unless (exists $REQUEST{'command'}) {
        sendError('COMMAND NOT SPECIFIED');
    }

    my %data;

    $data{'owner'} = $user_type;

    $data{'command'} = $REQUEST{'command'};

    $data{'plugin_name'} = $plugin_name;

    if (exists $REQUEST{'method'}) {
        $data{'method'} = $REQUEST{'method'};
    }

    if (exists $REQUEST{'params'}) {
        $data{'params'} = $REQUEST{'params'};
    }

    if (exists $REQUEST{'attachments[]'}) {
        $data{'attachments'} = [];
        foreach my $file ( @{$REQUEST{'attachments[]'}} ) {
            unshift (@{$data{'attachments'}}, $file);
        }
    }

    if ($data{'owner'} ne OWNER_ADMIN) {
        $data{'user_info'} = {
            'username' => $CURRENT_USER,
            'lve-id'   => _getUserIdByName($CURRENT_USER)
        };
    }

    if (exists $REQUEST{'mockJson'} && $REQUEST{'mockJson'}) {
        $data{'mockJson'} = $REQUEST{'mockJson'};
    }

    if (exists $REQUEST{'lang'} && $REQUEST{'lang'}) {
        $data{'lang'} = $REQUEST{'lang'};
    }

    my $fullCommandStr;

    if ($data{'owner'} eq OWNER_ADMIN) {
        $fullCommandStr = "ulimit -m unlimited -v unlimited && " . CLOUDLINUX_CLI;
    } elsif ($data{'owner'} eq OWNER_RESELLER) {
        $fullCommandStr = CLOUDLINUX_CLI
    } elsif ($data{'owner'} eq OWNER_USER) {
        $fullCommandStr = CLOUDLINUX_CLI_USER;
    }

    $fullCommandStr = sprintf(
        "%s --data=%s",
        $fullCommandStr, encode_base64(JSON::XS->new->encode(\%data), '')
    );

    my $responseInJson = safeRun($fullCommandStr);

    my %response;
    eval {
        %response = %{decode_json($responseInJson)};
    };

    # If decode_json is catched an exeption, send error header with backtrace
    if ($@ && $responseInJson ne '') {
        sendError('ERROR.wrong_received_data', 0, 0, $responseInJson);
    }

    if (exists $response{'result'} && $response{'result'} eq 'file') {
        responseFile($response{'filepath'})
    }

    if (exists $response{'result'} && $response{'result'} ne 'success' && $response{'result'} ne 'rollback') {
        sendError($responseInJson, 1);
    }

    if ($responseInJson eq '') {
        sendError('RESPONSE OF COMMAND IS EMPTY');
    }

    setJsonHeader($responseInJson);
}

sub detectOwner
{
    if (_isAdmin()) {
        return setOwner(OWNER_ADMIN);
    }
    if (_isReseller()) {
        return setOwner(OWNER_RESELLER);
    }
    return setOwner(OWNER_USER);
}

sub setOwner
{
    my ($owner) = @_;
    $user_type = $owner;
    return $owner;
}

sub _isAdmin
{
    if (Whostmgr::ACLS::hasroot()) {
        return 1;
    }
    return 0;
}

sub _isReseller
{
    my $RESELLER_LIST_FILE = '/var/cpanel/resellers';
    my $result = 0;
    if (-e $RESELLER_LIST_FILE) {
        open my $f, $RESELLER_LIST_FILE or die "Could not open $RESELLER_LIST_FILE: $!";


        while( my $line = <$f>)  {
            my @data = split /:/, $line;
            if ($CURRENT_USER eq $data[0]) {
                $result = 1;
                last;
            }
        }
        close $f;
    }
    return $result;
}

sub loadGlobalVariables
{
    my $appMode = _getApplicationMode();
    my $pluginVersion = getPluginVersion();
    my ($assetsStaticPath) = @_;
    printf(
        '<script type="text/javascript">' .
            'var userType = "%s";'.
            'var userName = "%s";'.
            'var currentLanguage = "%s";'.
            'var APP_MODE = "%s";'.
            'var localePath = "%s";'.
            'var assetsStaticPath = "%s";'.
            'var pluginVersion = "%s";'.
        '</script>',
        $user_type,
        $CURRENT_USER,
        $current_locale,
        $appMode,
        $assetsStaticPath.'/i18n/',
        $assetsStaticPath.'/',
        trim($pluginVersion)
    );
}

# Should be present for require
1;
