This commit is contained in:
msyu
2024-10-23 11:17:42 +03:00
commit ab5c944862
380 changed files with 55823 additions and 0 deletions
+514
View File
@@ -0,0 +1,514 @@
component
output = false
hint = "I provide a way to serialize complex ColdFusion data values as case-sensitive JavaScript Object Notation (JSON) strings."
{
//2024-06-27 modified by msyu: added server timezone
// I return the initialized component.
public any function init() {
// Every key is added to the full key list - used for one-time key serialization.
fullKeyList = {};
// Every key is added to the hint list so that we don't have to switch on the lists.
fullHintList = {};
// These key lists determine special data serialization.
stringKeyList = {};
booleanKeyList = {};
integerKeyList = {};
floatKeyList = {};
dateKeyList = {};
// These keys will NOT be used in serialization (ie. the key/value pairs will not be
// added to the serialized output).
blockedKeyList = {};
// Return the initialized component.
return( this );
}
// ---
// PUBLIC METHODS.
// ---
// I define the given key without a type. This is here to provide key-casing without caring
// about why type of data conversion takes place. Returns serializer.
public any function asAny( required string key ) {
return( defineKey( fullKeyList, key, "any" ) );
}
// I define the given key as a boolean. Returns serializer.
public any function asBoolean( required string key ) {
return( defineKey( booleanKeyList, key, "boolean" ) );
}
// I define the given key as a date. Returns serializer.
public any function asDate( required string key ) {
return( defineKey( dateKeyList, key, "date" ) );
}
// I define the given key as a float / decimal. Returns serializer.
public any function asFloat( required string key ) {
return( defineKey( floatKeyList, key, "float" ) );
}
// I define the given key as an integer. Returns serializer.
public any function asInteger( required string key ) {
return( defineKey( integerKeyList, key, "integer" ) );
}
// I define the given key as a string. Returns serializer.
public any function asString( required string key ) {
return( defineKey( stringKeyList, key, "string" ) );
}
// I define the key as one that should not be included in the serialized response.
public any function exclude( required string key ) {
blockedKeyList[ key ] = true;
return( this );
}
/**
* I serialize the given input as JavaScript Object Notation (JSON) using the case-sensitive
* values defined in the key-list.
*
* @output false
*/
public string function serialize( required any input ) {
// Write the serialized value to the output buffer.
savecontent variable = "local.serializedInput" {
serializeInput( input, "any" );
}
return( serializedInput );
}
// ---
// PRIVATE METHODS.
// ---
// I define the given key within the given key list.
private any function defineKey(
required struct keyList,
required string key,
required string hint
) {
if ( structKeyExists( fullKeyList, key ) ) {
throw(
type = "DuplicateKey",
message = "The key [#key#] has already been defined within the serializer.",
detail = "The current key list is: #structKeyList( fullKeyList, ', ' )#"
);
}
// Add to the appropriate data-type lists. This one is used for existence checking.
keyList[ key ] = key;
// Add all keys to the full key list as well. This one is used to store the serialization
// of the key so that it doesn't have to be recalculated each time the object is serialized.
fullKeyList[ key ] = serializeString( key );
// If we have a specific type, then add the hint to the full hint list as well. This will
// allow us to quickly look up the pass-through data type hint during serialization.
// --
// NOTE: The reason we don't want to pass through "any" is that we want parent types to be
// able to "fall through" during the object traversal. If we added "any" to the type list,
// then it would always overwrite the parent data type.
if ( hint != "any" ) {
fullHintList[ key ] = hint;
}
// Return this reference for method chaining.
return( this );
}
// I walk the given object, writing the serialized value to the output (which is expected to
// be a content buffer).
// ---
// NOTE: THIS METHOD IS HUGE - this is on purpose. Since serialization is a rather intense
// process, I am trying to cut out as much overhead as possible. In this case, we're cutting
// out extra stack space by inlining and duplicating a lot of functionality. This is being done
// at the COST of clarity and non-repetitive code.
private void function serializeInput(
required any input,
required string hint
) {
// Serialize the data base on the type of input. We are organizing this in terms of the
// most commonly-used values first. The anticipation is that the vast majority of data
// types will be simple values.
if ( isSimpleValue( input ) ) {
if ( ( hint == "string" ) || ( hint == "any" ) ) {
// If the string appears to be numeric, then we have to prefix it to make sure
// ColdFusion doesn't accidentally convert it to a number.
if ( isNumeric( input ) ) {
writeOutput( """" & input & """" );
} else {
serializeInputString( input );
}
} else if ( ( hint == "boolean" ) && isBoolean( input ) ) {
writeOutput( input ? "true" : "false" );
} else if ( ( ( hint == "integer" ) || ( hint == "float" ) ) && isNumeric( input ) ) {
writeOutput( input );
} else if ( ( ( hint == "integer" ) || ( hint == "float" ) ) && isBoolean( input ) ) {
writeOutput( input ? "1" : "0" );
} else if ( ( hint == "date" ) && ( isDate( input ) || isNumericDate( input ) ) ) {
// Write the date in ISO 8601 time string format. We're going to assume that the
// date is already in the desired timezone.
///writeOutput( """" & dateFormat( input, "yyyy-mm-dd" ) & "T" & timeFormat( input, "HH:mm:ss.l" ) & "Z""" );
//2024-06-27 modified by msyu: added server timezone
writeOutput( """" & dateFormat( input, "yyyy-mm-dd" ) & "T" & timeFormat( input, "HH:mm:ss.lXX" ) & """" );
} else {
serializeInputString( input );
}
return;
} // END: isSimpleValue().
// I'm expecting the struct to be the next most common data type since it will likely be
// the container for the majority of data values.
if ( isStruct( input ) ) {
writeOutput( "{" );
var isFirst = true;
for ( var key in input ) {
// Skip any black-listed keys.
if ( structKeyExists( blockedKeyList, key ) ) {
continue;
}
// Handle the item delimiter.
if ( isFirst ) {
isFirst = false;
} else {
writeOutput( "," );
}
// Ensure that the given key can be referenced on the full-key list. This way,
// the subsequent logic will be easier.
if ( ! structKeyExists( fullKeyList, key ) ) {
asAny( lcase( key ) );
}
writeOutput( fullKeyList[ key ] & ":" );
// Pass in the most appropriate data-type hint based on the parent key.
if ( structKeyExists( fullHintList, key ) ) {
serializeInput( input[ key ], fullHintList[ key ] );
// If the given key is unknown, just pass through the most recent hint as
// it may be defining the type for an entire structure.
} else {
serializeInput( input[ key ], hint );
}
}
writeOutput( "}" );
return;
} // END: isStruct().
if ( isArray( input ) ) {
writeOutput( "[" );
var isFirst = true;
// Handle the item delimiter.
for ( var value in input ) {
if ( isFirst ) {
isFirst = false;
} else {
writeOutput( "," );
}
// Since we don't have a key to go off of, pass-through the most recent hint.
serializeInput( value, hint );
}
writeOutput( "]" );
return;
} // END: isArray().
// When we serialize a query, we're going to treat it like an array of structs.
if ( isQuery( input ) ) {
var keys = listToArray( input.columnList );
// Make sure each column is defined as a known key - makes the subsequent logic easier.
for ( var key in keys ) {
if ( ! structKeyExists( fullKeyList, key ) ) {
asAny( lcase( key ) );
}
}
writeOutput( "[" );
// Serialize each row of the query as a struct.
for ( var i = 1 ; i <= input.recordCount ; i++ ) {
// Handle the row delimiter.
if ( i > 1 ) {
writeOutput( "," );
}
writeOutput( "{" );
var isFirst = true;
for ( var key in keys ) {
// Skip any black-listed keys.
if ( structKeyExists( blockedKeyList, key ) ) {
continue;
}
// Handle the item delimiter (in the current row).
if ( isFirst ) {
isFirst = false;
} else {
writeOutput( "," );
}
writeOutput( fullKeyList[ key ] & ":" );
// Pass in the most appropriate data-type hint based on the parent key.
if ( structKeyExists( fullHintList, key ) ) {
serializeInput( input[ key ][ i ], fullHintList[ key ] );
// If the given key is unknown, just pass through the most recent hint as
// it may be defining the type for an entire structure.
} else {
serializeInput( input[ key ][ i ], hint );
}
} // END: Key list.
writeOutput( "}" );
} // END: Row list.
writeOutput( "]" );
return;
} // END: isQuery().
// If we made it this far, we were given a data type that we're not actively supporting.
// As such, we just have to hand this off to the native serializer.
writeOutput( serializeJson( input ) );
}
/**
* I serialize and write the given string to the current output context, escaping all appropriate
* characters for the JSON specification.
*
* NOTE: We are using this manual-encoding process rather than the built-in serializeJson() function
* as there is a rather nasty bug that corrupts certain patterns in the output. Read more:
*
* http://www.bennadel.com/blog/2842-serializejson-and-the-input-and-output-encodings-are-not-same-errors-in-coldfusion.htm
*
* @input I am the string being serialized.
*/
private void function serializeInputString( required string input ) {
// While this may not be technically needed, this will ensure that we are not using any
// "undocumented features" of the language. If we explicitly cast to a Java string, and
// something goes wrong due to odd type-casting, it's a ColdFusion bug, at that point, not
// a logic error ;)
input = javaCast( "string", input );
var length = input.length();
writeOutput( """" );
for ( var i = 1 ; i <= length ; i++ ) {
var charCode = input.codePointAt( javaCast( "int", i - 1 ) );
// Check for the most common case first (normal characters).
if (
( charCode >= 32 ) &&
( charCode != 34 ) &&
( charCode != 47 ) &&
( charCode != 92 ) &&
( charCode != 8232 ) &&
( charCode != 8233 )
) {
writeOutput( chr( charCode ) );
// Check for the special cases next (control characters, characters that
// need to be escaped, and characters that need to be encoded nicely).
} else if ( charCode == 8 ) {
writeOutput( "\b" );
} else if ( charCode == 9 ) {
writeOutput( "\t" );
} else if ( charCode == 10 ) {
writeOutput( "\n" );
} else if ( charCode == 12 ) {
writeOutput( "\f" );
} else if ( charCode == 13 ) {
writeOutput( "\r" );
} else if (
( charCode < 32 ) ||
( charCode == 8232 ) ||
( charCode == 8233 )
) {
// For Unicode hex values, we need to enforce a 4-digit code.
writeOutput( "\u" & right( ( "000" & formatBaseN( charCode, 16 ) ), 4 ) );
} else if ( charCode == 34 ) {
writeOutput( "\""" );
} else if ( charCode == 47 ) {
writeOutput( "\/" );
} else if ( charCode == 92 ) {
writeOutput( "\\" );
}
}
writeOutput( """" );
}
/**
* I serialize and return the given string, escaping all appropriate characters for the
* JSON specification.
*
* @input I am the string being serialized.
* @output false
*/
private string function serializeString( required string input ) {
savecontent variable = "local.json" {
serializeInputString( input );
}
return( json );
}
}
+283
View File
@@ -0,0 +1,283 @@
component {
public any function init() {
variables.utcBaseDate = createObject( 'java', 'java.util.Date' ).init( javacast( 'int', 0 ) );
variables.ECParameterSpecCache = { };
}
function convertDateToUnixTimestamp( required date dateToConvert ) {
return dateDiff( 's', utcBaseDate, parseDateTime( dateToConvert ) );
}
function convertUnixTimestampToDate( required numeric timestamp ) {
return dateAdd( 's', timestamp, utcBaseDate );
}
function base64UrlToBinary( base64url ) {
var base64 = base64url.replace( '-', '+', 'all' ).replace( '_', '/', 'all' );
var padded = base64 & repeatString( '=', 4 - ( len( base64 ) % 4 ) );
return binaryDecode( padded, 'base64' );
}
function binaryToBase64Url( source ) {
return binaryEncode( source, 'base64' )
.replace( '+', '-', 'all' )
.replace( '/', '_', 'all' )
.replace( '=', '', 'all' );
}
/**
* The INTEGER encoding for DER consists of a 02 tag a length encoding of the value
* and then a signed, minimum sized, big endian encoding of the encoded number.
*
* - https://stackoverflow.com/questions/54718741/how-to-der-encode-an-ecdsa-signature
*/
function derEncodeIntegerBytes( byteArray ) {
// first remove any padding
for ( var i = 1; i <= arrayLen( byteArray ); i++ ) {
if ( byteArray[ i ] != 0 ) break;
}
var unpadded = arraySlice( byteArray, i );
// add sign if negative
if ( unpadded[ 1 ] < 0 ) {
unpadded.prepend( 0 );
}
// if len > 127 the length encoding will be wrong, but that won't happen for the supported signature sizes
var derEncoded = [ 2, unpadded.len() ];
derEncoded.append( unpadded, true );
return derEncoded;
}
/**
* The SEQUENCE encoding is simply a tag set to the byte value 30, the
* length encoding and then the concatenation of the two INTEGER
* structures.
*
* https://stackoverflow.com/questions/54718741/how-to-der-encode-an-ecdsa-signature
*
* Also see:
* https://crypto.stackexchange.com/questions/57731/ecdsa-signature-rs-to-asn1-der-encoding-question
*/
function convertP1363ToDER( signature ) {
var split = len( signature ) / 2;
var r = derEncodeIntegerBytes( arraySlice( signature, 1, split ) );
var s = derEncodeIntegerBytes( arraySlice( signature, split + 1, split ) );
var DERSignature = [ 48 ];
var length = r.len() + s.len();
if ( length > 255 ) {
throw(
type = 'jwtcfml.InvalidSignature',
message = 'Invalid P1363 key.',
detail = 'The P1363 signature is too long.'
);
}
/*
The length is simply a single byte if it is smaller than 128 (or hex 80)
of the size. If it is larger then it is two byte: one byte set to 81,
which indicates that one length byte will follow, and one byte
containing the actual value.
https://stackoverflow.com/questions/54718741/how-to-der-encode-an-ecdsa-signature
*/
if ( length > 127 ) {
DERSignature.append( -127 );
length -= 256;
}
DERSignature.append( length );
DERSignature.append( r, true );
DERSignature.append( s, true );
return javacast( 'byte[]', DERSignature );
}
function convertDERtoP1363( required any signature, required string algorithm ) {
// extract the two integers from the DER signature
// assuming a 02 tag byte followed by a single length byte since we should not see
// anything larger in the supported algorithms
var start = 3;
while ( signature[ start ] != 2 ) start++;
var r = arraySlice( signature, start + 2, signature[ start + 1 ] );
var s = arraySlice( signature, start + 2 + r.len() + 2 );
if ( r[ 1 ] == 0 ) r = arraySlice( r, 2 );
if ( s[ 1 ] == 0 ) s = arraySlice( s, 2 );
var lengthMap = {
ES256: 32,
ES384: 48,
ES512: 64
};
var P1363Signature = [ ];
for ( var i = 1; i <= lengthMap[ algorithm ] - r.len(); i++ ) P1363Signature.append( 0 );
P1363Signature.append( r, true );
for ( var i = 1; i <= lengthMap[ algorithm ] - s.len(); i++ ) P1363Signature.append( 0 );
P1363Signature.append( s, true );
return javacast( 'byte[]', P1363Signature );
}
function parsePEMEncodedKey( required string pemKey ) {
if ( reFind( '^-----BEGIN (RSA|EC) (PARAMETERS|PRIVATE)', pemKey ) ) {
throw(
type = 'jwtcfml.InvalidPrivateKey',
message = 'Invalid private key format.',
detail = 'Please encode your private key in PKCS8 format, e.g.: `openssl pkcs8 -topk8 -nocrypt -in privatekey.pem -out privatekey.pk8'
)
}
var binaryKey = binaryDecode(
trim( pemKey ).reReplace( '-----[A-Z\s]+-----', '', 'all' ).reReplace( '[\r\n]', '', 'all' ),
'base64'
);
if ( find( '-----BEGIN CERTIFICATE-----', pemKey ) ) {
var bis = createObject( 'java', 'java.io.ByteArrayInputStream' ).init( binaryKey );
return createObject( 'java', 'java.security.cert.CertificateFactory' )
.getInstance( 'X.509' )
.generateCertificate( bis )
.getPublicKey();
}
if ( find( '-----BEGIN PUBLIC KEY-----', pemKey ) ) {
var publicKeySpec = createObject( 'java', 'java.security.spec.X509EncodedKeySpec' ).init( binaryKey );
try {
return createObject( 'java', 'java.security.KeyFactory' )
.getInstance( 'RSA' )
.generatePublic( publicKeySpec );
} catch ( any e ) {
}
try {
return createObject( 'java', 'java.security.KeyFactory' )
.getInstance( 'EC' )
.generatePublic( publicKeySpec );
} catch ( any e ) {
}
}
if ( find( '-----BEGIN PRIVATE KEY-----', pemKey ) ) {
var privateKeySpec = createObject( 'java', 'java.security.spec.PKCS8EncodedKeySpec' ).init( binaryKey );
try {
return createObject( 'java', 'java.security.KeyFactory' )
.getInstance( 'RSA' )
.generatePrivate( privateKeySpec );
} catch ( any e ) {
}
try {
return createObject( 'java', 'java.security.KeyFactory' )
.getInstance( 'EC' )
.generatePrivate( privateKeySpec );
} catch ( any e ) {
}
}
throw(
type = 'jwtcfml.InvalidPEMKey',
message = 'Invalid PEM key.',
detail = 'Please ensure you are using an RSA or EC public or private key or certificate.'
)
}
function parseJWK( required struct jwk ) {
if ( jwk.kty == 'RSA' ) {
if ( jwk.keyExists( 'd' ) ) {
try {
var bigInts = bigIntegers( jwk, [ 'n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi' ] );
var keySpec = createObject( 'java', 'java.security.spec.RSAPrivateCrtKeySpec' ).init(
bigInts.n,
bigInts.e,
bigInts.d,
bigInts.p,
bigInts.q,
bigInts.dp,
bigInts.dq,
bigInts.qi
);
var kf = createObject( 'java', 'java.security.KeyFactory' ).getInstance( 'RSA' );
return kf.generatePrivate( keySpec );
} catch ( any e ) {
}
try {
var bigInts = bigIntegers( jwk, [ 'n', 'd' ] );
var keySpec = createObject( 'java', 'java.security.spec.RSAPrivateKeySpec' ).init(
bigInts.n,
bigInts.d
);
var kf = createObject( 'java', 'java.security.KeyFactory' ).getInstance( 'RSA' );
return kf.generatePrivate( keySpec );
} catch ( any e ) {
}
} else {
try {
var bigInts = bigIntegers( jwk, [ 'n', 'e' ] );
var ks = createObject( 'java', 'java.security.spec.RSAPublicKeySpec' ).init( bigInts.n, bigInts.e );
var kf = createObject( 'java', 'java.security.KeyFactory' ).getInstance( 'RSA' );
return kf.generatePublic( ks );
} catch ( any e ) {
}
}
}
if ( jwk.kty == 'EC' ) {
var kf = createObject( 'java', 'java.security.KeyFactory' ).getInstance( 'EC' );
var ECParameterSpec = getECParameterSpec( jwk.crv );
if ( jwk.keyExists( 'd' ) ) {
var bigInts = bigIntegers( jwk, [ 'd' ] );
var ks = createObject( 'java', 'java.security.spec.ECPrivateKeySpec' ).init(
bigInts.d,
ECParameterSpec
);
return kf.generatePrivate( ks );
} else {
var bigInts = bigIntegers( jwk, [ 'x', 'y' ] );
var ECPoint = createObject( 'java', 'java.security.spec.ECPoint' ).init( bigInts.x, bigInts.y );
var ks = createObject( 'java', 'java.security.spec.ECPublicKeySpec' ).init( ECPoint, ECParameterSpec );
return kf.generatePublic( ks );
}
}
throw(
type = 'jwtcfml.InvalidJWK',
message = 'Invalid JWK key.',
detail = 'Please ensure you are using an valid JWK RSA or EC public or private key.'
)
}
private function bigIntegers( jwk, keys ) {
var bigInts = { };
for ( var key in keys ) {
bigInts[ key ] = createObject( 'java', 'java.math.BigInteger' ).init( 1, base64UrlToBinary( jwk[ key ] ) );
}
return bigInts;
}
private function getECParameterSpec( crv ) {
if ( !variables.ECParameterSpecCache.keyExists( crv ) ) {
var kpg = createObject( 'java', 'java.security.KeyPairGenerator' ).getInstance( 'EC' );
var ecgp = createObject( 'java', 'java.security.spec.ECGenParameterSpec' ).init(
'secp#crv.listLast( '-' )#r1'
);
kpg.initialize( ecgp );
variables.ECParameterSpecCache[ crv ] = kpg
.generateKeyPair()
.getPublic()
.getParams();
}
return variables.ECParameterSpecCache[ crv ];
}
}
+66
View File
@@ -0,0 +1,66 @@
<cfsilent>
<!--- 20181209 v0.3 --->
<!---v0.3 добавлен атрибут cfSqlType--->
<!---v0.4 добавлен атрибут container for json--->
<!---v0.5 добавлен атрибут type и трансляция в CF_SQL_--->
<!--- 20210326 v0.8 formatter --->
<cffunction name="passThrough"
returntype="any"
output="false"
hint="just return argument">
<cfargument name="x" type="ANY" required="true"/>
<cfreturn #ARGUMENTS.x#/>
</cffunction>
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
<cfassociate basetag="cf_field_set" datacollection="fieldsArray"/>
<cfparam name="ATTRIBUTES.expression" default=""/>
<cfparam name="ATTRIBUTES.title" default=""/>
<cfparam name="ATTRIBUTES.name" default=""/>
<cfparam name="ATTRIBUTES.type" default="string"/>
<cfparam name="ATTRIBUTES.cfSqlType" default="#getCfSqlType(ATTRIBUTES.type)#"/>
<cfparam name="ATTRIBUTES.container" default=""/>
<cfparam name="ATTRIBUTES.formatter" default=#passThrough#/>
<cfif ATTRIBUTES.expression IS "">
<cfset ATTRIBUTES.expression=thisTag.generatedContent/>
</cfif>
<cfif ATTRIBUTES.name IS "">
<cfset regex="([[:word:]]+)[[:space:]]*$"/>
<cfset matches=REFindNoCase(regex, ATTRIBUTES.expression, 1, true)/>
<cfif arrayLen(matches.pos) GT 1>
<cfset ATTRIBUTES.name=mid(ATTRIBUTES.expression, matches.pos[2], matches.len[2])/>
</cfif>
</cfif>
</cfif>
<cffunction name="getCfSqlType">
<!---Translate CF type understandable by isValid to CF_SQL_*--->
<cfargument name="type"/>
<cfswitch expression=#ARGUMENTS.type#>
<cfcase value="string">
<cfreturn "CF_SQL_VARCHAR"/>
</cfcase>
<cfcase value="integer">
<cfreturn "CF_SQL_INTEGER"/>
</cfcase>
<cfcase value="boolean">
<cfreturn "CF_SQL_BIT"/>
</cfcase>
<cfcase value="numeric">
<cfreturn "CF_SQL_NUMERIC"/>
</cfcase>
<cfcase value="date,time">
<cfreturn "CF_SQL_TIMESTAMP"/>
</cfcase>
<cfdefaultcase>
<cfthrow type="custom" message="Unsupported type" detail="Type #ARGUMENTS.type# not supported"/>
</cfdefaultcase>
</cfswitch>
</cffunction>
</cfsilent>
+50
View File
@@ -0,0 +1,50 @@
<cfsilent>
<!--- 20181209 v0.3 добавлено поле cfSqlType--->
<!--- 20201029 v0.5 все атрибуты необязательные--->
<!--- 20201114 v0.6 добавлен атрибут "container" for json--->
<!--- 20201201 v0.7 исправлен listOut--->
<!--- 20210326 v0.8 formatter --->
<!--- Используется в селекте для структурирования списка полей. Выводит вместо себя список через запятую, экспортирует Map со структурами выражение+заголовок, с ключом, соответствующим имени колонки в селекте (ключ вычисляется регулярным выражением) --->
<cfif thisTag.executionMode is "end">
<cfparam name="ATTRIBUTES.titleMapOut" default=""/>
<cfparam name="ATTRIBUTES.lengthOut" default=""/>
<cfparam name="ATTRIBUTES.listOut" default=""/><!---возвращаемый список полей через запятую для селекта. Если не задан (чаще всего так), этот список возвращается в виде контента тега --->
<cfparam name="thisTag.fieldsArray" type="array"/><!--- Вложенные теги field отдают сюда свои данные --->
<cfset titleMap=structNew("linked")/><!---railo syntax--->
<cfset list=""/>
<cfset i=0/>
<cfloop array=#thisTag.fieldsArray# index="field">
<cfset i=i+1/>
<cfset list=listAppend(#list#, #field.expression#)/>
<cfif len(#field.name#)>
<cfset structInsert(#titleMap#, #field.name#, structNew())/>
<cfset "titleMap.#field.name#.ordinal"=#i#/>
<cfset "titleMap.#field.name#.title"=#field.title#/>
<cfset "titleMap.#field.name#.type"=#field.type#/>
<cfset "titleMap.#field.name#.cfSqlType"=#field.cfSqlType#/>
<cfset "titleMap.#field.name#.container"=#field.container#/>
<cfset "titleMap.#field.name#.formatter"=#field.formatter#/>
</cfif>
</cfloop>
<cfif len(ATTRIBUTES.lengthOut)>
<cfset "CALLER.#ATTRIBUTES.lengthOut#"=#arrayLen(thisTag.fieldsArray)#/>
</cfif>
<cfif len(ATTRIBUTES.titleMapOut)>
<cfset "CALLER.#ATTRIBUTES.titleMapOut#"=#titleMap#/>
</cfif>
<cfif len(ATTRIBUTES.listOut)>
<cfset "CALLER.#ATTRIBUTES.listOut#"=preserveSingleQuotes(list)/>
<cfset thisTag.generatedContent=""/>
<cfelse>
<cfset thisTag.generatedContent=preserveSingleQuotes(list)/>
</cfif>
</cfif>
</cfsilent>
+52
View File
@@ -0,0 +1,52 @@
<!--- version 2.01---><!---15:25 31.01.2019--->
<!--- version 3---><!---16:42 16.11.2020--->
<!--- build query filter string --->
<cfparam name="ATTRIBUTES.filter" type="struct"/>
<cfloop collection=#ATTRIBUTES.filter# item="item">
<cfsilent>
<cfset fltr=structFind(ATTRIBUTES.filter,item)>
<cfparam name="fltr.ftype"/>
<cfparam name="fltr.field"/>
<cfparam name="fltr.compare" default="EQ"/>
<cfparam name="fltr.val" default=""/>
<cfparam name="fltr.prefix" default=""/>
<cfparam name="fltr.suffix" default=""/>
<cfparam name="fltr.list" default="No"/>
</cfsilent><!---
---> AND <cfif structKeyExists(fltr,"expression") AND len(fltr.expression)><!---
---><cfset len=listLen(#fltr.expression#,"?")><!---
---><cfloop from=1 to=#len# index="i"><!---
---><cfoutput>#listGetAt(fltr.expression,i,"?")#</cfoutput><!---
---><cfif i LT #len#><cfqueryparam cfsqltype="#getCfSqLType(fltr.ftype)#" list="#fltr.list#" value="#fltr.prefix##fltr.val##fltr.suffix#"/></cfif><!---
---></cfloop><!---
---><cfelse><!---
---><cfoutput>#fltr.field#</cfoutput><cfswitch expression=#uCase(fltr.compare)#><cfcase value="EQ"> = </cfcase><cfcase value="NE,NEQ"> <> </cfcase><cfcase value="LE,LTE"> <= </cfcase><cfcase value="LT"> < </cfcase><cfcase value="GE,GTE"> >= </cfcase><cfcase value="GT"> > </cfcase><cfcase value="LIKE"> LIKE </cfcase><cfcase value="LIKE%,LIKEP"> LIKE <cfqueryparam cfsqltype=#getCfSqLType(fltr.ftype)# value="%#fltr.val#%"/><cfcontinue/></cfcase><cfcase value="IN"> IN (<cfqueryparam cfsqltype=#getCfSqLType(fltr.ftype)# list="#fltr.list#" value="#fltr.val#"/>)<cfcontinue/></cfcase><cfdefaultcase> = <cfoutput> #fltr.field#</cfoutput><cfcontinue/><!---***криво---></cfdefaultcase></cfswitch><cfqueryparam cfsqltype=#getCfSqLType(fltr.ftype)# value="#fltr.val#"/><cfcontinue/>
</cfif>
</cfloop>
<cfexit method="exittag"/>
<cffunction name="getCfSqlType" output="No">
<cfargument name="fieldType" default=""/>
<cfswitch expression=#ARGUMENTS.fieldType#>
<cfcase value="numeric">
<cfreturn "CF_SQL_NUMERIC"/>
</cfcase>
<cfcase value="integer">
<cfreturn "CF_SQL_INTEGER"/>
</cfcase>
<cfcase value="boolean">
<cfreturn "CF_SQL_BIT"/>
</cfcase>
<cfcase value="date,time,datetime">
<cfreturn "CF_SQL_TIMESTAMP"/>
</cfcase>
<cfcase value="string">
<cfreturn "CF_SQL_VARCHAR"/>
</cfcase>
<cfdefaultcase>
<cfreturn "CF_SQL_VARCHAR"/>
</cfdefaultcase>
</cfswitch>
</cffunction>
+1
View File
@@ -0,0 +1 @@
<cfoutput>index ***</cfoutput>
+262
View File
@@ -0,0 +1,262 @@
component {
variables.algorithmMap = {
HS256: 'HmacSHA256',
HS384: 'HmacSHA384',
HS512: 'HmacSHA512',
RS256: 'SHA256withRSA',
RS384: 'SHA384withRSA',
RS512: 'SHA512withRSA',
ES256: 'SHA256withECDSA',
ES384: 'SHA384withECDSA',
ES512: 'SHA512withECDSA'
};
public any function init() {
variables.encodingUtils = new encodingUtils();
variables.jss = createObject( 'java', 'java.security.Signature' );
variables.messageDigest = createObject( 'java', 'java.security.MessageDigest' );
return this;
}
public string function encode(
required struct payload,
required any key,
required string algorithm,
struct headers = { }
) {
if ( !algorithmMap.keyExists( algorithm ) ) {
throw(
type = 'jwtcfml.InvalidAlgorithm',
message = 'Invalid JWT Algorithm.',
detail = 'The passed in algorithm is not supported.'
);
}
var header = { };
header.append( headers );
header.append( {
'typ': 'JWT',
'alg': algorithm
} );
var duplicatedPayload = duplicate( payload );
for ( var claim in [ 'iat', 'exp', 'nbf' ] ) {
if ( duplicatedPayload.keyExists( claim ) && isDate( duplicatedPayload[ claim ] ) ) {
duplicatedPayload[ claim ] = encodingUtils.convertDateToUnixTimestamp( duplicatedPayload[ claim ] );
}
}
var stringToSignParts = [
encodingUtils.binaryToBase64Url( charsetDecode( serializeJSON( header ), 'utf-8' ) ),
encodingUtils.binaryToBase64Url( charsetDecode( serializeJSON( duplicatedPayload ), 'utf-8' ) )
];
var stringToSign = stringToSignParts.toList( '.' );
return stringToSign & '.' & encodingUtils.binaryToBase64Url( sign( stringToSign, key, algorithm ) );
}
public struct function decode(
required string token,
any key,
any algorithms = [ ],
struct claims = { },
boolean verify = true
) {
var parts = listToArray( token, '.' );
if ( arrayLen( parts ) != 3 ) {
throw(
type = 'jwtcfml.InvalidToken',
message = 'Invalid JWT.',
detail = 'The passed in token does not have three `.` delimited parts.'
);
}
algorithms = isArray( algorithms ) ? algorithms : [ algorithms ];
var decoded = {
header: deserializeJSON( charsetEncode( encodingUtils.base64UrlToBinary( parts[ 1 ] ), 'utf-8' ) ),
payload: deserializeJSON( charsetEncode( encodingUtils.base64UrlToBinary( parts[ 2 ] ), 'utf-8' ) )
};
if ( verify ) {
if (
!algorithms.find( decoded.header.alg ) ||
!algorithmMap.keyExists( decoded.header.alg )
) {
throw(
type = 'jwtcfml.InvalidAlgorithm',
message = 'Unsupported or invalid algorithm',
detail = 'The passed in token does not have an algorithm declaration or its declared algorithm (#decoded.header.alg#) does not match the specified algorithms of #serializeJSON( algorithms )#.'
);
}
var stringToSign = parts[ 1 ] & '.' & parts[ 2 ];
var signature = encodingUtils.base64UrlToBinary( parts[ 3 ] );
if (
!verifySignature(
stringToSign,
key,
signature,
decoded.header.alg
)
) {
throw(
type = 'jwtcfml.InvalidSignature',
message = 'Signature is Invalid',
detail = 'The signature of the passed in token is invalid.'
);
}
var baseClaims = {
'exp': true,
'nbf': true
};
baseClaims.append( claims );
verifyClaims( decoded.payload, baseClaims );
}
for ( var claim in [ 'iat', 'exp', 'nbf' ] ) {
if ( decoded.payload.keyExists( claim ) ) {
decoded.payload[ claim ] = encodingUtils.convertUnixTimestampToDate( decoded.payload[ claim ] );
}
}
return decoded.payload;
}
public struct function getHeader( required string token ) {
return deserializeJSON( charsetEncode( encodingUtils.base64UrlToBinary( listFirst( token, '.' ) ), 'utf-8' ) );
}
public function parsePEMEncodedKey( required string pemKey ) {
return encodingUtils.parsePEMEncodedKey( pemKey );
}
public function parseJWK( required struct jwk ) {
return encodingUtils.parseJWK( jwk );
}
private function sign( message, key, algorithm ) {
if ( left( algorithm, 1 ) == 'H' ) {
var sig = binaryDecode(
hmac(
message,
key,
algorithmMap[ algorithm ],
'utf-8'
),
'hex'
);
} else {
if ( isSimpleValue( key ) ) {
key = encodingUtils.parsePEMEncodedKey( key );
} else if ( isStruct( key ) ) {
key = encodingUtils.parseJWK( key );
}
var jssInstance = variables.jss.getInstance( algorithmMap[ algorithm ] );
jssInstance.initSign( key );
jssInstance.update( charsetDecode( message, 'utf-8' ) );
var sig = jssInstance.sign();
if ( left( algorithm, 1 ) == 'E' ) {
sig = encodingUtils.convertDERtoP1363( sig, algorithm );
}
}
return sig;
}
private function verifySignature( message, key, signature, algorithm ) {
if ( left( algorithm, 1 ) == 'H' ) {
var sig = binaryDecode(
hmac(
message,
key,
algorithmMap[ algorithm ],
'utf-8'
),
'hex'
);
return MessageDigest.isEqual( signature, sig );
}
if ( left( algorithm, 1 ) == 'E' ) {
signature = encodingUtils.convertP1363ToDER( signature );
}
if ( isSimpleValue( key ) ) {
key = encodingUtils.parsePEMEncodedKey( key );
} else if ( isStruct( key ) ) {
key = encodingUtils.parseJWK( key );
}
var jssInstance = variables.jss.getInstance( algorithmMap[ algorithm ] );
jssInstance.initVerify( key );
jssInstance.update( charsetDecode( message, 'utf-8' ) );
return jssInstance.verify( signature );
}
private function verifyClaims( payload, claims ) {
if (
structKeyExists( payload, 'exp' )
&& !verifyDateClaim( payload.exp, claims.exp, -1 )
) {
throw(
type = 'jwtcfml.ExpiredSignature',
message = 'Token has expired',
detail = 'The passed in token has expired.'
);
}
if (
structKeyExists( payload, 'nbf' )
&& !verifyDateClaim( payload.nbf, claims.nbf, 1 )
) {
throw(
type = 'jwtcfml.NotBeforeException',
message = 'Token is not valid',
detail = 'The passed in token has not yet become valid.'
);
}
if ( structKeyExists( claims, 'iss' ) ) {
if ( !structKeyExists( payload, 'iss' ) || compare( payload.iss, claims.iss ) != 0 ) {
throw(
type = 'jwtcfml.InvalidIssuer',
message = 'Token has an invalid issuer',
detail = 'The passed in token either does not specify an issuer or the claimed issuer is not valid.'
);
}
}
if ( structKeyExists( claims, 'aud' ) ) {
var audArray = isArray( claims.aud ) ? claims.aud : [ claims.aud ];
if ( !structKeyExists( payload, 'aud' ) || !audArray.find( payload.aud ) ) {
throw(
type = 'jwtcfml.InvalidAudience',
message = 'Token has an invalid audience',
detail = 'The passed in token either does not specify an audience or the claimed audience is not valid.'
);
}
}
}
private function verifyDateClaim( payloadDate, claim, failState ) {
var pd = encodingUtils.convertUnixTimestampToDate( payloadDate );
var cd = claim;
if ( !isBoolean( cd ) || cd ) {
if ( isNumeric( cd ) ) {
cd = encodingUtils.convertUnixTimestampToDate( cd );
} else if ( !isDate( cd ) ) {
cd = now();
}
return dateCompare( pd, cd ) != failState;
}
return true;
}
}
+33
View File
@@ -0,0 +1,33 @@
<cfcomponent
displayname="Notifier"
output="true"
hint="Notification helper, to be refactored">
<!---Внимание! необходимо зарегистрировать на сервере сертификат хоста maker.ifttt.com, иначе SSL соединение не установится и будет Connection Failure--->
<!--- Нужно реализовывать нормальную систему с подпиской на события, по типу
https://tonytruong.net/event-emitter-pattern-in-coldfusion/--->
<cffunction name="notify"
access="public"
returntype="any"
output="false"
hint="send notification">
<cfargument name="value1" type="string" required="true" />
<cfargument name="value2" type="string" required="true" />
<cfargument name="value3" type="string" required="true" />
<cftry>
<cfhttp name="ifttt" method="post" url="https://maker.ifttt.com/trigger/mms_task_event/with/key/drWn5g6pNjyP_XRru0tMrQ" result="resp" timeout="10">
<cfhttpparam type="header" name="Content-Type" value="application/json" />
<cfhttpparam type="body" value="#serializeJSON(arguments)#">
</cfhttp>
<cfreturn resp.filecontent/>
<cfcatch type="ANY">
<cfreturn "#cfcatch.message# #cfcatch.detail#"/>
</cfcatch>
</cftry>
</cffunction>
</cfcomponent>
+43
View File
@@ -0,0 +1,43 @@
<cfsilent>
<!--- build query sort string --->
<!---v2 11:21 16.11.2020--->
<!---v3 14:15 16.11.2020 input struct instead of array--->
<!---v4 2024-10-15 input ANY--->
<cfparam name="ATTRIBUTES.sortCollection" type="any">
<cfparam name="ATTRIBUTES.fieldCount" type="integer" default=0>
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
<cfset querySort="">
<cfset i=1 />
<cfloop collection=#ATTRIBUTES.sortCollection# index="item"><!--- *** Не уверен, что все будет хорошо, когда sortCollection - array, не перепутаются ли поля, порядок важен --->
<cftry>
<cfset sort=ATTRIBUTES.sortCollection[item] />
<cfif (i LE ATTRIBUTES.fieldCount) OR (ATTRIBUTES.fieldCount LE 0)>
<cfif sort.asc>
<cfset querySort=listAppend(querySort," #sort.fld# asc")/>
<cfelse>
<cfset querySort=listAppend(querySort," #sort.fld# desc")/>
</cfif>
</cfif>
<cfcatch type="Any">
<cfrethrow/>
</cfcatch>
</cftry>
<cfset i=i+1 />
</cfloop>
<cfif len(trim(querySort)) EQ 0>
<cfset querySort="1 desc">
</cfif>
<cfset querySort=trim(querySort)>
<cfif isDefined("ATTRIBUTES.output")>
<cfset "CALLER.#ATTRIBUTES.output#"=querySort/>
<cfelse>
<cfset thisTag.generatedContent=querySort/>
</cfif>
</cfif>
</cfsilent>
+391
View File
@@ -0,0 +1,391 @@
<cfcomponent
displayname="ReST API helper"
output="true"
hint="Static helper methods for ReST API">
<!--- v0.02 2024-10-15 --->
<cffunction name="empty2null"
access="public"
returntype="any"
output="false"
hint="replace empty field with null">
<cfargument name="value" type="any" required="true" />
<cfif isEmpty(ARGUMENTS.value)><!---*** некорректно, но просто, пустые строки превращает в нулл--->
<cfreturn javacast('null','')/>
<cfelse>
<cfreturn ARGUMENTS.value />
</cfif>
</cffunction>
<cffunction name="passThrough"
returntype="any"
output="false"
hint="just return argument">
<cfargument name="x" type="ANY" required="true"/>
<cfreturn #ARGUMENTS.x#/>
</cffunction>
<cffunction name="appendRecord"
access="public"
returntype="any"
output="false"
hint="put data into struct, replace empty fields with null">
<cfargument name="struct" type="struct" required="true" />
<cfargument name="key" type="string" required="true" />
<cfargument name="fieldMap" type="struct" required="true" />
<cfargument name="query" type="query" required="true" />
<cfargument name="fieldNameDecorator" type="function" required="false" default=#passThrough#/>
<cfif len(ARGUMENTS.key)>
<cfset ARGUMENTS.struct[ARGUMENTS.key]=structNew()/>
<cfset var container=ARGUMENTS.struct[ARGUMENTS.key]/>
<cfelse>
<cfset var container=ARGUMENTS.struct/>
</cfif>
<cfloop list=#structKeyList(ARGUMENTS.fieldMap)# index="local.col">
<cfset var obj=container/>
<cfset var formatter = structFind(ARGUMENTS.fieldMap[local.col], "formatter")/>
<cfif structKeyExists(ARGUMENTS.fieldMap[#local.col#],"container")>
<cfset var subContainerName = structFind(ARGUMENTS.fieldMap[local.col], "container")/>
<cfif len(subContainerName)>
<cfif !structKeyExists(container, subContainerName)>
<cfset structInsert(container, subContainerName, structNew())/>
</cfif>
<cfset obj=container[subContainerName]/>
</cfif>
</cfif>
<cfset obj[ARGUMENTS.fieldNameDecorator(local.col)]=empty2null(formatter(ARGUMENTS.query[local.col][ARGUMENTS.query.currentRow]))/>
<!---*** не уверен в надежности конструкции во всех реализациях CF, контекст query передастся ли--->
</cfloop>
<cfreturn ARGUMENTS.struct/>
</cffunction>
<cffunction name="parseFilterParams"
returntype="struct"
output="true"
hint="parse and collect filter params">
<!---*** add test for multiple values--->
<cfargument name="params" type="struct" required="true" />
<cfset var out=structNew()/>
<cfloop collection=#params# index="local.item">
<cfset var urlParamName=snake2camel(lCase(local.item))/>
<cfif structKeyExists(URL,urlParamName)>
<cfset var rawValue=URL[urlParamName]/>
<cfset var operator="EQ"/>
<cfset var value=#rawValue#/>
<cfif listLen(rawValue,":") GT 1>
<cfset var operator=listGetAt(rawValue,1,":")/>
<cfset var value=listGetAt(rawValue,2,":")/>
</cfif>
<cfif !isValid(ARGUMENTS.params[local.item].type,value)>
<cfthrow type="invalidParamValue" message="Filter parameter #urlParamName# value is not a valid #ARGUMENTS.params[local.item].type#"/><!---не выводим значение, чтобы исключить XSS--->
</cfif>
<cfif structKeyExists(ARGUMENTS.params[local.item], "prefix") AND len(ARGUMENTS.params[local.item].prefix)>
<cfset var fieldName="#ARGUMENTS.params[local.item].prefix#.#local.item#"/>
<cfelse>
<cfset var fieldName=#local.item#/>
</cfif>
<cfset var rec={field=#fieldName#, val=#value#, ftype=#ARGUMENTS.params[local.item].type#, compare=#operator#}/><!---*** add list--->
<cfset structInsert(out, local.item, rec, true)/>
</cfif>
</cfloop>
<cfreturn out/>
</cffunction>
<cffunction name="parseOrderBy"
returntype="struct"
output="false"
hint="parse and collect sort order param"><!--- deprecated --->
<cfargument name="params" type="struct" required="true" />
<cfargument name="orderBy" type="string" required="true" />
<cfset var out={}/>
<cfloop list=#ARGUMENTS.orderBy# index="local.item">
<cfif listLen(local.item,".") GT 1>
<cfset var fld=camel2snake(listGetAt(local.item,1,"."))/>
<cfswitch expression=#uCase(listGetAt(local.item,2,"."))#>
<cfcase value="ASC"><cfset var asc=true/></cfcase>
<cfcase value="DESC"><cfset var asc=false/></cfcase>
<cfdefaultcase>
<cfthrow type="InvalidParamValue" message="Invalid orderBy format" detail="orderBy suffix should be '.asc' or '.desc'" /><!---*** XSS vulnerability, so we would not show the suffix--->
</cfdefaultcase>
</cfswitch>
<cfelse>
<cfset var fld=camel2snake(local.item)/>
<cfset var asc=true/>
</cfif>
<cfif structKeyExists(ARGUMENTS.params, fld)>
<cfset structInsert(out,"#ARGUMENTS.params[fld].prefix#.#fld#", {fld="#ARGUMENTS.params[fld].prefix#.#fld#",asc=#asc#}, true)/>
<cfelse>
<cfthrow type="InvalidParamValue" message="Invalid orderBy field"/>
</cfif>
</cfloop>
<cfreturn out/>
</cffunction>
<cffunction name="parseNumericOrder"
returntype="array"
output="false"
hint="parse and collect sort order param with numeric notation">
<cfargument name="fieldSet" type="struct" required="true" />
<cfargument name="orderBy" type="string" required="true" />
<cfset var out=[]/>
<cfloop list=#ARGUMENTS.orderBy# index="local.item">
<cfif listLen(local.item,".") GT 1>
<cfset var fld=camel2snake(listGetAt(local.item,1,"."))/>
<cfswitch expression=#uCase(listGetAt(local.item,2,"."))#>
<cfcase value="ASC"><cfset var asc=true/></cfcase>
<cfcase value="DESC"><cfset var asc=false/></cfcase>
<cfdefaultcase>
<cfthrow type="InvalidParamValue" message="Invalid orderBy format" detail="orderBy suffix should be '.asc' or '.desc'" /><!---*** XSS vulnerability, so we would not show the suffix--->
</cfdefaultcase>
</cfswitch>
<cfelse>
<cfset var fld=camel2snake(local.item)/>
<cfset var asc=true/>
</cfif>
<cfif structKeyExists(ARGUMENTS.fieldSet, fld)>
<cfset arrayAppend(out,{fld="#ARGUMENTS.fieldSet[fld].ordinal#",asc=#asc#})/>
<cfelse>
<cfthrow type="InvalidParamValue" message="Invalid orderBy field"/>
</cfif>
</cfloop>
<cfreturn out/>
</cffunction>
<!---<cffunction name="snake2camel"
access="public"
returntype="any"
output="false"
hint="convert snake style name to camel style name">
<cfargument name="snake" type="string" required="true" />
<cfset var snake=ARGUMENTS.snake/>
<cfset var camel=""/>
<cfset var pos=1/>
<cfloop condition="true">
<cfset next=reFind("(_[a-z])", snake, pos, false)/>
<cfif NOT next GT 0>
<cfbreak/>
</cfif>
<cfset camel="#camel##mid(snake,pos,next-pos)##uCase(mid(snake,next+1,1))#"/>
<cfset pos=next+2/>
</cfloop>
<cfset camel="#camel##mid(snake,pos,len(snake))#"/>
<cfreturn #camel#/>
</cffunction>--->
<!---not used any more--->
<cffunction name="query4json" access="public" returntype="any" output="false"
hint="Converts query to an array of structs. Key names are lowercase. Empty fields are treated as nulls">
<cfargument name="Query" type="query" required="true" />
<cfset var out=arrayNew(1)/>
<cfset var i=0/>
<cfloop query=ARGUMENTS.Query>
<cfset i=i+1/>
<cfset out[i]=structNew()/>
<cfloop list=#ARGUMENTS.Query.ColumnList# index="local.col">
<cfset var value=ARGUMENTS.Query[local.col][currentRow]/>
<cfif isEmpty(value)><!---*** некорректно, но просто, пустые строки превращает в нулл--->
<cfset out[i][local.col]=javacast('null','')/>
<cfelse>
<cfset out[i][local.col]=ARGUMENTS.Query[local.col][currentRow]/>
</cfif>
</cfloop>
</cfloop>
<cfreturn(out)/>
</cffunction>
<cffunction name="snake2camel"
access="public"
returntype="any"
output="false"
hint="convert snake style name to camel style name">
<cfargument name="snake" type="string" required="true" />
<cfreturn #reReplace(ARGUMENTS.snake,"_([a-z])","\u\1","ALL")#/>
</cffunction>
<cffunction name="camel2snake"
access="public"
returntype="any"
output="false"
hint="convert camel style name to snake style name">
<cfargument name="snake" type="string" required="true" />
<cfreturn #reReplace(ARGUMENTS.snake,"([A-Z])","_\l\1","ALL")#/>
</cffunction>
<cffunction name="formatMessage"
access="public"
returntype="any"
output="true"
hint="formats Exception for display">
<cfargument name="message" type="string" required="true" />
<cfargument name="title" type="string" required="false" />
<cfset var title=structKeyExists(ARGUMENTS,"title") ? #ARGUMENTS.title# : #ARGUMENTS.message# />
<cfreturn {type="about:blank", title="#title#", detail="#ARGUMENTS.message#"}/>
</cffunction>
<cffunction name="formatException"
access="public"
returntype="any"
output="true"
hint="formats Exception for display">
<cfargument name="ex" type="struct" required="true" />
<cfargument name="title" type="string" required="false" />
<cfset var detail = listAppend(#ARGUMENTS.ex.message#,#ARGUMENTS.ex.detail#,":" )/>
<cfset var title = structKeyExists(ARGUMENTS,"title") ? #ARGUMENTS.title# : #ARGUMENTS.message# />
<cfreturn formatMessage(detail, title) />
</cffunction>
<cffunction name="formatBadRequestError"
access="public"
returntype="any"
output="true"
hint="formats Bad Request Exception">
<cfargument name="ex" type="struct" required="true" />
<cfset var detail=listAppend(#ARGUMENTS.ex.message#,#ARGUMENTS.ex.detail#,":" )/>
<cfreturn {type="about:blank", title="Bad Request", detail="#detail#"}/>
</cffunction>
<!---<cffunction name="wrapResultSet"
access="public"
returntype="any"
output="true"
hint="format resultset as json">
<cfargument name="qRead" type="query" required="true" />
<cfargument name="titleMap" type="struct" required="true" />
<cfargument name="startrow" type="integer" required="false" default="1" />
<cfset var resultSet=[]/>
<cfloop query=#ARGUMENTS.qRead# startRow=#ARGUMENTS.startrow#>
<cfset var rec={}/>
<cfset appendRecord(rec, "", ARGUMENTS.titleMap, ARGUMENTS.qRead, snake2camel)/>
<cfset arrayAppend(resultSet, rec)/>
</cfloop>
<cfset var out={
pageSize=
}/>
<cfcontent type="application/json"/>
<cfoutput>{
"pageSize":#pageSize#,
"page":#page#,
"orderBy":"#orderBy#",
"size":"#arrayLen(resultSet)#",
"total":"#qTotal.cnt#",
"results":#serializeJson(resultSet)#,
"queryDurationMs":#queryDurationMs#, <cfset runDurationMs=getTickCount()-request.startTickCount/>
"runDurationMs":#runDurationMs#
}</cfoutput>--->
<cffunction name="validateField"
access="public"
returntype="any"
output="false"
hint="validate field of a structure, if exisits">
<cfargument name="struct" type="struct" required=true/>
<cfargument name="name" required=true/>
<cfargument name="type" default="string"/>
<cfargument name="required" type="boolean" default="false"/>
<cfif structKeyExists(ARGUMENTS.struct, ARGUMENTS.name)>
<cfif NOT isValid(ARGUMENTS.type, ARGUMENTS.struct[ARGUMENTS.name])>
<cfthrow type="invalidParamValue" message="Parameter validation failed" detail="Supplied value of '#ARGUMENTS.name#' is not a valid #ARGUMENTS.type#"/>
</cfif>
<cfelse>
<cfif ARGUMENTS.required>
<cfthrow type="invalidParamValue" message="Required field not supplied" detail="Required field #ARGUMENTS.name# not found"/>
</cfif>
</cfif>
</cffunction>
<!--- returns false if the key does not exist and throws exception on invalid format--->
<cffunction name="keyExistsAndValid" access="public" returntype="boolean">
<cfargument name="struct" type="struct" required=true/>
<cfargument name="key" type="string" required=true/>
<cfargument name="type" type="string" required=false/>
<cfargument name="maxLength" type="numeric" required=false hint="Checked for strings only, negative value means no limit"/>
<cfif structKeyExists(arguments.struct, arguments.key)>
<cfif structKeyExists(arguments, "type")>
<cfset var x=arguments.struct[arguments.key]/>
<cfif isValid(arguments.type, x)>
<cfif lCase(arguments.type) EQ "string">
<cfif structKeyExists(arguments, "maxLength")>
<cfif ARGUMENTS.maxLength GE 0>
<cfif len(x) GT #arguments.maxLength#>
<cfthrow type="invalidParamValue" message="Invalid parameter value (string too long)" detail="Parameter #arguments.key# length #len(x)# is greater than #arguments.maxLength#" errorcode=400/>
</cfif>
</cfif>
</cfif>
</cfif>
<cfelse>
<cfthrow type="invalidParamValue" message="Invalid parameter value" detail="Parameter #arguments.key# is not a valid #arguments.type#" errorcode=400/>
</cfif>
</cfif>
<cfreturn true/>
<cfelse>
<cfreturn false/>
</cfif>
</cffunction>
<!--- returns false if the key does not exist or has invalid format--->
<cffunction name="keyExistsAndValidGraceful" access="public" returntype="boolean">
<cfargument name="struct" type="struct" required=true/>
<cfargument name="key" type="string" required=true/>
<cfargument name="type" type="string" required=false/>
<cfargument name="maxLength" type="numeric" required=false default="-1" hint="Checked for strings only"/>
<cftry>
<cfreturn keyExistsAndValid(ARGUMENTS.struct, ARGUMENTS.key, ARGUMENTS.type, ARGUMENTS.maxLength)/>
<cfcatch type="ANY"><!---<cfrethrow/>---></cfcatch>
</cftry>
<cfreturn false/>
</cffunction>
</cfcomponent>