This commit is contained in:
Go Johansson 2022-12-16 22:56:15 +01:00
parent 83f6699d7f
commit cec6349edd
8 changed files with 722 additions and 751 deletions

View File

@ -1,6 +1,5 @@
<?php
/**
/**
* Uguu
*
* @copyright Copyright (c) 2022 Go Johansson (nokonoko) <neku@pomf.se>
@ -19,16 +18,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Pomf\Uguu\Classes;
namespace Uguu\Classes;
class CuteGrills
{
class CuteGrills
{
public array $GRILLS;
/**
* Loads the list of grills, then redirects to a random grill
*/
public function showGrills(): void
public function showGrills():void
{
$this->loadGrills();
if (!headers_sent()) {
@ -36,7 +35,7 @@ class CuteGrills
'Location: /img/grills/' .
$this->GRILLS[array_rand($this->GRILLS)],
true,
303
303,
);
}
}
@ -44,8 +43,8 @@ class CuteGrills
/**
* Loads the images from the `img/grills/` directory into the `GRILLS` array
*/
public function loadGrills(): void
public function loadGrills():void
{
$this->GRILLS = array_slice(scandir('img/grills/'), 2);
}
}
}

View File

@ -1,6 +1,5 @@
<?php
/**
/**
* Uguu
*
* @copyright Copyright (c) 2022 Go Johansson (nokonoko) <neku@pomf.se>
@ -19,13 +18,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Pomf\Uguu\Classes;
namespace Uguu\Classes;
use Exception;
use PDO;
use Exception;
use PDO;
class Database
{
class Database
{
private PDO $DB;
/**
@ -33,7 +32,7 @@ class Database
*
* @param $DB PDO The database connection.
*/
public function setDB(PDO $DB): void
public function setDB(PDO $DB):void
{
$this->DB = $DB;
}
@ -46,14 +45,15 @@ class Database
* @return int The number of rows that match the query.
* @throws \Exception
*/
public function dbCheckNameExists(string $name): int
public function dbCheckNameExists(string $name):int
{
try {
$q = $this->DB->prepare('SELECT COUNT(filename) FROM files WHERE filename = (:name)');
$q->bindValue(':name', $name);
$q->execute();
return $q->fetchColumn();
} catch (Exception) {
}
catch (Exception) {
throw new Exception('Cant check if name exists in DB.', 500);
}
}
@ -65,7 +65,7 @@ class Database
*
* @throws \Exception
*/
public function checkFileBlacklist(array $FILE_INFO): void
public function checkFileBlacklist(array $FILE_INFO):void
{
try {
$q = $this->DB->prepare('SELECT hash, COUNT(*) AS count FROM blacklist WHERE hash = (:hash)');
@ -75,7 +75,8 @@ class Database
if ($result['count'] > 0) {
throw new Exception('File blacklisted!', 415);
}
} catch (Exception) {
}
catch (Exception) {
throw new Exception('Cant check blacklist DB.', 500);
}
}
@ -87,15 +88,14 @@ class Database
*
* @throws \Exception
*/
public function antiDupe(string $hash): bool | array | string
public function antiDupe(string $hash):bool|array|string
{
if (!$this->CONFIG['ANTI_DUPE']) {
return true;
}
try {
$q = $this->DB->prepare(
'SELECT filename, COUNT(*) AS count FROM files WHERE hash = (:hash)'
'SELECT filename, COUNT(*) AS count FROM files WHERE hash = (:hash)',
);
$q->bindValue(':hash', $hash);
$q->execute();
@ -105,7 +105,8 @@ class Database
} else {
return true;
}
} catch (Exception) {
}
catch (Exception) {
throw new Exception('Cant check for dupes in DB.', 500);
}
}
@ -118,12 +119,12 @@ class Database
*
* @throws \Exception
*/
public function newIntoDB(array $FILE_INFO, array $fingerPrintInfo): void
public function newIntoDB(array $FILE_INFO, array $fingerPrintInfo):void
{
try {
$q = $this->DB->prepare(
'INSERT INTO files (hash, originalname, filename, size, date, ip)' .
'VALUES (:hash, :orig, :name, :size, :date, :ip)'
'VALUES (:hash, :orig, :name, :size, :date, :ip)',
);
$q->bindValue(':hash', $FILE_INFO['SHA1']);
$q->bindValue(':orig', $FILE_INFO['NAME']);
@ -132,24 +133,23 @@ class Database
$q->bindValue(':date', $fingerPrintInfo['timestamp']);
$q->bindValue(':ip', $fingerPrintInfo['ip']);
$q->execute();
} catch (Exception) {
}
catch (Exception) {
throw new Exception('Cant insert into DB.', 500);
}
}
/**
* Creates a new row in the database with the information provided
*
* @param $fingerPrintInfo array
*/
public function createRateLimit(array $fingerPrintInfo): void
public function createRateLimit(array $fingerPrintInfo):void
{
$q = $this->DB->prepare(
'INSERT INTO timestamp (iphash, files, time)' .
'VALUES (:iphash, :files, :time)'
'VALUES (:iphash, :files, :time)',
);
$q->bindValue(':iphash', $fingerPrintInfo['ip_hash']);
$q->bindValue(':files', $fingerPrintInfo['files_amount']);
$q->bindValue(':time', $fingerPrintInfo['timestamp']);
@ -163,60 +163,52 @@ class Database
* @param $iStamp boolean A boolean value that determines whether or not to update the timestamp.
* @param $fingerPrintInfo array An array containing the following keys:
*/
public function updateRateLimit(int $fCount, bool $iStamp, array $fingerPrintInfo): void
public function updateRateLimit(int $fCount, bool $iStamp, array $fingerPrintInfo):void
{
if ($iStamp) {
$q = $this->DB->prepare(
'UPDATE ratelimit SET files = (:files), time = (:time) WHERE iphash = (:iphash)'
'UPDATE ratelimit SET files = (:files), time = (:time) WHERE iphash = (:iphash)',
);
$q->bindValue(':time', $fingerPrintInfo['timestamp']);
} else {
$q = $this->DB->prepare(
'UPDATE ratelimit SET files = (:files) WHERE iphash = (:iphash)'
'UPDATE ratelimit SET files = (:files) WHERE iphash = (:iphash)',
);
}
$q->bindValue(':files', $fCount);
$q->bindValue(':iphash', $fingerPrintInfo['ip_hash']);
$q->execute();
}
/**
* Checks if the user has uploaded more than 100 files in the last minute, if so it returns true, if not it updates the database with the new file count and
* timestamp
* Checks if the user has uploaded more than 100 files in the last minute, if so it returns true, if not it updates the database with the new file
* count and timestamp
*
* @param $fingerPrintInfo array An array containing the following:
*
* @return bool A boolean value.
*/
public function checkRateLimit(array $fingerPrintInfo): bool
public function checkRateLimit(array $fingerPrintInfo):bool
{
$q = $this->DB->prepare(
'SELECT files, time, iphash, COUNT(*) AS count FROM ratelimit WHERE iphash = (:iphash)'
'SELECT files, time, iphash, COUNT(*) AS count FROM ratelimit WHERE iphash = (:iphash)',
);
$q->bindValue(':iphash', $fingerPrintInfo['ip_hash']);
$q->execute();
$result = $q->fetch();
$nTime = $fingerPrintInfo['timestamp'] - (60);
switch (true) {
//If more then 100 files trigger rate-limit
case $result['files'] > 100:
return true;
//if timestamp is older than one minute, set new files count and timestamp
case $result['time'] < $nTime:
$this->updateRateLimit($fingerPrintInfo['files_amount'], true, $fingerPrintInfo);
break;
//if timestamp isn't older than one-minute update the files count
case $result['time'] > $nTime:
$this->updateRateLimit($fingerPrintInfo['files_amount'] + $result['files'], false, $fingerPrintInfo);
break;
//If there is no other match a record does not exist, create one
default:
$this->createRateLimit($fingerPrintInfo);
@ -224,4 +216,4 @@ class Database
}
return false;
}
}
}

View File

@ -1,6 +1,5 @@
<?php
/**
/**
* Uguu
*
* @copyright Copyright (c) 2022 Go Johansson (nokonoko) <neku@pomf.se>
@ -19,10 +18,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Pomf\Uguu\Classes;
namespace Uguu\Classes;
class Response
{
class Response
{
public mixed $type;
/**
@ -67,7 +66,7 @@ class Response
* @param $code mixed The HTTP status code to return.
* @param $desc string The description of the error.
*/
public function error(mixed $code, string $desc): void
public function error(mixed $code, string $desc):void
{
$response = match ($this->type) {
'csv' => $this->csvError($desc),
@ -80,7 +79,7 @@ class Response
}
/* Returning a string that contains the error message. */
private static function csvError(string $description): string
private static function csvError(string $description):string
{
return '"error"' . "\r\n" . "\"$description\"" . "\r\n";
}
@ -93,7 +92,7 @@ class Response
*
* @return string A string.
*/
private static function htmlError(int|string $code, string $description): string
private static function htmlError(int|string $code, string $description):string
{
return '<p>ERROR: (' . $code . ') ' . $description . '</p>';
}
@ -106,7 +105,7 @@ class Response
*
* @return bool|string A JSON string
*/
private static function jsonError(int|string $code, string $description): bool|string
private static function jsonError(int|string $code, string $description):bool|string
{
return json_encode([
'success' => false,
@ -115,7 +114,6 @@ class Response
], JSON_PRETTY_PRINT);
}
/**
* Returns a string that contains the error code and description
*
@ -124,7 +122,7 @@ class Response
*
* @return string A string with the error code and description.
*/
private static function textError(int|string $code, string $description): string
private static function textError(int|string $code, string $description):string
{
return 'ERROR: (' . $code . ') ' . $description;
}
@ -136,7 +134,7 @@ class Response
*
* @param $files array An array of file objects.
*/
public function send(array $files): void
public function send(array $files):void
{
$response = match ($this->type) {
'csv' => $this->csvSuccess($files),
@ -144,7 +142,6 @@ class Response
'json' => $this->jsonSuccess($files),
'text' => $this->textSuccess($files),
};
http_response_code(200); // "200 OK". Success.
echo $response;
}
@ -156,7 +153,7 @@ class Response
*
* @return string A string of the files in the array.
*/
private static function csvSuccess(array $files): string
private static function csvSuccess(array $files):string
{
$result = '"name","url","hash","size"' . "\r\n";
foreach ($files as $file) {
@ -165,7 +162,6 @@ class Response
'"' . $file['hash'] . '"' . ',' .
'"' . $file['size'] . '"' . "\r\n";
}
return $result;
}
@ -176,14 +172,12 @@ class Response
*
* @return string the result of the foreach loop.
*/
private static function htmlSuccess(array $files): string
private static function htmlSuccess(array $files):string
{
$result = '';
foreach ($files as $file) {
$result .= '<a href="' . $file['url'] . '">' . $file['url'] . '</a><br>';
}
return $result;
}
@ -194,7 +188,7 @@ class Response
*
* @return bool|string A JSON string
*/
private static function jsonSuccess(array $files): bool|string
private static function jsonSuccess(array $files):bool|string
{
return json_encode([
'success' => true,
@ -209,14 +203,12 @@ class Response
*
* @return string the url of the file.
*/
private static function textSuccess(array $files): string
private static function textSuccess(array $files):string
{
$result = '';
foreach ($files as $file) {
$result .= $file['url'] . "\n";
}
return $result;
}
}
}

View File

@ -1,6 +1,5 @@
<?php
/**
/**
* Uguu
*
* @copyright Copyright (c) 2022 Go Johansson (nokonoko) <neku@pomf.se>
@ -19,12 +18,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Pomf\Uguu\Classes;
namespace Uguu\Classes;
use Exception;
use Exception;
class Upload extends Response
{
class Upload extends Response
{
public array $FILE_INFO;
public array $fingerPrintInfo;
private mixed $Connector;
@ -37,7 +36,7 @@ class Upload extends Response
* @return array An array of arrays.
* @throws \Exception
*/
public function reFiles(array $files): array
public function reFiles(array $files):array
{
$this->Connector = new Connector();
$this->Connector->setDB($this->Connector->DB);
@ -52,7 +51,7 @@ class Upload extends Response
'SHA1' => $hash,
'EXTENSION' => $this->fileExtension($file),
'MIME' => $this->fileMIME($file),
'NEW_NAME' => $this->generateName($this->fileExtension($file), $hash)
'NEW_NAME' => $this->generateName($this->fileExtension($file), $hash),
];
$result[] = [
$this->FILE_INFO['TEMP_NAME'],
@ -60,11 +59,12 @@ class Upload extends Response
$this->FILE_INFO['SIZE'],
$this->FILE_INFO['SHA1'],
$this->FILE_INFO['EXTENSION'],
$this->FILE_INFO['MIME']
$this->FILE_INFO['MIME'],
];
}
return $result;
}
/**
* Takes an array of arrays and returns an array of arrays with the keys and values swapped
*
@ -92,7 +92,7 @@ class Upload extends Response
* ]
* ```
*/
public function diverseArray(array $files): array
public function diverseArray(array $files):array
{
$result = [];
foreach ($files as $key1 => $value1) {
@ -109,63 +109,57 @@ class Upload extends Response
* @return array An array containing the hash, name, url, and size of the file.
* @throws \Exception
*/
public function uploadFile(): array
public function uploadFile():array
{
if ($this->Connector->CONFIG['RATE_LIMIT']) {
$this->Connector->checkRateLimit($this->fingerPrintInfo);
}
if ($this->Connector->CONFIG['BLACKLIST_DB']) {
$this->Connector->checkFileBlacklist($this->FILE_INFO);
}
if ($this->Connector->CONFIG['FILTER_MODE'] && empty($this->FILE_INFO['EXTENSION'])) {
$this->checkMimeBlacklist();
}
if ($this->Connector->CONFIG['FILTER_MODE'] && !empty($this->FILE_INFO['EXTENSION'])) {
$this->checkMimeBlacklist();
$this->checkExtensionBlacklist();
}
if (!is_dir($this->Connector->CONFIG['FILES_ROOT'])) {
throw new Exception('File storage path not accessible.', 500);
}
if (
!move_uploaded_file($this->FILE_INFO['TEMP_NAME'], $this->Connector->CONFIG['FILES_ROOT'] .
$this->FILE_INFO['NEW_NAME'])
!move_uploaded_file(
$this->FILE_INFO['TEMP_NAME'],
$this->Connector->CONFIG['FILES_ROOT'] .
$this->FILE_INFO['NEW_NAME'],
)
) {
throw new Exception('Failed to move file to destination', 500);
}
if (!chmod($this->Connector->CONFIG['FILES_ROOT'] . $this->FILE_INFO['NEW_NAME'], 0644)) {
throw new Exception('Failed to change file permissions', 500);
}
if (!$this->Connector->CONFIG['LOG_IP']) {
$this->fingerPrintInfo['ip'] = null;
}
$this->Connector->newIntoDB($this->FILE_INFO, $this->fingerPrintInfo);
return [
'hash' => $this->FILE_INFO['SHA1'],
'name' => $this->FILE_INFO['NAME'],
'url' => $this->Connector->CONFIG['FILES_URL'] . '/' . $this->FILE_INFO['NEW_NAME'],
'size' => $this->FILE_INFO['SIZE']
'size' => $this->FILE_INFO['SIZE'],
];
}
/**
* 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
* 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
*
* @param $files_amount int The amount of files that are being uploaded.
*
* @throws \Exception
*/
public function fingerPrint(int $files_amount): void
public function fingerPrint(int $files_amount):void
{
if (!empty($_SERVER['HTTP_USER_AGENT'])) {
$USER_AGENT = filter_var($_SERVER['HTTP_USER_AGENT'], FILTER_SANITIZE_ENCODED);
@ -174,14 +168,13 @@ class Upload extends Response
'useragent' => $USER_AGENT,
'ip' => $_SERVER['REMOTE_ADDR'],
'ip_hash' => hash('sha1', $_SERVER['REMOTE_ADDR'] . $USER_AGENT),
'files_amount' => $files_amount
'files_amount' => $files_amount,
];
} else {
throw new Exception('Invalid user agent.', 500);
}
}
/**
* Returns the MIME type of a file
*
@ -189,7 +182,7 @@ class Upload extends Response
*
* @return string The MIME type of the file.
*/
public function fileMIME(array $file): string
public function fileMIME(array $file):string
{
$FILE_INFO = finfo_open(FILEINFO_MIME_TYPE);
return finfo_file($FILE_INFO, $file['tmp_name']);
@ -202,7 +195,7 @@ class Upload extends Response
*
* @return ?string The file extension of the file.
*/
public function fileExtension(array $file): ?string
public function fileExtension(array $file):?string
{
$extension = explode('.', $file['name']);
if (substr_count($file['name'], '.') > 0) {
@ -217,7 +210,7 @@ class Upload extends Response
*
* @throws \Exception
*/
public function checkMimeBlacklist(): void
public function checkMimeBlacklist():void
{
if (in_array($this->FILE_INFO['MIME'], $this->Connector->CONFIG['BLOCKED_MIME'])) {
throw new Exception('Filetype not allowed.', 415);
@ -229,7 +222,7 @@ class Upload extends Response
*
* @throws \Exception
*/
public function checkExtensionBlacklist(): void
public function checkExtensionBlacklist():void
{
if (in_array($this->FILE_INFO['EXTENSION'], $this->Connector->CONFIG['BLOCKED_EXTENSIONS'])) {
throw new Exception('Filetype not allowed.', 415);
@ -245,20 +238,18 @@ class Upload extends Response
* @return string A string
* @throws \Exception
*/
public function generateName(string $extension, string $hash): string
public function generateName(string $extension, string $hash):string
{
if ($this->Connector->antiDupe($hash)) {
do {
if ($this->Connector->CONFIG['FILES_RETRIES'] === 0) {
throw new Exception('Gave up trying to find an unused name!', 500);
}
$NEW_NAME = '';
for ($i = 0; $i < $this->Connector->CONFIG['NAME_LENGTH']; ++$i) {
$NEW_NAME .= $this->Connector->CONFIG['ID_CHARSET']
[mt_rand(0, strlen($this->Connector->CONFIG['ID_CHARSET']))];
}
if (!empty($extension)) {
$NEW_NAME .= '.' . $extension;
}
@ -268,4 +259,4 @@ class Upload extends Response
return $this->Connector->antiDupe($hash);
}
}
}
}

View File

@ -1,6 +1,5 @@
<?php
/**
/**
* Uguu
*
* @copyright Copyright (c) 2022 Go Johansson (nokonoko) <neku@pomf.se>
@ -19,12 +18,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Pomf\Uguu;
namespace Uguu;
class GrillLoader extends Classes\CuteGrills
{
class GrillLoader extends Classes\CuteGrills
{
public function __construct()
{
$this->showGrills();
}
}
}

View File

@ -18,10 +18,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Pomf\Uguu;
namespace Uguu;
use Exception;
use Pomf\Uguu\Classes\Response;
use Uguu\Classes\Response;
class UploadGateway extends Classes\Upload
{
@ -38,7 +38,6 @@
$type = 'json' ?? $output;
$response = (new Response($type));
if (!empty($_FILES['files'])) {
$files = $this->reFiles($files);
try {
$this->fingerPrint(count($files));
@ -53,7 +52,6 @@
catch (Exception $e) {
$response->error($e->getCode(), $e->getMessage());
}
} else {
$response->error(400, 'No input file(s)');
}

View File

@ -41,6 +41,6 @@
*/
require_once __DIR__ . '/../vendor/autoload.php';
use Pomf\Uguu\GrillLoader;
use Pomf\GrillLoader;
new GrillLoader();

View File

@ -41,7 +41,7 @@
checkConfig();
require_once __DIR__ . '/../vendor/autoload.php';
use Pomf\Uguu\UploadGateway;
use Uguu\UploadGateway;
try {
(new UploadGateway())->handleFile($_GET['output'], $_FILES['files']);