Przeglądaj źródła

ZeroBin 0.17

* added deletion link.
* small refactoring.
* improved regex checks.
* larger server alt on installation.
Sebastien SAUVAGE 12 lat temu
rodzic
commit
5b253cf77c
11 zmienionych plików z 217 dodań i 104 usunięć
  1. 1 1
      cfg/conf.ini
  2. 6 6
      css/zerobin.css
  3. 1 1
      index.php
  4. 17 4
      js/zerobin.js
  5. 71 0
      lib/serversalt.php
  6. 9 40
      lib/trafficlimiter.php
  7. 24 21
      lib/vizhash16x16.php
  8. 73 22
      lib/zerobin.php
  9. 9 6
      tpl/page.html
  10. 4 1
      tst/RainTPL.php
  11. 2 2
      tst/trafficlimiter.php

+ 1 - 1
cfg/conf.ini

@@ -5,7 +5,7 @@
 ; @link      http://sebsauvage.net/wiki/doku.php?id=php:zerobin
 ; @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
 ; @license   http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
-; @version   0.15
+; @version   0.17
 
 [main]
 ; enable or disable discussions

+ 6 - 6
css/zerobin.css

@@ -1,6 +1,5 @@
-/* ZeroBin 0.15 - http://sebsauvage.net/wiki/doku.php?id=php:zerobin */
+/* ZeroBin 0.17 - http://sebsauvage.net/wiki/doku.php?id=php:zerobin */
 
-/*   Hé, t'as vu Idleman, j'ai fait un effort sur les CSS !   ;-)      */
 
 /*  CSS Reset from YUI 3.4.1 (build 4118) - Copyright 2011 Yahoo! Inc. All rights reserved.
 Licensed under the BSD License. - http://yuilibrary.com/license/    */
@@ -100,8 +99,7 @@ h3 {
 	padding: 5px 10px;
 }
 
-
-#pastelink { 
+#pasteresult { 
 	background-color: #1F2833;
 	color: #fff;
 	padding: 4px 12px; 
@@ -111,9 +109,11 @@ h3 {
 	box-shadow: inset 0 2px 2px #000;
 }
 
-#pastelink a { color: #fff; }
+#pasteresult a { color: #fff; }
+
+#pasteresult button { margin-left: 11px }
 
-#pastelink button { margin-left: 11px }
+#deletelink { float: right; }
 
 #toolbar, #status { margin-bottom: 5px; }
 

+ 1 - 1
index.php

@@ -7,7 +7,7 @@
  * @link      http://sebsauvage.net/wiki/doku.php?id=php:zerobin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   0.15
+ * @version   0.17
  */
 
 // change this, if your php files and data is outside of your webservers document root

+ 17 - 4
js/zerobin.js

@@ -6,7 +6,7 @@
  * @link      http://sebsauvage.net/wiki/doku.php?id=php:zerobin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   0.15
+ * @version   0.17
  */
 
 // Immediately start random number generator collector.
@@ -328,8 +328,13 @@ function send_data() {
             if (data.status == 0) {
                 stateExistingPaste();
                 var url = scriptLocation() + "?" + data.id + '#' + randomkey;
+                var deleteUrl = scriptLocation() + "?pasteid=" + data.id + '&deletetoken=' + data.deletetoken;
                 showStatus('');
-                $('div#pastelink').html('Your paste is <a href="' + url + '">' + url + '</a>').removeClass('hidden');
+
+                $('div#pastelink').html('Your paste is <a href="' + url + '">' + url + '</a>');
+                $('div#deletelink').html('<a href="' + deleteUrl + '">Delete link</a>');
+                $('div#pasteresult').show();
+
                 setElementText($('div#cleartext'), $('textarea#message').val());
                 setElementText($('pre#prettyprint'), $('textarea#message').val());
                 urls2links($('div#cleartext'));
@@ -357,7 +362,7 @@ function stateNewPaste() {
     $('input#password').addClass('hidden'); //$('#password').removeClass('hidden');
     $('div#opendisc').removeClass('hidden');
     $('button#newbutton').removeClass('hidden');
-    $('div#pastelink').addClass('hidden');
+    $('div#pasteresult').addClass('hidden');
     $('textarea#message').text('');
     $('textarea#message').removeClass('hidden');
     $('div#cleartext').addClass('hidden');
@@ -385,7 +390,7 @@ function stateExistingPaste() {
     $('input#password').addClass('hidden');
     $('div#opendisc').addClass('hidden');
     $('button#newbutton').removeClass('hidden');
-    $('div#pastelink').addClass('hidden');
+    $('div#pasteresult').addClass('hidden');
     $('textarea#message').addClass('hidden');
     $('div#cleartext').addClass('hidden');
     $('div#prettymessage').removeClass('hidden');
@@ -505,6 +510,14 @@ $(function() {
         }
     });
 
+    // Display status returned by php code if any (eg. Paste was properly deleted.)
+    if ($('div#status').text().length > 0) {
+        showStatus($('div#status').text(),false);
+        return;
+    }
+
+    $('div#status').html(' '); // Keep line height even if content empty.
+
     // Display an existing paste
     if ($('div#cipherdata').text().length > 1) {
         // Missing decryption key in URL ?

+ 71 - 0
lib/serversalt.php

@@ -0,0 +1,71 @@
+<?php
+/**
+ * ZeroBin
+ *
+ * a zero-knowledge paste bin
+ *
+ * @link      http://sebsauvage.net/wiki/doku.php?id=php:zerobin
+ * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
+ * @license   http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
+ * @version   0.17
+ */
+
+/**
+ * serversalt
+ *
+ * This is a random string which is unique to each ZeroBin installation.
+ * It is automatically created if not present.
+ *
+ * Salt is used:
+ * - to generate unique VizHash in discussions (which are not reproductible across ZeroBin servers)
+ * - to generate unique deletion token (which are not re-usable across ZeroBin servers)
+ */
+class serversalt extends persistence
+{
+    /**
+     * @access private
+     * @static
+     * @var    string
+     */
+    private static $_salt = '';
+
+    /**
+     * generate a large random hexadecimal salt
+     *
+     * @access public
+     * @static
+     * @return string
+     */
+    public static function generate()
+    {
+        $randomSalt = '';
+        for($i=0; $i<16; ++$i) {
+            $randomSalt .= base_convert(mt_rand(), 10, 16);
+        }
+        self::$_salt = $randomSalt;
+        return self::$_salt;
+    }
+
+    /**
+     * get server salt
+     *
+     * @access public
+     * @static
+     * @return string
+     */
+    public static function get()
+    {
+    	if (strlen(self::$_salt)) return self::$_salt;
+
+        $file = 'salt.php';
+        if (!self::_exists($file)) {
+            self::_store(
+            	$file,
+                '<?php /* |'. self::generate() . '| */ ?>'
+            );
+        }
+        $items = explode('|', file_get_contents(self::getPath($file)));
+        self::$_salt = $items[1];
+        return $items[1];
+    }
+}

+ 9 - 40
lib/trafficlimiter.php

@@ -7,15 +7,15 @@
  * @link      http://sebsauvage.net/wiki/doku.php?id=php:zerobin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   0.15
+ * @version   0.17
  */
 
 /**
- * traffic_limiter
+ * trafficlimiter
  *
  * Handles traffic limiting, so no user does more than one call per 10 seconds.
  */
-class trafficlimiter
+class trafficlimiter extends persistence
 {
     /**
      * @access private
@@ -24,13 +24,6 @@ class trafficlimiter
      */
     private static $_limit = 10;
 
-    /**
-     * @access private
-     * @static
-     * @var    string
-     */
-    private static $_path = 'data';
-
     /**
      * set the time limit in seconds
      *
@@ -44,19 +37,6 @@ class trafficlimiter
         self::$_limit = $limit;
     }
 
-    /**
-     * set the path
-     *
-     * @access public
-     * @static
-     * @param  string $path
-     * @return void
-     */
-    public static function setPath($path)
-    {
-        self::$_path = $path;
-    }
-
     /**
      * traffic limiter
      *
@@ -72,29 +52,18 @@ class trafficlimiter
     	// disable limits if set to less then 1
     	if (self::$_limit < 1) return true;
 
-        // Create storage directory if it does not exist.
-        if (!is_dir(self::$_path)) mkdir(self::$_path, 0705);
-        // Create .htaccess file if it does not exist.
-        if (!is_file(self::$_path . '/.htaccess'))
-        {
-            file_put_contents(
-                self::$_path . '/.htaccess',
-                'Allow from none' . PHP_EOL .
-                'Deny from all'. PHP_EOL
-            );
-        }
-        $file = self::$_path . '/traffic_limiter.php';
-        if (!is_file($file))
+        $file = 'traffic_limiter.php';
+        if (!self::_exists($file))
         {
-            file_put_contents(
+            self::_store(
                 $file,
                 '<?php' . PHP_EOL .
                 '$GLOBALS[\'traffic_limiter\'] = array();' . PHP_EOL
             );
-            chmod($file, 0705);
         }
 
-        require $file;
+        $path = self::getPath($file);
+        require $path;
         $now = time();
         $tl = $GLOBALS['traffic_limiter'];
 
@@ -114,7 +83,7 @@ class trafficlimiter
             $tl[$ip] = time();
             $result = true;
         }
-        file_put_contents(
+        self::_store(
             $file,
             '<?php' . PHP_EOL .
             '$GLOBALS[\'traffic_limiter\'] = ' .

+ 24 - 21
lib/vizhash16x16.php

@@ -1,16 +1,27 @@
 <?php
-// VizHash_GD 0.0.4 beta ZeroBin 0.15
-// Visual Hash implementation in php4+GD, stripped down and modified version for ZeroBin
-// See: http://sebsauvage.net/wiki/doku.php?id=php:vizhash_gd
-// This is free software under the zlib/libpng licence
-// http://www.opensource.org/licenses/zlib-license.php
-/* Example:
-    $vz = new vizhash16x16();
-    $data = $vz->generate('hello');
-    header('Content-type: image/png');
-    echo $data;
-    exit;
-*/
+/**
+ * VizHash_GD
+ *
+ * Visual Hash implementation in php4+GD,
+ * stripped down and modified version for ZeroBin
+ *
+ * @link      http://sebsauvage.net/wiki/doku.php?id=php:vizhash_gd
+ * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
+ * @license   http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
+ * @version   0.0.4 beta ZeroBin 0.17
+ */
+
+/**
+ * vizhash16x16
+ *
+ * Example:
+ * $vz = new vizhash16x16();
+ * $data = $vz->generate('hello');
+ * header('Content-type: image/png');
+ * echo $data;
+ * exit;
+ */
+
 class vizhash16x16
 {
     private $VALUES;
@@ -22,15 +33,7 @@ class vizhash16x16
     {
         $this->width=16;
         $this->height=16;
-
-        // Read salt from file (and create it if does not exist).
-        // The salt will make vizhash avatar unique on each ZeroBin installation
-        // to prevent IP checking.
-        $saltfile = PATH . 'data/salt.php';
-        if (!is_file($saltfile))
-            file_put_contents($saltfile,'<?php /* |'.$this->randomSalt().'| */ ?>');
-        $items=explode('|',file_get_contents($saltfile));
-        $this->salt = $items[1];
+        $this->salt = serversalt::get();
     }
 
     // Generate a 16x16 png corresponding to $text.

+ 73 - 22
lib/zerobin.php

@@ -7,7 +7,7 @@
  * @link      http://sebsauvage.net/wiki/doku.php?id=php:zerobin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   0.15
+ * @version   0.17
  */
 
 /**
@@ -20,7 +20,7 @@ class zerobin
     /*
      * @const string version
      */
-    const VERSION = 'Alpha 0.15';
+    const VERSION = 'Alpha 0.17';
 
     /**
      * @access private
@@ -42,6 +42,12 @@ class zerobin
      */
     private $_error = '';
 
+    /**
+     * @access private
+     * @var    string
+     */
+    private $_status = '';
+
     /**
      * @access private
      * @var    zerobin_data
@@ -60,7 +66,7 @@ class zerobin
         if (version_compare(PHP_VERSION, '5.2.6') < 0)
             die('ZeroBin requires php 5.2.6 or above to work. Sorry.');
 
-        // In case stupid admin has left magic_quotes enabled in php.ini.
+        // in case stupid admin has left magic_quotes enabled in php.ini
         if (get_magic_quotes_gpc())
         {
             $_POST   = array_map('filter::stripslashes_deep', $_POST);
@@ -68,21 +74,26 @@ class zerobin
             $_COOKIE = array_map('filter::stripslashes_deep', $_COOKIE);
         }
 
-        // Load config from ini file.
+        // load config from ini file
         $this->_init();
 
-        // Create new paste or comment.
+        // create new paste or comment
         if (!empty($_POST['data']))
         {
-            $this->_create();
+            $this->_create($_POST['data']);
+        }
+        // delete an existing paste
+        elseif (!empty($_GET['deletetoken']) && !empty($_GET['pasteid']))
+        {
+            $this->_delete($_GET['pasteid'], $_GET['deletetoken']);
         }
-        // Display an existing paste.
+        // display an existing paste
         elseif (!empty($_SERVER['QUERY_STRING']))
         {
-            $this->_read();
+            $this->_read($_SERVER['QUERY_STRING']);
         }
 
-        // Display ZeroBin frontend
+        // display ZeroBin frontend
         $this->_view();
     }
 
@@ -126,7 +137,7 @@ class zerobin
     }
 
     /**
-     * Store new paste or comment.
+     * Store new paste or comment
      *
      * POST contains:
      * data (mandatory) = json encoded SJCL encrypted text (containing keys: iv,salt,ct)
@@ -139,9 +150,10 @@ class zerobin
      * pasteid (optional) = in discussion, which paste this comment belongs to.
      *
      * @access private
+     * @param  string $data
      * @return void
      */
-    private function _create()
+    private function _create($data)
     {
         header('Content-type: application/json');
         $error = false;
@@ -159,7 +171,6 @@ class zerobin
         );
 
         // Make sure content is not too big.
-        $data = $_POST['data'];
         if (
             strlen($data) > $this->_conf['main']['sizelimit']
         ) $this->_return_message(
@@ -252,8 +263,8 @@ class zerobin
             $pasteid  = $_POST['pasteid'];
             $parentid = $_POST['parentid'];
             if (
-                !preg_match('/[a-f\d]{16}/', $pasteid) ||
-                !preg_match('/[a-f\d]{16}/', $parentid)
+                !preg_match('/\A[a-f\d]{16}\z/', $pasteid) ||
+                !preg_match('/\A[a-f\d]{16}\z/', $parentid)
             ) $this->_return_message(1, 'Invalid data.');
 
             // Comments do not expire (it's the paste that expires)
@@ -297,23 +308,60 @@ class zerobin
                 $this->_model()->create($dataid, $storage) === false
             ) $this->_return_message(1, 'Error saving paste. Sorry.');
 
+            // Generate the "delete" token.
+            // The token is the hmac of the pasteid signed with the server salt.
+            // The paste can be delete by calling http://myserver.com/zerobin/?pasteid=<pasteid>&deletetoken=<deletetoken>
+            $deletetoken = hash_hmac('sha1', $dataid , serversalt::get());
+
             // 0 = no error
-            $this->_return_message(0, $dataid);
+            $this->_return_message(0, $dataid, array('deletetoken' => $deletetoken));
         }
 
         $this->_return_message(1, 'Server error.');
     }
 
+    /**
+     * Delete an existing paste
+     *
+     * @access private
+     * @param  string $dataid
+     * @param  string $deletetoken
+     * @return void
+     */
+    private function _delete($dataid, $deletetoken)
+    {
+        // Is this a valid paste identifier?
+        if (preg_match('\A[a-f\d]{16}\z', $dataid))
+        {
+            // Check that paste exists.
+            if (!$this->_model()->exists($dataid))
+            {
+                $this->_error = 'Paste does not exist, has expired or has been deleted.';
+                return;
+            }
+        }
+
+        // Make sure token is valid.
+        if ($deletetoken != hash_hmac('sha1', $dataid , serversalt::get()))
+        {
+            $this->_error = 'Wrong deletion token. Paste was not deleted.';
+            return;
+        }
+
+        // Paste exists and deletion token is valid: Delete the paste.
+        $this->_model()->delete($dataid);
+        $this->_status = 'Paste was properly deleted.';
+    }
+
     /**
-     * Read an existing paste or comment.
+     * Read an existing paste or comment
      *
      * @access private
+     * @param  string $dataid
      * @return void
      */
-    private function _read()
+    private function _read($dataid)
     {
-        $dataid = $_SERVER['QUERY_STRING'];
-
         // Is this a valid paste identifier?
         if (preg_match('\A[a-f\d]{16}\z', $dataid))
         {
@@ -331,7 +379,7 @@ class zerobin
                 {
                     // Delete the paste
                     $this->_model()->delete($dataid);
-                    $this->_error = 'Paste does not exist or has expired.';
+                    $this->_error = 'Paste does not exist, has expired or has been deleted.';
                 }
                 // If no error, return the paste.
                 else
@@ -398,7 +446,8 @@ class zerobin
         $page = new RainTPL;
         // we escape it here because ENT_NOQUOTES can't be used in RainTPL templates
         $page->assign('CIPHERDATA', htmlspecialchars($this->_data, ENT_NOQUOTES));
-        $page->assign('ERRORMESSAGE', $this->_error);
+        $page->assign('ERROR', $this->_error);
+        $page->assign('STATUS', $this->_status);
         $page->assign('VERSION', self::VERSION);
         $page->assign('BURNAFTERREADINGSELECTED', $this->_conf['main']['burnafterreadingselected']);
         $page->assign('OPENDISCUSSION', $this->_conf['main']['opendiscussion']);
@@ -414,9 +463,10 @@ class zerobin
      * @access private
      * @param  bool $status
      * @param  string $message
+     * @param  array $other
      * @return void
      */
-    private function _return_message($status, $message)
+    private function _return_message($status, $message, $other = array())
     {
         $result = array('status' => $status);
         if ($status)
@@ -427,6 +477,7 @@ class zerobin
         {
             $result['id'] = $message;
         }
+        $result += $other;
         exit(json_encode($result));
     }
 }

+ 9 - 6
tpl/page.html

@@ -43,14 +43,14 @@
 		</header>
 		<section>
 			<article>
-				<div id="status"> </div>
-				<div id="errormessage" class="hidden">{$ERRORMESSAGE|htmlspecialchars}</div>
+				<div id="status">{$STATUS|htmlspecialchars}</div>
+				<div id="errormessage" class="hidden">{$ERROR|htmlspecialchars}</div>
 				<div id="toolbar">
 					<button id="newbutton" onclick="window.location.href=scriptLocation();return false;" class="hidden"><img src="img/icon_new.png#" width="11" height="15" alt="" />New</button>
 					<button id="sendbutton" onclick="send_data();return false;" class="hidden"><img src="img/icon_send.png#" width="18" height="15" alt="" />Send</button>
 					<button id="clonebutton" onclick="clonePaste();return false;" class="hidden"><img src="img/icon_clone.png#" width="15" height="17" alt="" />Clone</button>
-		      <div id="expiration" class="hidden">Expire: 
-			      <select id="pasteExpiration" name="pasteExpiration">
+					<div id="expiration" class="hidden">Expire: 
+						<select id="pasteExpiration" name="pasteExpiration">
 							<option value="burn"{if="$BURNAFTERREADINGSELECTED"} selected="selected"{/if}>Burn after reading</option>{loop="EXPIRE"}
 							<option value="{$key}"{if="!$BURNAFTERREADINGSELECTED && $key == $EXPIREDEFAULT"} selected="selected"{/if}>{$value}</option>{/loop}
 						</select>
@@ -70,7 +70,10 @@
 						<label for="opendiscussion">Open discussion</label>
 					</div>
 				</div>
-				<div id="pastelink" class="hidden"></div>
+				<div id="pasteresult" style="display:none;">
+					<div id="deletelink"></div>
+					<div id="pastelink"></div>
+				</div>
 				<div id="prettymessage" class="hidden">
 					<pre id="prettyprint" class="prettyprint linenums:1"></pre>
 				</div>
@@ -85,5 +88,5 @@
 			</div>
 		</section>
 		<div id="cipherdata" class="hidden">{$CIPHERDATA}</div>
-  </body>
+	</body>
 </html>

+ 4 - 1
tst/RainTPL.php

@@ -5,6 +5,8 @@ class RainTPLTest extends PHPUnit_Framework_TestCase
 
     private static $error = 'foo bar';
 
+    private static $status = '!*#@?$+';
+
     private static $expire = array(
         '5min' => '5 minutes',
         '1hour' => '1 hour',
@@ -26,7 +28,8 @@ class RainTPLTest extends PHPUnit_Framework_TestCase
         $page = new RainTPL;
         // We escape it here because ENT_NOQUOTES can't be used in RainTPL templates.
         $page->assign('CIPHERDATA', htmlspecialchars(self::$data, ENT_NOQUOTES));
-        $page->assign('ERRORMESSAGE', self::$error);
+        $page->assign('ERROR', self::$error);
+        $page->assign('STATUS', self::$status);
         $page->assign('VERSION', self::$version);
         $page->assign('BURNAFTERREADINGSELECTED', false);
         $page->assign('OPENDISCUSSION', false);

+ 2 - 2
tst/trafficlimiter.php

@@ -6,14 +6,14 @@ class trafficlimiterTest extends PHPUnit_Framework_TestCase
     public function setUp()
     {
         /* Setup Routine */
-        $this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'trafficlimit' . DIRECTORY_SEPARATOR;
+        $this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'trafficlimit';
         trafficlimiter::setPath($this->_path);
     }
 
     public function tearDown()
     {
         /* Tear Down Routine */
-        helper::rmdir($this->_path);
+        helper::rmdir($this->_path . DIRECTORY_SEPARATOR);
     }
 
     public function testTrafficGetsLimited()