+2.37
+ 2012-02-09
+ * (Validate) Make match_2 work in the javascript layer (as shown in perldoc)
+ * (Validate) Add int, uint, and num types with range checking on int and uint
+ * (Validate) Allow for custom => sub { my ($k, $v) = @_; die "Always fail\n" } to pass the result of the failure via the value of die.
+ * (Validate) Allow for custom_js:function (args) { throw "Always fail ("+args.value+")" } which passes the error message of the test as the fail message
+ * (Validate) Allow for type, enum, and equals to short circuit (don't run match, compare, length, or custom checks if type fails)
+ * (App) Fix the test suite to not require installation of CGI
+ * (App) Add CGI::Ex::App::Constants module supporting constants as a source of documentation
+ * (App) Allow for access to constants via use CGI::Ex::App qw(:App); or use MyApp qw(:App);
+
+2.36
+ 2010-06-10
+ * (Auth) Make delete_cookie always delete, even for session Apps.
+
+2.35
+ 2010-05-24
+ * (Auth) More robust cookie reading and writing and deleting.
+ * Workaround CGI::Cookie->parse weird empty cookie implementation.
+ * (Auth) Allow cookie_domain to be modified more gracefully.
+ * (Auth) Consider case with multiple cookies for the same name.
+
+2.34
+ 2010-04-23
+ * (Auth) Don't attempted to decode base64 armor unless use_base64
+
+2.33
+ 2010-04-01
+ * (Auth) Allow for custom form user vs valid cookie check
+
2.32
2010-02-25
* (Validate) Allow for default to be an arrayref
Changes
lib/CGI/Ex.pm
lib/CGI/Ex/App.pm
+lib/CGI/Ex/App/Constants.pm
lib/CGI/Ex/App.pod
lib/CGI/Ex/Auth.pm
lib/CGI/Ex/Conf.pm
--- #YAML:1.0
-name: CGI-Ex
-version: 2.32
-abstract: CGI utility suite - makes powerful application writing fun and easy
-license: ~
-author:
+name: CGI-Ex
+version: 2.37
+abstract: CGI utility suite - makes powerful application writing fun and easy
+author:
- Paul Seamons
-generated_by: ExtUtils::MakeMaker version 6.42
-distribution_type: module
-requires:
- Template::Alloy: 1.004
+license: unknown
+distribution_type: module
+configure_requires:
+ ExtUtils::MakeMaker: 0
+build_requires:
+ ExtUtils::MakeMaker: 0
+requires:
+ Template::Alloy: 1.004
+no_index:
+ directory:
+ - t
+ - inc
+generated_by: ExtUtils::MakeMaker version 6.56
meta-spec:
- url: http://module-build.sourceforge.net/META-spec-v1.3.html
- version: 1.3
+ url: http://module-build.sourceforge.net/META-spec-v1.4.html
+ version: 1.4
=cut
###----------------------------------------------------------------###
-# Copyright 2007 - Paul Seamons #
+# Copyright 2003-2012 - Paul Seamons #
# Distributed under the Perl Artistic License without warranty #
###----------------------------------------------------------------###
use base qw(Exporter);
BEGIN {
- $VERSION = '2.32';
+ $VERSION = '2.37';
$PREFERRED_CGI_MODULE ||= 'CGI';
@EXPORT = ();
@EXPORT_OK = qw(get_form
my %hash = ();
foreach my $key ($obj->cookie) {
my @val = $obj->cookie($key);
- $hash{$key} = ($#val == -1) ? next : ($#val == 0) ? $val[0] : \@val;
+ $hash{$key} = ($#val == -1) ? "" : ($#val == 0) ? $val[0] : \@val;
}
return $self->{'cookies'} = \%hash;
}
###---------------------###
# See the perldoc in CGI/Ex/App.pod
-# Copyright 2008 - Paul Seamons
+# Copyright 2004-2012 - Paul Seamons
# Distributed under the Perl Artistic License without warranty
use strict;
eval { use Time::HiRes qw(time) };
eval { use Scalar::Util };
}
-our $VERSION = '2.32';
+our $VERSION = '2.37';
sub new {
my $class = shift || croak "Usage: ".__PACKAGE__."->new";
return;
}
+sub import { # only ever called with explicit use CGI::Ex::App qw() - not with use base
+ my $class = shift;
+ if (@_ = grep { /^:?App($|__)/ } @_) {
+ require CGI::Ex::App::Constants;
+ unshift @_, 'CGI::Ex::App::Constants';
+ goto &CGI::Ex::App::Constants::import;
+ }
+}
+
###---------------------###
sub navigate {
return 1;
}
+sub __forbidden_require_auth { 0 }
sub __forbidden_allow_morph { shift->allow_morph(@_) && 1 }
sub __forbidden_info_complete { 0 } # step that will be used the path method determines it is forbidden
sub __forbidden_hash_common { shift->stash }
-sub __forbidden_file_print { \ "<h1>Denied</h1>You do not have access to the step <b>\"[% forbidden_step %]\"</b>" }
+sub __forbidden_file_print { \ "<h1>Denied</h1>You do not have access to the step <b>\"[% forbidden_step.html %]\"</b>" }
sub __error_allow_morph { shift->allow_morph(@_) && 1 }
sub __error_info_complete { 0 } # step that is used by the default handle_error
sub __error_hash_common { shift->stash }
-sub __error_file_print { \ "<h1>A fatal error occurred</h1>Step: <b>\"[% error_step %]\"</b><br>[% TRY; CONFIG DUMP => {header => 0}; DUMP error; END %]" }
+sub __error_file_print { \ "<h1>A fatal error occurred</h1>Step: <b>\"[% error_step.html %]\"</b><br>[% TRY; CONFIG DUMP => {header => 0}; DUMP error; END %]" }
sub __login_require_auth { 0 }
sub __login_allow_morph { shift->allow_morph(@_) && 1 }
return \ "Hello World!";
}
-Well, you should put your content in an external file...
+Properly put content in an external file...
-------- File: /cgi-bin/my_cgi --------
Hello World!
-How about if we want to add substitutions...
+Adding substitutions...
-------- File: /cgi-bin/my_cgi --------
[% greeting %] World! ([% date %])
-How about a form with validation (inluding javascript validation)...
+Add forms and validation (inluding javascript validation)...
-------- File: /cgi-bin/my_cgi --------
See the discussions under the methods named "find_hook" and "run_hook" for more details.
+Some hooks expect "magic" values to be replaced. Often they are
+intuitive, but sometimes it is easy to forget. For example, the
+finalize hook should return true (default) to indicate the step is
+complete and false to indicate that it failed and the page should be
+redisplayed. You can import a set of constants that allows for human
+readible names.
+
+ use CGI::Ex::App qw(:App__finalize);
+ OR
+ use MyAppPkg qw(:App__finalize); # if it is a subclass of CGI::Ex::App
+
+This would import the following constants:
+App__finalize__failed_and_show_page (0),
+App__finalize__finished_and_move_to_next_step => (1 - default), and
+App__finalize__finished_but_show_page ("" - still false). These
+constants are provided by CGI::Ex::App::Constants which also contains
+more options for usage.
+
The following is the alphabetical list of methods and hooks.
=over 4
--- /dev/null
+package CGI::Ex::App::Constants;
+
+=head1 NAME
+
+CGI::Ex::App::Constants - Easier access to magic App values
+
+=cut
+
+use vars qw(%constants @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
+use strict;
+use warnings;
+use Exporter qw(import); # allow for goto from CGI::Ex::App
+use base qw(Exporter);
+
+$VERSION = '2.37';
+
+BEGIN {
+my $all = {
+ App__allow_morph__allow_reblessing => '1 - This will allow changing MyApp to MyApp::MyStep when on step my_step',
+ App__allow_morph__force_reblessing => '2 - This will force changing MyApp to MyApp::MyStep when on step my_step',
+ App__allow_morph__no_auto_reblessing => '0 - We will not look for another package to bless into',
+ App__auth_args => 'should return a hashref of args to pass to auth_obj',
+ App__auth_obj => 'should return a auth_obj - used when require_auth is true',
+ App__file_print => 'should file path, or be a scalar reference to the template to print',
+ App__file_val => 'should return a file path or a hash_validation hashref (default is {})',
+ App__fill_args => 'should return a hashref of args to pass to CGI::Ex::Fill::fill (default {})',
+ App__fill_template => 'void context - uses hashes to fill the template',
+ App__finalize__failed_and_show_page => '0 - additional processing failed so show the page',
+ App__finalize__finished_and_move_to_next_step => '1 - default',
+ App__finalize__finished_but_show_page => '',
+ App__form_name => 'must return a name',
+ App__generate_form => 'return coderef to form that can generate the form based on hash_validation',
+ App__get_valid_auth => 'needs to return a CGI::Ex::Auth::Data object (which can be either true or false)',
+ App__hash_base => 'should return a hashref',
+ App__hash_common => 'should return a hashref',
+ App__hash_errors => 'should return a hashref of errors that occurred on the page (normally populated via add_error)',
+ App__hash_fill => 'should return a hashref of things to get filled in forms in the template',
+ App__hash_form => 'should return a hashref - default is $self->form - normally not overridden',
+ App__hash_swap => 'should return a hashref of things to process in the template',
+ App__hash_validation => 'should return a CGI::Ex::Validate compatible hashref or {} (default empty hashref means all submitted information is always ok to finalize)',
+ App__info_complete__fail_and_show_page => '0 - we were not ready to finalize the data either because there was not any, or it failed validation',
+ App__info_complete__succeed_and_run_finalize => '1 - occurs because data is ready and is good or because there was no hash_validation to test against',
+ App__js_validation => 'return coderef to form that will generate javascript based validation based on hash_validation',
+ App__morph => 'void context - used to rebless into other package',
+ App__morph_package => 'return package name derivative for use when allow_morph is true (see perldoc on morph, morph_package, morph_base)',
+ App__name_module => 'return name of application - used when mapping to file_system (see file_print, file_val, conf_file)',
+ App__name_step => 'return step of current request (default is $current_step) - used for mapping to file_system',
+ App__path_info_map => 'return arrayref of matching arrayrefs - first one matching is used - others abort (only applies to current step)',
+ App__path_info_map_base => 'return arrayref of matching arrayrefs - first one matching is used - others abort (ran before nav_loop)',
+ App__post_loop__do_not_recurse => '1 - can be used to abort navigation if the loop is about to recurse to default step - no additional headers will be sent',
+ App__post_loop__recurse_loop => 0,
+ App__post_navigate => 'void context - called at the end of navigation unless $self->{_no_post_navigate} is true',
+ App__post_print => 'void context - run code after page is diplayed to user',
+ App__post_step__abort_navigation_flow => '1 - no additional headers will be sent',
+ App__post_step__move_to_next_step => 0,
+ App__pre_loop__begin_loop => 0,
+ App__pre_loop__do_not_loop => '1 - can be used to abort the nav_loop call early on - no additional headers will be sent',
+ App__pre_navigate__continue => '0 - go ahead and navigate the request',
+ App__pre_navigate__stop => '1 - can be used to abort the navigate call early on - no additional headers will be sent',
+ App__pre_step__abort_navigation_flow => '1 - no additional headers will be sent',
+ App__pre_step__continue_current_step => 0,
+ App__prepare__fail_and_show_page => 0,
+ App__prepare__ok_move_to_info_complete => '1 - default',
+ App__prepared_print => 'void context - gathers hashes - then calls print',
+ App__print => 'void context - uses hashes to swap and fill file_print, then calls print_out',
+ App__print_out => 'void context - prints headers and prepared content',
+ App__ready_validate__data_not_ready_show_page => '0 - either validate_when_data was alse and it was a GET',
+ App__ready_validate__ready_to_validate_data => '1 - either validate_when_data was true or we received a POST',
+ App__refine_path => 'void context - manipulates the path after a step. set_ready_validate(0) if next_step is true',
+ App__require_auth__needs_authentication => 1,
+ App__require_auth__no_authentication_needed => 0,
+ App__run_step__move_to_next_step => 0,
+ App__run_step__request_completed => '1 - no additional headers will be sent',
+ App__set_ready_validate => 'void context - sets ready_validate to true (fakes REQUEST_METHOD to POST OR GET)',
+ App__skip__continue_current_step => 0,
+ App__skip__move_to_next_step => '1 - make sure the path has a next step or it will default to main',
+ App__swap_template => 'should return swapped template (passed $step, $file, $swap)',
+ App__template_args => 'should return a hashref to pass to template_obj',
+ App__template_obj => 'should return a Template::Alloy type object for swapping the template (passed template_args)',
+ App__unmorph => 'void context - re-reblesses back to the original class before morph',
+ App__val_args => 'should return a hashref to pass to val_obj',
+ App__val_obj => 'should return a CGI::Ex::Validate type object for validating the data',
+ App__validate__data_was_ok => '1 - request data either passed hash_validation or hash_validation was empty - make info_complete succeed',
+ App__validate__failed_validation => 0,
+ App__validate_when_data__succeed_if_data => '1 - will be true if there is no hash_validation, or a key from hash_validation was in the form',
+ App__validate_when_data__use_ready_validate => 0,
+};
+
+no strict 'refs';
+while (my ($method, $val) = each %$all) {
+ my ($prefix, $tag, $name) = split /__/, $method;
+ if (! $name) {
+ $constants{$tag} = $val;
+ next;
+ }
+ $constants{$tag}->{$name} = $val;
+
+ my $tags = $EXPORT_TAGS{"App__${tag}"} ||= [];
+ push @{ $EXPORT_TAGS{"App"} }, $method;
+ push @$tags, $method;
+ push @EXPORT, $method;
+ push @EXPORT_OK, $method;
+
+ $val =~ s/\s+-.*//;
+ $val *= 1 if $val =~ /^\d+$/;
+ *{__PACKAGE__."::$method"} = sub () { $val };
+}
+
+}; # end of BEGIN
+
+sub constants {
+ print __PACKAGE__."\n---------------------\n";
+ no strict 'refs';
+ for (sort @EXPORT_OK) {
+ print "$_ (".$_->().")\n";
+ }
+}
+
+sub tags {
+ print __PACKAGE__." Tags\n---------------------\n";
+ print "$_\n" for sort keys %EXPORT_TAGS;
+}
+
+sub details {
+ require Data::Dumper;
+ local $Data::Dumper::SortKeys = 1;
+ print Data::Dumper::Dumper(\%constants);
+}
+
+1;
+
+__END__
+
+=head1 SYNOPSIS
+
+ use base qw(CGI::Ex::App);
+ use CGI::Ex::App::Constants; # load all
+ use CGI::Ex::App::Constants qw(:App); # also load all
+ use CGI::Ex::App qw(:App); # also load all
+
+ __PACKAGE__->navigate;
+
+ sub main_run_step {
+ my $self = shift;
+
+ $self->cgix->print_content_type;
+ print "Hello world\n";
+
+ return App__run_step__request_completed;
+ }
+
+
+ # you can request only certain tags
+ use CGI::Ex::App::Constants qw(:App__run_step);
+ use CGI::Ex::App qw(:App__run_step);
+
+ # you can request only certain constants
+ use CGI::Ex::App::Constants qw(App__run_step__request_completed);
+ use CGI::Ex::App qw(App__run_step__request_completed);
+
+=head1 CONSTANTS
+
+To see a list of the importable tags type:
+
+ perl -MCGI::Ex::App::Constants -e 'CGI::Ex::App::Constants::tags()'
+
+To see a list of the importable constants type:
+
+ perl -MCGI::Ex::App::Constants -e 'CGI::Ex::App::Constants::constants()'
+
+To see a little more discussion about the hooks and other CGI::Ex::App options type:
+
+ perl -MCGI::Ex::App::Constants -e 'CGI::Ex::App::Constants::details()'
+
+=cut
=cut
###----------------------------------------------------------------###
-# Copyright 2007 - Paul Seamons #
+# Copyright 2004-2012 - Paul Seamons #
# Distributed under the Perl Artistic License without warranty #
###----------------------------------------------------------------###
use CGI::Ex;
use Carp qw(croak);
-$VERSION = '2.32';
+$VERSION = '2.37';
###----------------------------------------------------------------###
$data = $self->verify_token({token => $cookie, from => 'cookie'});
if (defined $form_user) { # they had form data
my $user = $self->cleanup_user($form_user);
- if (! $data || $user ne $data->{'user'}) { # but the cookie didn't match
+ if (! $data || !$self->check_form_user_against_cookie($user, $data->{'user'}, $data)) { # but the cookie didn't match
$data = $self->{'_last_auth_data'} = $form_data; # restore old form data failure
$data->{'user'} = $user if ! defined $data->{'user'};
}
# make sure the cookie is gone
my $key_c = $self->key_cookie;
- $self->delete_cookie({name => $key_c}) if $self->cookies->{$key_c};
+ $self->delete_cookie({name => $key_c}) if exists $self->cookies->{$key_c};
# no valid login and we are checking for cookies - see if they have cookies
if (my $value = delete $form->{$self->key_verify}) {
my $args = shift;
return $self->{'delete_cookie'}->($self, $args) if $self->{'delete_cookie'};
local $args->{'value'} = '';
- local $args->{'expires'} = '-10y' if ! $self->use_session_cookie($args->{'name'}, '');
+ local $args->{'expires'} = '-10y';
+ if (my $dom = $ENV{HTTP_HOST}) {
+ $dom =~ s/:\d+$//;
+ do {
+ local $args->{'domain'} = $dom;
+ $self->set_cookie($args);
+ local $args->{'domain'} = ".$dom";
+ $self->set_cookie($args);
+ }
+ while ($dom =~ s/^[\w\-]*\.// and $dom =~ /\./);
+ }
$self->set_cookie($args);
delete $self->cookies->{$args->{'name'}};
}
sub no_cookies_print {
my $self = shift;
+ return $self->{'no_cookies_print'}->($self) if $self->{'no_cookies_print'};
$self->cgix->print_content_type;
print qq{<div style="border: 2px solid black;background:red;color:white">You do not appear to have cookies enabled.</div>};
- return 1;
}
sub login_print {
my $bkey;
for my $armor ('none', 'base64', 'blowfish') {
my $copy = ($armor eq 'none') ? $token
- : ($armor eq 'base64') ? eval { local $^W; decode_base64($token) }
+ : ($armor eq 'base64') ? $self->use_base64 ? eval { local $^W; decode_base64($token) } : next
: ($bkey = $self->use_blowfish) ? decrypt_blowfish($token, $bkey)
: next;
if ($self->complex_plaintext && $copy =~ m|^ ([^/]+) / (\d+) / (-?\d+) / ([^/]*) / (.*) $|x) {
return $user;
}
+sub check_form_user_against_cookie {
+ my ($self, $form_user, $cookie_user, $data) = @_;
+ return if ! defined($form_user) || ! defined($cookie_user);
+ return $form_user eq $cookie_user;
+}
+
sub get_pass_by_user {
my $self = shift;
my $user = shift;
=cut
###----------------------------------------------------------------###
-# Copyright 2007 - Paul Seamons #
+# Copyright 2003-2012 - Paul Seamons #
# Distributed under the Perl Artistic License without warranty #
###----------------------------------------------------------------###
);
@EXPORT_OK = qw(conf_read conf_write in_cache);
-$VERSION = '2.32';
+$VERSION = '2.37';
$DEFAULT_EXT = 'conf';
=cut
###----------------------------------------------------------------###
-# Copyright 2007 - Paul Seamons #
+# Copyright 2004-2012 - Paul Seamons #
# Distributed under the Perl Artistic License without warranty #
###----------------------------------------------------------------###
use CGI::Ex::Dump qw(debug ctrace dex_html);
BEGIN {
- $VERSION = '2.32';
+ $VERSION = '2.37';
$SHOW_TRACE = 0 if ! defined $SHOW_TRACE;
$IGNORE_EVAL = 0 if ! defined $IGNORE_EVAL;
$EXTENDED_ERRORS = 1 if ! defined $EXTENDED_ERRORS;
=cut
###----------------------------------------------------------------###
-# Copyright 2007 - Paul Seamons #
+# Copyright 2004-2012 - Paul Seamons #
# Distributed under the Perl Artistic License without warranty #
###----------------------------------------------------------------###
use strict;
use Exporter;
-$VERSION = '2.32';
+$VERSION = '2.37';
@ISA = qw(Exporter);
@EXPORT = qw(dex dex_warn dex_text dex_html ctrace dex_trace);
-@EXPORT_OK = qw(dex dex_warn dex_text dex_html ctrace dex_trace debug);
+@EXPORT_OK = qw(dex dex_warn dex_text dex_html ctrace dex_trace debug caller_trace);
### is on or off
sub on { $ON = 1 };
} else {
my $html = "<pre class=debug><span class=debughead><b>$called: $file line $line_n</b></span>\n";
for (0 .. $#dump) {
- $dump[$_] =~ s/\\n/\n/g;
+ $dump[$_] =~ s/(?<!\\)\\n/\n/g;
$dump[$_] = _html_quote($dump[$_]);
$dump[$_] =~ s|\$VAR1|<span class=debugvar><b>$var[$_]</b></span>|g;
$html .= $dump[$_];
CGI::Ex::print_content_type();
print $html;
}
+ return @_[0..$#_];
}
### some aliases
return \@i;
}
+*caller_trace = \&ctrace;
+
sub dex_trace {
_what_is_this(ctrace(1));
}
=cut
###----------------------------------------------------------------###
-# Copyright 2007 - Paul Seamons #
+# Copyright 2003-2012 - Paul Seamons #
# Distributed under the Perl Artistic License without warranty #
###----------------------------------------------------------------###
use base qw(Exporter);
BEGIN {
- $VERSION = '2.32';
+ $VERSION = '2.37';
@EXPORT = qw(form_fill);
@EXPORT_OK = qw(fill form_fill html_escape get_tagval_by_key swap_tagval_by_key);
};
=cut
###----------------------------------------------------------------###
-# Copyright 2007 - Paul Seamons #
+# Copyright 2006-2012 - Paul Seamons #
# Distributed under the Perl Artistic License without warranty #
###----------------------------------------------------------------###
use base qw(Exporter);
BEGIN {
- $VERSION = '2.32';
+ $VERSION = '2.37';
@EXPORT = qw(JSONDump);
@EXPORT_OK = @EXPORT;
$VOBJS
);
-$VERSION = '2.32';
+$VERSION = '2.37';
### install true symbol table aliases that can be localized
*QR_PRIVATE = *Template::Alloy::QR_PRIVATE;
###---------------------###
# See the perldoc in CGI/Ex/Validate.pod
-# Copyright 2008 - Paul Seamons
+# Copyright 2003-2012 - Paul Seamons
# Distributed under the Perl Artistic License without warranty
use strict;
use Carp qw(croak);
-our $VERSION = '2.32';
+our $VERSION = '2.37';
our $QR_EXTRA = qr/^(\w+_error|as_(array|string|hash)_\w+|no_\w+)/;
our @UNSUPPORTED_BROWSERS = (qr/MSIE\s+5.0\d/i);
our $JS_URI_PATH;
# at this point @errors should still be empty
my $content_checked; # allow later for possible untainting (only happens if content was checked)
- foreach my $value (@$values) {
+ OUTER: foreach my $value (@$values) {
if (exists $field_val->{'enum'}) {
my $ref = ref($field_val->{'enum'}) ? $field_val->{'enum'} : [split(/\s*\|\|\s*/,$field_val->{'enum'})];
if (! $found) {
return [] if $self->{'_check_conditional'};
push @errors, [$field, 'enum', $field_val, $ifs_match];
+ next OUTER;
+ }
+ $content_checked = 1;
+ }
+
+ # do specific type checks
+ if (exists $field_val->{'type'}) {
+ if (! $self->check_type($value, $field_val->{'type'}, $field, $form)){
+ return [] if $self->{'_check_conditional'};
+ push @errors, [$field, 'type', $field_val, $ifs_match];
+ next OUTER;
}
$content_checked = 1;
}
if ($not ? $success : ! $success) {
return [] if $self->{'_check_conditional'};
push @errors, [$field, $type, $field_val, $ifs_match];
+ next OUTER;
}
$content_checked = 1;
} }
# server side custom type
if ($types{'custom'}) { foreach my $type (@{ $types{'custom'} }) {
my $check = $field_val->{$type};
- next if UNIVERSAL::isa($check, 'CODE') ? &$check($field, $value, $field_val, $type) : $check;
+ my $err;
+ if (UNIVERSAL::isa($check, 'CODE')) {
+ my $ok;
+ $err = "$@" if ! eval { $ok = $check->($field, $value, $field_val, $type, $form); 1 };
+ next if $ok;
+ chomp($err) if !ref($@) && defined($err);
+ } else {
+ next if $check;
+ }
return [] if $self->{'_check_conditional'};
- push @errors, [$field, $type, $field_val, $ifs_match];
+ push @errors, [$field, $type, $field_val, $ifs_match, (defined($err) ? $err : ())];
$content_checked = 1;
} }
- # do specific type checks
- if ($types{'type'}) { foreach my $type (@{ $types{'type'} }) {
- if (! $self->check_type($value,$field_val->{'type'},$field,$form)){
- return [] if $self->{'_check_conditional'};
- push @errors, [$field, $type, $field_val, $ifs_match];
- }
- $content_checked = 1;
- } }
}
# allow for the data to be "untainted"
### used to validate specific types
sub check_type {
my ($self, $value, $type) = @_;
-
+ $type = lc $type;
if ($type eq 'email') {
return 0 if ! $value;
my ($local_p,$dom) = ($value =~ /^(.+)\@(.+?)$/) ? ($1,$2) : return 0;
return 0 if $value && ! $self->check_type($value,'uri');
# validate a uri - the path portion of a request
- } elsif ($type eq 'URI') {
+ } elsif ($type eq 'uri') {
return 0 if ! $value;
return 0 if $value =~ m/\s+/;
- } elsif ($type eq 'CC') {
+ } elsif ($type eq 'int') {
+ return 0 if $value !~ /^-? (?: 0 | [1-9]\d*) $/x;
+ return 0 if ($value < 0) ? $value < -2**31 : $value > 2**31-1;
+ } elsif ($type eq 'uint') {
+ return 0 if $value !~ /^ (?: 0 | [1-9]\d*) $/x;
+ return 0 if $value > 2**32-1;
+ } elsif ($type eq 'num') {
+ return 0 if $value !~ /^-? (?: 0 | [1-9]\d* (?:\.\d+)? | 0?\.\d+) $/x;
+
+ } elsif ($type eq 'cc') {
return 0 if ! $value;
return 0 if $value =~ /[^\d\-\ ]/;
$value =~ s/\D//g;
my $self = shift;
my $err = shift;
my $extra = $self->{extra} || {};
- my ($field, $type, $field_val, $ifs_match) = @$err;
+ my ($field, $type, $field_val, $ifs_match, $custom_err) = @$err;
+ return $custom_err if defined($custom_err) && length($custom_err);
my $dig = ($type =~ s/(_?\d+)$//) ? $1 : '';
my $type_lc = lc($type);
of the same type may be used for some validation types by adding a
number to the type (ie match, match2, match232). Multiple instances
are validated in sorted order. Types that allow multiple values are:
-compare, custom, custom_js, equals, enum, match, required_if, sql,
-type, validate_if, and replace (replace is a MODIFICATION TYPE).
+compare, custom, custom_js, equals, match, required_if, sql, validate_if,
+and replace (replace is a MODIFICATION TYPE).
=over 4
{
field => 'username',
custom => sub {
- my ($key, $val, $type, $field_val_hash) = @_;
+ my ($key, $val, $field_val_hash, $checktype, $form) = @_;
# do something here
return 0;
},
+ custom_error => '$name was not valid',
+ }
+
+Often it is desirable to specify a different message depending upon
+the code passed to custom. To use a custom error message simply die
+with the error message. Note that you will want to add a newline or
+else perl will add the line number and file for you -
+CGI::Ex::Validate will remove the trailing newline.
+
+ {
+ field => 'username',
+ custom => sub {
+ my ($key, $val) = @_;
+ die "Custom error message 1\n" if $val eq '1';
+ die "Custom error message 2\n" if $val eq '2';
+ return 0;
+ },
+ custom_error => '$name default custom error message',
}
=item C<custom_js>
-Custom value - only available in JS. Allows for extra programming types.
-May be a javascript function (if fully declared in javascript), a string containing
-a javascript function (that will be eval'ed into a real function),
-a boolean value pre-determined before calling validate, or may be
-section of javascript that will be eval'ed (the last value of
-the eval'ed javascript will determine if validation passed). A false response indicates
-the value did not pass validation. A true response indicates that it did. See
-the samples/validate_js_0_tests.html page for a sample of usages.
+Custom value - only available in JS. Allows for extra programming
+types. May be a javascript function (if fully declared in
+javascript), a string containing a javascript function (that will be
+eval'ed into a real function), a boolean value pre-determined before
+calling validate, or may be section of javascript that will be eval'ed
+(the last value of the eval'ed javascript will determine if validation
+passed). A false response indicates the value did not pass
+validation. A true response indicates that it did. See the
+samples/validate_js_0_tests.html page for a sample of usages.
{
field => 'date',
match => 'm|^\d\d\d\d/\d\d/\d\d$|',
match_error => 'Please enter date in YYYY/MM/DD format',
custom_js => "function (args) {
- var t=new Date();
- var y=t.getYear()+1900;
- var m=t.getMonth() + 1;
- var d=t.getDate();
- if (m<10) m = '0'+m;
- if (d<10) d = '0'+d;
+ var t = new Date();
+ var y = t.getYear()+1900;
+ var m = t.getMonth() + 1;
+ var d = t.getDate();
+ if (m < 10) m = '0'+m;
+ if (d < 10) d = '0'+d;
(args.value > ''+y+'/'+m+'/'+d) ? 1 : 0;
}",
custom_js_error => 'The date was not greater than today.',
}
+Often it is desirable to specify a different message depending upon
+the function passed to custom_js. To use a custom error message simply throw
+the error message.
+
+ {
+ field => 'username',
+ custom_js => 'function (args) {
+ if (args.value == 1) throw "Custom error message 1";
+ if (args.value == 2) throw "Custom error message 2";
+ return 0;
+ }',
+ custom_js_error => '$name default custom error message',
+ }
+
=item C<enum>
Allows for checking whether an item matches a set of options. In perl
=item C<required>
Requires the form field to have some value. If the field is not present,
-no other checks will be run.
+no other checks will be run and an error will be given.
+
+It has been common for code to try C<required => 0> which essentially has
+no effect - instead use C<validate_if => 'fieldname', required => 1>. This
+results in the fieldname only being required if the fieldname is present.
=item C<required_if>
required_if => 'some_condition',
+It is different in that other checks will run - whereas validate_if skips
+all validation if some condition is not met.
+
If a regex is used for the field name, the required_if
field will have any match patterns swapped in.
=item C<type>
Allows for more strict type checking. Currently supported types
-include CC (credit card), EMAIL, DOMAIN, IP, URL. Other types will be
-added upon request provided we can add a perl and a javascript
-version.
+include CC (credit card), EMAIL, DOMAIN, IP, URL, INT, UINT, and NUM.
+Other types will be added upon request provided we can add a perl and
+a javascript version (extra types often aren't necessary as the custom and
+custom_js options give arbitrary checking). If a type checks fails - other
+compare, custom, or length checks will not be ran.
{
field => 'credit_card',
type => 'CC',
}
+=over 4
+
+item C<CC>
+
+Simple Luhn-10 check. Note that spaces and dashes are left in place.
+
+=item C<EMAIL>
+
+Very basic check to see if the value looks like an address. The local part
+must only contain [\w.~!\#\$%\^&*\-=+?] and the domain must be a domain or ip.
+If you want full fledged RFC compliant checking consider something like:
+
+ {
+ field => 'email',
+ custom => sub {
+ my ($key, $val, $fv, $type, $form) = @_;
+ require Mail::Address;
+ my @a = Mail::Address->parse($val);
+ die "Invalid address\n" if @a != 1;
+ return $form->{$key} = $a[0]->address;
+ },
+ }
+
+=item C<DOMAIN>
+
+Checks for a valid domain name - does no lookup of the domain. For that use
+a custom sub.
+
+=item C<IP>
+
+Basic IPv4 check.
+
+=item C<URL>
+
+Basic check that string matches something resembling an http or https url.
+
+=item C<INT>
+
+Checks for an integer between -2147483648 and -2147483648
+
+=item C<UINT>
+
+Checks for an unsigned integer between 0 and 4294967295.
+
+=item C<NUM>
+
+Checks for something that looks like a number. Scientic notation is not allowed. No range enforced.
+
+=back
+
=item C<validate_if>
If validate_if is specified, the field will only be validated
= '<span style="font-weight:bold;color:red">!</span>';
document.getElementById(args.key+'_row').style.background
= '#ffdddd';
-};
+ };
-document.validate_clear_hook = function (args) {
- if (args.was_valid) {
+ document.validate_clear_hook = function (args) {
+ if (args.was_valid) {
document.getElementById(args.key+'_img').innerHTML
= '<span style="font-weight:bold;color:green">+</span>';
document.getElementById(args.key+'_row').style.background
= '#ddffdd';
- } else {
+ } else {
document.getElementById(args.key+'_img').innerHTML = '';
document.getElementById(args.key+'_row').style.background = '#fff';
- }
-};
+ }
+ };
+
+If you have jquery that looks like:
+
+ document.validate_set_hook = function (args) {
+ $('#'+args.key+'_img').html('<span style="font-weight:bold;color:red">!</span>');
+ $('#'+args.key+'_row').css('backgroundColor', '#ffdddd');
+ };
+
+ document.validate_clear_hook = function (args) {
+ if (args.was_valid) {
+ $('#'+args.key+'_img').html('<span style="font-weight:bold;color:green">+</span>');
+ $('#'+args.key+'_row').css('backgroundColor', '#ddffdd');
+ } else {
+ $('#'+args.key+'_img').html('');
+ $('#'+args.key+'_row').css('backgroundColor', '#fff');
+ }
+ };
These hooks can also be set as "group clear_hook" and "group set_hook"
which are defined further above.
that if the javascript didn't validate correctly, the user can still
submit the data.
-=head1 THANKS
-
-Thanks to Eamon Daly for providing bug fixes for bugs in validate.js
-caused by HTML::Prototype.
-
=head1 LICENSE
This module may be distributed under the same terms as Perl itself.
-// Copyright 2008 - Paul Seamons - $Revision: 1.18 $
+// Copyright 2003-2012 - Paul Seamons - ver 2.37
// Distributed under the Perl Artistic License without warranty
// See perldoc CGI::Ex::Validate for usage
var field_keys = [];
var m;
for (var key in val_hash) {
- if (key == 'extend') continue; // Protoype Array()
+ if (!val_hash.hasOwnProperty(key)) continue;
if (m = key.match(/^(general|group)\s+(\w+)/)) {
ARGS[m[2]] = val_hash[key];
continue;
var k = field_val.order[i];
var v = field_val[k];
if (typeof(v) == 'undefined') return {error:v_error('No matching validation found on field '+field+' for type '+k)};
- if (k.match(/^(min|max)_in_set(\d*)$/)) {
+ if (k.match(/^(min|max)_in_set_?(\d*)$/)) {
if (typeof(v) == 'string') {
if (! (m = v.match(/^\s*(\d+)(?:\s*[oO][fF])?\s+(.+)\s*$/))) return {error:v_error("Invalid "+k+" check "+v)};
field_val[k] = m[2].split(/[\s,]+/);
field_val[k].unshift(m[1]);
}
for (var j = 1; j < field_val[k].length; j++) if (field_val[k][j] != field_val.field) field_val.deps[field_val[k][j]] = 1;
- } else if (k.match(/^(enum|compare)\d*$/)) {
+ } else if (k.match(/^(enum|compare)_?\d*$/)) {
if (typeof(v) == 'string') field_val[k] = v.split(/\s*\|\|\s*/);
- } else if (k.match(/^match\d*$/)) {
+ } else if (k.match(/^match_?\d*$/)) {
if (typeof(v) == 'string') v = field_val[k] = v.split(/\s*\|\|\s*/);
for (var j = 0; j < v.length; j++) {
if (typeof(v[j]) != 'string' || v[j] == '!') continue;
v[j] = new RegExp(pat, opt);
if (not) v.splice(j, 0, '!');
}
- } else if (k.match(/^custom_js\d*$/)) {
+ } else if (k.match(/^custom_js_?\d*$/)) {
if (typeof(v) == 'string' && v.match(/^\s*function\s*\(/)) eval("field_val[k] = "+v);
- } else if (k.match(/^(validate|required)_if\d*$/)) {
+ } else if (k.match(/^(validate|required)_if_?\d*$/)) {
if (typeof(v) == 'string' || ! v.length) v = field_val[k] = [v];
var deps = v_clean_cond(v, N_level);
for (var k in deps) field_val.deps[k] = 2;
return values;
}
-function v_add_error (errors,field,type,field_val,ifs_match,form) {
- errors.push([field, type, field_val, ifs_match]);
+function v_add_error (errors,field,type,field_val,ifs_match,form,custom_err) {
+ errors.push([field, type, field_val, ifs_match, custom_err]);
if (field_val.clear_on_error) {
var el = form[field];
if (el && el.type && el.type.match(/(hidden|password|text|textarea|submit)/)) el.value = '';
function v_field_order (field_val) {
var o = [];
for (var k in field_val)
- if (! k.match(/^(extend|field|name|required|was_valid|was_checked|had_error)$/) && ! k.match(/_error$/)) o.push(k);
+ if (field_val.hasOwnProperty(k) && ! k.match(/^(field|name|required|was_valid|was_checked|had_error)$/) && ! k.match(/_error$/)) o.push(k);
return o.sort();
}
}
}
- for (var i = 0; i < types.length; i++) {
- var type = types[i];
- var _fv = field_val[type];
- for (var n = 0; n < values.length; n++) {
- var value = values[n];
+ for (var n = 0; n < values.length; n++) {
+ var value = values[n];
+
+ if (typeof field_val['enum'] != 'undefined') {
+ var is_found = 0;
+ for (var j = 0; j < field_val['enum'].length; j++) if (value == field_val['enum'][j]) { is_found = 1; break }
+ if (! is_found) {
+ v_add_error(errors, field, 'enum', field_val, ifs_match, form);
+ continue;
+ }
+ }
- if (type.match(/^enum\d*$/)) {
- var is_found = 0;
- for (var j = 0; j < _fv.length; j++) if (value == _fv[j]) { is_found = 1; break }
- if (! is_found) v_add_error(errors, field, type, field_val, ifs_match, form);
+ if (typeof field_val['type'] != 'undefined')
+ if (! v_check_type(value, field_val['type'], field, form)) {
+ v_add_error(errors, field, 'type', field_val, ifs_match, form);
+ continue;
}
- if (type.match(/^equals\d*$/)) {
+ for (var i = 0; i < types.length; i++) {
+ var type = types[i];
+ var _fv = field_val[type];
+
+ if (type.match(/^equals_?\d*$/)) {
var not = _fv.match(/^!\s*/);
if (not) _fv = _fv.substring(not[0].length);
var success = 0;
if (typeof(value2) == 'undefined') value2 = '';
if (value == value2) success = 1;
}
- if (not && success || ! not && ! success)
+ if (not && success || ! not && ! success) {
v_add_error(errors, field, type, field_val, ifs_match, form);
+ break;
+ }
}
if (type == 'min_len' && value.length < _fv) v_add_error(errors, field, 'min_len', field_val, ifs_match, form);
if (type == 'max_len' && value.length > _fv) v_add_error(errors, field, 'max_len', field_val, ifs_match, form);
- if (type.match(/^match\d*$/)) {
+ if (type.match(/^match_?\d*$/)) {
for (var j = 0; j < _fv.length; j++) {
if (typeof(_fv[j]) == 'string') continue;
var not = (j > 0 && typeof(_fv[j-1]) == 'string' && _fv[j-1] == '!') ? 1 : 0;
}
}
- if (type.match(/^compare\d*$/)) {
+ if (type.match(/^compare_?\d*$/)) {
for (var j = 0; j < _fv.length; j++) {
var comp = _fv[j];
if (! comp) continue;
if (! hold) v_add_error(errors, field, type, field_val, ifs_match, form);
}
}
-
- if (type.match(/^type\d*$/))
- if (! v_check_type(value, _fv, field, form))
- v_add_error(errors, field, type, field_val, ifs_match, form);
}
+ }
+ for (var i = 0; i < types.length; i++) {
+ var type = types[i];
+ var _fv = field_val[type];
// the js is evaluated and should return 1 for success
// or 0 for failure - the variables field, value, and field_val (the hash) are available
- if (type.match(/^custom_js\d*$/)) {
+ if (type.match(/^custom_js_?\d*$/)) {
var value = values.length == 1 ? values[0] : values;
- if (typeof(_fv) == 'function'
- ? ! _fv({'value':value, 'field_val':field_val, 'form':form, 'key':field_val.field, 'errors':errors, 'event':v_event})
- : ! eval(_fv)) v_add_error(errors, field, type, field_val, ifs_match, form);
+ var err;
+ var ok;
+ try { ok = (typeof _fv == 'function') ? _fv({'value':value, 'field_val':field_val, 'form':form, 'key':field_val.field, 'errors':errors, 'event':v_event}) : eval(_fv) } catch (e) { err = e }
+ if (!ok) v_add_error(errors, field, type, field_val, ifs_match, form, err);
}
}
if (! value) return 0;
if (value.match(/\s/)) return 0;
+ } else if (type == 'INT') {
+ if (!value.match(/^-?(?:0|[1-9]\d*)$/)) return 0;
+ if ((value < 0) ? value < -Math.pow(2,31) : value > Math.pow(2,31)-1) return 0;
+ } else if (type == 'UINT') {
+ if (!value.match(/^(?:0|[1-9]\d*)$/)) return 0;
+ if (value > Math.pow(2,32)-1) return 0;
+ } else if (type == 'NUM') {
+ if (!value.match(/^-?(?:0|[1-9]\d*(?:\.\d+)?|0?\.\d+)$/)) return 0;
+
} else if (type == 'CC') {
if (! value) return 0;
if (value.match(/[^\d\- ]/)) return 0;
var type = err[1];
var field_val = err[2];
var ifs_match = err[3];
- var m;
+ if (err.length == 5 && typeof err[4] != 'undefined' && err[4].length) return err[4]; // custom error from throw in custom_js
+ var m;
var dig = '';
- if (m = type.match(/^(.+?)(\d+)$/)) { type = m[1] ; dig = m[2] }
+ if (m = type.match(/^(.+?)(_?\d+)$/)) { type = m[1] ; dig = m[2] }
var type_lc = type.toLowerCase();
var v = field_val[type + dig];
var header = v_find_val('as_hash_header', extra, this.extra, '');
var footer = v_find_val('as_hash_footer', extra, this.extra, '');
for (var key in ret) {
- if (key == 'extend') continue; // Protoype Array()
+ if (!ret.hasOwnProperty(key)) continue;
ret[key] = header + ret[key].join(joiner) + footer;
}
}
if (v_event != 'load') {
for (var key in v_did_inline) {
- if (key == 'extend') continue; // Protoype Array()
+ if (!v_did_inline.hasOwnProperty(key)) continue;
v_inline_error_clear(key, val_hash, form);
}
}
if (! val_hash['group no_inline']) {
var hash = err_obj.as_hash({as_hash_suffix:""});
for (var key in hash) {
- if (key == 'extend') continue; // Protoype Array()
+ if (!hash.hasOwnProperty(key)) continue;
v_inline_error_set(key, hash[key], val_hash, form);
}
}
for (var j in clean.fields[i].deps) if (j != clean.fields[i].field) _add(j, clean.fields[i]);
}
for (var k in h) {
- if (k == 'extend') continue; // Protoype Array()
+ if (!h.hasOwnProperty(k)) continue;
var el = form[k];
if (! el) return v_error("No form element by the name "+k);
var _change = !types.change ? 0 : typeof(types.change) == 'object' ? types.change[k] : 1;
e = new ValidateError(e, {});
e = e.as_hash({as_hash_suffix:"", first_only:(val_hash['group first_only']?1:0)});
for (var k in e) {
- if (k == 'extend') continue; // Protoype Array()
+ if (!e.hasOwnProperty(k)) continue;
v_inline_error_set(k, e[k], val_hash, form);
}
};
ok(e.text1_error == "The field text1 is not in the given list.", "Got the right enum error");
e = validate({text1:1}, v);
ok(! e, "No enum error");
+
+ v = {text1:{'enum':[1, 2, 3],match:'m/3/'}};
+ e = validate({text1:1}, v);
+ ok(e, 'enum');
+ ok(e.text1_error == "The field text1 contains invalid characters.", 'enum shortcircuit');
+
e = validate({text1:4}, v);
- ok(e, "Got enum error");
+ ok(e, 'enum');
+ ok(e.text1_error == "The field text1 is not in the given list.", 'enum shortcircuit2');
v = {text2:{'enum':"1 || 2||3"}};
e = validate({text2:1}, v);
ok(e, "Got a match error");
ok(e.text1_error == "The field text1 contains invalid characters.", "Got the right match error");
+ v = {text1:{match_2:'m/^\\w+$/',match_2_error:'Bad'}};
+ e = validate({text1:"abc"}, v);
+ ok(! e, "No match error");
+ e = validate({text1:"abc."}, v);
+ ok(e, "Got a match error");
+ ok(e.text1_error == "Bad", "Got the right match error");
+
v = {text1:{match:['m/^\\w+$/', 'm/^[a-z]+$/']}};
e = validate({text1:"abc"}, v);
ok(! e, "No match error with multiple");
e = validate({text1:"str"}, v);
ok(! e, "No custom_js error for function type");
+ e = validate({text1: "str"}, {text1: {custom_js:"throw 'Always fail ('+value+')'"}});
+ ok(e, 'Got an error');
+ ok(e.text1_error == "Always fail (str)", "Passed along the message from throw");
+ e = validate({text1: "str2"}, {text1: {custom_js:function (args) { throw "Always fail2 ("+args.value+")" }}});
+ ok(e, 'Got an error');
+ ok(e.text1_error == "Always fail2 (str2)", "Passed along the message from throw");
+
+
+
// type checks
- v = {text1:{type:'ip'}};
- e = validate({text1:'209.108.25'}, v);
- ok(e, "Got type ip error");
- ok(e.text1_error == "The field text1 did not match type ip.", "Got the right type ip error");
- e = validate({text1:'209.108.25.111'}, v);
- ok(! e, "No type ip error");
+ v = {text1: {type: 'ip', match: 'm/^203\./'}};
+ e = validate({text1: '209.108.25'}, v);
+ ok(e, 'type ip - with short circuit');
+ ok(e.text1_error == 'The field text1 did not match type ip.', 'type ip - was: '+e.text1_error); // make sure they short circuit
+ e = validate({text1: '209.108.25.111'}, v);
+ ok(e, 'type ip - but had match error');
+ ok(e.text1_error == 'The field text1 contains invalid characters.', 'type ip');
+ e = validate({text1: '203.108.25.111'}, v);
+ ok(! e, 'type ip');
+
v = {text1:{type:'email'}};
e = validate({text1:'foo.bar.com'}, v)
ok(e, "Got an email error");
ok(e, "Got cc error");
e = validate({text1:'4241-4242-4242'}, {text1:{type:'cc'}});
ok(e, "Got cc error");
+ for (var $_ in {"0":1, "2":1, "23":1, "-0":1, "-2":1, "-23":1, "0.0":1, ".1":1, "0.1":1, "0.10":1, "1.0":1, "1.01":1})
+ ok(!validate({text1: $_}, {text1: {type: 'num'}}), "Type num "+$_)
+ for (var $_ in {"0":1, "2":1, "23":1, "-0":1, "-2":1, "-23":1, "2147483647":1, "-2147483648":1})
+ ok(!validate({text1: $_}, {text1: {type: 'int'}}), "Type int "+$_);
+ for (var $_ in {"0":1, "2":1, "23":1, "4294967295":1})
+ ok(!validate({text1: $_}, {text1: {type: 'uint'}}), "Type uint "+$_);
+ for (var $_ in {"0a":1, "a2":1, "-0a":1, "0..0":1, "00":1, "001":1, "1.":1})
+ ok(validate({text1: $_}, {text1: {type: 'num'}}), "Type num invalid "+$_);
+ for (var $_ in {"1.1":1, "0.1":1, "0.0":1, "-1.1":1, "0a":1, "a2":1, "a":1, "00":1, "001":1, "2147483648":1, "-2147483649":1})
+ ok(validate({text1: $_}, {text1: {type: 'int'}}), "Type int invalid "+$_);
+ for (var $_ in {"-1":1, "-0":1, "1.1":1, "0.1":1, "0.0":1, "-1.1":1, "0a":1, "a2":1, "a":1, "00":1, "001":1, "4294967296":1})
+ ok(validate({text1: $_}, {text1: {type: 'uint'}}), "Type uint invalid "+$_);
// min_in_set checks
v = {text1:{min_in_set:'2 of text1 text2 password1', max_values:5}};
min_len: 6,
max_len: 30,
match: ["m/\\d/", "m/[a-z]/"],
- match_error: "$name must contain both a letter and a number."
+ match_error: "$name must contain both a letter and a number.",
+ custom_js: function (args) {
+ var v = args.value;
+ var n = 0;
+ if (v.match(/[a-z]/)) n++;
+ if (v.match(/[A-Z]/)) n++;
+ if (v.match(/[0-9]/)) n++;
+ var sym = v.match(/[ ~!@#$%^&*()_,.?{}\[\]]/) ? 1 : 0;
+ var s = (! v.length) ? ''
+ : (v.length < 6) ? 'weak'
+ : (v.length < 7) ? (sym || n == 3) ? 'ok' : 'weak'
+ : (v.length < 10) ? (n < 3 && ! sym) ? 'ok' : 'good'
+ : sym ? 'excellent' : 'good';
+ document.getElementById('password_strength').innerHTML = s;
+ if (s === 'weak') throw "Cannot use a weak password. Try increasing the length or adding variation.";
+ return 1;
+ }
},
password2: {
validate_if: 'password was_valid',
compare: {
required: 1,
required_error: "Please type a number",
- replace: "s/\\D//g",
+ type: 'num',
+ type_error: 'Please type a valid number',
name: "Compare check",
compare: ['> 99', '< 1000'],
compare_error: '$name must be greater than 99 and less than 1000.'
foo: {
min_in_set: "2 of foo bar baz",
max_in_set: "2 of foo bar baz"
- },
- pw_strength: { // separate from the pw validation
- field: "password",
- custom_js: function (args) {
- var v = args.value;
- var n = 0;
- if (v.match(/[a-z]/)) n++;
- if (v.match(/[A-Z]/)) n++;
- if (v.match(/[0-9]/)) n++;
- var sym = v.match(/[ ~!@#$%^&*()_,.?{}\[\]]/) ? 1 : 0;
- var s = (! v.length) ? ''
- : (v.length < 3) ? 'weak'
- : (v.length < 5) ? (sym || n == 3) ? 'ok' : 'weak'
- : (v.length < 9) ? (n < 3 && ! sym) ? 'ok' : 'good'
- : sym ? 'excellent' : 'good';
- document.getElementById('password_strength').innerHTML = s;
- // we could return false to indicate the strength wasn't good enough
- return 1;
- }
}
};
if (document.check_form) document.check_form('a');
=cut
use strict;
-use Test::More tests => 120;
+use Test::More tests => 181;
use_ok('CGI::Ex::Validate');
my $v;
my $e;
-sub validate { scalar CGI::Ex::Validate::validate(@_) }
+sub validate { scalar CGI::Ex::Validate->new({as_array_title=>'',as_string_join=>"\n"})->validate(@_) }
### required
$v = {foo => {required => 1}};
$e = validate({foo => 1, bar => 2}, $v);
ok(! $e, 'enum');
-$e = validate({foo => 1, bar => 3}, $v);
-ok(! $e, 'enum');
+$v->{'foo'}->{'match'} = 'm/3/';
+$e = validate({foo => 1, bar => 2}, $v);
+ok($e, 'enum');
+is($e, "Foo contains invalid characters.", 'enum shortcircuit');
-$e = validate({foo => 1, bar => 4}, $v);
+$e = validate({foo => 4, bar => 1}, $v);
ok($e, 'enum');
+is($e, "Foo is not in the given list.", 'enum shortcircuit');
# equals
$v = {foo => {equals => 'bar'}};
$e = validate({foo => "str"}, $v);
ok(! $e, 'custom');
+$e = validate({foo => "str"}, {foo => {custom => sub { my ($k, $v) = @_; die "Always fail ($v)\n" }}});
+ok($e, 'Got an error');
+is($e->as_hash->{'foo_error'}, "Always fail (str)", "Passed along the message from die");
+
### type checks
-$v = {foo => {type => 'ip'}};
+$v = {foo => {type => 'ip', match => 'm/^203\./'}};
$e = validate({foo => '209.108.25'}, $v);
ok($e, 'type ip');
+is($e, 'Foo did not match type ip.', 'type ip'); # make sure they short circuit
$e = validate({foo => '209.108.25.111'}, $v);
+ok($e, 'type ip - but had match error');
+is($e, 'Foo contains invalid characters.', 'type ip');
+$e = validate({foo => '203.108.25.111'}, $v);
ok(! $e, 'type ip');
$v = {foo => {type => 'domain'}};
$e = validate({foo => '1234567890123456789012345678901234567890123456789012345678901234.com'}, $v);
ok($e, 'type domain');
+ok(!validate({n => $_}, {n => {type => 'num'}}), "Type num $_") for qw(0 2 23 -0 -2 -23 0.0 .1 0.1 0.10 1.0 1.01);
+ok(!validate({n => $_}, {n => {type => 'int'}}), "Type int $_") for qw(0 2 23 -0 -2 -23 2147483647 -2147483648);
+ok(!validate({n => $_}, {n => {type => 'uint'}}), "Type uint $_") for qw(0 2 23 4294967295);
+ok(validate({n => $_}, {n => {type => 'num'}}), "Type num invalid $_") for qw(0a a2 -0a 0..0 00 001 1.);
+ok(validate({n => $_}, {n => {type => 'int'}}), "Type int invalid $_") for qw(1.1 0.1 0.0 -1.1 0a a2 a 00 001 2147483648 -2147483649);
+ok(validate({n => $_}, {n => {type => 'uint'}}), "Type uint invalid $_") for qw(-1 -0 1.1 0.1 0.0 -1.1 0a a2 a 00 001 4294967296);
+
### min_in_set checks
$v = {foo => {min_in_set => '2 of foo bar baz', max_values => 5}};
$e = validate({foo => 1}, $v);
use Test::More tests => 234;
use strict;
use warnings;
-use CGI::Ex::Dump qw(debug);
+use CGI::Ex::Dump qw(debug caller_trace);
+{
+ package CGIXFail;
+ use vars qw($AUTOLOAD);
+ sub new { bless {}, __PACKAGE__ }
+ sub DESTROY {}
+ sub AUTOLOAD {
+ my $self = shift;
+ my $meth = ($AUTOLOAD =~ /::(\w+$)/) ? $1 : die "Invalid method $AUTOLOAD";
+ die "Not calling CGI::Ex method $meth while testing App";
+ }
+}
{
package Foo;
use base qw(CGI::Ex::App);
use vars qw($test_stdout);
+ use CGI::Ex::Dump qw(debug caller_trace);
+
+ sub cgix { shift->{'cgix'} ||= CGIXFail->new } # for our tests try not to access external
+
+ sub form { shift->{'form'} ||= {} }
+
+ sub cookies { shift->{'cookies'} ||= {} }
sub init { $test_stdout = '' }
if $type ne 'form';
my $meth2 = "add_to_$type";
- my $c = CGI::Ex::App->new;
+ my $c = CGI::Ex::App->new({cgix => CGI::Ex->new({form=>{}})});
$c->$meth2({bing => 'bang'});
$c->$meth2(bong => 'beng');