1
|
|
|
<?php
|
2
|
|
|
/**
|
3
|
|
|
* BulletProof
|
4
|
|
|
*
|
5
|
|
|
* A single class PHP-library for secure image uploading.
|
6
|
|
|
*
|
7
|
|
|
* PHP support 5.3+
|
8
|
|
|
*
|
9
|
|
|
* @package BulletProof
|
10
|
|
|
* @version 2.0.2
|
11
|
|
|
* @author https://twitter.com/_samayo
|
12
|
|
|
* @link https://github.com/samayo/bulletproof
|
13
|
|
|
* @license MIT
|
14
|
|
|
*/
|
15
|
|
|
namespace BulletProof;
|
16
|
|
|
|
17
|
|
|
class Image implements \ArrayAccess
|
18
|
|
|
{
|
19
|
|
|
/**
|
20
|
|
|
* @var string The new image name, to be provided or will be generated.
|
21
|
|
|
*/
|
22
|
|
|
protected $name;
|
23
|
|
|
|
24
|
|
|
/**
|
25
|
|
|
* @var int The image width in pixels
|
26
|
|
|
*/
|
27
|
|
|
protected $width;
|
28
|
|
|
|
29
|
|
|
/**
|
30
|
|
|
* @var int The image height in pixels
|
31
|
|
|
*/
|
32
|
|
|
protected $height;
|
33
|
|
|
|
34
|
|
|
/**
|
35
|
|
|
* @var string The image mime type (extension)
|
36
|
|
|
*/
|
37
|
|
|
protected $mime;
|
38
|
|
|
|
39
|
|
|
/**
|
40
|
|
|
* @var string The full image path (dir + image + mime)
|
41
|
|
|
*/
|
42
|
|
|
protected $fullPath;
|
43
|
|
|
|
44
|
|
|
/**
|
45
|
|
|
* @var string The folder or image storage location
|
46
|
|
|
*/
|
47
|
|
|
protected $location;
|
48
|
|
|
|
49
|
|
|
/**
|
50
|
|
|
* @var array A json format of all information about an image
|
51
|
|
|
*/
|
52
|
|
|
protected $serialize = array();
|
53
|
|
|
|
54
|
|
|
/**
|
55
|
|
|
* @var array The min and max image size allowed for upload (in bytes)
|
56
|
|
|
*/
|
57
|
|
|
protected $size = array(100, 50000);
|
58
|
|
|
|
59
|
|
|
/**
|
60
|
|
|
* @var array The max height and width image allowed
|
61
|
|
|
*/
|
62
|
|
|
protected $dimensions = array(500, 5000);
|
63
|
|
|
|
64
|
|
|
/**
|
65
|
|
|
* @var array The mime types allowed for upload
|
66
|
|
|
*/
|
67
|
|
|
protected $mimeTypes = array("jpeg", "png", "gif");
|
68
|
|
|
|
69
|
|
|
/**
|
70
|
|
|
* @var array list of known image types
|
71
|
|
|
*/
|
72
|
|
|
protected $imageMimes = array(
|
73
|
|
|
1 => "gif", "jpeg", "png", "swf", "psd",
|
74
|
|
|
"bmp", "tiff", "tiff", "jpc", "jp2", "jpx",
|
75
|
|
|
"jb2", "swc", "iff", "wbmp", "xbm", "ico"
|
76
|
|
|
);
|
77
|
|
|
|
78
|
|
|
/**
|
79
|
|
|
* @var array storage for the $_FILES global array
|
80
|
|
|
*/
|
81
|
|
|
private $_files = array();
|
82
|
|
|
|
83
|
|
|
/**
|
84
|
|
|
* @var string storage for any errors
|
85
|
|
|
*/
|
86
|
|
|
private $error = "";
|
87
|
|
|
|
88
|
|
|
/**
|
89
|
|
|
* @var array error messages strings
|
90
|
|
|
*/
|
91
|
|
|
private $error_messages = array(
|
92
|
|
|
'upload' => array(
|
93
|
|
|
UPLOAD_ERR_OK => "",
|
94
|
|
|
UPLOAD_ERR_INI_SIZE => "Image is larger than the specified amount set by the server",
|
95
|
|
|
UPLOAD_ERR_FORM_SIZE => "Image is larger than the specified amount specified by browser",
|
96
|
|
|
UPLOAD_ERR_PARTIAL => "Image could not be fully uploaded. Please try again later",
|
97
|
|
|
UPLOAD_ERR_NO_FILE => "Image is not found",
|
98
|
|
|
UPLOAD_ERR_NO_TMP_DIR => "Can't write to disk, due to server configuration ( No tmp dir found )",
|
99
|
|
|
UPLOAD_ERR_CANT_WRITE => "Failed to write file to disk. Please check you file permissions",
|
100
|
|
|
UPLOAD_ERR_EXTENSION => "A PHP extension has halted this file upload process"
|
101
|
|
|
),
|
102
|
|
|
'location' => "Folder %s could not be created",
|
103
|
|
|
'mime_type' => "Invalid File! Only (%s) image types are allowed",
|
104
|
|
|
'file_size' => "Image size should be atleast more than min: %s and less than max: %s kb",
|
105
|
|
|
'dimensions' => "Image height/width should be less than ' %s \ %s ' pixels",
|
106
|
|
|
'too_small' => "Invalid! Image height/width is too small or maybe corrupted",
|
107
|
|
|
'unknown' => "Upload failed, Unknown error occured"
|
108
|
|
|
);
|
109
|
|
|
|
110
|
|
|
/**
|
111
|
|
|
* @param array $_files represents the $_FILES array passed as dependency
|
112
|
|
|
*/
|
113
|
|
|
public function __construct(array $_files = [])
|
114
|
|
|
{
|
115
|
|
|
$this->_files = $_files;
|
116
|
|
|
}
|
117
|
|
|
|
118
|
|
|
/**
|
119
|
|
|
* Gets the real image mime type
|
120
|
|
|
*
|
121
|
|
|
* @param $tmp_name string The upload tmp directory
|
122
|
|
|
*
|
123
|
|
|
* @return bool|string
|
124
|
|
|
*/
|
125
|
|
|
protected function getImageMime($tmp_name)
|
126
|
|
|
{
|
127
|
|
|
if (isset($this->imageMimes[exif_imagetype($tmp_name)])) {
|
128
|
|
|
return $this->imageMimes [exif_imagetype($tmp_name)];
|
129
|
|
|
}
|
130
|
|
|
return false;
|
131
|
|
|
}
|
132
|
|
|
|
133
|
|
|
/**
|
134
|
|
|
* @param mixed $offset
|
135
|
|
|
* @param mixed $value
|
136
|
|
|
*/
|
137
|
|
|
public function offsetSet($offset, $value){}
|
138
|
|
|
|
139
|
|
|
/**
|
140
|
|
|
* @param mixed $offset
|
141
|
|
|
* @return null
|
142
|
|
|
*/
|
143
|
|
|
public function offsetExists($offset){}
|
144
|
|
|
|
145
|
|
|
/**
|
146
|
|
|
* @param mixed $offset
|
147
|
|
|
*/
|
148
|
|
|
public function offsetUnset($offset){}
|
149
|
|
|
|
150
|
|
|
/**
|
151
|
|
|
* Gets array value \ArrayAccess
|
152
|
|
|
*
|
153
|
|
|
* @param mixed $offset
|
154
|
|
|
*
|
155
|
|
|
* @return bool|mixed
|
156
|
|
|
*/
|
157
|
|
|
public function offsetGet($offset)
|
158
|
|
|
{
|
159
|
|
|
if ($offset == "error") {
|
160
|
|
|
return $this->error;
|
161
|
|
|
}
|
162
|
|
|
|
163
|
|
|
if (isset($this->_files[$offset]) && file_exists($this->_files[$offset]["tmp_name"])) {
|
164
|
|
|
$this->_files = $this->_files[$offset];
|
165
|
|
|
return true;
|
166
|
|
|
}
|
167
|
|
|
|
168
|
|
|
return false;
|
169
|
|
|
}
|
170
|
|
|
|
171
|
|
|
|
172
|
|
|
/**
|
173
|
|
|
* Provide image name if not provided
|
174
|
|
|
*
|
175
|
|
|
* @param null $isNameProvided
|
176
|
|
|
* @return $this
|
177
|
|
|
*/
|
178
|
|
|
public function setName($isNameProvided = null)
|
179
|
|
|
{
|
180
|
|
|
if ($isNameProvided) {
|
181
|
|
|
$this->name = filter_var($isNameProvided, FILTER_SANITIZE_STRING);
|
182
|
|
|
}
|
183
|
|
|
|
184
|
|
|
return $this;
|
185
|
|
|
}
|
186
|
|
|
|
187
|
|
|
/**
|
188
|
|
|
* Define a mime type for uploading
|
189
|
|
|
*
|
190
|
|
|
* @param array $fileTypes
|
191
|
|
|
*
|
192
|
|
|
* @return $this
|
193
|
|
|
*/
|
194
|
|
|
public function setMime(array $fileTypes)
|
195
|
|
|
{
|
196
|
|
|
$this->mimeTypes = $fileTypes;
|
197
|
|
|
return $this;
|
198
|
|
|
}
|
199
|
|
|
|
200
|
|
|
/**
|
201
|
|
|
* Define a min and max image size for uploading
|
202
|
|
|
*
|
203
|
|
|
* @param $min int minimum value in bytes
|
204
|
|
|
* @param $max int maximum value in bytes
|
205
|
|
|
*
|
206
|
|
|
* @return $this
|
207
|
|
|
*/
|
208
|
|
|
public function setSize($min, $max)
|
209
|
|
|
{
|
210
|
|
|
$this->size = array($min, $max);
|
211
|
|
|
return $this;
|
212
|
|
|
}
|
213
|
|
|
|
214
|
|
|
/**
|
215
|
|
|
* Creates a location for upload storage
|
216
|
|
|
*
|
217
|
|
|
* @param $dir string the folder name to create
|
218
|
|
|
* @param int $permission chmod permission
|
219
|
|
|
*
|
220
|
|
|
* @return $this
|
221
|
|
|
*/
|
222
|
|
|
public function setLocation($dir = "bulletproof", $permission = 0666)
|
223
|
|
|
{
|
224
|
|
|
if (!file_exists($dir) && !is_dir($dir) && !$this->location) {
|
225
|
|
|
$createFolder = @mkdir("" . $dir, (int) $permission, true);
|
226
|
|
|
if (!$createFolder) {
|
227
|
|
|
$this->error = sprintf($this->error_messages['location'], $dir);
|
228
|
|
|
return;
|
229
|
|
|
}
|
230
|
|
|
}
|
231
|
|
|
|
232
|
|
|
$this->location = $dir;
|
233
|
|
|
return $this;
|
234
|
|
|
}
|
235
|
|
|
|
236
|
|
|
/**
|
237
|
|
|
* Sets acceptable max image height and width
|
238
|
|
|
*
|
239
|
|
|
* @param $maxWidth int max width value
|
240
|
|
|
* @param $maxHeight int max height value
|
241
|
|
|
*
|
242
|
|
|
* @return $this
|
243
|
|
|
*/
|
244
|
|
|
public function setDimension($maxWidth, $maxHeight)
|
245
|
|
|
{
|
246
|
|
|
$this->dimensions = array($maxWidth, $maxHeight);
|
247
|
|
|
return $this;
|
248
|
|
|
}
|
249
|
|
|
|
250
|
|
|
/**
|
251
|
|
|
* Replace error_messages array values with values of given array
|
252
|
|
|
*
|
253
|
|
|
* @param $new_error_messages array Array containing new error messages
|
254
|
|
|
*
|
255
|
|
|
* @return $this
|
256
|
|
|
*/
|
257
|
|
|
public function setErrorMessages($new_error_messages)
|
258
|
|
|
{
|
259
|
|
|
if($new_array = array_replace_recursive($this->error_messages, $new_error_messages)) {
|
260
|
|
|
$this->error_messages = $new_array;
|
261
|
|
|
};
|
262
|
|
|
return $this;
|
263
|
|
|
}
|
264
|
|
|
|
265
|
|
|
/**
|
266
|
|
|
* Returns the image name
|
267
|
|
|
*
|
268
|
|
|
* @return string
|
269
|
|
|
*/
|
270
|
|
|
public function getName()
|
271
|
|
|
{
|
272
|
|
|
if (!$this->name) {
|
273
|
|
|
return uniqid(true) . "_" . str_shuffle(implode(range("e", "q")));
|
274
|
|
|
}
|
275
|
|
|
|
276
|
|
|
return $this->name;
|
277
|
|
|
}
|
278
|
|
|
|
279
|
|
|
/**
|
280
|
|
|
* Returns the full path of the image ex "location/image.mime"
|
281
|
|
|
*
|
282
|
|
|
* @return string
|
283
|
|
|
*/
|
284
|
|
|
public function getFullPath()
|
285
|
|
|
{
|
286
|
|
|
$this->fullPath = $this->location . "/" . $this->name . "." . $this->mime;
|
287
|
|
|
return $this->fullPath;
|
288
|
|
|
}
|
289
|
|
|
|
290
|
|
|
/**
|
291
|
|
|
* Returns the image size in bytes
|
292
|
|
|
*
|
293
|
|
|
* @return int
|
294
|
|
|
*/
|
295
|
|
|
public function getSize()
|
296
|
|
|
{
|
297
|
|
|
return (int) $this->_files["size"];
|
298
|
|
|
}
|
299
|
|
|
|
300
|
|
|
/**
|
301
|
|
|
* Returns the image height in pixels
|
302
|
|
|
*
|
303
|
|
|
* @return int
|
304
|
|
|
*/
|
305
|
|
|
public function getHeight()
|
306
|
|
|
{
|
307
|
|
|
if ($this->height != null) {
|
308
|
|
|
return $this->height;
|
309
|
|
|
}
|
310
|
|
|
|
311
|
|
|
list(, $height) = getImageSize($this->_files["tmp_name"]);
|
312
|
|
|
return $height;
|
313
|
|
|
}
|
314
|
|
|
|
315
|
|
|
/**
|
316
|
|
|
* Returns the image width
|
317
|
|
|
*
|
318
|
|
|
* @return int
|
319
|
|
|
*/
|
320
|
|
|
public function getWidth()
|
321
|
|
|
{
|
322
|
|
|
if ($this->width != null) {
|
323
|
|
|
return $this->width;
|
324
|
|
|
}
|
325
|
|
|
|
326
|
|
|
list($width) = getImageSize($this->_files["tmp_name"]);
|
327
|
|
|
return $width;
|
328
|
|
|
}
|
329
|
|
|
|
330
|
|
|
/**
|
331
|
|
|
* Returns the storage / folder name
|
332
|
|
|
*
|
333
|
|
|
* @return string
|
334
|
|
|
*/
|
335
|
|
|
public function getLocation()
|
336
|
|
|
{
|
337
|
|
|
if(!$this->location){
|
338
|
|
|
$this->setLocation();
|
339
|
|
|
}
|
340
|
|
|
|
341
|
|
|
return $this->location;
|
342
|
|
|
}
|
343
|
|
|
|
344
|
|
|
/**
|
345
|
|
|
* Returns a JSON format of the image width, height, name, mime ...
|
346
|
|
|
*
|
347
|
|
|
* @return string
|
348
|
|
|
*/
|
349
|
|
|
public function getJson()
|
350
|
|
|
{
|
351
|
|
|
return json_encode($this->serialize);
|
352
|
|
|
}
|
353
|
|
|
|
354
|
|
|
/**
|
355
|
|
|
* Returns the image mime type
|
356
|
|
|
*
|
357
|
|
|
* @return string
|
358
|
|
|
*/
|
359
|
|
|
public function getMime()
|
360
|
|
|
{
|
361
|
|
|
return $this->mime;
|
362
|
|
|
}
|
363
|
|
|
|
364
|
|
|
/**
|
365
|
|
|
* Returns error string or false if no errors occurred
|
366
|
|
|
*
|
367
|
|
|
* @return string|bool
|
368
|
|
|
*/
|
369
|
|
|
public function getError(){
|
370
|
|
|
return $this->error != "" ? $this->error : false;
|
371
|
|
|
}
|
372
|
|
|
|
373
|
|
|
/**
|
374
|
|
|
* Checks for the common upload errors
|
375
|
|
|
*
|
376
|
|
|
* @param $e int error constant
|
377
|
|
|
*/
|
378
|
|
|
protected function uploadErrors($e)
|
379
|
|
|
{
|
380
|
|
|
$errors = $this->error_messages['upload'];
|
381
|
|
|
return $errors[$e];
|
382
|
|
|
}
|
383
|
|
|
|
384
|
|
|
|
385
|
|
|
/**
|
386
|
|
|
* This methods validates and uploads the image
|
387
|
|
|
* @return bool|Image|null
|
388
|
|
|
* @throws ImageUploaderException
|
389
|
|
|
*/
|
390
|
|
|
public function upload()
|
391
|
|
|
{
|
392
|
|
|
/* modify variable names for convenience */
|
393
|
|
|
$image = $this;
|
394
|
|
|
$files = $this->_files;
|
395
|
|
|
|
396
|
|
|
/* check if php_exif is enabled */
|
397
|
|
|
if(!function_exists('exif_imagetype')){
|
398
|
|
|
$image->error = "Function 'exif_imagetype' Not found. Please enable \"php_exif\" in your PHP.ini";
|
399
|
|
|
return null;
|
400
|
|
|
}
|
401
|
|
|
|
402
|
|
|
/* initialize image properties */
|
403
|
|
|
$image->name = $image->getName();
|
404
|
|
|
$image->width = $image->getWidth();
|
405
|
|
|
$image->height = $image->getHeight();
|
406
|
|
|
$image->location = $image->getLocation();
|
407
|
|
|
|
408
|
|
|
/* get image sizes */
|
409
|
|
|
list($minSize, $maxSize) = $image->size;
|
410
|
|
|
|
411
|
|
|
/* check for common upload errors */
|
412
|
|
|
if($image->error = $image->uploadErrors($files["error"])){
|
413
|
|
|
return null;
|
414
|
|
|
}
|
415
|
|
|
|
416
|
|
|
/* check image for valid mime types and return mime */
|
417
|
|
|
$image->mime = $image->getImageMime($files["tmp_name"]);
|
|
|
|
|
418
|
|
|
|
419
|
|
|
/* validate image mime type */
|
420
|
|
|
if (!in_array($image->mime, $image->mimeTypes)) {
|
421
|
|
|
$ext = implode(", ", $image->mimeTypes);
|
422
|
|
|
$image->error = sprintf($this->error_messages['mime_type'], $ext);
|
423
|
|
|
return null;
|
424
|
|
|
}
|
425
|
|
|
|
426
|
|
|
/* check image size based on the settings */
|
427
|
|
|
if ($files["size"] < $minSize || $files["size"] > $maxSize) {
|
428
|
|
|
$min = intval($minSize / 1000) ?: 1; $max = intval($maxSize / 1000);
|
429
|
|
|
|
430
|
|
|
$image->error = sprintf($this->error_messages['file_size'], $min, $max);
|
431
|
|
|
return null;
|
432
|
|
|
}
|
433
|
|
|
|
434
|
|
|
/* check image dimension */
|
435
|
|
|
list($allowedWidth, $allowedHeight) = $image->dimensions;
|
436
|
|
|
|
437
|
|
|
if ($image->height > $allowedHeight || $image->width > $allowedWidth) {
|
438
|
|
|
$image->error = sprintf($this->error_messages['dimensions'], $allowedHeight, $allowedWidth);
|
439
|
|
|
return null;
|
440
|
|
|
}
|
441
|
|
|
|
442
|
|
|
if($image->height < 4 || $image->width < 4){
|
443
|
|
|
$image->error = $this->error_messages['too_small'];
|
444
|
|
|
return null;
|
445
|
|
|
}
|
446
|
|
|
|
447
|
|
|
/* set and get folder name */
|
448
|
|
|
$image->fullPath = $image->location. "/" . $image->name . "." . $image->mime;
|
449
|
|
|
|
450
|
|
|
/* gather image info for json storage */
|
451
|
|
|
$image->serialize = array(
|
452
|
|
|
"name" => $image->name,
|
453
|
|
|
"mime" => $image->mime,
|
454
|
|
|
"height" => $image->height,
|
455
|
|
|
"width" => $image->width,
|
456
|
|
|
"size" => $files["size"],
|
457
|
|
|
"location" => $image->location,
|
458
|
|
|
"fullpath" => $image->fullPath
|
459
|
|
|
);
|
460
|
|
|
|
461
|
|
|
if ($image->error === "") {
|
462
|
|
|
$moveUpload = $image->moveUploadedFile($files["tmp_name"], $image->fullPath);
|
463
|
|
|
if (false !== $moveUpload) {
|
464
|
|
|
return $image;
|
465
|
|
|
}
|
466
|
|
|
}
|
467
|
|
|
|
468
|
|
|
$image->error = $this->error_messages['unknown'];
|
469
|
|
|
return false;
|
470
|
|
|
}
|
471
|
|
|
|
472
|
|
|
/**
|
473
|
|
|
* Final upload method to be called, isolated for testing purposes
|
474
|
|
|
*
|
475
|
|
|
* @param $tmp_name int the temporary location of the image file
|
476
|
|
|
* @param $destination int upload destination
|
477
|
|
|
*
|
478
|
|
|
* @return bool
|
479
|
|
|
*/
|
480
|
|
|
public function moveUploadedFile($tmp_name, $destination)
|
481
|
|
|
{
|
482
|
|
|
return move_uploaded_file($tmp_name, $destination);
|
483
|
|
|
}
|
484
|
|
|
}
|
485
|
|
|
|
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 theid
property of an instance of theAccount
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.