cleanup: remove reasoning comments and translate helper docs
This commit is contained in:
+26
-61
@@ -1,19 +1,18 @@
|
||||
component /*https://www.bennadel.com/blog/2976-trying-to-generate-cryptographically-strong-random-tokens-in-coldfusion.htm*/
|
||||
output = false
|
||||
hint = "I generate random tokens using Java's SecureRandom class."
|
||||
hint = "Генерирует случайные токены с помощью Java SecureRandom."
|
||||
{
|
||||
|
||||
/**
|
||||
* I initialize the token generator.
|
||||
* Инициализирует генератор токенов.
|
||||
*
|
||||
* @output false
|
||||
*/
|
||||
public any function init() {
|
||||
|
||||
// I am the generator implementation used to generate the random token data.
|
||||
// --
|
||||
// NOTE: The SHA1PRNG is not the default in all implementations of the JVM. As
|
||||
// such, we're defining the algorithm explicitly to keep this code consistent.
|
||||
// Реализация генератора, используемая для построения случайных токенов.
|
||||
// SHA1PRNG не является алгоритмом по умолчанию во всех реализациях JVM,
|
||||
// поэтому он задается явно для предсказуемого поведения.
|
||||
generator = createObject( "java", "java.security.SecureRandom" )
|
||||
.getInstance(
|
||||
javaCast( "string", "SHA1PRNG" ),
|
||||
@@ -21,22 +20,14 @@ component /*https://www.bennadel.com/blog/2976-trying-to-generate-cryptographica
|
||||
)
|
||||
;
|
||||
|
||||
// Now that we've initialized the random generator, we have to generate a random
|
||||
// byte of data. This will ensure that the random generator is self-seeded using
|
||||
// a shared seed generator.
|
||||
// --
|
||||
// CAUTION: Since the underlying seed generator reads from sources of entropy,
|
||||
// this may hang until enough entropy has been collected. This is another good
|
||||
// reason to do it during initialization time rather than at first-use time.
|
||||
// После инициализации нужно сгенерировать случайный байт,
|
||||
// чтобы генератор сам засеялся через общий источник seed.
|
||||
// Это может блокироваться до накопления достаточной энтропии,
|
||||
// поэтому операция выполняется при инициализации, а не при первом использовании.
|
||||
generator.nextBytes( charsetDecode( " ", "utf-8" ) );
|
||||
|
||||
// I hold the future date at which time the generator should be reseeded in order
|
||||
// to keep it unpredictable.
|
||||
// --
|
||||
// NOTE: This doesn't affect the randomness of the values. But, the thinking
|
||||
// is that the longer the generator is producing values using the same seed,
|
||||
// the more likely an attacker is to be able to determine the original seed by
|
||||
// passively observing generated values.
|
||||
// Момент следующего пересева генератора.
|
||||
// Это снижает риск слишком долгой работы с одним и тем же seed.
|
||||
reseedAt = getNextReseedAt();
|
||||
|
||||
return( this );
|
||||
@@ -44,34 +35,23 @@ component /*https://www.bennadel.com/blog/2976-trying-to-generate-cryptographica
|
||||
}
|
||||
|
||||
|
||||
// ---
|
||||
// PUBLIC METHODS.
|
||||
// ---
|
||||
// Публичные методы.
|
||||
|
||||
|
||||
/**
|
||||
* I generate "cryptographically strong" random token strings that are based on the
|
||||
* given number of random bytes. The random bytes are subsequently encoded using a
|
||||
* base64url character-set so that they are URL-safe and can be used in a variety of
|
||||
* contexts. And, since base65url is a case-sensitive schema, the tokens will
|
||||
* naturally be case-sensitive.
|
||||
* Генерирует криптографически стойкий токен из заданного числа случайных байтов.
|
||||
* Байт-массив кодируется в форму, пригодную для использования в URL.
|
||||
*
|
||||
* @byteCount I am the number of random bytes used to generate the token.
|
||||
* @byteCount Количество случайных байтов для генерации токена.
|
||||
* @output false
|
||||
*/
|
||||
public string function nextToken( numeric byteCount = 32 ) {
|
||||
|
||||
// Check to see if the generator needs to be reseeded (using a double-check
|
||||
// locking approach to reduce the bottleneck).
|
||||
// Проверяем, нужен ли пересев генератора.
|
||||
if ( now() >= reseedAt ) {
|
||||
|
||||
// Synchronize the reseeding.
|
||||
// --
|
||||
// NOTE: From what I have read, I DON'T BELIEVE that .generateSeed() will
|
||||
// ever hang with the SHA1PRNG algorithm. However, it is unclear to me. As
|
||||
// such, I'm using [throwOnTimeout = false] so that parallel threads won't
|
||||
// error if the lock cannot be obtained in a timely manner and will just
|
||||
// fall through to using the generator with the pre-seeding state.
|
||||
// Пересев выполняется под lock, чтобы уменьшить гонки между потоками.
|
||||
// Если lock не получен вовремя, поток продолжает работу с текущим состоянием генератора.
|
||||
lock
|
||||
name = "TokenGenerator.reseedCheck"
|
||||
type = "exclusive"
|
||||
@@ -79,27 +59,21 @@ component /*https://www.bennadel.com/blog/2976-trying-to-generate-cryptographica
|
||||
throwOnTimeout = false
|
||||
{
|
||||
|
||||
// Perform double-check - generator may have already been reseeded by
|
||||
// a parallel request.
|
||||
// Повторная проверка внутри lock: другой поток мог уже обновить seed.
|
||||
if ( now() >= reseedAt ) {
|
||||
|
||||
reseedAt = getNextReseedAt();
|
||||
|
||||
// NOTE: Once the generator was seeded internally, this re-seeding
|
||||
// will only ever "add to" the existing seed. As such, this is still
|
||||
// building on top of the original randomness and calling this, on
|
||||
// interval, this will never reduce randomness.
|
||||
// Новый seed добавляется к уже существующему внутреннему состоянию генератора.
|
||||
generator.setSeed( generator.generateSeed( javaCast( "int", 32 ) ) );
|
||||
|
||||
}
|
||||
|
||||
} // END: Lock.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Create the byte buffer into which the random bytes will be written. Since
|
||||
// there's no "correct" way to generate a byte array in ColdFusion, we can
|
||||
// generate a string of the desired length and then decode it into bytes.
|
||||
// Создаем буфер байтов, в который будут записаны случайные значения.
|
||||
var byteBuffer = charsetDecode( repeatString( " ", byteCount ), "utf-8" );
|
||||
|
||||
generator.nextBytes( byteBuffer );
|
||||
@@ -109,27 +83,18 @@ component /*https://www.bennadel.com/blog/2976-trying-to-generate-cryptographica
|
||||
}
|
||||
|
||||
|
||||
// ---
|
||||
// PRIVATE METHODS.
|
||||
// ---
|
||||
// Приватные методы.
|
||||
|
||||
|
||||
/**
|
||||
* I encode the given byte array using the base64url character-set.
|
||||
* Кодирует массив байтов в строку токена.
|
||||
*
|
||||
* @bytes I am the byte array being encoded.
|
||||
* @bytes Кодируемый массив байтов.
|
||||
* @output false
|
||||
*/
|
||||
private string function encodeBytes( required binary bytes ) {
|
||||
|
||||
var token = binaryEncode( bytes, "base64" ); // *** вот поэтому длина отличается от заявленной
|
||||
|
||||
// Replace the characters that are not allowed in the base64url format. The
|
||||
// characters [+, /, =] are removed for URL-based base64 values because they
|
||||
// have significant meaning in the context of URL paths and query-strings.
|
||||
/*token = replace( token, "+", "-", "all" );
|
||||
token = replace( token, "/", "_", "all" );
|
||||
token = replace( token, "=", "", "all" ); */
|
||||
|
||||
// мы хотим убрать все спецсимволы, потому что мы используем токен в качестве псевдослучайного суффикса
|
||||
token = replace( token, "+", "a", "all" );
|
||||
@@ -142,7 +107,7 @@ component /*https://www.bennadel.com/blog/2976-trying-to-generate-cryptographica
|
||||
|
||||
|
||||
/**
|
||||
* I calculate the next date of reseeding.
|
||||
* Вычисляет время следующего пересева генератора.
|
||||
*
|
||||
* @output false
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<cfsilent></cfsilent>
|
||||
<!--- Используется в селекте для структурирования списка полей. Выводит вместо себя список через запятую, экспортирует Map со структурами выражение+заголовок, с ключом, соответствующим имени колонки в селекте (ключ вычисляется регулярным выражением) --->
|
||||
<!--- Используется в селекте для структурирования списка полей. Выводит вместо себя список через запятую и экспортирует Map со структурами expression+title с ключом, соответствующим имени колонки в селекте. --->
|
||||
|
||||
<cfset var local={}/> <!--- *** есть опасения, что это не будет работать в модуле .cfm (вообще var ведет себе странно) --->
|
||||
<cfset var local={}/>
|
||||
<cfif thisTag.executionMode is "end">
|
||||
|
||||
<cfparam name="ATTRIBUTES.titleMapOut" default=""/>
|
||||
@@ -19,11 +19,9 @@
|
||||
|
||||
<cfset i=0/>
|
||||
<cfloop array=#thisTag.fieldsArray# index="field">
|
||||
<!--- <cfdump var=#field#/> --->
|
||||
<cfif len(#field.name#) EQ 0
|
||||
OR listLen(ATTRIBUTES.fieldsToInclude) EQ 0
|
||||
OR listFindNoCase(ATTRIBUTES.fieldsToInclude,field.name)>
|
||||
<!--- пытаемся сохранить прежнюю логику - поля без имени не выбрасываются, но не попадают в titleMap --->
|
||||
<cfset i=i+1/>
|
||||
<cfset local.expressionList=listAppend(#local.expressionList#, #field.expression#)/>
|
||||
<cfset local.nameList=listAppend(#local.nameList#, #field.name#)/>
|
||||
@@ -52,7 +50,7 @@
|
||||
<cfset "CALLER.#ATTRIBUTES.nameListOut#"=#local.nameList#/>
|
||||
</cfif>
|
||||
|
||||
<cfif len(ATTRIBUTES.listOut)><!--- *** некрасиво --->
|
||||
<cfif len(ATTRIBUTES.listOut)>
|
||||
<cfset "CALLER.#ATTRIBUTES.listOut#"=local.expressionList/>
|
||||
<cfif ATTRIBUTES.listOutKeepContent>
|
||||
<cfset thisTag.generatedContent=preserveSingleQuotes(local.expressionList)/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<cfsilent>
|
||||
<!--- build query sort string --->
|
||||
<!--- Формирует строку сортировки для запроса --->
|
||||
|
||||
<cfparam name="ATTRIBUTES.sortCollection" type="any">
|
||||
<cfparam name="ATTRIBUTES.fieldCount" type="integer" default=0>
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<cfset querySort="">
|
||||
<cfset i=1 />
|
||||
<cfloop collection=#ATTRIBUTES.sortCollection# index="item"><!--- *** Не уверен, что все будет хорошо, когда sortCollection - array, не перепутаются ли поля, порядок важен --->
|
||||
<cfloop collection=#ATTRIBUTES.sortCollection# index="item">
|
||||
<cftry>
|
||||
<cfset sort=ATTRIBUTES.sortCollection[item] />
|
||||
<cfif (i LE ATTRIBUTES.fieldCount) OR (ATTRIBUTES.fieldCount LE 0)>
|
||||
|
||||
+23
-92
@@ -1,18 +1,17 @@
|
||||
<cfcomponent
|
||||
displayname="ReST API helper"
|
||||
output="true"
|
||||
hint="Static helper methods for ReST API">
|
||||
hint="Статические вспомогательные методы для ReST API">
|
||||
|
||||
<cffunction name="empty2null"
|
||||
access="public"
|
||||
returntype="any"
|
||||
output="false"
|
||||
hint="replace empty field with null">
|
||||
hint="Заменяет пустое поле на null">
|
||||
|
||||
<cfargument name="value" type="any" required="true" />
|
||||
|
||||
<cfif isSimpleValue(ARGUMENTS.value) AND isEmpty(ARGUMENTS.value)><!---*** некорректно, но просто, пустые строки превращает в нулл--->
|
||||
<!--- нам не нужно, чтобы пустые массивы превращались в нулл. Наверное, и структуры тоже --->
|
||||
<cfif isSimpleValue(ARGUMENTS.value) AND isEmpty(ARGUMENTS.value)>
|
||||
<cfreturn javacast('null','')/>
|
||||
<cfelse>
|
||||
<cfreturn ARGUMENTS.value />
|
||||
@@ -23,7 +22,7 @@
|
||||
<cffunction name="passThrough"
|
||||
returntype="any"
|
||||
output="false"
|
||||
hint="just return argument">
|
||||
hint="Возвращает аргумент без изменений">
|
||||
<cfargument name="x" type="ANY" required="true"/>
|
||||
<cfreturn #ARGUMENTS.x#/>
|
||||
</cffunction>
|
||||
@@ -33,7 +32,7 @@
|
||||
access="public"
|
||||
returntype="any"
|
||||
output="false"
|
||||
hint="put data into struct, replace empty fields with null">
|
||||
hint="Записывает данные в структуру, заменяя пустые поля на null">
|
||||
|
||||
<cfargument name="struct" type="struct" required="true" />
|
||||
<cfargument name="key" type="string" required="true" />
|
||||
@@ -65,9 +64,7 @@
|
||||
|
||||
<cfif queryColumnExists(ARGUMENTS.query, local.col)>
|
||||
<cfset obj[ARGUMENTS.fieldNameDecorator(local.col)]=empty2null(formatter(ARGUMENTS.query[local.col][ARGUMENTS.query.currentRow]))/>
|
||||
<!--- *** преобразование пустой строки в null преобразует в нулл также пустые списки, что искажает факт --->
|
||||
</cfif>
|
||||
<!---*** не уверен в надежности конструкции во всех реализациях CF, контекст query передастся ли--->
|
||||
</cfloop>
|
||||
|
||||
<cfreturn ARGUMENTS.struct/>
|
||||
@@ -78,13 +75,10 @@
|
||||
<cffunction name="parseFilterParams"
|
||||
returntype="array"
|
||||
output="true"
|
||||
hint="Parse and collect filter params from URL. For operators see filter_build">
|
||||
hint="Разбирает и собирает параметры фильтра из URL. Операторы задаются в filter_build">
|
||||
<cfargument name="params" type="struct" required="true" />
|
||||
<!--- используется альтернативный парсер query_string, чтобы не путать запятые в значении с разделителями списка list --->
|
||||
<!--- имена полей в URL в snake_case или camelCase, имена колонок в snake_case --->
|
||||
<!--- token example minSize=NEQ:2--->
|
||||
|
||||
<cfset var local = {}/><!--- *** --->
|
||||
<cfset var local = {}/>
|
||||
<cfset var out = []/>
|
||||
<cfset var urlParams = parseQs()/>
|
||||
|
||||
@@ -93,7 +87,7 @@
|
||||
<cfset var urlParamName=snake2camel(lCase(local.item))/>
|
||||
<cfif structKeyExists(urlParams,urlParamName)>
|
||||
|
||||
<cfloop array=#urlParams[urlParamName]# item="local.rawValue"><!--- when parameter occures in URL more than once, its values compose a comma delimited list --->
|
||||
<cfloop array=#urlParams[urlParamName]# item="local.rawValue"><!--- Если параметр встречается в URL несколько раз, его значения образуют список через запятую. --->
|
||||
<cfset var operator="EQ"/>
|
||||
<cfset var value=#local.rawValue#/>
|
||||
<cfif listLen(local.rawValue,":") GT 1>
|
||||
@@ -134,12 +128,10 @@
|
||||
<cffunction name="parseFilterParamsV1"
|
||||
returntype="array"
|
||||
output="true"
|
||||
hint="parse and collect filter params, for operators see filter_build">
|
||||
<!---e.g. duration=NEQ:2--->
|
||||
<!---*** add test for multiple values--->
|
||||
hint="Разбирает и собирает параметры фильтра; операторы задаются в filter_build">
|
||||
<cfargument name="params" type="struct" required="true" /><!--- имена полей и колонок в snake_case --->
|
||||
|
||||
<cfset var local = {}/><!--- *** --->
|
||||
<cfset var local = {}/>
|
||||
<cfset var out = []/>
|
||||
|
||||
<cfloop collection=#params# index="local.item">
|
||||
@@ -147,7 +139,7 @@
|
||||
<cfset var urlParamName=snake2camel(lCase(local.item))/>
|
||||
<cfif structKeyExists(URL,urlParamName)>
|
||||
|
||||
<cfloop list=#URL[urlParamName]# item="local.rawValue"><!--- when parameter occures in URL more than once, its values compose a comma delimited list --->
|
||||
<cfloop list=#URL[urlParamName]# item="local.rawValue"><!--- Если параметр встречается в URL несколько раз, его значения образуют список через запятую. --->
|
||||
<cfset var operator="EQ"/>
|
||||
<cfset var value=#local.rawValue#/>
|
||||
<cfif listLen(local.rawValue,":") GT 1>
|
||||
@@ -189,7 +181,7 @@
|
||||
<cffunction name="parseOrderBy"
|
||||
returntype="struct"
|
||||
output="false"
|
||||
hint="parse and collect sort order param"><!--- deprecated --->
|
||||
hint="Разбирает и собирает параметр сортировки"><!--- Устаревший метод --->
|
||||
|
||||
<cfargument name="params" type="struct" required="true" />
|
||||
<cfargument name="orderBy" type="string" required="true" />
|
||||
@@ -203,7 +195,7 @@
|
||||
<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--->
|
||||
<cfthrow type="InvalidParamValue" message="Invalid orderBy format" detail="orderBy suffix should be '.asc' or '.desc'" /><!--- Суффикс не возвращается пользователю, чтобы не расширять XSS-поверхность. --->
|
||||
</cfdefaultcase>
|
||||
</cfswitch>
|
||||
<cfelse>
|
||||
@@ -223,7 +215,7 @@
|
||||
<cffunction name="parseNumericOrder"
|
||||
returntype="array"
|
||||
output="false"
|
||||
hint="parse and collect sort order param with numeric notation">
|
||||
hint="Разбирает и собирает параметр сортировки в числовой нотации">
|
||||
|
||||
<cfargument name="fieldSet" type="struct" required="true" />
|
||||
<cfargument name="orderBy" type="string" required="true" />
|
||||
@@ -237,7 +229,7 @@
|
||||
<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--->
|
||||
<cfthrow type="InvalidParamValue" message="Invalid orderBy format" detail="orderBy suffix should be '.asc' or '.desc'" /><!--- Суффикс не возвращается пользователю, чтобы не расширять XSS-поверхность. --->
|
||||
</cfdefaultcase>
|
||||
</cfswitch>
|
||||
<cfelse>
|
||||
@@ -252,35 +244,9 @@
|
||||
</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">
|
||||
hint="Преобразует query в массив структур. Имена ключей приводятся к нижнему регистру, пустые поля считаются null">
|
||||
|
||||
<cfargument name="Query" type="query" required="true" />
|
||||
|
||||
@@ -308,7 +274,7 @@
|
||||
access="public"
|
||||
returntype="any"
|
||||
output="false"
|
||||
hint="convert snake style name to camel style name">
|
||||
hint="Преобразует имя в стиле snake_case в camelCase">
|
||||
<cfargument name="snake" type="string" required="true" />
|
||||
<cfreturn #reReplace(ARGUMENTS.snake,"_([a-z])","\u\1","ALL")#/>
|
||||
</cffunction>
|
||||
@@ -318,7 +284,7 @@
|
||||
access="public"
|
||||
returntype="any"
|
||||
output="false"
|
||||
hint="convert camel style name to snake style name">
|
||||
hint="Преобразует имя в стиле camelCase в snake_case">
|
||||
<cfargument name="snake" type="string" required="true" />
|
||||
<cfreturn #reReplace(ARGUMENTS.snake,"([A-Z])","_\l\1","ALL")#/>
|
||||
</cffunction>
|
||||
@@ -327,7 +293,7 @@
|
||||
access="public"
|
||||
returntype="any"
|
||||
output="true"
|
||||
hint="formats Exception for display">
|
||||
hint="Форматирует сообщение для вывода">
|
||||
|
||||
<cfargument name="message" type="string" required="true" />
|
||||
<cfargument name="title" type="string" required="false" />
|
||||
@@ -342,7 +308,7 @@
|
||||
access="public"
|
||||
returntype="any"
|
||||
output="true"
|
||||
hint="formats Exception for display">
|
||||
hint="Форматирует исключение для вывода">
|
||||
|
||||
<cfargument name="ex" type="struct" required="true" />
|
||||
<cfargument name="title" type="string" required="false" />
|
||||
@@ -357,7 +323,7 @@
|
||||
access="public"
|
||||
returntype="any"
|
||||
output="true"
|
||||
hint="formats Bad Request Exception">
|
||||
hint="Форматирует ошибку Bad Request">
|
||||
|
||||
<cfargument name="ex" type="struct" required="true" />
|
||||
|
||||
@@ -366,41 +332,6 @@
|
||||
<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="isValidX"
|
||||
access="private"
|
||||
returntype="boolean"
|
||||
@@ -420,7 +351,7 @@
|
||||
access="public"
|
||||
returntype="any"
|
||||
output="false"
|
||||
hint="validate field of a structure, if exisits">
|
||||
hint="Проверяет поле структуры, если оно существует">
|
||||
|
||||
<cfargument name="struct" type="struct" required=true/>
|
||||
<cfargument name="name" required=true/>
|
||||
|
||||
Reference in New Issue
Block a user