From f4212302ded7ec7ccecae8aa616ca3b4d3abd4af Mon Sep 17 00:00:00 2001 From: Philipp Date: Tue, 17 Sep 2024 22:05:34 +0200 Subject: [PATCH 01/17] WIP: refactor frontend with routing because we need more api routes for the file manipulation feature. - inject mod rewrite to webserver condig - moved function, config and classes to private directory - new try for timezone issue. writing it to config because lighttpd dosent hand over env vars to php - Dockerfile needed change for new dirs --- Dockerfile | 9 +- README.md | 1 + docker-compose-example.yml | 1 + files/runScanner.sh | 20 +- html/download.php | 23 - html/list.php | 55 --- html/scan.php | 46 -- html/timezone.php | 12 - update-container.sh | 6 +- .../bootstrap.5.1.3/bootstrap.bundle.min.js | 0 .../assets/bootstrap.5.1.3/bootstrap.min.css | 0 .../assets/fontawesome.5.15.4/LICENSE.txt | 0 .../assets/fontawesome.5.15.4/css/all.min.css | 0 .../webfonts/fa-brands-400.eot | Bin .../webfonts/fa-brands-400.svg | 0 .../webfonts/fa-brands-400.ttf | Bin .../webfonts/fa-brands-400.woff | Bin .../webfonts/fa-brands-400.woff2 | Bin .../webfonts/fa-regular-400.eot | Bin .../webfonts/fa-regular-400.svg | 0 .../webfonts/fa-regular-400.ttf | Bin .../webfonts/fa-regular-400.woff | Bin .../webfonts/fa-regular-400.woff2 | Bin .../webfonts/fa-solid-900.eot | Bin .../webfonts/fa-solid-900.svg | 0 .../webfonts/fa-solid-900.ttf | Bin .../webfonts/fa-solid-900.woff | Bin .../webfonts/fa-solid-900.woff2 | Bin .../html}/assets/jquery.3.7.1/jquery.min.js | 0 www/html/index.php | 72 +++ www/private/classes/AltoRouter.php | 300 ++++++++++++ www/private/helper.php | 25 + www/private/views/api/dev-timezone.php | 12 + www/private/views/api/file-delete.php | 0 www/private/views/api/file-download.php | 25 + www/private/views/api/file-rename.php | 0 www/private/views/api/scanner-scanto.php | 54 ++ .../private/views/api/scanner-status.php | 5 +- www/private/views/frontend/error.php | 1 + .../private/views/frontend/home.php | 462 +++++++++--------- www/private/views/frontend/list.php | 79 +++ 41 files changed, 829 insertions(+), 379 deletions(-) delete mode 100644 html/download.php delete mode 100644 html/list.php delete mode 100644 html/scan.php delete mode 100755 html/timezone.php rename {html => www/html}/assets/bootstrap.5.1.3/bootstrap.bundle.min.js (100%) rename {html => www/html}/assets/bootstrap.5.1.3/bootstrap.min.css (100%) rename {html => www/html}/assets/fontawesome.5.15.4/LICENSE.txt (100%) rename {html => www/html}/assets/fontawesome.5.15.4/css/all.min.css (100%) rename {html => www/html}/assets/fontawesome.5.15.4/webfonts/fa-brands-400.eot (100%) rename {html => www/html}/assets/fontawesome.5.15.4/webfonts/fa-brands-400.svg (100%) rename {html => www/html}/assets/fontawesome.5.15.4/webfonts/fa-brands-400.ttf (100%) rename {html => www/html}/assets/fontawesome.5.15.4/webfonts/fa-brands-400.woff (100%) rename {html => www/html}/assets/fontawesome.5.15.4/webfonts/fa-brands-400.woff2 (100%) rename {html => www/html}/assets/fontawesome.5.15.4/webfonts/fa-regular-400.eot (100%) rename {html => www/html}/assets/fontawesome.5.15.4/webfonts/fa-regular-400.svg (100%) rename {html => www/html}/assets/fontawesome.5.15.4/webfonts/fa-regular-400.ttf (100%) rename {html => www/html}/assets/fontawesome.5.15.4/webfonts/fa-regular-400.woff (100%) rename {html => www/html}/assets/fontawesome.5.15.4/webfonts/fa-regular-400.woff2 (100%) rename {html => www/html}/assets/fontawesome.5.15.4/webfonts/fa-solid-900.eot (100%) rename {html => www/html}/assets/fontawesome.5.15.4/webfonts/fa-solid-900.svg (100%) rename {html => www/html}/assets/fontawesome.5.15.4/webfonts/fa-solid-900.ttf (100%) rename {html => www/html}/assets/fontawesome.5.15.4/webfonts/fa-solid-900.woff (100%) rename {html => www/html}/assets/fontawesome.5.15.4/webfonts/fa-solid-900.woff2 (100%) rename {html => www/html}/assets/jquery.3.7.1/jquery.min.js (100%) create mode 100644 www/html/index.php create mode 100755 www/private/classes/AltoRouter.php create mode 100755 www/private/helper.php create mode 100755 www/private/views/api/dev-timezone.php create mode 100755 www/private/views/api/file-delete.php create mode 100644 www/private/views/api/file-download.php create mode 100755 www/private/views/api/file-rename.php create mode 100644 www/private/views/api/scanner-scanto.php rename html/active.php => www/private/views/api/scanner-status.php (89%) create mode 100755 www/private/views/frontend/error.php rename html/index.php => www/private/views/frontend/home.php (94%) mode change 100644 => 100755 create mode 100644 www/private/views/frontend/list.php diff --git a/Dockerfile b/Dockerfile index d12c0b4..d6a71ac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -73,13 +73,8 @@ EXPOSE 54925 EXPOSE 54921 EXPOSE 80 -#ADD files/gui/index.php /var/www/html -#ADD files/gui/main.css /var/www/html -#ADD files/api/scan.php /var/www/html -#ADD files/api/active.php /var/www/html -#ADD files/api/list.php /var/www/html -#ADD files/api/download.php /var/www/html -COPY html /var/www/html +# Copy the web files to the web directory +COPY www /var/www RUN chown -R www-data /var/www/ #directory for scans: VOLUME /scans diff --git a/README.md b/README.md index 5074e80..5d9096b 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ You can configure the tool via environment variables: | USE_JPEG_COMPRESSION | optional | use JPEG compression when creating PDFs | | TELEGRAM_TOKEN | optional | If TELEGRAM_TOKEN and TELEGRAM_CHATID are set, then this sends notification | | TELEGRAM_CHATID | optional | If TELEGRAM_TOKEN and TELEGRAM_CHATID are set, then this sends notification | +| ALLOW_GUI_FILEOPERATIONS | optional | true/false. Let you delete and rename files in files list | ### FTPS upload diff --git a/docker-compose-example.yml b/docker-compose-example.yml index ba1dc10..7e4b982 100755 --- a/docker-compose-example.yml +++ b/docker-compose-example.yml @@ -28,6 +28,7 @@ services: - OCR_PATH=ocr.php - TELEGRAM_TOKEN="" # note: keep the word bot in the string - TELEGRAM_CHATID=127585497 # note: target chat id. can be person or group + - ALLOW_GUI_FILEOPERATIONS=true restart: unless-stopped # optional, for OCR diff --git a/files/runScanner.sh b/files/runScanner.sh index bffb90a..39da60d 100755 --- a/files/runScanner.sh +++ b/files/runScanner.sh @@ -54,6 +54,7 @@ if [ "$WEBSERVER" == "true" ]; then echo "" - } > /var/www/html/config.php - chown www-data /var/www/html/config.php + } > /var/www/private/config.php + chown www-data /var/www/private/config.php + +# Rewrite-Regeln zur Lighttpd-Konfiguration hinzufügen + cat <> /etc/lighttpd/lighttpd.conf + +server.modules += ( "mod_rewrite" ) + +url.rewrite-if-not-file = ( + "^/(.*)$" => "/index.php" +) + +EOL + if [[ -z ${PORT} ]]; then PORT=80 fi diff --git a/html/download.php b/html/download.php deleted file mode 100644 index 45da56a..0000000 --- a/html/download.php +++ /dev/null @@ -1,23 +0,0 @@ - \ No newline at end of file diff --git a/html/list.php b/html/list.php deleted file mode 100644 index 9f48f23..0000000 --- a/html/list.php +++ /dev/null @@ -1,55 +0,0 @@ -
- -
- - filemtime($filePath), - 'size' => filesize($filePath), - 'permissions' => substr(sprintf('%o', fileperms($filePath)), -4), - 'owner' => posix_getpwuid(fileowner($filePath))['name'], - 'group' => posix_getgrgid(filegroup($filePath))['name'], - ); - } -} - -// Sort files by modification time (newest first) -uasort($filesWithMtime, function($a, $b) { - return $b['mtime'] <=> $a['mtime']; -}); - -// Output sorted files -foreach ($filesWithMtime as $file => $attributes) { -?> - -
- - -
-
Bytes
-
- - - - -
-
- - diff --git a/html/scan.php b/html/scan.php deleted file mode 100644 index ddfa94e..0000000 --- a/html/scan.php +++ /dev/null @@ -1,46 +0,0 @@ -)"); -} -if (in_array($target, array('file','email','image','ocr'))) { - if ($_SERVER['REQUEST_METHOD'] == 'POST') { - //return immediately - $handle = popen('sudo -b -u \#'.$UID.' /opt/brother/scanner/brscan-skey/script/scanto'.$target.'.sh', 'r'); - } else if ($_SERVER['REQUEST_METHOD'] == 'GET') { - //wait for completion - $output=shell_exec('sudo -u \#'.$UID.' /opt/brother/scanner/brscan-skey/script/scanto'.$target.'.sh'); - } -} -else -{ - header($_SERVER["SERVER_PROTOCOL"] . " 400 OK"); - die("Error: Thou shalt not inject unknown script names!"); -} - -//TODO: Fix serving of file on get -//if ($_SERVER['REQUEST_METHOD'] == 'GET') { -// $files = scandir('/scans', SCANDIR_SORT_DESCENDING); -// $newest_file = $files[0]; -// header($_SERVER["SERVER_PROTOCOL"] . " 200 OK"); -// header("Cache-Control: public"); // needed for internet explorer -// header("Content-Type: application/pdf"); -// header("Content-Transfer-Encoding: Binary"); -// header("Content-Length:".filesize($newest_file)); -// readfile($newest_file); -// die(); -//} - -?> \ No newline at end of file diff --git a/html/timezone.php b/html/timezone.php deleted file mode 100755 index 7d5a4e9..0000000 --- a/html/timezone.php +++ /dev/null @@ -1,12 +0,0 @@ -"; -echo "Time: " . date("Y-m-d H:i:s"); -?> \ No newline at end of file diff --git a/update-container.sh b/update-container.sh index 11eece0..901f0e2 100755 --- a/update-container.sh +++ b/update-container.sh @@ -1,2 +1,4 @@ -docker cp ./html brotherscannerdocker-brother-scanner-1:/var/www/ -docker cp ./script brotherscannerdocker-brother-scanner-1:/opt/brother/scanner/brscan-skey/ \ No newline at end of file +docker cp ./www brotherscannerdocker-brother-scanner-1:/var/ +docker exec brotherscannerdocker-brother-scanner-1 chown -R www-data:root /var/www/ +docker cp ./script brotherscannerdocker-brother-scanner-1:/opt/brother/scanner/brscan-skey/ +docker exec brotherscannerdocker-brother-scanner-1 chown -R root:root /opt/brother/scanner/brscan-skey/ \ No newline at end of file diff --git a/html/assets/bootstrap.5.1.3/bootstrap.bundle.min.js b/www/html/assets/bootstrap.5.1.3/bootstrap.bundle.min.js similarity index 100% rename from html/assets/bootstrap.5.1.3/bootstrap.bundle.min.js rename to www/html/assets/bootstrap.5.1.3/bootstrap.bundle.min.js diff --git a/html/assets/bootstrap.5.1.3/bootstrap.min.css b/www/html/assets/bootstrap.5.1.3/bootstrap.min.css similarity index 100% rename from html/assets/bootstrap.5.1.3/bootstrap.min.css rename to www/html/assets/bootstrap.5.1.3/bootstrap.min.css diff --git a/html/assets/fontawesome.5.15.4/LICENSE.txt b/www/html/assets/fontawesome.5.15.4/LICENSE.txt similarity index 100% rename from html/assets/fontawesome.5.15.4/LICENSE.txt rename to www/html/assets/fontawesome.5.15.4/LICENSE.txt diff --git a/html/assets/fontawesome.5.15.4/css/all.min.css b/www/html/assets/fontawesome.5.15.4/css/all.min.css similarity index 100% rename from html/assets/fontawesome.5.15.4/css/all.min.css rename to www/html/assets/fontawesome.5.15.4/css/all.min.css diff --git a/html/assets/fontawesome.5.15.4/webfonts/fa-brands-400.eot b/www/html/assets/fontawesome.5.15.4/webfonts/fa-brands-400.eot similarity index 100% rename from html/assets/fontawesome.5.15.4/webfonts/fa-brands-400.eot rename to www/html/assets/fontawesome.5.15.4/webfonts/fa-brands-400.eot diff --git a/html/assets/fontawesome.5.15.4/webfonts/fa-brands-400.svg b/www/html/assets/fontawesome.5.15.4/webfonts/fa-brands-400.svg similarity index 100% rename from html/assets/fontawesome.5.15.4/webfonts/fa-brands-400.svg rename to www/html/assets/fontawesome.5.15.4/webfonts/fa-brands-400.svg diff --git a/html/assets/fontawesome.5.15.4/webfonts/fa-brands-400.ttf b/www/html/assets/fontawesome.5.15.4/webfonts/fa-brands-400.ttf similarity index 100% rename from html/assets/fontawesome.5.15.4/webfonts/fa-brands-400.ttf rename to www/html/assets/fontawesome.5.15.4/webfonts/fa-brands-400.ttf diff --git a/html/assets/fontawesome.5.15.4/webfonts/fa-brands-400.woff b/www/html/assets/fontawesome.5.15.4/webfonts/fa-brands-400.woff similarity index 100% rename from html/assets/fontawesome.5.15.4/webfonts/fa-brands-400.woff rename to www/html/assets/fontawesome.5.15.4/webfonts/fa-brands-400.woff diff --git a/html/assets/fontawesome.5.15.4/webfonts/fa-brands-400.woff2 b/www/html/assets/fontawesome.5.15.4/webfonts/fa-brands-400.woff2 similarity index 100% rename from html/assets/fontawesome.5.15.4/webfonts/fa-brands-400.woff2 rename to www/html/assets/fontawesome.5.15.4/webfonts/fa-brands-400.woff2 diff --git a/html/assets/fontawesome.5.15.4/webfonts/fa-regular-400.eot b/www/html/assets/fontawesome.5.15.4/webfonts/fa-regular-400.eot similarity index 100% rename from html/assets/fontawesome.5.15.4/webfonts/fa-regular-400.eot rename to www/html/assets/fontawesome.5.15.4/webfonts/fa-regular-400.eot diff --git a/html/assets/fontawesome.5.15.4/webfonts/fa-regular-400.svg b/www/html/assets/fontawesome.5.15.4/webfonts/fa-regular-400.svg similarity index 100% rename from html/assets/fontawesome.5.15.4/webfonts/fa-regular-400.svg rename to www/html/assets/fontawesome.5.15.4/webfonts/fa-regular-400.svg diff --git a/html/assets/fontawesome.5.15.4/webfonts/fa-regular-400.ttf b/www/html/assets/fontawesome.5.15.4/webfonts/fa-regular-400.ttf similarity index 100% rename from html/assets/fontawesome.5.15.4/webfonts/fa-regular-400.ttf rename to www/html/assets/fontawesome.5.15.4/webfonts/fa-regular-400.ttf diff --git a/html/assets/fontawesome.5.15.4/webfonts/fa-regular-400.woff b/www/html/assets/fontawesome.5.15.4/webfonts/fa-regular-400.woff similarity index 100% rename from html/assets/fontawesome.5.15.4/webfonts/fa-regular-400.woff rename to www/html/assets/fontawesome.5.15.4/webfonts/fa-regular-400.woff diff --git a/html/assets/fontawesome.5.15.4/webfonts/fa-regular-400.woff2 b/www/html/assets/fontawesome.5.15.4/webfonts/fa-regular-400.woff2 similarity index 100% rename from html/assets/fontawesome.5.15.4/webfonts/fa-regular-400.woff2 rename to www/html/assets/fontawesome.5.15.4/webfonts/fa-regular-400.woff2 diff --git a/html/assets/fontawesome.5.15.4/webfonts/fa-solid-900.eot b/www/html/assets/fontawesome.5.15.4/webfonts/fa-solid-900.eot similarity index 100% rename from html/assets/fontawesome.5.15.4/webfonts/fa-solid-900.eot rename to www/html/assets/fontawesome.5.15.4/webfonts/fa-solid-900.eot diff --git a/html/assets/fontawesome.5.15.4/webfonts/fa-solid-900.svg b/www/html/assets/fontawesome.5.15.4/webfonts/fa-solid-900.svg similarity index 100% rename from html/assets/fontawesome.5.15.4/webfonts/fa-solid-900.svg rename to www/html/assets/fontawesome.5.15.4/webfonts/fa-solid-900.svg diff --git a/html/assets/fontawesome.5.15.4/webfonts/fa-solid-900.ttf b/www/html/assets/fontawesome.5.15.4/webfonts/fa-solid-900.ttf similarity index 100% rename from html/assets/fontawesome.5.15.4/webfonts/fa-solid-900.ttf rename to www/html/assets/fontawesome.5.15.4/webfonts/fa-solid-900.ttf diff --git a/html/assets/fontawesome.5.15.4/webfonts/fa-solid-900.woff b/www/html/assets/fontawesome.5.15.4/webfonts/fa-solid-900.woff similarity index 100% rename from html/assets/fontawesome.5.15.4/webfonts/fa-solid-900.woff rename to www/html/assets/fontawesome.5.15.4/webfonts/fa-solid-900.woff diff --git a/html/assets/fontawesome.5.15.4/webfonts/fa-solid-900.woff2 b/www/html/assets/fontawesome.5.15.4/webfonts/fa-solid-900.woff2 similarity index 100% rename from html/assets/fontawesome.5.15.4/webfonts/fa-solid-900.woff2 rename to www/html/assets/fontawesome.5.15.4/webfonts/fa-solid-900.woff2 diff --git a/html/assets/jquery.3.7.1/jquery.min.js b/www/html/assets/jquery.3.7.1/jquery.min.js similarity index 100% rename from html/assets/jquery.3.7.1/jquery.min.js rename to www/html/assets/jquery.3.7.1/jquery.min.js diff --git a/www/html/index.php b/www/html/index.php new file mode 100644 index 0000000..a5ac4a0 --- /dev/null +++ b/www/html/index.php @@ -0,0 +1,72 @@ +addMatchTypes(array('char' => '(?:[^\/]*)')); + + +$router->map( 'GET', '/', function() { + require 'views/frontend/home.php'; +}); + + +$router->map( 'GET', '/list-files', function() { + require 'views/frontend/list.php'; +}); + + +$router->map( 'GET', '/api/scanner/status', function() { + require 'views/api/scanner-status.php'; +}); + + +$router->map( 'POST', '/api/scanner/scanto', function() { + $scanto = $_POST["target"]; + $method = 'return'; + require 'views/api/scanner-scanto.php'; +}); + + +$router->map( 'GET', '/api/scanner/scanto/[char:parameter]', function( $parameter) { + $scanto = $parameter; + $method = 'wait'; + require 'views/api/scanner-scanto.php'; +}); + + +$router->map( 'GET', '/api/file/[char:file]/download', function( $file) { + require 'views/api/file-download.php'; +}); + + +$router->map( 'GET', '/api/dev/timezone', function() { + require 'views/api/dev-timezone.php'; +}); + + +$match = $router->match(); + + +if( is_array($match) && is_callable( $match['target'] ) ) { + call_user_func_array( $match['target'], $match['params'] ); +} else { + send_error_page(404, 'Not Found', 'The requested resource could not be found.'); + +} + +?> \ No newline at end of file diff --git a/www/private/classes/AltoRouter.php b/www/private/classes/AltoRouter.php new file mode 100755 index 0000000..daeab62 --- /dev/null +++ b/www/private/classes/AltoRouter.php @@ -0,0 +1,300 @@ + + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +class AltoRouter +{ + + /** + * @var array Array of all routes (incl. named routes). + */ + protected $routes = []; + + /** + * @var array Array of all named routes. + */ + protected $namedRoutes = []; + + /** + * @var string Can be used to ignore leading part of the Request URL (if main file lives in subdirectory of host) + */ + protected $basePath = ''; + + /** + * @var array Array of default match types (regex helpers) + */ + protected $matchTypes = [ + 'i' => '[0-9]++', + 'a' => '[0-9A-Za-z]++', + 'h' => '[0-9A-Fa-f]++', + '*' => '.+?', + '**' => '.++', + '' => '[^/\.]++' + ]; + + /** + * Create router in one call from config. + * + * @param array $routes + * @param string $basePath + * @param array $matchTypes + * @throws Exception + */ + public function __construct(array $routes = [], string $basePath = '', array $matchTypes = []) + { + $this->addRoutes($routes); + $this->setBasePath($basePath); + $this->addMatchTypes($matchTypes); + } + + /** + * Retrieves all routes. + * Useful if you want to process or display routes. + * @return array All routes. + */ + public function getRoutes(): array + { + return $this->routes; + } + + /** + * Add multiple routes at once from array in the following format: + * + * $routes = [ + * [$method, $route, $target, $name] + * ]; + * + * @param array $routes + * @return void + * @author Koen Punt + * @throws Exception + */ + public function addRoutes($routes) + { + if (!is_array($routes) && !$routes instanceof Traversable) { + throw new RuntimeException('Routes should be an array or an instance of Traversable'); + } + foreach ($routes as $route) { + call_user_func_array([$this, 'map'], $route); + } + } + + /** + * Set the base path. + * Useful if you are running your application from a subdirectory. + * @param string $basePath + */ + public function setBasePath(string $basePath) + { + $this->basePath = $basePath; + } + + /** + * Add named match types. It uses array_merge so keys can be overwritten. + * + * @param array $matchTypes The key is the name and the value is the regex. + */ + public function addMatchTypes(array $matchTypes) + { + $this->matchTypes = array_merge($this->matchTypes, $matchTypes); + } + + /** + * Map a route to a target + * + * @param string $method One of 5 HTTP Methods, or a pipe-separated list of multiple HTTP Methods (GET|POST|PATCH|PUT|DELETE) + * @param string $route The route regex, custom regex must start with an @. You can use multiple pre-set regex filters, like [i:id] + * @param mixed $target The target where this route should point to. Can be anything. + * @param string $name Optional name of this route. Supply if you want to reverse route this url in your application. + * @throws Exception + */ + public function map(string $method, string $route, $target, string $name = null) + { + + $this->routes[] = [$method, $route, $target, $name]; + + if ($name) { + if (isset($this->namedRoutes[$name])) { + throw new RuntimeException("Can not redeclare route '{$name}'"); + } + $this->namedRoutes[$name] = $route; + } + } + + /** + * Reversed routing + * + * Generate the URL for a named route. Replace regexes with supplied parameters + * + * @param string $routeName The name of the route. + * @param array $params Associative array of parameters to replace placeholders with. + * @return string The URL of the route with named parameters in place. + * @throws Exception + */ + public function generate(string $routeName, array $params = []): string + { + + // Check if named route exists + if (!isset($this->namedRoutes[$routeName])) { + throw new RuntimeException("Route '{$routeName}' does not exist."); + } + + // Replace named parameters + $route = $this->namedRoutes[$routeName]; + + // prepend base path to route url again + $url = $this->basePath . $route; + + if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) { + foreach ($matches as $index => $match) { + list($block, $pre, $type, $param, $optional) = $match; + + if ($pre) { + $block = substr($block, 1); + } + + if (isset($params[$param])) { + // Part is found, replace for param value + $url = str_replace($block, $params[$param], $url); + } elseif ($optional && $index !== 0) { + // Only strip preceding slash if it's not at the base + $url = str_replace($pre . $block, '', $url); + } else { + // Strip match block + $url = str_replace($block, '', $url); + } + } + } + + return $url; + } + + /** + * Match a given Request Url against stored routes + * @param string $requestUrl + * @param string $requestMethod + * @return array|boolean Array with route information on success, false on failure (no match). + */ + public function match(string $requestUrl = null, string $requestMethod = null) + { + + $params = []; + + // set Request Url if it isn't passed as parameter + if ($requestUrl === null) { + $requestUrl = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/'; + } + + // strip base path from request url + $requestUrl = substr($requestUrl, strlen($this->basePath)); + + // Strip query string (?a=b) from Request Url + if (($strpos = strpos($requestUrl, '?')) !== false) { + $requestUrl = substr($requestUrl, 0, $strpos); + } + + $lastRequestUrlChar = $requestUrl ? $requestUrl[strlen($requestUrl)-1] : ''; + + // set Request Method if it isn't passed as a parameter + if ($requestMethod === null) { + $requestMethod = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET'; + } + + foreach ($this->routes as $handler) { + list($methods, $route, $target, $name) = $handler; + + $method_match = (stripos($methods, $requestMethod) !== false); + + // Method did not match, continue to next route. + if (!$method_match) { + continue; + } + + if ($route === '*') { + // * wildcard (matches all) + $match = true; + } elseif (isset($route[0]) && $route[0] === '@') { + // @ regex delimiter + $pattern = '`' . substr($route, 1) . '`u'; + $match = preg_match($pattern, $requestUrl, $params) === 1; + } elseif (($position = strpos($route, '[')) === false) { + // No params in url, do string comparison + $match = strcmp($requestUrl, $route) === 0; + } else { + // Compare longest non-param string with url before moving on to regex + // Check if last character before param is a slash, because it could be optional if param is optional too (see https://github.com/dannyvankooten/AltoRouter/issues/241) + if (strncmp($requestUrl, $route, $position) !== 0 && ($lastRequestUrlChar === '/' || $route[$position-1] !== '/')) { + continue; + } + + $regex = $this->compileRoute($route); + $match = preg_match($regex, $requestUrl, $params) === 1; + } + + if ($match) { + if ($params) { + foreach ($params as $key => $value) { + if (is_numeric($key)) { + unset($params[$key]); + } + } + } + + return [ + 'target' => $target, + 'params' => $params, + 'name' => $name + ]; + } + } + + return false; + } + + /** + * Compile the regex for a given route (EXPENSIVE) + * @param string $route + * @return string + */ + protected function compileRoute(string $route): string + { + if (preg_match_all('`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER)) { + $matchTypes = $this->matchTypes; + foreach ($matches as $match) { + list($block, $pre, $type, $param, $optional) = $match; + + if (isset($matchTypes[$type])) { + $type = $matchTypes[$type]; + } + if ($pre === '.') { + $pre = '\.'; + } + + $optional = $optional !== '' ? '?' : null; + + //Older versions of PCRE require the 'P' in (?P) + $pattern = '(?:' + . ($pre !== '' ? $pre : null) + . '(' + . ($param !== '' ? "?P<$param>" : null) + . $type + . ')' + . $optional + . ')' + . $optional; + + $route = str_replace($block, $pattern, $route); + } + } + return "`^$route$`u"; + } +} \ No newline at end of file diff --git a/www/private/helper.php b/www/private/helper.php new file mode 100755 index 0000000..7ec24fb --- /dev/null +++ b/www/private/helper.php @@ -0,0 +1,25 @@ + $http_code, 'message' => $message)); + die(); +} + +?> \ No newline at end of file diff --git a/www/private/views/api/dev-timezone.php b/www/private/views/api/dev-timezone.php new file mode 100755 index 0000000..4c91887 --- /dev/null +++ b/www/private/views/api/dev-timezone.php @@ -0,0 +1,12 @@ + date_default_timezone_get(), + 'time' => date("Y-m-d H:i:s") + ) +); + +?> \ No newline at end of file diff --git a/www/private/views/api/file-delete.php b/www/private/views/api/file-delete.php new file mode 100755 index 0000000..e69de29 diff --git a/www/private/views/api/file-download.php b/www/private/views/api/file-download.php new file mode 100644 index 0000000..ff85dfd --- /dev/null +++ b/www/private/views/api/file-download.php @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/www/private/views/api/file-rename.php b/www/private/views/api/file-rename.php new file mode 100755 index 0000000..e69de29 diff --git a/www/private/views/api/scanner-scanto.php b/www/private/views/api/scanner-scanto.php new file mode 100644 index 0000000..aaa96c9 --- /dev/null +++ b/www/private/views/api/scanner-scanto.php @@ -0,0 +1,54 @@ + \ No newline at end of file diff --git a/html/active.php b/www/private/views/api/scanner-status.php similarity index 89% rename from html/active.php rename to www/private/views/api/scanner-status.php index 2b3e4d5..53db9d4 100644 --- a/html/active.php +++ b/www/private/views/api/scanner-status.php @@ -1,4 +1,6 @@ \ No newline at end of file diff --git a/www/private/views/frontend/error.php b/www/private/views/frontend/error.php new file mode 100755 index 0000000..b71ef5a --- /dev/null +++ b/www/private/views/frontend/error.php @@ -0,0 +1 @@ +ups \ No newline at end of file diff --git a/html/index.php b/www/private/views/frontend/home.php old mode 100644 new mode 100755 similarity index 94% rename from html/index.php rename to www/private/views/frontend/home.php index ee283af..cea0764 --- a/html/index.php +++ b/www/private/views/frontend/home.php @@ -1,231 +1,233 @@ - - - - - - - Brother <?php echo($MODEL); ?> - - - - - - - - - - - - - - - -
-
-
-
- -

-

Ready to scan

- - '.$button_file.'

'); - } - if (!isset($DISABLE_GUI_SCANTOEMAIL) || $DISABLE_GUI_SCANTOEMAIL != true) { - echo('

'.$button_email.'

'); - } - if (!isset($DISABLE_GUI_SCANTOIMAGE) || $DISABLE_GUI_SCANTOIMAGE != true) { - echo('

'.$button_image.'

'); - } - if (!isset($DISABLE_GUI_SCANTOOCR) || $DISABLE_GUI_SCANTOOCR != true) { - echo('

'.$button_ocr.'

'); - } - ?> -
-
-
-
- - -
- -
-
Last scanned
- -
-
- - - - - -
-
- - - - - - - - - - + + + + + + + Brother <?php echo($MODEL); ?> + + + + + + + + + + + + + + + +
+
+
+
+ +

+

Ready to scan

+ + '.$button_file.'

'); + } + if (!isset($DISABLE_GUI_SCANTOEMAIL) || $DISABLE_GUI_SCANTOEMAIL != true) { + echo('

'.$button_email.'

'); + } + if (!isset($DISABLE_GUI_SCANTOIMAGE) || $DISABLE_GUI_SCANTOIMAGE != true) { + echo('

'.$button_image.'

'); + } + if (!isset($DISABLE_GUI_SCANTOOCR) || $DISABLE_GUI_SCANTOOCR != true) { + echo('

'.$button_ocr.'

'); + } + ?> +
+
+
+
+ + +
+ +
+
Last scanned
+ +
+
+ + + + + +
+
+ + + + + + + + + + \ No newline at end of file diff --git a/www/private/views/frontend/list.php b/www/private/views/frontend/list.php new file mode 100644 index 0000000..72b1c3e --- /dev/null +++ b/www/private/views/frontend/list.php @@ -0,0 +1,79 @@ + filemtime($filePath), + 'size' => filesize($filePath), + 'permissions' => substr(sprintf('%o', fileperms($filePath)), -4), + 'owner' => posix_getpwuid(fileowner($filePath))['name'], + 'group' => posix_getgrgid(filegroup($filePath))['name'], + ); + } + } + + // Sort files by modification time (newest first) + uasort($filesWithMtime, function($a, $b) { + return $b['mtime'] <=> $a['mtime']; + }); + + return $filesWithMtime; + +} + +?> +
+ +
+ + $attributes) { + + $file_op_bottons= '
+ +
'; + if ($file_op) { + $file_op_bottons='
+ + + +
'; + } + ?> + +
+
+ + +
+
+
KB
+ +
+ +
+ + +
+
+ + From 87ba7aa44819cae1a27632bd6b52fb20fa5d1352 Mon Sep 17 00:00:00 2001 From: Philipp Date: Fri, 20 Sep 2024 20:14:02 +0200 Subject: [PATCH 02/17] separate js and css --- www/html/assets/scripts.js | 264 +++++++++++++++++++++++++++++++++ www/html/assets/scripts.min.js | 1 + www/html/assets/style.css | 16 ++ www/html/assets/style.min.css | 1 + 4 files changed, 282 insertions(+) create mode 100755 www/html/assets/scripts.js create mode 100755 www/html/assets/scripts.min.js create mode 100755 www/html/assets/style.css create mode 100755 www/html/assets/style.min.css diff --git a/www/html/assets/scripts.js b/www/html/assets/scripts.js new file mode 100755 index 0000000..88fccfe --- /dev/null +++ b/www/html/assets/scripts.js @@ -0,0 +1,264 @@ +function set_state_idle() { + $('#status-image').html(''); + $('#status-text').text('Ready to scan'); + $('.trigger-scan').removeClass('disabled'); +} + +function set_state_waiting() { + $('#status-image').html(''); + $('#status-text').text('Waiting for rear pages'); + $('.trigger-scan').removeClass('disabled'); +} + +function set_state_scan() { + let spinnerimage = ''; + if (spinnerimage != $('#status-image').html()) { + $('#status-image').html(spinnerimage); + } + $('#status-text').text('Scan in progress'); + $('.trigger-scan').addClass('disabled'); +} + +function set_state_ocr() { + $('#status-image').html(''); + $('#status-text').text('OCR in progress'); + $('.trigger-scan').removeClass('disabled'); +} + +function set_state(state) { + switch (state) { + case 'idle': + set_state_idle(); + break; + case 'waiting': + set_state_waiting(); + break; + case 'scan': + set_state_scan(); + break; + case 'ocr': + set_state_ocr(); + break; + default: + set_state_idle(); + } +} + +function show_file_offcanvas(){ + $.ajax({ + url: '/list-files', + method: 'GET', + success: function(response) { + // Populate the Offcanvas with the response content + $('#offcanvasContent').html(response); + + // Show the Offcanvas + var offcanvas = new bootstrap.Offcanvas($('#offcanvasFiles')[0]); + offcanvas.show(); + }, + error: function(xhr, status, error) { + console.error('Failed to load content:', error); + } + }); +} + +$(document).ready(function() { + + + $('.trigger-scan').click(function() { + var target = $(this).data('trigger'); + console.log('Triggered click event on element with class "trigger-scan" and data-trigger "' + target + '"'); + $.post('/api/scanner/scanto', { + target: target + }, function(data) { + console.log(data); + $(this).blur(); + + }); + }); + + + setInterval(function() { + $.get('/api/scanner/status', function(data) { + + + let state = 'idle'; + + + if (data.ocr && data.waiting && !data.scan) { + state = 'ocr'; + } else if (data.scan && data.waiting) { + state = 'scan'; + } else if (data.scan) { + state = 'scan'; + } else if (data.ocr && !data.scan) { + state = 'ocr'; + } else if (!data.ocr && !data.scan && data.waiting) { + state = 'waiting'; + } else if (!data.ocr && !data.scan && !data.waiting) { + state = 'idle'; + } + set_state(state); + }); + }, 1000); + + + + $('#triggerFiles').on('click', function(e) { + e.preventDefault(); + show_file_offcanvas(); + + }); + + + + + + +}); + +function toggle_file_rename(source_element){ + var parentDiv = source_element.closest('.list-group-item'); + var parentId = parentDiv.attr('id'); + + + $("#"+parentId+" .file-info-label-default").toggleClass('d-none'); + $("#"+parentId+" .file-info-label-rename").toggleClass('d-none'); + + $("#"+parentId+" .file-name").toggleClass('d-none'); + $("#"+parentId+" .file-name-new").toggleClass('d-none'); + + $("#"+parentId+" .file-buttons-default").toggleClass('d-none'); + $("#"+parentId+" .file-buttons-rename").toggleClass('d-none'); + + $("#"+parentId+" .file-rename-prefix-date").checked = true; +} + +function toggle_file_delete(source_element){ + var parentDiv = source_element.closest('.list-group-item'); + var parentId = parentDiv.attr('id'); + + + $("#"+parentId+" .file-info-label-default").toggleClass('d-none'); + $("#"+parentId+" .file-info-label-delete").toggleClass('d-none'); + + $("#"+parentId+" .file-buttons-default").toggleClass('d-none'); + $("#"+parentId+" .file-buttons-delete").toggleClass('d-none'); +} + +$(document).on("click", ".file-rename", function (e) { + e.preventDefault(); + toggle_file_rename($(this)); + +}); + +$(document).on("click", ".file-rename-confirm", function (e) { + e.preventDefault(); + url = $(this).attr('href'); + var parentDiv = $(this).closest('.list-group-item'); + var parentId = parentDiv.attr('id'); + + var filename = $("#"+parentId+" .file-name-original").val(); + var new_filename = $("#"+parentId+" .file-name-new").val(); + + + var new_filename_prefix = 'none'; + + if($("#"+parentId+" .file-rename-prefix-none").is(':checked')) { + new_filename_prefix = 'none'; + } + if($("#"+parentId+" .file-rename-prefix-date").is(':checked')) { + new_filename_prefix = 'date'; + } + if($("#"+parentId+" .file-rename-prefix-datetime").is(':checked')) { + new_filename_prefix = 'datetime'; + } + + + console.log('url: '+url); + console.log('filename: '+filename); + console.log('new_filename: '+new_filename); + console.log('new_filename_prefix: '+new_filename_prefix); + + + if (new_filename == '' && new_filename_prefix == 'none') { + alert('Please enter a new filename or select a prefix'); + }else{ + + + payload = { + 'new_filename': new_filename, + 'new_filename_prefix': new_filename_prefix + }; + console.log(payload); + $.ajax({ + url: url, + type: 'PUT', + contentType: 'application/json', + data: JSON.stringify(payload), + success: function(response) { + + console.log(response); + console.log('File renamed'); + toggle_file_rename($(this)); + show_file_offcanvas(); + }, + error: function(xhr, status, error) { + + console.log('File NOT renamed'); + } + }); + + } + + + + + +}); + +$(document).on("click", ".file-rename-cancel", function (e) { + e.preventDefault(); + toggle_file_rename($(this)); +}); + + + +$(document).on("click", ".file-delete", function (e) { + e.preventDefault(); + toggle_file_delete($(this)); +}); + +$(document).on("click", ".file-delete-confirm", function (e) { + e.preventDefault(); + url = $(this).attr('href'); + $.ajax({ + url: url, + type: 'DELETE', + success: function(response) { + + toggle_file_delete($(this)); + show_file_offcanvas(); + }, + error: function(response, xhr, status, error) { + console.error('Error:', response); + } + }); + +}); + +$(document).on("click", ".file-delete-cancel", function (e) { + e.preventDefault(); + toggle_file_delete($(this)); +}); + + + +//document.addEventListener('DOMContentLoaded', function () { +// var offcanvasElement = document.getElementById('offcanvasExample'); +// offcanvasElement.addEventListener('shown.bs.offcanvas', function () { +// // Trigger any necessary initialization here +// // For example, you can reinitialize the radio buttons or any other components +// console.log('Offcanvas is shown'); +// }); +//}); diff --git a/www/html/assets/scripts.min.js b/www/html/assets/scripts.min.js new file mode 100755 index 0000000..0b2fe32 --- /dev/null +++ b/www/html/assets/scripts.min.js @@ -0,0 +1 @@ +function set_state_idle(){$("#status-image").html(''),$("#status-text").text("Ready to scan"),$(".trigger-scan").removeClass("disabled")}function set_state_waiting(){$("#status-image").html(''),$("#status-text").text("Waiting for rear pages"),$(".trigger-scan").removeClass("disabled")}function set_state_scan(){let spinnerimage='';spinnerimage!=$("#status-image").html()&&$("#status-image").html(spinnerimage),$("#status-text").text("Scan in progress"),$(".trigger-scan").addClass("disabled")}function set_state_ocr(){$("#status-image").html(''),$("#status-text").text("OCR in progress"),$(".trigger-scan").removeClass("disabled")}function set_state(state){switch(state){case"idle":set_state_idle();break;case"waiting":set_state_waiting();break;case"scan":set_state_scan();break;case"ocr":set_state_ocr();break;default:set_state_idle()}}function show_file_offcanvas(){$.ajax({url:"/list-files",method:"GET",success:function(response){var offcanvas;$("#offcanvasContent").html(response),new bootstrap.Offcanvas($("#offcanvasFiles")[0]).show()},error:function(xhr,status,error){console.error("Failed to load content:",error)}})}function toggle_file_rename(source_element){var parentDiv,parentId=source_element.closest(".list-group-item").attr("id");$("#"+parentId+" .file-info-label-default").toggleClass("d-none"),$("#"+parentId+" .file-info-label-rename").toggleClass("d-none"),$("#"+parentId+" .file-name").toggleClass("d-none"),$("#"+parentId+" .file-name-new").toggleClass("d-none"),$("#"+parentId+" .file-buttons-default").toggleClass("d-none"),$("#"+parentId+" .file-buttons-rename").toggleClass("d-none"),$("#"+parentId+" .file-rename-prefix-date").checked=!0}function toggle_file_delete(source_element){var parentDiv,parentId=source_element.closest(".list-group-item").attr("id");$("#"+parentId+" .file-info-label-default").toggleClass("d-none"),$("#"+parentId+" .file-info-label-delete").toggleClass("d-none"),$("#"+parentId+" .file-buttons-default").toggleClass("d-none"),$("#"+parentId+" .file-buttons-delete").toggleClass("d-none")}$(document).ready((function(){$(".trigger-scan").click((function(){var target=$(this).data("trigger");console.log('Triggered click event on element with class "trigger-scan" and data-trigger "'+target+'"'),$.post("/api/scanner/scanto",{target:target},(function(data){console.log(data),$(this).blur()}))})),setInterval((function(){$.get("/api/scanner/status",(function(data){let state="idle";data.ocr&&data.waiting&&!data.scan?state="ocr":data.scan&&data.waiting?state="scan":data.scan?state="scan":data.ocr&&!data.scan?state="ocr":data.ocr||data.scan||!data.waiting?data.ocr||data.scan||data.waiting||(state="idle"):state="waiting",set_state(state)}))}),1e3),$("#triggerFiles").on("click",(function(e){e.preventDefault(),show_file_offcanvas()}))})),$(document).on("click",".file-rename",(function(e){e.preventDefault(),toggle_file_rename($(this))})),$(document).on("click",".file-rename-confirm",(function(e){e.preventDefault(),url=$(this).attr("href");var parentDiv,parentId=$(this).closest(".list-group-item").attr("id"),filename=$("#"+parentId+" .file-name-original").val(),new_filename=$("#"+parentId+" .file-name-new").val(),new_filename_prefix="none";$("#"+parentId+" .file-rename-prefix-none").is(":checked")&&(new_filename_prefix="none"),$("#"+parentId+" .file-rename-prefix-date").is(":checked")&&(new_filename_prefix="date"),$("#"+parentId+" .file-rename-prefix-datetime").is(":checked")&&(new_filename_prefix="datetime"),console.log("url: "+url),console.log("filename: "+filename),console.log("new_filename: "+new_filename),console.log("new_filename_prefix: "+new_filename_prefix),""==new_filename&&"none"==new_filename_prefix?alert("Please enter a new filename or select a prefix"):(payload={new_filename:new_filename,new_filename_prefix:new_filename_prefix},console.log(payload),$.ajax({url:url,type:"PUT",contentType:"application/json",data:JSON.stringify(payload),success:function(response){console.log(response),console.log("File renamed"),toggle_file_rename($(this)),show_file_offcanvas()},error:function(xhr,status,error){console.log("File NOT renamed")}}))})),$(document).on("click",".file-rename-cancel",(function(e){e.preventDefault(),toggle_file_rename($(this))})),$(document).on("click",".file-delete",(function(e){e.preventDefault(),toggle_file_delete($(this))})),$(document).on("click",".file-delete-confirm",(function(e){e.preventDefault(),url=$(this).attr("href"),$.ajax({url:url,type:"DELETE",success:function(response){toggle_file_delete($(this)),show_file_offcanvas()},error:function(response,xhr,status,error){console.error("Error:",response)}})})),$(document).on("click",".file-delete-cancel",(function(e){e.preventDefault(),toggle_file_delete($(this))})); \ No newline at end of file diff --git a/www/html/assets/style.css b/www/html/assets/style.css new file mode 100755 index 0000000..4bb2206 --- /dev/null +++ b/www/html/assets/style.css @@ -0,0 +1,16 @@ +.list-group-hover, +.list-group-item:hover { + background-color: #f5f5f5; +} + +.file-name-new{ + font-weight:bolder; + font-size: 1rem; +} + +#fileslist :focus, +#fileslist :active { + box-shadow: none; + outline: none; +} + diff --git a/www/html/assets/style.min.css b/www/html/assets/style.min.css new file mode 100755 index 0000000..f494bd9 --- /dev/null +++ b/www/html/assets/style.min.css @@ -0,0 +1 @@ +.list-group-hover,.list-group-item:hover{background-color:#f5f5f5}.file-name-new{font-weight:bolder;font-size:1rem}#fileslist :active,#fileslist :focus{box-shadow:none;outline:0} \ No newline at end of file From 75383e331e10b4982a279e6c7e2a988204e82978 Mon Sep 17 00:00:00 2001 From: Philipp Date: Fri, 20 Sep 2024 20:14:32 +0200 Subject: [PATCH 03/17] added routes --- www/html/index.php | 62 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/www/html/index.php b/www/html/index.php index a5ac4a0..e6ab587 100644 --- a/www/html/index.php +++ b/www/html/index.php @@ -11,62 +11,98 @@ if (!isset($TZ)) { $TZ = 'Europe/Berlin'; } - date_default_timezone_set($TZ); -session_start(); +#session_start(); $router = new AltoRouter(); $router->addMatchTypes(array('char' => '(?:[^\/]*)')); +// Frontend routes + $router->map( 'GET', '/', function() { - require 'views/frontend/home.php'; + require_once 'views/frontend/home.php'; }); $router->map( 'GET', '/list-files', function() { - require 'views/frontend/list.php'; + require_once 'views/frontend/file-list.php'; +}); + + +$router->map( 'GET', '/file/[char:file]/rename', function( $file) { + require_once 'views/frontend/file-rename.php'; }); +$router->map( 'GET', '/file/[char:file]/delete', function( $file) { + require_once 'views/frontend/file-delete.php'; +}); + + +// API routes + $router->map( 'GET', '/api/scanner/status', function() { - require 'views/api/scanner-status.php'; + require_once 'views/api/scanner-status.php'; }); $router->map( 'POST', '/api/scanner/scanto', function() { $scanto = $_POST["target"]; $method = 'return'; - require 'views/api/scanner-scanto.php'; + require_once 'views/api/scanner-scanto.php'; }); $router->map( 'GET', '/api/scanner/scanto/[char:parameter]', function( $parameter) { $scanto = $parameter; $method = 'wait'; - require 'views/api/scanner-scanto.php'; + require_once 'views/api/scanner-scanto.php'; +}); + + +$router->map( 'GET', '/api/file-list', function() { + require_once 'views/api/file-list.php'; +}); + +$router->map( 'GET', '/api/file/[char:file]/info', function( $file) { + require_once 'views/api/file-info.php'; }); $router->map( 'GET', '/api/file/[char:file]/download', function( $file) { - require 'views/api/file-download.php'; + require_once 'views/api/file-download.php'; +}); + +$router->map( 'DELETE', '/api/file/[char:file]/delete', function( $file) { + require_once 'views/api/file-delete.php'; +}); + + +$router->map( 'PUT', '/api/file/[char:file]/rename', function( $file) { + require_once 'views/api/file-rename.php'; }); $router->map( 'GET', '/api/dev/timezone', function() { - require 'views/api/dev-timezone.php'; + require_once 'views/api/dev-timezone.php'; }); $match = $router->match(); +// Call closure or throw 404 status if route not found + if( is_array($match) && is_callable( $match['target'] ) ) { - call_user_func_array( $match['target'], $match['params'] ); + call_user_func_array( $match['target'], $match['params'] ); } else { - send_error_page(404, 'Not Found', 'The requested resource could not be found.'); - + if (str_contains($_SERVER['REQUEST_URI'], '/api')) { + send_json_error(404, 'Not Found'); + } else { + send_error_page(404, $page_title='404', $page_message='Sorry, the page you are looking for could not be found.'); + } + exit(); } - ?> \ No newline at end of file From 5d633aee0f2703c9bfbfbd29505688dcd691960c Mon Sep 17 00:00:00 2001 From: Philipp Date: Fri, 20 Sep 2024 20:15:10 +0200 Subject: [PATCH 04/17] common functions --- www/private/helper.php | 142 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 138 insertions(+), 4 deletions(-) diff --git a/www/private/helper.php b/www/private/helper.php index 7ec24fb..8f1a23a 100755 --- a/www/private/helper.php +++ b/www/private/helper.php @@ -2,12 +2,23 @@ function json($data){ + header('Content-Type: application/json'); print_r(json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); die(); } + +function send_json_error($http_code, $message){ + + header('Content-Type: application/json'); + http_response_code($http_code); + json(array('code' => $http_code, 'message' => $message)); + die(); +} + + function send_error_page($http_code, $page_title='', $page_message=''){ http_response_code($http_code); if ($page_title != '' && $page_message != ''){ @@ -16,10 +27,133 @@ function send_error_page($http_code, $page_title='', $page_message=''){ die(); } -function send_json_error($http_code, $message){ - http_response_code($http_code); - json(array('code' => $http_code, 'message' => $message)); - die(); +/** + * Constructs a safe file path within a specified directory. + * + * This function takes a directory and a filename, constructs the full file path, + * and ensures that the file path is within the specified directory. It prevents + * directory traversal attacks by validating the real path of the constructed file path. + * + * @param string $directory The directory in which the file should be located. + * @param string $filename The name of the file. + * @return string|false The real path to the file if it is within the specified directory, or false if it is not. + */ +function file_get_real_filepath($directory, $filename) { + + $filename = basename($filename); + $filePath = $directory . DIRECTORY_SEPARATOR . $filename; + $realPath = realpath($filePath); + + if ($realPath === false || strpos($realPath, realpath($directory)) !== 0) { + return false; + } + + return $realPath; } + +function file_get_verified_fileinfo($dir, $file) { + + + $filename = file_get_real_filepath($dir, $file); + + if ($filename === false) { + send_json_error(400, "No valid file specified"); + } + + if(!file_exists($filename)){ + send_json_error(404, "File does not exist"); + } + + $pathInfo = pathinfo($filename); + $filenameWithoutExtension = pathinfo($filename, PATHINFO_FILENAME); + $fileCreationTime = filectime($filename); + $finfo = new finfo(FILEINFO_MIME_TYPE); + $mimetype = $finfo->file($filename); + + $file_info = array( + 'full_path' => $filename, + 'file' => $pathInfo['basename'] ?? '', + 'name' => $filenameWithoutExtension ?? '', + 'name_clean' => '', + 'dir' => $pathInfo['dirname'] ?? '', + 'date_from_name' => '', + 'time_from_name' => '', + 'fileCreationTime' => $fileCreationTime, + 'date_from_file' => date('Y-m-d', $fileCreationTime), + 'time_from_file' => date('H:i:s', $fileCreationTime), + 'extension' => $pathInfo['extension'] ?? '', + 'mimetype' => $mimetype, + 'size' => filesize($filename) + ); + + if (preg_match('/(\d{4}-\d{2}-\d{2})(?:-(\d{2})(?:-(\d{2})(?:-(\d{2}))?)?)?/', $filename, $matches)) { + + $file_info['date_from_name'] = $matches[1] ?? ''; + + if (isset($matches[2])) { + $file_info['time_from_name'] = $matches[2] . ':' . ($matches[3] ?? '00') . ':' . ($matches[4] ?? '00'); + } + } + // Combine date and time with the dash to form the full datetime string + $remove_datetime = $file_info['date_from_name'].'-'.$file_info['time_from_name']; + $clean_name = str_replace($remove_datetime, '', $filenameWithoutExtension); + + // Remove only the date and time without extra spaces around + $remove_date = $file_info['date_from_name']; + $clean_name = str_replace($remove_date, '', $clean_name); + + $remove_time = $file_info['time_from_name']; + $clean_name = str_replace($remove_time, '', $clean_name); + + // Trim any remaining leading or trailing spaces + $clean_name = trim($clean_name); + $file_info['name_clean'] = $clean_name; + + if($file_info['mimetype'] != 'application/pdf'){ + send_json_error(400, "No valid file specified"); + } + + + return $file_info; +} + + +function file_is_valid_name_string($filename) { + + $pattern = '/[<>:"\/\\|?*\x00-\x1F]/'; // Invalid characters for filenames + + if (preg_match($pattern, $filename)) { + return false; + } + + if (strlen($filename) > 255) { + return false; + } + + if (strlen($filename) < 3) { + return false; + } + + + return true; +} + +function list_files($dir){ + $files = scandir($dir); + $files = array_diff($files, array('.', '..')); + + $data = array(); + foreach ($files as $file) { + $filePath = $dir . '/' . $file; + if (is_file($filePath) && pathinfo($filePath, PATHINFO_EXTENSION) === 'pdf') { + $data[] = file_get_verified_fileinfo($dir, $file); + } + + } + uasort($data, function($a, $b) { + return $b['fileCreationTime'] <=> $a['fileCreationTime']; + }); + return array_values($data); +} ?> \ No newline at end of file From e32679c7c00f951e056ce82daa71245351849324 Mon Sep 17 00:00:00 2001 From: Philipp Date: Fri, 20 Sep 2024 20:15:50 +0200 Subject: [PATCH 05/17] routes for api and frontend --- www/private/views/api/dev-timezone.php | 10 +- www/private/views/api/file-delete.php | 25 +++ www/private/views/api/file-download.php | 22 ++- www/private/views/api/file-info.php | 14 ++ www/private/views/api/file-list.php | 9 ++ www/private/views/api/file-rename.php | 70 +++++++++ www/private/views/api/scanner-scanto.php | 2 + www/private/views/frontend/common-head.php | 6 + .../views/frontend/common-javascript.php | 2 + www/private/views/frontend/error.php | 46 +++++- www/private/views/frontend/file-list.php | 89 +++++++++++ www/private/views/frontend/home.php | 142 ++---------------- www/private/views/frontend/list.php | 79 ---------- 13 files changed, 287 insertions(+), 229 deletions(-) create mode 100755 www/private/views/api/file-info.php create mode 100755 www/private/views/api/file-list.php create mode 100755 www/private/views/frontend/common-head.php create mode 100755 www/private/views/frontend/common-javascript.php create mode 100644 www/private/views/frontend/file-list.php delete mode 100644 www/private/views/frontend/list.php diff --git a/www/private/views/api/dev-timezone.php b/www/private/views/api/dev-timezone.php index 4c91887..55ffc67 100755 --- a/www/private/views/api/dev-timezone.php +++ b/www/private/views/api/dev-timezone.php @@ -2,11 +2,11 @@ include('config.php'); require_once('helper.php'); -json( - array( - 'timezone' => date_default_timezone_get(), - 'time' => date("Y-m-d H:i:s") - ) +$timezone_data = array( + 'timezone' => date_default_timezone_get(), + 'datetime' => date("Y-m-d H:i:s") ); +json($timezone_data); + ?> \ No newline at end of file diff --git a/www/private/views/api/file-delete.php b/www/private/views/api/file-delete.php index e69de29..803848a 100755 --- a/www/private/views/api/file-delete.php +++ b/www/private/views/api/file-delete.php @@ -0,0 +1,25 @@ + 'success')); +} else { + send_json_error(500, "Could not delete file"); +} + +?> \ No newline at end of file diff --git a/www/private/views/api/file-download.php b/www/private/views/api/file-download.php index ff85dfd..f831d08 100644 --- a/www/private/views/api/file-download.php +++ b/www/private/views/api/file-download.php @@ -2,24 +2,20 @@ include('config.php'); require_once('helper.php'); +#'/scans/' + if(!isset($file)) { send_json_error(400, "No file specified"); } -if(str_contains($file, "..") || str_contains($file, "/")) { - send_json_error(400, "Invalid file specified"); -} - -$filename="/scans/".$file; - -if(!file_exists($filename)){ - send_json_error(400, "File does not exist"); -} - +$file_info = file_get_verified_fileinfo('/scans/', urldecode($file)); +$full_path = $file_info['full_path']; +$filename = $file_info['file']; +$mimetype = $file_info['mimetype']; -header("Content-type:application/pdf"); -header("Content-Disposition:attachment;filename=\"$file\""); -readfile($filename); +header("Content-type:$mimetype"); +header("Content-Disposition:attachment;filename=\"$filename\""); +readfile($full_path); die(); ?> \ No newline at end of file diff --git a/www/private/views/api/file-info.php b/www/private/views/api/file-info.php new file mode 100755 index 0000000..8b5c952 --- /dev/null +++ b/www/private/views/api/file-info.php @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/www/private/views/api/file-list.php b/www/private/views/api/file-list.php new file mode 100755 index 0000000..1a1e782 --- /dev/null +++ b/www/private/views/api/file-list.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/www/private/views/api/file-rename.php b/www/private/views/api/file-rename.php index e69de29..f279d24 100755 --- a/www/private/views/api/file-rename.php +++ b/www/private/views/api/file-rename.php @@ -0,0 +1,70 @@ + 'success')); +}else{ + send_json_error(400, "Invalid filename"); +} + + + +?> \ No newline at end of file diff --git a/www/private/views/api/scanner-scanto.php b/www/private/views/api/scanner-scanto.php index aaa96c9..7e9ba5b 100644 --- a/www/private/views/api/scanner-scanto.php +++ b/www/private/views/api/scanner-scanto.php @@ -41,8 +41,10 @@ function trigger_script($target, $UID, $method) { if ($method == 'return') { popen('sudo -b -u \#'.$UID.' /opt/brother/scanner/brscan-skey/script/scanto'.$target.'.sh', 'r'); + json(array('message' => 'Scan triggered','method' => 'post','target' => $target)); } else if ($method == 'wait') { shell_exec('sudo -u \#'.$UID.' /opt/brother/scanner/brscan-skey/script/scanto'.$target.'.sh'); + json(array('message' => 'Scan triggered','method' => 'get','target' => $target)); } } diff --git a/www/private/views/frontend/common-head.php b/www/private/views/frontend/common-head.php new file mode 100755 index 0000000..fdcb51a --- /dev/null +++ b/www/private/views/frontend/common-head.php @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/www/private/views/frontend/common-javascript.php b/www/private/views/frontend/common-javascript.php new file mode 100755 index 0000000..164d00f --- /dev/null +++ b/www/private/views/frontend/common-javascript.php @@ -0,0 +1,2 @@ + + diff --git a/www/private/views/frontend/error.php b/www/private/views/frontend/error.php index b71ef5a..c02d8b2 100755 --- a/www/private/views/frontend/error.php +++ b/www/private/views/frontend/error.php @@ -1 +1,45 @@ -ups \ No newline at end of file + + + + + + + <?php echo($page_title); ?> + + + + + +
+
+
+
+ +

+

+ + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/www/private/views/frontend/file-list.php b/www/private/views/frontend/file-list.php new file mode 100644 index 0000000..48c2dc8 --- /dev/null +++ b/www/private/views/frontend/file-list.php @@ -0,0 +1,89 @@ + +
+ +
+ + +
+
+ + + + +
+
+
KB
+
Are you sure you want to delete this file?
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+
+
+ + + + + + + +
+
+
+
+ + DELETE +
+
+
+
+ + SAVE +
+
+
+
+ + + +
+
+ + diff --git a/www/private/views/frontend/home.php b/www/private/views/frontend/home.php index cea0764..055910a 100755 --- a/www/private/views/frontend/home.php +++ b/www/private/views/frontend/home.php @@ -1,5 +1,4 @@ - + Brother <?php echo($MODEL); ?> - - - - - - - - -