diff --git a/composer.json b/composer.json index 15572a9..8475536 100644 --- a/composer.json +++ b/composer.json @@ -1,13 +1,13 @@ { "require": { - "flashwave/index": "^0.2410", - "flashii/rpcii": "^3.0", - "flashii/apii": "^0.3", + "flashwave/index": "^0.2503", + "flashii/rpcii": "^5.0", + "flashii/apii": "^0.4", "sentry/sdk": "^4.0", - "nesbot/carbon": "^3.7" + "nesbot/carbon": "^3.8" }, "require-dev": { - "phpstan/phpstan": "^2.0" + "phpstan/phpstan": "^2.1" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 4bd50d2..db07489 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7009c2a98d8882c938653f4455f52a82", + "content-hash": "2d07dcd636742c34625c576e67eaaf0b", "packages": [ { "name": "carbonphp/carbon-doctrine-types", @@ -77,18 +77,20 @@ }, { "name": "flashii/apii", - "version": "v0.3.0", + "version": "v0.4.0", "source": { "type": "git", "url": "https://patchii.net/flashii/apii-php.git", - "reference": "2d6c135faddd359341762afcb9c429e279d87059" + "reference": "d330e0792515dbae2832bd8e2ac761cc685175e9" }, "require": { - "php": ">=8.1" + "guzzlehttp/guzzle": "~7.9", + "php": ">=8.1", + "psr/http-client": "^1.0" }, "require-dev": { - "phpstan/phpstan": "^1.12", - "phpunit/phpunit": "^10.5" + "phpstan/phpstan": "~2.1", + "phpunit/phpunit": "~10.5" }, "type": "library", "autoload": { @@ -110,24 +112,26 @@ ], "description": "Client library for the Flashii.net API.", "homepage": "https://api.flashii.net", - "time": "2024-11-22T21:36:01+00:00" + "time": "2025-03-20T17:05:52+00:00" }, { "name": "flashii/rpcii", - "version": "v3.0.0", + "version": "v5.0.1", "source": { "type": "git", "url": "https://patchii.net/flashii/rpcii-php.git", - "reference": "25ac46d5dee60027032e175107e638dfb0b8f7f9" + "reference": "28c25e0a342173524f8894d03158663842e03252" }, "require": { "ext-msgpack": ">=2.2", - "flashwave/index": "^0.2410", - "php": ">=8.4" + "flashwave/index": "^0.2503", + "guzzlehttp/guzzle": "~7.0", + "php": ">=8.4", + "psr/http-client": "^1.0" }, "require-dev": { "phpstan/phpstan": "^2.1", - "phpunit/phpunit": "^11.5" + "phpunit/phpunit": "^12.0" }, "type": "library", "autoload": { @@ -149,25 +153,27 @@ ], "description": "HTTP RPC client/server library.", "homepage": "https://railgun.sh/rpcii", - "time": "2025-01-17T00:05:22+00:00" + "time": "2025-03-21T19:38:29+00:00" }, { "name": "flashwave/index", - "version": "v0.2410.830205", + "version": "v0.2503.221959", "source": { "type": "git", "url": "https://patchii.net/flash/index.git", - "reference": "416c716b2efab9619d14be02a20cd6540e18a83f" + "reference": "d99562db163589cc51e32e10e851085230fb3181" }, "require": { "ext-mbstring": "*", - "php": ">=8.3", - "twig/html-extra": "^3.13", - "twig/twig": "^3.14" + "php": ">=8.4", + "psr/http-message": "^2.0", + "psr/http-server-handler": "^1.0", + "twig/html-extra": "^3.20", + "twig/twig": "^3.20" }, "require-dev": { - "phpstan/phpstan": "^2.0", - "phpunit/phpunit": "^11.4" + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^12.0" }, "suggest": { "ext-memcache": "Support for the Index\\Cache\\Memcached namespace (only if you can't use ext-memcached for some reason).", @@ -204,7 +210,216 @@ ], "description": "Composer package for the common library for my projects.", "homepage": "https://railgun.sh/index", - "time": "2024-12-22T02:05:46+00:00" + "time": "2025-03-22T19:59:44+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.9.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.9.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2024-07-24T11:22:20+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2024-10-17T10:06:22+00:00" }, { "name": "guzzlehttp/psr7", @@ -324,16 +539,16 @@ }, { "name": "jean85/pretty-package-versions", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10" + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", - "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a", + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a", "shasum": "" }, "require": { @@ -343,8 +558,9 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", "jean85/composer-provided-replaced-stub-package": "^1.0", - "phpstan/phpstan": "^1.4", + "phpstan/phpstan": "^2.0", "phpunit/phpunit": "^7.5|^8.5|^9.6", + "rector/rector": "^2.0", "vimeo/psalm": "^4.3 || ^5.0" }, "type": "library", @@ -377,22 +593,22 @@ ], "support": { "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.0" + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1" }, - "time": "2024-11-18T16:19:46+00:00" + "time": "2025-03-19T14:43:43+00:00" }, { "name": "nesbot/carbon", - "version": "3.8.4", + "version": "3.8.6", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "129700ed449b1f02d70272d2ac802357c8c30c58" + "reference": "ff2f20cf83bd4d503720632ce8a426dc747bf7fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/129700ed449b1f02d70272d2ac802357c8c30c58", - "reference": "129700ed449b1f02d70272d2ac802357c8c30c58", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ff2f20cf83bd4d503720632ce8a426dc747bf7fd", + "reference": "ff2f20cf83bd4d503720632ce8a426dc747bf7fd", "shasum": "" }, "require": { @@ -468,8 +684,8 @@ ], "support": { "docs": "https://carbon.nesbot.com/docs", - "issues": "https://github.com/briannesbitt/Carbon/issues", - "source": "https://github.com/briannesbitt/Carbon" + "issues": "https://github.com/CarbonPHP/carbon/issues", + "source": "https://github.com/CarbonPHP/carbon" }, "funding": [ { @@ -485,7 +701,7 @@ "type": "tidelift" } ], - "time": "2024-12-27T09:25:35+00:00" + "time": "2025-02-20T17:33:38+00:00" }, { "name": "psr/clock", @@ -535,6 +751,58 @@ }, "time": "2022-11-25T14:36:26+00:00" }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, { "name": "psr/http-factory", "version": "1.1.0", @@ -643,6 +911,62 @@ }, "time": "2023-04-04T09:54:51+00:00" }, + { + "name": "psr/http-server-handler", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "source": "https://github.com/php-fig/http-server-handler/tree/1.0.2" + }, + "time": "2023-04-10T20:06:20+00:00" + }, { "name": "psr/log", "version": "3.0.2", @@ -1024,16 +1348,16 @@ }, { "name": "symfony/mime", - "version": "v7.2.1", + "version": "v7.2.4", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283" + "reference": "87ca22046b78c3feaff04b337f33b38510fd686b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/7f9617fcf15cb61be30f8b252695ed5e2bfac283", - "reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283", + "url": "https://api.github.com/repos/symfony/mime/zipball/87ca22046b78c3feaff04b337f33b38510fd686b", + "reference": "87ca22046b78c3feaff04b337f33b38510fd686b", "shasum": "" }, "require": { @@ -1088,7 +1412,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.2.1" + "source": "https://github.com/symfony/mime/tree/v7.2.4" }, "funding": [ { @@ -1104,7 +1428,7 @@ "type": "tidelift" } ], - "time": "2024-12-07T08:50:44+00:00" + "time": "2025-02-19T08:51:20+00:00" }, { "name": "symfony/options-resolver", @@ -1496,82 +1820,6 @@ ], "time": "2024-09-09T11:45:10+00:00" }, - { - "name": "symfony/polyfill-php81", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, { "name": "symfony/polyfill-php83", "version": "v1.31.0", @@ -1650,16 +1898,16 @@ }, { "name": "symfony/translation", - "version": "v7.2.2", + "version": "v7.2.4", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "e2674a30132b7cc4d74540d6c2573aa363f05923" + "reference": "283856e6981286cc0d800b53bd5703e8e363f05a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/e2674a30132b7cc4d74540d6c2573aa363f05923", - "reference": "e2674a30132b7cc4d74540d6c2573aa363f05923", + "url": "https://api.github.com/repos/symfony/translation/zipball/283856e6981286cc0d800b53bd5703e8e363f05a", + "reference": "283856e6981286cc0d800b53bd5703e8e363f05a", "shasum": "" }, "require": { @@ -1725,7 +1973,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.2.2" + "source": "https://github.com/symfony/translation/tree/v7.2.4" }, "funding": [ { @@ -1741,7 +1989,7 @@ "type": "tidelift" } ], - "time": "2024-12-07T08:18:10+00:00" + "time": "2025-02-13T10:27:23+00:00" }, { "name": "symfony/translation-contracts", @@ -1823,20 +2071,20 @@ }, { "name": "twig/html-extra", - "version": "v3.18.0", + "version": "v3.20.0", "source": { "type": "git", "url": "https://github.com/twigphp/html-extra.git", - "reference": "c63b28e192c1b7c15bb60f81d2e48b140846239a" + "reference": "f7d54d4de1b64182af745cfb66777f699b599734" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/html-extra/zipball/c63b28e192c1b7c15bb60f81d2e48b140846239a", - "reference": "c63b28e192c1b7c15bb60f81d2e48b140846239a", + "url": "https://api.github.com/repos/twigphp/html-extra/zipball/f7d54d4de1b64182af745cfb66777f699b599734", + "reference": "f7d54d4de1b64182af745cfb66777f699b599734", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/mime": "^5.4|^6.4|^7.0", "twig/twig": "^3.13|^4.0" @@ -1875,7 +2123,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/html-extra/tree/v3.18.0" + "source": "https://github.com/twigphp/html-extra/tree/v3.20.0" }, "funding": [ { @@ -1887,28 +2135,27 @@ "type": "tidelift" } ], - "time": "2024-12-29T10:29:59+00:00" + "time": "2025-01-31T20:45:36+00:00" }, { "name": "twig/twig", - "version": "v3.18.0", + "version": "v3.20.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50" + "reference": "3468920399451a384bef53cf7996965f7cd40183" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50", - "reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/3468920399451a384bef53cf7996965f7cd40183", + "reference": "3468920399451a384bef53cf7996965f7cd40183", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php81": "^1.29" + "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { "phpstan/phpstan": "^2.0", @@ -1955,7 +2202,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.18.0" + "source": "https://github.com/twigphp/Twig/tree/v3.20.0" }, "funding": [ { @@ -1967,22 +2214,22 @@ "type": "tidelift" } ], - "time": "2024-12-29T10:51:50+00:00" + "time": "2025-02-13T08:34:43+00:00" } ], "packages-dev": [ { "name": "phpstan/phpstan", - "version": "2.1.1", + "version": "2.1.8", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7" + "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7", - "reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f9adff3b87c03b12cc7e46a30a524648e497758f", + "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f", "shasum": "" }, "require": { @@ -2027,7 +2274,7 @@ "type": "github" } ], - "time": "2025-01-05T16:43:48+00:00" + "time": "2025-03-09T09:30:48+00:00" } ], "aliases": [], diff --git a/src/Auth/AuthContext.php b/src/Auth/AuthContext.php index f6bd2a4..99abec0 100644 --- a/src/Auth/AuthContext.php +++ b/src/Auth/AuthContext.php @@ -28,7 +28,8 @@ class AuthContext { public function createClient(Credentials $credentials): FlashiiClient { return new FlashiiClient('EEPROM', $credentials, new FlashiiUrls( - $this->config->getString('api', FlashiiUrls::PROD_API_URL) + $this->config->getString('url', FlashiiUrls::PROD_URL), + $this->config->getString('api', FlashiiUrls::PROD_API_URL), )); } diff --git a/src/Auth/AuthRoutes.php b/src/Auth/AuthRoutes.php index 45404b7..af54c02 100644 --- a/src/Auth/AuthRoutes.php +++ b/src/Auth/AuthRoutes.php @@ -1,17 +1,19 @@ <?php namespace EEPROM\Auth; -use Index\Http\Routing\{HttpMiddleware,RouteHandler,RouteHandlerTrait}; +use Index\Http\{HttpResponseBuilder,HttpRequest}; +use Index\Http\Routing\Processors\Preprocessor; +use Index\Http\Routing\{HttpMiddleware,RouteHandler,RouteHandlerCommon}; class AuthRoutes implements RouteHandler { - use RouteHandlerTrait; + use RouteHandlerCommon; public function __construct( private AuthContext $authCtx ) {} - #[HttpMiddleware('/')] - public function getIndex($response, $request) { + #[Preprocessor('eeprom:attempt-auth')] + public function getIndex(HttpResponseBuilder $response, HttpRequest $request) { $auth = $request->getHeaderLine('Authorization'); if(empty($auth)) { $cookie = (string)$request->getCookie('msz_auth'); @@ -21,7 +23,7 @@ class AuthRoutes implements RouteHandler { if(!empty($auth)) { $parts = explode(' ', $auth, 2); - $method = strval($parts[0] ?? ''); + $method = $parts[0]; $token = strval($parts[1] ?? ''); $flashii = null; diff --git a/src/DatabaseContext.php b/src/DatabaseContext.php index 176ee3d..af1bc22 100644 --- a/src/DatabaseContext.php +++ b/src/DatabaseContext.php @@ -3,22 +3,23 @@ namespace EEPROM; use Index\Db\DbConnection; use Index\Db\Migration\{DbMigrationManager,DbMigrationRepo,FsDbMigrationRepo}; -use Index\Http\Routing\{HttpMiddleware,RouteHandler,RouteHandlerTrait}; +use Index\Http\Routing\{RouteHandler,RouteHandlerCommon}; +use Index\Http\Routing\Filters\PrefixFilter; class DatabaseContext implements RouteHandler { - use RouteHandlerTrait; + use RouteHandlerCommon; public function __construct( - public private(set) DbConnection $connection + public private(set) DbConnection $conn ) {} public function getQueryCount(): int { - $result = $this->connection->query('SHOW SESSION STATUS LIKE "Questions"'); + $result = $this->conn->query('SHOW SESSION STATUS LIKE "Questions"'); return $result->next() ? $result->getInteger(1) : 0; } public function createMigrationManager(): DbMigrationManager { - return new DbMigrationManager($this->connection, 'prm_' . DbMigrationManager::DEFAULT_TABLE); + return new DbMigrationManager($this->conn, 'prm_' . DbMigrationManager::DEFAULT_TABLE); } public function createMigrationRepo(): DbMigrationRepo { @@ -29,7 +30,7 @@ class DatabaseContext implements RouteHandler { return sys_get_temp_dir() . '/eeprom-migrate-' . hash('sha256', PRM_ROOT) . '.lock'; } - #[HttpMiddleware('/')] + #[PrefixFilter('/')] public function middleware() { if(is_file($this->getMigrateLockPath())) return 503; diff --git a/src/EEPROMAccessControlHandler.php b/src/EEPROMAccessControlHandler.php new file mode 100644 index 0000000..669b5b4 --- /dev/null +++ b/src/EEPROMAccessControlHandler.php @@ -0,0 +1,30 @@ +<?php +namespace EEPROM; + +use Index\Http\HttpUri; +use Index\Http\Routing\HandlerContext; +use Index\Http\Routing\Routes\RouteInfo; +use Index\Http\Routing\AccessControl\{AccessControl,SimpleAccessControlHandler}; + +class EEPROMAccessControlHandler extends SimpleAccessControlHandler { + public function __construct( + private array $origins, + ) {} + + #[\Override] + public function checkAccess( + HandlerContext $context, + AccessControl $accessControl, + HttpUri $origin, + ?RouteInfo $routeInfo = null, + ): string|bool { + if($accessControl->credentials) { + $host = '.' . $origin->host; + foreach($this->origins as $allowOrigin) + if(str_ends_with($host, '.' . $allowOrigin)) + return (string)$origin; + } + + return true; + } +} diff --git a/src/EEPROMContext.php b/src/EEPROMContext.php index 6fd662b..c6aeafd 100644 --- a/src/EEPROMContext.php +++ b/src/EEPROMContext.php @@ -1,14 +1,16 @@ <?php namespace EEPROM; -use EEPROM\Auth\AuthInfoWrapper; +use Index\Dependencies; use Index\Config\Config; use Index\Db\DbConnection; use RPCii\HmacVerificationProvider; use RPCii\Server\HttpRpcServer; class EEPROMContext { - public private(set) DatabaseContext $database; + public private(set) Dependencies $deps; + + public private(set) DatabaseContext $dbCtx; public private(set) SnowflakeGenerator $snowflake; public private(set) Auth\AuthContext $authCtx; @@ -22,30 +24,26 @@ class EEPROMContext { private Config $config, DbConnection $dbConn ) { - $this->database = new DatabaseContext($dbConn); - $this->snowflake = new SnowflakeGenerator; + $this->deps = new Dependencies; + $this->deps->register($this); + $this->deps->register($this->deps); - $this->authCtx = new Auth\AuthContext($config->scopeTo('apii')); - $this->denylistCtx = new Denylist\DenylistContext($dbConn); - $this->poolsCtx = new Pools\PoolsContext($dbConn); - $this->storageCtx = new Storage\StorageContext($config->scopeTo('storage'), $dbConn, $this->snowflake); - $this->tasksCtx = new Tasks\TasksContext($config->scopeTo('domain'), $dbConn, $this->snowflake); - $this->uploadsCtx = new Uploads\UploadsContext( - $config->scopeTo('domain'), $dbConn, $this->snowflake, $this->poolsCtx, $this->storageCtx, - ); + $this->deps->register($this->dbCtx = new DatabaseContext($dbConn)); + $this->deps->register($this->dbCtx->conn); + + $this->deps->register($this->snowflake = $this->deps->constructLazy(SnowflakeGenerator::class)); + + $this->deps->register($this->authCtx = $this->deps->constructLazy(Auth\AuthContext::class, $config->scopeTo('apii'))); + $this->deps->register($this->denylistCtx = $this->deps->constructLazy(Denylist\DenylistContext::class)); + $this->deps->register($this->poolsCtx = $this->deps->constructLazy(Pools\PoolsContext::class)); + $this->deps->register($this->storageCtx = $this->deps->constructLazy(Storage\StorageContext::class, $config->scopeTo('storage'))); + $this->deps->register($this->tasksCtx = $this->deps->constructLazy(Tasks\TasksContext::class, $config->scopeTo('domain'))); + $this->deps->register($this->uploadsCtx = $this->deps->constructLazy(Uploads\UploadsContext::class, $config->scopeTo('domain'))); } public function createRouting(bool $isApiDomain): RoutingContext { $routingCtx = new RoutingContext($this->config->scopeTo('cors')); - $routingCtx->register($this->database); - - $routingCtx->register($uploadsViewsRoutes = new Uploads\UploadsViewRoutes( - $this->poolsCtx, - $this->uploadsCtx, - $this->storageCtx, - $this->denylistCtx, - $isApiDomain, - )); + $routingCtx->register($this->dbCtx); if($isApiDomain) { $rpcServer = new HttpRpcServer; @@ -53,33 +51,16 @@ class EEPROMContext { new HmacVerificationProvider(fn() => $this->config->getString('rpcii:secret')) )); - $rpcServer->register(new Pools\PoolsRpcHandler( - $this->poolsCtx, - )); - $rpcServer->register(new Tasks\TasksRpcHandler( - $this->tasksCtx, - $this->poolsCtx, - $this->uploadsCtx, - $this->storageCtx, - $this->denylistCtx, - )); - $rpcServer->register(new Uploads\UploadsRpcHandler( - $this->poolsCtx, - $this->uploadsCtx, - $this->storageCtx, - )); + $rpcServer->register($this->deps->constructLazy(Pools\PoolsRpcHandler::class)); + $rpcServer->register($this->deps->constructLazy(Tasks\TasksRpcHandler::class)); + $rpcServer->register($this->deps->constructLazy(Uploads\UploadsRpcHandler::class)); - $routingCtx->register(new Auth\AuthRoutes($this->authCtx)); - $routingCtx->register(new Tasks\TasksRoutes($this->tasksCtx)); - $routingCtx->register(new Uploads\UploadsLegacyRoutes( - $this->authCtx, - $this->poolsCtx, - $this->uploadsCtx, - $this->storageCtx, - $this->denylistCtx, - )); - $routingCtx->register(new LandingRoutes($this->database)); - } + $routingCtx->register($this->deps->constructLazy(Auth\AuthRoutes::class)); + $routingCtx->register($this->deps->constructLazy(Tasks\TasksRoutes::class)); + $routingCtx->register($this->deps->constructLazy(Uploads\UploadsLegacyRoutes::class)); + $routingCtx->register($this->deps->constructLazy(LandingRoutes::class)); + } else + $routingCtx->register($this->deps->constructLazy(Uploads\UploadsViewRoutes::class)); return $routingCtx; } diff --git a/src/EEPROMErrorHandler.php b/src/EEPROMErrorHandler.php index 48bf353..f7b0c75 100644 --- a/src/EEPROMErrorHandler.php +++ b/src/EEPROMErrorHandler.php @@ -1,10 +1,23 @@ <?php namespace EEPROM; -use Index\Http\{HttpErrorHandler,HttpResponseBuilder,HttpRequest}; +use Index\Http\HttpResponse; +use Index\Http\Routing\HandlerContext; +use Index\Http\Routing\ErrorHandling\ErrorHandler; +use Index\Http\Streams\Stream; -class EEPROMErrorHandler implements HttpErrorHandler { - public function handle(HttpResponseBuilder $response, HttpRequest $request, int $code, string $message): void { - $response->setContent(sprintf('<!doctype html><title>%1$03d %2$s</title><strong>%1$03d %2$s</strong>', $code, $message)); +class EEPROMErrorHandler implements ErrorHandler { + public function handle(HandlerContext $context): void { + if(!$context->response->needsBody) + return; + + $context->response->setTypeHTML(); + $context->response->body = Stream::createStream(sprintf( + '<!doctype html><title>%1$03d %2$s</title><strong>%1$03d %2$s</strong>', + $context->response->statusCode, + $context->response->reasonPhrase === '' + ? HttpResponse::defaultReasonPhase($context->response->statusCode) + : $context->response->reasonPhrase + )); } } diff --git a/src/LandingRoutes.php b/src/LandingRoutes.php index 5c6f191..e9224b9 100644 --- a/src/LandingRoutes.php +++ b/src/LandingRoutes.php @@ -2,22 +2,24 @@ namespace EEPROM; use stdClass; -use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerTrait}; +use Index\Http\HttpResponseBuilder; +use Index\Http\Routing\{RouteHandler,RouteHandlerCommon}; +use Index\Http\Routing\Routes\ExactRoute; class LandingRoutes implements RouteHandler { - use RouteHandlerTrait; + use RouteHandlerCommon; public function __construct( private DatabaseContext $dbCtx ) {} - #[HttpGet('/')] - public function getIndex($response) { + #[ExactRoute('GET', '/')] + public function getIndex(HttpResponseBuilder $response) { $response->accelRedirect('/index.html'); $response->setContentType('text/html; charset=utf-8'); } - #[HttpGet('/stats.json')] + #[ExactRoute('GET', '/stats.json')] public function getStats() { $stats = new stdClass; $stats->uploads = 0; @@ -26,13 +28,13 @@ class LandingRoutes implements RouteHandler { $stats->types = 0; $stats->members = 0; - $result = $this->dbCtx->connection->query('SELECT COUNT(*), COUNT(DISTINCT user_id) FROM prm_uploads'); + $result = $this->dbCtx->conn->query('SELECT COUNT(*), COUNT(DISTINCT user_id) FROM prm_uploads'); if($result->next()) { $stats->uploads = $result->getInteger(0); $stats->members = $result->getInteger(1); } - $result = $this->dbCtx->connection->query('SELECT COUNT(*), SUM(file_size), COUNT(DISTINCT file_type) FROM prm_files'); + $result = $this->dbCtx->conn->query('SELECT COUNT(*), SUM(file_size), COUNT(DISTINCT file_type) FROM prm_files'); if($result->next()) { $stats->files = $result->getInteger(0); $stats->size = $result->getInteger(1); @@ -42,8 +44,8 @@ class LandingRoutes implements RouteHandler { return $stats; } - #[HttpGet('/eeprom.js')] - public function getEepromJs($response) { + #[ExactRoute('GET', '/eeprom.js')] + public function getEepromJs(HttpResponseBuilder $response) { $response->accelRedirect('/scripts/eepromv1.js'); $response->setContentType('application/javascript; charset=utf-8'); } diff --git a/src/Pools/PoolsContext.php b/src/Pools/PoolsContext.php index c188ab5..3b1cd5a 100644 --- a/src/Pools/PoolsContext.php +++ b/src/Pools/PoolsContext.php @@ -2,7 +2,9 @@ namespace EEPROM\Pools; use RuntimeException; +use EEPROM\Storage\StorageRecord; use EEPROM\Uploads\UploadVariantInfo; +use Index\XArray; use Index\Db\DbConnection; class PoolsContext { @@ -34,7 +36,7 @@ class PoolsContext { ); } - public function getPoolFromVariant(UploadVariantInfo $variantInfo): StorageRecord { + public function getPoolFromVariant(UploadVariantInfo $variantInfo): PoolInfo { return $this->pools->getPool($variantInfo->fileId, PoolInfoGetField::Id); } @@ -69,7 +71,10 @@ class PoolsContext { if($removeStaleRule instanceof Rules\RemoveStaleRule && $removeStaleRule->inactiveForSeconds > 0) $body['idle_time'] = $removeStaleRule->inactiveForSeconds; - $ensureVariantRules = XArray::select($rules->getRules(Rules\EnsureVariantRule::TYPE), fn($ensureVariantRule) => $ensureVariantRule->variant); + $ensureVariantRules = XArray::select( + $rules->getRules(Rules\EnsureVariantRule::TYPE), + fn($ensureVariantRule) => $ensureVariantRule->variant + ); if(!empty($ensureVariantRules)) $body['variants'] = $ensureVariantRules; diff --git a/src/Pools/PoolsData.php b/src/Pools/PoolsData.php index 6b6f733..ad4afe4 100644 --- a/src/Pools/PoolsData.php +++ b/src/Pools/PoolsData.php @@ -49,7 +49,6 @@ class PoolsData { PoolInfoGetField::Id => 'pool_id = ?', PoolInfoGetField::Name => 'pool_name = ?', PoolInfoGetField::UploadId => 'pool_id = (SELECT pool_id FROM prm_uploads WHERE upload_id = ?)', - default => throw new InvalidArgumentException('$field is not an acceptable value'), }; $stmt = $this->cache->get(<<<SQL @@ -90,8 +89,10 @@ class PoolsData { SELECT rule_id, pool_id, rule_type, rule_params FROM prm_pools_rules SQL; - if($hasPoolInfo) - $query .= sprintf(' %s pool_id = ?', ++$args > 1 ? 'AND' : 'WHERE'); + if($hasPoolInfo) { + ++$args; + $query .= ' WHERE pool_id = ?'; + } if($hasTypes) $query .= sprintf(' %s rule_type IN (%s)', ++$args > 1 ? 'AND' : 'WHERE', DbTools::prepareListString($types)); diff --git a/src/RoutingContext.php b/src/RoutingContext.php index 5b0048a..b6459fc 100644 --- a/src/RoutingContext.php +++ b/src/RoutingContext.php @@ -2,42 +2,19 @@ namespace EEPROM; use Index\Config\Config; -use Index\Http\HttpRequest; -use Index\Http\Routing\{HttpRouter,Router,RouteHandler}; +use Index\Http\{HttpResponseBuilder,HttpRequest}; +use Index\Http\Routing\{Router,RouteHandler}; +use Index\Http\Routing\Filters\FilterInfo; class RoutingContext { - private HttpRouter $router; + public private(set) Router $router; - public function __construct(private Config $config) { - $this->router = new HttpRouter( + public function __construct(Config $config) { + $this->router = new Router( errorHandler: new EEPROMErrorHandler, + accessControlHandler: new EEPROMAccessControlHandler($config->getArray('origins')), ); - $this->router->use('/', $this->middleware(...)); - } - - private function middleware($response, $request) { - $response->setPoweredBy('EEPROM'); - - if($request->hasHeader('Origin')) { - $origin = $request->getHeaderLine('Origin'); - $response->setHeader('Access-Control-Allow-Origin', $origin); - $response->setHeader('Vary', 'Origin'); - $host = parse_url($origin, PHP_URL_HOST); - if(is_string($host)) { - $host = '.' . $host; - $allowCookieOrigins = $this->config->getArray('origins'); - foreach($allowCookieOrigins as $allowCookieOrigin) - if(str_ends_with($host, '.' . $allowCookieOrigin)) { - $response->setHeader('Access-Control-Allow-Credentials', 'true'); - break; - } - } - } else - $response->setHeader('Access-Control-Allow-Origin', '*'); - } - - public function getRouter(): Router { - return $this->router; + $this->router->filter(FilterInfo::prefix('/', fn(HttpResponseBuilder $response) => $response->setPoweredBy('EEPROM'))); } public function register(RouteHandler $handler): void { diff --git a/src/Storage/StorageFiles.php b/src/Storage/StorageFiles.php index 105dd0e..d24577a 100644 --- a/src/Storage/StorageFiles.php +++ b/src/Storage/StorageFiles.php @@ -79,18 +79,13 @@ class StorageFiles { $target = $this->getLocalPath($hash, true); if(is_file($target)) { - if($mode === StorageImportMode::Move || $mode === StorageImportMode::Upload) + if($mode === StorageImportMode::Move) unlink($path); } else { if($mode === StorageImportMode::Move) $success = rename($path, $target); elseif($mode === StorageImportMode::Copy) $success = copy($path, $target); - elseif($mode === StorageImportMode::Upload) - $success = move_uploaded_file($path, $target); - else - $success = false; - if(!$success) throw new RuntimeException('unable to move or copy $path using given $mode'); } diff --git a/src/Storage/StorageImportMode.php b/src/Storage/StorageImportMode.php index 0dcb3cf..c4a87ec 100644 --- a/src/Storage/StorageImportMode.php +++ b/src/Storage/StorageImportMode.php @@ -4,5 +4,4 @@ namespace EEPROM\Storage; enum StorageImportMode { case Copy; case Move; - case Upload; } diff --git a/src/Storage/StorageRecords.php b/src/Storage/StorageRecords.php index 44fe23b..7acdffc 100644 --- a/src/Storage/StorageRecords.php +++ b/src/Storage/StorageRecords.php @@ -37,12 +37,13 @@ class StorageRecords { SQL; $args = 0; - if($orphaned !== null) + if($orphaned !== null) { + ++$args; $query .= sprintf( - ' %s uf.file_id %s NULL', - ++$args > 1 ? 'OR' : 'WHERE', + ' WHERE uf.file_id %s NULL', $orphaned ? 'IS' : 'IS NOT' ); + } if($denied !== null) $query .= sprintf( ' %s dl.deny_hash %s NULL', @@ -63,7 +64,6 @@ class StorageRecords { $field = match($field) { StorageRecordGetFileField::Id => 'file_id', StorageRecordGetFileField::Hash => 'file_hash', - default => throw new InvalidArgumentException('$field is not an acceptable value'), }; $stmt = $this->cache->get(<<<SQL @@ -93,14 +93,14 @@ class StorageRecords { if($size < 0) throw new InvalidArgumentException('$size may not be negative'); - $fileId = $this->snowflake->nextHash($hash); + $fileId = (string)$this->snowflake->nextHash($hash); $stmt = $this->cache->get(<<<SQL INSERT INTO prm_files ( file_id, file_hash, file_type, file_size ) VALUES (?, ?, ?, ?) SQL); - $stmt->nextParameter($fileId); // generate snowflake + $stmt->nextParameter($fileId); $stmt->nextParameter($hash); $stmt->nextParameter($type); $stmt->nextParameter($size); @@ -120,7 +120,6 @@ class StorageRecords { $field = match($field) { StorageRecordGetFileField::Id => 'file_id', StorageRecordGetFileField::Hash => 'file_hash', - default => throw new InvalidArgumentException('$field is not an acceptable value'), }; $stmt = $this->cache->get(<<<SQL @@ -142,7 +141,6 @@ class StorageRecords { $field = match($field) { StorageRecordGetFileField::Id => '?', StorageRecordGetFileField::Hash => '(SELECT file_id FROM prm_files WHERE file_hash = ?)', - default => throw new InvalidArgumentException('$field is not an acceptable value'), }; $stmt = $this->cache->get(<<<SQL diff --git a/src/Tasks/TaskInfo.php b/src/Tasks/TaskInfo.php index 702459a..3f2b912 100644 --- a/src/Tasks/TaskInfo.php +++ b/src/Tasks/TaskInfo.php @@ -28,7 +28,7 @@ class TaskInfo { secret: $result->getString(1), userId: $result->getString(2), poolId: $result->getString(3), - state: TaskState::tryFrom($result->getString(4)) ?? TaskState::Ready, + state: TaskState::tryFrom($result->getString(4)) ?? TaskState::Pending, remoteAddress: $result->getString(5), name: $result->getString(6), size: $result->getInteger(7), diff --git a/src/Tasks/TasksData.php b/src/Tasks/TasksData.php index faa69f6..8aa00b4 100644 --- a/src/Tasks/TasksData.php +++ b/src/Tasks/TasksData.php @@ -3,6 +3,7 @@ namespace EEPROM\Tasks; use InvalidArgumentException; use RuntimeException; +use Stringable; use EEPROM\SnowflakeGenerator; use EEPROM\Pools\PoolInfo; use Index\XString; @@ -29,8 +30,10 @@ class TasksData { task_name, task_size, task_type, task_hash, UNIX_TIMESTAMP(task_created) FROM prm_tasks SQL; - if($hasState) - $query .= sprintf(' %s task_state = ?', ++$args > 1 ? 'AND' : 'WHERE'); + if($hasState) { + ++$args; + $query .= ' WHERE task_state = ?'; + } $stmt = $this->cache->get($query); if($hasState) @@ -77,7 +80,7 @@ class TasksData { string $type, string $hash ): TaskInfo { - $taskId = $this->snowflake->nextRandom(); + $taskId = (string)$this->snowflake->nextRandom(); $stmt = $this->cache->get(<<<SQL INSERT INTO prm_tasks ( diff --git a/src/Tasks/TasksRoutes.php b/src/Tasks/TasksRoutes.php index 6e28184..4f24af7 100644 --- a/src/Tasks/TasksRoutes.php +++ b/src/Tasks/TasksRoutes.php @@ -3,39 +3,36 @@ namespace EEPROM\Tasks; use RuntimeException; use Index\{ByteFormat,XNumber}; -use Index\Http\StringHttpContent; -use Index\Http\Routing\{HttpPut,RouteHandler,RouteHandlerTrait}; +use Index\Http\{HttpResponseBuilder,HttpRequest}; +use Index\Http\Routing\{HttpPut,RouteHandler,RouteHandlerCommon}; +use Index\Http\Routing\Routes\PatternRoute; class TasksRoutes implements RouteHandler { - use RouteHandlerTrait; + use RouteHandlerCommon; public function __construct( private TasksContext $tasksCtx, ) {} - #[HttpPut('/uploads/([A-Za-z0-9]+)(?:-([a-z0-9]+))?(?:\.([A-Za-z0-9\-_]+))?')] - public function putUpload($response, $request, string $taskId, string $variant = '', string $extension = '') { + #[PatternRoute('PUT', '/uploads/([A-Za-z0-9]+)(?:-([a-z0-9]+))?(?:\.([A-Za-z0-9\-_]+))?')] + public function putUpload( + HttpResponseBuilder $response, + HttpRequest $request, + string $taskId, + string $variant = '', + string $extension = '', + ) { if($request->getHeaderLine('Content-Type') !== 'application/octet-stream') { - $response->setStatusCode(400); + $response->statusCode = 400; return [ 'error' => 'bad_type', 'english' => 'Content-Type must be application/octet-stream.', ]; } - $content = $request->getContent(); - if($content instanceof StringHttpContent) { - $content = (string)$content; - } else { - $response->setStatusCode(400); - return [ - 'error' => 'bad_content', - 'english' => 'Request content could not be parsed as expected.', - ]; - } - + $content = (string)$request->getBody(); if(strlen($taskId) < 5) { - $response->setStatusCode(404); + $response->statusCode = 404; return [ 'error' => 'not_found', 'english' => 'Requested upload task does not exist.', @@ -43,8 +40,8 @@ class TasksRoutes implements RouteHandler { } if($variant !== '') { - $response->setStatusCode(405); - $response->setHeader('Allow', 'HEAD, GET'); + $response->statusCode = 405; + $response->setAllow(['HEAD', 'GET']); return [ 'error' => 'method_not_allowed', 'english' => 'PUT requests are not supported for upload variants.', @@ -53,7 +50,7 @@ class TasksRoutes implements RouteHandler { $length = (int)$request->getHeaderLine('Content-Length'); if($length < 1) { - $response->setStatusCode(411); + $response->statusCode = 411; return [ 'error' => 'length_required', 'english' => 'A Content-Length header with the size of the request body is required.', @@ -61,7 +58,7 @@ class TasksRoutes implements RouteHandler { } if(strlen($content) !== $length) { - $response->setStatusCode(400); + $response->statusCode = 400; return [ 'error' => 'length_mismatch', 'english' => 'Length specified in Content-Length differs from actual request body length.', @@ -69,7 +66,7 @@ class TasksRoutes implements RouteHandler { } if($length > TasksContext::CHUNK_SIZE) { - $response->setStatusCode(413); + $response->statusCode = 413; return [ 'error' => 'content_too_large', 'english' => sprintf( @@ -84,7 +81,7 @@ class TasksRoutes implements RouteHandler { if($request->hasHeader('X-Content-Index')) { if($request->hasParam('index')) { - $response->setStatusCode(400); + $response->statusCode = 400; return [ 'error' => 'bad_param', 'english' => 'the index query parameter may not be used at the same time as the X-Content-Index header.', @@ -93,19 +90,11 @@ class TasksRoutes implements RouteHandler { $index = (int)filter_var($request->getHeaderLine('X-Content-Index'), FILTER_SANITIZE_NUMBER_INT); } elseif($request->hasParam('index')) { - if($request->hasHeader('X-Content-Index')) { - $response->setStatusCode(400); - return [ - 'error' => 'bad_param', - 'english' => 'the X-Content-Index header may not be used at the same time as the index query parameter.', - ]; - } - - $index = (int)$request->getParam('index', FILTER_SANITIZE_NUMBER_INT); + $index = (int)$request->getFilteredParam('index', FILTER_SANITIZE_NUMBER_INT); } else $index = 0; if($index < 0) { - $response->setStatusCode(400); + $response->statusCode = 400; return [ 'error' => 'bad_index', 'english' => 'index parameter must be greater than or equal to 0.', @@ -113,19 +102,19 @@ class TasksRoutes implements RouteHandler { } $taskSecret = substr($taskId, -16); - $taskId = XNumber::fromBase62(substr($taskId, 0, -16)); + $taskId = (string)XNumber::fromBase62(substr($taskId, 0, -16)); try { $taskInfo = $this->tasksCtx->tasks->getTask($taskId); } catch(RuntimeException $ex) { - $response->setStatusCode(404); + $response->statusCode = 404; return [ 'error' => 'not_found', 'english' => 'Requested upload task does not exist.', ]; } if(!$taskInfo->verifySecret($taskSecret)) { - $response->setStatusCode(404); + $response->statusCode = 404; return [ 'error' => 'not_found', 'english' => 'Requested upload task does not exist.', @@ -133,7 +122,7 @@ class TasksRoutes implements RouteHandler { } if($taskInfo->state !== TaskState::Pending) { - $response->setStatusCode(409); + $response->statusCode = 409; return [ 'error' => 'not_pending', 'english' => 'Requested upload task is no longer accepting data.', @@ -142,7 +131,7 @@ class TasksRoutes implements RouteHandler { $offset = TasksContext::CHUNK_SIZE * $index; if($offset >= $taskInfo->size) { - $response->setStatusCode(400); + $response->statusCode = 400; return [ 'error' => 'offset_too_large', 'english' => 'Provided offset is greater than file size.', @@ -152,7 +141,7 @@ class TasksRoutes implements RouteHandler { $path = $this->tasksCtx->getTaskWorkingPath($taskInfo); if(!is_file($path)) { $this->tasksCtx->tasks->setTaskError($taskInfo, sprintf('working file despawned during PUT request: %s', $path)); - $response->setStatusCode(500); + $response->statusCode = 500; return [ 'error' => 'server_error', 'english' => 'Working file no longer exist on the server, the upload process cannot continue.', @@ -162,52 +151,50 @@ class TasksRoutes implements RouteHandler { $handle = fopen($path, 'rb+'); if($handle === false) { $this->tasksCtx->tasks->setTaskError($taskInfo, sprintf('failed to open during PUT request: %s', $path)); - $response->setStatusCode(500); + $response->statusCode = 500; return [ 'error' => 'server_error', 'english' => 'Was not able to open working file, the upload process cannot continue.', ]; } - try { - if(fseek($handle, $offset, SEEK_SET) < 0) { - $this->tasksCtx->tasks->setTaskError($taskInfo, sprintf('was unable to seek during PUT request: %s', $path)); - $response->setStatusCode(500); - return [ - 'error' => 'server_error', - 'english' => 'Was not able to seek to provided offset, the upload process cannot continue.', - ]; - } - - if(fwrite($handle, $content, $length) === false) { - $this->tasksCtx->tasks->setTaskError($taskInfo, sprintf('was unable to write during PUT request: %s', $path)); - $response->setStatusCode(500); - return [ - 'error' => 'server_error', - 'english' => 'Was not able to seek to write to file, the upload process cannot continue.', - ]; - } - } finally { - if(!fflush($handle)) { - $this->tasksCtx->tasks->setTaskError($taskInfo, sprintf('failed to flush file contents during PUT request: %s', $path)); - $response->setStatusCode(500); - return [ - 'error' => 'server_error', - 'english' => 'Was not able to flush file properly, the upload process cannot continue.', - ]; - } - - if(!fclose($handle)) { - $this->tasksCtx->tasks->setTaskError($taskInfo, sprintf('failed to close file contents during PUT request: %s', $path)); - $response->setStatusCode(500); - return [ - 'error' => 'server_error', - 'english' => 'Was not able to close file properly, the upload process cannot continue.', - ]; - } + if(fseek($handle, $offset, SEEK_SET) < 0) { + $this->tasksCtx->tasks->setTaskError($taskInfo, sprintf('was unable to seek during PUT request: %s', $path)); + $response->statusCode = 500; + return [ + 'error' => 'server_error', + 'english' => 'Was not able to seek to provided offset, the upload process cannot continue.', + ]; } - $response->setStatusCode(202); + if(fwrite($handle, $content, $length) === false) { + $this->tasksCtx->tasks->setTaskError($taskInfo, sprintf('was unable to write during PUT request: %s', $path)); + $response->statusCode = 500; + return [ + 'error' => 'server_error', + 'english' => 'Was not able to seek to write to file, the upload process cannot continue.', + ]; + } + + if(!fflush($handle)) { + $this->tasksCtx->tasks->setTaskError($taskInfo, sprintf('failed to flush file contents during PUT request: %s', $path)); + $response->statusCode = 500; + return [ + 'error' => 'server_error', + 'english' => 'Was not able to flush file properly, the upload process cannot continue.', + ]; + } + + if(!fclose($handle)) { + $this->tasksCtx->tasks->setTaskError($taskInfo, sprintf('failed to close file contents during PUT request: %s', $path)); + $response->statusCode = 500; + return [ + 'error' => 'server_error', + 'english' => 'Was not able to close file properly, the upload process cannot continue.', + ]; + } + + $response->statusCode = 202; return ''; } } diff --git a/src/Tasks/TasksRpcHandler.php b/src/Tasks/TasksRpcHandler.php index 4d6fb72..83aaf93 100644 --- a/src/Tasks/TasksRpcHandler.php +++ b/src/Tasks/TasksRpcHandler.php @@ -65,11 +65,8 @@ final class TasksRpcHandler implements RpcHandler { if($signature === null) return 'srpp'; - if(!str_contains($signature, '=')) { + if(!str_contains($signature, '=')) $signature = UriBase64::decode($signature); - if(!is_string($signature)) - $signature = ''; - } $signature = array_column(XArray::select( explode(';', $signature), @@ -94,7 +91,7 @@ final class TasksRpcHandler implements RpcHandler { if($sAlgo !== 'S256') return 'sanv'; - if(abs($cTime - $sTime) >= 30) + if(abs($cTime - (int)$sTime) >= 30) return 'stnv'; if(strlen($sSalt) < 20) @@ -182,15 +179,13 @@ final class TasksRpcHandler implements RpcHandler { return 'wfcf'; } - try { - if(!ftruncate($data, $fileSize)) { - $this->tasksCtx->tasks->setTaskError($taskInfo, sprintf('failed to reserve %d bytes for task working file: %s', $fileSize, $path)); - return 'wfrf'; - } - } finally { - if(!fclose($data)) - return 'wfff'; + if(!ftruncate($data, $fileSize)) { + $this->tasksCtx->tasks->setTaskError($taskInfo, sprintf('failed to reserve %d bytes for task working file: %s', $fileSize, $path)); + return 'wfrf'; } + + if(!fclose($data)) + return 'wfff'; } catch(RuntimeException $ex) { return 'utcf'; } @@ -210,7 +205,7 @@ final class TasksRpcHandler implements RpcHandler { #[RpcQuery('eeprom:tasks:get')] public function actionTasksGet(string $taskId): string|array { $taskSecret = substr($taskId, -16); - $taskId = XNumber::fromBase62(substr($taskId, 0, -16)); + $taskId = (string)XNumber::fromBase62(substr($taskId, 0, -16)); try { $taskInfo = $this->tasksCtx->tasks->getTask($taskId); @@ -256,7 +251,7 @@ final class TasksRpcHandler implements RpcHandler { #[RpcAction('eeprom:tasks:finish')] public function actionsTasksFinish(string $taskId) { $taskSecret = substr($taskId, -16); - $taskId = XNumber::fromBase62(substr($taskId, 0, -16)); + $taskId = (string)XNumber::fromBase62(substr($taskId, 0, -16)); try { $taskInfo = $this->tasksCtx->tasks->getTask($taskId); diff --git a/src/Uploads/UploadsContext.php b/src/Uploads/UploadsContext.php index facd161..ea4596a 100644 --- a/src/Uploads/UploadsContext.php +++ b/src/Uploads/UploadsContext.php @@ -77,7 +77,7 @@ class UploadsContext { 'hash' => $fileInfo->hashHexString, 'created' => $uploadInfo->createdAt->toIso8601ZuluString(), 'accessed' => $uploadInfo->accessedAt->toIso8601ZuluString(), - 'expires' => $uploadInfo->accessedAt->add(2, 'weeks')->toIso8601ZuluString(), + 'expires' => $uploadInfo->accessedAt->add('weeks', 2)->toIso8601ZuluString(), // These can never be reached, and in situation where they technically could it's because of an outdated local record 'deleted' => null, diff --git a/src/Uploads/UploadsData.php b/src/Uploads/UploadsData.php index 16036a4..162c8c4 100644 --- a/src/Uploads/UploadsData.php +++ b/src/Uploads/UploadsData.php @@ -13,7 +13,7 @@ class UploadsData { private DbStatementCache $cache; public function __construct( - private DbConnection $dbConn, + DbConnection $dbConn, private SnowflakeGenerator $snowflake ) { $this->cache = new DbStatementCache($dbConn); @@ -48,8 +48,10 @@ class UploadsData { SQL; $args = 0; - if($hasPoolInfo) - $query .= sprintf(' %s u.pool_id = ?', ++$args > 1 ? 'AND' : 'WHERE'); + if($hasPoolInfo) { + ++$args; + $query .= ' WHERE u.pool_id = ?'; + } if($hasUserId) $query .= sprintf(' %s u.user_id = ?', ++$args > 1 ? 'AND' : 'WHERE'); if($hasVariantExists) diff --git a/src/Uploads/UploadsLegacyRoutes.php b/src/Uploads/UploadsLegacyRoutes.php index 52c56a9..2e41722 100644 --- a/src/Uploads/UploadsLegacyRoutes.php +++ b/src/Uploads/UploadsLegacyRoutes.php @@ -5,12 +5,19 @@ use RuntimeException; use EEPROM\Auth\AuthContext; use EEPROM\Denylist\{DenylistContext,DenylistReason}; use EEPROM\Pools\PoolsContext; +use EEPROM\Pools\Rules\{ConstrainSizeRule,EnsureVariantRule,EnsureVariantRuleThumb}; use EEPROM\Storage\{StorageContext,StorageImportMode,StorageRecordGetFileField}; -use Index\{ByteFormat,XNumber}; -use Index\Http\Routing\{HttpDelete,HttpOptions,HttpPost,RouteHandler,RouteHandlerTrait}; +use Index\{ByteFormat,XArray,XNumber}; +use Index\Http\{HttpResponseBuilder,HttpRequest}; +use Index\Http\Content\MultipartFormContent; +use Index\Http\Content\Multipart\FileMultipartFormData; +use Index\Http\Routing\{HttpDelete,HttpOptions,HttpPost,RouteHandler,RouteHandlerCommon}; +use Index\Http\Routing\AccessControl\AccessControl; +use Index\Http\Routing\Processors\Before; +use Index\Http\Routing\Routes\{ExactRoute,PatternRoute}; class UploadsLegacyRoutes implements RouteHandler { - use RouteHandlerTrait; + use RouteHandlerCommon; public function __construct( private AuthContext $authCtx, @@ -20,21 +27,111 @@ class UploadsLegacyRoutes implements RouteHandler { private DenylistContext $denylistCtx ) {} - #[HttpOptions('/uploads')] - public function optionsUpload($response, $request): int { - $response->setHeader('Access-Control-Allow-Headers', 'Authorization'); - $response->setHeader('Access-Control-Allow-Methods', 'POST'); + #[AccessControl] + #[PatternRoute('GET', '/uploads/([A-Za-z0-9]+|[A-Za-z0-9\-_]{32})(?:-([a-z0-9]+))?(?:\.([A-Za-z0-9\-_]+))?')] + public function getUpload( + HttpResponseBuilder $response, + HttpRequest $request, + string $uploadId, + string $variant = '', + string $extension = '' + ) { + if(strlen($uploadId) < 5) + return 404; - return 204; + $variantQuery = false; + if($variant === '' && $request->hasParam('v')) { + $variantQuery = true; + $variant = (string)$request->getParam('v'); + } + + $isV1Json = ($variantQuery || $variant === '') && $extension === 'json'; + if($isV1Json) + $variant = ''; + + if(strlen($uploadId) === 32) { + $uploadId = $this->uploadsCtx->uploads->resolveLegacyId($uploadId) ?? $uploadId; + $uploadSecret = null; + } else { + $uploadSecret = substr($uploadId, -4); + $uploadId = XNumber::fromBase62(substr($uploadId, 0, -4)); + } + + try { + $uploadInfo = $this->uploadsCtx->uploads->getUpload(uploadId: $uploadId, secret: $uploadSecret); + } catch(RuntimeException $ex) { + return 404; + } + + try { + $variantInfo = $this->uploadsCtx->uploads->getUploadVariant($uploadInfo, $variant); + } catch(RuntimeException $ex) { + if($variant === '') + return 404; + + try { + $poolInfo = $this->poolsCtx->pools->getPool($uploadInfo->poolId); + $originalInfo = $this->storageCtx->records->getFile( + $this->uploadsCtx->uploads->getUploadVariant($uploadInfo, '')->fileId + ); + + $rule = XArray::first( + $this->poolsCtx->getPoolRules($poolInfo)->getRules(EnsureVariantRule::TYPE), + fn($rule) => $rule->variant === $variant + ); + if($rule instanceof EnsureVariantRule && $rule->onAccess) { + if($rule->params instanceof EnsureVariantRuleThumb) { + $storageInfo = $this->storageCtx->createThumbnailFromRule($originalInfo, $rule->params); + $variantInfo = $this->uploadsCtx->uploads->createUploadVariant($uploadInfo, $rule->variant, $storageInfo); + } + } + } catch(RuntimeException $ex) { + return 404; + } + } + + if(!isset($variantInfo)) + return 404; + + if(!isset($storageInfo)) + try { + $storageInfo = $this->storageCtx->records->getFile($variantInfo->fileId); + } catch(RuntimeException $ex) { + return 404; + } + + $this->uploadsCtx->uploads->updateUpload( + $uploadInfo, + accessedAt: true, + ); + + $denyInfo = $this->denylistCtx->getDenylistEntry($storageInfo); + if($denyInfo !== null) + return $denyInfo->isCopyrightTakedown ? 451 : 410; + + if($isV1Json) + return $this->uploadsCtx->convertToClientJsonV1($uploadInfo, $variantInfo, $storageInfo); + + $fileName = $uploadInfo->name; + if($variantInfo->variant !== '') // FAIRLY SIGNIFICANT TODO: obviously this should support more file exts than .jpg... + $fileName = sprintf('%s-%s.jpg', pathinfo($fileName, PATHINFO_FILENAME), $variantInfo->variant); + + $contentType = $variantInfo->type ?? $storageInfo->type; + if($contentType === 'application/octet-stream' || str_starts_with($contentType, 'text/')) + $contentType = 'text/plain'; + + $response->accelRedirect($this->storageCtx->files->getRemotePath($storageInfo)); + $response->setContentType($contentType); + $response->setFileName(addslashes($fileName)); } - #[HttpPost('/uploads')] - public function postUpload($response, $request) { - if(!$request->isFormContent()) - return 400; - + #[AccessControl(credentials: true, allowHeaders: ['Authorization'])] + #[Before('input:multipart')] + #[Before('eeprom:attempt-auth')] + #[ExactRoute('POST', '/uploads')] + public function postUpload(HttpResponseBuilder $response, HttpRequest $request, MultipartFormContent $content) { if(!$this->authCtx->info->authed) { - $response->setStatusCode(401); + $response->statusCode = 401; return [ 'error' => 'auth', 'english' => "You must be logged in to upload files. If you are and you're getting this error anyway, try refreshing the page!", @@ -42,29 +139,25 @@ class UploadsLegacyRoutes implements RouteHandler { } if($this->authCtx->info->restricted) { - $response->setStatusCode(403); + $response->statusCode = 403; return [ 'error' => 'restricted', 'english' => 'You are currently banned and are not allowed to upload or manage files.', ]; } - $content = $request->getContent(); - - try { - $file = $content->getUploadedFile('file'); - } catch(RuntimeException $ex) { - $response->setStatusCode(400); + $file = $content->getParamData('file'); + if(!($file instanceof FileMultipartFormData)) { + $response->statusCode = 400; return [ 'error' => 'file', 'english' => 'File upload is missing from request body.', ]; } - $localFile = $file->getLocalFileName(); - $fileSize = filesize($localFile); + $fileSize = $file->getSize(); if($fileSize < 1) { - $response->setStatusCode(400); + $response->statusCode = 400; return [ 'error' => 'empty', 'english' => 'The file you attempted to upload is empty.', @@ -72,16 +165,16 @@ class UploadsLegacyRoutes implements RouteHandler { } try { - $poolInfo = $this->poolsCtx->pools->getPool((string)$content->getParam('src', FILTER_VALIDATE_INT)); + $poolInfo = $this->poolsCtx->pools->getPool((string)$content->getFilteredParam('src', FILTER_VALIDATE_INT)); if($poolInfo->protected) { - $response->setStatusCode(404); + $response->statusCode = 404; return [ 'error' => 'pool_incompatible', 'english' => 'The target upload pool is not compatible with this endpoint.', ]; } } catch(RuntimeException $ex) { - $response->setStatusCode(404); + $response->statusCode = 404; return [ 'error' => 'pool', 'english' => 'The target upload pool does not exist.', @@ -92,8 +185,8 @@ class UploadsLegacyRoutes implements RouteHandler { // If there's no constrain_size rule on this pool its likely not meant to be used through this API! $maxSizeRule = $rules->getRule('constrain_size'); - if($maxSizeRule === null) { - $response->setStatusCode(500); + if(!($maxSizeRule instanceof ConstrainSizeRule)) { + $response->statusCode = 500; return [ 'error' => 'size_rule_missing', 'english' => 'A pool size constraint rule is required any this pool does not specify one.', @@ -104,10 +197,10 @@ class UploadsLegacyRoutes implements RouteHandler { if($maxSizeRule->allowLegacyMultiplier) $maxFileSize *= $this->authCtx->info->legacyMultiplier; - if($file->getSize() !== $fileSize || $fileSize > $maxFileSize) { - $response->setStatusCode(413); + if($fileSize > $maxFileSize) { + $response->statusCode = 413; $response->setHeader('Access-Control-Expose-Headers', 'X-EEPROM-Max-Size'); - $response->setHeader('X-EEPROM-Max-Size', $maxFileSize); + $response->setHeader('X-EEPROM-Max-Size', (string)$maxFileSize); return [ 'error' => 'size', 'english' => sprintf( @@ -120,11 +213,13 @@ class UploadsLegacyRoutes implements RouteHandler { ]; } - $hash = hash_file('sha256', $localFile, true); + $tmpFile = tempnam(sys_get_temp_dir(), 'eeprom-upload-'); + $file->moveTo($tmpFile); + $hash = hash_file('sha256', $tmpFile, true); $denyInfo = $this->denylistCtx->getDenylistEntry($hash); if($denyInfo !== null) { - $response->setStatusCode(451); + $response->statusCode = 451; return [ 'error' => 'takedown', 'english' => sprintf( @@ -139,17 +234,17 @@ class UploadsLegacyRoutes implements RouteHandler { ]; } - $fileName = $file->getSuggestedFileName(); - $mediaType = (string)$file->getSuggestedMediaType(); - if($mediaType === '/' || $mediaType === 'application/octet-stream') - $mediaType = mime_content_type($localFile); + $fileName = $file->getClientFilename(); + $mediaType = $file->getClientMediaType(); + if($mediaType === null || $mediaType === 'application/octet-stream') + $mediaType = mime_content_type($tmpFile); try { $storageInfo = $this->storageCtx->records->getFile($hash, StorageRecordGetFileField::Hash); } catch(RuntimeException $ex) { $storageInfo = $this->storageCtx->importFile( - $file->getLocalFileName(), - StorageImportMode::Upload, + $tmpFile, + StorageImportMode::Move, $mediaType ); } @@ -172,14 +267,14 @@ class UploadsLegacyRoutes implements RouteHandler { $poolInfo, $this->authCtx->info->userId, $fileName, - $request->getRemoteAddress() + $request->remoteAddress ); $variantInfo = $this->uploadsCtx->uploads->createUploadVariant( $uploadInfo, '', $storageInfo, $mediaType ); } - $response->setStatusCode(201); + $response->statusCode = 201; $response->setHeader('Content-Type', 'application/json; charset=utf-8'); return $this->uploadsCtx->convertToClientJsonV1($uploadInfo, $variantInfo, $storageInfo, [ @@ -187,10 +282,12 @@ class UploadsLegacyRoutes implements RouteHandler { ]); } - #[HttpDelete('/uploads/([A-Za-z0-9]+|[A-Za-z0-9\-_]{32})(?:-([a-z0-9]+))?(?:\.([A-Za-z0-9\-_]+))?')] - public function deleteUpload($response, $request, string $uploadId) { + #[AccessControl(credentials: true, allowHeaders: ['Authorization'])] + #[Before('eeprom:attempt-auth')] + #[PatternRoute('DELETE', '/uploads/([A-Za-z0-9]+|[A-Za-z0-9\-_]{32})(?:-([a-z0-9]+))?(?:\.([A-Za-z0-9\-_]+))?')] + public function deleteUpload(HttpResponseBuilder $response, HttpRequest $request, string $uploadId) { if(!$this->authCtx->info->authed) { - $response->setStatusCode(401); + $response->statusCode = 401; return [ 'error' => 'auth', 'english' => "You must be logged in to delete uploaded files. If you are and you're getting this error anyway, try refreshing the page!", @@ -198,7 +295,7 @@ class UploadsLegacyRoutes implements RouteHandler { } if(strlen($uploadId) < 5) { - $response->setStatusCode(404); + $response->statusCode = 404; return [ 'error' => 'none', 'english' => 'That file does not exist.', @@ -216,7 +313,7 @@ class UploadsLegacyRoutes implements RouteHandler { try { $uploadInfo = $this->uploadsCtx->uploads->getUpload(uploadId: $uploadId, secret: $uploadSecret); } catch(RuntimeException $ex) { - $response->setStatusCode(404); + $response->statusCode = 404; return [ 'error' => 'none', 'english' => 'That file does not exist.', @@ -224,7 +321,7 @@ class UploadsLegacyRoutes implements RouteHandler { } if($this->authCtx->info->restricted) { - $response->setStatusCode(403); + $response->statusCode = 403; return [ 'error' => 'restricted', 'english' => 'You are currently banned and are not allowed to upload or manage files.', @@ -232,7 +329,7 @@ class UploadsLegacyRoutes implements RouteHandler { } if($this->authCtx->info->userId !== $uploadInfo->userId) { - $response->setStatusCode(403); + $response->statusCode = 403; return [ 'error' => 'owner', 'english' => "You aren't allowed to delete files uploaded by others.", diff --git a/src/Uploads/UploadsRpcHandler.php b/src/Uploads/UploadsRpcHandler.php index 1389520..0465878 100644 --- a/src/Uploads/UploadsRpcHandler.php +++ b/src/Uploads/UploadsRpcHandler.php @@ -71,7 +71,7 @@ final class UploadsRpcHandler implements RpcHandler { return [ 'result' => $this->uploadsCtx->extractUpload( $this->uploadsCtx->uploads->getUpload( - uploadId: XNumber::fromBase62(substr($uploadId, 0, -4)), + uploadId: (string)XNumber::fromBase62(substr($uploadId, 0, -4)), secret: substr($uploadId, -4), userId: $userId ), @@ -93,7 +93,7 @@ final class UploadsRpcHandler implements RpcHandler { try { $uploadInfo = $this->uploadsCtx->uploads->getUpload( - uploadId: XNumber::fromBase62(substr($uploadId, 0, -4)), + uploadId: (string)XNumber::fromBase62(substr($uploadId, 0, -4)), secret: substr($uploadId, -4), ); } catch(RuntimeException $ex) { @@ -120,7 +120,7 @@ final class UploadsRpcHandler implements RpcHandler { try { $uploadInfo = $this->uploadsCtx->uploads->getUpload( - uploadId: XNumber::fromBase62(substr($uploadId, 0, -4)), + uploadId: (string)XNumber::fromBase62(substr($uploadId, 0, -4)), secret: substr($uploadId, -4), ); } catch(RuntimeException $ex) { @@ -149,7 +149,7 @@ final class UploadsRpcHandler implements RpcHandler { try { $uploadInfo = $this->uploadsCtx->uploads->getUpload( - uploadId: XNumber::fromBase62(substr($uploadId, 0, -4)), + uploadId: (string)XNumber::fromBase62(substr($uploadId, 0, -4)), secret: substr($uploadId, -4), ); } catch(RuntimeException $ex) { diff --git a/src/Uploads/UploadsViewRoutes.php b/src/Uploads/UploadsViewRoutes.php index 7150168..967ccdc 100644 --- a/src/Uploads/UploadsViewRoutes.php +++ b/src/Uploads/UploadsViewRoutes.php @@ -7,39 +7,30 @@ use EEPROM\Pools\PoolsContext; use EEPROM\Pools\Rules\{EnsureVariantRule,EnsureVariantRuleThumb}; use EEPROM\Storage\StorageContext; use Index\{XArray,XNumber}; -use Index\Http\Routing\{HandlerAttribute,HttpGet,HttpOptions,Router,RouteHandler}; +use Index\Http\{HttpResponseBuilder,HttpRequest}; +use Index\Http\Routing\{Router,RouteHandler,RouteHandlerCommon}; +use Index\Http\Routing\AccessControl\AccessControl; +use Index\Http\Routing\Routes\PatternRoute; class UploadsViewRoutes implements RouteHandler { + use RouteHandlerCommon; + public function __construct( private PoolsContext $poolsCtx, private UploadsContext $uploadsCtx, private StorageContext $storageCtx, - private DenylistContext $denylistCtx, - private bool $isApiDomain + private DenylistContext $denylistCtx ) {} - public function registerRoutes(Router $router): void { - if($this->isApiDomain) - $router = $router->scopeTo('/uploads'); - - HandlerAttribute::register($router, $this); - } - - #[HttpOptions('/([A-Za-z0-9]+|[A-Za-z0-9\-_]{32})(?:-([a-z0-9]+))?(?:\.([A-Za-z0-9\-_]+))?')] - public function optionsUpload($response, $request, string $uploadId, string $variant = '', string $extension = ''): int { - if($this->isApiDomain && $variant === '') { - $response->setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type, Content-Length, X-Content-Index'); - $response->setHeader('Access-Control-Allow-Methods', 'HEAD, GET, PUT, DELETE'); - $response->setHeader('Access-Control-Max-Age', '300'); - } else { - $response->setHeader('Access-Control-Allow-Methods', 'HEAD, GET'); - } - - return 204; - } - - #[HttpGet('/([A-Za-z0-9]+|[A-Za-z0-9\-_]{32})(?:-([a-z0-9]+))?(?:\.([A-Za-z0-9\-_]+))?')] - public function getUpload($response, $request, string $uploadId, string $variant = '', string $extension = '') { + #[AccessControl] + #[PatternRoute('GET', '/([A-Za-z0-9]+|[A-Za-z0-9\-_]{32})(?:-([a-z0-9]+))?(?:\.([A-Za-z0-9\-_]+))?')] + public function getUpload( + HttpResponseBuilder $response, + HttpRequest $request, + string $uploadId, + string $variant = '', + string $extension = '' + ) { if(strlen($uploadId) < 5) return 404; @@ -49,10 +40,6 @@ class UploadsViewRoutes implements RouteHandler { $variant = (string)$request->getParam('v'); } - $isV1Json = $this->isApiDomain && ($variantQuery || $variant === '') && $extension === 'json'; - if($isV1Json) - $variant = ''; - if(strlen($uploadId) === 32) { $uploadId = $this->uploadsCtx->uploads->resolveLegacyId($uploadId) ?? $uploadId; $uploadSecret = null; @@ -113,9 +100,6 @@ class UploadsViewRoutes implements RouteHandler { if($denyInfo !== null) return $denyInfo->isCopyrightTakedown ? 451 : 410; - if($isV1Json) - return $this->uploadsCtx->convertToClientJsonV1($uploadInfo, $variantInfo, $storageInfo); - $fileName = $uploadInfo->name; if($variantInfo->variant !== '') // FAIRLY SIGNIFICANT TODO: obviously this should support more file exts than .jpg... $fileName = sprintf('%s-%s.jpg', pathinfo($fileName, PATHINFO_FILENAME), $variantInfo->variant); diff --git a/tools/migrate b/tools/migrate index b899dc7..4166d9c 100755 --- a/tools/migrate +++ b/tools/migrate @@ -2,20 +2,20 @@ <?php require_once __DIR__ . '/../eeprom.php'; -$lockPath = $eeprom->database->getMigrateLockPath(); +$lockPath = $eeprom->dbCtx->getMigrateLockPath(); if(is_file($lockPath)) die('A migration script is already running.' . PHP_EOL); touch($lockPath); try { echo 'Creating migration manager...' . PHP_EOL; - $manager = $eeprom->database->createMigrationManager(); + $manager = $eeprom->dbCtx->createMigrationManager(); echo 'Preparing to run migrations...' . PHP_EOL; $manager->init(); echo 'Creating migration repository...' . PHP_EOL; - $repo = $eeprom->database->createMigrationRepo(); + $repo = $eeprom->dbCtx->createMigrationRepo(); echo 'Running migrations...' . PHP_EOL; $completed = $manager->processMigrations($repo); diff --git a/tools/migrate-override-toggle b/tools/migrate-override-toggle index 877d51e..0fafc00 100755 --- a/tools/migrate-override-toggle +++ b/tools/migrate-override-toggle @@ -2,7 +2,7 @@ <?php require_once __DIR__ . '/../eeprom.php'; -$lockPath = $eeprom->database->getMigrateLockPath(); +$lockPath = $eeprom->dbCtx->getMigrateLockPath(); if(is_file($lockPath)) { printf('Removing migration lock...%s', PHP_EOL); unlink($lockPath); diff --git a/tools/new-migration b/tools/new-migration index 5ed2eb2..919ae9e 100755 --- a/tools/new-migration +++ b/tools/new-migration @@ -4,14 +4,14 @@ use Index\Db\Migration\FsDbMigrationRepo; require_once __DIR__ . '/../eeprom.php'; -$repo = $eeprom->database->createMigrationRepo(); +$repo = $eeprom->dbCtx->createMigrationRepo(); if(!($repo instanceof FsDbMigrationRepo)) { echo 'Migration repository type does not support creation of templates.' . PHP_EOL; return; } $baseName = implode(' ', array_slice($argv, 1)); -$manager = $eeprom->database->createMigrationManager(); +$manager = $eeprom->dbCtx->createMigrationManager(); try { $names = $manager->createNames($baseName);