package Mojo::WebSocketProxy::Parser;

use strict;
use warnings;

our $VERSION = '0.14';    ## VERSION

sub parse_req {
    my ($c, $req_storage) = @_;

    my $result;
    my $args = $req_storage->{args};
    if (ref $args ne 'HASH') {
        # for invalid call, eg: not json
        $req_storage->{args} = {};
        $result = $c->wsp_error('error', 'BadRequest', 'The application sent an invalid request.');
    }

    $result = _check_sanity($c, $req_storage) unless $result;

    return $result;
}

sub _check_sanity {
    my ($c, $req_storage) = @_;

    my @failed;
    my $args   = $req_storage->{args};
    my $config = $c->wsp_config->{config};

    OUTER:
    foreach my $k (keys %$args) {
        if (not ref $args->{$k}) {
            last OUTER if (@failed = _failed_key_value($k, $args->{$k}, $config->{skip_check_sanity}));
        } else {
            if (ref $args->{$k} eq 'HASH') {
                foreach my $l (keys %{$args->{$k}}) {
                    last OUTER
                        if (@failed = _failed_key_value($l, $args->{$k}->{$l}, $config->{skip_check_sanity}));
                }
            } elsif (ref $args->{$k} eq 'ARRAY') {
                foreach my $l (@{$args->{$k}}) {
                    last OUTER if (@failed = _failed_key_value($k, $l, $config->{skip_check_sanity}));
                }
            }
        }
    }

    if (@failed) {
        my $result = $c->wsp_error('error', 'SanityCheckFailed', 'Parameters sanity check failed.');
        # emit notification
        $c->tx->emit(sanity_failed => \@failed);
        return $result;
    }
    return;
}

sub _failed_key_value {
    my ($key, $value, $skip_check_sanity) = @_;

    my $key_regex = qr/^[A-Za-z0-9_-]{1,50}$/;
    if ($key !~ /$key_regex/) {
        return ($key, $value);
    }

    if ($skip_check_sanity && $key =~ /$skip_check_sanity/) {
        return;
    }

    if (
        $key !~ /$key_regex/
        # !-~ to allow a range of acceptable characters. To find what is the range, look at ascii table

        # \p{L} is to ensure we include other Unicode letters outside the ASCII range
        # \p{Script=Common} is to match double byte characters in Japanese keyboards, eg: '1−1−1'
        # refer: http://perldoc.perl.org/perlunicode.html and http://perldoc.perl.org/perluniprops.html
        # null-values are allowed
        or ($value and $value !~ /^[\p{Script=Common}\p{Letter}\s\w\@_:!-~]{0,300}$/))
    {
        return ($key, $value);
    }
    return;
}

1;

__END__

=head1 NAME

Mojo::WebSocketProxy::Parser

=head1 DESCRIPTION

This module using for parse JSON websocket messages.

=head1 METHODS

=head2 parse_req

=head1 SEE ALSO

L<Mojolicious::Plugin::WebSocketProxy>,
L<Mojo::WebSocketProxy>,
L<Mojo::WebSocketProxy::Backend>,
L<Mojo::WebSocketProxy::Dispatcher>,
L<Mojo::WebSocketProxy::Config>
L<Mojo::WebSocketProxy::Parser>

=cut