]> jfr.im git - uguu.git/blame - src/Classes/Upload.php
bug fixes
[uguu.git] / src / Classes / Upload.php
CommitLineData
e480c0e5 1<?php
e480c0e5 2 /**
cec6349e 3 * Uguu
8f7f8840 4 *
cec6349e 5 * @copyright Copyright (c) 2022 Go Johansson (nokonoko) <neku@pomf.se>
8f7f8840 6 *
cec6349e
GJ
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.
8f7f8840 11 *
cec6349e
GJ
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.
8f7f8840 16 *
cec6349e
GJ
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/>.
8f7f8840 19 */
8f7f8840 20
f059e2cf 21 namespace Pomf\Uguu\Classes;
cec6349e
GJ
22
23 use Exception;
24
25 class Upload extends Response
e480c0e5 26 {
cec6349e
GJ
27 public array $FILE_INFO;
28 public array $fingerPrintInfo;
29 private mixed $Connector;
30
31 /**
32 * Takes an array of files, and returns an array of arrays containing the file's temporary name, name, size, SHA1 hash, extension, and MIME type
33 *
34 * @param $files array The files array from the $_FILES superglobal.
35 *
36 * @return array An array of arrays.
37 * @throws \Exception
38 */
39 public function reFiles(array $files):array
40 {
41 $this->Connector = new Connector();
42 $this->Connector->setDB($this->Connector->DB);
43 $result = [];
44 $files = $this->diverseArray($files);
45 foreach ($files as $file) {
46 $hash = sha1_file($file['tmp_name']);
47 $this->FILE_INFO = [
48 'TEMP_NAME' => $file['tmp_name'],
49 'NAME' => strip_tags($file['name']),
50 'SIZE' => $file['size'],
51 'SHA1' => $hash,
52 'EXTENSION' => $this->fileExtension($file),
53 'MIME' => $this->fileMIME($file),
54 'NEW_NAME' => $this->generateName($this->fileExtension($file), $hash),
55 ];
56 $result[] = [
57 $this->FILE_INFO['TEMP_NAME'],
58 $this->FILE_INFO['NAME'],
59 $this->FILE_INFO['SIZE'],
60 $this->FILE_INFO['SHA1'],
61 $this->FILE_INFO['EXTENSION'],
62 $this->FILE_INFO['MIME'],
63 ];
64 }
65 return $result;
e480c0e5 66 }
cec6349e
GJ
67
68 /**
69 * Takes an array of arrays and returns an array of arrays with the keys and values swapped
70 *
71 * @param $files array an array of arrays
72 *
73 * @return array ```
74 * array:2 [▼
75 * 0 => array:2 [▼
76 * 'TEMP_NAME' => 'example'
77 * 'NAME' => 'example'
78 * 'SIZE' => 'example'
79 * 'SHA1' => 'example'
80 * 'EXTENSION' => 'example'
81 * 'MIME' => 'example'
82 *
83 * ]
84 * 1 => array:2 [▼
85 * 'TEMP_NAME' => 'example'
86 * 'NAME' => 'example'
87 * 'SIZE' => 'example'
88 * 'SHA1' => 'example'
89 * 'EXTENSION' => 'example'
90 * 'MIME' => 'example'
91 * ]
92 * ]
93 * ```
94 */
95 public function diverseArray(array $files):array
96 {
97 $result = [];
98 foreach ($files as $key1 => $value1) {
99 foreach ($value1 as $key2 => $value2) {
100 $result[$key2][$key1] = $value2;
101 }
102 }
103 return $result;
e480c0e5 104 }
cec6349e
GJ
105
106 /**
107 * Takes a file, checks if it's blacklisted, moves it to the file storage, and then logs it to the database
108 *
109 * @return array An array containing the hash, name, url, and size of the file.
110 * @throws \Exception
111 */
112 public function uploadFile():array
113 {
114 if ($this->Connector->CONFIG['RATE_LIMIT']) {
115 $this->Connector->checkRateLimit($this->fingerPrintInfo);
116 }
117 if ($this->Connector->CONFIG['BLACKLIST_DB']) {
118 $this->Connector->checkFileBlacklist($this->FILE_INFO);
119 }
120 if ($this->Connector->CONFIG['FILTER_MODE'] && empty($this->FILE_INFO['EXTENSION'])) {
121 $this->checkMimeBlacklist();
122 }
123 if ($this->Connector->CONFIG['FILTER_MODE'] && !empty($this->FILE_INFO['EXTENSION'])) {
124 $this->checkMimeBlacklist();
125 $this->checkExtensionBlacklist();
126 }
127 if (!is_dir($this->Connector->CONFIG['FILES_ROOT'])) {
128 throw new Exception('File storage path not accessible.', 500);
129 }
130 if (
131 !move_uploaded_file(
132 $this->FILE_INFO['TEMP_NAME'],
133 $this->Connector->CONFIG['FILES_ROOT'] .
134 $this->FILE_INFO['NEW_NAME'],
135 )
136 ) {
137 throw new Exception('Failed to move file to destination', 500);
138 }
139 if (!chmod($this->Connector->CONFIG['FILES_ROOT'] . $this->FILE_INFO['NEW_NAME'], 0644)) {
140 throw new Exception('Failed to change file permissions', 500);
141 }
142 if (!$this->Connector->CONFIG['LOG_IP']) {
143 $this->fingerPrintInfo['ip'] = null;
144 }
145 $this->Connector->newIntoDB($this->FILE_INFO, $this->fingerPrintInfo);
146 return [
147 'hash' => $this->FILE_INFO['SHA1'],
148 'name' => $this->FILE_INFO['NAME'],
149 'url' => $this->Connector->CONFIG['FILES_URL'] . '/' . $this->FILE_INFO['NEW_NAME'],
150 'size' => $this->FILE_INFO['SIZE'],
151 ];
e480c0e5 152 }
cec6349e
GJ
153
154 /**
155 * Takes the amount of files that are being uploaded, and creates a fingerprint of the user's IP address, user agent, and the amount of files being
156 * uploaded
157 *
158 * @param $files_amount int The amount of files that are being uploaded.
159 *
160 * @throws \Exception
161 */
162 public function fingerPrint(int $files_amount):void
163 {
164 if (!empty($_SERVER['HTTP_USER_AGENT'])) {
165 $USER_AGENT = filter_var($_SERVER['HTTP_USER_AGENT'], FILTER_SANITIZE_ENCODED);
166 $this->fingerPrintInfo = [
167 'timestamp' => time(),
168 'useragent' => $USER_AGENT,
169 'ip' => $_SERVER['REMOTE_ADDR'],
170 'ip_hash' => hash('sha1', $_SERVER['REMOTE_ADDR'] . $USER_AGENT),
171 'files_amount' => $files_amount,
172 ];
173 } else {
174 throw new Exception('Invalid user agent.', 500);
175 }
e480c0e5 176 }
cec6349e
GJ
177
178 /**
179 * Returns the MIME type of a file
180 *
181 * @param $file array The file to be checked.
182 *
183 * @return string The MIME type of the file.
184 */
185 public function fileMIME(array $file):string
186 {
187 $FILE_INFO = finfo_open(FILEINFO_MIME_TYPE);
188 return finfo_file($FILE_INFO, $file['tmp_name']);
e480c0e5 189 }
cec6349e
GJ
190
191 /**
192 * Takes a file and returns the file extension
193 *
194 * @param $file array The file you want to get the extension from.
195 *
196 * @return ?string The file extension of the file.
197 */
198 public function fileExtension(array $file):?string
199 {
200 $extension = explode('.', $file['name']);
201 if (substr_count($file['name'], '.') > 0) {
202 return end($extension);
203 } else {
204 return null;
205 }
e480c0e5 206 }
cec6349e
GJ
207
208 /**
209 * > Check if the file's MIME type is in the blacklist
210 *
211 * @throws \Exception
212 */
213 public function checkMimeBlacklist():void
214 {
215 if (in_array($this->FILE_INFO['MIME'], $this->Connector->CONFIG['BLOCKED_MIME'])) {
216 throw new Exception('Filetype not allowed.', 415);
217 }
e480c0e5 218 }
cec6349e
GJ
219
220 /**
221 * > Check if the file extension is in the blacklist
222 *
223 * @throws \Exception
224 */
225 public function checkExtensionBlacklist():void
226 {
227 if (in_array($this->FILE_INFO['EXTENSION'], $this->Connector->CONFIG['BLOCKED_EXTENSIONS'])) {
228 throw new Exception('Filetype not allowed.', 415);
229 }
e480c0e5 230 }
cec6349e
GJ
231
232 /**
233 * Generates a random string of characters, checks if it exists in the database, and if it does, it generates another one
234 *
235 * @param $extension string The file extension.
236 * @param $hash string The hash of the file.
237 *
238 * @return string A string
239 * @throws \Exception
240 */
241 public function generateName(string $extension, string $hash):string
242 {
243 if ($this->Connector->antiDupe($hash)) {
244 do {
245 if ($this->Connector->CONFIG['FILES_RETRIES'] === 0) {
246 throw new Exception('Gave up trying to find an unused name!', 500);
247 }
248 $NEW_NAME = '';
8be20956
GJ
249 $count = strlen($this->Connector->CONFIG['ID_CHARSET']);
250 while ($this->Connector->CONFIG['NAME_LENGTH']--) {
251 $NEW_NAME .= $this->Connector->CONFIG['ID_CHARSET'][mt_rand(0, $count - 1)];
cec6349e
GJ
252 }
253 if (!empty($extension)) {
254 $NEW_NAME .= '.' . $extension;
255 }
256 } while ($this->Connector->dbCheckNameExists($NEW_NAME) > 0);
257 return $NEW_NAME;
258 } else {
259 return $this->Connector->antiDupe($hash);
260 }
e480c0e5
GJ
261 }
262 }