* add force download parameter
* HTTPS for selfpaste bash client
* multiple secrets
-* extending allowed filetypes
\ No newline at end of file
+* extending allowed filetypes
+* creation or even access with basic auth
+* flood protection
\ No newline at end of file
- You provide the service by hosting it. Your are responsible for it!
- Change your secret often
-Make sure DEBUG is false for production.
\ No newline at end of file
+Make sure DEBUG is false for production.
+
+Protect the storage location from direct access. Default solved with a .htaccess
+file. Better solution is to move the location outside the webroot.
\ No newline at end of file
}
# static helper class
-require 'lib/summoner.class.php';
+require_once 'lib/summoner.class.php';
# config file
-require 'config.php';
+require_once 'config.php';
+# upload / file handling
+require_once 'lib/mancubus.class.php';
$_short = false;
if(isset($_GET['s']) && !empty($_GET['s'])) {
$httpResponseCode = 404;
$contentBody = 'File not found.';
- $_requestFile = Summoner::createStoragePath($_short);
- $_requestFile .= $_short;
+ $_t = Summoner::b64sl_unpack_id($_short);
+ $_t = (string)$_t;
+ $_p = Summoner::forwardslashStringToPath($_t);
+ $_requestFile = Summoner::endsWith(SELFPASTE_UPLOAD_DIR,'/') ? SELFPASTE_UPLOAD_DIR : SELFPASTE_UPLOAD_DIR.'/';
+ $_requestFile .= $_p;
+ $_requestFile .= $_t;
if(is_readable($_requestFile)) {
$contentBody = $_requestFile;
$httpResponseCode = 200;
$_file = $_FILES['pasty'];
- $_file['short'] = Summoner::createShort();
- $_file['shortUrl'] = SELFPASTE_URL.'/'.$_file['short'];
- $_file['storagepath'] = Summoner::createStoragePath($_file['short']);
-
- $_checks = array('checkFileUploadStatus','checkAllowedFiletype','checkStorage','moveUploadedPasteFile');
- $_do['status'] = false;
- foreach($_checks as $_check) {
- if(method_exists('Summoner',$_check)) {
- $_do = Summoner::$_check($_file);
- $_message = $_do['message'];
- if($_do['status'] === true) {
- $httpResponseCode = 200;
- }
- else {
- $httpResponseCode = 400;
- break;
- }
+ $_fileObj = new Mancubus();
+ if($_fileObj->load($_FILES['pasty']) === true) {
+ $_fileObj->setSaveFilename();
+ $_fileObj->setShort();
+ $_fileObj->setStoragePath();
+ $_fileObj->setShortURL();
+
+ $_do = $_fileObj->process();
+ $_message = $_do['message'];
+ if($_do['status'] === true) {
+ $httpResponseCode = 200;
}
}
--- /dev/null
+<?php
+/**
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the COMMON DEVELOPMENT AND DISTRIBUTION LICENSE
+ *
+ * You should have received a copy of the
+ * COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
+ * along with this program. If not, see http://www.sun.com/cddl/cddl.html
+ *
+ * 2019 https://www.bananas-playground.net/projekt/selfpaste
+ */
+
+/**
+ * Handles the upload and the file itself
+ */
+class Mancubus {
+
+ private $_uploadedData;
+ private $_short;
+ private $_saveFilename;
+ private $_storagePath;
+ private $_shortURL;
+
+ /**
+ * Mancubus constructor.
+ */
+ function __construct() {
+ }
+
+ /**
+ * Requires a single upload from $_FILES
+ * @see https://www.php.net/manual/en/features.file-upload.post-method.php
+ * @param $file array
+ * @return bool
+ */
+ public function load($file) {
+ $ret = false;
+
+ if(isset($file['name'])
+ && isset($file['type'])
+ && isset($file['size'])
+ && isset($file['tmp_name'])
+ && isset($file['error'])
+ ) {
+ $this->_uploadedData = $file;
+ $ret = true;
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Either set short to given string
+ * or create from _saveFilename. In this case _saveFilename is a number
+ * @param string $short
+ */
+ public function setShort($short='') {
+ if($short != '') {
+ $this->_short = $short;
+ }
+ elseif(!empty($this->_saveFilename)) {
+ $this->_short = Summoner::b64sl_pack_id($this->_saveFilename);
+ }
+ }
+
+ /**
+ * Either set _saveFilename to given string
+ * or create from a random number. In this case _short needs this as a base
+ * @param string $string
+ * @throws Exception
+ */
+ public function setSaveFilename($string='') {
+ if($string != '') {
+ $this->_saveFilename = $string;
+ }
+ else {
+ $r = random_int(1000, 9999);
+ $this->_saveFilename = (string)$r;
+ }
+ }
+
+ /**
+ * Set _shortURL to given string
+ * or create based on SELFPASTE_URL and _short
+ * @param string $string
+ */
+ public function setShortURL($string='') {
+ if($string != '') {
+ $this->_shortURL = $string;
+ }
+ elseif(!empty($this->_short)) {
+ $this->_shortURL = SELFPASTE_URL.'/'.$this->_short;
+ }
+ }
+
+ /**
+ * set the right storage path based on _saveFilename
+ * and SELFPASTE_UPLOAD_DIR
+ */
+ public function setStoragePath() {
+ $string = $this->_saveFilename;
+
+ if(!empty($string)) {
+ $p = SELFPASTE_UPLOAD_DIR.'/';
+ $p .= Summoner::forwardslashStringToPath($string);
+ $this->_storagePath = $p;
+ }
+ }
+
+ public function process() {
+ $ret = array(
+ 'message' => '',
+ 'status' => false
+ );
+
+ try {
+ $ret = $this->_checkFileUploadStatus();
+ $ret = $this->_checkAllowedFiletype();
+ $ret = $this->_checkStorage();
+ $ret = $this->_moveUploadedFile();
+ }
+ catch (Exception $e) {
+ $ret['message'] = $e->getMessage();
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Check if the POST upload worked
+ * @return array message,status
+ * @throws Exception
+ */
+ private function _checkFileUploadStatus() {
+ $check = Summoner::checkFileUploadStatus($this->_uploadedData['error']);
+
+ if($check['status'] === true) {
+ # check has the structure we want already
+ return $check;
+ }
+ else {
+ throw new Exception($check['message']);
+ }
+ }
+
+ /**
+ * Check if the uploaded file matches the allowed filetypes
+ * @return array message,status
+ * @throws Exception
+ */
+ private function _checkAllowedFiletype() {
+ $message = "Filetype not supported";
+ $status = false;
+
+ $workWith = $this->_uploadedData['tmp_name'];
+ if(!empty($workWith)) {
+ $finfo = finfo_open(FILEINFO_MIME_TYPE);
+ $mime = finfo_file($finfo, $workWith);
+ finfo_close($finfo);
+ if(strpos(SELFPASTE_ALLOWED_FILETYPES,$mime) !== false) {
+ $status = true;
+ $message = "Filetype allowed";
+ }
+ else {
+ if(DEBUG) $message .= " $mime";
+ throw new Exception($message);
+ }
+ } else {
+ throw new Exception($message);
+ }
+
+ return array(
+ 'message' => $message,
+ 'status' => $status
+ );
+ }
+
+ private function _checkStorage() {
+ $message = "File storage failure";
+ $status = false;
+
+ $workwith = $this->_storagePath;
+ if(is_writable(SELFPASTE_UPLOAD_DIR)) {
+ if (mkdir($workwith,0777,true)) {
+ $message = "File storage creation success";
+ $status = true;
+ }
+ else {
+ if(DEBUG) $message .= " ".$workwith;
+ throw new Exception($message);
+ }
+ }
+ else {
+ throw new Exception('Storage location not writeable');
+ }
+
+ return array(
+ 'message' => $message,
+ 'status' => $status
+ );
+ }
+
+ /**
+ * Move the tmp_file from _uploadedData to the new location
+ * provided by _storagePath and _saveFilename
+ * @return array
+ * @throws Exception
+ */
+ private function _moveUploadedFile() {
+ $message = "File storage failure";
+ $status = false;
+
+ $workwithPath = $this->_storagePath;
+ $workwithFilename = $this->_saveFilename;
+
+ if(!empty($workwithPath) && !empty($workwithFilename)) {
+ $_newFilename = Summoner::endsWith($workwithPath,'/') ? $workwithPath : $workwithPath.'/';
+ $_newFilename .= $workwithFilename;
+ if(move_uploaded_file($this->_uploadedData['tmp_name'], $_newFilename)) {
+ $status = true;
+ $message = $this->_shortURL;
+ }
+ else {
+ if(DEBUG) $message .= " $_newFilename";
+ throw new Exception($message);
+ }
+ }
+ else {
+ throw new Exception('Failing requirements for saving');
+ }
+
+ return array(
+ 'message' => $message,
+ 'status' => $status
+ );
+ }
+}
\ No newline at end of file
/**
- * Simple helper to detect the $_FILE upload status
- * Expects an array from $_FILE
- * @param $file
+ * Simple helper to detect the $_FILES upload status
+ * Expects the error value from $_FILES['error']
+ * @param $error
* @return array
*/
- static function checkFileUploadStatus($file) {
+ static function checkFileUploadStatus($error) {
$message = "Unknown upload error";
$status = false;
- if(isset($file['error'])) {
- switch ($file['error']) {
- case UPLOAD_ERR_OK:
- $message = "There is no error, the file uploaded with success.";
- $status = true;
- break;
- 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;
- }
- }
-
- return array(
- 'message' => $message,
- 'status' => $status
- );
- }
-
- /**
- * Simple helper to detect the $_FILE type
- * Expects an array from $_FILE
- *
- * @see https://www.php.net/manual/en/intro.fileinfo.php
- *
- * @param $file
- * @return array
- */
- static function checkAllowedFiletype($file) {
- $message = "Filetype not supported";
- $status = false;
-
- if(isset($file['tmp_name'])) {
- $finfo = finfo_open(FILEINFO_MIME_TYPE);
- $mime = finfo_file($finfo, $file['tmp_name']);
- finfo_close($finfo);
- if(strpos(SELFPASTE_ALLOWED_FILETYPES,$mime) !== false) {
- $status = true;
- $message = "Filetype allowed";
- }
- if(DEBUG) $message .= " $mime";
- }
-
- return array(
- 'message' => $message,
- 'status' => $status
- );
- }
-
- /**
- * Simple helper to create and make sure the storage
- * location is available
- * Expects an array from $_FILE
- * with an extra key = storagepath
- *
- * @param $file
- * @return array
- */
- static function checkStorage($file) {
- $message = "File storage failure";
- $status = false;
-
- if(isset($file['storagepath']) && !empty($file['storagepath'])
- && is_writable(SELFPASTE_UPLOAD_DIR)) {
- if (mkdir($file['storagepath'],0777,true)) {
- $message = "File storage creation success";
+ switch ($error) {
+ case UPLOAD_ERR_OK:
+ $message = "There is no error, the file uploaded with success.";
$status = true;
- }
- }
-
- if(DEBUG) $message .= " ".$file['storagepath'];
-
- return array(
- 'message' => $message,
- 'status' => $status
- );
- }
-
- /**
- * move the uploaded file.
- * Depends on the _FILES info and the keys
- * storagepath, short, shortUrl
- * @param $file
- * @return array
- */
- static function moveUploadedPasteFile($file) {
- $message = "File storage failure";
- $status = false;
- //shortUrl
-
- if(isset($file['storagepath']) && !empty($file['storagepath'])
- && isset($file['short']) && !empty($file['short'])) {
- $_newFilename = self::endsWith($file['storagepath'],'/') ? $file['storagepath'] : $file['storagepath'].'/';
- $_newFilename .= $file['short'];
- if(move_uploaded_file($file['tmp_name'], $_newFilename)) {
- $status = true;
- $message = $file['shortUrl'];
- }
-
- if(DEBUG) $message .= " $_newFilename";
+ break;
+ 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;
}
return array(
);
}
- /**
- * Simple helper to create a new name
- *
- * @return string
- * @throws Exception
- */
- static function createShort() {
- $idstring = random_int(1000, 9999);
- return self::b64sl_pack_id($idstring);
- }
-
/**
* create a short string based on a integer
*
* @see https://www.jwz.org/base64-shortlinks/
- *
* @return string
*/
static function b64sl_pack_id($id) {
return $id;
}
+ /**
+ * Decode a base64-encoded big-endian integer of up to 64 bits.
+ *
+ * @see https://www.jwz.org/base64-shortlinks/
+ * @param $id
+ * @return false|int|string|string[]
+ */
+ static function b64sl_unpack_id($id) {
+ $id = str_replace ('-', '+', $id); // decode URL-unsafe "+" "/"
+ $id = str_replace ('_', '/', $id);
+ $id = base64_decode ($id);
+ while (strlen($id) < 8) { $id = "\000$id"; } // pad with leading NULs
+ $a = unpack ('N*', $id); // 32 bit big endian
+ $id = ($a[1] << 32) | $a[2]; // pack top and bottom word
+ return $id;
+ }
+
/**
* create based on the given string a path
* each char in string is a dir
- * and add SELFPASTE_UPLOAD_DIR
- * asd -> SELFPASTE_UPLOAD_DIR/a/s/d
+ * asdef -> a/s/d/e/f/
* @param $string
- * @return bool|string
+ * @return string
*/
- static function createStoragePath($string) {
- $p = false;
-
- if(!empty($string) && is_writable(SELFPASTE_UPLOAD_DIR)) {
- $p = SELFPASTE_UPLOAD_DIR.'/';
- for($i=0;$i<strlen($string);$i++) {
- $p .= $string[$i]."/";
+ static function forwardslashStringToPath($string) {
+ $ret = '';
+ if(!empty($string)) {
+ for ($i = 0; $i < strlen($string); $i++) {
+ $ret .= $string[$i] . "/";
}
}
-
- return $p;
+ return $ret;
}
}