]>
jfr.im git - uguu.git/blob - src/Classes/Upload.php
d5ee64104d678fc97f76fa373a0f995b38448179
5 * @copyright Copyright (c) 2022-2023 Go Johansson (nokonoko) <neku@pomf.se>
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 namespace Pomf\Uguu\Classes
;
23 class Upload
extends Response
25 public array $fingerPrintInfo;
26 private mixed $Connector;
29 * Takes an array of files, and returns an array of arrays containing the file's temporary name,
30 * name, size, SHA1 hash, extension, and MIME type
32 * @param $files array The files array from the $_FILES superglobal.
34 * @return array An array of arrays.
36 public function reFiles(array $files):array
38 $this->Connector
= new Connector();
40 $files = $this->diverseArray($files);
41 foreach ($files as $file) {
43 'TEMP_NAME' => $file['tmp_name'],
44 'NAME' => strip_tags($this->checkNameLength($file['name'])),
45 'SIZE' => $file['size'],
46 'SHA1' => sha1_file($file['tmp_name']),
47 'EXTENSION' => $this->fileExtension($file),
48 'MIME' => $this->fileMIME($file),
52 // Check if anti dupe is enabled
53 if ($this->Connector
->CONFIG
['ANTI_DUPE']) {
54 // Check if hash exists in DB, if it does return the name of the file
55 $dupeResult = $this->Connector
->antiDupe($FILE_INFO['SHA1']);
56 if ($dupeResult['result']) {
57 $FILE_INFO['FILENAME'] = $dupeResult['name'];
58 $FILE_INFO['DUPE'] = true;
61 // If its not a dupe then generate a new name
62 if (!$FILE_INFO['DUPE']) {
63 $FILE_INFO['FILENAME'] = $this->generateName($FILE_INFO['EXTENSION']);
65 $result[] = $FILE_INFO;
71 * Takes an array of arrays and returns an array of arrays with the keys and values swapped
73 * @param $files array an array of arrays
78 * 'TEMP_NAME' => 'example'
82 * 'EXTENSION' => 'example'
87 * 'TEMP_NAME' => 'example'
91 * 'EXTENSION' => 'example'
97 public function diverseArray(array $files):array
100 foreach ($files as $key1 => $value1) {
101 foreach ($value1 as $key2 => $value2) {
102 $result[$key2][$key1] = $value2;
109 * Takes a file, checks if it's blacklisted, moves it to the file storage, and then logs it to the database
111 * @return array An array containing the hash, name, url, and size of the file.
113 public function uploadFile($FILE_INFO):array
116 case $this->Connector
->CONFIG
['RATE_LIMIT']:
118 $this->Connector
->checkRateLimit(
119 $this->fingerPrintInfo
,
120 $this->Connector
->CONFIG
['RATE_LIMIT_TIMEOUT'],
121 $this->Connector
->CONFIG
['RATE_LIMIT_FILES'],
124 $this->Connector
->response
->error(
126 'Rate limit, please wait ' . $this->Connector
->CONFIG
['RATE_LIMIT_TIMEOUT'] .
127 ' seconds before uploading again.',
131 case $this->Connector
->CONFIG
['BLACKLIST_DB']:
132 $this->Connector
->checkFileBlacklist($FILE_INFO['SHA1']);
134 case $this->Connector
->CONFIG
['FILTER_MODE'] && empty($FILE_INFO['EXTENSION']):
135 $this->checkMimeBlacklist($FILE_INFO);
137 case $this->Connector
->CONFIG
['FILTER_MODE'] && !empty($FILE_INFO['EXTENSION']):
138 $this->checkMimeBlacklist($FILE_INFO);
139 $this->checkExtensionBlacklist($FILE_INFO);
142 // If its not a dupe then skip checking if file can be written and
143 // skip inserting it into the DB.
144 if (!$FILE_INFO['DUPE']) {
145 if (!is_dir($this->Connector
->CONFIG
['FILES_ROOT'])) {
146 $this->Connector
->response
->error(500, 'File storage path not accessible.');
150 $FILE_INFO['TEMP_NAME'],
151 $this->Connector
->CONFIG
['FILES_ROOT'] .
152 $FILE_INFO['FILENAME'],
155 $this->Connector
->response
->error(500, 'Failed to move file to destination.');
157 if (!chmod($this->Connector
->CONFIG
['FILES_ROOT'] . $FILE_INFO['FILENAME'], 0644)) {
158 $this->Connector
->response
->error(500, 'Failed to change file permissions.');
160 $this->Connector
->newIntoDB($FILE_INFO, $this->fingerPrintInfo
);
163 'hash' => $FILE_INFO['SHA1'],
164 'name' => $FILE_INFO['NAME'],
165 'filename' => $FILE_INFO['FILENAME'],
166 'url' => 'https://' . $this->Connector
->CONFIG
['FILE_DOMAIN'] . '/' . $FILE_INFO['FILENAME'],
167 'size' => $FILE_INFO['SIZE'],
168 'dupe' => $FILE_INFO['DUPE'],
173 * Takes the amount of files that are being uploaded, and creates a fingerprint of the user's IP address,
174 * user agent, and the amount of files being
177 * @param $files_amount int The amount of files that are being uploaded.
180 public function fingerPrint(int $files_amount):void
182 if (!empty($_SERVER['HTTP_USER_AGENT'])) {
183 $USER_AGENT = filter_var($_SERVER['HTTP_USER_AGENT'], FILTER_SANITIZE_ENCODED
);
185 if ($this->Connector
->CONFIG
['LOG_IP']) {
186 $ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'];
188 $this->fingerPrintInfo
= [
189 'timestamp' => time(),
190 'useragent' => $USER_AGENT,
192 'ip_hash' => hash('sha1', $_SERVER['REMOTE_ADDR'] . $USER_AGENT),
193 'files_amount' => $files_amount,
196 $this->Connector
->response
->error(500, 'Invalid user agent.');
201 * Returns the MIME type of a file
203 * @param $file array The file to be checked.
205 * @return string The MIME type of the file.
207 public function fileMIME(array $file):string
209 $FILE_INFO = finfo_open(FILEINFO_MIME_TYPE
);
210 return finfo_file($FILE_INFO, $file['tmp_name']);
214 * It takes an array of strings, and returns the last two strings joined by a dot,
215 * unless the last two strings are in the array of strings in the
216 * `DOUBLE_DOTS_EXTENSIONS` config variable, in which case it returns the last string
218 * @param $extension array The extension of the file.
220 * @return string The last two elements of the array are joined together and returned.
222 public function doubleDotExtension(array $extension):string
224 $doubleDotArray = array_slice($extension, -2, 2);
225 $doubleDot = strtolower(preg_replace('/[^a-zA-Z.]/', '', join('.', $doubleDotArray)));
226 if (in_array($doubleDot, $this->Connector
->CONFIG
['DOUBLE_DOTS_EXTENSIONS'])) {
229 return end($extension);
234 * Takes a file and returns the file extension
236 * @param $file array The file you want to get the extension from.
238 * @return string The file extension of the file.
240 public function fileExtension(array $file):?string
242 $extension = explode('.', $file['name']);
243 $dotCount = substr_count($file['name'], '.');
244 return match ($dotCount) {
245 0 => $this->lookupExtension($file['type']),
246 1 => end($extension),
247 2 => $this->doubleDotExtension($extension),
248 default => end($extension)
253 * > Check if the file's MIME type is in the blacklist
256 public function checkMimeBlacklist($FILE_INFO):void
258 if (in_array($FILE_INFO['MIME'], $this->Connector
->CONFIG
['BLOCKED_MIME'])) {
259 $this->Connector
->response
->error(415, 'Filetype not allowed');
264 * > Check if the file extension is in the blacklist
267 public function checkExtensionBlacklist($FILE_INFO):void
269 if (in_array($FILE_INFO['EXTENSION'], $this->Connector
->CONFIG
['BLOCKED_EXTENSIONS'])) {
270 $this->Connector
->response
->error(415, 'Filetype not allowed');
274 public function checkNameLength(string $fileName):string
276 if (strlen($fileName) > 250) {
277 return substr($fileName, 0, 250);
284 * Generates a random string of characters, checks if it exists in the database,
285 * and if it does, it generates another one
287 * @param $extension string The file extension.
289 * @return string A string
291 public function generateName(string $extension):string
294 if ($this->Connector
->CONFIG
['FILES_RETRIES'] === 0) {
295 $this->Connector
->response
->error(500, 'Gave up trying to find an unused name!');
298 for ($i = 0; $i < $this->Connector
->CONFIG
['NAME_LENGTH']; $i++
) {
299 $index = rand(0, strlen($this->Connector
->CONFIG
['ID_CHARSET']) - 1);
300 $NEW_NAME .= $this->Connector
->CONFIG
['ID_CHARSET'][$index];
302 if (!empty($extension)) {
303 $NEW_NAME .= '.' . $extension;
305 } while ($this->Connector
->dbCheckNameExists($NEW_NAME));
309 private function lookupExtension(string $mimetype):string
312 'image/gif' => 'gif',
313 'image/jpeg' => 'jpg',
314 'image/avif' => 'avif',
315 'image/png' => 'png',
316 'image/tiff' => 'tiff',
317 'image/vnd.wap.wbmp' => 'wbmp',
318 'image/webp' => 'webp',
319 'image/x-icon' => 'ico',
320 'image/x-jng' => 'jng',
321 'image/x-ms-bmp' => 'bmp',
322 'application/pdf' => 'pdf',
323 'application/postscript' => 'ps',
324 'application/x-7z-compressed' => '7z',
325 'application/zip' => 'zip',
326 'audio/midi' => 'mid',
327 'audio/mpeg' => 'mp3',
328 'audio/ogg' => 'ogg',
329 'audio/x-m4a' => 'm4a',
330 'audio/x-realaudio' => 'ra',
331 'video/3gpp' => '3gpp',
332 'video/mp2t' => 'ts',
333 'video/mp4' => 'mp4',
334 'video/mpeg' => 'mpeg',
335 'video/quicktime' => 'mov',
336 'video/webm' => 'webm',
337 'video/x-flv' => 'flv',
338 'video/x-m4v' => 'm4v',
339 'video/x-mng' => 'mng',
340 'video/x-ms-asf' => 'asx',
341 'video/x-ms-wmv' => 'wmv',
342 'video/x-msvideo' => 'avi',
344 if (isset($types[$mimetype])) {
345 return $types[$mimetype];
347 $this->Connector
->response
->error(400, 'Unknown MIME type. Add a file extension to your filename.');