MediaUploader::fetchMedia()   C
last analyzed

Complexity

Conditions 14
Paths 81

Size

Total Lines 47
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 210

Importance

Changes 0
Metric Value
cc 14
eloc 37
nc 81
nop 2
dl 0
loc 47
rs 6.2666
c 0
b 0
f 0
ccs 0
cts 36
cp 0
crap 210

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
 You may not change or alter any portion of this comment or credits
4
 of supporting developers from this source code or any supporting source code
5
 which is considered copyrighted (c) material of the original comment or credit authors.
6
7
 This program is distributed in the hope that it will be useful,
8
 but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
*/
11
12
namespace Xoops\Core;
13
14
/**
15
 * XOOPS file uploader
16
 *
17
 * @copyright   XOOPS Project (http://xoops.org)
18
 * @license     GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html)
19
 * @package     class
20
 * @since       2.0.0
21
 * @author      Kazumi Ono (http://www.myweb.ne.jp/, http://jp.xoops.org/)
22
 * @author      Taiwen Jiang <[email protected]>
23
 */
24
25
26
/**
27
 * XOOPS file uploader
28
 *
29
 * Example of usage:
30
 * <code>
31
 * $allowed_mimetypes = array('image/gif', 'image/jpeg', 'image/pjpeg', 'image/x-png');
32
 * $maxfilesize = 50000;
33
 * $maxfilewidth = 120;
34
 * $maxfileheight = 120;
35
 * $uploader = new Xoops\Core\MediaUploader(
36
 *      '/home/xoops/uploads',
37
 *      $allowed_mimetypes,
38
 *      $maxfilesize,
39
 *      $maxfilewidth,
40
 *      $maxfileheight
41
 * );
42
 * if ($uploader->fetchMedia($_POST['uploade_file_name'])) {
43
 *        if (!$uploader->upload()) {
44
 *           echo $uploader->getErrors();
45
 *        } else {
46
 *           echo '<h4>File uploaded successfully!</h4>'
47
 *           echo 'Saved as: ' . $uploader->getSavedFileName() . '<br />';
48
 *           echo 'Full path: ' . $uploader->getSavedDestination();
49
 *        }
50
 * } else {
51
 *        echo $uploader->getErrors();
52
 * }
53
 * </code>
54
 *
55
 * @category  Xoops\Core\MediaUploader
56
 * @package   MediaUploader
57
 * @author    Kazumi Ono (http://www.myweb.ne.jp/, http://jp.xoops.org/)
58
 * @author    Taiwen Jiang <[email protected]>
59
 * @copyright 2003-2014 XOOPS Project (http://xoops.org)
60
 * @license   GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html)
61
 * @link      http://xoops.org
62
 */
63
class MediaUploader
64
{
65
    /**
66
     * Flag indicating if unrecognized mimetypes should be allowed (use with precaution ! may lead to security issues )
67
     *
68
     * @var bool
69
     */
70
    public $allowUnknownTypes = false;
71
72
    /**
73
     * @var string
74
     */
75
    public $mediaName;
76
77
    /**
78
     * @var string
79
     */
80
    public $mediaType;
81
82
    /**
83
     * @var int
84
     */
85
    public $mediaSize;
86
87
    /**
88
     * @var string
89
     */
90
    public $mediaTmpName;
91
92
    /**
93
     * @var string
94
     */
95
    public $mediaError;
96
97
    /**
98
     * @var string
99
     */
100
    public $mediaRealType = '';
101
102
    /**
103
     * @var string
104
     */
105
    public $uploadDir = '';
106
107
    /**
108
     * @var array
109
     */
110
    public $allowedMimeTypes = array();
111
112
    /**
113
     * @var array
114
     */
115
    public $deniedMimeTypes = array(
116
        'application/x-httpd-php'
117
    );
118
119
    /**
120
     * @var int
121
     */
122
    public $maxFileSize = 0;
123
124
    /**
125
     * @var int
126
     */
127
    public $maxWidth;
128
129
    /**
130
     * @var int
131
     */
132
    public $maxHeight;
133
134
    /**
135
     * @var string
136
     */
137
    public $targetFileName;
138
139
    /**
140
     * @var
141
     */
142
    public $prefix;
143
144
    /**
145
     * @var array
146
     */
147
    public $errors = array();
148
149
    /**
150
     * @var string
151
     */
152
    public $savedDestination;
153
154
    /**
155
     * @var string
156
     */
157
    public $savedFileName;
158
159
    /**
160
     * @var array|mixed
161
     */
162
    //public $extensionToMime = array();
163
164
    /**
165
     * @var bool
166
     */
167
    public $checkImageType = true;
168
169
    /**
170
     * @var array
171
     */
172
    public $extensionsToBeSanitized = array(
173
        'php', 'phtml', 'phtm', 'php3', 'php4', 'cgi', 'pl', 'asp', 'php5'
174
    );
175
176
    /**
177
     * extensions needed image check (anti-IE Content-Type XSS)
178
     *
179
     * @var array
180
     */
181
    public $imageExtensions = array(
182
        1 => 'gif', 2 => 'jpg', 3 => 'png', 4 => 'swf', 5 => 'psd', 6 => 'bmp', 7 => 'tif', 8 => 'tif', 9 => 'jpc',
183
        10 => 'jp2', 11 => 'jpx', 12 => 'jb2', 13 => 'swf', 14 => 'iff', 15 => 'wbmp', 16 => 'xbm'
184
    );
185
186
    /**
187
     * Constructor
188
     *
189
     * @param string $uploadDir        upload directory
190
     * @param array  $allowedMimeTypes allowed mime types
191
     * @param int    $maxFileSize      max size
192
     * @param int    $maxWidth         max width
193
     * @param int    $maxHeight        max height
194
     */
195 11
    public function __construct($uploadDir, $allowedMimeTypes, $maxFileSize = 0, $maxWidth = null, $maxHeight = null)
196
    {
197 11
        if (is_array($allowedMimeTypes)) {
0 ignored issues
show
introduced by
The condition is_array($allowedMimeTypes) is always true.
Loading history...
198 11
            $this->allowedMimeTypes = $allowedMimeTypes;
199
        }
200 11
        $this->uploadDir = $uploadDir;
201 11
        $this->maxFileSize = (int)($maxFileSize);
202 11
        if (isset($maxWidth)) {
203 1
            $this->maxWidth = (int)($maxWidth);
204
        }
205 11
        if (isset($maxHeight)) {
206 1
            $this->maxHeight = (int)($maxHeight);
207
        }
208 11
    }
209
210
    /**
211
     * Fetch the uploaded file
212
     *
213
     * @param string $media_name Name of the file field
214
     * @param int    $index      Index of the file (if more than one uploaded under that name)
215
     *
216
     * @return bool
217
     */
218
    public function fetchMedia($media_name, $index = null)
219
    {
220
        if (!isset($_FILES[$media_name])) {
221
            $this->setErrors(\XoopsLocale::E_FILE_NOT_FOUND);
222
            return false;
223
        } else {
224
            if (is_array($_FILES[$media_name]['name']) && isset($index)) {
225
                $index = (int)($index);
226
                $this->mediaName = (get_magic_quotes_gpc()) ? stripslashes($_FILES[$media_name]['name'][$index])
227
                    : $_FILES[$media_name]['name'][$index];
228
                $this->mediaType = $_FILES[$media_name]['type'][$index];
229
                $this->mediaSize = $_FILES[$media_name]['size'][$index];
230
                $this->mediaTmpName = $_FILES[$media_name]['tmp_name'][$index];
231
                $this->mediaError = !empty($_FILES[$media_name]['error'][$index])
232
                    ? $_FILES[$media_name]['error'][$index] : 0;
233
            } else {
234
                $media_name = $_FILES[$media_name];
235
                $this->mediaName = (get_magic_quotes_gpc()) ? stripslashes($media_name['name']) : $media_name['name'];
236
                $this->mediaType = $media_name['type'];
237
                $this->mediaSize = $media_name['size'];
238
                $this->mediaTmpName = $media_name['tmp_name'];
239
                $this->mediaError = !empty($media_name['error']) ? $media_name['error'] : 0;
240
            }
241
        }
242
243
        $path_parts = pathinfo($this->mediaName);
244
        $ext = (isset($path_parts['extension'])) ? $path_parts['extension'] : '';
245
        $this->mediaRealType = \Xoops\Core\MimeTypes::findType($ext);
246
247
        $this->errors = array();
248
        if ((int)($this->mediaSize) < 0) {
249
            $this->setErrors(\XoopsLocale::E_INVALID_FILE_SIZE);
250
            return false;
251
        }
252
        if ($this->mediaName == '') {
253
            $this->setErrors(\XoopsLocale::E_FILE_NAME_MISSING);
254
            return false;
255
        }
256
        if ($this->mediaTmpName === 'none' || !is_uploaded_file($this->mediaTmpName)) {
257
            $this->setErrors(\XoopsLocale::NO_FILE_UPLOADED);
258
            return false;
259
        }
260
        if ($this->mediaError > 0) {
261
            $this->setErrors(sprintf(\XoopsLocale::EF_UNEXPECTED_ERROR, $this->mediaError));
262
            return false;
263
        }
264
        return true;
265
    }
266
267
    /**
268
     * Set the target filename
269
     *
270
     * @param string $value file name
271
     *
272
     * @return void
273
     */
274 1
    public function setTargetFileName($value)
275
    {
276 1
        $this->targetFileName = (string)(trim($value));
277 1
    }
278
279
    /**
280
     * Set the prefix
281
     *
282
     * @param string $value prefix
283
     *
284
     * @return void
285
     */
286 1
    public function setPrefix($value)
287
    {
288 1
        $this->prefix = (string)(trim($value));
289 1
    }
290
291
    /**
292
     * Get the uploaded filename
293
     *
294
     * @return string
295
     */
296 1
    public function getMediaName()
297
    {
298 1
        return $this->mediaName;
299
    }
300
301
    /**
302
     * Get the type of the uploaded file
303
     *
304
     * @return string
305
     */
306 1
    public function getMediaType()
307
    {
308 1
        return $this->mediaType;
309
    }
310
311
    /**
312
     * Get the size of the uploaded file
313
     *
314
     * @return int
315
     */
316 1
    public function getMediaSize()
317
    {
318 1
        return $this->mediaSize;
319
    }
320
321
    /**
322
     * Get the temporary name that the uploaded file was stored under
323
     *
324
     * @return string
325
     */
326 1
    public function getMediaTmpName()
327
    {
328 1
        return $this->mediaTmpName;
329
    }
330
331
    /**
332
     * Get the saved filename
333
     *
334
     * @return string
335
     */
336 1
    public function getSavedFileName()
337
    {
338 1
        return $this->savedFileName;
339
    }
340
341
    /**
342
     * Get the destination the file is saved to
343
     *
344
     * @return string
345
     */
346 1
    public function getSavedDestination()
347
    {
348 1
        return $this->savedDestination;
349
    }
350
351
    /**
352
     * Check the file and copy it to the destination
353
     *
354
     * @param int $chmod file permissions to set
355
     *
356
     * @return bool
357
     */
358
    public function upload($chmod = 0644)
359
    {
360
        if ($this->uploadDir == '') {
361
            $this->setErrors(\XoopsLocale::E_UPLOAD_DIRECTORY_NOT_SET);
362
            return false;
363
        }
364
        if (!is_dir($this->uploadDir)) {
365
            $this->setErrors(sprintf(\XoopsLocale::EF_DIRECTORY_NOT_OPENED, $this->uploadDir));
366
            return false;
367
        }
368
        if (!is_writeable($this->uploadDir)) {
369
            $this->setErrors(sprintf(\XoopsLocale::EF_DIRECTORY_WITH_WRITE_PERMISSION_NOT_OPENED, $this->uploadDir));
370
            return false;
371
        }
372
        $this->sanitizeMultipleExtensions();
373
374
        if (!$this->checkMaxFileSize()) {
375
            return false;
376
        }
377
        if (!$this->checkMaxWidth()) {
378
            return false;
379
        }
380
        if (!$this->checkMaxHeight()) {
381
            return false;
382
        }
383
        if (!$this->checkMimeType()) {
384
            return false;
385
        }
386
        if (!$this->checkImageType()) {
387
            return false;
388
        }
389
        if (count($this->errors) > 0) {
390
            return false;
391
        }
392
        return $this->copyFile($chmod);
393
    }
394
395
    /**
396
     * Copy the file to its destination
397
     *
398
     * @param int $chmod file permissions to set
399
     *
400
     * @return bool
401
     */
402
    protected function copyFile($chmod)
403
    {
404
        $matched = array();
405
        if (!preg_match("/\.([a-zA-Z0-9]+)$/", $this->mediaName, $matched)) {
406
            $this->setErrors(\XoopsLocale::E_INVALID_FILE_NAME);
407
            return false;
408
        }
409
        if (isset($this->targetFileName)) {
410
            $this->savedFileName = $this->targetFileName;
411
        } else {
412
            if (isset($this->prefix)) {
413
                $this->savedFileName = uniqid($this->prefix) . '.' . strtolower($matched[1]);
414
            } else {
415
                $this->savedFileName = strtolower($this->mediaName);
416
            }
417
        }
418
419
        $this->savedDestination = $this->uploadDir . '/' . $this->savedFileName;
420
        if (!move_uploaded_file($this->mediaTmpName, $this->savedDestination)) {
421
            $this->setErrors(sprintf(\XoopsLocale::EF_FILE_NOT_SAVED_TO, $this->savedDestination));
422
            return false;
423
        }
424
        // Check IE XSS before returning success
425
        $ext = strtolower(substr(strrchr($this->savedDestination, '.'), 1));
426
        if (in_array($ext, $this->imageExtensions)) {
427
            $info = @getimagesize($this->savedDestination);
428
            if ($info === false || $this->imageExtensions[(int)$info[2]] != $ext) {
429
                $this->setErrors(\XoopsLocale::E_SUSPICIOUS_IMAGE_UPLOAD_REFUSED);
430
                @unlink($this->savedDestination);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

430
                /** @scrutinizer ignore-unhandled */ @unlink($this->savedDestination);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
431
                return false;
432
            }
433
        }
434
        @chmod($this->savedDestination, $chmod);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

434
        /** @scrutinizer ignore-unhandled */ @chmod($this->savedDestination, $chmod);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
435
        return true;
436
    }
437
438
    /**
439
     * Is the file the right size?
440
     *
441
     * @return bool
442
     */
443
    public function checkMaxFileSize()
444
    {
445
        if (!isset($this->maxFileSize)) {
446
            return true;
447
        }
448
        if ($this->mediaSize > $this->maxFileSize) {
449
            $this->setErrors(sprintf(\XoopsLocale::EF_FILE_SIZE_TO_LARGE, $this->maxFileSize, $this->mediaSize));
450
            return false;
451
        }
452
        return true;
453
    }
454
455
    /**
456
     * Is the picture the right width?
457
     *
458
     * @return bool
459
     */
460
    public function checkMaxWidth()
461
    {
462
        if (!isset($this->maxWidth)) {
463
            return true;
464
        }
465
        if (false !== $dimension = getimagesize($this->mediaTmpName)) {
466
            if ($dimension[0] > $this->maxWidth) {
467
                $this->setErrors(sprintf(\XoopsLocale::EF_FILE_WIDTH_TO_LARGE, $this->maxWidth, $dimension[0]));
468
                return false;
469
            }
470
        } else {
471
            trigger_error(sprintf(\XoopsLocale::EF_IMAGE_SIZE_NOT_FETCHED, $this->mediaTmpName), E_USER_WARNING);
472
        }
473
        return true;
474
    }
475
476
    /**
477
     * Is the picture the right height?
478
     *
479
     * @return bool
480
     */
481
    public function checkMaxHeight()
482
    {
483
        if (!isset($this->maxHeight)) {
484
            return true;
485
        }
486
        if (false !== $dimension = getimagesize($this->mediaTmpName)) {
487
            if ($dimension[1] > $this->maxHeight) {
488
                $this->setErrors(sprintf(\XoopsLocale::EF_FILE_HEIGHT_TO_LARGE, $this->maxHeight, $dimension[1]));
489
                return false;
490
            }
491
        } else {
492
            trigger_error(sprintf(\XoopsLocale::EF_IMAGE_SIZE_NOT_FETCHED, $this->mediaTmpName), E_USER_WARNING);
493
        }
494
        return true;
495
    }
496
497
    /**
498
     * Check whether or not the uploaded file type is allowed
499
     *
500
     * @return bool
501
     */
502
    public function checkMimeType()
503
    {
504
        if (empty($this->mediaRealType) && empty($this->allowUnknownTypes)) {
505
            $this->setErrors(\XoopsLocale::E_FILE_TYPE_REJECTED);
506
            return false;
507
        }
508
509
        if ((!empty($this->allowedMimeTypes)
510
            && !in_array($this->mediaRealType, $this->allowedMimeTypes))
511
            || (!empty($this->deniedMimeTypes)
512
            && in_array($this->mediaRealType, $this->deniedMimeTypes))
513
        ) {
514
            $this->setErrors(sprintf(\XoopsLocale::EF_FILE_MIME_TYPE_NOT_ALLOWED, $this->mediaType));
515
            return false;
516
        }
517
        return true;
518
    }
519
520
    /**
521
     * Check whether or not the uploaded image type is valid
522
     *
523
     * @return bool
524
     */
525
    public function checkImageType()
526
    {
527
        if (empty($this->checkImageType)) {
528
            return true;
529
        }
530
531
        if (('image' === substr($this->mediaType, 0, strpos($this->mediaType, '/')))
532
            || (!empty($this->mediaRealType)
533
            && 'image' === substr($this->mediaRealType, 0, strpos($this->mediaRealType, '/')))
534
        ) {
535
            if (!@getimagesize($this->mediaTmpName)) {
536
                $this->setErrors(\XoopsLocale::E_INVALID_IMAGE_FILE);
537
                return false;
538
            }
539
        }
540
        return true;
541
    }
542
543
    /**
544
     * Sanitize executable filename with multiple extensions
545
     *
546
     * @return void
547
     */
548
    public function sanitizeMultipleExtensions()
549
    {
550
        if (empty($this->extensionsToBeSanitized)) {
551
            return;
552
        }
553
554
        $patterns = array();
555
        $replaces = array();
556
        foreach ($this->extensionsToBeSanitized as $ext) {
557
            $patterns[] = "/\." . preg_quote($ext) . "\./i";
558
            $replaces[] = "_" . $ext . ".";
559
        }
560
        $this->mediaName = preg_replace($patterns, $replaces, $this->mediaName);
561
    }
562
563
    /**
564
     * Add an error
565
     *
566
     * @param string $error message
567
     *
568
     * @return void
569
     */
570
    public function setErrors($error)
571
    {
572
        $this->errors[] = trim($error);
573
    }
574
575
    /**
576
     * Get generated errors
577
     *
578
     * @param bool $ashtml Format using HTML?
579
     *
580
     * @return array |string    Array of array messages OR HTML string
581
     */
582
    public function getErrors($ashtml = true)
583
    {
584
        if (!$ashtml) {
585
            return $this->errors;
586
        } else {
587
            $ret = '';
588
            if (count($this->errors) > 0) {
589
                $ret = '<h4>'
590
                . sprintf(\XoopsLocale::EF_ERRORS_RETURNED_WHILE_UPLOADING_FILE, $this->mediaName) . '</h4>';
591
                foreach ($this->errors as $error) {
592
                    $ret .= $error . '<br />';
593
                }
594
            }
595
            return $ret;
596
        }
597
    }
598
}
599