diff options
Diffstat (limited to 'modules')
| -rw-r--r-- | modules/calendar-sync.el | 43 |
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)) |
