1<?php
2/**
3 * Copyright 2011-2017 Horde LLC (http://www.horde.org/)
4 *
5 * See the enclosed file COPYING for license information (GPL). If you
6 * did not receive this file, see http://www.horde.org/licenses/gpl.
7 *
8 * @category  Horde
9 * @copyright 2011-2017 Horde LLC
10 * @license   http://www.horde.org/licenses/gpl GPL
11 * @package   IMP
12 */
13
14/**
15 * Method to import MBOX data into a mailbox.
16 *
17 * @author    Michael Slusarz <slusarz@horde.org>
18 * @category  Horde
19 * @copyright 2011-2017 Horde LLC
20 * @license   http://www.horde.org/licenses/gpl GPL
21 * @package   IMP
22 */
23class IMP_Mbox_Import
24{
25    /**
26     * Temporary data.
27     *
28     * @var array
29     */
30    protected $_import;
31
32    /**
33     * Import mailbox.
34     *
35     * @var IMP_Mailbox
36     */
37    protected $_mbox;
38
39    /**
40     * Import a MBOX file into a mailbox.
41     *
42     * @param string $mbox       The mailbox name to import into (UTF-8).
43     * @param string $form_name  The form field name that contains the MBOX
44     *                           data.
45     *
46     * @return string  Notification message.
47     * @throws Horde_Exception
48     */
49    public function import($mbox, $form_name)
50    {
51        $GLOBALS['browser']->wasFileUploaded($form_name, _("mailbox file"));
52        $this->_mbox = $mbox;
53
54        $res = $this->_import($_FILES[$form_name]['tmp_name'], $_FILES[$form_name]['type']);
55        $mbox_name = basename(Horde_Util::dispelMagicQuotes($_FILES[$form_name]['name']));
56
57        if ($res === false) {
58            throw new IMP_Exception(sprintf(_("There was an error importing %s."), $mbox_name));
59        }
60
61        return sprintf(ngettext('Imported %d message from %s.', 'Imported %d messages from %s', $res), $res, $mbox_name);
62    }
63
64    /**
65     * Imports messages from a mbox (see RFC 4155) -or- a message source
66     * (eml) file.
67     *
68     * @param string $fname  Filename containing the message data.
69     * @param string $type   The MIME type of the message data.
70     *
71     * @return mixed  False (boolean) on fail or the number of messages
72     *                imported (integer) on success.
73     * @throws IMP_Exception
74     */
75    protected function _import($fname, $type)
76    {
77        if (!is_readable($fname)) {
78            return false;
79        }
80
81        $fd = null;
82
83        switch ($type) {
84        case 'application/gzip':
85        case 'application/x-gzip':
86        case 'application/x-gzip-compressed':
87            // No need to default to Horde_Compress because it uses zlib
88            // also.
89            if (in_array('compress.zlib', stream_get_wrappers())) {
90                $fd = 'compress.zlib://' . $fname;
91            }
92            break;
93
94        case 'application/x-bzip2':
95        case 'application/x-bzip':
96            if (in_array('compress.bzip2', stream_get_wrappers())) {
97                $fd = 'compress.bzip2://' . $fname;
98            }
99            break;
100
101        case 'application/zip':
102        case 'application/x-compressed':
103        case 'application/x-zip-compressed':
104            if (in_array('zip', stream_get_wrappers())) {
105                $fd = 'zip://' . $fname;
106            } else {
107                try {
108                    $zip = Horde_Compress::factory('Zip');
109                    if ($zip->canDecompress) {
110                        $file_data = file_get_contents($fname);
111
112                        $zip_info = $zip->decompress($file_data, array(
113                            'action' => Horde_Compress_Zip::ZIP_LIST
114                        ));
115
116                        if (!empty($zip_info)) {
117                            $fd = fopen('php://temp', 'r+');
118
119                            foreach (array_keys($zip_info) as $key) {
120                                fwrite($fd, $zip->decompress($file_data, array(
121                                    'action' => Horde_Compress_Zip::ZIP_DATA,
122                                    'info' => $zip_info,
123                                    'key' => $key
124                                )));
125                            }
126
127                            rewind($fd);
128                        }
129                    }
130                } catch (Horde_Compress_Exception $e) {
131                    if ($fd) {
132                        fclose($fd);
133                        $fd = null;
134                    }
135                }
136            }
137            break;
138
139        default:
140            $fd = $fname;
141            break;
142        }
143
144        if (is_null($fd)) {
145            throw new IMP_Exception(_("The uploaded file cannot be opened."));
146        }
147
148        $parsed = new IMP_Mbox_Parse(
149            $fd,
150            $GLOBALS['injector']->getInstance('IMP_Factory_Imap')->create()->config->import_limit
151        );
152
153        $this->_import = array(
154            'data' => array(),
155            'msgs' => 0,
156            'size' => 0
157        );
158
159        if ($pcount = count($parsed)) {
160            foreach ($parsed as $key => $val) {
161                $this->_importHelper($val, ($key + 1) != $pcount);
162            }
163        } else {
164            $this->_importHelper($parsed[0]);
165        }
166
167        return $this->_import['msgs']
168            ? $this->_import['msgs']
169            : false;
170    }
171
172    /**
173     * Helper for _import().
174     *
175     * @param array $msg       Message data.
176     * @param integer $buffer  Buffer messages before sending?
177     */
178    protected function _importHelper($msg, $buffer = false)
179    {
180        $this->_import['data'][] = array_filter(array(
181            'data' => $msg['data'],
182            'internaldate' => $msg['date']
183        ));
184        $this->_import['size'] += $msg['size'];
185
186        /* Buffer 1 MB of messages before sending. */
187        if ($buffer && ($this->_import['size'] < 1048576)) {
188            return;
189        }
190
191        try {
192            $this->_mbox->imp_imap->append($this->_mbox, $this->_import['data']);
193            $this->_import['msgs'] += count($this->_import['data']);
194        } catch (IMP_Imap_Exception $e) {
195            throw new IMP_Exception(sprintf(_("Error when importing messages; %u messages successfully imported before error."), $this->_import['msgs']));
196        }
197
198        foreach ($this->_import['data'] as $val) {
199            fclose($val['data']);
200        }
201
202        $this->_import['data'] = array();
203        $this->_import['size'] = 0;
204    }
205
206}
207