Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.00% covered (warning)
84.00%
84 / 100
62.50% covered (warning)
62.50%
5 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
upload
84.00% covered (warning)
84.00%
84 / 100
62.50% covered (warning)
62.50%
5 / 8
47.89
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 upload
85.42% covered (warning)
85.42%
41 / 48
0.00% covered (danger)
0.00%
0 / 1
19.00
 create_thumbnail
60.00% covered (warning)
60.00%
6 / 10
0.00% covered (danger)
0.00%
0 / 1
5.02
 init_files_upload
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
 check_image
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
5
 check_attach_quota
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 check_disk_space
44.44% covered (danger)
44.44%
4 / 9
0.00% covered (danger)
0.00%
0 / 1
6.74
 fill_file_data
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 *
4 * This file is part of the phpBB Forum Software package.
5 *
6 * @copyright (c) phpBB Limited <https://www.phpbb.com>
7 * @license GNU General Public License, version 2 (GPL-2.0)
8 *
9 * For full copyright and license information, please see
10 * the docs/CREDITS.txt file.
11 *
12 */
13
14namespace phpbb\attachment;
15
16use phpbb\auth\auth;
17use phpbb\cache\service;
18use phpbb\config\config;
19use phpbb\event\dispatcher;
20use phpbb\language\language;
21use phpbb\plupload\plupload;
22use phpbb\storage\storage;
23use phpbb\filesystem\temp;
24use phpbb\user;
25
26/**
27 * Attachment upload class
28 */
29class upload
30{
31    /** @var auth */
32    protected $auth;
33
34    /** @var service */
35    protected $cache;
36
37    /** @var config */
38    protected $config;
39
40    /** @var \phpbb\files\upload Upload class */
41    protected $files_upload;
42
43    /** @var language */
44    protected $language;
45
46    /** @var dispatcher */
47    protected $phpbb_dispatcher;
48
49    /** @var string */
50    protected $phpbb_root_path;
51
52    /** @var plupload Plupload */
53    protected $plupload;
54
55    /** @var storage */
56    protected $storage;
57
58    /** @var temp */
59    protected $temp;
60
61    /** @var user */
62    protected $user;
63
64    /** @var \phpbb\files\filespec_storage Current filespec instance */
65    private $file;
66
67    /** @var array File data */
68    private $file_data = array(
69        'error'    => array()
70    );
71
72    /** @var array Extensions array */
73    private $extensions;
74
75    /**
76     * Constructor for attachments upload class
77     *
78     * @param auth $auth
79     * @param service $cache
80     * @param config $config
81     * @param \phpbb\files\upload $files_upload
82     * @param language $language
83     * @param dispatcher $phpbb_dispatcher
84     * @param plupload $plupload
85     * @param storage $storage
86     * @param temp $temp
87     * @param user $user
88     */
89    public function __construct(auth $auth, service $cache, config $config, \phpbb\files\upload $files_upload, language $language, dispatcher $phpbb_dispatcher, plupload $plupload, storage $storage, temp $temp, user $user)
90    {
91        $this->auth = $auth;
92        $this->cache = $cache;
93        $this->config = $config;
94        $this->files_upload = $files_upload;
95        $this->language = $language;
96        $this->phpbb_dispatcher = $phpbb_dispatcher;
97        $this->plupload = $plupload;
98        $this->storage = $storage;
99        $this->temp = $temp;
100        $this->user = $user;
101    }
102
103    /**
104     * Upload Attachment - filedata is generated here
105     * Uses upload class
106     *
107     * @param string            $form_name        The form name of the file upload input
108     * @param int            $forum_id        The id of the forum
109     * @param bool            $local            Whether the file is local or not
110     * @param string            $local_storage    The path to the local file
111     * @param bool            $is_message        Whether it is a PM or not
112     * @param array        $local_filedata    An file data object created for the local file
113     *
114     * @return array File data array
115     */
116    public function upload($form_name, $forum_id, $local = false, $local_storage = '', $is_message = false, $local_filedata = array())
117    {
118        $this->init_files_upload($forum_id, $is_message);
119
120        $this->file_data['post_attach'] = $local || $this->files_upload->is_valid($form_name);
121
122        if (!$this->file_data['post_attach'])
123        {
124            $this->file_data['error'][] = $this->language->lang('NO_UPLOAD_FORM_FOUND');
125            return $this->file_data;
126        }
127
128        $this->file = ($local) ? $this->files_upload->handle_upload('files.types.local_storage', $local_storage, $local_filedata) : $this->files_upload->handle_upload('files.types.form_storage', $form_name);
129
130        if ($this->file->init_error())
131        {
132            $this->file_data['post_attach'] = false;
133            return $this->file_data;
134        }
135
136        // Whether the uploaded file is in the image category
137        $is_image = (isset($this->extensions[$this->file->get('extension')]['display_cat'])) ? $this->extensions[$this->file->get('extension')]['display_cat'] == \phpbb\attachment\attachment_category::IMAGE : false;
138
139        if (!$this->auth->acl_get('a_') && !$this->auth->acl_get('m_', $forum_id))
140        {
141            // Check Image Size, if it is an image
142            if ($is_image)
143            {
144                $this->file->upload->set_allowed_dimensions(0, 0, $this->config['img_max_width'], $this->config['img_max_height']);
145            }
146
147            // Admins and mods are allowed to exceed the allowed filesize
148            if (!empty($this->extensions[$this->file->get('extension')]['max_filesize']))
149            {
150                $allowed_filesize = $this->extensions[$this->file->get('extension')]['max_filesize'];
151            }
152            else
153            {
154                $allowed_filesize = ($is_message) ? $this->config['max_filesize_pm'] : $this->config['max_filesize'];
155            }
156
157            $this->file->upload->set_max_filesize($allowed_filesize);
158        }
159
160        $this->file->clean_filename('unique', $this->user->data['user_id'] . '_');
161
162        // Do we have to create a thumbnail?
163        $this->file_data['thumbnail'] = ($is_image && $this->config['img_create_thumbnail']) ? 1 : 0;
164
165        // Make sure the image category only holds valid images...
166        $this->check_image($is_image);
167
168        if (count($this->file->error))
169        {
170            $this->file->remove($this->storage);
171            $this->file_data['error'] = array_merge($this->file_data['error'], $this->file->error);
172            $this->file_data['post_attach'] = false;
173
174            return $this->file_data;
175        }
176
177        $this->fill_file_data();
178
179        $filedata = $this->file_data;
180
181        /**
182         * Event to modify uploaded file before submit to the post
183         *
184         * @event core.modify_uploaded_file
185         * @var    array    filedata    Array containing uploaded file data
186         * @var    bool    is_image    Flag indicating if the file is an image
187         * @since 3.1.0-RC3
188         */
189        $vars = array(
190            'filedata',
191            'is_image',
192        );
193        extract($this->phpbb_dispatcher->trigger_event('core.modify_uploaded_file', compact($vars)));
194        $this->file_data = $filedata;
195        unset($filedata);
196
197        // Check for attachment quota and free space
198        if (!$this->check_attach_quota() || !$this->check_disk_space())
199        {
200            $this->file->remove($this->storage);
201            return $this->file_data;
202        }
203
204        // Create Thumbnail
205        $this->create_thumbnail();
206
207        // Are we uploading an image *and* this image being within the image category?
208        // Only then perform additional image checks.
209        $this->file->move_file($this->storage, false, !$is_image);
210
211        if (count($this->file->error))
212        {
213            $this->file->remove($this->storage);
214
215            // Remove thumbnail if exists
216            $thumbnail_file = 'thumb_' . $this->file->get('realname');
217            if ($this->storage->exists($thumbnail_file))
218            {
219                $this->storage->delete($thumbnail_file);
220            }
221
222            /** @psalm-suppress NoValue */
223            $this->file_data['error'] = array_merge($this->file_data['error'], $this->file->error);
224            $this->file_data['post_attach'] = false;
225
226            return $this->file_data;
227        }
228
229        return $this->file_data;
230    }
231
232    /**
233     * Create thumbnail for file if necessary
234     */
235    protected function create_thumbnail()
236    {
237        if ($this->file_data['thumbnail'])
238        {
239            $source = $this->file->get('filename');
240            $destination_name = 'thumb_' . $this->file->get('realname');
241            $destination = $this->temp->get_dir() . '/' . $destination_name;
242
243            if (create_thumbnail($source, $destination, $this->file->get('mimetype')))
244            {
245                // Move the thumbnail from temp folder to the storage
246                $fp = fopen($destination, 'rb');
247
248                $this->storage->write_stream($destination_name, $fp);
249
250                if (is_resource($fp))
251                {
252                    fclose($fp);
253                }
254            }
255            else
256            {
257                $this->file_data['thumbnail'] = 0;
258            }
259        }
260    }
261
262    /**
263     * Init files upload class
264     *
265     * @param int $forum_id Forum ID
266     * @param bool $is_message Whether attachment is inside PM or not
267     */
268    protected function init_files_upload($forum_id, $is_message)
269    {
270        if ($this->config['check_attachment_content'] && isset($this->config['mime_triggers']))
271        {
272            $this->files_upload->set_disallowed_content(explode('|', $this->config['mime_triggers']));
273        }
274        else if (!$this->config['check_attachment_content'])
275        {
276            $this->files_upload->set_disallowed_content(array());
277        }
278
279        $this->extensions = $this->cache->obtain_attach_extensions((($is_message) ? false : (int) $forum_id));
280        $this->files_upload->set_allowed_extensions(array_keys($this->extensions['_allowed_']));
281    }
282
283    /**
284     * Check if uploaded file is really an image
285     *
286     * @param bool $is_image Whether file is image
287     */
288    protected function check_image($is_image)
289    {
290        // Make sure the image category only holds valid images...
291        if ($is_image && !$this->file->is_image())
292        {
293            $this->file->remove($this->storage);
294
295            if ($this->plupload && $this->plupload->is_active())
296            {
297                $this->plupload->emit_error(104, 'ATTACHED_IMAGE_NOT_IMAGE');
298            }
299
300            // If this error occurs a user tried to exploit an IE Bug by renaming extensions
301            // Since the image category is displaying content inline we need to catch this.
302            $this->file->set_error($this->language->lang('ATTACHED_IMAGE_NOT_IMAGE'));
303        }
304    }
305
306    /**
307     * Check if attachment quota was reached
308     *
309     * @return bool False if attachment quota was reached, true if not
310     */
311    protected function check_attach_quota()
312    {
313        if ($this->config['attachment_quota'])
314        {
315            if (intval($this->config['upload_dir_size']) + $this->file->get('filesize') > $this->config['attachment_quota'])
316            {
317                $this->file_data['error'][] = $this->language->lang('ATTACH_QUOTA_REACHED');
318                $this->file_data['post_attach'] = false;
319
320                return false;
321            }
322        }
323
324        return true;
325    }
326
327    /**
328     * Check if there is enough free space available on disk
329     *
330     * @return bool True if disk space is available or not limited, false if not
331     */
332    protected function check_disk_space()
333    {
334        try
335        {
336            $free_space = $this->storage->free_space();
337
338            if ($free_space <= $this->file->get('filesize'))
339            {
340                if ($this->auth->acl_get('a_'))
341                {
342                    $this->file_data['error'][] = $this->language->lang('ATTACH_DISK_FULL');
343                }
344                else
345                {
346                    $this->file_data['error'][] = $this->language->lang('ATTACH_QUOTA_REACHED');
347                }
348
349                $this->file_data['post_attach'] = false;
350
351                return false;
352            }
353        }
354        catch (\phpbb\storage\exception\storage_exception $e)
355        {
356            // Do nothing
357        }
358
359        return true;
360    }
361
362    /**
363     * Fills file data with file information and current time as filetime
364     */
365    protected function fill_file_data()
366    {
367        $this->file_data['filesize'] = $this->file->get('filesize');
368        $this->file_data['mimetype'] = $this->file->get('mimetype');
369        $this->file_data['extension'] = $this->file->get('extension');
370        $this->file_data['physical_filename'] = $this->file->get('realname');
371        $this->file_data['real_filename'] = $this->file->get('uploadname');
372        $this->file_data['filetime'] = time();
373    }
374}