@@ -38,6 +38,10 @@ class UploadHandler
38
38
'image_resize ' => 'Failed to resize image '
39
39
);
40
40
41
+ const IMAGETYPE_GIF = 1 ;
42
+ const IMAGETYPE_JPEG = 2 ;
43
+ const IMAGETYPE_PNG = 3 ;
44
+
41
45
protected $ image_objects = array ();
42
46
43
47
public function __construct ($ options = null , $ initialize = true , $ error_messages = null ) {
@@ -48,7 +52,7 @@ public function __construct($options = null, $initialize = true, $error_messages
48
52
'upload_url ' => $ this ->get_full_url ().'/files/ ' ,
49
53
'input_stream ' => 'php://input ' ,
50
54
'user_dirs ' => false ,
51
- 'mkdir_mode ' => 0777 ,
55
+ 'mkdir_mode ' => 0755 ,
52
56
'param_name ' => 'files ' ,
53
57
// Set the following option to 'POST', if your server does not support
54
58
// DELETE requests. This is a parameter sent to the client:
@@ -86,21 +90,36 @@ public function __construct($options = null, $initialize = true, $error_messages
86
90
'download_via_php ' => false ,
87
91
// Read files in chunks to avoid memory limits when download_via_php
88
92
// is enabled, set to 0 to disable chunked reading of files:
89
- 'readfile_chunk_size ' => 2 * 1024 * 1024 , // 10 MiB
93
+ 'readfile_chunk_size ' => 10 * 1024 * 1024 , // 10 MiB
90
94
// Defines which files can be displayed inline when downloaded:
91
95
'inline_file_types ' => '/\.(gif|jpe?g|png)$/i ' ,
92
- // Defines which files (based on their names) are accepted for upload:
93
- 'accept_file_types ' => '/.+$/i ' ,
96
+ // Defines which files (based on their names) are accepted for upload.
97
+ // By default, only allows file uploads with image file extensions.
98
+ // Only change this setting after making sure that any allowed file
99
+ // types cannot be executed by the webserver in the files directory,
100
+ // e.g. PHP scripts, nor executed by the browser when downloaded,
101
+ // e.g. HTML files with embedded JavaScript code.
102
+ // Please also read the SECURITY.md document in this repository.
103
+ 'accept_file_types ' => '/\.(gif|jpe?g|png)$/i ' ,
104
+ // Replaces dots in filenames with the given string.
105
+ // Can be disabled by setting it to false or an empty string.
106
+ // Note that this is a security feature for servers that support
107
+ // multiple file extensions, e.g. the Apache AddHandler Directive:
108
+ // https://httpd.apache.org/docs/current/mod/mod_mime.html#addhandler
109
+ // Before disabling it, make sure that files uploaded with multiple
110
+ // extensions cannot be executed by the webserver, e.g.
111
+ // "example.php.png" with embedded PHP code, nor executed by the
112
+ // browser when downloaded, e.g. "example.html.gif" with embedded
113
+ // JavaScript code.
114
+ 'replace_dots_in_filenames ' => '- ' ,
94
115
// The php.ini settings upload_max_filesize and post_max_size
95
116
// take precedence over the following max_file_size setting:
96
117
'max_file_size ' => null ,
97
118
'min_file_size ' => 1 ,
98
119
// The maximum number of files for the upload directory:
99
120
'max_number_of_files ' => null ,
100
- // Defines which files are handled as image files:
101
- 'image_file_types ' => '/\.(gif|jpe?g|png)$/i ' ,
102
- // Use exif_imagetype on all files to correct file extensions:
103
- 'correct_image_extensions ' => true ,
121
+ // Reads first file bytes to identify and correct file extensions:
122
+ 'correct_image_extensions ' => false ,
104
123
// Image resolution restrictions:
105
124
'max_width ' => null ,
106
125
'max_height ' => null ,
@@ -131,33 +150,40 @@ public function __construct($options = null, $initialize = true, $error_messages
131
150
// Command or path for to the ImageMagick identify binary:
132
151
'identify_bin ' => 'identify ' ,
133
152
'image_versions ' => array (
134
- // The empty image version key defines options for the original image:
135
- // Keep in mind that these options are inherited by all other image versions!
153
+ // The empty image version key defines options for the original image.
154
+ // Keep in mind: these image manipulations are inherited by all other image versions from this point onwards.
155
+ // Also note that the property 'no_cache' is not inherited, since it's not a manipulation.
136
156
'' => array (
137
157
// Automatically rotate images based on EXIF meta data:
138
158
'auto_orient ' => true
139
159
),
140
- // Uncomment the following to create medium sized images:
160
+ // You can add arrays to generate different versions.
161
+ // The name of the key is the name of the version (example: 'medium').
162
+ // the array contains the options to apply.
141
163
/*
142
164
'medium' => array(
143
165
'max_width' => 800,
144
166
'max_height' => 600
145
167
),
168
+ */
146
169
'thumbnail ' => array (
147
170
// Uncomment the following to use a defined directory for the thumbnails
148
171
// instead of a subdirectory based on the version identifier.
149
172
// Make sure that this directory doesn't allow execution of files if you
150
173
// don't pose any restrictions on the type of uploaded files, e.g. by
151
174
// copying the .htaccess file from the files directory for Apache:
152
175
//'upload_dir' => dirname($this->get_server_var('SCRIPT_FILENAME')).'/thumb/',
153
- 'upload_url' => $this->get_full_url().'/thumb/',
176
+ // 'upload_url' => $this->get_full_url().'/thumb/',
154
177
// Uncomment the following to force the max
155
178
// dimensions and e.g. create square thumbnails:
156
- 'crop' => true,
157
- 'max_width' => 80,
158
- 'max_height' => 80
179
+ // 'auto_orient' => true,
180
+ // 'crop' => true,
181
+ // 'jpeg_quality' => 70,
182
+ // 'no_cache' => true, (there's a caching option, but this remembers thumbnail sizes from a previous action!)
183
+ // 'strip' => true, (this strips EXIF tags, such as geolocation)
184
+ 'max_width ' => 80 , // either specify width, or set to 0. Then width is automatically adjusted - keeping aspect ratio to a specified max_height.
185
+ 'max_height ' => 80 // either specify height, or set to 0. Then height is automatically adjusted - keeping aspect ratio to a specified max_width.
159
186
)
160
- */
161
187
),
162
188
'print_response ' => true
163
189
);
@@ -380,10 +406,6 @@ protected function validate($uploaded_file, $file, $error, $index) {
380
406
$ file ->error = $ this ->get_error_message ('accept_file_types ' );
381
407
return false ;
382
408
}
383
- if (preg_match ($ this ->options ['image_file_types ' ], $ file ->name ) && function_exists ('exif_imagetype ' ) && !@exif_imagetype ($ uploaded_file )) {
384
- $ file ->error = $ this ->get_error_message ('accept_file_types ' );
385
- return false ;
386
- }
387
409
if ($ uploaded_file && is_uploaded_file ($ uploaded_file )) {
388
410
$ file_size = $ this ->get_file_size ($ uploaded_file );
389
411
} else {
@@ -413,9 +435,8 @@ protected function validate($uploaded_file, $file, $error, $index) {
413
435
$ min_width = @$ this ->options ['min_width ' ];
414
436
$ min_height = @$ this ->options ['min_height ' ];
415
437
if (($ max_width || $ max_height || $ min_width || $ min_height )
416
- && preg_match ( $ this ->options [ ' image_file_types ' ], $ file -> name )) {
438
+ && $ this ->is_valid_image_file ( $ uploaded_file )) {
417
439
list ($ img_width , $ img_height ) = $ this ->get_image_size ($ uploaded_file );
418
-
419
440
// If we are auto rotating the image by default, do the checks on
420
441
// the correct orientation
421
442
if (
@@ -429,7 +450,6 @@ function_exists('exif_read_data') &&
429
450
$ img_height = $ tmp ;
430
451
unset($ tmp );
431
452
}
432
-
433
453
}
434
454
if (!empty ($ img_width )) {
435
455
if ($ max_width && $ img_width > $ max_width ) {
@@ -491,16 +511,15 @@ protected function fix_file_extension($file_path, $name, $size, $type, $error,
491
511
preg_match ('/^image\/(gif|jpe?g|png)/ ' , $ type , $ matches )) {
492
512
$ name .= '. ' .$ matches [1 ];
493
513
}
494
- if ($ this ->options ['correct_image_extensions ' ] &&
495
- function_exists ('exif_imagetype ' )) {
496
- switch (@exif_imagetype ($ file_path )){
497
- case IMAGETYPE_JPEG :
514
+ if ($ this ->options ['correct_image_extensions ' ]) {
515
+ switch ($ this ->imagetype ($ file_path )) {
516
+ case self ::IMAGETYPE_JPEG :
498
517
$ extensions = array ('jpg ' , 'jpeg ' );
499
518
break ;
500
- case IMAGETYPE_PNG :
519
+ case self :: IMAGETYPE_PNG :
501
520
$ extensions = array ('png ' );
502
521
break ;
503
- case IMAGETYPE_GIF :
522
+ case self :: IMAGETYPE_GIF :
504
523
$ extensions = array ('gif ' );
505
524
break ;
506
525
}
@@ -523,7 +542,17 @@ protected function trim_file_name($file_path, $name, $size, $type, $error,
523
542
// Remove path information and dots around the filename, to prevent uploading
524
543
// into different directories or replacing hidden system files.
525
544
// Also remove control characters and spaces (\x00..\x20) around the filename:
526
- $ name = trim ($ this ->basename (stripslashes ($ name )), "\x00.. \x20" );
545
+ $ name = trim ($ this ->basename (stripslashes ($ name )), ". \x00.. \x20" );
546
+ // Replace dots in filenames to avoid security issues with servers
547
+ // that interpret multiple file extensions, e.g. "example.php.png":
548
+ $ replacement = $ this ->options ['replace_dots_in_filenames ' ];
549
+ if (!empty ($ replacement )) {
550
+ $ parts = explode ('. ' , $ name );
551
+ if (count ($ parts ) > 2 ) {
552
+ $ ext = array_pop ($ parts );
553
+ $ name = implode ($ replacement , $ parts ).'. ' .$ ext ;
554
+ }
555
+ }
527
556
// Use a timestamp for empty filenames:
528
557
if (!$ name ) {
529
558
$ name = str_replace ('. ' , '- ' , microtime (true ));
@@ -867,26 +896,32 @@ protected function imagick_create_scaled_image($file_name, $version, $options) {
867
896
$ image_oriented = false ;
868
897
if (!empty ($ options ['auto_orient ' ])) {
869
898
$ image_oriented = $ this ->imagick_orient_image ($ image );
870
- }
899
+ }
900
+
901
+ $ image_resize = false ;
871
902
$ new_width = $ max_width = $ img_width = $ image ->getImageWidth ();
872
- $ new_height = $ max_height = $ img_height = $ image ->getImageHeight ();
873
- if (!empty ($ options ['max_width ' ])) {
874
- $ new_width = $ max_width = $ options ['max_width ' ];
875
- }
876
- if (!empty ($ options ['max_height ' ])) {
903
+ $ new_height = $ max_height = $ img_height = $ image ->getImageHeight ();
904
+
905
+ // use isset(). User might be setting max_width = 0 (auto in regular resizing). Value 0 would be considered empty when you use empty()
906
+ if (isset ($ options ['max_width ' ])) {
907
+ $ image_resize = true ;
908
+ $ new_width = $ max_width = $ options ['max_width ' ];
909
+ }
910
+ if (isset ($ options ['max_height ' ])) {
911
+ $ image_resize = true ;
877
912
$ new_height = $ max_height = $ options ['max_height ' ];
878
913
}
879
- $ image_strip = false ;
880
- if ( !empty ($ options ["strip " ]) ) {
881
- $ image_strip = $ options ["strip " ];
882
- }
883
- if ( !$ image_oriented && ($ max_width >= $ img_width ) && ($ max_height >= $ img_height ) && !$ image_strip && empty ($ options ["jpeg_quality " ]) ) {
914
+
915
+ $ image_strip = (isset ($ options ['strip ' ]) ? $ options ['strip ' ] : false );
916
+
917
+ if ( !$ image_oriented && ($ max_width >= $ img_width ) && ($ max_height >= $ img_height ) && !$ image_strip && empty ($ options ["jpeg_quality " ]) ) {
884
918
if ($ file_path !== $ new_file_path ) {
885
919
return copy ($ file_path , $ new_file_path );
886
920
}
887
921
return true ;
888
922
}
889
- $ crop = !empty ($ options ['crop ' ]);
923
+ $ crop = (isset ($ options ['crop ' ]) ? $ options ['crop ' ] : false );
924
+
890
925
if ($ crop ) {
891
926
$ x = 0 ;
892
927
$ y = 0 ;
@@ -1012,13 +1047,18 @@ protected function get_image_size($file_path) {
1012
1047
}
1013
1048
1014
1049
protected function create_scaled_image ($ file_name , $ version , $ options ) {
1015
- if ($ this ->options ['image_library ' ] === 2 ) {
1016
- return $ this ->imagemagick_create_scaled_image ($ file_name , $ version , $ options );
1017
- }
1018
- if ($ this ->options ['image_library ' ] && extension_loaded ('imagick ' )) {
1019
- return $ this ->imagick_create_scaled_image ($ file_name , $ version , $ options );
1050
+ try {
1051
+ if ($ this ->options ['image_library ' ] === 2 ) {
1052
+ return $ this ->imagemagick_create_scaled_image ($ file_name , $ version , $ options );
1053
+ }
1054
+ if ($ this ->options ['image_library ' ] && extension_loaded ('imagick ' )) {
1055
+ return $ this ->imagick_create_scaled_image ($ file_name , $ version , $ options );
1056
+ }
1057
+ return $ this ->gd_create_scaled_image ($ file_name , $ version , $ options );
1058
+ } catch (\Exception $ e ) {
1059
+ error_log ($ e ->getMessage ());
1060
+ return false ;
1020
1061
}
1021
- return $ this ->gd_create_scaled_image ($ file_name , $ version , $ options );
1022
1062
}
1023
1063
1024
1064
protected function destroy_image_object ($ file_path ) {
@@ -1027,15 +1067,30 @@ protected function destroy_image_object($file_path) {
1027
1067
}
1028
1068
}
1029
1069
1070
+ protected function imagetype ($ file_path ) {
1071
+ $ fp = fopen ($ file_path , 'r ' );
1072
+ $ data = fread ($ fp , 4 );
1073
+ fclose ($ fp );
1074
+ // GIF: 47 49 46 38
1075
+ if ($ data === 'GIF8 ' ) {
1076
+ return self ::IMAGETYPE_GIF ;
1077
+ }
1078
+ // JPG: FF D8 FF
1079
+ if (bin2hex (substr ($ data , 0 , 3 )) === 'ffd8ff ' ) {
1080
+ return self ::IMAGETYPE_JPEG ;
1081
+ }
1082
+ // PNG: 89 50 4E 47
1083
+ if (bin2hex (@$ data [0 ]).substr ($ data , 1 , 4 ) === '89PNG ' ) {
1084
+ return self ::IMAGETYPE_PNG ;
1085
+ }
1086
+ return false ;
1087
+ }
1088
+
1030
1089
protected function is_valid_image_file ($ file_path ) {
1031
- if (!preg_match ($ this -> options [ ' image_file_types ' ] , $ file_path )) {
1090
+ if (!preg_match (' /\.(gif|jpe?g|png)$/i ' , $ file_path )) {
1032
1091
return false ;
1033
1092
}
1034
- if (function_exists ('exif_imagetype ' )) {
1035
- return @exif_imagetype ($ file_path );
1036
- }
1037
- $ image_info = $ this ->get_image_size ($ file_path );
1038
- return $ image_info && $ image_info [0 ] && $ image_info [1 ];
1093
+ return !!$ this ->imagetype ($ file_path );
1039
1094
}
1040
1095
1041
1096
protected function handle_image_file ($ file_path , $ file ) {
@@ -1062,7 +1117,8 @@ protected function handle_image_file($file_path, $file) {
1062
1117
$ this ->destroy_image_object ($ file_path );
1063
1118
}
1064
1119
1065
- protected function handle_file_upload ($ uploaded_file , $ name , $ size , $ type , $ error , $ index = null , $ content_range = null ) {
1120
+ protected function handle_file_upload ($ uploaded_file , $ name , $ size , $ type , $ error ,
1121
+ $ index = null , $ content_range = null ) {
1066
1122
$ file = new \stdClass ();
1067
1123
$ file ->name = $ this ->get_file_name ($ uploaded_file , $ name , $ size , $ type , $ error ,
1068
1124
$ index , $ content_range );
@@ -1071,7 +1127,7 @@ protected function handle_file_upload($uploaded_file, $name, $size, $type, $erro
1071
1127
if ($ this ->validate ($ uploaded_file , $ file , $ error , $ index )) {
1072
1128
$ this ->handle_form_data ($ file , $ index );
1073
1129
$ upload_dir = $ this ->get_upload_path ();
1074
- if (!$ this -> options [ ' ftp ' ] && ! is_dir ($ upload_dir )) {
1130
+ if (!is_dir ($ upload_dir )) {
1075
1131
mkdir ($ upload_dir , $ this ->options ['mkdir_mode ' ], true );
1076
1132
}
1077
1133
$ file_path = $ this ->get_upload_path ($ file ->name );
@@ -1089,20 +1145,14 @@ protected function handle_file_upload($uploaded_file, $name, $size, $type, $erro
1089
1145
move_uploaded_file ($ uploaded_file , $ file_path );
1090
1146
}
1091
1147
} else {
1092
- if ($ this ->get_file_size ($ uploaded_file ) == $ file ->size ){
1093
- copy ($ uploaded_file , $ file_path );
1094
- }else {
1095
-
1096
- // Non-multipart uploads (PUT method support)
1097
- file_put_contents (
1098
- $ file_path ,
1099
- fopen ($ this ->options ['input_stream ' ], 'r ' ),
1100
- $ append_file ? FILE_APPEND : 0
1101
- );
1102
- }
1148
+ // Non-multipart uploads (PUT method support)
1149
+ file_put_contents (
1150
+ $ file_path ,
1151
+ fopen ($ this ->options ['input_stream ' ], 'r ' ),
1152
+ $ append_file ? FILE_APPEND : 0
1153
+ );
1103
1154
}
1104
1155
$ file_size = $ this ->get_file_size ($ file_path , $ append_file );
1105
- $ file ->path = $ file_path ;
1106
1156
if ($ file_size === $ file ->size ) {
1107
1157
$ file ->url = $ this ->get_download_url ($ file ->name );
1108
1158
if ($ this ->is_valid_image_file ($ file_path )) {
@@ -1317,7 +1367,6 @@ public function get($print_response = true) {
1317
1367
$ this ->options ['param_name ' ] => $ this ->get_file_objects ()
1318
1368
);
1319
1369
}
1320
-
1321
1370
return $ this ->generate_response ($ response , $ print_response );
1322
1371
}
1323
1372
@@ -1334,11 +1383,6 @@ public function post($print_response = true) {
1334
1383
'' ,
1335
1384
$ content_disposition_header
1336
1385
)) : null ;
1337
- // TODO check
1338
- // if (isset($content_disposition_header) && !empty($content_disposition_header) ) {
1339
- // $file_name = str_replace('attachment; filename="', '', $content_disposition_header);
1340
- // $file_name = str_replace('"', '', $file_name);
1341
- // }
1342
1386
// Parse the Content-Range header, which has the following form:
1343
1387
// Content-Range: bytes 0-524287/2000000
1344
1388
$ content_range_header = $ this ->get_server_var ('HTTP_CONTENT_RANGE ' );
@@ -1351,15 +1395,12 @@ public function post($print_response = true) {
1351
1395
// param_name is an array identifier like "files[]",
1352
1396
// $upload is a multi-dimensional array:
1353
1397
foreach ($ upload ['tmp_name ' ] as $ index => $ value ) {
1354
- if (!isset ($ upload ['error ' ][$ index ])){
1355
- $ upload ['error ' ][$ index ] = null ;
1356
- }
1357
1398
$ files [] = $ this ->handle_file_upload (
1358
1399
$ upload ['tmp_name ' ][$ index ],
1359
1400
$ file_name ? $ file_name : $ upload ['name ' ][$ index ],
1360
1401
$ size ? $ size : $ upload ['size ' ][$ index ],
1361
1402
$ upload ['type ' ][$ index ],
1362
- $ upload ['error ' ][ $ index ],
1403
+ isset ( $ upload ['error ' ]) ? $ upload [ ' error ' ][ $ index ] : null ,
1363
1404
$ index ,
1364
1405
$ content_range
1365
1406
);
@@ -1437,6 +1478,8 @@ public function onUploadEnd ($res){
1437
1478
$ magicianObj -> saveImage ($ targetFile );
1438
1479
}
1439
1480
1481
+
1482
+
1440
1483
$ thumbResult = create_img ($ targetFile , $ targetFileThumb , 122 , 91 );
1441
1484
1442
1485
if ( $ thumbResult !==true )
0 commit comments