Skip to content

Commit 29a1b2f

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 <[email protected]>
1 parent 7525da3 commit 29a1b2f

File tree

2 files changed

+168
-6
lines changed

2 files changed

+168
-6
lines changed

packages/core/src/integer.ts

+46-6
Original file line numberDiff line numberDiff line change
@@ -832,10 +832,12 @@ class Integer {
832832
* @access private
833833
* @param {string} str The textual representation of the Integer
834834
* @param {number=} radix The radix in which the text is written (2-36), defaults to 10
835+
* @param {Object} [opts={}] Configuration options
836+
* @param {boolean} [opts.strictStringValidation=false] Enable strict validation generated Integer.
835837
* @returns {!Integer} The corresponding Integer value
836838
* @expose
837839
*/
838-
static fromString(str: string, radix?: number): Integer {
840+
static fromString (str: string, radix?: number, { strictStringValidation }: { strictStringValidation?: boolean} = {}): Integer {
839841
if (str.length === 0) {
840842
throw newError('number format error: empty string')
841843
}
@@ -864,9 +866,15 @@ class Integer {
864866
const radixToPower = Integer.fromNumber(Math.pow(radix, 8))
865867

866868
let result = Integer.ZERO
867-
for (var i = 0; i < str.length; i += 8) {
868-
var size = Math.min(8, str.length - i)
869-
var value = parseInt(str.substring(i, i + size), radix)
869+
for (let i = 0; i < str.length; i += 8) {
870+
const size = Math.min(8, str.length - i)
871+
const valueString = str.substring(i, i + size)
872+
const value = parseInt(valueString, radix)
873+
874+
if (strictStringValidation === true && !_isValidNumberFromString(valueString, value, radix)) {
875+
throw newError(`number format error: "${valueString}" is NaN in radix ${radix}: ${str}`)
876+
}
877+
870878
if (size < 8) {
871879
var power = Integer.fromNumber(Math.pow(radix, size))
872880
result = result.multiply(power).add(Integer.fromNumber(value))
@@ -882,18 +890,20 @@ class Integer {
882890
* Converts the specified value to a Integer.
883891
* @access private
884892
* @param {!Integer|number|string|bigint|!{low: number, high: number}} val Value
893+
* @param {Object} [opts={}] Configuration options
894+
* @param {boolean} [opts.strictStringValidation=false] Enable strict validation generated Integer.
885895
* @returns {!Integer}
886896
* @expose
887897
*/
888-
static fromValue(val: Integerable): Integer {
898+
static fromValue (val: Integerable, opts: { strictStringValidation?: boolean} = {}): Integer {
889899
if (val /* is compatible */ instanceof Integer) {
890900
return val
891901
}
892902
if (typeof val === 'number') {
893903
return Integer.fromNumber(val)
894904
}
895905
if (typeof val === 'string') {
896-
return Integer.fromString(val)
906+
return Integer.fromString(val, undefined, opts)
897907
}
898908
if (typeof val === 'bigint') {
899909
return Integer.fromString(val.toString())
@@ -945,6 +955,34 @@ class Integer {
945955
}
946956
}
947957

958+
/**
959+
* @private
960+
* @param num
961+
* @param radix
962+
* @param minSize
963+
* @returns {string}
964+
*/
965+
function _convertNumberToString (num: number, radix: number, minSize: number): string {
966+
const theNumberString = num.toString(radix)
967+
const paddingLength = Math.max(minSize - theNumberString.length, 0)
968+
const padding = '0'.repeat(paddingLength)
969+
return `${padding}${theNumberString}`
970+
}
971+
972+
/**
973+
*
974+
* @private
975+
* @param theString
976+
* @param theNumber
977+
* @param radix
978+
* @return {boolean} True if valid
979+
*/
980+
function _isValidNumberFromString (theString: string, theNumber: number, radix: number): boolean {
981+
return !Number.isNaN(theString) &&
982+
!Number.isNaN(theNumber) &&
983+
_convertNumberToString(theNumber, radix, theString.length) === theString.toLowerCase()
984+
}
985+
948986
type Integerable =
949987
| number
950988
| string
@@ -1010,6 +1048,8 @@ var TWO_PWR_24 = Integer.fromInt(TWO_PWR_24_DBL)
10101048
* Cast value to Integer type.
10111049
* @access public
10121050
* @param {Mixed} value - The value to use.
1051+
* @param {Object} [opts={}] Configuration options
1052+
* @param {boolean} [opts.strictStringValidation=false] Enable strict validation generated Integer.
10131053
* @return {Integer} - An object of type Integer.
10141054
*/
10151055
const int = Integer.fromValue

packages/core/test/integer.test.ts

+122
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,26 @@ describe('Integer', () => {
255255
newError('number format error: interior "-" character: 123-2')
256256
))
257257

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

288+
test('int("7891a", { strictStringValidation: true }) toThrow invalid character', () =>
289+
expect(() => int('7891a', { strictStringValidation: true })).toThrow(
290+
newError('number format error: "7891a" is NaN in radix 10: 7891a')
291+
))
292+
293+
test('int("78a91", { strictStringValidation: true }) toThrow invalid character', () =>
294+
expect(() => int('78a91', { strictStringValidation: true })).toThrow(
295+
newError('number format error: "78a91" is NaN in radix 10: 78a91')
296+
))
297+
298+
test('int("a7891", { strictStringValidation: true }) toThrow invalid character', () =>
299+
expect(() => int('a7891', { strictStringValidation: true })).toThrow(
300+
newError('number format error: "a7891" is NaN in radix 10: a7891')
301+
))
302+
303+
test('int("7891123456789876a", { strictStringValidation: true }) toThrow invalid character', () =>
304+
expect(() => int('7891123456789876a', { strictStringValidation: true })).toThrow(
305+
newError('number format error: "a" is NaN in radix 10: 7891123456789876a')
306+
))
307+
308+
test('int("7891123456789876a") not toThrow invalid character', () =>
309+
expect(() => int('7891123456789876a')).not.toThrow())
310+
311+
test.each(malformedNumbers())('int("%s", { strictStringValidation: true }) toThrow invalid character', (theNumberString) =>
312+
expect(() => int(theNumberString, { strictStringValidation: true })).toThrow())
313+
314+
test.each(wellFormedNumbersAndRadix())('Integer.fromString("%s", %n, { strictStringValidation: true }) not toThrown', (theNumberString, radix) =>
315+
expect(() => Integer.fromString(theNumberString, radix, { strictStringValidation: true })).not.toThrow())
316+
268317
forEachStaticToNumberScenarios(({ input, expectedOutput }) =>
269318
test(`Integer.toNumber(${input}) toEqual ${expectedOutput}`, () =>
270319
expect(Integer.toNumber(input)).toEqual(expectedOutput))
@@ -1045,6 +1094,79 @@ function forEachStaticInSafeRangeScenarios(
10451094
].forEach(func)
10461095
}
10471096

1097+
function malformedNumbers (): string[] {
1098+
return [
1099+
'7a',
1100+
'7891123a',
1101+
'78911234a',
1102+
'789112345a',
1103+
'7891123456a',
1104+
'7891123456789876a',
1105+
'78911234567898765a',
1106+
'789112345678987654a',
1107+
'78911234567898765a2',
1108+
'7891123456789876a25',
1109+
'789112345678987a256',
1110+
'78911234567898a2567',
1111+
'7891123456789a25678',
1112+
'789112345678a256789',
1113+
'78911234567a2567898',
1114+
'7891123456a25678987',
1115+
'789112345a256789876',
1116+
'78911234a2567898765',
1117+
'7891123a25678987654',
1118+
'7891123ab2567898765',
1119+
'78911234ab256789876',
1120+
'789112345ab25678987',
1121+
'7891123456ab2567898',
1122+
'78911234567ab256789',
1123+
'78911234567abc25678',
1124+
'78911234567abcd2567',
1125+
'78911234567abcde256',
1126+
'78911234567abcdef25',
1127+
'78911234567abcdefg2',
1128+
'7891123456abcdefgh1',
1129+
'789112345abcdefgh12',
1130+
'78911234abcdefgh123',
1131+
'7891123abcdefgh1234',
1132+
'789112abcdefghij123',
1133+
'7kkkkabcdefghijklmn',
1134+
'7kkkkabcdefg12345mn',
1135+
'7kkkkabcdefg123456n',
1136+
'7kkkkab22efg123456n',
1137+
'7kkkkab22efg12345mn',
1138+
'7kkkkab223fg12345mn',
1139+
'kkkkk11223fg12345mn',
1140+
'kkkkk11223fg123456n',
1141+
'kkkkk11223fg1234567',
1142+
'kkkkk11223451234567',
1143+
'kkk111gkk3451234567',
1144+
'kkk111gkkkk51234567',
1145+
'kkk111gkkkkk123kk67',
1146+
'kkkk234',
1147+
'kkkk2345',
1148+
'kkkk23456',
1149+
'kkkk234567',
1150+
'kkkk2345679kk',
1151+
'kkkk2345679kkkkkk',
1152+
'kkk234567',
1153+
'kkk2345679',
1154+
'kk2345679',
1155+
'kkkkkkkkkkkkkkkkkkk',
1156+
]
1157+
}
1158+
1159+
function wellFormedNumbersAndRadix (): [string, number][] {
1160+
return [
1161+
['01', 2],
1162+
['012', 3],
1163+
['0123', 4],
1164+
['0123456789', 10],
1165+
['0123456789ab', 12],
1166+
['0123456789abcde', 16],
1167+
]
1168+
}
1169+
10481170
interface AssertionPair<I, O> {
10491171
input: I
10501172
expectedOutput: O

0 commit comments

Comments
 (0)