| @@ -1,9 +1,3 @@ | |||
| [submodule "moe"] | |||
| path = moe | |||
| url = https://github.com/pomf/moe | |||
| [submodule "php"] | |||
| path = php | |||
| url = https://github.com/pomf/pomf-php | |||
| [submodule "expiry"] | |||
| path = expiry | |||
| url = https://github.com/pomf/pomf-expiry.git | |||
| @@ -45,8 +45,8 @@ copy-img: | |||
| cp -vT $(CURDIR)/static/img/favicon.ico $(CURDIR)/build/favicon.ico | |||
| copy-php: | |||
| ifneq ($(wildcard $(CURDIR)/php/.),) | |||
| cp -rv $(CURDIR)/php/* $(CURDIR)/build/ | |||
| ifneq ($(wildcard $(CURDIR)/static/php/.),) | |||
| cp -rv $(CURDIR)/static/php/* $(CURDIR)/build/ | |||
| else | |||
| $(error The php submodule was not found) | |||
| endif | |||
| @@ -0,0 +1,21 @@ | |||
| Copyright (c) 2013, 2014, 2015 Eric Johansson <neku@pomf.se> | |||
| Copyright (c) 2013, 2014 Peter Lejeck <peter.lejeck@gmail.com> | |||
| Copyright (c) 2015 cenci0 <alchimist94@gmail.com> | |||
| Copyright (c) 2015, 2016 the Pantsu.cat developers <hostmaster@pantsu.cat> | |||
| 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. | |||
| @@ -0,0 +1,161 @@ | |||
| # Pomf | |||
| [](https://travis-ci.org/pomf/pomf) | |||
| [](https://david-dm.org/pomf/pomf) | |||
| [](https://david-dm.org/pomf/pomf#info=devDependencies) | |||
| [](https://raw.githubusercontent.com/pomf/pomf/master/LICENSE) | |||
| Pomf is a simple file uploading and sharing platform. | |||
| ## Features | |||
| - One click uploading, no registration required | |||
| - A minimal, modern web interface | |||
| - Drag & drop supported | |||
| - Upload API with multiple response choices | |||
| - JSON | |||
| - HTML | |||
| - Text | |||
| - CSV | |||
| - Supports [ShareX](https://getsharex.com/) and other screenshot tools | |||
| ### Demo | |||
| See the real world example at [Pantsu.cat](https://pantsu.cat/). | |||
| ## Requirements | |||
| Original development environment is Nginx + PHP5.5 + MySQL, but is confirmed to | |||
| work with Apache 2.4 and newer PHP versions. Should work with any other | |||
| PDO-compatible database. | |||
| ## Install | |||
| For the purposes of this guide, we won't cover setting up Nginx, PHP, MySQL, | |||
| Node, or NPM. So we'll just assume you already have them all running well. | |||
| ### Compiling | |||
| Assuming you already have Node and NPM working, compilation is easy. Use the | |||
| following shell code: | |||
| ```bash | |||
| git clone https://github.com/pomf/pomf | |||
| cd pomf/ | |||
| make | |||
| make install | |||
| ``` | |||
| OR | |||
| ```bash | |||
| make install DESTDIR=/desired/path/for/site | |||
| ``` | |||
| After this, the pomf site is now compressed and set up inside `dist/`, or, if specified, `DESTDIR`. | |||
| ## Configuring | |||
| Front-end related settings, such as the name of the site, and maximum allowable | |||
| file size, are found in `templates/site_variables.json`. Changes made here will | |||
| only take effect after rebuilding the site pages. This may be done by running | |||
| `make` from the root of the site directory. | |||
| Back-end related settings, such as database configuration, and path for uploaded files, are found in `static/php/includes/settings.inc.php`. Changes made here take effect immediately. | |||
| If you intend to allow uploading files larger than 2 MB, you may also need to | |||
| increase POST size limits in `php.ini` and webserver configuration. For PHP, | |||
| modify `upload_max_filesize` and `post_max_size` values. The configuration | |||
| option for nginx webserver is `client_max_body_size`. | |||
| Example nginx configs can be found in confs/. | |||
| ## Using SQLite as DB engine | |||
| We need to create the SQLite database before it may be used by pomf. | |||
| Fortunately, this is incredibly simple. | |||
| First create a directory for the database, e.g. `mkdir /var/db/pomf`. | |||
| Then, create a new SQLite database from the schema, e.g. `sqlite3 /var/db/pomf/pomf.sq3 -init /home/pomf/sqlite_schema.sql`. | |||
| Then, finally, ensure the permissions are correct, e.g. | |||
| ```bash | |||
| chown nginx:nginx /var/db/pomf | |||
| chmod 0750 /var/db/pomf | |||
| chmod 0640 /var/db/pomf/pomf.sq3 | |||
| ``` | |||
| Finally, edit `php/includes/settings.inc.php` to indicate this is the database engine you would like to use. Make the changes outlined below | |||
| ```php | |||
| define('POMF_DB_CONN', '[stuff]'); ---> define('POMF_DB_CONN', 'sqlite:/var/db/pomf/pomf.sq3');` | |||
| define('POMF_DB_USER', '[stuff]'); ---> define('POMF_DB_USER', null); | |||
| define('POMF_DB_PASS', '[stuff]'); ---> define('POMF_DB_PASS', null); | |||
| ``` | |||
| *NOTE: The directory where the SQLite database is stored, must be writable by the web server user* | |||
| ### Apache | |||
| If you are running Apache and want to compress your output when serving files, | |||
| add to your `.htaccess` file: | |||
| AddOutputFilterByType DEFLATE text/html text/plain text/css application/javascript application/x-javascript application/json | |||
| Remember to enable `deflate_module` and `filter_module` modules in your Apache | |||
| configuration file. | |||
| ### Migrating from MySQL to SQLite | |||
| For older versions of Pomf you may want to migrate to SQLite. Fortunately, it is incredibly simple to migrate your database. This may be done on a live server, and should require zero downtime. | |||
| _If doing this on a live server, you way wish to work in a subdirectory (or vhost, or equivelant), so that any complications or mistakes do not affect your main site. | |||
| If you choose not to do so, know that mistakes in the changes outlined below, will only temporarily impact **uploading**, causing **Server error** to be displayed. None of these steps are destructive, and are easily reverted._ | |||
| Run the following commands as root, to dump your database, and make a SQLite database with the contents. | |||
| ```bash | |||
| mkdir /var/db/pomf | |||
| wget -O /tmp/m2s https://github.com/dumblob/mysql2sqlite/raw/master/mysql2sqlite.sh | |||
| mysqldump -u OLD_DB_USER -p OLD_DB_PASS pomf | sh /tmp/m2s | sqlite3 /var/db/pomf/sq3 | |||
| rm /tmp/m2s | |||
| chown -R nginx:nginx /var/db/pomf #replace user as appropriate | |||
| chmod 0750 /var/db/pomf && chmod 0640 /var/db/pomf/sq3 | |||
| ``` | |||
| Edit the file `php/includes/settings.inc.php`, in the subdirectory you just made, making the changes outlined below. | |||
| ```php | |||
| define('POMF_DB_CONN', '[stuff]'); ---> define('POMF_DB_CONN', 'sqlite:/var/db/pomf/pomf.sq3');` | |||
| define('POMF_DB_USER', '[stuff]'); ---> define('POMF_DB_USER', null); | |||
| define('POMF_DB_PASS', '[stuff]'); ---> define('POMF_DB_PASS', null); | |||
| ``` | |||
| Then, run `make` to rebuild the website pages, and copy the new `settings.inc.php` file into place. | |||
| All done! You may disable or uninstall MySQL if you wish. | |||
| ## Getting help | |||
| The Pomf community gathers on IRC. You can also email the maintainer for help. | |||
| - IRC (users): `#pomfret` on Rizon (`irc.rizon.net`) | |||
| - Email: <hostmaster@pantsu.cat> | |||
| ## Contributing | |||
| We'd really like if you can take some time to make sure your coding style is | |||
| consistent with the project. Pomf follows [PHP | |||
| PSR-2](http://www.php-fig.org/psr/psr-2/) and [Airbnb JavaScript | |||
| (ES5)](https://github.com/airbnb/javascript/tree/master/es5) (`airbnb/legacy`) | |||
| coding style guides. We use ESLint and PHPCS tools to enforce these standards. | |||
| You can also help by sending us feature requests or writing documentation and | |||
| tests. | |||
| Thanks! | |||
| ## Credits | |||
| Pomf was created by Eric Johansson and Peter Lejeck for | |||
| [Pomf.se](http://pomf.se/). The software is currently maintained by the | |||
| community. | |||
| ## License | |||
| Pomf is free software, and is released under the terms of the Expat license. See | |||
| `LICENSE`. | |||
| @@ -0,0 +1,241 @@ | |||
| <?php | |||
| /** | |||
| * The Response class is a do-it-all for getting responses out in different | |||
| * formats. | |||
| * | |||
| * @todo Create sub-classes to split and extend this god object. | |||
| */ | |||
| class Response | |||
| { | |||
| /** | |||
| * Indicates response type used for routing. | |||
| * | |||
| * Valid strings are 'csv', 'html', 'json' and 'text'. | |||
| * | |||
| * @var string $type Response type | |||
| */ | |||
| private $type; | |||
| /** | |||
| * Indicates requested response type. | |||
| * | |||
| * Valid strings are 'csv', 'html', 'json', 'gyazo' and 'text'. | |||
| * | |||
| * @param string|null $response_type Response type | |||
| */ | |||
| public function __construct($response_type = null) | |||
| { | |||
| switch ($response_type) { | |||
| case 'csv': | |||
| header('Content-Type: text/csv; charset=UTF-8'); | |||
| $this->type = $response_type; | |||
| break; | |||
| case 'html': | |||
| header('Content-Type: text/html; charset=UTF-8'); | |||
| $this->type = $response_type; | |||
| break; | |||
| case 'json': | |||
| header('Content-Type: application/json; charset=UTF-8'); | |||
| $this->type = $response_type; | |||
| break; | |||
| case 'gyazo': | |||
| header('Content-Type: text/plain; charset=UTF-8'); | |||
| $this->type = 'text'; | |||
| break; | |||
| case 'text': | |||
| header('Content-Type: text/plain; charset=UTF-8'); | |||
| $this->type = $response_type; | |||
| break; | |||
| default: | |||
| header('Content-Type: application/json; charset=UTF-8'); | |||
| $this->type = 'json'; | |||
| $this->error(400, 'Invalid response type. Valid options are: csv, html, json, text.'); | |||
| break; | |||
| } | |||
| } | |||
| /** | |||
| * Routes error messages depending on response type. | |||
| * | |||
| * @param int $code HTTP status code number. | |||
| * @param int $desc Descriptive error message. | |||
| * @return void | |||
| */ | |||
| public function error($code, $desc) | |||
| { | |||
| $response = null; | |||
| switch ($this->type) { | |||
| case 'csv': | |||
| $response = $this->csvError($desc); | |||
| break; | |||
| case 'html': | |||
| $response = $this->htmlError($code, $desc); | |||
| break; | |||
| case 'json': | |||
| $response = $this->jsonError($code, $desc); | |||
| break; | |||
| case 'text': | |||
| $response = $this->textError($code, $desc); | |||
| break; | |||
| } | |||
| http_response_code(500); // "500 Internal Server Error" | |||
| echo $response; | |||
| } | |||
| /** | |||
| * Routes success messages depending on response type. | |||
| * | |||
| * @param mixed[] $files | |||
| * @return void | |||
| */ | |||
| public function send($files) | |||
| { | |||
| $response = null; | |||
| switch ($this->type) { | |||
| case 'csv': | |||
| $response = $this->csvSuccess($files); | |||
| break; | |||
| case 'html': | |||
| $response = $this->htmlSuccess($files); | |||
| break; | |||
| case 'json': | |||
| $response = $this->jsonSuccess($files); | |||
| break; | |||
| case 'text': | |||
| $response = $this->textSuccess($files); | |||
| break; | |||
| } | |||
| http_response_code(200); // "200 OK". Success. | |||
| echo $response; | |||
| } | |||
| /** | |||
| * Indicates with CSV body the request was invalid. | |||
| * | |||
| * @deprecated 2.1.0 Will be renamed to camelCase format. | |||
| * @param int $description Descriptive error message. | |||
| * @return string Error message in CSV format. | |||
| */ | |||
| private static function csvError($description) | |||
| { | |||
| return '"error"'."\r\n"."\"$description\""."\r\n"; | |||
| } | |||
| /** | |||
| * Indicates with CSV body the request was successful. | |||
| * | |||
| * @deprecated 2.1.0 Will be renamed to camelCase format. | |||
| * @param mixed[] $files | |||
| * @return string Success message in CSV format. | |||
| */ | |||
| private static function csvSuccess($files) | |||
| { | |||
| $result = '"name","url","hash","size"'."\r\n"; | |||
| foreach ($files as $file) { | |||
| $result .= '"'.$file['name'].'"'.','. | |||
| '"'.$file['url'].'"'.','. | |||
| '"'.$file['hash'].'"'.','. | |||
| '"'.$file['size'].'"'."\r\n"; | |||
| } | |||
| return $result; | |||
| } | |||
| /** | |||
| * Indicates with HTML body the request was invalid. | |||
| * | |||
| * @deprecated 2.1.0 Will be renamed to camelCase format. | |||
| * @param int $code HTTP status code number. | |||
| * @param int $description Descriptive error message. | |||
| * @return string Error message in HTML format. | |||
| */ | |||
| private static function htmlError($code, $description) | |||
| { | |||
| return '<p>ERROR: ('.$code.') '.$description.'</p>'; | |||
| } | |||
| /** | |||
| * Indicates with HTML body the request was successful. | |||
| * | |||
| * @deprecated 2.1.0 Will be renamed to camelCase format. | |||
| * @param mixed[] $files | |||
| * @return string Success message in HTML format. | |||
| */ | |||
| private static function htmlSuccess($files) | |||
| { | |||
| $result = ''; | |||
| foreach ($files as $file) { | |||
| $result .= '<a href="'.$file['url'].'">'.$file['url'].'</a><br>'; | |||
| } | |||
| return $result; | |||
| } | |||
| /** | |||
| * Indicates with JSON body the request was invalid. | |||
| * | |||
| * @deprecated 2.1.0 Will be renamed to camelCase format. | |||
| * @param int $code HTTP status code number. | |||
| * @param int $description Descriptive error message. | |||
| * @return string Error message in pretty-printed JSON format. | |||
| */ | |||
| private static function jsonError($code, $description) | |||
| { | |||
| return json_encode(array( | |||
| 'success' => false, | |||
| 'errorcode' => $code, | |||
| 'description' => $description, | |||
| ), JSON_PRETTY_PRINT); | |||
| } | |||
| /** | |||
| * Indicates with JSON body the request was successful. | |||
| * | |||
| * @deprecated 2.1.0 Will be renamed to camelCase format. | |||
| * @param mixed[] $files | |||
| * @return string Success message in pretty-printed JSON format. | |||
| */ | |||
| private static function jsonSuccess($files) | |||
| { | |||
| return json_encode(array( | |||
| 'success' => true, | |||
| 'files' => $files, | |||
| ), JSON_PRETTY_PRINT); | |||
| } | |||
| /** | |||
| * Indicates with plain text body the request was invalid. | |||
| * | |||
| * @deprecated 2.1.0 Will be renamed to camelCase format. | |||
| * @param int $code HTTP status code number. | |||
| * @param int $description Descriptive error message. | |||
| * @return string Error message in plain text format. | |||
| */ | |||
| private static function textError($code, $description) | |||
| { | |||
| return 'ERROR: ('.$code.') '.$description; | |||
| } | |||
| /** | |||
| * Indicates with plain text body the request was successful. | |||
| * | |||
| * @deprecated 2.1.0 Will be renamed to camelCase format. | |||
| * @param mixed[] $files | |||
| * @return string Success message in plain text format. | |||
| */ | |||
| private static function textSuccess($files) | |||
| { | |||
| $result = ''; | |||
| foreach ($files as $file) { | |||
| $result .= $file['url']."\n"; | |||
| } | |||
| return $result; | |||
| } | |||
| } | |||
| @@ -0,0 +1,63 @@ | |||
| <?php | |||
| /** | |||
| * Returns a human readable error description for file upload errors. | |||
| * | |||
| * @author Dan Brown <danbrown@php.net> | |||
| * @author Michiel Thalen | |||
| * @copyright Copyright © 1997 - 2016 by the PHP Documentation Group | |||
| * @license | |||
| * UploadException is licensed under a Creative Commons Attribution 3.0 License | |||
| * or later. | |||
| * | |||
| * Based on a work at | |||
| * https://secure.php.net/manual/en/features.file-upload.errors.php#89374. | |||
| * | |||
| * You should have received a copy of the Creative Commons Attribution 3.0 | |||
| * License with this program. If not, see | |||
| * <https://creativecommons.org/licenses/by/3.0/>. | |||
| */ | |||
| class UploadException extends Exception | |||
| { | |||
| public function __construct($code) | |||
| { | |||
| $message = $this->codeToMessage($code); | |||
| parent::__construct($message, 500); | |||
| } | |||
| private function codeToMessage($code) | |||
| { | |||
| switch ($code) { | |||
| case UPLOAD_ERR_INI_SIZE: | |||
| $message = 'The uploaded file exceeds the upload_max_filesize directive in php.ini'; | |||
| break; | |||
| case UPLOAD_ERR_FORM_SIZE: | |||
| $message = 'The uploaded file exceeds the MAX_FILE_SIZE directive that was '. | |||
| 'specified in the HTML form'; | |||
| break; | |||
| case UPLOAD_ERR_PARTIAL: | |||
| $message = 'The uploaded file was only partially uploaded'; | |||
| break; | |||
| case UPLOAD_ERR_NO_FILE: | |||
| $message = 'No file was uploaded'; | |||
| break; | |||
| case UPLOAD_ERR_NO_TMP_DIR: | |||
| $message = 'Missing a temporary folder'; | |||
| break; | |||
| case UPLOAD_ERR_CANT_WRITE: | |||
| $message = 'Failed to write file to disk'; | |||
| break; | |||
| case UPLOAD_ERR_EXTENSION: | |||
| $message = 'File upload stopped by extension'; | |||
| break; | |||
| default: | |||
| $message = 'Unknown upload error'; | |||
| break; | |||
| } | |||
| return $message; | |||
| } | |||
| } | |||
| @@ -0,0 +1,32 @@ | |||
| <?php | |||
| class UploadedFile | |||
| { | |||
| /* Public attributes */ | |||
| public $name; | |||
| public $mime; | |||
| public $size; | |||
| public $tempfile; | |||
| public $error; | |||
| /** | |||
| * SHA-1 checksum | |||
| * | |||
| * @var string 40 digit hexadecimal hash (160 bits) | |||
| */ | |||
| private $sha1; | |||
| /** | |||
| * Generates the SHA-1 or returns the cached SHA-1 hash for the file. | |||
| * | |||
| * @return string|false $sha1 | |||
| */ | |||
| public function getSha1() | |||
| { | |||
| if (!$this->sha1) { | |||
| $this->sha1 = sha1_file($this->tempfile); | |||
| } | |||
| return $this->sha1; | |||
| } | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| <?php | |||
| // Array of image paths, feel free to add/remove to/from this list | |||
| $images = array( | |||
| 'img/2.png', | |||
| 'img/3.png', | |||
| 'img/4.png', | |||
| 'img/5.png', | |||
| 'img/6.png', | |||
| 'img/7.png', | |||
| 'img/8.png', | |||
| 'img/9.png', | |||
| 'img/10.png', | |||
| ); | |||
| // Redirect to a random image from the above array using status code "303 See Other" | |||
| if (headers_sent() === false) { | |||
| header('Location: '.$images[array_rand($images)], true, 303); | |||
| } | |||
| @@ -0,0 +1,30 @@ | |||
| <?php | |||
| /** | |||
| * Prepares a PDO connection between Pomf and a database server. | |||
| * | |||
| * @copyright Copyright (c) 2013 Peter Lejeck <peter.lejeck@gmail.com> | |||
| * | |||
| * 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. | |||
| */ | |||
| require_once 'settings.inc.php'; | |||
| /* NOTE: we don't have to unref the PDO because we're not long-running */ | |||
| $db = new PDO(POMF_DB_CONN, POMF_DB_USER, POMF_DB_PASS); | |||
| @@ -0,0 +1,118 @@ | |||
| <?php | |||
| /** | |||
| * User configurable settings for Pomf. | |||
| * | |||
| * @copyright Copyright (c) 2013, 2014 Peter Lejeck <peter.lejeck@gmail.com> | |||
| * @copyright Copyright (c) 2015 cenci0 <alchimist94@gmail.com> | |||
| * @copyright Copyright (c) 2015, 2016 the Pantsu.cat developers | |||
| * <hostmaster@pantsu.cat> | |||
| * | |||
| * 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. | |||
| */ | |||
| /** | |||
| * PDO connection socket | |||
| * | |||
| * Database connection to use for communication. Currently, MySQL is the only | |||
| * DSN prefix supported. | |||
| * | |||
| * @see http://php.net/manual/en/ref.pdo-mysql.connection.php PHP manual for | |||
| * PDO_MYSQL DSN. | |||
| * @param string POMF_DB_CONN DSN:host|unix_socket=hostname|path;dbname=database | |||
| */ | |||
| define('POMF_DB_CONN', 'mysql:unix_socket=/var/run/mysqld/mysqld.sock;dbname=pomf'); | |||
| /** | |||
| * PDO database login credentials | |||
| */ | |||
| /** @param string POMF_DB_NAME Database username */ | |||
| define('POMF_DB_USER', 'pomf'); | |||
| /** @param string POMF_DB_PASS Database password */ | |||
| define('POMF_DB_PASS', ''); | |||
| /** | |||
| * File system location where to store uploaded files | |||
| * | |||
| * @param string Path to directory with trailing delimiter | |||
| */ | |||
| define('POMF_FILES_ROOT', '/mnt/pantsu/http/files/'); | |||
| /** | |||
| * Maximum number of iterations while generating a new filename | |||
| * | |||
| * Pomf uses an algorithm to generate random filenames. Sometimes a file may | |||
| * exist under a randomly generated filename, so we count tries and keep trying. | |||
| * If this value is exceeded, we give up trying to generate a new filename. | |||
| * | |||
| * @param int POMF_FILES_RETRIES Number of attempts to retry | |||
| */ | |||
| define('POMF_FILES_RETRIES', 15); | |||
| /** | |||
| * The length of generated filename (without file extension) | |||
| * | |||
| * @param int POMF_FILES_LENGTH Number of random alphabetical ASCII characters | |||
| * to use | |||
| */ | |||
| define('POMF_FILES_LENGTH', 6); | |||
| /** | |||
| * URI to prepend to links for uploaded files | |||
| * | |||
| * @param string POMF_URL URI with trailing delimiter | |||
| */ | |||
| define('POMF_URL', 'https://i.pantsu.cat/'); | |||
| /** | |||
| * URI for filename generation | |||
| * | |||
| * @param string characters to be used in generateName() | |||
| */ | |||
| define('ID_CHARSET', 'abcdefghijklmnopqrstuvwxyz'); | |||
| /** | |||
| * Filtered mime types | |||
| * @param string[] $FILTER_MIME allowed/blocked mime types | |||
| */ | |||
| $FILTER_MIME = array(); | |||
| /** | |||
| * Filter mode: whitelist (true) or blacklist (false) | |||
| * @param bool $FILTER_MODE mime type filter mode | |||
| */ | |||
| $FILTER_MODE = false; | |||
| /** | |||
| * Double dot file extensions | |||
| * | |||
| * Pomf keeps the last file extension for the uploaded file. In other words, an | |||
| * uploaded file with `.tar.gz` extension will be given a random filename which | |||
| * ends in `.gz` unless configured here to ignore discards for `.tar.gz`. | |||
| * | |||
| * @param string[] $doubledots Array of double dot file extensions strings | |||
| * without the first prefixing dot | |||
| */ | |||
| $doubledots = array_map('strrev', array( | |||
| 'tar.gz', | |||
| 'tar.bz', | |||
| 'tar.bz2', | |||
| 'tar.xz', | |||
| 'user.js', | |||
| )); | |||
| @@ -0,0 +1,236 @@ | |||
| <?php | |||
| session_start(); | |||
| /** | |||
| * Handles POST uploads, generates filenames, moves files around and commits | |||
| * uploaded metadata to database. | |||
| */ | |||
| require_once 'classes/Response.class.php'; | |||
| require_once 'classes/UploadException.class.php'; | |||
| require_once 'classes/UploadedFile.class.php'; | |||
| require_once 'includes/database.inc.php'; | |||
| /** | |||
| * Generates a random name for the file, retrying until we get an unused one. | |||
| * | |||
| * @param UploadedFile $file | |||
| * | |||
| * @return string | |||
| */ | |||
| function generateName($file) | |||
| { | |||
| global $db; | |||
| global $doubledots; | |||
| // We start at N retries, and --N until we give up | |||
| $tries = POMF_FILES_RETRIES; | |||
| $length = POMF_FILES_LENGTH; | |||
| $ext = pathinfo($file->name, PATHINFO_EXTENSION); | |||
| // Check if extension is a double-dot extension and, if true, override $ext | |||
| $revname = strrev($file->name); | |||
| foreach ($doubledots as $ddot) { | |||
| if (stripos($revname, $ddot) === 0) { | |||
| $ext = strrev($ddot); | |||
| } | |||
| } | |||
| do { | |||
| // Iterate until we reach the maximum number of retries | |||
| if ($tries-- === 0) { | |||
| throw new Exception( | |||
| 'Gave up trying to find an unused name', | |||
| 500 | |||
| ); // HTTP status code "500 Internal Server Error" | |||
| } | |||
| $chars = ID_CHARSET; | |||
| $name = ''; | |||
| for ($i = 0; $i < $length; ++$i) { | |||
| $name .= $chars[mt_rand(0, strlen($chars))]; | |||
| } | |||
| // Add the extension to the file name | |||
| if (isset($ext) && $ext !== '') { | |||
| $name .= '.'.$ext; | |||
| } | |||
| // Check if a file with the same name does already exist in the database | |||
| $q = $db->prepare('SELECT COUNT(filename) FROM files WHERE filename = (:name)'); | |||
| $q->bindValue(':name', $name, PDO::PARAM_STR); | |||
| $q->execute(); | |||
| $result = $q->fetchColumn(); | |||
| // If it does, generate a new name | |||
| } while ($result > 0); | |||
| return $name; | |||
| } | |||
| /** | |||
| * Handles the uploading and db entry for a file. | |||
| * | |||
| * @param UploadedFile $file | |||
| * | |||
| * @return array | |||
| */ | |||
| function uploadFile($file) | |||
| { | |||
| global $db; | |||
| global $FILTER_MODE; | |||
| global $FILTER_MIME; | |||
| // Handle file errors | |||
| if ($file->error) { | |||
| throw new UploadException($file->error); | |||
| } | |||
| // Check if mime type is blocked | |||
| if (!empty($FILTER_MIME)) { | |||
| if ($FILTER_MODE == true) { //whitelist mode | |||
| if (!in_array($file->mime, $FILTER_MIME)) { | |||
| throw new UploadException(UPLOAD_ERR_EXTENSION); | |||
| } | |||
| } else { //blacklist mode | |||
| if (in_array($file->mime, $FILTER_MIME)) { | |||
| throw new UploadException(UPLOAD_ERR_EXTENSION); | |||
| } | |||
| } | |||
| } | |||
| // Check if a file with the same hash and size (a file which is the same) | |||
| // does already exist in the database; if it does, return the proper link | |||
| // and data. PHP deletes the temporary file just uploaded automatically. | |||
| $q = $db->prepare('SELECT filename, COUNT(*) AS count FROM files WHERE hash = (:hash) '. | |||
| 'AND size = (:size)'); | |||
| $q->bindValue(':hash', $file->getSha1(), PDO::PARAM_STR); | |||
| $q->bindValue(':size', $file->size, PDO::PARAM_INT); | |||
| $q->execute(); | |||
| $result = $q->fetch(); | |||
| if ($result['count'] > 0) { | |||
| return array( | |||
| 'hash' => $file->getSha1(), | |||
| 'name' => $file->name, | |||
| 'url' => POMF_URL.rawurlencode($result['filename']), | |||
| 'size' => $file->size, | |||
| ); | |||
| } | |||
| // Generate a name for the file | |||
| $newname = generateName($file); | |||
| // Store the file's full file path in memory | |||
| $uploadFile = POMF_FILES_ROOT . $newname; | |||
| // Attempt to move it to the static directory | |||
| if (!move_uploaded_file($file->tempfile, $uploadFile)) { | |||
| throw new Exception( | |||
| 'Failed to move file to destination', | |||
| 500 | |||
| ); // HTTP status code "500 Internal Server Error" | |||
| } | |||
| // Need to change permissions for the new file to make it world readable | |||
| if (!chmod($uploadFile, 0644)) { | |||
| throw new Exception( | |||
| 'Failed to change file permissions', | |||
| 500 | |||
| ); // HTTP status code "500 Internal Server Error" | |||
| } | |||
| // Add it to the database | |||
| if (empty($_SESSION['id'])) { | |||
| // Query if user is NOT logged in | |||
| $q = $db->prepare('INSERT INTO files (hash, originalname, filename, size, date, ' . | |||
| 'expire, delid) VALUES (:hash, :orig, :name, :size, :date, ' . | |||
| ':exp, :del)'); | |||
| } else { | |||
| // Query if user is logged in (insert user id together with other data) | |||
| $q = $db->prepare('INSERT INTO files (hash, originalname, filename, size, date, ' . | |||
| 'expire, delid, user) VALUES (:hash, :orig, :name, :size, :date, ' . | |||
| ':exp, :del, :user)'); | |||
| $q->bindValue(':user', $_SESSION['id'], PDO::PARAM_INT); | |||
| } | |||
| // Common parameters binding | |||
| $q->bindValue(':hash', $file->getSha1(), PDO::PARAM_STR); | |||
| $q->bindValue(':orig', strip_tags($file->name), PDO::PARAM_STR); | |||
| $q->bindValue(':name', $newname, PDO::PARAM_STR); | |||
| $q->bindValue(':size', $file->size, PDO::PARAM_INT); | |||
| $q->bindValue(':date', date('Y-m-d'), PDO::PARAM_STR); | |||
| $q->bindValue(':exp', null, PDO::PARAM_STR); | |||
| $q->bindValue(':del', sha1($file->tempfile), PDO::PARAM_STR); | |||
| $q->execute(); | |||
| return array( | |||
| 'hash' => $file->getSha1(), | |||
| 'name' => $file->name, | |||
| 'url' => POMF_URL.rawurlencode($newname), | |||
| 'size' => $file->size, | |||
| ); | |||
| } | |||
| /** | |||
| * Reorder files array by file. | |||
| * | |||
| * @param $_FILES | |||
| * | |||
| * @return array | |||
| */ | |||
| function diverseArray($files) | |||
| { | |||
| $result = array(); | |||
| foreach ($files as $key1 => $value1) { | |||
| foreach ($value1 as $key2 => $value2) { | |||
| $result[$key2][$key1] = $value2; | |||
| } | |||
| } | |||
| return $result; | |||
| } | |||
| /** | |||
| * Reorganize the $_FILES array into something saner. | |||
| * | |||
| * @param $_FILES | |||
| * | |||
| * @return array | |||
| */ | |||
| function refiles($files) | |||
| { | |||
| $result = array(); | |||
| $files = diverseArray($files); | |||
| foreach ($files as $file) { | |||
| $f = new UploadedFile(); | |||
| $f->name = $file['name']; | |||
| $f->mime = $file['type']; | |||
| $f->size = $file['size']; | |||
| $f->tempfile = $file['tmp_name']; | |||
| $f->error = $file['error']; | |||
| //$f->expire = $file['expire']; | |||
| $result[] = $f; | |||
| } | |||
| return $result; | |||
| } | |||
| $type = isset($_GET['output']) ? $_GET['output'] : 'json'; | |||
| $response = new Response($type); | |||
| if (isset($_FILES['files'])) { | |||
| $uploads = refiles($_FILES['files']); | |||
| try { | |||
| foreach ($uploads as $upload) { | |||
| $res[] = uploadFile($upload); | |||
| } | |||
| $response->send($res); | |||
| } catch (Exception $e) { | |||
| $response->error($e->getCode(), $e->getMessage()); | |||
| } | |||
| } else { | |||
| $response->error(400, 'No input file(s)'); | |||
| } | |||
| @@ -1 +0,0 @@ | |||
| Subproject commit 89649d366da055384288e2c750ee891ece4d6536 | |||
| @@ -1 +0,0 @@ | |||
| Subproject commit db7d47444712bb7e568e3e3881ceec5774675230 | |||
| @@ -0,0 +1,3 @@ | |||
| node_modules | |||
| dist | |||
| dist.zip | |||
| @@ -0,0 +1,3 @@ | |||
| [submodule "moe"] | |||
| path = moe | |||
| url = https://github.com/pomf/moe | |||
| @@ -0,0 +1,6 @@ | |||
| Eric Johansson <neku@pomf.se> | |||
| Eric Johansson <neku@pomf.se> nokonoko <petanull@gmail.com> | |||
| Eliot Whalan <ewhal@pantsu.cat> <contact@pantsu.cat> | |||
| Juuso Lapinlampi <wub@partyvan.eu> | |||
| alchimist <alchimist@nwa.xyz> cenci0 <alchimist94@gmail.com> | |||
| Austin Gillmann <alucard@cuntflaps.me> | |||
| @@ -0,0 +1,16 @@ | |||
| language: php | |||
| php: | |||
| - '5.4' | |||
| - '5.5' | |||
| - '5.6' | |||
| - '7.0' | |||
| - hhvm | |||
| install: | |||
| - source ~/.nvm/nvm.sh | |||
| - nvm ls-remote | |||
| - nvm install stable | |||
| - nvm use stable | |||
| script: | |||
| - make | |||
| @@ -0,0 +1,21 @@ | |||
| Copyright (c) 2013, 2014, 2015 Eric Johansson <neku@pomf.se> | |||
| Copyright (c) 2013, 2014 Peter Lejeck <peter.lejeck@gmail.com> | |||
| Copyright (c) 2015 cenci0 <alchimist94@gmail.com> | |||
| Copyright (c) 2015, 2016 the Pantsu.cat developers <hostmaster@pantsu.cat> | |||
| 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. | |||
| @@ -0,0 +1,161 @@ | |||
| # Pomf | |||
| [](https://travis-ci.org/pomf/pomf) | |||
| [](https://david-dm.org/pomf/pomf) | |||
| [](https://david-dm.org/pomf/pomf#info=devDependencies) | |||
| [](https://raw.githubusercontent.com/pomf/pomf/master/LICENSE) | |||
| Pomf is a simple file uploading and sharing platform. | |||
| ## Features | |||
| - One click uploading, no registration required | |||
| - A minimal, modern web interface | |||
| - Drag & drop supported | |||
| - Upload API with multiple response choices | |||
| - JSON | |||
| - HTML | |||
| - Text | |||
| - CSV | |||
| - Supports [ShareX](https://getsharex.com/) and other screenshot tools | |||
| ### Demo | |||
| See the real world example at [Pantsu.cat](https://pantsu.cat/). | |||
| ## Requirements | |||
| Original development environment is Nginx + PHP5.5 + MySQL, but is confirmed to | |||
| work with Apache 2.4 and newer PHP versions. Should work with any other | |||
| PDO-compatible database. | |||
| ## Install | |||
| For the purposes of this guide, we won't cover setting up Nginx, PHP, MySQL, | |||
| Node, or NPM. So we'll just assume you already have them all running well. | |||
| ### Compiling | |||
| Assuming you already have Node and NPM working, compilation is easy. Use the | |||
| following shell code: | |||
| ```bash | |||
| git clone https://github.com/pomf/pomf | |||
| cd pomf/ | |||
| make | |||
| make install | |||
| ``` | |||
| OR | |||
| ```bash | |||
| make install DESTDIR=/desired/path/for/site | |||
| ``` | |||
| After this, the pomf site is now compressed and set up inside `dist/`, or, if specified, `DESTDIR`. | |||
| ## Configuring | |||
| Front-end related settings, such as the name of the site, and maximum allowable | |||
| file size, are found in `templates/site_variables.json`. Changes made here will | |||
| only take effect after rebuilding the site pages. This may be done by running | |||
| `make` from the root of the site directory. | |||
| Back-end related settings, such as database configuration, and path for uploaded files, are found in `static/php/includes/settings.inc.php`. Changes made here take effect immediately. | |||
| If you intend to allow uploading files larger than 2 MB, you may also need to | |||
| increase POST size limits in `php.ini` and webserver configuration. For PHP, | |||
| modify `upload_max_filesize` and `post_max_size` values. The configuration | |||
| option for nginx webserver is `client_max_body_size`. | |||
| Example nginx configs can be found in confs/. | |||
| ## Using SQLite as DB engine | |||
| We need to create the SQLite database before it may be used by pomf. | |||
| Fortunately, this is incredibly simple. | |||
| First create a directory for the database, e.g. `mkdir /var/db/pomf`. | |||
| Then, create a new SQLite database from the schema, e.g. `sqlite3 /var/db/pomf/pomf.sq3 -init /home/pomf/sqlite_schema.sql`. | |||
| Then, finally, ensure the permissions are correct, e.g. | |||
| ```bash | |||
| chown nginx:nginx /var/db/pomf | |||
| chmod 0750 /var/db/pomf | |||
| chmod 0640 /var/db/pomf/pomf.sq3 | |||
| ``` | |||
| Finally, edit `php/includes/settings.inc.php` to indicate this is the database engine you would like to use. Make the changes outlined below | |||
| ```php | |||
| define('POMF_DB_CONN', '[stuff]'); ---> define('POMF_DB_CONN', 'sqlite:/var/db/pomf/pomf.sq3');` | |||
| define('POMF_DB_USER', '[stuff]'); ---> define('POMF_DB_USER', null); | |||
| define('POMF_DB_PASS', '[stuff]'); ---> define('POMF_DB_PASS', null); | |||
| ``` | |||
| *NOTE: The directory where the SQLite database is stored, must be writable by the web server user* | |||
| ### Apache | |||
| If you are running Apache and want to compress your output when serving files, | |||
| add to your `.htaccess` file: | |||
| AddOutputFilterByType DEFLATE text/html text/plain text/css application/javascript application/x-javascript application/json | |||
| Remember to enable `deflate_module` and `filter_module` modules in your Apache | |||
| configuration file. | |||
| ### Migrating from MySQL to SQLite | |||
| For older versions of Pomf you may want to migrate to SQLite. Fortunately, it is incredibly simple to migrate your database. This may be done on a live server, and should require zero downtime. | |||
| _If doing this on a live server, you way wish to work in a subdirectory (or vhost, or equivelant), so that any complications or mistakes do not affect your main site. | |||
| If you choose not to do so, know that mistakes in the changes outlined below, will only temporarily impact **uploading**, causing **Server error** to be displayed. None of these steps are destructive, and are easily reverted._ | |||
| Run the following commands as root, to dump your database, and make a SQLite database with the contents. | |||
| ```bash | |||
| mkdir /var/db/pomf | |||
| wget -O /tmp/m2s https://github.com/dumblob/mysql2sqlite/raw/master/mysql2sqlite.sh | |||
| mysqldump -u OLD_DB_USER -p OLD_DB_PASS pomf | sh /tmp/m2s | sqlite3 /var/db/pomf/sq3 | |||
| rm /tmp/m2s | |||
| chown -R nginx:nginx /var/db/pomf #replace user as appropriate | |||
| chmod 0750 /var/db/pomf && chmod 0640 /var/db/pomf/sq3 | |||
| ``` | |||
| Edit the file `php/includes/settings.inc.php`, in the subdirectory you just made, making the changes outlined below. | |||
| ```php | |||
| define('POMF_DB_CONN', '[stuff]'); ---> define('POMF_DB_CONN', 'sqlite:/var/db/pomf/pomf.sq3');` | |||
| define('POMF_DB_USER', '[stuff]'); ---> define('POMF_DB_USER', null); | |||
| define('POMF_DB_PASS', '[stuff]'); ---> define('POMF_DB_PASS', null); | |||
| ``` | |||
| Then, run `make` to rebuild the website pages, and copy the new `settings.inc.php` file into place. | |||
| All done! You may disable or uninstall MySQL if you wish. | |||
| ## Getting help | |||
| The Pomf community gathers on IRC. You can also email the maintainer for help. | |||
| - IRC (users): `#pomfret` on Rizon (`irc.rizon.net`) | |||
| - Email: <hostmaster@pantsu.cat> | |||
| ## Contributing | |||
| We'd really like if you can take some time to make sure your coding style is | |||
| consistent with the project. Pomf follows [PHP | |||
| PSR-2](http://www.php-fig.org/psr/psr-2/) and [Airbnb JavaScript | |||
| (ES5)](https://github.com/airbnb/javascript/tree/master/es5) (`airbnb/legacy`) | |||
| coding style guides. We use ESLint and PHPCS tools to enforce these standards. | |||
| You can also help by sending us feature requests or writing documentation and | |||
| tests. | |||
| Thanks! | |||
| ## Credits | |||
| Pomf was created by Eric Johansson and Peter Lejeck for | |||
| [Pomf.se](http://pomf.se/). The software is currently maintained by the | |||
| community. | |||
| ## License | |||
| Pomf is free software, and is released under the terms of the Expat license. See | |||
| `LICENSE`. | |||
| @@ -0,0 +1,241 @@ | |||
| <?php | |||
| /** | |||
| * The Response class is a do-it-all for getting responses out in different | |||
| * formats. | |||
| * | |||
| * @todo Create sub-classes to split and extend this god object. | |||
| */ | |||
| class Response | |||
| { | |||
| /** | |||
| * Indicates response type used for routing. | |||
| * | |||
| * Valid strings are 'csv', 'html', 'json' and 'text'. | |||
| * | |||
| * @var string $type Response type | |||
| */ | |||
| private $type; | |||
| /** | |||
| * Indicates requested response type. | |||
| * | |||
| * Valid strings are 'csv', 'html', 'json', 'gyazo' and 'text'. | |||
| * | |||
| * @param string|null $response_type Response type | |||
| */ | |||
| public function __construct($response_type = null) | |||
| { | |||
| switch ($response_type) { | |||
| case 'csv': | |||
| header('Content-Type: text/csv; charset=UTF-8'); | |||
| $this->type = $response_type; | |||
| break; | |||
| case 'html': | |||
| header('Content-Type: text/html; charset=UTF-8'); | |||
| $this->type = $response_type; | |||
| break; | |||
| case 'json': | |||
| header('Content-Type: application/json; charset=UTF-8'); | |||
| $this->type = $response_type; | |||
| break; | |||
| case 'gyazo': | |||
| header('Content-Type: text/plain; charset=UTF-8'); | |||
| $this->type = 'text'; | |||
| break; | |||
| case 'text': | |||
| header('Content-Type: text/plain; charset=UTF-8'); | |||
| $this->type = $response_type; | |||
| break; | |||
| default: | |||
| header('Content-Type: application/json; charset=UTF-8'); | |||
| $this->type = 'json'; | |||
| $this->error(400, 'Invalid response type. Valid options are: csv, html, json, text.'); | |||
| break; | |||
| } | |||
| } | |||
| /** | |||
| * Routes error messages depending on response type. | |||
| * | |||
| * @param int $code HTTP status code number. | |||
| * @param int $desc Descriptive error message. | |||
| * @return void | |||
| */ | |||
| public function error($code, $desc) | |||
| { | |||
| $response = null; | |||
| switch ($this->type) { | |||
| case 'csv': | |||
| $response = $this->csvError($desc); | |||
| break; | |||
| case 'html': | |||
| $response = $this->htmlError($code, $desc); | |||
| break; | |||
| case 'json': | |||
| $response = $this->jsonError($code, $desc); | |||
| break; | |||
| case 'text': | |||
| $response = $this->textError($code, $desc); | |||
| break; | |||
| } | |||
| http_response_code(500); // "500 Internal Server Error" | |||
| echo $response; | |||
| } | |||
| /** | |||
| * Routes success messages depending on response type. | |||
| * | |||
| * @param mixed[] $files | |||
| * @return void | |||
| */ | |||
| public function send($files) | |||
| { | |||
| $response = null; | |||
| switch ($this->type) { | |||
| case 'csv': | |||
| $response = $this->csvSuccess($files); | |||
| break; | |||
| case 'html': | |||
| $response = $this->htmlSuccess($files); | |||
| break; | |||
| case 'json': | |||
| $response = $this->jsonSuccess($files); | |||
| break; | |||
| case 'text': | |||
| $response = $this->textSuccess($files); | |||
| break; | |||
| } | |||
| http_response_code(200); // "200 OK". Success. | |||
| echo $response; | |||
| } | |||
| /** | |||
| * Indicates with CSV body the request was invalid. | |||
| * | |||
| * @deprecated 2.1.0 Will be renamed to camelCase format. | |||
| * @param int $description Descriptive error message. | |||
| * @return string Error message in CSV format. | |||
| */ | |||
| private static function csvError($description) | |||
| { | |||
| return '"error"'."\r\n"."\"$description\""."\r\n"; | |||
| } | |||
| /** | |||
| * Indicates with CSV body the request was successful. | |||
| * | |||
| * @deprecated 2.1.0 Will be renamed to camelCase format. | |||
| * @param mixed[] $files | |||
| * @return string Success message in CSV format. | |||
| */ | |||
| private static function csvSuccess($files) | |||
| { | |||
| $result = '"name","url","hash","size"'."\r\n"; | |||
| foreach ($files as $file) { | |||
| $result .= '"'.$file['name'].'"'.','. | |||
| '"'.$file['url'].'"'.','. | |||
| '"'.$file['hash'].'"'.','. | |||
| '"'.$file['size'].'"'."\r\n"; | |||
| } | |||
| return $result; | |||
| } | |||
| /** | |||
| * Indicates with HTML body the request was invalid. | |||
| * | |||
| * @deprecated 2.1.0 Will be renamed to camelCase format. | |||
| * @param int $code HTTP status code number. | |||
| * @param int $description Descriptive error message. | |||
| * @return string Error message in HTML format. | |||
| */ | |||
| private static function htmlError($code, $description) | |||
| { | |||
| return '<p>ERROR: ('.$code.') '.$description.'</p>'; | |||
| } | |||
| /** | |||
| * Indicates with HTML body the request was successful. | |||
| * | |||
| * @deprecated 2.1.0 Will be renamed to camelCase format. | |||
| * @param mixed[] $files | |||
| * @return string Success message in HTML format. | |||
| */ | |||
| private static function htmlSuccess($files) | |||
| { | |||
| $result = ''; | |||
| foreach ($files as $file) { | |||
| $result .= '<a href="'.$file['url'].'">'.$file['url'].'</a><br>'; | |||
| } | |||
| return $result; | |||
| } | |||
| /** | |||
| * Indicates with JSON body the request was invalid. | |||
| * | |||
| * @deprecated 2.1.0 Will be renamed to camelCase format. | |||
| * @param int $code HTTP status code number. | |||
| * @param int $description Descriptive error message. | |||
| * @return string Error message in pretty-printed JSON format. | |||
| */ | |||
| private static function jsonError($code, $description) | |||
| { | |||
| return json_encode(array( | |||
| 'success' => false, | |||
| 'errorcode' => $code, | |||
| 'description' => $description, | |||
| ), JSON_PRETTY_PRINT); | |||
| } | |||
| /** | |||
| * Indicates with JSON body the request was successful. | |||
| * | |||
| * @deprecated 2.1.0 Will be renamed to camelCase format. | |||
| * @param mixed[] $files | |||
| * @return string Success message in pretty-printed JSON format. | |||
| */ | |||
| private static function jsonSuccess($files) | |||
| { | |||
| return json_encode(array( | |||
| 'success' => true, | |||
| 'files' => $files, | |||
| ), JSON_PRETTY_PRINT); | |||
| } | |||
| /** | |||
| * Indicates with plain text body the request was invalid. | |||
| * | |||
| * @deprecated 2.1.0 Will be renamed to camelCase format. | |||
| * @param int $code HTTP status code number. | |||
| * @param int $description Descriptive error message. | |||
| * @return string Error message in plain text format. | |||
| */ | |||
| private static function textError($code, $description) | |||
| { | |||
| return 'ERROR: ('.$code.') '.$description; | |||
| } | |||
| /** | |||
| * Indicates with plain text body the request was successful. | |||
| * | |||
| * @deprecated 2.1.0 Will be renamed to camelCase format. | |||
| * @param mixed[] $files | |||
| * @return string Success message in plain text format. | |||
| */ | |||
| private static function textSuccess($files) | |||
| { | |||
| $result = ''; | |||
| foreach ($files as $file) { | |||
| $result .= $file['url']."\n"; | |||
| } | |||
| return $result; | |||
| } | |||
| } | |||
| @@ -0,0 +1,63 @@ | |||
| <?php | |||
| /** | |||
| * Returns a human readable error description for file upload errors. | |||
| * | |||
| * @author Dan Brown <danbrown@php.net> | |||
| * @author Michiel Thalen | |||
| * @copyright Copyright © 1997 - 2016 by the PHP Documentation Group | |||
| * @license | |||
| * UploadException is licensed under a Creative Commons Attribution 3.0 License | |||
| * or later. | |||
| * | |||
| * Based on a work at | |||
| * https://secure.php.net/manual/en/features.file-upload.errors.php#89374. | |||
| * | |||
| * You should have received a copy of the Creative Commons Attribution 3.0 | |||
| * License with this program. If not, see | |||
| * <https://creativecommons.org/licenses/by/3.0/>. | |||
| */ | |||
| class UploadException extends Exception | |||
| { | |||
| public function __construct($code) | |||
| { | |||
| $message = $this->codeToMessage($code); | |||
| parent::__construct($message, 500); | |||
| } | |||
| private function codeToMessage($code) | |||
| { | |||
| switch ($code) { | |||
| case UPLOAD_ERR_INI_SIZE: | |||
| $message = 'The uploaded file exceeds the upload_max_filesize directive in php.ini'; | |||
| break; | |||
| case UPLOAD_ERR_FORM_SIZE: | |||
| $message = 'The uploaded file exceeds the MAX_FILE_SIZE directive that was '. | |||
| 'specified in the HTML form'; | |||
| break; | |||
| case UPLOAD_ERR_PARTIAL: | |||
| $message = 'The uploaded file was only partially uploaded'; | |||
| break; | |||
| case UPLOAD_ERR_NO_FILE: | |||
| $message = 'No file was uploaded'; | |||
| break; | |||
| case UPLOAD_ERR_NO_TMP_DIR: | |||
| $message = 'Missing a temporary folder'; | |||
| break; | |||
| case UPLOAD_ERR_CANT_WRITE: | |||
| $message = 'Failed to write file to disk'; | |||
| break; | |||
| case UPLOAD_ERR_EXTENSION: | |||
| $message = 'File upload stopped by extension'; | |||
| break; | |||
| default: | |||
| $message = 'Unknown upload error'; | |||
| break; | |||
| } | |||
| return $message; | |||
| } | |||
| } | |||
| @@ -0,0 +1,32 @@ | |||
| <?php | |||
| class UploadedFile | |||
| { | |||
| /* Public attributes */ | |||
| public $name; | |||
| public $mime; | |||
| public $size; | |||
| public $tempfile; | |||
| public $error; | |||
| /** | |||
| * SHA-1 checksum | |||
| * | |||
| * @var string 40 digit hexadecimal hash (160 bits) | |||
| */ | |||
| private $sha1; | |||
| /** | |||
| * Generates the SHA-1 or returns the cached SHA-1 hash for the file. | |||
| * | |||
| * @return string|false $sha1 | |||
| */ | |||
| public function getSha1() | |||
| { | |||
| if (!$this->sha1) { | |||
| $this->sha1 = sha1_file($this->tempfile); | |||
| } | |||
| return $this->sha1; | |||
| } | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| <?php | |||
| // Array of image paths, feel free to add/remove to/from this list | |||
| $images = array( | |||
| 'img/2.png', | |||
| 'img/3.png', | |||
| 'img/4.png', | |||
| 'img/5.png', | |||
| 'img/6.png', | |||
| 'img/7.png', | |||
| 'img/8.png', | |||
| 'img/9.png', | |||
| 'img/10.png', | |||
| ); | |||
| // Redirect to a random image from the above array using status code "303 See Other" | |||
| if (headers_sent() === false) { | |||
| header('Location: '.$images[array_rand($images)], true, 303); | |||
| } | |||
| @@ -0,0 +1,30 @@ | |||
| <?php | |||
| /** | |||
| * Prepares a PDO connection between Pomf and a database server. | |||
| * | |||
| * @copyright Copyright (c) 2013 Peter Lejeck <peter.lejeck@gmail.com> | |||
| * | |||
| * 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. | |||
| */ | |||
| require_once 'settings.inc.php'; | |||
| /* NOTE: we don't have to unref the PDO because we're not long-running */ | |||
| $db = new PDO(POMF_DB_CONN, POMF_DB_USER, POMF_DB_PASS); | |||
| @@ -0,0 +1,118 @@ | |||
| <?php | |||
| /** | |||
| * User configurable settings for Pomf. | |||
| * | |||
| * @copyright Copyright (c) 2013, 2014 Peter Lejeck <peter.lejeck@gmail.com> | |||
| * @copyright Copyright (c) 2015 cenci0 <alchimist94@gmail.com> | |||
| * @copyright Copyright (c) 2015, 2016 the Pantsu.cat developers | |||
| * <hostmaster@pantsu.cat> | |||
| * | |||
| * 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. | |||
| */ | |||
| /** | |||
| * PDO connection socket | |||
| * | |||
| * Database connection to use for communication. Currently, MySQL is the only | |||
| * DSN prefix supported. | |||
| * | |||
| * @see http://php.net/manual/en/ref.pdo-mysql.connection.php PHP manual for | |||
| * PDO_MYSQL DSN. | |||
| * @param string POMF_DB_CONN DSN:host|unix_socket=hostname|path;dbname=database | |||
| */ | |||
| define('POMF_DB_CONN', 'mysql:unix_socket=/var/run/mysqld/mysqld.sock;dbname=pomf'); | |||
| /** | |||
| * PDO database login credentials | |||
| */ | |||
| /** @param string POMF_DB_NAME Database username */ | |||
| define('POMF_DB_USER', 'pomf'); | |||
| /** @param string POMF_DB_PASS Database password */ | |||
| define('POMF_DB_PASS', ''); | |||
| /** | |||
| * File system location where to store uploaded files | |||
| * | |||
| * @param string Path to directory with trailing delimiter | |||
| */ | |||
| define('POMF_FILES_ROOT', '/mnt/pantsu/http/files/'); | |||
| /** | |||
| * Maximum number of iterations while generating a new filename | |||
| * | |||
| * Pomf uses an algorithm to generate random filenames. Sometimes a file may | |||
| * exist under a randomly generated filename, so we count tries and keep trying. | |||
| * If this value is exceeded, we give up trying to generate a new filename. | |||
| * | |||
| * @param int POMF_FILES_RETRIES Number of attempts to retry | |||
| */ | |||
| define('POMF_FILES_RETRIES', 15); | |||
| /** | |||
| * The length of generated filename (without file extension) | |||
| * | |||
| * @param int POMF_FILES_LENGTH Number of random alphabetical ASCII characters | |||
| * to use | |||
| */ | |||
| define('POMF_FILES_LENGTH', 6); | |||
| /** | |||
| * URI to prepend to links for uploaded files | |||
| * | |||
| * @param string POMF_URL URI with trailing delimiter | |||
| */ | |||
| define('POMF_URL', 'https://i.pantsu.cat/'); | |||
| /** | |||
| * URI for filename generation | |||
| * | |||
| * @param string characters to be used in generateName() | |||
| */ | |||
| define('ID_CHARSET', 'abcdefghijklmnopqrstuvwxyz'); | |||
| /** | |||
| * Filtered mime types | |||
| * @param string[] $FILTER_MIME allowed/blocked mime types | |||
| */ | |||
| $FILTER_MIME = array(); | |||
| /** | |||
| * Filter mode: whitelist (true) or blacklist (false) | |||
| * @param bool $FILTER_MODE mime type filter mode | |||
| */ | |||
| $FILTER_MODE = false; | |||
| /** | |||
| * Double dot file extensions | |||
| * | |||
| * Pomf keeps the last file extension for the uploaded file. In other words, an | |||
| * uploaded file with `.tar.gz` extension will be given a random filename which | |||
| * ends in `.gz` unless configured here to ignore discards for `.tar.gz`. | |||
| * | |||
| * @param string[] $doubledots Array of double dot file extensions strings | |||
| * without the first prefixing dot | |||
| */ | |||
| $doubledots = array_map('strrev', array( | |||
| 'tar.gz', | |||
| 'tar.bz', | |||
| 'tar.bz2', | |||
| 'tar.xz', | |||
| 'user.js', | |||
| )); | |||
| @@ -0,0 +1,236 @@ | |||
| <?php | |||
| session_start(); | |||
| /** | |||
| * Handles POST uploads, generates filenames, moves files around and commits | |||
| * uploaded metadata to database. | |||
| */ | |||
| require_once 'classes/Response.class.php'; | |||
| require_once 'classes/UploadException.class.php'; | |||
| require_once 'classes/UploadedFile.class.php'; | |||
| require_once 'includes/database.inc.php'; | |||
| /** | |||
| * Generates a random name for the file, retrying until we get an unused one. | |||
| * | |||
| * @param UploadedFile $file | |||
| * | |||
| * @return string | |||
| */ | |||
| function generateName($file) | |||
| { | |||
| global $db; | |||
| global $doubledots; | |||
| // We start at N retries, and --N until we give up | |||
| $tries = POMF_FILES_RETRIES; | |||
| $length = POMF_FILES_LENGTH; | |||
| $ext = pathinfo($file->name, PATHINFO_EXTENSION); | |||
| // Check if extension is a double-dot extension and, if true, override $ext | |||
| $revname = strrev($file->name); | |||
| foreach ($doubledots as $ddot) { | |||
| if (stripos($revname, $ddot) === 0) { | |||
| $ext = strrev($ddot); | |||
| } | |||
| } | |||
| do { | |||
| // Iterate until we reach the maximum number of retries | |||
| if ($tries-- === 0) { | |||
| throw new Exception( | |||
| 'Gave up trying to find an unused name', | |||
| 500 | |||
| ); // HTTP status code "500 Internal Server Error" | |||
| } | |||
| $chars = ID_CHARSET; | |||
| $name = ''; | |||
| for ($i = 0; $i < $length; ++$i) { | |||
| $name .= $chars[mt_rand(0, strlen($chars))]; | |||
| } | |||
| // Add the extension to the file name | |||
| if (isset($ext) && $ext !== '') { | |||
| $name .= '.'.$ext; | |||
| } | |||
| // Check if a file with the same name does already exist in the database | |||
| $q = $db->prepare('SELECT COUNT(filename) FROM files WHERE filename = (:name)'); | |||
| $q->bindValue(':name', $name, PDO::PARAM_STR); | |||
| $q->execute(); | |||
| $result = $q->fetchColumn(); | |||
| // If it does, generate a new name | |||
| } while ($result > 0); | |||
| return $name; | |||
| } | |||
| /** | |||
| * Handles the uploading and db entry for a file. | |||
| * | |||
| * @param UploadedFile $file | |||
| * | |||
| * @return array | |||
| */ | |||
| function uploadFile($file) | |||
| { | |||
| global $db; | |||
| global $FILTER_MODE; | |||
| global $FILTER_MIME; | |||
| // Handle file errors | |||
| if ($file->error) { | |||
| throw new UploadException($file->error); | |||
| } | |||
| // Check if mime type is blocked | |||
| if (!empty($FILTER_MIME)) { | |||
| if ($FILTER_MODE == true) { //whitelist mode | |||
| if (!in_array($file->mime, $FILTER_MIME)) { | |||
| throw new UploadException(UPLOAD_ERR_EXTENSION); | |||
| } | |||
| } else { //blacklist mode | |||
| if (in_array($file->mime, $FILTER_MIME)) { | |||
| throw new UploadException(UPLOAD_ERR_EXTENSION); | |||
| } | |||
| } | |||
| } | |||
| // Check if a file with the same hash and size (a file which is the same) | |||
| // does already exist in the database; if it does, return the proper link | |||
| // and data. PHP deletes the temporary file just uploaded automatically. | |||
| $q = $db->prepare('SELECT filename, COUNT(*) AS count FROM files WHERE hash = (:hash) '. | |||
| 'AND size = (:size)'); | |||
| $q->bindValue(':hash', $file->getSha1(), PDO::PARAM_STR); | |||
| $q->bindValue(':size', $file->size, PDO::PARAM_INT); | |||
| $q->execute(); | |||
| $result = $q->fetch(); | |||
| if ($result['count'] > 0) { | |||
| return array( | |||
| 'hash' => $file->getSha1(), | |||
| 'name' => $file->name, | |||
| 'url' => POMF_URL.rawurlencode($result['filename']), | |||
| 'size' => $file->size, | |||
| ); | |||
| } | |||
| // Generate a name for the file | |||
| $newname = generateName($file); | |||
| // Store the file's full file path in memory | |||
| $uploadFile = POMF_FILES_ROOT . $newname; | |||
| // Attempt to move it to the static directory | |||
| if (!move_uploaded_file($file->tempfile, $uploadFile)) { | |||
| throw new Exception( | |||
| 'Failed to move file to destination', | |||
| 500 | |||
| ); // HTTP status code "500 Internal Server Error" | |||
| } | |||
| // Need to change permissions for the new file to make it world readable | |||
| if (!chmod($uploadFile, 0644)) { | |||
| throw new Exception( | |||
| 'Failed to change file permissions', | |||
| 500 | |||
| ); // HTTP status code "500 Internal Server Error" | |||
| } | |||
| // Add it to the database | |||
| if (empty($_SESSION['id'])) { | |||
| // Query if user is NOT logged in | |||
| $q = $db->prepare('INSERT INTO files (hash, originalname, filename, size, date, ' . | |||
| 'expire, delid) VALUES (:hash, :orig, :name, :size, :date, ' . | |||
| ':exp, :del)'); | |||
| } else { | |||
| // Query if user is logged in (insert user id together with other data) | |||
| $q = $db->prepare('INSERT INTO files (hash, originalname, filename, size, date, ' . | |||
| 'expire, delid, user) VALUES (:hash, :orig, :name, :size, :date, ' . | |||
| ':exp, :del, :user)'); | |||
| $q->bindValue(':user', $_SESSION['id'], PDO::PARAM_INT); | |||
| } | |||
| // Common parameters binding | |||
| $q->bindValue(':hash', $file->getSha1(), PDO::PARAM_STR); | |||
| $q->bindValue(':orig', strip_tags($file->name), PDO::PARAM_STR); | |||
| $q->bindValue(':name', $newname, PDO::PARAM_STR); | |||
| $q->bindValue(':size', $file->size, PDO::PARAM_INT); | |||
| $q->bindValue(':date', date('Y-m-d'), PDO::PARAM_STR); | |||
| $q->bindValue(':exp', null, PDO::PARAM_STR); | |||
| $q->bindValue(':del', sha1($file->tempfile), PDO::PARAM_STR); | |||
| $q->execute(); | |||
| return array( | |||
| 'hash' => $file->getSha1(), | |||
| 'name' => $file->name, | |||
| 'url' => POMF_URL.rawurlencode($newname), | |||
| 'size' => $file->size, | |||
| ); | |||
| } | |||
| /** | |||
| * Reorder files array by file. | |||
| * | |||
| * @param $_FILES | |||
| * | |||
| * @return array | |||
| */ | |||
| function diverseArray($files) | |||
| { | |||
| $result = array(); | |||
| foreach ($files as $key1 => $value1) { | |||
| foreach ($value1 as $key2 => $value2) { | |||
| $result[$key2][$key1] = $value2; | |||
| } | |||
| } | |||
| return $result; | |||
| } | |||
| /** | |||
| * Reorganize the $_FILES array into something saner. | |||
| * | |||
| * @param $_FILES | |||
| * | |||
| * @return array | |||
| */ | |||
| function refiles($files) | |||
| { | |||
| $result = array(); | |||
| $files = diverseArray($files); | |||
| foreach ($files as $file) { | |||
| $f = new UploadedFile(); | |||
| $f->name = $file['name']; | |||
| $f->mime = $file['type']; | |||
| $f->size = $file['size']; | |||
| $f->tempfile = $file['tmp_name']; | |||
| $f->error = $file['error']; | |||
| //$f->expire = $file['expire']; | |||
| $result[] = $f; | |||
| } | |||
| return $result; | |||
| } | |||
| $type = isset($_GET['output']) ? $_GET['output'] : 'json'; | |||
| $response = new Response($type); | |||
| if (isset($_FILES['files'])) { | |||
| $uploads = refiles($_FILES['files']); | |||
| try { | |||
| foreach ($uploads as $upload) { | |||
| $res[] = uploadFile($upload); | |||
| } | |||
| $response->send($res); | |||
| } catch (Exception $e) { | |||
| $response->error($e->getCode(), $e->getMessage()); | |||
| } | |||
| } else { | |||
| $response->error(400, 'No input file(s)'); | |||
| } | |||