Skip to content

Commit edde4fc

Browse files
Add stricter string validation to neo4j.int (#985)
`neo4j.int` could have some surprising result when used with string. For avoiding this problem, a configuration option called `strictStringValidation` was added. When enable, `strictStringValidation` will trigger a deeper validation of the string. This option could slow down the conversion. Co-authored-by: Oskar Damkjaer <OskarDamkjaer@users.noreply.github.com>
1 parent 3a02a87 commit edde4fc

File tree

2 files changed

+73
-4
lines changed

2 files changed

+73
-4
lines changed

packages/core/src/integer.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -833,10 +833,12 @@ class Integer {
833833
* @access private
834834
* @param {string} str The textual representation of the Integer
835835
* @param {number=} radix The radix in which the text is written (2-36), defaults to 10
836+
* @param {Object} [opts={}] Configuration options
837+
* @param {boolean} [opts.strictStringValidation=false] Enable strict validation generated Integer.
836838
* @returns {!Integer} The corresponding Integer value
837839
* @expose
838840
*/
839-
static fromString (str: string, radix?: number): Integer {
841+
static fromString (str: string, radix?: number, { strictStringValidation }: { strictStringValidation?: boolean} = {}): Integer {
840842
if (str.length === 0) {
841843
throw newError('number format error: empty string')
842844
}
@@ -867,7 +869,13 @@ class Integer {
867869
let result = Integer.ZERO
868870
for (let i = 0; i < str.length; i += 8) {
869871
const size = Math.min(8, str.length - i)
870-
const value = parseInt(str.substring(i, i + size), radix)
872+
const valueString = str.substring(i, i + size)
873+
const value = parseInt(valueString, radix)
874+
875+
if (strictStringValidation === true && !_isValidNumberFromString(valueString, value, radix)) {
876+
throw newError(`number format error: "${valueString}" is NaN in radix ${radix}: ${str}`)
877+
}
878+
871879
if (size < 8) {
872880
const power = Integer.fromNumber(Math.pow(radix, size))
873881
result = result.multiply(power).add(Integer.fromNumber(value))
@@ -883,18 +891,20 @@ class Integer {
883891
* Converts the specified value to a Integer.
884892
* @access private
885893
* @param {!Integer|number|string|bigint|!{low: number, high: number}} val Value
894+
* @param {Object} [opts={}] Configuration options
895+
* @param {boolean} [opts.strictStringValidation=false] Enable strict validation generated Integer.
886896
* @returns {!Integer}
887897
* @expose
888898
*/
889-
static fromValue (val: Integerable): Integer {
899+
static fromValue (val: Integerable, opts: { strictStringValidation?: boolean} = {}): Integer {
890900
if (val /* is compatible */ instanceof Integer) {
891901
return val
892902
}
893903
if (typeof val === 'number') {
894904
return Integer.fromNumber(val)
895905
}
896906
if (typeof val === 'string') {
897-
return Integer.fromString(val)
907+
return Integer.fromString(val, undefined, opts)
898908
}
899909
if (typeof val === 'bigint') {
900910
return Integer.fromString(val.toString())
@@ -946,6 +956,20 @@ class Integer {
946956
}
947957
}
948958

959+
/**
960+
*
961+
* @private
962+
* @param theString
963+
* @param theNumber
964+
* @param radix
965+
* @return {boolean} True if valid
966+
*/
967+
function _isValidNumberFromString (theString: string, theNumber: number, radix: number): boolean {
968+
return !Number.isNaN(theString) &&
969+
!Number.isNaN(theNumber) &&
970+
theNumber.toString(radix).toLocaleLowerCase() === theString.toLocaleLowerCase()
971+
}
972+
949973
type Integerable =
950974
| number
951975
| string
@@ -1011,6 +1035,8 @@ const TWO_PWR_24 = Integer.fromInt(TWO_PWR_24_DBL)
10111035
* Cast value to Integer type.
10121036
* @access public
10131037
* @param {Mixed} value - The value to use.
1038+
* @param {Object} [opts={}] Configuration options
1039+
* @param {boolean} [opts.strictStringValidation=false] Enable strict validation generated Integer.
10141040
* @return {Integer} - An object of type Integer.
10151041
*/
10161042
const int = Integer.fromValue

packages/core/test/integer.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,26 @@ describe('Integer', () => {
256256
newError('number format error: interior "-" character: 123-2')
257257
))
258258

259+
test('Integer.fromString("7891a", undefined, { strictStringValidation: true }) toThrow invalid character', () =>
260+
expect(() => Integer.fromString('7891a', undefined, { strictStringValidation: true })).toThrow(
261+
newError('number format error: "7891a" is NaN in radix 10: 7891a')
262+
))
263+
264+
test('Integer.fromString("78a91", undefined, { strictStringValidation: true }) toThrow invalid character', () =>
265+
expect(() => Integer.fromString('78a91', undefined, { strictStringValidation: true })).toThrow(
266+
newError('number format error: "78a91" is NaN in radix 10: 78a91')
267+
))
268+
269+
test('Integer.fromString("a7891", undefined, { strictStringValidation: true }) toThrow invalid character', () =>
270+
expect(() => Integer.fromString('a7891', undefined, { strictStringValidation: true })).toThrow(
271+
newError('number format error: "a7891" is NaN in radix 10: a7891')
272+
))
273+
274+
test('Integer.fromString("7010", 2, { strictStringValidation: true }) toThrow invalid character', () =>
275+
expect(() => Integer.fromString('7010', 2, { strictStringValidation: true })).toThrow(
276+
newError('number format error: "7010" is NaN in radix 2: 7010')
277+
))
278+
259279
forEachFromValueScenarios(({ input, expectedOutput }) =>
260280
test(`Integer.fromValue(${mayIntegerToString(input)}) toEqual ${expectedOutput}`, () =>
261281
expect(Integer.fromValue(input)).toEqual(expectedOutput))
@@ -266,6 +286,29 @@ describe('Integer', () => {
266286
expect(int(input)).toEqual(expectedOutput))
267287
)
268288

289+
test('int("7891a", { strictStringValidation: true }) toThrow invalid character', () =>
290+
expect(() => int('7891a', { strictStringValidation: true })).toThrow(
291+
newError('number format error: "7891a" is NaN in radix 10: 7891a')
292+
))
293+
294+
test('int("78a91", { strictStringValidation: true }) toThrow invalid character', () =>
295+
expect(() => int('78a91', { strictStringValidation: true })).toThrow(
296+
newError('number format error: "78a91" is NaN in radix 10: 78a91')
297+
))
298+
299+
test('int("a7891", { strictStringValidation: true }) toThrow invalid character', () =>
300+
expect(() => int('a7891', { strictStringValidation: true })).toThrow(
301+
newError('number format error: "a7891" is NaN in radix 10: a7891')
302+
))
303+
304+
test('int("7891123456789876a", { strictStringValidation: true }) toThrow invalid character', () =>
305+
expect(() => int('7891123456789876a', { strictStringValidation: true })).toThrow(
306+
newError('number format error: "a" is NaN in radix 10: 7891123456789876a')
307+
))
308+
309+
test('int("7891123456789876a") not toThrow invalid character', () =>
310+
expect(() => int('7891123456789876a')).not.toThrow())
311+
269312
forEachStaticToNumberScenarios(({ input, expectedOutput }) =>
270313
test(`Integer.toNumber(${mayIntegerToString(input)}) toEqual ${expectedOutput}`, () =>
271314
expect(Integer.toNumber(input)).toEqual(expectedOutput))

0 commit comments

Comments
 (0)