cleanup: remove reasoning comments and translate helper docs

This commit is contained in:
Naeel
2026-04-29 20:51:44 +04:00
parent f6d5c868cb
commit 7b98eee156
6 changed files with 145 additions and 161 deletions
+26 -61
View File
@@ -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
*/
+3 -5
View File
@@ -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)/>
+2 -2
View File
@@ -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
View File
@@ -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/>