@forcecalendar/core 2.1.22 → 2.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -256,8 +256,11 @@ export class DateUtils {
|
|
|
256
256
|
* @returns {number}
|
|
257
257
|
*/
|
|
258
258
|
static differenceInDays(date1, date2) {
|
|
259
|
-
|
|
260
|
-
|
|
259
|
+
// Normalize to midnight to avoid DST-related hour differences
|
|
260
|
+
const d1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate());
|
|
261
|
+
const d2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
|
|
262
|
+
const diff = d1.getTime() - d2.getTime();
|
|
263
|
+
return Math.round(diff / (1000 * 60 * 60 * 24));
|
|
261
264
|
}
|
|
262
265
|
|
|
263
266
|
/**
|
package/core/events/Event.js
CHANGED
|
@@ -47,8 +47,12 @@ export class Event {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// Normalize string fields with size limits
|
|
50
|
-
normalized.id = String(normalized.id || '')
|
|
51
|
-
|
|
50
|
+
normalized.id = String(normalized.id || '')
|
|
51
|
+
.trim()
|
|
52
|
+
.slice(0, Event.FIELD_LIMITS.id);
|
|
53
|
+
normalized.title = String(normalized.title || '')
|
|
54
|
+
.trim()
|
|
55
|
+
.slice(0, Event.FIELD_LIMITS.title);
|
|
52
56
|
normalized.description = String(normalized.description || '')
|
|
53
57
|
.trim()
|
|
54
58
|
.slice(0, Event.FIELD_LIMITS.description);
|
|
@@ -241,8 +241,14 @@ export class EventStore {
|
|
|
241
241
|
for (let offset = -1; offset <= 1; offset++) {
|
|
242
242
|
let m = filters.month + offset;
|
|
243
243
|
let y = filters.year;
|
|
244
|
-
if (m < 1) {
|
|
245
|
-
|
|
244
|
+
if (m < 1) {
|
|
245
|
+
m = 12;
|
|
246
|
+
y--;
|
|
247
|
+
}
|
|
248
|
+
if (m > 12) {
|
|
249
|
+
m = 1;
|
|
250
|
+
y++;
|
|
251
|
+
}
|
|
246
252
|
const key = `${y}-${String(m).padStart(2, '0')}`;
|
|
247
253
|
const ids = this.indices.byMonth.get(key);
|
|
248
254
|
if (ids) {
|
|
@@ -639,6 +645,8 @@ export class EventStore {
|
|
|
639
645
|
this.indices.byDate.clear();
|
|
640
646
|
this.indices.byMonth.clear();
|
|
641
647
|
this.indices.recurring.clear();
|
|
648
|
+
this.indices.byCategory.clear();
|
|
649
|
+
this.indices.byStatus.clear();
|
|
642
650
|
|
|
643
651
|
this._notifyChange({
|
|
644
652
|
type: 'clear',
|
|
@@ -64,9 +64,7 @@ export class RRuleParser {
|
|
|
64
64
|
|
|
65
65
|
case 'BYWEEKNO':
|
|
66
66
|
// RFC 5545: valid range is 1-53 or -53 to -1 (no zero)
|
|
67
|
-
rule.byWeekNo = this.parseIntList(value).filter(
|
|
68
|
-
v => v !== 0 && v >= -53 && v <= 53
|
|
69
|
-
);
|
|
67
|
+
rule.byWeekNo = this.parseIntList(value).filter(v => v !== 0 && v >= -53 && v <= 53);
|
|
70
68
|
break;
|
|
71
69
|
|
|
72
70
|
case 'BYMONTH':
|
package/core/ics/ICSHandler.js
CHANGED
|
@@ -228,7 +228,9 @@ export class ICSHandler {
|
|
|
228
228
|
|
|
229
229
|
// Only allow http and https schemes
|
|
230
230
|
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
231
|
-
throw new Error(
|
|
231
|
+
throw new Error(
|
|
232
|
+
`URL scheme "${parsed.protocol}" is not allowed. Only http and https are permitted`
|
|
233
|
+
);
|
|
232
234
|
}
|
|
233
235
|
|
|
234
236
|
const hostname = parsed.hostname.toLowerCase();
|
package/core/ics/ICSParser.js
CHANGED
|
@@ -10,7 +10,10 @@ export class ICSParser {
|
|
|
10
10
|
static MAX_LINES = 100000; // 100k lines
|
|
11
11
|
static MAX_EVENTS = 10000; // 10k events
|
|
12
12
|
|
|
13
|
-
constructor() {
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
// Configurable max file size (defaults to static limit)
|
|
15
|
+
this.maxFileSize = options.maxFileSize || ICSParser.MAX_INPUT_SIZE;
|
|
16
|
+
|
|
14
17
|
// ICS line folding max width
|
|
15
18
|
this.maxLineLength = 75;
|
|
16
19
|
|
|
@@ -38,11 +41,12 @@ export class ICSParser {
|
|
|
38
41
|
* @returns {Array} Array of event objects
|
|
39
42
|
*/
|
|
40
43
|
parse(icsString) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
if (typeof icsString !== 'string') {
|
|
45
|
+
throw new Error('ICS input must be a string');
|
|
46
|
+
}
|
|
47
|
+
// Enforce input size limit (uses instance config, falling back to static default)
|
|
48
|
+
if (icsString.length > this.maxFileSize) {
|
|
49
|
+
throw new Error(`ICS input exceeds maximum size of ${this.maxFileSize / (1024 * 1024)}MB`);
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
const events = [];
|
|
@@ -306,6 +310,13 @@ export class ICSParser {
|
|
|
306
310
|
event.recurrenceRule = value;
|
|
307
311
|
event.recurring = true;
|
|
308
312
|
break;
|
|
313
|
+
|
|
314
|
+
case 'EXDATE': {
|
|
315
|
+
if (!event.excludeDates) event.excludeDates = [];
|
|
316
|
+
const dates = value.split(',').map(d => this.parseDate(d.trim(), property));
|
|
317
|
+
event.excludeDates.push(...dates.filter(d => d !== null));
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
309
320
|
}
|
|
310
321
|
}
|
|
311
322
|
|