summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/calendar-sync.el43
1 files changed, 34 insertions, 9 deletions
diff --git a/modules/calendar-sync.el b/modules/calendar-sync.el
index fadad6c0..6753e5fe 100644
--- a/modules/calendar-sync.el
+++ b/modules/calendar-sync.el
@@ -125,6 +125,13 @@ Default: 3 months. This keeps recent history visible in org-agenda.")
"Number of months in the future to include when expanding recurring events.
Default: 12 months. This provides a full year of future events.")
+(defvar calendar-sync-fetch-timeout 120
+ "Maximum time in seconds for a calendar fetch to complete.
+This is the total time allowed for the entire transfer (connect + download).
+Large calendars (thousands of events) may need more time on slow connections.
+A separate 10-second connect timeout ensures fast failure when a host is
+unreachable.")
+
;;; Internal state
(defvar calendar-sync--timer nil
@@ -275,6 +282,17 @@ Returns nil for nil input. Returns empty string for whitespace-only input."
(when text
(string-trim (calendar-sync--strip-html (calendar-sync--unescape-ics-text text)))))
+(defun calendar-sync--sanitize-org-body (text)
+ "Sanitize TEXT for safe inclusion as org body content.
+Replaces leading asterisks with dashes to prevent lines from being
+parsed as org headings. Handles multiple levels (e.g. ** becomes --)."
+ (when text
+ (replace-regexp-in-string
+ "^\\(\\*+\\) "
+ (lambda (match)
+ (concat (make-string (length (match-string 1 match)) ?-) " "))
+ text)))
+
;;; Date Utilities
(defun calendar-sync--add-months (date months)
@@ -413,9 +431,12 @@ Each exception plist contains :recurrence-id (parsed), :start, :end, :summary, e
(end-tzid (calendar-sync--extract-tzid dtend-line))
(start-parsed (calendar-sync--parse-timestamp dtstart start-tzid))
(end-parsed (and dtend (calendar-sync--parse-timestamp dtend end-tzid)))
- (summary (calendar-sync--get-property event-str "SUMMARY"))
- (description (calendar-sync--get-property event-str "DESCRIPTION"))
- (location (calendar-sync--get-property event-str "LOCATION")))
+ (summary (calendar-sync--clean-text
+ (calendar-sync--get-property event-str "SUMMARY")))
+ (description (calendar-sync--clean-text
+ (calendar-sync--get-property event-str "DESCRIPTION")))
+ (location (calendar-sync--clean-text
+ (calendar-sync--get-property event-str "LOCATION"))))
(when (and recurrence-id-parsed start-parsed)
;; Convert RECURRENCE-ID to local time
;; Handle: UTC (Z suffix), TZID, or assume local
@@ -658,9 +679,10 @@ Returns nil if property not found."
(when (string-match (format "^%s[^:\n]*:\\(.*\\)$" (regexp-quote property)) event)
(let ((value (match-string 1 event))
(start (match-end 0)))
- ;; Handle continuation lines (start with space or tab)
+ ;; Handle continuation lines (RFC 5545 ยง3.1: folded lines start with space or tab)
(while (and (< start (length event))
- (string-match "^\n[ \t]\\(.*\\)$" event start))
+ (string-match "\n[ \t]\\([^\n]*\\)" event start)
+ (= (match-beginning 0) start))
(setq value (concat value (match-string 1 event)))
(setq start (match-end 0)))
value)))
@@ -1164,16 +1186,16 @@ Description appears as body text after the drawer."
(push (format ":URL: %s" url) props))
(setq props (nreverse props))
;; Build output
- (let ((parts (list (format "* %s" summary) timestamp)))
+ (let ((parts (list timestamp (format "* %s" summary))))
;; Add property drawer if any properties exist
(when props
(push ":PROPERTIES:" parts)
(dolist (prop props)
(push prop parts))
(push ":END:" parts))
- ;; Add description as body text
+ ;; Add description as body text (sanitized to prevent org heading conflicts)
(when (and description (not (string-empty-p description)))
- (push description parts))
+ (push (calendar-sync--sanitize-org-body description) parts))
(string-join (nreverse parts) "\n"))))
(defun calendar-sync--event-start-time (event)
@@ -1254,7 +1276,10 @@ invoked when the fetch completes, either successfully or with an error."
(make-process
:name "calendar-sync-curl"
:buffer buffer
- :command (list "curl" "-s" "-L" "-m" "30" url)
+ :command (list "curl" "-s" "-L"
+ "--connect-timeout" "10"
+ "--max-time" (number-to-string calendar-sync-fetch-timeout)
+ url)
:sentinel
(lambda (process event)
(when (memq (process-status process) '(exit signal))