From bd238bfd7c443fd974f5b7edd6c1abc0349bfa78 Mon Sep 17 00:00:00 2001
From: Lars Henriksen <LarsHenriksen@get2net.dk>
Date: Thu, 7 May 2020 21:39:23 +0200
Subject: Fix recurrence rule expansion with ordered weekday

When the order of a weekday in BYDAY rule expansion (like -5SA or 5SU
for monthly or 55WE for yearly) exceeds the number of available weekdays
in the period (month or year), rule expansion with negative order could
result in a floating point exception. The reason: the modified frequency
might become zero.

Solution. Check order against number of available weekdays and terminate
expansion early whenever possible (also for positive orders).

Signed-off-by: Lars Henriksen <LarsHenriksen@get2net.dk>
Signed-off-by: Lukas Fleischer <lfleischer@calcurse.org>
---
 src/recur.c | 63 +++++++++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 43 insertions(+), 20 deletions(-)

(limited to 'src')

diff --git a/src/recur.c b/src/recur.c
index 997fb15..647ba11 100644
--- a/src/recur.c
+++ b/src/recur.c
@@ -1173,7 +1173,7 @@ static int expand_monthly(time_t start, long dur, struct rpt *rpt, llist_t *exc,
 		LLIST_FOREACH(&rpt->bywday, i) {
 			w = LLIST_GET_DATA(i);
 
-			int order, wday;
+			int order, wday, nbwd;
 
 			localtime_r(&start, &tm_start);
 			/*
@@ -1188,6 +1188,11 @@ static int expand_monthly(time_t start, long dur, struct rpt *rpt, llist_t *exc,
 				 */
 				order = *w / WEEKINDAYS;
 				wday = *w % WEEKINDAYS;
+				nbwd = wday_per_month(tm_day.tm_mon + 1,
+						      tm_day.tm_year + 1900,
+						      wday);
+				if (nbwd < order)
+					return 0;
 				r.freq = order;
 				tm_start.tm_mday = 1;
 				tm_start.tm_mon = tm_day.tm_mon;
@@ -1218,11 +1223,12 @@ static int expand_monthly(time_t start, long dur, struct rpt *rpt, llist_t *exc,
 				 */
 				order = -(*w) / WEEKINDAYS;
 				wday = -(*w) % WEEKINDAYS;
-				r.freq = wday_per_month(
-						tm_day.tm_mon + 1,
-						tm_day.tm_year + 1900,
-						wday
-					 ) - order + 1;
+				nbwd = wday_per_month(tm_day.tm_mon + 1,
+						      tm_day.tm_year + 1900,
+						      wday);
+				if (nbwd < order)
+					return 0;
+				r.freq = nbwd - order + 1;
 				tm_start.tm_mday = 1;
 				tm_start.tm_mon = tm_day.tm_mon;
 				tm_start.tm_year = tm_day.tm_year;
@@ -1259,7 +1265,7 @@ static int expand_yearly(time_t start, long dur, struct rpt *rpt, llist_t *exc,
 {
 	struct tm tm_start, tm_day;
 	llist_item_t *i, *j;
-	int *m, *w, mday, wday, order;
+	int *m, *w, mday, wday, order, nbwd;
 	time_t nstart;
 	struct rpt r;
 
@@ -1312,6 +1318,19 @@ static int expand_yearly(time_t start, long dur, struct rpt *rpt, llist_t *exc,
 				 */
 				order = *w / WEEKINDAYS;
 				wday = *w % WEEKINDAYS;
+				if (rpt->bymonth.head)
+					nbwd = wday_per_month(
+						tm_day.tm_mon + 1,
+						tm_day.tm_year + 1900,
+						wday
+					       );
+				else
+					nbwd = wday_per_year(
+						tm_day.tm_year + 1900,
+						wday
+					       );
+				if (nbwd < order)
+					return 0;
 				r.freq = order;
 				tm_start.tm_mday = 1;
 				if (rpt->bymonth.head)
@@ -1344,21 +1363,25 @@ static int expand_yearly(time_t start, long dur, struct rpt *rpt, llist_t *exc,
 				 */
 				order = -(*w) / WEEKINDAYS;
 				wday = -(*w) % WEEKINDAYS;
-				if (rpt->bymonth.head) {
-					r.freq = wday_per_month(
-							tm_day.tm_mon + 1,
-							tm_day.tm_year + 1900,
-							wday
-						 ) - order + 1;
+				if (rpt->bymonth.head)
+					nbwd = wday_per_month(
+						tm_day.tm_mon + 1,
+						tm_day.tm_year + 1900,
+						wday
+					       );
+				else
+					nbwd = wday_per_year(
+						tm_day.tm_year + 1900,
+						wday
+					       );
+				if (nbwd < order)
+					return 0;
+				r.freq = nbwd - order + 1;
+				tm_start.tm_mday = 1;
+				if (rpt->bymonth.head)
 					tm_start.tm_mon = tm_day.tm_mon;
-				} else {
-					r.freq = wday_per_year(
-							tm_day.tm_year + 1900,
-							wday
-						 ) - order + 1;
+				else
 					tm_start.tm_mon = 0;
-				}
-				tm_start.tm_mday = 1;
 				tm_start.tm_year = tm_day.tm_year;
 				tm_start.tm_isdst = -1;
 				nstart = date_sec_change(
-- 
cgit v1.2.3-70-g09d2