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

Merge branch 'master' into js-unit-testing

El RIDO 8 лет назад
Родитель
Сommit
85401a1513

+ 10 - 2
.travis.yml

@@ -10,10 +10,10 @@ php:
 
 # as this is a php project, node.js v4 (for JS unit testing) isn't installed
 install:
-  - rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install 4
+  - if [ ! -d "$HOME/.nvm" ]; then mkdir -p $HOME/.nvm && curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | NVM_METHOD=script bash; fi
+  - source ~/.nvm/nvm.sh && nvm install 4
 
 before_script:
-  - composer config -g github-oauth.github.com "$GITHUB_TOKEN"
   - composer install -n
   - npm install -g mocha
   - cd js
@@ -28,3 +28,11 @@ after_script:
   - cd ..
   - vendor/bin/codacycoverage clover tst/log/coverage-clover.xml
   - vendor/bin/test-reporter --coverage-report tst/log/coverage-clover.xml
+
+cache:
+  directories:
+    - $HOME/.composer/cache/files
+    - $HOME/.composer/cache/vcs
+    - $HOME/.nvm
+    - $HOME/.npm
+    - js/node_modules

+ 4 - 0
cfg/conf.sample.php

@@ -56,6 +56,10 @@ languageselection = false
 ; the pastes encryption key
 ; urlshortener = "https://shortener.example.com/api?link="
 
+; (optional) Let users create a QR code for sharing the paste URL with one click.
+; It works both when a new paste is created and when you view a paste.
+; qrcode = true
+
 ; (optional) IP based icons are a weak mechanism to detect if a comment was from
 ; a different user when the same username was used in a comment. It might be
 ; used to get the IP of a non anonymous comment poster if the server salt is

+ 0 - 6
composer.json

@@ -11,12 +11,6 @@
         "source": "https://github.com/PrivateBin/PrivateBin",
         "docs": "https://zerobin.dssr.ch/documentation/"
     },
-    "repositories": [
-        {
-            "type": "vcs",
-            "url": "https://github.com/PrivateBin/PrivateBin"
-        }
-    ],
     "require": {
         "php": "^5.4.0 || ^7.0",
         "paragonie/random_compat": "2.0.4",

+ 10 - 0
css/bootstrap/privatebin.css

@@ -76,6 +76,16 @@ body.loading {
 
 #deletelink {
 	float: right;
+	margin-left: 5px;
+}
+
+#qrcodemodalClose {
+	float: right;
+}
+#qrcode-display {
+	width: 200px;
+	height: 200px;
+	margin: auto;
 }
 
 #pastelink {

+ 7 - 7
css/privatebin.css

@@ -72,13 +72,13 @@ h3.title {
 	bottom: 8px;
 }
 
-#aboutbox { 
-	color: #94a3b4; 
+#aboutbox {
+	color: #94a3b4;
 	padding: 4px 8px 4px 16px;
-	position: relative; 
+	position: relative;
 	top: 10px;
 	border-left: 2px solid #94a3b4;
-	float: right;  
+	float: right;
 	width: 60%;
 }
 
@@ -109,12 +109,12 @@ h3.title {
 	height: auto;
 }
 
-#status { 
+#status {
 	clear: both;
 	padding: 5px 10px;
 }
 
-#pasteresult { 
+#pasteresult {
 	background-color: #1F2833;
 	color: #fff;
 	padding: 4px 12px;
@@ -132,7 +132,7 @@ h3.title {
 
 #toolbar, #status { margin-bottom: 5px; }
 
-#copyhint { color: #666; font-size: 0.85em; }
+#copyhint { color: #666; font-size: 0.85em }
 
 button, .button {
 	color: #fff;

BIN
img/icon_qr.png


+ 1 - 1
js/common.js

@@ -16,7 +16,7 @@ require('./prettify');
 global.prettyPrint = window.PR.prettyPrint;
 global.prettyPrintOne = window.PR.prettyPrintOne;
 global.showdown = require('./showdown-1.6.1');
-global.DOMPurify = require('./purify.min');
+global.DOMPurify = require('./purify-1.0.3');
 require('./bootstrap-3.3.7');
 require('./privatebin');
 

Разница между файлами не показана из-за своего большого размера
+ 1 - 0
js/kjua-0.1.2.js


+ 31 - 8
js/privatebin.js

@@ -21,6 +21,7 @@
 /** global: prettyPrintOne */
 /** global: showdown */
 /** global: sjcl */
+/** global: kjua */
 
 // Immediately start random number generator collector.
 sjcl.random.startCollectors();
@@ -1700,7 +1701,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
             }
 
             // set sanitized and linked text
-            var sanitizedLinkedText = DOMPurify.sanitize(Helper.urls2links(text), {SAFE_FOR_JQUERY: true});
+            var sanitizedLinkedText = DOMPurify.sanitize(Helper.urls2links(text));
             $plainText.html(sanitizedLinkedText);
             $prettyPrint.html(sanitizedLinkedText);
 
@@ -1713,7 +1714,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
                     });
                     // let showdown convert the HTML and sanitize HTML *afterwards*!
                     $plainText.html(
-                        DOMPurify.sanitize(converter.makeHtml(text), {SAFE_FOR_JQUERY: true})
+                        DOMPurify.sanitize(converter.makeHtml(text))
                     );
                     // add table classes from bootstrap css
                     $plainText.find('table').addClass('table-condensed table-bordered');
@@ -1727,8 +1728,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
 
                     $prettyPrint.html(
                         DOMPurify.sanitize(
-                            prettyPrintOne(Helper.urls2links(text), null, true),
-                            {SAFE_FOR_JQUERY: true}
+                            prettyPrintOne(Helper.urls2links(text), null, true)
                         )
                     );
                     // fall through, as the rest is the same
@@ -1824,6 +1824,8 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
          */
         me.setText = function(newText)
         {
+            // escape HTML entities
+            newText = $('<div />').text(newText).html();
             if (text !== newText) {
                 text = newText;
                 isChanged = true;
@@ -2220,8 +2222,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
             // set & parse text
             $commentEntryData.html(
                 DOMPurify.sanitize(
-                    Helper.urls2links(commentText),
-                    {SAFE_FOR_JQUERY: true}
+                    Helper.urls2links(commentText)
                 )
             );
 
@@ -2414,6 +2415,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
             $password,
             $passwordInput,
             $rawTextButton,
+            $qrCodeLink,
             $sendButton;
 
         var pasteExpiration = '1week';
@@ -2535,7 +2537,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
             for (var i = 0; i < $head.length; i++) {
                 newDoc.write($head[i].outerHTML);
             }
-            newDoc.write('</head><body><pre>' + DOMPurify.sanitize(paste, {SAFE_FOR_JQUERY: true}) + '</pre></body></html>');
+            newDoc.write('</head><body><pre>' + DOMPurify.sanitize(paste) + '</pre></body></html>');
             newDoc.close();
         }
 
@@ -2591,6 +2593,22 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
             event.preventDefault();
         }
 
+        /**
+         * Shows the QR code of the current paste (URL).
+         *
+         * @name   TopNav.displayQrCode
+         * @function
+         * @param  {Event} event
+         */
+        function displayQrCode(event)
+        {
+            var qrCanvas = kjua({
+                render: 'canvas',
+                text: window.location.href
+            });
+            $('#qrcode-display').html(qrCanvas);
+        }
+
         /**
          * Shows all elements belonging to viwing an existing pastes
          *
@@ -2607,6 +2625,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
             $newButton.removeClass('hidden');
             $cloneButton.removeClass('hidden');
             $rawTextButton.removeClass('hidden');
+            $qrCodeLink.removeClass('hidden');
 
             viewButtonsDisplayed = true;
         }
@@ -2627,6 +2646,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
             $newButton.addClass('hidden');
             $cloneButton.addClass('hidden');
             $rawTextButton.addClass('hidden');
+            $qrCodeLink.addClass('hidden');
 
             viewButtonsDisplayed = false;
         }
@@ -2877,6 +2897,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
             $passwordInput = $('#passwordinput');
             $rawTextButton = $('#rawtextbutton');
             $sendButton = $('#sendbutton');
+            $qrCodeLink = $('#qrcodelink');
 
             // bootstrap template drop down
             $('#language ul.dropdown-menu li a').click(setLanguage);
@@ -2891,6 +2912,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
             $cloneButton.click(Controller.clonePaste);
             $rawTextButton.click(rawText);
             $fileRemoveButton.click(removeAttachment);
+            $qrCodeLink.click(displayQrCode);
 
             // bootstrap template drop downs
             $('ul.dropdown-menu li a', $('#expiration').parent()).click(updateExpiration);
@@ -3944,10 +3966,11 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
             // first load translations
             I18n.loadTranslations();
 
+            DOMPurify.setConfig({SAFE_FOR_JQUERY: true});
+
             // initialize other modules/"classes"
             Alert.init();
             Model.init();
-
             AttachmentViewer.init();
             DiscussionViewer.init();
             Editor.init();

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
js/purify-1.0.3.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
js/purify.min.js


+ 1 - 1
js/test/PasteViewer.js

@@ -48,7 +48,7 @@ describe('PasteViewer', function () {
                 results.push(
                     $('#placeholder').hasClass('hidden') &&
                     !$.PrivateBin.PasteViewer.isPrettyPrinted() &&
-                    $.PrivateBin.PasteViewer.getText() == text
+                    $.PrivateBin.PasteViewer.getText() == $('<div />').text(text).html()
                 );
                 if (format == 'markdown') {
                     results.push(

+ 1 - 0
lib/Configuration.php

@@ -52,6 +52,7 @@ class Configuration
             'languageselection'        => false,
             'languagedefault'          => '',
             'urlshortener'             => '',
+            'qrcode'                   => true,
             'icon'                     => 'identicon',
             'cspheader'                => 'default-src \'none\'; manifest-src \'self\'; connect-src *; script-src \'self\'; style-src \'self\'; font-src \'self\'; img-src \'self\' data:; referrer no-referrer; sandbox allow-same-origin allow-scripts allow-forms allow-popups',
             'zerobincompatibility'     => false,

+ 1 - 0
lib/PrivateBin.php

@@ -448,6 +448,7 @@ class PrivateBin
         $page->assign('EXPIREDEFAULT', $this->_conf->getKey('default', 'expire'));
         $page->assign('EXPIRECLONE', !$this->_doesExpire || ($this->_doesExpire && $this->_conf->getKey('clone', 'expire')));
         $page->assign('URLSHORTENER', $this->_conf->getKey('urlshortener'));
+        $page->assign('QRCODE', $this->_conf->getKey('qrcode'));
         $page->draw($this->_conf->getKey('template'));
     }
 

+ 35 - 5
tpl/bootstrap.php

@@ -44,6 +44,11 @@ endif;
 		<script type="text/javascript" src="js/jquery-3.1.1.js" integrity="sha512-U6K1YLIFUWcvuw5ucmMtT9HH4t0uz3M366qrF5y4vnyH6dgDzndlcGvH/Lz5k8NFh80SN95aJ5rqGZEdaQZ7ZQ==" crossorigin="anonymous"></script>
 		<script type="text/javascript" src="js/sjcl-1.0.6.js" integrity="sha512-DsyxLV/uBoQlRTJmW5Gb2SxXUXB+aYeZ6zk+NuXy8LuLyi8oGti9AGn6He5fUY2DtgQ2//RjfaZog8exFuunUQ==" crossorigin="anonymous"></script>
 <?php
+if ($QRCODE):
+?>
+		<script async type="text/javascript" src="js/kjua-0.1.2.js" integrity="sha512-hmvfOhcr4J8bjQ2GuNVzfSbuulv72wgQCJpgnXc2+cCHKqvYo8pK2nc0Q4Esem2973zo1radyIMTEkt+xJlhBA==" crossorigin="anonymous"></script>
+<?php
+endif;
 if ($ZEROBINCOMPATIBILITY):
 ?>
 		<script type="text/javascript" src="js/base64-1.7.js" integrity="sha512-JdwsSP3GyHR+jaCkns9CL9NTt4JUJqm/BsODGmYhBcj5EAPKcHYh+OiMfyHbcDLECe17TL0hjXADFkusAqiYgA==" crossorigin="anonymous"></script>
@@ -66,11 +71,11 @@ endif;
 if ($MARKDOWN):
 ?>
 		<script type="text/javascript" src="js/showdown-1.6.1.js" integrity="sha512-e6kAsBTgFnTBnEQXrq8BV6+XFwxb3kyWHeEPOl+KhxaWt3xImE2zAW2+yP3E2CQ7F9yoJl1poVU9qxkOEtVsTQ==" crossorigin="anonymous"></script>
-		<script type="text/javascript" src="js/purify.min.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-jJuy143F5Oy7oS3VkjzeJGBxIUuQ1H0eSjuvLGD3FiQzeu8Pwp5vI/jQ2dxlxSrzejmNMicdLHnIqH7R8Ft0lQ==" crossorigin="anonymous"></script>
+		<script type="text/javascript" src="js/purify-1.0.3.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-uhzhZJSgc+XJoaxCOjiuRzQaf5klPlSSVKGw69+zT72hhfLbVwB4jbwI+f7NRucuRz6u0aFGMeZ+0PnGh73iBQ==" crossorigin="anonymous"></script>
 <?php
 endif;
 ?>
-		<script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-/YMgC56Z2fpsurdKZVLiOK7zTx63c68RO/9UZ8SC34ssNaVoXZhhNtEUscwtdEOCGeY7tU7UGc6r0+BSs7bbJQ==" crossorigin="anonymous"></script>
+		<script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-msvuz4bHPzxA4SPt80AzVlD8DybEUb+3819GfmoyMin//9poPKN7cPp8pXgLwQUxxaxYK9bUpXj7UmbSNLU+Xg==" crossorigin="anonymous"></script>
 		<!--[if lt IE 10]>
 		<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style>
 		<![endif]-->
@@ -88,8 +93,8 @@ if ($isCpct):
 ?> class="navbar-spacing"<?php
 endif;
 ?>>
-		<div id="passwordmodal" class="modal fade" role="dialog">
-			<div class="modal-dialog">
+		<div id="passwordmodal" tabindex="-1" class="modal fade" role="dialog" aria-hidden="true">
+			<div class="modal-dialog" role="document">
 				<div class="modal-content">
 					<div class="modal-body">
 						<form id="passwordform" role="form">
@@ -103,6 +108,22 @@ endif;
 				</div>
 			</div>
 		</div>
+<?php
+if ($QRCODE):
+?>
+		<div id="qrcodemodal" tabindex="-1" class="modal fade" aria-labelledby="qrcodemodalTitle" role="dialog" aria-hidden="true">
+			<div class="modal-dialog" role="document">
+				<div class="modal-content">
+					<div class="modal-body">
+						<div class="mx-auto" id="qrcode-display"></div>
+					</div>
+					<button type="button" class="btn btn-primary btn-block" data-dismiss="modal"><?php echo I18n::_('Close') ?></button>
+				</div>
+			</div>
+		</div>
+<?php
+endif;
+?>
 		<nav class="navbar navbar-<?php echo $isDark ? 'inverse' : 'default'; ?> navbar-<?php echo $isCpct ? 'fixed' : 'static'; ?>-top"><?php
 if ($isCpct):
 ?><div class="container"><?php
@@ -150,6 +171,15 @@ endif;
 						<button id="rawtextbutton" type="button" class="hidden btn btn-<?php echo $isDark ? 'warning' : 'default'; ?> navbar-btn">
 							<span class="glyphicon glyphicon-text-background" aria-hidden="true"></span> <?php echo I18n::_('Raw text'), PHP_EOL; ?>
 						</button>
+<?php
+if ($QRCODE):
+?>
+						<button id="qrcodelink" type="button" data-toggle="modal" data-target="#qrcodemodal" class="hidden btn btn-<?php echo $isDark ? 'warning' : 'default'; ?> navbar-btn">
+							<span class="glyphicon glyphicon-qrcode" aria-hidden="true"></span> <?php echo I18n::_('QR code'), PHP_EOL; ?>
+						</button>
+<?php
+endif;
+?>
 					</li>
 					<li class="dropdown">
 						<select id="pasteExpiration" name="pasteExpiration" class="hidden">
@@ -271,7 +301,7 @@ else:
         endif;
 ?> />
 								<?php echo I18n::_('Open discussion'), PHP_EOL; ?>
-						 	</label>
+							</label>
 						</div>
 					</li>
 <?php

+ 23 - 4
tpl/page.php

@@ -22,6 +22,7 @@ endif;
 ?>
 		<script type="text/javascript" src="js/jquery-3.1.1.js" integrity="sha512-U6K1YLIFUWcvuw5ucmMtT9HH4t0uz3M366qrF5y4vnyH6dgDzndlcGvH/Lz5k8NFh80SN95aJ5rqGZEdaQZ7ZQ==" crossorigin="anonymous"></script>
 		<script type="text/javascript" src="js/sjcl-1.0.6.js" integrity="sha512-DsyxLV/uBoQlRTJmW5Gb2SxXUXB+aYeZ6zk+NuXy8LuLyi8oGti9AGn6He5fUY2DtgQ2//RjfaZog8exFuunUQ==" crossorigin="anonymous"></script>
+		<script type="text/javascript" src="js/kjua.min.js" integrity="sha512-hmvfOhcr4J8bjQ2GuNVzfSbuulv72wgQCJpgnXc2+cCHKqvYo8pK2nc0Q4Esem2973zo1radyIMTEkt+xJlhBA==" crossorigin="anonymous"></script>
 <?php
 if ($ZEROBINCOMPATIBILITY):
 ?>
@@ -44,11 +45,16 @@ endif;
 if ($MARKDOWN):
 ?>
 		<script type="text/javascript" src="js/showdown-1.6.1.js" integrity="sha512-e6kAsBTgFnTBnEQXrq8BV6+XFwxb3kyWHeEPOl+KhxaWt3xImE2zAW2+yP3E2CQ7F9yoJl1poVU9qxkOEtVsTQ==" crossorigin="anonymous"></script>
-		<script type="text/javascript" src="js/purify.min.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-jJuy143F5Oy7oS3VkjzeJGBxIUuQ1H0eSjuvLGD3FiQzeu8Pwp5vI/jQ2dxlxSrzejmNMicdLHnIqH7R8Ft0lQ==" crossorigin="anonymous"></script>
+		<script type="text/javascript" src="js/purify-1.0.3.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-uhzhZJSgc+XJoaxCOjiuRzQaf5klPlSSVKGw69+zT72hhfLbVwB4jbwI+f7NRucuRz6u0aFGMeZ+0PnGh73iBQ==" crossorigin="anonymous"></script>
 <?php
 endif;
+if ($QRCODE):
 ?>
-		<script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-/YMgC56Z2fpsurdKZVLiOK7zTx63c68RO/9UZ8SC34ssNaVoXZhhNtEUscwtdEOCGeY7tU7UGc6r0+BSs7bbJQ==" crossorigin="anonymous"></script>
+		<script async type="text/javascript" src="js/kjua-0.1.2.js" integrity="sha512-hmvfOhcr4J8bjQ2GuNVzfSbuulv72wgQCJpgnXc2+cCHKqvYo8pK2nc0Q4Esem2973zo1radyIMTEkt+xJlhBA==" crossorigin="anonymous"></script>
+<?php
+endif;
+?>
+		<script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-msvuz4bHPzxA4SPt80AzVlD8DybEUb+3819GfmoyMin//9poPKN7cPp8pXgLwQUxxaxYK9bUpXj7UmbSNLU+Xg==" crossorigin="anonymous"></script>
 		<!--[if lt IE 10]>
 		<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style>
 		<![endif]-->
@@ -99,6 +105,13 @@ if ($EXPIRECLONE):
 endif;
 ?>
 					<button id="rawtextbutton" class="hidden"><img src="img/icon_raw.png" width="15" height="15" alt="" /><?php echo I18n::_('Raw text'); ?></button>
+<?php
+if ($QRCODE):
+?>
+					<button id="qrcodelink" class="hidden"><img src="img/icon_qr.png" width="15" height="15" alt="" /><?php echo I18n::_('QR code'); ?></button>
+<?php
+endif;
+?>
 					<div id="expiration" class="hidden button"><?php echo I18n::_('Expires'); ?>:
 						<select id="pasteExpiration" name="pasteExpiration">
 <?php
@@ -185,7 +198,13 @@ if (strlen($LANGUAGESELECTION)):
 endif;
 ?>
 				</div>
-				<div id="pastesuccess" class="hidden">
+<?php
+if ($QRCODE):
+?>
+				<div id="qrcode-display"></div>
+<?php
+endif;
+?>				<div id="pastesuccess" class="hidden">
 					<div id="deletelink"></div>
 					<div id="pastelink">
 <?php
@@ -242,7 +261,7 @@ if ($DISCUSSION):
 endif;
 ?>
 		</div>
-        <section class="container">
+		<section class="container">
 			<div id="noscript" role="alert" class="nonworking alert alert-info noscript-hide"><span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true">
 				<span> <?php echo I18n::_('Loading…'); ?></span><br>
 				<span class="small"><?php echo I18n::_('In case this message never disappears please have a look at <a href="https://github.com/PrivateBin/PrivateBin/wiki/FAQ#why-does-not-the-loading-message-go-away">this FAQ for information to troubleshoot</a>.'); ?></span>

+ 1 - 0
tst/ViewTest.php

@@ -56,6 +56,7 @@ class ViewTest extends PHPUnit_Framework_TestCase
         $page->assign('EXPIREDEFAULT', self::$expire_default);
         $page->assign('EXPIRECLONE', true);
         $page->assign('URLSHORTENER', '');
+        $page->assign('QRCODE', true);
 
         $dir = dir(PATH . 'tpl');
         while (false !== ($file = $dir->read())) {

Некоторые файлы не были показаны из-за большого количества измененных файлов