<commit_before>package OpenXPKI::Client::UI::Request;

use Moose;
use namespace::autoclean;

# Core modules
use Data::Dumper;
use MIME::Base64;
use Carp qw( confess );

# CPAN modules
use JSON;
use Log::Log4perl;
use Crypt::JWT qw( decode_jwt );
use Moose::Util::TypeConstraints;


has cgi => (
    required => 1,
    is => 'ro',
    isa => duck_type( [qw( param multi_param content_type )] ), # not "isa => 'CGI'" as we use CGIMock in tests
);

has session => (
    required => 1,
    is => 'rw',
    isa => 'CGI::Session',
);

has cache => (
    is => 'rw',
    isa => 'HashRef',
    default => sub { return {}; }
);

has method => (
    is => 'rw',
    isa => 'Str',
    default => 'GET',
);

has logger => (
    is => 'ro',
    isa => 'Log::Log4perl::Logger',
    lazy => 1,
    default => sub { return Log::Log4perl->get_logger; }
);

has _prefix_base64 => (
    is => 'ro',
    isa => 'Str',
    default => '_encoded_base64_',
);

has _prefix_jwt => (
    is => 'ro',
    isa => 'Str',
    default => '_encrypted_jwt_',
);

sub BUILD {

    my $self = shift;

    #
    # Preset all keys in the cache (for JSON data, also set the values)
    #
    my %cache;

    # store keys from CGI params
    my @keys = $self->cgi->param;
    $cache{$_} = undef for @keys;
    do { $self->logger->debug(sprintf('CGI param: %s=%s', $_, join(',', $self->cgi->multi_param($_)))) for $self->cgi->param } if $self->logger->is_debug;

    # store keys and values from JSON POST data
    if (($self->cgi->content_type // '') eq 'application/json') {
        $self->logger->debug('Incoming POST data in JSON format (application/json)');

        my $json = JSON->new->utf8;
        my $data = $json->decode( scalar $self->cgi->param('POSTDATA') );

        # wrap Scalars and HashRefs in an ArrayRef as param() expects it (but leave ArrayRefs as is)
        $cache{$_} = (ref $data->{$_} eq 'ARRAY' ? $data->{$_} : [ $data->{$_} ]) for keys %$data;

        $self->logger->debug('JSON param: ' . Dumper $data) if $self->logger->is_debug;
        $self->method('POST');
    }

    # special transformations: insert sanitized keys names in the cache so the
    # check in param() will succeed.
    foreach my $key (keys %cache) {
        # Base64 encoded binary data
        my $prefix_b64 = $self->_prefix_base64;
        if (my ($item) = $key =~ /^$prefix_b64(.*)/) { $cache{$item} = undef; next }
        # JWT encrypted data
        my $prefix_jwt = $self->_prefix_jwt;
        if (my ($item) = $key =~ /^$prefix_jwt(.*)/) { $cache{$item} = undef; next }
    }

    $self->cache( \%cache );

}

sub param {<commit_msg>Fix: re-add accidentially deleted parsing of "key{subkey}" parameters<commit_after>package OpenXPKI::Client::UI::Request;

use Moose;
use namespace::autoclean;

# Core modules
use Data::Dumper;
use MIME::Base64;
use Carp qw( confess );

# CPAN modules
use JSON;
use Log::Log4perl;
use Crypt::JWT qw( decode_jwt );
use Moose::Util::TypeConstraints;


has cgi => (
    required => 1,
    is => 'ro',
    isa => duck_type( [qw( param multi_param content_type )] ), # not "isa => 'CGI'" as we use CGIMock in tests
);

has session => (
    required => 1,
    is => 'rw',
    isa => 'CGI::Session',
);

has cache => (
    is => 'rw',
    isa => 'HashRef',
    default => sub { return {}; }
);

has method => (
    is => 'rw',
    isa => 'Str',
    default => 'GET',
);

has logger => (
    is => 'ro',
    isa => 'Log::Log4perl::Logger',
    lazy => 1,
    default => sub { return Log::Log4perl->get_logger; }
);

has _prefix_base64 => (
    is => 'ro',
    isa => 'Str',
    default => '_encoded_base64_',
);

has _prefix_jwt => (
    is => 'ro',
    isa => 'Str',
    default => '_encrypted_jwt_',
);

sub BUILD {

    my $self = shift;

    #
    # Preset all keys in the cache (for JSON data, also set the values)
    #
    my %cache;

    # store keys from CGI params
    my @keys = $self->cgi->param;
    $cache{$_} = undef for @keys;
    do { $self->logger->debug(sprintf('CGI param: %s=%s', $_, join(',', $self->cgi->multi_param($_)))) for $self->cgi->param } if $self->logger->is_debug;

    # store keys and values from JSON POST data
    if (($self->cgi->content_type // '') eq 'application/json') {
        $self->logger->debug('Incoming POST data in JSON format (application/json)');

        my $json = JSON->new->utf8;
        my $data = $json->decode( scalar $self->cgi->param('POSTDATA') );

        # Resolve stringified depth-one-hashes - turn parameters like
        #   key{one} = 34
        #   key{two} = 56
        # into a HashRef
        #   key => { one => 34, two => 56 }
        foreach my $combined_key (keys %$data) {
            if (my ($key, $subkey) = $combined_key =~ m{ \A (\w+)\{(\w+)\} \z }xs) {
                $data->{$key} //= {};
                $data->{$key}->{$subkey} = $data->{$combined_key};
            }
        }

        # wrap Scalars and HashRefs in an ArrayRef as param() expects it (but leave ArrayRefs as is)
        $cache{$_} = (ref $data->{$_} eq 'ARRAY' ? $data->{$_} : [ $data->{$_} ]) for keys %$data;

        $self->logger->debug('JSON param: ' . Dumper $data) if $self->logger->is_debug;
        $self->method('POST');
    }

    # special transformations: insert sanitized keys names in the cache so the
    # check in param() will succeed.
    foreach my $key (keys %cache) {
        # Base64 encoded binary data
        my $prefix_b64 = $self->_prefix_base64;
        if (my ($item) = $key =~ /^$prefix_b64(.*)/) { $cache{$item} = undef; next }

        # JWT encrypted data
        my $prefix_jwt = $self->_prefix_jwt;
        if (my ($item) = $key =~ /^$prefix_jwt(.*)/) { $cache{$item} = undef; next }
    }

    $self->cache( \%cache );

}

sub param {
{'fraction_non_alphanumeric': 0.1848357791754018, 'fraction_numerical': 0.008735150244584208, 'mean_word_length': 2.6280101394169835, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 132, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1998808', 'n_tokens_mistral': 2081, 'n_tokens_neox': 1978, 'n_words': 559}