_init(); // create new paste or comment if ( (array_key_exists('data', $_POST) && !empty($_POST['data'])) || (array_key_exists('attachment', $_POST) && !empty($_POST['attachment'])) ) { $this->_create(); } // delete an existing paste elseif (!empty($_GET['deletetoken']) && !empty($_GET['pasteid'])) { $this->_delete($_GET['pasteid'], $_GET['deletetoken']); } // display an existing paste elseif (!empty($_SERVER['QUERY_STRING'])) { $this->_read($_SERVER['QUERY_STRING']); } // output JSON or HTML if (strlen($this->_json)) { header('Content-type: application/json'); echo $this->_json; } else { $this->_view(); } } /** * initialize zerobin * * @access private * @return void */ private function _init() { foreach (array('cfg', 'lib') as $dir) { if (!is_file(PATH . $dir . DIRECTORY_SEPARATOR . '.htaccess')) file_put_contents( PATH . $dir . DIRECTORY_SEPARATOR . '.htaccess', 'Allow from none' . PHP_EOL . 'Deny from all'. PHP_EOL, LOCK_EX ); } $this->_conf = new configuration; $this->_model = new model($this->_conf); } /** * Store new paste or comment * * POST contains one or both: * data = json encoded SJCL encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct) * attachment = json encoded SJCL encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct) * * All optional data will go to meta information: * expire (optional) = expiration delay (never,5min,10min,1hour,1day,1week,1month,1year,burn) (default:never) * formatter (optional) = format to display the paste as (plaintext,syntaxhighlighting,markdown) (default:syntaxhighlighting) * burnafterreading (optional) = if this paste may only viewed once ? (0/1) (default:0) * opendiscusssion (optional) = is the discussion allowed on this paste ? (0/1) (default:0) * attachmentname = json encoded SJCL encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct) * nickname (optional) = in discussion, encoded SJCL encrypted text nickname of author of comment (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct) * parentid (optional) = in discussion, which comment this comment replies to. * pasteid (optional) = in discussion, which paste this comment belongs to. * * @access private * @return string */ private function _create() { $error = false; // Ensure last paste from visitors IP address was more than configured amount of seconds ago. trafficlimiter::setConfiguration($this->_conf); if (!trafficlimiter::canPass()) return $this->_return_message( 1, i18n::_( 'Please wait %d seconds between each post.', $this->_conf->getKey('limit', 'traffic') ) ); $has_attachment = array_key_exists('attachment', $_POST); $has_attachmentname = $has_attachment && array_key_exists('attachmentname', $_POST) && !empty($_POST['attachmentname']); $data = array_key_exists('data', $_POST) ? $_POST['data'] : ''; $attachment = $has_attachment ? $_POST['attachment'] : ''; $attachmentname = $has_attachmentname ? $_POST['attachmentname'] : ''; // Ensure content is not too big. $sizelimit = $this->_conf->getKey('sizelimit'); if ( strlen($data) + strlen($attachment) + strlen($attachmentname) > $sizelimit ) return $this->_return_message( 1, i18n::_( 'Paste is limited to %s of encrypted data.', filter::size_humanreadable($sizelimit) ) ); // The user posts a comment. if ( array_key_exists('parentid', $_POST) && !empty($_POST['parentid']) && array_key_exists('pasteid', $_POST) && !empty($_POST['pasteid']) ) { $paste = $this->_model->getPaste($_POST['pasteid']); if ($paste->exists()) { try { $comment = $paste->getComment($_POST['parentid']); if (array_key_exists('nickname', $_POST) && !empty($_POST['nickname']) ) $comment->setNickname($_POST['nickname']); $comment->setData($data); $comment->store(); } catch(Exception $e) { return $this->_return_message(1, $e->getMessage()); } $this->_return_message(0, $comment->getId()); } else { $this->_return_message(1, 'Invalid data.'); } } // The user posts a standard paste. else { $paste = $this->_model->getPaste(); try { if ($has_attachment) { $paste->setAttachment($attachment); if ($has_attachmentname) $paste->setAttachmentName($attachmentname); } if (array_key_exists('expire', $_POST) && !empty($_POST['expire']) ) $paste->setExpiration($_POST['expire']); if (array_key_exists('burnafterreading', $_POST) && !empty($_POST['burnafterreading']) ) $paste->setBurnafterreading($_POST['burnafterreading']); if (array_key_exists('opendiscussion', $_POST) && !empty($_POST['opendiscussion']) ) $paste->setOpendiscussion($_POST['opendiscussion']); if (array_key_exists('formatter', $_POST) && !empty($_POST['formatter']) ) $paste->setFormatter($_POST['formatter']); $paste->setData($data); $paste->store(); } catch (Exception $e) { return $this->_return_message(1, $e->getMessage()); } $this->_return_message(0, $paste->getId(), array('deletetoken' => $paste->getDeleteToken())); } } /** * Delete an existing paste * * @access private * @param string $dataid * @param string $deletetoken * @return void */ private function _delete($dataid, $deletetoken) { try { $paste = $this->_model->getPaste($dataid); if ($paste->exists()) { // accessing this property ensures that the paste would be // deleted if it has already expired $burnafterreading = $paste->isBurnafterreading(); if ($deletetoken == 'burnafterreading') { if ($burnafterreading) { $paste->delete(); $this->_return_message(0, $dataid); } else { $this->_return_message(1, 'Paste is not of burn-after-reading type.'); } } else { // Make sure the token is valid. serversalt::setPath($this->_conf->getKey('dir', 'traffic')); if (filter::slow_equals($deletetoken, $paste->getDeleteToken())) { // Paste exists and deletion token is valid: Delete the paste. $paste->delete(); $this->_status = 'Paste was properly deleted.'; } else { $this->_error = 'Wrong deletion token. Paste was not deleted.'; } } } else { $this->_error = self::GENERIC_ERROR; } } catch (Exception $e) { $this->_error = $e->getMessage(); } } /** * Read an existing paste or comment * * @access private * @param string $dataid * @return void */ private function _read($dataid) { $isJson = false; if (($pos = strpos($dataid, '&json')) !== false) { $isJson = true; $dataid = substr($dataid, 0, $pos); } try { $paste = $this->_model->getPaste($dataid); if ($paste->exists()) { // The paste itself is the first in the list of encrypted messages. $messages = array_merge( array($paste->get()), $paste->getComments() ); $this->_data = json_encode($messages); } else { $this->_error = self::GENERIC_ERROR; } } catch (Exception $e) { $this->_error = $e->getMessage(); return; } if ($isJson) { if (strlen($this->_error)) { $this->_return_message(1, $this->_error); } else { $this->_return_message(0, $dataid, array('messages' => $messages)); } } } /** * Display ZeroBin frontend. * * @access private * @return void */ private function _view() { // set headers to disable caching $time = gmdate('D, d M Y H:i:s \G\M\T'); header('Cache-Control: no-store, no-cache, must-revalidate'); header('Pragma: no-cache'); header('Expires: ' . $time); header('Last-Modified: ' . $time); header('Vary: Accept'); // label all the expiration options $expire = array(); foreach ($this->_conf->getSection('expire_options') as $time => $seconds) { $expire[$time] = ($seconds == 0) ? i18n::_(ucfirst($time)): filter::time_humanreadable($time); } // translate all the formatter options $formatters = array_map(array('i18n', 'translate'), $this->_conf->getSection('formatter_options')); // set language cookie if that functionality was enabled $languageselection = ''; if ($this->_conf->getKey('languageselection')) { $languageselection = i18n::getLanguage(); setcookie('lang', $languageselection); } $page = new RainTPL; $page::$path_replace = false; // we escape it here because ENT_NOQUOTES can't be used in RainTPL templates $page->assign('CIPHERDATA', htmlspecialchars($this->_data, ENT_NOQUOTES)); $page->assign('ERROR', i18n::_($this->_error)); $page->assign('STATUS', i18n::_($this->_status)); $page->assign('VERSION', self::VERSION); $page->assign('DISCUSSION', $this->_conf->getKey('discussion')); $page->assign('OPENDISCUSSION', $this->_conf->getKey('opendiscussion')); $page->assign('MARKDOWN', array_key_exists('markdown', $formatters)); $page->assign('SYNTAXHIGHLIGHTING', array_key_exists('syntaxhighlighting', $formatters)); $page->assign('SYNTAXHIGHLIGHTINGTHEME', $this->_conf->getKey('syntaxhighlightingtheme')); $page->assign('FORMATTER', $formatters); $page->assign('FORMATTERDEFAULT', $this->_conf->getKey('defaultformatter')); $page->assign('NOTICE', i18n::_($this->_conf->getKey('notice'))); $page->assign('BURNAFTERREADINGSELECTED', $this->_conf->getKey('burnafterreadingselected')); $page->assign('PASSWORD', $this->_conf->getKey('password')); $page->assign('FILEUPLOAD', $this->_conf->getKey('fileupload')); $page->assign('BASE64JSVERSION', $this->_conf->getKey('base64version')); $page->assign('LANGUAGESELECTION', $languageselection); $page->assign('LANGUAGES', i18n::getLanguageLabels(i18n::getAvailableLanguages())); $page->assign('EXPIRE', $expire); $page->assign('EXPIREDEFAULT', $this->_conf->getKey('default', 'expire')); $page->draw($this->_conf->getKey('template')); } /** * return JSON encoded message and exit * * @access private * @param bool $status * @param string $message * @param array $other * @return void */ private function _return_message($status, $message, $other = array()) { $result = array('status' => $status); if ($status) { $result['message'] = i18n::_($message); } else { $result['id'] = $message; } $result += $other; $this->_json = json_encode($result); } }