-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Zipdownload: various improvements to this plugin #10260
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
45ddf6d
9bd686c
5b72a5b
a6b857f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ class zipdownload extends rcube_plugin | |
| private $charset = 'ASCII'; | ||
| private $names = []; | ||
| private $default_limit = '50MB'; | ||
| private $timezone; | ||
|
|
||
| // RFC4155: mbox date format | ||
| public const MBOX_DATE_FORMAT = 'D M d H:i:s Y'; | ||
|
|
@@ -251,14 +252,15 @@ private function _download_messages($messageset) | |
| foreach ($uids as $uid) { | ||
| $headers = $imap->get_message_headers($uid); | ||
|
|
||
| // Received (internal) date | ||
| $date = rcube_utils::anytodatetime($headers->internaldate); | ||
|
|
||
| if ($mode == 'mbox') { | ||
| // Sender address | ||
| $from = rcube_mime::decode_address_list($headers->from, null, true, $headers->charset, true); | ||
| $from = array_shift($from); | ||
| $from = preg_replace('/\s/', '-', $from); | ||
|
|
||
| // Received (internal) date | ||
| $date = rcube_utils::anytodatetime($headers->internaldate); | ||
| if ($date) { | ||
| $date = $date->setTimezone($timezone)->format(self::MBOX_DATE_FORMAT); | ||
| } else { | ||
|
|
@@ -277,7 +279,7 @@ private function _download_messages($messageset) | |
| $path = $folders ? str_replace($delimiter, '/', $mbox) . '/' : ''; | ||
| $disp_name = $path . $uid . ($subject ? " {$subject}" : '') . '.eml'; | ||
|
|
||
| $messages[$uid . ':' . $mbox] = $disp_name; | ||
| $messages[$uid . ':' . $mbox] = ($date ? $date->getTimestamp() : '') . ':' . $disp_name; | ||
| } | ||
|
|
||
| $size += $headers->size; | ||
|
|
@@ -305,6 +307,7 @@ private function _download_messages($messageset) | |
| } | ||
|
|
||
| // open zip file | ||
| putenv('TZ=UTC'); // see _datetime_to_ziplocal() comments | ||
| $zip = new \ZipArchive(); | ||
| $zip->open($tmpfname, \ZipArchive::OVERWRITE); | ||
|
|
||
|
|
@@ -331,12 +334,17 @@ private function _download_messages($messageset) | |
| fwrite($tmpfp, "\r\n"); | ||
| } | ||
| } else { // maildir | ||
| [$date, $filename] = explode(':', $value, 2); | ||
| $tmpfn = rcube_utils::temp_filename('zipmessage'); | ||
| $fp = fopen($tmpfn, 'w'); | ||
| $imap->get_raw_body($uid, $fp); | ||
| $tempfiles[] = $tmpfn; | ||
| fclose($fp); | ||
| $zip->addFile($tmpfn, $value); | ||
| $zip->addFile($tmpfn, $filename); | ||
| if ($date) { | ||
| $date = $this->_datetime_to_ziplocal(new \DateTime('@' . $date)); | ||
| $zip->setMtimeName($filename, $date->getTimestamp()); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -360,6 +368,41 @@ private function _download_messages($messageset) | |
| exit; | ||
| } | ||
|
|
||
| /** | ||
| * Zip files do not store timezones; most extraction tools extract times | ||
| * as though they were local times. This converts the UTC times inside | ||
| * DateTime objects into a user's preferred local time (despite claiming | ||
| * to still be UTC) so that when added and later extracted to/from a zip | ||
| * file, they will be in that user's local time. | ||
| * | ||
| * Also, ZipArchive creation is affected the system's default timezone | ||
| * (NOT date_default_timezone_set); to mitigate this, putenv('TZ=UTC'). | ||
| * | ||
| * @param \DateTimeInterface $real The accurate DateTime of a file (is not changed) | ||
| * | ||
| * @return \DateTimeInterface A "fake" DateTimeImmutable for inclusion into a zip | ||
| */ | ||
| private function _datetime_to_ziplocal($real) | ||
| { | ||
| if (!$this->timezone) { | ||
| if ($this->timezone === false) { | ||
| return $real; | ||
| } | ||
| $rcmail = rcmail::get_instance(); | ||
| try { | ||
| $this->timezone = new \DateTimeZone($rcmail->config->get('timezone')); | ||
| } catch (\DateInvalidTimeZoneException) { | ||
| $this->timezone = false; | ||
| return $real; | ||
| } | ||
| } | ||
|
|
||
| $real = \DateTime::createFromInterface($real); | ||
| $real->setTimezone($this->timezone); | ||
| $local = max($real->format('Y/m/d H:i:s'), '1980/01/01 00:00:00'); // Earliest supported by zip | ||
| return \DateTimeImmutable::createFromFormat('Y/m/d H:i:s O', $local . ' +0000'); | ||
| } | ||
|
|
||
| /** | ||
| * Helper method to send the zip archive to the browser | ||
| */ | ||
|
|
@@ -369,7 +412,20 @@ private function _deliver_zipfile($tmpfname, $filename) | |
|
|
||
| $rcmail->output->download_headers($filename, ['length' => filesize($tmpfname)]); | ||
|
|
||
| readfile($tmpfname); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem is not readfile(), but output buffering. Can't we just disable output buffering before calling readfile()?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have a very slight preference to use
In practice, |
||
| $tmpfp = fopen($tmpfname, 'r'); | ||
| if (!$tmpfp) { | ||
| return; | ||
| } | ||
| while (true) { | ||
| $data = fread($tmpfp, 512 * 1024); | ||
| if (strlen($data) == 0) { | ||
| break; | ||
| } | ||
| echo $data; | ||
| ob_flush(); | ||
| flush(); | ||
| } | ||
| fclose($tmpfp); | ||
| } | ||
|
|
||
| /** | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this still be needed if we use
setMtimeName()on every file?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately yes.
libzipexpects times in UTC and always converts them to a local time unlesszip_file_set_dostime()is used whichZipArchivedoesn't expose.libzipuseslocaltime()to do the conversion which can be controlled by theTZenvironment variable on POSIX and Windows. Differentlibcs have different ways of setting a default, for exampleglibcuses/etc/localtime(man tzset). Overriding the default to UTC was the best way I could find to convert theselocaltime()calls to a no-op.(Note that I've only tested this on
glibcand Windows.)