From d5c2f15ccbdadb54af5309fe7a316d87353a0340 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Wed, 13 Jun 2012 17:39:18 +0200 Subject: [PATCH] Improve attachments handling: - use Content-ID as key in _attachments list; new attachments have numeric keys - generate a unique Content-ID for new/changed attachments in libkolab - bugfix: don't cache empty attachment entries --- plugins/calendar/calendar_ui.js | 2 +- .../calendar/drivers/kolab/kolab_calendar.php | 36 +++++++++------- .../calendar/drivers/kolab/kolab_driver.php | 1 + plugins/libkolab/lib/kolab_format_event.php | 7 +-- plugins/libkolab/lib/kolab_storage_folder.php | 43 ++++++++++++++----- 5 files changed, 60 insertions(+), 29 deletions(-) diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js index 520f8a59..5d84b1b1 100644 --- a/plugins/calendar/calendar_ui.js +++ b/plugins/calendar/calendar_ui.js @@ -220,7 +220,7 @@ function rcube_calendar_ui(settings) // check if the event has 'real' attendees, excluding the current user var has_attendees = function(event) { - return (event.attendees && (event.attendees.length > 1 || event.attendees[0].email != settings.identity.email)); + return (event.attendees && event.attendees.length && (event.attendees.length > 1 || event.attendees[0].email != settings.identity.email)); }; // check if the current user is an attendee of this event diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php index f8525bea..a8a2755a 100644 --- a/plugins/calendar/drivers/kolab/kolab_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_calendar.php @@ -429,9 +429,10 @@ class kolab_calendar $record['end'] = $record['start'] + 3600; if (!empty($record['_attachments'])) { - foreach ($record['_attachments'] as $name => $attachment) { + foreach ($record['_attachments'] as $key => $attachment) { if ($attachment !== false) { - $attachment['name'] = $name; + if (!$attachment['name']) + $attachment['name'] = $key; $attachments[] = $attachment; } } @@ -460,29 +461,34 @@ class kolab_calendar { $object = &$event; - // in Horde attachments are indexed by name + // in kolab_storage attachments are indexed by content-id $object['_attachments'] = array(); if (is_array($event['attachments'])) { $collisions = array(); foreach ($event['attachments'] as $idx => $attachment) { - // Roundcube ID has nothing to do with Horde ID, remove it - if ($attachment['content']) + $key = null; + // Roundcube ID has nothing to do with the storage ID, remove it + if ($attachment['content']) { unset($attachment['id']); + } + else { + foreach ((array)$old['_attachments'] as $cid => $oldatt) { + if ($attachment['id'] == $oldatt['id']) + $key = $cid; + } + } // flagged for deletion => set to false if ($attachment['_deleted']) { - $object['_attachments'][$attachment['name']] = false; + $object['_attachments'][$key] = false; } + // replace existing entry + else if ($key) { + $object['_attachments'][$key] = $attachment; + } + // append as new attachment else { - // Horde code assumes that there will be no more than - // one file with the same name: make filenames unique - $filename = $attachment['name']; - if ($collisions[$filename]++) { - $ext = preg_match('/(\.[a-z0-9]{1,6})$/i', $filename, $m) ? $m[1] : null; - $attachment['name'] = basename($filename, $ext) . '-' . $collisions[$filename] . $ext; - } - - $object['_attachments'][$attachment['name']] = $attachment; + $object['_attachments'][] = $attachment; } } diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php index 42d758cd..41dc3372 100644 --- a/plugins/calendar/drivers/kolab/kolab_driver.php +++ b/plugins/calendar/drivers/kolab/kolab_driver.php @@ -582,6 +582,7 @@ class kolab_driver extends calendar_driver } } } + unset($event['deleted_attachments']); } // handle attachments to add diff --git a/plugins/libkolab/lib/kolab_format_event.php b/plugins/libkolab/lib/kolab_format_event.php index c48b63d5..4742b511 100644 --- a/plugins/libkolab/lib/kolab_format_event.php +++ b/plugins/libkolab/lib/kolab_format_event.php @@ -77,12 +77,12 @@ class kolab_format_event extends kolab_format_xcal // save attachments $vattach = new vectorattachment; - foreach ((array)$object['_attachments'] as $name => $attr) { + foreach ((array)$object['_attachments'] as $cid => $attr) { if (empty($attr)) continue; $attach = new Attachment; - $attach->setLabel($name); - $attach->setUri('cid:' . $name, $attr['mimetype']); + $attach->setLabel((string)$attr['name']); + $attach->setUri('cid:' . $cid, $attr['mimetype']); $vattach->push($attach); } $this->obj->setAttachments($vattach); @@ -148,6 +148,7 @@ class kolab_format_event extends kolab_format_xcal $name = $attach->label(); $data = $attach->data(); $object['_attachments'][$name] = array( + 'name' => $name, 'mimetype' => $attach->mimetype(), 'size' => strlen($data), 'content' => $data, diff --git a/plugins/libkolab/lib/kolab_storage_folder.php b/plugins/libkolab/lib/kolab_storage_folder.php index d2a78de5..df5e8113 100644 --- a/plugins/libkolab/lib/kolab_storage_folder.php +++ b/plugins/libkolab/lib/kolab_storage_folder.php @@ -415,6 +415,7 @@ class kolab_storage_folder $key = $part->content_id ? trim($part->content_id, '<>') : $part->filename; $attachments[$key] = array( 'id' => $part->mime_id, + 'name' => $part->filename, 'mimetype' => $part->mimetype, 'size' => $part->size, ); @@ -509,14 +510,35 @@ class kolab_storage_folder // copy attachments from old message if (!empty($object['_msguid']) && ($old = $this->cache->get($object['_msguid'], $type, $object['_mailbox']))) { - foreach ((array)$old['_attachments'] as $name => $att) { - if (!isset($object['_attachments'][$name])) { - $object['_attachments'][$name] = $old['_attachments'][$name]; + foreach ((array)$old['_attachments'] as $key => $att) { + if (!isset($object['_attachments'][$key])) { + $object['_attachments'][$key] = $old['_attachments'][$key]; + } + // unset deleted attachment entries + if ($object['_attachments'][$key] == false) { + unset($object['_attachments'][$key]); } // load photo.attachment from old Kolab2 format to be directly embedded in xcard block - if ($name == 'photo.attachment' && !isset($object['photo']) && !$object['_attachments'][$name]['content'] && $att['id']) { + else if ($key == 'photo.attachment' && !isset($object['photo']) && !$object['_attachments'][$key]['content'] && $att['id']) { $object['photo'] = $this->get_attachment($object['_msguid'], $att['id'], $object['_mailbox']); - unset($object['_attachments'][$name]); + unset($object['_attachments'][$key]); + } + } + } + + // generate unique keys (used as content-id) for attachments + if (is_array($object['_attachments'])) { + $numatt = count($object['_attachments']); + foreach ($object['_attachments'] as $key => $attachment) { + if (is_numeric($key) && $key < $numatt) { + // derrive content-id from attachment file name + $ext = preg_match('/(\.[a-z0-9]{1,6})$/i', $attachment['name'], $m) ? $m[1] : null; + $basename = preg_replace('/[^a-z0-9_.-]/i', '', basename($attachment['name'], $ext)); // to 7bit ascii + if (!$basename) $basename = 'noname'; + $cid = $basename . '.' . microtime(true) . $ext; + + $object['_attachments'][$cid] = $attachment; + unset($object['_attachments'][$key]); } } } @@ -685,24 +707,25 @@ class kolab_storage_folder // save object attachments as separate parts // TODO: optimize memory consumption by using tempfiles for transfer - foreach ((array)$object['_attachments'] as $name => $att) { + foreach ((array)$object['_attachments'] as $key => $att) { if (empty($att['content']) && !empty($att['id'])) { $msguid = !empty($object['_msguid']) ? $object['_msguid'] : $object['uid']; $att['content'] = $this->get_attachment($msguid, $att['id'], $object['_mailbox']); } - $headers = array('Content-ID' => Mail_mimePart::encodeHeader('Content-ID', '<' . $name . '>', RCMAIL_CHARSET, 'quoted-printable')); + $headers = array('Content-ID' => Mail_mimePart::encodeHeader('Content-ID', '<' . $key . '>', RCMAIL_CHARSET, 'quoted-printable')); + $name = !empty($att['name']) ? $att['name'] : $key; if (!empty($att['content'])) { - $mime->addAttachment($att['content'], $att['mimetype'], $name, false, 'base64', 'attachment', '', '', '', null, null, '', null, $headers); + $mime->addAttachment($att['content'], $att['mimetype'], $name, false, 'base64', 'attachment', '', '', '', null, null, '', RCMAIL_CHARSET, $headers); $part_id++; } else if (!empty($att['path'])) { - $mime->addAttachment($att['path'], $att['mimetype'], $name, true, 'base64', 'attachment', '', '', '', null, null, '', null, $headers); + $mime->addAttachment($att['path'], $att['mimetype'], $name, true, 'base64', 'attachment', '', '', '', null, null, '', RCMAIL_CHARSET, $headers); $part_id++; } - $object['_attachments'][$name]['id'] = $part_id; + $object['_attachments'][$key]['id'] = $part_id; } return $mime->getMessage();