diff options
Diffstat (limited to 'lib/Params/Validate/XS.xs')
-rw-r--r-- | lib/Params/Validate/XS.xs | 1811 |
1 files changed, 1811 insertions, 0 deletions
diff --git a/lib/Params/Validate/XS.xs b/lib/Params/Validate/XS.xs new file mode 100644 index 0000000..109145a --- /dev/null +++ b/lib/Params/Validate/XS.xs @@ -0,0 +1,1811 @@ +/* Copyright (c) 2000-2012 Dave Rolsky and Ilya Martynov */ + +#include "EXTERN.h" +#include "perl.h" +#include "XSUB.h" +#include "ppport.h" + +#if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) +#define INLINE inline +#else +#define INLINE +#endif + +/* type constants */ +#define SCALAR 1 +#define ARRAYREF 2 +#define HASHREF 4 +#define CODEREF 8 +#define GLOB 16 +#define GLOBREF 32 +#define SCALARREF 64 +#define UNKNOWN 128 +#define UNDEF 256 +#define OBJECT 512 + +#define HANDLE (GLOB | GLOBREF) +#define BOOLEAN (SCALAR | UNDEF) + +/* return data macros */ +#define RETURN_ARRAY(ret) \ + STMT_START \ + { \ + I32 i; \ + switch(GIMME_V) \ + { \ + case G_VOID: \ + return; \ + case G_ARRAY: \ + EXTEND(SP, av_len(ret) + 1); \ + for(i = 0; i <= av_len(ret); i++) \ + { \ + PUSHs(*av_fetch(ret, i, 1)); \ + } \ + break; \ + case G_SCALAR: \ + XPUSHs(sv_2mortal(newRV_inc((SV*) ret))); \ + break; \ + } \ + } STMT_END \ + +#define RETURN_HASH(ret) \ + STMT_START \ + { \ + HE* he; \ + I32 keys; \ + switch(GIMME_V) \ + { \ + case G_VOID: \ + return; \ + case G_ARRAY: \ + keys = hv_iterinit(ret); \ + EXTEND(SP, keys * 2); \ + while ((he = hv_iternext(ret))) \ + { \ + PUSHs(HeSVKEY_force(he)); \ + PUSHs(HeVAL(he)); \ + } \ + break; \ + case G_SCALAR: \ + XPUSHs(sv_2mortal(newRV_inc((SV*) ret))); \ + break; \ + } \ + } STMT_END + + +static SV *module; +void peek(SV *thing) +{ + if (NULL == module) { + module = newSVpv("Devel::Peek", 0); + load_module(PERL_LOADMOD_NOIMPORT, module, NULL); + } + + { + dSP; + ENTER; + SAVETMPS; + + PUSHMARK(SP); + XPUSHs(thing); + PUTBACK; + + (void)call_pv("Devel::Peek::Dump", G_VOID); + + SPAGAIN; + + PUTBACK; + FREETMPS; + LEAVE; + } +} + +INLINE static bool +no_validation() { + SV* no_v; + + no_v = get_sv("Params::Validate::NO_VALIDATION", 0); + if (! no_v) + croak("Cannot retrieve $Params::Validate::NO_VALIDATION\n"); + + return SvTRUE(no_v); +} + +/* return type string that corresponds to typemask */ +INLINE static SV* +typemask_to_string(IV mask) { + SV* buffer; + IV empty = 1; + + buffer = sv_2mortal(newSVpv("", 0)); + + if (mask & SCALAR) { + sv_catpv(buffer, "scalar"); + empty = 0; + } + if (mask & ARRAYREF) { + sv_catpv(buffer, empty ? "arrayref" : " arrayref"); + empty = 0; + } + if (mask & HASHREF) { + sv_catpv(buffer, empty ? "hashref" : " hashref"); + empty = 0; + } + if (mask & CODEREF) { + sv_catpv(buffer, empty ? "coderef" : " coderef"); + empty = 0; + } + if (mask & GLOB) { + sv_catpv(buffer, empty ? "glob" : " glob"); + empty = 0; + } + if (mask & GLOBREF) { + sv_catpv(buffer, empty ? "globref" : " globref"); + empty = 0; + } + if (mask & SCALARREF) { + sv_catpv(buffer, empty ? "scalarref" : " scalarref"); + empty = 0; + } + if (mask & UNDEF) { + sv_catpv(buffer, empty ? "undef" : " undef"); + empty = 0; + } + if (mask & OBJECT) { + sv_catpv(buffer, empty ? "object" : " object"); + empty = 0; + } + if (mask & UNKNOWN) { + sv_catpv(buffer, empty ? "unknown" : " unknown"); + empty = 0; + } + + return buffer; +} + +/* compute numberic datatype for variable */ +INLINE static IV +get_type(SV* sv) { + IV type = 0; + + if (SvTYPE(sv) == SVt_PVGV) { + return GLOB; + } + if (!SvOK(sv)) { + return UNDEF; + } + if (!SvROK(sv)) { + return SCALAR; + } + + switch (SvTYPE(SvRV(sv))) { + case SVt_NULL: + case SVt_IV: + case SVt_NV: + case SVt_PV: + #if PERL_VERSION <= 10 + case SVt_RV: + #endif + case SVt_PVMG: + case SVt_PVIV: + case SVt_PVNV: + #if PERL_VERSION <= 8 + case SVt_PVBM: + #elif PERL_VERSION >= 11 + case SVt_REGEXP: + #endif + type = SCALARREF; + break; + case SVt_PVAV: + type = ARRAYREF; + break; + case SVt_PVHV: + type = HASHREF; + break; + case SVt_PVCV: + type = CODEREF; + break; + case SVt_PVGV: + type = GLOBREF; + break; + /* Perl 5.10 has a bunch of new types that I don't think will ever + actually show up here (I hope), but not handling them makes the + C compiler cranky. */ + default: + type = UNKNOWN; + break; + } + + if (type) { + if (sv_isobject(sv)) return type | OBJECT; + return type; + } + + /* Getting here should not be possible */ + return UNKNOWN; +} + +/* get an article for given string */ +INLINE static const char* +article(SV* string) { + STRLEN len; + char* rawstr; + + rawstr = SvPV(string, len); + if (len) { + switch(rawstr[0]) { + case 'a': + case 'e': + case 'i': + case 'o': + case 'u': + return "an"; + } + } + + return "a"; +} + +char * +string_representation(SV* value) { + if(SvOK(value)) { + return (void *)form("\"%s\"", SvPV_nolen(value)); + } + else { + return (void *)"undef"; + } +} + +/* raises exception either using user-defined callback or using + built-in method */ +static void +validation_failure(SV* message, HV* options) { + SV** temp; + SV* on_fail; + + if ((temp = hv_fetch(options, "on_fail", 7, 0))) { + SvGETMAGIC(*temp); + on_fail = *temp; + } + else { + on_fail = NULL; + } + + { + dSP; + ENTER; + SAVETMPS; + PUSHMARK(SP); + mXPUSHs(message); + PUTBACK; + + /* use user defined callback if available */ + if (on_fail) { + call_sv(on_fail, G_DISCARD); + } + else { + /* by default resort to Carp::confess for error reporting */ + call_pv("Carp::confess", G_DISCARD); + } + + /* We shouldn't get here if the thing we just called dies, but it + doesn't hurt to be careful. */ + SPAGAIN; + PUTBACK; + FREETMPS; + LEAVE; + } + + return; +} + +/* get called subroutine fully qualified name */ +static SV* +get_caller(HV* options) { + SV** temp; + + if ((temp = hv_fetch(options, "called", 6, 0))) { + SvGETMAGIC(*temp); + SvREFCNT_inc(*temp); + return *temp; + } + else { + IV frame; + SV *caller; +#if PERL_VERSION >= 14 + const PERL_CONTEXT *cx; + GV *cvgv; +# else + SV *buffer; +#endif + + if ((temp = hv_fetch(options, "stack_skip", 10, 0))) { + SvGETMAGIC(*temp); + frame = SvIV(*temp); + } + else { + frame = 1; + } + +#if PERL_VERSION >= 14 + if (frame > 0) { + frame--; + } + + cx = caller_cx(frame, NULL); + + if (cx) { + switch (CxTYPE(cx)) { + case CXt_EVAL: + caller = newSVpv("\"eval\"", 6); + break; + case CXt_SUB: + cvgv = CvGV(cx->blk_sub.cv); + caller = newSV(0); + if (cvgv && isGV(cvgv)) { + gv_efullname4(caller, cvgv, NULL, 1); + } + break; + default: + caller = newSVpv("(unknown)", 9); + break; + } + } + else { + caller = newSVpv("(unknown)", 9); + } +#else + buffer = sv_2mortal(newSVpvf("(caller(%d))[3]", (int) frame)); + + caller = eval_pv(SvPV_nolen(buffer), 1); + if (SvTYPE(caller) == SVt_NULL) { + sv_setpv(caller, "(unknown"); + } + + /* This will be decremented by the code that asked for this value, but + we need to do this here because the return value of caller() is + mortal and has a refcnt of 1. */ + SvREFCNT_inc(caller); +#endif + + return caller; + } +} + +/* $value->isa alike validation */ +static IV +validate_isa(SV* value, SV* package, char* id, HV* options) { + IV ok = 1; + + if (! value) { + return 0; + } + + SvGETMAGIC(value); + if (SvOK(value) && (sv_isobject(value) || (SvPOK(value) && ! looks_like_number(value)))) { + dSP; + + SV* ret; + IV count; + + ENTER; + SAVETMPS; + + PUSHMARK(SP); + EXTEND(SP, 2); + PUSHs(value); + PUSHs(package); + PUTBACK; + + count = call_method("isa", G_SCALAR); + + if (! count) + croak("Calling isa did not return a value"); + + SPAGAIN; + + ret = POPs; + SvGETMAGIC(ret); + + ok = SvTRUE(ret); + + PUTBACK; + FREETMPS; + LEAVE; + } + else { + ok = 0; + } + + if (! ok) { + SV *caller = get_caller(options); + SV* buffer = newSVpvf(id, string_representation(value)); + sv_catpv(buffer, " to "); + sv_catsv(buffer, caller); + SvREFCNT_dec(caller); + sv_catpv(buffer, " was not "); + sv_catpv(buffer, article(package)); + sv_catpv(buffer, " '"); + sv_catsv(buffer, package); + sv_catpv(buffer, "' (it is "); + if ( SvOK(value) ) { + sv_catpv(buffer, article(value)); + sv_catpv(buffer, " "); + sv_catsv(buffer, value); + } + else { + sv_catpv(buffer, "undef"); + } + sv_catpv(buffer, ")\n"); + validation_failure(buffer, options); + } + + return 1; +} + +static IV +validate_can(SV* value, SV* method, char* id, HV* options) { + IV ok = 1; + + if (! value) { + return 0; + } + + SvGETMAGIC(value); + if (SvOK(value) && (sv_isobject(value) || (SvPOK(value) && ! looks_like_number(value)))) { + dSP; + + SV* ret; + IV count; + + ENTER; + SAVETMPS; + + PUSHMARK(SP); + EXTEND(SP, 2); + PUSHs(value); + PUSHs(method); + PUTBACK; + + count = call_method("can", G_SCALAR); + + if (! count) + croak("Calling can did not return a value"); + + SPAGAIN; + + ret = POPs; + SvGETMAGIC(ret); + + ok = SvTRUE(ret); + + PUTBACK; + FREETMPS; + LEAVE; + } + else { + ok = 0; + } + + if (! ok) { + SV* buffer = newSVpvf(id, string_representation(value)); + SV *caller = get_caller(options); + sv_catpv(buffer, " to "); + sv_catsv(buffer, caller); + SvREFCNT_dec(caller); + sv_catpv(buffer, " does not have the method: '"); + sv_catsv(buffer, method); + sv_catpv(buffer, "'\n"); + validation_failure(buffer, options); + } + + return 1; +} + +/* validates specific parameter using supplied parameter specification */ +static IV +validate_one_param(SV* value, SV* params, HV* spec, char* id, HV* options, IV* untaint) { + SV** temp; + IV i; + + /* + HE* he; + hv_iterinit(spec); + + while (he = hv_iternext(spec)) { + STRLEN len; + char* key = HePV(he, len); + int ok = 0; + int j; + for ( j = 0; j < VALID_KEY_COUNT; j++ ) { + if ( strcmp( key, valid_keys[j] ) == 0) { + ok = 1; + break; + } + } + + if ( ! ok ) { + SV* buffer = sv_2mortal(newSVpv("\"",0)); + sv_catpv( buffer, key ); + sv_catpv( buffer, "\" is not an allowed validation spec key\n"); + validation_failure(buffer, options); + } + } + */ + + /* check type */ + if ((temp = hv_fetch(spec, "type", 4, 0))) { + IV type; + + if ( ! ( SvOK(*temp) + && looks_like_number(*temp) + && SvIV(*temp) > 0 ) ) { + + SV* buffer = newSVpvf(id, string_representation(value)); + sv_catpv( buffer, " has a type specification which is not a number. It is "); + if ( SvOK(*temp) ) { + sv_catpv( buffer, "a string - " ); + sv_catsv( buffer, *temp ); + } + else { + sv_catpv( buffer, "undef"); + } + sv_catpv( buffer, ".\n Use the constants exported by Params::Validate to declare types." ); + + validation_failure(buffer, options); + } + + SvGETMAGIC(*temp); + type = get_type(value); + if (! (type & SvIV(*temp))) { + SV* buffer = newSVpvf(id, string_representation(value)); + SV *caller = get_caller(options); + SV* is; + SV* allowed; + + sv_catpv(buffer, " to "); + sv_catsv(buffer, caller); + SvREFCNT_dec(caller); + sv_catpv(buffer, " was "); + is = typemask_to_string(type); + allowed = typemask_to_string(SvIV(*temp)); + sv_catpv(buffer, article(is)); + sv_catpv(buffer, " '"); + sv_catsv(buffer, is); + sv_catpv(buffer, "', which is not one of the allowed types: "); + sv_catsv(buffer, allowed); + sv_catpv(buffer, "\n"); + + validation_failure(buffer, options); + } + } + + /* check isa */ + if ((temp = hv_fetch(spec, "isa", 3, 0))) { + SvGETMAGIC(*temp); + + if (SvROK(*temp) && SvTYPE(SvRV(*temp)) == SVt_PVAV) { + AV* array = (AV*) SvRV(*temp); + + for(i = 0; i <= av_len(array); i++) { + SV* package; + + package = *av_fetch(array, i, 1); + if (! package) { + return 0; + } + + SvGETMAGIC(package); + if (! validate_isa(value, package, id, options)) { + return 0; + } + } + } + else { + if (! validate_isa(value, *temp, id, options)) { + return 0; + } + } + } + + /* check can */ + if ((temp = hv_fetch(spec, "can", 3, 0))) { + SvGETMAGIC(*temp); + if (SvROK(*temp) && SvTYPE(SvRV(*temp)) == SVt_PVAV) { + AV* array = (AV*) SvRV(*temp); + + for (i = 0; i <= av_len(array); i++) { + SV* method; + + method = *av_fetch(array, i, 1); + if (! method) { + return 0; + } + + SvGETMAGIC(method); + + if (! validate_can(value, method, id, options)) { + return 0; + } + } + } + else { + if (! validate_can(value, *temp, id, options)) { + return 0; + } + } + } + + /* let callbacks to do their tests */ + if ((temp = hv_fetch(spec, "callbacks", 9, 0))) { + HE* he; + + SvGETMAGIC(*temp); + if (!(SvROK(*temp) && SvTYPE(SvRV(*temp)) == SVt_PVHV)) { + SV* buffer = newSVpv("'callbacks' validation parameter for '", 0); + SV *caller = get_caller(options); + + sv_catsv(buffer, caller); + SvREFCNT_dec(caller); + sv_catpv(buffer, " must be a hash reference\n"); + validation_failure(buffer, options); + } + + hv_iterinit((HV*) SvRV(*temp)); + while ((he = hv_iternext((HV*) SvRV(*temp)))) { + SV* ret; + IV ok; + IV count; + SV *err; + + if (!(SvROK(HeVAL(he)) && SvTYPE(SvRV(HeVAL(he))) == SVt_PVCV)) { + SV* buffer = newSVpv("callback '", 0); + SV *caller = get_caller(options); + + sv_catsv(buffer, HeSVKEY_force(he)); + sv_catpv(buffer, "' for "); + sv_catsv(buffer, caller); + SvREFCNT_dec(caller); + sv_catpv(buffer, " is not a subroutine reference\n"); + validation_failure(buffer, options); + } + + { + dSP; + ENTER; + SAVETMPS; + + PUSHMARK(SP); + EXTEND(SP, 2); + PUSHs(value); + mPUSHs(newRV_inc(params)); + PUTBACK; + + /* local $@ = q{}; */ + save_scalar(PL_errgv); + sv_setpv(ERRSV, ""); + + count = call_sv(SvRV(HeVAL(he)), G_EVAL|G_SCALAR); + + SPAGAIN; + + if (!count) { + croak("Validation callback did not return anything"); + } + + ret = POPs; + SvGETMAGIC(ret); + ok = SvTRUE(ret); + + err = newSV(0); + SvSetSV_nosteal(err, ERRSV); + + PUTBACK; + FREETMPS; + LEAVE; + + if (! ok) { + if (SvROK(err)) { + validation_failure(err, options); + } + else { + SV* buffer = newSVpvf(id, string_representation(value)); + SV *caller = get_caller(options); + + sv_catpv(buffer, " to "); + sv_catsv(buffer, caller); + SvREFCNT_dec(caller); + sv_catpv(buffer, " did not pass the '"); + sv_catsv(buffer, HeSVKEY_force(he)); + sv_catpv(buffer, "' callback"); + if (SvLEN(err) > 0) { + sv_catpv(buffer, ": "); + sv_catsv(buffer, err); + } + sv_catpv(buffer, "\n"); + validation_failure(buffer, options); + } + } + else { + SvREFCNT_dec(err); + } + } + } + } + + if ((temp = hv_fetch(spec, "regex", 5, 0))) { + dSP; + + IV has_regex = 0; + IV ok; + + SvGETMAGIC(*temp); + if (SvPOK(*temp)) { + has_regex = 1; + } + else if (SvROK(*temp)) { + SV* svp; + + svp = (SV*)SvRV(*temp); + + #if PERL_VERSION <= 10 + if (SvMAGICAL(svp) && mg_find(svp, PERL_MAGIC_qr)) { + has_regex = 1; + } + #else + if (SvTYPE(svp) == SVt_REGEXP) { + has_regex = 1; + } + #endif + } + + if (!has_regex) { + SV* buffer = newSVpv("'regex' validation parameter for '", 0); + SV *caller = get_caller(options); + + sv_catsv(buffer, caller); + SvREFCNT_dec(caller); + sv_catpv(buffer, " must be a string or qr// regex\n"); + validation_failure(buffer, options); + } + + PUSHMARK(SP); + EXTEND(SP, 2); + PUSHs(value); + PUSHs(*temp); + PUTBACK; + call_pv("Params::Validate::XS::_check_regex_from_xs", G_SCALAR); + SPAGAIN; + ok = POPi; + PUTBACK; + + if (!ok) { + SV* buffer = newSVpvf(id, string_representation(value)); + SV *caller = get_caller(options); + + sv_catpv(buffer, " to "); + sv_catsv(buffer, caller); + SvREFCNT_dec(caller); + sv_catpv(buffer, " did not pass regex check\n"); + validation_failure(buffer, options); + } + } + + if ((temp = hv_fetch(spec, "untaint", 7, 0))) { + if (SvTRUE(*temp)) { + *untaint = 1; + } + } + + return 1; +} + +/* merges one hash into another (not deep copy) */ +static void +merge_hashes(HV* in, HV* out) { + HE* he; + + hv_iterinit(in); + while ((he = hv_iternext(in))) { + if (!hv_store_ent(out, HeSVKEY_force(he), + SvREFCNT_inc(HeVAL(he)), HeHASH(he))) { + SvREFCNT_dec(HeVAL(he)); + croak("Cannot add new key to hash"); + } + } +} + +/* convert array to hash */ +static IV +convert_array2hash(AV* in, HV* options, HV* out) { + IV i; + I32 len; + + len = av_len(in); + if (len > -1 && len % 2 != 1) { + SV* buffer = newSVpv("Odd number of parameters in call to ", 0); + SV *caller = get_caller(options); + + sv_catsv(buffer, caller); + SvREFCNT_dec(caller); + sv_catpv(buffer, " when named parameters were expected\n"); + + validation_failure(buffer, options); + } + + for (i = 0; i <= av_len(in); i += 2) { + SV* key; + SV* value; + + key = *av_fetch(in, i, 1); + if (! key) { + continue; + } + + SvGETMAGIC(key); + + /* We need to make a copy because if the array was @_, then the + values in the array are marked as readonly, which causes + problems when the hash being made gets returned to the + caller. */ + value = sv_2mortal( newSVsv( *av_fetch(in, i + 1, 1) ) ); + + if (value) { + SvGETMAGIC(value); + } + + if (! hv_store_ent(out, key, SvREFCNT_inc(value), 0)) { + SvREFCNT_dec(value); + croak("Cannot add new key to hash"); + } + } + + return 1; +} + +/* get current Params::Validate options */ +static HV* +get_options(HV* options) { + HV* OPTIONS; + HV* ret; + HE *he; + HV *stash; + SV* pkg; + SV *pkg_options; + + ret = (HV*) sv_2mortal((SV*) newHV()); + + /* get package specific options */ + stash = CopSTASH(PL_curcop); + pkg = sv_2mortal(newSVpv(HvNAME(stash), 0)); + + OPTIONS = get_hv("Params::Validate::OPTIONS", 1); + if ((he = hv_fetch_ent(OPTIONS, pkg, 0, 0))) { + pkg_options = HeVAL(he); + SvGETMAGIC(pkg_options); + if (SvROK(pkg_options) && SvTYPE(SvRV(pkg_options)) == SVt_PVHV) { + if (options) { + merge_hashes((HV*) SvRV(pkg_options), ret); + } + else { + return (HV*) SvRV(pkg_options); + } + } + } + if (options) { + merge_hashes(options, ret); + } + + return ret; +} + +static SV* +normalize_one_key(SV* key, SV* normalize_func, SV* strip_leading, IV ignore_case) { + SV* copy; + STRLEN len_sl; + STRLEN len; + char *rawstr_sl; + char *rawstr; + + copy = sv_2mortal(newSVsv(key)); + + /* if normalize_func is provided, ignore the other options */ + if (normalize_func) { + dSP; + + SV* normalized; + + PUSHMARK(SP); + XPUSHs(copy); + PUTBACK; + if (! call_sv(SvRV(normalize_func), G_SCALAR)) { + croak("The normalize_keys callback did not return anything"); + } + SPAGAIN; + normalized = POPs; + PUTBACK; + + if (! SvOK(normalized)) { + croak("The normalize_keys callback did not return a defined value when normalizing the key '%s'", SvPV_nolen(copy)); + } + + return normalized; + } + else if (ignore_case || strip_leading) { + if (ignore_case) { + STRLEN i; + + rawstr = SvPV(copy, len); + for (i = 0; i < len; i++) { + /* should this account for UTF8 strings? */ + *(rawstr + i) = toLOWER(*(rawstr + i)); + } + } + + if (strip_leading) { + rawstr_sl = SvPV(strip_leading, len_sl); + rawstr = SvPV(copy, len); + + if (len > len_sl && strnEQ(rawstr_sl, rawstr, len_sl)) { + copy = sv_2mortal(newSVpvn(rawstr + len_sl, len - len_sl)); + } + } + } + + return copy; +} + +static HV* +normalize_hash_keys(HV* p, SV* normalize_func, SV* strip_leading, IV ignore_case) { + SV* normalized; + HE* he; + HV* norm_p; + + if (!normalize_func && !ignore_case && !strip_leading) { + return p; + } + + norm_p = (HV*) sv_2mortal((SV*) newHV()); + hv_iterinit(p); + while ((he = hv_iternext(p))) { + normalized = + normalize_one_key(HeSVKEY_force(he), normalize_func, strip_leading, ignore_case); + + if (hv_fetch_ent(norm_p, normalized, 0, 0)) { + croak("The normalize_keys callback returned a key that already exists, '%s', when normalizing the key '%s'", + SvPV_nolen(normalized), SvPV_nolen(HeSVKEY_force(he))); + } + + if (! hv_store_ent(norm_p, normalized, SvREFCNT_inc(HeVAL(he)), 0)) { + SvREFCNT_dec(HeVAL(he)); + croak("Cannot add new key to hash"); + } + } + return norm_p; +} + +static IV +validate_pos_depends(AV* p, AV* specs, HV* options) { + IV p_idx; + SV** depends; + SV** p_spec; + + for (p_idx = 0; p_idx <= av_len(p); p_idx++) { + p_spec = av_fetch(specs, p_idx, 0); + + if (p_spec != NULL && SvROK(*p_spec) && + SvTYPE(SvRV(*p_spec)) == SVt_PVHV) { + + depends = hv_fetch((HV*) SvRV(*p_spec), "depends", 7, 0); + + if (! depends) { + return 1; + } + + if (SvROK(*depends)) { + croak("Arguments to 'depends' for validate_pos() must be a scalar"); + } + + if (av_len(p) < SvIV(*depends) -1) { + SV *buffer = + newSVpvf("Parameter #%d depends on parameter #%d, which was not given", + (int) p_idx + 1, + (int) SvIV(*depends)); + + validation_failure(buffer, options); + } + } + } + + return 1; +} + +static IV +validate_named_depends(HV* p, HV* specs, HV* options) { + HE* he; + HE* he1; + SV* buffer; + SV** depends_value; + AV* depends_list; + SV* depend_name; + SV* temp; + I32 d_idx; + + /* the basic idea here is to iterate through the parameters + * (which we assumed to have already gone through validation + * via validate_one_param()), and the check to see if that + * parameter contains a "depends" spec. If it does, we'll + * check if that parameter specified by depends exists in p + */ + hv_iterinit(p); + while ((he = hv_iternext(p))) { + he1 = hv_fetch_ent(specs, HeSVKEY_force(he), 0, HeHASH(he)); + + if (he1 && SvROK(HeVAL(he1)) && + SvTYPE(SvRV(HeVAL(he1))) == SVt_PVHV) { + + if (hv_exists((HV*) SvRV(HeVAL(he1)), "depends", 7)) { + + depends_value = hv_fetch((HV*) SvRV(HeVAL(he1)), "depends", 7, 0); + + if (! depends_value) { + return 1; + } + + if (! SvROK(*depends_value)) { + depends_list = (AV*) sv_2mortal((SV*) newAV()); + temp = sv_2mortal(newSVsv(*depends_value)); + av_push(depends_list,SvREFCNT_inc(temp)); + } + else if (SvTYPE(SvRV(*depends_value)) == SVt_PVAV) { + depends_list = (AV*) SvRV(*depends_value); + } + else { + croak("Arguments to 'depends' must be a scalar or arrayref"); + } + + for (d_idx =0; d_idx <= av_len(depends_list); d_idx++) { + + depend_name = *av_fetch(depends_list, d_idx, 0); + + /* first check if the parameter to which this + * depends on was given to us + */ + if (!hv_exists(p, SvPV_nolen(depend_name), + SvCUR(depend_name))) { + /* oh-oh, the parameter that this parameter + * depends on is not available. Let's first check + * if this is even valid in the spec (i.e., the + * spec actually contains a spec for such parameter) + */ + if (!hv_exists(specs, SvPV_nolen(depend_name), + SvCUR(depend_name))) { + + buffer = + sv_2mortal(newSVpv("Following parameter specified in depends for '", 0)); + + sv_catsv(buffer, HeSVKEY_force(he1)); + sv_catpv(buffer, "' does not exist in spec: "); + sv_catsv(buffer, depend_name); + + croak("%s", SvPV_nolen(buffer)); + } + /* if we got here, the spec was correct. we just + * need to issue a regular validation failure + */ + buffer = newSVpv( "Parameter '", 0); + sv_catsv(buffer, HeSVKEY_force(he1)); + sv_catpv(buffer, "' depends on parameter '"); + sv_catsv(buffer, depend_name); + sv_catpv(buffer, "', which was not given"); + validation_failure(buffer, options); + } + } + } + } + } + return 1; +} + +void +apply_defaults(HV *ret, HV *p, HV *specs, AV *missing) { + HE* he; + SV** temp; + + hv_iterinit(specs); + while ((he = hv_iternext(specs))) { + HV* spec; + SV* val; + + val = HeVAL(he); + + /* get extended param spec if available */ + if (val && SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVHV) { + spec = (HV*) SvRV(val); + } + else { + spec = NULL; + } + + /* test for parameter existence */ + if (hv_exists_ent(p, HeSVKEY_force(he), HeHASH(he))) { + continue; + } + + /* parameter may not be defined but we may have default */ + if (spec && (temp = hv_fetch(spec, "default", 7, 0))) { + SV* value; + + SvGETMAGIC(*temp); + value = sv_2mortal(newSVsv(*temp)); + + /* make sure that parameter is put into return hash */ + if (GIMME_V != G_VOID) { + if (!hv_store_ent(ret, HeSVKEY_force(he), + SvREFCNT_inc(value), HeHASH(he))) { + SvREFCNT_dec(value); + croak("Cannot add new key to hash"); + } + } + + continue; + } + + /* find if missing parameter is mandatory */ + if (! no_validation()) { + SV** temp; + + if (spec) { + if ((temp = hv_fetch(spec, "optional", 8, 0))) { + SvGETMAGIC(*temp); + + if (SvTRUE(*temp)) continue; + } + } + else if (!SvTRUE(HeVAL(he))) { + continue; + } + av_push(missing, SvREFCNT_inc(HeSVKEY_force(he))); + } + } +} + +static IV +validate(HV* p, HV* specs, HV* options, HV* ret) { + AV* missing; + AV* unmentioned; + HE* he; + HE* he1; + SV* hv; + SV* hv1; + IV ignore_case = 0; + SV* strip_leading = NULL; + IV allow_extra = 0; + SV** temp; + SV* normalize_func = NULL; + AV* untaint_keys = (AV*) sv_2mortal((SV*) newAV()); + IV i; + + if ((temp = hv_fetch(options, "ignore_case", 11, 0))) { + SvGETMAGIC(*temp); + ignore_case = SvTRUE(*temp); + } + + if ((temp = hv_fetch(options, "strip_leading", 13, 0))) { + SvGETMAGIC(*temp); + if (SvOK(*temp)) strip_leading = *temp; + } + + if ((temp = hv_fetch(options, "normalize_keys", 14, 0))) { + SvGETMAGIC(*temp); + if(SvROK(*temp) && SvTYPE(SvRV(*temp)) == SVt_PVCV) { + normalize_func = *temp; + } + } + + if (normalize_func || ignore_case || strip_leading) { + p = normalize_hash_keys(p, normalize_func, strip_leading, ignore_case); + specs = normalize_hash_keys(specs, normalize_func, strip_leading, ignore_case); + } + + /* short-circuit everything else when no_validation is true */ + if (no_validation()) { + if (GIMME_V != G_VOID) { + while ((he = hv_iternext(p))) { + hv = HeVAL(he); + if (! hv) { + continue; + } + + SvGETMAGIC(hv); + + /* put the parameter into return hash */ + if (!hv_store_ent(ret, HeSVKEY_force(he), SvREFCNT_inc(hv), + HeHASH(he))) { + SvREFCNT_dec(hv); + croak("Cannot add new key to hash"); + } + } + apply_defaults(ret, p, specs, NULL); + } + + return 1; + } + + if ((temp = hv_fetch(options, "allow_extra", 11, 0))) { + SvGETMAGIC(*temp); + allow_extra = SvTRUE(*temp); + } + + /* find extra parameters and validate good parameters */ + unmentioned = (AV*) sv_2mortal((SV*) newAV()); + + hv_iterinit(p); + while ((he = hv_iternext(p))) { + hv = HeVAL(he); + if (! hv) { + continue; + } + + SvGETMAGIC(hv); + + /* put the parameter into return hash */ + if (GIMME_V != G_VOID) { + if (!hv_store_ent(ret, HeSVKEY_force(he), SvREFCNT_inc(hv), + HeHASH(he))) { + SvREFCNT_dec(hv); + croak("Cannot add new key to hash"); + } + } + + /* check if this parameter is defined in spec and if it is + then validate it using spec */ + he1 = hv_fetch_ent(specs, HeSVKEY_force(he), 0, HeHASH(he)); + if(he1) { + hv1 = HeVAL(he1); + if (SvROK(hv1) && SvTYPE(SvRV(hv1)) == SVt_PVHV) { + char* buffer; + HV* spec; + IV untaint = 0; + + spec = (HV*) SvRV(hv1); + buffer = form("The '%s' parameter (%%s)", HePV(he, PL_na)); + + if (! validate_one_param(hv, (SV*) p, spec, buffer, options, &untaint)) + return 0; + + /* The value stored here is meaningless, we're just tracking + keys to untaint later */ + if (untaint) { + av_push(untaint_keys, SvREFCNT_inc(HeSVKEY_force(he1))); + } + } + } + else if (! allow_extra) { + av_push(unmentioned, SvREFCNT_inc(HeSVKEY_force(he))); + } + + if (av_len(unmentioned) > -1) { + SV* buffer = newSVpv("The following parameter", 0); + SV *caller = get_caller(options); + + if (av_len(unmentioned) != 0) { + sv_catpv(buffer, "s were "); + } + else { + sv_catpv(buffer, " was "); + } + sv_catpv(buffer, "passed in the call to "); + sv_catsv(buffer, caller); + SvREFCNT_dec(caller); + sv_catpv(buffer, " but "); + if (av_len(unmentioned) != 0) { + sv_catpv(buffer, "were "); + } + else { + sv_catpv(buffer, "was "); + } + sv_catpv(buffer, "not listed in the validation options: "); + for(i = 0; i <= av_len(unmentioned); i++) { + sv_catsv(buffer, *av_fetch(unmentioned, i, 1)); + if (i < av_len(unmentioned)) { + sv_catpv(buffer, " "); + } + } + sv_catpv(buffer, "\n"); + + validation_failure(buffer, options); + } + } + + validate_named_depends(p, specs, options); + + /* find missing parameters */ + missing = (AV*) sv_2mortal((SV*) newAV()); + + apply_defaults(ret, p, specs, missing); + + if (av_len(missing) > -1) { + SV* buffer = newSVpv("Mandatory parameter", 0); + SV *caller = get_caller(options); + + if (av_len(missing) > 0) { + sv_catpv(buffer, "s "); + } + else { + sv_catpv(buffer, " "); + } + + for(i = 0; i <= av_len(missing); i++) { + sv_catpvf(buffer, "'%s'", + SvPV_nolen(*av_fetch(missing, i, 0))); + if (i < av_len(missing)) { + sv_catpv(buffer, ", "); + } + } + sv_catpv(buffer, " missing in call to "); + sv_catsv(buffer, caller); + SvREFCNT_dec(caller); + sv_catpv(buffer, "\n"); + + validation_failure(buffer, options); + } + + if (GIMME_V != G_VOID) { + for (i = 0; i <= av_len(untaint_keys); i++) { + SvTAINTED_off(HeVAL(hv_fetch_ent(p, *av_fetch(untaint_keys, i, 0), 0, 0))); + } + } + + return 1; +} + +static SV* +validate_pos_failure(IV pnum, IV min, IV max, HV* options) { + SV* buffer; + SV** temp; + IV allow_extra; + + if ((temp = hv_fetch(options, "allow_extra", 11, 0))) { + SvGETMAGIC(*temp); + allow_extra = SvTRUE(*temp); + } + else { + allow_extra = 0; + } + + buffer = newSViv(pnum + 1); + if (pnum != 0) { + sv_catpv(buffer, " parameters were passed to "); + } + else { + sv_catpv(buffer, " parameter was passed to "); + } + sv_catsv(buffer, get_caller(options)); + sv_catpv(buffer, " but "); + if (!allow_extra) { + if (min != max) { + sv_catpvf(buffer, "%d - %d", (int) min + 1, (int) max + 1); + } + else { + sv_catpvf(buffer, "%d", (int) max + 1); + } + } + else { + sv_catpvf(buffer, "at least %d", (int) min + 1); + } + if ((allow_extra ? min : max) != 0) { + sv_catpv(buffer, " were expected\n"); + } + else { + sv_catpv(buffer, " was expected\n"); + } + + return buffer; +} + +/* Given a single parameter spec and a corresponding complex spec form + of it (which must be false if the spec is not complex), return true + says that the parameter is options. */ +static bool +spec_says_optional(SV* spec, IV complex_spec) { + SV** temp; + + if (complex_spec) { + if ((temp = hv_fetch((HV*) SvRV(spec), "optional", 8, 0))) { + SvGETMAGIC(*temp); + if (!SvTRUE(*temp)) + return FALSE; + } + else { + return FALSE; + } + } + else { + if (SvTRUE(spec)) { + return FALSE; + } + } + return TRUE; +} + +static IV +validate_pos(AV* p, AV* specs, HV* options, AV* ret) { + char* buffer; + SV* value; + SV* spec = NULL; + SV** temp; + IV i; + IV complex_spec = 0; + IV allow_extra; + /* Index of highest-indexed required parameter known so far, or -1 + if no required parameters are known yet. */ + IV min = -1; + AV* untaint_indexes = (AV*) sv_2mortal((SV*) newAV()); + + if (no_validation()) { + IV spec_count = av_len(specs); + IV p_count = av_len(p); + IV max = spec_count > p_count ? spec_count : p_count; + + if (GIMME_V == G_VOID) { + return 1; + } + + for (i = 0; i <= max; i++) { + if (i <= spec_count) { + spec = *av_fetch(specs, i, 1); + if (spec) { + SvGETMAGIC(spec); + } + complex_spec = (SvROK(spec) && SvTYPE(SvRV(spec)) == SVt_PVHV); + } + + if (i <= av_len(p)) { + value = *av_fetch(p, i, 1); + SvGETMAGIC(value); + av_push(ret, SvREFCNT_inc(value)); + } else if (complex_spec && + (temp = hv_fetch((HV*) SvRV(spec), "default", 7, 0))) { + SvGETMAGIC(*temp); + av_push(ret, SvREFCNT_inc(*temp)); + } + } + return 1; + } + + /* iterate through all parameters and validate them */ + for (i = 0; i <= av_len(specs); i++) { + spec = *av_fetch(specs, i, 1); + if (! spec) { + continue; + } + SvGETMAGIC(spec); + complex_spec = (SvROK(spec) && SvTYPE(SvRV(spec)) == SVt_PVHV); + + /* Unless the current spec refers to an optional argument, update + our notion of the index of the highest-idexed required + parameter. */ + if (! spec_says_optional(spec, complex_spec) ) { + min = i; + } + + if (i <= av_len(p)) { + value = *av_fetch(p, i, 1); + SvGETMAGIC(value); + + if (complex_spec) { + IV untaint = 0; + + buffer = form("Parameter #%d (%%s)", (int)i + 1); + + if (! validate_one_param(value, (SV*) p, (HV*) SvRV(spec), buffer, options, &untaint)) { + return 0; + } + + if (untaint) { + av_push(untaint_indexes, newSViv(i)); + } + } + + if (GIMME_V != G_VOID) { + av_push(ret, SvREFCNT_inc(value)); + } + + } else if (complex_spec && + (temp = hv_fetch((HV*) SvRV(spec), "default", 7, 0))) { + SvGETMAGIC(*temp); + + if (GIMME_V != G_VOID) { + av_store(ret, i, SvREFCNT_inc(*temp)); + } + + } + else { + if (i == min) { + /* We don't have as many arguments as the arg spec requires. */ + SV* buffer; + + /* Look forward through remaining argument specifications to + find the last non-optional one, so we can correctly report the + number of arguments required. */ + for (i++ ; i <= av_len(specs); i++) { + spec = *av_fetch(specs, i, 1); + if (! spec) { + continue; + } + + SvGETMAGIC(spec); + complex_spec = (SvROK(spec) && SvTYPE(SvRV(spec)) == SVt_PVHV); + if (! spec_says_optional(spec, complex_spec)) { + min = i; + } + if (min != i) + break; + } + + buffer = validate_pos_failure(av_len(p), min, av_len(specs), options); + + validation_failure(buffer, options); + } + } + } + + validate_pos_depends(p, specs, options); + + /* test for extra parameters */ + if (av_len(p) > av_len(specs)) { + if ((temp = hv_fetch(options, "allow_extra", 11, 0))) { + SvGETMAGIC(*temp); + allow_extra = SvTRUE(*temp); + } + else { + allow_extra = 0; + } + if (allow_extra) { + /* put all additional parameters into return array */ + if (GIMME_V != G_VOID) { + for(i = av_len(specs) + 1; i <= av_len(p); i++) { + value = *av_fetch(p, i, 1); + if (value) { + SvGETMAGIC(value); + av_push(ret, SvREFCNT_inc(value)); + } + else { + av_push(ret, &PL_sv_undef); + } + } + } + } + else { + SV* buffer = validate_pos_failure(av_len(p), min, av_len(specs), options); + validation_failure(buffer, options); + } + } + + if (GIMME_V != G_VOID) { + for (i = 0; i <= av_len(untaint_indexes); i++) { + SvTAINTED_off(*av_fetch(p, SvIV(*av_fetch(untaint_indexes, i, 0)), 0)); + } + } + + return 1; +} + +MODULE = Params::Validate::XS PACKAGE = Params::Validate::XS + +void +validate(p, specs) + SV* p + SV* specs + + PROTOTYPE: \@$ + + PPCODE: + + HV* ret = NULL; + AV* pa; + HV* ph; + HV* options; + + if (no_validation() && GIMME_V == G_VOID) { + XSRETURN(0); + } + + SvGETMAGIC(p); + if (! (SvROK(p) && SvTYPE(SvRV(p)) == SVt_PVAV)) { + croak("Expecting array reference as first parameter"); + } + + SvGETMAGIC(specs); + if (! (SvROK(specs) && SvTYPE(SvRV(specs)) == SVt_PVHV)) { + croak("Expecting hash reference as second parameter"); + } + + pa = (AV*) SvRV(p); + ph = NULL; + if (av_len(pa) == 0) { + /* we were called as validate( @_, ... ) where @_ has a + single element, a hash reference */ + SV* value; + + value = *av_fetch(pa, 0, 1); + if (value) { + SvGETMAGIC(value); + if (SvROK(value) && SvTYPE(SvRV(value)) == SVt_PVHV) { + ph = (HV*) SvRV(value); + } + } + } + + options = get_options(NULL); + + if (! ph) { + ph = (HV*) sv_2mortal((SV*) newHV()); + + if (! convert_array2hash(pa, options, ph) ) { + XSRETURN(0); + } + } + if (GIMME_V != G_VOID) { + ret = (HV*) sv_2mortal((SV*) newHV()); + } + if (! validate(ph, (HV*) SvRV(specs), options, ret)) { + XSRETURN(0); + } + RETURN_HASH(ret); + +void +validate_pos(p, ...) +SV* p + + PROTOTYPE: \@@ + + PPCODE: + + AV* specs; + AV* ret = NULL; + IV i; + + if (no_validation() && GIMME_V == G_VOID) { + XSRETURN(0); + } + + SvGETMAGIC(p); + if (!SvROK(p) || !(SvTYPE(SvRV(p)) == SVt_PVAV)) { + croak("Expecting array reference as first parameter"); + } + + specs = (AV*) sv_2mortal((SV*) newAV()); + av_extend(specs, items); + for(i = 1; i < items; i++) { + if (!av_store(specs, i - 1, SvREFCNT_inc(ST(i)))) { + SvREFCNT_dec(ST(i)); + croak("Cannot store value in array"); + } + } + + if (GIMME_V != G_VOID) { + ret = (AV*) sv_2mortal((SV*) newAV()); + } + + if (! validate_pos((AV*) SvRV(p), specs, get_options(NULL), ret)) { + XSRETURN(0); + } + + RETURN_ARRAY(ret); + +void +validate_with(...) + + PPCODE: + + HV* p; + SV* params; + SV* spec; + IV i; + + if (no_validation() && GIMME_V == G_VOID) XSRETURN(0); + + /* put input list into hash */ + p = (HV*) sv_2mortal((SV*) newHV()); + for(i = 0; i < items; i += 2) { + SV* key; + SV* value; + + key = ST(i); + if (i + 1 < items) { + value = ST(i + 1); + } + else { + value = &PL_sv_undef; + } + if (! hv_store_ent(p, key, SvREFCNT_inc(value), 0)) { + SvREFCNT_dec(value); + croak("Cannot add new key to hash"); + } + } + + params = *hv_fetch(p, "params", 6, 1); + SvGETMAGIC(params); + spec = *hv_fetch(p, "spec", 4, 1); + SvGETMAGIC(spec); + + if (SvROK(spec) && SvTYPE(SvRV(spec)) == SVt_PVAV) { + if (SvROK(params) && SvTYPE(SvRV(params)) == SVt_PVAV) { + AV* ret = NULL; + + if (GIMME_V != G_VOID) { + ret = (AV*) sv_2mortal((SV*) newAV()); + } + + PUTBACK; + + if (! validate_pos((AV*) SvRV(params), (AV*) SvRV(spec), get_options(p), ret)) { + SPAGAIN; + XSRETURN(0); + } + + SPAGAIN; + RETURN_ARRAY(ret); + } + else { + croak("Expecting array reference in 'params'"); + } + } + else if (SvROK(spec) && SvTYPE(SvRV(spec)) == SVt_PVHV) { + HV* hv; + HV* ret = NULL; + HV* options; + + options = get_options(p); + + if (SvROK(params) && SvTYPE(SvRV(params)) == SVt_PVHV) { + hv = (HV*) SvRV(params); + } + else if (SvROK(params) && SvTYPE(SvRV(params)) == SVt_PVAV) { + I32 hv_set = 0; + + /* Check to see if we have a one element array + containing a hash reference */ + if (av_len((AV*) SvRV(params)) == 0) { + SV** first_elem; + + first_elem = av_fetch((AV*) SvRV(params), 0, 0); + + if (first_elem && SvROK(*first_elem) && + SvTYPE(SvRV(*first_elem)) == SVt_PVHV) { + + hv = (HV*) SvRV(*first_elem); + hv_set = 1; + } + } + + if (! hv_set) { + hv = (HV*) sv_2mortal((SV*) newHV()); + + if (! convert_array2hash((AV*) SvRV(params), options, hv)) + XSRETURN(0); + } + } + else { + croak("Expecting array or hash reference in 'params'"); + } + + if (GIMME_V != G_VOID) { + ret = (HV*) sv_2mortal((SV*) newHV()); + } + + PUTBACK; + + if (! validate(hv, (HV*) SvRV(spec), options, ret)) { + SPAGAIN; + XSRETURN(0); + } + + SPAGAIN; + RETURN_HASH(ret); + } + else { + croak("Expecting array or hash reference in 'spec'"); + } |