This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | /** |
||
4 | * Uploader Class |
||
5 | * |
||
6 | * Main class for uploading, deleting files & directories |
||
7 | * |
||
8 | * @license http://opensource.org/licenses/MIT The MIT License (MIT) |
||
9 | * @author Omar El Gabry <[email protected]> |
||
10 | */ |
||
11 | |||
12 | class Uploader{ |
||
0 ignored issues
–
show
|
|||
13 | |||
14 | /** |
||
15 | * The mime types allowed for upload |
||
16 | * |
||
17 | * @var array |
||
18 | */ |
||
19 | private static $allowedMIME = [ |
||
20 | "image" => array('image/jpeg', 'image/png', 'image/gif'), |
||
21 | "csv" => array('text/csv', 'application/vnd.ms-excel', 'text/plain'), |
||
22 | "file" => array('application/msword', |
||
23 | 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', |
||
24 | 'application/pdf', |
||
25 | 'application/zip', |
||
26 | 'application/vnd.ms-powerpoint') |
||
27 | ]; |
||
28 | |||
29 | /** |
||
30 | * The min and max image size allowed for upload (in bytes) |
||
31 | * 1 KB = 1024 bytes, and 1 MB = 1048,576 bytes. |
||
32 | * |
||
33 | * @var array |
||
34 | */ |
||
35 | private static $fileSize = [100, 5242880]; |
||
36 | |||
37 | /** |
||
38 | * The max height and width image allowed for image |
||
39 | * |
||
40 | * @var array |
||
41 | */ |
||
42 | private static $dimensions = [2000, 2000]; |
||
43 | |||
44 | /** |
||
45 | * Array of validation errors |
||
46 | * |
||
47 | * @var array |
||
48 | */ |
||
49 | private static $errors = []; |
||
50 | |||
51 | /*** |
||
52 | * @access private |
||
53 | */ |
||
54 | private function __construct() {} |
||
55 | |||
56 | /** |
||
57 | * upload profile picture |
||
58 | * |
||
59 | * @param array $file |
||
60 | * @param mixed $id random id used in creating filename |
||
61 | * @return mixed false in case of failure, otherwise array of file created |
||
62 | * |
||
63 | */ |
||
64 | public static function uploadPicture($file, $id){ |
||
65 | self::$fileSize = [100, 2097152]; |
||
66 | return self::upload($file, IMAGES . "profile_pictures/", $id, "image"); |
||
67 | } |
||
68 | |||
69 | /** |
||
70 | * upload a file - default |
||
71 | * |
||
72 | * @param array $file |
||
73 | * @param mixed $id random id used for creating filename |
||
74 | * @return mixed false in case of failure, array otherwise |
||
75 | * |
||
76 | */ |
||
77 | public static function uploadFile($file, $id = null){ |
||
78 | return self::upload($file, APP . "uploads/" , $id); |
||
79 | } |
||
80 | |||
81 | /** |
||
82 | * upload a CSV File |
||
83 | * |
||
84 | * @param array $file |
||
85 | * @return mixed false in case of failure, array otherwise |
||
86 | * |
||
87 | */ |
||
88 | public static function uploadCSV($file){ |
||
89 | return self::upload($file, APP . "uploads/", null, "csv"); |
||
90 | } |
||
91 | |||
92 | /** |
||
93 | * upload & validate file |
||
94 | * |
||
95 | * @param array $file |
||
96 | * @param string $dir directory where we will upload the file |
||
97 | * @param mixed $id random id used for creating filename |
||
98 | * @param string $type it tells whether the file is image, csv, or normal file(default). |
||
99 | * @return mixed false in case of failure, array otherwise |
||
100 | * @throws Exception If file couldn't be uploaded |
||
101 | * |
||
102 | */ |
||
103 | private static function upload($file, $dir, $id, $type = "file"){ |
||
104 | |||
105 | $mimeTypes = self::getAllowedMime($type); |
||
106 | |||
107 | $validation = new Validation(); |
||
108 | $rules = "required|fileErrors|fileUploaded|mimeType(".Utility::commas($mimeTypes).")|fileSize(".Utility::commas(self::$fileSize).")"; |
||
109 | $rules = ($type === "image")? $rules . "|imageSize(".Utility::commas(self::$dimensions).")": $rules; |
||
110 | |||
111 | if(!$validation->validate([ |
||
112 | "File" => [$file, $rules]], true)){ |
||
113 | self::$errors = $validation->errors(); |
||
114 | return false; |
||
115 | } |
||
116 | |||
117 | if($type === "csv"){ |
||
118 | |||
119 | // you need to add the extension in case of csv files, |
||
120 | // because mime() will return text/plain. |
||
121 | $basename = "grades" . "." . "csv"; |
||
122 | $path = $dir . $basename; |
||
123 | |||
124 | $data = ["basename" => $basename, "extension" => "csv"]; |
||
125 | |||
126 | } else { |
||
127 | |||
128 | if(!empty($id)){ |
||
129 | |||
130 | // get safe filename |
||
131 | $filename = self::getFileName($file); |
||
132 | |||
133 | // mime mapping to extension |
||
134 | $ext = self::MimeToExtension(self::mime($file)); |
||
135 | |||
136 | // get hashed version using the given $id |
||
137 | // the $id is used to have a unique file name |
||
138 | // so, for example you would use it for profile picture, |
||
139 | // because every user can have only one picture |
||
140 | $hashedFileName = self::getHashedName($id); |
||
141 | |||
142 | $basename = $hashedFileName . "." . $ext; |
||
143 | $path = $dir . $basename; |
||
144 | |||
145 | // delete all files with the same name, but with different formats. |
||
146 | // not needed, but i like to clear unnecessary files |
||
147 | self::deleteFiles($dir . $hashedFileName, $mimeTypes); |
||
148 | |||
149 | $data = ["filename" => $filename, "basename" => $basename, "hashed_filename" => $hashedFileName, "extension" => $ext]; |
||
150 | |||
151 | } else { |
||
152 | |||
153 | $filename = self::getFileName($file); |
||
154 | $ext = self::MimeToExtension(self::mime($file)); |
||
155 | |||
156 | // hashed file name is created from the original filename and extension |
||
157 | // so uploading test.pdf & test.doc won't conflict, |
||
158 | // but, uploading file with test.pdf will return "file already exists" |
||
159 | $hashedFileName = self::getHashedName(strtolower($filename . $ext)); |
||
160 | |||
161 | $basename = $hashedFileName . "." . $ext; |
||
162 | $path = $dir . $basename; |
||
163 | |||
164 | if(!$validation->validate(["File" => [$path, "fileUnique"]])) { |
||
165 | self::$errors = $validation->errors(); |
||
166 | return false; |
||
167 | } |
||
168 | |||
169 | $data = ["filename" => $filename, "basename" => $basename, "hashed_filename" => $hashedFileName, "extension" => $ext]; |
||
170 | } |
||
171 | } |
||
172 | |||
173 | // upload the file. |
||
174 | if(!move_uploaded_file($file['tmp_name'], $path)){ |
||
175 | throw new Exception("File couldn't be uploaded"); |
||
176 | } |
||
177 | |||
178 | // set 644 permission to avoid any executable files |
||
179 | if(!chmod($path, 0644)) { |
||
180 | throw new Exception("File permissions couldn't be changed"); |
||
181 | } |
||
182 | |||
183 | return $data; |
||
184 | } |
||
185 | |||
186 | /** |
||
187 | * get mime type allowed from $allowedMIME |
||
188 | * |
||
189 | * @param string $key |
||
190 | * @return array |
||
191 | */ |
||
192 | private static function getAllowedMime($key){ |
||
193 | return isset(self::$allowedMIME[$key])? self::$allowedMIME[$key]: []; |
||
194 | } |
||
195 | |||
196 | /** |
||
197 | * If you can have only one file name based on each user, Then: |
||
198 | * Before uploading every new file, Delete all files with the same name and different extensions |
||
199 | * |
||
200 | * @param string $filePathWithoutExtension |
||
201 | * @param array $allowedMIME |
||
202 | * |
||
203 | */ |
||
204 | private static function deleteFiles($filePathWithoutExtension, $allowedMIME){ |
||
205 | |||
206 | foreach($allowedMIME as $mime){ |
||
207 | $ext = self::MimeToExtension($mime); |
||
208 | $path = $filePathWithoutExtension . "." . $ext; |
||
209 | |||
210 | if(file_exists($path)){ |
||
211 | unlink($path); |
||
212 | } |
||
213 | } |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * Deletes a file |
||
218 | * |
||
219 | * @param string $path |
||
220 | * @throws Exception File couldn't be deleted |
||
221 | * |
||
222 | */ |
||
223 | public static function deleteFile($path){ |
||
224 | if(file_exists($path)){ |
||
225 | if(!unlink($path)){ |
||
226 | throw new Exception("File ". $path ." couldn't be deleted"); |
||
227 | } |
||
228 | } else { |
||
229 | throw new Exception("File ". $path ." doesn't exist!"); |
||
230 | } |
||
231 | } |
||
232 | |||
233 | /** |
||
234 | * create a directory with random hashed name |
||
235 | * |
||
236 | * @param string $dir |
||
237 | * @return string |
||
238 | * @throws Exception If directory couldn't be created or If directory already exists! |
||
239 | */ |
||
240 | public static function createDirectory($dir){ |
||
241 | |||
242 | $hashedDirName = self::getHashedName(); |
||
243 | $newDir = $dir . $hashedDirName; |
||
244 | |||
245 | // create a directory if not exists |
||
246 | if(!file_exists($newDir) && !is_dir($newDir)){ |
||
247 | if(mkdir($newDir, 0755) === false){ |
||
248 | throw new Exception("directory couldn't be created"); |
||
249 | } |
||
250 | } else { |
||
251 | throw new Exception("Directory: " .$hashedDirName. "already exists or directory given is invalid"); |
||
252 | } |
||
253 | |||
254 | return $hashedDirName; |
||
255 | } |
||
256 | |||
257 | /** |
||
258 | * Deletes a directory. |
||
259 | * |
||
260 | * @param string $dir |
||
261 | * @throws Exception If directory couldn't be deleted |
||
262 | */ |
||
263 | public static function deleteDir($dir){ |
||
264 | if(!self::delTree($dir)){ |
||
265 | throw new Exception("Directory: " . $dir ." couldn't be deleted"); |
||
266 | } |
||
267 | } |
||
268 | |||
269 | /** |
||
270 | * checks if file exists in the File System or not |
||
271 | * |
||
272 | * @param string $path |
||
273 | * @return boolean |
||
274 | * |
||
275 | */ |
||
276 | public static function isFileExists($path){ |
||
277 | return file_exists($path) && is_file($path); |
||
278 | } |
||
279 | |||
280 | /** |
||
281 | * deletes a directory recursively |
||
282 | * |
||
283 | * @param string $dir |
||
284 | * @return boolean |
||
285 | * |
||
286 | */ |
||
287 | private static function delTree($dir) { |
||
288 | |||
289 | $files = scandir($dir); |
||
290 | foreach ($files as $file) { |
||
291 | if ($file != "." && $file != ".."){ |
||
292 | (is_dir("$dir/$file")) ? self::delTree("$dir/$file") : unlink("$dir/$file"); |
||
293 | } |
||
294 | } |
||
295 | return rmdir($dir); |
||
296 | } |
||
297 | |||
298 | /** |
||
299 | * get mime type of file |
||
300 | * |
||
301 | * Don't use either $_FILES["file"]["type"], or pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION, |
||
302 | * Because their values aren't secure and can be easily be spoofed. |
||
303 | * |
||
304 | * @param array $file |
||
305 | * @return mixed false if failed, string otherwise |
||
306 | * @throws Exception if finfo_open() method doesn't exists |
||
307 | * |
||
308 | */ |
||
309 | View Code Duplication | private static function mime($file){ |
|
0 ignored issues
–
show
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. ![]() |
|||
310 | |||
311 | if(!file_exists($file["tmp_name"])){ |
||
312 | return false; |
||
313 | } |
||
314 | if(!function_exists('finfo_open')) { |
||
315 | throw new Exception("Function finfo_open() doesn't exist"); |
||
316 | } |
||
317 | |||
318 | $finfo_open = finfo_open(FILEINFO_MIME_TYPE); |
||
319 | $finfo_file = finfo_file($finfo_open, $file["tmp_name"]); |
||
320 | finfo_close($finfo_open); |
||
321 | |||
322 | list($mime) = explode(';', $finfo_file); |
||
323 | return $mime; |
||
324 | } |
||
325 | |||
326 | /** |
||
327 | * get hashed file name, and Optionally provided by an id |
||
328 | * |
||
329 | * @access private |
||
330 | * @param string $id random id |
||
331 | * @return string hashed file name |
||
332 | * |
||
333 | */ |
||
334 | private static function getHashedName($id = null){ |
||
335 | |||
336 | if($id === null) $id = time(); |
||
337 | return substr(hash('sha256', $id), 0, 40); |
||
338 | } |
||
339 | |||
340 | /** |
||
341 | * Convert/Map the MIME of a file to extension |
||
342 | * |
||
343 | * @param string $mime |
||
344 | * @return string extension |
||
345 | * |
||
346 | */ |
||
347 | private static function MimeToExtension($mime){ |
||
348 | $arr = array( |
||
349 | 'image/jpeg' => 'jpeg', // for both jpeg & jpg. |
||
350 | 'image/png' => 'png', |
||
351 | 'image/gif' => 'gif', |
||
352 | 'application/msword' => 'doc', |
||
353 | 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', |
||
354 | 'application/pdf' => 'pdf', |
||
355 | 'application/zip' => 'zip', |
||
356 | 'application/vnd.ms-powerpoint' => 'ppt' |
||
357 | ); |
||
358 | return isset($arr[$mime])? $arr[$mime]: null; |
||
359 | } |
||
360 | |||
361 | /** |
||
362 | * get file name |
||
363 | * This ensures file name will be safe |
||
364 | * |
||
365 | * @param array $file |
||
366 | * @return string |
||
367 | * |
||
368 | */ |
||
369 | private static function getFileName($file){ |
||
370 | |||
371 | $filename = pathinfo($file['name'], PATHINFO_FILENAME); |
||
372 | $filename = preg_replace("/([^A-Za-z0-9_\-\.]|[\.]{2})/", "", $filename); |
||
373 | $filename = basename($filename); |
||
374 | return $filename; |
||
375 | } |
||
376 | |||
377 | /** |
||
378 | * get errors |
||
379 | * |
||
380 | * @return array errors |
||
381 | */ |
||
382 | public static function errors(){ |
||
383 | return self::$errors; |
||
384 | } |
||
385 | |||
386 | } |
||
387 |
You can fix this by adding a namespace to your class:
When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.