diff --git a/.gitignore b/.gitignore index 3873c71..7923e89 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ build vendor package-lock.json uguu.sq3 -.idea .phpdoc .vscode composer.phar diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/Uguu.iml b/.idea/Uguu.iml new file mode 100644 index 0000000..24d9036 --- /dev/null +++ b/.idea/Uguu.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/codeception.xml b/.idea/codeception.xml new file mode 100644 index 0000000..bb449a7 --- /dev/null +++ b/.idea/codeception.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/.idea/copyright/Uguu_GPL_3.xml b/.idea/copyright/Uguu_GPL_3.xml new file mode 100644 index 0000000..f3c61ed --- /dev/null +++ b/.idea/copyright/Uguu_GPL_3.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..f28da3c --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..1be475e --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..e0cbf93 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 0000000..24761fb --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/phpspec.xml b/.idea/phpspec.xml new file mode 100644 index 0000000..fb99e28 --- /dev/null +++ b/.idea/phpspec.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Makefile b/Makefile index ab3592f..d83dfc7 100755 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ CONTAINER_NAME = "$(CONTAINER_NAME)" pageList = $(shell $(CURDIR)/$(NODEJQ) -r ".pages[]" $(CURDIR)/$(CONF)) noExt = $(shell echo $(i) | cut -d '.' -f1) -all: builddirs npm_dependencies ejs minify copy-img copy-php +all: builddirs npm_dependencies ejs minify copy-img copy-php copy-benchmarks ejs: $(foreach i,$(pageList), \ @@ -49,6 +49,11 @@ copy-php: cp -v $(CURDIR)/src/static/php/*.php $(CURDIR)/build/php/ cp -v $(CURDIR)/src/Classes/*.php $(CURDIR)/build/php/Classes/ +copy-benchmarks: + cp -v $(CURDIR)/src/Benchmarks/*.php $(CURDIR)/build/php/Benchmarks/ + cp -v $(CURDIR)/src/Benchmarks/file.jpg $(CURDIR)/build/php/Benchmarks/ + cp -v $(CURDIR)/src/Benchmarks/runBenchmark.sh $(CURDIR)/build/php/Benchmarks/ + install: installdirs rm -rf $(DESTDIR)/* cp -rv $(CURDIR)/build/* $(DESTDIR)/ @@ -111,5 +116,5 @@ remove-container: docker rm -f uguu builddirs: - mkdir -p $(CURDIR)/build $(CURDIR)/build/img $(CURDIR)/build/html $(CURDIR)/build/html/min $(CURDIR)/build/html/unmin $(CURDIR)/build/js $(CURDIR)/build/css $(CURDIR)/build/php $(CURDIR)/build/php/Classes $(CURDIR)/build/public + mkdir -p $(CURDIR)/build $(CURDIR)/build/img $(CURDIR)/build/html $(CURDIR)/build/html/min $(CURDIR)/build/html/unmin $(CURDIR)/build/js $(CURDIR)/build/css $(CURDIR)/build/php $(CURDIR)/build/php/Classes $(CURDIR)/build/php/Benchmarks $(CURDIR)/build/php/Benchmarks/tmp $(CURDIR)/build/public diff --git a/README.md b/README.md index 1d14543..0e6e44a 100755 --- a/README.md +++ b/README.md @@ -47,9 +47,8 @@ Hit me up at [@nekunekus](https://twitter.com/nekunekus) or email me at neku@pom We'd really like if you can take some time to make sure your coding style is consistent with the project. Uguu follows [PHP -PSR-2](https://www.php-fig.org/psr/psr-12/) and [Airbnb JavaScript -(ES5)](https://github.com/airbnb/javascript/tree/es5-deprecated/es5) (`airbnb/legacy`) -coding style guides. We use ESLint and PHPCS tools to enforce these standards. +PSR-12](https://www.php-fig.org/psr/psr-12/) +coding style guides. We use PHPCS tools to enforce these standards. You can also help by sending us feature requests or writing documentation and tests. diff --git a/RELEASE_NOTE.md b/RELEASE_NOTE.md new file mode 100644 index 0000000..2a046b9 --- /dev/null +++ b/RELEASE_NOTE.md @@ -0,0 +1,44 @@ +## Uguu 1.8.6 + +### Whats new + +* Includes INDEX creation in the dbSchemas files, this greatly improves performance when performing filename generation, antidupe, blacklist or rate-limit checks against the database, + especially on big databases. It's recommended you follow the instructions below on how to add INDEX. +* time() is called once in connector to get a timestamp instead of multiple times. +* The function `diverseArray` is now called `transposeArray`, the variables within the function are also renamed to make it easier to understand. +* The function `uploadFile` performs a check if `BENCHMARK_MODE` is set in the configuration, if it is the file will not be uploaded. +* Benchmarking capbility added. +* Docs updated with how to use [Benchmarking](https://github.com/nokonoko/Uguu/wiki/Benchmarking) and also a [Optimization Guide](https://github.com/nokonoko/Uguu/wiki/Optimization). + +### Breaking changes + +* config.json must include the `"BENCHMARK_MODE"` value, should be set to `false` when not benchmarking, otherwise file(s) will not be uploaded. + +### Add INDEX to an existing Uguu installation + +#### SQLite + +``` +CREATE INDEX files_hash_idx ON files (hash); +CREATE INDEX files_name_idx ON files (filename); +CREATE INDEX ratelimit_iphash_idx ON ratelimit (iphash); +CREATE INDEX blacklist_hash_idx ON blacklist (hash); +``` + +#### PostgreSQL + +``` +CREATE INDEX files_hash_idx ON files (hash); +CREATE INDEX files_name_idx ON files (filename); +CREATE INDEX ratelimit_iphash_idx ON ratelimit (iphash); +CREATE INDEX blacklist_hash_idx ON blacklist (hash); +``` + +#### MySQL + +``` +CREATE INDEX files_hash_idx ON files (hash); +CREATE INDEX files_name_idx ON files (filename); +CREATE INDEX ratelimit_iphash_idx ON ratelimit (iphash); +CREATE INDEX blacklist_hash_idx ON blacklist (hash); +``` \ No newline at end of file diff --git a/package.json b/package.json index 243e60c..74db465 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uguu", - "version": "1.8.5", + "version": "1.8.6", "description": "Uguu is a simple lightweight temporary file host with support for drop, paste, click and API uploading.", "homepage": "https://uguu.se", "repository": { diff --git a/php_cs.dist b/php_cs.dist new file mode 100644 index 0000000..0cb6cf0 --- /dev/null +++ b/php_cs.dist @@ -0,0 +1,46 @@ + + * + * Note that this was previously distributed under the MIT license 2015-2022. + * + * If you are a company that wants to use Uguu I urge you to contact me to + * solve any potential license issues rather then using pre-2022 code. + * + * A special thanks goes out to the open source community around the world + * for supporting and being the backbone of projects like Uguu. + * + * This project can be found at . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +$finder = (new PhpCsFixer\Finder()) + ->in(__DIR__) + ->exclude([ + 'dist', + 'build', + 'node_modules' + ]) +; + +return (new PhpCsFixer\Config()) + ->setRules([ + '@PSR12' => true, + 'strict_param' => true, + ]) + ->setFinder($finder) +; diff --git a/src/Benchmarks/UguuBench.php b/src/Benchmarks/UguuBench.php new file mode 100644 index 0000000..08131fb --- /dev/null +++ b/src/Benchmarks/UguuBench.php @@ -0,0 +1,104 @@ + + * + * Note that this was previously distributed under the MIT license 2015-2022. + * + * If you are a company that wants to use Uguu I urge you to contact me to + * solve any potential license issues rather then using pre-2022 code. + * + * A special thanks goes out to the open source community around the world + * for supporting and being the backbone of projects like Uguu. + * + * This project can be found at . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Pomf\Uguu\Benchmarks; + +use Pomf\Uguu\Classes\Upload; +use Pomf\Uguu\Classes\Response; + +class UguuBench +{ + public function handleFiles(string $outputFormat, array $files): void + { + $upload = new Upload($outputFormat); + $files = $upload->reFiles($files); + $fCount = count($files); + $upload->fingerPrint($fCount); + $res = []; + $i = 0; + while ($i < $fCount) { + $res[] = $upload->uploadFile(); + $i++; + } + if (!empty($res)) { + $upload->send($res); + } + } + + public function handleUp() + { + //$tmp = tempnam(__DIR__ . '/tmp/', 'benchmarkUguu'); + //file_put_contents($tmp, file_get_contents(__DIR__ . '/file.jpg')); + // Mock the $_SERVER array + $_SERVER = [ + 'HTTP_USER_AGENT' => 'curl/8.4.0', + 'REMOTE_ADDR' => '1.2.3.4' + ]; + // Mock the $_FILES array + $_FILES = [ + 'files' => [ + 'name' => [ + 0 => 'file.jpg', + ], + 'full_path' => [ + 0 => 'file.jpg', + ], + 'type' => [ + 0 => 'image/jpeg', + ], + 'tmp_name' => [ + 0 => __DIR__ . '/file.jpg', + ], + 'error' => [ + 0 => 0, + ], + 'size' => [ + 0 => 12345, + ], + ], + ]; + $_GET['output'] = 'benchmark'; + $resType = (isset($_GET['output']) and !empty($_GET['output'])) ? strtolower(preg_replace('/[^a-zA-Z]/', '', $_GET['output'])) : 'json'; + $response = new Response($resType); + if (!isset($_FILES['files']) or empty($_FILES['files'])) { + $response->error(400, 'No input file(s)'); + } + $this->handleFiles($resType, $_FILES['files']); + } + + /** + * @Revs(500) + * @Iterations(15) + */ + public function benchTest() + { + $this->handleUp(); + } +} diff --git a/src/Benchmarks/file.jpg b/src/Benchmarks/file.jpg new file mode 100644 index 0000000..8002e60 Binary files /dev/null and b/src/Benchmarks/file.jpg differ diff --git a/src/Benchmarks/runBenchmark.sh b/src/Benchmarks/runBenchmark.sh new file mode 100755 index 0000000..64aa1bc --- /dev/null +++ b/src/Benchmarks/runBenchmark.sh @@ -0,0 +1,6 @@ +#!/bin/bash +cd .. +sqlite3 /Users/neku/repos/Uguu/dist/Benchmarks/tmp/uguu.sq3 -init /Users/neku/repos/Uguu/src/static/dbSchemas/sqlite_schema.sql "" +./vendor/bin/phpbench run Benchmarks --report=default +rm -rf Benchmarks/tmp/*.jpg +rm -rf Benchmarks/tmp/uguu.sq3 \ No newline at end of file diff --git a/src/Classes/Connector.php b/src/Classes/Connector.php index 9c5715d..7804aa2 100755 --- a/src/Classes/Connector.php +++ b/src/Classes/Connector.php @@ -36,12 +36,14 @@ class Connector extends Database { public PDO $DB; + public Redis $keyDB; public string $dbType; - public array $CONFIG; + public mixed $CONFIG; public Response $response; public Randomizer $randomizer; - - public function errorHandler(int $errno, string $errstr):void + public int $currentTime; + + public function errorHandler(int $errno, string $errstr): void { if ($this->CONFIG['DEBUG']) { $this->response->error(500, 'Server error: ' . $errstr); @@ -49,8 +51,8 @@ public function errorHandler(int $errno, string $errstr):void $this->response->error(500, 'Server error.'); } } - - public function fatalErrorHandler():void + + public function fatalErrorHandler(): void { if (!is_null($e = error_get_last())) { if ($this->CONFIG['DEBUG']) { @@ -60,7 +62,7 @@ public function fatalErrorHandler():void } } } - + /** * Reads the config.json file and populates the CONFIG property with the settings * Also assembles the PDO DB connection and registers error handlers. @@ -85,6 +87,8 @@ public function __construct() $this->CONFIG['DB_USER'], $this->CONFIG['DB_PASS'], ); + $this->randomizer = new Randomizer(); + $this->currentTime = time(); } } diff --git a/src/Classes/CuteGrills.php b/src/Classes/CuteGrills.php index 4f51fe7..2e59651 100755 --- a/src/Classes/CuteGrills.php +++ b/src/Classes/CuteGrills.php @@ -27,17 +27,16 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + namespace Pomf\Uguu\Classes; 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()) { @@ -49,11 +48,11 @@ public function showGrills():void ); } } - + /** * 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); } diff --git a/src/Classes/Database.php b/src/Classes/Database.php index 0d73e79..3d55ef9 100755 --- a/src/Classes/Database.php +++ b/src/Classes/Database.php @@ -27,14 +27,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + namespace Pomf\Uguu\Classes; use PDO; - + class Database { - public function dbCheckNameExists(string $name):bool + public function dbCheckNameExists(string $name): bool { $query = match ($this->dbType) { 'pgsql' => 'SELECT EXISTS(SELECT id FROM files WHERE filename = (:name)), filename FROM files WHERE filename = (:name) LIMIT 1', @@ -52,8 +52,8 @@ public function dbCheckNameExists(string $name):bool } return false; } - - public function checkFileBlacklist(string $hash):void + + public function checkFileBlacklist(string $hash): void { $query = match ($this->dbType) { 'pgsql' => 'SELECT EXISTS(SELECT id FROM blacklist WHERE hash = (:hash)), hash FROM blacklist WHERE hash = (:hash) LIMIT 1', @@ -70,8 +70,8 @@ public function checkFileBlacklist(string $hash):void $this->response->error(415, 'File blacklisted.'); } } - - public function antiDupe(string $hash):array + + public function antiDupe(string $hash): array { $query = match ($this->dbType) { 'pgsql' => 'SELECT EXISTS(SELECT id FROM files WHERE hash = (:hash)), filename FROM files WHERE hash = (:hash) LIMIT 1', @@ -93,8 +93,8 @@ public function antiDupe(string $hash):array ]; } } - - public function newIntoDB(array $FILE_INFO, array $fingerPrintInfo):void + + public function newIntoDB(array $FILE_INFO, array $fingerPrintInfo): void { $q = $this->DB->prepare( 'INSERT INTO files (hash, originalname, filename, size, date, ip)' . @@ -109,8 +109,8 @@ public function newIntoDB(array $FILE_INFO, array $fingerPrintInfo):void $q->execute(); $q->closeCursor(); } - - public function createRateLimit(array $fingerPrintInfo):void + + public function createRateLimit(array $fingerPrintInfo): void { $q = $this->DB->prepare( 'INSERT INTO ratelimit (iphash, files, time)' . @@ -122,8 +122,8 @@ public function createRateLimit(array $fingerPrintInfo):void $q->execute(); $q->closeCursor(); } - - 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( @@ -140,17 +140,17 @@ public function updateRateLimit(int $fCount, bool $iStamp, array $fingerPrintInf $q->execute(); $q->closeCursor(); } - - public function compareTime(int $timestamp, int $seconds_d):bool + + public function compareTime(int $timestamp, int $seconds_d): bool { - $diff = time() - $timestamp; + $diff = $this->currentTime - $timestamp; if ($diff > $seconds_d) { return true; } return false; } - - public function checkRateLimit(array $fingerPrintInfo, int $rateTimeout, int $fileLimit):bool + + public function checkRateLimit(array $fingerPrintInfo, int $rateTimeout, int $fileLimit): bool { $query = match ($this->dbType) { 'pgsql' => 'SELECT EXISTS(SELECT id FROM ratelimit WHERE iphash = (:iphash)), id, iphash, files, time FROM ratelimit WHERE iphash = (:iphash) LIMIT 1', diff --git a/src/Classes/GrillLoader.php b/src/Classes/GrillLoader.php index f0233af..e1e2f04 100755 --- a/src/Classes/GrillLoader.php +++ b/src/Classes/GrillLoader.php @@ -27,7 +27,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + namespace Pomf\Uguu\Classes; class GrillLoader extends CuteGrills diff --git a/src/Classes/Response.php b/src/Classes/Response.php index 4eafece..09e004b 100755 --- a/src/Classes/Response.php +++ b/src/Classes/Response.php @@ -27,13 +27,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + namespace Pomf\Uguu\Classes; class Response { public string $type; - + /** * Takes a string as an argument and sets the header to the appropriate content type * @@ -55,6 +55,9 @@ public function __construct(string $response_type) header('Content-Type: application/json; charset=UTF-8'); $this->type = $response_type; break; + case 'benchmark': + $this->type = $response_type; + break; case 'gyazo': header('Content-Type: text/plain; charset=UTF-8'); $this->type = 'text'; @@ -69,32 +72,33 @@ public function __construct(string $response_type) break; } } - + /** * Returns a string based on the type of response requested * * @param $code mixed The HTTP status code to return. * @param $desc string The description of the error. */ - public function error(int $code, string $desc):string + public function error(int $code, string $desc): string { $response = match ($this->type) { 'csv' => $this->csvError($desc), 'html' => $this->htmlError($code, $desc), 'json' => $this->jsonError($code, $desc), + 'benchmark' => $this->jsonError($code, $desc), 'text' => $this->textError($code, $desc), }; http_response_code($code); echo $response; exit(1); } - + /* 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"; } - + /** * Returns a string containing an HTML paragraph element with the error code and description * @@ -103,11 +107,11 @@ private static function csvError(string $description):string * * @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 '

ERROR: (' . $code . ') ' . $description . '

'; } - + /** * Returns a JSON string with the error code and description * @@ -116,7 +120,7 @@ private static function htmlError(int|string $code, string $description):string * * @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, @@ -124,7 +128,7 @@ private static function jsonError(int|string $code, string $description):bool|st 'description' => $description, ], JSON_PRETTY_PRINT); } - + /** * Returns a string that contains the error code and description * @@ -133,11 +137,11 @@ private static function jsonError(int|string $code, string $description):bool|st * * @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; } - + /** * "If the type is csv, then call the csvSuccess function, * if the type is html, then call the htmlSuccess function, etc." @@ -146,18 +150,21 @@ private static function textError(int|string $code, string $description):string * * @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), 'html' => $this->htmlSuccess($files), 'json' => $this->jsonSuccess($files), + 'benchmark' => true, 'text' => $this->textSuccess($files), }; http_response_code(200); // "200 OK". Success. - echo $response; + if($this->type != 'benchmark') { + echo $response; + } } - + /** * Takes an array of files and returns a CSV string * @@ -165,7 +172,7 @@ public function send(array $files):void * * @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) { @@ -176,7 +183,7 @@ private static function csvSuccess(array $files):string } return $result; } - + /** * Takes an array of files and returns a string of HTML links * @@ -184,7 +191,7 @@ private static function csvSuccess(array $files):string * * @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) { @@ -192,7 +199,7 @@ private static function htmlSuccess(array $files):string } return $result; } - + /** * Returns a JSON string that contains a success message and the files that were uploaded * @@ -200,14 +207,14 @@ private static function htmlSuccess(array $files):string * * @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, 'files' => $files, ], JSON_PRETTY_PRINT); } - + /** * Takes an array of files and returns a string of URLs * @@ -215,7 +222,7 @@ private static function jsonSuccess(array $files):bool|string * * @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) { diff --git a/src/Classes/Upload.php b/src/Classes/Upload.php index 894e8df..66176c2 100755 --- a/src/Classes/Upload.php +++ b/src/Classes/Upload.php @@ -29,6 +29,13 @@ * along with this program. If not, see . */ +/** + * 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 int $files_amount The amount of files that are being uploaded. + */ + namespace Pomf\Uguu\Classes; class Upload extends Response @@ -36,7 +43,7 @@ class Upload extends Response public array $FILE_INFO; public array $fingerPrintInfo; private mixed $Connector; - + /** * Resolves and processes an array of files, performing various checks and operations on each file. * @@ -64,11 +71,11 @@ class Upload extends Response * * - 'dupe' : Indicates if the uploaded file is a duplicate. * * - 'filename' : The final filename of the uploaded file. */ - public function reFiles(array $files):array + public function reFiles(array $files): array { $this->Connector = new Connector(); $result = []; - $files = $this->diverseArray($files); + $files = $this->transposeArray($files); foreach ($files as $file) { $this->FILE_INFO = [ 'TEMP_NAME' => $file['tmp_name'], @@ -106,25 +113,28 @@ public function reFiles(array $files):array } return $result; } - + /** - * Rearranges a multidimensional array by exchanging the keys of the first and second level. + * Transposes a 2-dimensional array. + * + * Transposes the given 2-dimensional array, where the rows of the input array become the columns + * of the transposed array. * - * @param array $files The multidimensional array to be rearranged. + * @param array $inputArray The input 2-dimensional array to transpose. * - * @return array The rearranged array with exchanged keys of the first and second level. + * @return array The transposed array, with exchanged keys of the first and second level. */ - public function diverseArray(array $files):array + public function transposeArray(array $inputArray): array { - $result = []; - foreach ($files as $key1 => $value1) { - foreach ($value1 as $key2 => $value2) { - $result[$key2][$key1] = $value2; + $transposedArray = []; + foreach ($inputArray as $key1 => $nestedArray) { + foreach ($nestedArray as $key2 => $nestedValue) { + $transposedArray[$key2][$key1] = $nestedValue; } } - return $result; + return $transposedArray; } - + /** * Performs various checks (if enabled), insert info into database, moves file to storage * location, then returns an array of file information. @@ -140,7 +150,7 @@ public function diverseArray(array $files):array * - size : The size of the uploaded file * - dupe : Boolean indicating whether the file is a duplicate */ - public function uploadFile():array + public function uploadFile(): array { if ($this->Connector->CONFIG['RATE_LIMIT']) { if ( @@ -180,17 +190,22 @@ public function uploadFile():array if (!is_dir($this->Connector->CONFIG['FILES_ROOT'])) { $this->Connector->response->error(500, 'File storage path not accessible.'); } - if ( - !move_uploaded_file( - $this->FILE_INFO['TEMP_NAME'], - $this->Connector->CONFIG['FILES_ROOT'] . - $this->FILE_INFO['FILENAME'], - ) - ) { - $this->Connector->response->error(500, 'Failed to move file to destination.'); + if (!is_writable($this->Connector->CONFIG['FILES_ROOT'])) { + $this->Connector->response->error(500, 'File storage path not writeable.'); } - if (!chmod($this->Connector->CONFIG['FILES_ROOT'] . $this->FILE_INFO['FILENAME'], 0644)) { - $this->Connector->response->error(500, 'Failed to change file permissions.'); + if(!$this->Connector->CONFIG['BENCHMARK_MODE']) { + if ( + !move_uploaded_file( + $this->FILE_INFO['TEMP_NAME'], + $this->Connector->CONFIG['FILES_ROOT'] . + $this->FILE_INFO['FILENAME'], + ) + ) { + $this->Connector->response->error(500, 'Failed to move file to destination.'); + } + if (!chmod($this->Connector->CONFIG['FILES_ROOT'] . $this->FILE_INFO['FILENAME'], 0644)) { + $this->Connector->response->error(500, 'Failed to change file permissions.'); + } } $this->Connector->newIntoDB($this->FILE_INFO, $this->fingerPrintInfo); } @@ -203,7 +218,7 @@ public function uploadFile():array 'dupe' => $this->FILE_INFO['DUPE'], ]; } - + /** * 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. @@ -211,7 +226,7 @@ public function uploadFile():array * @param $files_amount int The amount of files that are being uploaded. * */ - 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); @@ -220,7 +235,7 @@ public function fingerPrint(int $files_amount):void $ip = $_SERVER['REMOTE_ADDR']; } $this->fingerPrintInfo = [ - 'timestamp' => time(), + 'timestamp' => $this->Connector->currentTime, 'useragent' => $USER_AGENT, 'ip' => $ip, 'ip_hash' => hash('xxh3', $_SERVER['REMOTE_ADDR'] . $USER_AGENT), @@ -230,7 +245,7 @@ public function fingerPrint(int $files_amount):void $this->Connector->response->error(500, 'Invalid user agent.'); } } - + /** * Returns the MIME type of a file * @@ -238,12 +253,12 @@ public function fingerPrint(int $files_amount):void * * @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']); } - + /** * Determines the double dot file extension from the given file. * @@ -255,7 +270,7 @@ public function fileMIME(array $file):string * * @return string The extracted extension. */ - public function doubleDotExtension(array $extension):string + public function doubleDotExtension(array $extension): string { $doubleDotArray = array_slice($extension, -2, 2); $doubleDot = strtolower(preg_replace('/[^a-zA-Z.]/', '', join('.', $doubleDotArray))); @@ -264,7 +279,7 @@ public function doubleDotExtension(array $extension):string } return end($extension); } - + /** * Determines the file extension from the given file. * @@ -280,7 +295,7 @@ public function doubleDotExtension(array $extension):string * * @return string|bool The file extension if it exists, or false if the file name does not contain a dot. */ - public function fileExtension(array $file):string|bool + public function fileExtension(array $file): string|bool { if (str_contains($file['name'], '.')) { $extension = explode('.', $file['name']); @@ -292,7 +307,7 @@ public function fileExtension(array $file):string|bool } return false; } - + /** * Checks if the MIME type of the uploaded file is in the blacklist. * @@ -300,13 +315,13 @@ public function fileExtension(array $file):string|bool * is not allowed. * */ - public function checkMimeBlacklist():void + public function checkMimeBlacklist(): void { if (in_array($this->FILE_INFO['MIME'], $this->Connector->CONFIG['FILTER_MIME'])) { $this->Connector->response->error(415, 'Filetype not allowed'); } } - + /** * Checks if the MIME type of the uploaded file is in the whitelist. * @@ -314,13 +329,13 @@ public function checkMimeBlacklist():void * is not allowed. * */ - public function checkMimeWhitelist():void + public function checkMimeWhitelist(): void { if (!in_array($this->FILE_INFO['MIME'], $this->Connector->CONFIG['FILTER_MIME'])) { $this->Connector->response->error(415, 'Filetype not allowed'); } } - + /** * Checks if the extension of the uploaded file is in the blacklist. * @@ -328,13 +343,13 @@ public function checkMimeWhitelist():void * is not allowed. * */ - public function checkExtensionBlacklist():void + public function checkExtensionBlacklist(): void { if (in_array($this->FILE_INFO['EXTENSION'], $this->Connector->CONFIG['FILTER_EXTENSIONS'])) { $this->Connector->response->error(415, 'Filetype not allowed'); } } - + /** * Checks if the extension of the uploaded file is in the whitelist. * @@ -342,13 +357,13 @@ public function checkExtensionBlacklist():void * is not allowed. * */ - public function checkExtensionWhitelist():void + public function checkExtensionWhitelist(): void { if (!in_array($this->FILE_INFO['EXTENSION'], $this->Connector->CONFIG['FILTER_EXTENSIONS'])) { $this->Connector->response->error(415, 'Filetype not allowed'); } } - + /** * Checks if the length of the given filename exceeds 250 characters. * @@ -359,14 +374,14 @@ public function checkExtensionWhitelist():void * * @return string The filename, either unchanged or truncated if its length exceeds 250 characters. */ - public function checkNameLength(string $fileName):string + public function checkNameLength(string $fileName): string { if (strlen($fileName) > 250) { return substr($fileName, 0, 250); } return $fileName; } - + /** * Generates a unique name for a file. * @@ -381,7 +396,7 @@ public function checkNameLength(string $fileName):string * * @return string The generated unique name for the file. */ - public function generateName(string $extension):string + public function generateName(string $extension): string { do { if ($this->Connector->CONFIG['FILES_RETRIES'] === 0) { diff --git a/src/Classes/expireChecker.php b/src/Classes/expireChecker.php index f06b384..8a07fe5 100755 --- a/src/Classes/expireChecker.php +++ b/src/Classes/expireChecker.php @@ -27,11 +27,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + namespace Pomf\Uguu\Classes; use PDO; - + class expireChecker { public PDO $DB; @@ -42,8 +42,7 @@ class expireChecker * @var \Pomf\Uguu\Classes\Response */ private Response $response; - - public function checkDB():bool|array + public function checkDB(): bool|array { if (is_int($this->CONFIG['expireTime'])) { $this->timeUnit = strtoupper($this->CONFIG['expireTimeUnit']); @@ -71,8 +70,8 @@ public function checkDB():bool|array return false; } } - - public function cleanRateLimitDB():void + + public function cleanRateLimitDB(): void { $query = match ($this->dbType) { 'pgsql' => 'DELETE FROM ratelimit WHERE time < EXTRACT(epoch from NOW() - INTERVAL \'24 HOURS\')', @@ -82,15 +81,15 @@ public function cleanRateLimitDB():void $q->execute(); $q->closeCursor(); } - - public function deleteFiles(array $filenames):void + + public function deleteFiles(array $filenames): void { foreach ($filenames as $filename) { unlink($this->CONFIG['FILES_ROOT'] . $filename); } } - - public function deleteFromDB(array $ids):void + + public function deleteFromDB(array $ids): void { foreach ($ids as $id) { $query = match ($this->dbType) { @@ -103,7 +102,7 @@ public function deleteFromDB(array $ids):void $q->closeCursor(); } } - + /** * Reads the config.json file and populates the CONFIG property with the settings * Also assembles the PDO DB connection and registers error handlers. diff --git a/src/composer.json b/src/composer.json index ea1b94d..b820c67 100755 --- a/src/composer.json +++ b/src/composer.json @@ -9,6 +9,11 @@ "Pomf\\Uguu\\Classes\\": "./Classes" } }, + "autoload-dev": { + "psr-4": { + "Pomf\\Uguu\\Benchmarks\\": "./Benchmarks" + } + }, "authors": [ { "name": "Go Johansson (neku)", @@ -20,6 +25,9 @@ "ext-fileinfo": "*", "ext-pdo": "*" }, + "require-dev": { + "phpbench/phpbench": "^1.0" + }, "config": { "optimize-autoloader": true, "classmap-authoritative": true diff --git a/src/config.json b/src/config.json index e3d646a..2b867e6 100755 --- a/src/config.json +++ b/src/config.json @@ -1,12 +1,13 @@ { "dest": "dist", - "pkgVersion": "1.8.5", + "pkgVersion": "1.8.6", "pages": [ "index.ejs", "faq.ejs", "api.ejs" ], "DEBUG": false, + "BENCHMARK_MODE": true, "max_upload_size": 128, "expireTime": 8, "expireTimeUnit": "hours", @@ -27,17 +28,17 @@ "kofiUrl": "", "malwareBanner": false, "DB_MODE": "sqlite", - "DB_PATH": "/var/www/db/uguu.sq3", + "DB_PATH": "/var/www/uguu-dev/uguu.sq3", "DB_USER": "NULL", "DB_PASS": "NULL", "LOG_IP": false, "ANTI_DUPE": false, "BLACKLIST_DB": false, "FILTER_MODE": true, - "RATE_LIMIT": true, + "RATE_LIMIT": false, "RATE_LIMIT_TIMEOUT": 60, "RATE_LIMIT_FILES": 100, - "FILES_ROOT": "/var/www/files/", + "FILES_ROOT": "/var/www/dev-files/", "FILES_RETRIES": 15, "NAME_LENGTH": 8, "ID_CHARSET": "abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ", diff --git a/src/phpbench.json b/src/phpbench.json new file mode 100644 index 0000000..f69355d --- /dev/null +++ b/src/phpbench.json @@ -0,0 +1,4 @@ +{ + "$schema": "./vendor/phpbench/phpbench/phpbench.schema.json", + "runner.bootstrap": "vendor/autoload.php" +} \ No newline at end of file diff --git a/src/static/dbSchemas/mysql_schema.sql b/src/static/dbSchemas/mysql_schema.sql index 5a7d813..c3bb027 100755 --- a/src/static/dbSchemas/mysql_schema.sql +++ b/src/static/dbSchemas/mysql_schema.sql @@ -32,4 +32,9 @@ CREATE TABLE `ratelimit` `files` int(15) default NULL, `time` int(15) DEFAULT NULL, PRIMARY KEY (`id`) -); \ No newline at end of file +); + +CREATE INDEX files_hash_idx ON files (hash); +CREATE INDEX files_name_idx ON files (filename); +CREATE INDEX ratelimit_iphash_idx ON ratelimit (iphash); +CREATE INDEX blacklist_hash_idx ON blacklist (hash); \ No newline at end of file diff --git a/src/static/dbSchemas/postgres_schema.sql b/src/static/dbSchemas/postgres_schema.sql index 099b542..7472e86 100755 --- a/src/static/dbSchemas/postgres_schema.sql +++ b/src/static/dbSchemas/postgres_schema.sql @@ -32,4 +32,8 @@ CREATE TABLE accounts email text NOT NULL, pass text not null, level integer not null -); \ No newline at end of file +); +CREATE INDEX files_hash_idx ON files (hash); +CREATE INDEX files_name_idx ON files (filename); +CREATE INDEX ratelimit_iphash_idx ON ratelimit (iphash); +CREATE INDEX blacklist_hash_idx ON blacklist (hash); \ No newline at end of file diff --git a/src/static/dbSchemas/sqlite_schema.sql b/src/static/dbSchemas/sqlite_schema.sql index 9354ab4..5cb308b 100755 --- a/src/static/dbSchemas/sqlite_schema.sql +++ b/src/static/dbSchemas/sqlite_schema.sql @@ -28,4 +28,8 @@ CREATE TABLE `ratelimit` ( , `time` integer default NULL , `files` integer default NULL ); +CREATE INDEX files_hash_idx ON files (hash); +CREATE INDEX files_name_idx ON files (filename); +CREATE INDEX ratelimit_iphash_idx ON ratelimit (iphash); +CREATE INDEX blacklist_hash_idx ON blacklist (hash); END TRANSACTION; diff --git a/src/static/php/grill.php b/src/static/php/grill.php index b5390b7..c78dfcf 100755 --- a/src/static/php/grill.php +++ b/src/static/php/grill.php @@ -27,9 +27,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + require_once __DIR__ . '/../vendor/autoload.php'; - + use Pomf\Uguu\Classes\GrillLoader; - + new GrillLoader(); diff --git a/src/static/php/upload.php b/src/static/php/upload.php index 9fd5153..d0f35c5 100755 --- a/src/static/php/upload.php +++ b/src/static/php/upload.php @@ -32,7 +32,7 @@ use Pomf\Uguu\Classes\Upload; use Pomf\Uguu\Classes\Response; -function handleFiles(string $outputFormat, array $files):void +function handleFiles(string $outputFormat, array $files): void { $upload = new Upload($outputFormat); $files = $upload->reFiles($files);