fix(cron): caught some more bugs
ci/woodpecker/pr/build Pipeline is pending Details
ci/woodpecker/pr/docker Pipeline is pending Details
ci/woodpecker/pr/docs Pipeline is pending Details
ci/woodpecker/pr/lint Pipeline is pending Details
ci/woodpecker/pr/man Pipeline is pending Details
ci/woodpecker/pr/test Pipeline is pending Details

Jef Roosens 2023-01-15 19:03:33 +01:00
parent 6d1b4aadb6
commit 4f093c08a7
Signed by: Jef Roosens
GPG Key ID: B75D4F293C7052DB
4 changed files with 38 additions and 14 deletions

View File

@ -11,7 +11,8 @@ typedef enum cron_parse_error {
cron_parse_invalid_expression = 1, cron_parse_invalid_expression = 1,
cron_parse_invalid_number = 2, cron_parse_invalid_number = 2,
cron_parse_out_of_range = 3, cron_parse_out_of_range = 3,
cron_parse_too_many_parts = 4 cron_parse_too_many_parts = 4,
cron_parse_not_enough_parts = 5
} cron_parse_error; } cron_parse_error;
typedef struct cron_expression { typedef struct cron_expression {

View File

@ -112,7 +112,7 @@ cron_parse_error ce_parse_part(uint64_t *out, char *s, uint8_t min,
*out = 0; *out = 0;
char *next; char *next;
enum cron_parse_error res; cron_parse_error res;
while ((next = strchr(s, ',')) != NULL) { while ((next = strchr(s, ',')) != NULL) {
next[0] = '\0'; next[0] = '\0';
@ -180,13 +180,12 @@ uint8_t bf_to_nums(uint8_t **out, uint64_t bf, uint8_t min, uint8_t max) {
/* /*
* Parse a cron expression string into a cron_expression struct. * Parse a cron expression string into a cron_expression struct.
*/ */
enum cron_parse_error ce_parse_expression(cron_expression *out, char *s) { cron_parse_error ce_parse_expression(cron_expression *out, char *s) {
// The parsing functions modify the input string in-place // The parsing functions modify the input string in-place
s = strdup(s); s = strdup(s);
char *orig_s = s; char *orig_s = s;
enum cron_parse_error res = cron_parse_ok; cron_parse_error res = cron_parse_ok;
uint64_t bfs[max_parts];
// First we divide the input string into its parts, divided by spaces. // First we divide the input string into its parts, divided by spaces.
// Each part is delimited by a NULL byte. // Each part is delimited by a NULL byte.
@ -207,23 +206,19 @@ enum cron_parse_error ce_parse_expression(cron_expression *out, char *s) {
next[0] = '\0'; next[0] = '\0';
parts[part_count] = s; parts[part_count] = s;
part_count++;
// Skip multiple spaces // Skip multiple spaces
offset = 1; offset = 1;
while (next[offset] == ' ') { while (next[offset] == ' ') {
offset++; offset++;
} }
s = next + offset; s = next + offset;
part_count++;
} }
// The loop exited because we already have 4 parts, yet there's still at // Each iteration of the loop skips all trailing spaces. This means that, if
// least one more part that follows. // s[0] isn't '\0', there's still another part before the end of the string.
if (next != NULL) { if (s[0] != '\0') {
res = cron_parse_too_many_parts;
goto end;
} else if (s[0] != '\0') {
// There's one more excessive trailing part
if (part_count == max_parts) { if (part_count == max_parts) {
res = cron_parse_too_many_parts; res = cron_parse_too_many_parts;
goto end; goto end;
@ -233,6 +228,11 @@ enum cron_parse_error ce_parse_expression(cron_expression *out, char *s) {
part_count++; part_count++;
} }
if (part_count < min_parts) {
res = cron_parse_not_enough_parts;
goto end;
}
// We now parse the parts in reverse. This is because the month part // We now parse the parts in reverse. This is because the month part
// determines the maximum value of the day part. // determines the maximum value of the day part.
@ -268,6 +268,8 @@ enum cron_parse_error ce_parse_expression(cron_expression *out, char *s) {
res = ce_parse_part(&bit_field, parts[2], min[2], max_day_value); res = ce_parse_part(&bit_field, parts[2], min[2], max_day_value);
if (res != cron_parse_ok) { if (res != cron_parse_ok) {
free(out->months);
goto end; goto end;
} }
@ -285,6 +287,9 @@ enum cron_parse_error ce_parse_expression(cron_expression *out, char *s) {
res = ce_parse_part(&bit_field, parts[1], min[1], max[1]); res = ce_parse_part(&bit_field, parts[1], min[1], max[1]);
if (res != cron_parse_ok) { if (res != cron_parse_ok) {
free(out->months);
free(out->days);
goto end; goto end;
} }
@ -296,6 +301,10 @@ enum cron_parse_error ce_parse_expression(cron_expression *out, char *s) {
res = ce_parse_part(&bit_field, parts[0], min[0], max[0]); res = ce_parse_part(&bit_field, parts[0], min[0], max[0]);
if (res != cron_parse_ok) { if (res != cron_parse_ok) {
free(out->months);
free(out->days);
free(out->hours);
goto end; goto end;
} }

View File

@ -73,6 +73,7 @@ enum ParseError as u8 {
invalid_number = 2 invalid_number = 2
out_of_range = 3 out_of_range = 3
too_many_parts = 4 too_many_parts = 4
not_enough_parts = 5
} }
// str returns the string representation of a ParseError. // str returns the string representation of a ParseError.
@ -83,6 +84,7 @@ fn (e ParseError) str() string {
.invalid_number { 'Invalid number' } .invalid_number { 'Invalid number' }
.out_of_range { 'Out of range' } .out_of_range { 'Out of range' }
.too_many_parts { 'Too many parts' } .too_many_parts { 'Too many parts' }
.not_enough_parts { 'Not enough parts' }
} }
} }

View File

@ -24,6 +24,14 @@ fn test_not_allowed() {
res = false res = false
parse_expression('0 /5') or { res = true } parse_expression('0 /5') or { res = true }
assert res assert res
res = false
parse_expression('0 ') or { res = true }
assert res
res = false
parse_expression('0') or { res = true }
assert res
} }
fn test_leading_star() { fn test_leading_star() {
@ -43,3 +51,7 @@ fn test_auto_extend() ! {
assert ce1 == ce2 && ce2 == ce3 assert ce1 == ce2 && ce2 == ce3
} }
fn test_four() {
parse_expression('0 1 2 3 ') or { assert false }
}