|
27 | 27 | #include <errno.h>
|
28 | 28 | #include <limits.h>
|
29 | 29 | #include <string.h>
|
| 30 | +#include <stdarg.h> |
30 | 31 |
|
31 | 32 | #include "ccronexpr.h"
|
32 | 33 |
|
@@ -883,7 +884,7 @@ static char* str_replace(char *orig, const char *rep, const char *with) {
|
883 | 884 | static unsigned int parse_uint(const char* str, int* errcode) {
|
884 | 885 | char* endptr;
|
885 | 886 | errno = 0;
|
886 |
| - long int l = strtol(str, &endptr, 0); |
| 887 | + long int l = strtol(str, &endptr, 10); |
887 | 888 | if (errno == ERANGE || *endptr != '\0' || l < 0 || l > INT_MAX) {
|
888 | 889 | *errcode = 1;
|
889 | 890 | return 0;
|
@@ -1017,7 +1018,9 @@ void cron_init_custom_hash_fn(cron_custom_hash_fn func)
|
1017 | 1018 | }
|
1018 | 1019 |
|
1019 | 1020 | /**
|
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 | + * |
1021 | 1024 | * @param field CRON field which needs a value for its 'H' (in string form)
|
1022 | 1025 | * @param n Position of the field in the CRON string, from 0 - 5
|
1023 | 1026 | * @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
|
1254 | 1257 |
|
1255 | 1258 | }
|
1256 | 1259 |
|
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 | + |
1259 | 1266 | unsigned int fieldMax = 0, customMax = 0;
|
1260 | 1267 | // minBuf is 0xFF to see if it has been altered/read successfully, since 0 is a valid value for it
|
1261 | 1268 | 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"; |
1278 | 1274 | return field;
|
1279 | 1275 | }
|
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 || \ |
1284 | 1290 | (minBuf == 0xFF) || \
|
1285 | 1291 | (minBuf > maxBuf) || \
|
1286 | 1292 | (minBuf < min) || \
|
1287 | 1293 | // 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; |
1320 | 1298 | }
|
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!"; |
1325 | 1325 | 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; |
1326 | 1392 | }
|
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; |
1328 | 1401 | }
|
1329 | 1402 | return field;
|
1330 | 1403 | }
|
@@ -1468,6 +1541,7 @@ static char* w_check(char* field, cron_expr* target, const char** error)
|
1468 | 1541 | goto return_error;
|
1469 | 1542 | }
|
1470 | 1543 | memset(newField, 0, sizeof(char) * strlen(field));
|
| 1544 | + char *tracking = newField; |
1471 | 1545 | // Ensure only 1 day is specified, and W day is not the last in a range or list or iterator of days
|
1472 | 1546 | if ( has_char(field, '/') || has_char(field, '-')) {
|
1473 | 1547 | *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)
|
1502 | 1576 | cron_setBit(target->w_flags, w_day);
|
1503 | 1577 | }
|
1504 | 1578 | } 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; |
1506 | 1587 | }
|
1507 | 1588 | }
|
1508 | 1589 | free_splitted(splitField, len_out);
|
|
0 commit comments