From 3e57805de1e23d17ede5cd90b7a053ad8c9b021f Mon Sep 17 00:00:00 2001 From: Jos Ahrens Date: Fri, 15 Jun 2018 20:46:40 +0000 Subject: [PATCH] Initial commit --- .gitignore | 2 + LICENSE | 21 +++++++ README.md | 15 +++++ composer.json | 9 +++ config/config.dist.php | 25 ++++++++ docs/api.md | 41 +++++++++++++ docs/install.md | 9 +++ docs/setup.md | 57 +++++++++++++++++++ docs/sharex_config.md | 23 ++++++++ images/.gitignore | 2 + public/delete.php | 40 +++++++++++++ public/upload.php | 40 +++++++++++++ src/Config.php | 15 +++++ .../NamingStrategyInterface.php | 6 ++ src/NamingStrategy/RandomBytes.php | 15 +++++ src/autoload.php | 7 +++ src/helper.php | 37 ++++++++++++ src/validation_headers.php | 9 +++ src/validation_sharex_upload.php | 13 +++++ 19 files changed, 386 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 composer.json create mode 100644 config/config.dist.php create mode 100644 docs/api.md create mode 100644 docs/install.md create mode 100644 docs/setup.md create mode 100644 docs/sharex_config.md create mode 100644 images/.gitignore create mode 100644 public/delete.php create mode 100644 public/upload.php create mode 100644 src/Config.php create mode 100644 src/NamingStrategy/NamingStrategyInterface.php create mode 100644 src/NamingStrategy/RandomBytes.php create mode 100644 src/autoload.php create mode 100644 src/helper.php create mode 100644 src/validation_headers.php create mode 100644 src/validation_sharex_upload.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..79c355a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/config/config.php +/vendor/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..af5d486 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 Jos Ahrens + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..aa3e9e6 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# image.liefland.net + +(or alternatively known as: A super simplistic generic direct-link ShareX uploader service.) + +### Requirements + +PHP >= 7.2 + +### Documentation + +Read the documentation in `docs/` for full setup instructions. + +## License + +MIT diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..db6e6f0 --- /dev/null +++ b/composer.json @@ -0,0 +1,9 @@ +{ + "name": "zarthus/image.liefland.net", + "description": "A super simplistic generic direct-link ShareX uploader service.", + "type": "project", + "license": "MIT", + "require": { + "php": ">=7.2" + } +} diff --git a/config/config.dist.php b/config/config.dist.php new file mode 100644 index 0000000..7258c75 --- /dev/null +++ b/config/config.dist.php @@ -0,0 +1,25 @@ + [ + // Need to generate one? Here's a quick command to do it: + // $ php -r 'echo bin2hex(random_bytes(64));' + 'HTTP_X_AUTHORIZATION' => 'api key here', + 'HTTP_USER_AGENT' => 'ShareX', + ], + // Path to the directory where files should be stored. + 'uploadDir' => __DIR__ . '/../images/', + // URL where this project ("public") lives. + 'baseUrl' => 'https://example.org', + // URL Where the images ("uploadDir") lives. + 'cdnUrl' => 'https://example.org/images', + // ShareX sends the file name as the window it is recognized, enabling this might be a slight + // privacy risk at expense of knowing what you're clicking on when you link this image. + // If using this option, ensure ShareX's file naming is set to "%pn_%y-%mo-%d_%h-%mi-%s" or something else starting + // with "%pn_" + 'useAppNamePrefix' => true, + // Create an identically named file on the cdn with .json extension that contains metadata. Exposes information + // to the public you might not want. You could reject access with your webserver config, or just set this to false. + 'writeJsonMetadata' => true, +]; diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..9beaca0 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,41 @@ +JSON api responses: + +#### Image upload `POST` to `/upload.php`: + +```json +{ + "urls": { + "full": "full url for image / cdn", + "delete": "deletion link" + }, + "uploader": { + "name": "set if header X-Sender is provided." + }, + "file": { + "name": "original ShareX filename", + "size": "size in bytes", + "type": "the type we detected, currently we hardcode png as file extension" + }, + "meta": { + "uploaded_on": "DateTime in iso8601 format" + } +} +``` + +Or 401: Unauthorized (plain text) on any failure. + +#### Deletion request `DELETE` to `/delete.php`: + +```json +{ + "deleted": bool, + "uploader": { + "name": "set if header X-Sender is provided." + }, + "meta": { + "deleted_on": "DateTime in iso8601 format" + } +} +``` + +Or 401: Unauthorized (plain text) on any failure. diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 0000000..07d1149 --- /dev/null +++ b/docs/install.md @@ -0,0 +1,9 @@ +# Installation + +Ensure you have PHP >= 7.2 on your server (7.1 should work too, but is untested.) + +- Copy `config/config.dist.php` to `config/config.php` and edit it. +- Follow the instructions from `setup.md` for your webserver setup. +- Follow instructions from `sharex_config.md` to set up ShareX to upload to your service. +- Ensure `images/` has the right access for your httpd user. +- Upload an image. diff --git a/docs/setup.md b/docs/setup.md new file mode 100644 index 0000000..5b938e4 --- /dev/null +++ b/docs/setup.md @@ -0,0 +1,57 @@ +# Setup + +This is all just suggestion, and a near-direct copy of how my setup works. + +---- + +note: a `client_max_body_size` of more than the default (1m) is recommended. + +### The "Backend" ("base url") + +```conf +server { + root /srv/http/image.example.org/public; + + index index.php index.html; + + server_name image.example.org.net; + + // ssl config + + location ~ \.php$ { + include snippets/fastcgi-php.conf; + fastcgi_pass unix:/var/run/php/php7.2-fpm.sock; + } + + location / { + try_files $uri $uri/ =404; + } + + add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload"; + add_header Content-Security-Policy "default-src 'self'"; + add_header X-Frame-Options sameorigin; + add_header X-Content-Type-Options nosniff; + add_header X-XSS-Protection "1; mode=block"; + add_header Referrer-Policy same-origin; +} + +server { + if ($host = image.example.org) { + return 301 https://$host$request_uri; + } + + listen 80; + listen [::]:80; + + server_name image.example.org; + return 404; +} +``` + +The CDN or the image serving config could have the following block; + +``` + location /images { + alias /srv/http/image.example.org/images; + } +``` diff --git a/docs/sharex_config.md b/docs/sharex_config.md new file mode 100644 index 0000000..2a14879 --- /dev/null +++ b/docs/sharex_config.md @@ -0,0 +1,23 @@ +# ShareX configuration + +In ShareX, go to Destinations -> Custom Uploaders + +Add a new Uploader, configure the following: + +- Destination Type: `Image Uploader` +- Request Type: `POST` +- Request URL: URL identical to the baseUrl in `config.php` +- File form name: `sharex_image` +- Headers: + - X-Authorization: `Your API key` + - X-Uploader: `YourName` +- Response Type: `Response Text` + - URL: `$json:.urls.full$` + - Deletion URL: `$json:.urls.delete$` + +You should do the same, but for Request Type `DELETE`, because at the moment +there is no UI to insert the API key in. + +Test it, confirm it works. + +If it doesn't, that's gonna suck, because we only send `401 Unauthorized` with no logging. diff --git a/images/.gitignore b/images/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/images/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/public/delete.php b/public/delete.php new file mode 100644 index 0000000..400c699 --- /dev/null +++ b/public/delete.php @@ -0,0 +1,40 @@ + $uploadDir] = Config::get(); + +$deleteFile = $_POST['uuid']; +$filePath = $uploadDir . '/' . $deleteFile . '.png'; + +if (!preg_match('/^[a-zA-Z0-9_\-]+$/', $deleteFile) || !file_exists($filePath) || !is_file($filePath)) { + unauthorized(); +} + +$success = unlink($filePath); + +if (file_exists($uploadDir . '/' . $deleteFile . '.json')) { + $success = $success && unlink($uploadDir . '/' . $deleteFile . '.json'); +} + +$json = json_encode([ + 'deleted' => $success, + 'uploader' => [ + 'name' => strtolower($_SERVER['HTTP_X_SENDER'] ?? 'Unknown'), + ], + 'meta' => [ + 'deleted_on' => (new \DateTime('now', new \DateTimeZone('Etc/UTC')))->format(\DateTime::ATOM), + ] +]); + +echo $json; diff --git a/public/upload.php b/public/upload.php new file mode 100644 index 0000000..744d062 --- /dev/null +++ b/public/upload.php @@ -0,0 +1,40 @@ + $uploadDir, + 'baseUrl' => $baseUrl, + 'cdnUrl' => $cdnUrl, + 'useAppNamePrefix' => $useAppNamePrefix, + 'writeJsonMetadata' => $writeJsonMetadata +] = Config::get(); +[$uuid, $fileName] = sharex_create_filename($uploadDir, $_FILES['sharex_image']['name'], $useAppNamePrefix); + +move_uploaded_file($_FILES['sharex_image']['tmp_name'], $fileName); + +$json = json_encode([ + 'urls' => [ + 'full' => $cdnUrl . '/' . $uuid . '.png', + 'delete' => $baseUrl . '/delete.php?uuid=' . $uuid, + ], + 'uploader' => [ + 'name' => strtolower($_SERVER['HTTP_X_SENDER'] ?? 'Unknown'), + ], + 'file' => [ + 'name' => $_FILES['sharex_image']['name'] ?? null, + 'size' => $_FILES['sharex_image']['size'], + 'type' => image_type_to_mime_type(exif_imagetype($uploadDir . $uuid . '.png')) ?? 'image/png', + ], + 'meta' => [ + 'uploaded_on' => (new \DateTime('now', new \DateTimeZone('Etc/UTC')))->format(\DateTime::ATOM), + ] +]); + +if ($writeJsonMetadata) { + file_put_contents($uploadDir . $uuid . '.json', $json); +} + +echo $json; diff --git a/src/Config.php b/src/Config.php new file mode 100644 index 0000000..c586946 --- /dev/null +++ b/src/Config.php @@ -0,0 +1,15 @@ +generate(); + } while (file_exists($prefix . $uploadDir . $uuid . '.png')); + + return [$uuid, $prefix . $uploadDir . $uuid . '.png']; +} diff --git a/src/validation_headers.php b/src/validation_headers.php new file mode 100644 index 0000000..7b30ff1 --- /dev/null +++ b/src/validation_headers.php @@ -0,0 +1,9 @@ + $expectation) { + if (!isset($_SERVER[$header])) { + unauthorized(); + } + + if ($_SERVER[$header] !== $expectation) { + unauthorized(); + } +}