flashii/eeprom
Archived
3
0
Fork 1

Updated EEPROM to latest Index.

This commit is contained in:
flash 2025-03-22 20:08:22 +00:00
parent f1513d1c0f
commit 1d4e7f0918
Signed by: flash
GPG key ID: 2C9C2C574D47FE3E
27 changed files with 784 additions and 464 deletions

View file

@ -1,13 +1,13 @@
{ {
"require": { "require": {
"flashwave/index": "^0.2410", "flashwave/index": "^0.2503",
"flashii/rpcii": "^3.0", "flashii/rpcii": "^5.0",
"flashii/apii": "^0.3", "flashii/apii": "^0.4",
"sentry/sdk": "^4.0", "sentry/sdk": "^4.0",
"nesbot/carbon": "^3.7" "nesbot/carbon": "^3.8"
}, },
"require-dev": { "require-dev": {
"phpstan/phpstan": "^2.0" "phpstan/phpstan": "^2.1"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

535
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "7009c2a98d8882c938653f4455f52a82", "content-hash": "2d07dcd636742c34625c576e67eaaf0b",
"packages": [ "packages": [
{ {
"name": "carbonphp/carbon-doctrine-types", "name": "carbonphp/carbon-doctrine-types",
@ -77,18 +77,20 @@
}, },
{ {
"name": "flashii/apii", "name": "flashii/apii",
"version": "v0.3.0", "version": "v0.4.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://patchii.net/flashii/apii-php.git", "url": "https://patchii.net/flashii/apii-php.git",
"reference": "2d6c135faddd359341762afcb9c429e279d87059" "reference": "d330e0792515dbae2832bd8e2ac761cc685175e9"
}, },
"require": { "require": {
"php": ">=8.1" "guzzlehttp/guzzle": "~7.9",
"php": ">=8.1",
"psr/http-client": "^1.0"
}, },
"require-dev": { "require-dev": {
"phpstan/phpstan": "^1.12", "phpstan/phpstan": "~2.1",
"phpunit/phpunit": "^10.5" "phpunit/phpunit": "~10.5"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
@ -110,24 +112,26 @@
], ],
"description": "Client library for the Flashii.net API.", "description": "Client library for the Flashii.net API.",
"homepage": "https://api.flashii.net", "homepage": "https://api.flashii.net",
"time": "2024-11-22T21:36:01+00:00" "time": "2025-03-20T17:05:52+00:00"
}, },
{ {
"name": "flashii/rpcii", "name": "flashii/rpcii",
"version": "v3.0.0", "version": "v5.0.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://patchii.net/flashii/rpcii-php.git", "url": "https://patchii.net/flashii/rpcii-php.git",
"reference": "25ac46d5dee60027032e175107e638dfb0b8f7f9" "reference": "28c25e0a342173524f8894d03158663842e03252"
}, },
"require": { "require": {
"ext-msgpack": ">=2.2", "ext-msgpack": ">=2.2",
"flashwave/index": "^0.2410", "flashwave/index": "^0.2503",
"php": ">=8.4" "guzzlehttp/guzzle": "~7.0",
"php": ">=8.4",
"psr/http-client": "^1.0"
}, },
"require-dev": { "require-dev": {
"phpstan/phpstan": "^2.1", "phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^11.5" "phpunit/phpunit": "^12.0"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
@ -149,25 +153,27 @@
], ],
"description": "HTTP RPC client/server library.", "description": "HTTP RPC client/server library.",
"homepage": "https://railgun.sh/rpcii", "homepage": "https://railgun.sh/rpcii",
"time": "2025-01-17T00:05:22+00:00" "time": "2025-03-21T19:38:29+00:00"
}, },
{ {
"name": "flashwave/index", "name": "flashwave/index",
"version": "v0.2410.830205", "version": "v0.2503.221959",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://patchii.net/flash/index.git", "url": "https://patchii.net/flash/index.git",
"reference": "416c716b2efab9619d14be02a20cd6540e18a83f" "reference": "d99562db163589cc51e32e10e851085230fb3181"
}, },
"require": { "require": {
"ext-mbstring": "*", "ext-mbstring": "*",
"php": ">=8.3", "php": ">=8.4",
"twig/html-extra": "^3.13", "psr/http-message": "^2.0",
"twig/twig": "^3.14" "psr/http-server-handler": "^1.0",
"twig/html-extra": "^3.20",
"twig/twig": "^3.20"
}, },
"require-dev": { "require-dev": {
"phpstan/phpstan": "^2.0", "phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^11.4" "phpunit/phpunit": "^12.0"
}, },
"suggest": { "suggest": {
"ext-memcache": "Support for the Index\\Cache\\Memcached namespace (only if you can't use ext-memcached for some reason).", "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.", "description": "Composer package for the common library for my projects.",
"homepage": "https://railgun.sh/index", "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", "name": "guzzlehttp/psr7",
@ -324,16 +539,16 @@
}, },
{ {
"name": "jean85/pretty-package-versions", "name": "jean85/pretty-package-versions",
"version": "2.1.0", "version": "2.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Jean85/pretty-package-versions.git", "url": "https://github.com/Jean85/pretty-package-versions.git",
"reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10" "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a",
"reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -343,8 +558,9 @@
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "^3.2", "friendsofphp/php-cs-fixer": "^3.2",
"jean85/composer-provided-replaced-stub-package": "^1.0", "jean85/composer-provided-replaced-stub-package": "^1.0",
"phpstan/phpstan": "^1.4", "phpstan/phpstan": "^2.0",
"phpunit/phpunit": "^7.5|^8.5|^9.6", "phpunit/phpunit": "^7.5|^8.5|^9.6",
"rector/rector": "^2.0",
"vimeo/psalm": "^4.3 || ^5.0" "vimeo/psalm": "^4.3 || ^5.0"
}, },
"type": "library", "type": "library",
@ -377,22 +593,22 @@
], ],
"support": { "support": {
"issues": "https://github.com/Jean85/pretty-package-versions/issues", "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", "name": "nesbot/carbon",
"version": "3.8.4", "version": "3.8.6",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/CarbonPHP/carbon.git", "url": "https://github.com/CarbonPHP/carbon.git",
"reference": "129700ed449b1f02d70272d2ac802357c8c30c58" "reference": "ff2f20cf83bd4d503720632ce8a426dc747bf7fd"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/129700ed449b1f02d70272d2ac802357c8c30c58", "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ff2f20cf83bd4d503720632ce8a426dc747bf7fd",
"reference": "129700ed449b1f02d70272d2ac802357c8c30c58", "reference": "ff2f20cf83bd4d503720632ce8a426dc747bf7fd",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -468,8 +684,8 @@
], ],
"support": { "support": {
"docs": "https://carbon.nesbot.com/docs", "docs": "https://carbon.nesbot.com/docs",
"issues": "https://github.com/briannesbitt/Carbon/issues", "issues": "https://github.com/CarbonPHP/carbon/issues",
"source": "https://github.com/briannesbitt/Carbon" "source": "https://github.com/CarbonPHP/carbon"
}, },
"funding": [ "funding": [
{ {
@ -485,7 +701,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-12-27T09:25:35+00:00" "time": "2025-02-20T17:33:38+00:00"
}, },
{ {
"name": "psr/clock", "name": "psr/clock",
@ -535,6 +751,58 @@
}, },
"time": "2022-11-25T14:36:26+00:00" "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", "name": "psr/http-factory",
"version": "1.1.0", "version": "1.1.0",
@ -643,6 +911,62 @@
}, },
"time": "2023-04-04T09:54:51+00:00" "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", "name": "psr/log",
"version": "3.0.2", "version": "3.0.2",
@ -1024,16 +1348,16 @@
}, },
{ {
"name": "symfony/mime", "name": "symfony/mime",
"version": "v7.2.1", "version": "v7.2.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/mime.git", "url": "https://github.com/symfony/mime.git",
"reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283" "reference": "87ca22046b78c3feaff04b337f33b38510fd686b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/mime/zipball/7f9617fcf15cb61be30f8b252695ed5e2bfac283", "url": "https://api.github.com/repos/symfony/mime/zipball/87ca22046b78c3feaff04b337f33b38510fd686b",
"reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283", "reference": "87ca22046b78c3feaff04b337f33b38510fd686b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1088,7 +1412,7 @@
"mime-type" "mime-type"
], ],
"support": { "support": {
"source": "https://github.com/symfony/mime/tree/v7.2.1" "source": "https://github.com/symfony/mime/tree/v7.2.4"
}, },
"funding": [ "funding": [
{ {
@ -1104,7 +1428,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-12-07T08:50:44+00:00" "time": "2025-02-19T08:51:20+00:00"
}, },
{ {
"name": "symfony/options-resolver", "name": "symfony/options-resolver",
@ -1496,82 +1820,6 @@
], ],
"time": "2024-09-09T11:45:10+00:00" "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", "name": "symfony/polyfill-php83",
"version": "v1.31.0", "version": "v1.31.0",
@ -1650,16 +1898,16 @@
}, },
{ {
"name": "symfony/translation", "name": "symfony/translation",
"version": "v7.2.2", "version": "v7.2.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/translation.git", "url": "https://github.com/symfony/translation.git",
"reference": "e2674a30132b7cc4d74540d6c2573aa363f05923" "reference": "283856e6981286cc0d800b53bd5703e8e363f05a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/e2674a30132b7cc4d74540d6c2573aa363f05923", "url": "https://api.github.com/repos/symfony/translation/zipball/283856e6981286cc0d800b53bd5703e8e363f05a",
"reference": "e2674a30132b7cc4d74540d6c2573aa363f05923", "reference": "283856e6981286cc0d800b53bd5703e8e363f05a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1725,7 +1973,7 @@
"description": "Provides tools to internationalize your application", "description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/translation/tree/v7.2.2" "source": "https://github.com/symfony/translation/tree/v7.2.4"
}, },
"funding": [ "funding": [
{ {
@ -1741,7 +1989,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-12-07T08:18:10+00:00" "time": "2025-02-13T10:27:23+00:00"
}, },
{ {
"name": "symfony/translation-contracts", "name": "symfony/translation-contracts",
@ -1823,20 +2071,20 @@
}, },
{ {
"name": "twig/html-extra", "name": "twig/html-extra",
"version": "v3.18.0", "version": "v3.20.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/twigphp/html-extra.git", "url": "https://github.com/twigphp/html-extra.git",
"reference": "c63b28e192c1b7c15bb60f81d2e48b140846239a" "reference": "f7d54d4de1b64182af745cfb66777f699b599734"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/twigphp/html-extra/zipball/c63b28e192c1b7c15bb60f81d2e48b140846239a", "url": "https://api.github.com/repos/twigphp/html-extra/zipball/f7d54d4de1b64182af745cfb66777f699b599734",
"reference": "c63b28e192c1b7c15bb60f81d2e48b140846239a", "reference": "f7d54d4de1b64182af745cfb66777f699b599734",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=8.0.2", "php": ">=8.1.0",
"symfony/deprecation-contracts": "^2.5|^3", "symfony/deprecation-contracts": "^2.5|^3",
"symfony/mime": "^5.4|^6.4|^7.0", "symfony/mime": "^5.4|^6.4|^7.0",
"twig/twig": "^3.13|^4.0" "twig/twig": "^3.13|^4.0"
@ -1875,7 +2123,7 @@
"twig" "twig"
], ],
"support": { "support": {
"source": "https://github.com/twigphp/html-extra/tree/v3.18.0" "source": "https://github.com/twigphp/html-extra/tree/v3.20.0"
}, },
"funding": [ "funding": [
{ {
@ -1887,28 +2135,27 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-12-29T10:29:59+00:00" "time": "2025-01-31T20:45:36+00:00"
}, },
{ {
"name": "twig/twig", "name": "twig/twig",
"version": "v3.18.0", "version": "v3.20.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/twigphp/Twig.git", "url": "https://github.com/twigphp/Twig.git",
"reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50" "reference": "3468920399451a384bef53cf7996965f7cd40183"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50", "url": "https://api.github.com/repos/twigphp/Twig/zipball/3468920399451a384bef53cf7996965f7cd40183",
"reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50", "reference": "3468920399451a384bef53cf7996965f7cd40183",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=8.0.2", "php": ">=8.1.0",
"symfony/deprecation-contracts": "^2.5|^3", "symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-ctype": "^1.8", "symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3", "symfony/polyfill-mbstring": "^1.3"
"symfony/polyfill-php81": "^1.29"
}, },
"require-dev": { "require-dev": {
"phpstan/phpstan": "^2.0", "phpstan/phpstan": "^2.0",
@ -1955,7 +2202,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/twigphp/Twig/issues", "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": [ "funding": [
{ {
@ -1967,22 +2214,22 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-12-29T10:51:50+00:00" "time": "2025-02-13T08:34:43+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [
{ {
"name": "phpstan/phpstan", "name": "phpstan/phpstan",
"version": "2.1.1", "version": "2.1.8",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan.git", "url": "https://github.com/phpstan/phpstan.git",
"reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7" "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7", "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f9adff3b87c03b12cc7e46a30a524648e497758f",
"reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7", "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2027,7 +2274,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2025-01-05T16:43:48+00:00" "time": "2025-03-09T09:30:48+00:00"
} }
], ],
"aliases": [], "aliases": [],

View file

@ -28,7 +28,8 @@ class AuthContext {
public function createClient(Credentials $credentials): FlashiiClient { public function createClient(Credentials $credentials): FlashiiClient {
return new FlashiiClient('EEPROM', $credentials, new FlashiiUrls( 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),
)); ));
} }

View file

@ -1,17 +1,19 @@
<?php <?php
namespace EEPROM\Auth; 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 { class AuthRoutes implements RouteHandler {
use RouteHandlerTrait; use RouteHandlerCommon;
public function __construct( public function __construct(
private AuthContext $authCtx private AuthContext $authCtx
) {} ) {}
#[HttpMiddleware('/')] #[Preprocessor('eeprom:attempt-auth')]
public function getIndex($response, $request) { public function getIndex(HttpResponseBuilder $response, HttpRequest $request) {
$auth = $request->getHeaderLine('Authorization'); $auth = $request->getHeaderLine('Authorization');
if(empty($auth)) { if(empty($auth)) {
$cookie = (string)$request->getCookie('msz_auth'); $cookie = (string)$request->getCookie('msz_auth');
@ -21,7 +23,7 @@ class AuthRoutes implements RouteHandler {
if(!empty($auth)) { if(!empty($auth)) {
$parts = explode(' ', $auth, 2); $parts = explode(' ', $auth, 2);
$method = strval($parts[0] ?? ''); $method = $parts[0];
$token = strval($parts[1] ?? ''); $token = strval($parts[1] ?? '');
$flashii = null; $flashii = null;

View file

@ -3,22 +3,23 @@ namespace EEPROM;
use Index\Db\DbConnection; use Index\Db\DbConnection;
use Index\Db\Migration\{DbMigrationManager,DbMigrationRepo,FsDbMigrationRepo}; 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 { class DatabaseContext implements RouteHandler {
use RouteHandlerTrait; use RouteHandlerCommon;
public function __construct( public function __construct(
public private(set) DbConnection $connection public private(set) DbConnection $conn
) {} ) {}
public function getQueryCount(): int { 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; return $result->next() ? $result->getInteger(1) : 0;
} }
public function createMigrationManager(): DbMigrationManager { 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 { public function createMigrationRepo(): DbMigrationRepo {
@ -29,7 +30,7 @@ class DatabaseContext implements RouteHandler {
return sys_get_temp_dir() . '/eeprom-migrate-' . hash('sha256', PRM_ROOT) . '.lock'; return sys_get_temp_dir() . '/eeprom-migrate-' . hash('sha256', PRM_ROOT) . '.lock';
} }
#[HttpMiddleware('/')] #[PrefixFilter('/')]
public function middleware() { public function middleware() {
if(is_file($this->getMigrateLockPath())) if(is_file($this->getMigrateLockPath()))
return 503; return 503;

View file

@ -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;
}
}

View file

@ -1,14 +1,16 @@
<?php <?php
namespace EEPROM; namespace EEPROM;
use EEPROM\Auth\AuthInfoWrapper; use Index\Dependencies;
use Index\Config\Config; use Index\Config\Config;
use Index\Db\DbConnection; use Index\Db\DbConnection;
use RPCii\HmacVerificationProvider; use RPCii\HmacVerificationProvider;
use RPCii\Server\HttpRpcServer; use RPCii\Server\HttpRpcServer;
class EEPROMContext { 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) SnowflakeGenerator $snowflake;
public private(set) Auth\AuthContext $authCtx; public private(set) Auth\AuthContext $authCtx;
@ -22,30 +24,26 @@ class EEPROMContext {
private Config $config, private Config $config,
DbConnection $dbConn DbConnection $dbConn
) { ) {
$this->database = new DatabaseContext($dbConn); $this->deps = new Dependencies;
$this->snowflake = new SnowflakeGenerator; $this->deps->register($this);
$this->deps->register($this->deps);
$this->authCtx = new Auth\AuthContext($config->scopeTo('apii')); $this->deps->register($this->dbCtx = new DatabaseContext($dbConn));
$this->denylistCtx = new Denylist\DenylistContext($dbConn); $this->deps->register($this->dbCtx->conn);
$this->poolsCtx = new Pools\PoolsContext($dbConn);
$this->storageCtx = new Storage\StorageContext($config->scopeTo('storage'), $dbConn, $this->snowflake); $this->deps->register($this->snowflake = $this->deps->constructLazy(SnowflakeGenerator::class));
$this->tasksCtx = new Tasks\TasksContext($config->scopeTo('domain'), $dbConn, $this->snowflake);
$this->uploadsCtx = new Uploads\UploadsContext( $this->deps->register($this->authCtx = $this->deps->constructLazy(Auth\AuthContext::class, $config->scopeTo('apii')));
$config->scopeTo('domain'), $dbConn, $this->snowflake, $this->poolsCtx, $this->storageCtx, $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 { public function createRouting(bool $isApiDomain): RoutingContext {
$routingCtx = new RoutingContext($this->config->scopeTo('cors')); $routingCtx = new RoutingContext($this->config->scopeTo('cors'));
$routingCtx->register($this->database); $routingCtx->register($this->dbCtx);
$routingCtx->register($uploadsViewsRoutes = new Uploads\UploadsViewRoutes(
$this->poolsCtx,
$this->uploadsCtx,
$this->storageCtx,
$this->denylistCtx,
$isApiDomain,
));
if($isApiDomain) { if($isApiDomain) {
$rpcServer = new HttpRpcServer; $rpcServer = new HttpRpcServer;
@ -53,33 +51,16 @@ class EEPROMContext {
new HmacVerificationProvider(fn() => $this->config->getString('rpcii:secret')) new HmacVerificationProvider(fn() => $this->config->getString('rpcii:secret'))
)); ));
$rpcServer->register(new Pools\PoolsRpcHandler( $rpcServer->register($this->deps->constructLazy(Pools\PoolsRpcHandler::class));
$this->poolsCtx, $rpcServer->register($this->deps->constructLazy(Tasks\TasksRpcHandler::class));
)); $rpcServer->register($this->deps->constructLazy(Uploads\UploadsRpcHandler::class));
$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,
));
$routingCtx->register(new Auth\AuthRoutes($this->authCtx)); $routingCtx->register($this->deps->constructLazy(Auth\AuthRoutes::class));
$routingCtx->register(new Tasks\TasksRoutes($this->tasksCtx)); $routingCtx->register($this->deps->constructLazy(Tasks\TasksRoutes::class));
$routingCtx->register(new Uploads\UploadsLegacyRoutes( $routingCtx->register($this->deps->constructLazy(Uploads\UploadsLegacyRoutes::class));
$this->authCtx, $routingCtx->register($this->deps->constructLazy(LandingRoutes::class));
$this->poolsCtx, } else
$this->uploadsCtx, $routingCtx->register($this->deps->constructLazy(Uploads\UploadsViewRoutes::class));
$this->storageCtx,
$this->denylistCtx,
));
$routingCtx->register(new LandingRoutes($this->database));
}
return $routingCtx; return $routingCtx;
} }

View file

@ -1,10 +1,23 @@
<?php <?php
namespace EEPROM; 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 { class EEPROMErrorHandler implements ErrorHandler {
public function handle(HttpResponseBuilder $response, HttpRequest $request, int $code, string $message): void { public function handle(HandlerContext $context): void {
$response->setContent(sprintf('<!doctype html><title>%1$03d %2$s</title><strong>%1$03d %2$s</strong>', $code, $message)); 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
));
} }
} }

View file

@ -2,22 +2,24 @@
namespace EEPROM; namespace EEPROM;
use stdClass; 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 { class LandingRoutes implements RouteHandler {
use RouteHandlerTrait; use RouteHandlerCommon;
public function __construct( public function __construct(
private DatabaseContext $dbCtx private DatabaseContext $dbCtx
) {} ) {}
#[HttpGet('/')] #[ExactRoute('GET', '/')]
public function getIndex($response) { public function getIndex(HttpResponseBuilder $response) {
$response->accelRedirect('/index.html'); $response->accelRedirect('/index.html');
$response->setContentType('text/html; charset=utf-8'); $response->setContentType('text/html; charset=utf-8');
} }
#[HttpGet('/stats.json')] #[ExactRoute('GET', '/stats.json')]
public function getStats() { public function getStats() {
$stats = new stdClass; $stats = new stdClass;
$stats->uploads = 0; $stats->uploads = 0;
@ -26,13 +28,13 @@ class LandingRoutes implements RouteHandler {
$stats->types = 0; $stats->types = 0;
$stats->members = 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()) { if($result->next()) {
$stats->uploads = $result->getInteger(0); $stats->uploads = $result->getInteger(0);
$stats->members = $result->getInteger(1); $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()) { if($result->next()) {
$stats->files = $result->getInteger(0); $stats->files = $result->getInteger(0);
$stats->size = $result->getInteger(1); $stats->size = $result->getInteger(1);
@ -42,8 +44,8 @@ class LandingRoutes implements RouteHandler {
return $stats; return $stats;
} }
#[HttpGet('/eeprom.js')] #[ExactRoute('GET', '/eeprom.js')]
public function getEepromJs($response) { public function getEepromJs(HttpResponseBuilder $response) {
$response->accelRedirect('/scripts/eepromv1.js'); $response->accelRedirect('/scripts/eepromv1.js');
$response->setContentType('application/javascript; charset=utf-8'); $response->setContentType('application/javascript; charset=utf-8');
} }

View file

@ -2,7 +2,9 @@
namespace EEPROM\Pools; namespace EEPROM\Pools;
use RuntimeException; use RuntimeException;
use EEPROM\Storage\StorageRecord;
use EEPROM\Uploads\UploadVariantInfo; use EEPROM\Uploads\UploadVariantInfo;
use Index\XArray;
use Index\Db\DbConnection; use Index\Db\DbConnection;
class PoolsContext { 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); return $this->pools->getPool($variantInfo->fileId, PoolInfoGetField::Id);
} }
@ -69,7 +71,10 @@ class PoolsContext {
if($removeStaleRule instanceof Rules\RemoveStaleRule && $removeStaleRule->inactiveForSeconds > 0) if($removeStaleRule instanceof Rules\RemoveStaleRule && $removeStaleRule->inactiveForSeconds > 0)
$body['idle_time'] = $removeStaleRule->inactiveForSeconds; $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)) if(!empty($ensureVariantRules))
$body['variants'] = $ensureVariantRules; $body['variants'] = $ensureVariantRules;

View file

@ -49,7 +49,6 @@ class PoolsData {
PoolInfoGetField::Id => 'pool_id = ?', PoolInfoGetField::Id => 'pool_id = ?',
PoolInfoGetField::Name => 'pool_name = ?', PoolInfoGetField::Name => 'pool_name = ?',
PoolInfoGetField::UploadId => 'pool_id = (SELECT pool_id FROM prm_uploads WHERE upload_id = ?)', 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 $stmt = $this->cache->get(<<<SQL
@ -90,8 +89,10 @@ class PoolsData {
SELECT rule_id, pool_id, rule_type, rule_params SELECT rule_id, pool_id, rule_type, rule_params
FROM prm_pools_rules FROM prm_pools_rules
SQL; SQL;
if($hasPoolInfo) if($hasPoolInfo) {
$query .= sprintf(' %s pool_id = ?', ++$args > 1 ? 'AND' : 'WHERE'); ++$args;
$query .= ' WHERE pool_id = ?';
}
if($hasTypes) if($hasTypes)
$query .= sprintf(' %s rule_type IN (%s)', ++$args > 1 ? 'AND' : 'WHERE', DbTools::prepareListString($types)); $query .= sprintf(' %s rule_type IN (%s)', ++$args > 1 ? 'AND' : 'WHERE', DbTools::prepareListString($types));

View file

@ -2,42 +2,19 @@
namespace EEPROM; namespace EEPROM;
use Index\Config\Config; use Index\Config\Config;
use Index\Http\HttpRequest; use Index\Http\{HttpResponseBuilder,HttpRequest};
use Index\Http\Routing\{HttpRouter,Router,RouteHandler}; use Index\Http\Routing\{Router,RouteHandler};
use Index\Http\Routing\Filters\FilterInfo;
class RoutingContext { class RoutingContext {
private HttpRouter $router; public private(set) Router $router;
public function __construct(private Config $config) { public function __construct(Config $config) {
$this->router = new HttpRouter( $this->router = new Router(
errorHandler: new EEPROMErrorHandler, errorHandler: new EEPROMErrorHandler,
accessControlHandler: new EEPROMAccessControlHandler($config->getArray('origins')),
); );
$this->router->use('/', $this->middleware(...)); $this->router->filter(FilterInfo::prefix('/', fn(HttpResponseBuilder $response) => $response->setPoweredBy('EEPROM')));
}
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;
} }
public function register(RouteHandler $handler): void { public function register(RouteHandler $handler): void {

View file

@ -79,18 +79,13 @@ class StorageFiles {
$target = $this->getLocalPath($hash, true); $target = $this->getLocalPath($hash, true);
if(is_file($target)) { if(is_file($target)) {
if($mode === StorageImportMode::Move || $mode === StorageImportMode::Upload) if($mode === StorageImportMode::Move)
unlink($path); unlink($path);
} else { } else {
if($mode === StorageImportMode::Move) if($mode === StorageImportMode::Move)
$success = rename($path, $target); $success = rename($path, $target);
elseif($mode === StorageImportMode::Copy) elseif($mode === StorageImportMode::Copy)
$success = copy($path, $target); $success = copy($path, $target);
elseif($mode === StorageImportMode::Upload)
$success = move_uploaded_file($path, $target);
else
$success = false;
if(!$success) if(!$success)
throw new RuntimeException('unable to move or copy $path using given $mode'); throw new RuntimeException('unable to move or copy $path using given $mode');
} }

View file

@ -4,5 +4,4 @@ namespace EEPROM\Storage;
enum StorageImportMode { enum StorageImportMode {
case Copy; case Copy;
case Move; case Move;
case Upload;
} }

View file

@ -37,12 +37,13 @@ class StorageRecords {
SQL; SQL;
$args = 0; $args = 0;
if($orphaned !== null) if($orphaned !== null) {
++$args;
$query .= sprintf( $query .= sprintf(
' %s uf.file_id %s NULL', ' WHERE uf.file_id %s NULL',
++$args > 1 ? 'OR' : 'WHERE',
$orphaned ? 'IS' : 'IS NOT' $orphaned ? 'IS' : 'IS NOT'
); );
}
if($denied !== null) if($denied !== null)
$query .= sprintf( $query .= sprintf(
' %s dl.deny_hash %s NULL', ' %s dl.deny_hash %s NULL',
@ -63,7 +64,6 @@ class StorageRecords {
$field = match($field) { $field = match($field) {
StorageRecordGetFileField::Id => 'file_id', StorageRecordGetFileField::Id => 'file_id',
StorageRecordGetFileField::Hash => 'file_hash', StorageRecordGetFileField::Hash => 'file_hash',
default => throw new InvalidArgumentException('$field is not an acceptable value'),
}; };
$stmt = $this->cache->get(<<<SQL $stmt = $this->cache->get(<<<SQL
@ -93,14 +93,14 @@ class StorageRecords {
if($size < 0) if($size < 0)
throw new InvalidArgumentException('$size may not be negative'); throw new InvalidArgumentException('$size may not be negative');
$fileId = $this->snowflake->nextHash($hash); $fileId = (string)$this->snowflake->nextHash($hash);
$stmt = $this->cache->get(<<<SQL $stmt = $this->cache->get(<<<SQL
INSERT INTO prm_files ( INSERT INTO prm_files (
file_id, file_hash, file_type, file_size file_id, file_hash, file_type, file_size
) VALUES (?, ?, ?, ?) ) VALUES (?, ?, ?, ?)
SQL); SQL);
$stmt->nextParameter($fileId); // generate snowflake $stmt->nextParameter($fileId);
$stmt->nextParameter($hash); $stmt->nextParameter($hash);
$stmt->nextParameter($type); $stmt->nextParameter($type);
$stmt->nextParameter($size); $stmt->nextParameter($size);
@ -120,7 +120,6 @@ class StorageRecords {
$field = match($field) { $field = match($field) {
StorageRecordGetFileField::Id => 'file_id', StorageRecordGetFileField::Id => 'file_id',
StorageRecordGetFileField::Hash => 'file_hash', StorageRecordGetFileField::Hash => 'file_hash',
default => throw new InvalidArgumentException('$field is not an acceptable value'),
}; };
$stmt = $this->cache->get(<<<SQL $stmt = $this->cache->get(<<<SQL
@ -142,7 +141,6 @@ class StorageRecords {
$field = match($field) { $field = match($field) {
StorageRecordGetFileField::Id => '?', StorageRecordGetFileField::Id => '?',
StorageRecordGetFileField::Hash => '(SELECT file_id FROM prm_files WHERE file_hash = ?)', 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 $stmt = $this->cache->get(<<<SQL

View file

@ -28,7 +28,7 @@ class TaskInfo {
secret: $result->getString(1), secret: $result->getString(1),
userId: $result->getString(2), userId: $result->getString(2),
poolId: $result->getString(3), poolId: $result->getString(3),
state: TaskState::tryFrom($result->getString(4)) ?? TaskState::Ready, state: TaskState::tryFrom($result->getString(4)) ?? TaskState::Pending,
remoteAddress: $result->getString(5), remoteAddress: $result->getString(5),
name: $result->getString(6), name: $result->getString(6),
size: $result->getInteger(7), size: $result->getInteger(7),

View file

@ -3,6 +3,7 @@ namespace EEPROM\Tasks;
use InvalidArgumentException; use InvalidArgumentException;
use RuntimeException; use RuntimeException;
use Stringable;
use EEPROM\SnowflakeGenerator; use EEPROM\SnowflakeGenerator;
use EEPROM\Pools\PoolInfo; use EEPROM\Pools\PoolInfo;
use Index\XString; use Index\XString;
@ -29,8 +30,10 @@ class TasksData {
task_name, task_size, task_type, task_hash, UNIX_TIMESTAMP(task_created) task_name, task_size, task_type, task_hash, UNIX_TIMESTAMP(task_created)
FROM prm_tasks FROM prm_tasks
SQL; SQL;
if($hasState) if($hasState) {
$query .= sprintf(' %s task_state = ?', ++$args > 1 ? 'AND' : 'WHERE'); ++$args;
$query .= ' WHERE task_state = ?';
}
$stmt = $this->cache->get($query); $stmt = $this->cache->get($query);
if($hasState) if($hasState)
@ -77,7 +80,7 @@ class TasksData {
string $type, string $type,
string $hash string $hash
): TaskInfo { ): TaskInfo {
$taskId = $this->snowflake->nextRandom(); $taskId = (string)$this->snowflake->nextRandom();
$stmt = $this->cache->get(<<<SQL $stmt = $this->cache->get(<<<SQL
INSERT INTO prm_tasks ( INSERT INTO prm_tasks (

View file

@ -3,39 +3,36 @@ namespace EEPROM\Tasks;
use RuntimeException; use RuntimeException;
use Index\{ByteFormat,XNumber}; use Index\{ByteFormat,XNumber};
use Index\Http\StringHttpContent; use Index\Http\{HttpResponseBuilder,HttpRequest};
use Index\Http\Routing\{HttpPut,RouteHandler,RouteHandlerTrait}; use Index\Http\Routing\{HttpPut,RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Routes\PatternRoute;
class TasksRoutes implements RouteHandler { class TasksRoutes implements RouteHandler {
use RouteHandlerTrait; use RouteHandlerCommon;
public function __construct( public function __construct(
private TasksContext $tasksCtx, private TasksContext $tasksCtx,
) {} ) {}
#[HttpPut('/uploads/([A-Za-z0-9]+)(?:-([a-z0-9]+))?(?:\.([A-Za-z0-9\-_]+))?')] #[PatternRoute('PUT', '/uploads/([A-Za-z0-9]+)(?:-([a-z0-9]+))?(?:\.([A-Za-z0-9\-_]+))?')]
public function putUpload($response, $request, string $taskId, string $variant = '', string $extension = '') { public function putUpload(
HttpResponseBuilder $response,
HttpRequest $request,
string $taskId,
string $variant = '',
string $extension = '',
) {
if($request->getHeaderLine('Content-Type') !== 'application/octet-stream') { if($request->getHeaderLine('Content-Type') !== 'application/octet-stream') {
$response->setStatusCode(400); $response->statusCode = 400;
return [ return [
'error' => 'bad_type', 'error' => 'bad_type',
'english' => 'Content-Type must be application/octet-stream.', 'english' => 'Content-Type must be application/octet-stream.',
]; ];
} }
$content = $request->getContent(); $content = (string)$request->getBody();
if($content instanceof StringHttpContent) {
$content = (string)$content;
} else {
$response->setStatusCode(400);
return [
'error' => 'bad_content',
'english' => 'Request content could not be parsed as expected.',
];
}
if(strlen($taskId) < 5) { if(strlen($taskId) < 5) {
$response->setStatusCode(404); $response->statusCode = 404;
return [ return [
'error' => 'not_found', 'error' => 'not_found',
'english' => 'Requested upload task does not exist.', 'english' => 'Requested upload task does not exist.',
@ -43,8 +40,8 @@ class TasksRoutes implements RouteHandler {
} }
if($variant !== '') { if($variant !== '') {
$response->setStatusCode(405); $response->statusCode = 405;
$response->setHeader('Allow', 'HEAD, GET'); $response->setAllow(['HEAD', 'GET']);
return [ return [
'error' => 'method_not_allowed', 'error' => 'method_not_allowed',
'english' => 'PUT requests are not supported for upload variants.', 'english' => 'PUT requests are not supported for upload variants.',
@ -53,7 +50,7 @@ class TasksRoutes implements RouteHandler {
$length = (int)$request->getHeaderLine('Content-Length'); $length = (int)$request->getHeaderLine('Content-Length');
if($length < 1) { if($length < 1) {
$response->setStatusCode(411); $response->statusCode = 411;
return [ return [
'error' => 'length_required', 'error' => 'length_required',
'english' => 'A Content-Length header with the size of the request body is 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) { if(strlen($content) !== $length) {
$response->setStatusCode(400); $response->statusCode = 400;
return [ return [
'error' => 'length_mismatch', 'error' => 'length_mismatch',
'english' => 'Length specified in Content-Length differs from actual request body length.', '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) { if($length > TasksContext::CHUNK_SIZE) {
$response->setStatusCode(413); $response->statusCode = 413;
return [ return [
'error' => 'content_too_large', 'error' => 'content_too_large',
'english' => sprintf( 'english' => sprintf(
@ -84,7 +81,7 @@ class TasksRoutes implements RouteHandler {
if($request->hasHeader('X-Content-Index')) { if($request->hasHeader('X-Content-Index')) {
if($request->hasParam('index')) { if($request->hasParam('index')) {
$response->setStatusCode(400); $response->statusCode = 400;
return [ return [
'error' => 'bad_param', 'error' => 'bad_param',
'english' => 'the index query parameter may not be used at the same time as the X-Content-Index header.', '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); $index = (int)filter_var($request->getHeaderLine('X-Content-Index'), FILTER_SANITIZE_NUMBER_INT);
} elseif($request->hasParam('index')) { } elseif($request->hasParam('index')) {
if($request->hasHeader('X-Content-Index')) { $index = (int)$request->getFilteredParam('index', FILTER_SANITIZE_NUMBER_INT);
$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);
} else $index = 0; } else $index = 0;
if($index < 0) { if($index < 0) {
$response->setStatusCode(400); $response->statusCode = 400;
return [ return [
'error' => 'bad_index', 'error' => 'bad_index',
'english' => 'index parameter must be greater than or equal to 0.', 'english' => 'index parameter must be greater than or equal to 0.',
@ -113,19 +102,19 @@ class TasksRoutes implements RouteHandler {
} }
$taskSecret = substr($taskId, -16); $taskSecret = substr($taskId, -16);
$taskId = XNumber::fromBase62(substr($taskId, 0, -16)); $taskId = (string)XNumber::fromBase62(substr($taskId, 0, -16));
try { try {
$taskInfo = $this->tasksCtx->tasks->getTask($taskId); $taskInfo = $this->tasksCtx->tasks->getTask($taskId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
$response->setStatusCode(404); $response->statusCode = 404;
return [ return [
'error' => 'not_found', 'error' => 'not_found',
'english' => 'Requested upload task does not exist.', 'english' => 'Requested upload task does not exist.',
]; ];
} }
if(!$taskInfo->verifySecret($taskSecret)) { if(!$taskInfo->verifySecret($taskSecret)) {
$response->setStatusCode(404); $response->statusCode = 404;
return [ return [
'error' => 'not_found', 'error' => 'not_found',
'english' => 'Requested upload task does not exist.', 'english' => 'Requested upload task does not exist.',
@ -133,7 +122,7 @@ class TasksRoutes implements RouteHandler {
} }
if($taskInfo->state !== TaskState::Pending) { if($taskInfo->state !== TaskState::Pending) {
$response->setStatusCode(409); $response->statusCode = 409;
return [ return [
'error' => 'not_pending', 'error' => 'not_pending',
'english' => 'Requested upload task is no longer accepting data.', 'english' => 'Requested upload task is no longer accepting data.',
@ -142,7 +131,7 @@ class TasksRoutes implements RouteHandler {
$offset = TasksContext::CHUNK_SIZE * $index; $offset = TasksContext::CHUNK_SIZE * $index;
if($offset >= $taskInfo->size) { if($offset >= $taskInfo->size) {
$response->setStatusCode(400); $response->statusCode = 400;
return [ return [
'error' => 'offset_too_large', 'error' => 'offset_too_large',
'english' => 'Provided offset is greater than file size.', 'english' => 'Provided offset is greater than file size.',
@ -152,7 +141,7 @@ class TasksRoutes implements RouteHandler {
$path = $this->tasksCtx->getTaskWorkingPath($taskInfo); $path = $this->tasksCtx->getTaskWorkingPath($taskInfo);
if(!is_file($path)) { if(!is_file($path)) {
$this->tasksCtx->tasks->setTaskError($taskInfo, sprintf('working file despawned during PUT request: %s', $path)); $this->tasksCtx->tasks->setTaskError($taskInfo, sprintf('working file despawned during PUT request: %s', $path));
$response->setStatusCode(500); $response->statusCode = 500;
return [ return [
'error' => 'server_error', 'error' => 'server_error',
'english' => 'Working file no longer exist on the server, the upload process cannot continue.', '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+'); $handle = fopen($path, 'rb+');
if($handle === false) { if($handle === false) {
$this->tasksCtx->tasks->setTaskError($taskInfo, sprintf('failed to open during PUT request: %s', $path)); $this->tasksCtx->tasks->setTaskError($taskInfo, sprintf('failed to open during PUT request: %s', $path));
$response->setStatusCode(500); $response->statusCode = 500;
return [ return [
'error' => 'server_error', 'error' => 'server_error',
'english' => 'Was not able to open working file, the upload process cannot continue.', 'english' => 'Was not able to open working file, the upload process cannot continue.',
]; ];
} }
try { if(fseek($handle, $offset, SEEK_SET) < 0) {
if(fseek($handle, $offset, SEEK_SET) < 0) { $this->tasksCtx->tasks->setTaskError($taskInfo, sprintf('was unable to seek during PUT request: %s', $path));
$this->tasksCtx->tasks->setTaskError($taskInfo, sprintf('was unable to seek during PUT request: %s', $path)); $response->statusCode = 500;
$response->setStatusCode(500); return [
return [ 'error' => 'server_error',
'error' => 'server_error', 'english' => 'Was not able to seek to provided offset, the upload process cannot continue.',
'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.',
];
}
} }
$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 ''; return '';
} }
} }

View file

@ -65,11 +65,8 @@ final class TasksRpcHandler implements RpcHandler {
if($signature === null) if($signature === null)
return 'srpp'; return 'srpp';
if(!str_contains($signature, '=')) { if(!str_contains($signature, '='))
$signature = UriBase64::decode($signature); $signature = UriBase64::decode($signature);
if(!is_string($signature))
$signature = '';
}
$signature = array_column(XArray::select( $signature = array_column(XArray::select(
explode(';', $signature), explode(';', $signature),
@ -94,7 +91,7 @@ final class TasksRpcHandler implements RpcHandler {
if($sAlgo !== 'S256') if($sAlgo !== 'S256')
return 'sanv'; return 'sanv';
if(abs($cTime - $sTime) >= 30) if(abs($cTime - (int)$sTime) >= 30)
return 'stnv'; return 'stnv';
if(strlen($sSalt) < 20) if(strlen($sSalt) < 20)
@ -182,15 +179,13 @@ final class TasksRpcHandler implements RpcHandler {
return 'wfcf'; return 'wfcf';
} }
try { if(!ftruncate($data, $fileSize)) {
if(!ftruncate($data, $fileSize)) { $this->tasksCtx->tasks->setTaskError($taskInfo, sprintf('failed to reserve %d bytes for task working file: %s', $fileSize, $path));
$this->tasksCtx->tasks->setTaskError($taskInfo, sprintf('failed to reserve %d bytes for task working file: %s', $fileSize, $path)); return 'wfrf';
return 'wfrf';
}
} finally {
if(!fclose($data))
return 'wfff';
} }
if(!fclose($data))
return 'wfff';
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
return 'utcf'; return 'utcf';
} }
@ -210,7 +205,7 @@ final class TasksRpcHandler implements RpcHandler {
#[RpcQuery('eeprom:tasks:get')] #[RpcQuery('eeprom:tasks:get')]
public function actionTasksGet(string $taskId): string|array { public function actionTasksGet(string $taskId): string|array {
$taskSecret = substr($taskId, -16); $taskSecret = substr($taskId, -16);
$taskId = XNumber::fromBase62(substr($taskId, 0, -16)); $taskId = (string)XNumber::fromBase62(substr($taskId, 0, -16));
try { try {
$taskInfo = $this->tasksCtx->tasks->getTask($taskId); $taskInfo = $this->tasksCtx->tasks->getTask($taskId);
@ -256,7 +251,7 @@ final class TasksRpcHandler implements RpcHandler {
#[RpcAction('eeprom:tasks:finish')] #[RpcAction('eeprom:tasks:finish')]
public function actionsTasksFinish(string $taskId) { public function actionsTasksFinish(string $taskId) {
$taskSecret = substr($taskId, -16); $taskSecret = substr($taskId, -16);
$taskId = XNumber::fromBase62(substr($taskId, 0, -16)); $taskId = (string)XNumber::fromBase62(substr($taskId, 0, -16));
try { try {
$taskInfo = $this->tasksCtx->tasks->getTask($taskId); $taskInfo = $this->tasksCtx->tasks->getTask($taskId);

View file

@ -77,7 +77,7 @@ class UploadsContext {
'hash' => $fileInfo->hashHexString, 'hash' => $fileInfo->hashHexString,
'created' => $uploadInfo->createdAt->toIso8601ZuluString(), 'created' => $uploadInfo->createdAt->toIso8601ZuluString(),
'accessed' => $uploadInfo->accessedAt->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 // These can never be reached, and in situation where they technically could it's because of an outdated local record
'deleted' => null, 'deleted' => null,

View file

@ -13,7 +13,7 @@ class UploadsData {
private DbStatementCache $cache; private DbStatementCache $cache;
public function __construct( public function __construct(
private DbConnection $dbConn, DbConnection $dbConn,
private SnowflakeGenerator $snowflake private SnowflakeGenerator $snowflake
) { ) {
$this->cache = new DbStatementCache($dbConn); $this->cache = new DbStatementCache($dbConn);
@ -48,8 +48,10 @@ class UploadsData {
SQL; SQL;
$args = 0; $args = 0;
if($hasPoolInfo) if($hasPoolInfo) {
$query .= sprintf(' %s u.pool_id = ?', ++$args > 1 ? 'AND' : 'WHERE'); ++$args;
$query .= ' WHERE u.pool_id = ?';
}
if($hasUserId) if($hasUserId)
$query .= sprintf(' %s u.user_id = ?', ++$args > 1 ? 'AND' : 'WHERE'); $query .= sprintf(' %s u.user_id = ?', ++$args > 1 ? 'AND' : 'WHERE');
if($hasVariantExists) if($hasVariantExists)

View file

@ -5,12 +5,19 @@ use RuntimeException;
use EEPROM\Auth\AuthContext; use EEPROM\Auth\AuthContext;
use EEPROM\Denylist\{DenylistContext,DenylistReason}; use EEPROM\Denylist\{DenylistContext,DenylistReason};
use EEPROM\Pools\PoolsContext; use EEPROM\Pools\PoolsContext;
use EEPROM\Pools\Rules\{ConstrainSizeRule,EnsureVariantRule,EnsureVariantRuleThumb};
use EEPROM\Storage\{StorageContext,StorageImportMode,StorageRecordGetFileField}; use EEPROM\Storage\{StorageContext,StorageImportMode,StorageRecordGetFileField};
use Index\{ByteFormat,XNumber}; use Index\{ByteFormat,XArray,XNumber};
use Index\Http\Routing\{HttpDelete,HttpOptions,HttpPost,RouteHandler,RouteHandlerTrait}; 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 { class UploadsLegacyRoutes implements RouteHandler {
use RouteHandlerTrait; use RouteHandlerCommon;
public function __construct( public function __construct(
private AuthContext $authCtx, private AuthContext $authCtx,
@ -20,21 +27,111 @@ class UploadsLegacyRoutes implements RouteHandler {
private DenylistContext $denylistCtx private DenylistContext $denylistCtx
) {} ) {}
#[HttpOptions('/uploads')] #[AccessControl]
public function optionsUpload($response, $request): int { #[PatternRoute('GET', '/uploads/([A-Za-z0-9]+|[A-Za-z0-9\-_]{32})(?:-([a-z0-9]+))?(?:\.([A-Za-z0-9\-_]+))?')]
$response->setHeader('Access-Control-Allow-Headers', 'Authorization'); public function getUpload(
$response->setHeader('Access-Control-Allow-Methods', 'POST'); 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')] #[AccessControl(credentials: true, allowHeaders: ['Authorization'])]
public function postUpload($response, $request) { #[Before('input:multipart')]
if(!$request->isFormContent()) #[Before('eeprom:attempt-auth')]
return 400; #[ExactRoute('POST', '/uploads')]
public function postUpload(HttpResponseBuilder $response, HttpRequest $request, MultipartFormContent $content) {
if(!$this->authCtx->info->authed) { if(!$this->authCtx->info->authed) {
$response->setStatusCode(401); $response->statusCode = 401;
return [ return [
'error' => 'auth', '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!", '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) { if($this->authCtx->info->restricted) {
$response->setStatusCode(403); $response->statusCode = 403;
return [ return [
'error' => 'restricted', 'error' => 'restricted',
'english' => 'You are currently banned and are not allowed to upload or manage files.', 'english' => 'You are currently banned and are not allowed to upload or manage files.',
]; ];
} }
$content = $request->getContent(); $file = $content->getParamData('file');
if(!($file instanceof FileMultipartFormData)) {
try { $response->statusCode = 400;
$file = $content->getUploadedFile('file');
} catch(RuntimeException $ex) {
$response->setStatusCode(400);
return [ return [
'error' => 'file', 'error' => 'file',
'english' => 'File upload is missing from request body.', 'english' => 'File upload is missing from request body.',
]; ];
} }
$localFile = $file->getLocalFileName(); $fileSize = $file->getSize();
$fileSize = filesize($localFile);
if($fileSize < 1) { if($fileSize < 1) {
$response->setStatusCode(400); $response->statusCode = 400;
return [ return [
'error' => 'empty', 'error' => 'empty',
'english' => 'The file you attempted to upload is empty.', 'english' => 'The file you attempted to upload is empty.',
@ -72,16 +165,16 @@ class UploadsLegacyRoutes implements RouteHandler {
} }
try { 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) { if($poolInfo->protected) {
$response->setStatusCode(404); $response->statusCode = 404;
return [ return [
'error' => 'pool_incompatible', 'error' => 'pool_incompatible',
'english' => 'The target upload pool is not compatible with this endpoint.', 'english' => 'The target upload pool is not compatible with this endpoint.',
]; ];
} }
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
$response->setStatusCode(404); $response->statusCode = 404;
return [ return [
'error' => 'pool', 'error' => 'pool',
'english' => 'The target upload pool does not exist.', '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! // 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'); $maxSizeRule = $rules->getRule('constrain_size');
if($maxSizeRule === null) { if(!($maxSizeRule instanceof ConstrainSizeRule)) {
$response->setStatusCode(500); $response->statusCode = 500;
return [ return [
'error' => 'size_rule_missing', 'error' => 'size_rule_missing',
'english' => 'A pool size constraint rule is required any this pool does not specify one.', '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) if($maxSizeRule->allowLegacyMultiplier)
$maxFileSize *= $this->authCtx->info->legacyMultiplier; $maxFileSize *= $this->authCtx->info->legacyMultiplier;
if($file->getSize() !== $fileSize || $fileSize > $maxFileSize) { if($fileSize > $maxFileSize) {
$response->setStatusCode(413); $response->statusCode = 413;
$response->setHeader('Access-Control-Expose-Headers', 'X-EEPROM-Max-Size'); $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 [ return [
'error' => 'size', 'error' => 'size',
'english' => sprintf( '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); $denyInfo = $this->denylistCtx->getDenylistEntry($hash);
if($denyInfo !== null) { if($denyInfo !== null) {
$response->setStatusCode(451); $response->statusCode = 451;
return [ return [
'error' => 'takedown', 'error' => 'takedown',
'english' => sprintf( 'english' => sprintf(
@ -139,17 +234,17 @@ class UploadsLegacyRoutes implements RouteHandler {
]; ];
} }
$fileName = $file->getSuggestedFileName(); $fileName = $file->getClientFilename();
$mediaType = (string)$file->getSuggestedMediaType(); $mediaType = $file->getClientMediaType();
if($mediaType === '/' || $mediaType === 'application/octet-stream') if($mediaType === null || $mediaType === 'application/octet-stream')
$mediaType = mime_content_type($localFile); $mediaType = mime_content_type($tmpFile);
try { try {
$storageInfo = $this->storageCtx->records->getFile($hash, StorageRecordGetFileField::Hash); $storageInfo = $this->storageCtx->records->getFile($hash, StorageRecordGetFileField::Hash);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
$storageInfo = $this->storageCtx->importFile( $storageInfo = $this->storageCtx->importFile(
$file->getLocalFileName(), $tmpFile,
StorageImportMode::Upload, StorageImportMode::Move,
$mediaType $mediaType
); );
} }
@ -172,14 +267,14 @@ class UploadsLegacyRoutes implements RouteHandler {
$poolInfo, $poolInfo,
$this->authCtx->info->userId, $this->authCtx->info->userId,
$fileName, $fileName,
$request->getRemoteAddress() $request->remoteAddress
); );
$variantInfo = $this->uploadsCtx->uploads->createUploadVariant( $variantInfo = $this->uploadsCtx->uploads->createUploadVariant(
$uploadInfo, '', $storageInfo, $mediaType $uploadInfo, '', $storageInfo, $mediaType
); );
} }
$response->setStatusCode(201); $response->statusCode = 201;
$response->setHeader('Content-Type', 'application/json; charset=utf-8'); $response->setHeader('Content-Type', 'application/json; charset=utf-8');
return $this->uploadsCtx->convertToClientJsonV1($uploadInfo, $variantInfo, $storageInfo, [ 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\-_]+))?')] #[AccessControl(credentials: true, allowHeaders: ['Authorization'])]
public function deleteUpload($response, $request, string $uploadId) { #[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) { if(!$this->authCtx->info->authed) {
$response->setStatusCode(401); $response->statusCode = 401;
return [ return [
'error' => 'auth', '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!", '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) { if(strlen($uploadId) < 5) {
$response->setStatusCode(404); $response->statusCode = 404;
return [ return [
'error' => 'none', 'error' => 'none',
'english' => 'That file does not exist.', 'english' => 'That file does not exist.',
@ -216,7 +313,7 @@ class UploadsLegacyRoutes implements RouteHandler {
try { try {
$uploadInfo = $this->uploadsCtx->uploads->getUpload(uploadId: $uploadId, secret: $uploadSecret); $uploadInfo = $this->uploadsCtx->uploads->getUpload(uploadId: $uploadId, secret: $uploadSecret);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
$response->setStatusCode(404); $response->statusCode = 404;
return [ return [
'error' => 'none', 'error' => 'none',
'english' => 'That file does not exist.', 'english' => 'That file does not exist.',
@ -224,7 +321,7 @@ class UploadsLegacyRoutes implements RouteHandler {
} }
if($this->authCtx->info->restricted) { if($this->authCtx->info->restricted) {
$response->setStatusCode(403); $response->statusCode = 403;
return [ return [
'error' => 'restricted', 'error' => 'restricted',
'english' => 'You are currently banned and are not allowed to upload or manage files.', '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) { if($this->authCtx->info->userId !== $uploadInfo->userId) {
$response->setStatusCode(403); $response->statusCode = 403;
return [ return [
'error' => 'owner', 'error' => 'owner',
'english' => "You aren't allowed to delete files uploaded by others.", 'english' => "You aren't allowed to delete files uploaded by others.",

View file

@ -71,7 +71,7 @@ final class UploadsRpcHandler implements RpcHandler {
return [ return [
'result' => $this->uploadsCtx->extractUpload( 'result' => $this->uploadsCtx->extractUpload(
$this->uploadsCtx->uploads->getUpload( $this->uploadsCtx->uploads->getUpload(
uploadId: XNumber::fromBase62(substr($uploadId, 0, -4)), uploadId: (string)XNumber::fromBase62(substr($uploadId, 0, -4)),
secret: substr($uploadId, -4), secret: substr($uploadId, -4),
userId: $userId userId: $userId
), ),
@ -93,7 +93,7 @@ final class UploadsRpcHandler implements RpcHandler {
try { try {
$uploadInfo = $this->uploadsCtx->uploads->getUpload( $uploadInfo = $this->uploadsCtx->uploads->getUpload(
uploadId: XNumber::fromBase62(substr($uploadId, 0, -4)), uploadId: (string)XNumber::fromBase62(substr($uploadId, 0, -4)),
secret: substr($uploadId, -4), secret: substr($uploadId, -4),
); );
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
@ -120,7 +120,7 @@ final class UploadsRpcHandler implements RpcHandler {
try { try {
$uploadInfo = $this->uploadsCtx->uploads->getUpload( $uploadInfo = $this->uploadsCtx->uploads->getUpload(
uploadId: XNumber::fromBase62(substr($uploadId, 0, -4)), uploadId: (string)XNumber::fromBase62(substr($uploadId, 0, -4)),
secret: substr($uploadId, -4), secret: substr($uploadId, -4),
); );
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
@ -149,7 +149,7 @@ final class UploadsRpcHandler implements RpcHandler {
try { try {
$uploadInfo = $this->uploadsCtx->uploads->getUpload( $uploadInfo = $this->uploadsCtx->uploads->getUpload(
uploadId: XNumber::fromBase62(substr($uploadId, 0, -4)), uploadId: (string)XNumber::fromBase62(substr($uploadId, 0, -4)),
secret: substr($uploadId, -4), secret: substr($uploadId, -4),
); );
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {

View file

@ -7,39 +7,30 @@ use EEPROM\Pools\PoolsContext;
use EEPROM\Pools\Rules\{EnsureVariantRule,EnsureVariantRuleThumb}; use EEPROM\Pools\Rules\{EnsureVariantRule,EnsureVariantRuleThumb};
use EEPROM\Storage\StorageContext; use EEPROM\Storage\StorageContext;
use Index\{XArray,XNumber}; 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 { class UploadsViewRoutes implements RouteHandler {
use RouteHandlerCommon;
public function __construct( public function __construct(
private PoolsContext $poolsCtx, private PoolsContext $poolsCtx,
private UploadsContext $uploadsCtx, private UploadsContext $uploadsCtx,
private StorageContext $storageCtx, private StorageContext $storageCtx,
private DenylistContext $denylistCtx, private DenylistContext $denylistCtx
private bool $isApiDomain
) {} ) {}
public function registerRoutes(Router $router): void { #[AccessControl]
if($this->isApiDomain) #[PatternRoute('GET', '/([A-Za-z0-9]+|[A-Za-z0-9\-_]{32})(?:-([a-z0-9]+))?(?:\.([A-Za-z0-9\-_]+))?')]
$router = $router->scopeTo('/uploads'); public function getUpload(
HttpResponseBuilder $response,
HandlerAttribute::register($router, $this); HttpRequest $request,
} string $uploadId,
string $variant = '',
#[HttpOptions('/([A-Za-z0-9]+|[A-Za-z0-9\-_]{32})(?:-([a-z0-9]+))?(?:\.([A-Za-z0-9\-_]+))?')] string $extension = ''
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 = '') {
if(strlen($uploadId) < 5) if(strlen($uploadId) < 5)
return 404; return 404;
@ -49,10 +40,6 @@ class UploadsViewRoutes implements RouteHandler {
$variant = (string)$request->getParam('v'); $variant = (string)$request->getParam('v');
} }
$isV1Json = $this->isApiDomain && ($variantQuery || $variant === '') && $extension === 'json';
if($isV1Json)
$variant = '';
if(strlen($uploadId) === 32) { if(strlen($uploadId) === 32) {
$uploadId = $this->uploadsCtx->uploads->resolveLegacyId($uploadId) ?? $uploadId; $uploadId = $this->uploadsCtx->uploads->resolveLegacyId($uploadId) ?? $uploadId;
$uploadSecret = null; $uploadSecret = null;
@ -113,9 +100,6 @@ class UploadsViewRoutes implements RouteHandler {
if($denyInfo !== null) if($denyInfo !== null)
return $denyInfo->isCopyrightTakedown ? 451 : 410; return $denyInfo->isCopyrightTakedown ? 451 : 410;
if($isV1Json)
return $this->uploadsCtx->convertToClientJsonV1($uploadInfo, $variantInfo, $storageInfo);
$fileName = $uploadInfo->name; $fileName = $uploadInfo->name;
if($variantInfo->variant !== '') // FAIRLY SIGNIFICANT TODO: obviously this should support more file exts than .jpg... 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); $fileName = sprintf('%s-%s.jpg', pathinfo($fileName, PATHINFO_FILENAME), $variantInfo->variant);

View file

@ -2,20 +2,20 @@
<?php <?php
require_once __DIR__ . '/../eeprom.php'; require_once __DIR__ . '/../eeprom.php';
$lockPath = $eeprom->database->getMigrateLockPath(); $lockPath = $eeprom->dbCtx->getMigrateLockPath();
if(is_file($lockPath)) if(is_file($lockPath))
die('A migration script is already running.' . PHP_EOL); die('A migration script is already running.' . PHP_EOL);
touch($lockPath); touch($lockPath);
try { try {
echo 'Creating migration manager...' . PHP_EOL; echo 'Creating migration manager...' . PHP_EOL;
$manager = $eeprom->database->createMigrationManager(); $manager = $eeprom->dbCtx->createMigrationManager();
echo 'Preparing to run migrations...' . PHP_EOL; echo 'Preparing to run migrations...' . PHP_EOL;
$manager->init(); $manager->init();
echo 'Creating migration repository...' . PHP_EOL; echo 'Creating migration repository...' . PHP_EOL;
$repo = $eeprom->database->createMigrationRepo(); $repo = $eeprom->dbCtx->createMigrationRepo();
echo 'Running migrations...' . PHP_EOL; echo 'Running migrations...' . PHP_EOL;
$completed = $manager->processMigrations($repo); $completed = $manager->processMigrations($repo);

View file

@ -2,7 +2,7 @@
<?php <?php
require_once __DIR__ . '/../eeprom.php'; require_once __DIR__ . '/../eeprom.php';
$lockPath = $eeprom->database->getMigrateLockPath(); $lockPath = $eeprom->dbCtx->getMigrateLockPath();
if(is_file($lockPath)) { if(is_file($lockPath)) {
printf('Removing migration lock...%s', PHP_EOL); printf('Removing migration lock...%s', PHP_EOL);
unlink($lockPath); unlink($lockPath);

View file

@ -4,14 +4,14 @@ use Index\Db\Migration\FsDbMigrationRepo;
require_once __DIR__ . '/../eeprom.php'; require_once __DIR__ . '/../eeprom.php';
$repo = $eeprom->database->createMigrationRepo(); $repo = $eeprom->dbCtx->createMigrationRepo();
if(!($repo instanceof FsDbMigrationRepo)) { if(!($repo instanceof FsDbMigrationRepo)) {
echo 'Migration repository type does not support creation of templates.' . PHP_EOL; echo 'Migration repository type does not support creation of templates.' . PHP_EOL;
return; return;
} }
$baseName = implode(' ', array_slice($argv, 1)); $baseName = implode(' ', array_slice($argv, 1));
$manager = $eeprom->database->createMigrationManager(); $manager = $eeprom->dbCtx->createMigrationManager();
try { try {
$names = $manager->createNames($baseName); $names = $manager->createNames($baseName);