Skip to content

Commit 41199d9

Browse files
authored
Merge pull request #3 from lobaro/feature/multiple-h
feat: Implement support for multiple 'H' ranges in one field
2 parents 8137c52 + 3ccbfcf commit 41199d9

File tree

2 files changed

+487
-368
lines changed

2 files changed

+487
-368
lines changed

Diff for: ccronexpr.c

+143-62
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include <errno.h>
2828
#include <limits.h>
2929
#include <string.h>
30+
#include <stdarg.h>
3031

3132
#include "ccronexpr.h"
3233

@@ -883,7 +884,7 @@ static char* str_replace(char *orig, const char *rep, const char *with) {
883884
static unsigned int parse_uint(const char* str, int* errcode) {
884885
char* endptr;
885886
errno = 0;
886-
long int l = strtol(str, &endptr, 0);
887+
long int l = strtol(str, &endptr, 10);
887888
if (errno == ERANGE || *endptr != '\0' || l < 0 || l > INT_MAX) {
888889
*errcode = 1;
889890
return 0;
@@ -1017,7 +1018,9 @@ void cron_init_custom_hash_fn(cron_custom_hash_fn func)
10171018
}
10181019

10191020
/**
1020-
* Replace H parameter with integer in proper range. If using an iterator fielo, min/max have to be set to proper values before!
1021+
* Replace H parameter with integer in proper range. If using an iterator field, min/max have to be set to proper values before!
1022+
* The input field will always be freed, the returned char* should be used instead.
1023+
*
10211024
* @param field CRON field which needs a value for its 'H' (in string form)
10221025
* @param n Position of the field in the CRON string, from 0 - 5
10231026
* @param min Minimum value allowed in field/for replacement
@@ -1254,77 +1257,147 @@ void set_number_hits(const char* value, uint8_t* target, unsigned int min, unsig
12541257

12551258
}
12561259

1257-
static char* check_and_replace_h(char* field, unsigned int pos, unsigned int min, const char** error)
1258-
{
1260+
static char *replace_h_entry(char *field, unsigned int pos, unsigned int min, const char **error) {
1261+
char* has_h = strchr(field, 'H');
1262+
if (has_h == NULL) {
1263+
return field;
1264+
}
1265+
12591266
unsigned int fieldMax = 0, customMax = 0;
12601267
// minBuf is 0xFF to see if it has been altered/read successfully, since 0 is a valid value for it
12611268
unsigned int minBuf = 0xFF, maxBuf = 0;
1262-
char* has_h = strchr(field, 'H');
1263-
if (has_h) {
1264-
if ( *(has_h+1) == '/') { /* H before an iterator */
1265-
sscanf(has_h, "H/%2u", &customMax); // get value of iterator, so it will be used as maximum instead of standard maximum for field
1266-
if (!customMax) { /* iterator might have been specified as an ordinal instead... */
1267-
*error = "Hashed: Iterator error";
1268-
return field;
1269-
}
1270-
}
1271-
if ( (has_h != field) && (*(has_h-1) == '/') ) { /* H not allowed as iterator */
1272-
*error = "Hashed: 'H' not allowed as iterator";
1273-
return field;
1274-
}
1275-
if ( *(has_h+1) =='-' || \
1276-
( has_h != field && *(has_h-1) == '-') ) { // 'H' not starting field, so may be the end of a range
1277-
*error = "'H' is not allowed for use in ranges";
1269+
1270+
if(*(has_h + 1) == '/') { /* H before an iterator */
1271+
sscanf(has_h, "H/%2u", &customMax); // get value of iterator, so it will be used as maximum instead of standard maximum for field
1272+
if (!customMax) { /* iterator might have been specified as an ordinal instead... */
1273+
*error = "Hashed: Iterator error";
12781274
return field;
12791275
}
1280-
// Test if custom Range is specified
1281-
if ( *(has_h+1) == '(' ) {
1282-
sscanf(has_h, "H(%2u-%2u)", &minBuf, &maxBuf);
1283-
if ( !maxBuf || \
1276+
}
1277+
if ((has_h != field) && (*(has_h - 1) == '/') ) { /* H not allowed as iterator */
1278+
*error = "Hashed: 'H' not allowed as iterator";
1279+
return field;
1280+
}
1281+
if (*(has_h + 1) == '-' || \
1282+
(has_h != field && *(has_h - 1) == '-') ) { // 'H' not starting field, so may be the end of a range
1283+
*error = "'H' is not allowed for use in ranges";
1284+
return field;
1285+
}
1286+
// Test if custom Range is specified
1287+
if (*(has_h + 1) == '(' ) {
1288+
sscanf(has_h, "H(%2u-%2u)", &minBuf, &maxBuf);
1289+
if ( !maxBuf || \
12841290
(minBuf == 0xFF) || \
12851291
(minBuf > maxBuf) || \
12861292
(minBuf < min) || \
12871293
// if a customMax is present: Is read maximum bigger than it? (which it shouldn't be)
1288-
(customMax ? maxBuf > customMax : 0)
1289-
) {
1290-
*error = "'H' custom range error";
1291-
return field;
1292-
}
1293-
min = minBuf;
1294-
// maxBuf needs to be incremented by 1 to include it
1295-
customMax = maxBuf + 1;
1296-
}
1297-
switch (pos) {
1298-
case CRON_FIELD_SECOND:
1299-
fieldMax = CRON_MAX_SECONDS;
1300-
break;
1301-
case CRON_FIELD_MINUTE:
1302-
fieldMax = CRON_MAX_MINUTES;
1303-
break;
1304-
case CRON_FIELD_HOUR:
1305-
fieldMax = CRON_MAX_HOURS;
1306-
break;
1307-
case CRON_FIELD_DAY_OF_MONTH:
1308-
// limited to 28th so the hashed cron will be executed every month
1309-
fieldMax = 28;
1310-
break;
1311-
case CRON_FIELD_MONTH:
1312-
fieldMax = CRON_MAX_MONTHS;
1313-
break;
1314-
case CRON_FIELD_DAY_OF_WEEK:
1315-
fieldMax = CRON_MAX_DAYS_OF_WEEK;
1316-
break;
1317-
default:
1318-
*error = "Unknown field!";
1319-
return field;
1294+
(customMax ? maxBuf > customMax : 0)
1295+
) {
1296+
*error = "'H' custom range error";
1297+
return field;
13201298
}
1321-
if (!customMax) {
1322-
customMax = fieldMax;
1323-
} else if (customMax > fieldMax) {
1324-
*error = "'H' range maximum error";
1299+
min = minBuf;
1300+
// maxBuf needs to be incremented by 1 to include it
1301+
customMax = maxBuf + 1;
1302+
}
1303+
switch (pos) {
1304+
case CRON_FIELD_SECOND:
1305+
fieldMax = CRON_MAX_SECONDS;
1306+
break;
1307+
case CRON_FIELD_MINUTE:
1308+
fieldMax = CRON_MAX_MINUTES;
1309+
break;
1310+
case CRON_FIELD_HOUR:
1311+
fieldMax = CRON_MAX_HOURS;
1312+
break;
1313+
case CRON_FIELD_DAY_OF_MONTH:
1314+
// limited to 28th so the hashed cron will be executed every month
1315+
fieldMax = 28;
1316+
break;
1317+
case CRON_FIELD_MONTH:
1318+
fieldMax = CRON_MAX_MONTHS;
1319+
break;
1320+
case CRON_FIELD_DAY_OF_WEEK:
1321+
fieldMax = CRON_MAX_DAYS_OF_WEEK;
1322+
break;
1323+
default:
1324+
*error = "Unknown field!";
13251325
return field;
1326+
}
1327+
if (!customMax) {
1328+
customMax = fieldMax;
1329+
} else if (customMax > fieldMax) {
1330+
*error = "'H' range maximum error";
1331+
return field;
1332+
}
1333+
field = replace_hashed(field, pos, min, customMax, fn, error);
1334+
1335+
return field;
1336+
}
1337+
1338+
static char* check_and_replace_h(char* field, unsigned int pos, unsigned int min, const char** error)
1339+
{
1340+
char* has_h = strchr(field, 'H');
1341+
if (has_h) {
1342+
char *accum_field = NULL;
1343+
char **subfields = NULL;
1344+
size_t subfields_len = 0;
1345+
// Check if Field contains ',', if so, split into multiple subfields, and replace in each (with same position no)
1346+
char *has_comma = strchr(field, ',');
1347+
if (has_comma) {
1348+
// Iterate over split sub-fields, check for 'H' and replace if present
1349+
subfields = split_str(field, ',', &subfields_len);
1350+
if (subfields == NULL) {
1351+
*error = "Failed to split 'H' string in list";
1352+
goto return_error;
1353+
}
1354+
size_t res_len = 0;
1355+
size_t res_lens[subfields_len];
1356+
for (size_t i = 0; i < subfields_len; i++) {
1357+
has_h = strchr(subfields[i], 'H');
1358+
if (has_h) {
1359+
subfields[i] = replace_h_entry(subfields[i], pos, min, error);
1360+
}
1361+
if (*error != NULL) {
1362+
goto return_error;
1363+
}
1364+
res_lens[i] = strnlen(subfields[i], CRON_MAX_STR_LEN_TO_SPLIT);
1365+
res_len += res_lens[i];
1366+
}
1367+
// Allocate space for the full string: Result lengths + (result count - 1) for the commas + 1 for '\0'
1368+
accum_field = (char *) cronMalloc(res_len + subfields_len );
1369+
if (accum_field == NULL) {
1370+
*error = "Failed to merge 'H' in list";
1371+
goto return_error;
1372+
}
1373+
memset(accum_field, 0, res_len + subfields_len);
1374+
char *tracking = accum_field;
1375+
for (size_t i = 0; i < subfields_len; i++) {
1376+
// Sanity check: Is "tracking" still in the allocated memory boundaries?
1377+
if ((tracking - accum_field) > (res_len + subfields_len)) {
1378+
*error = "Failed to insert subfields to merged fields: String went oob";
1379+
goto return_error;
1380+
}
1381+
strncpy(tracking, subfields[i], res_lens[i]);
1382+
tracking += res_lens[i];
1383+
// Don't append comma to last list entry
1384+
if (i < subfields_len-1) {
1385+
strncpy(tracking, ",", 2); // using 2 to ensure the string ends in '\0', tracking will be set to that char
1386+
tracking += 1;
1387+
}
1388+
}
1389+
free_splitted(subfields, subfields_len);
1390+
cronFree(field);
1391+
return accum_field;
13261392
}
1327-
field = replace_hashed(field, pos, min, customMax, fn, error);
1393+
// only one H to find and replace, then return
1394+
field = replace_h_entry(field, pos, min, error);
1395+
return field;
1396+
1397+
return_error:
1398+
if (subfields) free_splitted(subfields, subfields_len);
1399+
if (accum_field) cronFree(accum_field);
1400+
return field;
13281401
}
13291402
return field;
13301403
}
@@ -1468,6 +1541,7 @@ static char* w_check(char* field, cron_expr* target, const char** error)
14681541
goto return_error;
14691542
}
14701543
memset(newField, 0, sizeof(char) * strlen(field));
1544+
char *tracking = newField;
14711545
// Ensure only 1 day is specified, and W day is not the last in a range or list or iterator of days
14721546
if ( has_char(field, '/') || has_char(field, '-')) {
14731547
*error = "W not allowed in iterators or ranges in 'day of month' field";
@@ -1502,7 +1576,14 @@ static char* w_check(char* field, cron_expr* target, const char** error)
15021576
cron_setBit(target->w_flags, w_day);
15031577
}
15041578
} else {
1505-
strcat(newField, splitField[i]);
1579+
if (tracking != newField) {
1580+
// A field was already added. Add a comma first
1581+
strncpy(tracking, ",", 2); // ensure string ends in '\0', tracking will be set to it
1582+
tracking += 1;
1583+
}
1584+
size_t field_len = strnlen(splitField[i], CRON_MAX_STR_LEN_TO_SPLIT);
1585+
strncpy(tracking, splitField[i], field_len);
1586+
tracking += field_len;
15061587
}
15071588
}
15081589
free_splitted(splitField, len_out);

0 commit comments

Comments
 (0)