Completed
Push — master ( b0b8e5...179439 )
by samayo
03:37
created

Image::uploadErrors()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * BulletProof
4
 *
5
 * A single-file PHP library for uploading images with security
6
 *
7
 * PHP support 5.3+
8
 *
9
 * @package     BulletProof
10
 * @version     3.0.0
11
 * @author      https://twitter.com/_samayo
12
 * @link        https://github.com/samayo/bulletproof
13
 * @license     MIT
14
 */
15
namespace Bulletproof;
16
class BulletproofException extends \Exception {}
17
18
19
20
class Image implements \ArrayAccess, \Serializable
0 ignored issues
show
Bug introduced by
There is at least one abstract method in this class. Maybe declare it as abstract, or implement the remaining methods: serialize, unserialize
Loading history...
21
{
22
    /**
23
     * @var string The new image name, to be provided or will be generated.
24
     */
25
    protected $name;
26
27
    /**
28
     * @var int The image width in pixels
29
     */
30
    protected $width;
31
32
    /**
33
     * @var int The image height in pixels
34
     */
35
    protected $height;
36
37
    /**
38
     * @var string The image mime type (extension)
39
     */
40
    protected $mime;
41
42
    /**
43
     * @var string The full image path (dir + image + mime)
44
     */
45
    protected $fullPath;
46
47
    /**
48
     * @var string The folder or image storage location
49
     */
50
    protected $location;
51
52
    /**
53
     * @var array A json format of all information about an image
54
     */
55
    protected $serialize = array();
56
57
    /**
58
     * @var array The min and max image size allowed for upload (in bytes)
59
     */
60
    protected $size = array(100, 50000);
61
62
    /**
63
     * @var array The max height and width image allowed
64
     */
65
    protected $dimensions = array(500, 5000);
66
67
    /**
68
     * @var array The mime types allowed for upload
69
     */
70
    protected $mimeTypes = array("jpeg", "png", "gif");
71
72
    /**
73
     * @var array list of known image types
74
     */
75
    protected $imageMimes = array(
76
        1 => "gif", "jpeg", "png", "swf", "psd",
77
        "bmp", "tiff", "tiff", "jpc", "jp2", "jpx",
78
        "jb2", "swc", "iff", "wbmp", "xbm", "ico"
79
    );
80
81
    /**
82
     * @var array storage for the $_FILES global array
83
     */
84
    private $_files = array();
85
86
	/**
87
     * @var array error messages strings
88
     */
89
    protected $commonErrors = array(
90
        UPLOAD_ERR_OK           => "",
91
        UPLOAD_ERR_INI_SIZE     => "Image is larger than the specified amount set by the server",
92
        UPLOAD_ERR_FORM_SIZE    => "Image is larger than the specified amount specified by browser",
93
        UPLOAD_ERR_PARTIAL      => "Image could not be fully uploaded. Please try again later",
94
        UPLOAD_ERR_NO_FILE      => "Image is not found",
95
        UPLOAD_ERR_NO_TMP_DIR   => "Can't write to disk, due to server configuration ( No tmp dir found )",
96
        UPLOAD_ERR_CANT_WRITE   => "Failed to write file to disk. Please check you file permissions",
97
        UPLOAD_ERR_EXTENSION    => "A PHP extension has halted this file upload process"
98
	);
99
100
    /**
101
     * Constructor
102
     * 
103
     * @param array $_files represents the $_FILES array passed as dependency
104
     */
105
    public function __construct(array $_files = [])
106
    {
107
        $this->_files = $_files;
108
    }
109
110
    /**
111
     * Gets the real image mime type
112
     *
113
     * @param $tmp_name string The upload tmp directory
114
     *
115
     * @return bool|string
116
     */
117
    protected function getImageMime($tmp_name)
118
    {
119
        if (isset($this->imageMimes[exif_imagetype($tmp_name)])) {
120
            return $this->imageMimes [exif_imagetype($tmp_name)];
121
        }
122
       
123
       throw new BulletproofException(" Unable to get mime type of $tmp_name ");
124
    }
125
126
    /**
127
     * @param mixed $offset
128
     * @param mixed $value
129
     */
130
    public function offsetSet($offset, $value){}
131
132
    /**
133
     * @param mixed $offset
134
     * @return null
135
     */
136
    public function offsetExists($offset){}
137
138
    /**
139
     * @param mixed $offset
140
     */
141
    public function offsetUnset($offset){}
142
143
    /**
144
     * Gets array value \ArrayAccess
145
     *
146
     * @param mixed $offset
147
     *
148
     * @return bool|mixed
149
     */
150
    public function offsetGet($offset)
151
    {
152
        if (isset($this->_files[$offset]) && file_exists($this->_files[$offset]["tmp_name"])) {
153
            $this->_files = $this->_files[$offset];
154
            return true;
155
        }
156
        
157
        throw new BulletProofException("Upload name $offset not found");
158
    }
159
160
161
    /**
162
     * Provide image name if not provided
163
     *
164
     * @param null $name
165
     * @return $this
166
     */
167
    public function setName($name = null)
168
    {
169
        if ($name) {
170
            $this->name = filter_var($name, FILTER_SANITIZE_STRING);
171
        }
172
        
173
        return $this;
174
    }
175
176
    /**
177
     * Define a mime type for uploading
178
     *
179
     * @param array $fileTypes
180
     *
181
     * @return $this
182
     */
183
    public function setMime(array $fileTypes)
184
    {
185
        $this->mimeTypes = $fileTypes;
186
        return $this;
187
    }
188
189
    /**
190
     * Define a min and max image size for uploading
191
     *
192
     * @param $min int minimum value in bytes
193
     * @param $max int maximum value in bytes
194
     *
195
     * @return $this
196
     */
197
    public function setSize($min, $max)
198
    {
199
        $this->size = array($min, $max);
200
        return $this;
201
    }
202
203
    /**
204
     * Creates a location for upload storage
205
     *
206
     * @param $dir string the folder name to create
207
     * @param int $permission chmod permission
208
     *
209
     * @return $this
210
     */
211
    public function setLocation($dir = "Uploads", $permission = 0666)
212
    {
213
        if (!file_exists($dir) && !is_dir($dir) && !$this->location) {
214
            $createFolder = @mkdir("" . $dir, (int) $permission, true);
215
            if (!$createFolder) {
216
                throw new BulletproofException("Unable to create folder name: $dir in " . __DIR__);
217
                return;
0 ignored issues
show
Unused Code introduced by
return; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
218
            }
219
        }
220
221
        $this->location = $dir;
222
        return $this;
223
    }
224
225
    /**
226
     * Sets acceptable max image height and width
227
     *
228
     * @param $maxWidth int max width value
229
     * @param $maxHeight int max height value
230
     *
231
     * @return $this
232
     */
233
    public function setDimension($maxWidth, $maxHeight)
234
    {
235
        $this->dimensions = array($maxWidth, $maxHeight);
236
        return $this;
237
    }
238
239
    /**
240
     * Returns the image name
241
     *
242
     * @return string
243
     */
244
    public function getName()
245
    {
246
        if (!$this->name) {
247
           return  uniqid(true) . "_" . str_shuffle(implode(range("e", "q")));
248
        }
249
250
        return $this->name;
251
    }
252
253
    /**
254
     * Returns the full path of the image ex "location/image.mime"
255
     *
256
     * @return string
257
     */
258
    public function getFullPath()
259
    {
260
        $this->fullPath = $this->location . "/" . $this->name . "." . $this->mime;
261
        return $this->fullPath;
262
    }
263
264
    /**
265
     * Returns the image size in bytes
266
     *
267
     * @return int
268
     */
269
    public function getSize()
270
    {
271
        return (int) $this->_files["size"];
272
    }
273
274
    /**
275
     * Returns the image height in pixels
276
     *
277
     * @return int
278
     */
279
    public function getHeight()
280
    {
281
        if ($this->height != null) {
282
            return $this->height;
283
        }
284
285
        list(, $height) = getImageSize($this->_files["tmp_name"]); 
286
        return $height;
287
    }
288
289
    /**
290
     * Returns the image width
291
     *
292
     * @return int
293
     */
294
    public function getWidth()
295
    {
296
        if ($this->width != null) {
297
            return $this->width;
298
        }
299
300
        list($width) = getImageSize($this->_files["tmp_name"]); 
301
        return $width;
302
    }
303
304
    /**
305
     * Returns the storage / folder name
306
     *
307
     * @return string
308
     */
309
    public function getLocation()
310
    {
311
        if(!$this->location){
312
            $this->setLocation(); 
313
        }
314
315
        return $this->location; 
316
    }
317
318
    /**
319
     * Returns a JSON format of the image width, height, name, mime ...
320
     *
321
     * @return string
322
     */
323
    public function getJson()
324
    {
325
        return json_encode($this->serialize);
326
    }
327
328
    /**
329
     * Returns the image mime type
330
     *
331
     * @return string
332
     */
333
    public function getMime()
334
    {
335
        return $this->mime;
336
    }
337
338
    /**
339
     * This methods validates and uploads the image
340
     *
341
     * @return bool|Image|null
342
     *
343
     * @throws BulletproofException
344
     */
345
    public function upload()
346
    {
347
        /* modify variable names for convenience */
348
        $image = $this;
349
        $files = $this->_files;
350
351
        /* check if required php extension is found */
352
        if(!function_exists('exif_imagetype')){
353
            throw new BulletproofException("Function 'exif_imagetype' Not found. Please enable \"php_exif\" in your PHP.ini");
354
        }
355
356
        /* initialize image properties */
357
        $image->name     = $image->getName();
358
        $image->width    = $image->getWidth();
359
        $image->height   = $image->getHeight(); 
360
        $image->location = $image->getLocation();
361
        list($minSize, $maxSize) = $image->size;
362
363
        /* check for common upload errors first */
364
        if($error = $this->commonErrors[$files["error"]]){
365
           throw new BulletproofException($error);
366
        }
367
368
        /* check image for valid mime types and return mime */
369
        $image->mime = $image->getImageMime($files["tmp_name"]);
0 ignored issues
show
Documentation Bug introduced by
It seems like $image->getImageMime($files['tmp_name']) can also be of type boolean. However, the property $mime is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
370
371
        /* validate image mime type */
372
        if (!in_array($image->mime, $image->mimeTypes)) {
373
            $ext = implode(", ", $image->mimeTypes);
374
            throw new BulletproofException("invalid mime type, please upload images of only ($ext) types");
375
        }
376
377
        /* check image size based on the settings */
378
        if ($files["size"] < $minSize || $files["size"] > $maxSize) {
379
            $min = intval($minSize / 1000) ?: 1; $max = intval($maxSize / 1000);
0 ignored issues
show
Unused Code introduced by
$min is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
Unused Code introduced by
$max is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
380
381
           throw new BulletproofException("invalid image size");
382
        }
383
384
        /* check image dimension */
385
        list($allowedWidth, $allowedHeight) = $image->dimensions;
386
387
        if ($image->height > $allowedHeight || $image->width > $allowedWidth) {
388
            throw new  BulletproofException("invalid image width/height");
389
        }
390
391
        if($image->height < 4 || $image->width < 4){
392
            throw new BulletproofException("invalid image width/height");
393
            
394
        }
395
 
396
        /* set and get folder name */
397
        $image->fullPath = $image->location. "/" . $image->name . "." . $image->mime;
398
399
        /* gather image info for json storage */ 
400
        $image->serialize = array(
401
            "name"     => $image->name,
402
            "mime"     => $image->mime,
403
            "height"   => $image->height,
404
            "width"    => $image->width,
405
            "size"     => $files["size"],
406
            "location" => $image->location,
407
            "fullpath" => $image->fullPath
408
        );
409
410
        $moveUpload = $image->moveUploadedFile($files["tmp_name"], $image->fullPath);
411
412
        if (false === $moveUpload) {
413
            throw new Exception("Unknown error during image upload");
414
        }
415
416
        return $image;
417
        
418
    }
419
420
    /**
421
     * Final upload method to be called, isolated for testing purposes
422
     *
423
     * @param $tmp_name int the temporary location of the image file
424
     * @param $destination int upload destination
425
     *
426
     * @return bool
427
     */
428
    public function moveUploadedFile($tmp_name, $destination)
429
    {
430
        return move_uploaded_file($tmp_name, $destination);
431
    }
432
}
433