]> jfr.im git - uguu.git/blob - src/Classes/Upload.php
minor changes + documentation
[uguu.git] / src / Classes / Upload.php
1 <?php
2
3 /**
4 * Uguu
5 *
6 * @copyright Copyright (c) 2022 Go Johansson (nokonoko) <neku@pomf.se>
7 *
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22 namespace Pomf\Uguu\Classes;
23
24 use Exception;
25
26 class Upload extends Response
27 {
28 public array $FILE_INFO;
29 public array $fingerPrintInfo;
30 private mixed $Connector;
31
32 /**
33 * 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
34 *
35 * @param $files array The files array from the $_FILES superglobal.
36 *
37 * @return array An array of arrays.
38 * @throws \Exception
39 */
40 public function reFiles(array $files): array
41 {
42 $this->Connector = new Connector();
43 $this->Connector->setDB($this->Connector->DB);
44 $result = [];
45 $files = $this->diverseArray($files);
46 foreach ($files as $file) {
47 $hash = sha1_file($file['tmp_name']);
48 $this->FILE_INFO = [
49 'TEMP_NAME' => $file['tmp_name'],
50 'NAME' => strip_tags($file['name']),
51 'SIZE' => $file['size'],
52 'SHA1' => $hash,
53 'EXTENSION' => $this->fileExtension($file),
54 'MIME' => $this->fileMIME($file),
55 'NEW_NAME' => $this->generateName($this->fileExtension($file), $hash)
56 ];
57 $result[] = [
58 $this->FILE_INFO['TEMP_NAME'],
59 $this->FILE_INFO['NAME'],
60 $this->FILE_INFO['SIZE'],
61 $this->FILE_INFO['SHA1'],
62 $this->FILE_INFO['EXTENSION'],
63 $this->FILE_INFO['MIME']
64 ];
65 }
66 return $result;
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;
104 }
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
115 if ($this->Connector->CONFIG['RATE_LIMIT']) {
116 $this->Connector->checkRateLimit($this->fingerPrintInfo);
117 }
118
119 if ($this->Connector->CONFIG['BLACKLIST_DB']) {
120 $this->Connector->checkFileBlacklist($this->FILE_INFO);
121 }
122
123 if ($this->Connector->CONFIG['FILTER_MODE'] && empty($this->FILE_INFO['EXTENSION'])) {
124 $this->checkMimeBlacklist();
125 }
126
127 if ($this->Connector->CONFIG['FILTER_MODE'] && !empty($this->FILE_INFO['EXTENSION'])) {
128 $this->checkMimeBlacklist();
129 $this->checkExtensionBlacklist();
130 }
131
132 if (!is_dir($this->Connector->CONFIG['FILES_ROOT'])) {
133 throw new Exception('File storage path not accessible.', 500);
134 }
135
136 if (
137 !move_uploaded_file($this->FILE_INFO['TEMP_NAME'], $this->Connector->CONFIG['FILES_ROOT'] .
138 $this->FILE_INFO['NEW_NAME'])
139 ) {
140 throw new Exception('Failed to move file to destination', 500);
141 }
142
143 if (!chmod($this->Connector->CONFIG['FILES_ROOT'] . $this->FILE_INFO['NEW_NAME'], 0644)) {
144 throw new Exception('Failed to change file permissions', 500);
145 }
146
147 if (!$this->Connector->CONFIG['LOG_IP']) {
148 $this->fingerPrintInfo['ip'] = null;
149 }
150
151 $this->Connector->newIntoDB($this->FILE_INFO, $this->fingerPrintInfo);
152
153 return [
154 'hash' => $this->FILE_INFO['SHA1'],
155 'name' => $this->FILE_INFO['NAME'],
156 'url' => $this->Connector->CONFIG['FILES_URL'] . '/' . $this->FILE_INFO['NEW_NAME'],
157 'size' => $this->FILE_INFO['SIZE']
158 ];
159 }
160
161 /**
162 * 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 uploaded
163 *
164 * @param $files_amount int The amount of files that are being uploaded.
165 *
166 * @throws \Exception
167 */
168 public function fingerPrint(int $files_amount): void
169 {
170 if (!empty($_SERVER['HTTP_USER_AGENT'])) {
171 $USER_AGENT = filter_var($_SERVER['HTTP_USER_AGENT'], FILTER_SANITIZE_ENCODED);
172 $this->fingerPrintInfo = [
173 'timestamp' => time(),
174 'useragent' => $USER_AGENT,
175 'ip' => $_SERVER['REMOTE_ADDR'],
176 'ip_hash' => hash('sha1', $_SERVER['REMOTE_ADDR'] . $USER_AGENT),
177 'files_amount' => $files_amount
178 ];
179 } else {
180 throw new Exception('Invalid user agent.', 500);
181 }
182 }
183
184
185 /**
186 * Returns the MIME type of a file
187 *
188 * @param $file array The file to be checked.
189 *
190 * @return string The MIME type of the file.
191 */
192 public function fileMIME(array $file): string
193 {
194 $FILE_INFO = finfo_open(FILEINFO_MIME_TYPE);
195 return finfo_file($FILE_INFO, $file['tmp_name']);
196 }
197
198 /**
199 * Takes a file and returns the file extension
200 *
201 * @param $file array The file you want to get the extension from.
202 *
203 * @return ?string The file extension of the file.
204 */
205 public function fileExtension(array $file): ?string
206 {
207 $extension = explode('.', $file['name']);
208 if (substr_count($file['name'], '.') > 0) {
209 return end($extension);
210 } else {
211 return null;
212 }
213 }
214
215 /**
216 * > Check if the file's MIME type is in the blacklist
217 *
218 * @throws \Exception
219 */
220 public function checkMimeBlacklist(): void
221 {
222 if (in_array($this->FILE_INFO['MIME'], $this->Connector->CONFIG['BLOCKED_MIME'])) {
223 throw new Exception('Filetype not allowed.', 415);
224 }
225 }
226
227 /**
228 * > Check if the file extension is in the blacklist
229 *
230 * @throws \Exception
231 */
232 public function checkExtensionBlacklist(): void
233 {
234 if (in_array($this->FILE_INFO['EXTENSION'], $this->Connector->CONFIG['BLOCKED_EXTENSIONS'])) {
235 throw new Exception('Filetype not allowed.', 415);
236 }
237 }
238
239 /**
240 * Generates a random string of characters, checks if it exists in the database, and if it does, it generates another one
241 *
242 * @param $extension string The file extension.
243 * @param $hash string The hash of the file.
244 *
245 * @return string A string
246 * @throws \Exception
247 */
248 public function generateName(string $extension, string $hash): string
249 {
250 if ($this->Connector->antiDupe($hash)) {
251 do {
252 if ($this->Connector->CONFIG['FILES_RETRIES'] === 0) {
253 throw new Exception('Gave up trying to find an unused name!', 500);
254 }
255
256 $NEW_NAME = '';
257 for ($i = 0; $i < $this->Connector->CONFIG['NAME_LENGTH']; ++$i) {
258 $NEW_NAME .= $this->Connector->CONFIG['ID_CHARSET']
259 [mt_rand(0, strlen($this->Connector->CONFIG['ID_CHARSET']))];
260 }
261
262 if (!empty($extension)) {
263 $NEW_NAME .= '.' . $extension;
264 }
265 } while ($this->Connector->dbCheckNameExists($NEW_NAME) > 0);
266 return $NEW_NAME;
267 } else {
268 return $this->Connector->antiDupe($hash);
269 }
270 }
271 }