XoopsMediaUploader::getSavedFileName()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
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
/**
13
 * @copyright    XOOPS Project https://xoops.org/
14
 * @license      GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html)
15
 * @package
16
 * @since
17
 * @author       XOOPS Development Team, Kazumi Ono (AKA onokazu)
18
 */
19
20
/*!
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
21
Example
22
23
require_once __DIR__ . '/uploader.php';
24
$allowed_mimetypes = array('image/gif', 'image/jpeg', 'image/pjpeg', 'image/x-png');
25
$maxfilesize = 50000;
26
$maxfilewidth = 120;
27
$maxfileheight = 120;
28
$uploader = new XoopsMediaUploader('/home/xoops/uploads', $allowed_mimetypes, $maxfilesize, $maxfilewidth, $maxfileheight);
29
if ($uploader->fetchMedia($_POST['uploade_file_name'])) {
30
if (!$uploader->upload()) {
31
echo $uploader->getErrors();
32
} else {
33
echo '<h4>File uploaded successfully!</h4>'
34
echo 'Saved as: ' . $uploader->getSavedFileName() . '<br>';
35
echo 'Full path: ' . $uploader->getSavedDestination();
36
}
37
} else {
38
echo $uploader->getErrors();
39
}
40
41
*/
42
43
/**
44
 * Upload Media files
45
 *
46
 * Example of usage:
47
 * <code>
48
 * require_once __DIR__ . '/uploader.php';
49
 * $allowed_mimetypes = array('image/gif', 'image/jpeg', 'image/pjpeg', 'image/x-png');
50
 * $maxfilesize = 50000;
51
 * $maxfilewidth = 120;
52
 * $maxfileheight = 120;
53
 * $uploader = new XoopsMediaUploader('/home/xoops/uploads', $allowed_mimetypes, $maxfilesize, $maxfilewidth, $maxfileheight);
54
 * if ($uploader->fetchMedia($_POST['uploade_file_name'])) {
55
 *   if (!$uploader->upload()) {
56
 *      echo $uploader->getErrors();
57
 *   } else {
58
 *      echo '<h4>File uploaded successfully!</h4>'
59
 *      echo 'Saved as: ' . $uploader->getSavedFileName() . '<br>';
60
 *      echo 'Full path: ' . $uploader->getSavedDestination();
61
 *   }
62
 * } else {
63
 *   echo $uploader->getErrors();
64
 * }
65
 * </code>
66
 *
67
 * @package          kernel
68
 * @subpackage       core
69
 *
70
 * @author           Kazumi Ono     <[email protected]>
71
 * @copyright    (c) 2000-2003 The Xoops Project - www.xoops.org
72
 */
73
class XoopsMediaUploader
74
{
75
    /**
76
     * Flag indicating if unrecognized mimetypes should be allowed (use with precaution ! may lead to security issues )
77
     **/
78
    public $allowUnknownTypes = false;
79
80
    public $mediaName;
81
    public $mediaType;
82
    public $mediaSize;
83
    public $mediaTmpName;
84
    public $mediaError;
85
    public $mediaRealType = '';
86
87
    public $uploadDir = '';
88
89
    public $allowedMimeTypes = array();
90
91
    public $maxFileSize = 0;
92
    public $maxWidth;
93
    public $maxHeight;
94
95
    public $targetFileName;
96
97
    public $prefix;
98
99
    public $errors = array();
100
101
    public $savedDestination;
102
103
    public $savedFileName;
104
105
    public $extensionToMime = array();
106
107
    /**
108
     * Constructor
109
     *
110
     * @param   string $uploadDir
111
     * @param   array  $allowedMimeTypes
112
     * @param   int    $maxFileSize
113
     * @param   int    $maxWidth
114
     * @param   int    $maxHeight
115
     * @internal param int $cmodvalue
116
     */
117
    public function __construct($uploadDir, $allowedMimeTypes, $maxFileSize = 0, $maxWidth = null, $maxHeight = null)
118
    {
119
        @$this->extensionToMime = include XOOPS_ROOT_PATH . '/class/mimetypes.inc.php';
0 ignored issues
show
Bug introduced by
The constant XOOPS_ROOT_PATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
120
        if (!is_array($this->extensionToMime)) {
121
            $this->extensionToMime = array();
122
123
            return false;
124
        }
125
        if (is_array($allowedMimeTypes)) {
126
            $this->allowedMimeTypes =& $allowedMimeTypes;
127
        }
128
        $this->uploadDir   = $uploadDir;
129
        $this->maxFileSize = (int)$maxFileSize;
130
        if (isset($maxWidth)) {
131
            $this->maxWidth = (int)$maxWidth;
132
        }
133
        if (isset($maxHeight)) {
134
            $this->maxHeight = (int)$maxHeight;
135
        }
136
    }
137
138
    /**
139
     * Fetch the uploaded file
140
     *
141
     * @param   string $media_name Name of the file field
142
     * @param   int    $index      Index of the file (if more than one uploaded under that name)
143
     * @return  bool
144
     **/
145
    public function fetchMedia($media_name, $index = null)
146
    {
147
        if (empty($this->extensionToMime)) {
148
            $this->setErrors('Error loading mimetypes definition');
149
150
            return false;
151
        }
152
        if (!isset($_FILES[$media_name])) {
153
            $this->setErrors('File not found');
154
155
            //echo " - no such file ";
156
            return false;
157
        } elseif (is_array($_FILES[$media_name]['name']) && isset($index)) {
158
            $index              = (int)$index;
159
            $this->mediaName    = get_magic_quotes_gpc() ? stripslashes($_FILES[$media_name]['name'][$index]) : $_FILES[$media_name]['name'][$index];
160
            $this->mediaType    = $_FILES[$media_name]['type'][$index];
161
            $this->mediaSize    = $_FILES[$media_name]['size'][$index];
162
            $this->mediaTmpName = $_FILES[$media_name]['tmp_name'][$index];
163
            $this->mediaError   = !empty($_FILES[$media_name]['error'][$index]) ? $_FILES[$media_name]['errir'][$index] : 0;
164
        } else {
165
            $media_name         =& $_FILES[$media_name];
166
            $this->mediaName    = get_magic_quotes_gpc() ? stripslashes($media_name['name']) : $media_name['name'];
167
            $this->mediaName    = $media_name['name'];
168
            $this->mediaType    = $media_name['type'];
169
            $this->mediaSize    = $media_name['size'];
170
            $this->mediaTmpName = $media_name['tmp_name'];
171
            $this->mediaError   = !empty($media_name['error']) ? $media_name['error'] : 0;
172
        }
173
        if (($ext = strrpos($this->mediaName, '.')) !== false) {
174
            $ext = substr($this->mediaName, $ext + 1);
175
            if (isset($this->extensionToMime[$ext])) {
176
                $this->mediaRealType = $this->extensionToMime[$ext];
177
                //trigger_error( "XoopsMediaUploader: Set mediaRealType to {$this->mediaRealType} (file extension is $ext)", E_USER_NOTICE );
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
178
            }
179
        }
180
        $this->errors = array();
181
        if ($ext && in_array($ext, array('gif', 'jpg', 'jpeg', 'png', 'bmp', 'xbm'))) {
182
            // Prevent sending of invalid images that would crash IE
183
            if (!($info = getimagesize($this->mediaTmpName))) {
0 ignored issues
show
Unused Code introduced by
The assignment to $info is dead and can be removed.
Loading history...
184
                $this->setErrors('Invalid file content');
185
186
                return false;
187
            }
188
        }
189
        if ((int)$this->mediaSize < 0) {
190
            $this->setErrors('Invalid File Size');
191
192
            return false;
193
        }
194
        if ($this->mediaName === '') {
195
            $this->setErrors('Filename Is Empty');
196
197
            return false;
198
        }
199
        if ($this->mediaTmpName === 'none' || !is_uploaded_file($this->mediaTmpName)) {
200
            $this->setErrors('No file uploaded');
201
202
            return false;
203
        }
204
        if ($this->mediaError > 0) {
205
            $this->setErrors('Error occurred: Error #' . $this->mediaError);
206
207
            return false;
208
        }
209
210
        return true;
211
    }
212
213
    /**
214
     * Set the target filename
215
     *
216
     * @param   string $value
217
     **/
218
    public function setTargetFileName($value)
219
    {
220
        $this->targetFileName = (string)trim($value);
221
    }
222
223
    /**
224
     * Set the prefix
225
     *
226
     * @param   string $value
227
     **/
228
    public function setPrefix($value)
229
    {
230
        $this->prefix = (string)trim($value);
231
    }
232
233
    /**
234
     * Get the uploaded filename
235
     *
236
     * @return  string
237
     **/
238
    public function getMediaName()
239
    {
240
        return $this->mediaName;
241
    }
242
243
    /**
244
     * Get the type of the uploaded file
245
     *
246
     * @return  string
247
     **/
248
    public function getMediaType()
249
    {
250
        return $this->mediaType;
251
    }
252
253
    /**
254
     * Get the size of the uploaded file
255
     *
256
     * @return  int
257
     **/
258
    public function getMediaSize()
259
    {
260
        return $this->mediaSize;
261
    }
262
263
    /**
264
     * Get the temporary name that the uploaded file was stored under
265
     *
266
     * @return  string
267
     **/
268
    public function getMediaTmpName()
269
    {
270
        return $this->mediaTmpName;
271
    }
272
273
    /**
274
     * Get the saved filename
275
     *
276
     * @return  string
277
     **/
278
    public function getSavedFileName()
279
    {
280
        return $this->savedFileName;
281
    }
282
283
    /**
284
     * Get the destination the file is saved to
285
     *
286
     * @return  string
287
     **/
288
    public function getSavedDestination()
289
    {
290
        return $this->savedDestination;
291
    }
292
293
    /**
294
     * Check the file and copy it to the destination
295
     *
296
     * @param int $chmod
297
     * @return bool
298
     */
299
    public function upload($chmod = 0644)
300
    {
301
        if ($this->uploadDir === '') {
302
            $this->setErrors('Upload directory not set');
303
304
            return false;
305
        }
306
        if (!is_dir($this->uploadDir)) {
307
            $this->setErrors('Failed opening directory: ' . $this->uploadDir);
308
        }
309
        if (!is_writable($this->uploadDir)) {
310
            $this->setErrors('Failed opening directory with write permission: ' . $this->uploadDir);
311
        }
312
        if (!$this->checkMaxFileSize()) {
313
            $this->setErrors('File size too large: ' . $this->mediaSize);
314
        }
315
        if (!$this->checkMaxWidth()) {
316
            $this->setErrors(sprintf('File width must be smaller than %u', $this->maxWidth));
317
        }
318
        if (!$this->checkMaxHeight()) {
319
            $this->setErrors(sprintf('File height must be smaller than %u', $this->maxHeight));
320
        }
321
        if (!$this->checkMimeType()) {
322
            $this->setErrors('MIME type not allowed: ' . $this->mediaType);
323
        }
324
        if (count($this->errors) > 0) {
325
            return false;
326
        }
327
        if (!$this->_copyFile($chmod)) {
328
            $this->setErrors('Failed uploading file: ' . $this->mediaName);
329
330
            return false;
331
        }
332
333
        return true;
334
    }
335
336
    /**
337
     * Copy the file to its destination
338
     *
339
     * @param $chmod
340
     * @return bool
341
     */
342
    public function _copyFile($chmod)
343
    {
344
        $matched = array();
345
        if (!preg_match("/\.([a-zA-Z0-9]+)$/", $this->mediaName, $matched)) {
346
            return false;
347
        }
348
        if (isset($this->targetFileName)) {
349
            $this->savedFileName = $this->targetFileName;
350
        } elseif (isset($this->prefix)) {
351
            $this->savedFileName = uniqid($this->prefix, true) . '.' . strtolower($matched[1]);
352
        } else {
353
            $this->savedFileName = strtolower($this->mediaName);
354
        }
355
        $this->savedDestination = $this->uploadDir . '/' . $this->savedFileName;
356
        if (!move_uploaded_file($this->mediaTmpName, $this->savedDestination)) {
0 ignored issues
show
Security introduced by
$this->savedDestination can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. Read from $_FILES, and $_FILES[$media_name] is assigned to $media_name
    in class/class.uploader.php on line 165
  2. $media_name['name'] is assigned to property XoopsMediaUploader::$mediaName
    in class/class.uploader.php on line 167
  3. Read from property XoopsMediaUploader::$mediaName, and Data is passed through strtolower(), and strtolower($this->mediaName) is assigned to property XoopsMediaUploader::$savedFileName
    in class/class.uploader.php on line 353
  4. Read from property XoopsMediaUploader::$savedFileName, and $this->uploadDir . '/' . $this->savedFileName is assigned to property XoopsMediaUploader::$savedDestination
    in class/class.uploader.php on line 355
  5. Read from property XoopsMediaUploader::$savedDestination
    in class/class.uploader.php on line 356

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
357
            return false;
358
        }
359
        @chmod($this->savedDestination, $chmod);
0 ignored issues
show
Security introduced by
$this->savedDestination can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. Read from $_FILES, and $_FILES[$media_name] is assigned to $media_name
    in class/class.uploader.php on line 165
  2. $media_name['name'] is assigned to property XoopsMediaUploader::$mediaName
    in class/class.uploader.php on line 167
  3. Read from property XoopsMediaUploader::$mediaName, and Data is passed through strtolower(), and strtolower($this->mediaName) is assigned to property XoopsMediaUploader::$savedFileName
    in class/class.uploader.php on line 353
  4. Read from property XoopsMediaUploader::$savedFileName, and $this->uploadDir . '/' . $this->savedFileName is assigned to property XoopsMediaUploader::$savedDestination
    in class/class.uploader.php on line 355
  5. Read from property XoopsMediaUploader::$savedDestination
    in class/class.uploader.php on line 359

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
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

359
        /** @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...
360
361
        return true;
362
    }
363
364
    /**
365
     * Is the file the right size?
366
     *
367
     * @return  bool
368
     **/
369
    public function checkMaxFileSize()
370
    {
371
        if ($this->mediaSize > $this->maxFileSize) {
372
            return false;
373
        }
374
375
        return true;
376
    }
377
378
    /**
379
     * Is the picture the right width?
380
     *
381
     * @return  bool
382
     **/
383 View Code Duplication
    public function checkMaxWidth()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
384
    {
385
        if (!isset($this->maxWidth)) {
386
            return true;
387
        }
388
        if (false !== $dimension = getimagesize($this->mediaTmpName)) {
389
            if ($dimension[0] > $this->maxWidth) {
390
                return false;
391
            }
392
            //$result = $dimension[0];
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
393
            //$this->width = $result;
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
394
        } else {
395
            trigger_error(sprintf('Failed fetching image size of %s, skipping max width check..', $this->mediaTmpName), E_USER_WARNING);
396
        }
397
398
        return true;
399
    }
400
401
    /**
402
     * @return mixed
403
     */
404 View Code Duplication
    public function getWidth()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
405
    {
406
        //$filename = $imgloc."/";
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
407
        $filename = $this->uploadDir . '/';
408
        $filename .= $this->savedFileName;
409
        if (false !== $dimension = getimagesize($filename)) {
410
            if ($dimension[0] > 0) {
411
                $result = $dimension[0];
412
            }
413
        } else {
414
            trigger_error(sprintf('Failed fetching image size of %s, image width unknown..', $this->mediaTmpName), E_USER_WARNING);
415
        }
416
417
        return $result;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.
Loading history...
418
    }
419
420
    /**
421
     * @return mixed
422
     */
423 View Code Duplication
    public function getHeight()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
424
    {
425
        //$filename = $imgloc."/";
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
426
        $filename = $this->uploadDir . '/';
427
        $filename .= $this->savedFileName;
428
        if (false !== $dimension = getimagesize($filename)) {
429
            if ($dimension[1] > 0) {
430
                $result = $dimension[1];
431
            }
432
        } else {
433
            trigger_error(sprintf('Failed fetching image size of %s, image height unknown..', $this->mediaTmpName), E_USER_WARNING);
434
        }
435
436
        return $result;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.
Loading history...
437
    }
438
439
    /**
440
     * Is the picture the right height?
441
     *
442
     * @return  bool
443
     **/
444 View Code Duplication
    public function checkMaxHeight()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
445
    {
446
        if (!isset($this->maxHeight)) {
447
            return true;
448
        }
449
        if (false !== $dimension = getimagesize($this->mediaTmpName)) {
450
            if ($dimension[1] > $this->maxHeight) {
451
                return false;
452
            }
453
        } else {
454
            trigger_error(sprintf('Failed fetching image size of %s, skipping max height check..', $this->mediaTmpName), E_USER_WARNING);
455
        }
456
457
        return true;
458
    }
459
460
    /**
461
     * Check whether or not the uploaded file type is allowed
462
     *
463
     * @return  bool
464
     **/
465
    public function checkMimeType()
466
    {
467
        if (empty($this->mediaRealType) && !$this->allowUnknownTypes) {
468
            $this->setErrors('Unknown filetype rejected');
469
470
            return false;
471
        }
472
473
        return (empty($this->allowedMimeTypes) || in_array($this->mediaRealType, $this->allowedMimeTypes));
474
    }
475
476
    /**
477
     * Add an error
478
     *
479
     * @param   string $error
480
     **/
481
    public function setErrors($error)
482
    {
483
        $this->errors[] = trim($error);
484
    }
485
486
    /**
487
     * Get generated errors
488
     *
489
     * @param    bool $ashtml Format using HTML?
490
     *
491
     * @return    array|string    Array of array messages OR HTML string
492
     */
493
    public function &getErrors($ashtml = true)
494
    {
495
        if (!$ashtml) {
496
            return $this->errors;
497
        } else {
498
            $ret = '';
499
            if (count($this->errors) > 0) {
500
                $ret = '<h4>Errors Returned While Uploading</h4>';
501
                foreach ($this->errors as $error) {
502
                    $ret .= $error . '<br>';
503
                }
504
            }
505
506
            return $ret;
507
        }
508
    }
509
}
510