Просмотр исходного кода

merging @vikstrous file upload feature for #20 from
https://github.com/vikstrous/ZeroBin/commit/8a6d2682780597afb456b370cf340ce0ee1ae547

El RIDO 11 лет назад
Родитель
Сommit
106141efa4
9 измененных файлов с 152 добавлено и 14 удалено
  1. 3 0
      cfg/conf.ini
  2. 2 2
      css/zerobin.css
  3. 8 1
      i18n/de.json
  4. 8 1
      i18n/fr.json
  5. 8 1
      i18n/pl.json
  6. 86 1
      js/zerobin.js
  7. 29 7
      lib/zerobin.php
  8. 7 1
      tpl/page.html
  9. 1 0
      tst/RainTPL.php

+ 3 - 0
cfg/conf.ini

@@ -17,6 +17,9 @@ opendiscussion = false
 ; enable or disable the password feature, defaults to true
 password = true
 
+; enable or disable the file upload feature, defaults to false
+fileupload = false
+
 ; preselect the burn-after-reading feature, defaults to false
 burnafterreadingselected = false
 

+ 2 - 2
css/zerobin.css

@@ -25,7 +25,7 @@ body {
 	padding-right: 60px;
 }
 
-a { color: #0f388f; }
+a { color: #0f388f; cursor:pointer; }
 
 h1.title {
 	font-size: 3.5em;
@@ -76,7 +76,7 @@ h3.title {
 
 #aboutbox a { color: #94a3b4; }
 
-#message, #cleartext, #prettymessage, .replymessage {
+#message, #cleartext, #prettymessage, #attachment, .replymessage {
 	clear: both;
 	color: #000;
 	background-color: #fff;

+ 8 - 1
i18n/de.json

@@ -127,5 +127,12 @@
     "Format": "Format",
     "Plain Text": "Nur Text",
     "Source Code": "Quellcode",
-    "Markdown": "Markdown"
+    "Markdown": "Markdown",
+    "Download attachment": "Anhang herunterladen",
+    "Cloned file attached.": "Kopierte Datei angehängt.",
+    "Attach a file:": "Datei anhängen:",
+    "Remove attachment": "Anhang entfernen",
+    "Your browser does not support uploading encrypted files. Please use a newer browser.":
+        "Dein Browser unterstützt das hochladen von verschlüsselten Dateien nicht. Bitte verwende einen neueren Browser.",
+    "Invalid attachment.": "Ungültiger Datei-Anhang."
 }

+ 8 - 1
i18n/fr.json

@@ -136,5 +136,12 @@
     "Format": "Format",
     "Plain Text": "texte",
     "Source Code": "code source",
-    "Markdown": "Markdown"
+    "Markdown": "Markdown",
+    "Download attachment": "Download attachment",
+    "Cloned file attached.": "Cloned file attached.",
+    "Attach a file:": "Attach a file:",
+    "Remove attachment": "Remove attachment",
+    "Your browser does not support uploading encrypted files. Please use a newer browser.":
+        "Your browser does not support uploading encrypted files. Please use a newer browser.",
+    "Invalid attachment.": "Invalid attachment."
 }

+ 8 - 1
i18n/pl.json

@@ -127,5 +127,12 @@
     "Format": "Format",
     "Plain Text": "Plain Text",
     "Source Code": "Source Code",
-    "Markdown": "Markdown"
+    "Markdown": "Markdown",
+    "Download attachment": "Download attachment",
+    "Cloned file attached.": "Cloned file attached.",
+    "Attach a file:": "Attach a file:",
+    "Remove attachment": "Remove attachment",
+    "Your browser does not support uploading encrypted files. Please use a newer browser.":
+        "Your browser does not support uploading encrypted files. Please use a newer browser.",
+    "Invalid attachment.": "Invalid attachment."
 }

+ 86 - 1
js/zerobin.js

@@ -585,6 +585,15 @@ $(function() {
                     }
                     if (cleartext.length == 0) throw 'failed to decipher message';
                     this.passwordInput.val(password);
+                    if (comments[0].attachment)
+                    {
+                        var attachment = filter.decipher(key, password, comments[0].attachment);
+                        if (attachment)
+                        {
+                            this.attachmentLink.attr('href', attachment);
+                            this.attachment.removeClass('hidden');
+                        }
+                    }
 
                     helper.setElementText(this.clearText, cleartext);
                     helper.setElementText(this.prettyPrint, cleartext);
@@ -799,9 +808,10 @@ $(function() {
         sendData: function(event)
         {
             event.preventDefault();
+            var files = document.getElementById('file').files; // FileList object
 
             // Do not send if no data.
-            if (this.message.val().length == 0) return;
+            if (this.message.val().length == 0 && !(files && files[0])) return;
 
             // If sjcl has not collected enough entropy yet, display a message.
             if (!sjcl.random.isReady())
@@ -818,6 +828,48 @@ $(function() {
             this.showStatus(i18n._('Sending paste...'), true);
 
             var randomkey = sjcl.codec.base64.fromBits(sjcl.random.randomWords(8, 0), 0);
+            var cipherdata_attachment;
+            var password = this.passwordInput.val();
+            if(files && files[0])
+            {
+                if(typeof FileReader === undefined)
+                {
+                    this.showError(i18n._('Your browser does not support uploading encrypted files. Please use a newer browser.'));
+                    return;
+                }
+                var reader = new FileReader();
+                // Closure to capture the file information.
+                reader.onload = (function(theFile)
+                {
+                    return function(e) {
+                        zerobin.sendDataContinue(
+                            randomkey,
+                            filter.cipher(randomkey, password, e.target.result)
+                        );
+                    }
+                })(files[0]);
+                reader.readAsDataURL(files[0]);
+            }
+            else if(this.attachmentLink.attr('href'))
+            {
+                this.sendDataContinue(
+                    randomkey,
+                    filter.cipher(randomkey, password, this.attachmentLink.attr('href'))
+                );
+            }
+            else
+            {
+                this.sendDataContinue(randomkey, '');
+            }
+        },
+
+        /**
+         * Send a new paste to server, step 2
+         *
+         * @param Event event
+         */
+        sendDataContinue: function(randomkey, cipherdata_attachment)
+        {
             var cipherdata = filter.cipher(randomkey, this.passwordInput.val(), this.message.val());
             var data_to_send = {
                 data:             cipherdata,
@@ -826,6 +878,10 @@ $(function() {
                 burnafterreading: this.burnAfterReading.is(':checked') ? 1 : 0,
                 opendiscussion:   this.openDiscussion.is(':checked') ? 1 : 0
             };
+            if (cipherdata_attachment.length > 0)
+            {
+                data_to_send.attachment = cipherdata_attachment;
+            }
             $.post(this.scriptLocation(), data_to_send, function(data)
             {
                 if (data.status == 0) {
@@ -880,6 +936,8 @@ $(function() {
             this.openDisc.removeClass('hidden');
             this.newButton.removeClass('hidden');
             this.password.removeClass('hidden');
+            this.attach.removeClass('hidden');
+            this.attachment.removeClass('hidden');
             this.message.removeClass('hidden');
             this.message.focus();
         },
@@ -902,6 +960,8 @@ $(function() {
             }
             this.rawTextButton.removeClass('hidden');
 
+            this.attach.addClass('hidden');
+            this.attachment.addClass('hidden');
             this.expiration.addClass('hidden');
             this.formatter.addClass('hidden');
             this.burnAfterReadingOption.addClass('hidden');
@@ -969,6 +1029,11 @@ $(function() {
             history.replaceState(document.title, document.title, this.scriptLocation());
 
             this.showStatus('', false);
+            if (this.attachmentLink.attr('href'))
+            {
+                this.clonedFile.removeClass('hidden');
+                this.fileWrap.addClass('hidden');
+            }
             this.message.text(this.clearText.text());
             $('.navbar-toggle').click();
         },
@@ -984,6 +1049,19 @@ $(function() {
             $('.navbar-toggle').click();
         },
 
+        /**
+         * Removes an attachment.
+         */
+        removeAttachment: function()
+        {
+            this.clonedFile.addClass('hidden');
+            // removes the saved decrypted file data
+            $('#attachment a').attr('href', '');
+            // the only way to deselect the file is to recreate the input
+            this.fileWrap.html(this.fileWrap.html());
+            this.fileWrap.removeClass('hidden');
+        },
+
         /**
          * Display an error message
          * (We use the same function for paste and reply to comments)
@@ -1042,6 +1120,7 @@ $(function() {
             this.sendButton.click($.proxy(this.sendData, this));
             this.cloneButton.click($.proxy(this.clonePaste, this));
             this.rawTextButton.click($.proxy(this.rawText, this));
+            this.fileRemoveButton.click($.proxy(this.removeAttachment, this));
             $('.reloadlink').click($.proxy(this.reloadPage, this));
         },
 
@@ -1054,15 +1133,21 @@ $(function() {
             $('#noscript').hide();
 
             // preload jQuery wrapped DOM elements and bind events
+            this.attach = $('#attach');
+            this.attachment = $('#attachment');
+            this.attachmentLink = $('#attachment a');
             this.burnAfterReading = $('#burnafterreading');
             this.burnAfterReadingOption = $('#burnafterreadingoption');
             this.cipherData = $('#cipherdata');
             this.clearText = $('#cleartext');
             this.cloneButton = $('#clonebutton');
+            this.clonedFile = $('#clonedfile');
             this.comments = $('#comments');
             this.discussion = $('#discussion');
             this.errorMessage = $('#errormessage');
             this.expiration = $('#expiration');
+            this.fileRemoveButton = $('#fileremovebutton');
+            this.fileWrap = $('#filewrap');
             this.formatter = $('#formatter');
             this.message = $('#message');
             this.newButton = $('#newbutton');

+ 29 - 7
lib/zerobin.php

@@ -116,9 +116,12 @@ class zerobin
         $this->_init();
 
         // create new paste or comment
-        if (!empty($_POST['data']))
+        if (
+            (array_key_exists('data', $_POST) && !empty($_POST['data'])) ||
+            (array_key_exists('attachment', $_POST) && !empty($_POST['attachment']))
+        )
         {
-            $this->_create($_POST['data']);
+            $this->_create();
         }
         // delete an existing paste
         elseif (!empty($_GET['deletetoken']) && !empty($_GET['pasteid']))
@@ -191,24 +194,30 @@ class zerobin
     /**
      * Store new paste or comment
      *
-     * POST contains:
-     * data (mandatory) = json encoded SJCL encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct)
+     * 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)
      * 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
-     * @param  string $data
      * @return string
      */
-    private function _create($data)
+    private function _create()
     {
         $error = false;
 
+        $has_attachment = array_key_exists('attachment', $_POST);
+        $data = array_key_exists('data', $_POST) ? $_POST['data'] : '';
+        $attachment = $has_attachment ? $_POST['attachment'] : '';
+
         // Make sure last paste from the IP address was more than X seconds ago.
         trafficlimiter::setLimit($this->_conf['traffic']['limit']);
         trafficlimiter::setPath($this->_conf['traffic']['dir']);
@@ -226,7 +235,7 @@ class zerobin
 
         // Make sure content is not too big.
         $sizelimit = (int) $this->_getMainConfig('sizelimit', 2097152);
-        if (strlen($data) > $sizelimit)
+        if (strlen($data) + strlen($attachment) > $sizelimit)
         {
             $this->_return_message(
                 1,
@@ -241,6 +250,15 @@ class zerobin
         // Make sure format is correct.
         if (!sjcl::isValid($data)) return $this->_return_message(1, 'Invalid data.');
 
+        // Make sure attachments are enabled and format is correct.
+        if($has_attachment)
+        {
+            if (
+                !$this->_getMainConfig('fileupload', false) ||
+                !sjcl::isValid($attachment)
+            ) $this->_return_message(1, 'Invalid attachment.');
+        }
+
         // Read additional meta-information.
         $meta = array();
 
@@ -416,6 +434,9 @@ class zerobin
                 return;
             }
 
+            // Add attachment if one was sent
+            if($has_attachment) $storage['attachment'] = $attachment;
+
             // New paste
             if (
                 $this->_model()->create($dataid, $storage) === false
@@ -634,6 +655,7 @@ class zerobin
         $page->assign('NOTICE', i18n::_($this->_getMainConfig('notice', '')));
         $page->assign('BURNAFTERREADINGSELECTED', $this->_getMainConfig('burnafterreadingselected', false));
         $page->assign('PASSWORD', $this->_getMainConfig('password', true));
+        $page->assign('FILEUPLOAD', $this->_getMainConfig('fileupload', false));
         $page->assign('BASE64JSVERSION', $this->_getMainConfig('base64version', '2.1.9'));
         $page->assign('EXPIRE', $expire);
         $page->assign('EXPIREDEFAULT', $this->_conf['expire']['default']);

+ 7 - 1
tpl/page.html

@@ -74,7 +74,13 @@
 				<div id="pasteresult" class="hidden">
 					<div id="deletelink"></div>
 					<div id="pastelink"></div>
-				</div>
+				</div>{if="$FILEUPLOAD"}
+				<div id="attachment" class="hidden"><a>{function="t('Download attachment')"}</a></div>
+				<div id="attach" class="hidden">
+					<span id="clonedfile" class="hidden">{function="t('Cloned file attached.')"}</span>
+					<span id="filewrap">{function="t('Attach a file:')"} <input type="file" id="file" name="file" /></span>
+					<button id="fileremovebutton">{function="t('Remove attachment')"}</button>
+				</div>{/if}
 				<div id="prettymessage" class="hidden">
 					<pre id="prettyprint" class="prettyprint linenums:1"></pre>
 				</div>

+ 1 - 0
tst/RainTPL.php

@@ -38,6 +38,7 @@ class RainTPLTest extends PHPUnit_Framework_TestCase
         $page->assign('SYNTAXHIGHLIGHTINGTHEME', 'sons-of-obsidian');
         $page->assign('BURNAFTERREADINGSELECTED', false);
         $page->assign('PASSWORD', true);
+        $page->assign('FILEUPLOAD', false);
         $page->assign('BASE64JSVERSION', '2.1.9');
         $page->assign('NOTICE', 'example');
         $page->assign('EXPIRE', self::$expire);