]> Dogcows Code - chaz/p5-CGI-Ex/commitdiff
CGI::Ex 2.37 v2.37
authorPaul Seamons <perl@seamons.com>
Thu, 9 Feb 2012 00:00:00 +0000 (00:00 +0000)
committerCharles McGarvey <chazmcgarvey@brokenzipper.com>
Fri, 9 May 2014 23:46:43 +0000 (17:46 -0600)
21 files changed:
Changes
MANIFEST
META.yml
lib/CGI/Ex.pm
lib/CGI/Ex/App.pm
lib/CGI/Ex/App.pod
lib/CGI/Ex/App/Constants.pm [new file with mode: 0644]
lib/CGI/Ex/Auth.pm
lib/CGI/Ex/Conf.pm
lib/CGI/Ex/Die.pm
lib/CGI/Ex/Dump.pm
lib/CGI/Ex/Fill.pm
lib/CGI/Ex/JSONDump.pm
lib/CGI/Ex/Template.pm
lib/CGI/Ex/Validate.pm
lib/CGI/Ex/Validate.pod
lib/CGI/Ex/validate.js
samples/validate_js_0_tests.html
samples/validate_js_2_onchange.html
t/1_validate_05_types.t
t/4_app_00_base.t

diff --git a/Changes b/Changes
index 708f5ff1df48fa507b5691c7e5e852fecbf8c998..08cff5c380c9b949555f33b29e0e9c946b7d46e2 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,3 +1,33 @@
+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
index 430c6dfc41c12995089ec4f24a5754f3a1909c55..61e40eb3ea9b7dc669c03acaa079229df34fb85b 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -1,6 +1,7 @@
 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
index 7216ca2cc45c9a4df6f602daf670adbdbd79ebe6..a1f8bb5c14a659b37dbd0634241d3bd4d4702151 100644 (file)
--- a/META.yml
+++ b/META.yml
@@ -1,14 +1,22 @@
 --- #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
index fa8d1fe3a431eb84f069659c900f9e2fb499cb44..b18f5ee204ac76a90a441fddb6e0de043379363a 100644 (file)
@@ -7,7 +7,7 @@ CGI::Ex - CGI utility suite - makes powerful application writing fun and easy
 =cut
 
 ###----------------------------------------------------------------###
-#  Copyright 2007 - Paul Seamons                                     #
+#  Copyright 2003-2012 - Paul Seamons                                #
 #  Distributed under the Perl Artistic License without warranty      #
 ###----------------------------------------------------------------###
 
@@ -24,7 +24,7 @@ use vars qw($VERSION
 use base qw(Exporter);
 
 BEGIN {
-    $VERSION               = '2.32';
+    $VERSION               = '2.37';
     $PREFERRED_CGI_MODULE  ||= 'CGI';
     @EXPORT = ();
     @EXPORT_OK = qw(get_form
@@ -195,7 +195,7 @@ sub get_cookies {
     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;
 }
index 79720fc4f9c9eef62ce785909fb498ae06d00c83..78a9d47a3b3ce7a7d099d156e8edb8d034f2b359 100644 (file)
@@ -2,7 +2,7 @@ package CGI::Ex::App;
 
 ###---------------------###
 #  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;
@@ -11,7 +11,7 @@ BEGIN {
     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";
@@ -33,6 +33,15 @@ sub init_from_conf {
     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 {
@@ -926,15 +935,16 @@ sub js_run_step { # step that allows for printing javascript libraries that are
     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 }
index 536741bccad218d896733a2074ae9cf86f07ef6e..e9f0888543f816610cbe8cec5ace480c8db9d34c 100644 (file)
@@ -20,7 +20,7 @@ A basic example:
         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 --------
 
@@ -38,7 +38,7 @@ Well, you should put your content in an external file...
 
     Hello World!
 
-How about if we want to add substitutions...
+Adding substitutions...
 
     -------- File: /cgi-bin/my_cgi --------
 
@@ -65,7 +65,7 @@ How about if we want to add substitutions...
     [% greeting %] World! ([% date %])
 
 
-How about a form with validation (inluding javascript validation)...
+Add forms and  validation (inluding javascript validation)...
 
     -------- File: /cgi-bin/my_cgi --------
 
@@ -976,6 +976,24 @@ possible to move some of those methods into an external package.
 
 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
diff --git a/lib/CGI/Ex/App/Constants.pm b/lib/CGI/Ex/App/Constants.pm
new file mode 100644 (file)
index 0000000..a7b6f56
--- /dev/null
@@ -0,0 +1,175 @@
+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
index 149123e13b6ecc7227d65c1f7ab72aba1c1eff24..21b82d45fe51f1e9aac701a6b4f45d8195f787d3 100644 (file)
@@ -7,7 +7,7 @@ CGI::Ex::Auth - Handle logins nicely.
 =cut
 
 ###----------------------------------------------------------------###
-#  Copyright 2007 - Paul Seamons                                     #
+#  Copyright 2004-2012 - Paul Seamons                                #
 #  Distributed under the Perl Artistic License without warranty      #
 ###----------------------------------------------------------------###
 
@@ -19,7 +19,7 @@ use Digest::MD5 qw(md5_hex);
 use CGI::Ex;
 use Carp qw(croak);
 
-$VERSION = '2.32';
+$VERSION = '2.37';
 
 ###----------------------------------------------------------------###
 
@@ -98,7 +98,7 @@ sub get_valid_auth {
             $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'};
                 }
@@ -183,7 +183,7 @@ sub handle_failure {
 
     # 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}) {
@@ -257,7 +257,17 @@ sub delete_cookie {
     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'}};
 }
@@ -333,9 +343,9 @@ sub js_uri_path {
 
 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 {
@@ -510,7 +520,7 @@ sub parse_token {
     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) {
@@ -702,6 +712,12 @@ sub cleanup_user {
     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;
index 04ff2a33ce71b7b413dbfa71af12bdc33a1be5ed..0fdb2f165d9258411d6db0be416c23f556e4f440 100644 (file)
@@ -7,7 +7,7 @@ CGI::Ex::Conf - Conf Reader/Writer for many different data format types
 =cut
 
 ###----------------------------------------------------------------###
-#  Copyright 2007 - Paul Seamons                                     #
+#  Copyright 2003-2012 - Paul Seamons                                #
 #  Distributed under the Perl Artistic License without warranty      #
 ###----------------------------------------------------------------###
 
@@ -29,7 +29,7 @@ use vars qw($VERSION
             );
 @EXPORT_OK = qw(conf_read conf_write in_cache);
 
-$VERSION = '2.32';
+$VERSION = '2.37';
 
 $DEFAULT_EXT = 'conf';
 
index 50aa0fdb924997479a30c38e12084b8a7f0176a2..e77b699ad202de9ebcfdc5715f372b1093952b4d 100644 (file)
@@ -7,7 +7,7 @@ CGI::Ex::Die - A CGI::Carp::FatalsToBrowser type utility.
 =cut
 
 ###----------------------------------------------------------------###
-#  Copyright 2007 - Paul Seamons                                     #
+#  Copyright 2004-2012 - Paul Seamons                                #
 #  Distributed under the Perl Artistic License without warranty      #
 ###----------------------------------------------------------------###
 
@@ -23,7 +23,7 @@ use CGI::Ex;
 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;
index 924d493402f7dfc8ad8a988a044e56fe223e5d90..574eae57438b258e422f25d2a6f10dc22962256b 100644 (file)
@@ -7,7 +7,7 @@ CGI::Ex::Dump - A debug utility
 =cut
 
 ###----------------------------------------------------------------###
-#  Copyright 2007 - Paul Seamons                                     #
+#  Copyright 2004-2012 - Paul Seamons                                #
 #  Distributed under the Perl Artistic License without warranty      #
 ###----------------------------------------------------------------###
 
@@ -17,10 +17,10 @@ use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION
 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 };
@@ -102,7 +102,7 @@ sub _what_is_this {
   } 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[$_];
@@ -113,6 +113,7 @@ sub _what_is_this {
     CGI::Ex::print_content_type();
     print $html;
   }
+  return @_[0..$#_];
 }
 
 ### some aliases
@@ -160,6 +161,8 @@ sub ctrace {
   return \@i;
 }
 
+*caller_trace = \&ctrace;
+
 sub dex_trace {
   _what_is_this(ctrace(1));
 }
index 1183950aba3146143fd095a4a6e7bb700d484093..74bff19cf06c5c6c992b0b82947133721c678781 100644 (file)
@@ -7,7 +7,7 @@ CGI::Ex::Fill - Fast but compliant regex based form filler
 =cut
 
 ###----------------------------------------------------------------###
-#  Copyright 2007 - Paul Seamons                                     #
+#  Copyright 2003-2012 - Paul Seamons                                #
 #  Distributed under the Perl Artistic License without warranty      #
 ###----------------------------------------------------------------###
 
@@ -24,7 +24,7 @@ use vars qw($VERSION
 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);
 };
index b45b11bd67b1ae1ceee203ac4a381a06219ed770..6a5e825a00fcd617675b458a2215a55208189a29 100644 (file)
@@ -7,7 +7,7 @@ CGI::Ex::JSONDump - Comprehensive data to JSON dump.
 =cut
 
 ###----------------------------------------------------------------###
-#  Copyright 2007 - Paul Seamons                                     #
+#  Copyright 2006-2012 - Paul Seamons                                #
 #  Distributed under the Perl Artistic License without warranty      #
 ###----------------------------------------------------------------###
 
@@ -17,7 +17,7 @@ use strict;
 use base qw(Exporter);
 
 BEGIN {
-    $VERSION  = '2.32';
+    $VERSION  = '2.37';
 
     @EXPORT = qw(JSONDump);
     @EXPORT_OK = @EXPORT;
index 1cd650a931f89fbb73fba45cb8cf529f56b18fe6..457b43767e84aff21616c24192e63a0fe787e3ae 100644 (file)
@@ -25,7 +25,7 @@ use vars qw($VERSION
             $VOBJS
             );
 
-$VERSION = '2.32';
+$VERSION = '2.37';
 
 ### install true symbol table aliases that can be localized
 *QR_PRIVATE        = *Template::Alloy::QR_PRIVATE;
index d2717ccdc2fc281aa86b7d7c35c6fe9cb729fd6b..450fa2e55142a4bbca4fb99c9fb7597eaade3c89 100644 (file)
@@ -2,13 +2,13 @@ package CGI::Ex::Validate;
 
 ###---------------------###
 #  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;
@@ -372,7 +372,7 @@ sub validate_buddy {
     # 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'})];
@@ -383,6 +383,17 @@ sub validate_buddy {
             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;
         }
@@ -403,6 +414,7 @@ sub validate_buddy {
             if ($not ? $success : ! $success) {
                 return [] if $self->{'_check_conditional'};
                 push @errors, [$field, $type, $field_val, $ifs_match];
+                next OUTER;
             }
             $content_checked = 1;
         } }
@@ -513,20 +525,20 @@ sub validate_buddy {
         # 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"
@@ -552,7 +564,7 @@ sub validate_buddy {
 ### 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;
@@ -587,11 +599,20 @@ sub check_type {
         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;
@@ -899,7 +920,8 @@ sub get_error_text {
     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);
 
index 9311c05c41ceb24d4473e28d963316867588622b..53a24b62b14f5b55c011a2f41d24c82cbe4f3a5b 100644 (file)
@@ -335,8 +335,8 @@ This section lists the available validation types.  Multiple instances
 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
 
@@ -366,22 +366,41 @@ validation and an error is added.
     {
         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',
@@ -389,17 +408,31 @@ the samples/validate_js_0_tests.html page for a sample of usages.
         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
@@ -483,7 +516,11 @@ to allow more than one item by any given name).
 =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>
 
@@ -496,6 +533,9 @@ as saying:
 
     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.
 
@@ -524,15 +564,67 @@ $self->{dbh} is a coderef - they will be called and should return a dbh.
 =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
@@ -1079,19 +1171,36 @@ samples/validate_js_2_onchange.html to highlight the row and set an icon.
         = '<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.
@@ -1104,11 +1213,6 @@ These hooks can also be set as "group clear_hook" and "group set_hook"
     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.
index 7b469115ba07c99fc76b2498ccf5f64454201544..a3c0db05c4557f052f4c5a163444cd1f9a868466 100644 (file)
@@ -1,4 +1,4 @@
-// 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
 
@@ -25,7 +25,7 @@ function v_get_ordered_fields (val_hash) {
  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;
@@ -97,16 +97,16 @@ function v_clean_field_val (field_val, N_level) {
   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;
@@ -120,9 +120,9 @@ function v_clean_field_val (field_val, N_level) {
     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;
@@ -248,8 +248,8 @@ function v_filter_types (type, types) {
  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 = '';
@@ -260,7 +260,7 @@ function v_add_error (errors,field,type,field_val,ifs_match,form) {
 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();
 }
 
@@ -408,19 +408,29 @@ function v_validate_buddy (form, field, field_val, val_hash, ifs_match) {
   }
  }
 
- 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;
@@ -431,14 +441,16 @@ function v_validate_buddy (form, field, field_val, val_hash, ifs_match) {
      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;
@@ -447,7 +459,7 @@ function v_validate_buddy (form, field, field_val, val_hash, ifs_match) {
     }
    }
 
-   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;
@@ -478,19 +490,20 @@ function v_validate_buddy (form, field, field_val, val_hash, ifs_match) {
      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);
   }
  }
 
@@ -542,6 +555,15 @@ function v_check_type (value, type, field, form) {
   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;
@@ -644,10 +666,11 @@ function v_get_error_text (err, extra1, extra2) {
  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];
 
@@ -795,7 +818,7 @@ function eob_as_hash (extra) {
   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;
   }
  }
@@ -820,7 +843,7 @@ document.validate = function (form, val_hash) {
 
  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);
   }
  }
@@ -844,7 +867,7 @@ document.validate = function (form, val_hash) {
  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);
   }
  }
@@ -946,7 +969,7 @@ document.check_form = function (form, val_hash) {
    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;
@@ -997,7 +1020,7 @@ function v_el_attach (el, fvs, form, val_hash, _change, _blur) {
   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);
   }
  };
index 5471ec8edc6fefefab6857a9672dbed780374101..90f42288f71d09294037e274135e3932e7b6dfd2 100644 (file)
@@ -135,8 +135,15 @@ function run_tests () {
   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);
@@ -200,6 +207,13 @@ function run_tests () {
   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");
@@ -323,13 +337,26 @@ function run_tests () {
   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");
@@ -369,6 +396,18 @@ function run_tests () {
   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}};
index 95a405e7370c183290c6f7532156467e37164eb0..7d6e23a1bc6fb09b3b9af23fc5ffa3df6f586e02 100644 (file)
@@ -145,7 +145,23 @@ document.validation = {
     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',
@@ -195,7 +211,8 @@ document.validation = {
   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.'
@@ -213,25 +230,6 @@ document.validation = {
   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');
index 34568037191da7525ab9d12c961e6d1be1299900..f0a491438e1fb5c4523e0be9b90f2221ca1e2cad 100644 (file)
@@ -7,14 +7,14 @@
 =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}};
@@ -110,11 +110,14 @@ ok(! $e, 'enum');
 $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'}};
@@ -331,11 +334,19 @@ ok($e, 'custom');
 $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'}};
@@ -357,6 +368,13 @@ ok($e, '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);
index 5d86acaeaa245e2e14bb367572c93cc6e3e54095..ef636489f31cc7cced25451be44ecaa4ebd3ee10 100644 (file)
@@ -16,13 +16,31 @@ we do try to put it through most paces.
 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 = '' }
 
@@ -693,7 +711,7 @@ foreach my $type (qw(base
         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');
 
This page took 0.066822 seconds and 4 git commands to generate.