From 1913cab8cadb249d80c9713d882a5a93a2937111 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Thu, 4 Jul 2013 17:07:58 +0200 Subject: [PATCH] Import and export embedded attachments in itip messages (#1988) --- plugins/calendar/calendar.php | 2 +- plugins/calendar/lib/calendar_ical.php | 47 ++++++++++++++++++++++++-- plugins/calendar/lib/calendar_itip.php | 2 +- 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index c8cb053a..70d09bdf 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -1035,7 +1035,7 @@ class calendar extends rcube_plugin header("Content-Type: text/calendar"); header("Content-Disposition: inline; filename=".$calname.'.ics'); - $this->get_ical()->export($events, '', true); + $this->get_ical()->export($events, '', true, array($this->driver, 'get_attachment_body')); if ($terminate) exit; diff --git a/plugins/calendar/lib/calendar_ical.php b/plugins/calendar/lib/calendar_ical.php index d7eac67e..66557fd6 100644 --- a/plugins/calendar/lib/calendar_ical.php +++ b/plugins/calendar/lib/calendar_ical.php @@ -99,6 +99,8 @@ class calendar_ical while (($line = fgets($fp, 2048)) !== false) { $buffer .= $line; if (preg_match('/END:VEVENT/i', $line)) { + if (preg_match('/BEGIN:VCALENDAR/i', $buffer)) + $buffer .= self::EOL ."END:VCALENDAR"; $parser->parsevCalendar($buffer, 'VCALENDAR', RCMAIL_CHARSET, false); $buffer = ''; } @@ -260,6 +262,26 @@ class calendar_ical $event['free_busy'] = strtolower($attr['value']); break; + case 'ATTACH': + // decode inline attachment + if (strtoupper($attr['params']['VALUE']) == 'BINARY' && !empty($attr['value'])) { + $data = !strcasecmp($attr['params']['ENCODING'], 'BASE64') ? base64_decode($attr['value']) : $attr['value']; + $mimetype = $attr['params']['FMTTYPE'] ? $attr['params']['FMTTYPE'] : rcube_mime::file_content_type($data, $attr['params']['X-LABEL'], 'application/octet-stream', true); + $extensions = rcube_mime::get_mime_extensions($mimetype); + $filename = $attr['params']['X-LABEL'] ? $attr['params']['X-LABEL'] : 'attachment' . count($event['attachments']) . '.' . $extensions[0]; + $event['attachments'][] = array( + 'mimetype' => $mimetype, + 'name' => $filename, + 'data' => $data, + 'size' => strlen($data), + ); + } + else if (!empty($attr['value']) && preg_match('!^[hftps]+://!', $attr['value'])) { + // TODO: add support for displaying/managing link attachments in UI + $event['links'][] = $attr['value']; + } + break; + default: if (substr($attr['name'], 0, 2) == 'X-') $event['x-custom'][] = array($attr['name'], $attr['value']); @@ -346,10 +368,13 @@ class calendar_ical * @param array Events as array * @param string VCalendar method to advertise * @param boolean Directly send data to stdout instead of returning + * @param callable Callback function to fetch attachment contents, false if no attachment export * @return string Events in iCalendar format (http://tools.ietf.org/html/rfc5545) */ - public function export($events, $method = null, $write = false, $recurrence_id = null) + public function export($events, $method = null, $write = false, $get_attachment = false, $recurrence_id = null) { + $memory_limit = parse_bytes(ini_get('memory_limit')); + if (!$recurrence_id) { $ical = "BEGIN:VCALENDAR" . self::EOL; $ical .= "VERSION:2.0" . self::EOL; @@ -432,7 +457,23 @@ class calendar_ical foreach ((array)$event['x-custom'] as $prop) $vevent .= $prop[0] . ':' . self::escape($prop[1]) . self::EOL; - // TODO: export attachments + // export attachments using the given callback function + if (is_callable($get_attachment) && !empty($event['attachments'])) { + foreach ((array)$event['attachments'] as $attach) { + // check available memory and skip attachment export if we can't buffer it + if ($memory_limit > 0 && ($memory_used = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024) + && $attach['size'] && $memory_used + $attach['size'] * 3 > $memory_limit) { + continue; + } + // TODO: let the callback print the data directly to stdout (with b64 encoding) + if ($data = call_user_func($get_attachment, $attach['id'], $event)) { + $vevent .= sprintf('ATTACH;VALUE=BINARY;ENCODING=BASE64;FMTTYPE=%s;X-LABEL=%s:', + self::escape($attach['mimetype']), self::escape($attach['name'])); + $vevent .= base64_encode($data) . self::EOL; + } + unset($data); // attempt to free memory + } + } $vevent .= "END:VEVENT" . self::EOL; @@ -441,7 +482,7 @@ class calendar_ical foreach ($event['recurrence']['EXCEPTIONS'] as $ex) { $exdate = clone $event['start']; $exdate->setDate($ex['start']->format('Y'), $ex['start']->format('n'), $ex['start']->format('j')); - $vevent .= $this->export(array($ex), null, false, + $vevent .= $this->export(array($ex), null, false, $get_attachment, $this->format_datetime('RECURRENCE-ID', $exdate, $event['allday'])); } } diff --git a/plugins/calendar/lib/calendar_itip.php b/plugins/calendar/lib/calendar_itip.php index cb196466..30fb8128 100644 --- a/plugins/calendar/lib/calendar_itip.php +++ b/plugins/calendar/lib/calendar_itip.php @@ -159,7 +159,7 @@ class calendar_itip // attach ics file for this event $ical = $this->cal->get_ical(); - $ics = $ical->export(array($event), $method); + $ics = $ical->export(array($event), $method, false, $method == 'REQUEST' ? array($this->cal->driver, 'get_attachment_body') : false); $message->addAttachment($ics, 'text/calendar', 'event.ics', false, '8bit', '', RCMAIL_CHARSET . "; method=" . $method); return $message;