diff --git a/saltgui/static/scripts/ParseCommandLine.js b/saltgui/static/scripts/ParseCommandLine.js index 2cce801ce..2f7122340 100644 --- a/saltgui/static/scripts/ParseCommandLine.js +++ b/saltgui/static/scripts/ParseCommandLine.js @@ -224,7 +224,15 @@ export class ParseCommandLine { // jobIds look like numbers but must be strings return { value: pStr }; } else if (patInteger.test(pStr)) { - return { value: Number.parseInt(pStr, 10) }; + const value = Number.parseInt(pStr, 10); + // The number travels to salt as its own decimal rendering, so it arrives + // unchanged exactly when that rendering is the text that was typed. + // 12345678901234567 renders back as ...68, and sending a different number + // than the one that was typed is worse than refusing it here. + if (String(value) !== pStr.replace(/^\+/, "")) { + return { error: "Integer argument is too large to be sent exactly. Make it a string by surrounding it with quotes or make it a float by appending .0" }; + } + return { value }; } else if (patFloat.test(pStr)) { const value = Number.parseFloat(pStr); if (!Number.isFinite(value)) { diff --git a/tests/unit/ParseCommandLine.test.js b/tests/unit/ParseCommandLine.test.js index 52ff9eea2..38af2db35 100644 --- a/tests/unit/ParseCommandLine.test.js +++ b/tests/unit/ParseCommandLine.test.js @@ -242,32 +242,55 @@ describe("Unittests for ParseCommandLine.js", () => { assert.equal(args[0], 0); assert.equal(Object.keys(params).length, 0); - // an integer that almost looks like a jobid, but one digit less - args = []; - params = {}; - result = ParseCommandLine.parseCommandLine("2018082000341133831", args, params); - assert.isNull(result); - assert.equal(args.length, 1); - assert.equal(args[0], 2018082000341133831); - assert.equal(Object.keys(params).length, 0); - - // an integer that almost looks like a jobid, but one digit more - args = []; - params = {}; - result = ParseCommandLine.parseCommandLine("201808200034113383170", args, params); - assert.isNull(result); - assert.equal(args.length, 1); - assert.equal(args[0], 201808200034113383170); - assert.equal(Object.keys(params).length, 0); - - // an integer that almost looks like a jobid, just not a true date-time - args = []; - params = {}; - result = ParseCommandLine.parseCommandLine("20182820003411338317", args, params); - assert.isNull(result); - assert.equal(args.length, 1); - assert.equal(args[0], 20182820003411338317); - assert.equal(Object.keys(params).length, 0); + // Integers beyond 2**53-1 cannot be sent exactly, so they are refused + // rather than silently rounded. Expectations compare against strings on + // purpose: written as numeric literals they would be rounded by the test + // itself and could not tell a correct value from a wrong one. + const tooLargeIntegers = [ + // almost a jobid, but one digit less + "2018082000341133831", + // almost a jobid, but one digit more + "201808200034113383170", + // jobid-shaped, just not a true date-time + "20182820003411338317", + // the first integer that no longer renders back as itself + "9007199254740993", + // loses its last digit: it renders back as ...68 + "12345678901234567" + ]; + for (const nr of tooLargeIntegers) { + const label = "value " + nr; + args = []; + params = {}; + result = ParseCommandLine.parseCommandLine(nr, args, params); + assert.equal( + result, + "Integer argument is too large to be sent exactly. Make it a string by surrounding it with quotes or make it a float by appending .0", + label); + assert.equal(args.length, 0, label); + } + + // Anything whose decimal rendering is the text that was typed is accepted, + // which reaches past isSafeInteger: 2**53, 2**54 and 10**16 all render back + // unchanged. A real jobid is matched as a string before the integer branch, + // so it survives too. + const exactValues = [ + "9007199254740991", + "9007199254740992", + "18014398509481984", + "10000000000000000", + "20180814033130818988" + ]; + for (const nr of exactValues) { + const label = "value " + nr; + args = []; + params = {}; + result = ParseCommandLine.parseCommandLine(nr, args, params); + assert.isNull(result, label); + assert.equal(args.length, 1, label); + assert.equal(String(args[0]), nr, label); + assert.equal(Object.keys(params).length, 0, label); + } // FLOAT