Skip to content

Commit 30179d0

Browse files
authored
More validation to ensure expression does not contain any unexpected characters (#331)
1 parent f387219 commit 30179d0

File tree

4 files changed

+43
-12
lines changed

4 files changed

+43
-12
lines changed

src/cronParser.ts

+20-9
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class CronParser {
2626
*/
2727
parse(): string[] {
2828
let parsed: string[];
29-
29+
3030
var expression = this.expression ?? '';
3131

3232
if (expression.startsWith('@')) {
@@ -316,12 +316,22 @@ export class CronParser {
316316
}
317317

318318
protected validate(parsed: string[]) {
319-
this.assertNoInvalidCharacters("DOW", parsed[5]);
320-
this.assertNoInvalidCharacters("DOM", parsed[3]);
321-
this.validateRange(parsed);
319+
const standardCronPartCharacters = "0-9,\\-*\/";
320+
this.validateOnlyExpectedCharactersFound(parsed[0], standardCronPartCharacters);
321+
this.validateOnlyExpectedCharactersFound(parsed[1], standardCronPartCharacters);
322+
this.validateOnlyExpectedCharactersFound(parsed[2], standardCronPartCharacters);
323+
// DOM
324+
this.validateOnlyExpectedCharactersFound(parsed[3], "0-9,\\-*\/LW");
325+
this.validateOnlyExpectedCharactersFound(parsed[4], standardCronPartCharacters);
326+
// DOW
327+
this.validateOnlyExpectedCharactersFound(parsed[5], "0-9,\\-*\/L#");
328+
this.validateOnlyExpectedCharactersFound(parsed[6], standardCronPartCharacters);
329+
330+
this.validateAnyRanges(parsed);
331+
322332
}
323333

324-
protected validateRange(parsed: string[]) {
334+
protected validateAnyRanges(parsed: string[]) {
325335
RangeValidator.secondRange(parsed[0]);
326336
RangeValidator.minuteRange(parsed[1]);
327337
RangeValidator.hourRange(parsed[2]);
@@ -330,11 +340,12 @@ export class CronParser {
330340
RangeValidator.dayOfWeekRange(parsed[5], this.dayOfWeekStartIndexZero);
331341
}
332342

333-
protected assertNoInvalidCharacters(partDescription: string, expression: string) {
334-
// No characters other than 'L' or 'W' should remain after normalization
335-
let invalidChars = expression.match(/[A-KM-VX-Z]+/gi);
343+
protected validateOnlyExpectedCharactersFound(cronPart: string, allowedCharsExpression: string) {
344+
// Write code that will ensure the expression string only contains numbers or any of the following characters , - * /
345+
// If any other characters are found, it is an error.
346+
let invalidChars = cronPart.match(new RegExp(`[^${allowedCharsExpression}]+`, "gi"));
336347
if (invalidChars && invalidChars.length) {
337-
throw new Error(`${partDescription} part contains invalid values: '${invalidChars.toString()}'`);
348+
throw new Error(`Expression contains invalid values: '${invalidChars.toString()}'`);
338349
}
339350
}
340351
}

src/expressionDescriptor.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,7 @@ export class ExpressionDescriptor {
653653
protected formatTime(hourExpression: string, minuteExpression: string, secondExpression: string) {
654654
let hourOffset: number = 0;
655655
let minuteOffset: number = 0;
656-
656+
657657
if(this.options.tzOffset) {
658658
hourOffset = this.options.tzOffset > 0 ? Math.floor(this.options.tzOffset) : Math.ceil(this.options.tzOffset);
659659

@@ -674,7 +674,7 @@ export class ExpressionDescriptor {
674674
minute += 60;
675675
hour -= 1;
676676
}
677-
677+
678678
if (hour >= 24) {
679679
hour = hour - 24;
680680
} else if (hour < 0) {

test/cronParser.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,21 @@ describe("CronParser", function () {
2525
it("should error if DOW part is not valid", function () {
2626
assert.throws(function () {
2727
new CronParser("* * * * MO").parse();
28-
}, `DOW part contains invalid values: 'MO'`);
28+
}, `Expression contains invalid values: 'MO'`);
29+
});
30+
31+
it("does not allow unexpected characters or statements in any part", function () {
32+
const maliciousStatement = "\nDROP\tDATABASE\tusers;";
33+
34+
for(let i = 0; i <= 6; i++) {
35+
const cleanCronParts = ["*", "*", "*", "*", "*", "*", "*"];
36+
cleanCronParts[i] = maliciousStatement;
37+
const cronToTest = cleanCronParts.join(" ");
38+
assert.throws(function () {
39+
new CronParser(cronToTest).parse();
40+
}, `Expression contains invalid values:`);
41+
}
42+
2943
});
3044

3145
it("should parse cron with multiple spaces between parts", function () {

test/cronstrue.ts

+6
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,12 @@ describe("Cronstrue", function () {
729729
}, "Error: Expression has only 1 part. At least 5 parts are required.");
730730
});
731731

732+
it("too many parts", function () {
733+
assert.throws(function () {
734+
cronstrue.toString("* * * * * * * *");
735+
}, "Expression has 8 parts; too many!");
736+
});
737+
732738
it("empty expression", function () {
733739
assert.throws(function () {
734740
cronstrue.toString("");

0 commit comments

Comments
 (0)